From 9ee8378d393778ac67314be7ea8d5bcbaeee9ee0 Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Sun, 7 Dec 2014 10:08:07 +0100 Subject: try out unqlite --- common/unqlite/jx9_vm.c | 7141 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 7141 insertions(+) create mode 100644 common/unqlite/jx9_vm.c (limited to 'common/unqlite/jx9_vm.c') diff --git a/common/unqlite/jx9_vm.c b/common/unqlite/jx9_vm.c new file mode 100644 index 0000000..1585b13 --- /dev/null +++ b/common/unqlite/jx9_vm.c @@ -0,0 +1,7141 @@ +/* + * 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: jx9_vm.c v1.0 FreeBSD 2012-12-09 00:19 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* + * The code in this file implements execution method of the JX9 Virtual Machine. + * The JX9 compiler (implemented in 'compiler.c' and 'parse.c') generates a bytecode program + * which is then executed by the virtual machine implemented here to do the work of the JX9 + * statements. + * JX9 bytecode programs are similar in form to assembly language. The program consists + * of a linear sequence of operations .Each operation has an opcode and 3 operands. + * Operands P1 and P2 are integers where the first is signed while the second is unsigned. + * Operand P3 is an arbitrary pointer specific to each instruction. The P2 operand is usually + * the jump destination used by the OP_JMP, OP_JZ, OP_JNZ, ... instructions. + * Opcodes will typically ignore one or more operands. Many opcodes ignore all three operands. + * Computation results are stored on a stack. Each entry on the stack is of type jx9_value. + * JX9 uses the jx9_value object to represent all values that can be stored in a JX9 variable. + * Since JX9 uses dynamic typing for the values it stores. Values stored in jx9_value objects + * can be integers, floating point values, strings, arrays, object instances (object in the JX9 jargon) + * and so on. + * Internally, the JX9 virtual machine manipulates nearly all values as jx9_values structures. + * Each jx9_value may cache multiple representations(string, integer etc.) of the same value. + * An implicit conversion from one type to the other occurs as necessary. + * Most of the code in this file is taken up by the [VmByteCodeExec()] function which does + * the work of interpreting a JX9 bytecode program. But other routines are also provided + * to help in building up a program instruction by instruction. + */ +/* + * Each active virtual machine frame is represented by an instance + * of the following structure. + * VM Frame hold local variables and other stuff related to function call. + */ +struct VmFrame +{ + VmFrame *pParent; /* Parent frame or NULL if global scope */ + void *pUserData; /* Upper layer private data associated with this frame */ + SySet sLocal; /* Local variables container (VmSlot instance) */ + jx9_vm *pVm; /* VM that own this frame */ + SyHash hVar; /* Variable hashtable for fast lookup */ + SySet sArg; /* Function arguments container */ + sxi32 iFlags; /* Frame configuration flags (See below)*/ + sxu32 iExceptionJump; /* Exception jump destination */ +}; +/* + * When a user defined variable is garbage collected, memory object index + * is stored in an instance of the following structure and put in the free object + * table so that it can be reused again without allocating a new memory object. + */ +typedef struct VmSlot VmSlot; +struct VmSlot +{ + sxu32 nIdx; /* Index in pVm->aMemObj[] */ + void *pUserData; /* Upper-layer private data */ +}; +/* + * Each parsed URI is recorded and stored in an instance of the following structure. + * This structure and it's related routines are taken verbatim from the xHT project + * [A modern embeddable HTTP engine implementing all the RFC2616 methods] + * the xHT project is developed internally by Symisc Systems. + */ +typedef struct SyhttpUri SyhttpUri; +struct SyhttpUri +{ + SyString sHost; /* Hostname or IP address */ + SyString sPort; /* Port number */ + SyString sPath; /* Mandatory resource path passed verbatim (Not decoded) */ + SyString sQuery; /* Query part */ + SyString sFragment; /* Fragment part */ + SyString sScheme; /* Scheme */ + SyString sUser; /* Username */ + SyString sPass; /* Password */ + SyString sRaw; /* Raw URI */ +}; +/* + * An instance of the following structure is used to record all MIME headers seen + * during a HTTP interaction. + * This structure and it's related routines are taken verbatim from the xHT project + * [A modern embeddable HTTP engine implementing all the RFC2616 methods] + * the xHT project is developed internally by Symisc Systems. + */ +typedef struct SyhttpHeader SyhttpHeader; +struct SyhttpHeader +{ + SyString sName; /* Header name [i.e:"Content-Type", "Host", "User-Agent"]. NOT NUL TERMINATED */ + SyString sValue; /* Header values [i.e: "text/html"]. NOT NUL TERMINATED */ +}; +/* + * Supported HTTP methods. + */ +#define HTTP_METHOD_GET 1 /* GET */ +#define HTTP_METHOD_HEAD 2 /* HEAD */ +#define HTTP_METHOD_POST 3 /* POST */ +#define HTTP_METHOD_PUT 4 /* PUT */ +#define HTTP_METHOD_OTHR 5 /* Other HTTP methods [i.e: DELETE, TRACE, OPTIONS...]*/ +/* + * Supported HTTP protocol version. + */ +#define HTTP_PROTO_10 1 /* HTTP/1.0 */ +#define HTTP_PROTO_11 2 /* HTTP/1.1 */ +/* + * Register a constant and it's associated expansion callback so that + * it can be expanded from the target JX9 program. + * The constant expansion mechanism under JX9 is extremely powerful yet + * simple and work as follows: + * Each registered constant have a C procedure associated with it. + * This procedure known as the constant expansion callback is responsible + * of expanding the invoked constant to the desired value, for example: + * The C procedure associated with the "__PI__" constant expands to 3.14 (the value of PI). + * The "__OS__" constant procedure expands to the name of the host Operating Systems + * (Windows, Linux, ...) and so on. + * Please refer to the official documentation for additional information. + */ +JX9_PRIVATE sxi32 jx9VmRegisterConstant( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Constant name */ + ProcConstant xExpand, /* Constant expansion callback */ + void *pUserData /* Last argument to xExpand() */ + ) +{ + jx9_constant *pCons; + SyHashEntry *pEntry; + char *zDupName; + sxi32 rc; + pEntry = SyHashGet(&pVm->hConstant, (const void *)pName->zString, pName->nByte); + if( pEntry ){ + /* Overwrite the old definition and return immediately */ + pCons = (jx9_constant *)pEntry->pUserData; + pCons->xExpand = xExpand; + pCons->pUserData = pUserData; + return SXRET_OK; + } + /* Allocate a new constant instance */ + pCons = (jx9_constant *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_constant)); + if( pCons == 0 ){ + return 0; + } + /* Duplicate constant name */ + zDupName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); + if( zDupName == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator, pCons); + return 0; + } + /* Install the constant */ + SyStringInitFromBuf(&pCons->sName, zDupName, pName->nByte); + pCons->xExpand = xExpand; + pCons->pUserData = pUserData; + rc = SyHashInsert(&pVm->hConstant, (const void *)zDupName, SyStringLength(&pCons->sName), pCons); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator, zDupName); + SyMemBackendPoolFree(&pVm->sAllocator, pCons); + return rc; + } + /* All done, constant can be invoked from JX9 code */ + return SXRET_OK; +} +/* + * Allocate a new foreign function instance. + * This function return SXRET_OK on success. Any other + * return value indicates failure. + * Please refer to the official documentation for an introduction to + * the foreign function mechanism. + */ +static sxi32 jx9NewForeignFunction( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Foreign function name */ + ProcHostFunction xFunc, /* Foreign function implementation */ + void *pUserData, /* Foreign function private data */ + jx9_user_func **ppOut /* OUT: VM image of the foreign function */ + ) +{ + jx9_user_func *pFunc; + char *zDup; + /* Allocate a new user function */ + pFunc = (jx9_user_func *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_user_func)); + if( pFunc == 0 ){ + return SXERR_MEM; + } + /* Duplicate function name */ + zDup = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); + if( zDup == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator, pFunc); + return SXERR_MEM; + } + /* Zero the structure */ + SyZero(pFunc, sizeof(jx9_user_func)); + /* Initialize structure fields */ + SyStringInitFromBuf(&pFunc->sName, zDup, pName->nByte); + pFunc->pVm = pVm; + pFunc->xFunc = xFunc; + pFunc->pUserData = pUserData; + SySetInit(&pFunc->aAux, &pVm->sAllocator, sizeof(jx9_aux_data)); + /* Write a pointer to the new function */ + *ppOut = pFunc; + return SXRET_OK; +} +/* + * Install a foreign function and it's associated callback so that + * it can be invoked from the target JX9 code. + * This function return SXRET_OK on successful registration. Any other + * return value indicates failure. + * Please refer to the official documentation for an introduction to + * the foreign function mechanism. + */ +JX9_PRIVATE sxi32 jx9VmInstallForeignFunction( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Foreign function name */ + ProcHostFunction xFunc, /* Foreign function implementation */ + void *pUserData /* Foreign function private data */ + ) +{ + jx9_user_func *pFunc; + SyHashEntry *pEntry; + sxi32 rc; + /* Overwrite any previously registered function with the same name */ + pEntry = SyHashGet(&pVm->hHostFunction, pName->zString, pName->nByte); + if( pEntry ){ + pFunc = (jx9_user_func *)pEntry->pUserData; + pFunc->pUserData = pUserData; + pFunc->xFunc = xFunc; + SySetReset(&pFunc->aAux); + return SXRET_OK; + } + /* Create a new user function */ + rc = jx9NewForeignFunction(&(*pVm), &(*pName), xFunc, pUserData, &pFunc); + if( rc != SXRET_OK ){ + return rc; + } + /* Install the function in the corresponding hashtable */ + rc = SyHashInsert(&pVm->hHostFunction, SyStringData(&pFunc->sName), pName->nByte, pFunc); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName)); + SyMemBackendPoolFree(&pVm->sAllocator, pFunc); + return rc; + } + /* User function successfully installed */ + return SXRET_OK; +} +/* + * Initialize a VM function. + */ +JX9_PRIVATE sxi32 jx9VmInitFuncState( + jx9_vm *pVm, /* Target VM */ + jx9_vm_func *pFunc, /* Target Fucntion */ + const char *zName, /* Function name */ + sxu32 nByte, /* zName length */ + sxi32 iFlags, /* Configuration flags */ + void *pUserData /* Function private data */ + ) +{ + /* Zero the structure */ + SyZero(pFunc, sizeof(jx9_vm_func)); + /* Initialize structure fields */ + /* Arguments container */ + SySetInit(&pFunc->aArgs, &pVm->sAllocator, sizeof(jx9_vm_func_arg)); + /* Static variable container */ + SySetInit(&pFunc->aStatic, &pVm->sAllocator, sizeof(jx9_vm_func_static_var)); + /* Bytecode container */ + SySetInit(&pFunc->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); + /* Preallocate some instruction slots */ + SySetAlloc(&pFunc->aByteCode, 0x10); + pFunc->iFlags = iFlags; + pFunc->pUserData = pUserData; + SyStringInitFromBuf(&pFunc->sName, zName, nByte); + return SXRET_OK; +} +/* + * Install a user defined function in the corresponding VM container. + */ +JX9_PRIVATE sxi32 jx9VmInstallUserFunction( + jx9_vm *pVm, /* Target VM */ + jx9_vm_func *pFunc, /* Target function */ + SyString *pName /* Function name */ + ) +{ + SyHashEntry *pEntry; + sxi32 rc; + if( pName == 0 ){ + /* Use the built-in name */ + pName = &pFunc->sName; + } + /* Check for duplicates (functions with the same name) first */ + pEntry = SyHashGet(&pVm->hFunction, pName->zString, pName->nByte); + if( pEntry ){ + jx9_vm_func *pLink = (jx9_vm_func *)pEntry->pUserData; + if( pLink != pFunc ){ + /* Link */ + pFunc->pNextName = pLink; + pEntry->pUserData = pFunc; + } + return SXRET_OK; + } + /* First time seen */ + pFunc->pNextName = 0; + rc = SyHashInsert(&pVm->hFunction, pName->zString, pName->nByte, pFunc); + return rc; +} +/* + * Instruction builder interface. + */ +JX9_PRIVATE sxi32 jx9VmEmitInstr( + jx9_vm *pVm, /* Target VM */ + sxi32 iOp, /* Operation to perform */ + sxi32 iP1, /* First operand */ + sxu32 iP2, /* Second operand */ + void *p3, /* Third operand */ + sxu32 *pIndex /* Instruction index. NULL otherwise */ + ) +{ + VmInstr sInstr; + sxi32 rc; + /* Fill the VM instruction */ + sInstr.iOp = (sxu8)iOp; + sInstr.iP1 = iP1; + sInstr.iP2 = iP2; + sInstr.p3 = p3; + if( pIndex ){ + /* Instruction index in the bytecode array */ + *pIndex = SySetUsed(pVm->pByteContainer); + } + /* Finally, record the instruction */ + rc = SySetPut(pVm->pByteContainer, (const void *)&sInstr); + if( rc != SXRET_OK ){ + jx9GenCompileError(&pVm->sCodeGen, E_ERROR, 1, "Fatal, Cannot emit instruction due to a memory failure"); + /* Fall throw */ + } + return rc; +} +/* + * Swap the current bytecode container with the given one. + */ +JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer) +{ + if( pContainer == 0 ){ + /* Point to the default container */ + pVm->pByteContainer = &pVm->aByteCode; + }else{ + /* Change container */ + pVm->pByteContainer = &(*pContainer); + } + return SXRET_OK; +} +/* + * Return the current bytecode container. + */ +JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm) +{ + return pVm->pByteContainer; +} +/* + * Extract the VM instruction rooted at nIndex. + */ +JX9_PRIVATE VmInstr * jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex) +{ + VmInstr *pInstr; + pInstr = (VmInstr *)SySetAt(pVm->pByteContainer, nIndex); + return pInstr; +} +/* + * Return the total number of VM instructions recorded so far. + */ +JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm) +{ + return SySetUsed(pVm->pByteContainer); +} +/* + * Pop the last VM instruction. + */ +JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm) +{ + return (VmInstr *)SySetPop(pVm->pByteContainer); +} +/* + * Peek the last VM instruction. + */ +JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm) +{ + return (VmInstr *)SySetPeek(pVm->pByteContainer); +} +/* + * Allocate a new virtual machine frame. + */ +static VmFrame * VmNewFrame( + jx9_vm *pVm, /* Target VM */ + void *pUserData /* Upper-layer private data */ + ) +{ + VmFrame *pFrame; + /* Allocate a new vm frame */ + pFrame = (VmFrame *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmFrame)); + if( pFrame == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pFrame, sizeof(VmFrame)); + /* Initialize frame fields */ + pFrame->pUserData = pUserData; + pFrame->pVm = pVm; + SyHashInit(&pFrame->hVar, &pVm->sAllocator, 0, 0); + SySetInit(&pFrame->sArg, &pVm->sAllocator, sizeof(VmSlot)); + SySetInit(&pFrame->sLocal, &pVm->sAllocator, sizeof(VmSlot)); + return pFrame; +} +/* + * Enter a VM frame. + */ +static sxi32 VmEnterFrame( + jx9_vm *pVm, /* Target VM */ + void *pUserData, /* Upper-layer private data */ + VmFrame **ppFrame /* OUT: Top most active frame */ + ) +{ + VmFrame *pFrame; + /* Allocate a new frame */ + pFrame = VmNewFrame(&(*pVm), pUserData); + if( pFrame == 0 ){ + return SXERR_MEM; + } + /* Link to the list of active VM frame */ + pFrame->pParent = pVm->pFrame; + pVm->pFrame = pFrame; + if( ppFrame ){ + /* Write a pointer to the new VM frame */ + *ppFrame = pFrame; + } + return SXRET_OK; +} +/* + * Link a foreign variable with the TOP most active frame. + * Refer to the JX9_OP_UPLINK instruction implementation for more + * information. + */ +static sxi32 VmFrameLink(jx9_vm *pVm,SyString *pName) +{ + VmFrame *pTarget, *pFrame; + SyHashEntry *pEntry = 0; + sxi32 rc; + /* Point to the upper frame */ + pFrame = pVm->pFrame; + pTarget = pFrame; + pFrame = pTarget->pParent; + while( pFrame ){ + /* Query the current frame */ + pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); + if( pEntry ){ + /* Variable found */ + break; + } + /* Point to the upper frame */ + pFrame = pFrame->pParent; + } + if( pEntry == 0 ){ + /* Inexistant variable */ + return SXERR_NOTFOUND; + } + /* Link to the current frame */ + rc = SyHashInsert(&pTarget->hVar, pEntry->pKey, pEntry->nKeyLen, pEntry->pUserData); + return rc; +} +/* + * Leave the top-most active frame. + */ +static void VmLeaveFrame(jx9_vm *pVm) +{ + VmFrame *pFrame = pVm->pFrame; + if( pFrame ){ + /* Unlink from the list of active VM frame */ + pVm->pFrame = pFrame->pParent; + if( pFrame->pParent ){ + VmSlot *aSlot; + sxu32 n; + /* Restore local variable to the free pool so that they can be reused again */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sLocal); + for(n = 0 ; n < SySetUsed(&pFrame->sLocal) ; ++n ){ + /* Unset the local variable */ + jx9VmUnsetMemObj(&(*pVm), aSlot[n].nIdx); + } + } + /* Release internal containers */ + SyHashRelease(&pFrame->hVar); + SySetRelease(&pFrame->sArg); + SySetRelease(&pFrame->sLocal); + /* Release the whole structure */ + SyMemBackendPoolFree(&pVm->sAllocator, pFrame); + } +} +/* + * Compare two functions signature and return the comparison result. + */ +static int VmOverloadCompare(SyString *pFirst, SyString *pSecond) +{ + const char *zSend = &pSecond->zString[pSecond->nByte]; + const char *zFend = &pFirst->zString[pFirst->nByte]; + const char *zSin = pSecond->zString; + const char *zFin = pFirst->zString; + const char *zPtr = zFin; + for(;;){ + if( zFin >= zFend || zSin >= zSend ){ + break; + } + if( zFin[0] != zSin[0] ){ + /* mismatch */ + break; + } + zFin++; + zSin++; + } + return (int)(zFin-zPtr); +} +/* + * Select the appropriate VM function for the current call context. + * This is the implementation of the powerful 'function overloading' feature + * introduced by the version 2 of the JX9 engine. + * Refer to the official documentation for more information. + */ +static jx9_vm_func * VmOverload( + jx9_vm *pVm, /* Target VM */ + jx9_vm_func *pList, /* Linked list of candidates for overloading */ + jx9_value *aArg, /* Array of passed arguments */ + int nArg /* Total number of passed arguments */ + ) +{ + int iTarget, i, j, iCur, iMax; + jx9_vm_func *apSet[10]; /* Maximum number of candidates */ + jx9_vm_func *pLink; + SyString sArgSig; + SyBlob sSig; + + pLink = pList; + i = 0; + /* Put functions expecting the same number of passed arguments */ + while( i < (int)SX_ARRAYSIZE(apSet) ){ + if( pLink == 0 ){ + break; + } + if( (int)SySetUsed(&pLink->aArgs) == nArg ){ + /* Candidate for overloading */ + apSet[i++] = pLink; + } + /* Point to the next entry */ + pLink = pLink->pNextName; + } + if( i < 1 ){ + /* No candidates, return the head of the list */ + return pList; + } + if( nArg < 1 || i < 2 ){ + /* Return the only candidate */ + return apSet[0]; + } + /* Calculate function signature */ + SyBlobInit(&sSig, &pVm->sAllocator); + for( j = 0 ; j < nArg ; j++ ){ + int c = 'n'; /* null */ + if( aArg[j].iFlags & MEMOBJ_HASHMAP ){ + /* Hashmap */ + c = 'h'; + }else if( aArg[j].iFlags & MEMOBJ_BOOL ){ + /* bool */ + c = 'b'; + }else if( aArg[j].iFlags & MEMOBJ_INT ){ + /* int */ + c = 'i'; + }else if( aArg[j].iFlags & MEMOBJ_STRING ){ + /* String */ + c = 's'; + }else if( aArg[j].iFlags & MEMOBJ_REAL ){ + /* Float */ + c = 'f'; + } + if( c > 0 ){ + SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); + } + } + SyStringInitFromBuf(&sArgSig, SyBlobData(&sSig), SyBlobLength(&sSig)); + iTarget = 0; + iMax = -1; + /* Select the appropriate function */ + for( j = 0 ; j < i ; j++ ){ + /* Compare the two signatures */ + iCur = VmOverloadCompare(&sArgSig, &apSet[j]->sSignature); + if( iCur > iMax ){ + iMax = iCur; + iTarget = j; + } + } + SyBlobRelease(&sSig); + /* Appropriate function for the current call context */ + return apSet[iTarget]; +} +/* + * Dummy read-only buffer used for slot reservation. + */ +static const char zDummy[sizeof(jx9_value)] = { 0 }; /* Must be >= sizeof(jx9_value) */ +/* + * Reserve a constant memory object. + * Return a pointer to the raw jx9_value on success. NULL on failure. + */ +JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex) +{ + jx9_value *pObj; + sxi32 rc; + if( pIndex ){ + /* Object index in the object table */ + *pIndex = SySetUsed(&pVm->aLitObj); + } + /* Reserve a slot for the new object */ + rc = SySetPut(&pVm->aLitObj, (const void *)zDummy); + if( rc != SXRET_OK ){ + /* 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 0; + } + pObj = (jx9_value *)SySetPeek(&pVm->aLitObj); + return pObj; +} +/* + * Reserve a memory object. + * Return a pointer to the raw jx9_value on success. NULL on failure. + */ +static jx9_value * VmReserveMemObj(jx9_vm *pVm, sxu32 *pIndex) +{ + jx9_value *pObj; + sxi32 rc; + if( pIndex ){ + /* Object index in the object table */ + *pIndex = SySetUsed(&pVm->aMemObj); + } + /* Reserve a slot for the new object */ + rc = SySetPut(&pVm->aMemObj, (const void *)zDummy); + if( rc != SXRET_OK ){ + /* 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 0; + } + pObj = (jx9_value *)SySetPeek(&pVm->aMemObj); + return pObj; +} +/* Forward declaration */ +static sxi32 VmEvalChunk(jx9_vm *pVm, jx9_context *pCtx, SyString *pChunk, int iFlags, int bTrueReturn); +/* + * Built-in functions that cannot be implemented directly as foreign functions. + */ +#define JX9_BUILTIN_LIB \ + "function scandir(string $directory, int $sort_order = SCANDIR_SORT_ASCENDING)"\ + "{"\ + " if( func_num_args() < 1 ){ return FALSE; }"\ + " $aDir = [];"\ + " $pHandle = opendir($directory);"\ + " if( $pHandle == FALSE ){ return FALSE; }"\ + " while(FALSE !== ($pEntry = readdir($pHandle)) ){"\ + " $aDir[] = $pEntry;"\ + " }"\ + " closedir($pHandle);"\ + " if( $sort_order == SCANDIR_SORT_DESCENDING ){"\ + " rsort($aDir);"\ + " }else if( $sort_order == SCANDIR_SORT_ASCENDING ){"\ + " sort($aDir);"\ + " }"\ + " return $aDir;"\ + "}"\ + "function glob(string $pattern, int $iFlags = 0){"\ + "/* Open the target directory */"\ + "$zDir = dirname($pattern);"\ + "if(!is_string($zDir) ){ $zDir = './'; }"\ + "$pHandle = opendir($zDir);"\ + "if( $pHandle == FALSE ){"\ + " /* IO error while opening the current directory, return FALSE */"\ + " return FALSE;"\ + "}"\ + "$pattern = basename($pattern);"\ + "$pArray = []; /* Empty array */"\ + "/* Loop throw available entries */"\ + "while( FALSE !== ($pEntry = readdir($pHandle)) ){"\ + " /* Use the built-in strglob function which is a Symisc eXtension for wildcard comparison*/"\ + " $rc = strglob($pattern, $pEntry);"\ + " if( $rc ){"\ + " if( is_dir($pEntry) ){"\ + " if( $iFlags & GLOB_MARK ){"\ + " /* Adds a slash to each directory returned */"\ + " $pEntry .= DIRECTORY_SEPARATOR;"\ + " }"\ + " }else if( $iFlags & GLOB_ONLYDIR ){"\ + " /* Not a directory, ignore */"\ + " continue;"\ + " }"\ + " /* Add the entry */"\ + " $pArray[] = $pEntry;"\ + " }"\ + " }"\ + "/* Close the handle */"\ + "closedir($pHandle);"\ + "if( ($iFlags & GLOB_NOSORT) == 0 ){"\ + " /* Sort the array */"\ + " sort($pArray);"\ + "}"\ + "if( ($iFlags & GLOB_NOCHECK) && sizeof($pArray) < 1 ){"\ + " /* Return the search pattern if no files matching were found */"\ + " $pArray[] = $pattern;"\ + "}"\ + "/* Return the created array */"\ + "return $pArray;"\ + "}"\ + "/* Creates a temporary file */"\ + "function tmpfile(){"\ + " /* Extract the temp directory */"\ + " $zTempDir = sys_get_temp_dir();"\ + " if( strlen($zTempDir) < 1 ){"\ + " /* Use the current dir */"\ + " $zTempDir = '.';"\ + " }"\ + " /* Create the file */"\ + " $pHandle = fopen($zTempDir.DIRECTORY_SEPARATOR.'JX9'.rand_str(12), 'w+');"\ + " return $pHandle;"\ + "}"\ + "/* Creates a temporary filename */"\ + "function tempnam(string $zDir = sys_get_temp_dir() /* Symisc eXtension */, string $zPrefix = 'JX9')"\ + "{"\ + " return $zDir.DIRECTORY_SEPARATOR.$zPrefix.rand_str(12);"\ + "}"\ + "function max(){"\ + " $pArgs = func_get_args();"\ + " if( sizeof($pArgs) < 1 ){"\ + " return null;"\ + " }"\ + " if( sizeof($pArgs) < 2 ){"\ + " $pArg = $pArgs[0];"\ + " if( !is_array($pArg) ){"\ + " return $pArg; "\ + " }"\ + " if( sizeof($pArg) < 1 ){"\ + " return null;"\ + " }"\ + " $pArg = array_copy($pArgs[0]);"\ + " reset($pArg);"\ + " $max = current($pArg);"\ + " while( FALSE !== ($val = next($pArg)) ){"\ + " if( $val > $max ){"\ + " $max = $val;"\ + " }"\ + " }"\ + " return $max;"\ + " }"\ + " $max = $pArgs[0];"\ + " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ + " $val = $pArgs[$i];"\ + "if( $val > $max ){"\ + " $max = $val;"\ + "}"\ + " }"\ + " return $max;"\ + "}"\ + "function min(){"\ + " $pArgs = func_get_args();"\ + " if( sizeof($pArgs) < 1 ){"\ + " return null;"\ + " }"\ + " if( sizeof($pArgs) < 2 ){"\ + " $pArg = $pArgs[0];"\ + " if( !is_array($pArg) ){"\ + " return $pArg; "\ + " }"\ + " if( sizeof($pArg) < 1 ){"\ + " return null;"\ + " }"\ + " $pArg = array_copy($pArgs[0]);"\ + " reset($pArg);"\ + " $min = current($pArg);"\ + " while( FALSE !== ($val = next($pArg)) ){"\ + " if( $val < $min ){"\ + " $min = $val;"\ + " }"\ + " }"\ + " return $min;"\ + " }"\ + " $min = $pArgs[0];"\ + " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ + " $val = $pArgs[$i];"\ + "if( $val < $min ){"\ + " $min = $val;"\ + " }"\ + " }"\ + " return $min;"\ + "}" +/* + * Initialize a freshly allocated JX9 Virtual Machine so that we can + * start compiling the target JX9 program. + */ +JX9_PRIVATE sxi32 jx9VmInit( + jx9_vm *pVm, /* Initialize this */ + jx9 *pEngine /* Master engine */ + ) +{ + SyString sBuiltin; + jx9_value *pObj; + sxi32 rc; + /* Zero the structure */ + SyZero(pVm, sizeof(jx9_vm)); + /* Initialize VM fields */ + pVm->pEngine = &(*pEngine); + SyMemBackendInitFromParent(&pVm->sAllocator, &pEngine->sAllocator); + /* Instructions containers */ + SySetInit(&pVm->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); + SySetAlloc(&pVm->aByteCode, 0xFF); + pVm->pByteContainer = &pVm->aByteCode; + /* Object containers */ + SySetInit(&pVm->aMemObj, &pVm->sAllocator, sizeof(jx9_value)); + SySetAlloc(&pVm->aMemObj, 0xFF); + /* Virtual machine internal containers */ + SyBlobInit(&pVm->sConsumer, &pVm->sAllocator); + SyBlobInit(&pVm->sWorker, &pVm->sAllocator); + SyBlobInit(&pVm->sArgv, &pVm->sAllocator); + SySetInit(&pVm->aLitObj, &pVm->sAllocator, sizeof(jx9_value)); + SySetAlloc(&pVm->aLitObj, 0xFF); + SyHashInit(&pVm->hHostFunction, &pVm->sAllocator, 0, 0); + SyHashInit(&pVm->hFunction, &pVm->sAllocator, 0, 0); + SyHashInit(&pVm->hConstant, &pVm->sAllocator, 0, 0); + SyHashInit(&pVm->hSuper, &pVm->sAllocator, 0, 0); + SySetInit(&pVm->aFreeObj, &pVm->sAllocator, sizeof(VmSlot)); + /* Configuration containers */ + SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString)); + SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString)); + SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString)); + SySetInit(&pVm->aIOstream, &pVm->sAllocator, sizeof(jx9_io_stream *)); + /* Error callbacks containers */ + jx9MemObjInit(&(*pVm), &pVm->sAssertCallback); + /* Set a default recursion limit */ +#if defined(__WINNT__) || defined(__UNIXES__) + pVm->nMaxDepth = 32; +#else + pVm->nMaxDepth = 16; +#endif + /* Default assertion flags */ + pVm->iAssertFlags = JX9_ASSERT_WARNING; /* Issue a warning for each failed assertion */ + /* PRNG context */ + SyRandomnessInit(&pVm->sPrng, 0, 0); + /* Install the null constant */ + pObj = jx9VmReserveConstObj(&(*pVm), 0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + jx9MemObjInit(pVm, pObj); + /* Install the boolean TRUE constant */ + pObj = jx9VmReserveConstObj(&(*pVm), 0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + jx9MemObjInitFromBool(pVm, pObj, 1); + /* Install the boolean FALSE constant */ + pObj = jx9VmReserveConstObj(&(*pVm), 0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + jx9MemObjInitFromBool(pVm, pObj, 0); + /* Create the global frame */ + rc = VmEnterFrame(&(*pVm), 0, 0); + if( rc != SXRET_OK ){ + goto Err; + } + /* Initialize the code generator */ + rc = jx9InitCodeGenerator(pVm, pEngine->xConf.xErr, pEngine->xConf.pErrData); + if( rc != SXRET_OK ){ + goto Err; + } + /* VM correctly initialized, set the magic number */ + pVm->nMagic = JX9_VM_INIT; + SyStringInitFromBuf(&sBuiltin,JX9_BUILTIN_LIB, sizeof(JX9_BUILTIN_LIB)-1); + /* Compile the built-in library */ + VmEvalChunk(&(*pVm), 0, &sBuiltin, 0, FALSE); + /* Reset the code generator */ + jx9ResetCodeGenerator(&(*pVm), pEngine->xConf.xErr, pEngine->xConf.pErrData); + return SXRET_OK; +Err: + SyMemBackendRelease(&pVm->sAllocator); + return rc; +} +/* + * Default VM output consumer callback.That is, all VM output is redirected to this + * routine which store the output in an internal blob. + * The output can be extracted later after program execution [jx9_vm_exec()] via + * the [jx9_vm_config()] interface with a configuration verb set to + * jx9VM_CONFIG_EXTRACT_OUTPUT. + * Refer to the official docurmentation for additional information. + * Note that for performance reason it's preferable to install a VM output + * consumer callback via (jx9VM_CONFIG_OUTPUT) rather than waiting for the VM + * to finish executing and extracting the output. + */ +JX9_PRIVATE sxi32 jx9VmBlobConsumer( + const void *pOut, /* VM Generated output*/ + unsigned int nLen, /* Generated output length */ + void *pUserData /* User private data */ + ) +{ + sxi32 rc; + /* Store the output in an internal BLOB */ + rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen); + return rc; +} +#define VM_STACK_GUARD 16 +/* + * Allocate a new operand stack so that we can start executing + * our compiled JX9 program. + * Return a pointer to the operand stack (array of jx9_values) + * on success. NULL (Fatal error) on failure. + */ +static jx9_value * VmNewOperandStack( + jx9_vm *pVm, /* Target VM */ + sxu32 nInstr /* Total numer of generated bytecode instructions */ + ) +{ + jx9_value *pStack; + /* No instruction ever pushes more than a single element onto the + ** stack and the stack never grows on successive executions of the + ** same loop. So the total number of instructions is an upper bound + ** on the maximum stack depth required. + ** + ** Allocation all the stack space we will ever need. + */ + nInstr += VM_STACK_GUARD; + pStack = (jx9_value *)SyMemBackendAlloc(&pVm->sAllocator, nInstr * sizeof(jx9_value)); + if( pStack == 0 ){ + return 0; + } + /* Initialize the operand stack */ + while( nInstr > 0 ){ + jx9MemObjInit(&(*pVm), &pStack[nInstr - 1]); + --nInstr; + } + /* Ready for bytecode execution */ + return pStack; +} +/* Forward declaration */ +static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm); +/* + * Prepare the Virtual Machine for bytecode execution. + * This routine gets called by the JX9 engine after + * successful compilation of the target JX9 program. + */ +JX9_PRIVATE sxi32 jx9VmMakeReady( + jx9_vm *pVm /* Target VM */ + ) +{ + sxi32 rc; + if( pVm->nMagic != JX9_VM_INIT ){ + /* Initialize your VM first */ + return SXERR_CORRUPT; + } + /* Mark the VM ready for bytecode execution */ + pVm->nMagic = JX9_VM_RUN; + /* Release the code generator now we have compiled our program */ + jx9ResetCodeGenerator(pVm, 0, 0); + /* Emit the DONE instruction */ + rc = jx9VmEmitInstr(&(*pVm), JX9_OP_DONE, 0, 0, 0, 0); + if( rc != SXRET_OK ){ + return SXERR_MEM; + } + /* Script return value */ + jx9MemObjInit(&(*pVm), &pVm->sExec); /* Assume a NULL return value */ + /* Allocate a new operand stack */ + pVm->aOps = VmNewOperandStack(&(*pVm), SySetUsed(pVm->pByteContainer)); + if( pVm->aOps == 0 ){ + return SXERR_MEM; + } + /* Set the default VM output consumer callback and it's + * private data. */ + pVm->sVmConsumer.xConsumer = jx9VmBlobConsumer; + pVm->sVmConsumer.pUserData = &pVm->sConsumer; + /* Register special functions first [i.e: print, func_get_args(), die, etc.] */ + rc = VmRegisterSpecialFunction(&(*pVm)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return rc; + } + /* Create superglobals [i.e: $GLOBALS, $_GET, $_POST...] */ + rc = jx9HashmapLoadBuiltin(&(*pVm)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return rc; + } + /* Register built-in constants [i.e: JX9_EOL, JX9_OS...] */ + jx9RegisterBuiltInConstant(&(*pVm)); + /* Register built-in functions [i.e: is_null(), array_diff(), strlen(), etc.] */ + jx9RegisterBuiltInFunction(&(*pVm)); + /* VM is ready for bytecode execution */ + return SXRET_OK; +} +/* + * Reset a Virtual Machine to it's initial state. + */ +JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm) +{ + if( pVm->nMagic != JX9_VM_RUN && pVm->nMagic != JX9_VM_EXEC ){ + return SXERR_CORRUPT; + } + /* TICKET 1433-003: As of this version, the VM is automatically reset */ + SyBlobReset(&pVm->sConsumer); + jx9MemObjRelease(&pVm->sExec); + /* Set the ready flag */ + pVm->nMagic = JX9_VM_RUN; + return SXRET_OK; +} +/* + * Release a Virtual Machine. + * Every virtual machine must be destroyed in order to avoid memory leaks. + */ +JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm) +{ + /* Set the stale magic number */ + pVm->nMagic = JX9_VM_STALE; + /* Release the private memory subsystem */ + SyMemBackendRelease(&pVm->sAllocator); + return SXRET_OK; +} +/* + * Initialize a foreign function call context. + * The context in which a foreign function executes is stored in a jx9_context object. + * A pointer to a jx9_context object is always first parameter to application-defined foreign + * functions. + * The application-defined foreign function implementation will pass this pointer through into + * calls to dozens of interfaces, these includes jx9_result_int(), jx9_result_string(), jx9_result_value(), + * jx9_context_new_scalar(), jx9_context_alloc_chunk(), jx9_context_output(), jx9_context_throw_error() + * and many more. Refer to the C/C++ Interfaces documentation for additional information. + */ +static sxi32 VmInitCallContext( + jx9_context *pOut, /* Call Context */ + jx9_vm *pVm, /* Target VM */ + jx9_user_func *pFunc, /* Foreign function to execute shortly */ + jx9_value *pRet, /* Store return value here*/ + sxi32 iFlags /* Control flags */ + ) +{ + pOut->pFunc = pFunc; + pOut->pVm = pVm; + SySetInit(&pOut->sVar, &pVm->sAllocator, sizeof(jx9_value *)); + SySetInit(&pOut->sChunk, &pVm->sAllocator, sizeof(jx9_aux_data)); + /* Assume a null return value */ + MemObjSetType(pRet, MEMOBJ_NULL); + pOut->pRet = pRet; + pOut->iFlags = iFlags; + return SXRET_OK; +} +/* + * Release a foreign function call context and cleanup the mess + * left behind. + */ +static void VmReleaseCallContext(jx9_context *pCtx) +{ + sxu32 n; + if( SySetUsed(&pCtx->sVar) > 0 ){ + jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar); + for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ + if( apObj[n] == 0 ){ + /* Already released */ + continue; + } + jx9MemObjRelease(apObj[n]); + SyMemBackendPoolFree(&pCtx->pVm->sAllocator, apObj[n]); + } + SySetRelease(&pCtx->sVar); + } + if( SySetUsed(&pCtx->sChunk) > 0 ){ + jx9_aux_data *aAux; + void *pChunk; + /* Automatic release of dynamically allocated chunk + * using [jx9_context_alloc_chunk()]. + */ + aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk); + for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ + pChunk = aAux[n].pAuxData; + /* Release the chunk */ + if( pChunk ){ + SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk); + } + } + SySetRelease(&pCtx->sChunk); + } +} +/* + * Release a jx9_value allocated from the body of a foreign function. + * Refer to [jx9_context_release_value()] for additional information. + */ +JX9_PRIVATE void jx9VmReleaseContextValue( + jx9_context *pCtx, /* Call context */ + jx9_value *pValue /* Release this value */ + ) +{ + if( pValue == 0 ){ + /* NULL value is a harmless operation */ + return; + } + if( SySetUsed(&pCtx->sVar) > 0 ){ + jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar); + sxu32 n; + for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ + if( apObj[n] == pValue ){ + jx9MemObjRelease(pValue); + SyMemBackendPoolFree(&pCtx->pVm->sAllocator, pValue); + /* Mark as released */ + apObj[n] = 0; + break; + } + } + } +} +/* + * Pop and release as many memory object from the operand stack. + */ +static void VmPopOperand( + jx9_value **ppTos, /* Operand stack */ + sxi32 nPop /* Total number of memory objects to pop */ + ) +{ + jx9_value *pTos = *ppTos; + while( nPop > 0 ){ + jx9MemObjRelease(pTos); + pTos--; + nPop--; + } + /* Top of the stack */ + *ppTos = pTos; +} +/* + * Reserve a memory object. + * Return a pointer to the raw jx9_value on success. NULL on failure. + */ +JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIdx) +{ + jx9_value *pObj = 0; + VmSlot *pSlot; + sxu32 nIdx; + /* Check for a free slot */ + nIdx = SXU32_HIGH; /* cc warning */ + pSlot = (VmSlot *)SySetPop(&pVm->aFreeObj); + if( pSlot ){ + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx); + nIdx = pSlot->nIdx; + } + if( pObj == 0 ){ + /* Reserve a new memory object */ + pObj = VmReserveMemObj(&(*pVm), &nIdx); + if( pObj == 0 ){ + return 0; + } + } + /* Set a null default value */ + jx9MemObjInit(&(*pVm), pObj); + if( pIdx ){ + *pIdx = nIdx; + } + pObj->nIdx = nIdx; + return pObj; +} +/* + * Extract a variable value from the top active VM frame. + * Return a pointer to the variable value on success. + * NULL otherwise (non-existent variable/Out-of-memory, ...). + */ +static jx9_value * VmExtractMemObj( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Variable name */ + int bDup, /* True to duplicate variable name */ + int bCreate /* True to create the variable if non-existent */ + ) +{ + int bNullify = FALSE; + SyHashEntry *pEntry; + VmFrame *pFrame; + jx9_value *pObj; + sxu32 nIdx; + sxi32 rc; + /* Point to the top active frame */ + pFrame = pVm->pFrame; + /* Perform the lookup */ + if( pName == 0 || pName->nByte < 1 ){ + static const SyString sAnnon = { " " , sizeof(char) }; + pName = &sAnnon; + /* Always nullify the object */ + bNullify = TRUE; + bDup = FALSE; + } + /* Check the superglobals table first */ + pEntry = SyHashGet(&pVm->hSuper, (const void *)pName->zString, pName->nByte); + if( pEntry == 0 ){ + /* Query the top active frame */ + pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); + if( pEntry == 0 ){ + char *zName = (char *)pName->zString; + VmSlot sLocal; + if( !bCreate ){ + /* Do not create the variable, return NULL */ + return 0; + } + /* No such variable, automatically create a new one and install + * it in the current frame. + */ + pObj = jx9VmReserveMemObj(&(*pVm),&nIdx); + if( pObj == 0 ){ + return 0; + } + if( bDup ){ + /* Duplicate name */ + zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); + if( zName == 0 ){ + return 0; + } + } + /* Link to the top active VM frame */ + rc = SyHashInsert(&pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx)); + if( rc != SXRET_OK ){ + /* Return the slot to the free pool */ + sLocal.nIdx = nIdx; + sLocal.pUserData = 0; + SySetPut(&pVm->aFreeObj, (const void *)&sLocal); + return 0; + } + if( pFrame->pParent != 0 ){ + /* Local variable */ + sLocal.nIdx = nIdx; + SySetPut(&pFrame->sLocal, (const void *)&sLocal); + } + }else{ + /* Extract variable contents */ + nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( bNullify && pObj ){ + jx9MemObjRelease(pObj); + } + } + }else{ + /* Superglobal */ + nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + } + return pObj; +} +/* + * Extract a superglobal variable such as $_GET, $_POST, $_HEADERS, .... + * Return a pointer to the variable value on success.NULL otherwise. + */ +static jx9_value * VmExtractSuper( + jx9_vm *pVm, /* Target VM */ + const char *zName, /* Superglobal name: NOT NULL TERMINATED */ + sxu32 nByte /* zName length */ + ) +{ + SyHashEntry *pEntry; + jx9_value *pValue; + sxu32 nIdx; + /* Query the superglobal table */ + pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); + if( pEntry == 0 ){ + /* No such entry */ + return 0; + } + /* Extract the superglobal index in the global object pool */ + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + /* Extract the variable value */ + pValue = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + return pValue; +} +/* + * Perform a raw hashmap insertion. + * Refer to the [jx9VmConfigure()] implementation for additional information. + */ +static sxi32 VmHashmapInsert( + jx9_hashmap *pMap, /* Target hashmap */ + const char *zKey, /* Entry key */ + int nKeylen, /* zKey length*/ + const char *zData, /* Entry data */ + int nLen /* zData length */ + ) +{ + jx9_value sKey,sValue; + jx9_value *pKey; + sxi32 rc; + pKey = 0; + jx9MemObjInit(pMap->pVm, &sKey); + jx9MemObjInitFromString(pMap->pVm, &sValue, 0); + if( zKey ){ + if( nKeylen < 0 ){ + nKeylen = (int)SyStrlen(zKey); + } + jx9MemObjStringAppend(&sKey, zKey, (sxu32)nKeylen); + pKey = &sKey; + } + if( zData ){ + if( nLen < 0 ){ + /* Compute length automatically */ + nLen = (int)SyStrlen(zData); + } + jx9MemObjStringAppend(&sValue, zData, (sxu32)nLen); + } + /* Perform the insertion */ + rc = jx9HashmapInsert(&(*pMap),pKey,&sValue); + jx9MemObjRelease(&sKey); + jx9MemObjRelease(&sValue); + return rc; +} +/* Forward declaration */ +static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte); +/* + * Configure a working virtual machine instance. + * + * This routine is used to configure a JX9 virtual machine obtained by a prior + * successful call to one of the compile interface such as jx9_compile() + * jx9_compile_v2() or jx9_compile_file(). + * The second argument to this function is an integer configuration option + * that determines what property of the JX9 virtual machine is to be configured. + * Subsequent arguments vary depending on the configuration option in the second + * argument. There are many verbs but the most important are JX9_VM_CONFIG_OUTPUT, + * JX9_VM_CONFIG_HTTP_REQUEST and JX9_VM_CONFIG_ARGV_ENTRY. + * Refer to the official documentation for the list of allowed verbs. + */ +JX9_PRIVATE sxi32 jx9VmConfigure( + jx9_vm *pVm, /* Target VM */ + sxi32 nOp, /* Configuration verb */ + va_list ap /* Subsequent option arguments */ + ) +{ + sxi32 rc = SXRET_OK; + switch(nOp){ + case JX9_VM_CONFIG_OUTPUT: { + ProcConsumer xConsumer = va_arg(ap, ProcConsumer); + void *pUserData = va_arg(ap, void *); + /* VM output consumer callback */ +#ifdef UNTRUST + if( xConsumer == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + /* Install the output consumer */ + pVm->sVmConsumer.xConsumer = xConsumer; + pVm->sVmConsumer.pUserData = pUserData; + break; + } + case JX9_VM_CONFIG_IMPORT_PATH: { + /* Import path */ + const char *zPath; + SyString sPath; + zPath = va_arg(ap, const char *); +#if defined(UNTRUST) + if( zPath == 0 ){ + rc = SXERR_EMPTY; + break; + } +#endif + SyStringInitFromBuf(&sPath, zPath, SyStrlen(zPath)); + /* Remove trailing slashes and backslashes */ +#ifdef __WINNT__ + SyStringTrimTrailingChar(&sPath, '\\'); +#endif + SyStringTrimTrailingChar(&sPath, '/'); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sPath); + if( sPath.nByte > 0 ){ + /* Store the path in the corresponding conatiner */ + rc = SySetPut(&pVm->aPaths, (const void *)&sPath); + } + break; + } + case JX9_VM_CONFIG_ERR_REPORT: + /* Run-Time Error report */ + pVm->bErrReport = 1; + break; + case JX9_VM_CONFIG_RECURSION_DEPTH:{ + /* Recursion depth */ + int nDepth = va_arg(ap, int); + if( nDepth > 2 && nDepth < 1024 ){ + pVm->nMaxDepth = nDepth; + } + break; + } + case JX9_VM_OUTPUT_LENGTH: { + /* VM output length in bytes */ + sxu32 *pOut = va_arg(ap, sxu32 *); +#ifdef UNTRUST + if( pOut == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *pOut = pVm->nOutputLen; + break; + } + case JX9_VM_CONFIG_CREATE_VAR: { + /* Create a new superglobal/global variable */ + const char *zName = va_arg(ap, const char *); + jx9_value *pValue = va_arg(ap, jx9_value *); + SyHashEntry *pEntry; + jx9_value *pObj; + sxu32 nByte; + sxu32 nIdx; +#ifdef UNTRUST + if( SX_EMPTY_STR(zName) || pValue == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + nByte = SyStrlen(zName); + /* Check if the superglobal is already installed */ + pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); + if( pEntry ){ + /* Variable already installed */ + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + /* Extract contents */ + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( pObj ){ + /* Overwrite old contents */ + jx9MemObjStore(pValue, pObj); + } + }else{ + /* Install a new variable */ + pObj = jx9VmReserveMemObj(&(*pVm),&nIdx); + if( pObj == 0 ){ + rc = SXERR_MEM; + break; + } + /* Copy value */ + jx9MemObjStore(pValue, pObj); + /* Install the superglobal */ + rc = SyHashInsert(&pVm->hSuper, (const void *)zName, nByte, SX_INT_TO_PTR(nIdx)); + } + break; + } + case JX9_VM_CONFIG_SERVER_ATTR: + case JX9_VM_CONFIG_ENV_ATTR: { + const char *zKey = va_arg(ap, const char *); + const char *zValue = va_arg(ap, const char *); + int nLen = va_arg(ap, int); + jx9_hashmap *pMap; + jx9_value *pValue; + if( nOp == JX9_VM_CONFIG_ENV_ATTR ){ + /* Extract the $_ENV superglobal */ + pValue = VmExtractSuper(&(*pVm), "_ENV", sizeof("_ENV")-1); + }else{ + /* Extract the $_SERVER superglobal */ + pValue = VmExtractSuper(&(*pVm), "_SERVER", sizeof("_SERVER")-1); + } + if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* No such entry */ + rc = SXERR_NOTFOUND; + break; + } + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pValue->x.pOther; + /* Perform the insertion */ + rc = VmHashmapInsert(pMap, zKey, -1, zValue, nLen); + break; + } + case JX9_VM_CONFIG_ARGV_ENTRY:{ + /* Script arguments */ + const char *zValue = va_arg(ap, const char *); + jx9_hashmap *pMap; + jx9_value *pValue; + /* Extract the $argv array */ + pValue = VmExtractSuper(&(*pVm), "argv", sizeof("argv")-1); + if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* No such entry */ + rc = SXERR_NOTFOUND; + break; + } + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pValue->x.pOther; + /* Perform the insertion */ + rc = VmHashmapInsert(pMap, 0, 0, zValue,-1); + if( rc == SXRET_OK && zValue && zValue[0] != 0 ){ + if( pMap->nEntry > 1 ){ + /* Append space separator first */ + SyBlobAppend(&pVm->sArgv, (const void *)" ", sizeof(char)); + } + SyBlobAppend(&pVm->sArgv, (const void *)zValue,SyStrlen(zValue)); + } + break; + } + case JX9_VM_CONFIG_EXEC_VALUE: { + /* Script return value */ + jx9_value **ppValue = va_arg(ap, jx9_value **); +#ifdef UNTRUST + if( ppValue == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *ppValue = &pVm->sExec; + break; + } + case JX9_VM_CONFIG_IO_STREAM: { + /* Register an IO stream device */ + const jx9_io_stream *pStream = va_arg(ap, const jx9_io_stream *); + /* Make sure we are dealing with a valid IO stream */ + if( pStream == 0 || pStream->zName == 0 || pStream->zName[0] == 0 || + pStream->xOpen == 0 || pStream->xRead == 0 ){ + /* Invalid stream */ + rc = SXERR_INVALID; + break; + } + if( pVm->pDefStream == 0 && SyStrnicmp(pStream->zName, "file", sizeof("file")-1) == 0 ){ + /* Make the 'file://' stream the defaut stream device */ + pVm->pDefStream = pStream; + } + /* Insert in the appropriate container */ + rc = SySetPut(&pVm->aIOstream, (const void *)&pStream); + break; + } + case JX9_VM_CONFIG_EXTRACT_OUTPUT: { + /* Point to the VM internal output consumer buffer */ + const void **ppOut = va_arg(ap, const void **); + unsigned int *pLen = va_arg(ap, unsigned int *); +#ifdef UNTRUST + if( ppOut == 0 || pLen == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *ppOut = SyBlobData(&pVm->sConsumer); + *pLen = SyBlobLength(&pVm->sConsumer); + break; + } + case JX9_VM_CONFIG_HTTP_REQUEST:{ + /* Raw HTTP request*/ + const char *zRequest = va_arg(ap, const char *); + int nByte = va_arg(ap, int); + if( SX_EMPTY_STR(zRequest) ){ + rc = SXERR_EMPTY; + break; + } + if( nByte < 0 ){ + /* Compute length automatically */ + nByte = (int)SyStrlen(zRequest); + } + /* Process the request */ + rc = VmHttpProcessRequest(&(*pVm), zRequest, nByte); + break; + } + default: + /* Unknown configuration option */ + rc = SXERR_UNKNOWN; + break; + } + return rc; +} +/* Forward declaration */ +static const char * VmInstrToString(sxi32 nOp); +/* + * This routine is used to dump JX9 bytecode instructions to a human readable + * format. + * The dump is redirected to the given consumer callback which is responsible + * of consuming the generated dump perhaps redirecting it to its standard output + * (STDOUT). + */ +static sxi32 VmByteCodeDump( + SySet *pByteCode, /* Bytecode container */ + ProcConsumer xConsumer, /* Dump consumer callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + static const char zDump[] = { + "====================================================\n" + "JX9 VM Dump Copyright (C) 2012-2013 Symisc Systems\n" + " http://jx9.symisc.net/\n" + "====================================================\n" + }; + VmInstr *pInstr, *pEnd; + sxi32 rc = SXRET_OK; + sxu32 n; + /* Point to the JX9 instructions */ + pInstr = (VmInstr *)SySetBasePtr(pByteCode); + pEnd = &pInstr[SySetUsed(pByteCode)]; + n = 0; + xConsumer((const void *)zDump, sizeof(zDump)-1, pUserData); + /* Dump instructions */ + for(;;){ + if( pInstr >= pEnd ){ + /* No more instructions */ + break; + } + /* Format and call the consumer callback */ + rc = SyProcFormat(xConsumer, pUserData, "%s %8d %8u %#8x [%u]\n", + VmInstrToString(pInstr->iOp), pInstr->iP1, pInstr->iP2, + SX_PTR_TO_INT(pInstr->p3), n); + if( rc != SXRET_OK ){ + /* Consumer routine request an operation abort */ + return rc; + } + ++n; + pInstr++; /* Next instruction in the stream */ + } + return rc; +} +/* + * Consume a generated run-time error message by invoking the VM output + * consumer callback. + */ +static sxi32 VmCallErrorHandler(jx9_vm *pVm, SyBlob *pMsg) +{ + jx9_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + /* Append a new line */ +#ifdef __WINNT__ + SyBlobAppend(pMsg, "\r\n", sizeof("\r\n")-1); +#else + SyBlobAppend(pMsg, "\n", sizeof(char)); +#endif + /* Invoke the output consumer callback */ + rc = pCons->xConsumer(SyBlobData(pMsg), SyBlobLength(pMsg), pCons->pUserData); + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(pMsg); + + return rc; +} +/* + * Throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error()] for additional + * information. + */ +JX9_PRIVATE sxi32 jx9VmThrowError( + jx9_vm *pVm, /* Target VM */ + SyString *pFuncName, /* Function name. NULL otherwise */ + sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice]*/ + const char *zMessage /* Null terminated error message */ + ) +{ + SyBlob *pWorker = &pVm->sWorker; + SyString *pFile; + char *zErr; + sxi32 rc; + if( !pVm->bErrReport ){ + /* Don't bother reporting errors */ + return SXRET_OK; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Peek the processed file if available */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + /* Append file name */ + SyBlobAppend(pWorker, pFile->zString, pFile->nByte); + SyBlobAppend(pWorker, (const void *)" ", sizeof(char)); + } + zErr = "Error: "; + switch(iErr){ + case JX9_CTX_WARNING: zErr = "Warning: "; break; + case JX9_CTX_NOTICE: zErr = "Notice: "; break; + default: + iErr = JX9_CTX_ERR; + break; + } + SyBlobAppend(pWorker, zErr, SyStrlen(zErr)); + if( pFuncName ){ + /* Append function name first */ + SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte); + SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1); + } + SyBlobAppend(pWorker, zMessage, SyStrlen(zMessage)); + /* Consume the error message */ + rc = VmCallErrorHandler(&(*pVm), pWorker); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error_format()] for additional + * information. + */ +static sxi32 VmThrowErrorAp( + jx9_vm *pVm, /* Target VM */ + SyString *pFuncName, /* Function name. NULL otherwise */ + sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice] */ + const char *zFormat, /* Format message */ + va_list ap /* Variable list of arguments */ + ) +{ + SyBlob *pWorker = &pVm->sWorker; + SyString *pFile; + char *zErr; + sxi32 rc; + if( !pVm->bErrReport ){ + /* Don't bother reporting errors */ + return SXRET_OK; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Peek the processed file if available */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + /* Append file name */ + SyBlobAppend(pWorker, pFile->zString, pFile->nByte); + SyBlobAppend(pWorker, (const void *)" ", sizeof(char)); + } + zErr = "Error: "; + switch(iErr){ + case JX9_CTX_WARNING: zErr = "Warning: "; break; + case JX9_CTX_NOTICE: zErr = "Notice: "; break; + default: + iErr = JX9_CTX_ERR; + break; + } + SyBlobAppend(pWorker, zErr, SyStrlen(zErr)); + if( pFuncName ){ + /* Append function name first */ + SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte); + SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1); + } + SyBlobFormatAp(pWorker, zFormat, ap); + /* Consume the error message */ + rc = VmCallErrorHandler(&(*pVm), pWorker); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error_format()] for additional + * information. + * ------------------------------------ + * Simple boring wrapper function. + * ------------------------------------ + */ +static sxi32 VmErrorFormat(jx9_vm *pVm, sxi32 iErr, const char *zFormat, ...) +{ + va_list ap; + sxi32 rc; + va_start(ap, zFormat); + rc = VmThrowErrorAp(&(*pVm), 0, iErr, zFormat, ap); + va_end(ap); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error_format()] for additional + * information. + * ------------------------------------ + * Simple boring wrapper function. + * ------------------------------------ + */ +JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap) +{ + sxi32 rc; + rc = VmThrowErrorAp(&(*pVm), &(*pFuncName), iErr, zFormat, ap); + return rc; +} +/* Forward declaration */ +static sxi32 VmLocalExec(jx9_vm *pVm,SySet *pByteCode,jx9_value *pResult); +/* + * Execute as much of a JX9 bytecode program as we can then return. + * + * [jx9VmMakeReady()] must be called before this routine in order to + * close the program with a final OP_DONE and to set up the default + * consumer routines and other stuff. Refer to the implementation + * of [jx9VmMakeReady()] for additional information. + * If the installed VM output consumer callback ever returns JX9_ABORT + * then the program execution is halted. + * After this routine has finished, [jx9VmRelease()] or [jx9VmReset()] + * should be used respectively to clean up the mess that was left behind + * or to reset the VM to it's initial state. + */ +static sxi32 VmByteCodeExec( + jx9_vm *pVm, /* Target VM */ + VmInstr *aInstr, /* JX9 bytecode program */ + jx9_value *pStack, /* Operand stack */ + int nTos, /* Top entry in the operand stack (usually -1) */ + jx9_value *pResult /* Store program return value here. NULL otherwise */ + ) +{ + VmInstr *pInstr; + jx9_value *pTos; + SySet aArg; + sxi32 pc; + sxi32 rc; + /* Argument container */ + SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *)); + if( nTos < 0 ){ + pTos = &pStack[-1]; + }else{ + pTos = &pStack[nTos]; + } + pc = 0; + /* Execute as much as we can */ + for(;;){ + /* Fetch the instruction to execute */ + pInstr = &aInstr[pc]; + rc = SXRET_OK; +/* + * What follows here is a massive switch statement where each case implements a + * separate instruction in the virtual machine. If we follow the usual + * indentation convention each case should be indented by 6 spaces. But + * that is a lot of wasted space on the left margin. So the code within + * the switch statement will break with convention and be flush-left. + */ + switch(pInstr->iOp){ +/* + * DONE: P1 * * + * + * Program execution completed: Clean up the mess left behind + * and return immediately. + */ +case JX9_OP_DONE: + if( pInstr->iP1 ){ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pResult ){ + /* Execution result */ + jx9MemObjStore(pTos, pResult); + } + VmPopOperand(&pTos, 1); + } + goto Done; +/* + * HALT: P1 * * + * + * Program execution aborted: Clean up the mess left behind + * and abort immediately. + */ +case JX9_OP_HALT: + if( pInstr->iP1 ){ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pTos->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + /* Output the exit message */ + pVm->sVmConsumer.xConsumer(SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob), + pVm->sVmConsumer.pUserData); + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&pTos->sBlob); + } + }else if(pTos->iFlags & MEMOBJ_INT ){ + /* Record exit status */ + pVm->iExitStatus = (sxi32)pTos->x.iVal; + } + VmPopOperand(&pTos, 1); + } + goto Abort; +/* + * JMP: * P2 * + * + * Unconditional jump: The next instruction executed will be + * the one at index P2 from the beginning of the program. + */ +case JX9_OP_JMP: + pc = pInstr->iP2 - 1; + break; +/* + * JZ: P1 P2 * + * + * Take the jump if the top value is zero (FALSE jump).Pop the top most + * entry in the stack if P1 is zero. + */ +case JX9_OP_JZ: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Get a boolean value */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if( !pTos->x.iVal ){ + /* Take the jump */ + pc = pInstr->iP2 - 1; + } + if( !pInstr->iP1 ){ + VmPopOperand(&pTos, 1); + } + break; +/* + * JNZ: P1 P2 * + * + * Take the jump if the top value is not zero (TRUE jump).Pop the top most + * entry in the stack if P1 is zero. + */ +case JX9_OP_JNZ: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Get a boolean value */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if( pTos->x.iVal ){ + /* Take the jump */ + pc = pInstr->iP2 - 1; + } + if( !pInstr->iP1 ){ + VmPopOperand(&pTos, 1); + } + break; +/* + * NOOP: * * * + * + * Do nothing. This instruction is often useful as a jump + * destination. + */ +case JX9_OP_NOOP: + break; +/* + * POP: P1 * * + * + * Pop P1 elements from the operand stack. + */ +case JX9_OP_POP: { + sxi32 n = pInstr->iP1; + if( &pTos[-n+1] < pStack ){ + /* TICKET 1433-51 Stack underflow must be handled at run-time */ + n = (sxi32)(pTos - pStack); + } + VmPopOperand(&pTos, n); + break; + } +/* + * CVT_INT: * * * + * + * Force the top of the stack to be an integer. + */ +case JX9_OP_CVT_INT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_INT); + break; +/* + * CVT_REAL: * * * + * + * Force the top of the stack to be a real. + */ +case JX9_OP_CVT_REAL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_REAL); + break; +/* + * CVT_STR: * * * + * + * Force the top of the stack to be a string. + */ +case JX9_OP_CVT_STR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pTos); + } + break; +/* + * CVT_BOOL: * * * + * + * Force the top of the stack to be a boolean. + */ +case JX9_OP_CVT_BOOL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + break; +/* + * CVT_NULL: * * * + * + * Nullify the top of the stack. + */ +case JX9_OP_CVT_NULL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + jx9MemObjRelease(pTos); + break; +/* + * CVT_NUMC: * * * + * + * Force the top of the stack to be a numeric type (integer, real or both). + */ +case JX9_OP_CVT_NUMC: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric cast */ + jx9MemObjToNumeric(pTos); + break; +/* + * CVT_ARRAY: * * * + * + * Force the top of the stack to be a hashmap aka 'array'. + */ +case JX9_OP_CVT_ARRAY: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a hashmap cast */ + rc = jx9MemObjToHashmap(pTos); + if( rc != SXRET_OK ){ + /* Not so fatal, emit a simple warning */ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING, + "JX9 engine is running out of memory while performing an array cast"); + } + break; +/* + * LOADC P1 P2 * + * + * Load a constant [i.e: JX9_EOL, JX9_OS, __TIME__, ...] indexed at P2 in the constant pool. + * If P1 is set, then this constant is candidate for expansion via user installable callbacks. + */ +case JX9_OP_LOADC: { + jx9_value *pObj; + /* Reserve a room */ + pTos++; + if( (pObj = (jx9_value *)SySetAt(&pVm->aLitObj, pInstr->iP2)) != 0 ){ + if( pInstr->iP1 == 1 && SyBlobLength(&pObj->sBlob) <= 64 ){ + SyHashEntry *pEntry; + /* Candidate for expansion via user defined callbacks */ + pEntry = SyHashGet(&pVm->hConstant, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); + if( pEntry ){ + jx9_constant *pCons = (jx9_constant *)pEntry->pUserData; + /* Set a NULL default value */ + MemObjSetType(pTos, MEMOBJ_NULL); + SyBlobReset(&pTos->sBlob); + /* Invoke the callback and deal with the expanded value */ + pCons->xExpand(pTos, pCons->pUserData); + /* Mark as constant */ + pTos->nIdx = SXU32_HIGH; + break; + } + } + jx9MemObjLoad(pObj, pTos); + }else{ + /* Set a NULL value */ + MemObjSetType(pTos, MEMOBJ_NULL); + } + /* Mark as constant */ + pTos->nIdx = SXU32_HIGH; + break; + } +/* + * LOAD: P1 * P3 + * + * Load a variable where it's name is taken from the top of the stack or + * from the P3 operand. + * If P1 is set, then perform a lookup only.In other words do not create + * the variable if non existent and push the NULL constant instead. + */ +case JX9_OP_LOAD:{ + jx9_value *pObj; + SyString sName; + if( pInstr->p3 == 0 ){ + /* Take the variable name from the top of the stack */ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a string cast */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pTos); + } + SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + }else{ + SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3)); + /* Reserve a room for the target object */ + pTos++; + } + /* Extract the requested memory object */ + pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, pInstr->iP1 != 1); + if( pObj == 0 ){ + if( pInstr->iP1 ){ + /* Variable not found, load NULL */ + if( !pInstr->p3 ){ + jx9MemObjRelease(pTos); + }else{ + MemObjSetType(pTos, MEMOBJ_NULL); + } + pTos->nIdx = SXU32_HIGH; /* Mark as constant */ + break; + }else{ + /* Fatal error */ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName); + goto Abort; + } + } + /* Load variable contents */ + jx9MemObjLoad(pObj, pTos); + pTos->nIdx = pObj->nIdx; + break; + } +/* + * LOAD_MAP P1 * * + * + * Allocate a new empty hashmap (array in the JX9 jargon) and push it on the stack. + * If the P1 operand is greater than zero then pop P1 elements from the + * stack and insert them (key => value pair) in the new hashmap. + */ +case JX9_OP_LOAD_MAP: { + jx9_hashmap *pMap; + int is_json_object; /* TRUE if we are dealing with a JSON object */ + int iIncr = 1; + /* Allocate a new hashmap instance */ + pMap = jx9NewHashmap(&(*pVm), 0, 0); + if( pMap == 0 ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "Fatal, JX9 engine is running out of memory while loading JSON array/object at instruction #:%d", pc); + goto Abort; + } + is_json_object = 0; + if( pInstr->iP2 ){ + /* JSON object, record that */ + pMap->iFlags |= HASHMAP_JSON_OBJECT; + is_json_object = 1; + iIncr = 2; + } + if( pInstr->iP1 > 0 ){ + jx9_value *pEntry = &pTos[-pInstr->iP1+1]; /* Point to the first entry */ + /* Perform the insertion */ + while( pEntry <= pTos ){ + /* Standard insertion */ + jx9HashmapInsert(pMap, + is_json_object ? pEntry : 0 /* Automatic index assign */, + is_json_object ? &pEntry[1] : pEntry + ); + /* Next pair on the stack */ + pEntry += iIncr; + } + /* Pop P1 elements */ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Push the hashmap */ + pTos++; + pTos->x.pOther = pMap; + MemObjSetType(pTos, MEMOBJ_HASHMAP); + break; + } +/* + * LOAD_IDX: P1 P2 * + * + * Load a hasmap entry where it's index (either numeric or string) is taken + * from the stack. + * If the index does not refer to a valid element, then push the NULL constant + * instead. + */ +case JX9_OP_LOAD_IDX: { + jx9_hashmap_node *pNode = 0; /* cc warning */ + jx9_hashmap *pMap = 0; + jx9_value *pIdx; + pIdx = 0; + if( pInstr->iP1 == 0 ){ + if( !pInstr->iP2){ + /* No available index, load NULL */ + if( pTos >= pStack ){ + jx9MemObjRelease(pTos); + }else{ + /* TICKET 1433-020: Empty stack */ + pTos++; + MemObjSetType(pTos, MEMOBJ_NULL); + pTos->nIdx = SXU32_HIGH; + } + /* Emit a notice */ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_NOTICE, + "JSON Array/Object: Attempt to access an undefined member, JX9 is loading NULL"); + break; + } + }else{ + pIdx = pTos; + pTos--; + } + if( pTos->iFlags & MEMOBJ_STRING ){ + /* String access */ + if( pIdx ){ + sxu32 nOfft; + if( (pIdx->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an int cast */ + jx9MemObjToInteger(pIdx); + } + nOfft = (sxu32)pIdx->x.iVal; + if( nOfft >= SyBlobLength(&pTos->sBlob) ){ + /* Invalid offset, load null */ + jx9MemObjRelease(pTos); + }else{ + const char *zData = (const char *)SyBlobData(&pTos->sBlob); + int c = zData[nOfft]; + jx9MemObjRelease(pTos); + MemObjSetType(pTos, MEMOBJ_STRING); + SyBlobAppend(&pTos->sBlob, (const void *)&c, sizeof(char)); + } + }else{ + /* No available index, load NULL */ + MemObjSetType(pTos, MEMOBJ_NULL); + } + break; + } + if( pInstr->iP2 && (pTos->iFlags & MEMOBJ_HASHMAP) == 0 ){ + if( pTos->nIdx != SXU32_HIGH ){ + jx9_value *pObj; + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjToHashmap(pObj); + jx9MemObjLoad(pObj, pTos); + } + } + } + rc = SXERR_NOTFOUND; /* Assume the index is invalid */ + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pTos->x.pOther; + if( pIdx ){ + /* Load the desired entry */ + rc = jx9HashmapLookup(pMap, pIdx, &pNode); + } + if( rc != SXRET_OK && pInstr->iP2 ){ + /* Create a new empty entry */ + rc = jx9HashmapInsert(pMap, pIdx, 0); + if( rc == SXRET_OK ){ + /* Point to the last inserted entry */ + pNode = pMap->pLast; + } + } + } + if( pIdx ){ + jx9MemObjRelease(pIdx); + } + if( rc == SXRET_OK ){ + /* Load entry contents */ + if( pMap->iRef < 2 ){ + /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy + * of the entry value, rather than pointing to it. + */ + pTos->nIdx = SXU32_HIGH; + jx9HashmapExtractNodeValue(pNode, pTos, TRUE); + }else{ + pTos->nIdx = pNode->nValIdx; + jx9HashmapExtractNodeValue(pNode, pTos, FALSE); + jx9HashmapUnref(pMap); + } + }else{ + /* No such entry, load NULL */ + jx9MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + } + break; + } +/* + * STORE * P2 P3 + * + * Perform a store (Assignment) operation. + */ +case JX9_OP_STORE: { + jx9_value *pObj; + SyString sName; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pInstr->iP2 ){ + sxu32 nIdx; + /* Member store operation */ + nIdx = pTos->nIdx; + VmPopOperand(&pTos, 1); + if( nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, + "Cannot perform assignment on a constant object attribute, JX9 is loading NULL"); + pTos->nIdx = SXU32_HIGH; + }else{ + /* Point to the desired memory object */ + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( pObj ){ + /* Perform the store operation */ + jx9MemObjStore(pTos, pObj); + } + } + break; + }else if( pInstr->p3 == 0 ){ + /* Take the variable name from the next on the stack */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + pTos--; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + }else{ + SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3)); + } + /* Extract the desired variable and if not available dynamically create it */ + pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, TRUE); + if( pObj == 0 ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName); + goto Abort; + } + if( !pInstr->p3 ){ + jx9MemObjRelease(&pTos[1]); + } + /* Perform the store operation */ + jx9MemObjStore(pTos, pObj); + break; + } +/* + * STORE_IDX: P1 * P3 + * + * Perfrom a store operation an a hashmap entry. + */ +case JX9_OP_STORE_IDX: { + jx9_hashmap *pMap = 0; /* cc warning */ + jx9_value *pKey; + sxu32 nIdx; + if( pInstr->iP1 ){ + /* Key is next on stack */ + pKey = pTos; + pTos--; + }else{ + pKey = 0; + } + nIdx = pTos->nIdx; + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Hashmap already loaded */ + pMap = (jx9_hashmap *)pTos->x.pOther; + if( pMap->iRef < 2 ){ + /* TICKET 1433-48: Prevent garbage collection */ + pMap->iRef = 2; + } + }else{ + jx9_value *pObj; + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( pObj == 0 ){ + if( pKey ){ + jx9MemObjRelease(pKey); + } + VmPopOperand(&pTos, 1); + break; + } + /* Phase#1: Load the array */ + if( (pObj->iFlags & MEMOBJ_STRING) ){ + VmPopOperand(&pTos, 1); + if( (pTos->iFlags&MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + if( pKey == 0 ){ + /* Append string */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + } + }else{ + sxu32 nOfft; + if((pKey->iFlags & MEMOBJ_INT)){ + /* Force an int cast */ + jx9MemObjToInteger(pKey); + } + nOfft = (sxu32)pKey->x.iVal; + if( nOfft < SyBlobLength(&pObj->sBlob) && SyBlobLength(&pTos->sBlob) > 0 ){ + const char *zBlob = (const char *)SyBlobData(&pTos->sBlob); + char *zData = (char *)SyBlobData(&pObj->sBlob); + zData[nOfft] = zBlob[0]; + }else{ + if( SyBlobLength(&pTos->sBlob) >= sizeof(char) ){ + /* Perform an append operation */ + SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), sizeof(char)); + } + } + } + if( pKey ){ + jx9MemObjRelease(pKey); + } + break; + }else if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Force a hashmap cast */ + rc = jx9MemObjToHashmap(pObj); + if( rc != SXRET_OK ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while creating a new array"); + goto Abort; + } + } + pMap = (jx9_hashmap *)pObj->x.pOther; + } + VmPopOperand(&pTos, 1); + /* Phase#2: Perform the insertion */ + jx9HashmapInsert(pMap, pKey, pTos); + if( pKey ){ + jx9MemObjRelease(pKey); + } + break; + } +/* + * INCR: P1 * * + * + * Force a numeric cast and increment the top of the stack by 1. + * If the P1 operand is set then perform a duplication of the top of + * the stack and increment after that. + */ +case JX9_OP_INCR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0 ){ + if( pTos->nIdx != SXU32_HIGH ){ + jx9_value *pObj; + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pObj); + if( pObj->iFlags & MEMOBJ_REAL ){ + pObj->x.rVal++; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pObj->x.iVal++; + MemObjSetType(pTos, MEMOBJ_INT); + } + if( pInstr->iP1 ){ + /* Pre-icrement */ + jx9MemObjStore(pObj, pTos); + } + } + }else{ + if( pInstr->iP1 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pTos); + /* Pre-increment */ + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal++; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pTos->x.iVal++; + MemObjSetType(pTos, MEMOBJ_INT); + } + } + } + } + break; +/* + * DECR: P1 * * + * + * Force a numeric cast and decrement the top of the stack by 1. + * If the P1 operand is set then perform a duplication of the top of the stack + * and decrement after that. + */ +case JX9_OP_DECR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES|MEMOBJ_NULL)) == 0 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pTos); + if( pTos->nIdx != SXU32_HIGH ){ + jx9_value *pObj; + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pObj); + if( pObj->iFlags & MEMOBJ_REAL ){ + pObj->x.rVal--; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pObj->x.iVal--; + MemObjSetType(pTos, MEMOBJ_INT); + } + if( pInstr->iP1 ){ + /* Pre-icrement */ + jx9MemObjStore(pObj, pTos); + } + } + }else{ + if( pInstr->iP1 ){ + /* Pre-increment */ + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal--; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pTos->x.iVal--; + MemObjSetType(pTos, MEMOBJ_INT); + } + } + } + } + break; +/* + * UMINUS: * * * + * + * Perform a unary minus operation. + */ +case JX9_OP_UMINUS: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric (integer, real or both) cast */ + jx9MemObjToNumeric(pTos); + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal = -pTos->x.rVal; + } + if( pTos->iFlags & MEMOBJ_INT ){ + pTos->x.iVal = -pTos->x.iVal; + } + break; +/* + * UPLUS: * * * + * + * Perform a unary plus operation. + */ +case JX9_OP_UPLUS: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric (integer, real or both) cast */ + jx9MemObjToNumeric(pTos); + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal = +pTos->x.rVal; + } + if( pTos->iFlags & MEMOBJ_INT ){ + pTos->x.iVal = +pTos->x.iVal; + } + break; +/* + * OP_LNOT: * * * + * + * Interpret the top of the stack as a boolean value. Replace it + * with its complement. + */ +case JX9_OP_LNOT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + pTos->x.iVal = !pTos->x.iVal; + break; +/* + * OP_BITNOT: * * * + * + * Interpret the top of the stack as an value.Replace it + * with its ones-complement. + */ +case JX9_OP_BITNOT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force an integer cast */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + pTos->x.iVal = ~pTos->x.iVal; + break; +/* OP_MUL * * * + * OP_MUL_STORE * * * + * + * Pop the top two elements from the stack, multiply them together, + * and push the result back onto the stack. + */ +case JX9_OP_MUL: +case JX9_OP_MUL_STORE: { + jx9_value *pNos = &pTos[-1]; + /* Force the operand to be numeric */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + jx9MemObjToNumeric(pTos); + jx9MemObjToNumeric(pNos); + /* Perform the requested operation */ + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + jx9_real a, b, r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + a = pNos->x.rVal; + b = pTos->x.rVal; + r = a * b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a, b, r; + a = pNos->x.iVal; + b = pTos->x.iVal; + r = a * b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + } + if( pInstr->iOp == JX9_OP_MUL_STORE ){ + jx9_value *pObj; + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_ADD * * * + * + * Pop the top two elements from the stack, add them together, + * and push the result back onto the stack. + */ +case JX9_OP_ADD:{ + jx9_value *pNos = &pTos[-1]; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Perform the addition */ + jx9MemObjAdd(pNos, pTos, FALSE); + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_ADD_STORE * * * + * + * Pop the top two elements from the stack, add them together, + * and push the result back onto the stack. + */ +case JX9_OP_ADD_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxu32 nIdx; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Perform the addition */ + nIdx = pTos->nIdx; + jx9MemObjAdd(pTos, pNos, TRUE); + /* Peform the store operation */ + if( nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx)) != 0 ){ + jx9MemObjStore(pTos, pObj); + } + /* Ticket 1433-35: Perform a stack dup */ + jx9MemObjStore(pTos, pNos); + VmPopOperand(&pTos, 1); + break; + } +/* OP_SUB * * * + * + * Pop the top two elements from the stack, subtract the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result back onto the stack. + */ +case JX9_OP_SUB: { + jx9_value *pNos = &pTos[-1]; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + jx9_real a, b, r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + a = pNos->x.rVal; + b = pTos->x.rVal; + r = a - b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a, b, r; + a = pNos->x.iVal; + b = pTos->x.iVal; + r = a - b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_SUB_STORE * * * + * + * Pop the top two elements from the stack, subtract the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result back onto the stack. + */ +case JX9_OP_SUB_STORE: { + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + jx9_real a, b, r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + a = pTos->x.rVal; + b = pNos->x.rVal; + r = a - b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a, b, r; + a = pTos->x.iVal; + b = pNos->x.iVal; + r = a - b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + } + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } + +/* + * OP_MOD * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the remainder after division + * onto the stack. + * Note: Only integer arithemtic is allowed. + */ +case JX9_OP_MOD:{ + jx9_value *pNos = &pTos[-1]; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = pTos->x.iVal; + if( b == 0 ){ + r = 0; + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a); + /* goto Abort; */ + }else{ + r = a%b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_MOD_STORE * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the remainder after division + * onto the stack. + * Note: Only integer arithemtic is allowed. + */ +case JX9_OP_MOD_STORE: { + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = pNos->x.iVal; + if( b == 0 ){ + r = 0; + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a); + /* goto Abort; */ + }else{ + r = a%b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_DIV * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result onto the stack. + * Note: Only floating point arithemtic is allowed. + */ +case JX9_OP_DIV:{ + jx9_value *pNos = &pTos[-1]; + jx9_real a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be real */ + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + /* Perform the requested operation */ + a = pNos->x.rVal; + b = pTos->x.rVal; + if( b == 0 ){ + /* Division by zero */ + r = 0; + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Division by zero"); + /* goto Abort; */ + }else{ + r = a/b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + } + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_DIV_STORE * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result onto the stack. + * Note: Only floating point arithemtic is allowed. + */ +case JX9_OP_DIV_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + jx9_real a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be real */ + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + /* Perform the requested operation */ + a = pTos->x.rVal; + b = pNos->x.rVal; + if( b == 0 ){ + /* Division by zero */ + r = 0; + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd/0", a); + /* goto Abort; */ + }else{ + r = a/b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + } + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_BAND * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise AND of the + * two elements. +*/ +/* OP_BOR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise OR of the + * two elements. + */ +/* OP_BXOR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise XOR of the + * two elements. + */ +case JX9_OP_BAND: +case JX9_OP_BOR: +case JX9_OP_BXOR:{ + jx9_value *pNos = &pTos[-1]; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = pTos->x.iVal; + switch(pInstr->iOp){ + case JX9_OP_BOR_STORE: + case JX9_OP_BOR: r = a|b; break; + case JX9_OP_BXOR_STORE: + case JX9_OP_BXOR: r = a^b; break; + case JX9_OP_BAND_STORE: + case JX9_OP_BAND: + default: r = a&b; break; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + VmPopOperand(&pTos, 1); + break; + } +/* OP_BAND_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise AND of the + * two elements. +*/ +/* OP_BOR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise OR of the + * two elements. + */ +/* OP_BXOR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise XOR of the + * two elements. + */ +case JX9_OP_BAND_STORE: +case JX9_OP_BOR_STORE: +case JX9_OP_BXOR_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = pNos->x.iVal; + switch(pInstr->iOp){ + case JX9_OP_BOR_STORE: + case JX9_OP_BOR: r = a|b; break; + case JX9_OP_BXOR_STORE: + case JX9_OP_BXOR: r = a^b; break; + case JX9_OP_BAND_STORE: + case JX9_OP_BAND: + default: r = a&b; break; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_SHL * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * left by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +/* OP_SHR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * right by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +case JX9_OP_SHL: +case JX9_OP_SHR: { + jx9_value *pNos = &pTos[-1]; + sxi64 a, r; + sxi32 b; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = (sxi32)pTos->x.iVal; + if( pInstr->iOp == JX9_OP_SHL ){ + r = a << b; + }else{ + r = a >> b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + VmPopOperand(&pTos, 1); + break; + } +/* OP_SHL_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * left by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +/* OP_SHR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * right by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +case JX9_OP_SHL_STORE: +case JX9_OP_SHR_STORE: { + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxi64 a, r; + sxi32 b; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = (sxi32)pNos->x.iVal; + if( pInstr->iOp == JX9_OP_SHL_STORE ){ + r = a << b; + }else{ + r = a >> b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* CAT: P1 * * + * + * Pop P1 elements from the stack. Concatenate them togeher and push the result + * back. + */ +case JX9_OP_CAT:{ + jx9_value *pNos, *pCur; + if( pInstr->iP1 < 1 ){ + pNos = &pTos[-1]; + }else{ + pNos = &pTos[-pInstr->iP1+1]; + } +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a string cast */ + if( (pNos->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pNos); + } + pCur = &pNos[1]; + while( pCur <= pTos ){ + if( (pCur->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pCur); + } + /* Perform the concatenation */ + if( SyBlobLength(&pCur->sBlob) > 0 ){ + jx9MemObjStringAppend(pNos, (const char *)SyBlobData(&pCur->sBlob), SyBlobLength(&pCur->sBlob)); + } + SyBlobRelease(&pCur->sBlob); + pCur++; + } + pTos = pNos; + break; + } +/* CAT_STORE: * * * + * + * Pop two elements from the stack. Concatenate them togeher and push the result + * back. + */ +case JX9_OP_CAT_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + if((pNos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pNos); + } + /* Perform the concatenation (Reverse order) */ + if( SyBlobLength(&pNos->sBlob) > 0 ){ + jx9MemObjStringAppend(pTos, (const char *)SyBlobData(&pNos->sBlob), SyBlobLength(&pNos->sBlob)); + } + /* Perform the store operation */ + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pTos, pObj); + } + jx9MemObjStore(pTos, pNos); + VmPopOperand(&pTos, 1); + break; + } +/* OP_AND: * * * + * + * Pop two values off the stack. Take the logical AND of the + * two values and push the resulting boolean value back onto the + * stack. + */ +/* OP_OR: * * * + * + * Pop two values off the stack. Take the logical OR of the + * two values and push the resulting boolean value back onto the + * stack. + */ +case JX9_OP_LAND: +case JX9_OP_LOR: { + jx9_value *pNos = &pTos[-1]; + sxi32 v1, v2; /* 0==TRUE, 1==FALSE, 2==UNKNOWN or NULL */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pNos); + } + v1 = pNos->x.iVal == 0 ? 1 : 0; + v2 = pTos->x.iVal == 0 ? 1 : 0; + if( pInstr->iOp == JX9_OP_LAND ){ + static const unsigned char and_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 }; + v1 = and_logic[v1*3+v2]; + }else{ + static const unsigned char or_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 }; + v1 = or_logic[v1*3+v2]; + } + if( v1 == 2 ){ + v1 = 1; + } + VmPopOperand(&pTos, 1); + pTos->x.iVal = v1 == 0 ? 1 : 0; + MemObjSetType(pTos, MEMOBJ_BOOL); + break; + } +/* OP_LXOR: * * * + * + * Pop two values off the stack. Take the logical XOR of the + * two values and push the resulting boolean value back onto the + * stack. + * According to the JX9 language reference manual: + * $a xor $b is evaluated to TRUE if either $a or $b is + * TRUE, but not both. + */ +case JX9_OP_LXOR:{ + jx9_value *pNos = &pTos[-1]; + sxi32 v = 0; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pNos); + } + if( (pNos->x.iVal && !pTos->x.iVal) || (pTos->x.iVal && !pNos->x.iVal) ){ + v = 1; + } + VmPopOperand(&pTos, 1); + pTos->x.iVal = v; + MemObjSetType(pTos, MEMOBJ_BOOL); + break; + } +/* OP_EQ P1 P2 P3 + * + * Pop the top two elements from the stack. If they are equal, then + * jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +/* OP_NEQ P1 P2 P3 + * + * Pop the top two elements from the stack. If they are not equal, then + * jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +case JX9_OP_EQ: +case JX9_OP_NEQ: { + jx9_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = jx9MemObjCmp(pNos, pTos, FALSE, 0); + if( pInstr->iOp == JX9_OP_EQ ){ + rc = rc == 0; + }else{ + rc = rc != 0; + } + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_TEQ P1 P2 * + * + * Pop the top two elements from the stack. If they have the same type and are equal + * then jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +case JX9_OP_TEQ: { + jx9_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) == 0; + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_TNE P1 P2 * + * + * Pop the top two elements from the stack.If they are not equal an they are not + * of the same type, then jump to instruction P2. Otherwise, continue to the next + * instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + * + */ +case JX9_OP_TNE: { + jx9_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) != 0; + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_LT P1 P2 P3 + * + * Pop the top two elements from the stack. If the second element (the top of stack) + * is less than the first (next on stack), then jump to instruction P2.Otherwise + * continue to the next instruction. In other words, jump if pNosiOp == JX9_OP_LE ){ + rc = rc < 1; + }else{ + rc = rc < 0; + } + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_GT P1 P2 P3 + * + * Pop the top two elements from the stack. If the second element (the top of stack) + * is greater than the first (next on stack), then jump to instruction P2.Otherwise + * continue to the next instruction. In other words, jump if pNosiOp == JX9_OP_GE ){ + rc = rc >= 0; + }else{ + rc = rc > 0; + } + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* + * OP_FOREACH_INIT * P2 P3 + * Prepare a foreach step. + */ +case JX9_OP_FOREACH_INIT: { + jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3; + void *pName; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( SyStringLength(&pInfo->sValue) < 1 ){ + /* Take the variable name from the top of the stack */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + /* Duplicate name */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + SyStringInitFromBuf(&pInfo->sValue, pName, SyBlobLength(&pTos->sBlob)); + } + VmPopOperand(&pTos, 1); + } + if( (pInfo->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) < 1 ){ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + /* Duplicate name */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + SyStringInitFromBuf(&pInfo->sKey, pName, SyBlobLength(&pTos->sBlob)); + } + VmPopOperand(&pTos, 1); + } + /* Make sure we are dealing with a hashmap [i.e. JSON array or object ]*/ + if( (pTos->iFlags & (MEMOBJ_HASHMAP)) == 0 || SyStringLength(&pInfo->sValue) < 1 ){ + /* Jump out of the loop */ + if( (pTos->iFlags & MEMOBJ_NULL) == 0 ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING, + "Invalid argument supplied for the foreach statement, expecting JSON array or object instance"); + } + pc = pInstr->iP2 - 1; + }else{ + jx9_foreach_step *pStep; + pStep = (jx9_foreach_step *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_foreach_step)); + if( pStep == 0 ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step"); + /* Jump out of the loop */ + pc = pInstr->iP2 - 1; + }else{ + /* Zero the structure */ + SyZero(pStep, sizeof(jx9_foreach_step)); + /* Prepare the step */ + pStep->iFlags = pInfo->iFlags; + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap = (jx9_hashmap *)pTos->x.pOther; + /* Reset the internal loop cursor */ + jx9HashmapResetLoopCursor(pMap); + /* Mark the step */ + pStep->pMap = pMap; + pMap->iRef++; + } + } + if( SXRET_OK != SySetPut(&pInfo->aStep, (const void *)&pStep) ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step"); + SyMemBackendPoolFree(&pVm->sAllocator, pStep); + /* Jump out of the loop */ + pc = pInstr->iP2 - 1; + } + } + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_FOREACH_STEP * P2 P3 + * Perform a foreach step. Jump to P2 at the end of the step. + */ +case JX9_OP_FOREACH_STEP: { + jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3; + jx9_foreach_step **apStep, *pStep; + jx9_hashmap_node *pNode; + jx9_hashmap *pMap; + jx9_value *pValue; + /* Peek the last step */ + apStep = (jx9_foreach_step **)SySetBasePtr(&pInfo->aStep); + pStep = apStep[SySetUsed(&pInfo->aStep) - 1]; + pMap = pStep->pMap; + /* Extract the current node value */ + pNode = jx9HashmapGetNextEntry(pMap); + if( pNode == 0 ){ + /* No more entry to process */ + pc = pInstr->iP2 - 1; /* Jump to this destination */ + /* Automatically reset the loop cursor */ + jx9HashmapResetLoopCursor(pMap); + /* Cleanup the mess left behind */ + SyMemBackendPoolFree(&pVm->sAllocator, pStep); + SySetPop(&pInfo->aStep); + jx9HashmapUnref(pMap); + }else{ + if( (pStep->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0 ){ + jx9_value *pKey = VmExtractMemObj(&(*pVm), &pInfo->sKey, FALSE, TRUE); + if( pKey ){ + jx9HashmapExtractNodeKey(pNode, pKey); + } + } + /* Make a copy of the entry value */ + pValue = VmExtractMemObj(&(*pVm), &pInfo->sValue, FALSE, TRUE); + if( pValue ){ + jx9HashmapExtractNodeValue(pNode, pValue, TRUE); + } + } + break; + } +/* + * OP_MEMBER P1 P2 + * Load JSON object entry on the stack. + */ +case JX9_OP_MEMBER: { + jx9_hashmap_node *pNode = 0; /* cc warning */ + jx9_hashmap *pMap = 0; + jx9_value *pIdx; + pIdx = pTos; + pTos--; + rc = SXERR_NOTFOUND; /* Assume the index is invalid */ + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pTos->x.pOther; + /* Load the desired entry */ + rc = jx9HashmapLookup(pMap, pIdx, &pNode); + } + jx9MemObjRelease(pIdx); + if( rc == SXRET_OK ){ + /* Load entry contents */ + if( pMap->iRef < 2 ){ + /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy + * of the entry value, rather than pointing to it. + */ + pTos->nIdx = SXU32_HIGH; + jx9HashmapExtractNodeValue(pNode, pTos, TRUE); + }else{ + pTos->nIdx = pNode->nValIdx; + jx9HashmapExtractNodeValue(pNode, pTos, FALSE); + jx9HashmapUnref(pMap); + } + }else{ + /* No such entry, load NULL */ + jx9MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + } + break; + } +/* + * OP_SWITCH * * P3 + * This is the bytecode implementation of the complex switch() JX9 construct. + */ +case JX9_OP_SWITCH: { + jx9_switch *pSwitch = (jx9_switch *)pInstr->p3; + jx9_case_expr *aCase, *pCase; + jx9_value sValue, sCaseValue; + sxu32 n, nEntry; +#ifdef UNTRUST + if( pSwitch == 0 || pTos < pStack ){ + goto Abort; + } +#endif + /* Point to the case table */ + aCase = (jx9_case_expr *)SySetBasePtr(&pSwitch->aCaseExpr); + nEntry = SySetUsed(&pSwitch->aCaseExpr); + /* Select the appropriate case block to execute */ + jx9MemObjInit(pVm, &sValue); + jx9MemObjInit(pVm, &sCaseValue); + for( n = 0 ; n < nEntry ; ++n ){ + pCase = &aCase[n]; + jx9MemObjLoad(pTos, &sValue); + /* Execute the case expression first */ + VmLocalExec(pVm,&pCase->aByteCode, &sCaseValue); + /* Compare the two expression */ + rc = jx9MemObjCmp(&sValue, &sCaseValue, FALSE, 0); + jx9MemObjRelease(&sValue); + jx9MemObjRelease(&sCaseValue); + if( rc == 0 ){ + /* Value match, jump to this block */ + pc = pCase->nStart - 1; + break; + } + } + VmPopOperand(&pTos, 1); + if( n >= nEntry ){ + /* No approprite case to execute, jump to the default case */ + if( pSwitch->nDefault > 0 ){ + pc = pSwitch->nDefault - 1; + }else{ + /* No default case, jump out of this switch */ + pc = pSwitch->nOut - 1; + } + } + break; + } +/* + * OP_UPLINK P1 * * + * Link a variable to the top active VM frame. + * This is used to implement the 'uplink' JX9 construct. + */ +case JX9_OP_UPLINK: { + if( pVm->pFrame->pParent ){ + jx9_value *pLink = &pTos[-pInstr->iP1+1]; + SyString sName; + /* Perform the link */ + while( pLink <= pTos ){ + if((pLink->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pLink); + } + SyStringInitFromBuf(&sName, SyBlobData(&pLink->sBlob), SyBlobLength(&pLink->sBlob)); + if( sName.nByte > 0 ){ + VmFrameLink(&(*pVm), &sName); + } + pLink++; + } + } + VmPopOperand(&pTos, pInstr->iP1); + break; + } +/* + * OP_CALL P1 * * + * Call a JX9 or a foreign function and push the return value of the called + * function on the stack. + */ +case JX9_OP_CALL: { + jx9_value *pArg = &pTos[-pInstr->iP1]; + SyHashEntry *pEntry; + SyString sName; + /* Extract function name */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Raise exception: Invalid function name */ + VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Invalid function name, JX9 is returning NULL."); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + /* Check for a compiled function first */ + pEntry = SyHashGet(&pVm->hFunction, (const void *)sName.zString, sName.nByte); + if( pEntry ){ + jx9_vm_func_arg *aFormalArg; + jx9_value *pFrameStack; + jx9_vm_func *pVmFunc; + VmFrame *pFrame; + jx9_value *pObj; + VmSlot sArg; + sxu32 n; + pVmFunc = (jx9_vm_func *)pEntry->pUserData; + /* Check The recursion limit */ + if( pVm->nRecursionDepth > pVm->nMaxDepth ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "Recursion limit reached while invoking user function '%z', JX9 will set a NULL return value", + &pVmFunc->sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + if( pVmFunc->pNextName ){ + /* Function is candidate for overloading, select the appropriate function to call */ + pVmFunc = VmOverload(&(*pVm), pVmFunc, pArg, (int)(pTos-pArg)); + } + /* Extract the formal argument set */ + aFormalArg = (jx9_vm_func_arg *)SySetBasePtr(&pVmFunc->aArgs); + /* Create a new VM frame */ + rc = VmEnterFrame(&(*pVm),pVmFunc,&pFrame); + if( rc != SXRET_OK ){ + /* Raise exception: Out of memory */ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.", + &pVmFunc->sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + if( SySetUsed(&pVmFunc->aStatic) > 0 ){ + jx9_vm_func_static_var *pStatic, *aStatic; + /* Install static variables */ + aStatic = (jx9_vm_func_static_var *)SySetBasePtr(&pVmFunc->aStatic); + for( n = 0 ; n < SySetUsed(&pVmFunc->aStatic) ; ++n ){ + pStatic = &aStatic[n]; + if( pStatic->nIdx == SXU32_HIGH ){ + /* Initialize the static variables */ + pObj = VmReserveMemObj(&(*pVm), &pStatic->nIdx); + if( pObj ){ + /* Assume a NULL initialization value */ + jx9MemObjInit(&(*pVm), pObj); + if( SySetUsed(&pStatic->aByteCode) > 0 ){ + /* Evaluate initialization expression (Any complex expression) */ + VmLocalExec(&(*pVm), &pStatic->aByteCode, pObj); + } + pObj->nIdx = pStatic->nIdx; + }else{ + continue; + } + } + /* Install in the current frame */ + SyHashInsert(&pFrame->hVar, SyStringData(&pStatic->sName), SyStringLength(&pStatic->sName), + SX_INT_TO_PTR(pStatic->nIdx)); + } + } + /* Push arguments in the local frame */ + n = 0; + while( pArg < pTos ){ + if( n < SySetUsed(&pVmFunc->aArgs) ){ + if( (pArg->iFlags & MEMOBJ_NULL) && SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ + /* NULL values are redirected to default arguments */ + rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pArg); + if( rc == JX9_ABORT ){ + goto Abort; + } + } + /* Make sure the given arguments are of the correct type */ + if( aFormalArg[n].nType > 0 ){ + if( ((pArg->iFlags & aFormalArg[n].nType) == 0) ){ + ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType); + /* Cast to the desired type */ + if( xCast ){ + xCast(pArg); + } + } + } + /* Pass by value, make a copy of the given argument */ + pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE); + }else{ + char zName[32]; + SyString sName; + /* Set a dummy name */ + sName.nByte = SyBufferFormat(zName, sizeof(zName), "[%u]apArg", n); + sName.zString = zName; + /* Annonymous argument */ + pObj = VmExtractMemObj(&(*pVm), &sName, TRUE, TRUE); + } + if( pObj ){ + jx9MemObjStore(pArg, pObj); + /* Insert argument index */ + sArg.nIdx = pObj->nIdx; + sArg.pUserData = 0; + SySetPut(&pFrame->sArg, (const void *)&sArg); + } + jx9MemObjRelease(pArg); + pArg++; + ++n; + } + /* Process default values */ + while( n < SySetUsed(&pVmFunc->aArgs) ){ + if( SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ + pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE); + if( pObj ){ + /* Evaluate the default value and extract it's result */ + rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pObj); + if( rc == JX9_ABORT ){ + goto Abort; + } + /* Insert argument index */ + sArg.nIdx = pObj->nIdx; + sArg.pUserData = 0; + SySetPut(&pFrame->sArg, (const void *)&sArg); + /* Make sure the default argument is of the correct type */ + if( aFormalArg[n].nType > 0 && ((pObj->iFlags & aFormalArg[n].nType) == 0) ){ + ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType); + /* Cast to the desired type */ + xCast(pObj); + } + } + } + ++n; + } + /* Pop arguments, function name from the operand stack and assume the function + * does not return anything. + */ + jx9MemObjRelease(pTos); + pTos = &pTos[-pInstr->iP1]; + /* Allocate a new operand stack and evaluate the function body */ + pFrameStack = VmNewOperandStack(&(*pVm), SySetUsed(&pVmFunc->aByteCode)); + if( pFrameStack == 0 ){ + /* Raise exception: Out of memory */ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.", + &pVmFunc->sName); + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + break; + } + /* Increment nesting level */ + pVm->nRecursionDepth++; + /* Execute function body */ + rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(&pVmFunc->aByteCode), pFrameStack, -1, pTos); + /* Decrement nesting level */ + pVm->nRecursionDepth--; + /* Free the operand stack */ + SyMemBackendFree(&pVm->sAllocator, pFrameStack); + /* Leave the frame */ + VmLeaveFrame(&(*pVm)); + if( rc == JX9_ABORT ){ + /* Abort processing immeditaley */ + goto Abort; + } + }else{ + jx9_user_func *pFunc; + jx9_context sCtx; + jx9_value sRet; + /* Look for an installed foreign function */ + pEntry = SyHashGet(&pVm->hHostFunction, (const void *)sName.zString, sName.nByte); + if( pEntry == 0 ){ + /* Call to undefined function */ + VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Call to undefined function '%z', JX9 is returning NULL.", &sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + pFunc = (jx9_user_func *)pEntry->pUserData; + /* Start collecting function arguments */ + SySetReset(&aArg); + while( pArg < pTos ){ + SySetPut(&aArg, (const void *)&pArg); + pArg++; + } + /* Assume a null return value */ + jx9MemObjInit(&(*pVm), &sRet); + /* Init the call context */ + VmInitCallContext(&sCtx, &(*pVm), pFunc, &sRet, 0); + /* Call the foreign function */ + rc = pFunc->xFunc(&sCtx, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg)); + /* Release the call context */ + VmReleaseCallContext(&sCtx); + if( rc == JX9_ABORT ){ + goto Abort; + } + if( pInstr->iP1 > 0 ){ + /* Pop function name and arguments */ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Save foreign function return value */ + jx9MemObjStore(&sRet, pTos); + jx9MemObjRelease(&sRet); + } + break; + } +/* + * OP_CONSUME: P1 * * + * Consume (Invoke the installed VM output consumer callback) and POP P1 elements from the stack. + */ +case JX9_OP_CONSUME: { + jx9_output_consumer *pCons = &pVm->sVmConsumer; + jx9_value *pCur, *pOut = pTos; + + pOut = &pTos[-pInstr->iP1 + 1]; + pCur = pOut; + /* Start the consume process */ + while( pOut <= pTos ){ + /* Force a string cast */ + if( (pOut->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pOut); + } + if( SyBlobLength(&pOut->sBlob) > 0 ){ + /*SyBlobNullAppend(&pOut->sBlob);*/ + /* Invoke the output consumer callback */ + rc = pCons->xConsumer(SyBlobData(&pOut->sBlob), SyBlobLength(&pOut->sBlob), pCons->pUserData); + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&pOut->sBlob); + SyBlobRelease(&pOut->sBlob); + if( rc == SXERR_ABORT ){ + /* Output consumer callback request an operation abort. */ + goto Abort; + } + } + pOut++; + } + pTos = &pCur[-1]; + break; + } + + } /* Switch() */ + pc++; /* Next instruction in the stream */ + } /* For(;;) */ +Done: + SySetRelease(&aArg); + return SXRET_OK; +Abort: + SySetRelease(&aArg); + while( pTos >= pStack ){ + jx9MemObjRelease(pTos); + pTos--; + } + return JX9_ABORT; +} +/* + * Execute as much of a local JX9 bytecode program as we can then return. + * This function is a wrapper around [VmByteCodeExec()]. + * See block-comment on that function for additional information. + */ +static sxi32 VmLocalExec(jx9_vm *pVm, SySet *pByteCode,jx9_value *pResult) +{ + jx9_value *pStack; + sxi32 rc; + /* Allocate a new operand stack */ + pStack = VmNewOperandStack(&(*pVm), SySetUsed(pByteCode)); + if( pStack == 0 ){ + return SXERR_MEM; + } + /* Execute the program */ + rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pByteCode), pStack, -1, &(*pResult)); + /* Free the operand stack */ + SyMemBackendFree(&pVm->sAllocator, pStack); + /* Execution result */ + return rc; +} +/* + * Execute as much of a JX9 bytecode program as we can then return. + * This function is a wrapper around [VmByteCodeExec()]. + * See block-comment on that function for additional information. + */ +JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm) +{ + /* Make sure we are ready to execute this program */ + if( pVm->nMagic != JX9_VM_RUN ){ + return pVm->nMagic == JX9_VM_EXEC ? SXERR_LOCKED /* Locked VM */ : SXERR_CORRUPT; /* Stale VM */ + } + /* Set the execution magic number */ + pVm->nMagic = JX9_VM_EXEC; + /* Execute the program */ + VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pVm->pByteContainer), pVm->aOps, -1, &pVm->sExec); + /* + * TICKET 1433-100: Do not remove the JX9_VM_EXEC magic number + * so that any following call to [jx9_vm_exec()] without calling + * [jx9_vm_reset()] first would fail. + */ + return SXRET_OK; +} +/* + * Extract a memory object (i.e. a variable) from the running script. + * This function must be called after calling jx9_vm_exec(). Otherwise + * NULL is returned. + */ +JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar) +{ + jx9_value *pValue; + if( pVm->nMagic != JX9_VM_EXEC ){ + /* call jx9_vm_exec() first */ + return 0; + } + /* Perform the lookup */ + pValue = VmExtractMemObj(pVm,pVar,FALSE,FALSE); + /* Lookup result */ + return pValue; +} +/* + * Invoke the installed VM output consumer callback to consume + * the desired message. + * Refer to the implementation of [jx9_context_output()] defined + * in 'api.c' for additional information. + */ +JX9_PRIVATE sxi32 jx9VmOutputConsume( + jx9_vm *pVm, /* Target VM */ + SyString *pString /* Message to output */ + ) +{ + jx9_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + /* Call the output consumer */ + if( pString->nByte > 0 ){ + rc = pCons->xConsumer((const void *)pString->zString, pString->nByte, pCons->pUserData); + /* Increment output length */ + pVm->nOutputLen += pString->nByte; + } + return rc; +} +/* + * Format a message and invoke the installed VM output consumer + * callback to consume the formatted message. + * Refer to the implementation of [jx9_context_output_format()] defined + * in 'api.c' for additional information. + */ +JX9_PRIVATE sxi32 jx9VmOutputConsumeAp( + jx9_vm *pVm, /* Target VM */ + const char *zFormat, /* Formatted message to output */ + va_list ap /* Variable list of arguments */ + ) +{ + jx9_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + SyBlob sWorker; + /* Format the message and call the output consumer */ + SyBlobInit(&sWorker, &pVm->sAllocator); + SyBlobFormatAp(&sWorker, zFormat, ap); + if( SyBlobLength(&sWorker) > 0 ){ + /* Consume the formatted message */ + rc = pCons->xConsumer(SyBlobData(&sWorker), SyBlobLength(&sWorker), pCons->pUserData); + } + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&sWorker); + /* Release the working buffer */ + SyBlobRelease(&sWorker); + return rc; +} +/* + * Return a string representation of the given JX9 OP code. + * This function never fail and always return a pointer + * to a null terminated string. + */ +static const char * VmInstrToString(sxi32 nOp) +{ + const char *zOp = "Unknown "; + switch(nOp){ + case JX9_OP_DONE: zOp = "DONE "; break; + case JX9_OP_HALT: zOp = "HALT "; break; + case JX9_OP_LOAD: zOp = "LOAD "; break; + case JX9_OP_LOADC: zOp = "LOADC "; break; + case JX9_OP_LOAD_MAP: zOp = "LOAD_MAP "; break; + case JX9_OP_LOAD_IDX: zOp = "LOAD_IDX "; break; + case JX9_OP_NOOP: zOp = "NOOP "; break; + case JX9_OP_JMP: zOp = "JMP "; break; + case JX9_OP_JZ: zOp = "JZ "; break; + case JX9_OP_JNZ: zOp = "JNZ "; break; + case JX9_OP_POP: zOp = "POP "; break; + case JX9_OP_CAT: zOp = "CAT "; break; + case JX9_OP_CVT_INT: zOp = "CVT_INT "; break; + case JX9_OP_CVT_STR: zOp = "CVT_STR "; break; + case JX9_OP_CVT_REAL: zOp = "CVT_REAL "; break; + case JX9_OP_CALL: zOp = "CALL "; break; + case JX9_OP_UMINUS: zOp = "UMINUS "; break; + case JX9_OP_UPLUS: zOp = "UPLUS "; break; + case JX9_OP_BITNOT: zOp = "BITNOT "; break; + case JX9_OP_LNOT: zOp = "LOGNOT "; break; + case JX9_OP_MUL: zOp = "MUL "; break; + case JX9_OP_DIV: zOp = "DIV "; break; + case JX9_OP_MOD: zOp = "MOD "; break; + case JX9_OP_ADD: zOp = "ADD "; break; + case JX9_OP_SUB: zOp = "SUB "; break; + case JX9_OP_SHL: zOp = "SHL "; break; + case JX9_OP_SHR: zOp = "SHR "; break; + case JX9_OP_LT: zOp = "LT "; break; + case JX9_OP_LE: zOp = "LE "; break; + case JX9_OP_GT: zOp = "GT "; break; + case JX9_OP_GE: zOp = "GE "; break; + case JX9_OP_EQ: zOp = "EQ "; break; + case JX9_OP_NEQ: zOp = "NEQ "; break; + case JX9_OP_TEQ: zOp = "TEQ "; break; + case JX9_OP_TNE: zOp = "TNE "; break; + case JX9_OP_BAND: zOp = "BITAND "; break; + case JX9_OP_BXOR: zOp = "BITXOR "; break; + case JX9_OP_BOR: zOp = "BITOR "; break; + case JX9_OP_LAND: zOp = "LOGAND "; break; + case JX9_OP_LOR: zOp = "LOGOR "; break; + case JX9_OP_LXOR: zOp = "LOGXOR "; break; + case JX9_OP_STORE: zOp = "STORE "; break; + case JX9_OP_STORE_IDX: zOp = "STORE_IDX "; break; + case JX9_OP_PULL: zOp = "PULL "; break; + case JX9_OP_SWAP: zOp = "SWAP "; break; + case JX9_OP_YIELD: zOp = "YIELD "; break; + case JX9_OP_CVT_BOOL: zOp = "CVT_BOOL "; break; + case JX9_OP_CVT_NULL: zOp = "CVT_NULL "; break; + case JX9_OP_CVT_ARRAY: zOp = "CVT_JSON "; break; + case JX9_OP_CVT_NUMC: zOp = "CVT_NUMC "; break; + case JX9_OP_INCR: zOp = "INCR "; break; + case JX9_OP_DECR: zOp = "DECR "; break; + case JX9_OP_ADD_STORE: zOp = "ADD_STORE "; break; + case JX9_OP_SUB_STORE: zOp = "SUB_STORE "; break; + case JX9_OP_MUL_STORE: zOp = "MUL_STORE "; break; + case JX9_OP_DIV_STORE: zOp = "DIV_STORE "; break; + case JX9_OP_MOD_STORE: zOp = "MOD_STORE "; break; + case JX9_OP_CAT_STORE: zOp = "CAT_STORE "; break; + case JX9_OP_SHL_STORE: zOp = "SHL_STORE "; break; + case JX9_OP_SHR_STORE: zOp = "SHR_STORE "; break; + case JX9_OP_BAND_STORE: zOp = "BAND_STORE "; break; + case JX9_OP_BOR_STORE: zOp = "BOR_STORE "; break; + case JX9_OP_BXOR_STORE: zOp = "BXOR_STORE "; break; + case JX9_OP_CONSUME: zOp = "CONSUME "; break; + case JX9_OP_MEMBER: zOp = "MEMBER "; break; + case JX9_OP_UPLINK: zOp = "UPLINK "; break; + case JX9_OP_SWITCH: zOp = "SWITCH "; break; + case JX9_OP_FOREACH_INIT: + zOp = "4EACH_INIT "; break; + case JX9_OP_FOREACH_STEP: + zOp = "4EACH_STEP "; break; + default: + break; + } + return zOp; +} +/* + * Dump JX9 bytecodes instructions to a human readable format. + * The xConsumer() callback which is an used defined function + * is responsible of consuming the generated dump. + */ +JX9_PRIVATE sxi32 jx9VmDump( + jx9_vm *pVm, /* Target VM */ + ProcConsumer xConsumer, /* Output [i.e: dump] consumer callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + sxi32 rc; + rc = VmByteCodeDump(pVm->pByteContainer, xConsumer, pUserData); + return rc; +} +/* + * Default constant expansion callback used by the 'const' statement if used + * outside a object body [i.e: global or function scope]. + * Refer to the implementation of [JX9_CompileConstant()] defined + * in 'compile.c' for additional information. + */ +JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData) +{ + SySet *pByteCode = (SySet *)pUserData; + /* Evaluate and expand constant value */ + VmLocalExec((jx9_vm *)SySetGetUserData(pByteCode), pByteCode, (jx9_value *)pVal); +} +/* + * Section: + * Function handling functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * int func_num_args(void) + * Returns the number of arguments passed to the function. + * Parameters + * None. + * Return + * Total number of arguments passed into the current user-defined function + * or -1 if called from the globe scope. + */ +static int vm_builtin_func_num_args(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + VmFrame *pFrame; + jx9_vm *pVm; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Current frame */ + pFrame = pVm->pFrame; + if( pFrame->pParent == 0 ){ + SXUNUSED(nArg); + SXUNUSED(apArg); + /* Global frame, return -1 */ + jx9_result_int(pCtx, -1); + return SXRET_OK; + } + /* Total number of arguments passed to the enclosing function */ + nArg = (int)SySetUsed(&pFrame->sArg); + jx9_result_int(pCtx, nArg); + return SXRET_OK; +} +/* + * value func_get_arg(int $arg_num) + * Return an item from the argument list. + * Parameters + * Argument number(index start from zero). + * Return + * Returns the specified argument or FALSE on error. + */ +static int vm_builtin_func_get_arg(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pObj = 0; + VmSlot *pSlot = 0; + VmFrame *pFrame; + jx9_vm *pVm; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Current frame */ + pFrame = pVm->pFrame; + if( nArg < 1 || pFrame->pParent == 0 ){ + /* Global frame or Missing arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope"); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Extract the desired index */ + nArg = jx9_value_to_int(apArg[0]); + if( nArg < 0 || nArg >= (int)SySetUsed(&pFrame->sArg) ){ + /* Invalid index, return FALSE */ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Extract the desired argument */ + if( (pSlot = (VmSlot *)SySetAt(&pFrame->sArg, (sxu32)nArg)) != 0 ){ + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx)) != 0 ){ + /* Return the desired argument */ + jx9_result_value(pCtx, (jx9_value *)pObj); + }else{ + /* No such argument, return false */ + jx9_result_bool(pCtx, 0); + } + }else{ + /* CAN'T HAPPEN */ + jx9_result_bool(pCtx, 0); + } + return SXRET_OK; +} +/* + * array func_get_args(void) + * Returns an array comprising a copy of function's argument list. + * Parameters + * None. + * Return + * Returns an array in which each element is a copy of the corresponding + * member of the current user-defined function's argument list. + * Otherwise FALSE is returned on failure. + */ +static int vm_builtin_func_get_args(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pObj = 0; + jx9_value *pArray; + VmFrame *pFrame; + VmSlot *aSlot; + sxu32 n; + /* Point to the current frame */ + pFrame = pCtx->pVm->pFrame; + if( pFrame->pParent == 0 ){ + /* Global frame, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope"); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Start filling the array with the given arguments */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sArg); + for( n = 0; n < SySetUsed(&pFrame->sArg) ; n++ ){ + pObj = (jx9_value *)SySetAt(&pCtx->pVm->aMemObj, aSlot[n].nIdx); + if( pObj ){ + jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pObj); + } + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return SXRET_OK; +} +/* + * bool function_exists(string $name) + * Return TRUE if the given function has been defined. + * Parameters + * The name of the desired function. + * Return + * Return TRUE if the given function has been defined.False otherwise + */ +static int vm_builtin_func_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zName; + jx9_vm *pVm; + int nLen; + int res; + if( nArg < 1 ){ + /* Missing argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Extract the function name */ + zName = jx9_value_to_string(apArg[0], &nLen); + /* Assume the function is not defined */ + res = 0; + /* Perform the lookup */ + if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 || + SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){ + /* Function is defined */ + res = 1; + } + jx9_result_bool(pCtx, res); + return SXRET_OK; +} +/* + * Verify that the contents of a variable can be called as a function. + * [i.e: Whether it is callable or not]. + * Return TRUE if callable.FALSE otherwise. + */ +JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue) +{ + int res = 0; + if( pValue->iFlags & MEMOBJ_STRING ){ + const char *zName; + int nLen; + /* Extract the name */ + zName = jx9_value_to_string(pValue, &nLen); + /* Perform the lookup */ + if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 || + SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){ + /* Function is callable */ + res = 1; + } + } + return res; +} +/* + * bool is_callable(callable $name[, bool $syntax_only = false]) + * Verify that the contents of a variable can be called as a function. + * Parameters + * $name + * The callback function to check + * $syntax_only + * If set to TRUE the function only verifies that name might be a function or method. + * It will only reject simple variables that are not strings, or an array that does + * not have a valid structure to be used as a callback. The valid ones are supposed + * to have only 2 entries, the first of which is an object or a string, and the second + * a string. + * Return + * TRUE if name is callable, FALSE otherwise. + */ +static int vm_builtin_is_callable(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vm *pVm; + int res; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Perform the requested operation */ + res = jx9VmIsCallable(pVm, apArg[0]); + jx9_result_bool(pCtx, res); + return SXRET_OK; +} +/* + * Hash walker callback used by the [get_defined_functions()] function + * defined below. + */ +static int VmHashFuncStep(SyHashEntry *pEntry, void *pUserData) +{ + jx9_value *pArray = (jx9_value *)pUserData; + jx9_value sName; + sxi32 rc; + /* Prepare the function name for insertion */ + jx9MemObjInitFromString(pArray->pVm, &sName, 0); + jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen); + /* Perform the insertion */ + rc = jx9_array_add_elem(pArray, 0/* Automatic index assign */, &sName); /* Will make it's own copy */ + jx9MemObjRelease(&sName); + return rc; +} +/* + * array get_defined_functions(void) + * Returns an array of all defined functions. + * Parameter + * None. + * Return + * Returns an multidimensional array containing a list of all defined functions + * both built-in (internal) and user-defined. + * The internal functions will be accessible via $arr["internal"], and the user + * defined ones using $arr["user"]. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_defined_func(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray; + /* NOTE: + * Don't worry about freeing memory here, every allocated resource will be released + * automatically by the engine as soon we return from this foreign function. + */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Fill with the appropriate information */ + SyHashForEach(&pCtx->pVm->hHostFunction,VmHashFuncStep,pArray); + /* Fill with the appropriate information */ + SyHashForEach(&pCtx->pVm->hFunction, VmHashFuncStep,pArray); + /* Return a copy of the array array */ + jx9_result_value(pCtx, pArray); + return SXRET_OK; +} +/* + * Call a user defined or foreign function where the name of the function + * is stored in the pFunc parameter and the given arguments are stored + * in the apArg[] array. + * Return SXRET_OK if the function was successfuly called.Any other + * return value indicates failure. + */ +JX9_PRIVATE sxi32 jx9VmCallUserFunction( + jx9_vm *pVm, /* Target VM */ + jx9_value *pFunc, /* Callback name */ + int nArg, /* Total number of given arguments */ + jx9_value **apArg, /* Callback arguments */ + jx9_value *pResult /* Store callback return value here. NULL otherwise */ + ) +{ + jx9_value *aStack; + VmInstr aInstr[2]; + int i; + if((pFunc->iFlags & (MEMOBJ_STRING)) == 0 ){ + /* Don't bother processing, it's invalid anyway */ + if( pResult ){ + /* Assume a null return value */ + jx9MemObjRelease(pResult); + } + return SXERR_INVALID; + } + /* Create a new operand stack */ + aStack = VmNewOperandStack(&(*pVm), 1+nArg); + if( aStack == 0 ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, + "JX9 is running out of memory while invoking user callback"); + if( pResult ){ + /* Assume a null return value */ + jx9MemObjRelease(pResult); + } + return SXERR_MEM; + } + /* Fill the operand stack with the given arguments */ + for( i = 0 ; i < nArg ; i++ ){ + jx9MemObjLoad(apArg[i], &aStack[i]); + aStack[i].nIdx = apArg[i]->nIdx; + } + /* Push the function name */ + jx9MemObjLoad(pFunc, &aStack[i]); + aStack[i].nIdx = SXU32_HIGH; /* Mark as constant */ + /* Emit the CALL istruction */ + aInstr[0].iOp = JX9_OP_CALL; + aInstr[0].iP1 = nArg; /* Total number of given arguments */ + aInstr[0].iP2 = 0; + aInstr[0].p3 = 0; + /* Emit the DONE instruction */ + aInstr[1].iOp = JX9_OP_DONE; + aInstr[1].iP1 = 1; /* Extract function return value if available */ + aInstr[1].iP2 = 0; + aInstr[1].p3 = 0; + /* Execute the function body (if available) */ + VmByteCodeExec(&(*pVm), aInstr, aStack, nArg, pResult); + /* Clean up the mess left behind */ + SyMemBackendFree(&pVm->sAllocator, aStack); + return JX9_OK; +} +/* + * Call a user defined or foreign function whith a varibale number + * of arguments where the name of the function is stored in the pFunc + * parameter. + * Return SXRET_OK if the function was successfuly called.Any other + * return value indicates failure. + */ +JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp( + jx9_vm *pVm, /* Target VM */ + jx9_value *pFunc, /* Callback name */ + jx9_value *pResult, /* Store callback return value here. NULL otherwise */ + ... /* 0 (Zero) or more Callback arguments */ + ) +{ + jx9_value *pArg; + SySet aArg; + va_list ap; + sxi32 rc; + SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *)); + /* Copy arguments one after one */ + va_start(ap, pResult); + for(;;){ + pArg = va_arg(ap, jx9_value *); + if( pArg == 0 ){ + break; + } + SySetPut(&aArg, (const void *)&pArg); + } + /* Call the core routine */ + rc = jx9VmCallUserFunction(&(*pVm), pFunc, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg), pResult); + /* Cleanup */ + SySetRelease(&aArg); + return rc; +} +/* + * bool defined(string $name) + * Checks whether a given named constant exists. + * Parameter: + * Name of the desired constant. + * Return + * TRUE if the given constant exists.FALSE otherwise. + */ +static int vm_builtin_defined(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zName; + int nLen = 0; + int res = 0; + if( nArg < 1 ){ + /* Missing constant name, return FALSE */ + jx9_context_throw_error(pCtx,JX9_CTX_NOTICE,"Missing constant name"); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Extract constant name */ + zName = jx9_value_to_string(apArg[0], &nLen); + /* Perform the lookup */ + if( nLen > 0 && SyHashGet(&pCtx->pVm->hConstant, (const void *)zName, (sxu32)nLen) != 0 ){ + /* Already defined */ + res = 1; + } + jx9_result_bool(pCtx, res); + return SXRET_OK; +} +/* + * Hash walker callback used by the [get_defined_constants()] function + * defined below. + */ +static int VmHashConstStep(SyHashEntry *pEntry, void *pUserData) +{ + jx9_value *pArray = (jx9_value *)pUserData; + jx9_value sName; + sxi32 rc; + /* Prepare the constant name for insertion */ + jx9MemObjInitFromString(pArray->pVm, &sName, 0); + jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen); + /* Perform the insertion */ + rc = jx9_array_add_elem(pArray, 0, &sName); /* Will make it's own copy */ + jx9MemObjRelease(&sName); + return rc; +} +/* + * array get_defined_constants(void) + * Returns an associative array with the names of all defined + * constants. + * Parameters + * NONE. + * Returns + * Returns the names of all the constants currently defined. + */ +static int vm_builtin_get_defined_constants(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray; + /* Create the array first*/ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Fill the array with the defined constants */ + SyHashForEach(&pCtx->pVm->hConstant, VmHashConstStep, pArray); + /* Return the created array */ + jx9_result_value(pCtx, pArray); + return SXRET_OK; +} +/* + * Section: + * Random numbers/string generators. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * Generate a random 32-bit unsigned integer. + * JX9 use it's own private PRNG which is based on the one + * used by te SQLite3 library. + */ +JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm) +{ + sxu32 iNum; + SyRandomness(&pVm->sPrng, (void *)&iNum, sizeof(sxu32)); + return iNum; +} +/* + * Generate a random string (English Alphabet) of length nLen. + * Note that the generated string is NOT null terminated. + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen) +{ + static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */ + int i; + /* Generate a binary string first */ + SyRandomness(&pVm->sPrng, zBuf, (sxu32)nLen); + /* Turn the binary string into english based alphabet */ + for( i = 0 ; i < nLen ; ++i ){ + zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)]; + } +} +/* + * int rand() + * Generate a random (unsigned 32-bit) integer. + * Parameter + * $min + * The lowest value to return (default: 0) + * $max + * The highest value to return (default: getrandmax()) + * Return + * A pseudo random value between min (or 0) and max (or getrandmax(), inclusive). + * Note: + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_rand(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + sxu32 iNum; + /* Generate the random number */ + iNum = jx9VmRandomNum(pCtx->pVm); + if( nArg > 1 ){ + sxu32 iMin, iMax; + iMin = (sxu32)jx9_value_to_int(apArg[0]); + iMax = (sxu32)jx9_value_to_int(apArg[1]); + if( iMin < iMax ){ + sxu32 iDiv = iMax+1-iMin; + if( iDiv > 0 ){ + iNum = (iNum % iDiv)+iMin; + } + }else if(iMax > 0 ){ + iNum %= iMax; + } + } + /* Return the number */ + jx9_result_int64(pCtx, (jx9_int64)iNum); + return SXRET_OK; +} +/* + * int getrandmax(void) + * Show largest possible random value + * Return + * The largest possible random value returned by rand() which is in + * this implementation 0xFFFFFFFF. + * Note: + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_getrandmax(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + jx9_result_int64(pCtx, SXU32_HIGH); + return SXRET_OK; +} +/* + * string rand_str() + * string rand_str(int $len) + * Generate a random string (English alphabet). + * Parameter + * $len + * Length of the desired string (default: 16, Min: 1, Max: 1024) + * Return + * A pseudo random string. + * Note: + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_rand_str(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + char zString[1024]; + int iLen = 0x10; + if( nArg > 0 ){ + /* Get the desired length */ + iLen = jx9_value_to_int(apArg[0]); + if( iLen < 1 || iLen > 1024 ){ + /* Default length */ + iLen = 0x10; + } + } + /* Generate the random string */ + jx9VmRandomString(pCtx->pVm, zString, iLen); + /* Return the generated string */ + jx9_result_string(pCtx, zString, iLen); /* Will make it's own copy */ + return SXRET_OK; +} +/* + * Section: + * Language construct implementation as foreign functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * void print($string...) + * Output one or more messages. + * Parameters + * $string + * Message to output. + * Return + * NULL. + */ +static int vm_builtin_print(jx9_context *pCtx, int nArg,jx9_value **apArg) +{ + const char *zData; + int nDataLen = 0; + jx9_vm *pVm; + int i, rc; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Output */ + for( i = 0 ; i < nArg ; ++i ){ + zData = jx9_value_to_string(apArg[i], &nDataLen); + if( nDataLen > 0 ){ + rc = pVm->sVmConsumer.xConsumer((const void *)zData, (unsigned int)nDataLen, pVm->sVmConsumer.pUserData); + /* Increment output length */ + pVm->nOutputLen += nDataLen; + if( rc == SXERR_ABORT ){ + /* Output consumer callback request an operation abort */ + return JX9_ABORT; + } + } + } + return SXRET_OK; +} +/* + * void exit(string $msg) + * void exit(int $status) + * void die(string $ms) + * void die(int $status) + * Output a message and terminate program execution. + * Parameter + * If status is a string, this function prints the status just before exiting. + * If status is an integer, that value will be used as the exit status + * and not printed + * Return + * NULL + */ +static int vm_builtin_exit(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg > 0 ){ + if( jx9_value_is_string(apArg[0]) ){ + const char *zData; + int iLen = 0; + /* Print exit message */ + zData = jx9_value_to_string(apArg[0], &iLen); + jx9_context_output(pCtx, zData, iLen); + }else if(jx9_value_is_int(apArg[0]) ){ + sxi32 iExitStatus; + /* Record exit status code */ + iExitStatus = jx9_value_to_int(apArg[0]); + pCtx->pVm->iExitStatus = iExitStatus; + } + } + /* Abort processing immediately */ + return JX9_ABORT; +} +/* + * Unset a memory object [i.e: a jx9_value]. + */ +JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm,sxu32 nObjIdx) +{ + jx9_value *pObj; + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nObjIdx); + if( pObj ){ + VmSlot sFree; + /* Release the object */ + jx9MemObjRelease(pObj); + /* Restore to the free list */ + sFree.nIdx = nObjIdx; + sFree.pUserData = 0; + SySetPut(&pVm->aFreeObj, (const void *)&sFree); + } + return SXRET_OK; +} +/* + * string gettype($var) + * Get the type of a variable + * Parameters + * $var + * The variable being type checked. + * Return + * String representation of the given variable type. + */ +static int vm_builtin_gettype(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zType = "null"; + if( nArg > 0 ){ + zType = jx9MemObjTypeDump(apArg[0]); + } + /* Return the variable type */ + jx9_result_string(pCtx, zType, -1/*Compute length automatically*/); + return SXRET_OK; +} +/* + * string get_resource_type(resource $handle) + * This function gets the type of the given resource. + * Parameters + * $handle + * The evaluated resource handle. + * Return + * If the given handle is a resource, this function will return a string + * representing its type. If the type is not identified by this function + * the return value will be the string Unknown. + * This function will return FALSE and generate an error if handle + * is not a resource. + */ +static int vm_builtin_get_resource_type(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE*/ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + jx9_result_string_format(pCtx, "resID_%#x", apArg[0]->x.pOther); + return SXRET_OK; +} +/* + * void dump(expression, ....) + * dump — Dumps information about a variable + * Parameters + * One or more expression to dump. + * Returns + * Nothing. + */ +static int vm_builtin_dump(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyBlob sDump; /* Generated dump is stored here */ + int i; + SyBlobInit(&sDump,&pCtx->pVm->sAllocator); + /* Dump one or more expressions */ + for( i = 0 ; i < nArg ; i++ ){ + jx9_value *pObj = apArg[i]; + /* Reset the working buffer */ + SyBlobReset(&sDump); + /* Dump the given expression */ + jx9MemObjDump(&sDump,pObj); + /* Output */ + if( SyBlobLength(&sDump) > 0 ){ + jx9_context_output(pCtx, (const char *)SyBlobData(&sDump), (int)SyBlobLength(&sDump)); + } + } + /* Release the working buffer */ + SyBlobRelease(&sDump); + return SXRET_OK; +} +/* + * Section: + * Version, Credits and Copyright related functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Stable. + */ +/* + * string jx9_version(void) + * string jx9_credits(void) + * Returns the running version of the jx9 version. + * Parameters + * None + * Return + * Current jx9 version. + */ +static int vm_builtin_jx9_version(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SXUNUSED(nArg); + SXUNUSED(apArg); /* cc warning */ + /* Current engine version, signature and cipyright notice */ + jx9_result_string_format(pCtx,"%s %s, %s",JX9_VERSION,JX9_SIG,JX9_COPYRIGHT); + return JX9_OK; +} +/* + * Section: + * URL related routines. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* Forward declaration */ +static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen); +/* + * value parse_url(string $url [, int $component = -1 ]) + * Parse a URL and return its fields. + * Parameters + * $url + * The URL to parse. + * $component + * Specify one of JX9_URL_SCHEME, JX9_URL_HOST, JX9_URL_PORT, JX9_URL_USER + * JX9_URL_PASS, JX9_URL_PATH, JX9_URL_QUERY or JX9_URL_FRAGMENT to retrieve + * just a specific URL component as a string (except when JX9_URL_PORT is given + * in which case the return value will be an integer). + * Return + * If the component parameter is omitted, an associative array is returned. + * At least one element will be present within the array. Potential keys within + * this array are: + * scheme - e.g. http + * host + * port + * user + * pass + * path + * query - after the question mark ? + * fragment - after the hashmark # + * Note: + * FALSE is returned on failure. + * This function work with relative URL unlike the one shipped + * with the standard JX9 engine. + */ +static int vm_builtin_parse_url(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zStr; /* Input string */ + SyString *pComp; /* Pointer to the URI component */ + SyhttpUri sURI; /* Parse of the given URI */ + int nLen; + sxi32 rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the given URI */ + zStr = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Get a parse */ + rc = VmHttpSplitURI(&sURI, zStr, (sxu32)nLen); + if( rc != SXRET_OK ){ + /* Malformed input, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( nArg > 1 ){ + int nComponent = jx9_value_to_int(apArg[1]); + /* Refer to constant.c for constants values */ + switch(nComponent){ + case 1: /* JX9_URL_SCHEME */ + pComp = &sURI.sScheme; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 2: /* JX9_URL_HOST */ + pComp = &sURI.sHost; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 3: /* JX9_URL_PORT */ + pComp = &sURI.sPort; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + int iPort = 0; + /* Cast the value to integer */ + SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0); + jx9_result_int(pCtx, iPort); + } + break; + case 4: /* JX9_URL_USER */ + pComp = &sURI.sUser; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 5: /* JX9_URL_PASS */ + pComp = &sURI.sPass; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 7: /* JX9_URL_QUERY */ + pComp = &sURI.sQuery; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 8: /* JX9_URL_FRAGMENT */ + pComp = &sURI.sFragment; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 6: /* JX9_URL_PATH */ + pComp = &sURI.sPath; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + default: + /* No such entry, return NULL */ + jx9_result_null(pCtx); + break; + } + }else{ + jx9_value *pArray, *pValue; + /* Return an associative array */ + pArray = jx9_context_new_array(pCtx); /* Empty array */ + pValue = jx9_context_new_scalar(pCtx); /* Array value */ + if( pArray == 0 || pValue == 0 ){ + /* Out of memory */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "jx9 engine is running out of memory"); + /* Return false */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Fill the array */ + pComp = &sURI.sScheme; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "scheme", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sHost; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "host", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sPort; + if( pComp->nByte > 0 ){ + int iPort = 0;/* cc warning */ + /* Convert to integer */ + SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0); + jx9_value_int(pValue, iPort); + jx9_array_add_strkey_elem(pArray, "port", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sUser; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "user", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sPass; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "pass", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sPath; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "path", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sQuery; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "query", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sFragment; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "fragment", pValue); /* Will make it's own copy */ + } + /* Return the created array */ + jx9_result_value(pCtx, pArray); + /* NOTE: + * Don't worry about freeing 'pValue', everything will be released + * automatically as soon we return from this function. + */ + } + /* All done */ + return JX9_OK; +} +/* + * Section: + * Array related routines. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + * Note 2012-5-21 01:04:15: + * Array related functions that need access to the underlying + * virtual machine are implemented here rather than 'hashmap.c' + */ +/* + * The [extract()] function store it's state information in an instance + * of the following structure. + */ +typedef struct extract_aux_data extract_aux_data; +struct extract_aux_data +{ + jx9_vm *pVm; /* VM that own this instance */ + int iCount; /* Number of variables successfully imported */ + const char *zPrefix; /* Prefix name */ + int Prefixlen; /* Prefix length */ + int iFlags; /* Control flags */ + char zWorker[1024]; /* Working buffer */ +}; +/* Forward declaration */ +static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData); +/* + * int extract(array $var_array[, int $extract_type = EXTR_OVERWRITE[, string $prefix = NULL ]]) + * Import variables into the current symbol table from an array. + * Parameters + * $var_array + * An associative array. This function treats keys as variable names and values + * as variable values. For each key/value pair it will create a variable in the current symbol + * table, subject to extract_type and prefix parameters. + * You must use an associative array; a numerically indexed array will not produce results + * unless you use EXTR_PREFIX_ALL or EXTR_PREFIX_INVALID. + * $extract_type + * The way invalid/numeric keys and collisions are treated is determined by the extract_type. + * It can be one of the following values: + * EXTR_OVERWRITE + * If there is a collision, overwrite the existing variable. + * EXTR_SKIP + * If there is a collision, don't overwrite the existing variable. + * EXTR_PREFIX_SAME + * If there is a collision, prefix the variable name with prefix. + * EXTR_PREFIX_ALL + * Prefix all variable names with prefix. + * EXTR_PREFIX_INVALID + * Only prefix invalid/numeric variable names with prefix. + * EXTR_IF_EXISTS + * Only overwrite the variable if it already exists in the current symbol table + * otherwise do nothing. + * This is useful for defining a list of valid variables and then extracting only those + * variables you have defined out of $_REQUEST, for example. + * EXTR_PREFIX_IF_EXISTS + * Only create prefixed variable names if the non-prefixed version of the same variable exists in + * the current symbol table. + * $prefix + * Note that prefix is only required if extract_type is EXTR_PREFIX_SAME, EXTR_PREFIX_ALL + * EXTR_PREFIX_INVALID or EXTR_PREFIX_IF_EXISTS. If the prefixed result is not a valid variable name + * it is not imported into the symbol table. Prefixes are automatically separated from the array key by an + * underscore character. + * Return + * Returns the number of variables successfully imported into the symbol table. + */ +static int vm_builtin_extract(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + extract_aux_data sAux; + jx9_hashmap *pMap; + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the target hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Empty map, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Prepare the aux data */ + SyZero(&sAux, sizeof(extract_aux_data)-sizeof(sAux.zWorker)); + if( nArg > 1 ){ + sAux.iFlags = jx9_value_to_int(apArg[1]); + if( nArg > 2 ){ + sAux.zPrefix = jx9_value_to_string(apArg[2], &sAux.Prefixlen); + } + } + sAux.pVm = pCtx->pVm; + /* Invoke the worker callback */ + jx9HashmapWalk(pMap, VmExtractCallback, &sAux); + /* Number of variables successfully imported */ + jx9_result_int(pCtx, sAux.iCount); + return JX9_OK; +} +/* + * Worker callback for the [extract()] function defined + * below. + */ +static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData) +{ + extract_aux_data *pAux = (extract_aux_data *)pUserData; + int iFlags = pAux->iFlags; + jx9_vm *pVm = pAux->pVm; + jx9_value *pObj; + SyString sVar; + if( (iFlags & 0x10/* EXTR_PREFIX_INVALID */) && (pKey->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL|MEMOBJ_REAL))){ + iFlags |= 0x08; /*EXTR_PREFIX_ALL*/ + } + /* Perform a string cast */ + jx9MemObjToString(pKey); + if( SyBlobLength(&pKey->sBlob) < 1 ){ + /* Unavailable variable name */ + return SXRET_OK; + } + sVar.nByte = 0; /* cc warning */ + if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/ ) && pAux->Prefixlen > 0 ){ + sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker, sizeof(pAux->zWorker), "%.*s_%.*s", + pAux->Prefixlen, pAux->zPrefix, + SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob) + ); + }else{ + sVar.nByte = (sxu32) SyMemcpy(SyBlobData(&pKey->sBlob), pAux->zWorker, + SXMIN(SyBlobLength(&pKey->sBlob), sizeof(pAux->zWorker))); + } + sVar.zString = pAux->zWorker; + /* Try to extract the variable */ + pObj = VmExtractMemObj(pVm, &sVar, TRUE, FALSE); + if( pObj ){ + /* Collision */ + if( iFlags & 0x02 /* EXTR_SKIP */ ){ + return SXRET_OK; + } + if( iFlags & 0x04 /* EXTR_PREFIX_SAME */ ){ + if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/) || pAux->Prefixlen < 1){ + /* Already prefixed */ + return SXRET_OK; + } + sVar.nByte = SyBufferFormat( + pAux->zWorker, sizeof(pAux->zWorker), + "%.*s_%.*s", + pAux->Prefixlen, pAux->zPrefix, + SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob) + ); + pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE); + } + }else{ + /* Create the variable */ + pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE); + } + if( pObj ){ + /* Overwrite the old value */ + jx9MemObjStore(pValue, pObj); + /* Increment counter */ + pAux->iCount++; + } + return SXRET_OK; +} +/* + * Compile and evaluate a JX9 chunk at run-time. + * Refer to the include language construct implementation for more + * information. + */ +static sxi32 VmEvalChunk( + jx9_vm *pVm, /* Underlying Virtual Machine */ + jx9_context *pCtx, /* Call Context */ + SyString *pChunk, /* JX9 chunk to evaluate */ + int iFlags, /* Compile flag */ + int bTrueReturn /* TRUE to return execution result */ + ) +{ + SySet *pByteCode, aByteCode; + ProcConsumer xErr = 0; + void *pErrData = 0; + /* Initialize bytecode container */ + SySetInit(&aByteCode, &pVm->sAllocator, sizeof(VmInstr)); + SySetAlloc(&aByteCode, 0x20); + /* Reset the code generator */ + if( bTrueReturn ){ + /* Included file, log compile-time errors */ + xErr = pVm->pEngine->xConf.xErr; + pErrData = pVm->pEngine->xConf.pErrData; + } + jx9ResetCodeGenerator(pVm, xErr, pErrData); + /* Swap bytecode container */ + pByteCode = pVm->pByteContainer; + pVm->pByteContainer = &aByteCode; + /* Compile the chunk */ + jx9CompileScript(pVm, pChunk, iFlags); + if( pVm->sCodeGen.nErr > 0 ){ + /* Compilation error, return false */ + if( pCtx ){ + jx9_result_bool(pCtx, 0); + } + }else{ + jx9_value sResult; /* Return value */ + if( SXRET_OK != jx9VmEmitInstr(pVm, JX9_OP_DONE, 0, 0, 0, 0) ){ + /* Out of memory */ + if( pCtx ){ + jx9_result_bool(pCtx, 0); + } + goto Cleanup; + } + if( bTrueReturn ){ + /* Assume a boolean true return value */ + jx9MemObjInitFromBool(pVm, &sResult, 1); + }else{ + /* Assume a null return value */ + jx9MemObjInit(pVm, &sResult); + } + /* Execute the compiled chunk */ + VmLocalExec(pVm, &aByteCode, &sResult); + if( pCtx ){ + /* Set the execution result */ + jx9_result_value(pCtx, &sResult); + } + jx9MemObjRelease(&sResult); + } +Cleanup: + /* Cleanup the mess left behind */ + pVm->pByteContainer = pByteCode; + SySetRelease(&aByteCode); + return SXRET_OK; +} +/* + * Check if a file path is already included. + */ +static int VmIsIncludedFile(jx9_vm *pVm, SyString *pFile) +{ + SyString *aEntries; + sxu32 n; + aEntries = (SyString *)SySetBasePtr(&pVm->aIncluded); + /* Perform a linear search */ + for( n = 0 ; n < SySetUsed(&pVm->aIncluded) ; ++n ){ + if( SyStringCmp(pFile, &aEntries[n], SyMemcmp) == 0 ){ + /* Already included */ + return TRUE; + } + } + return FALSE; +} +/* + * Push a file path in the appropriate VM container. + */ +JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew) +{ + SyString sPath; + char *zDup; +#ifdef __WINNT__ + char *zCur; +#endif + sxi32 rc; + if( nLen < 0 ){ + nLen = SyStrlen(zPath); + } + /* Duplicate the file path first */ + zDup = SyMemBackendStrDup(&pVm->sAllocator, zPath, nLen); + if( zDup == 0 ){ + return SXERR_MEM; + } +#ifdef __WINNT__ + /* Normalize path on windows + * Example: + * Path/To/File.jx9 + * becomes + * path\to\file.jx9 + */ + zCur = zDup; + while( zCur[0] != 0 ){ + if( zCur[0] == '/' ){ + zCur[0] = '\\'; + }else if( (unsigned char)zCur[0] < 0xc0 && SyisUpper(zCur[0]) ){ + int c = SyToLower(zCur[0]); + zCur[0] = (char)c; /* MSVC stupidity */ + } + zCur++; + } +#endif + /* Install the file path */ + SyStringInitFromBuf(&sPath, zDup, nLen); + if( !bMain ){ + if( VmIsIncludedFile(&(*pVm), &sPath) ){ + /* Already included */ + *pNew = 0; + }else{ + /* Insert in the corresponding container */ + rc = SySetPut(&pVm->aIncluded, (const void *)&sPath); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator, zDup); + return rc; + } + *pNew = 1; + } + } + SySetPut(&pVm->aFiles, (const void *)&sPath); + return SXRET_OK; +} +/* + * Compile and Execute a JX9 script at run-time. + * SXRET_OK is returned on sucessful evaluation.Any other return values + * indicates failure. + * Note that the JX9 script to evaluate can be a local or remote file.In + * either cases the [jx9StreamReadWholeFile()] function handle all the underlying + * operations. + * If the [jJX9_DISABLE_BUILTIN_FUNC] compile-time directive is defined, then + * this function is a no-op. + * Refer to the implementation of the include(), import() language + * constructs for more information. + */ +static sxi32 VmExecIncludedFile( + jx9_context *pCtx, /* Call Context */ + SyString *pPath, /* Script path or URL*/ + int IncludeOnce /* TRUE if called from import() or require_once() */ + ) +{ + sxi32 rc; +#ifndef JX9_DISABLE_BUILTIN_FUNC + const jx9_io_stream *pStream; + SyBlob sContents; + void *pHandle; + jx9_vm *pVm; + int isNew; + /* Initialize fields */ + pVm = pCtx->pVm; + SyBlobInit(&sContents, &pVm->sAllocator); + isNew = 0; + /* Extract the associated stream */ + pStream = jx9VmGetStreamDevice(pVm, &pPath->zString, pPath->nByte); + /* + * Open the file or the URL [i.e: http://jx9.symisc.net/example/hello.jx9.txt"] + * in a read-only mode. + */ + pHandle = jx9StreamOpenHandle(pVm, pStream,pPath->zString, JX9_IO_OPEN_RDONLY, TRUE, 0, TRUE, &isNew); + if( pHandle == 0 ){ + return SXERR_IO; + } + rc = SXRET_OK; /* Stupid cc warning */ + if( IncludeOnce && !isNew ){ + /* Already included */ + rc = SXERR_EXISTS; + }else{ + /* Read the whole file contents */ + rc = jx9StreamReadWholeFile(pHandle, pStream, &sContents); + if( rc == SXRET_OK ){ + SyString sScript; + /* Compile and execute the script */ + SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents)); + VmEvalChunk(pCtx->pVm, &(*pCtx), &sScript, 0, TRUE); + } + } + /* Pop from the set of included file */ + (void)SySetPop(&pVm->aFiles); + /* Close the handle */ + jx9StreamCloseHandle(pStream, pHandle); + /* Release the working buffer */ + SyBlobRelease(&sContents); +#else + pCtx = 0; /* cc warning */ + pPath = 0; + IncludeOnce = 0; + rc = SXERR_IO; +#endif /* JX9_DISABLE_BUILTIN_FUNC */ + return rc; +} +/* * include: + * According to the JX9 reference manual. + * The include() function includes and evaluates the specified file. + * Files are included based on the file path given or, if none is given + * the include_path specified.If the file isn't found in the include_path + * include() will finally check in the calling script's own directory + * and the current working directory before failing. The include() + * construct will emit a warning if it cannot find a file; this is different + * behavior from require(), which will emit a fatal error. + * If a path is defined — whether absolute (starting with a drive letter + * or \ on Windows, or / on Unix/Linux systems) or relative to the current + * directory (starting with . or ..) — the include_path will be ignored altogether. + * For example, if a filename begins with ../, the parser will look in the parent + * directory to find the requested file. + * When a file is included, the code it contains inherits the variable scope + * of the line on which the include occurs. Any variables available at that line + * in the calling file will be available within the called file, from that point forward. + * However, all functions and objectes defined in the included file have the global scope. + */ +static int vm_builtin_include(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Open, compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx), &sFile, FALSE); + if( rc != SXRET_OK ){ + /* Emit a warning and return false */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile); + jx9_result_bool(pCtx, 0); + } + return SXRET_OK; +} +/* + * import: + * According to the JX9 reference manual. + * The import() statement includes and evaluates the specified file during + * the execution of the script. This is a behavior similar to the include() + * statement, with the only difference being that if the code from a file has already + * been included, it will not be included again. As the name suggests, it will be included + * just once. + */ +static int vm_builtin_import(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Open, compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx), &sFile, TRUE); + if( rc == SXERR_EXISTS ){ + /* File already included, return TRUE */ + jx9_result_bool(pCtx, 1); + return SXRET_OK; + } + if( rc != SXRET_OK ){ + /* Emit a warning and return false */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile); + jx9_result_bool(pCtx, 0); + } + return SXRET_OK; +} +/* + * Section: + * Command line arguments processing. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * Check if a short option argument [i.e: -c] is available in the command + * line string. Return a pointer to the start of the stream on success. + * NULL otherwise. + */ +static const char * VmFindShortOpt(int c, const char *zIn, const char *zEnd) +{ + while( zIn < zEnd ){ + if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == c ){ + /* Got one */ + return &zIn[1]; + } + /* Advance the cursor */ + zIn++; + } + /* No such option */ + return 0; +} +/* + * Check if a long option argument [i.e: --opt] is available in the command + * line string. Return a pointer to the start of the stream on success. + * NULL otherwise. + */ +static const char * VmFindLongOpt(const char *zLong, int nByte, const char *zIn, const char *zEnd) +{ + const char *zOpt; + while( zIn < zEnd ){ + if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == '-' ){ + zIn += 2; + zOpt = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + if( zIn[0] == '=' /* --opt=val */){ + break; + } + zIn++; + } + /* Test */ + if( (int)(zIn-zOpt) == nByte && SyMemcmp(zOpt, zLong, nByte) == 0 ){ + /* Got one, return it's value */ + return zIn; + } + + }else{ + zIn++; + } + } + /* No such option */ + return 0; +} +/* + * Long option [i.e: --opt] arguments private data structure. + */ +struct getopt_long_opt +{ + const char *zArgIn, *zArgEnd; /* Command line arguments */ + jx9_value *pWorker; /* Worker variable*/ + jx9_value *pArray; /* getopt() return value */ + jx9_context *pCtx; /* Call Context */ +}; +/* Forward declaration */ +static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData); +/* + * Extract short or long argument option values. + */ +static void VmExtractOptArgValue( + jx9_value *pArray, /* getopt() return value */ + jx9_value *pWorker, /* Worker variable */ + const char *zArg, /* Argument stream */ + const char *zArgEnd, /* End of the argument stream */ + int need_val, /* TRUE to fetch option argument */ + jx9_context *pCtx, /* Call Context */ + const char *zName /* Option name */) +{ + jx9_value_bool(pWorker, 0); + if( !need_val ){ + /* + * Option does not need arguments. + * Insert the option name and a boolean FALSE. + */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ + }else{ + const char *zCur; + /* Extract option argument */ + zArg++; + if( zArg < zArgEnd && zArg[0] == '=' ){ + zArg++; + } + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + if( zArg >= zArgEnd || zArg[0] == '-' ){ + /* + * Argument not found. + * Insert the option name and a boolean FALSE. + */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ + return; + } + /* Delimit the value */ + zCur = zArg; + if( zArg[0] == '\'' || zArg[0] == '"' ){ + int d = zArg[0]; + /* Delimt the argument */ + zArg++; + zCur = zArg; + while( zArg < zArgEnd ){ + if( zArg[0] == d && zArg[-1] != '\\' ){ + /* Delimiter found, exit the loop */ + break; + } + zArg++; + } + /* Save the value */ + jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); + if( zArg < zArgEnd ){ zArg++; } + }else{ + while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ + zArg++; + } + /* Save the value */ + jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); + } + /* + * Check if we are dealing with multiple values. + * If so, create an array to hold them, rather than a scalar variable. + */ + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + if( zArg < zArgEnd && zArg[0] != '-' ){ + jx9_value *pOptArg; /* Array of option arguments */ + pOptArg = jx9_context_new_array(pCtx); + if( pOptArg == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + }else{ + /* Insert the first value */ + jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */ + for(;;){ + if( zArg >= zArgEnd || zArg[0] == '-' ){ + /* No more value */ + break; + } + /* Delimit the value */ + zCur = zArg; + if( zArg < zArgEnd && zArg[0] == '\\' ){ + zArg++; + zCur = zArg; + } + while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ + zArg++; + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pWorker); + /* Save the value */ + jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); + /* Insert */ + jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */ + /* Jump trailing white spaces */ + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + } + /* Insert the option arg array */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pOptArg); /* Will make it's own copy */ + /* Safely release */ + jx9_context_release_value(pCtx, pOptArg); + } + }else{ + /* Single value */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ + } + } +} +/* + * array getopt(string $options[, array $longopts ]) + * Gets options from the command line argument list. + * Parameters + * $options + * Each character in this string will be used as option characters + * and matched against options passed to the script starting with + * a single hyphen (-). For example, an option string "x" recognizes + * an option -x. Only a-z, A-Z and 0-9 are allowed. + * $longopts + * An array of options. Each element in this array will be used as option + * strings and matched against options passed to the script starting with + * two hyphens (--). For example, an longopts element "opt" recognizes an + * option --opt. + * Return + * This function will return an array of option / argument pairs or FALSE + * on failure. + */ +static int vm_builtin_getopt(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn, *zEnd, *zArg, *zArgIn, *zArgEnd; + struct getopt_long_opt sLong; + jx9_value *pArray, *pWorker; + SyBlob *pArg; + int nByte; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Missing/Invalid option arguments"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract option arguments */ + zIn = jx9_value_to_string(apArg[0], &nByte); + zEnd = &zIn[nByte]; + /* Point to the string representation of the $argv[] array */ + pArg = &pCtx->pVm->sArgv; + /* Create a new empty array and a worker variable */ + pArray = jx9_context_new_array(pCtx); + pWorker = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pWorker == 0 ){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( SyBlobLength(pArg) < 1 ){ + /* Empty command line, return the empty array*/ + jx9_result_value(pCtx, pArray); + /* Everything will be released automatically when we return + * from this function. + */ + return JX9_OK; + } + zArgIn = (const char *)SyBlobData(pArg); + zArgEnd = &zArgIn[SyBlobLength(pArg)]; + /* Fill the long option structure */ + sLong.pArray = pArray; + sLong.pWorker = pWorker; + sLong.zArgIn = zArgIn; + sLong.zArgEnd = zArgEnd; + sLong.pCtx = pCtx; + /* Start processing */ + while( zIn < zEnd ){ + int c = zIn[0]; + int need_val = 0; + /* Advance the stream cursor */ + zIn++; + /* Ignore non-alphanum characters */ + if( !SyisAlphaNum(c) ){ + continue; + } + if( zIn < zEnd && zIn[0] == ':' ){ + zIn++; + need_val = 1; + if( zIn < zEnd && zIn[0] == ':' ){ + zIn++; + } + } + /* Find option */ + zArg = VmFindShortOpt(c, zArgIn, zArgEnd); + if( zArg == 0 ){ + /* No such option */ + continue; + } + /* Extract option argument value */ + VmExtractOptArgValue(pArray, pWorker, zArg, zArgEnd, need_val, pCtx, (const char *)&c); + } + if( nArg > 1 && jx9_value_is_json_array(apArg[1]) && jx9_array_count(apArg[1]) > 0 ){ + /* Process long options */ + jx9_array_walk(apArg[1], VmProcessLongOpt, &sLong); + } + /* Return the option array */ + jx9_result_value(pCtx, pArray); + /* + * Don't worry about freeing memory, everything will be released + * automatically as soon we return from this foreign function. + */ + return JX9_OK; +} +/* + * Array walker callback used for processing long options values. + */ +static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData) +{ + struct getopt_long_opt *pOpt = (struct getopt_long_opt *)pUserData; + const char *zArg, *zOpt, *zEnd; + int need_value = 0; + int nByte; + /* Value must be of type string */ + if( !jx9_value_is_string(pValue) ){ + /* Simply ignore */ + return JX9_OK; + } + zOpt = jx9_value_to_string(pValue, &nByte); + if( nByte < 1 ){ + /* Empty string, ignore */ + return JX9_OK; + } + zEnd = &zOpt[nByte - 1]; + if( zEnd[0] == ':' ){ + char *zTerm; + /* Try to extract a value */ + need_value = 1; + while( zEnd >= zOpt && zEnd[0] == ':' ){ + zEnd--; + } + if( zOpt >= zEnd ){ + /* Empty string, ignore */ + SXUNUSED(pKey); + return JX9_OK; + } + zEnd++; + zTerm = (char *)zEnd; + zTerm[0] = 0; + }else{ + zEnd = &zOpt[nByte]; + } + /* Find the option */ + zArg = VmFindLongOpt(zOpt, (int)(zEnd-zOpt), pOpt->zArgIn, pOpt->zArgEnd); + if( zArg == 0 ){ + /* No such option, return immediately */ + return JX9_OK; + } + /* Try to extract a value */ + VmExtractOptArgValue(pOpt->pArray, pOpt->pWorker, zArg, pOpt->zArgEnd, need_value, pOpt->pCtx, zOpt); + return JX9_OK; +} +/* + * int utf8_encode(string $input) + * UTF-8 encoding. + * This function encodes the string data to UTF-8, and returns the encoded version. + * UTF-8 is a standard mechanism used by Unicode for encoding wide character values + * into a byte stream. UTF-8 is transparent to plain ASCII characters, is self-synchronized + * (meaning it is possible for a program to figure out where in the bytestream characters start) + * and can be used with normal string comparison functions for sorting and such. + * Notes on UTF-8 (According to SQLite3 authors): + * Byte-0 Byte-1 Byte-2 Byte-3 Value + * 0xxxxxxx 00000000 00000000 0xxxxxxx + * 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx + * 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx + * 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx + * Parameters + * $input + * String to encode or NULL on failure. + * Return + * An UTF-8 encoded string. + */ +static int vm_builtin_utf8_encode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nByte, c, e; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte); + if( nByte < 1 ){ + /* Empty string, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + zEnd = &zIn[nByte]; + /* Start the encoding process */ + for(;;){ + if( zIn >= zEnd ){ + /* End of input */ + break; + } + c = zIn[0]; + /* Advance the stream cursor */ + zIn++; + /* Encode */ + if( c<0x00080 ){ + e = (c&0xFF); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + }else if( c<0x00800 ){ + e = 0xC0 + ((c>>6)&0x1F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + (c & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + }else if( c<0x10000 ){ + e = 0xE0 + ((c>>12)&0x0F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + ((c>>6) & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + (c & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + }else{ + e = 0xF0 + ((c>>18) & 0x07); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + ((c>>12) & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + ((c>>6) & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + (c & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + } + } + /* All done */ + return JX9_OK; +} +/* + * UTF-8 decoding routine extracted from the sqlite3 source tree. + * Original author: D. Richard Hipp (http://www.sqlite.org) + * Status: Public Domain + */ +/* +** This lookup table is used to help decode the first byte of +** a multi-byte UTF8 character. +*/ +static const unsigned char UtfTrans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; +/* +** Translate a single UTF-8 character. Return the unicode value. +** +** During translation, assume that the byte that zTerm points +** is a 0x00. +** +** Write a pointer to the next unread byte back into *pzNext. +** +** Notes On Invalid UTF-8: +** +** * This routine never allows a 7-bit character (0x00 through 0x7f) to +** be encoded as a multi-byte character. Any multi-byte character that +** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd. +** +** * This routine never allows a UTF16 surrogate value to be encoded. +** If a multi-byte character attempts to encode a value between +** 0xd800 and 0xe000 then it is rendered as 0xfffd. +** +** * Bytes in the range of 0x80 through 0xbf which occur as the first +** byte of a character are interpreted as single-byte characters +** and rendered as themselves even though they are technically +** invalid characters. +** +** * This routine accepts an infinite number of different UTF8 encodings +** for unicode values 0x80 and greater. It do not change over-length +** encodings to 0xfffd as some systems recommend. +*/ +#define READ_UTF8(zIn, zTerm, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = UtfTrans1[c-0xc0]; \ + while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + if( c<0x80 \ + || (c&0xFFFFF800)==0xD800 \ + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ + } +JX9_PRIVATE int jx9Utf8Read( + const unsigned char *z, /* First byte of UTF-8 character */ + const unsigned char *zTerm, /* Pretend this byte is 0x00 */ + const unsigned char **pzNext /* Write first byte past UTF-8 char here */ +){ + int c; + READ_UTF8(z, zTerm, c); + *pzNext = z; + return c; +} +/* + * string utf8_decode(string $data) + * This function decodes data, assumed to be UTF-8 encoded, to unicode. + * Parameters + * data + * An UTF-8 encoded string. + * Return + * Unicode decoded string or NULL on failure. + */ +static int vm_builtin_utf8_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nByte, c; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte); + if( nByte < 1 ){ + /* Empty string, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + zEnd = &zIn[nByte]; + /* Start the decoding process */ + while( zIn < zEnd ){ + c = jx9Utf8Read(zIn, zEnd, &zIn); + if( c == 0x0 ){ + break; + } + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + } + return JX9_OK; +} +/* + * string json_encode(mixed $value) + * Returns a string containing the JSON representation of value. + * Parameters + * $value + * The value being encoded. Can be any type except a resource. + * Return + * Returns a JSON encoded string on success. FALSE otherwise + */ +static int vm_builtin_json_encode(jx9_context *pCtx,int nArg,jx9_value **apArg) +{ + SyBlob sBlob; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Init the working buffer */ + SyBlobInit(&sBlob,&pCtx->pVm->sAllocator); + /* Perform the encoding operation */ + jx9JsonSerialize(apArg[0],&sBlob); + /* Return the serialized value */ + jx9_result_string(pCtx,(const char *)SyBlobData(&sBlob),(int)SyBlobLength(&sBlob)); + /* Cleanup */ + SyBlobRelease(&sBlob); + /* All done */ + return JX9_OK; +} +/* + * mixed json_decode(string $json) + * Takes a JSON encoded string and converts it into a JX9 variable. + * Parameters + * $json + * The json string being decoded. + * Return + * The value encoded in json in appropriate JX9 type. Values true, false and null (case-insensitive) + * are returned as TRUE, FALSE and NULL respectively. NULL is returned if the json cannot be decoded + * or if the encoded data is deeper than the recursion limit. + */ +static int vm_builtin_json_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zJSON; + int nByte; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the JSON string */ + zJSON = jx9_value_to_string(apArg[0], &nByte); + if( nByte < 1 ){ + /* Empty string, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Decode the raw JSON */ + jx9JsonDecode(pCtx,zJSON,nByte); + return JX9_OK; +} +/* Table of built-in VM functions. */ +static const jx9_builtin_func aVmFunc[] = { + /* JSON Encoding/Decoding */ + { "json_encode", vm_builtin_json_encode }, + { "json_decode", vm_builtin_json_decode }, + /* Functions calls */ + { "func_num_args" , vm_builtin_func_num_args }, + { "func_get_arg" , vm_builtin_func_get_arg }, + { "func_get_args" , vm_builtin_func_get_args }, + { "function_exists", vm_builtin_func_exists }, + { "is_callable" , vm_builtin_is_callable }, + { "get_defined_functions", vm_builtin_get_defined_func }, + /* Constants management */ + { "defined", vm_builtin_defined }, + { "get_defined_constants", vm_builtin_get_defined_constants }, + /* Random numbers/strings generators */ + { "rand", vm_builtin_rand }, + { "rand_str", vm_builtin_rand_str }, + { "getrandmax", vm_builtin_getrandmax }, + /* Language constructs functions */ + { "print", vm_builtin_print }, + { "exit", vm_builtin_exit }, + { "die", vm_builtin_exit }, + /* Variable handling functions */ + { "gettype", vm_builtin_gettype }, + { "get_resource_type", vm_builtin_get_resource_type}, + /* Variable dumping */ + { "dump", vm_builtin_dump }, + /* Release info */ + {"jx9_version", vm_builtin_jx9_version }, + {"jx9_credits", vm_builtin_jx9_version }, + {"jx9_info", vm_builtin_jx9_version }, + {"jx9_copyright", vm_builtin_jx9_version }, + /* hashmap */ + {"extract", vm_builtin_extract }, + /* URL related function */ + {"parse_url", vm_builtin_parse_url }, + /* UTF-8 encoding/decoding */ + {"utf8_encode", vm_builtin_utf8_encode}, + {"utf8_decode", vm_builtin_utf8_decode}, + /* Command line processing */ + {"getopt", vm_builtin_getopt }, + /* Files/URI inclusion facility */ + { "include", vm_builtin_include }, + { "import", vm_builtin_import } +}; +/* + * Register the built-in VM functions defined above. + */ +static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm) +{ + sxi32 rc; + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aVmFunc) ; ++n ){ + /* Note that these special functions have access + * to the underlying virtual machine as their + * private data. + */ + rc = jx9_create_function(&(*pVm), aVmFunc[n].zName, aVmFunc[n].xFunc, &(*pVm)); + if( rc != SXRET_OK ){ + return rc; + } + } + return SXRET_OK; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +/* + * Extract the IO stream device associated with a given scheme. + * Return a pointer to an instance of jx9_io_stream when the scheme + * have an associated IO stream registered with it. NULL otherwise. + * If no scheme:// is avalilable then the file:// scheme is assumed. + * For more information on how to register IO stream devices, please + * refer to the official documentation. + */ +JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice( + jx9_vm *pVm, /* Target VM */ + const char **pzDevice, /* Full path, URI, ... */ + int nByte /* *pzDevice length*/ + ) +{ + const char *zIn, *zEnd, *zCur, *zNext; + jx9_io_stream **apStream, *pStream; + SyString sDev, sCur; + sxu32 n, nEntry; + int rc; + /* Check if a scheme [i.e: file://, http://, zip://...] is available */ + zNext = zCur = zIn = *pzDevice; + zEnd = &zIn[nByte]; + while( zIn < zEnd ){ + if( zIn < &zEnd[-3]/*://*/ && zIn[0] == ':' && zIn[1] == '/' && zIn[2] == '/' ){ + /* Got one */ + zNext = &zIn[sizeof("://")-1]; + break; + } + /* Advance the cursor */ + zIn++; + } + if( zIn >= zEnd ){ + /* No such scheme, return the default stream */ + return pVm->pDefStream; + } + SyStringInitFromBuf(&sDev, zCur, zIn-zCur); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sDev); + /* Perform a linear lookup on the installed stream devices */ + apStream = (jx9_io_stream **)SySetBasePtr(&pVm->aIOstream); + nEntry = SySetUsed(&pVm->aIOstream); + for( n = 0 ; n < nEntry ; n++ ){ + pStream = apStream[n]; + SyStringInitFromBuf(&sCur, pStream->zName, SyStrlen(pStream->zName)); + /* Perfrom a case-insensitive comparison */ + rc = SyStringCmp(&sDev, &sCur, SyStrnicmp); + if( rc == 0 ){ + /* Stream device found */ + *pzDevice = zNext; + return pStream; + } + } + /* No such stream, return NULL */ + return 0; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* + * Section: + * HTTP/URI related routines. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ + /* + * URI Parser: Split an URI into components [i.e: Host, Path, Query, ...]. + * URI syntax: [method:/][/[user[:pwd]@]host[:port]/][document] + * This almost, but not quite, RFC1738 URI syntax. + * This routine is not a validator, it does not check for validity + * nor decode URI parts, the only thing this routine does is splitting + * the input to its fields. + * Upper layer are responsible of decoding and validating URI parts. + * On success, this function populate the "SyhttpUri" structure passed + * as the first argument. Otherwise SXERR_* is returned when a malformed + * input is encountered. + */ + static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen) + { + const char *zEnd = &zUri[nLen]; + sxu8 bHostOnly = FALSE; + sxu8 bIPv6 = FALSE ; + const char *zCur; + SyString *pComp; + sxu32 nPos = 0; + sxi32 rc; + /* Zero the structure first */ + SyZero(pOut, sizeof(SyhttpUri)); + /* Remove leading and trailing white spaces */ + SyStringInitFromBuf(&pOut->sRaw, zUri, nLen); + SyStringFullTrim(&pOut->sRaw); + /* Find the first '/' separator */ + rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos); + if( rc != SXRET_OK ){ + /* Assume a host name only */ + zCur = zEnd; + bHostOnly = TRUE; + goto ProcessHost; + } + zCur = &zUri[nPos]; + if( zUri != zCur && zCur[-1] == ':' ){ + /* Extract a scheme: + * Not that we can get an invalid scheme here. + * Fortunately the caller can discard any URI by comparing this scheme with its + * registered schemes and will report the error as soon as his comparison function + * fail. + */ + pComp = &pOut->sScheme; + SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri - 1)); + SyStringLeftTrim(pComp); + } + if( zCur[1] != '/' ){ + if( zCur == zUri || zCur[-1] == ':' ){ + /* No authority */ + goto PathSplit; + } + /* There is something here , we will assume its an authority + * and someone has forgot the two prefix slashes "//", + * sooner or later we will detect if we are dealing with a malicious + * user or not, but now assume we are dealing with an authority + * and let the caller handle all the validation process. + */ + goto ProcessHost; + } + zUri = &zCur[2]; + zCur = zEnd; + rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos); + if( rc == SXRET_OK ){ + zCur = &zUri[nPos]; + } + ProcessHost: + /* Extract user information if present */ + rc = SyByteFind(zUri, (sxu32)(zCur - zUri), '@', &nPos); + if( rc == SXRET_OK ){ + if( nPos > 0 ){ + sxu32 nPassOfft; /* Password offset */ + pComp = &pOut->sUser; + SyStringInitFromBuf(pComp, zUri, nPos); + /* Extract the password if available */ + rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPassOfft); + if( rc == SXRET_OK && nPassOfft < nPos){ + pComp->nByte = nPassOfft; + pComp = &pOut->sPass; + pComp->zString = &zUri[nPassOfft+sizeof(char)]; + pComp->nByte = nPos - nPassOfft - 1; + } + /* Update the cursor */ + zUri = &zUri[nPos+1]; + }else{ + zUri++; + } + } + pComp = &pOut->sHost; + while( zUri < zCur && SyisSpace(zUri[0])){ + zUri++; + } + SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri)); + if( pComp->zString[0] == '[' ){ + /* An IPv6 Address: Make a simple naive test + */ + zUri++; pComp->zString++; pComp->nByte = 0; + while( ((unsigned char)zUri[0] < 0xc0 && SyisHex(zUri[0])) || zUri[0] == ':' ){ + zUri++; pComp->nByte++; + } + if( zUri[0] != ']' ){ + return SXERR_CORRUPT; /* Malformed IPv6 address */ + } + zUri++; + bIPv6 = TRUE; + } + /* Extract a port number if available */ + rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPos); + if( rc == SXRET_OK ){ + if( bIPv6 == FALSE ){ + pComp->nByte = (sxu32)(&zUri[nPos] - zUri); + } + pComp = &pOut->sPort; + SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zCur - &zUri[nPos+1])); + } + if( bHostOnly == TRUE ){ + return SXRET_OK; + } +PathSplit: + zUri = zCur; + pComp = &pOut->sPath; + SyStringInitFromBuf(pComp, zUri, (sxu32)(zEnd-zUri)); + if( pComp->nByte == 0 ){ + return SXRET_OK; /* Empty path */ + } + if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '?', &nPos) ){ + pComp->nByte = nPos; /* Update path length */ + pComp = &pOut->sQuery; + SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1])); + } + if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '#', &nPos) ){ + /* Update path or query length */ + if( pComp == &pOut->sPath ){ + pComp->nByte = nPos; + }else{ + if( &zUri[nPos] < (char *)SyStringData(pComp) ){ + /* Malformed syntax : Query must be present before fragment */ + return SXERR_SYNTAX; + } + pComp->nByte -= (sxu32)(zEnd - &zUri[nPos]); + } + pComp = &pOut->sFragment; + SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1])) + } + return SXRET_OK; + } + /* + * Extract a single line from a raw HTTP request. + * Return SXRET_OK on success, SXERR_EOF when end of input + * and SXERR_MORE when more input is needed. + */ +static sxi32 VmGetNextLine(SyString *pCursor, SyString *pCurrent) +{ + const char *zIn; + sxu32 nPos; + /* Jump leading white spaces */ + SyStringLeftTrim(pCursor); + if( pCursor->nByte < 1 ){ + SyStringInitFromBuf(pCurrent, 0, 0); + return SXERR_EOF; /* End of input */ + } + zIn = SyStringData(pCursor); + if( SXRET_OK != SyByteListFind(pCursor->zString, pCursor->nByte, "\r\n", &nPos) ){ + /* Line not found, tell the caller to read more input from source */ + SyStringDupPtr(pCurrent, pCursor); + return SXERR_MORE; + } + pCurrent->zString = zIn; + pCurrent->nByte = nPos; + /* advance the cursor so we can call this routine again */ + pCursor->zString = &zIn[nPos]; + pCursor->nByte -= nPos; + return SXRET_OK; + } + /* + * Split a single MIME header into a name value pair. + * This function return SXRET_OK, SXERR_CONTINUE on success. + * Otherwise SXERR_NEXT is returned when a malformed header + * is encountered. + * Note: This function handle also mult-line headers. + */ + static sxi32 VmHttpProcessOneHeader(SyhttpHeader *pHdr, SyhttpHeader *pLast, const char *zLine, sxu32 nLen) + { + SyString *pName; + sxu32 nPos; + sxi32 rc; + if( nLen < 1 ){ + return SXERR_NEXT; + } + /* Check for multi-line header */ + if( pLast && (zLine[-1] == ' ' || zLine[-1] == '\t') ){ + SyString *pTmp = &pLast->sValue; + SyStringFullTrim(pTmp); + if( pTmp->nByte == 0 ){ + SyStringInitFromBuf(pTmp, zLine, nLen); + }else{ + /* Update header value length */ + pTmp->nByte = (sxu32)(&zLine[nLen] - pTmp->zString); + } + /* Simply tell the caller to reset its states and get another line */ + return SXERR_CONTINUE; + } + /* Split the header */ + pName = &pHdr->sName; + rc = SyByteFind(zLine, nLen, ':', &nPos); + if(rc != SXRET_OK ){ + return SXERR_NEXT; /* Malformed header;Check the next entry */ + } + SyStringInitFromBuf(pName, zLine, nPos); + SyStringFullTrim(pName); + /* Extract a header value */ + SyStringInitFromBuf(&pHdr->sValue, &zLine[nPos + 1], nLen - nPos - 1); + /* Remove leading and trailing whitespaces */ + SyStringFullTrim(&pHdr->sValue); + return SXRET_OK; + } + /* + * Extract all MIME headers associated with a HTTP request. + * After processing the first line of a HTTP request, the following + * routine is called in order to extract MIME headers. + * This function return SXRET_OK on success, SXERR_MORE when it needs + * more inputs. + * Note: Any malformed header is simply discarded. + */ + static sxi32 VmHttpExtractHeaders(SyString *pRequest, SySet *pOut) + { + SyhttpHeader *pLast = 0; + SyString sCurrent; + SyhttpHeader sHdr; + sxu8 bEol; + sxi32 rc; + if( SySetUsed(pOut) > 0 ){ + pLast = (SyhttpHeader *)SySetAt(pOut, SySetUsed(pOut)-1); + } + bEol = FALSE; + for(;;){ + SyZero(&sHdr, sizeof(SyhttpHeader)); + /* Extract a single line from the raw HTTP request */ + rc = VmGetNextLine(pRequest, &sCurrent); + if(rc != SXRET_OK ){ + if( sCurrent.nByte < 1 ){ + break; + } + bEol = TRUE; + } + /* Process the header */ + if( SXRET_OK == VmHttpProcessOneHeader(&sHdr, pLast, sCurrent.zString, sCurrent.nByte)){ + if( SXRET_OK != SySetPut(pOut, (const void *)&sHdr) ){ + break; + } + /* Retrieve the last parsed header so we can handle multi-line header + * in case we face one of them. + */ + pLast = (SyhttpHeader *)SySetPeek(pOut); + } + if( bEol ){ + break; + } + } /* for(;;) */ + return SXRET_OK; + } + /* + * Process the first line of a HTTP request. + * This routine perform the following operations + * 1) Extract the HTTP method. + * 2) Split the request URI to it's fields [ie: host, path, query, ...]. + * 3) Extract the HTTP protocol version. + */ + static sxi32 VmHttpProcessFirstLine( + SyString *pRequest, /* Raw HTTP request */ + sxi32 *pMethod, /* OUT: HTTP method */ + SyhttpUri *pUri, /* OUT: Parse of the URI */ + sxi32 *pProto /* OUT: HTTP protocol */ + ) + { + static const char *azMethods[] = { "get", "post", "head", "put"}; + static const sxi32 aMethods[] = { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PUT}; + const char *zIn, *zEnd, *zPtr; + SyString sLine; + sxu32 nLen; + sxi32 rc; + /* Extract the first line and update the pointer */ + rc = VmGetNextLine(pRequest, &sLine); + if( rc != SXRET_OK ){ + return rc; + } + if ( sLine.nByte < 1 ){ + /* Empty HTTP request */ + return SXERR_EMPTY; + } + /* Delimit the line and ignore trailing and leading white spaces */ + zIn = sLine.zString; + zEnd = &zIn[sLine.nByte]; + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the HTTP method */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + *pMethod = HTTP_METHOD_OTHR; + if( zIn > zPtr ){ + sxu32 i; + nLen = (sxu32)(zIn-zPtr); + for( i = 0 ; i < SX_ARRAYSIZE(azMethods) ; ++i ){ + if( SyStrnicmp(azMethods[i], zPtr, nLen) == 0 ){ + *pMethod = aMethods[i]; + break; + } + } + } + /* Jump trailing white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the request URI */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn > zPtr ){ + nLen = (sxu32)(zIn-zPtr); + /* Split raw URI to it's fields */ + VmHttpSplitURI(pUri, zPtr, nLen); + } + /* Jump trailing white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the HTTP version */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + *pProto = HTTP_PROTO_11; /* HTTP/1.1 */ + rc = 1; + if( zIn > zPtr ){ + rc = SyStrnicmp(zPtr, "http/1.0", (sxu32)(zIn-zPtr)); + } + if( !rc ){ + *pProto = HTTP_PROTO_10; /* HTTP/1.0 */ + } + return SXRET_OK; + } + /* + * Tokenize, decode and split a raw query encoded as: "x-www-form-urlencoded" + * into a name value pair. + * Note that this encoding is implicit in GET based requests. + * After the tokenization process, register the decoded queries + * in the $_GET/$_POST/$_REQUEST superglobals arrays. + */ + static sxi32 VmHttpSplitEncodedQuery( + jx9_vm *pVm, /* Target VM */ + SyString *pQuery, /* Raw query to decode */ + SyBlob *pWorker, /* Working buffer */ + int is_post /* TRUE if we are dealing with a POST request */ + ) + { + const char *zEnd = &pQuery->zString[pQuery->nByte]; + const char *zIn = pQuery->zString; + jx9_value *pGet, *pRequest; + SyString sName, sValue; + const char *zPtr; + sxu32 nBlobOfft; + /* Extract superglobals */ + if( is_post ){ + /* $_POST superglobal */ + pGet = VmExtractSuper(&(*pVm), "_POST", sizeof("_POST")-1); + }else{ + /* $_GET superglobal */ + pGet = VmExtractSuper(&(*pVm), "_GET", sizeof("_GET")-1); + } + pRequest = VmExtractSuper(&(*pVm), "_REQUEST", sizeof("_REQUEST")-1); + /* Split up the raw query */ + for(;;){ + /* Jump leading white spaces */ + while(zIn < zEnd && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + break; + } + zPtr = zIn; + while( zPtr < zEnd && zPtr[0] != '=' && zPtr[0] != '&' && zPtr[0] != ';' ){ + zPtr++; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Decode the entry */ + SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); + /* Save the entry */ + sName.nByte = SyBlobLength(pWorker); + sValue.zString = 0; + sValue.nByte = 0; + if( zPtr < zEnd && zPtr[0] == '=' ){ + zPtr++; + zIn = zPtr; + /* Store field value */ + while( zPtr < zEnd && zPtr[0] != '&' && zPtr[0] != ';' ){ + zPtr++; + } + if( zPtr > zIn ){ + /* Decode the value */ + nBlobOfft = SyBlobLength(pWorker); + SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); + sValue.zString = (const char *)SyBlobDataAt(pWorker, nBlobOfft); + sValue.nByte = SyBlobLength(pWorker) - nBlobOfft; + + } + /* Synchronize pointers */ + zIn = zPtr; + } + sName.zString = (const char *)SyBlobData(pWorker); + /* Install the decoded query in the $_GET/$_REQUEST array */ + if( pGet && (pGet->iFlags & MEMOBJ_HASHMAP) ){ + VmHashmapInsert((jx9_hashmap *)pGet->x.pOther, + sName.zString, (int)sName.nByte, + sValue.zString, (int)sValue.nByte + ); + } + if( pRequest && (pRequest->iFlags & MEMOBJ_HASHMAP) ){ + VmHashmapInsert((jx9_hashmap *)pRequest->x.pOther, + sName.zString, (int)sName.nByte, + sValue.zString, (int)sValue.nByte + ); + } + /* Advance the pointer */ + zIn = &zPtr[1]; + } + /* All done*/ + return SXRET_OK; + } + /* + * Extract MIME header value from the given set. + * Return header value on success. NULL otherwise. + */ + static SyString * VmHttpExtractHeaderValue(SySet *pSet, const char *zMime, sxu32 nByte) + { + SyhttpHeader *aMime, *pMime; + SyString sMime; + sxu32 n; + SyStringInitFromBuf(&sMime, zMime, nByte); + /* Point to the MIME entries */ + aMime = (SyhttpHeader *)SySetBasePtr(pSet); + /* Perform the lookup */ + for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ + pMime = &aMime[n]; + if( SyStringCmp(&sMime, &pMime->sName, SyStrnicmp) == 0 ){ + /* Header found, return it's associated value */ + return &pMime->sValue; + } + } + /* No such MIME header */ + return 0; + } + /* + * Tokenize and decode a raw "Cookie:" MIME header into a name value pair + * and insert it's fields [i.e name, value] in the $_COOKIE superglobal. + */ + static sxi32 VmHttpPorcessCookie(jx9_vm *pVm, SyBlob *pWorker, const char *zIn, sxu32 nByte) + { + const char *zPtr, *zDelimiter, *zEnd = &zIn[nByte]; + SyString sName, sValue; + jx9_value *pCookie; + sxu32 nOfft; + /* Make sure the $_COOKIE superglobal is available */ + pCookie = VmExtractSuper(&(*pVm), "_COOKIE", sizeof("_COOKIE")-1); + if( pCookie == 0 || (pCookie->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* $_COOKIE superglobal not available */ + return SXERR_NOTFOUND; + } + for(;;){ + /* Jump leading white spaces */ + while( zIn < zEnd && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + break; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + zDelimiter = zIn; + /* Delimit the name[=value]; pair */ + while( zDelimiter < zEnd && zDelimiter[0] != ';' ){ + zDelimiter++; + } + zPtr = zIn; + while( zPtr < zDelimiter && zPtr[0] != '=' ){ + zPtr++; + } + /* Decode the cookie */ + SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); + sName.nByte = SyBlobLength(pWorker); + zPtr++; + sValue.zString = 0; + sValue.nByte = 0; + if( zPtr < zDelimiter ){ + /* Got a Cookie value */ + nOfft = SyBlobLength(pWorker); + SyUriDecode(zPtr, (sxu32)(zDelimiter-zPtr), jx9VmBlobConsumer, pWorker, TRUE); + SyStringInitFromBuf(&sValue, SyBlobDataAt(pWorker, nOfft), SyBlobLength(pWorker)-nOfft); + } + /* Synchronize pointers */ + zIn = &zDelimiter[1]; + /* Perform the insertion */ + sName.zString = (const char *)SyBlobData(pWorker); + VmHashmapInsert((jx9_hashmap *)pCookie->x.pOther, + sName.zString, (int)sName.nByte, + sValue.zString, (int)sValue.nByte + ); + } + return SXRET_OK; + } + /* + * Process a full HTTP request and populate the appropriate arrays + * such as $_SERVER, $_GET, $_POST, $_COOKIE, $_REQUEST, ... with the information + * extracted from the raw HTTP request. As an extension Symisc introduced + * the $_HEADER array which hold a copy of the processed HTTP MIME headers + * and their associated values. [i.e: $_HEADER['Server'], $_HEADER['User-Agent'], ...]. + * This function return SXRET_OK on success. Any other return value indicates + * a malformed HTTP request. + */ + static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte) + { + SyString *pName, *pValue, sRequest; /* Raw HTTP request */ + jx9_value *pHeaderArray; /* $_HEADER superglobal (Symisc eXtension to the JX9 specification)*/ + SyhttpHeader *pHeader; /* MIME header */ + SyhttpUri sUri; /* Parse of the raw URI*/ + SyBlob sWorker; /* General purpose working buffer */ + SySet sHeader; /* MIME headers set */ + sxi32 iMethod; /* HTTP method [i.e: GET, POST, HEAD...]*/ + sxi32 iVer; /* HTTP protocol version */ + sxi32 rc; + SyStringInitFromBuf(&sRequest, zRequest, nByte); + SySetInit(&sHeader, &pVm->sAllocator, sizeof(SyhttpHeader)); + SyBlobInit(&sWorker, &pVm->sAllocator); + /* Ignore leading and trailing white spaces*/ + SyStringFullTrim(&sRequest); + /* Process the first line */ + rc = VmHttpProcessFirstLine(&sRequest, &iMethod, &sUri, &iVer); + if( rc != SXRET_OK ){ + return rc; + } + /* Process MIME headers */ + VmHttpExtractHeaders(&sRequest, &sHeader); + /* + * Setup $_SERVER environments + */ + /* 'SERVER_PROTOCOL': Name and revision of the information protocol via which the page was requested */ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "SERVER_PROTOCOL", + iVer == HTTP_PROTO_10 ? "HTTP/1.0" : "HTTP/1.1", + sizeof("HTTP/1.1")-1 + ); + /* 'REQUEST_METHOD': Which request method was used to access the page */ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "REQUEST_METHOD", + iMethod == HTTP_METHOD_GET ? "GET" : + (iMethod == HTTP_METHOD_POST ? "POST": + (iMethod == HTTP_METHOD_PUT ? "PUT" : + (iMethod == HTTP_METHOD_HEAD ? "HEAD" : "OTHER"))), + -1 /* Compute attribute length automatically */ + ); + if( SyStringLength(&sUri.sQuery) > 0 && iMethod == HTTP_METHOD_GET ){ + pValue = &sUri.sQuery; + /* 'QUERY_STRING': The query string, if any, via which the page was accessed */ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "QUERY_STRING", + pValue->zString, + pValue->nByte + ); + /* Decoded the raw query */ + VmHttpSplitEncodedQuery(&(*pVm), pValue, &sWorker, FALSE); + } + /* REQUEST_URI: The URI which was given in order to access this page; for instance, '/index.html' */ + pValue = &sUri.sRaw; + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "REQUEST_URI", + pValue->zString, + pValue->nByte + ); + /* + * 'PATH_INFO' + * 'ORIG_PATH_INFO' + * Contains any client-provided pathname information trailing the actual script filename but preceding + * the query string, if available. For instance, if the current script was accessed via the URL + * http://www.example.com/jx9/path_info.jx9/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain + * /some/stuff. + */ + pValue = &sUri.sPath; + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "PATH_INFO", + pValue->zString, + pValue->nByte + ); + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "ORIG_PATH_INFO", + pValue->zString, + pValue->nByte + ); + /* 'HTTP_ACCEPT': Contents of the Accept: header from the current request, if there is one */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept", sizeof("Accept")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_CHARSET': Contents of the Accept-Charset: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Charset", sizeof("Accept-Charset")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_CHARSET", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_ENCODING': Contents of the Accept-Encoding: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Encoding", sizeof("Accept-Encoding")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_ENCODING", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_LANGUAGE': Contents of the Accept-Language: header from the current request, if there is one */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Language", sizeof("Accept-Language")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_LANGUAGE", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_CONNECTION': Contents of the Connection: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Connection", sizeof("Connection")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_CONNECTION", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_HOST': Contents of the Host: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Host", sizeof("Host")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_HOST", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_REFERER': Contents of the Referer: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Referer", sizeof("Referer")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_REFERER", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_USER_AGENT': Contents of the Referer: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "User-Agent", sizeof("User-Agent")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_USER_AGENT", + pValue->zString, + pValue->nByte + ); + } + /* 'JX9_AUTH_DIGEST': When doing Digest HTTP authentication this variable is set to the 'Authorization' + * header sent by the client (which you should then use to make the appropriate validation). + */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Authorization", sizeof("Authorization")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "JX9_AUTH_DIGEST", + pValue->zString, + pValue->nByte + ); + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "JX9_AUTH", + pValue->zString, + pValue->nByte + ); + } + /* Install all clients HTTP headers in the $_HEADER superglobal */ + pHeaderArray = VmExtractSuper(&(*pVm), "_HEADER", sizeof("_HEADER")-1); + /* Iterate throw the available MIME headers*/ + SySetResetCursor(&sHeader); + pHeader = 0; /* stupid cc warning */ + while( SXRET_OK == SySetGetNextEntry(&sHeader, (void **)&pHeader) ){ + pName = &pHeader->sName; + pValue = &pHeader->sValue; + if( pHeaderArray && (pHeaderArray->iFlags & MEMOBJ_HASHMAP)){ + /* Insert the MIME header and it's associated value */ + VmHashmapInsert((jx9_hashmap *)pHeaderArray->x.pOther, + pName->zString, (int)pName->nByte, + pValue->zString, (int)pValue->nByte + ); + } + if( pName->nByte == sizeof("Cookie")-1 && SyStrnicmp(pName->zString, "Cookie", sizeof("Cookie")-1) == 0 + && pValue->nByte > 0){ + /* Process the name=value pair and insert them in the $_COOKIE superglobal array */ + VmHttpPorcessCookie(&(*pVm), &sWorker, pValue->zString, pValue->nByte); + } + } + if( iMethod == HTTP_METHOD_POST ){ + /* Extract raw POST data */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Type", sizeof("Content-Type") - 1); + if( pValue && pValue->nByte >= sizeof("application/x-www-form-urlencoded") - 1 && + SyMemcmp("application/x-www-form-urlencoded", pValue->zString, pValue->nByte) == 0 ){ + /* Extract POST data length */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Length", sizeof("Content-Length") - 1); + if( pValue ){ + sxi32 iLen = 0; /* POST data length */ + SyStrToInt32(pValue->zString, pValue->nByte, (void *)&iLen, 0); + if( iLen > 0 ){ + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sRequest); + if( (int)sRequest.nByte > iLen ){ + sRequest.nByte = (sxu32)iLen; + } + /* Decode POST data now */ + VmHttpSplitEncodedQuery(&(*pVm), &sRequest, &sWorker, TRUE); + } + } + } + } + /* All done, clean-up the mess left behind */ + SySetRelease(&sHeader); + SyBlobRelease(&sWorker); + return SXRET_OK; + } -- cgit v1.2.3