/* * Symisc UnQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2019, Symisc Systems http://unqlite.org/ * Version 1.1.9 * 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://unqlite.org/licensing.html */ /* * Copyright (C) 2012, 2019 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* $SymiscID: unqlite.c v1.1.9 Win10 2108-04-27 02:35:11 stable $ */ /* This file is an amalgamation of many separate C source files from unqlite version 1.1.9 * By combining all the individual C code files into this single large file, the entire code * can be compiled as a single translation unit. This allows many compilers to do optimization's * that would not be possible if the files were compiled separately. Performance improvements * are commonly seen when unqlite is compiled as a single translation unit. * * This file is all you need to compile unqlite. To use unqlite in other programs, you need * this file and the "unqlite.h" header file that defines the programming interface to the * unqlite engine.(If you do not have the "unqlite.h" header file at hand, you will find * a copy embedded within the text of this file.Search for "Header file: " to find * the start of the embedded unqlite.h header file.) Additional code files may be needed if * you want a wrapper to interface unqlite with your choice of programming language. * To get the official documentation, please visit http://unqlite.org/ */ /* * Make the sure the following directive is defined in the amalgamation build. */ #ifndef UNQLITE_AMALGAMATION #define UNQLITE_AMALGAMATION #define JX9_AMALGAMATION /* Marker for routines not intended for external use */ #define JX9_PRIVATE static #endif /* UNQLITE_AMALGAMATION */ /* * Embedded header file for unqlite: */ /* * ---------------------------------------------------------- * File: unqlite.h * ---------------------------------------------------------- */ /* This file was automatically generated. Do not edit (Except for compile time directives)! */ #ifndef _UNQLITE_H_ #define _UNQLITE_H_ /* * Symisc UnQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2019, Symisc Systems http://unqlite.org/ * Version 1.1.9 * 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://unqlite.org/licensing.html */ /* * Copyright (C) 2012, 2019 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* $SymiscID: unqlite.h v1.3 Win10 2108-04-27 02:35:11 stable $ */ #include /* needed for the definition of va_list */ /* * Compile time engine version, signature, identification in the symisc source tree * and copyright notice. * Each macro have an equivalent C interface associated with it that provide the same * information but are associated with the library instead of the header file. * Refer to [unqlite_lib_version()], [unqlite_lib_signature()], [unqlite_lib_ident()] and * [unqlite_lib_copyright()] for more information. */ /* * The UNQLITE_VERSION C preprocessor macroevaluates to a string literal * that is the unqlite version in the format "X.Y.Z" where X is the major * version number and Y is the minor version number and Z is the release * number. */ #define UNQLITE_VERSION "1.1.9" /* * The UNQLITE_VERSION_NUMBER C preprocessor macro resolves to an integer * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same * numbers used in [UNQLITE_VERSION]. */ #define UNQLITE_VERSION_NUMBER 1001009 /* * The UNQLITE_SIG C preprocessor macro evaluates to a string * literal which is the public signature of the unqlite engine. * This signature could be included for example in a host-application * generated Server MIME header as follows: * Server: YourWebServer/x.x unqlite/x.x.x \r\n */ #define UNQLITE_SIG "unqlite/1.1.9" /* * UnQLite identification in the Symisc source tree: * Each particular check-in of a particular software released * by symisc systems have an unique identifier associated with it. * This macro hold the one associated with unqlite. */ #define UNQLITE_IDENT "unqlite:29c173b1-ac2c-4b49-93ba-e600619e304e" /* * Copyright notice. * If you have any questions about the licensing situation, please * visit http://unqlite.org/licensing.html * or contact Symisc Systems via: * legal@symisc.net * licensing@symisc.net * contact@symisc.net */ #define UNQLITE_COPYRIGHT "Copyright (C) Symisc Systems, S.U.A.R.L [Mrad Chems Eddine ] 2012-2019, http://unqlite.org/" /* Make sure we can call this stuff from C++ */ #ifdef __cplusplus extern "C" { #endif /* Forward declaration to public objects */ typedef struct unqlite_io_methods unqlite_io_methods; typedef struct unqlite_kv_methods unqlite_kv_methods; typedef struct unqlite_kv_engine unqlite_kv_engine; typedef struct jx9_io_stream unqlite_io_stream; typedef struct jx9_context unqlite_context; typedef struct jx9_value unqlite_value; typedef struct unqlite_vfs unqlite_vfs; typedef struct unqlite_vm unqlite_vm; typedef struct unqlite unqlite; /* * ------------------------------ * Compile time directives * ------------------------------ * For most purposes, UnQLite can be built just fine using the default compilation options. * However, if required, the compile-time options documented below can be used to omit UnQLite * features (resulting in a smaller compiled library size) or to change the default values * of some parameters. * Every effort has been made to ensure that the various combinations of compilation options * work harmoniously and produce a working library. * * UNQLITE_ENABLE_THREADS * This option controls whether or not code is included in UnQLite to enable it to operate * safely in a multithreaded environment. The default is not. All mutexing code is omitted * and it is unsafe to use UnQLite in a multithreaded program. When compiled with the * UNQLITE_ENABLE_THREADS directive enabled, UnQLite can be used in a multithreaded program * and it is safe to share the same virtual machine and engine handle between two or more threads. * The value of UNQLITE_ENABLE_THREADS can be determined at run-time using the unqlite_lib_is_threadsafe() * interface. * When UnQLite has been compiled with threading support then the threading mode can be altered * at run-time using the unqlite_lib_config() interface together with one of these verbs: * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI * Platforms others than Windows and UNIX systems must install their own mutex subsystem via * unqlite_lib_config() with a configuration verb set to UNQLITE_LIB_CONFIG_USER_MUTEX. * Otherwise the library is not threadsafe. * Note that you must link UnQLite with the POSIX threads library under UNIX systems (i.e: -lpthread). * * Options To Omit/Enable Features * * The following options can be used to reduce the size of the compiled library by omitting optional * features. This is probably only useful in embedded systems where space is especially tight, as even * with all features included the UnQLite library is relatively small. Don't forget to tell your * compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler * to optimize for size usually has a much larger impact on library footprint than employing * any of these compile-time options. * * JX9_DISABLE_BUILTIN_FUNC * Jx9 is shipped with more than 312 built-in functions suitable for most purposes like * string and INI processing, ZIP extracting, Base64 encoding/decoding, JSON encoding/decoding * and so forth. * If this directive is enabled, then all built-in Jx9 functions are omitted from the build. * Note that special functions such as db_create(), db_store(), db_fetch(), etc. are not omitted * from the build and are not affected by this directive. * * JX9_ENABLE_MATH_FUNC * If this directive is enabled, built-in math functions such as sqrt(), abs(), log(), ceil(), etc. * are included in the build. Note that you may need to link UnQLite with the math library in same * Linux/BSD flavor (i.e: -lm). * * JX9_DISABLE_DISK_IO * If this directive is enabled, built-in VFS functions such as chdir(), mkdir(), chroot(), unlink(), * sleep(), etc. are omitted from the build. * * UNQLITE_ENABLE_JX9_HASH_IO * If this directive is enabled, built-in hash functions such as md5(), sha1(), md5_file(), crc32(), etc. * are included in the build. */ /* Symisc public definitions */ #if !defined(SYMISC_STANDARD_DEFS) #define SYMISC_STANDARD_DEFS #if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE) /* Windows Systems */ #if !defined(__WINNT__) #define __WINNT__ #endif /* * Determine if we are dealing with WindowsCE - which has a much * reduced API. */ #if defined(_WIN32_WCE) #ifndef __WIN_CE__ #define __WIN_CE__ #endif /* __WIN_CE__ */ #endif /* _WIN32_WCE */ #else /* * By default we will assume that we are compiling on a UNIX systems. * Otherwise the OS_OTHER directive must be defined. */ #if !defined(OS_OTHER) #if !defined(__UNIXES__) #define __UNIXES__ #endif /* __UNIXES__ */ #else #endif /* OS_OTHER */ #endif /* __WINNT__/__UNIXES__ */ #if defined(_MSC_VER) || defined(__BORLANDC__) typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ #else typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ #endif /* _MSC_VER */ /* Signature of the consumer routine */ typedef int (*ProcConsumer)(const void *, unsigned int, void *); /* Forward reference */ typedef struct SyMutexMethods SyMutexMethods; typedef struct SyMemMethods SyMemMethods; typedef struct SyString SyString; typedef struct syiovec syiovec; typedef struct SyMutex SyMutex; typedef struct Sytm Sytm; /* Scatter and gather array. */ struct syiovec { #if defined (__WINNT__) /* Same fields type and offset as WSABUF structure defined one winsock2 header */ unsigned long nLen; char *pBase; #else void *pBase; unsigned long nLen; #endif }; struct SyString { const char *zString; /* Raw string (may not be null terminated) */ unsigned int nByte; /* Raw string length */ }; /* Time structure. */ struct Sytm { int tm_sec; /* seconds (0 - 60) */ int tm_min; /* minutes (0 - 59) */ int tm_hour; /* hours (0 - 23) */ int tm_mday; /* day of month (1 - 31) */ int tm_mon; /* month of year (0 - 11) */ int tm_year; /* year + 1900 */ int tm_wday; /* day of week (Sunday = 0) */ int tm_yday; /* day of year (0 - 365) */ int tm_isdst; /* is summer time in effect? */ char *tm_zone; /* abbreviation of timezone name */ long tm_gmtoff; /* offset from UTC in seconds */ }; /* Convert a tm structure (struct tm *) found in to a Sytm structure */ #define STRUCT_TM_TO_SYTM(pTM, pSYTM) \ (pSYTM)->tm_hour = (pTM)->tm_hour;\ (pSYTM)->tm_min = (pTM)->tm_min;\ (pSYTM)->tm_sec = (pTM)->tm_sec;\ (pSYTM)->tm_mon = (pTM)->tm_mon;\ (pSYTM)->tm_mday = (pTM)->tm_mday;\ (pSYTM)->tm_year = (pTM)->tm_year + 1900;\ (pSYTM)->tm_yday = (pTM)->tm_yday;\ (pSYTM)->tm_wday = (pTM)->tm_wday;\ (pSYTM)->tm_isdst = (pTM)->tm_isdst;\ (pSYTM)->tm_gmtoff = 0;\ (pSYTM)->tm_zone = 0; /* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */ #define SYSTEMTIME_TO_SYTM(pSYSTIME, pSYTM) \ (pSYTM)->tm_hour = (pSYSTIME)->wHour;\ (pSYTM)->tm_min = (pSYSTIME)->wMinute;\ (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\ (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\ (pSYTM)->tm_mday = (pSYSTIME)->wDay;\ (pSYTM)->tm_year = (pSYSTIME)->wYear;\ (pSYTM)->tm_yday = 0;\ (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\ (pSYTM)->tm_gmtoff = 0;\ (pSYTM)->tm_isdst = -1;\ (pSYTM)->tm_zone = 0; /* Dynamic memory allocation methods. */ struct SyMemMethods { void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ void * (*xRealloc)(void *, unsigned int); /* [Required:] Re-allocate a memory chunk */ void (*xFree)(void *); /* [Required:] Release a memory chunk */ unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ int (*xInit)(void *); /* [Optional:] Initialization callback */ void (*xRelease)(void *); /* [Optional:] Release callback */ void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */ }; /* Out of memory callback signature. */ typedef int (*ProcMemError)(void *); /* Mutex methods. */ struct SyMutexMethods { int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */ void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */ SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */ void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */ void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */ int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */ void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */ }; #if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec) #define SX_APIIMPORT __declspec(dllimport) #define SX_APIEXPORT __declspec(dllexport) #else #define SX_APIIMPORT #define SX_APIEXPORT #endif /* Standard return values from Symisc public interfaces */ #define SXRET_OK 0 /* Not an error */ #define SXERR_MEM (-1) /* Out of memory */ #define SXERR_IO (-2) /* IO error */ #define SXERR_EMPTY (-3) /* Empty field */ #define SXERR_LOCKED (-4) /* Locked operation */ #define SXERR_ORANGE (-5) /* Out of range value */ #define SXERR_NOTFOUND (-6) /* Item not found */ #define SXERR_LIMIT (-7) /* Limit reached */ #define SXERR_MORE (-8) /* Need more input */ #define SXERR_INVALID (-9) /* Invalid parameter */ #define SXERR_ABORT (-10) /* User callback request an operation abort */ #define SXERR_EXISTS (-11) /* Item exists */ #define SXERR_SYNTAX (-12) /* Syntax error */ #define SXERR_UNKNOWN (-13) /* Unknown error */ #define SXERR_BUSY (-14) /* Busy operation */ #define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */ #define SXERR_WILLBLOCK (-16) /* Operation will block */ #define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */ #define SXERR_EOF (-18) /* End of input */ #define SXERR_PERM (-19) /* Permission error */ #define SXERR_NOOP (-20) /* No-op */ #define SXERR_FORMAT (-21) /* Invalid format */ #define SXERR_NEXT (-22) /* Not an error */ #define SXERR_OS (-23) /* System call return an error */ #define SXERR_CORRUPT (-24) /* Corrupted pointer */ #define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */ #define SXERR_NOMATCH (-26) /* No match */ #define SXERR_RESET (-27) /* Operation reset */ #define SXERR_DONE (-28) /* Not an error */ #define SXERR_SHORT (-29) /* Buffer too short */ #define SXERR_PATH (-30) /* Path error */ #define SXERR_TIMEOUT (-31) /* Timeout */ #define SXERR_BIG (-32) /* Too big for processing */ #define SXERR_RETRY (-33) /* Retry your call */ #define SXERR_IGNORE (-63) /* Ignore */ #endif /* SYMISC_PUBLIC_DEFS */ /* * Marker for exported interfaces. */ #define UNQLITE_APIEXPORT SX_APIEXPORT /* * If compiling for a processor that lacks floating point * support, substitute integer for floating-point. */ #ifdef UNQLITE_OMIT_FLOATING_POINT typedef sxi64 uqlite_real; #else typedef double unqlite_real; #endif typedef sxi64 unqlite_int64; /* Standard UnQLite return values */ #define UNQLITE_OK SXRET_OK /* Successful result */ /* Beginning of error codes */ #define UNQLITE_NOMEM SXERR_MEM /* Out of memory */ #define UNQLITE_ABORT SXERR_ABORT /* Another thread have released this instance */ #define UNQLITE_IOERR SXERR_IO /* IO error */ #define UNQLITE_CORRUPT SXERR_CORRUPT /* Corrupt pointer */ #define UNQLITE_LOCKED SXERR_LOCKED /* Forbidden Operation */ #define UNQLITE_BUSY SXERR_BUSY /* The database file is locked */ #define UNQLITE_DONE SXERR_DONE /* Operation done */ #define UNQLITE_PERM SXERR_PERM /* Permission error */ #define UNQLITE_NOTIMPLEMENTED SXERR_NOTIMPLEMENTED /* Method not implemented by the underlying Key/Value storage engine */ #define UNQLITE_NOTFOUND SXERR_NOTFOUND /* No such record */ #define UNQLITE_NOOP SXERR_NOOP /* No such method */ #define UNQLITE_INVALID SXERR_INVALID /* Invalid parameter */ #define UNQLITE_EOF SXERR_EOF /* End Of Input */ #define UNQLITE_UNKNOWN SXERR_UNKNOWN /* Unknown configuration option */ #define UNQLITE_LIMIT SXERR_LIMIT /* Database limit reached */ #define UNQLITE_EXISTS SXERR_EXISTS /* Record exists */ #define UNQLITE_EMPTY SXERR_EMPTY /* Empty record */ #define UNQLITE_COMPILE_ERR (-70) /* Compilation error */ #define UNQLITE_VM_ERR (-71) /* Virtual machine error */ #define UNQLITE_FULL (-73) /* Full database (unlikely) */ #define UNQLITE_CANTOPEN (-74) /* Unable to open the database file */ #define UNQLITE_READ_ONLY (-75) /* Read only Key/Value storage engine */ #define UNQLITE_LOCKERR (-76) /* Locking protocol error */ /* end-of-error-codes */ /* * Database Handle Configuration Commands. * * The following set of constants are the available configuration verbs that can * be used by the host-application to configure an UnQLite database handle. * These constants must be passed as the second argument to [unqlite_config()]. * * Each options require a variable number of arguments. * The [unqlite_config()] interface will return UNQLITE_OK on success, any other * return value indicates failure. * For a full discussion on the configuration verbs and their expected * parameters, please refer to this page: * http://unqlite.org/c_api/unqlite_config.html */ #define UNQLITE_CONFIG_JX9_ERR_LOG 1 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ #define UNQLITE_CONFIG_MAX_PAGE_CACHE 2 /* ONE ARGUMENT: int nMaxPage */ #define UNQLITE_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ #define UNQLITE_CONFIG_KV_ENGINE 4 /* ONE ARGUMENT: const char *zKvName */ #define UNQLITE_CONFIG_DISABLE_AUTO_COMMIT 5 /* NO ARGUMENTS */ #define UNQLITE_CONFIG_GET_KV_NAME 6 /* ONE ARGUMENT: const char **pzPtr */ /* * UnQLite/Jx9 Virtual Machine Configuration Commands. * * The following set of constants are the available configuration verbs that can * be used by the host-application to configure the Jx9 (Via UnQLite) Virtual machine. * These constants must be passed as the second argument to the [unqlite_vm_config()] * interface. * Each options require a variable number of arguments. * The [unqlite_vm_config()] interface will return UNQLITE_OK on success, any other return * value indicates failure. * There are many options but the most important are: UNQLITE_VM_CONFIG_OUTPUT which install * a VM output consumer callback, UNQLITE_VM_CONFIG_HTTP_REQUEST which parse and register * a HTTP request and UNQLITE_VM_CONFIG_ARGV_ENTRY which populate the $argv array. * For a full discussion on the configuration verbs and their expected parameters, please * refer to this page: * http://unqlite.org/c_api/unqlite_vm_config.html */ #define UNQLITE_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ #define UNQLITE_VM_CONFIG_IMPORT_PATH 2 /* ONE ARGUMENT: const char *zIncludePath */ #define UNQLITE_VM_CONFIG_ERR_REPORT 3 /* NO ARGUMENTS: Report all run-time errors in the VM output */ #define UNQLITE_VM_CONFIG_RECURSION_DEPTH 4 /* ONE ARGUMENT: int nMaxDepth */ #define UNQLITE_VM_OUTPUT_LENGTH 5 /* ONE ARGUMENT: unsigned int *pLength */ #define UNQLITE_VM_CONFIG_CREATE_VAR 6 /* TWO ARGUMENTS: const char *zName, unqlite_value *pValue */ #define UNQLITE_VM_CONFIG_HTTP_REQUEST 7 /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ #define UNQLITE_VM_CONFIG_SERVER_ATTR 8 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ #define UNQLITE_VM_CONFIG_ENV_ATTR 9 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ #define UNQLITE_VM_CONFIG_EXEC_VALUE 10 /* ONE ARGUMENT: unqlite_value **ppValue */ #define UNQLITE_VM_CONFIG_IO_STREAM 11 /* ONE ARGUMENT: const unqlite_io_stream *pStream */ #define UNQLITE_VM_CONFIG_ARGV_ENTRY 12 /* ONE ARGUMENT: const char *zValue */ #define UNQLITE_VM_CONFIG_EXTRACT_OUTPUT 13 /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ /* * Storage engine configuration commands. * * The following set of constants are the available configuration verbs that can * be used by the host-application to configure the underlying storage engine (i.e Hash, B+tree, R+tree). * These constants must be passed as the first argument to [unqlite_kv_config()]. * Each options require a variable number of arguments. * The [unqlite_kv_config()] interface will return UNQLITE_OK on success, any other return * value indicates failure. * For a full discussion on the configuration verbs and their expected parameters, please * refer to this page: * http://unqlite.org/c_api/unqlite_kv_config.html */ #define UNQLITE_KV_CONFIG_HASH_FUNC 1 /* ONE ARGUMENT: unsigned int (*xHash)(const void *,unsigned int) */ #define UNQLITE_KV_CONFIG_CMP_FUNC 2 /* ONE ARGUMENT: int (*xCmp)(const void *,const void *,unsigned int) */ /* * Global Library Configuration Commands. * * The following set of constants are the available configuration verbs that can * be used by the host-application to configure the whole library. * These constants must be passed as the first argument to [unqlite_lib_config()]. * * Each options require a variable number of arguments. * The [unqlite_lib_config()] interface will return UNQLITE_OK on success, any other return * value indicates failure. * Notes: * The default configuration is recommended for most applications and so the call to * [unqlite_lib_config()] is usually not necessary. It is provided to support rare * applications with unusual needs. * The [unqlite_lib_config()] interface is not threadsafe. The application must insure that * no other [unqlite_*()] interfaces are invoked by other threads while [unqlite_lib_config()] * is running. Furthermore, [unqlite_lib_config()] may only be invoked prior to library * initialization using [unqlite_lib_init()] or [unqlite_init()] or after shutdown * by [unqlite_lib_shutdown()]. If [unqlite_lib_config()] is called after [unqlite_lib_init()] * or [unqlite_init()] and before [unqlite_lib_shutdown()] then it will return UNQLITE_LOCKED. * For a full discussion on the configuration verbs and their expected parameters, please * refer to this page: * http://unqlite.org/c_api/unqlite_lib.html */ #define UNQLITE_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ #define UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ #define UNQLITE_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ #define UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ #define UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ #define UNQLITE_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const unqlite_vfs *pVfs */ #define UNQLITE_LIB_CONFIG_STORAGE_ENGINE 7 /* ONE ARGUMENT: unqlite_kv_methods *pStorage */ #define UNQLITE_LIB_CONFIG_PAGE_SIZE 8 /* ONE ARGUMENT: int iPageSize */ /* * These bit values are intended for use in the 3rd parameter to the [unqlite_open()] interface * and in the 4th parameter to the xOpen method of the [unqlite_vfs] object. */ #define UNQLITE_OPEN_READONLY 0x00000001 /* Read only mode. Ok for [unqlite_open] */ #define UNQLITE_OPEN_READWRITE 0x00000002 /* Ok for [unqlite_open] */ #define UNQLITE_OPEN_CREATE 0x00000004 /* Ok for [unqlite_open] */ #define UNQLITE_OPEN_EXCLUSIVE 0x00000008 /* VFS only */ #define UNQLITE_OPEN_TEMP_DB 0x00000010 /* VFS only */ #define UNQLITE_OPEN_NOMUTEX 0x00000020 /* Ok for [unqlite_open] */ #define UNQLITE_OPEN_OMIT_JOURNALING 0x00000040 /* Omit journaling for this database. Ok for [unqlite_open] */ #define UNQLITE_OPEN_IN_MEMORY 0x00000080 /* An in memory database. Ok for [unqlite_open]*/ #define UNQLITE_OPEN_MMAP 0x00000100 /* Obtain a memory view of the whole file. Ok for [unqlite_open] */ /* * Synchronization Type Flags * * When UnQLite invokes the xSync() method of an [unqlite_io_methods] object it uses * a combination of these integer values as the second argument. * * When the UNQLITE_SYNC_DATAONLY flag is used, it means that the sync operation only * needs to flush data to mass storage. Inode information need not be flushed. * If the lower four bits of the flag equal UNQLITE_SYNC_NORMAL, that means to use normal * fsync() semantics. If the lower four bits equal UNQLITE_SYNC_FULL, that means to use * Mac OS X style fullsync instead of fsync(). */ #define UNQLITE_SYNC_NORMAL 0x00002 #define UNQLITE_SYNC_FULL 0x00003 #define UNQLITE_SYNC_DATAONLY 0x00010 /* * File Locking Levels * * UnQLite uses one of these integer values as the second * argument to calls it makes to the xLock() and xUnlock() methods * of an [unqlite_io_methods] object. */ #define UNQLITE_LOCK_NONE 0 #define UNQLITE_LOCK_SHARED 1 #define UNQLITE_LOCK_RESERVED 2 #define UNQLITE_LOCK_PENDING 3 #define UNQLITE_LOCK_EXCLUSIVE 4 /* * CAPIREF: OS Interface: Open File Handle * * An [unqlite_file] object represents an open file in the [unqlite_vfs] OS interface * layer. * Individual OS interface implementations will want to subclass this object by appending * additional fields for their own use. The pMethods entry is a pointer to an * [unqlite_io_methods] object that defines methods for performing * I/O operations on the open file. */ typedef struct unqlite_file unqlite_file; struct unqlite_file { const unqlite_io_methods *pMethods; /* Methods for an open file. MUST BE FIRST */ }; /* * CAPIREF: OS Interface: File Methods Object * * Every file opened by the [unqlite_vfs] xOpen method populates an * [unqlite_file] object (or, more commonly, a subclass of the * [unqlite_file] object) with a pointer to an instance of this object. * This object defines the methods used to perform various operations * against the open file represented by the [unqlite_file] object. * * If the xOpen method sets the unqlite_file.pMethods element * to a non-NULL pointer, then the unqlite_io_methods.xClose method * may be invoked even if the xOpen reported that it failed. The * only way to prevent a call to xClose following a failed xOpen * is for the xOpen to set the unqlite_file.pMethods element to NULL. * * The flags argument to xSync may be one of [UNQLITE_SYNC_NORMAL] or * [UNQLITE_SYNC_FULL]. The first choice is the normal fsync(). * The second choice is a Mac OS X style fullsync. The [UNQLITE_SYNC_DATAONLY] * flag may be ORed in to indicate that only the data of the file * and not its inode needs to be synced. * * The integer values to xLock() and xUnlock() are one of * * UNQLITE_LOCK_NONE * UNQLITE_LOCK_SHARED * UNQLITE_LOCK_RESERVED * UNQLITE_LOCK_PENDING * UNQLITE_LOCK_EXCLUSIVE * * xLock() increases the lock. xUnlock() decreases the lock. * The xCheckReservedLock() method checks whether any database connection, * either in this process or in some other process, is holding a RESERVED, * PENDING, or EXCLUSIVE lock on the file. It returns true if such a lock exists * and false otherwise. * * The xSectorSize() method returns the sector size of the device that underlies * the file. The sector size is the minimum write that can be performed without * disturbing other bytes in the file. * */ struct unqlite_io_methods { int iVersion; /* Structure version number (currently 1) */ int (*xClose)(unqlite_file*); int (*xRead)(unqlite_file*, void*, unqlite_int64 iAmt, unqlite_int64 iOfst); int (*xWrite)(unqlite_file*, const void*, unqlite_int64 iAmt, unqlite_int64 iOfst); int (*xTruncate)(unqlite_file*, unqlite_int64 size); int (*xSync)(unqlite_file*, int flags); int (*xFileSize)(unqlite_file*, unqlite_int64 *pSize); int (*xLock)(unqlite_file*, int); int (*xUnlock)(unqlite_file*, int); int (*xCheckReservedLock)(unqlite_file*, int *pResOut); int (*xSectorSize)(unqlite_file*); }; /* * CAPIREF: OS Interface Object * * An instance of the unqlite_vfs object defines the interface between * the UnQLite core and the underlying operating system. The "vfs" * in the name of the object stands for "Virtual File System". * * Only a single vfs can be registered within the UnQLite core. * Vfs registration is done using the [unqlite_lib_config()] interface * with a configuration verb set to UNQLITE_LIB_CONFIG_VFS. * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users * does not have to worry about registering and installing a vfs since UnQLite * come with a built-in vfs for these platforms that implements most the methods * defined below. * * Clients running on exotic systems (ie: Other than Windows and UNIX systems) * must register their own vfs in order to be able to use the UnQLite library. * * The value of the iVersion field is initially 1 but may be larger in * future versions of UnQLite. * * The szOsFile field is the size of the subclassed [unqlite_file] structure * used by this VFS. mxPathname is the maximum length of a pathname in this VFS. * * At least szOsFile bytes of memory are allocated by UnQLite to hold the [unqlite_file] * structure passed as the third argument to xOpen. The xOpen method does not have to * allocate the structure; it should just fill it in. Note that the xOpen method must * set the unqlite_file.pMethods to either a valid [unqlite_io_methods] object or to NULL. * xOpen must do this even if the open fails. UnQLite expects that the unqlite_file.pMethods * element will be valid after xOpen returns regardless of the success or failure of the * xOpen call. * */ struct unqlite_vfs { const char *zName; /* Name of this virtual file system [i.e: Windows, UNIX, etc.] */ int iVersion; /* Structure version number (currently 1) */ int szOsFile; /* Size of subclassed unqlite_file */ int mxPathname; /* Maximum file pathname length */ int (*xOpen)(unqlite_vfs*, const char *zName, unqlite_file*,unsigned int flags); int (*xDelete)(unqlite_vfs*, const char *zName, int syncDir); int (*xAccess)(unqlite_vfs*, const char *zName, int flags, int *pResOut); int (*xFullPathname)(unqlite_vfs*, const char *zName,int buf_len,char *zBuf); int (*xTmpDir)(unqlite_vfs*,char *zBuf,int buf_len); int (*xSleep)(unqlite_vfs*, int microseconds); int (*xCurrentTime)(unqlite_vfs*,Sytm *pOut); int (*xGetLastError)(unqlite_vfs*, int, char *); }; /* * Flags for the xAccess VFS method * * These integer constants can be used as the third parameter to * the xAccess method of an [unqlite_vfs] object. They determine * what kind of permissions the xAccess method is looking for. * With UNQLITE_ACCESS_EXISTS, the xAccess method * simply checks whether the file exists. * With UNQLITE_ACCESS_READWRITE, the xAccess method * checks whether the named directory is both readable and writable * (in other words, if files can be added, removed, and renamed within * the directory). * The UNQLITE_ACCESS_READWRITE constant is currently used only by the * [temp_store_directory pragma], though this could change in a future * release of UnQLite. * With UNQLITE_ACCESS_READ, the xAccess method * checks whether the file is readable. The UNQLITE_ACCESS_READ constant is * currently unused, though it might be used in a future release of * UnQLite. */ #define UNQLITE_ACCESS_EXISTS 0 #define UNQLITE_ACCESS_READWRITE 1 #define UNQLITE_ACCESS_READ 2 /* * The type used to represent a page number. The first page in a file * is called page 1. 0 is used to represent "not a page". * A page number is an unsigned 64-bit integer. */ typedef sxu64 pgno; /* * A database disk page is represented by an instance * of the follwoing structure. */ typedef struct unqlite_page unqlite_page; struct unqlite_page { unsigned char *zData; /* Content of this page */ void *pUserData; /* Extra content */ pgno pgno; /* Page number for this page */ }; /* * UnQLite handle to the underlying Key/Value Storage Engine (See below). */ typedef void * unqlite_kv_handle; /* * UnQLite pager IO methods. * * An instance of the following structure define the exported methods of the UnQLite pager * to the underlying Key/Value storage engine. */ typedef struct unqlite_kv_io unqlite_kv_io; struct unqlite_kv_io { unqlite_kv_handle pHandle; /* UnQLite handle passed as the first parameter to the * method defined below. */ unqlite_kv_methods *pMethods; /* Underlying storage engine */ /* Pager methods */ int (*xGet)(unqlite_kv_handle,pgno,unqlite_page **); int (*xLookup)(unqlite_kv_handle,pgno,unqlite_page **); int (*xNew)(unqlite_kv_handle,unqlite_page **); int (*xWrite)(unqlite_page *); int (*xDontWrite)(unqlite_page *); int (*xDontJournal)(unqlite_page *); int (*xDontMkHot)(unqlite_page *); int (*xPageRef)(unqlite_page *); int (*xPageUnref)(unqlite_page *); int (*xPageSize)(unqlite_kv_handle); int (*xReadOnly)(unqlite_kv_handle); unsigned char * (*xTmpPage)(unqlite_kv_handle); void (*xSetUnpin)(unqlite_kv_handle,void (*xPageUnpin)(void *)); void (*xSetReload)(unqlite_kv_handle,void (*xPageReload)(void *)); void (*xErr)(unqlite_kv_handle,const char *); }; /* * Key/Value Storage Engine Cursor Object * * An instance of a subclass of the following object defines a cursor * used to scan through a key-value storage engine. */ typedef struct unqlite_kv_cursor unqlite_kv_cursor; struct unqlite_kv_cursor { unqlite_kv_engine *pStore; /* Must be first */ /* Subclasses will typically add additional fields */ }; /* * Possible seek positions. */ #define UNQLITE_CURSOR_MATCH_EXACT 1 #define UNQLITE_CURSOR_MATCH_LE 2 #define UNQLITE_CURSOR_MATCH_GE 3 /* * Key/Value Storage Engine. * * A Key-Value storage engine is defined by an instance of the following * object. * UnQLite works with run-time interchangeable storage engines (i.e. Hash, B+Tree, R+Tree, LSM, etc.). * The storage engine works with key/value pairs where both the key * and the value are byte arrays of arbitrary length and with no restrictions on content. * UnQLite come with two built-in KV storage engine: A Virtual Linear Hash (VLH) storage * engine is used for persistent on-disk databases with O(1) lookup time and an in-memory * hash-table or Red-black tree storage engine is used for in-memory databases. * Future versions of UnQLite might add other built-in storage engines (i.e. LSM). * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. */ struct unqlite_kv_engine { const unqlite_kv_io *pIo; /* IO methods: MUST be first */ /* Subclasses will typically add additional fields */ }; /* * Key/Value Storage Engine Virtual Method Table. * * Key/Value storage engine methods is defined by an instance of the following * object. * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. */ struct unqlite_kv_methods { const char *zName; /* Storage engine name [i.e. Hash, B+tree, LSM, R-tree, Mem, etc.]*/ int szKv; /* 'unqlite_kv_engine' subclass size */ int szCursor; /* 'unqlite_kv_cursor' subclass size */ int iVersion; /* Structure version, currently 1 */ /* Storage engine methods */ int (*xInit)(unqlite_kv_engine *,int iPageSize); void (*xRelease)(unqlite_kv_engine *); int (*xConfig)(unqlite_kv_engine *,int op,va_list ap); int (*xOpen)(unqlite_kv_engine *,pgno); int (*xReplace)( unqlite_kv_engine *, const void *pKey,int nKeyLen, const void *pData,unqlite_int64 nDataLen ); int (*xAppend)( unqlite_kv_engine *, const void *pKey,int nKeyLen, const void *pData,unqlite_int64 nDataLen ); void (*xCursorInit)(unqlite_kv_cursor *); int (*xSeek)(unqlite_kv_cursor *,const void *pKey,int nByte,int iPos); /* Mandatory */ int (*xFirst)(unqlite_kv_cursor *); int (*xLast)(unqlite_kv_cursor *); int (*xValid)(unqlite_kv_cursor *); int (*xNext)(unqlite_kv_cursor *); int (*xPrev)(unqlite_kv_cursor *); int (*xDelete)(unqlite_kv_cursor *); int (*xKeyLength)(unqlite_kv_cursor *,int *); int (*xKey)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); int (*xDataLength)(unqlite_kv_cursor *,unqlite_int64 *); int (*xData)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); void (*xReset)(unqlite_kv_cursor *); void (*xCursorRelease)(unqlite_kv_cursor *); }; /* * UnQLite journal file suffix. */ #ifndef UNQLITE_JOURNAL_FILE_SUFFIX #define UNQLITE_JOURNAL_FILE_SUFFIX "_unqlite_journal" #endif /* * Call Context - Error Message Serverity Level. * * The following constans are the allowed severity level that can * passed as the second argument to the [unqlite_context_throw_error()] or * [unqlite_context_throw_error_format()] interfaces. * Refer to the official documentation for additional information. */ #define UNQLITE_CTX_ERR 1 /* Call context error such as unexpected number of arguments, invalid types and so on. */ #define UNQLITE_CTX_WARNING 2 /* Call context Warning */ #define UNQLITE_CTX_NOTICE 3 /* Call context Notice */ /* * C-API-REF: Please refer to the official documentation for interfaces * purpose and expected parameters. */ /* Database Engine Handle */ UNQLITE_APIEXPORT int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode); UNQLITE_APIEXPORT int unqlite_config(unqlite *pDb,int nOp,...); UNQLITE_APIEXPORT int unqlite_close(unqlite *pDb); /* Key/Value (KV) Store Interfaces */ UNQLITE_APIEXPORT int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); UNQLITE_APIEXPORT int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); UNQLITE_APIEXPORT int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); UNQLITE_APIEXPORT int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); UNQLITE_APIEXPORT int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 /* in|out */*pBufLen); UNQLITE_APIEXPORT int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey, int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); UNQLITE_APIEXPORT int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen); UNQLITE_APIEXPORT int unqlite_kv_config(unqlite *pDb,int iOp,...); /* Document (JSON) Store Interfaces powered by the Jx9 Scripting Language */ UNQLITE_APIEXPORT int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut); UNQLITE_APIEXPORT int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut); UNQLITE_APIEXPORT int unqlite_vm_config(unqlite_vm *pVm,int iOp,...); UNQLITE_APIEXPORT int unqlite_vm_exec(unqlite_vm *pVm); UNQLITE_APIEXPORT int unqlite_vm_reset(unqlite_vm *pVm); UNQLITE_APIEXPORT int unqlite_vm_release(unqlite_vm *pVm); UNQLITE_APIEXPORT int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData); UNQLITE_APIEXPORT unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname); /* Cursor Iterator Interfaces */ UNQLITE_APIEXPORT int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut); UNQLITE_APIEXPORT int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur); UNQLITE_APIEXPORT int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos); UNQLITE_APIEXPORT int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor); UNQLITE_APIEXPORT int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor); UNQLITE_APIEXPORT int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor); UNQLITE_APIEXPORT int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor); UNQLITE_APIEXPORT int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor); UNQLITE_APIEXPORT int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte); UNQLITE_APIEXPORT int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); UNQLITE_APIEXPORT int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnData); UNQLITE_APIEXPORT int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); UNQLITE_APIEXPORT int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor); UNQLITE_APIEXPORT int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor); /* Manual Transaction Manager */ UNQLITE_APIEXPORT int unqlite_begin(unqlite *pDb); UNQLITE_APIEXPORT int unqlite_commit(unqlite *pDb); UNQLITE_APIEXPORT int unqlite_rollback(unqlite *pDb); /* Utility interfaces */ UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize); UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize); UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size); UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb); /* In-process extending interfaces */ UNQLITE_APIEXPORT int unqlite_create_function(unqlite_vm *pVm,const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData); UNQLITE_APIEXPORT int unqlite_delete_function(unqlite_vm *pVm, const char *zName); UNQLITE_APIEXPORT int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData); UNQLITE_APIEXPORT int unqlite_delete_constant(unqlite_vm *pVm, const char *zName); /* On Demand Object allocation interfaces */ UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm); UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm); UNQLITE_APIEXPORT int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue); UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx); UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_array(unqlite_context *pCtx); UNQLITE_APIEXPORT void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue); /* Dynamically Typed Value Object Management Interfaces */ UNQLITE_APIEXPORT int unqlite_value_int(unqlite_value *pVal, int iValue); UNQLITE_APIEXPORT int unqlite_value_int64(unqlite_value *pVal, unqlite_int64 iValue); UNQLITE_APIEXPORT int unqlite_value_bool(unqlite_value *pVal, int iBool); UNQLITE_APIEXPORT int unqlite_value_null(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_double(unqlite_value *pVal, double Value); UNQLITE_APIEXPORT int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen); UNQLITE_APIEXPORT int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...); UNQLITE_APIEXPORT int unqlite_value_reset_string_cursor(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_resource(unqlite_value *pVal, void *pUserData); UNQLITE_APIEXPORT int unqlite_value_release(unqlite_value *pVal); /* Foreign Function Parameter Values */ UNQLITE_APIEXPORT int unqlite_value_to_int(unqlite_value *pValue); UNQLITE_APIEXPORT int unqlite_value_to_bool(unqlite_value *pValue); UNQLITE_APIEXPORT unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue); UNQLITE_APIEXPORT double unqlite_value_to_double(unqlite_value *pValue); UNQLITE_APIEXPORT const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen); UNQLITE_APIEXPORT void * unqlite_value_to_resource(unqlite_value *pValue); UNQLITE_APIEXPORT int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict); /* Setting The Result Of A Foreign Function */ UNQLITE_APIEXPORT int unqlite_result_int(unqlite_context *pCtx, int iValue); UNQLITE_APIEXPORT int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue); UNQLITE_APIEXPORT int unqlite_result_bool(unqlite_context *pCtx, int iBool); UNQLITE_APIEXPORT int unqlite_result_double(unqlite_context *pCtx, double Value); UNQLITE_APIEXPORT int unqlite_result_null(unqlite_context *pCtx); UNQLITE_APIEXPORT int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen); UNQLITE_APIEXPORT int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...); UNQLITE_APIEXPORT int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue); UNQLITE_APIEXPORT int unqlite_result_resource(unqlite_context *pCtx, void *pUserData); /* Dynamically Typed Value Object Query Interfaces */ UNQLITE_APIEXPORT int unqlite_value_is_int(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_float(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_bool(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_string(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_null(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_numeric(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_callable(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_scalar(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_json_array(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_json_object(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_resource(unqlite_value *pVal); UNQLITE_APIEXPORT int unqlite_value_is_empty(unqlite_value *pVal); /* JSON Array/Object Management Interfaces */ UNQLITE_APIEXPORT unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte); UNQLITE_APIEXPORT int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData); UNQLITE_APIEXPORT int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue); UNQLITE_APIEXPORT int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue); UNQLITE_APIEXPORT int unqlite_array_count(unqlite_value *pArray); /* Call Context Handling Interfaces */ UNQLITE_APIEXPORT int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen); UNQLITE_APIEXPORT int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...); UNQLITE_APIEXPORT int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr); UNQLITE_APIEXPORT int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...); UNQLITE_APIEXPORT unsigned int unqlite_context_random_num(unqlite_context *pCtx); UNQLITE_APIEXPORT int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen); UNQLITE_APIEXPORT void * unqlite_context_user_data(unqlite_context *pCtx); UNQLITE_APIEXPORT int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData); UNQLITE_APIEXPORT void * unqlite_context_peek_aux_data(unqlite_context *pCtx); UNQLITE_APIEXPORT unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx); UNQLITE_APIEXPORT const char * unqlite_function_name(unqlite_context *pCtx); /* Call Context Memory Management Interfaces */ UNQLITE_APIEXPORT void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); UNQLITE_APIEXPORT void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte); UNQLITE_APIEXPORT void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk); /* Global Library Management Interfaces */ UNQLITE_APIEXPORT int unqlite_lib_config(int nConfigOp,...); UNQLITE_APIEXPORT int unqlite_lib_init(void); UNQLITE_APIEXPORT int unqlite_lib_shutdown(void); UNQLITE_APIEXPORT int unqlite_lib_is_threadsafe(void); UNQLITE_APIEXPORT const char * unqlite_lib_version(void); UNQLITE_APIEXPORT const char * unqlite_lib_signature(void); UNQLITE_APIEXPORT const char * unqlite_lib_ident(void); UNQLITE_APIEXPORT const char * unqlite_lib_copyright(void); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* _UNQLITE_H_ */ /* * ---------------------------------------------------------- * File: jx9.h * MD5: d23a1e182f596794001533e1d6aa16a0 * ---------------------------------------------------------- */ /* This file was automatically generated. Do not edit (except for compile time directive)! */ #ifndef _JX9H_ #define _JX9H_ /* * 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/ */ /* * Copyright (C) 2012, 2013 Symisc Systems. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Redistributions in any form must be accompanied by information on * how to obtain complete source code for the JX9 engine and any * accompanying software that uses the JX9 engine software. * The source code must either be included in the distribution * or be available for no more than the cost of distribution plus * a nominal fee, and must be freely redistributable under reasonable * conditions. For an executable file, complete source code means * the source code for all modules it contains.It does not include * source code for modules or files that typically accompany the major * components of the operating system on which the executable file runs. * * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* $SymiscID: jx9.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable $ */ #include "unqlite.h" /* * Compile time engine version, signature, identification in the symisc source tree * and copyright notice. * Each macro have an equivalent C interface associated with it that provide the same * information but are associated with the library instead of the header file. * Refer to [jx9_lib_version()], [jx9_lib_signature()], [jx9_lib_ident()] and * [jx9_lib_copyright()] for more information. */ /* * The JX9_VERSION C preprocessor macroevaluates to a string literal * that is the jx9 version in the format "X.Y.Z" where X is the major * version number and Y is the minor version number and Z is the release * number. */ #define JX9_VERSION "1.7.2" /* * The JX9_VERSION_NUMBER C preprocessor macro resolves to an integer * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same * numbers used in [JX9_VERSION]. */ #define JX9_VERSION_NUMBER 1007002 /* * The JX9_SIG C preprocessor macro evaluates to a string * literal which is the public signature of the jx9 engine. * This signature could be included for example in a host-application * generated Server MIME header as follows: * Server: YourWebServer/x.x Jx9/x.x.x \r\n */ #define JX9_SIG "Jx9/1.7.2" /* * JX9 identification in the Symisc source tree: * Each particular check-in of a particular software released * by symisc systems have an unique identifier associated with it. * This macro hold the one associated with jx9. */ #define JX9_IDENT "jx9:d217a6e8c7f10fb35a8becb2793101fd2036aeb7" /* * Copyright notice. * If you have any questions about the licensing situation, please * visit http://jx9.symisc.net/licensing.html * or contact Symisc Systems via: * legal@symisc.net * licensing@symisc.net * contact@symisc.net */ #define JX9_COPYRIGHT "Copyright (C) Symisc Systems 2012-2013, http://jx9.symisc.net/" /* Forward declaration to public objects */ typedef struct jx9_io_stream jx9_io_stream; typedef struct jx9_context jx9_context; typedef struct jx9_value jx9_value; typedef struct jx9_vfs jx9_vfs; typedef struct jx9_vm jx9_vm; typedef struct jx9 jx9; #include "unqlite.h" #if !defined( UNQLITE_ENABLE_JX9_HASH_FUNC ) #define JX9_DISABLE_HASH_FUNC #endif /* UNQLITE_ENABLE_JX9_HASH_FUNC */ #ifdef UNQLITE_ENABLE_THREADS #define JX9_ENABLE_THREADS #endif /* UNQLITE_ENABLE_THREADS */ /* Standard JX9 return values */ #define JX9_OK SXRET_OK /* Successful result */ /* beginning-of-error-codes */ #define JX9_NOMEM UNQLITE_NOMEM /* Out of memory */ #define JX9_ABORT UNQLITE_ABORT /* Foreign Function request operation abort/Another thread have released this instance */ #define JX9_IO_ERR UNQLITE_IOERR /* IO error */ #define JX9_CORRUPT UNQLITE_CORRUPT /* Corrupt pointer/Unknown configuration option */ #define JX9_LOOKED UNQLITE_LOCKED /* Forbidden Operation */ #define JX9_COMPILE_ERR UNQLITE_COMPILE_ERR /* Compilation error */ #define JX9_VM_ERR UNQLITE_VM_ERR /* Virtual machine error */ /* end-of-error-codes */ /* * If compiling for a processor that lacks floating point * support, substitute integer for floating-point. */ #ifdef JX9_OMIT_FLOATING_POINT typedef sxi64 jx9_real; #else typedef double jx9_real; #endif typedef sxi64 jx9_int64; /* * Engine Configuration Commands. * * The following set of constants are the available configuration verbs that can * be used by the host-application to configure the JX9 engine. * These constants must be passed as the second argument to the [jx9_config()] * interface. * Each options require a variable number of arguments. * The [jx9_config()] interface will return JX9_OK on success, any other * return value indicates failure. * For a full discussion on the configuration verbs and their expected * parameters, please refer to this page: * http://jx9.symisc.net/c_api_func.html#jx9_config */ #define JX9_CONFIG_ERR_ABORT 1 /* RESERVED FOR FUTURE USE */ #define JX9_CONFIG_ERR_LOG 2 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ /* * Virtual Machine Configuration Commands. * * The following set of constants are the available configuration verbs that can * be used by the host-application to configure the JX9 Virtual machine. * These constants must be passed as the second argument to the [jx9_vm_config()] * interface. * Each options require a variable number of arguments. * The [jx9_vm_config()] interface will return JX9_OK on success, any other return * value indicates failure. * There are many options but the most importants are: JX9_VM_CONFIG_OUTPUT which install * a VM output consumer callback, JX9_VM_CONFIG_HTTP_REQUEST which parse and register * a HTTP request and JX9_VM_CONFIG_ARGV_ENTRY which populate the $argv array. * For a full discussion on the configuration verbs and their expected parameters, please * refer to this page: * http://jx9.symisc.net/c_api_func.html#jx9_vm_config */ #define JX9_VM_CONFIG_OUTPUT UNQLITE_VM_CONFIG_OUTPUT /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ #define JX9_VM_CONFIG_IMPORT_PATH UNQLITE_VM_CONFIG_IMPORT_PATH /* ONE ARGUMENT: const char *zIncludePath */ #define JX9_VM_CONFIG_ERR_REPORT UNQLITE_VM_CONFIG_ERR_REPORT /* NO ARGUMENTS: Report all run-time errors in the VM output */ #define JX9_VM_CONFIG_RECURSION_DEPTH UNQLITE_VM_CONFIG_RECURSION_DEPTH /* ONE ARGUMENT: int nMaxDepth */ #define JX9_VM_OUTPUT_LENGTH UNQLITE_VM_OUTPUT_LENGTH /* ONE ARGUMENT: unsigned int *pLength */ #define JX9_VM_CONFIG_CREATE_VAR UNQLITE_VM_CONFIG_CREATE_VAR /* TWO ARGUMENTS: const char *zName, jx9_value *pValue */ #define JX9_VM_CONFIG_HTTP_REQUEST UNQLITE_VM_CONFIG_HTTP_REQUEST /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ #define JX9_VM_CONFIG_SERVER_ATTR UNQLITE_VM_CONFIG_SERVER_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ #define JX9_VM_CONFIG_ENV_ATTR UNQLITE_VM_CONFIG_ENV_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ #define JX9_VM_CONFIG_EXEC_VALUE UNQLITE_VM_CONFIG_EXEC_VALUE /* ONE ARGUMENT: jx9_value **ppValue */ #define JX9_VM_CONFIG_IO_STREAM UNQLITE_VM_CONFIG_IO_STREAM /* ONE ARGUMENT: const jx9_io_stream *pStream */ #define JX9_VM_CONFIG_ARGV_ENTRY UNQLITE_VM_CONFIG_ARGV_ENTRY /* ONE ARGUMENT: const char *zValue */ #define JX9_VM_CONFIG_EXTRACT_OUTPUT UNQLITE_VM_CONFIG_EXTRACT_OUTPUT /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ /* * Global Library Configuration Commands. * * The following set of constants are the available configuration verbs that can * be used by the host-application to configure the whole library. * These constants must be passed as the first argument to the [jx9_lib_config()] * interface. * Each options require a variable number of arguments. * The [jx9_lib_config()] interface will return JX9_OK on success, any other return * value indicates failure. * Notes: * The default configuration is recommended for most applications and so the call to * [jx9_lib_config()] is usually not necessary. It is provided to support rare * applications with unusual needs. * The [jx9_lib_config()] interface is not threadsafe. The application must insure that * no other [jx9_*()] interfaces are invoked by other threads while [jx9_lib_config()] * is running. Furthermore, [jx9_lib_config()] may only be invoked prior to library * initialization using [jx9_lib_init()] or [jx9_init()] or after shutdown * by [jx9_lib_shutdown()]. If [jx9_lib_config()] is called after [jx9_lib_init()] * or [jx9_init()] and before [jx9_lib_shutdown()] then it will return jx9LOCKED. * For a full discussion on the configuration verbs and their expected parameters, please * refer to this page: * http://jx9.symisc.net/c_api_func.html#Global_Library_Management_Interfaces */ #define JX9_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ #define JX9_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ #define JX9_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ #define JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ #define JX9_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ #define JX9_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const jx9_vfs *pVfs */ /* * Call Context - Error Message Serverity Level. */ #define JX9_CTX_ERR UNQLITE_CTX_ERR /* Call context error such as unexpected number of arguments, invalid types and so on. */ #define JX9_CTX_WARNING UNQLITE_CTX_WARNING /* Call context Warning */ #define JX9_CTX_NOTICE UNQLITE_CTX_NOTICE /* Call context Notice */ /* Current VFS structure version*/ #define JX9_VFS_VERSION 2 /* * JX9 Virtual File System (VFS). * * An instance of the jx9_vfs object defines the interface between the JX9 core * and the underlying operating system. The "vfs" in the name of the object stands * for "virtual file system". The vfs is used to implement JX9 system functions * such as mkdir(), chdir(), stat(), get_user_name() and many more. * The value of the iVersion field is initially 2 but may be larger in future versions * of JX9. * Additional fields may be appended to this object when the iVersion value is increased. * Only a single vfs can be registered within the JX9 core. Vfs registration is done * using the jx9_lib_config() interface with a configuration verb set to JX9_LIB_CONFIG_VFS. * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users does not have to * worry about registering and installing a vfs since JX9 come with a built-in vfs for these * platforms which implement most the methods defined below. * Host-application running on exotic systems (ie: Other than Windows and UNIX systems) must * register their own vfs in order to be able to use and call JX9 system functions. * Also note that the jx9_compile_file() interface depend on the xMmap() method of the underlying * vfs which mean that this method must be available (Always the case using the built-in VFS) * in order to use this interface. * Developers wishing to implement their own vfs an contact symisc systems to obtain * the JX9 VFS C/C++ Specification manual. */ struct jx9_vfs { const char *zName; /* Underlying VFS name [i.e: FreeBSD/Linux/Windows...] */ int iVersion; /* Current VFS structure version [default 2] */ /* Directory functions */ int (*xChdir)(const char *); /* Change directory */ int (*xChroot)(const char *); /* Change the root directory */ int (*xGetcwd)(jx9_context *); /* Get the current working directory */ int (*xMkdir)(const char *, int, int); /* Make directory */ int (*xRmdir)(const char *); /* Remove directory */ int (*xIsdir)(const char *); /* Tells whether the filename is a directory */ int (*xRename)(const char *, const char *); /* Renames a file or directory */ int (*xRealpath)(const char *, jx9_context *); /* Return canonicalized absolute pathname*/ /* Systems functions */ int (*xSleep)(unsigned int); /* Delay execution in microseconds */ int (*xUnlink)(const char *); /* Deletes a file */ int (*xFileExists)(const char *); /* Checks whether a file or directory exists */ int (*xChmod)(const char *, int); /* Changes file mode */ int (*xChown)(const char *, const char *); /* Changes file owner */ int (*xChgrp)(const char *, const char *); /* Changes file group */ jx9_int64 (*xFreeSpace)(const char *); /* Available space on filesystem or disk partition */ jx9_int64 (*xTotalSpace)(const char *); /* Total space on filesystem or disk partition */ jx9_int64 (*xFileSize)(const char *); /* Gets file size */ jx9_int64 (*xFileAtime)(const char *); /* Gets last access time of file */ jx9_int64 (*xFileMtime)(const char *); /* Gets file modification time */ jx9_int64 (*xFileCtime)(const char *); /* Gets inode change time of file */ int (*xStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */ int (*xlStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */ int (*xIsfile)(const char *); /* Tells whether the filename is a regular file */ int (*xIslink)(const char *); /* Tells whether the filename is a symbolic link */ int (*xReadable)(const char *); /* Tells whether a file exists and is readable */ int (*xWritable)(const char *); /* Tells whether the filename is writable */ int (*xExecutable)(const char *); /* Tells whether the filename is executable */ int (*xFiletype)(const char *, jx9_context *); /* Gets file type [i.e: fifo, dir, file..] */ int (*xGetenv)(const char *, jx9_context *); /* Gets the value of an environment variable */ int (*xSetenv)(const char *, const char *); /* Sets the value of an environment variable */ int (*xTouch)(const char *, jx9_int64, jx9_int64); /* Sets access and modification time of file */ int (*xMmap)(const char *, void **, jx9_int64 *); /* Read-only memory map of the whole file */ void (*xUnmap)(void *, jx9_int64); /* Unmap a memory view */ int (*xLink)(const char *, const char *, int); /* Create hard or symbolic link */ int (*xUmask)(int); /* Change the current umask */ void (*xTempDir)(jx9_context *); /* Get path of the temporary directory */ unsigned int (*xProcessId)(void); /* Get running process ID */ int (*xUid)(void); /* user ID of the process */ int (*xGid)(void); /* group ID of the process */ void (*xUsername)(jx9_context *); /* Running username */ int (*xExec)(const char *, jx9_context *); /* Execute an external program */ }; /* Current JX9 IO stream structure version. */ #define JX9_IO_STREAM_VERSION 1 /* * Possible open mode flags that can be passed to the xOpen() routine * of the underlying IO stream device . * Refer to the JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html) * for additional information. */ #define JX9_IO_OPEN_RDONLY 0x001 /* Read-only open */ #define JX9_IO_OPEN_WRONLY 0x002 /* Write-only open */ #define JX9_IO_OPEN_RDWR 0x004 /* Read-write open. */ #define JX9_IO_OPEN_CREATE 0x008 /* If the file does not exist it will be created */ #define JX9_IO_OPEN_TRUNC 0x010 /* Truncate the file to zero length */ #define JX9_IO_OPEN_APPEND 0x020 /* Append mode.The file offset is positioned at the end of the file */ #define JX9_IO_OPEN_EXCL 0x040 /* Ensure that this call creates the file, the file must not exist before */ #define JX9_IO_OPEN_BINARY 0x080 /* Simple hint: Data is binary */ #define JX9_IO_OPEN_TEMP 0x100 /* Simple hint: Temporary file */ #define JX9_IO_OPEN_TEXT 0x200 /* Simple hint: Data is textual */ /* * JX9 IO Stream Device. * * An instance of the jx9_io_stream object defines the interface between the JX9 core * and the underlying stream device. * A stream is a smart mechanism for generalizing file, network, data compression * and other IO operations which share a common set of functions using an abstracted * unified interface. * A stream device is additional code which tells the stream how to handle specific * protocols/encodings. For example, the http device knows how to translate a URL * into an HTTP/1.1 request for a file on a remote server. * JX9 come with two built-in IO streams device: * The file:// stream which perform very efficient disk IO and the jx9:// stream * which is a special stream that allow access various I/O streams (See the JX9 official * documentation for more information on this stream). * A stream is referenced as: scheme://target * scheme(string) - The name of the wrapper to be used. Examples include: file, http, https, ftp, * ftps, compress.zlib, compress.bz2, and jx9. If no wrapper is specified, the function default * is used (typically file://). * target - Depends on the device used. For filesystem related streams this is typically a path * and filename of the desired file.For network related streams this is typically a hostname, often * with a path appended. * IO stream devices are registered using a call to jx9_vm_config() with a configuration verb * set to JX9_VM_CONFIG_IO_STREAM. * Currently the JX9 development team is working on the implementation of the http:// and ftp:// * IO stream protocols. These devices will be available in the next major release of the JX9 engine. * Developers wishing to implement their own IO stream devices must understand and follow * The JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html). */ struct jx9_io_stream { const char *zName; /* Underlying stream name [i.e: file/http/zip/jx9, ..] */ int iVersion; /* IO stream structure version [default 1]*/ int (*xOpen)(const char *, int, jx9_value *, void **); /* Open handle*/ int (*xOpenDir)(const char *, jx9_value *, void **); /* Open directory handle */ void (*xClose)(void *); /* Close file handle */ void (*xCloseDir)(void *); /* Close directory handle */ jx9_int64 (*xRead)(void *, void *, jx9_int64); /* Read from the open stream */ int (*xReadDir)(void *, jx9_context *); /* Read entry from directory handle */ jx9_int64 (*xWrite)(void *, const void *, jx9_int64); /* Write to the open stream */ int (*xSeek)(void *, jx9_int64, int); /* Seek on the open stream */ int (*xLock)(void *, int); /* Lock/Unlock the open stream */ void (*xRewindDir)(void *); /* Rewind directory handle */ jx9_int64 (*xTell)(void *); /* Current position of the stream read/write pointer */ int (*xTrunc)(void *, jx9_int64); /* Truncates the open stream to a given length */ int (*xSync)(void *); /* Flush open stream data */ int (*xStat)(void *, jx9_value *, jx9_value *); /* Stat an open stream handle */ }; /* * C-API-REF: Please refer to the official documentation for interfaces * purpose and expected parameters. */ /* Engine Handling Interfaces */ JX9_PRIVATE int jx9_init(jx9 **ppEngine); /*JX9_PRIVATE int jx9_config(jx9 *pEngine, int nConfigOp, ...);*/ JX9_PRIVATE int jx9_release(jx9 *pEngine); /* Compile Interfaces */ JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm); JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm); /* Virtual Machine Handling Interfaces */ JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...); /*JX9_PRIVATE int jx9_vm_exec(jx9_vm *pVm, int *pExitStatus);*/ /*JX9_PRIVATE jx9_value * jx9_vm_extract_variable(jx9_vm *pVm,const char *zVarname);*/ /*JX9_PRIVATE int jx9_vm_reset(jx9_vm *pVm);*/ JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm); /*JX9_PRIVATE int jx9_vm_dump_v2(jx9_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData);*/ /* In-process Extending Interfaces */ JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData); /*JX9_PRIVATE int jx9_delete_function(jx9_vm *pVm, const char *zName);*/ JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData); /*JX9_PRIVATE int jx9_delete_constant(jx9_vm *pVm, const char *zName);*/ /* Foreign Function Parameter Values */ JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue); JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue); JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue); JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue); JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen); JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue); JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict); /* Setting The Result Of A Foreign Function */ JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue); JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue); JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool); JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value); JX9_PRIVATE int jx9_result_null(jx9_context *pCtx); JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen); JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...); JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue); JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData); /* Call Context Handling Interfaces */ JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen); /*JX9_PRIVATE int jx9_context_output_format(jx9_context *pCtx, const char *zFormat, ...);*/ JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr); JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...); JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx); JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen); JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx); JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData); JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx); JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx); JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx); JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx); /* Call Context Memory Management Interfaces */ JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease); JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte); JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk); /* On Demand Dynamically Typed Value Object allocation interfaces */ JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm); JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm); JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue); JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx); JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx); JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue); /* Dynamically Typed Value Object Management Interfaces */ JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue); JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue); JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool); JX9_PRIVATE int jx9_value_null(jx9_value *pVal); JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value); JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen); JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...); JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal); JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData); JX9_PRIVATE int jx9_value_release(jx9_value *pVal); /* JSON Array/Object Management Interfaces */ JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte); JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue); JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue); JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray); /* Dynamically Typed Value Object Query Interfaces */ JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal); JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal); /* Global Library Management Interfaces */ /*JX9_PRIVATE int jx9_lib_init(void);*/ JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...); JX9_PRIVATE int jx9_lib_shutdown(void); /*JX9_PRIVATE int jx9_lib_is_threadsafe(void);*/ /*JX9_PRIVATE const char * jx9_lib_version(void);*/ JX9_PRIVATE const char * jx9_lib_signature(void); /*JX9_PRIVATE const char * jx9_lib_ident(void);*/ /*JX9_PRIVATE const char * jx9_lib_copyright(void);*/ #endif /* _JX9H_ */ /* * ---------------------------------------------------------- * File: jx9Int.h * MD5: fb8dffc8ba1425a139091aa145067e16 * ---------------------------------------------------------- */ /* * 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: jx9Int.h v1.9 FreeBSD 2012-08-13 23:25 devel $ */ #ifndef __JX9INT_H__ #define __JX9INT_H__ /* Internal interface definitions for JX9. */ #ifdef JX9_AMALGAMATION #ifndef JX9_PRIVATE /* Marker for routines not intended for external use */ #define JX9_PRIVATE static #endif /* JX9_PRIVATE */ #else #define JX9_PRIVATE #include "jx9.h" #endif #ifndef JX9_PI /* Value of PI */ #define JX9_PI 3.1415926535898 #endif /* * Constants for the largest and smallest possible 64-bit signed integers. * These macros are designed to work correctly on both 32-bit and 64-bit * compilers. */ #ifndef LARGEST_INT64 #define LARGEST_INT64 (0xffffffff|(((sxi64)0x7fffffff)<<32)) #endif #ifndef SMALLEST_INT64 #define SMALLEST_INT64 (((sxi64)-1) - LARGEST_INT64) #endif /* Forward declaration of private structures */ typedef struct jx9_foreach_info jx9_foreach_info; typedef struct jx9_foreach_step jx9_foreach_step; typedef struct jx9_hashmap_node jx9_hashmap_node; typedef struct jx9_hashmap jx9_hashmap; /* Symisc Standard types */ #if !defined(SYMISC_STD_TYPES) #define SYMISC_STD_TYPES #ifdef __WINNT__ /* Disable nuisance warnings on Borland compilers */ #if defined(__BORLANDC__) #pragma warn -rch /* unreachable code */ #pragma warn -ccc /* Condition is always true or false */ #pragma warn -aus /* Assigned value is never used */ #pragma warn -csu /* Comparing signed and unsigned */ #pragma warn -spa /* Suspicious pointer arithmetic */ #endif #endif typedef signed char sxi8; /* signed char */ typedef unsigned char sxu8; /* unsigned char */ typedef signed short int sxi16; /* 16 bits(2 bytes) signed integer */ typedef unsigned short int sxu16; /* 16 bits(2 bytes) unsigned integer */ typedef int sxi32; /* 32 bits(4 bytes) integer */ typedef unsigned int sxu32; /* 32 bits(4 bytes) unsigned integer */ typedef long sxptr; typedef unsigned long sxuptr; typedef long sxlong; typedef unsigned long sxulong; typedef sxi32 sxofft; typedef sxi64 sxofft64; typedef long double sxlongreal; typedef double sxreal; #define SXI8_HIGH 0x7F #define SXU8_HIGH 0xFF #define SXI16_HIGH 0x7FFF #define SXU16_HIGH 0xFFFF #define SXI32_HIGH 0x7FFFFFFF #define SXU32_HIGH 0xFFFFFFFF #define SXI64_HIGH 0x7FFFFFFFFFFFFFFF #define SXU64_HIGH 0xFFFFFFFFFFFFFFFF #if !defined(TRUE) #define TRUE 1 #endif #if !defined(FALSE) #define FALSE 0 #endif /* * The following macros are used to cast pointers to integers and * integers to pointers. */ #if defined(__PTRDIFF_TYPE__) # define SX_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) # define SX_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) #elif !defined(__GNUC__) # define SX_INT_TO_PTR(X) ((void*)&((char*)0)[X]) # define SX_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) #else # define SX_INT_TO_PTR(X) ((void*)(X)) # define SX_PTR_TO_INT(X) ((int)(X)) #endif #define SXMIN(a, b) ((a < b) ? (a) : (b)) #define SXMAX(a, b) ((a < b) ? (b) : (a)) #endif /* SYMISC_STD_TYPES */ /* Symisc Run-time API private definitions */ #if !defined(SYMISC_PRIVATE_DEFS) #define SYMISC_PRIVATE_DEFS typedef sxi32 (*ProcRawStrCmp)(const SyString *, const SyString *); #define SyStringData(RAW) ((RAW)->zString) #define SyStringLength(RAW) ((RAW)->nByte) #define SyStringInitFromBuf(RAW, ZBUF, NLEN){\ (RAW)->zString = (const char *)ZBUF;\ (RAW)->nByte = (sxu32)(NLEN);\ } #define SyStringUpdatePtr(RAW, NBYTES){\ if( NBYTES > (RAW)->nByte ){\ (RAW)->nByte = 0;\ }else{\ (RAW)->zString += NBYTES;\ (RAW)->nByte -= NBYTES;\ }\ } #define SyStringDupPtr(RAW1, RAW2)\ (RAW1)->zString = (RAW2)->zString;\ (RAW1)->nByte = (RAW2)->nByte; #define SyStringTrimLeadingChar(RAW, CHAR)\ while((RAW)->nByte > 0 && (RAW)->zString[0] == CHAR ){\ (RAW)->zString++;\ (RAW)->nByte--;\ } #define SyStringTrimTrailingChar(RAW, CHAR)\ while((RAW)->nByte > 0 && (RAW)->zString[(RAW)->nByte - 1] == CHAR){\ (RAW)->nByte--;\ } #define SyStringCmp(RAW1, RAW2, xCMP)\ (((RAW1)->nByte == (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW1)->nByte - (RAW2)->nByte)) #define SyStringCmp2(RAW1, RAW2, xCMP)\ (((RAW1)->nByte >= (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW2)->nByte - (RAW1)->nByte)) #define SyStringCharCmp(RAW, CHAR) \ (((RAW)->nByte == sizeof(char)) ? ((RAW)->zString[0] == CHAR ? 0 : CHAR - (RAW)->zString[0]) : ((RAW)->zString[0] == CHAR ? 0 : (RAW)->nByte - sizeof(char))) #define SX_ADDR(PTR) ((sxptr)PTR) #define SX_ARRAYSIZE(X) (sizeof(X)/sizeof(X[0])) #define SXUNUSED(P) (P = 0) #define SX_EMPTY(PTR) (PTR == 0) #define SX_EMPTY_STR(STR) (STR == 0 || STR[0] == 0 ) typedef struct SyMemBackend SyMemBackend; typedef struct SyBlob SyBlob; typedef struct SySet SySet; /* Standard function signatures */ typedef sxi32 (*ProcCmp)(const void *, const void *, sxu32); typedef sxi32 (*ProcPatternMatch)(const char *, sxu32, const char *, sxu32, sxu32 *); typedef sxi32 (*ProcSearch)(const void *, sxu32, const void *, sxu32, ProcCmp, sxu32 *); typedef sxu32 (*ProcHash)(const void *, sxu32); typedef sxi32 (*ProcHashSum)(const void *, sxu32, unsigned char *, sxu32); typedef sxi32 (*ProcSort)(void *, sxu32, sxu32, ProcCmp); #define MACRO_LIST_PUSH(Head, Item)\ Item->pNext = Head;\ Head = Item; #define MACRO_LD_PUSH(Head, Item)\ if( Head == 0 ){\ Head = Item;\ }else{\ Item->pNext = Head;\ Head->pPrev = Item;\ Head = Item;\ } #define MACRO_LD_REMOVE(Head, Item)\ if( Head == Item ){\ Head = Head->pNext;\ }\ if( Item->pPrev ){ Item->pPrev->pNext = Item->pNext;}\ if( Item->pNext ){ Item->pNext->pPrev = Item->pPrev;} /* * A generic dynamic set. */ struct SySet { SyMemBackend *pAllocator; /* Memory backend */ void *pBase; /* Base pointer */ sxu32 nUsed; /* Total number of used slots */ sxu32 nSize; /* Total number of available slots */ sxu32 eSize; /* Size of a single slot */ sxu32 nCursor; /* Loop cursor */ void *pUserData; /* User private data associated with this container */ }; #define SySetBasePtr(S) ((S)->pBase) #define SySetBasePtrJump(S, OFFT) (&((char *)(S)->pBase)[OFFT*(S)->eSize]) #define SySetUsed(S) ((S)->nUsed) #define SySetSize(S) ((S)->nSize) #define SySetElemSize(S) ((S)->eSize) #define SySetCursor(S) ((S)->nCursor) #define SySetGetAllocator(S) ((S)->pAllocator) #define SySetSetUserData(S, DATA) ((S)->pUserData = DATA) #define SySetGetUserData(S) ((S)->pUserData) /* * A variable length containers for generic data. */ struct SyBlob { SyMemBackend *pAllocator; /* Memory backend */ void *pBlob; /* Base pointer */ sxu32 nByte; /* Total number of used bytes */ sxu32 mByte; /* Total number of available bytes */ sxu32 nFlags; /* Blob internal flags, see below */ }; #define SXBLOB_LOCKED 0x01 /* Blob is locked [i.e: Cannot auto grow] */ #define SXBLOB_STATIC 0x02 /* Not allocated from heap */ #define SXBLOB_RDONLY 0x04 /* Read-Only data */ #define SyBlobFreeSpace(BLOB) ((BLOB)->mByte - (BLOB)->nByte) #define SyBlobLength(BLOB) ((BLOB)->nByte) #define SyBlobData(BLOB) ((BLOB)->pBlob) #define SyBlobCurData(BLOB) ((void*)(&((char*)(BLOB)->pBlob)[(BLOB)->nByte])) #define SyBlobDataAt(BLOB, OFFT) ((void *)(&((char *)(BLOB)->pBlob)[OFFT])) #define SyBlobGetAllocator(BLOB) ((BLOB)->pAllocator) #define SXMEM_POOL_INCR 3 #define SXMEM_POOL_NBUCKETS 12 #define SXMEM_BACKEND_MAGIC 0xBAC3E67D #define SXMEM_BACKEND_CORRUPT(BACKEND) (BACKEND == 0 || BACKEND->nMagic != SXMEM_BACKEND_MAGIC) #define SXMEM_BACKEND_RETRY 3 /* A memory backend subsystem is defined by an instance of the following structures */ typedef union SyMemHeader SyMemHeader; typedef struct SyMemBlock SyMemBlock; struct SyMemBlock { SyMemBlock *pNext, *pPrev; /* Chain of allocated memory blocks */ #ifdef UNTRUST sxu32 nGuard; /* magic number associated with each valid block, so we * can detect misuse. */ #endif }; /* * Header associated with each valid memory pool block. */ union SyMemHeader { SyMemHeader *pNext; /* Next chunk of size 1 << (nBucket + SXMEM_POOL_INCR) in the list */ sxu32 nBucket; /* Bucket index in aPool[] */ }; struct SyMemBackend { const SyMutexMethods *pMutexMethods; /* Mutex methods */ const SyMemMethods *pMethods; /* Memory allocation methods */ SyMemBlock *pBlocks; /* List of valid memory blocks */ sxu32 nBlock; /* Total number of memory blocks allocated so far */ ProcMemError xMemError; /* Out-of memory callback */ void *pUserData; /* First arg to xMemError() */ SyMutex *pMutex; /* Per instance mutex */ sxu32 nMagic; /* Sanity check against misuse */ SyMemHeader *apPool[SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR]; /* Pool of memory chunks */ }; /* Mutex types */ #define SXMUTEX_TYPE_FAST 1 #define SXMUTEX_TYPE_RECURSIVE 2 #define SXMUTEX_TYPE_STATIC_1 3 #define SXMUTEX_TYPE_STATIC_2 4 #define SXMUTEX_TYPE_STATIC_3 5 #define SXMUTEX_TYPE_STATIC_4 6 #define SXMUTEX_TYPE_STATIC_5 7 #define SXMUTEX_TYPE_STATIC_6 8 #define SyMutexGlobalInit(METHOD){\ if( (METHOD)->xGlobalInit ){\ (METHOD)->xGlobalInit();\ }\ } #define SyMutexGlobalRelease(METHOD){\ if( (METHOD)->xGlobalRelease ){\ (METHOD)->xGlobalRelease();\ }\ } #define SyMutexNew(METHOD, TYPE) (METHOD)->xNew(TYPE) #define SyMutexRelease(METHOD, MUTEX){\ if( MUTEX && (METHOD)->xRelease ){\ (METHOD)->xRelease(MUTEX);\ }\ } #define SyMutexEnter(METHOD, MUTEX){\ if( MUTEX ){\ (METHOD)->xEnter(MUTEX);\ }\ } #define SyMutexTryEnter(METHOD, MUTEX){\ if( MUTEX && (METHOD)->xTryEnter ){\ (METHOD)->xTryEnter(MUTEX);\ }\ } #define SyMutexLeave(METHOD, MUTEX){\ if( MUTEX ){\ (METHOD)->xLeave(MUTEX);\ }\ } /* Comparison, byte swap, byte copy macros */ #define SX_MACRO_FAST_CMP(X1, X2, SIZE, RC){\ register unsigned char *r1 = (unsigned char *)X1;\ register unsigned char *r2 = (unsigned char *)X2;\ register sxu32 LEN = SIZE;\ for(;;){\ if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ }\ RC = !LEN ? 0 : r1[0] - r2[0];\ } #define SX_MACRO_FAST_MEMCPY(SRC, DST, SIZ){\ register unsigned char *xSrc = (unsigned char *)SRC;\ register unsigned char *xDst = (unsigned char *)DST;\ register sxu32 xLen = SIZ;\ for(;;){\ if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ }\ } #define SX_MACRO_BYTE_SWAP(X, Y, Z){\ register unsigned char *s = (unsigned char *)X;\ register unsigned char *d = (unsigned char *)Y;\ sxu32 ZLong = Z; \ sxi32 c; \ for(;;){\ if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ }\ } #define SX_MSEC_PER_SEC (1000) /* Millisec per seconds */ #define SX_USEC_PER_SEC (1000000) /* Microsec per seconds */ #define SX_NSEC_PER_SEC (1000000000) /* Nanosec per seconds */ #endif /* SYMISC_PRIVATE_DEFS */ /* Symisc Run-time API auxiliary definitions */ #if !defined(SYMISC_PRIVATE_AUX_DEFS) #define SYMISC_PRIVATE_AUX_DEFS typedef struct SyHashEntry_Pr SyHashEntry_Pr; typedef struct SyHashEntry SyHashEntry; typedef struct SyHash SyHash; /* * Each public hashtable entry is represented by an instance * of the following structure. */ struct SyHashEntry { const void *pKey; /* Hash key */ sxu32 nKeyLen; /* Key length */ void *pUserData; /* User private data */ }; #define SyHashEntryGetUserData(ENTRY) ((ENTRY)->pUserData) #define SyHashEntryGetKey(ENTRY) ((ENTRY)->pKey) /* Each active hashtable is identified by an instance of the following structure */ struct SyHash { SyMemBackend *pAllocator; /* Memory backend */ ProcHash xHash; /* Hash function */ ProcCmp xCmp; /* Comparison function */ SyHashEntry_Pr *pList, *pCurrent; /* Linked list of hash entries user for linear traversal */ sxu32 nEntry; /* Total number of entries */ SyHashEntry_Pr **apBucket; /* Hash buckets */ sxu32 nBucketSize; /* Current bucket size */ }; #define SXHASH_BUCKET_SIZE 16 /* Initial bucket size: must be a power of two */ #define SXHASH_FILL_FACTOR 3 /* Hash access macro */ #define SyHashFunc(HASH) ((HASH)->xHash) #define SyHashCmpFunc(HASH) ((HASH)->xCmp) #define SyHashTotalEntry(HASH) ((HASH)->nEntry) #define SyHashGetPool(HASH) ((HASH)->pAllocator) /* * An instance of the following structure define a single context * for an Pseudo Random Number Generator. * * Nothing in this file or anywhere else in the library does any kind of * encryption. The RC4 algorithm is being used as a PRNG (pseudo-random * number generator) not as an encryption device. * This implementation is taken from the SQLite3 source tree. */ typedef struct SyPRNGCtx SyPRNGCtx; struct SyPRNGCtx { sxu8 i, j; /* State variables */ unsigned char s[256]; /* State variables */ sxu16 nMagic; /* Sanity check */ }; typedef sxi32 (*ProcRandomSeed)(void *, unsigned int, void *); /* High resolution timer.*/ typedef struct sytime sytime; struct sytime { long tm_sec; /* seconds */ long tm_usec; /* microseconds */ }; /* Forward declaration */ typedef struct SyStream SyStream; typedef struct SyToken SyToken; typedef struct SyLex SyLex; /* * Tokenizer callback signature. */ typedef sxi32 (*ProcTokenizer)(SyStream *, SyToken *, void *, void *); /* * Each token in the input is represented by an instance * of the following structure. */ struct SyToken { SyString sData; /* Token text and length */ sxu32 nType; /* Token type */ sxu32 nLine; /* Token line number */ void *pUserData; /* User private data associated with this token */ }; /* * During tokenization, information about the state of the input * stream is held in an instance of the following structure. */ struct SyStream { const unsigned char *zInput; /* Complete text of the input */ const unsigned char *zText; /* Current input we are processing */ const unsigned char *zEnd; /* End of input marker */ sxu32 nLine; /* Total number of processed lines */ sxu32 nIgn; /* Total number of ignored tokens */ SySet *pSet; /* Token containers */ }; /* * Each lexer is represented by an instance of the following structure. */ struct SyLex { SyStream sStream; /* Input stream */ ProcTokenizer xTokenizer; /* Tokenizer callback */ void * pUserData; /* Third argument to xTokenizer() */ SySet *pTokenSet; /* Token set */ }; #define SyLexTotalToken(LEX) SySetTotalEntry(&(LEX)->aTokenSet) #define SyLexTotalLines(LEX) ((LEX)->sStream.nLine) #define SyLexTotalIgnored(LEX) ((LEX)->sStream.nIgn) #define XLEX_IN_LEN(STREAM) (sxu32)(STREAM->zEnd - STREAM->zText) #endif /* SYMISC_PRIVATE_AUX_DEFS */ /* ** 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 ** */ /* ** Assuming zIn points to the first byte of a UTF-8 character, ** advance zIn to point to the first byte of the next UTF-8 character. */ #define SX_JMP_UTF8(zIn, zEnd)\ while(zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ zIn++; } #define SX_WRITE_UTF8(zOut, c) { \ if( c<0x00080 ){ \ *zOut++ = (sxu8)(c&0xFF); \ }else if( c<0x00800 ){ \ *zOut++ = 0xC0 + (sxu8)((c>>6)&0x1F); \ *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ }else if( c<0x10000 ){ \ *zOut++ = 0xE0 + (sxu8)((c>>12)&0x0F); \ *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ }else{ \ *zOut++ = 0xF0 + (sxu8)((c>>18) & 0x07); \ *zOut++ = 0x80 + (sxu8)((c>>12) & 0x3F); \ *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ } \ } /* Rely on the standard ctype */ #include #define SyToUpper(c) toupper(c) #define SyToLower(c) tolower(c) #define SyisUpper(c) isupper(c) #define SyisLower(c) islower(c) #define SyisSpace(c) isspace(c) #define SyisBlank(c) isspace(c) #define SyisAlpha(c) isalpha(c) #define SyisDigit(c) isdigit(c) #define SyisHex(c) isxdigit(c) #define SyisPrint(c) isprint(c) #define SyisPunct(c) ispunct(c) #define SyisSpec(c) iscntrl(c) #define SyisCtrl(c) iscntrl(c) #define SyisAscii(c) isascii(c) #define SyisAlphaNum(c) isalnum(c) #define SyisGraph(c) isgraph(c) #define SyDigToHex(c) "0123456789ABCDEF"[c & 0x0F] #define SyDigToInt(c) ((c < 0xc0 && SyisDigit(c))? (c - '0') : 0 ) #define SyCharToUpper(c) ((c < 0xc0 && SyisLower(c))? SyToUpper(c) : c) #define SyCharToLower(c) ((c < 0xc0 && SyisUpper(c))? SyToLower(c) : c) /* Remove white space/NUL byte from a raw string */ #define SyStringLeftTrim(RAW)\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ (RAW)->nByte--;\ (RAW)->zString++;\ } #define SyStringLeftTrimSafe(RAW)\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && ((RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ (RAW)->nByte--;\ (RAW)->zString++;\ } #define SyStringRightTrim(RAW)\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ (RAW)->nByte--;\ } #define SyStringRightTrimSafe(RAW)\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ (( RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ (RAW)->nByte--;\ } #define SyStringFullTrim(RAW)\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ (RAW)->nByte--;\ (RAW)->zString++;\ }\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ (RAW)->nByte--;\ } #define SyStringFullTrimSafe(RAW)\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && \ ( (RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ (RAW)->nByte--;\ (RAW)->zString++;\ }\ while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ ( (RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ (RAW)->nByte--;\ } #ifndef JX9_DISABLE_BUILTIN_FUNC /* * An XML raw text, CDATA, tag name and son is parsed out and stored * in an instance of the following structure. */ typedef struct SyXMLRawStr SyXMLRawStr; struct SyXMLRawStr { const char *zString; /* Raw text [UTF-8 ENCODED EXCEPT CDATA] [NOT NULL TERMINATED] */ sxu32 nByte; /* Text length */ sxu32 nLine; /* Line number this text occurs */ }; /* * Event callback signatures. */ typedef sxi32 (*ProcXMLStartTagHandler)(SyXMLRawStr *, SyXMLRawStr *, sxu32, SyXMLRawStr *, void *); typedef sxi32 (*ProcXMLTextHandler)(SyXMLRawStr *, void *); typedef sxi32 (*ProcXMLEndTagHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); typedef sxi32 (*ProcXMLPIHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); typedef sxi32 (*ProcXMLDoctypeHandler)(SyXMLRawStr *, void *); typedef sxi32 (*ProcXMLSyntaxErrorHandler)(const char *, int, SyToken *, void *); typedef sxi32 (*ProcXMLStartDocument)(void *); typedef sxi32 (*ProcXMLNameSpaceStart)(SyXMLRawStr *, SyXMLRawStr *, void *); typedef sxi32 (*ProcXMLNameSpaceEnd)(SyXMLRawStr *, void *); typedef sxi32 (*ProcXMLEndDocument)(void *); /* XML processing control flags */ #define SXML_ENABLE_NAMESPACE 0x01 /* Parse XML with namespace support enbaled */ #define SXML_ENABLE_QUERY 0x02 /* Not used */ #define SXML_OPTION_CASE_FOLDING 0x04 /* Controls whether case-folding is enabled for this XML parser */ #define SXML_OPTION_SKIP_TAGSTART 0x08 /* Specify how many characters should be skipped in the beginning of a tag name.*/ #define SXML_OPTION_SKIP_WHITE 0x10 /* Whether to skip values consisting of whitespace characters. */ #define SXML_OPTION_TARGET_ENCODING 0x20 /* Default encoding: UTF-8 */ /* XML error codes */ enum xml_err_code{ SXML_ERROR_NONE = 1, SXML_ERROR_NO_MEMORY, SXML_ERROR_SYNTAX, SXML_ERROR_NO_ELEMENTS, SXML_ERROR_INVALID_TOKEN, SXML_ERROR_UNCLOSED_TOKEN, SXML_ERROR_PARTIAL_CHAR, SXML_ERROR_TAG_MISMATCH, SXML_ERROR_DUPLICATE_ATTRIBUTE, SXML_ERROR_JUNK_AFTER_DOC_ELEMENT, SXML_ERROR_PARAM_ENTITY_REF, SXML_ERROR_UNDEFINED_ENTITY, SXML_ERROR_RECURSIVE_ENTITY_REF, SXML_ERROR_ASYNC_ENTITY, SXML_ERROR_BAD_CHAR_REF, SXML_ERROR_BINARY_ENTITY_REF, SXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF, SXML_ERROR_MISPLACED_XML_PI, SXML_ERROR_UNKNOWN_ENCODING, SXML_ERROR_INCORRECT_ENCODING, SXML_ERROR_UNCLOSED_CDATA_SECTION, SXML_ERROR_EXTERNAL_ENTITY_HANDLING }; /* Each active XML SAX parser is represented by an instance * of the following structure. */ typedef struct SyXMLParser SyXMLParser; struct SyXMLParser { SyMemBackend *pAllocator; /* Memory backend */ void *pUserData; /* User private data forwarded varbatim by the XML parser * as the last argument to the users callbacks. */ SyHash hns; /* Namespace hashtable */ SySet sToken; /* XML tokens */ SyLex sLex; /* Lexical analyzer */ sxi32 nFlags; /* Control flags */ /* User callbacks */ ProcXMLStartTagHandler xStartTag; /* Start element handler */ ProcXMLEndTagHandler xEndTag; /* End element handler */ ProcXMLTextHandler xRaw; /* Raw text/CDATA handler */ ProcXMLDoctypeHandler xDoctype; /* DOCTYPE handler */ ProcXMLPIHandler xPi; /* Processing instruction (PI) handler*/ ProcXMLSyntaxErrorHandler xError; /* Error handler */ ProcXMLStartDocument xStartDoc; /* StartDoc handler */ ProcXMLEndDocument xEndDoc; /* EndDoc handler */ ProcXMLNameSpaceStart xNameSpace; /* Namespace declaration handler */ ProcXMLNameSpaceEnd xNameSpaceEnd; /* End namespace declaration handler */ }; /* * -------------- * Archive extractor: * -------------- * Each open ZIP/TAR archive is identified by an instance of the following structure. * That is, a process can open one or more archives and manipulates them in thread safe * way by simply working with pointers to the following structure. * Each entry in the archive is remembered in a hashtable. * Lookup is very fast and entry with the same name are chained together. */ typedef struct SyArchiveEntry SyArchiveEntry; typedef struct SyArchive SyArchive; struct SyArchive { SyMemBackend *pAllocator; /* Memory backend */ SyArchiveEntry *pCursor; /* Cursor for linear traversal of archive entries */ SyArchiveEntry *pList; /* Pointer to the List of the loaded archive */ SyArchiveEntry **apHash; /* Hashtable for archive entry */ ProcRawStrCmp xCmp; /* Hash comparison function */ ProcHash xHash; /* Hash Function */ sxu32 nSize; /* Hashtable size */ sxu32 nEntry; /* Total number of entries in the zip/tar archive */ sxu32 nLoaded; /* Total number of entries loaded in memory */ sxu32 nCentralOfft; /* Central directory offset(ZIP only. Otherwise Zero) */ sxu32 nCentralSize; /* Central directory size(ZIP only. Otherwise Zero) */ void *pUserData; /* Upper layer private data */ sxu32 nMagic; /* Sanity check */ }; #define SXARCH_MAGIC 0xDEAD635A #define SXARCH_INVALID(ARCH) (ARCH == 0 || ARCH->nMagic != SXARCH_MAGIC) #define SXARCH_ENTRY_INVALID(ENTRY) (ENTRY == 0 || ENTRY->nMagic != SXARCH_MAGIC) #define SyArchiveHashFunc(ARCH) (ARCH)->xHash #define SyArchiveCmpFunc(ARCH) (ARCH)->xCmp #define SyArchiveUserData(ARCH) (ARCH)->pUserData #define SyArchiveSetUserData(ARCH, DATA) (ARCH)->pUserData = DATA /* * Each loaded archive record is identified by an instance * of the following structure. */ struct SyArchiveEntry { sxu32 nByte; /* Contents size before compression */ sxu32 nByteCompr; /* Contents size after compression */ sxu32 nReadCount; /* Read counter */ sxu32 nCrc; /* Contents CRC32 */ Sytm sFmt; /* Last-modification time */ sxu32 nOfft; /* Data offset. */ sxu16 nComprMeth; /* Compression method 0 == stored/8 == deflated and so on (see appnote.txt)*/ sxu16 nExtra; /* Extra size if any */ SyString sFileName; /* entry name & length */ sxu32 nDup; /* Total number of entries with the same name */ SyArchiveEntry *pNextHash, *pPrevHash; /* Hash collision chains */ SyArchiveEntry *pNextName; /* Next entry with the same name */ SyArchiveEntry *pNext, *pPrev; /* Next and previous entry in the list */ sxu32 nHash; /* Hash of the entry name */ void *pUserData; /* User data */ sxu32 nMagic; /* Sanity check */ }; /* * Extra flags for extending the file local header */ #define SXZIP_EXTRA_TIMESTAMP 0x001 /* Extended UNIX timestamp */ #endif /* JX9_DISABLE_BUILTIN_FUNC */ #ifndef JX9_DISABLE_HASH_FUNC /* MD5 context */ typedef struct MD5Context MD5Context; struct MD5Context { sxu32 buf[4]; sxu32 bits[2]; unsigned char in[64]; }; /* SHA1 context */ typedef struct SHA1Context SHA1Context; struct SHA1Context { unsigned int state[5]; unsigned int count[2]; unsigned char buffer[64]; }; #endif /* JX9_DISABLE_HASH_FUNC */ /* JX9 private declaration */ /* * Memory Objects. * Internally, the JX9 virtual machine manipulates nearly all JX9 values * [i.e: string, int, float, resource, object, bool, null] as jx9_values structures. * Each jx9_values struct may cache multiple representations (string, integer etc.) * of the same value. */ struct jx9_value { union{ jx9_real rVal; /* Real value */ sxi64 iVal; /* Integer value */ void *pOther; /* Other values (Object, Array, Resource, Namespace, etc.) */ }x; sxi32 iFlags; /* Control flags (see below) */ jx9_vm *pVm; /* VM this instance belong */ SyBlob sBlob; /* String values */ sxu32 nIdx; /* Object index in the global pool */ }; /* Allowed value types. */ #define MEMOBJ_STRING 0x001 /* Memory value is a UTF-8 string */ #define MEMOBJ_INT 0x002 /* Memory value is an integer */ #define MEMOBJ_REAL 0x004 /* Memory value is a real number */ #define MEMOBJ_BOOL 0x008 /* Memory value is a boolean */ #define MEMOBJ_NULL 0x020 /* Memory value is NULL */ #define MEMOBJ_HASHMAP 0x040 /* Memory value is a hashmap (JSON representation of Array and Objects) */ #define MEMOBJ_RES 0x100 /* Memory value is a resource [User private data] */ /* Mask of all known types */ #define MEMOBJ_ALL (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES) /* Scalar variables * According to the JX9 language reference manual * Scalar variables are those containing an integer, float, string or boolean. * Types array, object and resource are not scalar. */ #define MEMOBJ_SCALAR (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) /* * The following macro clear the current jx9_value type and replace * it with the given one. */ #define MemObjSetType(OBJ, TYPE) ((OBJ)->iFlags = ((OBJ)->iFlags&~MEMOBJ_ALL)|TYPE) /* jx9_value cast method signature */ typedef sxi32 (*ProcMemObjCast)(jx9_value *); /* Forward reference */ typedef struct jx9_output_consumer jx9_output_consumer; typedef struct jx9_user_func jx9_user_func; typedef struct jx9_conf jx9_conf; /* * An instance of the following structure store the default VM output * consumer and it's private data. * Client-programs can register their own output consumer callback * via the [JX9_VM_CONFIG_OUTPUT] configuration directive. * Please refer to the official documentation for more information * on how to register an output consumer callback. */ struct jx9_output_consumer { ProcConsumer xConsumer; /* VM output consumer routine */ void *pUserData; /* Third argument to xConsumer() */ ProcConsumer xDef; /* Default output consumer routine */ void *pDefData; /* Third argument to xDef() */ }; /* * JX9 engine [i.e: jx9 instance] configuration is stored in * an instance of the following structure. * Please refer to the official documentation for more information * on how to configure your jx9 engine instance. */ struct jx9_conf { ProcConsumer xErr; /* Compile-time error consumer callback */ void *pErrData; /* Third argument to xErr() */ SyBlob sErrConsumer; /* Default error consumer */ }; /* * Signature of the C function responsible of expanding constant values. */ typedef void (*ProcConstant)(jx9_value *, void *); /* * Each registered constant [i.e: __TIME__, __DATE__, JX9_OS, INT_MAX, etc.] is stored * in an instance of the following structure. * Please refer to the official documentation for more information * on how to create/install foreign constants. */ typedef struct jx9_constant jx9_constant; struct jx9_constant { SyString sName; /* Constant name */ ProcConstant xExpand; /* Function responsible of expanding constant value */ void *pUserData; /* Last argument to xExpand() */ }; typedef struct jx9_aux_data jx9_aux_data; /* * Auxiliary data associated with each foreign function is stored * in a stack of the following structure. * Note that automatic tracked chunks are also stored in an instance * of this structure. */ struct jx9_aux_data { void *pAuxData; /* Aux data */ }; /* Foreign functions signature */ typedef int (*ProcHostFunction)(jx9_context *, int, jx9_value **); /* * Each installed foreign function is recored in an instance of the following * structure. * Please refer to the official documentation for more information on how * to create/install foreign functions. */ struct jx9_user_func { jx9_vm *pVm; /* VM that own this instance */ SyString sName; /* Foreign function name */ ProcHostFunction xFunc; /* Implementation of the foreign function */ void *pUserData; /* User private data [Refer to the official documentation for more information]*/ SySet aAux; /* Stack of auxiliary data [Refer to the official documentation for more information]*/ }; /* * The 'context' argument for an installable function. A pointer to an * instance of this structure is the first argument to the routines used * implement the foreign functions. */ struct jx9_context { jx9_user_func *pFunc; /* Function information. */ jx9_value *pRet; /* Return value is stored here. */ SySet sVar; /* Container of dynamically allocated jx9_values * [i.e: Garbage collection purposes.] */ SySet sChunk; /* Track dynamically allocated chunks [jx9_aux_data instance]. * [i.e: Garbage collection purposes.] */ jx9_vm *pVm; /* Virtual machine that own this context */ sxi32 iFlags; /* Call flags */ }; /* Hashmap control flags */ #define HASHMAP_JSON_OBJECT 0x001 /* Hashmap represent JSON Object*/ /* * Each hashmap entry [i.e: array(4, 5, 6)] is recorded in an instance * of the following structure. */ struct jx9_hashmap_node { jx9_hashmap *pMap; /* Hashmap that own this instance */ sxi32 iType; /* Node type */ union{ sxi64 iKey; /* Int key */ SyBlob sKey; /* Blob key */ }xKey; sxi32 iFlags; /* Control flags */ sxu32 nHash; /* Key hash value */ sxu32 nValIdx; /* Value stored in this node */ jx9_hashmap_node *pNext, *pPrev; /* Link to other entries [i.e: linear traversal] */ jx9_hashmap_node *pNextCollide, *pPrevCollide; /* Collision chain */ }; /* * Each active hashmap aka array in the JX9 jargon is represented * by an instance of the following structure. */ struct jx9_hashmap { jx9_vm *pVm; /* VM that own this instance */ jx9_hashmap_node **apBucket; /* Hash bucket */ jx9_hashmap_node *pFirst; /* First inserted entry */ jx9_hashmap_node *pLast; /* Last inserted entry */ jx9_hashmap_node *pCur; /* Current entry */ sxu32 nSize; /* Bucket size */ sxu32 nEntry; /* Total number of inserted entries */ sxu32 (*xIntHash)(sxi64); /* Hash function for int_keys */ sxu32 (*xBlobHash)(const void *, sxu32); /* Hash function for blob_keys */ sxi32 iFlags; /* Hashmap control flags */ sxi64 iNextIdx; /* Next available automatically assigned index */ sxi32 iRef; /* Reference count */ }; /* An instance of the following structure is the context * for the FOREACH_STEP/FOREACH_INIT VM instructions. * Those instructions are used to implement the 'foreach' * statement. * This structure is made available to these instructions * as the P3 operand. */ struct jx9_foreach_info { SyString sKey; /* Key name. Empty otherwise*/ SyString sValue; /* Value name */ sxi32 iFlags; /* Control flags */ SySet aStep; /* Stack of steps [i.e: jx9_foreach_step instance] */ }; struct jx9_foreach_step { sxi32 iFlags; /* Control flags (see below) */ /* Iterate on this map*/ jx9_hashmap *pMap; /* Hashmap [i.e: array in the JX9 jargon] iteration * Ex: foreach(array(1, 2, 3) as $key=>$value){} */ }; /* Foreach step control flags */ #define JX9_4EACH_STEP_KEY 0x001 /* Make Key available */ /* * Each JX9 engine is identified by an instance of the following structure. * Please refer to the official documentation for more information * on how to configure your JX9 engine instance. */ struct jx9 { SyMemBackend sAllocator; /* Low level memory allocation subsystem */ const jx9_vfs *pVfs; /* Underlying Virtual File System */ jx9_conf xConf; /* Configuration */ #if defined(JX9_ENABLE_THREADS) SyMutex *pMutex; /* Per-engine mutex */ #endif jx9_vm *pVms; /* List of active VM */ sxi32 iVm; /* Total number of active VM */ jx9 *pNext, *pPrev; /* List of active engines */ sxu32 nMagic; /* Sanity check against misuse */ }; /* Code generation data structures */ typedef sxi32 (*ProcErrorGen)(void *, sxi32, sxu32, const char *, ...); typedef struct jx9_expr_node jx9_expr_node; typedef struct jx9_expr_op jx9_expr_op; typedef struct jx9_gen_state jx9_gen_state; typedef struct GenBlock GenBlock; typedef sxi32 (*ProcLangConstruct)(jx9_gen_state *); typedef sxi32 (*ProcNodeConstruct)(jx9_gen_state *, sxi32); /* * Each supported operator [i.e: +, -, ==, *, %, >>, >=, new, etc.] is represented * by an instance of the following structure. * The JX9 parser does not use any external tools and is 100% handcoded. * That is, the JX9 parser is thread-safe , full reentrant, produce consistant * compile-time errrors and at least 7 times faster than the standard JX9 parser. */ struct jx9_expr_op { SyString sOp; /* String representation of the operator [i.e: "+", "*", "=="...] */ sxi32 iOp; /* Operator ID */ sxi32 iPrec; /* Operator precedence: 1 == Highest */ sxi32 iAssoc; /* Operator associativity (either left, right or non-associative) */ sxi32 iVmOp; /* VM OP code for this operator [i.e: JX9_OP_EQ, JX9_OP_LT, JX9_OP_MUL...]*/ }; /* * Each expression node is parsed out and recorded * in an instance of the following structure. */ struct jx9_expr_node { const jx9_expr_op *pOp; /* Operator ID or NULL if literal, constant, variable, function or object method call */ jx9_expr_node *pLeft; /* Left expression tree */ jx9_expr_node *pRight; /* Right expression tree */ SyToken *pStart; /* Stream of tokens that belong to this node */ SyToken *pEnd; /* End of token stream */ sxi32 iFlags; /* Node construct flags */ ProcNodeConstruct xCode; /* C routine responsible of compiling this node */ SySet aNodeArgs; /* Node arguments. Only used by postfix operators [i.e: function call]*/ jx9_expr_node *pCond; /* Condition: Only used by the ternary operator '?:' */ }; /* Node Construct flags */ #define EXPR_NODE_PRE_INCR 0x01 /* Pre-icrement/decrement [i.e: ++$i, --$j] node */ /* * A block of instructions is recorded in an instance of the following structure. * This structure is used only during compile-time and have no meaning * during bytecode execution. */ struct GenBlock { jx9_gen_state *pGen; /* State of the code generator */ GenBlock *pParent; /* Upper block or NULL if global */ sxu32 nFirstInstr; /* First instruction to execute */ sxi32 iFlags; /* Block control flags (see below) */ SySet aJumpFix; /* Jump fixup (JumpFixup instance) */ void *pUserData; /* Upper layer private data */ /* The following two fields are used only when compiling * the 'do..while()' language construct. */ sxu8 bPostContinue; /* TRUE when compiling the do..while() statement */ SySet aPostContFix; /* Post-continue jump fix */ }; /* * Code generator state is remembered in an instance of the following * structure. We put the information in this structure and pass around * a pointer to this structure, rather than pass around all of the * information separately. This helps reduce the number of arguments * to generator functions. * This structure is used only during compile-time and have no meaning * during bytecode execution. */ struct jx9_gen_state { jx9_vm *pVm; /* VM that own this instance */ SyHash hLiteral; /* Constant string Literals table */ SyHash hNumLiteral; /* Numeric literals table */ SyHash hVar; /* Collected variable hashtable */ GenBlock *pCurrent; /* Current processed block */ GenBlock sGlobal; /* Global block */ ProcConsumer xErr; /* Error consumer callback */ void *pErrData; /* Third argument to xErr() */ SyToken *pIn; /* Current processed token */ SyToken *pEnd; /* Last token in the stream */ sxu32 nErr; /* Total number of compilation error */ }; /* Forward references */ typedef struct jx9_vm_func_static_var jx9_vm_func_static_var; typedef struct jx9_vm_func_arg jx9_vm_func_arg; typedef struct jx9_vm_func jx9_vm_func; typedef struct VmFrame VmFrame; /* * Each collected function argument is recorded in an instance * of the following structure. * Note that as an extension, JX9 implements full type hinting * which mean that any function can have it's own signature. * Example: * function foo(int $a, string $b, float $c, ClassInstance $d){} * This is how the powerful function overloading mechanism is * implemented. * Note that as an extension, JX9 allow function arguments to have * any complex default value associated with them unlike the standard * JX9 engine. * Example: * function foo(int $a = rand() & 1023){} * now, when foo is called without arguments [i.e: foo()] the * $a variable (first parameter) will be set to a random number * between 0 and 1023 inclusive. * Refer to the official documentation for more information on this * mechanism and other extension introduced by the JX9 engine. */ struct jx9_vm_func_arg { SyString sName; /* Argument name */ SySet aByteCode; /* Compiled default value associated with this argument */ sxu32 nType; /* Type of this argument [i.e: array, int, string, float, object, etc.] */ sxi32 iFlags; /* Configuration flags */ }; /* * Each static variable is parsed out and remembered in an instance * of the following structure. * Note that as an extension, JX9 allow static variable have * any complex default value associated with them unlike the standard * JX9 engine. * Example: * static $rand_str = 'JX9'.rand_str(3); // Concatenate 'JX9' with * // a random three characters(English alphabet) * dump($rand_str); * //You should see something like this * string(6 'JX9awt'); */ struct jx9_vm_func_static_var { SyString sName; /* Static variable name */ SySet aByteCode; /* Compiled initialization expression */ sxu32 nIdx; /* Object index in the global memory object container */ }; /* Function configuration flags */ #define VM_FUNC_ARG_HAS_DEF 0x001 /* Argument has default value associated with it */ #define VM_FUNC_ARG_IGNORE 0x002 /* Do not install argument in the current frame */ /* * Each user defined function is parsed out and stored in an instance * of the following structure. * JX9 introduced some powerfull extensions to the JX9 5 programming * language like function overloading, type hinting, complex default * arguments values and many more. * Please refer to the official documentation for more information. */ struct jx9_vm_func { SySet aArgs; /* Expected arguments (jx9_vm_func_arg instance) */ SySet aStatic; /* Static variable (jx9_vm_func_static_var instance) */ SyString sName; /* Function name */ SySet aByteCode; /* Compiled function body */ sxi32 iFlags; /* VM function configuration */ SyString sSignature; /* Function signature used to implement function overloading * (Refer to the official docuemntation for more information * on this powerfull feature) */ void *pUserData; /* Upper layer private data associated with this instance */ jx9_vm_func *pNextName; /* Next VM function with the same name as this one */ }; /* Forward reference */ typedef struct jx9_builtin_constant jx9_builtin_constant; typedef struct jx9_builtin_func jx9_builtin_func; /* * Each built-in foreign function (C function) is stored in an * instance of the following structure. * Please refer to the official documentation for more information * on how to create/install foreign functions. */ struct jx9_builtin_func { const char *zName; /* Function name [i.e: strlen(), rand(), array_merge(), etc.]*/ ProcHostFunction xFunc; /* C routine performing the computation */ }; /* * Each built-in foreign constant is stored in an instance * of the following structure. * Please refer to the official documentation for more information * on how to create/install foreign constants. */ struct jx9_builtin_constant { const char *zName; /* Constant name */ ProcConstant xExpand; /* C routine responsible of expanding constant value*/ }; /* * A single instruction of the virtual machine has an opcode * and as many as three operands. * Each VM instruction resulting from compiling a JX9 script * is stored in an instance of the following structure. */ typedef struct VmInstr VmInstr; struct VmInstr { sxu8 iOp; /* Operation to preform */ sxi32 iP1; /* First operand */ sxu32 iP2; /* Second operand (Often the jump destination) */ void *p3; /* Third operand (Often Upper layer private data) */ }; /* Forward reference */ typedef struct jx9_case_expr jx9_case_expr; typedef struct jx9_switch jx9_switch; /* * Each compiled case block in a swicth statement is compiled * and stored in an instance of the following structure. */ struct jx9_case_expr { SySet aByteCode; /* Compiled body of the case block */ sxu32 nStart; /* First instruction to execute */ }; /* * Each compiled switch statement is parsed out and stored * in an instance of the following structure. */ struct jx9_switch { SySet aCaseExpr; /* Compile case block */ sxu32 nOut; /* First instruction to execute after this statement */ sxu32 nDefault; /* First instruction to execute in the default block */ }; /* Assertion flags */ #define JX9_ASSERT_DISABLE 0x01 /* Disable assertion */ #define JX9_ASSERT_WARNING 0x02 /* Issue a warning for each failed assertion */ #define JX9_ASSERT_BAIL 0x04 /* Terminate execution on failed assertions */ #define JX9_ASSERT_QUIET_EVAL 0x08 /* Not used */ #define JX9_ASSERT_CALLBACK 0x10 /* Callback to call on failed assertions */ /* * An instance of the following structure hold the bytecode instructions * resulting from compiling a JX9 script. * This structure contains the complete state of the virtual machine. */ struct jx9_vm { SyMemBackend sAllocator; /* Memory backend */ #if defined(JX9_ENABLE_THREADS) SyMutex *pMutex; /* Recursive mutex associated with this VM. */ #endif jx9 *pEngine; /* Interpreter that own this VM */ SySet aByteCode; /* Default bytecode container */ SySet *pByteContainer; /* Current bytecode container */ VmFrame *pFrame; /* Stack of active frames */ SyPRNGCtx sPrng; /* PRNG context */ SySet aMemObj; /* Object allocation table */ SySet aLitObj; /* Literals allocation table */ jx9_value *aOps; /* Operand stack */ SySet aFreeObj; /* Stack of free memory objects */ SyHash hConstant; /* Host-application and user defined constants container */ SyHash hHostFunction; /* Host-application installable functions */ SyHash hFunction; /* Compiled functions */ SyHash hSuper; /* Global variable */ SyBlob sConsumer; /* Default VM consumer [i.e Redirect all VM output to this blob] */ SyBlob sWorker; /* General purpose working buffer */ SyBlob sArgv; /* $argv[] collector [refer to the [getopt()] implementation for more information] */ SySet aFiles; /* Stack of processed files */ SySet aPaths; /* Set of import paths */ SySet aIncluded; /* Set of included files */ SySet aIOstream; /* Installed IO stream container */ const jx9_io_stream *pDefStream; /* Default IO stream [i.e: typically this is the 'file://' stream] */ jx9_value sExec; /* Compiled script return value [Can be extracted via the JX9_VM_CONFIG_EXEC_VALUE directive]*/ void *pStdin; /* STDIN IO stream */ void *pStdout; /* STDOUT IO stream */ void *pStderr; /* STDERR IO stream */ int bErrReport; /* TRUE to report all runtime Error/Warning/Notice */ int nRecursionDepth; /* Current recursion depth */ int nMaxDepth; /* Maximum allowed recusion depth */ sxu32 nOutputLen; /* Total number of generated output */ jx9_output_consumer sVmConsumer; /* Registered output consumer callback */ int iAssertFlags; /* Assertion flags */ jx9_value sAssertCallback; /* Callback to call on failed assertions */ sxi32 iExitStatus; /* Script exit status */ jx9_gen_state sCodeGen; /* Code generator module */ jx9_vm *pNext, *pPrev; /* List of active VM's */ sxu32 nMagic; /* Sanity check against misuse */ }; /* * Allowed value for jx9_vm.nMagic */ #define JX9_VM_INIT 0xEA12CD72 /* VM correctly initialized */ #define JX9_VM_RUN 0xBA851227 /* VM ready to execute JX9 bytecode */ #define JX9_VM_EXEC 0xCDFE1DAD /* VM executing JX9 bytecode */ #define JX9_VM_STALE 0xDEAD2BAD /* Stale VM */ /* * Error codes according to the JX9 language reference manual. */ enum iErrCode { E_ABORT = -1, /* deadliness error, should halt script execution. */ E_ERROR = 1, /* Fatal run-time errors. These indicate errors that can not be recovered * from, such as a memory allocation problem. Execution of the script is * halted. * The only fatal error under JX9 is an out-of-memory. All others erros * even a call to undefined function will not halt script execution. */ E_WARNING , /* Run-time warnings (non-fatal errors). Execution of the script is not halted. */ E_PARSE , /* Compile-time parse errors. Parse errors should only be generated by the parser.*/ E_NOTICE , /* Run-time notices. Indicate that the script encountered something that could * indicate an error, but could also happen in the normal course of running a script. */ }; /* * Each VM instruction resulting from compiling a JX9 script is represented * by one of the following OP codes. * The program consists of a linear sequence of operations. Each operation * has an opcode and 3 operands.Operands P1 is an integer. * Operand P2 is an unsigned integer and operand P3 is a memory address. * Few opcodes use all 3 operands. */ enum jx9_vm_op { JX9_OP_DONE = 1, /* Done */ JX9_OP_HALT, /* Halt */ JX9_OP_LOAD, /* Load memory object */ JX9_OP_LOADC, /* Load constant */ JX9_OP_LOAD_IDX, /* Load array entry */ JX9_OP_LOAD_MAP, /* Load hashmap('array') */ JX9_OP_NOOP, /* NOOP */ JX9_OP_JMP, /* Unconditional jump */ JX9_OP_JZ, /* Jump on zero (FALSE jump) */ JX9_OP_JNZ, /* Jump on non-zero (TRUE jump) */ JX9_OP_POP, /* Stack POP */ JX9_OP_CAT, /* Concatenation */ JX9_OP_CVT_INT, /* Integer cast */ JX9_OP_CVT_STR, /* String cast */ JX9_OP_CVT_REAL, /* Float cast */ JX9_OP_CALL, /* Function call */ JX9_OP_UMINUS, /* Unary minus '-'*/ JX9_OP_UPLUS, /* Unary plus '+'*/ JX9_OP_BITNOT, /* Bitwise not '~' */ JX9_OP_LNOT, /* Logical not '!' */ JX9_OP_MUL, /* Multiplication '*' */ JX9_OP_DIV, /* Division '/' */ JX9_OP_MOD, /* Modulus '%' */ JX9_OP_ADD, /* Add '+' */ JX9_OP_SUB, /* Sub '-' */ JX9_OP_SHL, /* Left shift '<<' */ JX9_OP_SHR, /* Right shift '>>' */ JX9_OP_LT, /* Less than '<' */ JX9_OP_LE, /* Less or equal '<=' */ JX9_OP_GT, /* Greater than '>' */ JX9_OP_GE, /* Greater or equal '>=' */ JX9_OP_EQ, /* Equal '==' */ JX9_OP_NEQ, /* Not equal '!=' */ JX9_OP_TEQ, /* Type equal '===' */ JX9_OP_TNE, /* Type not equal '!==' */ JX9_OP_BAND, /* Bitwise and '&' */ JX9_OP_BXOR, /* Bitwise xor '^' */ JX9_OP_BOR, /* Bitwise or '|' */ JX9_OP_LAND, /* Logical and '&&','and' */ JX9_OP_LOR, /* Logical or '||','or' */ JX9_OP_LXOR, /* Logical xor 'xor' */ JX9_OP_STORE, /* Store Object */ JX9_OP_STORE_IDX, /* Store indexed object */ JX9_OP_PULL, /* Stack pull */ JX9_OP_SWAP, /* Stack swap */ JX9_OP_YIELD, /* Stack yield */ JX9_OP_CVT_BOOL, /* Boolean cast */ JX9_OP_CVT_NUMC, /* Numeric (integer, real or both) type cast */ JX9_OP_INCR, /* Increment ++ */ JX9_OP_DECR, /* Decrement -- */ JX9_OP_ADD_STORE, /* Add and store '+=' */ JX9_OP_SUB_STORE, /* Sub and store '-=' */ JX9_OP_MUL_STORE, /* Mul and store '*=' */ JX9_OP_DIV_STORE, /* Div and store '/=' */ JX9_OP_MOD_STORE, /* Mod and store '%=' */ JX9_OP_CAT_STORE, /* Cat and store '.=' */ JX9_OP_SHL_STORE, /* Shift left and store '>>=' */ JX9_OP_SHR_STORE, /* Shift right and store '<<=' */ JX9_OP_BAND_STORE, /* Bitand and store '&=' */ JX9_OP_BOR_STORE, /* Bitor and store '|=' */ JX9_OP_BXOR_STORE, /* Bitxor and store '^=' */ JX9_OP_CONSUME, /* Consume VM output */ JX9_OP_MEMBER, /* Object member run-time access */ JX9_OP_UPLINK, /* Run-Time frame link */ JX9_OP_CVT_NULL, /* NULL cast */ JX9_OP_CVT_ARRAY, /* Array cast */ JX9_OP_FOREACH_INIT, /* For each init */ JX9_OP_FOREACH_STEP, /* For each step */ JX9_OP_SWITCH /* Switch operation */ }; /* -- END-OF INSTRUCTIONS -- */ /* * Expression Operators ID. */ enum jx9_expr_id { EXPR_OP_DOT, /* Member access */ EXPR_OP_DC, /* :: */ EXPR_OP_SUBSCRIPT, /* []: Subscripting */ EXPR_OP_FUNC_CALL, /* func_call() */ EXPR_OP_INCR, /* ++ */ EXPR_OP_DECR, /* -- */ EXPR_OP_BITNOT, /* ~ */ EXPR_OP_UMINUS, /* Unary minus */ EXPR_OP_UPLUS, /* Unary plus */ EXPR_OP_TYPECAST, /* Type cast [i.e: (int), (float), (string)...] */ EXPR_OP_ALT, /* @ */ EXPR_OP_INSTOF, /* instanceof */ EXPR_OP_LOGNOT, /* logical not ! */ EXPR_OP_MUL, /* Multiplication */ EXPR_OP_DIV, /* division */ EXPR_OP_MOD, /* Modulus */ EXPR_OP_ADD, /* Addition */ EXPR_OP_SUB, /* Substraction */ EXPR_OP_DDOT, /* Concatenation */ EXPR_OP_SHL, /* Left shift */ EXPR_OP_SHR, /* Right shift */ EXPR_OP_LT, /* Less than */ EXPR_OP_LE, /* Less equal */ EXPR_OP_GT, /* Greater than */ EXPR_OP_GE, /* Greater equal */ EXPR_OP_EQ, /* Equal == */ EXPR_OP_NE, /* Not equal != <> */ EXPR_OP_TEQ, /* Type equal === */ EXPR_OP_TNE, /* Type not equal !== */ EXPR_OP_SEQ, /* String equal 'eq' */ EXPR_OP_SNE, /* String not equal 'ne' */ EXPR_OP_BAND, /* Biwise and '&' */ EXPR_OP_REF, /* Reference operator '&' */ EXPR_OP_XOR, /* bitwise xor '^' */ EXPR_OP_BOR, /* bitwise or '|' */ EXPR_OP_LAND, /* Logical and '&&','and' */ EXPR_OP_LOR, /* Logical or '||','or'*/ EXPR_OP_LXOR, /* Logical xor 'xor' */ EXPR_OP_QUESTY, /* Ternary operator '?' */ EXPR_OP_ASSIGN, /* Assignment '=' */ EXPR_OP_ADD_ASSIGN, /* Combined operator: += */ EXPR_OP_SUB_ASSIGN, /* Combined operator: -= */ EXPR_OP_MUL_ASSIGN, /* Combined operator: *= */ EXPR_OP_DIV_ASSIGN, /* Combined operator: /= */ EXPR_OP_MOD_ASSIGN, /* Combined operator: %= */ EXPR_OP_DOT_ASSIGN, /* Combined operator: .= */ EXPR_OP_AND_ASSIGN, /* Combined operator: &= */ EXPR_OP_OR_ASSIGN, /* Combined operator: |= */ EXPR_OP_XOR_ASSIGN, /* Combined operator: ^= */ EXPR_OP_SHL_ASSIGN, /* Combined operator: <<= */ EXPR_OP_SHR_ASSIGN, /* Combined operator: >>= */ EXPR_OP_COMMA /* Comma expression */ }; /* * Lexer token codes * The following set of constants are the tokens recognized * by the lexer when processing JX9 input. * Important: Token values MUST BE A POWER OF TWO. */ #define JX9_TK_INTEGER 0x0000001 /* Integer */ #define JX9_TK_REAL 0x0000002 /* Real number */ #define JX9_TK_NUM (JX9_TK_INTEGER|JX9_TK_REAL) /* Numeric token, either integer or real */ #define JX9_TK_KEYWORD 0x0000004 /* Keyword [i.e: while, for, if, foreach...] */ #define JX9_TK_ID 0x0000008 /* Alphanumeric or UTF-8 stream */ #define JX9_TK_DOLLAR 0x0000010 /* '$' Dollar sign */ #define JX9_TK_OP 0x0000020 /* Operator [i.e: +, *, /...] */ #define JX9_TK_OCB 0x0000040 /* Open curly brace'{' */ #define JX9_TK_CCB 0x0000080 /* Closing curly brace'}' */ #define JX9_TK_DOT 0x0000100 /* Dot . */ #define JX9_TK_LPAREN 0x0000200 /* Left parenthesis '(' */ #define JX9_TK_RPAREN 0x0000400 /* Right parenthesis ')' */ #define JX9_TK_OSB 0x0000800 /* Open square bracket '[' */ #define JX9_TK_CSB 0x0001000 /* Closing square bracket ']' */ #define JX9_TK_DSTR 0x0002000 /* Double quoted string "$str" */ #define JX9_TK_SSTR 0x0004000 /* Single quoted string 'str' */ #define JX9_TK_NOWDOC 0x0010000 /* Nowdoc <<< */ #define JX9_TK_COMMA 0x0020000 /* Comma ',' */ #define JX9_TK_SEMI 0x0040000 /* Semi-colon ";" */ #define JX9_TK_BSTR 0x0080000 /* Backtick quoted string [i.e: Shell command `date`] */ #define JX9_TK_COLON 0x0100000 /* single Colon ':' */ #define JX9_TK_AMPER 0x0200000 /* Ampersand '&' */ #define JX9_TK_EQUAL 0x0400000 /* Equal '=' */ #define JX9_TK_OTHER 0x1000000 /* Other symbols */ /* * JX9 keyword. * These words have special meaning in JX9. Some of them represent things which look like * functions, some look like constants, and so on, but they're not, really: they are language constructs. * You cannot use any of the following words as constants, object names, function or method names. * Using them as variable names is generally OK, but could lead to confusion. */ #define JX9_TKWRD_SWITCH 1 /* switch */ #define JX9_TKWRD_PRINT 2 /* print */ #define JX9_TKWRD_ELIF 0x4000000 /* elseif: MUST BE A POWER OF TWO */ #define JX9_TKWRD_ELSE 0x8000000 /* else: MUST BE A POWER OF TWO */ #define JX9_TKWRD_IF 3 /* if */ #define JX9_TKWRD_STATIC 4 /* static */ #define JX9_TKWRD_CASE 5 /* case */ #define JX9_TKWRD_FUNCTION 6 /* function */ #define JX9_TKWRD_CONST 7 /* const */ /* The number '8' is reserved for JX9_TK_ID */ #define JX9_TKWRD_WHILE 9 /* while */ #define JX9_TKWRD_DEFAULT 10 /* default */ #define JX9_TKWRD_AS 11 /* as */ #define JX9_TKWRD_CONTINUE 12 /* continue */ #define JX9_TKWRD_EXIT 13 /* exit */ #define JX9_TKWRD_DIE 14 /* die */ #define JX9_TKWRD_IMPORT 15 /* import */ #define JX9_TKWRD_INCLUDE 16 /* include */ #define JX9_TKWRD_FOR 17 /* for */ #define JX9_TKWRD_FOREACH 18 /* foreach */ #define JX9_TKWRD_RETURN 19 /* return */ #define JX9_TKWRD_BREAK 20 /* break */ #define JX9_TKWRD_UPLINK 21 /* uplink */ #define JX9_TKWRD_BOOL 0x8000 /* bool: MUST BE A POWER OF TWO */ #define JX9_TKWRD_INT 0x10000 /* int: MUST BE A POWER OF TWO */ #define JX9_TKWRD_FLOAT 0x20000 /* float: MUST BE A POWER OF TWO */ #define JX9_TKWRD_STRING 0x40000 /* string: MUST BE A POWER OF TWO */ /* api.c */ JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap); JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName); JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName); /* json.c function prototypes */ JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut); JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte); /* memobj.c function prototypes */ JX9_PRIVATE sxi32 jx9MemObjDump(SyBlob *pOut, jx9_value *pObj); JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal); JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore); JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest); JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal); JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray); #if 0 /* Not used in the current release of the JX9 engine */ JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal); #endif JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal); JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal); JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen); #if 0 /* Not used in the current release of the JX9 engine */ JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap); #endif JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest); JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest); JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj); JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags); JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj); JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj); JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pData); /* lex.c function prototypes */ JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput, sxu32 nLen, SySet *pOut); /* vm.c function prototypes */ JX9_PRIVATE void jx9VmReleaseContextValue(jx9_context *pCtx, jx9_value *pValue); JX9_PRIVATE sxi32 jx9VmInitFuncState(jx9_vm *pVm, jx9_vm_func *pFunc, const char *zName, sxu32 nByte, sxi32 iFlags, void *pUserData); JX9_PRIVATE sxi32 jx9VmInstallUserFunction(jx9_vm *pVm, jx9_vm_func *pFunc, SyString *pName); JX9_PRIVATE sxi32 jx9VmRegisterConstant(jx9_vm *pVm, const SyString *pName, ProcConstant xExpand, void *pUserData); JX9_PRIVATE sxi32 jx9VmInstallForeignFunction(jx9_vm *pVm, const SyString *pName, ProcHostFunction xFunc, void *pUserData); JX9_PRIVATE sxi32 jx9VmBlobConsumer(const void *pSrc, unsigned int nLen, void *pUserData); JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIndex); JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex); JX9_PRIVATE sxi32 jx9VmOutputConsume(jx9_vm *pVm, SyString *pString); JX9_PRIVATE sxi32 jx9VmOutputConsumeAp(jx9_vm *pVm, const char *zFormat, va_list ap); JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap); JX9_PRIVATE sxi32 jx9VmThrowError(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zMessage); JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData); JX9_PRIVATE sxi32 jx9VmDump(jx9_vm *pVm, ProcConsumer xConsumer, void *pUserData); JX9_PRIVATE sxi32 jx9VmInit(jx9_vm *pVm, jx9 *pEngine); JX9_PRIVATE sxi32 jx9VmConfigure(jx9_vm *pVm, sxi32 nOp, va_list ap); JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm); JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar); JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm); JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm); JX9_PRIVATE sxi32 jx9VmMakeReady(jx9_vm *pVm); JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm); JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm); JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm); JX9_PRIVATE VmInstr *jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex); JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm); JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer); JX9_PRIVATE sxi32 jx9VmEmitInstr(jx9_vm *pVm, sxi32 iOp, sxi32 iP1, sxu32 iP2, void *p3, sxu32 *pIndex); JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm); JX9_PRIVATE sxi32 jx9VmCallUserFunction(jx9_vm *pVm, jx9_value *pFunc, int nArg, jx9_value **apArg, jx9_value *pResult); JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp(jx9_vm *pVm, jx9_value *pFunc, jx9_value *pResult, ...); JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm, sxu32 nObjIdx); JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen); JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue); JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew); #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(jx9_vm *pVm, const char **pzDevice, int nByte); #endif /* JX9_DISABLE_BUILTIN_FUNC */ 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 */ ); /* parse.c function prototypes */ JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID); JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot); JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart, SyToken *pEnd, SyToken **ppNext); JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn, SyToken *pEnd, sxu32 nTokStart, sxu32 nTokEnd, SyToken **ppEnd); JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast); JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet); /* compile.c function prototypes */ JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType); JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen, sxi32 iCompileFlag); JX9_PRIVATE sxi32 jx9InitCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); JX9_PRIVATE sxi32 jx9ResetCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...); JX9_PRIVATE sxi32 jx9CompileScript(jx9_vm *pVm, SyString *pScript, sxi32 iFlags); /* constant.c function prototypes */ JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm); /* builtin.c function prototypes */ JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm); /* hashmap.c function prototypes */ JX9_PRIVATE jx9_hashmap * jx9NewHashmap(jx9_vm *pVm, sxu32 (*xIntHash)(sxi64), sxu32 (*xBlobHash)(const void *, sxu32)); JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm); JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS); JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap); JX9_PRIVATE sxi32 jx9HashmapLookup(jx9_hashmap *pMap, jx9_value *pKey, jx9_hashmap_node **ppNode); JX9_PRIVATE sxi32 jx9HashmapInsert(jx9_hashmap *pMap, jx9_value *pKey, jx9_value *pVal); JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight); JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest); JX9_PRIVATE sxi32 jx9HashmapCmp(jx9_hashmap *pLeft, jx9_hashmap *pRight, int bStrict); JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap); JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap); JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode); JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore); JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode, jx9_value *pKey); JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm); JX9_PRIVATE sxi32 jx9HashmapWalk(jx9_hashmap *pMap, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut); /* builtin.c function prototypes */ JX9_PRIVATE sxi32 jx9InputFormat(int (*xConsumer)(jx9_context *, const char *, int, void *), jx9_context *pCtx, const char *zIn, int nByte, int nArg, jx9_value **apArg, void *pUserData, int vf); JX9_PRIVATE sxi32 jx9ProcessCsv(const char *zInput, int nByte, int delim, int encl, int escape, sxi32 (*xConsumer)(const char *, int, void *), void *pUserData); JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData); JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen); JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection); #endif /* vfs.c */ #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile, int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew); JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut); JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle); #endif /* JX9_DISABLE_BUILTIN_FUNC */ JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen); JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm); JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void); JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm); JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm); JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm); /* lib.c function prototypes */ #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp); JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch); JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch); JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry); JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen); #endif /* JX9_DISABLE_BUILTIN_FUNC */ #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData); #endif /* JX9_DISABLE_BUILTIN_FUNC */ #ifndef JX9_DISABLE_BUILTIN_FUNC #ifndef JX9_DISABLE_HASH_FUNC JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen); JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len); JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx); JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx); JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]); JX9_PRIVATE void SHA1Init(SHA1Context *context); JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len); JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]); JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]); #endif #endif /* JX9_DISABLE_BUILTIN_FUNC */ JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen); JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void *pUserData); JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...); JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap); JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...); JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...); #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE const char *SyTimeGetMonth(sxi32 iMonth); JX9_PRIVATE const char *SyTimeGetDay(sxi32 iDay); #endif /* JX9_DISABLE_BUILTIN_FUNC */ JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8); #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); #endif JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex); JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp); JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData); #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); #endif /* JX9_DISABLE_BUILTIN_FUNC */ JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen); JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); JX9_PRIVATE sxi32 SyHexToint(sxi32 c); JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail); JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData); JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32(*xStep)(SyHashEntry *, void *), void *pUserData); JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData); JX9_PRIVATE SyHashEntry *SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen); JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash); JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp); JX9_PRIVATE void *SySetAt(SySet *pSet, sxu32 nIdx); JX9_PRIVATE void *SySetPop(SySet *pSet); JX9_PRIVATE void *SySetPeek(SySet *pSet); JX9_PRIVATE sxi32 SySetRelease(SySet *pSet); JX9_PRIVATE sxi32 SySetReset(SySet *pSet); JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet); JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry); JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem); JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem); JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize); #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft); #endif JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob); JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob); JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen); JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest); JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob); JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize); JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte); JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator); JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize); JX9_PRIVATE char *SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize); JX9_PRIVATE void *SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize); JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend); JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void *pUserData); JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void *pUserData); JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent); #if 0 /* Not used in the current release of the JX9 engine */ JX9_PRIVATE void *SyMemBackendPoolRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); #endif JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void *pChunk); JX9_PRIVATE void *SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte); JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void *pChunk); JX9_PRIVATE void *SyMemBackendRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); JX9_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte); JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen); JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize); JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize); JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen); JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen); #if !defined(JX9_DISABLE_BUILTIN_FUNC) || defined(__APPLE__) JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen); #endif JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos); #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); #endif JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); JX9_PRIVATE sxu32 SyStrlen(const char *zSrc); #if defined(JX9_ENABLE_THREADS) JX9_PRIVATE const SyMutexMethods *SyMutexExportMethods(void); JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods); JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend); #endif JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb); JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB); JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb); JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB); JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64); JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64); JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64); JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32); JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16); JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut); JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut); #endif /* __JX9INT_H__ */ /* * ---------------------------------------------------------- * File: unqliteInt.h * MD5: 325816ce05f6adbaab2c39a41875dedd * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: unqliteInt.h v1.7 FreeBSD 2012-11-02 11:25 devel $ */ #ifndef __UNQLITEINT_H__ #define __UNQLITEINT_H__ /* Internal interface definitions for UnQLite. */ #ifdef UNQLITE_AMALGAMATION /* Marker for routines not intended for external use */ #define UNQLITE_PRIVATE static #define JX9_AMALGAMATION #else #define UNQLITE_PRIVATE #include "unqlite.h" #include "jx9Int.h" #endif /* forward declaration */ typedef struct unqlite_db unqlite_db; /* ** The following values may be passed as the second argument to ** UnqliteOsLock(). The various locks exhibit the following semantics: ** ** SHARED: Any number of processes may hold a SHARED lock simultaneously. ** RESERVED: A single process may hold a RESERVED lock on a file at ** any time. Other processes may hold and obtain new SHARED locks. ** PENDING: A single process may hold a PENDING lock on a file at ** any one time. Existing SHARED locks may persist, but no new ** SHARED locks may be obtained by other processes. ** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. ** ** PENDING_LOCK may not be passed directly to UnqliteOsLock(). Instead, a ** process that requests an EXCLUSIVE lock may actually obtain a PENDING ** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to ** UnqliteOsLock(). */ #define NO_LOCK 0 #define SHARED_LOCK 1 #define RESERVED_LOCK 2 #define PENDING_LOCK 3 #define EXCLUSIVE_LOCK 4 /* * UnQLite Locking Strategy (Same as SQLite3) * * The following #defines specify the range of bytes used for locking. * SHARED_SIZE is the number of bytes available in the pool from which * a random byte is selected for a shared lock. The pool of bytes for * shared locks begins at SHARED_FIRST. * * The same locking strategy and byte ranges are used for Unix and Windows. * This leaves open the possiblity of having clients on winNT, and * unix all talking to the same shared file and all locking correctly. * To do so would require that samba (or whatever * tool is being used for file sharing) implements locks correctly between * windows and unix. I'm guessing that isn't likely to happen, but by * using the same locking range we are at least open to the possibility. * * Locking in windows is mandatory. For this reason, we cannot store * actual data in the bytes used for locking. The pager never allocates * the pages involved in locking therefore. SHARED_SIZE is selected so * that all locks will fit on a single page even at the minimum page size. * PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE * is set high so that we don't have to allocate an unused page except * for very large databases. But one should test the page skipping logic * by setting PENDING_BYTE low and running the entire regression suite. * * Changing the value of PENDING_BYTE results in a subtly incompatible * file format. Depending on how it is changed, you might not notice * the incompatibility right away, even running a full regression test. * The default location of PENDING_BYTE is the first byte past the * 1GB boundary. */ #define PENDING_BYTE (0x40000000) #define RESERVED_BYTE (PENDING_BYTE+1) #define SHARED_FIRST (PENDING_BYTE+2) #define SHARED_SIZE 510 /* * The default size of a disk sector in bytes. */ #ifndef UNQLITE_DEFAULT_SECTOR_SIZE #define UNQLITE_DEFAULT_SECTOR_SIZE 512 #endif /* * Each open database file is managed by a separate instance * of the "Pager" structure. */ typedef struct Pager Pager; /* * Each database file to be accessed by the system is an instance * of the following structure. */ struct unqlite_db { Pager *pPager; /* Pager and Transaction manager */ jx9 *pJx9; /* Jx9 Engine handle */ unqlite_kv_cursor *pCursor; /* Database cursor for common usage */ }; /* * Each database connection is an instance of the following structure. */ struct unqlite { SyMemBackend sMem; /* Memory allocator subsystem */ SyBlob sErr; /* Error log */ unqlite_db sDB; /* Storage backend */ #if defined(UNQLITE_ENABLE_THREADS) const SyMutexMethods *pMethods; /* Mutex methods */ SyMutex *pMutex; /* Per-handle mutex */ #endif unqlite_vm *pVms; /* List of active VM */ sxi32 iVm; /* Total number of active VM */ sxi32 iFlags; /* Control flags (See below) */ unqlite *pNext,*pPrev; /* List of active DB handles */ sxu32 nMagic; /* Sanity check against misuse */ }; #define UNQLITE_FL_DISABLE_AUTO_COMMIT 0x001 /* Disable auto-commit on close */ /* * VM control flags (Mostly related to collection handling). */ #define UNQLITE_VM_COLLECTION_CREATE 0x001 /* Create a new collection */ #define UNQLITE_VM_COLLECTION_EXISTS 0x002 /* Exists old collection */ #define UNQLITE_VM_AUTO_LOAD 0x004 /* Auto load a collection from the vfs */ /* Forward declaration */ typedef struct unqlite_col_record unqlite_col_record; typedef struct unqlite_col unqlite_col; /* * Each an in-memory collection record is stored in an instance * of the following structure. */ struct unqlite_col_record { unqlite_col *pCol; /* Collecion this record belong */ jx9_int64 nId; /* Unique record ID */ jx9_value sValue; /* In-memory value of the record */ unqlite_col_record *pNextCol,*pPrevCol; /* Collision chain */ unqlite_col_record *pNext,*pPrev; /* Linked list of records */ }; /* * Magic number to identify a valid collection on disk. */ #define UNQLITE_COLLECTION_MAGIC 0x611E /* sizeof(unsigned short) 2 bytes */ /* * A loaded collection is identified by an instance of the following structure. */ struct unqlite_col { unqlite_vm *pVm; /* VM that own this instance */ SyString sName; /* ID of the collection */ sxu32 nHash; /* sName hash */ jx9_value sSchema; /* Collection schema */ sxu32 nSchemaOfft; /* Shema offset in sHeader */ SyBlob sWorker; /* General purpose working buffer */ SyBlob sHeader; /* Collection binary header */ jx9_int64 nLastid; /* Last collection record ID */ jx9_int64 nCurid; /* Current record ID */ jx9_int64 nTotRec; /* Total number of records in the collection */ int iFlags; /* Control flags (see below) */ unqlite_col_record **apRecord; /* Hashtable of loaded records */ unqlite_col_record *pList; /* Linked list of records */ sxu32 nRec; /* Total number of records in apRecord[] */ sxu32 nRecSize; /* apRecord[] size */ Sytm sCreation; /* Colleation creation time */ unqlite_kv_cursor *pCursor; /* Cursor pointing to the raw binary data */ unqlite_col *pNext,*pPrev; /* Next and previous collection in the chain */ unqlite_col *pNextCol,*pPrevCol; /* Collision chain */ }; /* * Each unQLite Virtual Machine resulting from successful compilation of * a Jx9 script is represented by an instance of the following structure. */ struct unqlite_vm { unqlite *pDb; /* Database handle that own this instance */ SyMemBackend sAlloc; /* Private memory allocator */ #if defined(UNQLITE_ENABLE_THREADS) SyMutex *pMutex; /* Recursive mutex associated with this VM. */ #endif unqlite_col **apCol; /* Table of loaded collections */ unqlite_col *pCol; /* List of loaded collections */ sxu32 iCol; /* Total number of loaded collections */ sxu32 iColSize; /* apCol[] size */ jx9_vm *pJx9Vm; /* Compiled Jx9 script*/ unqlite_vm *pNext,*pPrev; /* Linked list of active unQLite VM */ sxu32 nMagic; /* Magic number to avoid misuse */ }; /* * Database signature to identify a valid database image. */ #define UNQLITE_DB_SIG "unqlite" /* * Database magic number (4 bytes). */ #define UNQLITE_DB_MAGIC 0xDB7C2712 /* * Maximum page size in bytes. */ #ifdef UNQLITE_MAX_PAGE_SIZE # undef UNQLITE_MAX_PAGE_SIZE #endif #define UNQLITE_MAX_PAGE_SIZE 65536 /* 65K */ /* * Minimum page size in bytes. */ #ifdef UNQLITE_MIN_PAGE_SIZE # undef UNQLITE_MIN_PAGE_SIZE #endif #define UNQLITE_MIN_PAGE_SIZE 512 /* * The default size of a database page. */ #ifndef UNQLITE_DEFAULT_PAGE_SIZE # undef UNQLITE_DEFAULT_PAGE_SIZE #endif # define UNQLITE_DEFAULT_PAGE_SIZE 4096 /* 4K */ /* Forward declaration */ typedef struct Bitvec Bitvec; /* Private library functions */ /* api.c */ UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void); UNQLITE_PRIVATE int unqliteDataConsumer( const void *pOut, /* Data to consume */ unsigned int nLen, /* Data length */ void *pUserData /* User private data */ ); UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore( const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */ sxu32 nByte /* zName length */ ); UNQLITE_PRIVATE int unqliteGetPageSize(void); UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr); UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...); UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb); /* unql_vm.c */ UNQLITE_PRIVATE int unqliteExistsCollection(unqlite_vm *pVm, SyString *pName); UNQLITE_PRIVATE int unqliteCreateCollection(unqlite_vm *pVm,SyString *pName); UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol); UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol); UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(unqlite_col *pCol,jx9_int64 nId); UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol); UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol); UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue); UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(unqlite_col *pCol,jx9_int64 nId,jx9_value *pValue); UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(unqlite_vm *pVm,SyString *pCol,int iFlag); UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue); UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag); UNQLITE_PRIVATE int unqliteCollectionDropRecord(unqlite_col *pCol,jx9_int64 nId,int wr_header,int log_err); UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol); /* unql_jx9.c */ UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm); /* fastjson.c */ UNQLITE_PRIVATE sxi32 FastJsonEncode( jx9_value *pValue, /* Value to encode */ SyBlob *pOut, /* Store encoded value here */ int iNest /* Nesting limit */ ); UNQLITE_PRIVATE sxi32 FastJsonDecode( const void *pIn, /* Binary JSON */ sxu32 nByte, /* Chunk delimiter */ jx9_value *pOut, /* Decoded value */ const unsigned char **pzPtr, int iNest /* Nesting limit */ ); /* vfs.c [io_win.c, io_unix.c ] */ UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void); /* mem_kv.c */ UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void); /* lhash_kv.c */ UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void); /* os.c */ UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset); UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset); UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size); UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags); UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize); UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType); UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType); UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut); UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id); UNQLITE_PRIVATE int unqliteOsOpen( unqlite_vfs *pVfs, SyMemBackend *pAlloc, const char *zPath, unqlite_file **ppOut, unsigned int flags ); UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId); UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync); UNQLITE_PRIVATE int unqliteOsAccess(unqlite_vfs *pVfs,const char *zPath,int flags,int *pResOut); /* bitmap.c */ UNQLITE_PRIVATE Bitvec *unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize); UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i); UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i); UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p); /* pager.c */ UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut); UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur); UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage); UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager); UNQLITE_PRIVATE int unqlitePagerOpen( unqlite_vfs *pVfs, /* The virtual file system to use */ unqlite *pDb, /* Database handle */ const char *zFilename, /* Name of the database file to open */ unsigned int iFlags /* flags controlling this file */ ); UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods); UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb); UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager); UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager); UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine); UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen); UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager); #endif /* __UNQLITEINT_H__ */ /* * ---------------------------------------------------------- * File: api.c * MD5: d79e8404e50dacd0ea75635c1ebe553a * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: api.c v2.0 FreeBSD 2012-11-08 23:07 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* This file implement the public interfaces presented to host-applications. * Routines in other files are for internal use by UnQLite and should not be * accessed by users of the library. */ #define UNQLITE_DB_MISUSE(DB) (DB == 0 || DB->nMagic != UNQLITE_DB_MAGIC) #define UNQLITE_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE) /* If another thread have released a working instance, the following macros * evaluates to true. These macros are only used when the library * is built with threading support enabled. */ #define UNQLITE_THRD_DB_RELEASE(DB) (DB->nMagic != UNQLITE_DB_MAGIC) #define UNQLITE_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE) /* IMPLEMENTATION: unqlite@embedded@symisc 118-09-4785 */ /* * All global variables are collected in the structure named "sUnqlMPGlobal". * That way it is clear in the code when we are using static variable because * its name start with sUnqlMPGlobal. */ static struct unqlGlobal_Data { SyMemBackend sAllocator; /* Global low level memory allocator */ #if defined(UNQLITE_ENABLE_THREADS) const SyMutexMethods *pMutexMethods; /* Mutex methods */ SyMutex *pMutex; /* Global mutex */ sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded * The threading level can be set using the [unqlite_lib_config()] * interface with a configuration verb set to * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE or * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI */ #endif SySet kv_storage; /* Installed KV storage engines */ int iPageSize; /* Default Page size */ unqlite_vfs *pVfs; /* Underlying virtual file system (Vfs) */ sxi32 nDB; /* Total number of active DB handles */ unqlite *pDB; /* List of active DB handles */ sxu32 nMagic; /* Sanity check against library misuse */ }sUnqlMPGlobal = { {0, 0, 0, 0, 0, 0, 0, 0, {0}}, #if defined(UNQLITE_ENABLE_THREADS) 0, 0, 0, #endif {0, 0, 0, 0, 0, 0, 0 }, UNQLITE_DEFAULT_PAGE_SIZE, 0, 0, 0, 0 }; #define UNQLITE_LIB_MAGIC 0xEA1495BA #define UNQLITE_LIB_MISUSE (sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC) /* * Supported threading level. * These options have meaning only when the library is compiled with multi-threading * support. That is, the UNQLITE_ENABLE_THREADS compile time directive must be defined * when UnQLite is built. * UNQLITE_THREAD_LEVEL_SINGLE: * In this mode, mutexing is disabled and the library can only be used by a single thread. * UNQLITE_THREAD_LEVEL_MULTI * In this mode, all mutexes including the recursive mutexes on [unqlite] objects * are enabled so that the application is free to share the same database handle * between different threads at the same time. */ #define UNQLITE_THREAD_LEVEL_SINGLE 1 #define UNQLITE_THREAD_LEVEL_MULTI 2 /* * Find a Key Value storage engine from the set of installed engines. * Return a pointer to the storage engine methods on success. NULL on failure. */ UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore( const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */ sxu32 nByte /* zName length */ ) { unqlite_kv_methods **apStore,*pEntry; sxu32 n,nMax; /* Point to the set of installed engines */ apStore = (unqlite_kv_methods **)SySetBasePtr(&sUnqlMPGlobal.kv_storage); nMax = SySetUsed(&sUnqlMPGlobal.kv_storage); for( n = 0 ; n < nMax; ++n ){ pEntry = apStore[n]; if( nByte == SyStrlen(pEntry->zName) && SyStrnicmp(pEntry->zName,zName,nByte) == 0 ){ /* Storage engine found */ return pEntry; } } /* No such entry, return NULL */ return 0; } /* * Configure the UnQLite library. * Return UNQLITE_OK on success. Any other return value indicates failure. * Refer to [unqlite_lib_config()]. */ static sxi32 unqliteCoreConfigure(sxi32 nOp, va_list ap) { int rc = UNQLITE_OK; switch(nOp){ case UNQLITE_LIB_CONFIG_PAGE_SIZE: { /* Default page size: Must be a power of two */ int iPage = va_arg(ap,int); if( iPage >= UNQLITE_MIN_PAGE_SIZE && iPage <= UNQLITE_MAX_PAGE_SIZE ){ if( !(iPage & (iPage - 1)) ){ sUnqlMPGlobal.iPageSize = iPage; }else{ /* Invalid page size */ rc = UNQLITE_INVALID; } }else{ /* Invalid page size */ rc = UNQLITE_INVALID; } break; } case UNQLITE_LIB_CONFIG_STORAGE_ENGINE: { /* Install a key value storage engine */ unqlite_kv_methods *pMethods = va_arg(ap,unqlite_kv_methods *); /* Make sure we are delaing with a valid methods */ if( pMethods == 0 || SX_EMPTY_STR(pMethods->zName) || pMethods->xSeek == 0 || pMethods->xData == 0 || pMethods->xKey == 0 || pMethods->xDataLength == 0 || pMethods->xKeyLength == 0 || pMethods->szKv < (int)sizeof(unqlite_kv_engine) ){ rc = UNQLITE_INVALID; break; } /* Install it */ rc = SySetPut(&sUnqlMPGlobal.kv_storage,(const void *)&pMethods); break; } case UNQLITE_LIB_CONFIG_VFS:{ /* Install a virtual file system */ unqlite_vfs *pVfs = va_arg(ap,unqlite_vfs *); if( pVfs ){ sUnqlMPGlobal.pVfs = pVfs; } break; } case UNQLITE_LIB_CONFIG_USER_MALLOC: { /* Use an alternative low-level memory allocation routines */ const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *); /* Save the memory failure callback (if available) */ ProcMemError xMemErr = sUnqlMPGlobal.sAllocator.xMemError; void *pMemErr = sUnqlMPGlobal.sAllocator.pUserData; if( pMethods == 0 ){ /* Use the built-in memory allocation subsystem */ rc = SyMemBackendInit(&sUnqlMPGlobal.sAllocator, xMemErr, pMemErr); }else{ rc = SyMemBackendInitFromOthers(&sUnqlMPGlobal.sAllocator, pMethods, xMemErr, pMemErr); } break; } case UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK: { /* Memory failure callback */ ProcMemError xMemErr = va_arg(ap, ProcMemError); void *pUserData = va_arg(ap, void *); sUnqlMPGlobal.sAllocator.xMemError = xMemErr; sUnqlMPGlobal.sAllocator.pUserData = pUserData; break; } case UNQLITE_LIB_CONFIG_USER_MUTEX: { #if defined(UNQLITE_ENABLE_THREADS) /* Use an alternative low-level mutex subsystem */ const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *); #if defined (UNTRUST) if( pMethods == 0 ){ rc = UNQLITE_CORRUPT; } #endif /* Sanity check */ if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){ /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */ rc = UNQLITE_CORRUPT; break; } if( sUnqlMPGlobal.pMutexMethods ){ /* Overwrite the previous mutex subsystem */ SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){ sUnqlMPGlobal.pMutexMethods->xGlobalRelease(); } sUnqlMPGlobal.pMutex = 0; } /* Initialize and install the new mutex subsystem */ if( pMethods->xGlobalInit ){ rc = pMethods->xGlobalInit(); if ( rc != UNQLITE_OK ){ break; } } /* Create the global mutex */ sUnqlMPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); if( sUnqlMPGlobal.pMutex == 0 ){ /* * If the supplied mutex subsystem is so sick that we are unable to * create a single mutex, there is no much we can do here. */ if( pMethods->xGlobalRelease ){ pMethods->xGlobalRelease(); } rc = UNQLITE_CORRUPT; break; } sUnqlMPGlobal.pMutexMethods = pMethods; if( sUnqlMPGlobal.nThreadingLevel == 0 ){ /* Set a default threading level */ sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI; } #endif break; } case UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE: #if defined(UNQLITE_ENABLE_THREADS) /* Single thread mode (Only one thread is allowed to play with the library) */ sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_SINGLE; jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE); #endif break; case UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI: #if defined(UNQLITE_ENABLE_THREADS) /* Multi-threading mode (library is thread safe and database handles and virtual machines * may be shared between multiple threads). */ sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI; jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_MULTI); #endif break; default: /* Unknown configuration option */ rc = UNQLITE_CORRUPT; break; } return rc; } /* * [CAPIREF: unqlite_lib_config()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_lib_config(int nConfigOp,...) { va_list ap; int rc; if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){ /* Library is already initialized, this operation is forbidden */ return UNQLITE_LOCKED; } va_start(ap,nConfigOp); rc = unqliteCoreConfigure(nConfigOp,ap); va_end(ap); return rc; } /* * Global library initialization * Refer to [unqlite_lib_init()] * This routine must be called to initialize the memory allocation subsystem, the mutex * subsystem prior to doing any serious work with the library. The first thread to call * this routine does the initialization process and set the magic number so no body later * can re-initialize the library. If subsequent threads call this routine before the first * thread have finished the initialization process, then the subsequent threads must block * until the initialization process is done. */ static sxi32 unqliteCoreInitialize(void) { const unqlite_kv_methods *pMethods; const unqlite_vfs *pVfs; /* Built-in vfs */ #if defined(UNQLITE_ENABLE_THREADS) const SyMutexMethods *pMutexMethods = 0; SyMutex *pMaster = 0; #endif int rc; /* * If the library is already initialized, then a call to this routine * is a no-op. */ if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){ return UNQLITE_OK; /* Already initialized */ } if( sUnqlMPGlobal.pVfs == 0 ){ /* Point to the built-in vfs */ pVfs = unqliteExportBuiltinVfs(); /* Install it */ unqlite_lib_config(UNQLITE_LIB_CONFIG_VFS, pVfs); } #if defined(UNQLITE_ENABLE_THREADS) if( sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_SINGLE ){ pMutexMethods = sUnqlMPGlobal.pMutexMethods; if( pMutexMethods == 0 ){ /* Use the built-in mutex subsystem */ pMutexMethods = SyMutexExportMethods(); if( pMutexMethods == 0 ){ return UNQLITE_CORRUPT; /* Can't happen */ } /* Install the mutex subsystem */ rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MUTEX, pMutexMethods); if( rc != UNQLITE_OK ){ return rc; } } /* Obtain a static mutex so we can initialize the library without calling malloc() */ pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1); if( pMaster == 0 ){ return UNQLITE_CORRUPT; /* Can't happen */ } } /* Lock the master mutex */ rc = UNQLITE_OK; SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ #endif if( sUnqlMPGlobal.sAllocator.pMethods == 0 ){ /* Install a memory subsystem */ rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */ if( rc != UNQLITE_OK ){ /* If we are unable to initialize the memory backend, there is no much we can do here.*/ goto End; } } #if defined(UNQLITE_ENABLE_THREADS) if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ /* Protect the memory allocation subsystem */ rc = SyMemBackendMakeThreadSafe(&sUnqlMPGlobal.sAllocator, sUnqlMPGlobal.pMutexMethods); if( rc != UNQLITE_OK ){ goto End; } } #endif SySetInit(&sUnqlMPGlobal.kv_storage,&sUnqlMPGlobal.sAllocator,sizeof(unqlite_kv_methods *)); /* Install the built-in Key Value storage engines */ pMethods = unqliteExportMemKvStorage(); /* In-memory storage */ unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods); /* Default disk key/value storage engine */ pMethods = unqliteExportDiskKvStorage(); /* Disk storage */ unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods); /* Default page size */ if( sUnqlMPGlobal.iPageSize < UNQLITE_MIN_PAGE_SIZE ){ unqlite_lib_config(UNQLITE_LIB_CONFIG_PAGE_SIZE,UNQLITE_DEFAULT_PAGE_SIZE); } /* Our library is initialized, set the magic number */ sUnqlMPGlobal.nMagic = UNQLITE_LIB_MAGIC; rc = UNQLITE_OK; #if defined(UNQLITE_ENABLE_THREADS) } /* sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC */ #endif End: #if defined(UNQLITE_ENABLE_THREADS) /* Unlock the master mutex */ SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ #endif return rc; } /* Forward declaration */ static int unqliteVmRelease(unqlite_vm *pVm); /* * Release a single instance of an unqlite database handle. */ static int unqliteDbRelease(unqlite *pDb) { unqlite_db *pStore = &pDb->sDB; unqlite_vm *pVm,*pNext; int rc = UNQLITE_OK; if( (pDb->iFlags & UNQLITE_FL_DISABLE_AUTO_COMMIT) == 0 ){ /* Commit any outstanding transaction */ rc = unqlitePagerCommit(pStore->pPager); if( rc != UNQLITE_OK ){ /* Rollback the transaction */ rc = unqlitePagerRollback(pStore->pPager,FALSE); } }else{ /* Rollback any outstanding transaction */ rc = unqlitePagerRollback(pStore->pPager,FALSE); } /* Close the pager */ unqlitePagerClose(pStore->pPager); /* Release any active VM's */ pVm = pDb->pVms; for(;;){ if( pDb->iVm < 1 ){ break; } /* Point to the next entry */ pNext = pVm->pNext; unqliteVmRelease(pVm); pVm = pNext; pDb->iVm--; } /* Release the Jx9 handle */ jx9_release(pStore->pJx9); /* Set a dummy magic number */ pDb->nMagic = 0x7250; /* Release the whole memory subsystem */ SyMemBackendRelease(&pDb->sMem); /* Commit or rollback result */ return rc; } /* * Release all resources consumed by the library. * Note: This call is not thread safe. Refer to [unqlite_lib_shutdown()]. */ static void unqliteCoreShutdown(void) { unqlite *pDb, *pNext; /* Release all active databases handles */ pDb = sUnqlMPGlobal.pDB; for(;;){ if( sUnqlMPGlobal.nDB < 1 ){ break; } pNext = pDb->pNext; unqliteDbRelease(pDb); pDb = pNext; sUnqlMPGlobal.nDB--; } /* Release the storage methods container */ SySetRelease(&sUnqlMPGlobal.kv_storage); #if defined(UNQLITE_ENABLE_THREADS) /* Release the mutex subsystem */ if( sUnqlMPGlobal.pMutexMethods ){ if( sUnqlMPGlobal.pMutex ){ SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); sUnqlMPGlobal.pMutex = 0; } if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){ sUnqlMPGlobal.pMutexMethods->xGlobalRelease(); } sUnqlMPGlobal.pMutexMethods = 0; } sUnqlMPGlobal.nThreadingLevel = 0; #endif if( sUnqlMPGlobal.sAllocator.pMethods ){ /* Release the memory backend */ SyMemBackendRelease(&sUnqlMPGlobal.sAllocator); } sUnqlMPGlobal.nMagic = 0x1764; /* Finally, shutdown the Jx9 library */ jx9_lib_shutdown(); } /* * [CAPIREF: unqlite_lib_init()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_lib_init(void) { int rc; rc = unqliteCoreInitialize(); return rc; } /* * [CAPIREF: unqlite_lib_shutdown()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_lib_shutdown(void) { if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ /* Already shut */ return UNQLITE_OK; } unqliteCoreShutdown(); return UNQLITE_OK; } /* * [CAPIREF: unqlite_lib_is_threadsafe()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_lib_is_threadsafe(void) { if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ return 0; } #if defined(UNQLITE_ENABLE_THREADS) if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ /* Muli-threading support is enabled */ return 1; }else{ /* Single-threading */ return 0; } #else return 0; #endif } /* * * [CAPIREF: unqlite_lib_version()] * Please refer to the official documentation for function purpose and expected parameters. */ const char * unqlite_lib_version(void) { return UNQLITE_VERSION; } /* * * [CAPIREF: unqlite_lib_signature()] * Please refer to the official documentation for function purpose and expected parameters. */ const char * unqlite_lib_signature(void) { return UNQLITE_SIG; } /* * * [CAPIREF: unqlite_lib_ident()] * Please refer to the official documentation for function purpose and expected parameters. */ const char * unqlite_lib_ident(void) { return UNQLITE_IDENT; } /* * * [CAPIREF: unqlite_lib_copyright()] * Please refer to the official documentation for function purpose and expected parameters. */ const char * unqlite_lib_copyright(void) { return UNQLITE_COPYRIGHT; } /* * Remove harmfull and/or stale flags passed to the [unqlite_open()] interface. */ static unsigned int unqliteSanityzeFlag(unsigned int iFlags) { iFlags &= ~UNQLITE_OPEN_EXCLUSIVE; /* Reserved flag */ if( iFlags & UNQLITE_OPEN_TEMP_DB ){ /* Omit journaling for temporary database */ iFlags |= UNQLITE_OPEN_OMIT_JOURNALING|UNQLITE_OPEN_CREATE; } if( (iFlags & (UNQLITE_OPEN_READONLY|UNQLITE_OPEN_READWRITE)) == 0 ){ /* Auto-append the R+W flag */ iFlags |= UNQLITE_OPEN_READWRITE; } if( iFlags & UNQLITE_OPEN_CREATE ){ iFlags &= ~(UNQLITE_OPEN_MMAP|UNQLITE_OPEN_READONLY); /* Auto-append the R+W flag */ iFlags |= UNQLITE_OPEN_READWRITE; }else{ if( iFlags & UNQLITE_OPEN_READONLY ){ iFlags &= ~UNQLITE_OPEN_READWRITE; }else if( iFlags & UNQLITE_OPEN_READWRITE ){ iFlags &= ~UNQLITE_OPEN_MMAP; } } return iFlags; } /* * This routine does the work of initializing a database handle on behalf * of [unqlite_open()]. */ static int unqliteInitDatabase( unqlite *pDB, /* Database handle */ SyMemBackend *pParent, /* Master memory backend */ const char *zFilename, /* Target database */ unsigned int iFlags /* Open flags */ ) { unqlite_db *pStorage = &pDB->sDB; int rc; /* Initialize the memory subsystem */ SyMemBackendInitFromParent(&pDB->sMem,pParent); SyBlobInit(&pDB->sErr,&pDB->sMem); /* Sanitize flags */ iFlags = unqliteSanityzeFlag(iFlags); /* Init the pager and the transaction manager */ rc = unqlitePagerOpen(sUnqlMPGlobal.pVfs,pDB,zFilename,iFlags); if( rc != UNQLITE_OK ){ return rc; } /* Allocate a new Jx9 engine handle */ rc = jx9_init(&pStorage->pJx9); if( rc != JX9_OK ){ return rc; } return UNQLITE_OK; } /* * Allocate and initialize a new UnQLite Virtual Mahcine and attach it * to the compiled Jx9 script. */ static int unqliteInitVm(unqlite *pDb,jx9_vm *pJx9Vm,unqlite_vm **ppOut) { unqlite_vm *pVm; *ppOut = 0; /* Allocate a new VM instance */ pVm = (unqlite_vm *)SyMemBackendPoolAlloc(&pDb->sMem,sizeof(unqlite_vm)); if( pVm == 0 ){ return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pVm,sizeof(unqlite_vm)); /* Initialize */ SyMemBackendInitFromParent(&pVm->sAlloc,&pDb->sMem); /* Allocate a new collection table */ pVm->apCol = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc,32 * sizeof(unqlite_col *)); if( pVm->apCol == 0 ){ goto fail; } pVm->iColSize = 32; /* Must be a power of two */ /* Zero the table */ SyZero((void *)pVm->apCol,pVm->iColSize * sizeof(unqlite_col *)); #if defined(UNQLITE_ENABLE_THREADS) if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ /* Associate a recursive mutex with this instance */ pVm->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); if( pVm->pMutex == 0 ){ goto fail; } } #endif /* Link the VM to the list of active virtual machines */ pVm->pJx9Vm = pJx9Vm; pVm->pDb = pDb; MACRO_LD_PUSH(pDb->pVms,pVm); pDb->iVm++; /* Register Jx9 functions */ unqliteRegisterJx9Functions(pVm); /* Set the magic number */ pVm->nMagic = JX9_VM_INIT; /* Same magic number as Jx9 */ /* All done */ *ppOut = pVm; return UNQLITE_OK; fail: SyMemBackendRelease(&pVm->sAlloc); SyMemBackendPoolFree(&pDb->sMem,pVm); return UNQLITE_NOMEM; } /* * Release an active VM. */ static int unqliteVmRelease(unqlite_vm *pVm) { /* Release the Jx9 VM */ jx9_vm_release(pVm->pJx9Vm); /* Release the private memory backend */ SyMemBackendRelease(&pVm->sAlloc); /* Upper layer will discard this VM from the list * of active VM. */ return UNQLITE_OK; } /* * Return the default page size. */ UNQLITE_PRIVATE int unqliteGetPageSize(void) { int iSize = sUnqlMPGlobal.iPageSize; if( iSize < UNQLITE_MIN_PAGE_SIZE || iSize > UNQLITE_MAX_PAGE_SIZE ){ iSize = UNQLITE_DEFAULT_PAGE_SIZE; } return iSize; } /* * Generate an error message. */ UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr) { int rc; /* Append the error message */ rc = SyBlobAppend(&pDb->sErr,(const void *)zErr,SyStrlen(zErr)); /* Append a new line */ SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char)); return rc; } /* * Generate an error message (Printf like). */ UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...) { va_list ap; int rc; va_start(ap,zFmt); rc = SyBlobFormatAp(&pDb->sErr,zFmt,ap); va_end(ap); /* Append a new line */ SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char)); return rc; } /* * Generate an error message (Out of memory). */ UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb) { int rc; rc = unqliteGenError(pDb,"unQLite is running out of memory"); return rc; } /* * Configure a working UnQLite database handle. */ static int unqliteConfigure(unqlite *pDb,int nOp,va_list ap) { int rc = UNQLITE_OK; switch(nOp){ case UNQLITE_CONFIG_JX9_ERR_LOG: /* Jx9 compile-time error log */ rc = jx9EngineConfig(pDb->sDB.pJx9,JX9_CONFIG_ERR_LOG,ap); break; case UNQLITE_CONFIG_MAX_PAGE_CACHE: { int max_page = va_arg(ap,int); /* Maximum number of page to cache (Simple hint). */ rc = unqlitePagerSetCachesize(pDb->sDB.pPager,max_page); break; } case UNQLITE_CONFIG_ERR_LOG: { /* Database error log if any */ const char **pzPtr = va_arg(ap, const char **); int *pLen = va_arg(ap, int *); if( pzPtr == 0 ){ rc = JX9_CORRUPT; break; } /* NULL terminate the error-log buffer */ SyBlobNullAppend(&pDb->sErr); /* Point to the error-log buffer */ *pzPtr = (const char *)SyBlobData(&pDb->sErr); if( pLen ){ if( SyBlobLength(&pDb->sErr) > 1 /* NULL '\0' terminator */ ){ *pLen = (int)SyBlobLength(&pDb->sErr); }else{ *pLen = 0; } } break; } case UNQLITE_CONFIG_DISABLE_AUTO_COMMIT:{ /* Disable auto-commit */ pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; break; } case UNQLITE_CONFIG_GET_KV_NAME: { /* Name of the underlying KV storage engine */ const char **pzPtr = va_arg(ap,const char **); if( pzPtr ){ unqlite_kv_engine *pEngine; pEngine = unqlitePagerGetKvEngine(pDb); /* Point to the name */ *pzPtr = pEngine->pIo->pMethods->zName; } break; } default: /* Unknown configuration option */ rc = UNQLITE_UNKNOWN; break; } return rc; } /* * Export the global (master) memory allocator to submodules. */ UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void) { return &sUnqlMPGlobal.sAllocator; } /* * [CAPIREF: unqlite_open()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode) { unqlite *pHandle; int rc; #if defined(UNTRUST) if( ppDB == 0 ){ return UNQLITE_CORRUPT; } #endif *ppDB = 0; /* One-time automatic library initialization */ rc = unqliteCoreInitialize(); if( rc != UNQLITE_OK ){ return rc; } /* Allocate a new database handle */ pHandle = (unqlite *)SyMemBackendPoolAlloc(&sUnqlMPGlobal.sAllocator, sizeof(unqlite)); if( pHandle == 0 ){ return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pHandle,sizeof(unqlite)); if( iMode < 1 ){ /* Assume a read-only database */ iMode = UNQLITE_OPEN_READONLY|UNQLITE_OPEN_MMAP; } /* Init the database */ rc = unqliteInitDatabase(pHandle,&sUnqlMPGlobal.sAllocator,zFilename,iMode); if( rc != UNQLITE_OK ){ goto Release; } #if defined(UNQLITE_ENABLE_THREADS) if( !(iMode & UNQLITE_OPEN_NOMUTEX) && (sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE) ){ /* Associate a recursive mutex with this instance */ pHandle->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); if( pHandle->pMutex == 0 ){ rc = UNQLITE_NOMEM; goto Release; } } #endif /* Link to the list of active DB handles */ #if defined(UNQLITE_ENABLE_THREADS) /* Enter the global mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ #endif MACRO_LD_PUSH(sUnqlMPGlobal.pDB,pHandle); sUnqlMPGlobal.nDB++; #if defined(UNQLITE_ENABLE_THREADS) /* Leave the global mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ #endif /* Set the magic number to identify a valid DB handle */ pHandle->nMagic = UNQLITE_DB_MAGIC; /* Make the handle available to the caller */ *ppDB = pHandle; return UNQLITE_OK; Release: SyMemBackendRelease(&pHandle->sMem); SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pHandle); return rc; } /* * [CAPIREF: unqlite_config()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_config(unqlite *pDb,int nConfigOp,...) { va_list ap; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif va_start(ap, nConfigOp); rc = unqliteConfigure(&(*pDb),nConfigOp, ap); va_end(ap); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_close()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_close(unqlite *pDb) { int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Release the database handle */ rc = unqliteDbRelease(pDb); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ /* Release DB mutex */ SyMutexRelease(sUnqlMPGlobal.pMutexMethods, pDb->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif #if defined(UNQLITE_ENABLE_THREADS) /* Enter the global mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ #endif /* Unlink from the list of active database handles */ MACRO_LD_REMOVE(sUnqlMPGlobal.pDB, pDb); sUnqlMPGlobal.nDB--; #if defined(UNQLITE_ENABLE_THREADS) /* Leave the global mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ #endif /* Release the memory chunk allocated to this handle */ SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pDb); return rc; } /* * [CAPIREF: unqlite_compile()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut) { jx9_vm *pVm; int rc; if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; } #endif /* Compile the Jx9 script first */ rc = jx9_compile(pDb->sDB.pJx9,zJx9,nByte,&pVm); if( rc == JX9_OK ){ /* Allocate a new unqlite VM instance */ rc = unqliteInitVm(pDb,pVm,ppOut); if( rc != UNQLITE_OK ){ /* Release the Jx9 VM */ jx9_vm_release(pVm); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_compile_file()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut) { jx9_vm *pVm; int rc; if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; } #endif /* Compile the Jx9 script first */ rc = jx9_compile_file(pDb->sDB.pJx9,zPath,&pVm); if( rc == JX9_OK ){ /* Allocate a new unqlite VM instance */ rc = unqliteInitVm(pDb,pVm,ppOut); if( rc != UNQLITE_OK ){ /* Release the Jx9 VM */ jx9_vm_release(pVm); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * Configure an unqlite virtual machine (Mostly Jx9 VM) instance. */ static int unqliteVmConfig(unqlite_vm *pVm,sxi32 iOp,va_list ap) { int rc; rc = jx9VmConfigure(pVm->pJx9Vm,iOp,ap); return rc; } /* * [CAPIREF: unqlite_vm_config()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_vm_config(unqlite_vm *pVm,int iOp,...) { va_list ap; int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif va_start(ap,iOp); rc = unqliteVmConfig(pVm,iOp,ap); va_end(ap); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_vm_exec()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_vm_exec(unqlite_vm *pVm) { int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Execute the Jx9 bytecode program */ rc = jx9VmByteCodeExec(pVm->pJx9Vm); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_vm_release()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_vm_release(unqlite_vm *pVm) { int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Release the VM */ rc = unqliteVmRelease(pVm); #if defined(UNQLITE_ENABLE_THREADS) /* Leave VM mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ /* Release VM mutex */ SyMutexRelease(sUnqlMPGlobal.pMutexMethods,pVm->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif if( rc == UNQLITE_OK ){ unqlite *pDb = pVm->pDb; /* Unlink from the list of active VM's */ #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif MACRO_LD_REMOVE(pDb->pVms, pVm); pDb->iVm--; /* Release the memory chunk allocated to this instance */ SyMemBackendPoolFree(&pDb->sMem,pVm); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif } return rc; } /* * [CAPIREF: unqlite_vm_reset()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_vm_reset(unqlite_vm *pVm) { int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Reset the Jx9 VM */ rc = jx9VmReset(pVm->pJx9Vm); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_vm_dump()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData) { int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Dump the Jx9 VM */ rc = jx9VmDump(pVm->pJx9Vm,xConsumer,pUserData); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_vm_extract_variable()] * Please refer to the official documentation for function purpose and expected parameters. */ unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname) { unqlite_value *pValue; SyString sVariable; if( UNQLITE_VM_MISUSE(pVm) ){ return 0; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return 0; /* Another thread have released this instance */ } #endif /* Extract the target variable */ SyStringInitFromBuf(&sVariable,zVarname,SyStrlen(zVarname)); pValue = jx9VmExtractVariable(pVm->pJx9Vm,&sVariable); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return pValue; } /* * [CAPIREF: unqlite_create_function()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_create_function(unqlite_vm *pVm, const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData) { SyString sName; int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); /* Remove leading and trailing white spaces */ SyStringFullTrim(&sName); /* Ticket 1433-003: NULL values are not allowed */ if( sName.nByte < 1 || xFunc == 0 ){ return UNQLITE_INVALID; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Install the foreign function */ rc = jx9VmInstallForeignFunction(pVm->pJx9Vm,&sName,xFunc,pUserData); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_delete_function()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_delete_function(unqlite_vm *pVm, const char *zName) { int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Unlink the foreign function */ rc = jx9DeleteFunction(pVm->pJx9Vm,zName); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_create_constant()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData) { SyString sName; int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); /* Remove leading and trailing white spaces */ SyStringFullTrim(&sName); if( sName.nByte < 1 ){ /* Empty constant name */ return UNQLITE_INVALID; } /* TICKET 1433-003: NULL pointer is harmless operation */ if( xExpand == 0 ){ return UNQLITE_INVALID; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Install the foreign constant */ rc = jx9VmRegisterConstant(pVm->pJx9Vm,&sName,xExpand,pUserData); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_delete_constant()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_delete_constant(unqlite_vm *pVm, const char *zName) { int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Unlink the foreign constant */ rc = Jx9DeleteConstant(pVm->pJx9Vm,zName); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_value_int()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_int(unqlite_value *pVal, int iValue) { return jx9_value_int(pVal,iValue); } /* * [CAPIREF: unqlite_value_int64()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_int64(unqlite_value *pVal,unqlite_int64 iValue) { return jx9_value_int64(pVal,iValue); } /* * [CAPIREF: unqlite_value_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_bool(unqlite_value *pVal, int iBool) { return jx9_value_bool(pVal,iBool); } /* * [CAPIREF: unqlite_value_null()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_null(unqlite_value *pVal) { return jx9_value_null(pVal); } /* * [CAPIREF: unqlite_value_double()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_double(unqlite_value *pVal, double Value) { return jx9_value_double(pVal,Value); } /* * [CAPIREF: unqlite_value_string()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen) { return jx9_value_string(pVal,zString,nLen); } /* * [CAPIREF: unqlite_value_string_format()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...) { va_list ap; int rc; if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ /* Invalidate any prior representation */ jx9MemObjRelease(pVal); MemObjSetType(pVal, MEMOBJ_STRING); } va_start(ap, zFormat); rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap); va_end(ap); return UNQLITE_OK; } /* * [CAPIREF: unqlite_value_reset_string_cursor()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_reset_string_cursor(unqlite_value *pVal) { return jx9_value_reset_string_cursor(pVal); } /* * [CAPIREF: unqlite_value_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_resource(unqlite_value *pVal,void *pUserData) { return jx9_value_resource(pVal,pUserData); } /* * [CAPIREF: unqlite_value_release()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_release(unqlite_value *pVal) { return jx9_value_release(pVal); } /* * [CAPIREF: unqlite_value_to_int()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_to_int(unqlite_value *pValue) { return jx9_value_to_int(pValue); } /* * [CAPIREF: unqlite_value_to_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_to_bool(unqlite_value *pValue) { return jx9_value_to_bool(pValue); } /* * [CAPIREF: unqlite_value_to_int64()] * Please refer to the official documentation for function purpose and expected parameters. */ unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue) { return jx9_value_to_int64(pValue); } /* * [CAPIREF: unqlite_value_to_double()] * Please refer to the official documentation for function purpose and expected parameters. */ double unqlite_value_to_double(unqlite_value *pValue) { return jx9_value_to_double(pValue); } /* * [CAPIREF: unqlite_value_to_string()] * Please refer to the official documentation for function purpose and expected parameters. */ const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen) { return jx9_value_to_string(pValue,pLen); } /* * [CAPIREF: unqlite_value_to_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ void * unqlite_value_to_resource(unqlite_value *pValue) { return jx9_value_to_resource(pValue); } /* * [CAPIREF: unqlite_value_compare()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict) { return jx9_value_compare(pLeft,pRight,bStrict); } /* * [CAPIREF: unqlite_result_int()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_int(unqlite_context *pCtx, int iValue) { return jx9_result_int(pCtx,iValue); } /* * [CAPIREF: unqlite_result_int64()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue) { return jx9_result_int64(pCtx,iValue); } /* * [CAPIREF: unqlite_result_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_bool(unqlite_context *pCtx, int iBool) { return jx9_result_bool(pCtx,iBool); } /* * [CAPIREF: unqlite_result_double()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_double(unqlite_context *pCtx, double Value) { return jx9_result_double(pCtx,Value); } /* * [CAPIREF: unqlite_result_null()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_null(unqlite_context *pCtx) { return jx9_result_null(pCtx); } /* * [CAPIREF: unqlite_result_string()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen) { return jx9_result_string(pCtx,zString,nLen); } /* * [CAPIREF: unqlite_result_string_format()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...) { jx9_value *p; va_list ap; int rc; p = pCtx->pRet; if( (p->iFlags & MEMOBJ_STRING) == 0 ){ /* Invalidate any prior representation */ jx9MemObjRelease(p); MemObjSetType(p, MEMOBJ_STRING); } /* Format the given string */ va_start(ap, zFormat); rc = SyBlobFormatAp(&p->sBlob, zFormat, ap); va_end(ap); return rc; } /* * [CAPIREF: unqlite_result_value()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue) { return jx9_result_value(pCtx,pValue); } /* * [CAPIREF: unqlite_result_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_result_resource(unqlite_context *pCtx, void *pUserData) { return jx9_result_resource(pCtx,pUserData); } /* * [CAPIREF: unqlite_value_is_int()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_int(unqlite_value *pVal) { return jx9_value_is_int(pVal); } /* * [CAPIREF: unqlite_value_is_float()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_float(unqlite_value *pVal) { return jx9_value_is_float(pVal); } /* * [CAPIREF: unqlite_value_is_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_bool(unqlite_value *pVal) { return jx9_value_is_bool(pVal); } /* * [CAPIREF: unqlite_value_is_string()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_string(unqlite_value *pVal) { return jx9_value_is_string(pVal); } /* * [CAPIREF: unqlite_value_is_null()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_null(unqlite_value *pVal) { return jx9_value_is_null(pVal); } /* * [CAPIREF: unqlite_value_is_numeric()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_numeric(unqlite_value *pVal) { return jx9_value_is_numeric(pVal); } /* * [CAPIREF: unqlite_value_is_callable()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_callable(unqlite_value *pVal) { return jx9_value_is_callable(pVal); } /* * [CAPIREF: unqlite_value_is_scalar()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_scalar(unqlite_value *pVal) { return jx9_value_is_scalar(pVal); } /* * [CAPIREF: unqlite_value_is_json_array()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_json_array(unqlite_value *pVal) { return jx9_value_is_json_array(pVal); } /* * [CAPIREF: unqlite_value_is_json_object()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_json_object(unqlite_value *pVal) { return jx9_value_is_json_object(pVal); } /* * [CAPIREF: unqlite_value_is_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_resource(unqlite_value *pVal) { return jx9_value_is_resource(pVal); } /* * [CAPIREF: unqlite_value_is_empty()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_value_is_empty(unqlite_value *pVal) { return jx9_value_is_empty(pVal); } /* * [CAPIREF: unqlite_array_fetch()] * Please refer to the official documentation for function purpose and expected parameters. */ unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte) { return jx9_array_fetch(pArray,zKey,nByte); } /* * [CAPIREF: unqlite_array_walk()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData) { return jx9_array_walk(pArray,xWalk,pUserData); } /* * [CAPIREF: unqlite_array_add_elem()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue) { return jx9_array_add_elem(pArray,pKey,pValue); } /* * [CAPIREF: unqlite_array_add_strkey_elem()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue) { return jx9_array_add_strkey_elem(pArray,zKey,pValue); } /* * [CAPIREF: unqlite_array_count()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_array_count(unqlite_value *pArray) { return (int)jx9_array_count(pArray); } /* * [CAPIREF: unqlite_vm_new_scalar()] * Please refer to the official documentation for function purpose and expected parameters. */ unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm) { unqlite_value *pValue; if( UNQLITE_VM_MISUSE(pVm) ){ return 0; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return 0; /* Another thread have released this instance */ } #endif pValue = jx9_new_scalar(pVm->pJx9Vm); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return pValue; } /* * [CAPIREF: unqlite_vm_new_array()] * Please refer to the official documentation for function purpose and expected parameters. */ unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm) { unqlite_value *pValue; if( UNQLITE_VM_MISUSE(pVm) ){ return 0; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return 0; /* Another thread have released this instance */ } #endif pValue = jx9_new_array(pVm->pJx9Vm); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return pValue; } /* * [CAPIREF: unqlite_vm_release_value()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue) { int rc; if( UNQLITE_VM_MISUSE(pVm) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_VM_RELEASE(pVm) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif rc = jx9_release_value(pVm->pJx9Vm,pValue); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_context_output()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen) { return jx9_context_output(pCtx,zString,nLen); } /* * [CAPIREF: unqlite_context_output_format()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...) { va_list ap; int rc; va_start(ap, zFormat); rc = jx9VmOutputConsumeAp(pCtx->pVm,zFormat, ap); va_end(ap); return rc; } /* * [CAPIREF: unqlite_context_throw_error()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr) { return jx9_context_throw_error(pCtx,iErr,zErr); } /* * [CAPIREF: unqlite_context_throw_error_format()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...) { va_list ap; int rc; if( zFormat == 0){ return JX9_OK; } va_start(ap, zFormat); rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap); va_end(ap); return rc; } /* * [CAPIREF: unqlite_context_random_num()] * Please refer to the official documentation for function purpose and expected parameters. */ unsigned int unqlite_context_random_num(unqlite_context *pCtx) { return jx9_context_random_num(pCtx); } /* * [CAPIREF: unqlite_context_random_string()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen) { return jx9_context_random_string(pCtx,zBuf,nBuflen); } /* * [CAPIREF: unqlite_context_user_data()] * Please refer to the official documentation for function purpose and expected parameters. */ void * unqlite_context_user_data(unqlite_context *pCtx) { return jx9_context_user_data(pCtx); } /* * [CAPIREF: unqlite_context_push_aux_data()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData) { return jx9_context_push_aux_data(pCtx,pUserData); } /* * [CAPIREF: unqlite_context_peek_aux_data()] * Please refer to the official documentation for function purpose and expected parameters. */ void * unqlite_context_peek_aux_data(unqlite_context *pCtx) { return jx9_context_peek_aux_data(pCtx); } /* * [CAPIREF: unqlite_context_pop_aux_data()] * Please refer to the official documentation for function purpose and expected parameters. */ void * unqlite_context_pop_aux_data(unqlite_context *pCtx) { return jx9_context_pop_aux_data(pCtx); } /* * [CAPIREF: unqlite_context_result_buf_length()] * Please refer to the official documentation for function purpose and expected parameters. */ unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx) { return jx9_context_result_buf_length(pCtx); } /* * [CAPIREF: unqlite_function_name()] * Please refer to the official documentation for function purpose and expected parameters. */ const char * unqlite_function_name(unqlite_context *pCtx) { return jx9_function_name(pCtx); } /* * [CAPIREF: unqlite_context_new_scalar()] * Please refer to the official documentation for function purpose and expected parameters. */ unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx) { return jx9_context_new_scalar(pCtx); } /* * [CAPIREF: unqlite_context_new_array()] * Please refer to the official documentation for function purpose and expected parameters. */ unqlite_value * unqlite_context_new_array(unqlite_context *pCtx) { return jx9_context_new_array(pCtx); } /* * [CAPIREF: unqlite_context_release_value()] * Please refer to the official documentation for function purpose and expected parameters. */ void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue) { jx9_context_release_value(pCtx,pValue); } /* * [CAPIREF: unqlite_context_alloc_chunk()] * Please refer to the official documentation for function purpose and expected parameters. */ void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease) { return jx9_context_alloc_chunk(pCtx,nByte,ZeroChunk,AutoRelease); } /* * [CAPIREF: unqlite_context_realloc_chunk()] * Please refer to the official documentation for function purpose and expected parameters. */ void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte) { return jx9_context_realloc_chunk(pCtx,pChunk,nByte); } /* * [CAPIREF: unqlite_context_free_chunk()] * Please refer to the official documentation for function purpose and expected parameters. */ void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk) { jx9_context_free_chunk(pCtx,pChunk); } /* * [CAPIREF: unqlite_kv_store()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen) { unqlite_kv_engine *pEngine; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); if( pEngine->pIo->pMethods->xReplace == 0 ){ /* Storage engine does not implement such method */ unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine"); rc = UNQLITE_NOTIMPLEMENTED; }else{ if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ unqliteGenError(pDb,"Empty key"); rc = UNQLITE_EMPTY; }else{ /* Perform the requested operation */ rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,pData,nDataLen); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_store_fmt()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...) { unqlite_kv_engine *pEngine; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); if( pEngine->pIo->pMethods->xReplace == 0 ){ /* Storage engine does not implement such method */ unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine"); rc = UNQLITE_NOTIMPLEMENTED; }else{ if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ unqliteGenError(pDb,"Empty key"); rc = UNQLITE_EMPTY; }else{ SyBlob sWorker; /* Working buffer */ va_list ap; SyBlobInit(&sWorker,&pDb->sMem); /* Format the data */ va_start(ap,zFormat); SyBlobFormatAp(&sWorker,zFormat,ap); va_end(ap); /* Perform the requested operation */ rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker)); /* Clean up */ SyBlobRelease(&sWorker); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_append()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen) { unqlite_kv_engine *pEngine; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); if( pEngine->pIo->pMethods->xAppend == 0 ){ /* Storage engine does not implement such method */ unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine"); rc = UNQLITE_NOTIMPLEMENTED; }else{ if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ unqliteGenError(pDb,"Empty key"); rc = UNQLITE_EMPTY; }else{ /* Perform the requested operation */ rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,pData,nDataLen); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_append_fmt()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...) { unqlite_kv_engine *pEngine; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); if( pEngine->pIo->pMethods->xAppend == 0 ){ /* Storage engine does not implement such method */ unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine"); rc = UNQLITE_NOTIMPLEMENTED; }else{ if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ unqliteGenError(pDb,"Empty key"); rc = UNQLITE_EMPTY; }else{ SyBlob sWorker; /* Working buffer */ va_list ap; SyBlobInit(&sWorker,&pDb->sMem); /* Format the data */ va_start(ap,zFormat); SyBlobFormatAp(&sWorker,zFormat,ap); va_end(ap); /* Perform the requested operation */ rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker)); /* Clean up */ SyBlobRelease(&sWorker); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_fetch()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 *pBufLen) { unqlite_kv_methods *pMethods; unqlite_kv_engine *pEngine; unqlite_kv_cursor *pCur; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); pMethods = pEngine->pIo->pMethods; pCur = pDb->sDB.pCursor; if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ unqliteGenError(pDb,"Empty key"); rc = UNQLITE_EMPTY; }else{ /* Seek to the record position */ rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); } if( rc == UNQLITE_OK ){ if( pBuf == 0 ){ /* Data length only */ rc = pMethods->xDataLength(pCur,pBufLen); }else{ SyBlob sBlob; /* Initialize the data consumer */ SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)*pBufLen); /* Consume the data */ rc = pMethods->xData(pCur,unqliteDataConsumer,&sBlob); /* Data length */ *pBufLen = (unqlite_int64)SyBlobLength(&sBlob); /* Cleanup */ SyBlobRelease(&sBlob); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_fetch_callback()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey,int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) { unqlite_kv_methods *pMethods; unqlite_kv_engine *pEngine; unqlite_kv_cursor *pCur; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); pMethods = pEngine->pIo->pMethods; pCur = pDb->sDB.pCursor; if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ unqliteGenError(pDb,"Empty key"); rc = UNQLITE_EMPTY; }else{ /* Seek to the record position */ rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); } if( rc == UNQLITE_OK && xConsumer ){ /* Consume the data directly */ rc = pMethods->xData(pCur,xConsumer,pUserData); } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_delete()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen) { unqlite_kv_methods *pMethods; unqlite_kv_engine *pEngine; unqlite_kv_cursor *pCur; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); pMethods = pEngine->pIo->pMethods; pCur = pDb->sDB.pCursor; if( pMethods->xDelete == 0 ){ /* Storage engine does not implement such method */ unqliteGenError(pDb,"xDelete() method not implemented in the underlying storage engine"); rc = UNQLITE_NOTIMPLEMENTED; }else{ if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ unqliteGenError(pDb,"Empty key"); rc = UNQLITE_EMPTY; }else{ /* Seek to the record position */ rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); } if( rc == UNQLITE_OK ){ /* Exact match found, delete the entry */ rc = pMethods->xDelete(pCur); } } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_config()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_config(unqlite *pDb,int iOp,...) { unqlite_kv_engine *pEngine; int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Point to the underlying storage engine */ pEngine = unqlitePagerGetKvEngine(pDb); if( pEngine->pIo->pMethods->xConfig == 0 ){ /* Storage engine does not implements such method */ unqliteGenError(pDb,"xConfig() method not implemented in the underlying storage engine"); rc = UNQLITE_NOTIMPLEMENTED; }else{ va_list ap; /* Configure the storage engine */ va_start(ap,iOp); rc = pEngine->pIo->pMethods->xConfig(pEngine,iOp,ap); va_end(ap); } #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_cursor_init()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut) { int rc; if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0 /* Noop */){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Allocate a new cursor */ rc = unqliteInitCursor(pDb,ppOut); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_cursor_release()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur) { int rc; if( UNQLITE_DB_MISUSE(pDb) || pCur == 0 /* Noop */){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Release the cursor */ rc = unqliteReleaseCursor(pDb,pCur); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_kv_cursor_first_entry()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Check if the requested method is implemented by the underlying storage engine */ if( pCursor->pStore->pIo->pMethods->xFirst == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ /* Seek to the first entry */ rc = pCursor->pStore->pIo->pMethods->xFirst(pCursor); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_last_entry()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Check if the requested method is implemented by the underlying storage engine */ if( pCursor->pStore->pIo->pMethods->xLast == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ /* Seek to the last entry */ rc = pCursor->pStore->pIo->pMethods->xLast(pCursor); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_valid_entry()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Check if the requested method is implemented by the underlying storage engine */ if( pCursor->pStore->pIo->pMethods->xValid == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ rc = pCursor->pStore->pIo->pMethods->xValid(pCursor); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_next_entry()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Check if the requested method is implemented by the underlying storage engine */ if( pCursor->pStore->pIo->pMethods->xNext == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ /* Seek to the next entry */ rc = pCursor->pStore->pIo->pMethods->xNext(pCursor); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_prev_entry()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Check if the requested method is implemented by the underlying storage engine */ if( pCursor->pStore->pIo->pMethods->xPrev == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ /* Seek to the previous entry */ rc = pCursor->pStore->pIo->pMethods->xPrev(pCursor); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_delete_entry()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Check if the requested method is implemented by the underlying storage engine */ if( pCursor->pStore->pIo->pMethods->xDelete == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ /* Delete the entry */ rc = pCursor->pStore->pIo->pMethods->xDelete(pCursor); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_reset()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor) { int rc = UNQLITE_OK; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Check if the requested method is implemented by the underlying storage engine */ if( pCursor->pStore->pIo->pMethods->xReset == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ /* Reset */ pCursor->pStore->pIo->pMethods->xReset(pCursor); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_seek()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos) { int rc = UNQLITE_OK; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif if( nKeyLen < 0 ){ /* Assume a null terminated string and compute it's length */ nKeyLen = SyStrlen((const char *)pKey); } if( !nKeyLen ){ rc = UNQLITE_EMPTY; }else{ /* Seek to the desired location */ rc = pCursor->pStore->pIo->pMethods->xSeek(pCursor,pKey,nKeyLen,iPos); } return rc; } /* * Default data consumer callback. That is, all retrieved is redirected to this * routine which store the output in an internal blob. */ UNQLITE_PRIVATE int unqliteDataConsumer( const void *pOut, /* Data to consume */ unsigned int nLen, /* Data length */ void *pUserData /* User private data */ ) { sxi32 rc; /* Store the output in an internal BLOB */ rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen); return rc; } /* * [CAPIREF: unqlite_kv_cursor_data_callback()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Consume the key directly */ rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,xConsumer,pUserData); return rc; } /* * [CAPIREF: unqlite_kv_cursor_key()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif if( pBuf == 0 ){ /* Key length only */ rc = pCursor->pStore->pIo->pMethods->xKeyLength(pCursor,pnByte); }else{ SyBlob sBlob; if( (*pnByte) < 0 ){ return UNQLITE_CORRUPT; } /* Initialize the data consumer */ SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte)); /* Consume the key */ rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,unqliteDataConsumer,&sBlob); /* Key length */ *pnByte = SyBlobLength(&sBlob); /* Cleanup */ SyBlobRelease(&sBlob); } return rc; } /* * [CAPIREF: unqlite_kv_cursor_data_callback()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif /* Consume the data directly */ rc = pCursor->pStore->pIo->pMethods->xData(pCursor,xConsumer,pUserData); return rc; } /* * [CAPIREF: unqlite_kv_cursor_data()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnByte) { int rc; #ifdef UNTRUST if( pCursor == 0 ){ return UNQLITE_CORRUPT; } #endif if( pBuf == 0 ){ /* Data length only */ rc = pCursor->pStore->pIo->pMethods->xDataLength(pCursor,pnByte); }else{ SyBlob sBlob; if( (*pnByte) < 0 ){ return UNQLITE_CORRUPT; } /* Initialize the data consumer */ SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte)); /* Consume the data */ rc = pCursor->pStore->pIo->pMethods->xData(pCursor,unqliteDataConsumer,&sBlob); /* Data length */ *pnByte = SyBlobLength(&sBlob); /* Cleanup */ SyBlobRelease(&sBlob); } return rc; } /* * [CAPIREF: unqlite_begin()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_begin(unqlite *pDb) { int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Begin the write transaction */ rc = unqlitePagerBegin(pDb->sDB.pPager); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_commit()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_commit(unqlite *pDb) { int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Commit the transaction */ rc = unqlitePagerCommit(pDb->sDB.pPager); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_rollback()] * Please refer to the official documentation for function purpose and expected parameters. */ int unqlite_rollback(unqlite *pDb) { int rc; if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Rollback the transaction */ rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: unqlite_util_load_mmaped_file()] * Please refer to the official documentation for function purpose and expected parameters. */ UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize) { const jx9_vfs *pVfs; int rc; if( SX_EMPTY_STR(zFile) || ppMap == 0 || pFileSize == 0){ /* Sanity check */ return UNQLITE_CORRUPT; } *ppMap = 0; /* Extract the Jx9 Vfs */ pVfs = jx9ExportBuiltinVfs(); /* * Check if the underlying vfs implement the memory map routines * [i.e: mmap() under UNIX/MapViewOfFile() under windows]. */ if( pVfs == 0 || pVfs->xMmap == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ /* Try to get a read-only memory view of the whole file */ rc = pVfs->xMmap(zFile,ppMap,pFileSize); } return rc; } /* * [CAPIREF: unqlite_util_release_mmaped_file()] * Please refer to the official documentation for function purpose and expected parameters. */ UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize) { const jx9_vfs *pVfs; int rc = UNQLITE_OK; if( pMap == 0 ){ return UNQLITE_OK; } /* Extract the Jx9 Vfs */ pVfs = jx9ExportBuiltinVfs(); if( pVfs == 0 || pVfs->xUnmap == 0 ){ rc = UNQLITE_NOTIMPLEMENTED; }else{ pVfs->xUnmap(pMap,iFileSize); } return rc; } /* * [CAPIREF: unqlite_util_random_string()] * Please refer to the official documentation for function purpose and expected parameters. */ UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size) { if( UNQLITE_DB_MISUSE(pDb) ){ return UNQLITE_CORRUPT; } if( zBuf == 0 || buf_size < 3 ){ /* Buffer must be long enough to hold three bytes */ return UNQLITE_INVALID; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return UNQLITE_ABORT; /* Another thread have released this instance */ } #endif /* Generate the random string */ unqlitePagerRandomString(pDb->sDB.pPager,zBuf,buf_size); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return UNQLITE_OK; } /* * [CAPIREF: unqlite_util_random_num()] * Please refer to the official documentation for function purpose and expected parameters. */ UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb) { sxu32 iNum; if( UNQLITE_DB_MISUSE(pDb) ){ return 0; } #if defined(UNQLITE_ENABLE_THREADS) /* Acquire DB mutex */ SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && UNQLITE_THRD_DB_RELEASE(pDb) ){ return 0; /* Another thread have released this instance */ } #endif /* Generate the random number */ iNum = unqlitePagerRandomNum(pDb->sDB.pPager); #if defined(UNQLITE_ENABLE_THREADS) /* Leave DB mutex */ SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ #endif return iNum; } /* * ---------------------------------------------------------- * File: bitvec.c * MD5: 7e3376710d8454ebcf8c77baacca880f * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: bitvec.c v1.0 Win7 2013-02-27 15:16 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /** This file implements an object that represents a dynmaic ** bitmap. ** ** A bitmap is used to record which pages of a database file have been ** journalled during a transaction, or which pages have the "dont-write" ** property. Usually only a few pages are meet either condition. ** So the bitmap is usually sparse and has low cardinality. */ /* * Actually, this is not a bitmap but a simple hashtable where page * number (64-bit unsigned integers) are used as the lookup keys. */ typedef struct bitvec_rec bitvec_rec; struct bitvec_rec { pgno iPage; /* Page number */ bitvec_rec *pNext,*pNextCol; /* Collison link */ }; struct Bitvec { SyMemBackend *pAlloc; /* Memory allocator */ sxu32 nRec; /* Total number of records */ sxu32 nSize; /* Table size */ bitvec_rec **apRec; /* Record table */ bitvec_rec *pList; /* List of records */ }; /* * Allocate a new bitvec instance. */ UNQLITE_PRIVATE Bitvec * unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize) { bitvec_rec **apNew; Bitvec *p; p = (Bitvec *)SyMemBackendAlloc(pAlloc,sizeof(*p) ); if( p == 0 ){ SXUNUSED(iSize); /* cc warning */ return 0; } /* Zero the structure */ SyZero(p,sizeof(Bitvec)); /* Allocate a new table */ p->nSize = 64; /* Must be a power of two */ apNew = (bitvec_rec **)SyMemBackendAlloc(pAlloc,p->nSize * sizeof(bitvec_rec *)); if( apNew == 0 ){ SyMemBackendFree(pAlloc,p); return 0; } /* Zero the new table */ SyZero((void *)apNew,p->nSize * sizeof(bitvec_rec *)); /* Fill-in */ p->apRec = apNew; p->pAlloc = pAlloc; return p; } /* * Check if the given page number is already installed in the table. * Return true if installed. False otherwise. */ UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i) { bitvec_rec *pRec; /* Point to the desired bucket */ pRec = p->apRec[i & (p->nSize - 1)]; for(;;){ if( pRec == 0 ){ break; } if( pRec->iPage == i ){ /* Page found */ return 1; } /* Point to the next entry */ pRec = pRec->pNextCol; if( pRec == 0 ){ break; } if( pRec->iPage == i ){ /* Page found */ return 1; } /* Point to the next entry */ pRec = pRec->pNextCol; if( pRec == 0 ){ break; } if( pRec->iPage == i ){ /* Page found */ return 1; } /* Point to the next entry */ pRec = pRec->pNextCol; if( pRec == 0 ){ break; } if( pRec->iPage == i ){ /* Page found */ return 1; } /* Point to the next entry */ pRec = pRec->pNextCol; } /* No such entry */ return 0; } /* * Install a given page number in our bitmap (Actually, our hashtable). */ UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i) { bitvec_rec *pRec; sxi32 iBuck; /* Allocate a new instance */ pRec = (bitvec_rec *)SyMemBackendPoolAlloc(p->pAlloc,sizeof(bitvec_rec)); if( pRec == 0 ){ return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pRec,sizeof(bitvec_rec)); /* Fill-in */ pRec->iPage = i; iBuck = i & (p->nSize - 1); pRec->pNextCol = p->apRec[iBuck]; p->apRec[iBuck] = pRec; pRec->pNext = p->pList; p->pList = pRec; p->nRec++; if( p->nRec >= (p->nSize * 3) && p->nRec < 100000 ){ /* Grow the hashtable */ sxu32 nNewSize = p->nSize << 1; bitvec_rec *pEntry,**apNew; sxu32 n; apNew = (bitvec_rec **)SyMemBackendAlloc(p->pAlloc, nNewSize * sizeof(bitvec_rec *)); if( apNew ){ sxu32 iBucket; /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(bitvec_rec *)); /* Rehash all entries */ n = 0; pEntry = p->pList; for(;;){ /* Loop one */ if( n >= p->nRec ){ break; } pEntry->pNextCol = 0; /* Install in the new bucket */ iBucket = pEntry->iPage & (nNewSize - 1); pEntry->pNextCol = apNew[iBucket]; apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; } /* Release the old table and reflect the change */ SyMemBackendFree(p->pAlloc,(void *)p->apRec); p->apRec = apNew; p->nSize = nNewSize; } } return UNQLITE_OK; } /* * Destroy a bitvec instance. Reclaim all memory used. */ UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p) { bitvec_rec *pNext,*pRec = p->pList; SyMemBackend *pAlloc = p->pAlloc; for(;;){ if( p->nRec < 1 ){ break; } pNext = pRec->pNext; SyMemBackendPoolFree(pAlloc,(void *)pRec); pRec = pNext; p->nRec--; if( p->nRec < 1 ){ break; } pNext = pRec->pNext; SyMemBackendPoolFree(pAlloc,(void *)pRec); pRec = pNext; p->nRec--; if( p->nRec < 1 ){ break; } pNext = pRec->pNext; SyMemBackendPoolFree(pAlloc,(void *)pRec); pRec = pNext; p->nRec--; if( p->nRec < 1 ){ break; } pNext = pRec->pNext; SyMemBackendPoolFree(pAlloc,(void *)pRec); pRec = pNext; p->nRec--; } SyMemBackendFree(pAlloc,(void *)p->apRec); SyMemBackendFree(pAlloc,p); } /* * ---------------------------------------------------------- * File: fastjson.c * MD5: 3693c0022edc7d37b65124d7aef68397 * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: fastjson.c v1.1 FreeBSD 2012-12-05 22:52 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* JSON binary encoding, decoding and stuff like that */ #ifndef UNQLITE_FAST_JSON_NEST_LIMIT #if defined(__WINNT__) || defined(__UNIXES__) #define UNQLITE_FAST_JSON_NEST_LIMIT 64 /* Nesting limit */ #else #define UNQLITE_FAST_JSON_NEST_LIMIT 32 /* Nesting limit */ #endif #endif /* UNQLITE_FAST_JSON_NEST_LIMIT */ /* * JSON to Binary using the FastJSON implementation (BigEndian). */ /* * FastJSON implemented binary token. */ #define FJSON_DOC_START 1 /* { */ #define FJSON_DOC_END 2 /* } */ #define FJSON_ARRAY_START 3 /* [ */ #define FJSON_ARRAY_END 4 /* ] */ #define FJSON_COLON 5 /* : */ #define FJSON_COMMA 6 /* , */ #define FJSON_ID 7 /* ID + 4 Bytes length */ #define FJSON_STRING 8 /* String + 4 bytes length */ #define FJSON_BYTE 9 /* Byte */ #define FJSON_INT64 10 /* Integer 64 + 8 bytes */ #define FJSON_REAL 18 /* Floating point value + 2 bytes */ #define FJSON_NULL 23 /* NULL */ #define FJSON_TRUE 24 /* TRUE */ #define FJSON_FALSE 25 /* FALSE */ /* * Encode a Jx9 value to binary JSON. */ UNQLITE_PRIVATE sxi32 FastJsonEncode( jx9_value *pValue, /* Value to encode */ SyBlob *pOut, /* Store encoded value here */ int iNest /* Nesting limit */ ) { sxi32 iType = pValue ? pValue->iFlags : MEMOBJ_NULL; sxi32 rc = SXRET_OK; int c; if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ /* Nesting limit reached */ return SXERR_LIMIT; } if( iType & (MEMOBJ_NULL|MEMOBJ_RES) ){ /* * Resources are encoded as null also. */ c = FJSON_NULL; rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); }else if( iType & MEMOBJ_BOOL ){ c = pValue->x.iVal ? FJSON_TRUE : FJSON_FALSE; rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); }else if( iType & MEMOBJ_STRING ){ unsigned char zBuf[sizeof(sxu32)]; /* String length */ c = FJSON_STRING; SyBigEndianPack32(zBuf,SyBlobLength(&pValue->sBlob)); rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc == SXRET_OK ){ rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); if( rc == SXRET_OK ){ rc = SyBlobAppend(pOut,SyBlobData(&pValue->sBlob),SyBlobLength(&pValue->sBlob)); } } }else if( iType & MEMOBJ_INT ){ unsigned char zBuf[8]; /* 64bit big endian integer */ c = FJSON_INT64; rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc == SXRET_OK ){ SyBigEndianPack64(zBuf,(sxu64)pValue->x.iVal); rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); } }else if( iType & MEMOBJ_REAL ){ /* Real number */ c = FJSON_REAL; rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc == SXRET_OK ){ sxu32 iOfft = SyBlobLength(pOut); rc = SyBlobAppendBig16(pOut,0); if( rc == SXRET_OK ){ unsigned char *zBlob; SyBlobFormat(pOut,"%.15g",pValue->x.rVal); zBlob = (unsigned char *)SyBlobDataAt(pOut,iOfft); SyBigEndianPack16(zBlob,(sxu16)(SyBlobLength(pOut) - ( 2 + iOfft))); } } }else if( iType & MEMOBJ_HASHMAP ){ /* A JSON object or array */ jx9_hashmap *pMap = (jx9_hashmap *)pValue->x.pOther; jx9_hashmap_node *pNode; jx9_value *pEntry; /* Reset the hashmap loop cursor */ jx9HashmapResetLoopCursor(pMap); if( pMap->iFlags & HASHMAP_JSON_OBJECT ){ jx9_value sKey; /* A JSON object */ c = FJSON_DOC_START; /* { */ rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc == SXRET_OK ){ jx9MemObjInit(pMap->pVm,&sKey); /* Encode object entries */ while((pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ /* Extract the key */ jx9HashmapExtractNodeKey(pNode,&sKey); /* Encode it */ rc = FastJsonEncode(&sKey,pOut,iNest+1); if( rc != SXRET_OK ){ break; } c = FJSON_COLON; rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc != SXRET_OK ){ break; } /* Extract the value */ pEntry = jx9HashmapGetNodeValue(pNode); /* Encode it */ rc = FastJsonEncode(pEntry,pOut,iNest+1); if( rc != SXRET_OK ){ break; } /* Delimit the entry */ c = FJSON_COMMA; rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc != SXRET_OK ){ break; } } jx9MemObjRelease(&sKey); if( rc == SXRET_OK ){ c = FJSON_DOC_END; /* } */ rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); } } }else{ /* A JSON array */ c = FJSON_ARRAY_START; /* [ */ rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc == SXRET_OK ){ /* Encode array entries */ while( (pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ /* Extract the value */ pEntry = jx9HashmapGetNodeValue(pNode); /* Encode it */ rc = FastJsonEncode(pEntry,pOut,iNest+1); if( rc != SXRET_OK ){ break; } /* Delimit the entry */ c = FJSON_COMMA; rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); if( rc != SXRET_OK ){ break; } } if( rc == SXRET_OK ){ c = FJSON_ARRAY_END; /* ] */ rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); } } } } return rc; } /* * Decode a FastJSON binary blob. */ UNQLITE_PRIVATE sxi32 FastJsonDecode( const void *pIn, /* Binary JSON */ sxu32 nByte, /* Chunk delimiter */ jx9_value *pOut, /* Decoded value */ const unsigned char **pzPtr, int iNest /* Nesting limit */ ) { const unsigned char *zIn = (const unsigned char *)pIn; const unsigned char *zEnd = &zIn[nByte]; sxi32 rc = SXRET_OK; int c; if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ /* Nesting limit reached */ return SXERR_LIMIT; } c = zIn[0]; /* Advance the stream cursor */ zIn++; /* Process the binary token */ switch(c){ case FJSON_NULL: /* null */ jx9_value_null(pOut); break; case FJSON_FALSE: /* Boolean FALSE */ jx9_value_bool(pOut,0); break; case FJSON_TRUE: /* Boolean TRUE */ jx9_value_bool(pOut,1); break; case FJSON_INT64: { /* 64Bit integer */ sxu64 iVal; /* Sanity check */ if( &zIn[8] >= zEnd ){ /* Corrupt chunk */ rc = SXERR_CORRUPT; break; } SyBigEndianUnpack64(zIn,&iVal); /* Advance the pointer */ zIn += 8; jx9_value_int64(pOut,(jx9_int64)iVal); break; } case FJSON_REAL: { /* Real number */ double iVal = 0; /* cc warning */ sxu16 iLen; /* Sanity check */ if( &zIn[2] >= zEnd ){ /* Corrupt chunk */ rc = SXERR_CORRUPT; break; } SyBigEndianUnpack16(zIn,&iLen); if( &zIn[iLen] >= zEnd ){ /* Corrupt chunk */ rc = SXERR_CORRUPT; break; } zIn += 2; SyStrToReal((const char *)zIn,(sxu32)iLen,&iVal,0); /* Advance the pointer */ zIn += iLen; jx9_value_double(pOut,iVal); break; } case FJSON_STRING: { /* UTF-8/Binary chunk */ sxu32 iLength; /* Sanity check */ if( &zIn[4] >= zEnd ){ /* Corrupt chunk */ rc = SXERR_CORRUPT; break; } SyBigEndianUnpack32(zIn,&iLength); if( &zIn[iLength] >= zEnd ){ /* Corrupt chunk */ rc = SXERR_CORRUPT; break; } zIn += 4; /* Invalidate any prior representation */ if( pOut->iFlags & MEMOBJ_STRING ){ /* Reset the string cursor */ SyBlobReset(&pOut->sBlob); } rc = jx9MemObjStringAppend(pOut,(const char *)zIn,iLength); /* Update pointer */ zIn += iLength; break; } case FJSON_ARRAY_START: { /* Binary JSON array */ jx9_hashmap *pMap; jx9_value sVal; /* Allocate a new hashmap */ pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); if( pMap == 0 ){ rc = SXERR_MEM; break; } jx9MemObjInit(pOut->pVm,&sVal); jx9MemObjRelease(pOut); MemObjSetType(pOut,MEMOBJ_HASHMAP); pOut->x.pOther = pMap; rc = SXRET_OK; for(;;){ /* Jump leading binary commas */ while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ zIn++; } if( zIn >= zEnd || zIn[0] == FJSON_ARRAY_END ){ if( zIn < zEnd ){ zIn++; /* Jump the trailing binary ] */ } break; } /* Decode the value */ rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); if( rc != SXRET_OK ){ break; } /* Insert the decoded value */ rc = jx9HashmapInsert(pMap,0,&sVal); if( rc != UNQLITE_OK ){ break; } } if( rc != SXRET_OK ){ jx9MemObjRelease(pOut); } jx9MemObjRelease(&sVal); break; } case FJSON_DOC_START: { /* Binary JSON object */ jx9_value sVal,sKey; jx9_hashmap *pMap; /* Allocate a new hashmap */ pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); if( pMap == 0 ){ rc = SXERR_MEM; break; } jx9MemObjInit(pOut->pVm,&sVal); jx9MemObjInit(pOut->pVm,&sKey); jx9MemObjRelease(pOut); MemObjSetType(pOut,MEMOBJ_HASHMAP); pOut->x.pOther = pMap; rc = SXRET_OK; for(;;){ /* Jump leading binary commas */ while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ zIn++; } if( zIn >= zEnd || zIn[0] == FJSON_DOC_END ){ if( zIn < zEnd ){ zIn++; /* Jump the trailing binary } */ } break; } /* Extract the key */ rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sKey,&zIn,iNest+1); if( rc != UNQLITE_OK ){ break; } if( zIn >= zEnd || zIn[0] != FJSON_COLON ){ rc = UNQLITE_CORRUPT; break; } zIn++; /* Jump the binary colon ':' */ if( zIn >= zEnd ){ rc = UNQLITE_CORRUPT; break; } /* Decode the value */ rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); if( rc != SXRET_OK ){ break; } /* Insert the key and its associated value */ rc = jx9HashmapInsert(pMap,&sKey,&sVal); if( rc != UNQLITE_OK ){ break; } } if( rc != SXRET_OK ){ jx9MemObjRelease(pOut); } jx9MemObjRelease(&sVal); jx9MemObjRelease(&sKey); break; } default: /* Corrupt data */ rc = SXERR_CORRUPT; break; } if( pzPtr ){ *pzPtr = zIn; } return rc; } /* * ---------------------------------------------------------- * File: jx9_api.c * MD5: 73cba599c009cee0ff878666d0543438 * ---------------------------------------------------------- */ /* * 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: api.c v1.7 FreeBSD 2012-12-18 06:54 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* This file implement the public interfaces presented to host-applications. * Routines in other files are for internal use by JX9 and should not be * accessed by users of the library. */ #define JX9_ENGINE_MAGIC 0xF874BCD7 #define JX9_ENGINE_MISUSE(ENGINE) (ENGINE == 0 || ENGINE->nMagic != JX9_ENGINE_MAGIC) #define JX9_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE) /* If another thread have released a working instance, the following macros * evaluates to true. These macros are only used when the library * is built with threading support enabled which is not the case in * the default built. */ #define JX9_THRD_ENGINE_RELEASE(ENGINE) (ENGINE->nMagic != JX9_ENGINE_MAGIC) #define JX9_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE) /* IMPLEMENTATION: jx9@embedded@symisc 311-12-32 */ /* * All global variables are collected in the structure named "sJx9MPGlobal". * That way it is clear in the code when we are using static variable because * its name start with sJx9MPGlobal. */ static struct Jx9Global_Data { SyMemBackend sAllocator; /* Global low level memory allocator */ #if defined(JX9_ENABLE_THREADS) const SyMutexMethods *pMutexMethods; /* Mutex methods */ SyMutex *pMutex; /* Global mutex */ sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded * The threading level can be set using the [jx9_lib_config()] * interface with a configuration verb set to * JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE or * JX9_LIB_CONFIG_THREAD_LEVEL_MULTI */ #endif const jx9_vfs *pVfs; /* Underlying virtual file system */ sxi32 nEngine; /* Total number of active engines */ jx9 *pEngines; /* List of active engine */ sxu32 nMagic; /* Sanity check against library misuse */ }sJx9MPGlobal = { {0, 0, 0, 0, 0, 0, 0, 0, {0}}, #if defined(JX9_ENABLE_THREADS) 0, 0, 0, #endif 0, 0, 0, 0 }; #define JX9_LIB_MAGIC 0xEA1495BA #define JX9_LIB_MISUSE (sJx9MPGlobal.nMagic != JX9_LIB_MAGIC) /* * Supported threading level. * These options have meaning only when the library is compiled with multi-threading * support.That is, the JX9_ENABLE_THREADS compile time directive must be defined * when JX9 is built. * JX9_THREAD_LEVEL_SINGLE: * In this mode, mutexing is disabled and the library can only be used by a single thread. * JX9_THREAD_LEVEL_MULTI * In this mode, all mutexes including the recursive mutexes on [jx9] objects * are enabled so that the application is free to share the same engine * between different threads at the same time. */ #define JX9_THREAD_LEVEL_SINGLE 1 #define JX9_THREAD_LEVEL_MULTI 2 /* * Configure a running JX9 engine instance. * return JX9_OK on success.Any other return * value indicates failure. * Refer to [jx9_config()]. */ JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap) { jx9_conf *pConf = &pEngine->xConf; int rc = JX9_OK; /* Perform the requested operation */ switch(nOp){ case JX9_CONFIG_ERR_LOG:{ /* Extract compile-time error log if any */ const char **pzPtr = va_arg(ap, const char **); int *pLen = va_arg(ap, int *); if( pzPtr == 0 ){ rc = JX9_CORRUPT; break; } /* NULL terminate the error-log buffer */ SyBlobNullAppend(&pConf->sErrConsumer); /* Point to the error-log buffer */ *pzPtr = (const char *)SyBlobData(&pConf->sErrConsumer); if( pLen ){ if( SyBlobLength(&pConf->sErrConsumer) > 1 /* NULL '\0' terminator */ ){ *pLen = (int)SyBlobLength(&pConf->sErrConsumer); }else{ *pLen = 0; } } break; } case JX9_CONFIG_ERR_ABORT: /* Reserved for future use */ break; default: /* Unknown configuration verb */ rc = JX9_CORRUPT; break; } /* Switch() */ return rc; } /* * Configure the JX9 library. * Return JX9_OK on success. Any other return value indicates failure. * Refer to [jx9_lib_config()]. */ static sxi32 Jx9CoreConfigure(sxi32 nOp, va_list ap) { int rc = JX9_OK; switch(nOp){ case JX9_LIB_CONFIG_VFS:{ /* Install a virtual file system */ const jx9_vfs *pVfs = va_arg(ap, const jx9_vfs *); sJx9MPGlobal.pVfs = pVfs; break; } case JX9_LIB_CONFIG_USER_MALLOC: { /* Use an alternative low-level memory allocation routines */ const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *); /* Save the memory failure callback (if available) */ ProcMemError xMemErr = sJx9MPGlobal.sAllocator.xMemError; void *pMemErr = sJx9MPGlobal.sAllocator.pUserData; if( pMethods == 0 ){ /* Use the built-in memory allocation subsystem */ rc = SyMemBackendInit(&sJx9MPGlobal.sAllocator, xMemErr, pMemErr); }else{ rc = SyMemBackendInitFromOthers(&sJx9MPGlobal.sAllocator, pMethods, xMemErr, pMemErr); } break; } case JX9_LIB_CONFIG_MEM_ERR_CALLBACK: { /* Memory failure callback */ ProcMemError xMemErr = va_arg(ap, ProcMemError); void *pUserData = va_arg(ap, void *); sJx9MPGlobal.sAllocator.xMemError = xMemErr; sJx9MPGlobal.sAllocator.pUserData = pUserData; break; } case JX9_LIB_CONFIG_USER_MUTEX: { #if defined(JX9_ENABLE_THREADS) /* Use an alternative low-level mutex subsystem */ const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *); #if defined (UNTRUST) if( pMethods == 0 ){ rc = JX9_CORRUPT; } #endif /* Sanity check */ if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){ /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */ rc = JX9_CORRUPT; break; } if( sJx9MPGlobal.pMutexMethods ){ /* Overwrite the previous mutex subsystem */ SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){ sJx9MPGlobal.pMutexMethods->xGlobalRelease(); } sJx9MPGlobal.pMutex = 0; } /* Initialize and install the new mutex subsystem */ if( pMethods->xGlobalInit ){ rc = pMethods->xGlobalInit(); if ( rc != JX9_OK ){ break; } } /* Create the global mutex */ sJx9MPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); if( sJx9MPGlobal.pMutex == 0 ){ /* * If the supplied mutex subsystem is so sick that we are unable to * create a single mutex, there is no much we can do here. */ if( pMethods->xGlobalRelease ){ pMethods->xGlobalRelease(); } rc = JX9_CORRUPT; break; } sJx9MPGlobal.pMutexMethods = pMethods; if( sJx9MPGlobal.nThreadingLevel == 0 ){ /* Set a default threading level */ sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI; } #endif break; } case JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE: #if defined(JX9_ENABLE_THREADS) /* Single thread mode(Only one thread is allowed to play with the library) */ sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_SINGLE; #endif break; case JX9_LIB_CONFIG_THREAD_LEVEL_MULTI: #if defined(JX9_ENABLE_THREADS) /* Multi-threading mode (library is thread safe and JX9 engines and virtual machines * may be shared between multiple threads). */ sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI; #endif break; default: /* Unknown configuration option */ rc = JX9_CORRUPT; break; } return rc; } /* * [CAPIREF: jx9_lib_config()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...) { va_list ap; int rc; if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){ /* Library is already initialized, this operation is forbidden */ return JX9_LOOKED; } va_start(ap, nConfigOp); rc = Jx9CoreConfigure(nConfigOp, ap); va_end(ap); return rc; } /* * Global library initialization * Refer to [jx9_lib_init()] * This routine must be called to initialize the memory allocation subsystem, the mutex * subsystem prior to doing any serious work with the library.The first thread to call * this routine does the initialization process and set the magic number so no body later * can re-initialize the library.If subsequent threads call this routine before the first * thread have finished the initialization process, then the subsequent threads must block * until the initialization process is done. */ static sxi32 Jx9CoreInitialize(void) { const jx9_vfs *pVfs; /* Built-in vfs */ #if defined(JX9_ENABLE_THREADS) const SyMutexMethods *pMutexMethods = 0; SyMutex *pMaster = 0; #endif int rc; /* * If the library is already initialized, then a call to this routine * is a no-op. */ if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){ return JX9_OK; /* Already initialized */ } if( sJx9MPGlobal.pVfs == 0 ){ /* Point to the built-in vfs */ pVfs = jx9ExportBuiltinVfs(); /* Install it */ jx9_lib_config(JX9_LIB_CONFIG_VFS, pVfs); } #if defined(JX9_ENABLE_THREADS) if( sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_SINGLE ){ pMutexMethods = sJx9MPGlobal.pMutexMethods; if( pMutexMethods == 0 ){ /* Use the built-in mutex subsystem */ pMutexMethods = SyMutexExportMethods(); if( pMutexMethods == 0 ){ return JX9_CORRUPT; /* Can't happen */ } /* Install the mutex subsystem */ rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MUTEX, pMutexMethods); if( rc != JX9_OK ){ return rc; } } /* Obtain a static mutex so we can initialize the library without calling malloc() */ pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1); if( pMaster == 0 ){ return JX9_CORRUPT; /* Can't happen */ } } /* Lock the master mutex */ rc = JX9_OK; SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){ #endif if( sJx9MPGlobal.sAllocator.pMethods == 0 ){ /* Install a memory subsystem */ rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */ if( rc != JX9_OK ){ /* If we are unable to initialize the memory backend, there is no much we can do here.*/ goto End; } } #if defined(JX9_ENABLE_THREADS) if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ /* Protect the memory allocation subsystem */ rc = SyMemBackendMakeThreadSafe(&sJx9MPGlobal.sAllocator, sJx9MPGlobal.pMutexMethods); if( rc != JX9_OK ){ goto End; } } #endif /* Our library is initialized, set the magic number */ sJx9MPGlobal.nMagic = JX9_LIB_MAGIC; rc = JX9_OK; #if defined(JX9_ENABLE_THREADS) } /* sJx9MPGlobal.nMagic != JX9_LIB_MAGIC */ #endif End: #if defined(JX9_ENABLE_THREADS) /* Unlock the master mutex */ SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ #endif return rc; } /* * Release an active JX9 engine and it's associated active virtual machines. */ static sxi32 EngineRelease(jx9 *pEngine) { jx9_vm *pVm, *pNext; /* Release all active VM */ pVm = pEngine->pVms; for(;;){ if( pEngine->iVm < 1 ){ break; } pNext = pVm->pNext; jx9VmRelease(pVm); pVm = pNext; pEngine->iVm--; } /* Set a dummy magic number */ pEngine->nMagic = 0x7635; /* Release the private memory subsystem */ SyMemBackendRelease(&pEngine->sAllocator); return JX9_OK; } /* * Release all resources consumed by the library. * If JX9 is already shut when this routine is invoked then this * routine is a harmless no-op. * Note: This call is not thread safe. Refer to [jx9_lib_shutdown()]. */ static void JX9CoreShutdown(void) { jx9 *pEngine, *pNext; /* Release all active engines first */ pEngine = sJx9MPGlobal.pEngines; for(;;){ if( sJx9MPGlobal.nEngine < 1 ){ break; } pNext = pEngine->pNext; EngineRelease(pEngine); pEngine = pNext; sJx9MPGlobal.nEngine--; } #if defined(JX9_ENABLE_THREADS) /* Release the mutex subsystem */ if( sJx9MPGlobal.pMutexMethods ){ if( sJx9MPGlobal.pMutex ){ SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); sJx9MPGlobal.pMutex = 0; } if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){ sJx9MPGlobal.pMutexMethods->xGlobalRelease(); } sJx9MPGlobal.pMutexMethods = 0; } sJx9MPGlobal.nThreadingLevel = 0; #endif if( sJx9MPGlobal.sAllocator.pMethods ){ /* Release the memory backend */ SyMemBackendRelease(&sJx9MPGlobal.sAllocator); } sJx9MPGlobal.nMagic = 0x1928; } /* * [CAPIREF: jx9_lib_shutdown()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_lib_shutdown(void) { if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){ /* Already shut */ return JX9_OK; } JX9CoreShutdown(); return JX9_OK; } /* * [CAPIREF: jx9_lib_signature()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE const char * jx9_lib_signature(void) { return JX9_SIG; } /* * [CAPIREF: jx9_init()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_init(jx9 **ppEngine) { jx9 *pEngine; int rc; #if defined(UNTRUST) if( ppEngine == 0 ){ return JX9_CORRUPT; } #endif *ppEngine = 0; /* One-time automatic library initialization */ rc = Jx9CoreInitialize(); if( rc != JX9_OK ){ return rc; } /* Allocate a new engine */ pEngine = (jx9 *)SyMemBackendPoolAlloc(&sJx9MPGlobal.sAllocator, sizeof(jx9)); if( pEngine == 0 ){ return JX9_NOMEM; } /* Zero the structure */ SyZero(pEngine, sizeof(jx9)); /* Initialize engine fields */ pEngine->nMagic = JX9_ENGINE_MAGIC; rc = SyMemBackendInitFromParent(&pEngine->sAllocator, &sJx9MPGlobal.sAllocator); if( rc != JX9_OK ){ goto Release; } //#if defined(JX9_ENABLE_THREADS) // SyMemBackendDisbaleMutexing(&pEngine->sAllocator); //#endif /* Default configuration */ SyBlobInit(&pEngine->xConf.sErrConsumer, &pEngine->sAllocator); /* Install a default compile-time error consumer routine */ pEngine->xConf.xErr = jx9VmBlobConsumer; pEngine->xConf.pErrData = &pEngine->xConf.sErrConsumer; /* Built-in vfs */ pEngine->pVfs = sJx9MPGlobal.pVfs; #if defined(JX9_ENABLE_THREADS) if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ /* Associate a recursive mutex with this instance */ pEngine->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); if( pEngine->pMutex == 0 ){ rc = JX9_NOMEM; goto Release; } } #endif /* Link to the list of active engines */ #if defined(JX9_ENABLE_THREADS) /* Enter the global mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ #endif MACRO_LD_PUSH(sJx9MPGlobal.pEngines, pEngine); sJx9MPGlobal.nEngine++; #if defined(JX9_ENABLE_THREADS) /* Leave the global mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ #endif /* Write a pointer to the new instance */ *ppEngine = pEngine; return JX9_OK; Release: SyMemBackendRelease(&pEngine->sAllocator); SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator,pEngine); return rc; } /* * [CAPIREF: jx9_release()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_release(jx9 *pEngine) { int rc; if( JX9_ENGINE_MISUSE(pEngine) ){ return JX9_CORRUPT; } #if defined(JX9_ENABLE_THREADS) /* Acquire engine mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_ENGINE_RELEASE(pEngine) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif /* Release the engine */ rc = EngineRelease(&(*pEngine)); #if defined(JX9_ENABLE_THREADS) /* Leave engine mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ /* Release engine mutex */ SyMutexRelease(sJx9MPGlobal.pMutexMethods, pEngine->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif #if defined(JX9_ENABLE_THREADS) /* Enter the global mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ #endif /* Unlink from the list of active engines */ MACRO_LD_REMOVE(sJx9MPGlobal.pEngines, pEngine); sJx9MPGlobal.nEngine--; #if defined(JX9_ENABLE_THREADS) /* Leave the global mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ #endif /* Release the memory chunk allocated to this engine */ SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator, pEngine); return rc; } /* * Compile a raw JX9 script. * To execute a JX9 code, it must first be compiled into a bytecode program using this routine. * If something goes wrong [i.e: compile-time error], your error log [i.e: error consumer callback] * should display the appropriate error message and this function set ppVm to null and return * an error code that is different from JX9_OK. Otherwise when the script is successfully compiled * ppVm should hold the JX9 bytecode and it's safe to call [jx9_vm_exec(), jx9_vm_reset(), etc.]. * This API does not actually evaluate the JX9 code. It merely compile and prepares the JX9 script * for evaluation. */ static sxi32 ProcessScript( jx9 *pEngine, /* Running JX9 engine */ jx9_vm **ppVm, /* OUT: A pointer to the virtual machine */ SyString *pScript, /* Raw JX9 script to compile */ sxi32 iFlags, /* Compile-time flags */ const char *zFilePath /* File path if script come from a file. NULL otherwise */ ) { jx9_vm *pVm; int rc; /* Allocate a new virtual machine */ pVm = (jx9_vm *)SyMemBackendPoolAlloc(&pEngine->sAllocator, sizeof(jx9_vm)); if( pVm == 0 ){ /* If the supplied memory subsystem is so sick that we are unable to allocate * a tiny chunk of memory, there is no much we can do here. */ if( ppVm ){ *ppVm = 0; } return JX9_NOMEM; } if( iFlags < 0 ){ /* Default compile-time flags */ iFlags = 0; } /* Initialize the Virtual Machine */ rc = jx9VmInit(pVm, &(*pEngine)); if( rc != JX9_OK ){ SyMemBackendPoolFree(&pEngine->sAllocator, pVm); if( ppVm ){ *ppVm = 0; } return JX9_VM_ERR; } if( zFilePath ){ /* Push processed file path */ jx9VmPushFilePath(pVm, zFilePath, -1, TRUE, 0); } /* Reset the error message consumer */ SyBlobReset(&pEngine->xConf.sErrConsumer); /* Compile the script */ jx9CompileScript(pVm, &(*pScript), iFlags); if( pVm->sCodeGen.nErr > 0 || pVm == 0){ sxu32 nErr = pVm->sCodeGen.nErr; /* Compilation error or null ppVm pointer, release this VM */ SyMemBackendRelease(&pVm->sAllocator); SyMemBackendPoolFree(&pEngine->sAllocator, pVm); if( ppVm ){ *ppVm = 0; } return nErr > 0 ? JX9_COMPILE_ERR : JX9_OK; } /* Prepare the virtual machine for bytecode execution */ rc = jx9VmMakeReady(pVm); if( rc != JX9_OK ){ goto Release; } /* Install local import path which is the current directory */ jx9_vm_config(pVm, JX9_VM_CONFIG_IMPORT_PATH, "./"); #if defined(JX9_ENABLE_THREADS) if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ /* Associate a recursive mutex with this instance */ pVm->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); if( pVm->pMutex == 0 ){ goto Release; } } #endif /* Script successfully compiled, link to the list of active virtual machines */ MACRO_LD_PUSH(pEngine->pVms, pVm); pEngine->iVm++; /* Point to the freshly created VM */ *ppVm = pVm; /* Ready to execute JX9 bytecode */ return JX9_OK; Release: SyMemBackendRelease(&pVm->sAllocator); SyMemBackendPoolFree(&pEngine->sAllocator, pVm); *ppVm = 0; return JX9_VM_ERR; } /* * [CAPIREF: jx9_compile()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm) { SyString sScript; int rc; if( JX9_ENGINE_MISUSE(pEngine) ){ return JX9_CORRUPT; } if( zSource == 0 ){ /* Empty Jx9 statement ';' */ zSource = ";"; nLen = (int)sizeof(char); } if( nLen < 0 ){ /* Compute input length automatically */ nLen = (int)SyStrlen(zSource); } SyStringInitFromBuf(&sScript, zSource, nLen); #if defined(JX9_ENABLE_THREADS) /* Acquire engine mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_ENGINE_RELEASE(pEngine) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif /* Compile the script */ rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,0,0); #if defined(JX9_ENABLE_THREADS) /* Leave engine mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif /* Compilation result */ return rc; } /* * [CAPIREF: jx9_compile_file()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm) { const jx9_vfs *pVfs; int rc; if( ppOutVm ){ *ppOutVm = 0; } rc = JX9_OK; /* cc warning */ if( JX9_ENGINE_MISUSE(pEngine) || SX_EMPTY_STR(zFilePath) ){ return JX9_CORRUPT; } #if defined(JX9_ENABLE_THREADS) /* Acquire engine mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_ENGINE_RELEASE(pEngine) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif /* * Check if the underlying vfs implement the memory map * [i.e: mmap() under UNIX/MapViewOfFile() under windows] function. */ pVfs = pEngine->pVfs; if( pVfs == 0 || pVfs->xMmap == 0 ){ /* Memory map routine not implemented */ rc = JX9_IO_ERR; }else{ void *pMapView = 0; /* cc warning */ jx9_int64 nSize = 0; /* cc warning */ SyString sScript; /* Try to get a memory view of the whole file */ rc = pVfs->xMmap(zFilePath, &pMapView, &nSize); if( rc != JX9_OK ){ /* Assume an IO error */ rc = JX9_IO_ERR; }else{ /* Compile the file */ SyStringInitFromBuf(&sScript, pMapView, nSize); rc = ProcessScript(&(*pEngine), ppOutVm, &sScript,0,zFilePath); /* Release the memory view of the whole file */ if( pVfs->xUnmap ){ pVfs->xUnmap(pMapView, nSize); } } } #if defined(JX9_ENABLE_THREADS) /* Leave engine mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif /* Compilation result */ return rc; } /* * [CAPIREF: jx9_vm_config()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...) { va_list ap; int rc; /* Ticket 1433-002: NULL VM is harmless operation */ if ( JX9_VM_MISUSE(pVm) ){ return JX9_CORRUPT; } #if defined(JX9_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_VM_RELEASE(pVm) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif /* Confiugure the virtual machine */ va_start(ap, iConfigOp); rc = jx9VmConfigure(&(*pVm), iConfigOp, ap); va_end(ap); #if defined(JX9_ENABLE_THREADS) /* Leave VM mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif return rc; } /* * [CAPIREF: jx9_vm_release()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm) { jx9 *pEngine; int rc; /* Ticket 1433-002: NULL VM is harmless operation */ if ( JX9_VM_MISUSE(pVm) ){ return JX9_CORRUPT; } #if defined(JX9_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_VM_RELEASE(pVm) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif pEngine = pVm->pEngine; rc = jx9VmRelease(&(*pVm)); #if defined(JX9_ENABLE_THREADS) /* Leave VM mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ /* Release VM mutex */ SyMutexRelease(sJx9MPGlobal.pMutexMethods, pVm->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif if( rc == JX9_OK ){ /* Unlink from the list of active VM */ #if defined(JX9_ENABLE_THREADS) /* Acquire engine mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_ENGINE_RELEASE(pEngine) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif MACRO_LD_REMOVE(pEngine->pVms, pVm); pEngine->iVm--; /* Release the memory chunk allocated to this VM */ SyMemBackendPoolFree(&pEngine->sAllocator, pVm); #if defined(JX9_ENABLE_THREADS) /* Leave engine mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif } return rc; } /* * [CAPIREF: jx9_create_function()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData) { SyString sName; int rc; /* Ticket 1433-002: NULL VM is harmless operation */ if ( JX9_VM_MISUSE(pVm) ){ return JX9_CORRUPT; } SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); /* Remove leading and trailing white spaces */ SyStringFullTrim(&sName); /* Ticket 1433-003: NULL values are not allowed */ if( sName.nByte < 1 || xFunc == 0 ){ return JX9_CORRUPT; } #if defined(JX9_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_VM_RELEASE(pVm) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif /* Install the foreign function */ rc = jx9VmInstallForeignFunction(&(*pVm), &sName, xFunc, pUserData); #if defined(JX9_ENABLE_THREADS) /* Leave VM mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif return rc; } JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName) { jx9_user_func *pFunc = 0; /* cc warning */ int rc; /* Perform the deletion */ rc = SyHashDeleteEntry(&pVm->hHostFunction, (const void *)zName, SyStrlen(zName), (void **)&pFunc); if( rc == JX9_OK ){ /* Release internal fields */ SySetRelease(&pFunc->aAux); SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName)); SyMemBackendPoolFree(&pVm->sAllocator, pFunc); } return rc; } /* * [CAPIREF: jx9_create_constant()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData) { SyString sName; int rc; /* Ticket 1433-002: NULL VM is harmless operation */ if ( JX9_VM_MISUSE(pVm) ){ return JX9_CORRUPT; } SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); /* Remove leading and trailing white spaces */ SyStringFullTrim(&sName); if( sName.nByte < 1 ){ /* Empty constant name */ return JX9_CORRUPT; } /* TICKET 1433-003: NULL pointer is harmless operation */ if( xExpand == 0 ){ return JX9_CORRUPT; } #if defined(JX9_ENABLE_THREADS) /* Acquire VM mutex */ SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && JX9_THRD_VM_RELEASE(pVm) ){ return JX9_ABORT; /* Another thread have released this instance */ } #endif /* Perform the registration */ rc = jx9VmRegisterConstant(&(*pVm), &sName, xExpand, pUserData); #if defined(JX9_ENABLE_THREADS) /* Leave VM mutex */ SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ #endif return rc; } JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName) { jx9_constant *pCons; int rc; /* Query the constant hashtable */ rc = SyHashDeleteEntry(&pVm->hConstant, (const void *)zName, SyStrlen(zName), (void **)&pCons); if( rc == JX9_OK ){ /* Perform the deletion */ SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pCons->sName)); SyMemBackendPoolFree(&pVm->sAllocator, pCons); } return rc; } /* * [CAPIREF: jx9_new_scalar()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm) { jx9_value *pObj; /* Ticket 1433-002: NULL VM is harmless operation */ if ( JX9_VM_MISUSE(pVm) ){ return 0; } /* Allocate a new scalar variable */ pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value)); if( pObj == 0 ){ return 0; } /* Nullify the new scalar */ jx9MemObjInit(pVm, pObj); return pObj; } /* * [CAPIREF: jx9_new_array()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm) { jx9_hashmap *pMap; jx9_value *pObj; /* Ticket 1433-002: NULL VM is harmless operation */ if ( JX9_VM_MISUSE(pVm) ){ return 0; } /* Create a new hashmap first */ pMap = jx9NewHashmap(&(*pVm), 0, 0); if( pMap == 0 ){ return 0; } /* Associate a new jx9_value with this hashmap */ pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value)); if( pObj == 0 ){ jx9HashmapRelease(pMap, TRUE); return 0; } jx9MemObjInitFromArray(pVm, pObj, pMap); return pObj; } /* * [CAPIREF: jx9_release_value()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue) { /* Ticket 1433-002: NULL VM is a harmless operation */ if ( JX9_VM_MISUSE(pVm) ){ return JX9_CORRUPT; } if( pValue ){ /* Release the value */ jx9MemObjRelease(pValue); SyMemBackendPoolFree(&pVm->sAllocator, pValue); } return JX9_OK; } /* * [CAPIREF: jx9_value_to_int()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue) { int rc; rc = jx9MemObjToInteger(pValue); if( rc != JX9_OK ){ return 0; } return (int)pValue->x.iVal; } /* * [CAPIREF: jx9_value_to_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue) { int rc; rc = jx9MemObjToBool(pValue); if( rc != JX9_OK ){ return 0; } return (int)pValue->x.iVal; } /* * [CAPIREF: jx9_value_to_int64()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue) { int rc; rc = jx9MemObjToInteger(pValue); if( rc != JX9_OK ){ return 0; } return pValue->x.iVal; } /* * [CAPIREF: jx9_value_to_double()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue) { int rc; rc = jx9MemObjToReal(pValue); if( rc != JX9_OK ){ return (double)0; } return (double)pValue->x.rVal; } /* * [CAPIREF: jx9_value_to_string()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen) { jx9MemObjToString(pValue); if( SyBlobLength(&pValue->sBlob) > 0 ){ SyBlobNullAppend(&pValue->sBlob); if( pLen ){ *pLen = (int)SyBlobLength(&pValue->sBlob); } return (const char *)SyBlobData(&pValue->sBlob); }else{ /* Return the empty string */ if( pLen ){ *pLen = 0; } return ""; } } /* * [CAPIREF: jx9_value_to_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue) { if( (pValue->iFlags & MEMOBJ_RES) == 0 ){ /* Not a resource, return NULL */ return 0; } return pValue->x.pOther; } /* * [CAPIREF: jx9_value_compare()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict) { int rc; if( pLeft == 0 || pRight == 0 ){ /* TICKET 1433-24: NULL values is harmless operation */ return 1; } /* Perform the comparison */ rc = jx9MemObjCmp(&(*pLeft), &(*pRight), bStrict, 0); /* Comparison result */ return rc; } /* * [CAPIREF: jx9_result_int()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue) { return jx9_value_int(pCtx->pRet, iValue); } /* * [CAPIREF: jx9_result_int64()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue) { return jx9_value_int64(pCtx->pRet, iValue); } /* * [CAPIREF: jx9_result_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool) { return jx9_value_bool(pCtx->pRet, iBool); } /* * [CAPIREF: jx9_result_double()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value) { return jx9_value_double(pCtx->pRet, Value); } /* * [CAPIREF: jx9_result_null()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_null(jx9_context *pCtx) { /* Invalidate any prior representation and set the NULL flag */ jx9MemObjRelease(pCtx->pRet); return JX9_OK; } /* * [CAPIREF: jx9_result_string()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen) { return jx9_value_string(pCtx->pRet, zString, nLen); } /* * [CAPIREF: jx9_result_string_format()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...) { jx9_value *p; va_list ap; int rc; p = pCtx->pRet; if( (p->iFlags & MEMOBJ_STRING) == 0 ){ /* Invalidate any prior representation */ jx9MemObjRelease(p); MemObjSetType(p, MEMOBJ_STRING); } /* Format the given string */ va_start(ap, zFormat); rc = SyBlobFormatAp(&p->sBlob, zFormat, ap); va_end(ap); return rc; } /* * [CAPIREF: jx9_result_value()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue) { int rc = JX9_OK; if( pValue == 0 ){ jx9MemObjRelease(pCtx->pRet); }else{ rc = jx9MemObjStore(pValue, pCtx->pRet); } return rc; } /* * [CAPIREF: jx9_result_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData) { return jx9_value_resource(pCtx->pRet, pUserData); } /* * [CAPIREF: jx9_context_new_scalar()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx) { jx9_value *pVal; pVal = jx9_new_scalar(pCtx->pVm); if( pVal ){ /* Record value address so it can be freed automatically * when the calling function returns. */ SySetPut(&pCtx->sVar, (const void *)&pVal); } return pVal; } /* * [CAPIREF: jx9_context_new_array()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx) { jx9_value *pVal; pVal = jx9_new_array(pCtx->pVm); if( pVal ){ /* Record value address so it can be freed automatically * when the calling function returns. */ SySetPut(&pCtx->sVar, (const void *)&pVal); } return pVal; } /* * [CAPIREF: jx9_context_release_value()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue) { jx9VmReleaseContextValue(&(*pCtx), pValue); } /* * [CAPIREF: jx9_context_alloc_chunk()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease) { void *pChunk; pChunk = SyMemBackendAlloc(&pCtx->pVm->sAllocator, nByte); if( pChunk ){ if( ZeroChunk ){ /* Zero the memory chunk */ SyZero(pChunk, nByte); } if( AutoRelease ){ jx9_aux_data sAux; /* Track the chunk so that it can be released automatically * upon this context is destroyed. */ sAux.pAuxData = pChunk; SySetPut(&pCtx->sChunk, (const void *)&sAux); } } return pChunk; } /* * Check if the given chunk address is registered in the call context * chunk container. * Return TRUE if registered.FALSE otherwise. * Refer to [jx9_context_realloc_chunk(), jx9_context_free_chunk()]. */ static jx9_aux_data * ContextFindChunk(jx9_context *pCtx, void *pChunk) { jx9_aux_data *aAux, *pAux; sxu32 n; if( SySetUsed(&pCtx->sChunk) < 1 ){ /* Don't bother processing, the container is empty */ return 0; } /* Perform the lookup */ aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk); for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ pAux = &aAux[n]; if( pAux->pAuxData == pChunk ){ /* Chunk found */ return pAux; } } /* No such allocated chunk */ return 0; } /* * [CAPIREF: jx9_context_realloc_chunk()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte) { jx9_aux_data *pAux; void *pNew; pNew = SyMemBackendRealloc(&pCtx->pVm->sAllocator, pChunk, nByte); if( pNew ){ pAux = ContextFindChunk(pCtx, pChunk); if( pAux ){ pAux->pAuxData = pNew; } } return pNew; } /* * [CAPIREF: jx9_context_free_chunk()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk) { jx9_aux_data *pAux; if( pChunk == 0 ){ /* TICKET-1433-93: NULL chunk is a harmless operation */ return; } pAux = ContextFindChunk(pCtx, pChunk); if( pAux ){ /* Mark as destroyed */ pAux->pAuxData = 0; } SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk); } /* * [CAPIREF: jx9_array_fetch()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte) { jx9_hashmap_node *pNode; jx9_value *pValue; jx9_value skey; int rc; /* Make sure we are dealing with a valid hashmap */ if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ return 0; } if( nByte < 0 ){ nByte = (int)SyStrlen(zKey); } /* Convert the key to a jx9_value */ jx9MemObjInit(pArray->pVm, &skey); jx9MemObjStringAppend(&skey, zKey, (sxu32)nByte); /* Perform the lookup */ rc = jx9HashmapLookup((jx9_hashmap *)pArray->x.pOther, &skey, &pNode); jx9MemObjRelease(&skey); if( rc != JX9_OK ){ /* No such entry */ return 0; } /* Extract the target value */ pValue = (jx9_value *)SySetAt(&pArray->pVm->aMemObj, pNode->nValIdx); return pValue; } /* * [CAPIREF: jx9_array_walk()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *pValue, jx9_value *, void *), void *pUserData) { int rc; if( xWalk == 0 ){ return JX9_CORRUPT; } /* Make sure we are dealing with a valid hashmap */ if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ return JX9_CORRUPT; } /* Start the walk process */ rc = jx9HashmapWalk((jx9_hashmap *)pArray->x.pOther, xWalk, pUserData); return rc != JX9_OK ? JX9_ABORT /* User callback request an operation abort*/ : JX9_OK; } /* * [CAPIREF: jx9_array_add_elem()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue) { int rc; /* Make sure we are dealing with a valid hashmap */ if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ return JX9_CORRUPT; } /* Perform the insertion */ rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &(*pKey), &(*pValue)); return rc; } /* * [CAPIREF: jx9_array_add_strkey_elem()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue) { int rc; /* Make sure we are dealing with a valid hashmap */ if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ return JX9_CORRUPT; } /* Perform the insertion */ if( SX_EMPTY_STR(zKey) ){ /* Empty key, assign an automatic index */ rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, 0, &(*pValue)); }else{ jx9_value sKey; jx9MemObjInitFromString(pArray->pVm, &sKey, 0); jx9MemObjStringAppend(&sKey, zKey, (sxu32)SyStrlen(zKey)); rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &sKey, &(*pValue)); jx9MemObjRelease(&sKey); } return rc; } /* * [CAPIREF: jx9_array_count()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray) { jx9_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ return 0; } /* Point to the internal representation of the hashmap */ pMap = (jx9_hashmap *)pArray->x.pOther; return pMap->nEntry; } /* * [CAPIREF: jx9_context_output()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen) { SyString sData; int rc; if( nLen < 0 ){ nLen = (int)SyStrlen(zString); } SyStringInitFromBuf(&sData, zString, nLen); rc = jx9VmOutputConsume(pCtx->pVm, &sData); return rc; } /* * [CAPIREF: jx9_context_throw_error()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr) { int rc = JX9_OK; if( zErr ){ rc = jx9VmThrowError(pCtx->pVm, &pCtx->pFunc->sName, iErr, zErr); } return rc; } /* * [CAPIREF: jx9_context_throw_error_format()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...) { va_list ap; int rc; if( zFormat == 0){ return JX9_OK; } va_start(ap, zFormat); rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap); va_end(ap); return rc; } /* * [CAPIREF: jx9_context_random_num()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx) { sxu32 n; n = jx9VmRandomNum(pCtx->pVm); return n; } /* * [CAPIREF: jx9_context_random_string()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen) { if( nBuflen < 3 ){ return JX9_CORRUPT; } jx9VmRandomString(pCtx->pVm, zBuf, nBuflen); return JX9_OK; } /* * [CAPIREF: jx9_context_user_data()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx) { return pCtx->pFunc->pUserData; } /* * [CAPIREF: jx9_context_push_aux_data()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData) { jx9_aux_data sAux; int rc; sAux.pAuxData = pUserData; rc = SySetPut(&pCtx->pFunc->aAux, (const void *)&sAux); return rc; } /* * [CAPIREF: jx9_context_peek_aux_data()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx) { jx9_aux_data *pAux; pAux = (jx9_aux_data *)SySetPeek(&pCtx->pFunc->aAux); return pAux ? pAux->pAuxData : 0; } /* * [CAPIREF: jx9_context_pop_aux_data()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx) { jx9_aux_data *pAux; pAux = (jx9_aux_data *)SySetPop(&pCtx->pFunc->aAux); return pAux ? pAux->pAuxData : 0; } /* * [CAPIREF: jx9_context_result_buf_length()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx) { return SyBlobLength(&pCtx->pRet->sBlob); } /* * [CAPIREF: jx9_function_name()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx) { SyString *pName; pName = &pCtx->pFunc->sName; return pName->zString; } /* * [CAPIREF: jx9_value_int()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue) { /* Invalidate any prior representation */ jx9MemObjRelease(pVal); pVal->x.iVal = (jx9_int64)iValue; MemObjSetType(pVal, MEMOBJ_INT); return JX9_OK; } /* * [CAPIREF: jx9_value_int64()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue) { /* Invalidate any prior representation */ jx9MemObjRelease(pVal); pVal->x.iVal = iValue; MemObjSetType(pVal, MEMOBJ_INT); return JX9_OK; } /* * [CAPIREF: jx9_value_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool) { /* Invalidate any prior representation */ jx9MemObjRelease(pVal); pVal->x.iVal = iBool ? 1 : 0; MemObjSetType(pVal, MEMOBJ_BOOL); return JX9_OK; } /* * [CAPIREF: jx9_value_null()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_null(jx9_value *pVal) { /* Invalidate any prior representation and set the NULL flag */ jx9MemObjRelease(pVal); return JX9_OK; } /* * [CAPIREF: jx9_value_double()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value) { /* Invalidate any prior representation */ jx9MemObjRelease(pVal); pVal->x.rVal = (jx9_real)Value; MemObjSetType(pVal, MEMOBJ_REAL); /* Try to get an integer representation also */ jx9MemObjTryInteger(pVal); return JX9_OK; } /* * [CAPIREF: jx9_value_string()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen) { if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ /* Invalidate any prior representation */ jx9MemObjRelease(pVal); MemObjSetType(pVal, MEMOBJ_STRING); } if( zString ){ if( nLen < 0 ){ /* Compute length automatically */ nLen = (int)SyStrlen(zString); } SyBlobAppend(&pVal->sBlob, (const void *)zString, (sxu32)nLen); } return JX9_OK; } /* * [CAPIREF: jx9_value_string_format()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...) { va_list ap; int rc; if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ /* Invalidate any prior representation */ jx9MemObjRelease(pVal); MemObjSetType(pVal, MEMOBJ_STRING); } va_start(ap, zFormat); rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap); va_end(ap); return JX9_OK; } /* * [CAPIREF: jx9_value_reset_string_cursor()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal) { /* Reset the string cursor */ SyBlobReset(&pVal->sBlob); return JX9_OK; } /* * [CAPIREF: jx9_value_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData) { /* Invalidate any prior representation */ jx9MemObjRelease(pVal); /* Reflect the new type */ pVal->x.pOther = pUserData; MemObjSetType(pVal, MEMOBJ_RES); return JX9_OK; } /* * [CAPIREF: jx9_value_release()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_release(jx9_value *pVal) { jx9MemObjRelease(pVal); return JX9_OK; } /* * [CAPIREF: jx9_value_is_int()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_INT) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_float()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_REAL) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_bool()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_BOOL) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_string()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_STRING) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_null()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_NULL) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_numeric()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal) { int rc; rc = jx9MemObjIsNumeric(pVal); return rc; } /* * [CAPIREF: jx9_value_is_callable()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal) { int rc; rc = jx9VmIsCallable(pVal->pVm, pVal); return rc; } /* * [CAPIREF: jx9_value_is_scalar()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_SCALAR) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_json_array()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_HASHMAP) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_json_object()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal) { jx9_hashmap *pMap; if( (pVal->iFlags & MEMOBJ_HASHMAP) == 0 ){ return FALSE; } pMap = (jx9_hashmap *)pVal->x.pOther; if( (pMap->iFlags & HASHMAP_JSON_OBJECT) == 0 ){ return FALSE; } return TRUE; } /* * [CAPIREF: jx9_value_is_resource()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal) { return (pVal->iFlags & MEMOBJ_RES) ? TRUE : FALSE; } /* * [CAPIREF: jx9_value_is_empty()] * Please refer to the official documentation for function purpose and expected parameters. */ JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal) { int rc; rc = jx9MemObjIsEmpty(pVal); return rc; } /* * ---------------------------------------------------------- * File: jx9_builtin.c * MD5: 97ae6ddf8ded9fe14634060675e12f80 * ---------------------------------------------------------- */ /* * 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: builtin.c v1.7 Win7 2012-12-13 00:01 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* This file implement built-in 'foreign' functions for the JX9 engine */ /* * Section: * Variable handling Functions. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* * bool is_bool($var) * Finds out whether a variable is a boolean. * Parameters * $var: The variable being evaluated. * Return * TRUE if var is a boolean. False otherwise. */ static int jx9Builtin_is_bool(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_bool(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_float($var) * bool is_real($var) * bool is_double($var) * Finds out whether a variable is a float. * Parameters * $var: The variable being evaluated. * Return * TRUE if var is a float. False otherwise. */ static int jx9Builtin_is_float(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_float(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_int($var) * bool is_integer($var) * bool is_long($var) * Finds out whether a variable is an integer. * Parameters * $var: The variable being evaluated. * Return * TRUE if var is an integer. False otherwise. */ static int jx9Builtin_is_int(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_int(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_string($var) * Finds out whether a variable is a string. * Parameters * $var: The variable being evaluated. * Return * TRUE if var is string. False otherwise. */ static int jx9Builtin_is_string(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_string(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_null($var) * Finds out whether a variable is NULL. * Parameters * $var: The variable being evaluated. * Return * TRUE if var is NULL. False otherwise. */ static int jx9Builtin_is_null(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_null(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_numeric($var) * Find out whether a variable is NULL. * Parameters * $var: The variable being evaluated. * Return * True if var is numeric. False otherwise. */ static int jx9Builtin_is_numeric(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_numeric(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_scalar($var) * Find out whether a variable is a scalar. * Parameters * $var: The variable being evaluated. * Return * True if var is scalar. False otherwise. */ static int jx9Builtin_is_scalar(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_scalar(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_array($var) * Find out whether a variable is an array. * Parameters * $var: The variable being evaluated. * Return * True if var is an array. False otherwise. */ static int jx9Builtin_is_array(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_json_array(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_object($var) * Find out whether a variable is an object. * Parameters * $var: The variable being evaluated. * Return * True if var is an object. False otherwise. */ static int jx9Builtin_is_object(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_json_object(apArg[0]); } /* Query result */ jx9_result_bool(pCtx, res); return JX9_OK; } /* * bool is_resource($var) * Find out whether a variable is a resource. * Parameters * $var: The variable being evaluated. * Return * True if a resource. False otherwise. */ static int jx9Builtin_is_resource(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 0; /* Assume false by default */ if( nArg > 0 ){ res = jx9_value_is_resource(apArg[0]); } jx9_result_bool(pCtx, res); return JX9_OK; } /* * float floatval($var) * Get float value of a variable. * Parameter * $var: The variable being processed. * Return * the float value of a variable. */ static int jx9Builtin_floatval(jx9_context *pCtx, int nArg, jx9_value **apArg) { if( nArg < 1 ){ /* return 0.0 */ jx9_result_double(pCtx, 0); }else{ double dval; /* Perform the cast */ dval = jx9_value_to_double(apArg[0]); jx9_result_double(pCtx, dval); } return JX9_OK; } /* * int intval($var) * Get integer value of a variable. * Parameter * $var: The variable being processed. * Return * the int value of a variable. */ static int jx9Builtin_intval(jx9_context *pCtx, int nArg, jx9_value **apArg) { if( nArg < 1 ){ /* return 0 */ jx9_result_int(pCtx, 0); }else{ sxi64 iVal; /* Perform the cast */ iVal = jx9_value_to_int64(apArg[0]); jx9_result_int64(pCtx, iVal); } return JX9_OK; } /* * string strval($var) * Get the string representation of a variable. * Parameter * $var: The variable being processed. * Return * the string value of a variable. */ static int jx9Builtin_strval(jx9_context *pCtx, int nArg, jx9_value **apArg) { if( nArg < 1 ){ /* return NULL */ jx9_result_null(pCtx); }else{ const char *zVal; int iLen = 0; /* cc -O6 warning */ /* Perform the cast */ zVal = jx9_value_to_string(apArg[0], &iLen); jx9_result_string(pCtx, zVal, iLen); } return JX9_OK; } /* * bool empty($var) * Determine whether a variable is empty. * Parameters * $var: The variable being checked. * Return * 0 if var has a non-empty and non-zero value.1 otherwise. */ static int jx9Builtin_empty(jx9_context *pCtx, int nArg, jx9_value **apArg) { int res = 1; /* Assume empty by default */ if( nArg > 0 ){ res = jx9_value_is_empty(apArg[0]); } jx9_result_bool(pCtx, res); return JX9_OK; } #ifndef JX9_DISABLE_BUILTIN_FUNC #ifdef JX9_ENABLE_MATH_FUNC /* * Section: * Math Functions. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ #include /* abs */ #include /* * float sqrt(float $arg ) * Square root of the given number. * Parameter * The number to process. * Return * The square root of arg or the special value Nan of failure. */ static int jx9Builtin_sqrt(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = sqrt(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float exp(float $arg ) * Calculates the exponent of e. * Parameter * The number to process. * Return * 'e' raised to the power of arg. */ static int jx9Builtin_exp(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = exp(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float floor(float $arg ) * Round fractions down. * Parameter * The number to process. * Return * Returns the next lowest integer value by rounding down value if necessary. */ static int jx9Builtin_floor(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = floor(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float cos(float $arg ) * Cosine. * Parameter * The number to process. * Return * The cosine of arg. */ static int jx9Builtin_cos(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = cos(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float acos(float $arg ) * Arc cosine. * Parameter * The number to process. * Return * The arc cosine of arg. */ static int jx9Builtin_acos(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = acos(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float cosh(float $arg ) * Hyperbolic cosine. * Parameter * The number to process. * Return * The hyperbolic cosine of arg. */ static int jx9Builtin_cosh(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = cosh(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float sin(float $arg ) * Sine. * Parameter * The number to process. * Return * The sine of arg. */ static int jx9Builtin_sin(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = sin(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float asin(float $arg ) * Arc sine. * Parameter * The number to process. * Return * The arc sine of arg. */ static int jx9Builtin_asin(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = asin(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float sinh(float $arg ) * Hyperbolic sine. * Parameter * The number to process. * Return * The hyperbolic sine of arg. */ static int jx9Builtin_sinh(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = sinh(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float ceil(float $arg ) * Round fractions up. * Parameter * The number to process. * Return * The next highest integer value by rounding up value if necessary. */ static int jx9Builtin_ceil(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = ceil(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float tan(float $arg ) * Tangent. * Parameter * The number to process. * Return * The tangent of arg. */ static int jx9Builtin_tan(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = tan(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float atan(float $arg ) * Arc tangent. * Parameter * The number to process. * Return * The arc tangent of arg. */ static int jx9Builtin_atan(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = atan(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float tanh(float $arg ) * Hyperbolic tangent. * Parameter * The number to process. * Return * The Hyperbolic tangent of arg. */ static int jx9Builtin_tanh(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = tanh(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float atan2(float $y, float $x) * Arc tangent of two variable. * Parameter * $y = Dividend parameter. * $x = Divisor parameter. * Return * The arc tangent of y/x in radian. */ static int jx9Builtin_atan2(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x, y; if( nArg < 2 ){ /* Missing arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } y = jx9_value_to_double(apArg[0]); x = jx9_value_to_double(apArg[1]); /* Perform the requested operation */ r = atan2(y, x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float/int64 abs(float/int64 $arg ) * Absolute value. * Parameter * The number to process. * Return * The absolute value of number. */ static int jx9Builtin_abs(jx9_context *pCtx, int nArg, jx9_value **apArg) { int is_float; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } is_float = jx9_value_is_float(apArg[0]); if( is_float ){ double r, x; x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = fabs(x); jx9_result_double(pCtx, r); }else{ int r, x; x = jx9_value_to_int(apArg[0]); /* Perform the requested operation */ r = abs(x); jx9_result_int(pCtx, r); } return JX9_OK; } /* * float log(float $arg, [int/float $base]) * Natural logarithm. * Parameter * $arg: The number to process. * $base: The optional logarithmic base to use. (only base-10 is supported) * Return * The logarithm of arg to base, if given, or the natural logarithm. * Note: * only Natural log and base-10 log are supported. */ static int jx9Builtin_log(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ if( nArg == 2 && jx9_value_is_numeric(apArg[1]) && jx9_value_to_int(apArg[1]) == 10 ){ /* Base-10 log */ r = log10(x); }else{ r = log(x); } /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float log10(float $arg ) * Base-10 logarithm. * Parameter * The number to process. * Return * The Base-10 logarithm of the given number. */ static int jx9Builtin_log10(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); /* Perform the requested operation */ r = log10(x); /* store the result back */ jx9_result_double(pCtx, r); return JX9_OK; } /* * number pow(number $base, number $exp) * Exponential expression. * Parameter * base * The base to use. * exp * The exponent. * Return * base raised to the power of exp. * If the result can be represented as integer it will be returned * as type integer, else it will be returned as type float. */ static int jx9Builtin_pow(jx9_context *pCtx, int nArg, jx9_value **apArg) { double r, x, y; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } x = jx9_value_to_double(apArg[0]); y = jx9_value_to_double(apArg[1]); /* Perform the requested operation */ r = pow(x, y); jx9_result_double(pCtx, r); return JX9_OK; } /* * float pi(void) * Returns an approximation of pi. * Note * you can use the M_PI constant which yields identical results to pi(). * Return * The value of pi as float. */ static int jx9Builtin_pi(jx9_context *pCtx, int nArg, jx9_value **apArg) { SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); jx9_result_double(pCtx, JX9_PI); return JX9_OK; } /* * float fmod(float $x, float $y) * Returns the floating point remainder (modulo) of the division of the arguments. * Parameters * $x * The dividend * $y * The divisor * Return * The floating point remainder of x/y. */ static int jx9Builtin_fmod(jx9_context *pCtx, int nArg, jx9_value **apArg) { double x, y, r; if( nArg < 2 ){ /* Missing arguments */ jx9_result_double(pCtx, 0); return JX9_OK; } /* Extract given arguments */ x = jx9_value_to_double(apArg[0]); y = jx9_value_to_double(apArg[1]); /* Perform the requested operation */ r = fmod(x, y); /* Processing result */ jx9_result_double(pCtx, r); return JX9_OK; } /* * float hypot(float $x, float $y) * Calculate the length of the hypotenuse of a right-angle triangle . * Parameters * $x * Length of first side * $y * Length of first side * Return * Calculated length of the hypotenuse. */ static int jx9Builtin_hypot(jx9_context *pCtx, int nArg, jx9_value **apArg) { double x, y, r; if( nArg < 2 ){ /* Missing arguments */ jx9_result_double(pCtx, 0); return JX9_OK; } /* Extract given arguments */ x = jx9_value_to_double(apArg[0]); y = jx9_value_to_double(apArg[1]); /* Perform the requested operation */ r = hypot(x, y); /* Processing result */ jx9_result_double(pCtx, r); return JX9_OK; } #endif /* JX9_ENABLE_MATH_FUNC */ /* * float round ( float $val [, int $precision = 0 [, int $mode = JX9_ROUND_HALF_UP ]] ) * Exponential expression. * Parameter * $val * The value to round. * $precision * The optional number of decimal digits to round to. * $mode * One of JX9_ROUND_HALF_UP, JX9_ROUND_HALF_DOWN, JX9_ROUND_HALF_EVEN, or JX9_ROUND_HALF_ODD. * (not supported). * Return * The rounded value. */ static int jx9Builtin_round(jx9_context *pCtx, int nArg, jx9_value **apArg) { int n = 0; double r; if( nArg < 1 ){ /* Missing argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the precision if available */ if( nArg > 1 ){ n = jx9_value_to_int(apArg[1]); if( n>30 ){ n = 30; } if( n<0 ){ n = 0; } } r = jx9_value_to_double(apArg[0]); /* If Y==0 and X will fit in a 64-bit int, * handle the rounding directly.Otherwise * use our own cutsom printf [i.e:SyBufferFormat()]. */ if( n==0 && r>=0 && r= 0xc0 ){ /* UTF-8 stream */ zString++; while( zString < zEnd && (((unsigned char)zString[0] & 0xc0) == 0x80) ){ zString++; } }else{ if( SyisHex(zString[0]) ){ break; } /* Ignore */ zString++; } } if( zString < zEnd ){ /* Cast */ SyHexStrToInt64(zString, (sxu32)(zEnd-zString), (void *)&iVal, 0); } }else{ /* Extract as a 64-bit integer */ iVal = jx9_value_to_int64(apArg[0]); } /* Return the number */ jx9_result_int64(pCtx, iVal); return JX9_OK; } /* * int64 bindec(string $bin_string) * Binary to decimal. * Parameters * $bin_string * The binary string to convert * Return * Returns the decimal equivalent of the binary number represented by the binary_string argument. */ static int jx9Builtin_bindec(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString; jx9_int64 iVal; int nLen; if( nArg < 1 ){ /* Missing arguments, return -1 */ jx9_result_int(pCtx, -1); return JX9_OK; } iVal = 0; if( jx9_value_is_string(apArg[0]) ){ /* Extract the given string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen > 0 ){ /* Perform a binary cast */ SyBinaryStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0); } }else{ /* Extract as a 64-bit integer */ iVal = jx9_value_to_int64(apArg[0]); } /* Return the number */ jx9_result_int64(pCtx, iVal); return JX9_OK; } /* * int64 octdec(string $oct_string) * Octal to decimal. * Parameters * $oct_string * The octal string to convert * Return * Returns the decimal equivalent of the octal number represented by the octal_string argument. */ static int jx9Builtin_octdec(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString; jx9_int64 iVal; int nLen; if( nArg < 1 ){ /* Missing arguments, return -1 */ jx9_result_int(pCtx, -1); return JX9_OK; } iVal = 0; if( jx9_value_is_string(apArg[0]) ){ /* Extract the given string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen > 0 ){ /* Perform the cast */ SyOctalStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0); } }else{ /* Extract as a 64-bit integer */ iVal = jx9_value_to_int64(apArg[0]); } /* Return the number */ jx9_result_int64(pCtx, iVal); return JX9_OK; } /* * string base_convert(string $number, int $frombase, int $tobase) * Convert a number between arbitrary bases. * Parameters * $number * The number to convert * $frombase * The base number is in * $tobase * The base to convert number to * Return * Number converted to base tobase */ static int jx9Builtin_base_convert(jx9_context *pCtx, int nArg, jx9_value **apArg) { int nLen, iFbase, iTobase; const char *zNum; jx9_int64 iNum; if( nArg < 3 ){ /* Return the empty string*/ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Base numbers */ iFbase = jx9_value_to_int(apArg[1]); iTobase = jx9_value_to_int(apArg[2]); if( jx9_value_is_string(apArg[0]) ){ /* Extract the target number */ zNum = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Return the empty string*/ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Base conversion */ switch(iFbase){ case 16: /* Hex */ SyHexStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); break; case 8: /* Octal */ SyOctalStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); break; case 2: /* Binary */ SyBinaryStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); break; default: /* Decimal */ SyStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); break; } }else{ iNum = jx9_value_to_int64(apArg[0]); } switch(iTobase){ case 16: /* Hex */ jx9_result_string_format(pCtx, "%qx", iNum); /* Quad hex */ break; case 8: /* Octal */ jx9_result_string_format(pCtx, "%qo", iNum); /* Quad octal */ break; case 2: /* Binary */ jx9_result_string_format(pCtx, "%qB", iNum); /* Quad binary */ break; default: /* Decimal */ jx9_result_string_format(pCtx, "%qd", iNum); /* Quad decimal */ break; } return JX9_OK; } /* * Section: * String handling Functions. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* * string substr(string $string, int $start[, int $length ]) * Return part of a string. * Parameters * $string * The input string. Must be one character or longer. * $start * If start is non-negative, the returned string will start at the start'th position * in string, counting from zero. For instance, in the string 'abcdef', the character * at position 0 is 'a', the character at position 2 is 'c', and so forth. * If start is negative, the returned string will start at the start'th character * from the end of string. * If string is less than or equal to start characters long, FALSE will be returned. * $length * If length is given and is positive, the string returned will contain at most length * characters beginning from start (depending on the length of string). * If length is given and is negative, then that many characters will be omitted from * the end of string (after the start position has been calculated when a start is negative). * If start denotes the position of this truncation or beyond, false will be returned. * If length is given and is 0, FALSE or NULL an empty string will be returned. * If length is omitted, the substring starting from start until the end of the string * will be returned. * Return * Returns the extracted part of string, or FALSE on failure or an empty string. */ static int jx9Builtin_substr(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zSource, *zOfft; int nOfft, nLen, nSrcLen; if( nArg < 2 ){ /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zSource = jx9_value_to_string(apArg[0], &nSrcLen); if( nSrcLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = nSrcLen; /* cc warning */ /* Extract the offset */ nOfft = jx9_value_to_int(apArg[1]); if( nOfft < 0 ){ zOfft = &zSource[nSrcLen+nOfft]; if( zOfft < zSource ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = (int)(&zSource[nSrcLen]-zOfft); nOfft = (int)(zOfft-zSource); }else if( nOfft >= nSrcLen ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; }else{ zOfft = &zSource[nOfft]; nLen = nSrcLen - nOfft; } if( nArg > 2 ){ /* Extract the length */ nLen = jx9_value_to_int(apArg[2]); if( nLen == 0 ){ /* Invalid length, return an empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; }else if( nLen < 0 ){ nLen = nSrcLen + nLen - nOfft; if( nLen < 1 ){ /* Invalid length */ nLen = nSrcLen - nOfft; } } if( nLen + nOfft > nSrcLen ){ /* Invalid length */ nLen = nSrcLen - nOfft; } } /* Return the substring */ jx9_result_string(pCtx, zOfft, nLen); return JX9_OK; } /* * int substr_compare(string $main_str, string $str , int $offset[, int $length[, bool $case_insensitivity = false ]]) * Binary safe comparison of two strings from an offset, up to length characters. * Parameters * $main_str * The main string being compared. * $str * The secondary string being compared. * $offset * The start position for the comparison. If negative, it starts counting from * the end of the string. * $length * The length of the comparison. The default value is the largest of the length * of the str compared to the length of main_str less the offset. * $case_insensitivity * If case_insensitivity is TRUE, comparison is case insensitive. * Return * Returns < 0 if main_str from position offset is less than str, > 0 if it is greater than * str, and 0 if they are equal. If offset is equal to or greater than the length of main_str * or length is set and is less than 1, substr_compare() prints a warning and returns FALSE. */ static int jx9Builtin_substr_compare(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zSource, *zOfft, *zSub; int nOfft, nLen, nSrcLen, nSublen; int iCase = 0; int rc; if( nArg < 3 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zSource = jx9_value_to_string(apArg[0], &nSrcLen); if( nSrcLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = nSrcLen; /* cc warning */ /* Extract the substring */ zSub = jx9_value_to_string(apArg[1], &nSublen); if( nSublen < 1 || nSublen > nSrcLen){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the offset */ nOfft = jx9_value_to_int(apArg[2]); if( nOfft < 0 ){ zOfft = &zSource[nSrcLen+nOfft]; if( zOfft < zSource ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = (int)(&zSource[nSrcLen]-zOfft); nOfft = (int)(zOfft-zSource); }else if( nOfft >= nSrcLen ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; }else{ zOfft = &zSource[nOfft]; nLen = nSrcLen - nOfft; } if( nArg > 3 ){ /* Extract the length */ nLen = jx9_value_to_int(apArg[3]); if( nLen < 1 ){ /* Invalid length */ jx9_result_int(pCtx, 1); return JX9_OK; }else if( nLen + nOfft > nSrcLen ){ /* Invalid length */ nLen = nSrcLen - nOfft; } if( nArg > 4 ){ /* Case-sensitive or not */ iCase = jx9_value_to_bool(apArg[4]); } } /* Perform the comparison */ if( iCase ){ rc = SyStrnicmp(zOfft, zSub, (sxu32)nLen); }else{ rc = SyStrncmp(zOfft, zSub, (sxu32)nLen); } /* Comparison result */ jx9_result_int(pCtx, rc); return JX9_OK; } /* * int substr_count(string $haystack, string $needle[, int $offset = 0 [, int $length ]]) * Count the number of substring occurrences. * Parameters * $haystack * The string to search in * $needle * The substring to search for * $offset * The offset where to start counting * $length (NOT USED) * The maximum length after the specified offset to search for the substring. * It outputs a warning if the offset plus the length is greater than the haystack length. * Return * Toral number of substring occurrences. */ static int jx9Builtin_substr_count(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zText, *zPattern, *zEnd; int nTextlen, nPatlen; int iCount = 0; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Point to the haystack */ zText = jx9_value_to_string(apArg[0], &nTextlen); /* Point to the neddle */ zPattern = jx9_value_to_string(apArg[1], &nPatlen); if( nTextlen < 1 || nPatlen < 1 || nPatlen > nTextlen ){ /* NOOP, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } if( nArg > 2 ){ int nOfft; /* Extract the offset */ nOfft = jx9_value_to_int(apArg[2]); if( nOfft < 0 || nOfft > nTextlen ){ /* Invalid offset, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Point to the desired offset */ zText = &zText[nOfft]; /* Adjust length */ nTextlen -= nOfft; } /* Point to the end of the string */ zEnd = &zText[nTextlen]; if( nArg > 3 ){ int nLen; /* Extract the length */ nLen = jx9_value_to_int(apArg[3]); if( nLen < 0 || nLen > nTextlen ){ /* Invalid length, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Adjust pointer */ nTextlen = nLen; zEnd = &zText[nTextlen]; } /* Perform the search */ for(;;){ rc = SyBlobSearch((const void *)zText, (sxu32)(zEnd-zText), (const void *)zPattern, nPatlen, &nOfft); if( rc != SXRET_OK ){ /* Pattern not found, break immediately */ break; } /* Increment counter and update the offset */ iCount++; zText += nOfft + nPatlen; if( zText >= zEnd ){ break; } } /* Pattern count */ jx9_result_int(pCtx, iCount); return JX9_OK; } /* * string chunk_split(string $body[, int $chunklen = 76 [, string $end = "\r\n" ]]) * Split a string into smaller chunks. * Parameters * $body * The string to be chunked. * $chunklen * The chunk length. * $end * The line ending sequence. * Return * The chunked string or NULL on failure. */ static int jx9Builtin_chunk_split(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn, *zEnd, *zSep = "\r\n"; int nSepLen, nChunkLen, nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Nothing to split, return null */ jx9_result_null(pCtx); return JX9_OK; } /* initialize/Extract arguments */ nSepLen = (int)sizeof("\r\n") - 1; nChunkLen = 76; zIn = jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nArg > 1 ){ /* Chunk length */ nChunkLen = jx9_value_to_int(apArg[1]); if( nChunkLen < 1 ){ /* Switch back to the default length */ nChunkLen = 76; } if( nArg > 2 ){ /* Separator */ zSep = jx9_value_to_string(apArg[2], &nSepLen); if( nSepLen < 1 ){ /* Switch back to the default separator */ zSep = "\r\n"; nSepLen = (int)sizeof("\r\n") - 1; } } } /* Perform the requested operation */ if( nChunkLen > nLen ){ /* Nothing to split, return the string and the separator */ jx9_result_string_format(pCtx, "%.*s%.*s", nLen, zIn, nSepLen, zSep); return JX9_OK; } while( zIn < zEnd ){ if( nChunkLen > (int)(zEnd-zIn) ){ nChunkLen = (int)(zEnd - zIn); } /* Append the chunk and the separator */ jx9_result_string_format(pCtx, "%.*s%.*s", nChunkLen, zIn, nSepLen, zSep); /* Point beyond the chunk */ zIn += nChunkLen; } return JX9_OK; } /* * string htmlspecialchars(string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $charset]]) * HTML escaping of special characters. * The translations performed are: * '&' (ampersand) ==> '&' * '"' (double quote) ==> '"' when ENT_NOQUOTES is not set. * "'" (single quote) ==> ''' only when ENT_QUOTES is set. * '<' (less than) ==> '<' * '>' (greater than) ==> '>' * Parameters * $string * The string being converted. * $flags * A bitmask of one or more of the following flags, which specify how to handle quotes. * The default is ENT_COMPAT | ENT_HTML401. * ENT_COMPAT Will convert double-quotes and leave single-quotes alone. * ENT_QUOTES Will convert both double and single quotes. * ENT_NOQUOTES Will leave both double and single quotes unconverted. * ENT_IGNORE Silently discard invalid code unit sequences instead of returning an empty string. * $charset * Defines character set used in conversion. The default character set is ISO-8859-1. (Not used) * Return * The escaped string or NULL on failure. */ static int jx9Builtin_htmlspecialchars(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zCur, *zIn, *zEnd; int iFlags = 0x01|0x40; /* ENT_COMPAT | ENT_HTML401 */ int nLen, c; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; /* Extract the flags if available */ if( nArg > 1 ){ iFlags = jx9_value_to_int(apArg[1]); if( iFlags < 0 ){ iFlags = 0x01|0x40; } } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ break; } zCur = zIn; while( zIn < zEnd && zIn[0] != '&' && zIn[0] != '\'' && zIn[0] != '"' && zIn[0] != '<' && zIn[0] != '>' ){ zIn++; } if( zCur < zIn ){ /* Append the raw string verbatim */ jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); } if( zIn >= zEnd ){ break; } c = zIn[0]; if( c == '&' ){ /* Expand '&' */ jx9_result_string(pCtx, "&", (int)sizeof("&")-1); }else if( c == '<' ){ /* Expand '<' */ jx9_result_string(pCtx, "<", (int)sizeof("<")-1); }else if( c == '>' ){ /* Expand '>' */ jx9_result_string(pCtx, ">", (int)sizeof(">")-1); }else if( c == '\'' ){ if( iFlags & 0x02 /*ENT_QUOTES*/ ){ /* Expand ''' */ jx9_result_string(pCtx, "'", (int)sizeof("'")-1); }else{ /* Leave the single quote untouched */ jx9_result_string(pCtx, "'", (int)sizeof(char)); } }else if( c == '"' ){ if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ /* Expand '"' */ jx9_result_string(pCtx, """, (int)sizeof(""")-1); }else{ /* Leave the double quote untouched */ jx9_result_string(pCtx, "\"", (int)sizeof(char)); } } /* Ignore the unsafe HTML character */ zIn++; } return JX9_OK; } /* * string htmlspecialchars_decode(string $string[, int $quote_style = ENT_COMPAT ]) * Unescape HTML entities. * Parameters * $string * The string to decode * $quote_style * The quote style. One of the following constants: * ENT_COMPAT Will convert double-quotes and leave single-quotes alone (default) * ENT_QUOTES Will convert both double and single quotes * ENT_NOQUOTES Will leave both double and single quotes unconverted * Return * The unescaped string or NULL on failure. */ static int jx9Builtin_htmlspecialchars_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zCur, *zIn, *zEnd; int iFlags = 0x01; /* ENT_COMPAT */ int nLen, nJump; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; /* Extract the flags if available */ if( nArg > 1 ){ iFlags = jx9_value_to_int(apArg[1]); if( iFlags < 0 ){ iFlags = 0x01; } } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ break; } zCur = zIn; while( zIn < zEnd && zIn[0] != '&' ){ zIn++; } if( zCur < zIn ){ /* Append the raw string verbatim */ jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); } nLen = (int)(zEnd-zIn); nJump = (int)sizeof(char); if( nLen >= (int)sizeof("&")-1 && SyStrnicmp(zIn, "&", sizeof("&")-1) == 0 ){ /* & ==> '&' */ jx9_result_string(pCtx, "&", (int)sizeof(char)); nJump = (int)sizeof("&")-1; }else if( nLen >= (int)sizeof("<")-1 && SyStrnicmp(zIn, "<", sizeof("<")-1) == 0 ){ /* < ==> < */ jx9_result_string(pCtx, "<", (int)sizeof(char)); nJump = (int)sizeof("<")-1; }else if( nLen >= (int)sizeof(">")-1 && SyStrnicmp(zIn, ">", sizeof(">")-1) == 0 ){ /* > ==> '>' */ jx9_result_string(pCtx, ">", (int)sizeof(char)); nJump = (int)sizeof(">")-1; }else if( nLen >= (int)sizeof(""")-1 && SyStrnicmp(zIn, """, sizeof(""")-1) == 0 ){ /* " ==> '"' */ if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ jx9_result_string(pCtx, "\"", (int)sizeof(char)); }else{ /* Leave untouched */ jx9_result_string(pCtx, """, (int)sizeof(""")-1); } nJump = (int)sizeof(""")-1; }else if( nLen >= (int)sizeof("'")-1 && SyStrnicmp(zIn, "'", sizeof("'")-1) == 0 ){ /* ' ==> ''' */ if( iFlags & 0x02 /*ENT_QUOTES*/ ){ /* Expand ''' */ jx9_result_string(pCtx, "'", (int)sizeof(char)); }else{ /* Leave untouched */ jx9_result_string(pCtx, "'", (int)sizeof("'")-1); } nJump = (int)sizeof("'")-1; }else if( nLen >= (int)sizeof(char) ){ /* expand '&' */ jx9_result_string(pCtx, "&", (int)sizeof(char)); }else{ /* No more input to process */ break; } zIn += nJump; } return JX9_OK; } /* HTML encoding/Decoding table * Source: Symisc RunTime API.[chm@symisc.net] */ static const char *azHtmlEscape[] = { "<", "<", ">", ">", "&", "&", """, "\"", "'", "'", "!", "!", "$", "$", "#", "#", "%", "%", "(", "(", ")", ")", "{", "{", "}", "}", "=", "=", "+", "+", "?", "?", "[", "[", "]", "]", "@", "@", ",", "," }; /* * array get_html_translation_table(void) * Returns the translation table used by htmlspecialchars() and htmlentities(). * Parameters * None * Return * The translation table as an array or NULL on failure. */ static int jx9Builtin_get_html_translation_table(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pArray, *pValue; sxu32 n; /* Element value */ pValue = jx9_context_new_scalar(pCtx); if( pValue == 0 ){ SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Make the table */ for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ /* Prepare the value */ jx9_value_string(pValue, azHtmlEscape[n], -1 /* Compute length automatically */); /* Insert the value */ jx9_array_add_strkey_elem(pArray, azHtmlEscape[n+1], pValue); /* Reset the string cursor */ jx9_value_reset_string_cursor(pValue); } /* * Return the array. * Don't worry about freeing memory, everything will be automatically * released upon we return from this function. */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * string htmlentities( string $string [, int $flags = ENT_COMPAT | ENT_HTML401]); * Convert all applicable characters to HTML entities * Parameters * $string * The input string. * $flags * A bitmask of one or more of the flags (see block-comment on jx9Builtin_htmlspecialchars()) * Return * The encoded string. */ static int jx9Builtin_htmlentities(jx9_context *pCtx, int nArg, jx9_value **apArg) { int iFlags = 0x01; /* ENT_COMPAT */ const char *zIn, *zEnd; int nLen, c; sxu32 n; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; /* Extract the flags if available */ if( nArg > 1 ){ iFlags = jx9_value_to_int(apArg[1]); if( iFlags < 0 ){ iFlags = 0x01; } } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* No more input to process */ break; } c = zIn[0]; /* Perform a linear lookup on the decoding table */ for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ if( azHtmlEscape[n+1][0] == c ){ /* Got one */ break; } } if( n < SX_ARRAYSIZE(azHtmlEscape) ){ /* Output the safe sequence [i.e: '<' ==> '<"] */ if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ /* Expand the double quote verbatim */ jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); }else if(c == '\'' && ((iFlags & 0x02 /*ENT_QUOTES*/) == 0 || (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ /* expand single quote verbatim */ jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); }else{ jx9_result_string(pCtx, azHtmlEscape[n], -1/*Compute length automatically */); } }else{ /* Output character verbatim */ jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); } zIn++; } return JX9_OK; } /* * string html_entity_decode(string $string [, int $quote_style = ENT_COMPAT [, string $charset = 'UTF-8' ]]) * Perform the reverse operation of html_entity_decode(). * Parameters * $string * The input string. * $flags * A bitmask of one or more of the flags (see comment on jx9Builtin_htmlspecialchars()) * Return * The decoded string. */ static int jx9Builtin_html_entity_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zCur, *zIn, *zEnd; int iFlags = 0x01; /* ENT_COMPAT */ int nLen; sxu32 n; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; /* Extract the flags if available */ if( nArg > 1 ){ iFlags = jx9_value_to_int(apArg[1]); if( iFlags < 0 ){ iFlags = 0x01; } } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* No more input to process */ break; } zCur = zIn; while( zIn < zEnd && zIn[0] != '&' ){ zIn++; } if( zCur < zIn ){ /* Append raw string verbatim */ jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); } if( zIn >= zEnd ){ break; } nLen = (int)(zEnd-zIn); /* Find an encoded sequence */ for(n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ int iLen = (int)SyStrlen(azHtmlEscape[n]); if( nLen >= iLen && SyStrnicmp(zIn, azHtmlEscape[n], (sxu32)iLen) == 0 ){ /* Got one */ zIn += iLen; break; } } if( n < SX_ARRAYSIZE(azHtmlEscape) ){ int c = azHtmlEscape[n+1][0]; /* Output the decoded character */ if( c == '\'' && ((iFlags & 0x02) == 0 /*ENT_QUOTES*/|| (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ /* Do not process single quotes */ jx9_result_string(pCtx, azHtmlEscape[n], -1); }else if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ /* Do not process double quotes */ jx9_result_string(pCtx, azHtmlEscape[n], -1); }else{ jx9_result_string(pCtx, azHtmlEscape[n+1], -1); /* Compute length automatically */ } }else{ /* Append '&' */ jx9_result_string(pCtx, "&", (int)sizeof(char)); zIn++; } } return JX9_OK; } /* * int strlen($string) * return the length of the given string. * Parameter * string: The string being measured for length. * Return * length of the given string. */ static int jx9Builtin_strlen(jx9_context *pCtx, int nArg, jx9_value **apArg) { int iLen = 0; if( nArg > 0 ){ jx9_value_to_string(apArg[0], &iLen); } /* String length */ jx9_result_int(pCtx, iLen); return JX9_OK; } /* * int strcmp(string $str1, string $str2) * Perform a binary safe string comparison. * Parameter * str1: The first string * str2: The second string * Return * Returns < 0 if str1 is less than str2; > 0 if str1 is greater * than str2, and 0 if they are equal. */ static int jx9Builtin_strcmp(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *z1, *z2; int n1, n2; int res; if( nArg < 2 ){ res = nArg == 0 ? 0 : 1; jx9_result_int(pCtx, res); return JX9_OK; } /* Perform the comparison */ z1 = jx9_value_to_string(apArg[0], &n1); z2 = jx9_value_to_string(apArg[1], &n2); res = SyStrncmp(z1, z2, (sxu32)(SXMAX(n1, n2))); /* Comparison result */ jx9_result_int(pCtx, res); return JX9_OK; } /* * int strncmp(string $str1, string $str2, int n) * Perform a binary safe string comparison of the first n characters. * Parameter * str1: The first string * str2: The second string * Return * Returns < 0 if str1 is less than str2; > 0 if str1 is greater * than str2, and 0 if they are equal. */ static int jx9Builtin_strncmp(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *z1, *z2; int res; int n; if( nArg < 3 ){ /* Perform a standard comparison */ return jx9Builtin_strcmp(pCtx, nArg, apArg); } /* Desired comparison length */ n = jx9_value_to_int(apArg[2]); if( n < 0 ){ /* Invalid length */ jx9_result_int(pCtx, -1); return JX9_OK; } /* Perform the comparison */ z1 = jx9_value_to_string(apArg[0], 0); z2 = jx9_value_to_string(apArg[1], 0); res = SyStrncmp(z1, z2, (sxu32)n); /* Comparison result */ jx9_result_int(pCtx, res); return JX9_OK; } /* * int strcasecmp(string $str1, string $str2, int n) * Perform a binary safe case-insensitive string comparison. * Parameter * str1: The first string * str2: The second string * Return * Returns < 0 if str1 is less than str2; > 0 if str1 is greater * than str2, and 0 if they are equal. */ static int jx9Builtin_strcasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *z1, *z2; int n1, n2; int res; if( nArg < 2 ){ res = nArg == 0 ? 0 : 1; jx9_result_int(pCtx, res); return JX9_OK; } /* Perform the comparison */ z1 = jx9_value_to_string(apArg[0], &n1); z2 = jx9_value_to_string(apArg[1], &n2); res = SyStrnicmp(z1, z2, (sxu32)(SXMAX(n1, n2))); /* Comparison result */ jx9_result_int(pCtx, res); return JX9_OK; } /* * int strncasecmp(string $str1, string $str2, int n) * Perform a binary safe case-insensitive string comparison of the first n characters. * Parameter * $str1: The first string * $str2: The second string * $len: The length of strings to be used in the comparison. * Return * Returns < 0 if str1 is less than str2; > 0 if str1 is greater * than str2, and 0 if they are equal. */ static int jx9Builtin_strncasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *z1, *z2; int res; int n; if( nArg < 3 ){ /* Perform a standard comparison */ return jx9Builtin_strcasecmp(pCtx, nArg, apArg); } /* Desired comparison length */ n = jx9_value_to_int(apArg[2]); if( n < 0 ){ /* Invalid length */ jx9_result_int(pCtx, -1); return JX9_OK; } /* Perform the comparison */ z1 = jx9_value_to_string(apArg[0], 0); z2 = jx9_value_to_string(apArg[1], 0); res = SyStrnicmp(z1, z2, (sxu32)n); /* Comparison result */ jx9_result_int(pCtx, res); return JX9_OK; } /* * Implode context [i.e: it's private data]. * A pointer to the following structure is forwarded * verbatim to the array walker callback defined below. */ struct implode_data { jx9_context *pCtx; /* Call context */ int bRecursive; /* TRUE if recursive implode [this is a symisc eXtension] */ const char *zSep; /* Arguments separator if any */ int nSeplen; /* Separator length */ int bFirst; /* TRUE if first call */ int nRecCount; /* Recursion count to avoid infinite loop */ }; /* * Implode walker callback for the [jx9_array_walk()] interface. * The following routine is invoked for each array entry passed * to the implode() function. */ static int implode_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData) { struct implode_data *pData = (struct implode_data *)pUserData; const char *zData; int nLen; if( pData->bRecursive && jx9_value_is_json_array(pValue) && pData->nRecCount < 32 ){ if( pData->nSeplen > 0 ){ if( !pData->bFirst ){ /* append the separator first */ jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen); }else{ pData->bFirst = 0; } } /* Recurse */ pData->bFirst = 1; pData->nRecCount++; jx9HashmapWalk((jx9_hashmap *)pValue->x.pOther, implode_callback, pData); pData->nRecCount--; return JX9_OK; } /* Extract the string representation of the entry value */ zData = jx9_value_to_string(pValue, &nLen); if( nLen > 0 ){ if( pData->nSeplen > 0 ){ if( !pData->bFirst ){ /* append the separator first */ jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen); }else{ pData->bFirst = 0; } } jx9_result_string(pData->pCtx, zData, nLen); }else{ SXUNUSED(pKey); /* cc warning */ } return JX9_OK; } /* * string implode(string $glue, array $pieces, ...) * string implode(array $pieces, ...) * Join array elements with a string. * $glue * Defaults to an empty string. This is not the preferred usage of implode() as glue * would be the second parameter and thus, the bad prototype would be used. * $pieces * The array of strings to implode. * Return * Returns a string containing a string representation of all the array elements in the same * order, with the glue string between each element. */ static int jx9Builtin_implode(jx9_context *pCtx, int nArg, jx9_value **apArg) { struct implode_data imp_data; int i = 1; if( nArg < 1 ){ /* Missing argument, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Prepare the implode context */ imp_data.pCtx = pCtx; imp_data.bRecursive = 0; imp_data.bFirst = 1; imp_data.nRecCount = 0; if( !jx9_value_is_json_array(apArg[0]) ){ imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen); }else{ imp_data.zSep = 0; imp_data.nSeplen = 0; i = 0; } jx9_result_string(pCtx, "", 0); /* Set an empty stirng */ /* Start the 'join' process */ while( i < nArg ){ if( jx9_value_is_json_array(apArg[i]) ){ /* Iterate throw array entries */ jx9_array_walk(apArg[i], implode_callback, &imp_data); }else{ const char *zData; int nLen; /* Extract the string representation of the jx9 value */ zData = jx9_value_to_string(apArg[i], &nLen); if( nLen > 0 ){ if( imp_data.nSeplen > 0 ){ if( !imp_data.bFirst ){ /* append the separator first */ jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen); }else{ imp_data.bFirst = 0; } } jx9_result_string(pCtx, zData, nLen); } } i++; } return JX9_OK; } /* * string implode_recursive(string $glue, array $pieces, ...) * Purpose * Same as implode() but recurse on arrays. * Example: * $a = array('usr', array('home', 'dean')); * print implode_recursive("/", $a); * Will output * usr/home/dean. * While the standard implode would produce. * usr/Array. * Parameter * Refer to implode(). * Return * Refer to implode(). */ static int jx9Builtin_implode_recursive(jx9_context *pCtx, int nArg, jx9_value **apArg) { struct implode_data imp_data; int i = 1; if( nArg < 1 ){ /* Missing argument, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Prepare the implode context */ imp_data.pCtx = pCtx; imp_data.bRecursive = 1; imp_data.bFirst = 1; imp_data.nRecCount = 0; if( !jx9_value_is_json_array(apArg[0]) ){ imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen); }else{ imp_data.zSep = 0; imp_data.nSeplen = 0; i = 0; } jx9_result_string(pCtx, "", 0); /* Set an empty stirng */ /* Start the 'join' process */ while( i < nArg ){ if( jx9_value_is_json_array(apArg[i]) ){ /* Iterate throw array entries */ jx9_array_walk(apArg[i], implode_callback, &imp_data); }else{ const char *zData; int nLen; /* Extract the string representation of the jx9 value */ zData = jx9_value_to_string(apArg[i], &nLen); if( nLen > 0 ){ if( imp_data.nSeplen > 0 ){ if( !imp_data.bFirst ){ /* append the separator first */ jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen); }else{ imp_data.bFirst = 0; } } jx9_result_string(pCtx, zData, nLen); } } i++; } return JX9_OK; } /* * array explode(string $delimiter, string $string[, int $limit ]) * Returns an array of strings, each of which is a substring of string * formed by splitting it on boundaries formed by the string delimiter. * Parameters * $delimiter * The boundary string. * $string * The input string. * $limit * If limit is set and positive, the returned array will contain a maximum * of limit elements with the last element containing the rest of string. * If the limit parameter is negative, all fields except the last -limit are returned. * If the limit parameter is zero, then this is treated as 1. * Returns * Returns an array of strings created by splitting the string parameter * on boundaries formed by the delimiter. * If delimiter is an empty string (""), explode() will return FALSE. * If delimiter contains a value that is not contained in string and a negative * limit is used, then an empty array will be returned, otherwise an array containing string * will be returned. * NOTE: * Negative limit is not supported. */ static int jx9Builtin_explode(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zDelim, *zString, *zCur, *zEnd; int nDelim, nStrlen, iLimit; jx9_value *pArray; jx9_value *pValue; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the delimiter */ zDelim = jx9_value_to_string(apArg[0], &nDelim); if( nDelim < 1 ){ /* Empty delimiter, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the string */ zString = jx9_value_to_string(apArg[1], &nStrlen); if( nStrlen < 1 ){ /* Empty delimiter, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the end of the string */ zEnd = &zString[nStrlen]; /* Create the array */ pArray = jx9_context_new_array(pCtx); pValue = jx9_context_new_scalar(pCtx); if( pArray == 0 || pValue == 0 ){ /* Out of memory, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Set a defualt limit */ iLimit = SXI32_HIGH; if( nArg > 2 ){ iLimit = jx9_value_to_int(apArg[2]); if( iLimit < 0 ){ iLimit = -iLimit; } if( iLimit == 0 ){ iLimit = 1; } iLimit--; } /* Start exploding */ for(;;){ if( zString >= zEnd ){ /* No more entry to process */ break; } rc = SyBlobSearch(zString, (sxu32)(zEnd-zString), zDelim, nDelim, &nOfft); if( rc != SXRET_OK || iLimit <= (int)jx9_array_count(pArray) ){ /* Limit reached, insert the rest of the string and break */ if( zEnd > zString ){ jx9_value_string(pValue, zString, (int)(zEnd-zString)); jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue); } break; } /* Point to the desired offset */ zCur = &zString[nOfft]; if( zCur > zString ){ /* Perform the store operation */ jx9_value_string(pValue, zString, (int)(zCur-zString)); jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue); } /* Point beyond the delimiter */ zString = &zCur[nDelim]; /* Reset the cursor */ jx9_value_reset_string_cursor(pValue); } /* Return the freshly created array */ jx9_result_value(pCtx, pArray); /* NOTE that every allocated jx9_value will be automatically * released as soon we return from this foregin function. */ return JX9_OK; } /* * string trim(string $str[, string $charlist ]) * Strip whitespace (or other characters) from the beginning and end of a string. * Parameters * $str * The string that will be trimmed. * $charlist * Optionally, the stripped characters can also be specified using the charlist parameter. * Simply list all characters that you want to be stripped. * With .. you can specify a range of characters. * Returns. * Thr processed string. */ static int jx9Builtin_trim(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString; int nLen; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Start the trim process */ if( nArg < 2 ){ SyString sStr; /* Remove white spaces and NUL bytes */ SyStringInitFromBuf(&sStr, zString, nLen); SyStringFullTrimSafe(&sStr); jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); }else{ /* Char list */ const char *zList; int nListlen; zList = jx9_value_to_string(apArg[1], &nListlen); if( nListlen < 1 ){ /* Return the string unchanged */ jx9_result_string(pCtx, zString, nLen); }else{ const char *zEnd = &zString[nLen]; const char *zCur = zString; const char *zPtr; int i; /* Left trim */ for(;;){ if( zCur >= zEnd ){ break; } zPtr = zCur; for( i = 0 ; i < nListlen ; i++ ){ if( zCur < zEnd && zCur[0] == zList[i] ){ zCur++; } } if( zCur == zPtr ){ /* No match, break immediately */ break; } } /* Right trim */ zEnd--; for(;;){ if( zEnd <= zCur ){ break; } zPtr = zEnd; for( i = 0 ; i < nListlen ; i++ ){ if( zEnd > zCur && zEnd[0] == zList[i] ){ zEnd--; } } if( zEnd == zPtr ){ break; } } if( zCur >= zEnd ){ /* Return the empty string */ jx9_result_string(pCtx, "", 0); }else{ zEnd++; jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); } } } return JX9_OK; } /* * string rtrim(string $str[, string $charlist ]) * Strip whitespace (or other characters) from the end of a string. * Parameters * $str * The string that will be trimmed. * $charlist * Optionally, the stripped characters can also be specified using the charlist parameter. * Simply list all characters that you want to be stripped. * With .. you can specify a range of characters. * Returns. * Thr processed string. */ static int jx9Builtin_rtrim(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString; int nLen; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Start the trim process */ if( nArg < 2 ){ SyString sStr; /* Remove white spaces and NUL bytes*/ SyStringInitFromBuf(&sStr, zString, nLen); SyStringRightTrimSafe(&sStr); jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); }else{ /* Char list */ const char *zList; int nListlen; zList = jx9_value_to_string(apArg[1], &nListlen); if( nListlen < 1 ){ /* Return the string unchanged */ jx9_result_string(pCtx, zString, nLen); }else{ const char *zEnd = &zString[nLen - 1]; const char *zCur = zString; const char *zPtr; int i; /* Right trim */ for(;;){ if( zEnd <= zCur ){ break; } zPtr = zEnd; for( i = 0 ; i < nListlen ; i++ ){ if( zEnd > zCur && zEnd[0] == zList[i] ){ zEnd--; } } if( zEnd == zPtr ){ break; } } if( zEnd <= zCur ){ /* Return the empty string */ jx9_result_string(pCtx, "", 0); }else{ zEnd++; jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); } } } return JX9_OK; } /* * string ltrim(string $str[, string $charlist ]) * Strip whitespace (or other characters) from the beginning and end of a string. * Parameters * $str * The string that will be trimmed. * $charlist * Optionally, the stripped characters can also be specified using the charlist parameter. * Simply list all characters that you want to be stripped. * With .. you can specify a range of characters. * Returns. * The processed string. */ static int jx9Builtin_ltrim(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString; int nLen; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Start the trim process */ if( nArg < 2 ){ SyString sStr; /* Remove white spaces and NUL byte */ SyStringInitFromBuf(&sStr, zString, nLen); SyStringLeftTrimSafe(&sStr); jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); }else{ /* Char list */ const char *zList; int nListlen; zList = jx9_value_to_string(apArg[1], &nListlen); if( nListlen < 1 ){ /* Return the string unchanged */ jx9_result_string(pCtx, zString, nLen); }else{ const char *zEnd = &zString[nLen]; const char *zCur = zString; const char *zPtr; int i; /* Left trim */ for(;;){ if( zCur >= zEnd ){ break; } zPtr = zCur; for( i = 0 ; i < nListlen ; i++ ){ if( zCur < zEnd && zCur[0] == zList[i] ){ zCur++; } } if( zCur == zPtr ){ /* No match, break immediately */ break; } } if( zCur >= zEnd ){ /* Return the empty string */ jx9_result_string(pCtx, "", 0); }else{ jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); } } } return JX9_OK; } /* * string strtolower(string $str) * Make a string lowercase. * Parameters * $str * The input string. * Returns. * The lowercased string. */ static int jx9Builtin_strtolower(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zCur, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Perform the requested operation */ zEnd = &zString[nLen]; for(;;){ if( zString >= zEnd ){ /* No more input, break immediately */ break; } if( (unsigned char)zString[0] >= 0xc0 ){ /* UTF-8 stream, output verbatim */ zCur = zString; zString++; while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ zString++; } /* Append UTF-8 stream */ jx9_result_string(pCtx, zCur, (int)(zString-zCur)); }else{ int c = zString[0]; if( SyisUpper(c) ){ c = SyToLower(zString[0]); } /* Append character */ jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); /* Advance the cursor */ zString++; } } return JX9_OK; } /* * string strtolower(string $str) * Make a string uppercase. * Parameters * $str * The input string. * Returns. * The uppercased string. */ static int jx9Builtin_strtoupper(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zCur, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Perform the requested operation */ zEnd = &zString[nLen]; for(;;){ if( zString >= zEnd ){ /* No more input, break immediately */ break; } if( (unsigned char)zString[0] >= 0xc0 ){ /* UTF-8 stream, output verbatim */ zCur = zString; zString++; while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ zString++; } /* Append UTF-8 stream */ jx9_result_string(pCtx, zCur, (int)(zString-zCur)); }else{ int c = zString[0]; if( SyisLower(c) ){ c = SyToUpper(zString[0]); } /* Append character */ jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); /* Advance the cursor */ zString++; } } return JX9_OK; } /* * int ord(string $string) * Returns the ASCII value of the first character of string. * Parameters * $str * The input string. * Returns. * The ASCII value as an integer. */ static int jx9Builtin_ord(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString; int nLen, c; if( nArg < 1 ){ /* Missing arguments, return -1 */ jx9_result_int(pCtx, -1); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return -1 */ jx9_result_int(pCtx, -1); return JX9_OK; } /* Extract the ASCII value of the first character */ c = zString[0]; /* Return that value */ jx9_result_int(pCtx, c); return JX9_OK; } /* * string chr(int $ascii) * Returns a one-character string containing the character specified by ascii. * Parameters * $ascii * The ascii code. * Returns. * The specified character. */ static int jx9Builtin_chr(jx9_context *pCtx, int nArg, jx9_value **apArg) { int c; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the ASCII value */ c = jx9_value_to_int(apArg[0]); /* Return the specified character */ jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); return JX9_OK; } /* * Binary to hex consumer callback. * This callback is the default consumer used by the hash functions * [i.e: bin2hex(), md5(), sha1(), md5_file() ... ] defined below. */ static int HashConsumer(const void *pData, unsigned int nLen, void *pUserData) { /* Append hex chunk verbatim */ jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); return SXRET_OK; } /* * string bin2hex(string $str) * Convert binary data into hexadecimal representation. * Parameters * $str * The input string. * Returns. * Returns the hexadecimal representation of the given string. */ static int jx9Builtin_bin2hex(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString; int nLen; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Perform the requested operation */ SyBinToHexConsumer((const void *)zString, (sxu32)nLen, HashConsumer, pCtx); return JX9_OK; } /* Search callback signature */ typedef sxi32 (*ProcStringMatch)(const void *, sxu32, const void *, sxu32, sxu32 *); /* * Case-insensitive pattern match. * Brute force is the default search method used here. * This is due to the fact that brute-forcing works quite * well for short/medium texts on modern hardware. */ static sxi32 iPatternMatch(const void *pText, sxu32 nLen, const void *pPattern, sxu32 iPatLen, sxu32 *pOfft) { const char *zpIn = (const char *)pPattern; const char *zIn = (const char *)pText; const char *zpEnd = &zpIn[iPatLen]; const char *zEnd = &zIn[nLen]; const char *zPtr, *zPtr2; int c, d; if( iPatLen > nLen ){ /* Don't bother processing */ return SXERR_NOTFOUND; } for(;;){ if( zIn >= zEnd ){ break; } c = SyToLower(zIn[0]); d = SyToLower(zpIn[0]); if( c == d ){ zPtr = &zIn[1]; zPtr2 = &zpIn[1]; for(;;){ if( zPtr2 >= zpEnd ){ /* Pattern found */ if( pOfft ){ *pOfft = (sxu32)(zIn-(const char *)pText); } return SXRET_OK; } if( zPtr >= zEnd ){ break; } c = SyToLower(zPtr[0]); d = SyToLower(zPtr2[0]); if( c != d ){ break; } zPtr++; zPtr2++; } } zIn++; } /* Pattern not found */ return SXERR_NOTFOUND; } /* * string strstr(string $haystack, string $needle[, bool $before_needle = false ]) * Find the first occurrence of a string. * Parameters * $haystack * The input string. * $needle * Search pattern (must be a string). * $before_needle * If TRUE, strstr() returns the part of the haystack before the first occurrence * of the needle (excluding the needle). * Return * Returns the portion of string, or FALSE if needle is not found. */ static int jx9Builtin_strstr(jx9_context *pCtx, int nArg, jx9_value **apArg) { ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ const char *zBlob, *zPattern; int nLen, nPatLen; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the needle and the haystack */ zBlob = jx9_value_to_string(apArg[0], &nLen); zPattern = jx9_value_to_string(apArg[1], &nPatLen); nOfft = 0; /* cc warning */ if( nLen > 0 && nPatLen > 0 ){ int before = 0; /* Perform the lookup */ rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); if( rc != SXRET_OK ){ /* Pattern not found, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return the portion of the string */ if( nArg > 2 ){ before = jx9_value_to_int(apArg[2]); } if( before ){ jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob)); }else{ jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); } }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * string stristr(string $haystack, string $needle[, bool $before_needle = false ]) * Case-insensitive strstr(). * Parameters * $haystack * The input string. * $needle * Search pattern (must be a string). * $before_needle * If TRUE, strstr() returns the part of the haystack before the first occurrence * of the needle (excluding the needle). * Return * Returns the portion of string, or FALSE if needle is not found. */ static int jx9Builtin_stristr(jx9_context *pCtx, int nArg, jx9_value **apArg) { ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ const char *zBlob, *zPattern; int nLen, nPatLen; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the needle and the haystack */ zBlob = jx9_value_to_string(apArg[0], &nLen); zPattern = jx9_value_to_string(apArg[1], &nPatLen); nOfft = 0; /* cc warning */ if( nLen > 0 && nPatLen > 0 ){ int before = 0; /* Perform the lookup */ rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); if( rc != SXRET_OK ){ /* Pattern not found, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return the portion of the string */ if( nArg > 2 ){ before = jx9_value_to_int(apArg[2]); } if( before ){ jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob)); }else{ jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); } }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * int strpos(string $haystack, string $needle [, int $offset = 0 ] ) * Returns the numeric position of the first occurrence of needle in the haystack string. * Parameters * $haystack * The input string. * $needle * Search pattern (must be a string). * $offset * This optional offset parameter allows you to specify which character in haystack * to start searching. The position returned is still relative to the beginning * of haystack. * Return * Returns the position as an integer.If needle is not found, strpos() will return FALSE. */ static int jx9Builtin_strpos(jx9_context *pCtx, int nArg, jx9_value **apArg) { ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ const char *zBlob, *zPattern; int nLen, nPatLen, nStart; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the needle and the haystack */ zBlob = jx9_value_to_string(apArg[0], &nLen); zPattern = jx9_value_to_string(apArg[1], &nPatLen); nOfft = 0; /* cc warning */ nStart = 0; /* Peek the starting offset if available */ if( nArg > 2 ){ nStart = jx9_value_to_int(apArg[2]); if( nStart < 0 ){ nStart = -nStart; } if( nStart >= nLen ){ /* Invalid offset */ nStart = 0; }else{ zBlob += nStart; nLen -= nStart; } } if( nLen > 0 && nPatLen > 0 ){ /* Perform the lookup */ rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); if( rc != SXRET_OK ){ /* Pattern not found, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return the pattern position */ jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart)); }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * int stripos(string $haystack, string $needle [, int $offset = 0 ] ) * Case-insensitive strpos. * Parameters * $haystack * The input string. * $needle * Search pattern (must be a string). * $offset * This optional offset parameter allows you to specify which character in haystack * to start searching. The position returned is still relative to the beginning * of haystack. * Return * Returns the position as an integer.If needle is not found, strpos() will return FALSE. */ static int jx9Builtin_stripos(jx9_context *pCtx, int nArg, jx9_value **apArg) { ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ const char *zBlob, *zPattern; int nLen, nPatLen, nStart; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the needle and the haystack */ zBlob = jx9_value_to_string(apArg[0], &nLen); zPattern = jx9_value_to_string(apArg[1], &nPatLen); nOfft = 0; /* cc warning */ nStart = 0; /* Peek the starting offset if available */ if( nArg > 2 ){ nStart = jx9_value_to_int(apArg[2]); if( nStart < 0 ){ nStart = -nStart; } if( nStart >= nLen ){ /* Invalid offset */ nStart = 0; }else{ zBlob += nStart; nLen -= nStart; } } if( nLen > 0 && nPatLen > 0 ){ /* Perform the lookup */ rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); if( rc != SXRET_OK ){ /* Pattern not found, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return the pattern position */ jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart)); }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * int strrpos(string $haystack, string $needle [, int $offset = 0 ] ) * Find the numeric position of the last occurrence of needle in the haystack string. * Parameters * $haystack * The input string. * $needle * Search pattern (must be a string). * $offset * If specified, search will start this number of characters counted from the beginning * of the string. If the value is negative, search will instead start from that many * characters from the end of the string, searching backwards. * Return * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. */ static int jx9Builtin_strrpos(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd; ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ int nLen, nPatLen; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the needle and the haystack */ zBlob = jx9_value_to_string(apArg[0], &nLen); zPattern = jx9_value_to_string(apArg[1], &nPatLen); /* Point to the end of the pattern */ zPtr = &zBlob[nLen - 1]; zEnd = &zBlob[nLen]; /* Save the starting posistion */ zStart = zBlob; nOfft = 0; /* cc warning */ /* Peek the starting offset if available */ if( nArg > 2 ){ int nStart; nStart = jx9_value_to_int(apArg[2]); if( nStart < 0 ){ nStart = -nStart; if( nStart >= nLen ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; }else{ nLen -= nStart; zPtr = &zBlob[nLen - 1]; zEnd = &zBlob[nLen]; } }else{ if( nStart >= nLen ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; }else{ zBlob += nStart; nLen -= nStart; } } } if( nLen > 0 && nPatLen > 0 ){ /* Perform the lookup */ for(;;){ if( zBlob >= zPtr ){ break; } rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft); if( rc == SXRET_OK ){ /* Pattern found, return it's position */ jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart)); return JX9_OK; } zPtr--; } /* Pattern not found, return FALSE */ jx9_result_bool(pCtx, 0); }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * int strripos(string $haystack, string $needle [, int $offset = 0 ] ) * Case-insensitive strrpos. * Parameters * $haystack * The input string. * $needle * Search pattern (must be a string). * $offset * If specified, search will start this number of characters counted from the beginning * of the string. If the value is negative, search will instead start from that many * characters from the end of the string, searching backwards. * Return * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. */ static int jx9Builtin_strripos(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd; ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ int nLen, nPatLen; sxu32 nOfft; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the needle and the haystack */ zBlob = jx9_value_to_string(apArg[0], &nLen); zPattern = jx9_value_to_string(apArg[1], &nPatLen); /* Point to the end of the pattern */ zPtr = &zBlob[nLen - 1]; zEnd = &zBlob[nLen]; /* Save the starting posistion */ zStart = zBlob; nOfft = 0; /* cc warning */ /* Peek the starting offset if available */ if( nArg > 2 ){ int nStart; nStart = jx9_value_to_int(apArg[2]); if( nStart < 0 ){ nStart = -nStart; if( nStart >= nLen ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; }else{ nLen -= nStart; zPtr = &zBlob[nLen - 1]; zEnd = &zBlob[nLen]; } }else{ if( nStart >= nLen ){ /* Invalid offset */ jx9_result_bool(pCtx, 0); return JX9_OK; }else{ zBlob += nStart; nLen -= nStart; } } } if( nLen > 0 && nPatLen > 0 ){ /* Perform the lookup */ for(;;){ if( zBlob >= zPtr ){ break; } rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft); if( rc == SXRET_OK ){ /* Pattern found, return it's position */ jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart)); return JX9_OK; } zPtr--; } /* Pattern not found, return FALSE */ jx9_result_bool(pCtx, 0); }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * int strrchr(string $haystack, mixed $needle) * Find the last occurrence of a character in a string. * Parameters * $haystack * The input string. * $needle * If needle contains more than one character, only the first is used. * This behavior is different from that of strstr(). * If needle is not a string, it is converted to an integer and applied * as the ordinal value of a character. * Return * This function returns the portion of string, or FALSE if needle is not found. */ static int jx9Builtin_strrchr(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zBlob; int nLen, c; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the haystack */ zBlob = jx9_value_to_string(apArg[0], &nLen); c = 0; /* cc warning */ if( nLen > 0 ){ sxu32 nOfft; sxi32 rc; if( jx9_value_is_string(apArg[1]) ){ const char *zPattern; zPattern = jx9_value_to_string(apArg[1], 0); /* Never fail, so there is no need to check * for NULL pointer. */ c = zPattern[0]; }else{ /* Int cast */ c = jx9_value_to_int(apArg[1]); } /* Perform the lookup */ rc = SyByteFind2(zBlob, (sxu32)nLen, c, &nOfft); if( rc != SXRET_OK ){ /* No such entry, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return the string portion */ jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * string strrev(string $string) * Reverse a string. * Parameters * $string * String to be reversed. * Return * The reversed string. */ static int jx9Builtin_strrev(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn, *zEnd; int nLen, c; if( nArg < 1 ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string Return null */ jx9_result_null(pCtx); return JX9_OK; } /* Perform the requested operation */ zEnd = &zIn[nLen - 1]; for(;;){ if( zEnd < zIn ){ /* No more input to process */ break; } /* Append current character */ c = zEnd[0]; jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); zEnd--; } return JX9_OK; } /* * string str_repeat(string $input, int $multiplier) * Returns input repeated multiplier times. * Parameters * $string * String to be repeated. * $multiplier * Number of time the input string should be repeated. * multiplier has to be greater than or equal to 0. If the multiplier is set * to 0, the function will return an empty string. * Return * The repeated string. */ static int jx9Builtin_str_repeat(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn; int nLen, nMul; int rc; if( nArg < 2 ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string.Return null */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the multiplier */ nMul = jx9_value_to_int(apArg[1]); if( nMul < 1 ){ /* Return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( nMul < 1 ){ break; } /* Append the copy */ rc = jx9_result_string(pCtx, zIn, nLen); if( rc != JX9_OK ){ /* Out of memory, break immediately */ break; } nMul--; } return JX9_OK; } /* * string nl2br(string $string[, bool $is_xhtml = true ]) * Inserts HTML line breaks before all newlines in a string. * Parameters * $string * The input string. * $is_xhtml * Whenever to use XHTML compatible line breaks or not. * Return * The processed string. */ static int jx9Builtin_nl2br(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn, *zCur, *zEnd; int is_xhtml = 0; int nLen; if( nArg < 1 ){ /* Missing arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string, return null */ jx9_result_null(pCtx); return JX9_OK; } if( nArg > 1 ){ is_xhtml = jx9_value_to_bool(apArg[1]); } zEnd = &zIn[nLen]; /* Perform the requested operation */ for(;;){ zCur = zIn; /* Delimit the string */ while( zIn < zEnd && (zIn[0] != '\n'&& zIn[0] != '\r') ){ zIn++; } if( zCur < zIn ){ /* Output chunk verbatim */ jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); } if( zIn >= zEnd ){ /* No more input to process */ break; } /* Output the HTML line break */ if( is_xhtml ){ jx9_result_string(pCtx, "
", (int)sizeof("
")-1); }else{ jx9_result_string(pCtx, "
", (int)sizeof("
")-1); } zCur = zIn; /* Append trailing line */ while( zIn < zEnd && (zIn[0] == '\n' || zIn[0] == '\r') ){ zIn++; } if( zCur < zIn ){ /* Output chunk verbatim */ jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); } } return JX9_OK; } /* * Format a given string and invoke the given callback on each processed chunk. * According to the JX9 reference manual. * The format string is composed of zero or more directives: ordinary characters * (excluding %) that are copied directly to the result, and conversion * specifications, each of which results in fetching its own parameter. * This applies to both sprintf() and printf(). * Each conversion specification consists of a percent sign (%), followed by one * or more of these elements, in order: * An optional sign specifier that forces a sign (- or +) to be used on a number. * By default, only the - sign is used on a number if it's negative. This specifier forces * positive numbers to have the + sign attached as well. * An optional padding specifier that says what character will be used for padding * the results to the right string size. This may be a space character or a 0 (zero character). * The default is to pad with spaces. An alternate padding character can be specified by prefixing * it with a single quote ('). See the examples below. * An optional alignment specifier that says if the result should be left-justified or right-justified. * The default is right-justified; a - character here will make it left-justified. * An optional number, a width specifier that says how many characters (minimum) this conversion * should result in. * An optional precision specifier in the form of a period (`.') followed by an optional decimal * digit string that says how many decimal digits should be displayed for floating-point numbers. * When using this specifier on a string, it acts as a cutoff point, setting a maximum character * limit to the string. * A type specifier that says what type the argument data should be treated as. Possible types: * % - a literal percent character. No argument is required. * b - the argument is treated as an integer, and presented as a binary number. * c - the argument is treated as an integer, and presented as the character with that ASCII value. * d - the argument is treated as an integer, and presented as a (signed) decimal number. * e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands * for the number of digits after the decimal point. * E - like %e but uses uppercase letter (e.g. 1.2E+2). * u - the argument is treated as an integer, and presented as an unsigned decimal number. * f - the argument is treated as a float, and presented as a floating-point number (locale aware). * F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). * g - shorter of %e and %f. * G - shorter of %E and %f. * o - the argument is treated as an integer, and presented as an octal number. * s - the argument is treated as and presented as a string. * x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters). * X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters). */ /* * This implementation is based on the one found in the SQLite3 source tree. */ #define JX9_FMT_BUFSIZ 1024 /* Conversion buffer size */ /* ** Conversion types fall into various categories as defined by the ** following enumeration. */ #define JX9_FMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ #define JX9_FMT_FLOAT 2 /* Floating point.%f */ #define JX9_FMT_EXP 3 /* Exponentional notation.%e and %E */ #define JX9_FMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ #define JX9_FMT_SIZE 5 /* Total number of characters processed so far.%n */ #define JX9_FMT_STRING 6 /* Strings.%s */ #define JX9_FMT_PERCENT 7 /* Percent symbol.%% */ #define JX9_FMT_CHARX 8 /* Characters.%c */ #define JX9_FMT_ERROR 9 /* Used to indicate no such conversion type */ /* ** Allowed values for jx9_fmt_info.flags */ #define JX9_FMT_FLAG_SIGNED 0x01 #define JX9_FMT_FLAG_UNSIGNED 0x02 /* ** Each builtin conversion character (ex: the 'd' in "%d") is described ** by an instance of the following structure */ typedef struct jx9_fmt_info jx9_fmt_info; struct jx9_fmt_info { char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */ sxu8 base; /* The base for radix conversion */ int flags; /* One or more of JX9_FMT_FLAG_ constants below */ sxu8 type; /* Conversion paradigm */ char *charset; /* The character set for conversion */ char *prefix; /* Prefix on non-zero values in alt format */ }; #ifndef JX9_OMIT_FLOATING_POINT /* ** "*val" is a double such that 0.1 <= *val < 10.0 ** Return the ascii code for the leading digit of *val, then ** multiply "*val" by 10.0 to renormalize. ** ** Example: ** input: *val = 3.14159 ** output: *val = 1.4159 function return = '3' ** ** The counter *cnt is incremented each time. After counter exceeds ** 16 (the number of significant digits in a 64-bit float) '0' is ** always returned. */ static int vxGetdigit(sxlongreal *val, int *cnt) { sxlongreal d; int digit; if( (*cnt)++ >= 16 ){ return '0'; } digit = (int)*val; d = digit; *val = (*val - d)*10.0; return digit + '0' ; } #endif /* JX9_OMIT_FLOATING_POINT */ /* * The following table is searched linearly, so it is good to put the most frequently * used conversion types first. */ static const jx9_fmt_info aFmt[] = { { 'd', 10, JX9_FMT_FLAG_SIGNED, JX9_FMT_RADIX, "0123456789", 0 }, { 's', 0, 0, JX9_FMT_STRING, 0, 0 }, { 'c', 0, 0, JX9_FMT_CHARX, 0, 0 }, { 'x', 16, 0, JX9_FMT_RADIX, "0123456789abcdef", "x0" }, { 'X', 16, 0, JX9_FMT_RADIX, "0123456789ABCDEF", "X0" }, { 'b', 2, 0, JX9_FMT_RADIX, "01", "b0"}, { 'o', 8, 0, JX9_FMT_RADIX, "01234567", "0" }, { 'u', 10, 0, JX9_FMT_RADIX, "0123456789", 0 }, { 'f', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 }, { 'F', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 }, { 'e', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "e", 0 }, { 'E', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "E", 0 }, { 'g', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "e", 0 }, { 'G', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "E", 0 }, { '%', 0, 0, JX9_FMT_PERCENT, 0, 0 } }; /* * Format a given string. * The root program. All variations call this core. * INPUTS: * xConsumer This is a pointer to a function taking four arguments * 1. A pointer to the call context. * 2. A pointer to the list of characters to be output * (Note, this list is NOT null terminated.) * 3. An integer number of characters to be output. * (Note: This number might be zero.) * 4. Upper layer private data. * zIn This is the format string, as in the usual print. * apArg This is a pointer to a list of arguments. */ JX9_PRIVATE sxi32 jx9InputFormat( int (*xConsumer)(jx9_context *, const char *, int, void *), /* Format consumer */ jx9_context *pCtx, /* call context */ const char *zIn, /* Format string */ int nByte, /* Format string length */ int nArg, /* Total argument of the given arguments */ jx9_value **apArg, /* User arguments */ void *pUserData, /* Last argument to xConsumer() */ int vf /* TRUE if called from vfprintf, vsprintf context */ ) { char spaces[] = " "; #define etSPACESIZE ((int)sizeof(spaces)-1) const char *zCur, *zEnd = &zIn[nByte]; char *zBuf, zWorker[JX9_FMT_BUFSIZ]; /* Working buffer */ const jx9_fmt_info *pInfo; /* Pointer to the appropriate info structure */ int flag_alternateform; /* True if "#" flag is present */ int flag_leftjustify; /* True if "-" flag is present */ int flag_blanksign; /* True if " " flag is present */ int flag_plussign; /* True if "+" flag is present */ int flag_zeropad; /* True if field width constant starts with zero */ jx9_value *pArg; /* Current processed argument */ jx9_int64 iVal; int precision; /* Precision of the current field */ char *zExtra; int c, rc, n; int length; /* Length of the field */ int prefix; sxu8 xtype; /* Conversion paradigm */ int width; /* Width of the current field */ int idx; n = (vf == TRUE) ? 0 : 1; #define NEXT_ARG ( n < nArg ? apArg[n++] : 0 ) /* Start the format process */ for(;;){ zCur = zIn; while( zIn < zEnd && zIn[0] != '%' ){ zIn++; } if( zCur < zIn ){ /* Consume chunk verbatim */ rc = xConsumer(pCtx, zCur, (int)(zIn-zCur), pUserData); if( rc == SXERR_ABORT ){ /* Callback request an operation abort */ break; } } if( zIn >= zEnd ){ /* No more input to process, break immediately */ break; } /* Find out what flags are present */ flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_zeropad = 0; zIn++; /* Jump the precent sign */ do{ c = zIn[0]; switch( c ){ case '-': flag_leftjustify = 1; c = 0; break; case '+': flag_plussign = 1; c = 0; break; case ' ': flag_blanksign = 1; c = 0; break; case '#': flag_alternateform = 1; c = 0; break; case '0': flag_zeropad = 1; c = 0; break; case '\'': zIn++; if( zIn < zEnd ){ /* An alternate padding character can be specified by prefixing it with a single quote (') */ c = zIn[0]; for(idx = 0 ; idx < etSPACESIZE ; ++idx ){ spaces[idx] = (char)c; } c = 0; } break; default: break; } }while( c==0 && (zIn++ < zEnd) ); /* Get the field width */ width = 0; while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ width = width*10 + (zIn[0] - '0'); zIn++; } if( zIn < zEnd && zIn[0] == '$' ){ /* Position specifer */ if( width > 0 ){ n = width; if( vf && n > 0 ){ n--; } } zIn++; width = 0; if( zIn < zEnd && zIn[0] == '0' ){ flag_zeropad = 1; zIn++; } while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ width = width*10 + (zIn[0] - '0'); zIn++; } } if( width > JX9_FMT_BUFSIZ-10 ){ width = JX9_FMT_BUFSIZ-10; } /* Get the precision */ precision = -1; if( zIn < zEnd && zIn[0] == '.' ){ precision = 0; zIn++; while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ precision = precision*10 + (zIn[0] - '0'); zIn++; } } if( zIn >= zEnd ){ /* No more input */ break; } /* Fetch the info entry for the field */ pInfo = 0; xtype = JX9_FMT_ERROR; c = zIn[0]; zIn++; /* Jump the format specifer */ for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ if( c==aFmt[idx].fmttype ){ pInfo = &aFmt[idx]; xtype = pInfo->type; break; } } zBuf = zWorker; /* Point to the working buffer */ length = 0; zExtra = 0; /* ** At this point, variables are initialized as follows: ** ** flag_alternateform TRUE if a '#' is present. ** flag_plussign TRUE if a '+' is present. ** flag_leftjustify TRUE if a '-' is present or if the ** field width was negative. ** flag_zeropad TRUE if the width began with 0. ** the conversion character. ** flag_blanksign TRUE if a ' ' is present. ** width The specified field width. This is ** always non-negative. Zero is the default. ** precision The specified precision. The default ** is -1. */ switch(xtype){ case JX9_FMT_PERCENT: /* A literal percent character */ zWorker[0] = '%'; length = (int)sizeof(char); break; case JX9_FMT_CHARX: /* The argument is treated as an integer, and presented as the character * with that ASCII value */ pArg = NEXT_ARG; if( pArg == 0 ){ c = 0; }else{ c = jx9_value_to_int(pArg); } /* NUL byte is an acceptable value */ zWorker[0] = (char)c; length = (int)sizeof(char); break; case JX9_FMT_STRING: /* the argument is treated as and presented as a string */ pArg = NEXT_ARG; if( pArg == 0 ){ length = 0; }else{ zBuf = (char *)jx9_value_to_string(pArg, &length); } if( length < 1 ){ zBuf = " "; length = (int)sizeof(char); } if( precision>=0 && precisionJX9_FMT_BUFSIZ-40 ){ precision = JX9_FMT_BUFSIZ-40; } #if 1 /* For the format %#x, the value zero is printed "0" not "0x0". ** I think this is stupid.*/ if( iVal==0 ) flag_alternateform = 0; #else /* More sensible: turn off the prefix for octal (to prevent "00"), ** but leave the prefix for hex.*/ if( iVal==0 && pInfo->base==8 ) flag_alternateform = 0; #endif if( pInfo->flags & JX9_FMT_FLAG_SIGNED ){ if( iVal<0 ){ iVal = -iVal; /* Ticket 1433-003 */ if( iVal < 0 ){ /* Overflow */ iVal= 0x7FFFFFFFFFFFFFFF; } prefix = '-'; }else if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; }else{ if( iVal<0 ){ iVal = -iVal; /* Ticket 1433-003 */ if( iVal < 0 ){ /* Overflow */ iVal= 0x7FFFFFFFFFFFFFFF; } } prefix = 0; } if( flag_zeropad && precisioncharset; base = pInfo->base; do{ /* Convert to ascii */ *(--zBuf) = cset[iVal%base]; iVal = iVal/base; }while( iVal>0 ); } length = (int)(&zWorker[JX9_FMT_BUFSIZ-1]-zBuf); for(idx=precision-length; idx>0; idx--){ *(--zBuf) = '0'; /* Zero pad */ } if( prefix ) *(--zBuf) = (char)prefix; /* Add sign */ if( flag_alternateform && pInfo->prefix ){ /* Add "0" or "0x" */ char *pre, x; pre = pInfo->prefix; if( *zBuf!=pre[0] ){ for(pre=pInfo->prefix; (x=(*pre))!=0; pre++) *(--zBuf) = x; } } length = (int)(&zWorker[JX9_FMT_BUFSIZ-1]-zBuf); break; case JX9_FMT_FLOAT: case JX9_FMT_EXP: case JX9_FMT_GENERIC:{ #ifndef JX9_OMIT_FLOATING_POINT long double realvalue; int exp; /* exponent of real numbers */ double rounder; /* Used for rounding floating point values */ int flag_dp; /* True if decimal point should be shown */ int flag_rtz; /* True if trailing zeros should be removed */ int flag_exp; /* True to force display of the exponent */ int nsd; /* Number of significant digits returned */ pArg = NEXT_ARG; if( pArg == 0 ){ realvalue = 0; }else{ realvalue = jx9_value_to_double(pArg); } if( precision<0 ) precision = 6; /* Set default precision */ if( precision>JX9_FMT_BUFSIZ-40) precision = JX9_FMT_BUFSIZ-40; if( realvalue<0.0 ){ realvalue = -realvalue; prefix = '-'; }else{ if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; } if( pInfo->type==JX9_FMT_GENERIC && precision>0 ) precision--; rounder = 0.0; #if 0 /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); #else /* It makes more sense to use 0.5 */ for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); #endif if( pInfo->type==JX9_FMT_FLOAT ) realvalue += rounder; /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ exp = 0; if( realvalue>0.0 ){ while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } if( exp>350 || exp<-350 ){ zBuf = "NaN"; length = 3; break; } } zBuf = zWorker; /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ flag_exp = xtype==JX9_FMT_EXP; if( xtype!=JX9_FMT_FLOAT ){ realvalue += rounder; if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } } if( xtype==JX9_FMT_GENERIC ){ flag_rtz = !flag_alternateform; if( exp<-4 || exp>precision ){ xtype = JX9_FMT_EXP; }else{ precision = precision - exp; xtype = JX9_FMT_FLOAT; } }else{ flag_rtz = 0; } /* ** The "exp+precision" test causes output to be of type etEXP if ** the precision is too large to fit in buf[]. */ nsd = 0; if( xtype==JX9_FMT_FLOAT && exp+precision0 || flag_alternateform); if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ if( exp<0 ) *(zBuf++) = '0'; /* Digits before "." */ else for(; exp>=0; exp--) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); if( flag_dp ) *(zBuf++) = '.'; /* The decimal point */ for(exp++; exp<0 && precision>0; precision--, exp++){ *(zBuf++) = '0'; } while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); *(zBuf--) = 0; /* Null terminate */ if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; } zBuf++; /* point to next free slot */ }else{ /* etEXP or etGENERIC */ flag_dp = (precision>0 || flag_alternateform); if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); /* First digit */ if( flag_dp ) *(zBuf++) = '.'; /* Decimal point */ while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); zBuf--; /* point to last digit */ if( flag_rtz && flag_dp ){ /* Remove tail zeros */ while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; } zBuf++; /* point to next free slot */ if( exp || flag_exp ){ *(zBuf++) = pInfo->charset[0]; if( exp<0 ){ *(zBuf++) = '-'; exp = -exp; } /* sign of exp */ else { *(zBuf++) = '+'; } if( exp>=100 ){ *(zBuf++) = (char)((exp/100)+'0'); /* 100's digit */ exp %= 100; } *(zBuf++) = (char)(exp/10+'0'); /* 10's digit */ *(zBuf++) = (char)(exp%10+'0'); /* 1's digit */ } } /* The converted number is in buf[] and zero terminated.Output it. ** Note that the number is in the usual order, not reversed as with ** integer conversions.*/ length = (int)(zBuf-zWorker); zBuf = zWorker; /* Special case: Add leading zeros if the flag_zeropad flag is ** set and we are not left justified */ if( flag_zeropad && !flag_leftjustify && length < width){ int i; int nPad = width - length; for(i=width; i>=nPad; i--){ zBuf[i] = zBuf[i-nPad]; } i = prefix!=0; while( nPad-- ) zBuf[i++] = '0'; length = width; } #else zBuf = " "; length = (int)sizeof(char); #endif /* JX9_OMIT_FLOATING_POINT */ break; } default: /* Invalid format specifer */ zWorker[0] = '?'; length = (int)sizeof(char); break; } /* ** The text of the conversion is pointed to by "zBuf" and is ** "length" characters long.The field width is "width".Do ** the output. */ if( !flag_leftjustify ){ register int nspace; nspace = width-length; if( nspace>0 ){ while( nspace>=etSPACESIZE ){ rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } nspace -= etSPACESIZE; } if( nspace>0 ){ rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } } } } if( length>0 ){ rc = xConsumer(pCtx, zBuf, (unsigned int)length, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } } if( flag_leftjustify ){ register int nspace; nspace = width-length; if( nspace>0 ){ while( nspace>=etSPACESIZE ){ rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } nspace -= etSPACESIZE; } if( nspace>0 ){ rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } } } } }/* for(;;) */ return SXRET_OK; } /* * Callback [i.e: Formatted input consumer] of the sprintf function. */ static int sprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) { /* Consume directly */ jx9_result_string(pCtx, zInput, nLen); SXUNUSED(pUserData); /* cc warning */ return JX9_OK; } /* * string sprintf(string $format[, mixed $args [, mixed $... ]]) * Return a formatted string. * Parameters * $format * The format string (see block comment above) * Return * A string produced according to the formatting string format. */ static int jx9Builtin_sprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFormat; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the string format */ zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Format the string */ jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, nArg, apArg, 0, FALSE); return JX9_OK; } /* * Callback [i.e: Formatted input consumer] of the printf function. */ static int printfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) { jx9_int64 *pCounter = (jx9_int64 *)pUserData; /* Call the VM output consumer directly */ jx9_context_output(pCtx, zInput, nLen); /* Increment counter */ *pCounter += nLen; return JX9_OK; } /* * int64 printf(string $format[, mixed $args[, mixed $... ]]) * Output a formatted string. * Parameters * $format * See sprintf() for a description of format. * Return * The length of the outputted string. */ static int jx9Builtin_printf(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_int64 nCounter = 0; const char *zFormat; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the string format */ zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Format the string */ jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, nArg, apArg, (void *)&nCounter, FALSE); /* Return the length of the outputted string */ jx9_result_int64(pCtx, nCounter); return JX9_OK; } /* * int vprintf(string $format, array $args) * Output a formatted string. * Parameters * $format * See sprintf() for a description of format. * Return * The length of the outputted string. */ static int jx9Builtin_vprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_int64 nCounter = 0; const char *zFormat; jx9_hashmap *pMap; SySet sArg; int nLen, n; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ /* Missing/Invalid arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the string format */ zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Point to the hashmap */ pMap = (jx9_hashmap *)apArg[1]->x.pOther; /* Extract arguments from the hashmap */ n = jx9HashmapValuesToSet(pMap, &sArg); /* Format the string */ jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&nCounter, TRUE); /* Return the length of the outputted string */ jx9_result_int64(pCtx, nCounter); /* Release the container */ SySetRelease(&sArg); return JX9_OK; } /* * int vsprintf(string $format, array $args) * Output a formatted string. * Parameters * $format * See sprintf() for a description of format. * Return * A string produced according to the formatting string format. */ static int jx9Builtin_vsprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFormat; jx9_hashmap *pMap; SySet sArg; int nLen, n; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ /* Missing/Invalid arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the string format */ zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Point to hashmap */ pMap = (jx9_hashmap *)apArg[1]->x.pOther; /* Extract arguments from the hashmap */ n = jx9HashmapValuesToSet(pMap, &sArg); /* Format the string */ jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), 0, TRUE); /* Release the container */ SySetRelease(&sArg); return JX9_OK; } /* * string size_format(int64 $size) * Return a smart string represenation of the given size [i.e: 64-bit integer] * Example: * print size_format(1*1024*1024*1024);// 1GB * print size_format(512*1024*1024); // 512 MB * print size_format(file_size(/path/to/my/file_8192)); //8KB * Parameter * $size * Entity size in bytes. * Return * Formatted string representation of the given size. */ static int jx9Builtin_size_format(jx9_context *pCtx, int nArg, jx9_value **apArg) { /*Kilo*/ /*Mega*/ /*Giga*/ /*Tera*/ /*Peta*/ /*Exa*/ /*Zeta*/ static const char zUnit[] = {"KMGTPEZ"}; sxi32 nRest, i_32; jx9_int64 iSize; int c = -1; /* index in zUnit[] */ if( nArg < 1 ){ /* Missing argument, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the given size */ iSize = jx9_value_to_int64(apArg[0]); if( iSize < 100 /* Bytes */ ){ /* Don't bother formatting, return immediately */ jx9_result_string(pCtx, "0.1 KB", (int)sizeof("0.1 KB")-1); return JX9_OK; } for(;;){ nRest = (sxi32)(iSize & 0x3FF); iSize >>= 10; c++; if( (iSize & (~0 ^ 1023)) == 0 ){ break; } } nRest /= 100; if( nRest > 9 ){ nRest = 9; } if( iSize > 999 ){ c++; nRest = 9; iSize = 0; } i_32 = (sxi32)iSize; /* Format */ jx9_result_string_format(pCtx, "%d.%d %cB", i_32, nRest, zUnit[c]); return JX9_OK; } #if !defined(JX9_DISABLE_HASH_FUNC) /* * string md5(string $str[, bool $raw_output = false]) * Calculate the md5 hash of a string. * Parameter * $str * Input string * $raw_output * If the optional raw_output is set to TRUE, then the md5 digest * is instead returned in raw binary format with a length of 16. * Return * MD5 Hash as a 32-character hexadecimal string. */ static int jx9Builtin_md5(jx9_context *pCtx, int nArg, jx9_value **apArg) { unsigned char zDigest[16]; int raw_output = FALSE; const void *pIn; int nLen; if( nArg < 1 ){ /* Missing arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the input string */ pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } if( nArg > 1 && jx9_value_is_bool(apArg[1])){ raw_output = jx9_value_to_bool(apArg[1]); } /* Compute the MD5 digest */ SyMD5Compute(pIn, (sxu32)nLen, zDigest); if( raw_output ){ /* Output raw digest */ jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest)); }else{ /* Perform a binary to hex conversion */ SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx); } return JX9_OK; } /* * string sha1(string $str[, bool $raw_output = false]) * Calculate the sha1 hash of a string. * Parameter * $str * Input string * $raw_output * If the optional raw_output is set to TRUE, then the md5 digest * is instead returned in raw binary format with a length of 16. * Return * SHA1 Hash as a 40-character hexadecimal string. */ static int jx9Builtin_sha1(jx9_context *pCtx, int nArg, jx9_value **apArg) { unsigned char zDigest[20]; int raw_output = FALSE; const void *pIn; int nLen; if( nArg < 1 ){ /* Missing arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the input string */ pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } if( nArg > 1 && jx9_value_is_bool(apArg[1])){ raw_output = jx9_value_to_bool(apArg[1]); } /* Compute the SHA1 digest */ SySha1Compute(pIn, (sxu32)nLen, zDigest); if( raw_output ){ /* Output raw digest */ jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest)); }else{ /* Perform a binary to hex conversion */ SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx); } return JX9_OK; } /* * int64 crc32(string $str) * Calculates the crc32 polynomial of a strin. * Parameter * $str * Input string * Return * CRC32 checksum of the given input (64-bit integer). */ static int jx9Builtin_crc32(jx9_context *pCtx, int nArg, jx9_value **apArg) { const void *pIn; sxu32 nCRC; int nLen; if( nArg < 1 ){ /* Missing arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the input string */ pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty string */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Calculate the sum */ nCRC = SyCrc32(pIn, (sxu32)nLen); /* Return the CRC32 as 64-bit integer */ jx9_result_int64(pCtx, (jx9_int64)nCRC^ 0xFFFFFFFF); return JX9_OK; } #endif /* JX9_DISABLE_HASH_FUNC */ /* * Parse a CSV string and invoke the supplied callback for each processed xhunk. */ JX9_PRIVATE sxi32 jx9ProcessCsv( const char *zInput, /* Raw input */ int nByte, /* Input length */ int delim, /* Delimiter */ int encl, /* Enclosure */ int escape, /* Escape character */ sxi32 (*xConsumer)(const char *, int, void *), /* User callback */ void *pUserData /* Last argument to xConsumer() */ ) { const char *zEnd = &zInput[nByte]; const char *zIn = zInput; const char *zPtr; int isEnc; /* Start processing */ for(;;){ if( zIn >= zEnd ){ /* No more input to process */ break; } isEnc = 0; zPtr = zIn; /* Find the first delimiter */ while( zIn < zEnd ){ if( zIn[0] == delim && !isEnc){ /* Delimiter found, break imediately */ break; }else if( zIn[0] == encl ){ /* Inside enclosure? */ isEnc = !isEnc; }else if( zIn[0] == escape ){ /* Escape sequence */ zIn++; } /* Advance the cursor */ zIn++; } if( zIn > zPtr ){ int nByte = (int)(zIn-zPtr); sxi32 rc; /* Invoke the supllied callback */ if( zPtr[0] == encl ){ zPtr++; nByte-=2; } if( nByte > 0 ){ rc = xConsumer(zPtr, nByte, pUserData); if( rc == SXERR_ABORT ){ /* User callback request an operation abort */ break; } } } /* Ignore trailing delimiter */ while( zIn < zEnd && zIn[0] == delim ){ zIn++; } } return SXRET_OK; } /* * Default consumer callback for the CSV parsing routine defined above. * All the processed input is insereted into an array passed as the last * argument to this callback. */ JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData) { jx9_value *pArray = (jx9_value *)pUserData; jx9_value sEntry; SyString sToken; /* Insert the token in the given array */ SyStringInitFromBuf(&sToken, zToken, nTokenLen); /* Remove trailing and leading white spcaces and null bytes */ SyStringFullTrimSafe(&sToken); if( sToken.nByte < 1){ return SXRET_OK; } jx9MemObjInitFromString(pArray->pVm, &sEntry, &sToken); jx9_array_add_elem(pArray, 0, &sEntry); jx9MemObjRelease(&sEntry); return SXRET_OK; } /* * array str_getcsv(string $input[, string $delimiter = ', '[, string $enclosure = '"' [, string $escape='\\']]]) * Parse a CSV string into an array. * Parameters * $input * The string to parse. * $delimiter * Set the field delimiter (one character only). * $enclosure * Set the field enclosure character (one character only). * $escape * Set the escape character (one character only). Defaults as a backslash (\) * Return * An indexed array containing the CSV fields or NULL on failure. */ static int jx9Builtin_str_getcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zInput, *zPtr; jx9_value *pArray; int delim = ','; /* Delimiter */ int encl = '"' ; /* Enclosure */ int escape = '\\'; /* Escape character */ int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the raw input */ zInput = jx9_value_to_string(apArg[0], &nLen); if( nArg > 1 ){ int i; if( jx9_value_is_string(apArg[1]) ){ /* Extract the delimiter */ zPtr = jx9_value_to_string(apArg[1], &i); if( i > 0 ){ delim = zPtr[0]; } } if( nArg > 2 ){ if( jx9_value_is_string(apArg[2]) ){ /* Extract the enclosure */ zPtr = jx9_value_to_string(apArg[2], &i); if( i > 0 ){ encl = zPtr[0]; } } if( nArg > 3 ){ if( jx9_value_is_string(apArg[3]) ){ /* Extract the escape character */ zPtr = jx9_value_to_string(apArg[3], &i); if( i > 0 ){ escape = zPtr[0]; } } } } } /* Create our array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_null(pCtx); return JX9_OK; } /* Parse the raw input */ jx9ProcessCsv(zInput, nLen, delim, encl, escape, jx9CsvConsumer, pArray); /* Return the freshly created array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * Extract a tag name from a raw HTML input and insert it in the given * container. * Refer to [strip_tags()]. */ static sxi32 AddTag(SySet *pSet, const char *zTag, int nByte) { const char *zEnd = &zTag[nByte]; const char *zPtr; SyString sEntry; /* Strip tags */ for(;;){ while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' || zTag[0] == '!' || zTag[0] == '-' || ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ zTag++; } if( zTag >= zEnd ){ break; } zPtr = zTag; /* Delimit the tag */ while(zTag < zEnd ){ if( (unsigned char)zTag[0] >= 0xc0 ){ /* UTF-8 stream */ zTag++; SX_JMP_UTF8(zTag, zEnd); }else if( !SyisAlphaNum(zTag[0]) ){ break; }else{ zTag++; } } if( zTag > zPtr ){ /* Perform the insertion */ SyStringInitFromBuf(&sEntry, zPtr, (int)(zTag-zPtr)); SyStringFullTrim(&sEntry); SySetPut(pSet, (const void *)&sEntry); } /* Jump the trailing '>' */ zTag++; } return SXRET_OK; } /* * Check if the given HTML tag name is present in the given container. * Return SXRET_OK if present.SXERR_NOTFOUND otherwise. * Refer to [strip_tags()]. */ static sxi32 FindTag(SySet *pSet, const char *zTag, int nByte) { if( SySetUsed(pSet) > 0 ){ const char *zCur, *zEnd = &zTag[nByte]; SyString sTag; while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' || ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ zTag++; } /* Delimit the tag */ zCur = zTag; while(zTag < zEnd ){ if( (unsigned char)zTag[0] >= 0xc0 ){ /* UTF-8 stream */ zTag++; SX_JMP_UTF8(zTag, zEnd); }else if( !SyisAlphaNum(zTag[0]) ){ break; }else{ zTag++; } } SyStringInitFromBuf(&sTag, zCur, zTag-zCur); /* Trim leading white spaces and null bytes */ SyStringLeftTrimSafe(&sTag); if( sTag.nByte > 0 ){ SyString *aEntry, *pEntry; sxi32 rc; sxu32 n; /* Perform the lookup */ aEntry = (SyString *)SySetBasePtr(pSet); for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ pEntry = &aEntry[n]; /* Do the comparison */ rc = SyStringCmp(pEntry, &sTag, SyStrnicmp); if( !rc ){ return SXRET_OK; } } } } /* No such tag */ return SXERR_NOTFOUND; } /* * This function tries to return a string [i.e: in the call context result buffer] * with all NUL bytes, HTML and JX9 tags stripped from a given string. * Refer to [strip_tags()]. */ JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen) { const char *zEnd = &zIn[nByte]; const char *zPtr, *zTag; SySet sSet; /* initialize the set of allowed tags */ SySetInit(&sSet, &pCtx->pVm->sAllocator, sizeof(SyString)); if( nTaglen > 0 ){ /* Set of allowed tags */ AddTag(&sSet, zTaglist, nTaglen); } /* Set the empty string */ jx9_result_string(pCtx, "", 0); /* Start processing */ for(;;){ if(zIn >= zEnd){ /* No more input to process */ break; } zPtr = zIn; /* Find a tag */ while( zIn < zEnd && zIn[0] != '<' && zIn[0] != 0 /* NUL byte */ ){ zIn++; } if( zIn > zPtr ){ /* Consume raw input */ jx9_result_string(pCtx, zPtr, (int)(zIn-zPtr)); } /* Ignore trailing null bytes */ while( zIn < zEnd && zIn[0] == 0 ){ zIn++; } if(zIn >= zEnd){ /* No more input to process */ break; } if( zIn[0] == '<' ){ sxi32 rc; zTag = zIn++; /* Delimit the tag */ while( zIn < zEnd && zIn[0] != '>' ){ zIn++; } if( zIn < zEnd ){ zIn++; /* Ignore the trailing closing tag */ } /* Query the set */ rc = FindTag(&sSet, zTag, (int)(zIn-zTag)); if( rc == SXRET_OK ){ /* Keep the tag */ jx9_result_string(pCtx, zTag, (int)(zIn-zTag)); } } } /* Cleanup */ SySetRelease(&sSet); return SXRET_OK; } /* * string strip_tags(string $str[, string $allowable_tags]) * Strip HTML and JX9 tags from a string. * Parameters * $str * The input string. * $allowable_tags * You can use the optional second parameter to specify tags which should not be stripped. * Return * Returns the stripped string. */ static int jx9Builtin_strip_tags(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zTaglist = 0; const char *zString; int nTaglen = 0; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Point to the raw string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nArg > 1 && jx9_value_is_string(apArg[1]) ){ /* Allowed tag */ zTaglist = jx9_value_to_string(apArg[1], &nTaglen); } /* Process input */ jx9StripTagsFromString(pCtx, zString, nLen, zTaglist, nTaglen); return JX9_OK; } /* * array str_split(string $string[, int $split_length = 1 ]) * Convert a string to an array. * Parameters * $str * The input string. * $split_length * Maximum length of the chunk. * Return * If the optional split_length parameter is specified, the returned array * will be broken down into chunks with each being split_length in length, otherwise * each chunk will be one character in length. FALSE is returned if split_length is less than 1. * If the split_length length exceeds the length of string, the entire string is returned * as the first (and only) array element. */ static int jx9Builtin_str_split(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zEnd; jx9_value *pArray, *pValue; int split_len; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target string */ zString = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Nothing to process, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } split_len = (int)sizeof(char); if( nArg > 1 ){ /* Split length */ split_len = jx9_value_to_int(apArg[1]); if( split_len < 1 ){ /* Invalid length, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } if( split_len > nLen ){ split_len = nLen; } } /* Create the array and the scalar value */ pArray = jx9_context_new_array(pCtx); /*Chunk value */ pValue = jx9_context_new_scalar(pCtx); if( pValue == 0 || pArray == 0 ){ /* Return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the end of the string */ zEnd = &zString[nLen]; /* Perform the requested operation */ for(;;){ int nMax; if( zString >= zEnd ){ /* No more input to process */ break; } nMax = (int)(zEnd-zString); if( nMax < split_len ){ split_len = nMax; } /* Copy the current chunk */ jx9_value_string(pValue, zString, split_len); /* Insert it */ jx9_array_add_elem(pArray, 0, pValue); /* Will make it's own copy */ /* reset the string cursor */ jx9_value_reset_string_cursor(pValue); /* Update position */ zString += split_len; } /* * Return the array. * Don't worry about freeing memory, everything will be automatically released * upon we return from this function. */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * Tokenize a raw string and extract the first non-space token. * Refer to [strspn()]. */ static sxi32 ExtractNonSpaceToken(const char **pzIn, const char *zEnd, SyString *pOut) { const char *zIn = *pzIn; const char *zPtr; /* Ignore leading white spaces */ while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ zIn++; } if( zIn >= zEnd ){ /* End of input */ return SXERR_EOF; } zPtr = zIn; /* Extract the token */ while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && !SyisSpace(zIn[0]) ){ zIn++; } SyStringInitFromBuf(pOut, zPtr, zIn-zPtr); /* Synchronize pointers */ *pzIn = zIn; /* Return to the caller */ return SXRET_OK; } /* * Check if the given string contains only characters from the given mask. * return the longest match. * Refer to [strspn()]. */ static int LongestStringMask(const char *zString, int nLen, const char *zMask, int nMaskLen) { const char *zEnd = &zString[nLen]; const char *zIn = zString; int i, c; for(;;){ if( zString >= zEnd ){ break; } /* Extract current character */ c = zString[0]; /* Perform the lookup */ for( i = 0 ; i < nMaskLen ; i++ ){ if( c == zMask[i] ){ /* Character found */ break; } } if( i >= nMaskLen ){ /* Character not in the current mask, break immediately */ break; } /* Advance cursor */ zString++; } /* Longest match */ return (int)(zString-zIn); } /* * Do the reverse operation of the previous function [i.e: LongestStringMask()]. * Refer to [strcspn()]. */ static int LongestStringMask2(const char *zString, int nLen, const char *zMask, int nMaskLen) { const char *zEnd = &zString[nLen]; const char *zIn = zString; int i, c; for(;;){ if( zString >= zEnd ){ break; } /* Extract current character */ c = zString[0]; /* Perform the lookup */ for( i = 0 ; i < nMaskLen ; i++ ){ if( c == zMask[i] ){ break; } } if( i < nMaskLen ){ /* Character in the current mask, break immediately */ break; } /* Advance cursor */ zString++; } /* Longest match */ return (int)(zString-zIn); } /* * int strspn(string $str, string $mask[, int $start[, int $length]]) * Finds the length of the initial segment of a string consisting entirely * of characters contained within a given mask. * Parameters * $str * The input string. * $mask * The list of allowable characters. * $start * The position in subject to start searching. * If start is given and is non-negative, then strspn() will begin examining * subject at the start'th position. For instance, in the string 'abcdef', the character * at position 0 is 'a', the character at position 2 is 'c', and so forth. * If start is given and is negative, then strspn() will begin examining subject at the * start'th position from the end of subject. * $length * The length of the segment from subject to examine. * If length is given and is non-negative, then subject will be examined for length * characters after the starting position. * If lengthis given and is negative, then subject will be examined from the starting * position up to length characters from the end of subject. * Return * Returns the length of the initial segment of subject which consists entirely of characters * in mask. */ static int jx9Builtin_strspn(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zMask, *zEnd; int iMasklen, iLen; SyString sToken; int iCount = 0; int rc; if( nArg < 2 ){ /* Missing agruments, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &iLen); /* Extract the mask */ zMask = jx9_value_to_string(apArg[1], &iMasklen); if( iLen < 1 || iMasklen < 1 ){ /* Nothing to process, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } if( nArg > 2 ){ int nOfft; /* Extract the offset */ nOfft = jx9_value_to_int(apArg[2]); if( nOfft < 0 ){ const char *zBase = &zString[iLen + nOfft]; if( zBase > zString ){ iLen = (int)(&zString[iLen]-zBase); zString = zBase; }else{ /* Invalid offset */ jx9_result_int(pCtx, 0); return JX9_OK; } }else{ if( nOfft >= iLen ){ /* Invalid offset */ jx9_result_int(pCtx, 0); return JX9_OK; }else{ /* Update offset */ zString += nOfft; iLen -= nOfft; } } if( nArg > 3 ){ int iUserlen; /* Extract the desired length */ iUserlen = jx9_value_to_int(apArg[3]); if( iUserlen > 0 && iUserlen < iLen ){ iLen = iUserlen; } } } /* Point to the end of the string */ zEnd = &zString[iLen]; /* Extract the first non-space token */ rc = ExtractNonSpaceToken(&zString, zEnd, &sToken); if( rc == SXRET_OK && sToken.nByte > 0 ){ /* Compare against the current mask */ iCount = LongestStringMask(sToken.zString, (int)sToken.nByte, zMask, iMasklen); } /* Longest match */ jx9_result_int(pCtx, iCount); return JX9_OK; } /* * int strcspn(string $str, string $mask[, int $start[, int $length]]) * Find length of initial segment not matching mask. * Parameters * $str * The input string. * $mask * The list of not allowed characters. * $start * The position in subject to start searching. * If start is given and is non-negative, then strspn() will begin examining * subject at the start'th position. For instance, in the string 'abcdef', the character * at position 0 is 'a', the character at position 2 is 'c', and so forth. * If start is given and is negative, then strspn() will begin examining subject at the * start'th position from the end of subject. * $length * The length of the segment from subject to examine. * If length is given and is non-negative, then subject will be examined for length * characters after the starting position. * If lengthis given and is negative, then subject will be examined from the starting * position up to length characters from the end of subject. * Return * Returns the length of the segment as an integer. */ static int jx9Builtin_strcspn(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zMask, *zEnd; int iMasklen, iLen; SyString sToken; int iCount = 0; int rc; if( nArg < 2 ){ /* Missing agruments, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the target string */ zString = jx9_value_to_string(apArg[0], &iLen); /* Extract the mask */ zMask = jx9_value_to_string(apArg[1], &iMasklen); if( iLen < 1 ){ /* Nothing to process, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } if( iMasklen < 1 ){ /* No given mask, return the string length */ jx9_result_int(pCtx, iLen); return JX9_OK; } if( nArg > 2 ){ int nOfft; /* Extract the offset */ nOfft = jx9_value_to_int(apArg[2]); if( nOfft < 0 ){ const char *zBase = &zString[iLen + nOfft]; if( zBase > zString ){ iLen = (int)(&zString[iLen]-zBase); zString = zBase; }else{ /* Invalid offset */ jx9_result_int(pCtx, 0); return JX9_OK; } }else{ if( nOfft >= iLen ){ /* Invalid offset */ jx9_result_int(pCtx, 0); return JX9_OK; }else{ /* Update offset */ zString += nOfft; iLen -= nOfft; } } if( nArg > 3 ){ int iUserlen; /* Extract the desired length */ iUserlen = jx9_value_to_int(apArg[3]); if( iUserlen > 0 && iUserlen < iLen ){ iLen = iUserlen; } } } /* Point to the end of the string */ zEnd = &zString[iLen]; /* Extract the first non-space token */ rc = ExtractNonSpaceToken(&zString, zEnd, &sToken); if( rc == SXRET_OK && sToken.nByte > 0 ){ /* Compare against the current mask */ iCount = LongestStringMask2(sToken.zString, (int)sToken.nByte, zMask, iMasklen); } /* Longest match */ jx9_result_int(pCtx, iCount); return JX9_OK; } /* * string strpbrk(string $haystack, string $char_list) * Search a string for any of a set of characters. * Parameters * $haystack * The string where char_list is looked for. * $char_list * This parameter is case sensitive. * Return * Returns a string starting from the character found, or FALSE if it is not found. */ static int jx9Builtin_strpbrk(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zList, *zEnd; int iLen, iListLen, i, c; sxu32 nOfft, nMax; sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the haystack and the char list */ zString = jx9_value_to_string(apArg[0], &iLen); zList = jx9_value_to_string(apArg[1], &iListLen); if( iLen < 1 ){ /* Nothing to process, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the end of the string */ zEnd = &zString[iLen]; nOfft = nMax = SXU32_HIGH; /* perform the requested operation */ for( i = 0 ; i < iListLen ; i++ ){ c = zList[i]; rc = SyByteFind(zString, (sxu32)iLen, c, &nMax); if( rc == SXRET_OK ){ if( nMax < nOfft ){ nOfft = nMax; } } } if( nOfft == SXU32_HIGH ){ /* No such substring, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Return the substring */ jx9_result_string(pCtx, &zString[nOfft], (int)(zEnd-&zString[nOfft])); } return JX9_OK; } /* * string soundex(string $str) * Calculate the soundex key of a string. * Parameters * $str * The input string. * Return * Returns the soundex key as a string. * Note: * This implementation is based on the one found in the SQLite3 * source tree. */ static int jx9Builtin_soundex(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn; char zResult[8]; int i, j; static const unsigned char iCode[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, }; if( nArg < 1 ){ /* Missing arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } zIn = (unsigned char *)jx9_value_to_string(apArg[0], 0); for(i=0; zIn[i] && zIn[i] < 0xc0 && !SyisAlpha(zIn[i]); i++){} if( zIn[i] ){ unsigned char prevcode = iCode[zIn[i]&0x7f]; zResult[0] = (char)SyToUpper(zIn[i]); for(j=1; j<4 && zIn[i]; i++){ int code = iCode[zIn[i]&0x7f]; if( code>0 ){ if( code!=prevcode ){ prevcode = (unsigned char)code; zResult[j++] = (char)code + '0'; } }else{ prevcode = 0; } } while( j<4 ){ zResult[j++] = '0'; } jx9_result_string(pCtx, zResult, 4); }else{ jx9_result_string(pCtx, "?000", 4); } return JX9_OK; } /* * string wordwrap(string $str[, int $width = 75[, string $break = "\n"]]) * Wraps a string to a given number of characters. * Parameters * $str * The input string. * $width * The column width. * $break * The line is broken using the optional break parameter. * Return * Returns the given string wrapped at the specified column. */ static int jx9Builtin_wordwrap(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn, *zEnd, *zBreak; int iLen, iBreaklen, iChunk; if( nArg < 1 ){ /* Missing arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the input string */ zIn = jx9_value_to_string(apArg[0], &iLen); if( iLen < 1 ){ /* Nothing to process, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Chunk length */ iChunk = 75; iBreaklen = 0; zBreak = ""; /* cc warning */ if( nArg > 1 ){ iChunk = jx9_value_to_int(apArg[1]); if( iChunk < 1 ){ iChunk = 75; } if( nArg > 2 ){ zBreak = jx9_value_to_string(apArg[2], &iBreaklen); } } if( iBreaklen < 1 ){ /* Set a default column break */ #ifdef __WINNT__ zBreak = "\r\n"; iBreaklen = (int)sizeof("\r\n")-1; #else zBreak = "\n"; iBreaklen = (int)sizeof(char); #endif } /* Perform the requested operation */ zEnd = &zIn[iLen]; for(;;){ int nMax; if( zIn >= zEnd ){ /* No more input to process */ break; } nMax = (int)(zEnd-zIn); if( iChunk > nMax ){ iChunk = nMax; } /* Append the column first */ jx9_result_string(pCtx, zIn, iChunk); /* Will make it's own copy */ /* Advance the cursor */ zIn += iChunk; if( zIn < zEnd ){ /* Append the line break */ jx9_result_string(pCtx, zBreak, iBreaklen); } } return JX9_OK; } /* * Check if the given character is a member of the given mask. * Return TRUE on success. FALSE otherwise. * Refer to [strtok()]. */ static int CheckMask(int c, const char *zMask, int nMasklen, int *pOfft) { int i; for( i = 0 ; i < nMasklen ; ++i ){ if( c == zMask[i] ){ if( pOfft ){ *pOfft = i; } return TRUE; } } return FALSE; } /* * Extract a single token from the input stream. * Refer to [strtok()]. */ static sxi32 ExtractToken(const char **pzIn, const char *zEnd, const char *zMask, int nMasklen, SyString *pOut) { const char *zIn = *pzIn; const char *zPtr; /* Ignore leading delimiter */ while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && CheckMask(zIn[0], zMask, nMasklen, 0) ){ zIn++; } if( zIn >= zEnd ){ /* End of input */ return SXERR_EOF; } zPtr = zIn; /* Extract the token */ while( zIn < zEnd ){ if( (unsigned char)zIn[0] >= 0xc0 ){ /* UTF-8 stream */ zIn++; SX_JMP_UTF8(zIn, zEnd); }else{ if( CheckMask(zIn[0], zMask, nMasklen, 0) ){ break; } zIn++; } } SyStringInitFromBuf(pOut, zPtr, zIn-zPtr); /* Update the cursor */ *pzIn = zIn; /* Return to the caller */ return SXRET_OK; } /* strtok auxiliary private data */ typedef struct strtok_aux_data strtok_aux_data; struct strtok_aux_data { const char *zDup; /* Complete duplicate of the input */ const char *zIn; /* Current input stream */ const char *zEnd; /* End of input */ }; /* * string strtok(string $str, string $token) * string strtok(string $token) * strtok() splits a string (str) into smaller strings (tokens), with each token * being delimited by any character from token. That is, if you have a string like * "This is an example string" you could tokenize this string into its individual * words by using the space character as the token. * Note that only the first call to strtok uses the string argument. Every subsequent * call to strtok only needs the token to use, as it keeps track of where it is in * the current string. To start over, or to tokenize a new string you simply call strtok * with the string argument again to initialize it. Note that you may put multiple tokens * in the token parameter. The string will be tokenized when any one of the characters in * the argument are found. * Parameters * $str * The string being split up into smaller strings (tokens). * $token * The delimiter used when splitting up str. * Return * Current token or FALSE on EOF. */ static int jx9Builtin_strtok(jx9_context *pCtx, int nArg, jx9_value **apArg) { strtok_aux_data *pAux; const char *zMask; SyString sToken; int nMasklen; sxi32 rc; if( nArg < 2 ){ /* Extract top aux data */ pAux = (strtok_aux_data *)jx9_context_peek_aux_data(pCtx); if( pAux == 0 ){ /* No aux data, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } nMasklen = 0; zMask = ""; /* cc warning */ if( nArg > 0 ){ /* Extract the mask */ zMask = jx9_value_to_string(apArg[0], &nMasklen); } if( nMasklen < 1 ){ /* Invalid mask, return FALSE */ jx9_context_free_chunk(pCtx, (void *)pAux->zDup); jx9_context_free_chunk(pCtx, pAux); (void)jx9_context_pop_aux_data(pCtx); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the token */ rc = ExtractToken(&pAux->zIn, pAux->zEnd, zMask, nMasklen, &sToken); if( rc != SXRET_OK ){ /* EOF , discard the aux data */ jx9_context_free_chunk(pCtx, (void *)pAux->zDup); jx9_context_free_chunk(pCtx, pAux); (void)jx9_context_pop_aux_data(pCtx); jx9_result_bool(pCtx, 0); }else{ /* Return the extracted token */ jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte); } }else{ const char *zInput, *zCur; char *zDup; int nLen; /* Extract the raw input */ zCur = zInput = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Empty input, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the mask */ zMask = jx9_value_to_string(apArg[1], &nMasklen); if( nMasklen < 1 ){ /* Set a default mask */ #define TOK_MASK " \n\t\r\f" zMask = TOK_MASK; nMasklen = (int)sizeof(TOK_MASK) - 1; #undef TOK_MASK } /* Extract a single token */ rc = ExtractToken(&zInput, &zInput[nLen], zMask, nMasklen, &sToken); if( rc != SXRET_OK ){ /* Empty input */ jx9_result_bool(pCtx, 0); return JX9_OK; }else{ /* Return the extracted token */ jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte); } /* Create our auxilliary data and copy the input */ pAux = (strtok_aux_data *)jx9_context_alloc_chunk(pCtx, sizeof(strtok_aux_data), TRUE, FALSE); if( pAux ){ nLen -= (int)(zInput-zCur); if( nLen < 1 ){ jx9_context_free_chunk(pCtx, pAux); return JX9_OK; } /* Duplicate input */ zDup = (char *)jx9_context_alloc_chunk(pCtx, (unsigned int)(nLen+1), TRUE, FALSE); if( zDup ){ SyMemcpy(zInput, zDup, (sxu32)nLen); /* Register the aux data */ pAux->zDup = pAux->zIn = zDup; pAux->zEnd = &zDup[nLen]; jx9_context_push_aux_data(pCtx, pAux); } } } return JX9_OK; } /* * string str_pad(string $input, int $pad_length[, string $pad_string = " " [, int $pad_type = STR_PAD_RIGHT]]) * Pad a string to a certain length with another string * Parameters * $input * The input string. * $pad_length * If the value of pad_length is negative, less than, or equal to the length of the input * string, no padding takes place. * $pad_string * Note: * The pad_string WIIL NOT BE truncated if the required number of padding characters can't be evenly * divided by the pad_string's length. * $pad_type * Optional argument pad_type can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type * is not specified it is assumed to be STR_PAD_RIGHT. * Return * The padded string. */ static int jx9Builtin_str_pad(jx9_context *pCtx, int nArg, jx9_value **apArg) { int iLen, iPadlen, iType, i, iDiv, iStrpad, iRealPad, jPad; const char *zIn, *zPad; if( nArg < 2 ){ /* Missing arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract the target string */ zIn = jx9_value_to_string(apArg[0], &iLen); /* Padding length */ iRealPad = iPadlen = jx9_value_to_int(apArg[1]); if( iPadlen > 0 ){ iPadlen -= iLen; } if( iPadlen < 1 ){ /* Return the string verbatim */ jx9_result_string(pCtx, zIn, iLen); return JX9_OK; } zPad = " "; /* Whitespace padding */ iStrpad = (int)sizeof(char); iType = 1 ; /* STR_PAD_RIGHT */ if( nArg > 2 ){ /* Padding string */ zPad = jx9_value_to_string(apArg[2], &iStrpad); if( iStrpad < 1 ){ /* Empty string */ zPad = " "; /* Whitespace padding */ iStrpad = (int)sizeof(char); } if( nArg > 3 ){ /* Padd type */ iType = jx9_value_to_int(apArg[3]); if( iType != 0 /* STR_PAD_LEFT */ && iType != 2 /* STR_PAD_BOTH */ ){ iType = 1 ; /* STR_PAD_RIGHT */ } } } iDiv = 1; if( iType == 2 ){ iDiv = 2; /* STR_PAD_BOTH */ } /* Perform the requested operation */ if( iType == 0 /* STR_PAD_LEFT */ || iType == 2 /* STR_PAD_BOTH */ ){ jPad = iStrpad; for( i = 0 ; i < iPadlen/iDiv ; i += jPad ){ /* Padding */ if( (int)jx9_context_result_buf_length(pCtx) + iLen + jPad >= iRealPad ){ break; } jx9_result_string(pCtx, zPad, jPad); } if( iType == 0 /* STR_PAD_LEFT */ ){ while( (int)jx9_context_result_buf_length(pCtx) + iLen < iRealPad ){ jPad = iRealPad - (iLen + (int)jx9_context_result_buf_length(pCtx) ); if( jPad > iStrpad ){ jPad = iStrpad; } if( jPad < 1){ break; } jx9_result_string(pCtx, zPad, jPad); } } } if( iLen > 0 ){ /* Append the input string */ jx9_result_string(pCtx, zIn, iLen); } if( iType == 1 /* STR_PAD_RIGHT */ || iType == 2 /* STR_PAD_BOTH */ ){ for( i = 0 ; i < iPadlen/iDiv ; i += iStrpad ){ /* Padding */ if( (int)jx9_context_result_buf_length(pCtx) + iStrpad >= iRealPad ){ break; } jx9_result_string(pCtx, zPad, iStrpad); } while( (int)jx9_context_result_buf_length(pCtx) < iRealPad ){ jPad = iRealPad - (int)jx9_context_result_buf_length(pCtx); if( jPad > iStrpad ){ jPad = iStrpad; } if( jPad < 1){ break; } jx9_result_string(pCtx, zPad, jPad); } } return JX9_OK; } /* * String replacement private data. */ typedef struct str_replace_data str_replace_data; struct str_replace_data { /* The following two fields are only used by the strtr function */ SyBlob *pWorker; /* Working buffer */ ProcStringMatch xMatch; /* Pattern match routine */ /* The following two fields are only used by the str_replace function */ SySet *pCollector; /* Argument collector*/ jx9_context *pCtx; /* Call context */ }; /* * Remove a substring. */ #define STRDEL(SRC, SLEN, OFFT, ILEN){\ for(;;){\ if( OFFT + ILEN >= SLEN ) break; SRC[OFFT] = SRC[OFFT+ILEN]; ++OFFT;\ }\ } /* * Shift right and insert algorithm. */ #define SHIFTRANDINSERT(SRC, LEN, OFFT, ENTRY, ELEN){\ sxu32 INLEN = LEN - OFFT;\ for(;;){\ if( LEN > 0 ){ LEN--; } if(INLEN < 1 ) break; SRC[LEN + ELEN] = SRC[LEN] ; --INLEN; \ }\ for(;;){\ if(ELEN < 1)break; SRC[OFFT] = ENTRY[0]; OFFT++; ENTRY++; --ELEN;\ }\ } /* * Replace all occurrences of the search string at offset (nOfft) with the given * replacement string [i.e: zReplace]. */ static int StringReplace(SyBlob *pWorker, sxu32 nOfft, int nLen, const char *zReplace, int nReplen) { char *zInput = (char *)SyBlobData(pWorker); sxu32 n, m; n = SyBlobLength(pWorker); m = nOfft; /* Delete the old entry */ STRDEL(zInput, n, m, nLen); SyBlobLength(pWorker) -= nLen; if( nReplen > 0 ){ sxi32 iRep = nReplen; sxi32 rc; /* * Make sure the working buffer is big enough to hold the replacement * string. */ rc = SyBlobAppend(pWorker, 0/* Grow without an append operation*/, (sxu32)nReplen); if( rc != SXRET_OK ){ /* Simply ignore any memory failure problem */ return SXRET_OK; } /* Perform the insertion now */ zInput = (char *)SyBlobData(pWorker); n = SyBlobLength(pWorker); SHIFTRANDINSERT(zInput, n, nOfft, zReplace, iRep); SyBlobLength(pWorker) += nReplen; } return SXRET_OK; } /* * String replacement walker callback. * The following callback is invoked for each array entry that hold * the replace string. * Refer to the strtr() implementation for more information. */ static int StringReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData) { str_replace_data *pRepData = (str_replace_data *)pUserData; const char *zTarget, *zReplace; SyBlob *pWorker; int tLen, nLen; sxu32 nOfft; sxi32 rc; /* Point to the working buffer */ pWorker = pRepData->pWorker; if( !jx9_value_is_string(pKey) ){ /* Target and replace must be a string */ return JX9_OK; } /* Extract the target and the replace */ zTarget = jx9_value_to_string(pKey, &tLen); if( tLen < 1 ){ /* Empty target, return immediately */ return JX9_OK; } /* Perform a pattern search */ rc = pRepData->xMatch(SyBlobData(pWorker), SyBlobLength(pWorker), (const void *)zTarget, (sxu32)tLen, &nOfft); if( rc != SXRET_OK ){ /* Pattern not found */ return JX9_OK; } /* Extract the replace string */ zReplace = jx9_value_to_string(pData, &nLen); /* Perform the replace process */ StringReplace(pWorker, nOfft, tLen, zReplace, nLen); /* All done */ return JX9_OK; } /* * The following walker callback is invoked by the str_rplace() function inorder * to collect search/replace string. * This callback is invoked only if the given argument is of type array. */ static int StrReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData) { str_replace_data *pRep = (str_replace_data *)pUserData; SyString sWorker; const char *zIn; int nByte; /* Extract a string representation of the given argument */ zIn = jx9_value_to_string(pData, &nByte); SyStringInitFromBuf(&sWorker, 0, 0); if( nByte > 0 ){ char *zDup; /* Duplicate the chunk */ zDup = (char *)jx9_context_alloc_chunk(pRep->pCtx, (unsigned int)nByte, FALSE, TRUE /* Release the chunk automatically, upon this context is destroyd */ ); if( zDup == 0 ){ /* Ignore any memory failure problem */ jx9_context_throw_error(pRep->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); return JX9_OK; } SyMemcpy(zIn, zDup, (sxu32)nByte); /* Save the chunk */ SyStringInitFromBuf(&sWorker, zDup, nByte); } /* Save for later processing */ SySetPut(pRep->pCollector, (const void *)&sWorker); /* All done */ SXUNUSED(pKey); /* cc warning */ return JX9_OK; } /* * mixed str_replace(mixed $search, mixed $replace, mixed $subject[, int &$count ]) * mixed str_ireplace(mixed $search, mixed $replace, mixed $subject[, int &$count ]) * Replace all occurrences of the search string with the replacement string. * Parameters * If search and replace are arrays, then str_replace() takes a value from each * array and uses them to search and replace on subject. If replace has fewer values * than search, then an empty string is used for the rest of replacement values. * If search is an array and replace is a string, then this replacement string is used * for every value of search. The converse would not make sense, though. * If search or replace are arrays, their elements are processed first to last. * $search * The value being searched for, otherwise known as the needle. An array may be used * to designate multiple needles. * $replace * The replacement value that replaces found search values. An array may be used * to designate multiple replacements. * $subject * The string or array being searched and replaced on, otherwise known as the haystack. * If subject is an array, then the search and replace is performed with every entry * of subject, and the return value is an array as well. * $count (Not used) * If passed, this will be set to the number of replacements performed. * Return * This function returns a string or an array with the replaced values. */ static int jx9Builtin_str_replace(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyString sTemp, *pSearch, *pReplace; ProcStringMatch xMatch; const char *zIn, *zFunc; str_replace_data sRep; SyBlob sWorker; SySet sReplace; SySet sSearch; int rep_str; int nByte; sxi32 rc; if( nArg < 3 ){ /* Missing/Invalid arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Initialize fields */ SySetInit(&sSearch, &pCtx->pVm->sAllocator, sizeof(SyString)); SySetInit(&sReplace, &pCtx->pVm->sAllocator, sizeof(SyString)); SyBlobInit(&sWorker, &pCtx->pVm->sAllocator); SyZero(&sRep, sizeof(str_replace_data)); sRep.pCtx = pCtx; sRep.pCollector = &sSearch; rep_str = 0; /* Extract the subject */ zIn = jx9_value_to_string(apArg[2], &nByte); if( nByte < 1 ){ /* Nothing to replace, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Copy the subject */ SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nByte); /* Search string */ if( jx9_value_is_json_array(apArg[0]) ){ /* Collect search string */ jx9_array_walk(apArg[0], StrReplaceWalker, &sRep); }else{ /* Single pattern */ zIn = jx9_value_to_string(apArg[0], &nByte); if( nByte < 1 ){ /* Return the subject untouched since no search string is available */ jx9_result_value(pCtx, apArg[2]/* Subject as thrird argument*/); return JX9_OK; } SyStringInitFromBuf(&sTemp, zIn, nByte); /* Save for later processing */ SySetPut(&sSearch, (const void *)&sTemp); } /* Replace string */ if( jx9_value_is_json_array(apArg[1]) ){ /* Collect replace string */ sRep.pCollector = &sReplace; jx9_array_walk(apArg[1], StrReplaceWalker, &sRep); }else{ /* Single needle */ zIn = jx9_value_to_string(apArg[1], &nByte); rep_str = 1; SyStringInitFromBuf(&sTemp, zIn, nByte); /* Save for later processing */ SySetPut(&sReplace, (const void *)&sTemp); } /* Reset loop cursors */ SySetResetCursor(&sSearch); SySetResetCursor(&sReplace); pReplace = pSearch = 0; /* cc warning */ SyStringInitFromBuf(&sTemp, "", 0); /* Extract function name */ zFunc = jx9_function_name(pCtx); /* Set the default pattern match routine */ xMatch = SyBlobSearch; if( SyStrncmp(zFunc, "str_ireplace", sizeof("str_ireplace") - 1) == 0 ){ /* Case insensitive pattern match */ xMatch = iPatternMatch; } /* Start the replace process */ while( SXRET_OK == SySetGetNextEntry(&sSearch, (void **)&pSearch) ){ sxu32 nCount, nOfft; if( pSearch->nByte < 1 ){ /* Empty string, ignore */ continue; } /* Extract the replace string */ if( rep_str ){ pReplace = (SyString *)SySetPeek(&sReplace); }else{ if( SXRET_OK != SySetGetNextEntry(&sReplace, (void **)&pReplace) ){ /* Sepecial case when 'replace set' has fewer values than the search set. * An empty string is used for the rest of replacement values */ pReplace = 0; } } if( pReplace == 0 ){ /* Use an empty string instead */ pReplace = &sTemp; } nOfft = nCount = 0; for(;;){ if( nCount >= SyBlobLength(&sWorker) ){ break; } /* Perform a pattern lookup */ rc = xMatch(SyBlobDataAt(&sWorker, nCount), SyBlobLength(&sWorker) - nCount, (const void *)pSearch->zString, pSearch->nByte, &nOfft); if( rc != SXRET_OK ){ /* Pattern not found */ break; } /* Perform the replace operation */ StringReplace(&sWorker, nCount+nOfft, (int)pSearch->nByte, pReplace->zString, (int)pReplace->nByte); /* Increment offset counter */ nCount += nOfft + pReplace->nByte; } } /* All done, clean-up the mess left behind */ jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker), (int)SyBlobLength(&sWorker)); SySetRelease(&sSearch); SySetRelease(&sReplace); SyBlobRelease(&sWorker); return JX9_OK; } /* * string strtr(string $str, string $from, string $to) * string strtr(string $str, array $replace_pairs) * Translate characters or replace substrings. * Parameters * $str * The string being translated. * $from * The string being translated to to. * $to * The string replacing from. * $replace_pairs * The replace_pairs parameter may be used instead of to and * from, in which case it's an array in the form array('from' => 'to', ...). * Return * The translated string. * If replace_pairs contains a key which is an empty string (""), FALSE will be returned. */ static int jx9Builtin_strtr(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn; int nLen; if( nArg < 1 ){ /* Nothing to replace, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 || nArg < 2 ){ /* Invalid arguments */ jx9_result_string(pCtx, zIn, nLen); return JX9_OK; } if( nArg == 2 && jx9_value_is_json_array(apArg[1]) ){ str_replace_data sRepData; SyBlob sWorker; /* Initilaize the working buffer */ SyBlobInit(&sWorker, &pCtx->pVm->sAllocator); /* Copy raw string */ SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nLen); /* Init our replace data instance */ sRepData.pWorker = &sWorker; sRepData.xMatch = SyBlobSearch; /* Iterate throw array entries and perform the replace operation.*/ jx9_array_walk(apArg[1], StringReplaceWalker, &sRepData); /* All done, return the result string */ jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker), (int)SyBlobLength(&sWorker)); /* Will make it's own copy */ /* Clean-up */ SyBlobRelease(&sWorker); }else{ int i, flen, tlen, c, iOfft; const char *zFrom, *zTo; if( nArg < 3 ){ /* Nothing to replace */ jx9_result_string(pCtx, zIn, nLen); return JX9_OK; } /* Extract given arguments */ zFrom = jx9_value_to_string(apArg[1], &flen); zTo = jx9_value_to_string(apArg[2], &tlen); if( flen < 1 || tlen < 1 ){ /* Nothing to replace */ jx9_result_string(pCtx, zIn, nLen); return JX9_OK; } /* Start the replace process */ for( i = 0 ; i < nLen ; ++i ){ c = zIn[i]; if( CheckMask(c, zFrom, flen, &iOfft) ){ if ( iOfft < tlen ){ c = zTo[iOfft]; } } jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); } } return JX9_OK; } /* * Parse an INI string. * According to wikipedia * The INI file format is an informal standard for configuration files for some platforms or software. * INI files are simple text files with a basic structure composed of "sections" and "properties". * Format * Properties * The basic element contained in an INI file is the property. Every property has a name and a value * delimited by an equals sign (=). The name appears to the left of the equals sign. * Example: * name=value * Sections * Properties may be grouped into arbitrarily named sections. The section name appears on a line by itself * in square brackets ([ and ]). All properties after the section declaration are associated with that section. * There is no explicit "end of section" delimiter; sections end at the next section declaration * or the end of the file. Sections may not be nested. * Example: * [section] * Comments * Semicolons (;) at the beginning of the line indicate a comment. Comment lines are ignored. * This function return an array holding parsed values on success.FALSE otherwise. */ JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection) { jx9_value *pCur, *pArray, *pSection, *pWorker, *pValue; const char *zCur, *zEnd = &zIn[nByte]; SyHashEntry *pEntry; SyString sEntry; SyHash sHash; int c; /* Create an empty array and worker variables */ pArray = jx9_context_new_array(pCtx); pWorker = jx9_context_new_scalar(pCtx); pValue = jx9_context_new_scalar(pCtx); if( pArray == 0 || pWorker == 0 || pValue == 0){ /* Out of memory */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); /* Return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } SyHashInit(&sHash, &pCtx->pVm->sAllocator, 0, 0); pCur = pArray; /* Start the parse process */ for(;;){ /* Ignore leading white spaces */ while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0])){ zIn++; } if( zIn >= zEnd ){ /* No more input to process */ break; } if( zIn[0] == ';' || zIn[0] == '#' ){ /* Comment til the end of line */ zIn++; while(zIn < zEnd && zIn[0] != '\n' ){ zIn++; } continue; } /* Reset the string cursor of the working variable */ jx9_value_reset_string_cursor(pWorker); if( zIn[0] == '[' ){ /* Section: Extract the section name */ zIn++; zCur = zIn; while( zIn < zEnd && zIn[0] != ']' ){ zIn++; } if( zIn > zCur && bProcessSection ){ /* Save the section name */ SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur)); SyStringFullTrim(&sEntry); jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); if( sEntry.nByte > 0 ){ /* Associate an array with the section */ pSection = jx9_context_new_array(pCtx); if( pSection ){ jx9_array_add_elem(pArray, pWorker/*Section name*/, pSection); pCur = pSection; } } } zIn++; /* Trailing square brackets ']' */ }else{ jx9_value *pOldCur; int is_array; int iLen; /* Properties */ is_array = 0; zCur = zIn; iLen = 0; /* cc warning */ pOldCur = pCur; while( zIn < zEnd && zIn[0] != '=' ){ if( zIn[0] == '[' && !is_array ){ /* Array */ iLen = (int)(zIn-zCur); is_array = 1; if( iLen > 0 ){ jx9_value *pvArr = 0; /* cc warning */ /* Query the hashtable */ SyStringInitFromBuf(&sEntry, zCur, iLen); SyStringFullTrim(&sEntry); pEntry = SyHashGet(&sHash, (const void *)sEntry.zString, sEntry.nByte); if( pEntry ){ pvArr = (jx9_value *)SyHashEntryGetUserData(pEntry); }else{ /* Create an empty array */ pvArr = jx9_context_new_array(pCtx); if( pvArr ){ /* Save the entry */ SyHashInsert(&sHash, (const void *)sEntry.zString, sEntry.nByte, pvArr); /* Insert the entry */ jx9_value_reset_string_cursor(pWorker); jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); jx9_array_add_elem(pCur, pWorker, pvArr); jx9_value_reset_string_cursor(pWorker); } } if( pvArr ){ pCur = pvArr; } } while ( zIn < zEnd && zIn[0] != ']' ){ zIn++; } } zIn++; } if( !is_array ){ iLen = (int)(zIn-zCur); } /* Trim the key */ SyStringInitFromBuf(&sEntry, zCur, iLen); SyStringFullTrim(&sEntry); if( sEntry.nByte > 0 ){ if( !is_array ){ /* Save the key name */ jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); } /* extract key value */ jx9_value_reset_string_cursor(pValue); zIn++; /* '=' */ while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ zIn++; } if( zIn < zEnd ){ zCur = zIn; c = zIn[0]; if( c == '"' || c == '\'' ){ zIn++; /* Delimit the value */ while( zIn < zEnd ){ if ( zIn[0] == c && zIn[-1] != '\\' ){ break; } zIn++; } if( zIn < zEnd ){ zIn++; } }else{ while( zIn < zEnd ){ if( zIn[0] == '\n' ){ if( zIn[-1] != '\\' ){ break; } }else if( zIn[0] == ';' || zIn[0] == '#' ){ /* Inline comments */ break; } zIn++; } } /* Trim the value */ SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur)); SyStringFullTrim(&sEntry); if( c == '"' || c == '\'' ){ SyStringTrimLeadingChar(&sEntry, c); SyStringTrimTrailingChar(&sEntry, c); } if( sEntry.nByte > 0 ){ jx9_value_string(pValue, sEntry.zString, (int)sEntry.nByte); } /* Insert the key and it's value */ jx9_array_add_elem(pCur, is_array ? 0 /*Automatic index assign */: pWorker, pValue); } }else{ while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && ( SyisSpace(zIn[0]) || zIn[0] == '=' ) ){ zIn++; } } pCur = pOldCur; } } SyHashRelease(&sHash); /* Return the parse of the INI string */ jx9_result_value(pCtx, pArray); return SXRET_OK; } /* * array parse_ini_string(string $ini[, bool $process_sections = false[, int $scanner_mode = INI_SCANNER_NORMAL ]]) * Parse a configuration string. * Parameters * $ini * The contents of the ini file being parsed. * $process_sections * By setting the process_sections parameter to TRUE, you get a multidimensional array, with the section names * and settings included. The default for process_sections is FALSE. * $scanner_mode (Not used) * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. If INI_SCANNER_RAW is supplied * then option values will not be parsed. * Return * The settings are returned as an associative array on success, and FALSE on failure. */ static int jx9Builtin_parse_ini_string(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIni; int nByte; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE*/ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the raw INI buffer */ zIni = jx9_value_to_string(apArg[0], &nByte); /* Process the INI buffer*/ jx9ParseIniString(pCtx, zIni, (sxu32)nByte, (nArg > 1) ? jx9_value_to_bool(apArg[1]) : 0); return JX9_OK; } /* * Ctype Functions. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* * bool ctype_alnum(string $text) * Checks if all of the characters in the provided string, text, are alphanumeric. * Parameters * $text * The tested string. * Return * TRUE if every character in text is either a letter or a digit, FALSE otherwise. */ static int jx9Builtin_ctype_alnum(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( !SyisAlphaNum(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_alpha(string $text) * Checks if all of the characters in the provided string, text, are alphabetic. * Parameters * $text * The tested string. * Return * TRUE if every character in text is a letter from the current locale, FALSE otherwise. */ static int jx9Builtin_ctype_alpha(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( !SyisAlpha(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_cntrl(string $text) * Checks if all of the characters in the provided string, text, are control characters. * Parameters * $text * The tested string. * Return * TRUE if every character in text is a control characters, FALSE otherwise. */ static int jx9Builtin_ctype_cntrl(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( zIn[0] >= 0xc0 ){ /* UTF-8 stream */ break; } if( !SyisCtrl(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_digit(string $text) * Checks if all of the characters in the provided string, text, are numerical. * Parameters * $text * The tested string. * Return * TRUE if every character in the string text is a decimal digit, FALSE otherwise. */ static int jx9Builtin_ctype_digit(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( zIn[0] >= 0xc0 ){ /* UTF-8 stream */ break; } if( !SyisDigit(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_xdigit(string $text) * Check for character(s) representing a hexadecimal digit. * Parameters * $text * The tested string. * Return * Returns TRUE if every character in text is a hexadecimal 'digit', that is * a decimal digit or a character from [A-Fa-f] , FALSE otherwise. */ static int jx9Builtin_ctype_xdigit(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( zIn[0] >= 0xc0 ){ /* UTF-8 stream */ break; } if( !SyisHex(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_graph(string $text) * Checks if all of the characters in the provided string, text, creates visible output. * Parameters * $text * The tested string. * Return * Returns TRUE if every character in text is printable and actually creates visible output * (no white space), FALSE otherwise. */ static int jx9Builtin_ctype_graph(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( zIn[0] >= 0xc0 ){ /* UTF-8 stream */ break; } if( !SyisGraph(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_print(string $text) * Checks if all of the characters in the provided string, text, are printable. * Parameters * $text * The tested string. * Return * Returns TRUE if every character in text will actually create output (including blanks). * Returns FALSE if text contains control characters or characters that do not have any output * or control function at all. */ static int jx9Builtin_ctype_print(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( zIn[0] >= 0xc0 ){ /* UTF-8 stream */ break; } if( !SyisPrint(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_punct(string $text) * Checks if all of the characters in the provided string, text, are punctuation character. * Parameters * $text * The tested string. * Return * Returns TRUE if every character in text is printable, but neither letter * digit or blank, FALSE otherwise. */ static int jx9Builtin_ctype_punct(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( zIn[0] >= 0xc0 ){ /* UTF-8 stream */ break; } if( !SyisPunct(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_space(string $text) * Checks if all of the characters in the provided string, text, creates whitespace. * Parameters * $text * The tested string. * Return * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. * Besides the blank character this also includes tab, vertical tab, line feed, carriage return * and form feed characters. */ static int jx9Builtin_ctype_space(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( zIn[0] >= 0xc0 ){ /* UTF-8 stream */ break; } if( !SyisSpace(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_lower(string $text) * Checks if all of the characters in the provided string, text, are lowercase letters. * Parameters * $text * The tested string. * Return * Returns TRUE if every character in text is a lowercase letter in the current locale. */ static int jx9Builtin_ctype_lower(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( !SyisLower(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * bool ctype_upper(string $text) * Checks if all of the characters in the provided string, text, are uppercase letters. * Parameters * $text * The tested string. * Return * Returns TRUE if every character in text is a uppercase letter in the current locale. */ static int jx9Builtin_ctype_upper(jx9_context *pCtx, int nArg, jx9_value **apArg) { const unsigned char *zIn, *zEnd; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target string */ zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); zEnd = &zIn[nLen]; if( nLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ for(;;){ if( zIn >= zEnd ){ /* If we reach the end of the string, then the test succeeded. */ jx9_result_bool(pCtx, 1); return JX9_OK; } if( !SyisUpper(zIn[0]) ){ break; } /* Point to the next character */ zIn++; } /* The test failed, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* * Date/Time functions * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Devel. */ #include #ifdef __WINNT__ /* GetSystemTime() */ #include #ifdef _WIN32_WCE /* ** WindowsCE does not have a localtime() function. So create a ** substitute. ** Taken from the SQLite3 source tree. ** Status: Public domain */ struct tm *__cdecl localtime(const time_t *t) { static struct tm y; FILETIME uTm, lTm; SYSTEMTIME pTm; jx9_int64 t64; t64 = *t; t64 = (t64 + 11644473600)*10000000; uTm.dwLowDateTime = (DWORD)(t64 & 0xFFFFFFFF); uTm.dwHighDateTime= (DWORD)(t64 >> 32); FileTimeToLocalFileTime(&uTm, &lTm); FileTimeToSystemTime(&lTm, &pTm); y.tm_year = pTm.wYear - 1900; y.tm_mon = pTm.wMonth - 1; y.tm_wday = pTm.wDayOfWeek; y.tm_mday = pTm.wDay; y.tm_hour = pTm.wHour; y.tm_min = pTm.wMinute; y.tm_sec = pTm.wSecond; return &y; } #endif /*_WIN32_WCE */ #elif defined(__UNIXES__) #include #endif /* __WINNT__*/ /* * int64 time(void) * Current Unix timestamp * Parameters * None. * Return * Returns the current time measured in the number of seconds * since the Unix Epoch (January 1 1970 00:00:00 GMT). */ static int jx9Builtin_time(jx9_context *pCtx, int nArg, jx9_value **apArg) { time_t tt; SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* Extract the current time */ time(&tt); /* Return as 64-bit integer */ jx9_result_int64(pCtx, (jx9_int64)tt); return JX9_OK; } /* * string/float microtime([ bool $get_as_float = false ]) * microtime() returns the current Unix timestamp with microseconds. * Parameters * $get_as_float * If used and set to TRUE, microtime() will return a float instead of a string * as described in the return values section below. * Return * By default, microtime() returns a string in the form "msec sec", where sec * is the current time measured in the number of seconds since the Unix * epoch (0:00:00 January 1, 1970 GMT), and msec is the number of microseconds * that have elapsed since sec expressed in seconds. * If get_as_float is set to TRUE, then microtime() returns a float, which represents * the current time in seconds since the Unix epoch accurate to the nearest microsecond. */ static int jx9Builtin_microtime(jx9_context *pCtx, int nArg, jx9_value **apArg) { int bFloat = 0; sytime sTime; #if defined(__UNIXES__) struct timeval tv; gettimeofday(&tv, 0); sTime.tm_sec = (long)tv.tv_sec; sTime.tm_usec = (long)tv.tv_usec; #else time_t tt; time(&tt); sTime.tm_sec = (long)tt; sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); #endif /* __UNIXES__ */ if( nArg > 0 ){ bFloat = jx9_value_to_bool(apArg[0]); } if( bFloat ){ /* Return as float */ jx9_result_double(pCtx, (double)sTime.tm_sec); }else{ /* Return as string */ jx9_result_string_format(pCtx, "%ld %ld", sTime.tm_usec, sTime.tm_sec); } return JX9_OK; } /* * array getdate ([ int $timestamp = time() ]) * Get date/time information. * Parameter * $timestamp: The optional timestamp parameter is an integer Unix timestamp * that defaults to the current local time if a timestamp is not given. * In other words, it defaults to the value of time(). * Returns * Returns an associative array of information related to the timestamp. * Elements from the returned associative array are as follows: * KEY VALUE * --------- ------- * "seconds" Numeric representation of seconds 0 to 59 * "minutes" Numeric representation of minutes 0 to 59 * "hours" Numeric representation of hours 0 to 23 * "mday" Numeric representation of the day of the month 1 to 31 * "wday" Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) * "mon" Numeric representation of a month 1 through 12 * "year" A full numeric representation of a year, 4 digits Examples: 1999 or 2003 * "yday" Numeric representation of the day of the year 0 through 365 * "weekday" A full textual representation of the day of the week Sunday through Saturday * "month" A full textual representation of a month, such as January or March January through December * 0 Seconds since the Unix Epoch, similar to the values returned by time() and used by date(). * NOTE: * NULL is returned on failure. */ static int jx9Builtin_getdate(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pValue, *pArray; Sytm sTm; if( nArg < 1 ){ #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif }else{ /* Use the given timestamp */ time_t t; struct tm *pTm; #ifdef __WINNT__ #ifdef _MSC_VER #if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ #pragma warning(disable:4996) /* _CRT_SECURE...*/ #endif #endif #endif if( jx9_value_is_int(apArg[0]) ){ t = (time_t)jx9_value_to_int64(apArg[0]); pTm = localtime(&t); if( pTm == 0 ){ time(&t); } }else{ time(&t); } pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); } /* Element value */ pValue = jx9_context_new_scalar(pCtx); if( pValue == 0 ){ /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Fill the array */ /* Seconds */ jx9_value_int(pValue, sTm.tm_sec); jx9_array_add_strkey_elem(pArray, "seconds", pValue); /* Minutes */ jx9_value_int(pValue, sTm.tm_min); jx9_array_add_strkey_elem(pArray, "minutes", pValue); /* Hours */ jx9_value_int(pValue, sTm.tm_hour); jx9_array_add_strkey_elem(pArray, "hours", pValue); /* mday */ jx9_value_int(pValue, sTm.tm_mday); jx9_array_add_strkey_elem(pArray, "mday", pValue); /* wday */ jx9_value_int(pValue, sTm.tm_wday); jx9_array_add_strkey_elem(pArray, "wday", pValue); /* mon */ jx9_value_int(pValue, sTm.tm_mon+1); jx9_array_add_strkey_elem(pArray, "mon", pValue); /* year */ jx9_value_int(pValue, sTm.tm_year); jx9_array_add_strkey_elem(pArray, "year", pValue); /* yday */ jx9_value_int(pValue, sTm.tm_yday); jx9_array_add_strkey_elem(pArray, "yday", pValue); /* Weekday */ jx9_value_string(pValue, SyTimeGetDay(sTm.tm_wday), -1); jx9_array_add_strkey_elem(pArray, "weekday", pValue); /* Month */ jx9_value_reset_string_cursor(pValue); jx9_value_string(pValue, SyTimeGetMonth(sTm.tm_mon), -1); jx9_array_add_strkey_elem(pArray, "month", pValue); /* Seconds since the epoch */ jx9_value_int64(pValue, (jx9_int64)time(0)); jx9_array_add_elem(pArray, 0 /* Index zero */, pValue); /* Return the freshly created array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * mixed gettimeofday([ bool $return_float = false ] ) * Returns an associative array containing the data returned from the system call. * Parameters * $return_float * When set to TRUE, a float instead of an array is returned. * Return * By default an array is returned. If return_float is set, then * a float is returned. */ static int jx9Builtin_gettimeofday(jx9_context *pCtx, int nArg, jx9_value **apArg) { int bFloat = 0; sytime sTime; #if defined(__UNIXES__) struct timeval tv; gettimeofday(&tv, 0); sTime.tm_sec = (long)tv.tv_sec; sTime.tm_usec = (long)tv.tv_usec; #else time_t tt; time(&tt); sTime.tm_sec = (long)tt; sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); #endif /* __UNIXES__ */ if( nArg > 0 ){ bFloat = jx9_value_to_bool(apArg[0]); } if( bFloat ){ /* Return as float */ jx9_result_double(pCtx, (double)sTime.tm_sec); }else{ /* Return an associative array */ jx9_value *pValue, *pArray; /* Create a new array */ pArray = jx9_context_new_array(pCtx); /* Element value */ pValue = jx9_context_new_scalar(pCtx); if( pValue == 0 || pArray == 0 ){ /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Fill the array */ /* sec */ jx9_value_int64(pValue, sTime.tm_sec); jx9_array_add_strkey_elem(pArray, "sec", pValue); /* usec */ jx9_value_int64(pValue, sTime.tm_usec); jx9_array_add_strkey_elem(pArray, "usec", pValue); /* Return the array */ jx9_result_value(pCtx, pArray); } return JX9_OK; } /* Check if the given year is leap or not */ #define IS_LEAP_YEAR(YEAR) (YEAR % 400 ? ( YEAR % 100 ? ( YEAR % 4 ? 0 : 1 ) : 0 ) : 1) /* ISO-8601 numeric representation of the day of the week */ static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 }; /* * Format a given date string. * Supported format: (Taken from JX9 online docs) * character Description * d Day of the month * D A textual representation of a days * j Day of the month without leading zeros * l A full textual representation of the day of the week * N ISO-8601 numeric representation of the day of the week * w Numeric representation of the day of the week * z The day of the year (starting from 0) * F A full textual representation of a month, such as January or March * m Numeric representation of a month, with leading zeros 01 through 12 * M A short textual representation of a month, three letters Jan through Dec * n Numeric representation of a month, without leading zeros 1 through 12 * t Number of days in the given month 28 through 31 * L Whether it's a leap year 1 if it is a leap year, 0 otherwise. * o ISO-8601 year number. This has the same value as Y, except that if the ISO week number * (W) belongs to the previous or next year, that year is used instead. (added in JX9 5.1.0) Examples: 1999 or 2003 * Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003 * y A two digit representation of a year Examples: 99 or 03 * a Lowercase Ante meridiem and Post meridiem am or pm * A Uppercase Ante meridiem and Post meridiem AM or PM * g 12-hour format of an hour without leading zeros 1 through 12 * G 24-hour format of an hour without leading zeros 0 through 23 * h 12-hour format of an hour with leading zeros 01 through 12 * H 24-hour format of an hour with leading zeros 00 through 23 * i Minutes with leading zeros 00 to 59 * s Seconds, with leading zeros 00 through 59 * u Microseconds Example: 654321 * e Timezone identifier Examples: UTC, GMT, Atlantic/Azores * I (capital i) Whether or not the date is in daylight saving time 1 if Daylight Saving Time, 0 otherwise. * r RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 +0200 * U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) * S English ordinal suffix for the day of the month, 2 characters * O Difference to Greenwich time (GMT) in hours * Z Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those * east of UTC is always positive. * c ISO 8601 date */ static sxi32 DateFormat(jx9_context *pCtx, const char *zIn, int nLen, Sytm *pTm) { const char *zEnd = &zIn[nLen]; const char *zCur; /* Start the format process */ for(;;){ if( zIn >= zEnd ){ /* No more input to process */ break; } switch(zIn[0]){ case 'd': /* Day of the month, 2 digits with leading zeros */ jx9_result_string_format(pCtx, "%02d", pTm->tm_mday); break; case 'D': /*A textual representation of a day, three letters*/ zCur = SyTimeGetDay(pTm->tm_wday); jx9_result_string(pCtx, zCur, 3); break; case 'j': /* Day of the month without leading zeros */ jx9_result_string_format(pCtx, "%d", pTm->tm_mday); break; case 'l': /* A full textual representation of the day of the week */ zCur = SyTimeGetDay(pTm->tm_wday); jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/); break; case 'N':{ /* ISO-8601 numeric representation of the day of the week */ jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]); break; } case 'w': /*Numeric representation of the day of the week*/ jx9_result_string_format(pCtx, "%d", pTm->tm_wday); break; case 'z': /*The day of the year*/ jx9_result_string_format(pCtx, "%d", pTm->tm_yday); break; case 'F': /*A full textual representation of a month, such as January or March*/ zCur = SyTimeGetMonth(pTm->tm_mon); jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/); break; case 'm': /*Numeric representation of a month, with leading zeros*/ jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1); break; case 'M': /*A short textual representation of a month, three letters*/ zCur = SyTimeGetMonth(pTm->tm_mon); jx9_result_string(pCtx, zCur, 3); break; case 'n': /*Numeric representation of a month, without leading zeros*/ jx9_result_string_format(pCtx, "%d", pTm->tm_mon + 1); break; case 't':{ static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int nDays = aMonDays[pTm->tm_mon % 12 ]; if( pTm->tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(pTm->tm_year) ){ nDays = 28; } /*Number of days in the given month*/ jx9_result_string_format(pCtx, "%d", nDays); break; } case 'L':{ int isLeap = IS_LEAP_YEAR(pTm->tm_year); /* Whether it's a leap year */ jx9_result_string_format(pCtx, "%d", isLeap); break; } case 'o': /* ISO-8601 year number.*/ jx9_result_string_format(pCtx, "%4d", pTm->tm_year); break; case 'Y': /* A full numeric representation of a year, 4 digits */ jx9_result_string_format(pCtx, "%4d", pTm->tm_year); break; case 'y': /*A two digit representation of a year*/ jx9_result_string_format(pCtx, "%02d", pTm->tm_year%100); break; case 'a': /* Lowercase Ante meridiem and Post meridiem */ jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", 2); break; case 'A': /* Uppercase Ante meridiem and Post meridiem */ jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", 2); break; case 'g': /* 12-hour format of an hour without leading zeros*/ jx9_result_string_format(pCtx, "%d", 1+(pTm->tm_hour%12)); break; case 'G': /* 24-hour format of an hour without leading zeros */ jx9_result_string_format(pCtx, "%d", pTm->tm_hour); break; case 'h': /* 12-hour format of an hour with leading zeros */ jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12)); break; case 'H': /* 24-hour format of an hour with leading zeros */ jx9_result_string_format(pCtx, "%02d", pTm->tm_hour); break; case 'i': /* Minutes with leading zeros */ jx9_result_string_format(pCtx, "%02d", pTm->tm_min); break; case 's': /* second with leading zeros */ jx9_result_string_format(pCtx, "%02d", pTm->tm_sec); break; case 'u': /* Microseconds */ jx9_result_string_format(pCtx, "%u", pTm->tm_sec * SX_USEC_PER_SEC); break; case 'S':{ /* English ordinal suffix for the day of the month, 2 characters */ static const char zSuffix[] = "thstndrdthththththth"; int v = pTm->tm_mday; jx9_result_string(pCtx, &zSuffix[2 * (int)(v / 10 % 10 != 1 ? v % 10 : 0)], (int)sizeof(char) * 2); break; } case 'e': /* Timezone identifier */ zCur = pTm->tm_zone; if( zCur == 0 ){ /* Assume GMT */ zCur = "GMT"; } jx9_result_string(pCtx, zCur, -1); break; case 'I': /* Whether or not the date is in daylight saving time */ #ifdef __WINNT__ #ifdef _MSC_VER #ifndef _WIN32_WCE _get_daylight(&pTm->tm_isdst); #endif #endif #endif jx9_result_string_format(pCtx, "%d", pTm->tm_isdst == 1); break; case 'r': /* RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 */ jx9_result_string_format(pCtx, "%.3s, %02d %.3s %4d %02d:%02d:%02d", SyTimeGetDay(pTm->tm_wday), pTm->tm_mday, SyTimeGetMonth(pTm->tm_mon), pTm->tm_year, pTm->tm_hour, pTm->tm_min, pTm->tm_sec ); break; case 'U':{ time_t tt; /* Seconds since the Unix Epoch */ time(&tt); jx9_result_string_format(pCtx, "%u", (unsigned int)tt); break; } case 'O': case 'P': /* Difference to Greenwich time (GMT) in hours */ jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff); break; case 'Z': /* Timezone offset in seconds. The offset for timezones west of UTC * is always negative, and for those east of UTC is always positive. */ jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff); break; case 'c': /* ISO 8601 date */ jx9_result_string_format(pCtx, "%4d-%02d-%02dT%02d:%02d:%02d%+05d", pTm->tm_year, pTm->tm_mon+1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec, pTm->tm_gmtoff ); break; case '\\': zIn++; /* Expand verbatim */ if( zIn < zEnd ){ jx9_result_string(pCtx, zIn, (int)sizeof(char)); } break; default: /* Unknown format specifer, expand verbatim */ jx9_result_string(pCtx, zIn, (int)sizeof(char)); break; } /* Point to the next character */ zIn++; } return SXRET_OK; } /* * JX9 implementation of the strftime() function. * The following formats are supported: * %a An abbreviated textual representation of the day * %A A full textual representation of the day * %d Two-digit day of the month (with leading zeros) * %e Day of the month, with a space preceding single digits. * %j Day of the year, 3 digits with leading zeros * %u ISO-8601 numeric representation of the day of the week 1 (for Monday) though 7 (for Sunday) * %w Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) * %U Week number of the given year, starting with the first Sunday as the first week * %V ISO-8601:1988 week number of the given year, starting with the first week of the year with at least * 4 weekdays, with Monday being the start of the week. * %W A numeric representation of the week of the year * %b Abbreviated month name, based on the locale * %B Full month name, based on the locale * %h Abbreviated month name, based on the locale (an alias of %b) * %m Two digit representation of the month * %C Two digit representation of the century (year divided by 100, truncated to an integer) * %g Two digit representation of the year going by ISO-8601:1988 standards (see %V) * %G The full four-digit version of %g * %y Two digit representation of the year * %Y Four digit representation for the year * %H Two digit representation of the hour in 24-hour format * %I Two digit representation of the hour in 12-hour format * %l (lower-case 'L') Hour in 12-hour format, with a space preceeding single digits * %M Two digit representation of the minute * %p UPPER-CASE 'AM' or 'PM' based on the given time * %P lower-case 'am' or 'pm' based on the given time * %r Same as "%I:%M:%S %p" * %R Same as "%H:%M" * %S Two digit representation of the second * %T Same as "%H:%M:%S" * %X Preferred time representation based on locale, without the date * %z Either the time zone offset from UTC or the abbreviation * %Z The time zone offset/abbreviation option NOT given by %z * %c Preferred date and time stamp based on local * %D Same as "%m/%d/%y" * %F Same as "%Y-%m-%d" * %s Unix Epoch Time timestamp (same as the time() function) * %x Preferred date representation based on locale, without the time * %n A newline character ("\n") * %t A Tab character ("\t") * %% A literal percentage character ("%") */ static int jx9Strftime( jx9_context *pCtx, /* Call context */ const char *zIn, /* Input string */ int nLen, /* Input length */ Sytm *pTm /* Parse of the given time */ ) { const char *zCur, *zEnd = &zIn[nLen]; int c; /* Start the format process */ for(;;){ zCur = zIn; while(zIn < zEnd && zIn[0] != '%' ){ zIn++; } if( zIn > zCur ){ /* Consume input verbatim */ jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); } zIn++; /* Jump the percent sign */ if( zIn >= zEnd ){ /* No more input to process */ break; } c = zIn[0]; /* Act according to the current specifer */ switch(c){ case '%': /* A literal percentage character ("%") */ jx9_result_string(pCtx, "%", (int)sizeof(char)); break; case 't': /* A Tab character */ jx9_result_string(pCtx, "\t", (int)sizeof(char)); break; case 'n': /* A newline character */ jx9_result_string(pCtx, "\n", (int)sizeof(char)); break; case 'a': /* An abbreviated textual representation of the day */ jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), (int)sizeof(char)*3); break; case 'A': /* A full textual representation of the day */ jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), -1/*Compute length automatically*/); break; case 'e': /* Day of the month, 2 digits with leading space for single digit*/ jx9_result_string_format(pCtx, "%2d", pTm->tm_mday); break; case 'd': /* Two-digit day of the month (with leading zeros) */ jx9_result_string_format(pCtx, "%02d", pTm->tm_mon+1); break; case 'j': /*The day of the year, 3 digits with leading zeros*/ jx9_result_string_format(pCtx, "%03d", pTm->tm_yday); break; case 'u': /* ISO-8601 numeric representation of the day of the week */ jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]); break; case 'w': /* Numeric representation of the day of the week */ jx9_result_string_format(pCtx, "%d", pTm->tm_wday); break; case 'b': case 'h': /*A short textual representation of a month, three letters (Not based on locale)*/ jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), (int)sizeof(char)*3); break; case 'B': /* Full month name (Not based on locale) */ jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), -1/*Compute length automatically*/); break; case 'm': /*Numeric representation of a month, with leading zeros*/ jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1); break; case 'C': /* Two digit representation of the century */ jx9_result_string_format(pCtx, "%2d", pTm->tm_year/100); break; case 'y': case 'g': /* Two digit representation of the year */ jx9_result_string_format(pCtx, "%2d", pTm->tm_year%100); break; case 'Y': case 'G': /* Four digit representation of the year */ jx9_result_string_format(pCtx, "%4d", pTm->tm_year); break; case 'I': /* 12-hour format of an hour with leading zeros */ jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12)); break; case 'l': /* 12-hour format of an hour with leading space */ jx9_result_string_format(pCtx, "%2d", 1+(pTm->tm_hour%12)); break; case 'H': /* 24-hour format of an hour with leading zeros */ jx9_result_string_format(pCtx, "%02d", pTm->tm_hour); break; case 'M': /* Minutes with leading zeros */ jx9_result_string_format(pCtx, "%02d", pTm->tm_min); break; case 'S': /* Seconds with leading zeros */ jx9_result_string_format(pCtx, "%02d", pTm->tm_sec); break; case 'z': case 'Z': /* Timezone identifier */ zCur = pTm->tm_zone; if( zCur == 0 ){ /* Assume GMT */ zCur = "GMT"; } jx9_result_string(pCtx, zCur, -1); break; case 'T': case 'X': /* Same as "%H:%M:%S" */ jx9_result_string_format(pCtx, "%02d:%02d:%02d", pTm->tm_hour, pTm->tm_min, pTm->tm_sec); break; case 'R': /* Same as "%H:%M" */ jx9_result_string_format(pCtx, "%02d:%02d", pTm->tm_hour, pTm->tm_min); break; case 'P': /* Lowercase Ante meridiem and Post meridiem */ jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", (int)sizeof(char)*2); break; case 'p': /* Uppercase Ante meridiem and Post meridiem */ jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", (int)sizeof(char)*2); break; case 'r': /* Same as "%I:%M:%S %p" */ jx9_result_string_format(pCtx, "%02d:%02d:%02d %s", 1+(pTm->tm_hour%12), pTm->tm_min, pTm->tm_sec, pTm->tm_hour > 12 ? "PM" : "AM" ); break; case 'D': case 'x': /* Same as "%m/%d/%y" */ jx9_result_string_format(pCtx, "%02d/%02d/%02d", pTm->tm_mon+1, pTm->tm_mday, pTm->tm_year%100 ); break; case 'F': /* Same as "%Y-%m-%d" */ jx9_result_string_format(pCtx, "%d-%02d-%02d", pTm->tm_year, pTm->tm_mon+1, pTm->tm_mday ); break; case 'c': jx9_result_string_format(pCtx, "%d-%02d-%02d %02d:%02d:%02d", pTm->tm_year, pTm->tm_mon+1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec ); break; case 's':{ time_t tt; /* Seconds since the Unix Epoch */ time(&tt); jx9_result_string_format(pCtx, "%u", (unsigned int)tt); break; } default: /* unknown specifer, simply ignore*/ break; } /* Advance the cursor */ zIn++; } return SXRET_OK; } /* * string date(string $format [, int $timestamp = time() ] ) * Returns a string formatted according to the given format string using * the given integer timestamp or the current time if no timestamp is given. * In other words, timestamp is optional and defaults to the value of time(). * Parameters * $format * The format of the outputted date string (See code above) * $timestamp * The optional timestamp parameter is an integer Unix timestamp * that defaults to the current local time if a timestamp is not given. * In other words, it defaults to the value of time(). * Return * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. */ static int jx9Builtin_date(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFormat; int nLen; Sytm sTm; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Don't bother processing return the empty string */ jx9_result_string(pCtx, "", 0); } if( nArg < 2 ){ #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif }else{ /* Use the given timestamp */ time_t t; struct tm *pTm; if( jx9_value_is_int(apArg[1]) ){ t = (time_t)jx9_value_to_int64(apArg[1]); pTm = localtime(&t); if( pTm == 0 ){ time(&t); } }else{ time(&t); } pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); } /* Format the given string */ DateFormat(pCtx, zFormat, nLen, &sTm); return JX9_OK; } /* * string strftime(string $format [, int $timestamp = time() ] ) * Format a local time/date (PLATFORM INDEPENDANT IMPLEENTATION NOT BASED ON LOCALE) * Parameters * $format * The format of the outputted date string (See code above) * $timestamp * The optional timestamp parameter is an integer Unix timestamp * that defaults to the current local time if a timestamp is not given. * In other words, it defaults to the value of time(). * Return * Returns a string formatted according format using the given timestamp * or the current local time if no timestamp is given. */ static int jx9Builtin_strftime(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFormat; int nLen; Sytm sTm; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Don't bother processing return FALSE */ jx9_result_bool(pCtx, 0); } if( nArg < 2 ){ #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif }else{ /* Use the given timestamp */ time_t t; struct tm *pTm; if( jx9_value_is_int(apArg[1]) ){ t = (time_t)jx9_value_to_int64(apArg[1]); pTm = localtime(&t); if( pTm == 0 ){ time(&t); } }else{ time(&t); } pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); } /* Format the given string */ jx9Strftime(pCtx, zFormat, nLen, &sTm); if( jx9_context_result_buf_length(pCtx) < 1 ){ /* Nothing was formatted, return FALSE */ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * string gmdate(string $format [, int $timestamp = time() ] ) * Identical to the date() function except that the time returned * is Greenwich Mean Time (GMT). * Parameters * $format * The format of the outputted date string (See code above) * $timestamp * The optional timestamp parameter is an integer Unix timestamp * that defaults to the current local time if a timestamp is not given. * In other words, it defaults to the value of time(). * Return * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. */ static int jx9Builtin_gmdate(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFormat; int nLen; Sytm sTm; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Don't bother processing return the empty string */ jx9_result_string(pCtx, "", 0); } if( nArg < 2 ){ #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = gmtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif }else{ /* Use the given timestamp */ time_t t; struct tm *pTm; if( jx9_value_is_int(apArg[1]) ){ t = (time_t)jx9_value_to_int64(apArg[1]); pTm = gmtime(&t); if( pTm == 0 ){ time(&t); } }else{ time(&t); } pTm = gmtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); } /* Format the given string */ DateFormat(pCtx, zFormat, nLen, &sTm); return JX9_OK; } /* * array localtime([ int $timestamp = time() [, bool $is_associative = false ]]) * Return the local time. * Parameter * $timestamp: The optional timestamp parameter is an integer Unix timestamp * that defaults to the current local time if a timestamp is not given. * In other words, it defaults to the value of time(). * $is_associative * If set to FALSE or not supplied then the array is returned as a regular, numerically * indexed array. If the argument is set to TRUE then localtime() returns an associative * array containing all the different elements of the structure returned by the C function * call to localtime. The names of the different keys of the associative array are as follows: * "tm_sec" - seconds, 0 to 59 * "tm_min" - minutes, 0 to 59 * "tm_hour" - hours, 0 to 23 * "tm_mday" - day of the month, 1 to 31 * "tm_mon" - month of the year, 0 (Jan) to 11 (Dec) * "tm_year" - years since 1900 * "tm_wday" - day of the week, 0 (Sun) to 6 (Sat) * "tm_yday" - day of the year, 0 to 365 * "tm_isdst" - is daylight savings time in effect? Positive if yes, 0 if not, negative if unknown. * Returns * An associative array of information related to the timestamp. */ static int jx9Builtin_localtime(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pValue, *pArray; int isAssoc = 0; Sytm sTm; if( nArg < 1 ){ #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); /* TODO(chems): GMT not local */ SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif }else{ /* Use the given timestamp */ time_t t; struct tm *pTm; if( jx9_value_is_int(apArg[0]) ){ t = (time_t)jx9_value_to_int64(apArg[0]); pTm = localtime(&t); if( pTm == 0 ){ time(&t); } }else{ time(&t); } pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); } /* Element value */ pValue = jx9_context_new_scalar(pCtx); if( pValue == 0 ){ /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } if( nArg > 1 ){ isAssoc = jx9_value_to_bool(apArg[1]); } /* Fill the array */ /* Seconds */ jx9_value_int(pValue, sTm.tm_sec); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_sec", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* Minutes */ jx9_value_int(pValue, sTm.tm_min); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_min", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* Hours */ jx9_value_int(pValue, sTm.tm_hour); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_hour", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* mday */ jx9_value_int(pValue, sTm.tm_mday); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_mday", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* mon */ jx9_value_int(pValue, sTm.tm_mon); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_mon", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* year since 1900 */ jx9_value_int(pValue, sTm.tm_year-1900); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_year", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* wday */ jx9_value_int(pValue, sTm.tm_wday); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_wday", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* yday */ jx9_value_int(pValue, sTm.tm_yday); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_yday", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* isdst */ #ifdef __WINNT__ #ifdef _MSC_VER #ifndef _WIN32_WCE _get_daylight(&sTm.tm_isdst); #endif #endif #endif jx9_value_int(pValue, sTm.tm_isdst); if( isAssoc ){ jx9_array_add_strkey_elem(pArray, "tm_isdst", pValue); }else{ jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); } /* Return the array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * int idate(string $format [, int $timestamp = time() ]) * Returns a number formatted according to the given format string * using the given integer timestamp or the current local time if * no timestamp is given. In other words, timestamp is optional and defaults * to the value of time(). * Unlike the function date(), idate() accepts just one char in the format * parameter. * $Parameters * Supported format * d Day of the month * h Hour (12 hour format) * H Hour (24 hour format) * i Minutes * I (uppercase i)1 if DST is activated, 0 otherwise * L (uppercase l) returns 1 for leap year, 0 otherwise * m Month number * s Seconds * t Days in current month * U Seconds since the Unix Epoch - January 1 1970 00:00:00 UTC - this is the same as time() * w Day of the week (0 on Sunday) * W ISO-8601 week number of year, weeks starting on Monday * y Year (1 or 2 digits - check note below) * Y Year (4 digits) * z Day of the year * Z Timezone offset in seconds * $timestamp * The optional timestamp parameter is an integer Unix timestamp that defaults * to the current local time if a timestamp is not given. In other words, it defaults * to the value of time(). * Return * An integer. */ static int jx9Builtin_idate(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFormat; jx9_int64 iVal = 0; int nLen; Sytm sTm; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return -1 */ jx9_result_int(pCtx, -1); return JX9_OK; } zFormat = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Don't bother processing return -1*/ jx9_result_int(pCtx, -1); } if( nArg < 2 ){ #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif }else{ /* Use the given timestamp */ time_t t; struct tm *pTm; if( jx9_value_is_int(apArg[1]) ){ t = (time_t)jx9_value_to_int64(apArg[1]); pTm = localtime(&t); if( pTm == 0 ){ time(&t); } }else{ time(&t); } pTm = localtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); } /* Perform the requested operation */ switch(zFormat[0]){ case 'd': /* Day of the month */ iVal = sTm.tm_mday; break; case 'h': /* Hour (12 hour format)*/ iVal = 1 + (sTm.tm_hour % 12); break; case 'H': /* Hour (24 hour format)*/ iVal = sTm.tm_hour; break; case 'i': /*Minutes*/ iVal = sTm.tm_min; break; case 'I': /* returns 1 if DST is activated, 0 otherwise */ #ifdef __WINNT__ #ifdef _MSC_VER #ifndef _WIN32_WCE _get_daylight(&sTm.tm_isdst); #endif #endif #endif iVal = sTm.tm_isdst; break; case 'L': /* returns 1 for leap year, 0 otherwise */ iVal = IS_LEAP_YEAR(sTm.tm_year); break; case 'm': /* Month number*/ iVal = sTm.tm_mon; break; case 's': /*Seconds*/ iVal = sTm.tm_sec; break; case 't':{ /*Days in current month*/ static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; int nDays = aMonDays[sTm.tm_mon % 12 ]; if( sTm.tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(sTm.tm_year) ){ nDays = 28; } iVal = nDays; break; } case 'U': /*Seconds since the Unix Epoch*/ iVal = (jx9_int64)time(0); break; case 'w': /* Day of the week (0 on Sunday) */ iVal = sTm.tm_wday; break; case 'W': { /* ISO-8601 week number of year, weeks starting on Monday */ static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 }; iVal = aISO8601[sTm.tm_wday % 7 ]; break; } case 'y': /* Year (2 digits) */ iVal = sTm.tm_year % 100; break; case 'Y': /* Year (4 digits) */ iVal = sTm.tm_year; break; case 'z': /* Day of the year */ iVal = sTm.tm_yday; break; case 'Z': /*Timezone offset in seconds*/ iVal = sTm.tm_gmtoff; break; default: /* unknown format, throw a warning */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Unknown date format token"); break; } /* Return the time value */ jx9_result_int64(pCtx, iVal); return JX9_OK; } /* * int mktime/gmmktime([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") * [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] ) * Returns the Unix timestamp corresponding to the arguments given. This timestamp is a 64bit integer * containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time * specified. * Arguments may be left out in order from right to left; any arguments thus omitted will be set to * the current value according to the local date and time. * Parameters * $hour * The number of the hour relevant to the start of the day determined by month, day and year. * Negative values reference the hour before midnight of the day in question. Values greater * than 23 reference the appropriate hour in the following day(s). * $minute * The number of the minute relevant to the start of the hour. Negative values reference * the minute in the previous hour. Values greater than 59 reference the appropriate minute * in the following hour(s). * $second * The number of seconds relevant to the start of the minute. Negative values reference * the second in the previous minute. Values greater than 59 reference the appropriate * second in the following minute(s). * $month * The number of the month relevant to the end of the previous year. Values 1 to 12 reference * the normal calendar months of the year in question. Values less than 1 (including negative values) * reference the months in the previous year in reverse order, so 0 is December, -1 is November)... * $day * The number of the day relevant to the end of the previous month. Values 1 to 28, 29, 30 or 31 * (depending upon the month) reference the normal days in the relevant month. Values less than 1 * (including negative values) reference the days in the previous month, so 0 is the last day * of the previous month, -1 is the day before that, etc. Values greater than the number of days * in the relevant month reference the appropriate day in the following month(s). * $year * The number of the year, may be a two or four digit value, with values between 0-69 mapping * to 2000-2069 and 70-100 to 1970-2000. On systems where time_t is a 32bit signed integer, as * most common today, the valid range for year is somewhere between 1901 and 2038. * $is_dst * This parameter can be set to 1 if the time is during daylight savings time (DST), 0 if it is not, * or -1 (the default) if it is unknown whether the time is within daylight savings time or not. * Return * mktime() returns the Unix timestamp of the arguments given. * If the arguments are invalid, the function returns FALSE */ static int jx9Builtin_mktime(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFunction; jx9_int64 iVal = 0; struct tm *pTm; time_t t; /* Extract function name */ zFunction = jx9_function_name(pCtx); /* Get the current time */ time(&t); if( zFunction[0] == 'g' /* gmmktime */ ){ pTm = gmtime(&t); }else{ /* localtime */ pTm = localtime(&t); } if( nArg > 0 ){ int iVal; /* Hour */ iVal = jx9_value_to_int(apArg[0]); pTm->tm_hour = iVal; if( nArg > 1 ){ /* Minutes */ iVal = jx9_value_to_int(apArg[1]); pTm->tm_min = iVal; if( nArg > 2 ){ /* Seconds */ iVal = jx9_value_to_int(apArg[2]); pTm->tm_sec = iVal; if( nArg > 3 ){ /* Month */ iVal = jx9_value_to_int(apArg[3]); pTm->tm_mon = iVal - 1; if( nArg > 4 ){ /* mday */ iVal = jx9_value_to_int(apArg[4]); pTm->tm_mday = iVal; if( nArg > 5 ){ /* Year */ iVal = jx9_value_to_int(apArg[5]); if( iVal > 1900 ){ iVal -= 1900; } pTm->tm_year = iVal; if( nArg > 6 ){ /* is_dst */ iVal = jx9_value_to_bool(apArg[6]); pTm->tm_isdst = iVal; } } } } } } } /* Make the time */ iVal = (jx9_int64)mktime(pTm); /* Return the timesatmp as a 64bit integer */ jx9_result_int64(pCtx, iVal); return JX9_OK; } /* * Section: * URL handling Functions. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* * Output consumer callback for the standard Symisc routines. * [i.e: SyBase64Encode(), SyBase64Decode(), SyUriEncode(), ...]. */ static int Consumer(const void *pData, unsigned int nLen, void *pUserData) { /* Store in the call context result buffer */ jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); return SXRET_OK; } /* * string base64_encode(string $data) * string convert_uuencode(string $data) * Encodes data with MIME base64 * Parameter * $data * Data to encode * Return * Encoded data or FALSE on failure. */ static int jx9Builtin_base64_encode(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the input string */ zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Nothing to process, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the BASE64 encoding */ SyBase64Encode(zIn, (sxu32)nLen, Consumer, pCtx); return JX9_OK; } /* * string base64_decode(string $data) * string convert_uudecode(string $data) * Decodes data encoded with MIME base64 * Parameter * $data * Encoded data. * Return * Returns the original data or FALSE on failure. */ static int jx9Builtin_base64_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the input string */ zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Nothing to process, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the BASE64 decoding */ SyBase64Decode(zIn, (sxu32)nLen, Consumer, pCtx); return JX9_OK; } /* * string urlencode(string $str) * URL encoding * Parameter * $data * Input string. * Return * Returns a string in which all non-alphanumeric characters except -_. have * been replaced with a percent (%) sign followed by two hex digits and spaces * encoded as plus (+) signs. */ static int jx9Builtin_urlencode(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the input string */ zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Nothing to process, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the URL encoding */ SyUriEncode(zIn, (sxu32)nLen, Consumer, pCtx); return JX9_OK; } /* * string urldecode(string $str) * Decodes any %## encoding in the given string. * Plus symbols ('+') are decoded to a space character. * Parameter * $data * Input string. * Return * Decoded URL or FALSE on failure. */ static int jx9Builtin_urldecode(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zIn; int nLen; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the input string */ zIn = jx9_value_to_string(apArg[0], &nLen); if( nLen < 1 ){ /* Nothing to process, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the URL decoding */ SyUriDecode(zIn, (sxu32)nLen, Consumer, pCtx, TRUE); return JX9_OK; } #endif /* JX9_DISABLE_BUILTIN_FUNC */ /* Table of the built-in functions */ static const jx9_builtin_func aBuiltInFunc[] = { /* Variable handling functions */ { "is_bool" , jx9Builtin_is_bool }, { "is_float" , jx9Builtin_is_float }, { "is_real" , jx9Builtin_is_float }, { "is_double" , jx9Builtin_is_float }, { "is_int" , jx9Builtin_is_int }, { "is_integer" , jx9Builtin_is_int }, { "is_long" , jx9Builtin_is_int }, { "is_string" , jx9Builtin_is_string }, { "is_null" , jx9Builtin_is_null }, { "is_numeric" , jx9Builtin_is_numeric }, { "is_scalar" , jx9Builtin_is_scalar }, { "is_array" , jx9Builtin_is_array }, { "is_object" , jx9Builtin_is_object }, { "is_resource", jx9Builtin_is_resource }, { "douleval" , jx9Builtin_floatval }, { "floatval" , jx9Builtin_floatval }, { "intval" , jx9Builtin_intval }, { "strval" , jx9Builtin_strval }, { "empty" , jx9Builtin_empty }, #ifndef JX9_DISABLE_BUILTIN_FUNC #ifdef JX9_ENABLE_MATH_FUNC /* Math functions */ { "abs" , jx9Builtin_abs }, { "sqrt" , jx9Builtin_sqrt }, { "exp" , jx9Builtin_exp }, { "floor", jx9Builtin_floor }, { "cos" , jx9Builtin_cos }, { "sin" , jx9Builtin_sin }, { "acos" , jx9Builtin_acos }, { "asin" , jx9Builtin_asin }, { "cosh" , jx9Builtin_cosh }, { "sinh" , jx9Builtin_sinh }, { "ceil" , jx9Builtin_ceil }, { "tan" , jx9Builtin_tan }, { "tanh" , jx9Builtin_tanh }, { "atan" , jx9Builtin_atan }, { "atan2", jx9Builtin_atan2 }, { "log" , jx9Builtin_log }, { "log10" , jx9Builtin_log10 }, { "pow" , jx9Builtin_pow }, { "pi", jx9Builtin_pi }, { "fmod", jx9Builtin_fmod }, { "hypot", jx9Builtin_hypot }, #endif /* JX9_ENABLE_MATH_FUNC */ { "round", jx9Builtin_round }, { "dechex", jx9Builtin_dechex }, { "decoct", jx9Builtin_decoct }, { "decbin", jx9Builtin_decbin }, { "hexdec", jx9Builtin_hexdec }, { "bindec", jx9Builtin_bindec }, { "octdec", jx9Builtin_octdec }, { "base_convert", jx9Builtin_base_convert }, /* String handling functions */ { "substr", jx9Builtin_substr }, { "substr_compare", jx9Builtin_substr_compare }, { "substr_count", jx9Builtin_substr_count }, { "chunk_split", jx9Builtin_chunk_split}, { "htmlspecialchars", jx9Builtin_htmlspecialchars }, { "htmlspecialchars_decode", jx9Builtin_htmlspecialchars_decode }, { "get_html_translation_table", jx9Builtin_get_html_translation_table }, { "htmlentities", jx9Builtin_htmlentities}, { "html_entity_decode", jx9Builtin_html_entity_decode}, { "strlen" , jx9Builtin_strlen }, { "strcmp" , jx9Builtin_strcmp }, { "strcoll" , jx9Builtin_strcmp }, { "strncmp" , jx9Builtin_strncmp }, { "strcasecmp" , jx9Builtin_strcasecmp }, { "strncasecmp", jx9Builtin_strncasecmp}, { "implode" , jx9Builtin_implode }, { "join" , jx9Builtin_implode }, { "implode_recursive" , jx9Builtin_implode_recursive }, { "join_recursive" , jx9Builtin_implode_recursive }, { "explode" , jx9Builtin_explode }, { "trim" , jx9Builtin_trim }, { "rtrim" , jx9Builtin_rtrim }, { "chop" , jx9Builtin_rtrim }, { "ltrim" , jx9Builtin_ltrim }, { "strtolower", jx9Builtin_strtolower }, { "mb_strtolower", jx9Builtin_strtolower }, /* Only UTF-8 encoding is supported */ { "strtoupper", jx9Builtin_strtoupper }, { "mb_strtoupper", jx9Builtin_strtoupper }, /* Only UTF-8 encoding is supported */ { "ord", jx9Builtin_ord }, { "chr", jx9Builtin_chr }, { "bin2hex", jx9Builtin_bin2hex }, { "strstr", jx9Builtin_strstr }, { "stristr", jx9Builtin_stristr }, { "strchr", jx9Builtin_strstr }, { "strpos", jx9Builtin_strpos }, { "stripos", jx9Builtin_stripos }, { "strrpos", jx9Builtin_strrpos }, { "strripos", jx9Builtin_strripos }, { "strrchr", jx9Builtin_strrchr }, { "strrev", jx9Builtin_strrev }, { "str_repeat", jx9Builtin_str_repeat }, { "nl2br", jx9Builtin_nl2br }, { "sprintf", jx9Builtin_sprintf }, { "printf", jx9Builtin_printf }, { "vprintf", jx9Builtin_vprintf }, { "vsprintf", jx9Builtin_vsprintf }, { "size_format", jx9Builtin_size_format}, #if !defined(JX9_DISABLE_HASH_FUNC) { "md5", jx9Builtin_md5 }, { "sha1", jx9Builtin_sha1 }, { "crc32", jx9Builtin_crc32 }, #endif /* JX9_DISABLE_HASH_FUNC */ { "str_getcsv", jx9Builtin_str_getcsv }, { "strip_tags", jx9Builtin_strip_tags }, { "str_split", jx9Builtin_str_split }, { "strspn", jx9Builtin_strspn }, { "strcspn", jx9Builtin_strcspn }, { "strpbrk", jx9Builtin_strpbrk }, { "soundex", jx9Builtin_soundex }, { "wordwrap", jx9Builtin_wordwrap }, { "strtok", jx9Builtin_strtok }, { "str_pad", jx9Builtin_str_pad }, { "str_replace", jx9Builtin_str_replace}, { "str_ireplace", jx9Builtin_str_replace}, { "strtr", jx9Builtin_strtr }, { "parse_ini_string", jx9Builtin_parse_ini_string}, /* Ctype functions */ { "ctype_alnum", jx9Builtin_ctype_alnum }, { "ctype_alpha", jx9Builtin_ctype_alpha }, { "ctype_cntrl", jx9Builtin_ctype_cntrl }, { "ctype_digit", jx9Builtin_ctype_digit }, { "ctype_xdigit", jx9Builtin_ctype_xdigit}, { "ctype_graph", jx9Builtin_ctype_graph }, { "ctype_print", jx9Builtin_ctype_print }, { "ctype_punct", jx9Builtin_ctype_punct }, { "ctype_space", jx9Builtin_ctype_space }, { "ctype_lower", jx9Builtin_ctype_lower }, { "ctype_upper", jx9Builtin_ctype_upper }, /* Time functions */ { "time" , jx9Builtin_time }, { "microtime", jx9Builtin_microtime }, { "getdate" , jx9Builtin_getdate }, { "gettimeofday", jx9Builtin_gettimeofday }, { "date", jx9Builtin_date }, { "strftime", jx9Builtin_strftime }, { "idate", jx9Builtin_idate }, { "gmdate", jx9Builtin_gmdate }, { "localtime", jx9Builtin_localtime }, { "mktime", jx9Builtin_mktime }, { "gmmktime", jx9Builtin_mktime }, /* URL functions */ { "base64_encode", jx9Builtin_base64_encode }, { "base64_decode", jx9Builtin_base64_decode }, { "convert_uuencode", jx9Builtin_base64_encode }, { "convert_uudecode", jx9Builtin_base64_decode }, { "urlencode", jx9Builtin_urlencode }, { "urldecode", jx9Builtin_urldecode }, { "rawurlencode", jx9Builtin_urlencode }, { "rawurldecode", jx9Builtin_urldecode }, #endif /* JX9_DISABLE_BUILTIN_FUNC */ }; /* * Register the built-in functions defined above, the array functions * defined in hashmap.c and the IO functions defined in vfs.c. */ JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm) { sxu32 n; for( n = 0 ; n < SX_ARRAYSIZE(aBuiltInFunc) ; ++n ){ jx9_create_function(&(*pVm), aBuiltInFunc[n].zName, aBuiltInFunc[n].xFunc, 0); } /* Register hashmap functions [i.e: sort(), count(), array_diff(), ...] */ jx9RegisterHashmapFunctions(&(*pVm)); /* Register IO functions [i.e: fread(), fwrite(), chdir(), mkdir(), file(), ...] */ jx9RegisterIORoutine(&(*pVm)); } /* * ---------------------------------------------------------- * File: jx9_compile.c * MD5: 562e73eb7214f890e71713c6b97a7863 * ---------------------------------------------------------- */ /* * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ * Version 1.7.2 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES * please contact Symisc Systems via: * legal@symisc.net * licensing@symisc.net * contact@symisc.net * or visit: * http://jx9.symisc.net/ */ /* $SymiscID: compile.c v1.7 FreeBSD 2012-12-11 21:46 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* * This file implement a thread-safe and full-reentrant compiler for the JX9 engine. * That is, routines defined in this file takes a stream of tokens and output * JX9 bytecode instructions. */ /* Forward declaration */ typedef struct LangConstruct LangConstruct; typedef struct JumpFixup JumpFixup; /* Block [i.e: set of statements] control flags */ #define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for, while, ...] */ #define GEN_BLOCK_PROTECTED 0x002 /* Protected block */ #define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/ #define GEN_BLOCK_FUNC 0x008 /* Function body */ #define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/ #define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */ #define GEN_BLOCK_EXPR 0x040 /* Expression */ #define GEN_BLOCK_STD 0x080 /* Standard block */ #define GEN_BLOCK_SWITCH 0x100 /* Switch statement */ /* * Compilation of some JX9 constructs such as if, for, while, the logical or * (||) and logical and (&&) operators in expressions requires the * generation of forward jumps. * Since the destination PC target of these jumps isn't known when the jumps * are emitted, we record each forward jump in an instance of the following * structure. Those jumps are fixed later when the jump destination is resolved. */ struct JumpFixup { sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ }; /* * Each language construct is represented by an instance * of the following structure. */ struct LangConstruct { sxu32 nID; /* Language construct ID [i.e: JX9_TKWRD_WHILE, JX9_TKWRD_FOR, JX9_TKWRD_IF...] */ ProcLangConstruct xConstruct; /* C function implementing the language construct */ }; /* Compilation flags */ #define JX9_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */ /* Token stream synchronization macros */ #define SWAP_TOKEN_STREAM(GEN, START, END)\ pTmp = GEN->pEnd;\ pGen->pIn = START;\ pGen->pEnd = END #define UPDATE_TOKEN_STREAM(GEN)\ if( GEN->pIn < pTmp ){\ GEN->pIn++;\ }\ GEN->pEnd = pTmp #define SWAP_DELIMITER(GEN, START, END)\ pTmpIn = GEN->pIn;\ pTmpEnd = GEN->pEnd;\ GEN->pIn = START;\ GEN->pEnd = END #define RE_SWAP_DELIMITER(GEN)\ GEN->pIn = pTmpIn;\ GEN->pEnd = pTmpEnd /* Flags related to expression compilation */ #define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */ #define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'JX9_OP_LOAD' VM instruction for more information */ #define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by object attributes) */ /* Forward declaration */ static sxi32 jx9CompileExpr( jx9_gen_state *pGen, /* Code generator state */ sxi32 iFlags, /* Control flags */ sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ ); /* * Recover from a compile-time error. In other words synchronize * the token stream cursor with the first semi-colon seen. */ static sxi32 jx9ErrorRecover(jx9_gen_state *pGen) { /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI /*';'*/) == 0){ pGen->pIn++; } return SXRET_OK; } /* * Check if the given identifier name is reserved or not. * Return TRUE if reserved.FALSE otherwise. */ static int GenStateIsReservedID(SyString *pName) { if( pName->nByte == sizeof("null") - 1 ){ if( SyStrnicmp(pName->zString, "null", sizeof("null")-1) == 0 ){ return TRUE; }else if( SyStrnicmp(pName->zString, "true", sizeof("true")-1) == 0 ){ return TRUE; } }else if( pName->nByte == sizeof("false") - 1 ){ if( SyStrnicmp(pName->zString, "false", sizeof("false")-1) == 0 ){ return TRUE; } } /* Not a reserved constant */ return FALSE; } /* * Check if a given token value is installed in the literal table. */ static sxi32 GenStateFindLiteral(jx9_gen_state *pGen, const SyString *pValue, sxu32 *pIdx) { SyHashEntry *pEntry; pEntry = SyHashGet(&pGen->hLiteral, (const void *)pValue->zString, pValue->nByte); if( pEntry == 0 ){ return SXERR_NOTFOUND; } *pIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); return SXRET_OK; } /* * Install a given constant index in the literal table. * In order to be installed, the jx9_value must be of type string. */ static sxi32 GenStateInstallLiteral(jx9_gen_state *pGen,jx9_value *pObj, sxu32 nIdx) { if( SyBlobLength(&pObj->sBlob) > 0 ){ SyHashInsert(&pGen->hLiteral, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), SX_INT_TO_PTR(nIdx)); } return SXRET_OK; } /* * Generate a fatal error. */ static sxi32 GenStateOutOfMem(jx9_gen_state *pGen) { jx9GenCompileError(pGen,E_ERROR,1,"Fatal, Jx9 compiler is running out of memory"); /* Abort compilation immediately */ return SXERR_ABORT; } /* * Fetch a block that correspond to the given criteria from the stack of * compiled blocks. * Return a pointer to that block on success. NULL otherwise. */ static GenBlock * GenStateFetchBlock(GenBlock *pCurrent, sxi32 iBlockType, sxi32 iCount) { GenBlock *pBlock = pCurrent; for(;;){ if( pBlock->iFlags & iBlockType ){ iCount--; /* Decrement nesting level */ if( iCount < 1 ){ /* Block meet with the desired criteria */ return pBlock; } } /* Point to the upper block */ pBlock = pBlock->pParent; if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){ /* Forbidden */ break; } } /* No such block */ return 0; } /* * Initialize a freshly allocated block instance. */ static void GenStateInitBlock( jx9_gen_state *pGen, /* Code generator state */ GenBlock *pBlock, /* Target block */ sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ sxu32 nFirstInstr, /* First instruction to compile */ void *pUserData /* Upper layer private data */ ) { /* Initialize block fields */ pBlock->nFirstInstr = nFirstInstr; pBlock->pUserData = pUserData; pBlock->pGen = pGen; pBlock->iFlags = iType; pBlock->pParent = 0; pBlock->bPostContinue = 0; SySetInit(&pBlock->aJumpFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); SySetInit(&pBlock->aPostContFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); } /* * Allocate a new block instance. * Return SXRET_OK and write a pointer to the new instantiated block * on success.Otherwise generate a compile-time error and abort * processing on failure. */ static sxi32 GenStateEnterBlock( jx9_gen_state *pGen, /* Code generator state */ sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ sxu32 nFirstInstr, /* First instruction to compile */ void *pUserData, /* Upper layer private data */ GenBlock **ppBlock /* OUT: instantiated block */ ) { GenBlock *pBlock; /* Allocate a new block instance */ pBlock = (GenBlock *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(GenBlock)); if( pBlock == 0 ){ /* If the supplied memory subsystem is so sick that we are unable to allocate * a tiny chunk of memory, there is no much we can do here. */ return GenStateOutOfMem(pGen); } /* Zero the structure */ SyZero(pBlock, sizeof(GenBlock)); GenStateInitBlock(&(*pGen), pBlock, iType, nFirstInstr, pUserData); /* Link to the parent block */ pBlock->pParent = pGen->pCurrent; /* Mark as the current block */ pGen->pCurrent = pBlock; if( ppBlock ){ /* Write a pointer to the new instance */ *ppBlock = pBlock; } return SXRET_OK; } /* * Release block fields without freeing the whole instance. */ static void GenStateReleaseBlock(GenBlock *pBlock) { SySetRelease(&pBlock->aPostContFix); SySetRelease(&pBlock->aJumpFix); } /* * Release a block. */ static void GenStateFreeBlock(GenBlock *pBlock) { jx9_gen_state *pGen = pBlock->pGen; GenStateReleaseBlock(&(*pBlock)); /* Free the instance */ SyMemBackendPoolFree(&pGen->pVm->sAllocator, pBlock); } /* * POP and release a block from the stack of compiled blocks. */ static sxi32 GenStateLeaveBlock(jx9_gen_state *pGen, GenBlock **ppBlock) { GenBlock *pBlock = pGen->pCurrent; if( pBlock == 0 ){ /* No more block to pop */ return SXERR_EMPTY; } /* Point to the upper block */ pGen->pCurrent = pBlock->pParent; if( ppBlock ){ /* Write a pointer to the popped block */ *ppBlock = pBlock; }else{ /* Safely release the block */ GenStateFreeBlock(&(*pBlock)); } return SXRET_OK; } /* * Emit a forward jump. * Notes on forward jumps * Compilation of some JX9 constructs such as if, for, while and the logical or * (||) and logical and (&&) operators in expressions requires the * generation of forward jumps. * Since the destination PC target of these jumps isn't known when the jumps * are emitted, we record each forward jump in an instance of the following * structure. Those jumps are fixed later when the jump destination is resolved. */ static sxi32 GenStateNewJumpFixup(GenBlock *pBlock, sxi32 nJumpType, sxu32 nInstrIdx) { JumpFixup sJumpFix; sxi32 rc; /* Init the JumpFixup structure */ sJumpFix.nJumpType = nJumpType; sJumpFix.nInstrIdx = nInstrIdx; /* Insert in the jump fixup table */ rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix); return rc; } /* * Fix a forward jump now the jump destination is resolved. * Return the total number of fixed jumps. * Notes on forward jumps: * Compilation of some JX9 constructs such as if, for, while and the logical or * (||) and logical and (&&) operators in expressions requires the * generation of forward jumps. * Since the destination PC target of these jumps isn't known when the jumps * are emitted, we record each forward jump in an instance of the following * structure.Those jumps are fixed later when the jump destination is resolved. */ static sxu32 GenStateFixJumps(GenBlock *pBlock, sxi32 nJumpType, sxu32 nJumpDest) { JumpFixup *aFix; VmInstr *pInstr; sxu32 nFixed; sxu32 n; /* Point to the jump fixup table */ aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix); /* Fix the desired jumps */ for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){ if( aFix[n].nJumpType < 0 ){ /* Already fixed */ continue; } if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){ /* Not of our interest */ continue; } /* Point to the instruction to fix */ pInstr = jx9VmGetInstr(pBlock->pGen->pVm, aFix[n].nInstrIdx); if( pInstr ){ pInstr->iP2 = nJumpDest; nFixed++; /* Mark as fixed */ aFix[n].nJumpType = -1; } } /* Total number of fixed jumps */ return nFixed; } /* * Reserve a room for a numeric constant [i.e: 64-bit integer or real number] * in the constant table. */ static jx9_value * GenStateInstallNumLiteral(jx9_gen_state *pGen, sxu32 *pIdx) { jx9_value *pObj; sxu32 nIdx = 0; /* cc warning */ /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ GenStateOutOfMem(pGen); return 0; } *pIdx = nIdx; /* TODO(chems): Create a numeric table (64bit int keys) same as * the constant string iterals table [optimization purposes]. */ return pObj; } /* * Compile a numeric [i.e: integer or real] literal. * Notes on the integer type. * According to the JX9 language reference manual * Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) * or binary (base 2) notation, optionally preceded by a sign (- or +). * To use octal notation, precede the number with a 0 (zero). To use hexadecimal * notation precede the number with 0x. To use binary notation precede the number with 0b. */ static sxi32 jx9CompileNumLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyToken *pToken = pGen->pIn; /* Raw token */ sxu32 nIdx = 0; if( pToken->nType & JX9_TK_INTEGER ){ jx9_value *pObj; sxi64 iValue; iValue = jx9TokenValueToInt64(&pToken->sData); pObj = GenStateInstallNumLiteral(&(*pGen), &nIdx); if( pObj == 0 ){ SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } jx9MemObjInitFromInt(pGen->pVm, pObj, iValue); }else{ /* Real number */ jx9_value *pObj; /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); jx9MemObjToReal(pObj); } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile a nowdoc string. * According to the JX9 language reference manual: * * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. * The construct is ideal for embedding JX9 code or other large blocks of text without the * need for escaping. It shares some features in common with the SGML * construct, in that it declares a block of text which is not for parsing. * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier * which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc * identifiers also apply to nowdoc identifiers, especially those regarding the appearance * of the closing identifier. */ static sxi32 jx9CompileNowdoc(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ jx9_value *pObj; sxu32 nIdx; nIdx = 0; /* Prevent compiler warning */ if( pStr->nByte <= 0 ){ /* Empty string, load NULL */ jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; } /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "JX9 engine is running out of memory"); SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } /* No processing is done here, simply a memcpy() operation */ jx9MemObjInitFromString(pGen->pVm, pObj, pStr); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile a single quoted string. * According to the JX9 language reference manual: * * The simplest way to specify a string is to enclose it in single quotes (the character ' ). * To specify a literal single quote, escape it with a backslash (\). To specify a literal * backslash, double it (\\). All other instances of backslash will be treated as a literal * backslash: this means that the other escape sequences you might be used to, such as \r * or \n, will be output literally as specified rather than having any special meaning. * */ JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag) { SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ const char *zIn, *zCur, *zEnd; jx9_value *pObj; sxu32 nIdx; nIdx = 0; /* Prevent compiler warning */ /* Delimit the string */ zIn = pStr->zString; zEnd = &zIn[pStr->nByte]; if( zIn > zEnd ){ /* Empty string, load NULL */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; } if( SXRET_OK == GenStateFindLiteral(&(*pGen), pStr, &nIdx) ){ /* Already processed, emit the load constant instruction * and return. */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); return SXRET_OK; } /* Reserve a new constant */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, 1, "JX9 engine is running out of memory"); SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } jx9MemObjInitFromString(pGen->pVm, pObj, 0); /* Compile the node */ for(;;){ if( zIn >= zEnd ){ /* End of input */ break; } zCur = zIn; while( zIn < zEnd && zIn[0] != '\\' ){ zIn++; } if( zIn > zCur ){ /* Append raw contents*/ jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); } else { jx9MemObjStringAppend(pObj, "", 0); } zIn++; if( zIn < zEnd ){ if( zIn[0] == '\\' ){ /* A literal backslash */ jx9MemObjStringAppend(pObj, "\\", sizeof(char)); }else if( zIn[0] == '\'' ){ /* A single quote */ jx9MemObjStringAppend(pObj, "'", sizeof(char)); }else{ /* verbatim copy */ zIn--; jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2); zIn++; } } /* Advance the stream cursor */ zIn++; } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); if( pStr->nByte < 1024 ){ /* Install in the literal table */ GenStateInstallLiteral(pGen, pObj, nIdx); } /* Node successfully compiled */ return SXRET_OK; } /* * Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string. * According to the JX9 language reference manual * When a string is specified in double quotes or with heredoc, variables are parsed within it. * There are two types of syntax: a simple one and a complex one. The simple syntax is the most * common and convenient. It provides a way to embed a variable, an array value, or an object * property in a string with a minimum of effort. * Simple syntax * If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible * to form a valid variable name. Enclose the variable name in curly braces to explicitly specify * the end of the name. * Similarly, an array index or an object property can be parsed. With array indices, the closing * square bracket (]) marks the end of the index. The same rules apply to object properties * as to simple variables. * Complex (curly) syntax * This isn't called complex because the syntax is complex, but because it allows for the use * of complex expressions. * Any scalar variable, array element or object property with a string representation can be * included via this syntax. Simply write the expression the same way as it would appear outside * the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only * be recognised when the $ immediately follows the {. Use {\$ to get a literal {$ */ static sxi32 GenStateProcessStringExpression( jx9_gen_state *pGen, /* Code generator state */ const char *zIn, /* Raw expression */ const char *zEnd /* End of the expression */ ) { SyToken *pTmpIn, *pTmpEnd; SySet sToken; sxi32 rc; /* Initialize the token set */ SySetInit(&sToken, &pGen->pVm->sAllocator, sizeof(SyToken)); /* Preallocate some slots */ SySetAlloc(&sToken, 0x08); /* Tokenize the text */ jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken); /* Swap delimiter */ pTmpIn = pGen->pIn; pTmpEnd = pGen->pEnd; pGen->pIn = (SyToken *)SySetBasePtr(&sToken); pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)]; /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Restore token stream */ pGen->pIn = pTmpIn; pGen->pEnd = pTmpEnd; /* Release the token set */ SySetRelease(&sToken); /* Compilation result */ return rc; } /* * Reserve a new constant for a double quoted/heredoc string. */ static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount) { jx9_value *pConstObj; sxu32 nIdx = 0; /* Reserve a new constant */ pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pConstObj == 0 ){ GenStateOutOfMem(&(*pGen)); return 0; } (*pCount)++; jx9MemObjInitFromString(pGen->pVm, pConstObj, 0); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); return pConstObj; } /* * Compile a double quoted/heredoc string. * According to the JX9 language reference manual * Heredoc * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier * is provided, then a newline. The string itself follows, and then the same identifier again * to close the quotation. * The closing identifier must begin in the first column of the line. Also, the identifier must * follow the same naming rules as any other label in JX9: it must contain only alphanumeric * characters and underscores, and must start with a non-digit character or underscore. * Warning * It is very important to note that the line with the closing identifier must contain * no other characters, except possibly a semicolon (;). That means especially that the identifier * may not be indented, and there may not be any spaces or tabs before or after the semicolon. * It's also important to realize that the first character before the closing identifier must * be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X. * The closing delimiter (possibly followed by a semicolon) must also be followed by a newline. * If this rule is broken and the closing identifier is not "clean", it will not be considered a closing * identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before * the end of the current file, a parse error will result at the last line. * Heredocs can not be used for initializing object properties. * Double quoted * If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters: * Escaped characters Sequence Meaning * \n linefeed (LF or 0x0A (10) in ASCII) * \r carriage return (CR or 0x0D (13) in ASCII) * \t horizontal tab (HT or 0x09 (9) in ASCII) * \v vertical tab (VT or 0x0B (11) in ASCII) * \f form feed (FF or 0x0C (12) in ASCII) * \\ backslash * \$ dollar sign * \" double-quote * \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation * \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation * As in single quoted strings, escaping any other character will result in the backslash being printed too. * The most important feature of double-quoted strings is the fact that variable names will be expanded. * See string parsing for details. */ static sxi32 GenStateCompileString(jx9_gen_state *pGen) { SyString *pStr = &pGen->pIn->sData; /* Raw token value */ const char *zIn, *zCur, *zEnd; jx9_value *pObj = 0; sxi32 iCons; sxi32 rc; /* Delimit the string */ zIn = pStr->zString; zEnd = &zIn[pStr->nByte]; if( zIn > zEnd ){ /* Empty string, load NULL */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; } zCur = 0; /* Compile the node */ iCons = 0; for(;;){ zCur = zIn; while( zIn < zEnd && zIn[0] != '\\' ){ if(zIn[0] == '$' && &zIn[1] < zEnd && (((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){ break; } zIn++; } if( zIn > zCur ){ if( pObj == 0 ){ pObj = GenStateNewStrObj(&(*pGen), &iCons); if( pObj == 0 ){ return SXERR_ABORT; } } jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); } else { if( pObj == 0 ){ pObj = GenStateNewStrObj(&(*pGen), &iCons); if( pObj == 0 ){ return SXERR_ABORT; } } jx9MemObjStringAppend(pObj, "", 0); } if( zIn >= zEnd ){ break; } if( zIn[0] == '\\' ){ const char *zPtr = 0; sxu32 n; zIn++; if( zIn >= zEnd ){ break; } if( pObj == 0 ){ pObj = GenStateNewStrObj(&(*pGen), &iCons); if( pObj == 0 ){ return SXERR_ABORT; } } n = sizeof(char); /* size of conversion */ switch( zIn[0] ){ case '$': /* Dollar sign */ jx9MemObjStringAppend(pObj, "$", sizeof(char)); break; case '\\': /* A literal backslash */ jx9MemObjStringAppend(pObj, "\\", sizeof(char)); break; case 'a': /* The "alert" character (BEL)[ctrl+g] ASCII code 7 */ jx9MemObjStringAppend(pObj, "\a", sizeof(char)); break; case 'b': /* Backspace (BS)[ctrl+h] ASCII code 8 */ jx9MemObjStringAppend(pObj, "\b", sizeof(char)); break; case 'f': /* Form-feed (FF)[ctrl+l] ASCII code 12 */ jx9MemObjStringAppend(pObj, "\f", sizeof(char)); break; case 'n': /* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */ jx9MemObjStringAppend(pObj, "\n", sizeof(char)); break; case 'r': /* Carriage return (CR)[ctrl+m] ASCII code 13 */ jx9MemObjStringAppend(pObj, "\r", sizeof(char)); break; case 't': /* Horizontal tab (HT)[ctrl+i] ASCII code 9 */ jx9MemObjStringAppend(pObj, "\t", sizeof(char)); break; case 'v': /* Vertical tab(VT)[ctrl+k] ASCII code 11 */ jx9MemObjStringAppend(pObj, "\v", sizeof(char)); break; case '\'': /* Single quote */ jx9MemObjStringAppend(pObj, "'", sizeof(char)); break; case '"': /* Double quote */ jx9MemObjStringAppend(pObj, "\"", sizeof(char)); break; case '0': /* NUL byte */ jx9MemObjStringAppend(pObj, "\0", sizeof(char)); break; case 'x': if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){ int c; /* Hex digit */ c = SyHexToint(zIn[1]) << 4; if( &zIn[2] < zEnd ){ c += SyHexToint(zIn[2]); } /* Output char */ jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); n += sizeof(char) * 2; }else{ /* Output literal character */ jx9MemObjStringAppend(pObj, "x", sizeof(char)); } break; case 'o': if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){ /* Octal digit stream */ int c; c = 0; zIn++; for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){ if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){ break; } c = c * 8 + (zPtr[0] - '0'); } if ( c > 0 ){ jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); } n = (sxu32)(zPtr-zIn); }else{ /* Output literal character */ jx9MemObjStringAppend(pObj, "o", sizeof(char)); } break; default: /* Output without a slash */ jx9MemObjStringAppend(pObj, zIn, sizeof(char)); break; } /* Advance the stream cursor */ zIn += n; continue; } if( zIn[0] == '{' ){ /* Curly syntax */ const char *zExpr; sxi32 iNest = 1; zIn++; zExpr = zIn; /* Synchronize with the next closing curly braces */ while( zIn < zEnd ){ if( zIn[0] == '{' ){ /* Increment nesting level */ iNest++; }else if(zIn[0] == '}' ){ /* Decrement nesting level */ iNest--; if( iNest <= 0 ){ break; } } zIn++; } /* Process the expression */ rc = GenStateProcessStringExpression(&(*pGen),zExpr,zIn); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( rc != SXERR_EMPTY ){ ++iCons; } if( zIn < zEnd ){ /* Jump the trailing curly */ zIn++; } }else{ /* Simple syntax */ const char *zExpr = zIn; /* Assemble variable name */ for(;;){ /* Jump leading dollars */ while( zIn < zEnd && zIn[0] == '$' ){ zIn++; } for(;;){ while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){ zIn++; } if((unsigned char)zIn[0] >= 0xc0 ){ /* UTF-8 stream */ zIn++; while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ zIn++; } continue; } break; } if( zIn >= zEnd ){ break; } if( zIn[0] == '[' ){ sxi32 iSquare = 1; zIn++; while( zIn < zEnd ){ if( zIn[0] == '[' ){ iSquare++; }else if (zIn[0] == ']' ){ iSquare--; if( iSquare <= 0 ){ break; } } zIn++; } if( zIn < zEnd ){ zIn++; } break; }else if( zIn[0] == '.' ){ /* Member access operator '.' */ zIn++; }else{ break; } } /* Process the expression */ rc = GenStateProcessStringExpression(&(*pGen),zExpr, zIn); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( rc != SXERR_EMPTY ){ ++iCons; } } /* Invalidate the previously used constant */ pObj = 0; }/*for(;;)*/ if( iCons > 1 ){ /* Concatenate all compiled constants */ jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0); } /* Node successfully compiled */ return SXRET_OK; } /* * Compile a double quoted string. * See the block-comment above for more information. */ JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag) { sxi32 rc; rc = GenStateCompileString(&(*pGen)); SXUNUSED(iCompileFlag); /* cc warning */ /* Compilation result */ return rc; } /* * Compile a literal which is an identifier(name) for simple values. */ JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyToken *pToken = pGen->pIn; jx9_value *pObj; SyString *pStr; sxu32 nIdx; /* Extract token value */ pStr = &pToken->sData; /* Deal with the reserved literals [i.e: null, false, true, ...] first */ if( pStr->nByte == sizeof("NULL") - 1 ){ if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){ /* NULL constant are always indexed at 0 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); return SXRET_OK; }else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){ /* TRUE constant are always indexed at 1 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1, 0, 0); return SXRET_OK; } }else if (pStr->nByte == sizeof("FALSE") - 1 && SyStrnicmp(pStr->zString, "false", sizeof("FALSE")-1) == 0 ){ /* FALSE constant are always indexed at 2 */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 2, 0, 0); return SXRET_OK; }else if(pStr->nByte == sizeof("__LINE__") - 1 && SyMemcmp(pStr->zString, "__LINE__", sizeof("__LINE__")-1) == 0 ){ /* TICKET 1433-004: __LINE__ constant must be resolved at compile time, not run time */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ SXUNUSED(iCompileFlag); /* cc warning */ return GenStateOutOfMem(pGen); } jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); return SXRET_OK; }else if( pStr->nByte == sizeof("__FUNCTION__") - 1 && SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-1) == 0 ){ GenBlock *pBlock = pGen->pCurrent; /* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time, not run time */ while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){ /* Point to the upper block */ pBlock = pBlock->pParent; } if( pBlock == 0 ){ /* Called in the global scope, load NULL */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); }else{ /* Extract the target function/method */ jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData; pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName); /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); } return SXRET_OK; } /* Query literal table */ if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){ jx9_value *pObj; /* Unknown literal, install it in the literal table */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); GenStateInstallLiteral(&(*pGen), pObj, nIdx); } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile an array entry whether it is a key or a value. */ static sxi32 GenStateCompileJSONEntry( jx9_gen_state *pGen, /* Code generator state */ SyToken *pIn, /* Token stream */ SyToken *pEnd, /* End of the token stream */ sxi32 iFlags, /* Compilation flags */ sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */ ) { SyToken *pTmpIn, *pTmpEnd; sxi32 rc; /* Swap token stream */ SWAP_DELIMITER(pGen, pIn, pEnd); /* Compile the expression*/ rc = jx9CompileExpr(&(*pGen), iFlags, xValidator); /* Restore token stream */ RE_SWAP_DELIMITER(pGen); return rc; } /* * Compile a Jx9 JSON Array. */ JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag) { sxi32 nPair = 0; SyToken *pCur; sxi32 rc; pGen->pIn++; /* Jump the open square bracket '['*/ pGen->pEnd--; SXUNUSED(iCompileFlag); /* cc warning */ for(;;){ /* Jump leading commas */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ pGen->pIn++; } pCur = pGen->pIn; if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ /* No more entry to process */ break; } /* Compile entry */ rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } nPair++; } /* Emit the load map instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0); /* Node successfully compiled */ return SXRET_OK; } /* * Node validator for a given JSON key. */ static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) { sxi32 rc = SXRET_OK; if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString && pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){ /* Unexpected expression */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0, "JSON Object: Unexpected expression, key must be of type string, literal or simple variable"); if( rc != SXERR_ABORT ){ rc = SXERR_INVALID; } } return rc; } /* * Compile a Jx9 JSON Object */ JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag) { SyToken *pKey, *pCur; sxi32 nPair = 0; sxi32 rc; pGen->pIn++; /* Jump the open querly braces '{'*/ pGen->pEnd--; SXUNUSED(iCompileFlag); /* cc warning */ for(;;){ /* Jump leading commas */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ pGen->pIn++; } pCur = pGen->pIn; if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ /* No more entry to process */ break; } /* Compile the key */ pKey = pCur; while( pCur < pGen->pIn ){ if( pCur->nType & JX9_TK_COLON /*':'*/ ){ break; } pCur++; } rc = SXERR_EMPTY; if( (pCur->nType & JX9_TK_COLON) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ABORT, pCur->nLine, "JSON Object: Missing colon string \":\""); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } return SXRET_OK; } if( pCur < pGen->pIn ){ if( &pCur[1] >= pGen->pIn ){ /* Missing value */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } return SXRET_OK; } /* Compile the expression holding the key */ rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur, EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */, GenStateJSONObjectKeyNodeValidator /* Node validator callback */ ); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pCur++; /* Jump the double colon ':' */ }else if( pKey == pCur ){ /* Key is omitted, emit an error */ jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key"); pCur++; /* Jump the double colon ':' */ }else{ /* Reset back the cursor and point to the entry value */ pCur = pKey; } /* Compile indice value */ rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } nPair++; } /* Emit the load map instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile a function [i.e: print, exit(), include(), ...] which is a langauge * construct. */ JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag) { SyString *pName; sxu32 nKeyID; sxi32 rc; /* Name of the language construct [i.e: print, die...]*/ pName = &pGen->pIn->sData; nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); pGen->pIn++; /* Jump the language construct keyword */ if( nKeyID == JX9_TKWRD_PRINT ){ SyToken *pTmp, *pNext = 0; /* Compile arguments one after one */ pTmp = pGen->pEnd; jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0); while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ if( pGen->pIn < pNext ){ pGen->pEnd = pNext; rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( rc != SXERR_EMPTY ){ /* Ticket 1433-008: Optimization #1: Consume input directly * without the overhead of a function call. * This is a very powerful optimization that improve * performance greatly. */ jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0); } } /* Jump trailing commas */ while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){ pNext++; } pGen->pIn = pNext; } /* Restore token stream */ pGen->pEnd = pTmp; }else{ sxi32 nArg = 0; sxu32 nIdx = 0; rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nArg = 1; } if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){ jx9_value *pObj; /* Emit the call instruction */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ SXUNUSED(iCompileFlag); /* cc warning */ return GenStateOutOfMem(pGen); } jx9MemObjInitFromString(pGen->pVm, pObj, pName); /* Install in the literal table */ GenStateInstallLiteral(&(*pGen), pObj, nIdx); } /* Emit the call instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0); } /* Node successfully compiled */ return SXRET_OK; } /* * Compile a node holding a variable declaration. * According to the J9X language reference * Variables in JX9 are represented by a dollar sign followed by the name of the variable. * The variable name is case-sensitive. * Variable names follow the same rules as other labels in JX9. A valid variable name * starts with a letter, underscore or any UTF-8 stream, followed by any number of letters * numbers, or underscores. * By default, variables are always assigned by value unless the target value is a JSON * array or a JSON object which is passed by reference. */ JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag) { sxu32 nLine = pGen->pIn->nLine; SyHashEntry *pEntry; SyString *pName; char *zName = 0; sxi32 iP1; void *p3; sxi32 rc; pGen->pIn++; /* Jump the dollar sign '$' */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid variable name */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* Extract variable name */ pName = &pGen->pIn->sData; /* Advance the stream cursor */ pGen->pIn++; pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte); if( pEntry == 0 ){ /* Duplicate name */ zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); if( zName == 0 ){ return GenStateOutOfMem(pGen); } /* Install in the hashtable */ SyHashInsert(&pGen->hVar, zName, pName->nByte, zName); }else{ /* Name already available */ zName = (char *)pEntry->pUserData; } p3 = (void *)zName; iP1 = 0; if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){ if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){ /* Read-only load.In other words do not create the variable if inexistant */ iP1 = 1; } } /* Emit the load instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0); /* Node successfully compiled */ return SXRET_OK; } /* Forward declaration */ static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc); /* * Compile an annoynmous function or a closure. * According to the JX9 language reference * Anonymous functions, also known as closures, allow the creation of functions * which have no specified name. They are most useful as the value of callback * parameters, but they have many other uses. Closures can also be used as * the values of variables; Assigning a closure to a variable uses the same * syntax as any other assignment, including the trailing semicolon: * Example Anonymous function variable assignment example * $greet = function($name) * { * printf("Hello %s\r\n", $name); * }; * $greet('World'); * $greet('JX9'); * Note that the implementation of annoynmous function and closure under * JX9 is completely different from the one used by the engine. */ JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag) { jx9_vm_func *pAnnonFunc; /* Annonymous function body */ char zName[512]; /* Unique lambda name */ static int iCnt = 1; /* There is no worry about thread-safety here, because only * one thread is allowed to compile the script. */ jx9_value *pObj; SyString sName; sxu32 nIdx; sxu32 nLen; sxi32 rc; pGen->pIn++; /* Jump the 'function' keyword */ if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ pGen->pIn++; } /* Reserve a constant for the lambda */ pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); if( pObj == 0 ){ GenStateOutOfMem(pGen); SXUNUSED(iCompileFlag); /* cc warning */ return SXERR_ABORT; } /* Generate a unique name */ nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); /* Make sure the generated name is unique */ while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){ nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); } SyStringInitFromBuf(&sName, zName, nLen); jx9MemObjInitFromString(pGen->pVm, pObj, &sName); /* Compile the lambda body */ rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Emit the load constant instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); /* Node successfully compiled */ return SXRET_OK; } /* * Compile the 'continue' statement. * According to the JX9 language reference * continue is used within looping structures to skip the rest of the current loop iteration * and continue execution at the condition evaluation and then the beginning of the next * iteration. * Note: Note that in JX9 the switch statement is considered a looping structure for * the purposes of continue. * continue accepts an optional numeric argument which tells it how many levels * of enclosing loops it should skip to the end of. * Note: * continue 0; and continue 1; is the same as running continue;. */ static sxi32 jx9CompileContinue(jx9_gen_state *pGen) { GenBlock *pLoop; /* Target loop */ sxi32 iLevel; /* How many nesting loop to skip */ sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; iLevel = 0; /* Jump the 'continue' keyword */ pGen->pIn++; if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ /* optional numeric argument which tells us how many levels * of enclosing loops we should skip to the end of. */ iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); if( iLevel < 2 ){ iLevel = 0; } pGen->pIn++; /* Jump the optional numeric argument */ } /* Point to the target loop */ pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); if( pLoop == 0 ){ /* Illegal continue */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } }else{ sxu32 nInstrIdx = 0; /* Emit the unconditional jump to the beginning of the target loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx); if( pLoop->bPostContinue == TRUE ){ JumpFixup sJumpFix; /* Post-continue */ sJumpFix.nJumpType = JX9_OP_JMP; sJumpFix.nInstrIdx = nInstrIdx; SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix); } } if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Not so fatal, emit a warning only */ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement"); } /* Statement successfully compiled */ return SXRET_OK; } /* * Compile the 'break' statement. * According to the JX9 language reference * break ends execution of the current for, foreach, while, do-while or switch * structure. * break accepts an optional numeric argument which tells it how many nested * enclosing structures are to be broken out of. */ static sxi32 jx9CompileBreak(jx9_gen_state *pGen) { GenBlock *pLoop; /* Target loop */ sxi32 iLevel; /* How many nesting loop to skip */ sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; iLevel = 0; /* Jump the 'break' keyword */ pGen->pIn++; if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ /* optional numeric argument which tells us how many levels * of enclosing loops we should skip to the end of. */ iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); if( iLevel < 2 ){ iLevel = 0; } pGen->pIn++; /* Jump the optional numeric argument */ } /* Extract the target loop */ pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); if( pLoop == 0 ){ /* Illegal break */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } }else{ sxu32 nInstrIdx; rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx); if( rc == SXRET_OK ){ /* Fix the jump later when the jump destination is resolved */ GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx); } } if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Not so fatal, emit a warning only */ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement"); } /* Statement successfully compiled */ return SXRET_OK; } /* Forward declaration */ static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags); /* * Compile a JX9 block. * A block is simply one or more JX9 statements and expressions to compile * optionally delimited by braces {}. * Return SXRET_OK on success. Any other return value indicates failure * and this function takes care of generating the appropriate error * message. */ static sxi32 jx9CompileBlock( jx9_gen_state *pGen /* Code generator state */ ) { sxi32 rc; if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){ sxu32 nLine = pGen->pIn->nLine; rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(pGen->pVm), 0, 0); if( rc != SXRET_OK ){ return SXERR_ABORT; } pGen->pIn++; /* Compile until we hit the closing braces '}' */ for(;;){ if( pGen->pIn >= pGen->pEnd ){ /* No more token to process. Missing closing braces */ jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'"); break; } if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){ /* Closing braces found, break immediately*/ pGen->pIn++; break; } /* Compile a single statement */ rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } GenStateLeaveBlock(&(*pGen), 0); }else{ /* Compile a single statement */ rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } /* Jump trailing semi-colons ';' */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the gentle 'while' statement. * According to the JX9 language reference * while loops are the simplest type of loop in JX9.They behave just like their C counterparts. * The basic form of a while statement is: * while (expr) * statement * The meaning of a while statement is simple. It tells JX9 to execute the nested statement(s) * repeatedly, as long as the while expression evaluates to TRUE. The value of the expression * is checked each time at the beginning of the loop, so even if this value changes during * the execution of the nested statement(s), execution will not stop until the end of the iteration * (each time JX9 runs the statements in the loop is one iteration). Sometimes, if the while * expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once. * Like with the if statement, you can group multiple statements within the same while loop by surrounding * a group of statements with curly braces, or by using the alternate syntax: * while (expr): * statement * endwhile; */ static sxi32 jx9CompileWhile(jx9_gen_state *pGen) { GenBlock *pWhileBlock = 0; SyToken *pTmp, *pEnd = 0; sxu32 nFalseJump; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; /* Jump the 'while' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pGen->pIn++; /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Delimit the condition */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } /* Update token stream */ while(pGen->pIn < pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pGen->pIn++; } /* Synchronize pointers */ pGen->pIn = &pEnd[1]; pGen->pEnd = pTmp; /* Emit the false jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump); /* Compile the loop body */ rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Emit the unconditional jump to the start of the loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0); /* Fix all jumps now the destination is resolved */ GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';' so we can avoid * compiling this erroneous block. */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the complex and powerful 'for' statement. * According to the JX9 language reference * for loops are the most complex loops in JX9. They behave like their C counterparts. * The syntax of a for loop is: * for (expr1; expr2; expr3) * statement * The first expression (expr1) is evaluated (executed) once unconditionally at * the beginning of the loop. * In the beginning of each iteration, expr2 is evaluated. If it evaluates to * TRUE, the loop continues and the nested statement(s) are executed. If it evaluates * to FALSE, the execution of the loop ends. * At the end of each iteration, expr3 is evaluated (executed). * Each of the expressions can be empty or contain multiple expressions separated by commas. * In expr2, all expressions separated by a comma are evaluated but the result is taken * from the last part. expr2 being empty means the loop should be run indefinitely * (JX9 implicitly considers it as TRUE, like C). This may not be as useless as you might * think, since often you'd want to end the loop using a conditional break statement instead * of using the for truth expression. */ static sxi32 jx9CompileFor(jx9_gen_state *pGen) { SyToken *pTmp, *pPostStart, *pEnd = 0; GenBlock *pForBlock = 0; sxu32 nFalseJump; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; /* Jump the 'for' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* Jump the left parenthesis '(' */ pGen->pIn++; /* Delimit the init-expr;condition;post-expr */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } /* Synchronize */ pGen->pIn = pEnd; if( pGen->pIn < pGen->pEnd ){ pGen->pIn++; } return SXRET_OK; } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; /* Compile initialization expressions if available */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Pop operand lvalues */ if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; }else if( rc != SXERR_EMPTY ){ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ';' after initialization expressions"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* Jump the trailing ';' */ pGen->pIn++; /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Deffer continue jumps */ pForBlock->bPostContinue = TRUE; /* Compile the condition */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; }else if( rc != SXERR_EMPTY ){ /* Emit the false jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump); } if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ';' after conditionals expressions"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } /* Jump the trailing ';' */ pGen->pIn++; /* Save the post condition stream */ pPostStart = pGen->pIn; /* Compile the loop body */ pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */ pGen->pEnd = pTmp; rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Fix post-continue jumps */ if( SySetUsed(&pForBlock->aPostContFix) > 0 ){ JumpFixup *aPost; VmInstr *pInstr; sxu32 nJumpDest; sxu32 n; aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix); nJumpDest = jx9VmInstrLength(pGen->pVm); for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){ pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx); if( pInstr ){ /* Fix jump */ pInstr->iP2 = nJumpDest; } } } /* compile the post-expressions if available */ while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){ pPostStart++; } if( pPostStart < pEnd ){ SyToken *pTmpIn, *pTmpEnd; SWAP_DELIMITER(pGen, pPostStart, pEnd); rc = jx9CompileExpr(&(*pGen), 0, 0); if( pGen->pIn < pGen->pEnd ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } return SXRET_OK; } RE_SWAP_DELIMITER(pGen); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; }else if( rc != SXERR_EMPTY){ /* Pop operand lvalue */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } } /* Emit the unconditional jump to the start of the loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0); /* Fix all jumps now the destination is resolved */ GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; } /* Expression tree validator callback used by the 'foreach' statement. * Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...] * are allowed. */ static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) { sxi32 rc = SXRET_OK; /* Assume a valid expression tree */ if( pRoot->xCode != jx9CompileVariable ){ /* Unexpected expression */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0, "foreach: Expecting a variable name" ); if( rc != SXERR_ABORT ){ rc = SXERR_INVALID; } } return rc; } /* * Compile the 'foreach' statement. * According to the JX9 language reference * The foreach construct simply gives an easy way to iterate over arrays. foreach works * only on arrays (and objects), and will issue an error when you try to use it on a variable * with a different data type or an uninitialized variable. There are two syntaxes; the second * is a minor but useful extension of the first: * foreach (json_array_json_object as $value) * statement * foreach (json_array_json_objec as $key,$value) * statement * The first form loops over the array given by array_expression. On each loop, the value * of the current element is assigned to $value and the internal array pointer is advanced * by one (so on the next loop, you'll be looking at the next element). * The second form does the same thing, except that the current element's key will be assigned * to the variable $key on each loop. * Note: * When foreach first starts executing, the internal array pointer is automatically reset to the * first element of the array. This means that you do not need to call reset() before a foreach loop. */ static sxi32 jx9CompileForeach(jx9_gen_state *pGen) { SyToken *pCur, *pTmp, *pEnd = 0; GenBlock *pForeachBlock = 0; jx9_foreach_info *pInfo; sxu32 nFalseJump; VmInstr *pInstr; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; /* Jump the 'foreach' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pGen->pIn++; /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Delimit the expression */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } /* Synchronize */ pGen->pIn = pEnd; if( pGen->pIn < pGen->pEnd ){ pGen->pIn++; } return SXRET_OK; } /* Compile the array expression */ pCur = pGen->pIn; while( pCur < pEnd ){ if( pCur->nType & JX9_TK_KEYWORD ){ sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData); if( nKeywrd == JX9_TKWRD_AS ){ /* Break with the first 'as' found */ break; } } /* Advance the stream cursor */ pCur++; } if( pCur <= pGen->pIn ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing array/object expression"); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } goto Synchronize; } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pCur; rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } /* Update token stream */ while(pGen->pIn < pCur ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } pGen->pIn++; } pCur++; /* Jump the 'as' keyword */ pGen->pIn = pCur; if( pGen->pIn >= pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } /* Create the foreach context */ pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info)); if( pInfo == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory"); return SXERR_ABORT; } /* Zero the structure */ SyZero(pInfo, sizeof(jx9_foreach_info)); /* Initialize structure fields */ SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *)); /* Check if we have a key field */ while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){ pCur++; } if( pCur < pEnd ){ /* Compile the expression holding the key name */ if( pGen->pIn >= pCur ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key"); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } }else{ pGen->pEnd = pCur; rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } pInstr = jx9VmPopInstr(pGen->pVm); if( pInstr->p3 ){ /* Record key name */ SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3)); } pInfo->iFlags |= JX9_4EACH_STEP_KEY; } pGen->pIn = &pCur[1]; /* Jump the arrow */ } pGen->pEnd = pEnd; if( pGen->pIn >= pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value"); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } goto Synchronize; } /* Compile the expression holding the value name */ rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } pInstr = jx9VmPopInstr(pGen->pVm); if( pInstr->p3 ){ /* Record value name */ SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3)); } /* Emit the 'FOREACH_INIT' instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump); /* Record the first instruction to execute */ pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm); /* Emit the FOREACH_STEP instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump); /* Compile the loop body */ pGen->pIn = &pEnd[1]; pGen->pEnd = pTmp; rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } /* Emit the unconditional jump to the start of the loop */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0); /* Fix all jumps now the destination is resolved */ GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';' so we can avoid * compiling this erroneous block. */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the infamous if/elseif/else if/else statements. * According to the JX9 language reference * The if construct is one of the most important features of many languages JX9 included. * It allows for conditional execution of code fragments. JX9 features an if structure * that is similar to that of C: * if (expr) * statement * else construct: * Often you'd want to execute a statement if a certain condition is met, and a different * statement if the condition is not met. This is what else is for. else extends an if statement * to execute a statement in case the expression in the if statement evaluates to FALSE. * For example, the following code would display a is greater than b if $a is greater than * $b, and a is NOT greater than b otherwise. * The else statement is only executed if the if expression evaluated to FALSE, and if there * were any elseif expressions - only if they evaluated to FALSE as well * elseif * elseif, as its name suggests, is a combination of if and else. Like else, it extends * an if statement to execute a different statement in case the original if expression evaluates * to FALSE. However, unlike else, it will execute that alternative expression only if the elseif * conditional expression evaluates to TRUE. For example, the following code would display a is bigger * than b, a equal to b or a is smaller than b: * if ($a > $b) { * print "a is bigger than b"; * } elseif ($a == $b) { * print "a is equal to b"; * } else { * print "a is smaller than b"; * } */ static sxi32 jx9CompileIf(jx9_gen_state *pGen) { SyToken *pToken, *pTmp, *pEnd = 0; GenBlock *pCondBlock = 0; sxu32 nJumpIdx; sxu32 nKeyID; sxi32 rc; /* Jump the 'if' keyword */ pGen->pIn++; pToken = pGen->pIn; /* Create the conditional block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(pGen->pVm), 0, &pCondBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Process as many [if/else if/elseif/else] blocks as we can */ for(;;){ if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ if( pToken >= pGen->pEnd ){ pToken--; } rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pToken++; /* Delimit the condition */ jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){ /* Syntax error */ if( pToken >= pGen->pEnd ){ pToken--; } rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Swap token streams */ SWAP_TOKEN_STREAM(pGen, pToken, pEnd); /* Compile the condition */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Update token stream */ while(pGen->pIn < pEnd ){ jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); pGen->pIn++; } pGen->pIn = &pEnd[1]; pGen->pEnd = pTmp; if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } /* Emit the false jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx); /* Compile the body */ rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ break; } /* Ensure that the keyword ID is 'else if' or 'else' */ nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){ break; } /* Emit the unconditional jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx); /* Save the instruction index so we can fix it later when the jump destination is resolved */ GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx); if( nKeyID & JX9_TKWRD_ELSE ){ pToken = &pGen->pIn[1]; if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 || SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){ break; } pGen->pIn++; /* Jump the 'else' keyword */ } pGen->pIn++; /* Jump the 'elseif/if' keyword */ /* Synchronize cursors */ pToken = pGen->pIn; /* Fix the false jump */ GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); } /* For(;;) */ /* Fix the false jump */ GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) && (SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){ /* Compile the else block */ pGen->pIn++; rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } nJumpIdx = jx9VmInstrLength(pGen->pVm); /* Fix all unconditional jumps now the destination is resolved */ GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx); /* Release the conditional block */ GenStateLeaveBlock(pGen, 0); /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block. */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the return statement. * According to the JX9 language reference * If called from within a function, the return() statement immediately ends execution * of the current function, and returns its argument as the value of the function call. * return() will also end the execution of an eval() statement or script file. * If called from the global scope, then execution of the current script file is ended. * If the current script file was include()ed or require()ed, then control is passed back * to the calling file. Furthermore, if the current script file was include()ed, then the value * given to return() will be returned as the value of the include() call. If return() is called * from within the main script file, then script execution end. * Note that since return() is a language construct and not a function, the parentheses * surrounding its arguments are not required. It is common to leave them out, and you actually * should do so as JX9 has less work to do in this case. * Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead.. */ static sxi32 jx9CompileReturn(jx9_gen_state *pGen) { sxi32 nRet = 0; /* TRUE if there is a return value */ sxi32 rc; /* Jump the 'return' keyword */ pGen->pIn++; if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nRet = 1; } } /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, nRet, 0, 0, 0); return SXRET_OK; } /* * Compile the die/exit language construct. * The role of these constructs is to terminate execution of the script. * Shutdown functions will always be executed even if exit() is called. */ static sxi32 jx9CompileHalt(jx9_gen_state *pGen) { sxi32 nExpr = 0; sxi32 rc; /* Jump the die/exit keyword */ pGen->pIn++; if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nExpr = 1; } } /* Emit the HALT instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0); return SXRET_OK; } /* * Compile the static statement. * According to the JX9 language reference * Another important feature of variable scoping is the static variable. * A static variable exists only in a local function scope, but it does not lose its value * when program execution leaves this scope. * Static variables also provide one way to deal with recursive functions. */ static sxi32 jx9CompileStatic(jx9_gen_state *pGen) { jx9_vm_func_static_var sStatic; /* Structure describing the static variable */ jx9_vm_func *pFunc; /* Enclosing function */ GenBlock *pBlock; SyString *pName; char *zDup; sxu32 nLine; sxi32 rc; /* Jump the static keyword */ nLine = pGen->pIn->nLine; pGen->pIn++; /* Extract the enclosing function if any */ pBlock = pGen->pCurrent; while( pBlock ){ if( pBlock->iFlags & GEN_BLOCK_FUNC){ break; } /* Point to the upper block */ pBlock = pBlock->pParent; } if( pBlock == 0 ){ /* Static statement, called outside of a function body, treat it as a simple variable. */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } goto Synchronize; } /* Compile the expression holding the variable */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if( rc != SXERR_EMPTY ){ /* Emit the POP instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } return SXRET_OK; } pFunc = (jx9_vm_func *)pBlock->pUserData; /* Make sure we are dealing with a valid statement */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd || (pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; /* Extract variable name */ pName = &pGen->pIn->sData; pGen->pIn++; /* Jump the var name */ if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData); goto Synchronize; } /* Initialize the structure describing the static variable */ SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); sStatic.nIdx = SXU32_HIGH; /* Not yet created */ /* Duplicate variable name */ zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); if( zDup == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory"); return SXERR_ABORT; } SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte); /* Check if we have an expression to compile */ if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){ SySet *pInstrContainer; pGen->pIn++; /* Jump the equal '=' sign */ /* Swap bytecode container */ pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode); /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); /* Restore default bytecode container */ jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); } /* Finally save the compiled static variable in the appropriate container */ SySetPut(&pFunc->aStatic, (const void *)&sStatic); return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous * statement. */ while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the 'const' statement. * According to the JX9 language reference * A constant is an identifier (name) for a simple value. As the name suggests, that value * cannot change during the execution of the script (except for magic constants, which aren't actually constants). * A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. * The name of a constant follows the same rules as any label in JX9. A valid constant name starts * with a letter or underscore, followed by any number of letters, numbers, or underscores. * As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* * Syntax * You can define a constant by using the define()-function or by using the const keyword outside * a object definition. Once a constant is defined, it can never be changed or undefined. * You can get the value of a constant by simply specifying its name. Unlike with variables * you should not prepend a constant with a $. You can also use the function constant() to read * a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants() * to get a list of all defined constants. */ static sxi32 jx9CompileConstant(jx9_gen_state *pGen) { SySet *pConsCode, *pInstrContainer; sxu32 nLine = pGen->pIn->nLine; SyString *pName; sxi32 rc; pGen->pIn++; /* Jump the 'const' keyword */ if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid constant name */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Peek constant name */ pName = &pGen->pIn->sData; /* Make sure the constant name isn't reserved */ if( GenStateIsReservedID(pName) ){ /* Reserved constant */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){ /* Invalid statement*/ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; /*Jump the equal sign */ /* Allocate a new constant value container */ pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet)); if( pConsCode == 0 ){ return GenStateOutOfMem(pGen); } SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); /* Swap bytecode container */ pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, pConsCode); /* Compile constant value */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } SySetSetUserData(pConsCode, pGen->pVm); /* Register the constant */ rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode); if( rc != SXRET_OK ){ SySetRelease(pConsCode); SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode); } return SXRET_OK; Synchronize: /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Compile the uplink construct. * According to the JX9 language reference * In JX9 global variables must be declared uplink inside a function if they are going * to be used in that function. * Example #1 Using global * $a = 1; * $b = 2; * function Sum() * { * uplink $a, $b; * $b = $a + $b; * } * Sum(); * print $b; * ?> * The above script will output 3. By declaring $a and $b global within the function * all references to either variable will refer to the global version. There is no limit * to the number of global variables that can be manipulated by a function. */ static sxi32 jx9CompileUplink(jx9_gen_state *pGen) { SyToken *pTmp, *pNext = 0; sxi32 nExpr; sxi32 rc; /* Jump the 'uplink' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){ /* Nothing to process */ return SXRET_OK; } pTmp = pGen->pEnd; nExpr = 0; while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ if( pGen->pIn < pNext ){ pGen->pEnd = pNext; if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } }else{ pGen->pIn++; if( pGen->pIn >= pGen->pEnd ){ /* Emit a warning */ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name"); }else{ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ return SXERR_ABORT; }else if(rc != SXERR_EMPTY ){ nExpr++; } } } } /* Next expression in the stream */ pGen->pIn = pNext; /* Jump trailing commas */ while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){ pGen->pIn++; } } /* Restore token stream */ pGen->pEnd = pTmp; if( nExpr > 0 ){ /* Emit the uplink instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0); } return SXRET_OK; } /* * Compile a switch block. * (See block-comment below for more information) */ static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart) { sxi32 rc = SXRET_OK; while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){ /* Unexpected token */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pGen->pIn++; } pGen->pIn++; /* First instruction to execute in this block. */ *pBlockStart = jx9VmInstrLength(pGen->pVm); /* Compile the block until we hit a case/default/endswitch keyword * or the '}' token */ for(;;){ if( pGen->pIn >= pGen->pEnd ){ /* No more input to process */ break; } rc = SXRET_OK; if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){ rc = SXERR_EOF; break; } }else{ sxi32 nKwrd; /* Extract the keyword */ nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){ break; } } /* Compile block */ rc = jx9CompileBlock(&(*pGen)); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } return rc; } /* * Compile a case eXpression. * (See block-comment below for more information) */ static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_case_expr *pExpr) { SySet *pInstrContainer; SyToken *pEnd, *pTmp; sxi32 iNest = 0; sxi32 rc; /* Delimit the expression */ pEnd = pGen->pIn; while( pEnd < pGen->pEnd ){ if( pEnd->nType & JX9_TK_LPAREN /*(*/ ){ /* Increment nesting level */ iNest++; }else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){ /* Decrement nesting level */ iNest--; }else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){ break; } pEnd++; } if( pGen->pIn >= pEnd ){ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } } /* Swap token stream */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode); rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); /* Update token stream */ pGen->pIn = pEnd; pGen->pEnd = pTmp; if( rc == SXERR_ABORT ){ return SXERR_ABORT; } return SXRET_OK; } /* * Compile the smart switch statement. * According to the JX9 language reference manual * The switch statement is similar to a series of IF statements on the same expression. * In many occasions, you may want to compare the same variable (or expression) with many * different values, and execute a different piece of code depending on which value it equals to. * This is exactly what the switch statement is for. * Note: Note that unlike some other languages, the continue statement applies to switch and acts * similar to break. If you have a switch inside a loop and wish to continue to the next iteration * of the outer loop, use continue 2. * Note that switch/case does loose comparision. * It is important to understand how the switch statement is executed in order to avoid mistakes. * The switch statement executes line by line (actually, statement by statement). * In the beginning, no code is executed. Only when a case statement is found with a value that * matches the value of the switch expression does JX9 begin to execute the statements. * JX9 continues to execute the statements until the end of the switch block, or the first time * it sees a break statement. If you don't write a break statement at the end of a case's statement list. * In a switch statement, the condition is evaluated only once and the result is compared to each * case statement. In an elseif statement, the condition is evaluated again. If your condition * is more complicated than a simple compare and/or is in a tight loop, a switch may be faster. * The statement list for a case can also be empty, which simply passes control into the statement * list for the next case. * The case expression may be any expression that evaluates to a simple type, that is, integer * or floating-point numbers and strings. */ static sxi32 jx9CompileSwitch(jx9_gen_state *pGen) { GenBlock *pSwitchBlock; SyToken *pTmp, *pEnd; jx9_switch *pSwitch; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; /* Jump the 'switch' keyword */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } goto Synchronize; } /* Jump the left parenthesis '(' */ pGen->pIn++; pEnd = 0; /* cc warning */ /* Create the loop block */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH, jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock); if( rc != SXRET_OK ){ return SXERR_ABORT; } /* Delimit the condition */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ /* Empty expression */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword"); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } } /* Swap token streams */ pTmp = pGen->pEnd; pGen->pEnd = pEnd; /* Compile the expression */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc == SXERR_ABORT ){ /* Expression handler request an operation abort [i.e: Out-of-memory] */ return SXERR_ABORT; } /* Update token stream */ while(pGen->pIn < pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } pGen->pIn++; } pGen->pIn = &pEnd[1]; pGen->pEnd = pTmp; if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){ pTmp = pGen->pIn; if( pTmp >= pGen->pEnd ){ pTmp--; } /* Unexpected token */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } goto Synchronize; } pGen->pIn++; /* Jump the leading curly braces/colons */ /* Create the switch blocks container */ pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch)); if( pSwitch == 0 ){ /* Abort compilation */ return GenStateOutOfMem(pGen); } /* Zero the structure */ SyZero(pSwitch, sizeof(jx9_switch)); /* Initialize fields */ SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr)); /* Emit the switch instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0); /* Compile case blocks */ for(;;){ sxu32 nKwrd; if( pGen->pIn >= pGen->pEnd ){ /* No more input to process */ break; } if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){ /* Unexpected token */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* FALL THROUGH */ } /* Block compiled */ break; } /* Extract the keyword */ nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); if( nKwrd == JX9_TKWRD_DEFAULT ){ /* * Accroding to the JX9 language reference manual * A special case is the default case. This case matches anything * that wasn't matched by the other cases. */ if( pSwitch->nDefault > 0 ){ /* Default case already compiled */ rc = jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } pGen->pIn++; /* Jump the 'default' keyword */ /* Compile the default block */ rc = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault); if( rc == SXERR_ABORT){ return SXERR_ABORT; }else if( rc == SXERR_EOF ){ break; } }else if( nKwrd == JX9_TKWRD_CASE ){ jx9_case_expr sCase; /* Standard case block */ pGen->pIn++; /* Jump the 'case' keyword */ /* initialize the structure */ SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); /* Compile the case expression */ rc = GenStateCompileCaseExpr(pGen, &sCase); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Compile the case block */ rc = GenStateCompileSwitchBlock(pGen,&sCase.nStart); /* Insert in the switch container */ SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase); if( rc == SXERR_ABORT){ return SXERR_ABORT; }else if( rc == SXERR_EOF ){ break; } }else{ /* Unexpected token */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } break; } } /* Fix all jumps now the destination is resolved */ pSwitch->nOut = jx9VmInstrLength(pGen->pVm); GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm)); /* Release the loop block */ GenStateLeaveBlock(pGen, 0); if( pGen->pIn < pGen->pEnd ){ /* Jump the trailing curly braces */ pGen->pIn++; } /* Statement successfully compiled */ return SXRET_OK; Synchronize: /* Synchronize with the first semi-colon */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* * Process default argument values. That is, a function may define C++-style default value * as follows: * function makecoffee($type = "cappuccino") * { * return "Making a cup of $type.\n"; * } * Some features: * 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous * functions, array member, ..] * 2 -) Full type hinting: (Arguments are automatically casted to the desired type) * Example: * function a(string $a){} function b(int $a, string $c, float $d){} * 3 -) Function overloading!! * Example: * function foo($a) { * return $a.JX9_EOL; * } * function foo($a, $b) { * return $a + $b; * } * print foo(5); // Prints "5" * print foo(5, 2); // Prints "7" * // Same arg * function foo(string $a) * { * print "a is a string\n"; * dump($a); * } * function foo(int $a) * { * print "a is integer\n"; * dump($a); * } * function foo(array $a) * { * print "a is an array\n"; * dump($a); * } * foo('This is a great feature'); // a is a string [first foo] * foo(52); // a is integer [second foo] * foo(array(14, __TIME__, __DATE__)); // a is an array [third foo] * Please refer to the official documentation for more information on the powerful extension * introduced by the JX9 engine. */ static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd) { SyToken *pTmpIn, *pTmpEnd; SySet *pInstrContainer; sxi32 rc; /* Swap token stream */ SWAP_DELIMITER(pGen, pIn, pEnd); pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode); /* Compile the expression holding the argument value */ rc = jx9CompileExpr(&(*pGen), 0, 0); /* Emit the done instruction */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); RE_SWAP_DELIMITER(pGen); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } return SXRET_OK; } /* * Collect function arguments one after one. * According to the JX9 language reference manual. * Information may be passed to functions via the argument list, which is a comma-delimited * list of expressions. * JX9 supports passing arguments by value (the default), passing by reference * and default argument values. Variable-length argument lists are also supported, * see also the function references for func_num_args(), func_get_arg(), and func_get_args() * for more information. * Example #1 Passing arrays to functions * * Making arguments be passed by reference * By default, function arguments are passed by value (so that if the value of the argument * within the function is changed, it does not get changed outside of the function). * To allow a function to modify its arguments, they must be passed by reference. * To have an argument to a function always passed by reference, prepend an ampersand (&) * to the argument name in the function definition: * Example #2 Passing function parameters by reference * * * JX9 have introduced powerful extension including full type hinting, function overloading * complex agrument values.Please refer to the official documentation for more information * on these extension. */ static sxi32 GenStateCollectFuncArgs(jx9_vm_func *pFunc, jx9_gen_state *pGen, SyToken *pEnd) { jx9_vm_func_arg sArg; /* Current processed argument */ SyToken *pCur, *pIn; /* Token stream */ SyBlob sSig; /* Function signature */ char *zDup; /* Copy of argument name */ sxi32 rc; pIn = pGen->pIn; pCur = 0; SyBlobInit(&sSig, &pGen->pVm->sAllocator); /* Process arguments one after one */ for(;;){ if( pIn >= pEnd ){ /* No more arguments to process */ break; } SyZero(&sArg, sizeof(jx9_vm_func_arg)); SySetInit(&sArg.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); if( pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ if( pIn->nType & JX9_TK_KEYWORD ){ sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData)); if( nKey & JX9_TKWRD_BOOL ){ sArg.nType = MEMOBJ_BOOL; }else if( nKey & JX9_TKWRD_INT ){ sArg.nType = MEMOBJ_INT; }else if( nKey & JX9_TKWRD_STRING ){ sArg.nType = MEMOBJ_STRING; }else if( nKey & JX9_TKWRD_FLOAT ){ sArg.nType = MEMOBJ_REAL; }else{ jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Invalid argument type '%z', Automatic cast will not be performed", &pIn->sData); } } pIn++; } if( pIn >= pEnd ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Missing argument name"); return rc; } if( pIn >= pEnd || (pIn->nType & JX9_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid argument */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid argument name"); return rc; } pIn++; /* Jump the dollar sign */ /* Copy argument name */ zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, SyStringData(&pIn->sData), SyStringLength(&pIn->sData)); if( zDup == 0 ){ return GenStateOutOfMem(pGen); } SyStringInitFromBuf(&sArg.sName, zDup, SyStringLength(&pIn->sData)); pIn++; if( pIn < pEnd ){ if( pIn->nType & JX9_TK_EQUAL ){ SyToken *pDefend; sxi32 iNest = 0; pIn++; /* Jump the equal sign */ pDefend = pIn; /* Process the default value associated with this argument */ while( pDefend < pEnd ){ if( (pDefend->nType & JX9_TK_COMMA) && iNest <= 0 ){ break; } if( pDefend->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*[*/) ){ /* Increment nesting level */ iNest++; }else if( pDefend->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*]*/) ){ /* Decrement nesting level */ iNest--; } pDefend++; } if( pIn >= pDefend ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Missing argument default value"); return rc; } /* Process default value */ rc = GenStateProcessArgValue(&(*pGen), &sArg, pIn, pDefend); if( rc != SXRET_OK ){ return rc; } /* Point beyond the default value */ pIn = pDefend; } if( pIn < pEnd && (pIn->nType & JX9_TK_COMMA) == 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Unexpected token '%z'", &pIn->sData); return rc; } pIn++; /* Jump the trailing comma */ } /* Append argument signature */ if( sArg.nType > 0 ){ int c; c = 'n'; /* cc warning */ /* Type leading character */ switch(sArg.nType){ case MEMOBJ_HASHMAP: /* Hashmap aka 'array' */ c = 'h'; break; case MEMOBJ_INT: /* Integer */ c = 'i'; break; case MEMOBJ_BOOL: /* Bool */ c = 'b'; break; case MEMOBJ_REAL: /* Float */ c = 'f'; break; case MEMOBJ_STRING: /* String */ c = 's'; break; default: break; } SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); } /* Save in the argument set */ SySetPut(&pFunc->aArgs, (const void *)&sArg); } if( SyBlobLength(&sSig) > 0 ){ /* Save function signature */ SyStringInitFromBuf(&pFunc->sSignature, SyBlobData(&sSig), SyBlobLength(&sSig)); } return SXRET_OK; } /* * Compile function [i.e: standard function, annonymous function or closure ] body. * Return SXRET_OK on success. Any other return value indicates failure * and this routine takes care of generating the appropriate error message. */ static sxi32 GenStateCompileFuncBody( jx9_gen_state *pGen, /* Code generator state */ jx9_vm_func *pFunc /* Function state */ ) { SySet *pInstrContainer; /* Instruction container */ GenBlock *pBlock; sxi32 rc; /* Attach the new function */ rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC,jx9VmInstrLength(pGen->pVm), pFunc, &pBlock); if( rc != SXRET_OK ){ return GenStateOutOfMem(pGen); } /* Swap bytecode containers */ pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); jx9VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode); /* Compile the body */ jx9CompileBlock(&(*pGen)); /* Emit the final return if not yet done */ jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, 0, 0, 0, 0); /* Restore the default container */ jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); /* Leave function block */ GenStateLeaveBlock(&(*pGen), 0); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } /* All done, function body compiled */ return SXRET_OK; } /* * Compile a JX9 function whether is a Standard or Annonymous function. * According to the JX9 language reference manual. * Function names follow the same rules as other labels in JX9. A valid function name * starts with a letter or underscore, followed by any number of letters, numbers, or * underscores. As a regular expression, it would be expressed thus: * [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. * Functions need not be defined before they are referenced. * All functions and objectes in JX9 have the global scope - they can be called outside * a function even if they were defined inside and vice versa. * It is possible to call recursive functions in JX9. However avoid recursive function/method * calls with over 32-64 recursion levels. * * JX9 have introduced powerful extension including full type hinting, function overloading, * complex agrument values and more. Please refer to the official documentation for more information * on these extension. */ static sxi32 GenStateCompileFunc( jx9_gen_state *pGen, /* Code generator state */ SyString *pName, /* Function name. NULL otherwise */ sxi32 iFlags, /* Control flags */ jx9_vm_func **ppFunc /* OUT: function state */ ) { jx9_vm_func *pFunc; SyToken *pEnd; sxu32 nLine; char *zName; sxi32 rc; /* Extract line number */ nLine = pGen->pIn->nLine; /* Jump the left parenthesis '(' */ pGen->pIn++; /* Delimit the function signature */ jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); if( pEnd >= pGen->pEnd ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after function '%z' signature", pName); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } pGen->pIn = pGen->pEnd; return SXRET_OK; } /* Create the function state */ pFunc = (jx9_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_vm_func)); if( pFunc == 0 ){ goto OutOfMem; } /* function ID */ zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); if( zName == 0 ){ /* Don't worry about freeing memory, everything will be released shortly */ goto OutOfMem; } /* Initialize the function state */ jx9VmInitFuncState(pGen->pVm, pFunc, zName, pName->nByte, iFlags, 0); if( pGen->pIn < pEnd ){ /* Collect function arguments */ rc = GenStateCollectFuncArgs(pFunc, &(*pGen), pEnd); if( rc == SXERR_ABORT ){ /* Don't worry about freeing memory, everything will be released shortly */ return SXERR_ABORT; } } /* Compile function body */ pGen->pIn = &pEnd[1]; /* Compile the body */ rc = GenStateCompileFuncBody(&(*pGen), pFunc); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } if( ppFunc ){ *ppFunc = pFunc; } /* Finally register the function */ rc = jx9VmInstallUserFunction(pGen->pVm, pFunc, 0); return rc; /* Fall through if something goes wrong */ OutOfMem: /* If the supplied memory subsystem is so sick that we are unable to allocate * a tiny chunk of memory, there is no much we can do here. */ return GenStateOutOfMem(pGen); } /* * Compile a standard JX9 function. * Refer to the block-comment above for more information. */ static sxi32 jx9CompileFunction(jx9_gen_state *pGen) { SyString *pName; sxi32 iFlags; sxu32 nLine; sxi32 rc; nLine = pGen->pIn->nLine; pGen->pIn++; /* Jump the 'function' keyword */ iFlags = 0; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ /* Invalid function name */ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Invalid function name"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* Sychronize with the next semi-colon or braces*/ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } pName = &pGen->pIn->sData; nLine = pGen->pIn->nLine; /* Jump the function name */ pGen->pIn++; if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after function name '%z'", pName); if( rc == SXERR_ABORT ){ /* Error count limit reached, abort immediately */ return SXERR_ABORT; } /* Sychronize with the next semi-colon or '{' */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ pGen->pIn++; } return SXRET_OK; } /* Compile function body */ rc = GenStateCompileFunc(&(*pGen),pName,iFlags,0); return rc; } /* * Generate bytecode for a given expression tree. * If something goes wrong while generating bytecode * for the expression tree (A very unlikely scenario) * this function takes care of generating the appropriate * error message. */ static sxi32 GenStateEmitExprCode( jx9_gen_state *pGen, /* Code generator state */ jx9_expr_node *pNode, /* Root of the expression tree */ sxi32 iFlags /* Control flags */ ) { VmInstr *pInstr; sxu32 nJmpIdx; sxi32 iP1 = 0; sxu32 iP2 = 0; void *p3 = 0; sxi32 iVmOp; sxi32 rc; if( pNode->xCode ){ SyToken *pTmpIn, *pTmpEnd; /* Compile node */ SWAP_DELIMITER(pGen, pNode->pStart, pNode->pEnd); rc = pNode->xCode(&(*pGen), iFlags); RE_SWAP_DELIMITER(pGen); return rc; } if( pNode->pOp == 0 ){ jx9GenCompileError(&(*pGen), E_ERROR, pNode->pStart->nLine, "Invalid expression node, JX9 is aborting compilation"); return SXERR_ABORT; } iVmOp = pNode->pOp->iVmOp; if( pNode->pOp->iOp == EXPR_OP_QUESTY ){ sxu32 nJz, nJmp; /* Ternary operator require special handling */ /* Phase#1: Compile the condition */ rc = GenStateEmitExprCode(&(*pGen), pNode->pCond, iFlags); if( rc != SXRET_OK ){ return rc; } nJz = nJmp = 0; /* cc -O6 warning */ /* Phase#2: Emit the false jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJz); if( pNode->pLeft ){ /* Phase#3: Compile the 'then' expression */ rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); if( rc != SXRET_OK ){ return rc; } } /* Phase#4: Emit the unconditional jump */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJmp); /* Phase#5: Fix the false jump now the jump destination is resolved. */ pInstr = jx9VmGetInstr(pGen->pVm, nJz); if( pInstr ){ pInstr->iP2 = jx9VmInstrLength(pGen->pVm); } /* Phase#6: Compile the 'else' expression */ if( pNode->pRight ){ rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); if( rc != SXRET_OK ){ return rc; } } if( nJmp > 0 ){ /* Phase#7: Fix the unconditional jump */ pInstr = jx9VmGetInstr(pGen->pVm, nJmp); if( pInstr ){ pInstr->iP2 = jx9VmInstrLength(pGen->pVm); } } /* All done */ return SXRET_OK; } /* Generate code for the left tree */ if( pNode->pLeft ){ if( iVmOp == JX9_OP_CALL ){ jx9_expr_node **apNode; sxi32 n; /* Recurse and generate bytecodes for function arguments */ apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); /* Read-only load */ iFlags |= EXPR_FLAG_RDONLY_LOAD; for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); if( rc != SXRET_OK ){ return rc; } } /* Total number of given arguments */ iP1 = (sxi32)SySetUsed(&pNode->aNodeArgs); /* Remove stale flags now */ iFlags &= ~EXPR_FLAG_RDONLY_LOAD; } rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); if( rc != SXRET_OK ){ return rc; } if( iVmOp == JX9_OP_CALL ){ pInstr = jx9VmPeekInstr(pGen->pVm); if( pInstr ){ if ( pInstr->iOp == JX9_OP_LOADC ){ /* Prevent constant expansion */ pInstr->iP1 = 0; }else if( pInstr->iOp == JX9_OP_MEMBER /* $a.b(1, 2, 3) */ ){ /* Annonymous function call, flag that */ pInstr->iP2 = 1; } } }else if( iVmOp == JX9_OP_LOAD_IDX ){ jx9_expr_node **apNode; sxi32 n; /* Recurse and generate bytecodes for array index */ apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); if( rc != SXRET_OK ){ return rc; } } if( SySetUsed(&pNode->aNodeArgs) > 0 ){ iP1 = 1; /* Node have an index associated with it */ } if( iFlags & EXPR_FLAG_LOAD_IDX_STORE ){ /* Create an empty entry when the desired index is not found */ iP2 = 1; } }else if( pNode->pOp->iOp == EXPR_OP_COMMA ){ /* POP the left node */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } } rc = SXRET_OK; nJmpIdx = 0; /* Generate code for the right tree */ if( pNode->pRight ){ if( iVmOp == JX9_OP_LAND ){ /* Emit the false jump so we can short-circuit the logical and */ jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); }else if (iVmOp == JX9_OP_LOR ){ /* Emit the true jump so we can short-circuit the logical or*/ jx9VmEmitInstr(pGen->pVm, JX9_OP_JNZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); }else if( pNode->pOp->iPrec == 18 /* Combined binary operators [i.e: =, '.=', '+=', *=' ...] precedence */ ){ iFlags |= EXPR_FLAG_LOAD_IDX_STORE; } rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); if( iVmOp == JX9_OP_STORE ){ pInstr = jx9VmPeekInstr(pGen->pVm); if( pInstr ){ if(pInstr->iOp == JX9_OP_MEMBER ){ /* Perform a member store operation [i.e: $this.x = 50] */ iP2 = 1; }else{ if( pInstr->iOp == JX9_OP_LOAD_IDX ){ /* Transform the STORE instruction to STORE_IDX instruction */ iVmOp = JX9_OP_STORE_IDX; iP1 = pInstr->iP1; }else{ p3 = pInstr->p3; } /* POP the last dynamic load instruction */ (void)jx9VmPopInstr(pGen->pVm); } } } } if( iVmOp > 0 ){ if( iVmOp == JX9_OP_INCR || iVmOp == JX9_OP_DECR ){ if( pNode->iFlags & EXPR_NODE_PRE_INCR ){ /* Pre-increment/decrement operator [i.e: ++$i, --$j ] */ iP1 = 1; } } /* Finally, emit the VM instruction associated with this operator */ jx9VmEmitInstr(pGen->pVm, iVmOp, iP1, iP2, p3, 0); if( nJmpIdx > 0 ){ /* Fix short-circuited jumps now the destination is resolved */ pInstr = jx9VmGetInstr(pGen->pVm, nJmpIdx); if( pInstr ){ pInstr->iP2 = jx9VmInstrLength(pGen->pVm); } } } return rc; } /* * Compile a JX9 expression. * According to the JX9 language reference manual: * Expressions are the most important building stones of JX9. * In JX9, almost anything you write is an expression. * The simplest yet most accurate way to define an expression * is "anything that has a value". * If something goes wrong while compiling the expression, this * function takes care of generating the appropriate error * message. */ static sxi32 jx9CompileExpr( jx9_gen_state *pGen, /* Code generator state */ sxi32 iFlags, /* Control flags */ sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ ) { jx9_expr_node *pRoot; SySet sExprNode; SyToken *pEnd; sxi32 nExpr; sxi32 iNest; sxi32 rc; /* Initialize worker variables */ nExpr = 0; pRoot = 0; SySetInit(&sExprNode, &pGen->pVm->sAllocator, sizeof(jx9_expr_node *)); SySetAlloc(&sExprNode, 0x10); rc = SXRET_OK; /* Delimit the expression */ pEnd = pGen->pIn; iNest = 0; while( pEnd < pGen->pEnd ){ if( pEnd->nType & JX9_TK_OCB /* '{' */ ){ /* Ticket 1433-30: Annonymous/Closure functions body */ iNest++; }else if(pEnd->nType & JX9_TK_CCB /* '}' */ ){ iNest--; }else if( pEnd->nType & JX9_TK_SEMI /* ';' */ ){ if( iNest <= 0 ){ break; } } pEnd++; } if( iFlags & EXPR_FLAG_COMMA_STATEMENT ){ SyToken *pEnd2 = pGen->pIn; iNest = 0; /* Stop at the first comma */ while( pEnd2 < pEnd ){ if( pEnd2->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_LPAREN/*'('*/) ){ iNest++; }else if(pEnd2->nType & (JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*']'*/|JX9_TK_RPAREN/*')'*/)){ iNest--; }else if( pEnd2->nType & JX9_TK_COMMA /*','*/ ){ if( iNest <= 0 ){ break; } } pEnd2++; } if( pEnd2 pGen->pIn ){ SyToken *pTmp = pGen->pEnd; /* Swap delimiter */ pGen->pEnd = pEnd; /* Try to get an expression tree */ rc = jx9ExprMakeTree(&(*pGen), &sExprNode, &pRoot); if( rc == SXRET_OK && pRoot ){ rc = SXRET_OK; if( xTreeValidator ){ /* Call the upper layer validator callback */ rc = xTreeValidator(&(*pGen), pRoot); } if( rc != SXERR_ABORT ){ /* Generate code for the given tree */ rc = GenStateEmitExprCode(&(*pGen), pRoot, iFlags); } nExpr = 1; } /* Release the whole tree */ jx9ExprFreeTree(&(*pGen), &sExprNode); /* Synchronize token stream */ pGen->pEnd = pTmp; pGen->pIn = pEnd; if( rc == SXERR_ABORT ){ SySetRelease(&sExprNode); return SXERR_ABORT; } } SySetRelease(&sExprNode); return nExpr > 0 ? SXRET_OK : SXERR_EMPTY; } /* * Return a pointer to the node construct handler associated * with a given node type [i.e: string, integer, float, ...]. */ JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType) { if( nNodeType & JX9_TK_NUM ){ /* Numeric literal: Either real or integer */ return jx9CompileNumLiteral; }else if( nNodeType & JX9_TK_DSTR ){ /* Double quoted string */ return jx9CompileString; }else if( nNodeType & JX9_TK_SSTR ){ /* Single quoted string */ return jx9CompileSimpleString; }else if( nNodeType & JX9_TK_NOWDOC ){ /* Nowdoc */ return jx9CompileNowdoc; } return 0; } /* * Jx9 Language construct table. */ static const LangConstruct aLangConstruct[] = { { JX9_TKWRD_IF, jx9CompileIf }, { JX9_TKWRD_FUNCTION, jx9CompileFunction }, { JX9_TKWRD_FOREACH, jx9CompileForeach }, { JX9_TKWRD_WHILE, jx9CompileWhile }, { JX9_TKWRD_FOR, jx9CompileFor }, { JX9_TKWRD_SWITCH, jx9CompileSwitch }, { JX9_TKWRD_DIE, jx9CompileHalt }, { JX9_TKWRD_EXIT, jx9CompileHalt }, { JX9_TKWRD_RETURN, jx9CompileReturn }, { JX9_TKWRD_BREAK, jx9CompileBreak }, { JX9_TKWRD_CONTINUE, jx9CompileContinue }, { JX9_TKWRD_STATIC, jx9CompileStatic }, { JX9_TKWRD_UPLINK, jx9CompileUplink }, { JX9_TKWRD_CONST, jx9CompileConstant }, }; /* * Return a pointer to the statement handler routine associated * with a given JX9 keyword [i.e: if, for, while, ...]. */ static ProcLangConstruct GenStateGetStatementHandler( sxu32 nKeywordID /* Keyword ID*/ ) { sxu32 n = 0; for(;;){ if( n >= SX_ARRAYSIZE(aLangConstruct) ){ break; } if( aLangConstruct[n].nID == nKeywordID ){ /* Return a pointer to the handler. */ return aLangConstruct[n].xConstruct; } n++; } /* Not a language construct */ return 0; } /* * Compile a jx9 program. * If something goes wrong while compiling the Jx9 chunk, this function * takes care of generating the appropriate error message. */ static sxi32 GenStateCompileChunk( jx9_gen_state *pGen, /* Code generator state */ sxi32 iFlags /* Compile flags */ ) { ProcLangConstruct xCons; sxi32 rc; rc = SXRET_OK; /* Prevent compiler warning */ for(;;){ if( pGen->pIn >= pGen->pEnd ){ /* No more input to process */ break; } xCons = 0; if( pGen->pIn->nType & JX9_TK_KEYWORD ){ sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); /* Try to extract a language construct handler */ xCons = GenStateGetStatementHandler(nKeyword); if( xCons == 0 && !jx9IsLangConstruct(nKeyword) ){ rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Syntax error: Unexpected keyword '%z'", &pGen->pIn->sData); if( rc == SXERR_ABORT ){ break; } /* Synchronize with the first semi-colon and avoid compiling * this erroneous statement. */ xCons = jx9ErrorRecover; } } if( xCons == 0 ){ /* Assume an expression an try to compile it */ rc = jx9CompileExpr(&(*pGen), 0, 0); if( rc != SXERR_EMPTY ){ /* Pop l-value */ jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); } }else{ /* Go compile the sucker */ rc = xCons(&(*pGen)); } if( rc == SXERR_ABORT ){ /* Request to abort compilation */ break; } /* Ignore trailing semi-colons ';' */ while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ pGen->pIn++; } if( iFlags & JX9_COMPILE_SINGLE_STMT ){ /* Compile a single statement and return */ break; } /* LOOP ONE */ /* LOOP TWO */ /* LOOP THREE */ /* LOOP FOUR */ } /* Return compilation status */ return rc; } /* * Compile a raw chunk. The raw chunk can contain JX9 code embedded * in HTML, XML and so on. This function handle all the stuff. * This is the only compile interface exported from this file. */ JX9_PRIVATE sxi32 jx9CompileScript( jx9_vm *pVm, /* Generate JX9 bytecodes for this Virtual Machine */ SyString *pScript, /* Script to compile */ sxi32 iFlags /* Compile flags */ ) { jx9_gen_state *pGen; SySet aToken; sxi32 rc; if( pScript->nByte < 1 ){ /* Nothing to compile */ return JX9_OK; } /* Initialize the tokens containers */ SySetInit(&aToken, &pVm->sAllocator, sizeof(SyToken)); SySetAlloc(&aToken, 0xc0); pGen = &pVm->sCodeGen; rc = JX9_OK; /* Tokenize the JX9 chunk first */ jx9Tokenize(pScript->zString,pScript->nByte,&aToken); if( SySetUsed(&aToken) < 1 ){ return SXERR_EMPTY; } /* Point to the head and tail of the token stream. */ pGen->pIn = (SyToken *)SySetBasePtr(&aToken); pGen->pEnd = &pGen->pIn[SySetUsed(&aToken)]; /* Compile the chunk */ rc = GenStateCompileChunk(pGen,iFlags); /* Cleanup */ SySetRelease(&aToken); return rc; } /* * Utility routines.Initialize the code generator. */ JX9_PRIVATE sxi32 jx9InitCodeGenerator( jx9_vm *pVm, /* Target VM */ ProcConsumer xErr, /* Error log consumer callabck */ void *pErrData /* Last argument to xErr() */ ) { jx9_gen_state *pGen = &pVm->sCodeGen; /* Zero the structure */ SyZero(pGen, sizeof(jx9_gen_state)); /* Initial state */ pGen->pVm = &(*pVm); pGen->xErr = xErr; pGen->pErrData = pErrData; SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0); SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0); /* Create the global scope */ GenStateInitBlock(pGen, &pGen->sGlobal,GEN_BLOCK_GLOBAL,jx9VmInstrLength(&(*pVm)), 0); /* Point to the global scope */ pGen->pCurrent = &pGen->sGlobal; return SXRET_OK; } /* * Utility routines. Reset the code generator to it's initial state. */ JX9_PRIVATE sxi32 jx9ResetCodeGenerator( jx9_vm *pVm, /* Target VM */ ProcConsumer xErr, /* Error log consumer callabck */ void *pErrData /* Last argument to xErr() */ ) { jx9_gen_state *pGen = &pVm->sCodeGen; GenBlock *pBlock, *pParent; /* Point to the global scope */ pBlock = pGen->pCurrent; while( pBlock->pParent != 0 ){ pParent = pBlock->pParent; GenStateFreeBlock(pBlock); pBlock = pParent; } pGen->xErr = xErr; pGen->pErrData = pErrData; pGen->pCurrent = &pGen->sGlobal; pGen->pIn = pGen->pEnd = 0; pGen->nErr = 0; return SXRET_OK; } /* * Generate a compile-time error message. * If the error count limit is reached (usually 15 error message) * this function return SXERR_ABORT.In that case upper-layers must * abort compilation immediately. */ JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...) { SyBlob *pWorker = &pGen->pVm->pEngine->xConf.sErrConsumer; const char *zErr = "Error"; va_list ap; if( nErrType == E_ERROR ){ /* Increment the error counter */ pGen->nErr++; if( pGen->nErr > 15 ){ /* Error count limit reached */ SyBlobFormat(pWorker, "%u Error count limit reached, JX9 is aborting compilation\n", nLine); /* Abort immediately */ return SXERR_ABORT; } } switch(nErrType){ case E_WARNING: zErr = "Warning"; break; case E_PARSE: zErr = "Parse error"; break; case E_NOTICE: zErr = "Notice"; break; default: break; } /* Format the error message */ SyBlobFormat(pWorker, "%u %s: ", nLine, zErr); va_start(ap, zFormat); SyBlobFormatAp(pWorker, zFormat, ap); va_end(ap); /* Append a new line */ SyBlobAppend(pWorker, (const void *)"\n", sizeof(char)); return JX9_OK; } /* * ---------------------------------------------------------- * File: jx9_const.c * MD5: f3980b00dd1eda0bb2b749424a8dfffe * ---------------------------------------------------------- */ /* * 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: const.c v1.7 Win7 2012-12-13 00:01 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* This file implement built-in constants for the JX9 engine. */ /* * JX9_VERSION * __JX9__ * Expand the current version of the JX9 engine. */ static void JX9_VER_Const(jx9_value *pVal, void *pUnused) { SXUNUSED(pUnused); jx9_value_string(pVal, jx9_lib_signature(), -1/*Compute length automatically*/); } #ifdef __WINNT__ #include #elif defined(__UNIXES__) #include #endif /* * JX9_OS * __OS__ * Expand the name of the host Operating System. */ static void JX9_OS_Const(jx9_value *pVal, void *pUnused) { #if defined(__WINNT__) jx9_value_string(pVal, "WinNT", (int)sizeof("WinNT")-1); #elif defined(__UNIXES__) struct utsname sInfo; if( uname(&sInfo) != 0 ){ jx9_value_string(pVal, "Unix", (int)sizeof("Unix")-1); }else{ jx9_value_string(pVal, sInfo.sysname, -1); } #else jx9_value_string(pVal,"Host OS", (int)sizeof("Host OS")-1); #endif SXUNUSED(pUnused); } /* * JX9_EOL * Expand the correct 'End Of Line' symbol for this platform. */ static void JX9_EOL_Const(jx9_value *pVal, void *pUnused) { SXUNUSED(pUnused); #ifdef __WINNT__ jx9_value_string(pVal, "\r\n", (int)sizeof("\r\n")-1); #else jx9_value_string(pVal, "\n", (int)sizeof(char)); #endif } /* * JX9_INT_MAX * Expand the largest integer supported. * Note that JX9 deals with 64-bit integer for all platforms. */ static void JX9_INTMAX_Const(jx9_value *pVal, void *pUnused) { SXUNUSED(pUnused); jx9_value_int64(pVal, SXI64_HIGH); } /* * JX9_INT_SIZE * Expand the size in bytes of a 64-bit integer. */ static void JX9_INTSIZE_Const(jx9_value *pVal, void *pUnused) { SXUNUSED(pUnused); jx9_value_int64(pVal, sizeof(sxi64)); } /* * DIRECTORY_SEPARATOR. * Expand the directory separator character. */ static void JX9_DIRSEP_Const(jx9_value *pVal, void *pUnused) { SXUNUSED(pUnused); #ifdef __WINNT__ jx9_value_string(pVal, "\\", (int)sizeof(char)); #else jx9_value_string(pVal, "/", (int)sizeof(char)); #endif } /* * PATH_SEPARATOR. * Expand the path separator character. */ static void JX9_PATHSEP_Const(jx9_value *pVal, void *pUnused) { SXUNUSED(pUnused); #ifdef __WINNT__ jx9_value_string(pVal, ";", (int)sizeof(char)); #else jx9_value_string(pVal, ":", (int)sizeof(char)); #endif } #ifndef __WINNT__ #include #endif /* * __TIME__ * Expand the current time (GMT). */ static void JX9_TIME_Const(jx9_value *pVal, void *pUnused) { Sytm sTm; #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = gmtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif SXUNUSED(pUnused); /* cc warning */ /* Expand */ jx9_value_string_format(pVal, "%02d:%02d:%02d", sTm.tm_hour, sTm.tm_min, sTm.tm_sec); } /* * __DATE__ * Expand the current date in the ISO-8601 format. */ static void JX9_DATE_Const(jx9_value *pVal, void *pUnused) { Sytm sTm; #ifdef __WINNT__ SYSTEMTIME sOS; GetSystemTime(&sOS); SYSTEMTIME_TO_SYTM(&sOS, &sTm); #else struct tm *pTm; time_t t; time(&t); pTm = gmtime(&t); STRUCT_TM_TO_SYTM(pTm, &sTm); #endif SXUNUSED(pUnused); /* cc warning */ /* Expand */ jx9_value_string_format(pVal, "%04d-%02d-%02d", sTm.tm_year, sTm.tm_mon+1, sTm.tm_mday); } /* * __FILE__ * Path of the processed script. */ static void JX9_FILE_Const(jx9_value *pVal, void *pUserData) { jx9_vm *pVm = (jx9_vm *)pUserData; SyString *pFile; /* Peek the top entry */ pFile = (SyString *)SySetPeek(&pVm->aFiles); if( pFile == 0 ){ /* Expand the magic word: ":MEMORY:" */ jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1); }else{ jx9_value_string(pVal, pFile->zString, pFile->nByte); } } /* * __DIR__ * Directory holding the processed script. */ static void JX9_DIR_Const(jx9_value *pVal, void *pUserData) { jx9_vm *pVm = (jx9_vm *)pUserData; SyString *pFile; /* Peek the top entry */ pFile = (SyString *)SySetPeek(&pVm->aFiles); if( pFile == 0 ){ /* Expand the magic word: ":MEMORY:" */ jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1); }else{ if( pFile->nByte > 0 ){ const char *zDir; int nLen; zDir = jx9ExtractDirName(pFile->zString, (int)pFile->nByte, &nLen); jx9_value_string(pVal, zDir, nLen); }else{ /* Expand '.' as the current directory*/ jx9_value_string(pVal, ".", (int)sizeof(char)); } } } /* * E_ERROR * Expands 1 */ static void JX9_E_ERROR_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 1); SXUNUSED(pUserData); } /* * E_WARNING * Expands 2 */ static void JX9_E_WARNING_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 2); SXUNUSED(pUserData); } /* * E_PARSE * Expands 4 */ static void JX9_E_PARSE_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 4); SXUNUSED(pUserData); } /* * E_NOTICE * Expands 8 */ static void JX9_E_NOTICE_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 8); SXUNUSED(pUserData); } /* * CASE_LOWER * Expands 0. */ static void JX9_CASE_LOWER_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 0); SXUNUSED(pUserData); } /* * CASE_UPPER * Expands 1. */ static void JX9_CASE_UPPER_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 1); SXUNUSED(pUserData); } /* * STR_PAD_LEFT * Expands 0. */ static void JX9_STR_PAD_LEFT_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 0); SXUNUSED(pUserData); } /* * STR_PAD_RIGHT * Expands 1. */ static void JX9_STR_PAD_RIGHT_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 1); SXUNUSED(pUserData); } /* * STR_PAD_BOTH * Expands 2. */ static void JX9_STR_PAD_BOTH_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 2); SXUNUSED(pUserData); } /* * COUNT_NORMAL * Expands 0 */ static void JX9_COUNT_NORMAL_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 0); SXUNUSED(pUserData); } /* * COUNT_RECURSIVE * Expands 1. */ static void JX9_COUNT_RECURSIVE_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 1); SXUNUSED(pUserData); } /* * SORT_ASC * Expands 1. */ static void JX9_SORT_ASC_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 1); SXUNUSED(pUserData); } /* * SORT_DESC * Expands 2. */ static void JX9_SORT_DESC_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 2); SXUNUSED(pUserData); } /* * SORT_REGULAR * Expands 3. */ static void JX9_SORT_REG_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 3); SXUNUSED(pUserData); } /* * SORT_NUMERIC * Expands 4. */ static void JX9_SORT_NUMERIC_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 4); SXUNUSED(pUserData); } /* * SORT_STRING * Expands 5. */ static void JX9_SORT_STRING_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 5); SXUNUSED(pUserData); } /* * JX9_ROUND_HALF_UP * Expands 1. */ static void JX9_JX9_ROUND_HALF_UP_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 1); SXUNUSED(pUserData); } /* * SJX9_ROUND_HALF_DOWN * Expands 2. */ static void JX9_JX9_ROUND_HALF_DOWN_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 2); SXUNUSED(pUserData); } /* * JX9_ROUND_HALF_EVEN * Expands 3. */ static void JX9_JX9_ROUND_HALF_EVEN_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 3); SXUNUSED(pUserData); } /* * JX9_ROUND_HALF_ODD * Expands 4. */ static void JX9_JX9_ROUND_HALF_ODD_Const(jx9_value *pVal, void *pUserData) { jx9_value_int(pVal, 4); SXUNUSED(pUserData); } #ifdef JX9_ENABLE_MATH_FUNC /* * PI * Expand the value of pi. */ static void JX9_M_PI_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, JX9_PI); } /* * M_E * Expand 2.7182818284590452354 */ static void JX9_M_E_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 2.7182818284590452354); } /* * M_LOG2E * Expand 2.7182818284590452354 */ static void JX9_M_LOG2E_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 1.4426950408889634074); } /* * M_LOG10E * Expand 0.4342944819032518276 */ static void JX9_M_LOG10E_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 0.4342944819032518276); } /* * M_LN2 * Expand 0.69314718055994530942 */ static void JX9_M_LN2_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 0.69314718055994530942); } /* * M_LN10 * Expand 2.30258509299404568402 */ static void JX9_M_LN10_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 2.30258509299404568402); } /* * M_PI_2 * Expand 1.57079632679489661923 */ static void JX9_M_PI_2_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 1.57079632679489661923); } /* * M_PI_4 * Expand 0.78539816339744830962 */ static void JX9_M_PI_4_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 0.78539816339744830962); } /* * M_1_PI * Expand 0.31830988618379067154 */ static void JX9_M_1_PI_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 0.31830988618379067154); } /* * M_2_PI * Expand 0.63661977236758134308 */ static void JX9_M_2_PI_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 0.63661977236758134308); } /* * M_SQRTPI * Expand 1.77245385090551602729 */ static void JX9_M_SQRTPI_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 1.77245385090551602729); } /* * M_2_SQRTPI * Expand 1.12837916709551257390 */ static void JX9_M_2_SQRTPI_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 1.12837916709551257390); } /* * M_SQRT2 * Expand 1.41421356237309504880 */ static void JX9_M_SQRT2_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 1.41421356237309504880); } /* * M_SQRT3 * Expand 1.73205080756887729352 */ static void JX9_M_SQRT3_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 1.73205080756887729352); } /* * M_SQRT1_2 * Expand 0.70710678118654752440 */ static void JX9_M_SQRT1_2_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 0.70710678118654752440); } /* * M_LNPI * Expand 1.14472988584940017414 */ static void JX9_M_LNPI_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 1.14472988584940017414); } /* * M_EULER * Expand 0.57721566490153286061 */ static void JX9_M_EULER_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_double(pVal, 0.57721566490153286061); } #endif /* JX9_DISABLE_BUILTIN_MATH */ /* * DATE_ATOM * Expand Atom (example: 2005-08-15T15:52:01+00:00) */ static void JX9_DATE_ATOM_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/); } /* * DATE_COOKIE * HTTP Cookies (example: Monday, 15-Aug-05 15:52:01 UTC) */ static void JX9_DATE_COOKIE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/); } /* * DATE_ISO8601 * ISO-8601 (example: 2005-08-15T15:52:01+0000) */ static void JX9_DATE_ISO8601_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "Y-m-d\\TH:i:sO", -1/*Compute length automatically*/); } /* * DATE_RFC822 * RFC 822 (example: Mon, 15 Aug 05 15:52:01 +0000) */ static void JX9_DATE_RFC822_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/); } /* * DATE_RFC850 * RFC 850 (example: Monday, 15-Aug-05 15:52:01 UTC) */ static void JX9_DATE_RFC850_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/); } /* * DATE_RFC1036 * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) */ static void JX9_DATE_RFC1036_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/); } /* * DATE_RFC1123 * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) */ static void JX9_DATE_RFC1123_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); } /* * DATE_RFC2822 * RFC 2822 (Mon, 15 Aug 2005 15:52:01 +0000) */ static void JX9_DATE_RFC2822_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); } /* * DATE_RSS * RSS (Mon, 15 Aug 2005 15:52:01 +0000) */ static void JX9_DATE_RSS_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); } /* * DATE_W3C * World Wide Web Consortium (example: 2005-08-15T15:52:01+00:00) */ static void JX9_DATE_W3C_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/); } /* * ENT_COMPAT * Expand 0x01 (Must be a power of two) */ static void JX9_ENT_COMPAT_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x01); } /* * ENT_QUOTES * Expand 0x02 (Must be a power of two) */ static void JX9_ENT_QUOTES_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x02); } /* * ENT_NOQUOTES * Expand 0x04 (Must be a power of two) */ static void JX9_ENT_NOQUOTES_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x04); } /* * ENT_IGNORE * Expand 0x08 (Must be a power of two) */ static void JX9_ENT_IGNORE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x08); } /* * ENT_SUBSTITUTE * Expand 0x10 (Must be a power of two) */ static void JX9_ENT_SUBSTITUTE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x10); } /* * ENT_DISALLOWED * Expand 0x20 (Must be a power of two) */ static void JX9_ENT_DISALLOWED_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x20); } /* * ENT_HTML401 * Expand 0x40 (Must be a power of two) */ static void JX9_ENT_HTML401_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x40); } /* * ENT_XML1 * Expand 0x80 (Must be a power of two) */ static void JX9_ENT_XML1_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x80); } /* * ENT_XHTML * Expand 0x100 (Must be a power of two) */ static void JX9_ENT_XHTML_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x100); } /* * ENT_HTML5 * Expand 0x200 (Must be a power of two) */ static void JX9_ENT_HTML5_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x200); } /* * ISO-8859-1 * ISO_8859_1 * Expand 1 */ static void JX9_ISO88591_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * UTF-8 * UTF8 * Expand 2 */ static void JX9_UTF8_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * HTML_ENTITIES * Expand 1 */ static void JX9_HTML_ENTITIES_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * HTML_SPECIALCHARS * Expand 2 */ static void JX9_HTML_SPECIALCHARS_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 2); } /* * JX9_URL_SCHEME. * Expand 1 */ static void JX9_JX9_URL_SCHEME_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * JX9_URL_HOST. * Expand 2 */ static void JX9_JX9_URL_HOST_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 2); } /* * JX9_URL_PORT. * Expand 3 */ static void JX9_JX9_URL_PORT_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 3); } /* * JX9_URL_USER. * Expand 4 */ static void JX9_JX9_URL_USER_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 4); } /* * JX9_URL_PASS. * Expand 5 */ static void JX9_JX9_URL_PASS_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 5); } /* * JX9_URL_PATH. * Expand 6 */ static void JX9_JX9_URL_PATH_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 6); } /* * JX9_URL_QUERY. * Expand 7 */ static void JX9_JX9_URL_QUERY_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 7); } /* * JX9_URL_FRAGMENT. * Expand 8 */ static void JX9_JX9_URL_FRAGMENT_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 8); } /* * JX9_QUERY_RFC1738 * Expand 1 */ static void JX9_JX9_QUERY_RFC1738_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * JX9_QUERY_RFC3986 * Expand 1 */ static void JX9_JX9_QUERY_RFC3986_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 2); } /* * FNM_NOESCAPE * Expand 0x01 (Must be a power of two) */ static void JX9_FNM_NOESCAPE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x01); } /* * FNM_PATHNAME * Expand 0x02 (Must be a power of two) */ static void JX9_FNM_PATHNAME_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x02); } /* * FNM_PERIOD * Expand 0x04 (Must be a power of two) */ static void JX9_FNM_PERIOD_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x04); } /* * FNM_CASEFOLD * Expand 0x08 (Must be a power of two) */ static void JX9_FNM_CASEFOLD_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x08); } /* * PATHINFO_DIRNAME * Expand 1. */ static void JX9_PATHINFO_DIRNAME_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * PATHINFO_BASENAME * Expand 2. */ static void JX9_PATHINFO_BASENAME_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 2); } /* * PATHINFO_EXTENSION * Expand 3. */ static void JX9_PATHINFO_EXTENSION_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 3); } /* * PATHINFO_FILENAME * Expand 4. */ static void JX9_PATHINFO_FILENAME_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 4); } /* * ASSERT_ACTIVE. * Expand the value of JX9_ASSERT_ACTIVE defined in jx9Int.h */ static void JX9_ASSERT_ACTIVE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, JX9_ASSERT_DISABLE); } /* * ASSERT_WARNING. * Expand the value of JX9_ASSERT_WARNING defined in jx9Int.h */ static void JX9_ASSERT_WARNING_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, JX9_ASSERT_WARNING); } /* * ASSERT_BAIL. * Expand the value of JX9_ASSERT_BAIL defined in jx9Int.h */ static void JX9_ASSERT_BAIL_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, JX9_ASSERT_BAIL); } /* * ASSERT_QUIET_EVAL. * Expand the value of JX9_ASSERT_QUIET_EVAL defined in jx9Int.h */ static void JX9_ASSERT_QUIET_EVAL_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, JX9_ASSERT_QUIET_EVAL); } /* * ASSERT_CALLBACK. * Expand the value of JX9_ASSERT_CALLBACK defined in jx9Int.h */ static void JX9_ASSERT_CALLBACK_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, JX9_ASSERT_CALLBACK); } /* * SEEK_SET. * Expand 0 */ static void JX9_SEEK_SET_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0); } /* * SEEK_CUR. * Expand 1 */ static void JX9_SEEK_CUR_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * SEEK_END. * Expand 2 */ static void JX9_SEEK_END_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 2); } /* * LOCK_SH. * Expand 2 */ static void JX9_LOCK_SH_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * LOCK_NB. * Expand 5 */ static void JX9_LOCK_NB_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 5); } /* * LOCK_EX. * Expand 0x01 (MUST BE A POWER OF TWO) */ static void JX9_LOCK_EX_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x01); } /* * LOCK_UN. * Expand 0 */ static void JX9_LOCK_UN_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0); } /* * FILE_USE_INC_PATH * Expand 0x01 (Must be a power of two) */ static void JX9_FILE_USE_INCLUDE_PATH_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x1); } /* * FILE_IGN_NL * Expand 0x02 (Must be a power of two) */ static void JX9_FILE_IGNORE_NEW_LINES_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x2); } /* * FILE_SKIP_EL * Expand 0x04 (Must be a power of two) */ static void JX9_FILE_SKIP_EMPTY_LINES_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x4); } /* * FILE_APPEND * Expand 0x08 (Must be a power of two) */ static void JX9_FILE_APPEND_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x08); } /* * SCANDIR_SORT_ASCENDING * Expand 0 */ static void JX9_SCANDIR_SORT_ASCENDING_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0); } /* * SCANDIR_SORT_DESCENDING * Expand 1 */ static void JX9_SCANDIR_SORT_DESCENDING_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * SCANDIR_SORT_NONE * Expand 2 */ static void JX9_SCANDIR_SORT_NONE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 2); } /* * GLOB_MARK * Expand 0x01 (must be a power of two) */ static void JX9_GLOB_MARK_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x01); } /* * GLOB_NOSORT * Expand 0x02 (must be a power of two) */ static void JX9_GLOB_NOSORT_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x02); } /* * GLOB_NOCHECK * Expand 0x04 (must be a power of two) */ static void JX9_GLOB_NOCHECK_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x04); } /* * GLOB_NOESCAPE * Expand 0x08 (must be a power of two) */ static void JX9_GLOB_NOESCAPE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x08); } /* * GLOB_BRACE * Expand 0x10 (must be a power of two) */ static void JX9_GLOB_BRACE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x10); } /* * GLOB_ONLYDIR * Expand 0x20 (must be a power of two) */ static void JX9_GLOB_ONLYDIR_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x20); } /* * GLOB_ERR * Expand 0x40 (must be a power of two) */ static void JX9_GLOB_ERR_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x40); } /* * STDIN * Expand the STDIN handle as a resource. */ static void JX9_STDIN_Const(jx9_value *pVal, void *pUserData) { jx9_vm *pVm = (jx9_vm *)pUserData; void *pResource; pResource = jx9ExportStdin(pVm); jx9_value_resource(pVal, pResource); } /* * STDOUT * Expand the STDOUT handle as a resource. */ static void JX9_STDOUT_Const(jx9_value *pVal, void *pUserData) { jx9_vm *pVm = (jx9_vm *)pUserData; void *pResource; pResource = jx9ExportStdout(pVm); jx9_value_resource(pVal, pResource); } /* * STDERR * Expand the STDERR handle as a resource. */ static void JX9_STDERR_Const(jx9_value *pVal, void *pUserData) { jx9_vm *pVm = (jx9_vm *)pUserData; void *pResource; pResource = jx9ExportStderr(pVm); jx9_value_resource(pVal, pResource); } /* * INI_SCANNER_NORMAL * Expand 1 */ static void JX9_INI_SCANNER_NORMAL_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 1); } /* * INI_SCANNER_RAW * Expand 2 */ static void JX9_INI_SCANNER_RAW_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 2); } /* * EXTR_OVERWRITE * Expand 0x01 (Must be a power of two) */ static void JX9_EXTR_OVERWRITE_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x1); } /* * EXTR_SKIP * Expand 0x02 (Must be a power of two) */ static void JX9_EXTR_SKIP_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x2); } /* * EXTR_PREFIX_SAME * Expand 0x04 (Must be a power of two) */ static void JX9_EXTR_PREFIX_SAME_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x4); } /* * EXTR_PREFIX_ALL * Expand 0x08 (Must be a power of two) */ static void JX9_EXTR_PREFIX_ALL_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x8); } /* * EXTR_PREFIX_INVALID * Expand 0x10 (Must be a power of two) */ static void JX9_EXTR_PREFIX_INVALID_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x10); } /* * EXTR_IF_EXISTS * Expand 0x20 (Must be a power of two) */ static void JX9_EXTR_IF_EXISTS_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x20); } /* * EXTR_PREFIX_IF_EXISTS * Expand 0x40 (Must be a power of two) */ static void JX9_EXTR_PREFIX_IF_EXISTS_Const(jx9_value *pVal, void *pUserData) { SXUNUSED(pUserData); /* cc warning */ jx9_value_int(pVal, 0x40); } /* * Table of built-in constants. */ static const jx9_builtin_constant aBuiltIn[] = { {"JX9_VERSION", JX9_VER_Const }, {"JX9_ENGINE", JX9_VER_Const }, {"__JX9__", JX9_VER_Const }, {"JX9_OS", JX9_OS_Const }, {"__OS__", JX9_OS_Const }, {"JX9_EOL", JX9_EOL_Const }, {"JX9_INT_MAX", JX9_INTMAX_Const }, {"MAXINT", JX9_INTMAX_Const }, {"JX9_INT_SIZE", JX9_INTSIZE_Const }, {"PATH_SEPARATOR", JX9_PATHSEP_Const }, {"DIRECTORY_SEPARATOR", JX9_DIRSEP_Const }, {"DIR_SEP", JX9_DIRSEP_Const }, {"__TIME__", JX9_TIME_Const }, {"__DATE__", JX9_DATE_Const }, {"__FILE__", JX9_FILE_Const }, {"__DIR__", JX9_DIR_Const }, {"E_ERROR", JX9_E_ERROR_Const }, {"E_WARNING", JX9_E_WARNING_Const}, {"E_PARSE", JX9_E_PARSE_Const }, {"E_NOTICE", JX9_E_NOTICE_Const }, {"CASE_LOWER", JX9_CASE_LOWER_Const }, {"CASE_UPPER", JX9_CASE_UPPER_Const }, {"STR_PAD_LEFT", JX9_STR_PAD_LEFT_Const }, {"STR_PAD_RIGHT", JX9_STR_PAD_RIGHT_Const}, {"STR_PAD_BOTH", JX9_STR_PAD_BOTH_Const }, {"COUNT_NORMAL", JX9_COUNT_NORMAL_Const }, {"COUNT_RECURSIVE", JX9_COUNT_RECURSIVE_Const }, {"SORT_ASC", JX9_SORT_ASC_Const }, {"SORT_DESC", JX9_SORT_DESC_Const }, {"SORT_REGULAR", JX9_SORT_REG_Const }, {"SORT_NUMERIC", JX9_SORT_NUMERIC_Const }, {"SORT_STRING", JX9_SORT_STRING_Const }, {"JX9_ROUND_HALF_DOWN", JX9_JX9_ROUND_HALF_DOWN_Const }, {"JX9_ROUND_HALF_EVEN", JX9_JX9_ROUND_HALF_EVEN_Const }, {"JX9_ROUND_HALF_UP", JX9_JX9_ROUND_HALF_UP_Const }, {"JX9_ROUND_HALF_ODD", JX9_JX9_ROUND_HALF_ODD_Const }, #ifdef JX9_ENABLE_MATH_FUNC {"PI", JX9_M_PI_Const }, {"M_E", JX9_M_E_Const }, {"M_LOG2E", JX9_M_LOG2E_Const }, {"M_LOG10E", JX9_M_LOG10E_Const }, {"M_LN2", JX9_M_LN2_Const }, {"M_LN10", JX9_M_LN10_Const }, {"M_PI_2", JX9_M_PI_2_Const }, {"M_PI_4", JX9_M_PI_4_Const }, {"M_1_PI", JX9_M_1_PI_Const }, {"M_2_PI", JX9_M_2_PI_Const }, {"M_SQRTPI", JX9_M_SQRTPI_Const }, {"M_2_SQRTPI", JX9_M_2_SQRTPI_Const }, {"M_SQRT2", JX9_M_SQRT2_Const }, {"M_SQRT3", JX9_M_SQRT3_Const }, {"M_SQRT1_2", JX9_M_SQRT1_2_Const }, {"M_LNPI", JX9_M_LNPI_Const }, {"M_EULER", JX9_M_EULER_Const }, #endif /* JX9_ENABLE_MATH_FUNC */ {"DATE_ATOM", JX9_DATE_ATOM_Const }, {"DATE_COOKIE", JX9_DATE_COOKIE_Const }, {"DATE_ISO8601", JX9_DATE_ISO8601_Const }, {"DATE_RFC822", JX9_DATE_RFC822_Const }, {"DATE_RFC850", JX9_DATE_RFC850_Const }, {"DATE_RFC1036", JX9_DATE_RFC1036_Const }, {"DATE_RFC1123", JX9_DATE_RFC1123_Const }, {"DATE_RFC2822", JX9_DATE_RFC2822_Const }, {"DATE_RFC3339", JX9_DATE_ATOM_Const }, {"DATE_RSS", JX9_DATE_RSS_Const }, {"DATE_W3C", JX9_DATE_W3C_Const }, {"ENT_COMPAT", JX9_ENT_COMPAT_Const }, {"ENT_QUOTES", JX9_ENT_QUOTES_Const }, {"ENT_NOQUOTES", JX9_ENT_NOQUOTES_Const }, {"ENT_IGNORE", JX9_ENT_IGNORE_Const }, {"ENT_SUBSTITUTE", JX9_ENT_SUBSTITUTE_Const}, {"ENT_DISALLOWED", JX9_ENT_DISALLOWED_Const}, {"ENT_HTML401", JX9_ENT_HTML401_Const }, {"ENT_XML1", JX9_ENT_XML1_Const }, {"ENT_XHTML", JX9_ENT_XHTML_Const }, {"ENT_HTML5", JX9_ENT_HTML5_Const }, {"ISO-8859-1", JX9_ISO88591_Const }, {"ISO_8859_1", JX9_ISO88591_Const }, {"UTF-8", JX9_UTF8_Const }, {"UTF8", JX9_UTF8_Const }, {"HTML_ENTITIES", JX9_HTML_ENTITIES_Const}, {"HTML_SPECIALCHARS", JX9_HTML_SPECIALCHARS_Const }, {"JX9_URL_SCHEME", JX9_JX9_URL_SCHEME_Const}, {"JX9_URL_HOST", JX9_JX9_URL_HOST_Const}, {"JX9_URL_PORT", JX9_JX9_URL_PORT_Const}, {"JX9_URL_USER", JX9_JX9_URL_USER_Const}, {"JX9_URL_PASS", JX9_JX9_URL_PASS_Const}, {"JX9_URL_PATH", JX9_JX9_URL_PATH_Const}, {"JX9_URL_QUERY", JX9_JX9_URL_QUERY_Const}, {"JX9_URL_FRAGMENT", JX9_JX9_URL_FRAGMENT_Const}, {"JX9_QUERY_RFC1738", JX9_JX9_QUERY_RFC1738_Const}, {"JX9_QUERY_RFC3986", JX9_JX9_QUERY_RFC3986_Const}, {"FNM_NOESCAPE", JX9_FNM_NOESCAPE_Const }, {"FNM_PATHNAME", JX9_FNM_PATHNAME_Const }, {"FNM_PERIOD", JX9_FNM_PERIOD_Const }, {"FNM_CASEFOLD", JX9_FNM_CASEFOLD_Const }, {"PATHINFO_DIRNAME", JX9_PATHINFO_DIRNAME_Const }, {"PATHINFO_BASENAME", JX9_PATHINFO_BASENAME_Const }, {"PATHINFO_EXTENSION", JX9_PATHINFO_EXTENSION_Const}, {"PATHINFO_FILENAME", JX9_PATHINFO_FILENAME_Const }, {"ASSERT_ACTIVE", JX9_ASSERT_ACTIVE_Const }, {"ASSERT_WARNING", JX9_ASSERT_WARNING_Const }, {"ASSERT_BAIL", JX9_ASSERT_BAIL_Const }, {"ASSERT_QUIET_EVAL", JX9_ASSERT_QUIET_EVAL_Const }, {"ASSERT_CALLBACK", JX9_ASSERT_CALLBACK_Const }, {"SEEK_SET", JX9_SEEK_SET_Const }, {"SEEK_CUR", JX9_SEEK_CUR_Const }, {"SEEK_END", JX9_SEEK_END_Const }, {"LOCK_EX", JX9_LOCK_EX_Const }, {"LOCK_SH", JX9_LOCK_SH_Const }, {"LOCK_NB", JX9_LOCK_NB_Const }, {"LOCK_UN", JX9_LOCK_UN_Const }, {"FILE_USE_INC_PATH", JX9_FILE_USE_INCLUDE_PATH_Const}, {"FILE_IGN_NL", JX9_FILE_IGNORE_NEW_LINES_Const}, {"FILE_SKIP_EL", JX9_FILE_SKIP_EMPTY_LINES_Const}, {"FILE_APPEND", JX9_FILE_APPEND_Const }, {"SCANDIR_SORT_ASC", JX9_SCANDIR_SORT_ASCENDING_Const }, {"SCANDIR_SORT_DESC", JX9_SCANDIR_SORT_DESCENDING_Const }, {"SCANDIR_SORT_NONE", JX9_SCANDIR_SORT_NONE_Const }, {"GLOB_MARK", JX9_GLOB_MARK_Const }, {"GLOB_NOSORT", JX9_GLOB_NOSORT_Const }, {"GLOB_NOCHECK", JX9_GLOB_NOCHECK_Const }, {"GLOB_NOESCAPE", JX9_GLOB_NOESCAPE_Const}, {"GLOB_BRACE", JX9_GLOB_BRACE_Const }, {"GLOB_ONLYDIR", JX9_GLOB_ONLYDIR_Const }, {"GLOB_ERR", JX9_GLOB_ERR_Const }, {"STDIN", JX9_STDIN_Const }, {"stdin", JX9_STDIN_Const }, {"STDOUT", JX9_STDOUT_Const }, {"stdout", JX9_STDOUT_Const }, {"STDERR", JX9_STDERR_Const }, {"stderr", JX9_STDERR_Const }, {"INI_SCANNER_NORMAL", JX9_INI_SCANNER_NORMAL_Const }, {"INI_SCANNER_RAW", JX9_INI_SCANNER_RAW_Const }, {"EXTR_OVERWRITE", JX9_EXTR_OVERWRITE_Const }, {"EXTR_SKIP", JX9_EXTR_SKIP_Const }, {"EXTR_PREFIX_SAME", JX9_EXTR_PREFIX_SAME_Const }, {"EXTR_PREFIX_ALL", JX9_EXTR_PREFIX_ALL_Const }, {"EXTR_PREFIX_INVALID", JX9_EXTR_PREFIX_INVALID_Const }, {"EXTR_IF_EXISTS", JX9_EXTR_IF_EXISTS_Const }, {"EXTR_PREFIX_IF_EXISTS", JX9_EXTR_PREFIX_IF_EXISTS_Const} }; /* * Register the built-in constants defined above. */ JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm) { sxu32 n; /* * Note that all built-in constants have access to the jx9 virtual machine * that trigger the constant invocation as their private data. */ for( n = 0 ; n < SX_ARRAYSIZE(aBuiltIn) ; ++n ){ jx9_create_constant(&(*pVm), aBuiltIn[n].zName, aBuiltIn[n].xExpand, &(*pVm)); } } /* * ---------------------------------------------------------- * File: jx9_hashmap.c * MD5: 4e93d15cd37e6093e25d8ede3064e210 * ---------------------------------------------------------- */ /* * 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: hashmap.c v2.6 Win7 2012-12-11 00:50 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* This file implement generic hashmaps used to represent JSON arrays and objects */ /* Allowed node types */ #define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */ #define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */ /* * Default hash function for int [i.e; 64-bit integer] keys. */ static sxu32 IntHash(sxi64 iKey) { return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8)); } /* * Default hash function for string/BLOB keys. */ static sxu32 BinHash(const void *pSrc, sxu32 nLen) { register unsigned char *zIn = (unsigned char *)pSrc; unsigned char *zEnd; sxu32 nH = 5381; zEnd = &zIn[nLen]; for(;;){ if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; } return nH; } /* * Return the total number of entries in a given hashmap. * If bRecurisve is set to TRUE then recurse on hashmap entries. * If the nesting limit is reached, this function abort immediately. */ static sxi64 HashmapCount(jx9_hashmap *pMap, int bRecursive, int iRecCount) { sxi64 iCount = 0; if( !bRecursive ){ iCount = pMap->nEntry; }else{ /* Recursive hashmap walk */ jx9_hashmap_node *pEntry = pMap->pLast; jx9_value *pElem; sxu32 n = 0; for(;;){ if( n >= pMap->nEntry ){ break; } /* Point to the element value */ pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pEntry->nValIdx); if( pElem ){ if( pElem->iFlags & MEMOBJ_HASHMAP ){ if( iRecCount > 31 ){ /* Nesting limit reached */ return iCount; } /* Recurse */ iRecCount++; iCount += HashmapCount((jx9_hashmap *)pElem->x.pOther, TRUE, iRecCount); iRecCount--; } } /* Point to the next entry */ pEntry = pEntry->pNext; ++n; } /* Update count */ iCount += pMap->nEntry; } return iCount; } /* * Allocate a new hashmap node with a 64-bit integer key. * If something goes wrong [i.e: out of memory], this function return NULL. * Otherwise a fresh [jx9_hashmap_node] instance is returned. */ static jx9_hashmap_node * HashmapNewIntNode(jx9_hashmap *pMap, sxi64 iKey, sxu32 nHash, sxu32 nValIdx) { jx9_hashmap_node *pNode; /* Allocate a new node */ pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); if( pNode == 0 ){ return 0; } /* Zero the stucture */ SyZero(pNode, sizeof(jx9_hashmap_node)); /* Fill in the structure */ pNode->pMap = &(*pMap); pNode->iType = HASHMAP_INT_NODE; pNode->nHash = nHash; pNode->xKey.iKey = iKey; pNode->nValIdx = nValIdx; return pNode; } /* * Allocate a new hashmap node with a BLOB key. * If something goes wrong [i.e: out of memory], this function return NULL. * Otherwise a fresh [jx9_hashmap_node] instance is returned. */ static jx9_hashmap_node * HashmapNewBlobNode(jx9_hashmap *pMap, const void *pKey, sxu32 nKeyLen, sxu32 nHash, sxu32 nValIdx) { jx9_hashmap_node *pNode; /* Allocate a new node */ pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); if( pNode == 0 ){ return 0; } /* Zero the stucture */ SyZero(pNode, sizeof(jx9_hashmap_node)); /* Fill in the structure */ pNode->pMap = &(*pMap); pNode->iType = HASHMAP_BLOB_NODE; pNode->nHash = nHash; SyBlobInit(&pNode->xKey.sKey, &pMap->pVm->sAllocator); SyBlobAppend(&pNode->xKey.sKey, pKey, nKeyLen); pNode->nValIdx = nValIdx; return pNode; } /* * link a hashmap node to the given bucket index (last argument to this function). */ static void HashmapNodeLink(jx9_hashmap *pMap, jx9_hashmap_node *pNode, sxu32 nBucketIdx) { /* Link */ if( pMap->apBucket[nBucketIdx] != 0 ){ pNode->pNextCollide = pMap->apBucket[nBucketIdx]; pMap->apBucket[nBucketIdx]->pPrevCollide = pNode; } pMap->apBucket[nBucketIdx] = pNode; /* Link to the map list */ if( pMap->pFirst == 0 ){ pMap->pFirst = pMap->pLast = pNode; /* Point to the first inserted node */ pMap->pCur = pNode; }else{ MACRO_LD_PUSH(pMap->pLast, pNode); } ++pMap->nEntry; } /* * Unlink a node from the hashmap. * If the node count reaches zero then release the whole hash-bucket. */ static void jx9HashmapUnlinkNode(jx9_hashmap_node *pNode) { jx9_hashmap *pMap = pNode->pMap; jx9_vm *pVm = pMap->pVm; /* Unlink from the corresponding bucket */ if( pNode->pPrevCollide == 0 ){ pMap->apBucket[pNode->nHash & (pMap->nSize - 1)] = pNode->pNextCollide; }else{ pNode->pPrevCollide->pNextCollide = pNode->pNextCollide; } if( pNode->pNextCollide ){ pNode->pNextCollide->pPrevCollide = pNode->pPrevCollide; } if( pMap->pFirst == pNode ){ pMap->pFirst = pNode->pPrev; } if( pMap->pCur == pNode ){ /* Advance the node cursor */ pMap->pCur = pMap->pCur->pPrev; /* Reverse link */ } /* Unlink from the map list */ MACRO_LD_REMOVE(pMap->pLast, pNode); /* Restore to the free list */ jx9VmUnsetMemObj(pVm, pNode->nValIdx); if( pNode->iType == HASHMAP_BLOB_NODE ){ SyBlobRelease(&pNode->xKey.sKey); } SyMemBackendPoolFree(&pVm->sAllocator, pNode); pMap->nEntry--; if( pMap->nEntry < 1 ){ /* Free the hash-bucket */ SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); pMap->apBucket = 0; pMap->nSize = 0; pMap->pFirst = pMap->pLast = pMap->pCur = 0; } } #define HASHMAP_FILL_FACTOR 3 /* * Grow the hash-table and rehash all entries. */ static sxi32 HashmapGrowBucket(jx9_hashmap *pMap) { if( pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR ){ jx9_hashmap_node **apOld = pMap->apBucket; jx9_hashmap_node *pEntry, **apNew; sxu32 nNew = pMap->nSize << 1; sxu32 nBucket; sxu32 n; if( nNew < 1 ){ nNew = 16; } /* Allocate a new bucket */ apNew = (jx9_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator, nNew * sizeof(jx9_hashmap_node *)); if( apNew == 0 ){ if( pMap->nSize < 1 ){ return SXERR_MEM; /* Fatal */ } /* Not so fatal here, simply a performance hit */ return SXRET_OK; } /* Zero the table */ SyZero((void *)apNew, nNew * sizeof(jx9_hashmap_node *)); /* Reflect the change */ pMap->apBucket = apNew; pMap->nSize = nNew; if( apOld == 0 ){ /* First allocated table [i.e: no entry], return immediately */ return SXRET_OK; } /* Rehash old entries */ pEntry = pMap->pFirst; n = 0; for( ;; ){ if( n >= pMap->nEntry ){ break; } /* Clear the old collision link */ pEntry->pNextCollide = pEntry->pPrevCollide = 0; /* Link to the new bucket */ nBucket = pEntry->nHash & (nNew - 1); if( pMap->apBucket[nBucket] != 0 ){ pEntry->pNextCollide = pMap->apBucket[nBucket]; pMap->apBucket[nBucket]->pPrevCollide = pEntry; } pMap->apBucket[nBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n++; } /* Free the old table */ SyMemBackendFree(&pMap->pVm->sAllocator, (void *)apOld); } return SXRET_OK; } /* * Insert a 64-bit integer key and it's associated value (if any) in the given * hashmap. */ static sxi32 HashmapInsertIntKey(jx9_hashmap *pMap,sxi64 iKey,jx9_value *pValue) { jx9_hashmap_node *pNode; jx9_value *pObj; sxu32 nIdx; sxu32 nHash; sxi32 rc; /* Reserve a jx9_value for the value */ pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); if( pObj == 0 ){ return SXERR_MEM; } if( pValue ){ /* Duplicate the value */ jx9MemObjStore(pValue, pObj); } /* Hash the key */ nHash = pMap->xIntHash(iKey); /* Allocate a new int node */ pNode = HashmapNewIntNode(&(*pMap), iKey, nHash, nIdx); if( pNode == 0 ){ return SXERR_MEM; } /* Make sure the bucket is big enough to hold the new entry */ rc = HashmapGrowBucket(&(*pMap)); if( rc != SXRET_OK ){ SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); return rc; } /* Perform the insertion */ HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); /* All done */ return SXRET_OK; } /* * Insert a BLOB key and it's associated value (if any) in the given * hashmap. */ static sxi32 HashmapInsertBlobKey(jx9_hashmap *pMap,const void *pKey,sxu32 nKeyLen,jx9_value *pValue) { jx9_hashmap_node *pNode; jx9_value *pObj; sxu32 nHash; sxu32 nIdx; sxi32 rc; /* Reserve a jx9_value for the value */ pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); if( pObj == 0 ){ return SXERR_MEM; } if( pValue ){ /* Duplicate the value */ jx9MemObjStore(pValue, pObj); } /* Hash the key */ nHash = pMap->xBlobHash(pKey, nKeyLen); /* Allocate a new blob node */ pNode = HashmapNewBlobNode(&(*pMap), pKey, nKeyLen, nHash, nIdx); if( pNode == 0 ){ return SXERR_MEM; } /* Make sure the bucket is big enough to hold the new entry */ rc = HashmapGrowBucket(&(*pMap)); if( rc != SXRET_OK ){ SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); return rc; } /* Perform the insertion */ HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); /* All done */ return SXRET_OK; } /* * Check if a given 64-bit integer key exists in the given hashmap. * Write a pointer to the target node on success. Otherwise * SXERR_NOTFOUND is returned on failure. */ static sxi32 HashmapLookupIntKey( jx9_hashmap *pMap, /* Target hashmap */ sxi64 iKey, /* lookup key */ jx9_hashmap_node **ppNode /* OUT: target node on success */ ) { jx9_hashmap_node *pNode; sxu32 nHash; if( pMap->nEntry < 1 ){ /* Don't bother hashing, there is no entry anyway */ return SXERR_NOTFOUND; } /* Hash the key first */ nHash = pMap->xIntHash(iKey); /* Point to the appropriate bucket */ pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; /* Perform the lookup */ for(;;){ if( pNode == 0 ){ break; } if( pNode->iType == HASHMAP_INT_NODE && pNode->nHash == nHash && pNode->xKey.iKey == iKey ){ /* Node found */ if( ppNode ){ *ppNode = pNode; } return SXRET_OK; } /* Follow the collision link */ pNode = pNode->pNextCollide; } /* No such entry */ return SXERR_NOTFOUND; } /* * Check if a given BLOB key exists in the given hashmap. * Write a pointer to the target node on success. Otherwise * SXERR_NOTFOUND is returned on failure. */ static sxi32 HashmapLookupBlobKey( jx9_hashmap *pMap, /* Target hashmap */ const void *pKey, /* Lookup key */ sxu32 nKeyLen, /* Key length in bytes */ jx9_hashmap_node **ppNode /* OUT: target node on success */ ) { jx9_hashmap_node *pNode; sxu32 nHash; if( pMap->nEntry < 1 ){ /* Don't bother hashing, there is no entry anyway */ return SXERR_NOTFOUND; } /* Hash the key first */ nHash = pMap->xBlobHash(pKey, nKeyLen); /* Point to the appropriate bucket */ pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; /* Perform the lookup */ for(;;){ if( pNode == 0 ){ break; } if( pNode->iType == HASHMAP_BLOB_NODE && pNode->nHash == nHash && SyBlobLength(&pNode->xKey.sKey) == nKeyLen && SyMemcmp(SyBlobData(&pNode->xKey.sKey), pKey, nKeyLen) == 0 ){ /* Node found */ if( ppNode ){ *ppNode = pNode; } return SXRET_OK; } /* Follow the collision link */ pNode = pNode->pNextCollide; } /* No such entry */ return SXERR_NOTFOUND; } /* * Check if the given BLOB key looks like a decimal number. * Retrurn TRUE on success.FALSE otherwise. */ static int HashmapIsIntKey(SyBlob *pKey) { const char *zIn = (const char *)SyBlobData(pKey); const char *zEnd = &zIn[SyBlobLength(pKey)]; if( (int)(zEnd-zIn) > 1 && zIn[0] == '0' ){ /* Octal not decimal number */ return FALSE; } if( (zIn[0] == '-' || zIn[0] == '+') && &zIn[1] < zEnd ){ zIn++; } for(;;){ if( zIn >= zEnd ){ return TRUE; } if( (unsigned char)zIn[0] >= 0xc0 /* UTF-8 stream */ || !SyisDigit(zIn[0]) ){ break; } zIn++; } /* Key does not look like a decimal number */ return FALSE; } /* * Check if a given key exists in the given hashmap. * Write a pointer to the target node on success. * Otherwise SXERR_NOTFOUND is returned on failure. */ static sxi32 HashmapLookup( jx9_hashmap *pMap, /* Target hashmap */ jx9_value *pKey, /* Lookup key */ jx9_hashmap_node **ppNode /* OUT: target node on success */ ) { jx9_hashmap_node *pNode = 0; /* cc -O6 warning */ sxi32 rc; if( pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES) ){ if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ /* Force a string cast */ jx9MemObjToString(&(*pKey)); } if( SyBlobLength(&pKey->sBlob) > 0 ){ /* Perform a blob lookup */ rc = HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode); goto result; } } /* Perform an int lookup */ if((pKey->iFlags & MEMOBJ_INT) == 0 ){ /* Force an integer cast */ jx9MemObjToInteger(pKey); } /* Perform an int lookup */ rc = HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode); result: if( rc == SXRET_OK ){ /* Node found */ if( ppNode ){ *ppNode = pNode; } return SXRET_OK; } /* No such entry */ return SXERR_NOTFOUND; } /* * Insert a given key and it's associated value (if any) in the given * hashmap. * If a node with the given key already exists in the database * then this function overwrite the old value. */ static sxi32 HashmapInsert( jx9_hashmap *pMap, /* Target hashmap */ jx9_value *pKey, /* Lookup key */ jx9_value *pVal /* Node value */ ) { jx9_hashmap_node *pNode = 0; sxi32 rc = SXRET_OK; if( pMap->nEntry < 1 && pKey && (pKey->iFlags & MEMOBJ_STRING) ){ pMap->iFlags |= HASHMAP_JSON_OBJECT; } if( pKey && (pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES)) ){ if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ /* Force a string cast */ jx9MemObjToString(&(*pKey)); } if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){ if(SyBlobLength(&pKey->sBlob) < 1){ /* Automatic index assign */ pKey = 0; } goto IntKey; } if( SXRET_OK == HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode) ){ /* Overwrite the old value */ jx9_value *pElem; pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); if( pElem ){ if( pVal ){ jx9MemObjStore(pVal, pElem); }else{ /* Nullify the entry */ jx9MemObjToNull(pElem); } } return SXRET_OK; } /* Perform a blob-key insertion */ rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&(*pVal)); return rc; } IntKey: if( pKey ){ if((pKey->iFlags & MEMOBJ_INT) == 0 ){ /* Force an integer cast */ jx9MemObjToInteger(pKey); } if( SXRET_OK == HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode) ){ /* Overwrite the old value */ jx9_value *pElem; pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); if( pElem ){ if( pVal ){ jx9MemObjStore(pVal, pElem); }else{ /* Nullify the entry */ jx9MemObjToNull(pElem); } } return SXRET_OK; } /* Perform a 64-bit-int-key insertion */ rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, &(*pVal)); if( rc == SXRET_OK ){ if( pKey->x.iVal >= pMap->iNextIdx ){ /* Increment the automatic index */ pMap->iNextIdx = pKey->x.iVal + 1; /* Make sure the automatic index is not reserved */ while( SXRET_OK == HashmapLookupIntKey(&(*pMap), pMap->iNextIdx, 0) ){ pMap->iNextIdx++; } } } }else{ /* Assign an automatic index */ rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,&(*pVal)); if( rc == SXRET_OK ){ ++pMap->iNextIdx; } } /* Insertion result */ return rc; } /* * Extract node value. */ static jx9_value * HashmapExtractNodeValue(jx9_hashmap_node *pNode) { /* Point to the desired object */ jx9_value *pObj; pObj = (jx9_value *)SySetAt(&pNode->pMap->pVm->aMemObj, pNode->nValIdx); return pObj; } /* * Insert a node in the given hashmap. * If a node with the given key already exists in the database * then this function overwrite the old value. */ static sxi32 HashmapInsertNode(jx9_hashmap *pMap, jx9_hashmap_node *pNode, int bPreserve) { jx9_value *pObj; sxi32 rc; /* Extract the node value */ pObj = HashmapExtractNodeValue(&(*pNode)); if( pObj == 0 ){ return SXERR_EMPTY; } /* Preserve key */ if( pNode->iType == HASHMAP_INT_NODE){ /* Int64 key */ if( !bPreserve ){ /* Assign an automatic index */ rc = HashmapInsert(&(*pMap), 0, pObj); }else{ rc = HashmapInsertIntKey(&(*pMap), pNode->xKey.iKey, pObj); } }else{ /* Blob key */ rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey), pObj); } return rc; } /* * Compare two node values. * Return 0 if the node values are equals, > 0 if pLeft is greater than pRight * or < 0 if pRight is greater than pLeft. * For a full description on jx9_values comparison, refer to the implementation * of the [jx9MemObjCmp()] function defined in memobj.c or the official * documenation. */ static sxi32 HashmapNodeCmp(jx9_hashmap_node *pLeft, jx9_hashmap_node *pRight, int bStrict) { jx9_value sObj1, sObj2; sxi32 rc; if( pLeft == pRight ){ /* * Same node.Refer to the sort() implementation defined * below for more information on this sceanario. */ return 0; } /* Do the comparison */ jx9MemObjInit(pLeft->pMap->pVm, &sObj1); jx9MemObjInit(pLeft->pMap->pVm, &sObj2); jx9HashmapExtractNodeValue(pLeft, &sObj1, FALSE); jx9HashmapExtractNodeValue(pRight, &sObj2, FALSE); rc = jx9MemObjCmp(&sObj1, &sObj2, bStrict, 0); jx9MemObjRelease(&sObj1); jx9MemObjRelease(&sObj2); return rc; } /* * Rehash a node with a 64-bit integer key. * Refer to [merge_sort(), array_shift()] implementations for more information. */ static void HashmapRehashIntNode(jx9_hashmap_node *pEntry) { jx9_hashmap *pMap = pEntry->pMap; sxu32 nBucket; /* Remove old collision links */ if( pEntry->pPrevCollide ){ pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; }else{ pMap->apBucket[pEntry->nHash & (pMap->nSize - 1)] = pEntry->pNextCollide; } if( pEntry->pNextCollide ){ pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; } pEntry->pNextCollide = pEntry->pPrevCollide = 0; /* Compute the new hash */ pEntry->nHash = pMap->xIntHash(pMap->iNextIdx); pEntry->xKey.iKey = pMap->iNextIdx; nBucket = pEntry->nHash & (pMap->nSize - 1); /* Link to the new bucket */ pEntry->pNextCollide = pMap->apBucket[nBucket]; if( pMap->apBucket[nBucket] ){ pMap->apBucket[nBucket]->pPrevCollide = pEntry; } pEntry->pNextCollide = pMap->apBucket[nBucket]; pMap->apBucket[nBucket] = pEntry; /* Increment the automatic index */ pMap->iNextIdx++; } /* * Perform a linear search on a given hashmap. * Write a pointer to the target node on success. * Otherwise SXERR_NOTFOUND is returned on failure. * Refer to [array_intersect(), array_diff(), in_array(), ...] implementations * for more information. */ static int HashmapFindValue( jx9_hashmap *pMap, /* Target hashmap */ jx9_value *pNeedle, /* Lookup key */ jx9_hashmap_node **ppNode, /* OUT: target node on success */ int bStrict /* TRUE for strict comparison */ ) { jx9_hashmap_node *pEntry; jx9_value sVal, *pVal; jx9_value sNeedle; sxi32 rc; sxu32 n; /* Perform a linear search since we cannot sort the hashmap based on values */ pEntry = pMap->pFirst; n = pMap->nEntry; jx9MemObjInit(pMap->pVm, &sVal); jx9MemObjInit(pMap->pVm, &sNeedle); for(;;){ if( n < 1 ){ break; } /* Extract node value */ pVal = HashmapExtractNodeValue(pEntry); if( pVal ){ if( (pVal->iFlags|pNeedle->iFlags) & MEMOBJ_NULL ){ sxi32 iF1 = pVal->iFlags; sxi32 iF2 = pNeedle->iFlags; if( iF1 == iF2 ){ /* NULL values are equals */ if( ppNode ){ *ppNode = pEntry; } return SXRET_OK; } }else{ /* Duplicate value */ jx9MemObjLoad(pVal, &sVal); jx9MemObjLoad(pNeedle, &sNeedle); rc = jx9MemObjCmp(&sNeedle, &sVal, bStrict, 0); jx9MemObjRelease(&sVal); jx9MemObjRelease(&sNeedle); if( rc == 0 ){ if( ppNode ){ *ppNode = pEntry; } /* Match found*/ return SXRET_OK; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* No such entry */ return SXERR_NOTFOUND; } /* * Compare two hashmaps. * Return 0 if the hashmaps are equals.Any other value indicates inequality. * Note on array comparison operators. * According to the JX9 language reference manual. * Array Operators Example Name Result * $a + $b Union Union of $a and $b. * $a == $b Equality TRUE if $a and $b have the same key/value pairs. * $a === $b Identity TRUE if $a and $b have the same key/value pairs in the same * order and of the same types. * $a != $b Inequality TRUE if $a is not equal to $b. * $a <> $b Inequality TRUE if $a is not equal to $b. * $a !== $b Non-identity TRUE if $a is not identical to $b. * The + operator returns the right-hand array appended to the left-hand array; * For keys that exist in both arrays, the elements from the left-hand array will be used * and the matching elements from the right-hand array will be ignored. * "apple", "b" => "banana"); * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); * $c = $a + $b; // Union of $a and $b * print "Union of \$a and \$b: \n"; * dump($c); * $c = $b + $a; // Union of $b and $a * print "Union of \$b and \$a: \n"; * dump($c); * ?> * When executed, this script will print the following: * Union of $a and $b: * array(3) { * ["a"]=> * string(5) "apple" * ["b"]=> * string(6) "banana" * ["c"]=> * string(6) "cherry" * } * Union of $b and $a: * array(3) { * ["a"]=> * string(4) "pear" * ["b"]=> * string(10) "strawberry" * ["c"]=> * string(6) "cherry" * } * Elements of arrays are equal for the comparison if they have the same key and value. */ JX9_PRIVATE sxi32 jx9HashmapCmp( jx9_hashmap *pLeft, /* Left hashmap */ jx9_hashmap *pRight, /* Right hashmap */ int bStrict /* TRUE for strict comparison */ ) { jx9_hashmap_node *pLe, *pRe; sxi32 rc; sxu32 n; if( pLeft == pRight ){ /* Same hashmap instance. This can easily happen since hashmaps are passed by reference. * Unlike the engine. */ return 0; } if( pLeft->nEntry != pRight->nEntry ){ /* Must have the same number of entries */ return pLeft->nEntry > pRight->nEntry ? 1 : -1; } /* Point to the first inserted entry of the left hashmap */ pLe = pLeft->pFirst; pRe = 0; /* cc warning */ /* Perform the comparison */ n = pLeft->nEntry; for(;;){ if( n < 1 ){ break; } if( pLe->iType == HASHMAP_INT_NODE){ /* Int key */ rc = HashmapLookupIntKey(&(*pRight), pLe->xKey.iKey, &pRe); }else{ SyBlob *pKey = &pLe->xKey.sKey; /* Blob key */ rc = HashmapLookupBlobKey(&(*pRight), SyBlobData(pKey), SyBlobLength(pKey), &pRe); } if( rc != SXRET_OK ){ /* No such entry in the right side */ return 1; } rc = 0; if( bStrict ){ /* Make sure, the keys are of the same type */ if( pLe->iType != pRe->iType ){ rc = 1; } } if( !rc ){ /* Compare nodes */ rc = HashmapNodeCmp(pLe, pRe, bStrict); } if( rc != 0 ){ /* Nodes key/value differ */ return rc; } /* Point to the next entry */ pLe = pLe->pPrev; /* Reverse link */ n--; } return 0; /* Hashmaps are equals */ } /* * Merge two hashmaps. * Note on the merge process * According to the JX9 language reference manual. * Merges the elements of two arrays together so that the values of one are appended * to the end of the previous one. It returns the resulting array (pDest). * If the input arrays have the same string keys, then the later value for that key * will overwrite the previous one. If, however, the arrays contain numeric keys * the later value will not overwrite the original value, but will be appended. * Values in the input array with numeric keys will be renumbered with incrementing * keys starting from zero in the result array. */ static sxi32 HashmapMerge(jx9_hashmap *pSrc, jx9_hashmap *pDest) { jx9_hashmap_node *pEntry; jx9_value sKey, *pVal; sxi32 rc; sxu32 n; if( pSrc == pDest ){ /* Same map. This can easily happen since hashmaps are passed by reference. * Unlike the engine. */ return SXRET_OK; } /* Point to the first inserted entry in the source */ pEntry = pSrc->pFirst; /* Perform the merge */ for( n = 0 ; n < pSrc->nEntry ; ++n ){ /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if( pEntry->iType == HASHMAP_BLOB_NODE ){ /* Blob key insertion */ jx9MemObjInitFromString(pDest->pVm, &sKey, 0); jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); jx9MemObjRelease(&sKey); }else{ rc = HashmapInsert(&(*pDest), 0/* Automatic index assign */, pVal); } if( rc != SXRET_OK ){ return rc; } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * Duplicate the contents of a hashmap. Store the copy in pDest. * Refer to the [array_pad(), array_copy(), ...] implementation for more information. */ JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest) { jx9_hashmap_node *pEntry; jx9_value sKey, *pVal; sxi32 rc; sxu32 n; if( pSrc == pDest ){ /* Same map. This can easily happen since hashmaps are passed by reference. * Unlike the engine. */ return SXRET_OK; } /* Point to the first inserted entry in the source */ pEntry = pSrc->pFirst; /* Perform the duplication */ for( n = 0 ; n < pSrc->nEntry ; ++n ){ /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if( pEntry->iType == HASHMAP_BLOB_NODE ){ /* Blob key insertion */ jx9MemObjInitFromString(pDest->pVm, &sKey, 0); jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); jx9MemObjRelease(&sKey); }else{ /* Int key insertion */ rc = HashmapInsertIntKey(&(*pDest), pEntry->xKey.iKey, pVal); } if( rc != SXRET_OK ){ return rc; } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * Perform the union of two hashmaps. * This operation is performed only if the user uses the '+' operator * with a variable holding an array as follows: * "apple", "b" => "banana"); * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); * $c = $a + $b; // Union of $a and $b * print "Union of \$a and \$b: \n"; * dump($c); * $c = $b + $a; // Union of $b and $a * print "Union of \$b and \$a: \n"; * dump($c); * ?> * When executed, this script will print the following: * Union of $a and $b: * array(3) { * ["a"]=> * string(5) "apple" * ["b"]=> * string(6) "banana" * ["c"]=> * string(6) "cherry" * } * Union of $b and $a: * array(3) { * ["a"]=> * string(4) "pear" * ["b"]=> * string(10) "strawberry" * ["c"]=> * string(6) "cherry" * } * The + operator returns the right-hand array appended to the left-hand array; * For keys that exist in both arrays, the elements from the left-hand array will be used * and the matching elements from the right-hand array will be ignored. */ JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight) { jx9_hashmap_node *pEntry; sxi32 rc = SXRET_OK; jx9_value *pObj; sxu32 n; if( pLeft == pRight ){ /* Same map. This can easily happen since hashmaps are passed by reference. * Unlike the engine. */ return SXRET_OK; } /* Perform the union */ pEntry = pRight->pFirst; for(n = 0 ; n < pRight->nEntry ; ++n ){ /* Make sure the given key does not exists in the left array */ if( pEntry->iType == HASHMAP_BLOB_NODE ){ /* BLOB key */ if( SXRET_OK != HashmapLookupBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), 0) ){ pObj = HashmapExtractNodeValue(pEntry); if( pObj ){ /* Perform the insertion */ rc = HashmapInsertBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey),pObj); if( rc != SXRET_OK ){ return rc; } } } }else{ /* INT key */ if( SXRET_OK != HashmapLookupIntKey(&(*pLeft), pEntry->xKey.iKey, 0) ){ pObj = HashmapExtractNodeValue(pEntry); if( pObj ){ /* Perform the insertion */ rc = HashmapInsertIntKey(&(*pLeft), pEntry->xKey.iKey, pObj); if( rc != SXRET_OK ){ return rc; } } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } return SXRET_OK; } /* * Allocate a new hashmap. * Return a pointer to the freshly allocated hashmap on success.NULL otherwise. */ JX9_PRIVATE jx9_hashmap * jx9NewHashmap( jx9_vm *pVm, /* VM that trigger the hashmap creation */ sxu32 (*xIntHash)(sxi64), /* Hash function for int keys.NULL otherwise*/ sxu32 (*xBlobHash)(const void *, sxu32) /* Hash function for BLOB keys.NULL otherwise */ ) { jx9_hashmap *pMap; /* Allocate a new instance */ pMap = (jx9_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_hashmap)); if( pMap == 0 ){ return 0; } /* Zero the structure */ SyZero(pMap, sizeof(jx9_hashmap)); /* Fill in the structure */ pMap->pVm = &(*pVm); pMap->iRef = 1; /* pMap->iFlags = 0; */ /* Default hash functions */ pMap->xIntHash = xIntHash ? xIntHash : IntHash; pMap->xBlobHash = xBlobHash ? xBlobHash : BinHash; return pMap; } /* * Install superglobals in the given virtual machine. * Note on superglobals. * According to the JX9 language reference manual. * Superglobals are built-in variables that are always available in all scopes. * Description * All predefined variables in JX9 are "superglobals", which means they * are available in all scopes throughout a script. * These variables are: * $_SERVER * $_GET * $_POST * $_FILES * $_REQUEST * $_ENV */ JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm) { static const char * azSuper[] = { "_SERVER", /* $_SERVER */ "_GET", /* $_GET */ "_POST", /* $_POST */ "_FILES", /* $_FILES */ "_REQUEST", /* $_REQUEST */ "_COOKIE", /* $_COOKIE */ "_ENV", /* $_ENV */ "_HEADER", /* $_HEADER */ "argv" /* $argv */ }; SyString *pFile; sxi32 rc; sxu32 n; /* Install globals variable now */ for( n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++ ){ jx9_value *pSuper; /* Request an empty array */ pSuper = jx9_new_array(&(*pVm)); if( pSuper == 0 ){ return SXERR_MEM; } /* Install */ rc = jx9_vm_config(&(*pVm),JX9_VM_CONFIG_CREATE_VAR, azSuper[n]/* Super-global name*/, pSuper/* Super-global value */); if( rc != SXRET_OK ){ return rc; } /* Release the value now it have been installed */ jx9_release_value(&(*pVm), pSuper); } /* Set some $_SERVER entries */ pFile = (SyString *)SySetPeek(&pVm->aFiles); /* * 'SCRIPT_FILENAME' * The absolute pathname of the currently executing script. */ jx9_vm_config(pVm, JX9_VM_CONFIG_SERVER_ATTR, "SCRIPT_FILENAME", pFile ? pFile->zString : ":Memory:", pFile ? pFile->nByte : sizeof(":Memory:") - 1 ); /* All done, all global variables are installed now */ return SXRET_OK; } /* * Release a hashmap. */ JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS) { jx9_hashmap_node *pEntry, *pNext; jx9_vm *pVm = pMap->pVm; sxu32 n; /* Start the release process */ n = 0; pEntry = pMap->pFirst; for(;;){ if( n >= pMap->nEntry ){ break; } pNext = pEntry->pPrev; /* Reverse link */ /* Restore the jx9_value to the free list */ jx9VmUnsetMemObj(pVm, pEntry->nValIdx); /* Release the node */ if( pEntry->iType == HASHMAP_BLOB_NODE ){ SyBlobRelease(&pEntry->xKey.sKey); } SyMemBackendPoolFree(&pVm->sAllocator, pEntry); /* Point to the next entry */ pEntry = pNext; n++; } if( pMap->nEntry > 0 ){ /* Release the hash bucket */ SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); } if( FreeDS ){ /* Free the whole instance */ SyMemBackendPoolFree(&pVm->sAllocator, pMap); }else{ /* Keep the instance but reset it's fields */ pMap->apBucket = 0; pMap->iNextIdx = 0; pMap->nEntry = pMap->nSize = 0; pMap->pFirst = pMap->pLast = pMap->pCur = 0; } return SXRET_OK; } /* * Decrement the reference count of a given hashmap. * If the count reaches zero which mean no more variables * are pointing to this hashmap, then release the whole instance. */ JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap) { pMap->iRef--; if( pMap->iRef < 1 ){ jx9HashmapRelease(pMap, TRUE); } } /* * Check if a given key exists in the given hashmap. * Write a pointer to the target node on success. * Otherwise SXERR_NOTFOUND is returned on failure. */ JX9_PRIVATE sxi32 jx9HashmapLookup( jx9_hashmap *pMap, /* Target hashmap */ jx9_value *pKey, /* Lookup key */ jx9_hashmap_node **ppNode /* OUT: Target node on success */ ) { sxi32 rc; if( pMap->nEntry < 1 ){ /* TICKET 1433-25: Don't bother hashing, the hashmap is empty anyway. */ return SXERR_NOTFOUND; } rc = HashmapLookup(&(*pMap), &(*pKey), ppNode); return rc; } /* * Insert a given key and it's associated value (if any) in the given * hashmap. * If a node with the given key already exists in the database * then this function overwrite the old value. */ JX9_PRIVATE sxi32 jx9HashmapInsert( jx9_hashmap *pMap, /* Target hashmap */ jx9_value *pKey, /* Lookup key */ jx9_value *pVal /* Node value.NULL otherwise */ ) { sxi32 rc; rc = HashmapInsert(&(*pMap), &(*pKey), &(*pVal)); return rc; } /* * Reset the node cursor of a given hashmap. */ JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap) { /* Reset the loop cursor */ pMap->pCur = pMap->pFirst; } /* * Return a pointer to the node currently pointed by the node cursor. * If the cursor reaches the end of the list, then this function * return NULL. * Note that the node cursor is automatically advanced by this function. */ JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap) { jx9_hashmap_node *pCur = pMap->pCur; if( pCur == 0 ){ /* End of the list, return null */ return 0; } /* Advance the node cursor */ pMap->pCur = pCur->pPrev; /* Reverse link */ return pCur; } /* * Extract a node value. */ JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode) { jx9_value *pValue; pValue = HashmapExtractNodeValue(pNode); return pValue; } /* * Extract a node value (Second). */ JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore) { jx9_value *pEntry = HashmapExtractNodeValue(pNode); if( pEntry ){ if( bStore ){ jx9MemObjStore(pEntry, pValue); }else{ jx9MemObjLoad(pEntry, pValue); } }else{ jx9MemObjRelease(pValue); } } /* * Extract a node key. */ JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode,jx9_value *pKey) { /* Fill with the current key */ if( pNode->iType == HASHMAP_INT_NODE ){ if( SyBlobLength(&pKey->sBlob) > 0 ){ SyBlobRelease(&pKey->sBlob); } pKey->x.iVal = pNode->xKey.iKey; MemObjSetType(pKey, MEMOBJ_INT); }else{ SyBlobReset(&pKey->sBlob); SyBlobAppend(&pKey->sBlob, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey)); MemObjSetType(pKey, MEMOBJ_STRING); } } #ifndef JX9_DISABLE_BUILTIN_FUNC /* * Store the address of nodes value in the given container. * Refer to the [vfprintf(), vprintf(), vsprintf()] implementations * defined in 'builtin.c' for more information. */ JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut) { jx9_hashmap_node *pEntry = pMap->pFirst; jx9_value *pValue; sxu32 n; /* Initialize the container */ SySetInit(pOut, &pMap->pVm->sAllocator, sizeof(jx9_value *)); for(n = 0 ; n < pMap->nEntry ; n++ ){ /* Extract node value */ pValue = HashmapExtractNodeValue(pEntry); if( pValue ){ SySetPut(pOut, (const void *)&pValue); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Total inserted entries */ return (int)SySetUsed(pOut); } #endif /* JX9_DISABLE_BUILTIN_FUNC */ /* * Merge sort. * The merge sort implementation is based on the one found in the SQLite3 source tree. * Status: Public domain */ /* Node comparison callback signature */ typedef sxi32 (*ProcNodeCmp)(jx9_hashmap_node *, jx9_hashmap_node *, void *); /* ** Inputs: ** a: A sorted, null-terminated linked list. (May be null). ** b: A sorted, null-terminated linked list. (May be null). ** cmp: A pointer to the comparison function. ** ** Return Value: ** A pointer to the head of a sorted list containing the elements ** of both a and b. ** ** Side effects: ** The "next", "prev" pointers for elements in the lists a and b are ** changed. */ static jx9_hashmap_node * HashmapNodeMerge(jx9_hashmap_node *pA, jx9_hashmap_node *pB, ProcNodeCmp xCmp, void *pCmpData) { jx9_hashmap_node result, *pTail; /* Prevent compiler warning */ result.pNext = result.pPrev = 0; pTail = &result; while( pA && pB ){ if( xCmp(pA, pB, pCmpData) < 0 ){ pTail->pPrev = pA; pA->pNext = pTail; pTail = pA; pA = pA->pPrev; }else{ pTail->pPrev = pB; pB->pNext = pTail; pTail = pB; pB = pB->pPrev; } } if( pA ){ pTail->pPrev = pA; pA->pNext = pTail; }else if( pB ){ pTail->pPrev = pB; pB->pNext = pTail; }else{ pTail->pPrev = pTail->pNext = 0; } return result.pPrev; } /* ** Inputs: ** Map: Input hashmap ** cmp: A comparison function. ** ** Return Value: ** Sorted hashmap. ** ** Side effects: ** The "next" pointers for elements in list are changed. */ #define N_SORT_BUCKET 32 static sxi32 HashmapMergeSort(jx9_hashmap *pMap, ProcNodeCmp xCmp, void *pCmpData) { jx9_hashmap_node *a[N_SORT_BUCKET], *p, *pIn; sxu32 i; SyZero(a, sizeof(a)); /* Point to the first inserted entry */ pIn = pMap->pFirst; while( pIn ){ p = pIn; pIn = p->pPrev; p->pPrev = 0; for(i=0; ipNext = 0; /* Reflect the change */ pMap->pFirst = p; /* Reset the loop cursor */ pMap->pCur = pMap->pFirst; return SXRET_OK; } /* * Node comparison callback. * used-by: [sort(), asort(), ...] */ static sxi32 HashmapCmpCallback1(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) { jx9_value sA, sB; sxi32 iFlags; int rc; if( pCmpData == 0 ){ /* Perform a standard comparison */ rc = HashmapNodeCmp(pA, pB, FALSE); return rc; } iFlags = SX_PTR_TO_INT(pCmpData); /* Duplicate node values */ jx9MemObjInit(pA->pMap->pVm, &sA); jx9MemObjInit(pA->pMap->pVm, &sB); jx9HashmapExtractNodeValue(pA, &sA, FALSE); jx9HashmapExtractNodeValue(pB, &sB, FALSE); if( iFlags == 5 ){ /* String cast */ if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ jx9MemObjToString(&sA); } if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ jx9MemObjToString(&sB); } }else{ /* Numeric cast */ jx9MemObjToNumeric(&sA); jx9MemObjToNumeric(&sB); } /* Perform the comparison */ rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); jx9MemObjRelease(&sA); jx9MemObjRelease(&sB); return rc; } /* * Node comparison callback. * Used by: [rsort(), arsort()]; */ static sxi32 HashmapCmpCallback3(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) { jx9_value sA, sB; sxi32 iFlags; int rc; if( pCmpData == 0 ){ /* Perform a standard comparison */ rc = HashmapNodeCmp(pA, pB, FALSE); return -rc; } iFlags = SX_PTR_TO_INT(pCmpData); /* Duplicate node values */ jx9MemObjInit(pA->pMap->pVm, &sA); jx9MemObjInit(pA->pMap->pVm, &sB); jx9HashmapExtractNodeValue(pA, &sA, FALSE); jx9HashmapExtractNodeValue(pB, &sB, FALSE); if( iFlags == 5 ){ /* String cast */ if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ jx9MemObjToString(&sA); } if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ jx9MemObjToString(&sB); } }else{ /* Numeric cast */ jx9MemObjToNumeric(&sA); jx9MemObjToNumeric(&sB); } /* Perform the comparison */ rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); jx9MemObjRelease(&sA); jx9MemObjRelease(&sB); return -rc; } /* * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. * used-by: [usort(), uasort()] */ static sxi32 HashmapCmpCallback4(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) { jx9_value sResult, *pCallback; jx9_value *pV1, *pV2; jx9_value *apArg[2]; /* Callback arguments */ sxi32 rc; /* Point to the desired callback */ pCallback = (jx9_value *)pCmpData; /* initialize the result value */ jx9MemObjInit(pA->pMap->pVm, &sResult); /* Extract nodes values */ pV1 = HashmapExtractNodeValue(pA); pV2 = HashmapExtractNodeValue(pB); apArg[0] = pV1; apArg[1] = pV2; /* Invoke the callback */ rc = jx9VmCallUserFunction(pA->pMap->pVm, pCallback, 2, apArg, &sResult); if( rc != SXRET_OK ){ /* An error occured while calling user defined function [i.e: not defined] */ rc = -1; /* Set a dummy result */ }else{ /* Extract callback result */ if((sResult.iFlags & MEMOBJ_INT) == 0 ){ /* Perform an int cast */ jx9MemObjToInteger(&sResult); } rc = (sxi32)sResult.x.iVal; } jx9MemObjRelease(&sResult); /* Callback result */ return rc; } /* * Rehash all nodes keys after a merge-sort have been applied. * Used by [sort(), usort() and rsort()]. */ static void HashmapSortRehash(jx9_hashmap *pMap) { jx9_hashmap_node *p, *pLast; sxu32 i; /* Rehash all entries */ pLast = p = pMap->pFirst; pMap->iNextIdx = 0; /* Reset the automatic index */ i = 0; for( ;; ){ if( i >= pMap->nEntry ){ pMap->pLast = pLast; /* Fix the last link broken by the merge-sort */ break; } if( p->iType == HASHMAP_BLOB_NODE ){ /* Do not maintain index association as requested by the JX9 specification */ SyBlobRelease(&p->xKey.sKey); /* Change key type */ p->iType = HASHMAP_INT_NODE; } HashmapRehashIntNode(p); /* Point to the next entry */ i++; pLast = p; p = p->pPrev; /* Reverse link */ } } /* * Array functions implementation. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* * bool sort(array &$array[, int $sort_flags = SORT_REGULAR ] ) * Sort an array. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. * */ static int jx9_hashmap_sort(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->nEntry > 1 ){ sxi32 iCmpFlags = 0; if( nArg > 1 ){ /* Extract comparison flags */ iCmpFlags = jx9_value_to_int(apArg[1]); if( iCmpFlags == 3 /* SORT_REGULAR */ ){ iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback1, SX_INT_TO_PTR(iCmpFlags)); /* Rehash [Do not maintain index association as requested by the JX9 specification] */ HashmapSortRehash(pMap); } /* All done, return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * bool rsort(array &$array[, int $sort_flags = SORT_REGULAR ] ) * Sort an array in reverse order. * Parameters * $array * The input array. * $sort_flags * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: * Sorting type flags: * SORT_REGULAR - compare items normally (don't change types) * SORT_NUMERIC - compare items numerically * SORT_STRING - compare items as strings * Return * TRUE on success or FALSE on failure. */ static int jx9_hashmap_rsort(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->nEntry > 1 ){ sxi32 iCmpFlags = 0; if( nArg > 1 ){ /* Extract comparison flags */ iCmpFlags = jx9_value_to_int(apArg[1]); if( iCmpFlags == 3 /* SORT_REGULAR */ ){ iCmpFlags = 0; /* Standard comparison */ } } /* Do the merge sort */ HashmapMergeSort(pMap, HashmapCmpCallback3, SX_INT_TO_PTR(iCmpFlags)); /* Rehash [Do not maintain index association as requested by the JX9 specification] */ HashmapSortRehash(pMap); } /* All done, return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * bool usort(array &$array, callable $cmp_function) * Sort an array by values using a user-defined comparison function. * Parameters * $array * The input array. * $cmp_function * The comparison function must return an integer less than, equal to, or greater * than zero if the first argument is considered to be respectively less than, equal * to, or greater than the second. * int callback ( mixed $a, mixed $b ) * Return * TRUE on success or FALSE on failure. */ static int jx9_hashmap_usort(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; /* Make sure we are dealing with a valid hashmap */ if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->nEntry > 1 ){ jx9_value *pCallback = 0; ProcNodeCmp xCmp; xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ if( nArg > 1 && jx9_value_is_callable(apArg[1]) ){ /* Point to the desired callback */ pCallback = apArg[1]; }else{ /* Use the default comparison function */ xCmp = HashmapCmpCallback1; } /* Do the merge sort */ HashmapMergeSort(pMap, xCmp, pCallback); /* Rehash [Do not maintain index association as requested by the JX9 specification] */ HashmapSortRehash(pMap); } /* All done, return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * int count(array $var [, int $mode = COUNT_NORMAL ]) * Count all elements in an array, or something in an object. * Parameters * $var * The array or the object. * $mode * If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count() * will recursively count the array. This is particularly useful for counting * all the elements of a multidimensional array. count() does not detect infinite * recursion. * Return * Returns the number of elements in the array. */ static int jx9_hashmap_count(jx9_context *pCtx, int nArg, jx9_value **apArg) { int bRecursive = FALSE; sxi64 iCount; if( nArg < 1 ){ /* Missing arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } if( !jx9_value_is_json_array(apArg[0]) ){ /* TICKET 1433-19: Handle objects */ int res = !jx9_value_is_null(apArg[0]); jx9_result_int(pCtx, res); return JX9_OK; } if( nArg > 1 ){ /* Recursive count? */ bRecursive = jx9_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */; } /* Count */ iCount = HashmapCount((jx9_hashmap *)apArg[0]->x.pOther, bRecursive, 0); jx9_result_int64(pCtx, iCount); return JX9_OK; } /* * bool array_key_exists(value $key, array $search) * Checks if the given key or index exists in the array. * Parameters * $key * Value to check. * $search * An array with keys to check. * Return * TRUE on success or FALSE on failure. */ static int jx9_hashmap_key_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) { sxi32 rc; if( nArg < 2 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[1]) ){ /* Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the lookup */ rc = jx9HashmapLookup((jx9_hashmap *)apArg[1]->x.pOther, apArg[0], 0); /* lookup result */ jx9_result_bool(pCtx, rc == SXRET_OK ? 1 : 0); return JX9_OK; } /* * value array_pop(array $array) * POP the last inserted element from the array. * Parameter * The array to get the value from. * Return * Poped value or NULL on failure. */ static int jx9_hashmap_pop(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return null */ jx9_result_null(pCtx); return JX9_OK; } pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->nEntry < 1 ){ /* Noting to pop, return NULL */ jx9_result_null(pCtx); }else{ jx9_hashmap_node *pLast = pMap->pLast; jx9_value *pObj; pObj = HashmapExtractNodeValue(pLast); if( pObj ){ /* Node value */ jx9_result_value(pCtx, pObj); /* Unlink the node */ jx9HashmapUnlinkNode(pLast); }else{ jx9_result_null(pCtx); } /* Reset the cursor */ pMap->pCur = pMap->pFirst; } return JX9_OK; } /* * int array_push($array, $var, ...) * Push one or more elements onto the end of array. (Stack insertion) * Parameters * array * The input array. * var * On or more value to push. * Return * New array count (including old items). */ static int jx9_hashmap_push(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; sxi32 rc; int i; if( nArg < 1 ){ /* Missing arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; /* Start pushing given values */ for( i = 1 ; i < nArg ; ++i ){ rc = jx9HashmapInsert(pMap, 0, apArg[i]); if( rc != SXRET_OK ){ break; } } /* Return the new count */ jx9_result_int64(pCtx, (sxi64)pMap->nEntry); return JX9_OK; } /* * value array_shift(array $array) * Shift an element off the beginning of array. * Parameter * The array to get the value from. * Return * Shifted value or NULL on failure. */ static int jx9_hashmap_shift(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; if( nArg < 1 ){ /* Missing arguments, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return null */ jx9_result_null(pCtx); return JX9_OK; } /* Point to the internal representation of the hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->nEntry < 1 ){ /* Empty hashmap, return NULL */ jx9_result_null(pCtx); }else{ jx9_hashmap_node *pEntry = pMap->pFirst; jx9_value *pObj; sxu32 n; pObj = HashmapExtractNodeValue(pEntry); if( pObj ){ /* Node value */ jx9_result_value(pCtx, pObj); /* Unlink the first node */ jx9HashmapUnlinkNode(pEntry); }else{ jx9_result_null(pCtx); } /* Rehash all int keys */ n = pMap->nEntry; pEntry = pMap->pFirst; pMap->iNextIdx = 0; /* Reset the automatic index */ for(;;){ if( n < 1 ){ break; } if( pEntry->iType == HASHMAP_INT_NODE ){ HashmapRehashIntNode(pEntry); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Reset the cursor */ pMap->pCur = pMap->pFirst; } return JX9_OK; } /* * Extract the node cursor value. */ static sxi32 HashmapCurrentValue(jx9_context *pCtx, jx9_hashmap *pMap, int iDirection) { jx9_hashmap_node *pCur = pMap->pCur; jx9_value *pVal; if( pCur == 0 ){ /* Cursor does not point to anything, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } if( iDirection != 0 ){ if( iDirection > 0 ){ /* Point to the next entry */ pMap->pCur = pCur->pPrev; /* Reverse link */ pCur = pMap->pCur; }else{ /* Point to the previous entry */ pMap->pCur = pCur->pNext; /* Reverse link */ pCur = pMap->pCur; } if( pCur == 0 ){ /* End of input reached, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } } /* Point to the desired element */ pVal = HashmapExtractNodeValue(pCur); if( pVal ){ jx9_result_value(pCtx, pVal); }else{ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * value current(array $array) * Return the current element in an array. * Parameter * $input: The input array. * Return * The current() function simply returns the value of the array element that's currently * being pointed to by the internal pointer. It does not move the pointer in any way. * If the internal pointer points beyond the end of the elements list or the array * is empty, current() returns FALSE. */ static int jx9_hashmap_current(jx9_context *pCtx, int nArg, jx9_value **apArg) { if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 0); return JX9_OK; } /* * value next(array $input) * Advance the internal array pointer of an array. * Parameter * $input: The input array. * Return * next() behaves like current(), with one difference. It advances the internal array * pointer one place forward before returning the element value. That means it returns * the next array value and advances the internal array pointer by one. */ static int jx9_hashmap_next(jx9_context *pCtx, int nArg, jx9_value **apArg) { if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 1); return JX9_OK; } /* * value prev(array $input) * Rewind the internal array pointer. * Parameter * $input: The input array. * Return * Returns the array value in the previous place that's pointed * to by the internal array pointer, or FALSE if there are no more * elements. */ static int jx9_hashmap_prev(jx9_context *pCtx, int nArg, jx9_value **apArg) { if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, -1); return JX9_OK; } /* * value end(array $input) * Set the internal pointer of an array to its last element. * Parameter * $input: The input array. * Return * Returns the value of the last element or FALSE for empty array. */ static int jx9_hashmap_end(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; /* Point to the last node */ pMap->pCur = pMap->pLast; /* Return the last node value */ HashmapCurrentValue(&(*pCtx), pMap, 0); return JX9_OK; } /* * value reset(array $array ) * Set the internal pointer of an array to its first element. * Parameter * $input: The input array. * Return * Returns the value of the first array element, or FALSE if the array is empty. */ static int jx9_hashmap_reset(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; /* Point to the first node */ pMap->pCur = pMap->pFirst; /* Return the last node value if available */ HashmapCurrentValue(&(*pCtx), pMap, 0); return JX9_OK; } /* * value key(array $array) * Fetch a key from an array * Parameter * $input * The input array. * Return * The key() function simply returns the key of the array element that's currently * being pointed to by the internal pointer. It does not move the pointer in any way. * If the internal pointer points beyond the end of the elements list or the array * is empty, key() returns NULL. */ static int jx9_hashmap_simple_key(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap_node *pCur; jx9_hashmap *pMap; if( nArg < 1 ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return NULL */ jx9_result_null(pCtx); return JX9_OK; } pMap = (jx9_hashmap *)apArg[0]->x.pOther; pCur = pMap->pCur; if( pCur == 0 ){ /* Cursor does not point to anything, return NULL */ jx9_result_null(pCtx); return JX9_OK; } if( pCur->iType == HASHMAP_INT_NODE){ /* Key is integer */ jx9_result_int64(pCtx, pCur->xKey.iKey); }else{ /* Key is blob */ jx9_result_string(pCtx, (const char *)SyBlobData(&pCur->xKey.sKey), (int)SyBlobLength(&pCur->xKey.sKey)); } return JX9_OK; } /* * array each(array $input) * Return the current key and value pair from an array and advance the array cursor. * Parameter * $input * The input array. * Return * Returns the current key and value pair from the array array. This pair is returned * in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key * contain the key name of the array element, and 1 and value contain the data. * If the internal pointer for the array points past the end of the array contents * each() returns FALSE. */ static int jx9_hashmap_each(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap_node *pCur; jx9_hashmap *pMap; jx9_value *pArray; jx9_value *pVal; jx9_value sKey; if( nArg < 1 ){ /* Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the internal representation that describe the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->pCur == 0 ){ /* Cursor does not point to anything, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } pCur = pMap->pCur; /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_result_bool(pCtx, 0); return JX9_OK; } pVal = HashmapExtractNodeValue(pCur); /* Insert the current value */ jx9_array_add_strkey_elem(pArray, "1", pVal); jx9_array_add_strkey_elem(pArray, "value", pVal); /* Make the key */ if( pCur->iType == HASHMAP_INT_NODE ){ jx9MemObjInitFromInt(pMap->pVm, &sKey, pCur->xKey.iKey); }else{ jx9MemObjInitFromString(pMap->pVm, &sKey, 0); jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pCur->xKey.sKey), SyBlobLength(&pCur->xKey.sKey)); } /* Insert the current key */ jx9_array_add_elem(pArray, 0, &sKey); jx9_array_add_strkey_elem(pArray, "key", &sKey); jx9MemObjRelease(&sKey); /* Advance the cursor */ pMap->pCur = pCur->pPrev; /* Reverse link */ /* Return the current entry */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * array array_values(array $input) * Returns all the values from the input array and indexes numerically the array. * Parameters * input: The input array. * Return * An indexed array of values or NULL on failure. */ static int jx9_hashmap_values(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap_node *pNode; jx9_hashmap *pMap; jx9_value *pArray; jx9_value *pObj; sxu32 n; if( nArg < 1 ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Point to the internal representation that describe the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_result_null(pCtx); return JX9_OK; } /* Perform the requested operation */ pNode = pMap->pFirst; for( n = 0 ; n < pMap->nEntry ; ++n ){ pObj = HashmapExtractNodeValue(pNode); if( pObj ){ /* perform the insertion */ jx9_array_add_elem(pArray, 0/* Automatic index assign */, pObj); } /* Point to the next entry */ pNode = pNode->pPrev; /* Reverse link */ } /* return the new array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * bool array_same(array $arr1, array $arr2) * Return TRUE if the given arrays are the same instance. * This function is useful under JX9 since arrays and objects * are passed by reference. * Parameters * $arr1 * First array * $arr2 * Second array * Return * TRUE if the arrays are the same instance. FALSE otherwise. * Note * This function is a symisc eXtension. */ static int jx9_hashmap_same(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *p1, *p2; int rc; if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ /* Missing or invalid arguments, return FALSE*/ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the hashmaps */ p1 = (jx9_hashmap *)apArg[0]->x.pOther; p2 = (jx9_hashmap *)apArg[1]->x.pOther; rc = (p1 == p2); /* Same instance? */ jx9_result_bool(pCtx, rc); return JX9_OK; } /* * array array_merge(array $array1, ...) * Merge one or more arrays. * Parameters * $array1 * Initial array to merge. * ... * More array to merge. * Return * The resulting array. */ static int jx9_hashmap_merge(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap, *pSrc; jx9_value *pArray; int i; if( nArg < 1 ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_result_null(pCtx); return JX9_OK; } /* Point to the internal representation of the hashmap */ pMap = (jx9_hashmap *)pArray->x.pOther; /* Start merging */ for( i = 0 ; i < nArg ; i++ ){ /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[i]) ){ /* Insert scalar value */ jx9_array_add_elem(pArray, 0, apArg[i]); }else{ pSrc = (jx9_hashmap *)apArg[i]->x.pOther; /* Merge the two hashmaps */ HashmapMerge(pSrc, pMap); } } /* Return the freshly created array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * bool in_array(value $needle, array $haystack[, bool $strict = FALSE ]) * Checks if a value exists in an array. * Parameters * $needle * The searched value. * Note: * If needle is a string, the comparison is done in a case-sensitive manner. * $haystack * The target array. * $strict * If the third parameter strict is set to TRUE then the in_array() function * will also check the types of the needle in the haystack. */ static int jx9_hashmap_in_array(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pNeedle; int bStrict; int rc; if( nArg < 2 ){ /* Missing argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } pNeedle = apArg[0]; bStrict = 0; if( nArg > 2 ){ bStrict = jx9_value_to_bool(apArg[2]); } if( !jx9_value_is_json_array(apArg[1]) ){ /* haystack must be an array, perform a standard comparison */ rc = jx9_value_compare(pNeedle, apArg[1], bStrict); /* Set the comparison result */ jx9_result_bool(pCtx, rc == 0); return JX9_OK; } /* Perform the lookup */ rc = HashmapFindValue((jx9_hashmap *)apArg[1]->x.pOther, pNeedle, 0, bStrict); /* Lookup result */ jx9_result_bool(pCtx, rc == SXRET_OK); return JX9_OK; } /* * array array_copy(array $source) * Make a blind copy of the target array. * Parameters * $source * Target array * Return * Copy of the target array on success. NULL otherwise. * Note * This function is a symisc eXtension. */ static int jx9_hashmap_copy(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; jx9_value *pArray; if( nArg < 1 ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_result_null(pCtx); return JX9_OK; } /* Point to the internal representation of the hashmap */ pMap = (jx9_hashmap *)pArray->x.pOther; if( jx9_value_is_json_array(apArg[0])){ /* Point to the internal representation of the source */ jx9_hashmap *pSrc = (jx9_hashmap *)apArg[0]->x.pOther; /* Perform the copy */ jx9HashmapDup(pSrc, pMap); }else{ /* Simple insertion */ jx9HashmapInsert(pMap, 0/* Automatic index assign*/, apArg[0]); } /* Return the duplicated array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * bool array_erase(array $source) * Remove all elements from a given array. * Parameters * $source * Target array * Return * TRUE on success. FALSE otherwise. * Note * This function is a symisc eXtension. */ static int jx9_hashmap_erase(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; if( nArg < 1 ){ /* Missing arguments */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; /* Erase */ jx9HashmapRelease(pMap, FALSE); return JX9_OK; } /* * array array_diff(array $array1, array $array2, ...) * Computes the difference of arrays. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an array containing all the entries from array1 that * are not present in any of the other arrays. */ static int jx9_hashmap_diff(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap_node *pEntry; jx9_hashmap *pSrc, *pMap; jx9_value *pArray; jx9_value *pVal; sxi32 rc; sxu32 n; int i; if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } if( nArg == 1 ){ /* Return the first array since we cannot perform a diff */ jx9_result_value(pCtx, apArg[0]); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_result_null(pCtx); return JX9_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (jx9_hashmap *)apArg[0]->x.pOther; /* Perform the diff */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;){ if( n < 1 ){ break; } /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if( pVal ){ for( i = 1 ; i < nArg ; i++ ){ if( !jx9_value_is_json_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (jx9_hashmap *)apArg[i]->x.pOther; /* Perform the lookup */ rc = HashmapFindValue(pMap, pVal, 0, TRUE); if( rc == SXRET_OK ){ /* Value exist */ break; } } if( i >= nArg ){ /* Perform the insertion */ HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * array array_intersect(array $array1 , array $array2, ...) * Computes the intersection of arrays. * Parameters * $array1 * The array to compare from * $array2 * An array to compare against * $... * More arrays to compare against * Return * Returns an array containing all of the values in array1 whose values exist * in all of the parameters. . * Note that NULL is returned on failure. */ static int jx9_hashmap_intersect(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap_node *pEntry; jx9_hashmap *pSrc, *pMap; jx9_value *pArray; jx9_value *pVal; sxi32 rc; sxu32 n; int i; if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ /* Missing arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } if( nArg == 1 ){ /* Return the first array since we cannot perform a diff */ jx9_result_value(pCtx, apArg[0]); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_result_null(pCtx); return JX9_OK; } /* Point to the internal representation of the source hashmap */ pSrc = (jx9_hashmap *)apArg[0]->x.pOther; /* Perform the intersection */ pEntry = pSrc->pFirst; n = pSrc->nEntry; for(;;){ if( n < 1 ){ break; } /* Extract the node value */ pVal = HashmapExtractNodeValue(pEntry); if( pVal ){ for( i = 1 ; i < nArg ; i++ ){ if( !jx9_value_is_json_array(apArg[i])) { /* ignore */ continue; } /* Point to the internal representation of the hashmap */ pMap = (jx9_hashmap *)apArg[i]->x.pOther; /* Perform the lookup */ rc = HashmapFindValue(pMap, pVal, 0, TRUE); if( rc != SXRET_OK ){ /* Value does not exist */ break; } } if( i >= nArg ){ /* Perform the insertion */ HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* Return the freshly created array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * number array_sum(array $array ) * Calculate the sum of values in an array. * Parameters * $array: The input array. * Return * Returns the sum of values as an integer or float. */ static void DoubleSum(jx9_context *pCtx, jx9_hashmap *pMap) { jx9_hashmap_node *pEntry; jx9_value *pObj; double dSum = 0; sxu32 n; pEntry = pMap->pFirst; for( n = 0 ; n < pMap->nEntry ; n++ ){ pObj = HashmapExtractNodeValue(pEntry); if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ if( pObj->iFlags & MEMOBJ_REAL ){ dSum += pObj->x.rVal; }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ dSum += (double)pObj->x.iVal; }else if( pObj->iFlags & MEMOBJ_STRING ){ if( SyBlobLength(&pObj->sBlob) > 0 ){ double dv = 0; SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); dSum += dv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return sum */ jx9_result_double(pCtx, dSum); } static void Int64Sum(jx9_context *pCtx, jx9_hashmap *pMap) { jx9_hashmap_node *pEntry; jx9_value *pObj; sxi64 nSum = 0; sxu32 n; pEntry = pMap->pFirst; for( n = 0 ; n < pMap->nEntry ; n++ ){ pObj = HashmapExtractNodeValue(pEntry); if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ if( pObj->iFlags & MEMOBJ_REAL ){ nSum += (sxi64)pObj->x.rVal; }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ nSum += pObj->x.iVal; }else if( pObj->iFlags & MEMOBJ_STRING ){ if( SyBlobLength(&pObj->sBlob) > 0 ){ sxi64 nv = 0; SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); nSum += nv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return sum */ jx9_result_int64(pCtx, nSum); } /* number array_sum(array $array ) * (See block-coment above) */ static int jx9_hashmap_sum(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; jx9_value *pObj; if( nArg < 1 ){ /* Missing arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->nEntry < 1 ){ /* Nothing to compute, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* If the first element is of type float, then perform floating * point computaion.Otherwise switch to int64 computaion. */ pObj = HashmapExtractNodeValue(pMap->pFirst); if( pObj == 0 ){ jx9_result_int(pCtx, 0); return JX9_OK; } if( pObj->iFlags & MEMOBJ_REAL ){ DoubleSum(pCtx, pMap); }else{ Int64Sum(pCtx, pMap); } return JX9_OK; } /* * number array_product(array $array ) * Calculate the product of values in an array. * Parameters * $array: The input array. * Return * Returns the product of values as an integer or float. */ static void DoubleProd(jx9_context *pCtx, jx9_hashmap *pMap) { jx9_hashmap_node *pEntry; jx9_value *pObj; double dProd; sxu32 n; pEntry = pMap->pFirst; dProd = 1; for( n = 0 ; n < pMap->nEntry ; n++ ){ pObj = HashmapExtractNodeValue(pEntry); if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ if( pObj->iFlags & MEMOBJ_REAL ){ dProd *= pObj->x.rVal; }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ dProd *= (double)pObj->x.iVal; }else if( pObj->iFlags & MEMOBJ_STRING ){ if( SyBlobLength(&pObj->sBlob) > 0 ){ double dv = 0; SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); dProd *= dv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return product */ jx9_result_double(pCtx, dProd); } static void Int64Prod(jx9_context *pCtx, jx9_hashmap *pMap) { jx9_hashmap_node *pEntry; jx9_value *pObj; sxi64 nProd; sxu32 n; pEntry = pMap->pFirst; nProd = 1; for( n = 0 ; n < pMap->nEntry ; n++ ){ pObj = HashmapExtractNodeValue(pEntry); if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ if( pObj->iFlags & MEMOBJ_REAL ){ nProd *= (sxi64)pObj->x.rVal; }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ nProd *= pObj->x.iVal; }else if( pObj->iFlags & MEMOBJ_STRING ){ if( SyBlobLength(&pObj->sBlob) > 0 ){ sxi64 nv = 0; SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); nProd *= nv; } } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* Return product */ jx9_result_int64(pCtx, nProd); } /* number array_product(array $array ) * (See block-block comment above) */ static int jx9_hashmap_product(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_hashmap *pMap; jx9_value *pObj; if( nArg < 1 ){ /* Missing arguments, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid hashmap */ if( !jx9_value_is_json_array(apArg[0]) ){ /* Invalid argument, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } pMap = (jx9_hashmap *)apArg[0]->x.pOther; if( pMap->nEntry < 1 ){ /* Nothing to compute, return 0 */ jx9_result_int(pCtx, 0); return JX9_OK; } /* If the first element is of type float, then perform floating * point computaion.Otherwise switch to int64 computaion. */ pObj = HashmapExtractNodeValue(pMap->pFirst); if( pObj == 0 ){ jx9_result_int(pCtx, 0); return JX9_OK; } if( pObj->iFlags & MEMOBJ_REAL ){ DoubleProd(pCtx, pMap); }else{ Int64Prod(pCtx, pMap); } return JX9_OK; } /* * array array_map(callback $callback, array $arr1) * Applies the callback to the elements of the given arrays. * Parameters * $callback * Callback function to run for each element in each array. * $arr1 * An array to run through the callback function. * Return * Returns an array containing all the elements of arr1 after applying * the callback function to each one. * NOTE: * array_map() passes only a single value to the callback. */ static int jx9_hashmap_map(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pArray, *pValue, sKey, sResult; jx9_hashmap_node *pEntry; jx9_hashmap *pMap; sxu32 n; if( nArg < 2 || !jx9_value_is_json_array(apArg[1]) ){ /* Invalid arguments, return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Create a new array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_result_null(pCtx); return JX9_OK; } /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[1]->x.pOther; jx9MemObjInit(pMap->pVm, &sResult); jx9MemObjInit(pMap->pVm, &sKey); /* Perform the requested operation */ pEntry = pMap->pFirst; for( n = 0 ; n < pMap->nEntry ; n++ ){ /* Extrcat the node value */ pValue = HashmapExtractNodeValue(pEntry); if( pValue ){ sxi32 rc; /* Invoke the supplied callback */ rc = jx9VmCallUserFunction(pMap->pVm, apArg[0], 1, &pValue, &sResult); /* Extract the node key */ jx9HashmapExtractNodeKey(pEntry, &sKey); if( rc != SXRET_OK ){ /* An error occured while invoking the supplied callback [i.e: not defined] */ jx9_array_add_elem(pArray, &sKey, pValue); /* Keep the same value */ }else{ /* Insert the callback return value */ jx9_array_add_elem(pArray, &sKey, &sResult); } jx9MemObjRelease(&sKey); jx9MemObjRelease(&sResult); } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } jx9_result_value(pCtx, pArray); return JX9_OK; } /* * bool array_walk(array &$array, callback $funcname [, value $userdata ] ) * Apply a user function to every member of an array. * Parameters * $array * The input array. * $funcname * Typically, funcname takes on two parameters.The array parameter's value being * the first, and the key/index second. * Note: * If funcname needs to be working with the actual values of the array, specify the first * parameter of funcname as a reference. Then, any changes made to those elements will * be made in the original array itself. * $userdata * If the optional userdata parameter is supplied, it will be passed as the third parameter * to the callback funcname. * Return * Returns TRUE on success or FALSE on failure. */ static int jx9_hashmap_walk(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pValue, *pUserData, sKey; jx9_hashmap_node *pEntry; jx9_hashmap *pMap; sxi32 rc; sxu32 n; if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) ){ /* Invalid/Missing arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } pUserData = nArg > 2 ? apArg[2] : 0; /* Point to the internal representation of the input hashmap */ pMap = (jx9_hashmap *)apArg[0]->x.pOther; jx9MemObjInit(pMap->pVm, &sKey); /* Perform the desired operation */ pEntry = pMap->pFirst; for( n = 0 ; n < pMap->nEntry ; n++ ){ /* Extract the node value */ pValue = HashmapExtractNodeValue(pEntry); if( pValue ){ /* Extract the entry key */ jx9HashmapExtractNodeKey(pEntry, &sKey); /* Invoke the supplied callback */ rc = jx9VmCallUserFunctionAp(pMap->pVm, apArg[1], 0, pValue, &sKey, pUserData, 0); jx9MemObjRelease(&sKey); if( rc != SXRET_OK ){ /* An error occured while invoking the supplied callback [i.e: not defined] */ jx9_result_bool(pCtx, 0); /* return FALSE */ return JX9_OK; } } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ } /* All done, return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * Table of built-in hashmap functions. */ static const jx9_builtin_func aHashmapFunc[] = { {"count", jx9_hashmap_count }, {"sizeof", jx9_hashmap_count }, {"array_key_exists", jx9_hashmap_key_exists }, {"array_pop", jx9_hashmap_pop }, {"array_push", jx9_hashmap_push }, {"array_shift", jx9_hashmap_shift }, {"array_product", jx9_hashmap_product }, {"array_sum", jx9_hashmap_sum }, {"array_values", jx9_hashmap_values }, {"array_same", jx9_hashmap_same }, {"array_merge", jx9_hashmap_merge }, {"array_diff", jx9_hashmap_diff }, {"array_intersect", jx9_hashmap_intersect}, {"in_array", jx9_hashmap_in_array }, {"array_copy", jx9_hashmap_copy }, {"array_erase", jx9_hashmap_erase }, {"array_map", jx9_hashmap_map }, {"array_walk", jx9_hashmap_walk }, {"sort", jx9_hashmap_sort }, {"rsort", jx9_hashmap_rsort }, {"usort", jx9_hashmap_usort }, {"current", jx9_hashmap_current }, {"each", jx9_hashmap_each }, {"pos", jx9_hashmap_current }, {"next", jx9_hashmap_next }, {"prev", jx9_hashmap_prev }, {"end", jx9_hashmap_end }, {"reset", jx9_hashmap_reset }, {"key", jx9_hashmap_simple_key } }; /* * Register the built-in hashmap functions defined above. */ JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm) { sxu32 n; for( n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++ ){ jx9_create_function(&(*pVm), aHashmapFunc[n].zName, aHashmapFunc[n].xFunc, 0); } } /* * Iterate throw hashmap entries and invoke the given callback [i.e: xWalk()] for each * retrieved entry. * Note that argument are passed to the callback by copy. That is, any modification to * the entry value in the callback body will not alter the real value. * If the callback wishes to abort processing [i.e: it's invocation] it must return * a value different from JX9_OK. * Refer to [jx9_array_walk()] for more information. */ JX9_PRIVATE sxi32 jx9HashmapWalk( jx9_hashmap *pMap, /* Target hashmap */ int (*xWalk)(jx9_value *, jx9_value *, void *), /* Walker callback */ void *pUserData /* Last argument to xWalk() */ ) { jx9_hashmap_node *pEntry; jx9_value sKey, sValue; sxi32 rc; sxu32 n; /* Initialize walker parameter */ rc = SXRET_OK; jx9MemObjInit(pMap->pVm, &sKey); jx9MemObjInit(pMap->pVm, &sValue); n = pMap->nEntry; pEntry = pMap->pFirst; /* Start the iteration process */ for(;;){ if( n < 1 ){ break; } /* Extract a copy of the key and a copy the current value */ jx9HashmapExtractNodeKey(pEntry, &sKey); jx9HashmapExtractNodeValue(pEntry, &sValue, FALSE); /* Invoke the user callback */ rc = xWalk(&sKey, &sValue, pUserData); /* Release the copy of the key and the value */ jx9MemObjRelease(&sKey); jx9MemObjRelease(&sValue); if( rc != JX9_OK ){ /* Callback request an operation abort */ return SXERR_ABORT; } /* Point to the next entry */ pEntry = pEntry->pPrev; /* Reverse link */ n--; } /* All done */ return SXRET_OK; } /* * ---------------------------------------------------------- * File: jx9_json.c * MD5: 31a27f8797418de511c669feed763341 * ---------------------------------------------------------- */ /* * 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: json.c v1.0 FreeBSD 2012-12-16 00:28 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* This file deals with JSON serialization, decoding and stuff like that. */ /* * Section: * JSON encoding/decoding routines. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Devel. */ /* Forward reference */ static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); static int VmJsonObjectEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); /* * JSON encoder state is stored in an instance * of the following structure. */ typedef struct json_private_data json_private_data; struct json_private_data { SyBlob *pOut; /* Output consumer buffer */ int isFirst; /* True if first encoded entry */ int iFlags; /* JSON encoding flags */ int nRecCount; /* Recursion count */ }; /* * Returns the JSON representation of a value.In other word perform a JSON encoding operation. * According to wikipedia * JSON's basic types are: * Number (double precision floating-point format in JavaScript, generally depends on implementation) * String (double-quoted Unicode, with backslash escaping) * Boolean (true or false) * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values * do not need to be of the same type) * Object (an unordered collection of key:value pairs with the ':' character separating the key * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should * be distinct from each other) * null (empty) * Non-significant white space may be added freely around the "structural characters" * (i.e. the brackets "[{]}", colon ":" and comma ","). */ static sxi32 VmJsonEncode( jx9_value *pIn, /* Encode this value */ json_private_data *pData /* Context data */ ){ SyBlob *pOut = pData->pOut; int nByte; if( jx9_value_is_null(pIn) || jx9_value_is_resource(pIn)){ /* null */ SyBlobAppend(pOut, "null", sizeof("null")-1); }else if( jx9_value_is_bool(pIn) ){ int iBool = jx9_value_to_bool(pIn); sxu32 iLen; /* true/false */ iLen = iBool ? sizeof("true") : sizeof("false"); SyBlobAppend(pOut, iBool ? "true" : "false", iLen-1); }else if( jx9_value_is_numeric(pIn) && !jx9_value_is_string(pIn) ){ const char *zNum; /* Get a string representation of the number */ zNum = jx9_value_to_string(pIn, &nByte); SyBlobAppend(pOut,zNum,nByte); }else if( jx9_value_is_string(pIn) ){ const char *zIn, *zEnd; int c; /* Encode the string */ zIn = jx9_value_to_string(pIn, &nByte); zEnd = &zIn[nByte]; /* Append the double quote */ SyBlobAppend(pOut,"\"", sizeof(char)); for(;;){ if( zIn >= zEnd ){ /* No more input to process */ break; } c = zIn[0]; /* Advance the stream cursor */ zIn++; if( c == '"' || c == '\\' ){ /* Unescape the character */ SyBlobAppend(pOut,"\\", sizeof(char)); } /* Append character verbatim */ SyBlobAppend(pOut,(const char *)&c,sizeof(char)); } /* Append the double quote */ SyBlobAppend(pOut,"\"",sizeof(char)); }else if( jx9_value_is_json_array(pIn) ){ /* Encode the array/object */ pData->isFirst = 1; if( jx9_value_is_json_object(pIn) ){ /* Encode the object instance */ pData->isFirst = 1; /* Append the curly braces */ SyBlobAppend(pOut, "{",sizeof(char)); /* Iterate throw object attribute */ jx9_array_walk(pIn,VmJsonObjectEncode, pData); /* Append the closing curly braces */ SyBlobAppend(pOut, "}",sizeof(char)); }else{ /* Append the square bracket or curly braces */ SyBlobAppend(pOut,"[",sizeof(char)); /* Iterate throw array entries */ jx9_array_walk(pIn, VmJsonArrayEncode, pData); /* Append the closing square bracket or curly braces */ SyBlobAppend(pOut,"]",sizeof(char)); } }else{ /* Can't happen */ SyBlobAppend(pOut,"null",sizeof("null")-1); } /* All done */ return JX9_OK; } /* * The following walker callback is invoked each time we need * to encode an array to JSON. */ static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData) { json_private_data *pJson = (json_private_data *)pUserData; if( pJson->nRecCount > 31 ){ /* Recursion limit reached, return immediately */ SXUNUSED(pKey); /* cc warning */ return JX9_OK; } if( !pJson->isFirst ){ /* Append the colon first */ SyBlobAppend(pJson->pOut,",",(int)sizeof(char)); } /* Encode the value */ pJson->nRecCount++; VmJsonEncode(pValue, pJson); pJson->nRecCount--; pJson->isFirst = 0; return JX9_OK; } /* * The following walker callback is invoked each time we need to encode * a object instance [i.e: Object in the JX9 jargon] to JSON. */ static int VmJsonObjectEncode(jx9_value *pKey,jx9_value *pValue,void *pUserData) { json_private_data *pJson = (json_private_data *)pUserData; const char *zKey; int nByte; if( pJson->nRecCount > 31 ){ /* Recursion limit reached, return immediately */ return JX9_OK; } if( !pJson->isFirst ){ /* Append the colon first */ SyBlobAppend(pJson->pOut,",",sizeof(char)); } /* Extract a string representation of the key */ zKey = jx9_value_to_string(pKey, &nByte); /* Append the key and the double colon */ if( nByte > 0 ){ SyBlobAppend(pJson->pOut,"\"",sizeof(char)); SyBlobAppend(pJson->pOut,zKey,(sxu32)nByte); SyBlobAppend(pJson->pOut,"\"",sizeof(char)); }else{ /* Can't happen */ SyBlobAppend(pJson->pOut,"null",sizeof("null")-1); } SyBlobAppend(pJson->pOut,":",sizeof(char)); /* Encode the value */ pJson->nRecCount++; VmJsonEncode(pValue, pJson); pJson->nRecCount--; pJson->isFirst = 0; return JX9_OK; } /* * Returns a string containing the JSON representation of value. * In other words, perform the serialization of the given JSON object. */ JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut) { json_private_data sJson; /* Prepare the JSON data */ sJson.nRecCount = 0; sJson.pOut = pOut; sJson.isFirst = 1; sJson.iFlags = 0; /* Perform the encoding operation */ VmJsonEncode(pValue, &sJson); /* All done */ return JX9_OK; } /* Possible tokens from the JSON tokenization process */ #define JSON_TK_TRUE 0x001 /* Boolean true */ #define JSON_TK_FALSE 0x002 /* Boolean false */ #define JSON_TK_STR 0x004 /* String enclosed in double quotes */ #define JSON_TK_NULL 0x008 /* null */ #define JSON_TK_NUM 0x010 /* Numeric */ #define JSON_TK_OCB 0x020 /* Open curly braces '{' */ #define JSON_TK_CCB 0x040 /* Closing curly braces '}' */ #define JSON_TK_OSB 0x080 /* Open square bracke '[' */ #define JSON_TK_CSB 0x100 /* Closing square bracket ']' */ #define JSON_TK_COLON 0x200 /* Single colon ':' */ #define JSON_TK_COMMA 0x400 /* Single comma ',' */ #define JSON_TK_ID 0x800 /* ID */ #define JSON_TK_INVALID 0x1000 /* Unexpected token */ /* * Tokenize an entire JSON input. * Get a single low-level token from the input file. * Update the stream pointer so that it points to the first * character beyond the extracted token. */ static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, void *pCtxData) { int *pJsonErr = (int *)pUserData; SyString *pStr; int c; /* Ignore leading white spaces */ while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ /* Advance the stream cursor */ if( pStream->zText[0] == '\n' ){ /* Update line counter */ pStream->nLine++; } pStream->zText++; } if( pStream->zText >= pStream->zEnd ){ /* End of input reached */ SXUNUSED(pCtxData); /* cc warning */ return SXERR_EOF; } /* Record token starting position and line */ pToken->nLine = pStream->nLine; pToken->pUserData = 0; pStr = &pToken->sData; SyStringInitFromBuf(pStr, pStream->zText, 0); if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ /* The following code fragment is taken verbatim from the xPP source tree. * xPP is a modern embeddable macro processor with advanced features useful for * application seeking for a production quality, ready to use macro processor. * xPP is a widely used library developed and maintened by Symisc Systems. * You can reach the xPP home page by following this link: * http://xpp.symisc.net/ */ const unsigned char *zIn; /* Isolate UTF-8 or alphanumeric stream */ if( pStream->zText[0] < 0xc0 ){ pStream->zText++; } for(;;){ zIn = pStream->zText; if( zIn[0] >= 0xc0 ){ zIn++; /* UTF-8 stream */ while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ zIn++; } } /* Skip alphanumeric stream */ while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ zIn++; } if( zIn == pStream->zText ){ /* Not an UTF-8 or alphanumeric stream */ break; } /* Synchronize pointers */ pStream->zText = zIn; } /* Record token length */ pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); /* A simple identifier */ pToken->nType = JSON_TK_ID; if( pStr->nByte == sizeof("true") -1 && SyStrnicmp(pStr->zString, "true", sizeof("true")-1) == 0 ){ /* boolean true */ pToken->nType = JSON_TK_TRUE; }else if( pStr->nByte == sizeof("false") -1 && SyStrnicmp(pStr->zString,"false", sizeof("false")-1) == 0 ){ /* boolean false */ pToken->nType = JSON_TK_FALSE; }else if( pStr->nByte == sizeof("null") -1 && SyStrnicmp(pStr->zString,"null", sizeof("null")-1) == 0 ){ /* NULL */ pToken->nType = JSON_TK_NULL; } return SXRET_OK; } if( pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']' || pStream->zText[0] == ':' || pStream->zText[0] == ',' ){ /* Single character */ c = pStream->zText[0]; /* Set token type */ switch(c){ case '[': pToken->nType = JSON_TK_OSB; break; case '{': pToken->nType = JSON_TK_OCB; break; case '}': pToken->nType = JSON_TK_CCB; break; case ']': pToken->nType = JSON_TK_CSB; break; case ':': pToken->nType = JSON_TK_COLON; break; case ',': pToken->nType = JSON_TK_COMMA; break; default: break; } /* Advance the stream cursor */ pStream->zText++; }else if( pStream->zText[0] == '"') { /* JSON string */ pStream->zText++; pStr->zString++; /* Delimit the string */ while( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '"' && pStream->zText[-1] != '\\' ){ break; } if( pStream->zText[0] == '\n' ){ /* Update line counter */ pStream->nLine++; } pStream->zText++; } if( pStream->zText >= pStream->zEnd ){ /* Missing closing '"' */ pToken->nType = JSON_TK_INVALID; *pJsonErr = SXERR_SYNTAX; }else{ pToken->nType = JSON_TK_STR; pStream->zText++; /* Jump the closing double quotes */ } }else if( pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ /* Number */ pStream->zText++; pToken->nType = JSON_TK_NUM; while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( c == '.' ){ /* Real number */ pStream->zText++; while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( c=='e' || c=='E' ){ pStream->zText++; if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( c =='+' || c=='-' ){ pStream->zText++; } while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } } } } }else if( c=='e' || c=='E' ){ /* Real number */ pStream->zText++; if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( c =='+' || c=='-' ){ pStream->zText++; } while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } } } } }else{ /* Unexpected token */ pToken->nType = JSON_TK_INVALID; /* Advance the stream cursor */ pStream->zText++; *pJsonErr = SXERR_SYNTAX; /* Abort processing immediatley */ return SXERR_ABORT; } /* record token length */ pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); if( pToken->nType == JSON_TK_STR ){ pStr->nByte--; } /* Return to the lexer */ return SXRET_OK; } /* * JSON decoded input consumer callback signature. */ typedef int (*ProcJSONConsumer)(jx9_context *, jx9_value *, jx9_value *, void *); /* * JSON decoder state is kept in the following structure. */ typedef struct json_decoder json_decoder; struct json_decoder { jx9_context *pCtx; /* Call context */ ProcJSONConsumer xConsumer; /* Consumer callback */ void *pUserData; /* Last argument to xConsumer() */ int iFlags; /* Configuration flags */ SyToken *pIn; /* Token stream */ SyToken *pEnd; /* End of the token stream */ int rec_count; /* Current nesting level */ int *pErr; /* JSON decoding error if any */ }; /* Forward declaration */ static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData); /* * Dequote [i.e: Resolve all backslash escapes ] a JSON string and store * the result in the given jx9_value. */ static void VmJsonDequoteString(const SyString *pStr, jx9_value *pWorker) { const char *zIn = pStr->zString; const char *zEnd = &pStr->zString[pStr->nByte]; const char *zCur; int c; /* Mark the value as a string */ jx9_value_string(pWorker, "", 0); /* Empty string */ for(;;){ zCur = zIn; while( zIn < zEnd && zIn[0] != '\\' ){ zIn++; } if( zIn > zCur ){ /* Append chunk verbatim */ jx9_value_string(pWorker, zCur, (int)(zIn-zCur)); } zIn++; if( zIn >= zEnd ){ /* End of the input reached */ break; } c = zIn[0]; /* Unescape the character */ switch(c){ case '"': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; case '\\': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; case 'n': jx9_value_string(pWorker, "\n", (int)sizeof(char)); break; case 'r': jx9_value_string(pWorker, "\r", (int)sizeof(char)); break; case 't': jx9_value_string(pWorker, "\t", (int)sizeof(char)); break; case 'f': jx9_value_string(pWorker, "\f", (int)sizeof(char)); break; default: jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; } /* Advance the stream cursor */ zIn++; } } /* * Returns a jx9_value holding the image of a JSON string. In other word perform a JSON decoding operation. * According to wikipedia * JSON's basic types are: * Number (double precision floating-point format in JavaScript, generally depends on implementation) * String (double-quoted Unicode, with backslash escaping) * Boolean (true or false) * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values * do not need to be of the same type) * Object (an unordered collection of key:value pairs with the ':' character separating the key * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should * be distinct from each other) * null (empty) * Non-significant white space may be added freely around the "structural characters" (i.e. the brackets "[{]}", colon ":" and comma ", "). */ static sxi32 VmJsonDecode( json_decoder *pDecoder, /* JSON decoder */ jx9_value *pArrayKey /* Key for the decoded array */ ){ jx9_value *pWorker; /* Worker variable */ sxi32 rc; /* Check if we do not nest to much */ if( pDecoder->rec_count > 31 ){ /* Nesting limit reached, abort decoding immediately */ return SXERR_ABORT; } if( pDecoder->pIn->nType & (JSON_TK_STR|JSON_TK_ID|JSON_TK_TRUE|JSON_TK_FALSE|JSON_TK_NULL|JSON_TK_NUM) ){ /* Scalar value */ pWorker = jx9_context_new_scalar(pDecoder->pCtx); if( pWorker == 0 ){ jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); /* Abort the decoding operation immediately */ return SXERR_ABORT; } /* Reflect the JSON image */ if( pDecoder->pIn->nType & JSON_TK_NULL ){ /* Nullify the value.*/ jx9_value_null(pWorker); }else if( pDecoder->pIn->nType & (JSON_TK_TRUE|JSON_TK_FALSE) ){ /* Boolean value */ jx9_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0 ); }else if( pDecoder->pIn->nType & JSON_TK_NUM ){ SyString *pStr = &pDecoder->pIn->sData; /* * Numeric value. * Get a string representation first then try to get a numeric * value. */ jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); /* Obtain a numeric representation */ jx9MemObjToNumeric(pWorker); }else if( pDecoder->pIn->nType & JSON_TK_ID ){ SyString *pStr = &pDecoder->pIn->sData; jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); }else{ /* Dequote the string */ VmJsonDequoteString(&pDecoder->pIn->sData, pWorker); } /* Invoke the consumer callback */ rc = pDecoder->xConsumer(pDecoder->pCtx, pArrayKey, pWorker, pDecoder->pUserData); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } /* All done, advance the stream cursor */ pDecoder->pIn++; }else if( pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) { ProcJSONConsumer xOld; void *pOld; /* Array representation*/ pDecoder->pIn++; /* Create a working array */ pWorker = jx9_context_new_array(pDecoder->pCtx); if( pWorker == 0 ){ jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); /* Abort the decoding operation immediately */ return SXERR_ABORT; } /* Save the old consumer */ xOld = pDecoder->xConsumer; pOld = pDecoder->pUserData; /* Set the new consumer */ pDecoder->xConsumer = VmJsonArrayDecoder; pDecoder->pUserData = pWorker; /* Decode the array */ for(;;){ /* Jump trailing comma. Note that the standard JX9 engine will not let you * do this. */ while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ pDecoder->pIn++; } if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/ ){ if( pDecoder->pIn < pDecoder->pEnd ){ pDecoder->pIn++; /* Jump the trailing ']' */ } break; } /* Recurse and decode the entry */ pDecoder->rec_count++; rc = VmJsonDecode(pDecoder, 0); pDecoder->rec_count--; if( rc == SXERR_ABORT ){ /* Abort processing immediately */ return SXERR_ABORT; } /*The cursor is automatically advanced by the VmJsonDecode() function */ if( (pDecoder->pIn < pDecoder->pEnd) && ((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/|JSON_TK_COMMA/*','*/))==0) ){ /* Unexpected token, abort immediatley */ *pDecoder->pErr = SXERR_SYNTAX; return SXERR_ABORT; } } /* Restore the old consumer */ pDecoder->xConsumer = xOld; pDecoder->pUserData = pOld; /* Invoke the old consumer on the decoded array */ xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); }else if( pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) { ProcJSONConsumer xOld; jx9_value *pKey; void *pOld; /* Object representation*/ pDecoder->pIn++; /* Create a working array */ pWorker = jx9_context_new_array(pDecoder->pCtx); pKey = jx9_context_new_scalar(pDecoder->pCtx); if( pWorker == 0 || pKey == 0){ jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); /* Abort the decoding operation immediately */ return SXERR_ABORT; } /* Save the old consumer */ xOld = pDecoder->xConsumer; pOld = pDecoder->pUserData; /* Set the new consumer */ pDecoder->xConsumer = VmJsonArrayDecoder; pDecoder->pUserData = pWorker; /* Decode the object */ for(;;){ /* Jump trailing comma. Note that the standard JX9 engine will not let you * do this. */ while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ pDecoder->pIn++; } if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/ ){ if( pDecoder->pIn < pDecoder->pEnd ){ pDecoder->pIn++; /* Jump the trailing ']' */ } break; } if( (pDecoder->pIn->nType & (JSON_TK_ID|JSON_TK_STR)) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd || (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0){ /* Syntax error, return immediately */ *pDecoder->pErr = SXERR_SYNTAX; return SXERR_ABORT; } if( pDecoder->pIn->nType & JSON_TK_ID ){ SyString *pStr = &pDecoder->pIn->sData; jx9_value_string(pKey, pStr->zString, (int)pStr->nByte); }else{ /* Dequote the key */ VmJsonDequoteString(&pDecoder->pIn->sData, pKey); } /* Jump the key and the colon */ pDecoder->pIn += 2; /* Recurse and decode the value */ pDecoder->rec_count++; rc = VmJsonDecode(pDecoder, pKey); pDecoder->rec_count--; if( rc == SXERR_ABORT ){ /* Abort processing immediately */ return SXERR_ABORT; } /* Reset the internal buffer of the key */ jx9_value_reset_string_cursor(pKey); /*The cursor is automatically advanced by the VmJsonDecode() function */ } /* Restore the old consumer */ pDecoder->xConsumer = xOld; pDecoder->pUserData = pOld; /* Invoke the old consumer on the decoded object*/ xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); /* Release the key */ jx9_context_release_value(pDecoder->pCtx, pKey); }else{ /* Unexpected token */ return SXERR_ABORT; /* Abort immediately */ } /* Release the worker variable */ jx9_context_release_value(pDecoder->pCtx, pWorker); return SXRET_OK; } /* * The following JSON decoder callback is invoked each time * a JSON array representation [i.e: [15, "hello", FALSE] ] * is being decoded. */ static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) { jx9_value *pArray = (jx9_value *)pUserData; /* Insert the entry */ jx9_array_add_elem(pArray, pKey, pWorker); /* Will make it's own copy */ SXUNUSED(pCtx); /* cc warning */ /* All done */ return SXRET_OK; } /* * Standard JSON decoder callback. */ static int VmJsonDefaultDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) { /* Return the value directly */ jx9_result_value(pCtx, pWorker); /* Will make it's own copy */ SXUNUSED(pKey); /* cc warning */ SXUNUSED(pUserData); /* All done */ return SXRET_OK; } /* * Exported JSON decoding interface */ JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte) { jx9_vm *pVm = pCtx->pVm; json_decoder sDecoder; SySet sToken; SyLex sLex; sxi32 rc; /* Tokenize the input */ SySetInit(&sToken, &pVm->sAllocator, sizeof(SyToken)); rc = SXRET_OK; SyLexInit(&sLex, &sToken, VmJsonTokenize, &rc); SyLexTokenizeInput(&sLex,zJSON,(sxu32)nByte, 0, 0, 0); if( rc != SXRET_OK ){ /* Something goes wrong while tokenizing input. [i.e: Unexpected token] */ SyLexRelease(&sLex); SySetRelease(&sToken); /* return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Fill the decoder */ sDecoder.pCtx = pCtx; sDecoder.pErr = &rc; sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken); sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)]; sDecoder.iFlags = 0; sDecoder.rec_count = 0; /* Set a default consumer */ sDecoder.xConsumer = VmJsonDefaultDecoder; sDecoder.pUserData = 0; /* Decode the raw JSON input */ rc = VmJsonDecode(&sDecoder, 0); if( rc == SXERR_ABORT ){ /* * Something goes wrong while decoding JSON input.Return NULL. */ jx9_result_null(pCtx); } /* Clean-up the mess left behind */ SyLexRelease(&sLex); SySetRelease(&sToken); /* All done */ return JX9_OK; } /* * ---------------------------------------------------------- * File: jx9_lex.c * MD5: a79518c0635dbaf5dcfaca62efa2faf8 * ---------------------------------------------------------- */ /* * 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: lex.c v1.0 FreeBSD 2012-12-09 00:19 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* This file implements a thread-safe and full reentrant lexical analyzer for the Jx9 programming language */ /* Forward declarations */ static sxu32 keywordCode(const char *z,int n); static sxi32 LexExtractNowdoc(SyStream *pStream,SyToken *pToken); /* * Tokenize a raw jx9 input. * Get a single low-level token from the input file. Update the stream pointer so that * it points to the first character beyond the extracted token. */ static sxi32 jx9TokenizeInput(SyStream *pStream,SyToken *pToken,void *pUserData,void *pCtxData) { SyString *pStr; sxi32 rc; /* Ignore leading white spaces */ while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ /* Advance the stream cursor */ if( pStream->zText[0] == '\n' ){ /* Update line counter */ pStream->nLine++; } pStream->zText++; } if( pStream->zText >= pStream->zEnd ){ /* End of input reached */ return SXERR_EOF; } /* Record token starting position and line */ pToken->nLine = pStream->nLine; pToken->pUserData = 0; pStr = &pToken->sData; SyStringInitFromBuf(pStr, pStream->zText, 0); if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ /* The following code fragment is taken verbatim from the xPP source tree. * xPP is a modern embeddable macro processor with advanced features useful for * application seeking for a production quality, ready to use macro processor. * xPP is a widely used library developed and maintened by Symisc Systems. * You can reach the xPP home page by following this link: * http://xpp.symisc.net/ */ const unsigned char *zIn; sxu32 nKeyword; /* Isolate UTF-8 or alphanumeric stream */ if( pStream->zText[0] < 0xc0 ){ pStream->zText++; } for(;;){ zIn = pStream->zText; if( zIn[0] >= 0xc0 ){ zIn++; /* UTF-8 stream */ while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ zIn++; } } /* Skip alphanumeric stream */ while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ zIn++; } if( zIn == pStream->zText ){ /* Not an UTF-8 or alphanumeric stream */ break; } /* Synchronize pointers */ pStream->zText = zIn; } /* Record token length */ pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); nKeyword = keywordCode(pStr->zString, (int)pStr->nByte); if( nKeyword != JX9_TK_ID ){ /* We are dealing with a keyword [i.e: if, function, CREATE, ...], save the keyword ID */ pToken->nType = JX9_TK_KEYWORD; pToken->pUserData = SX_INT_TO_PTR(nKeyword); }else{ /* A simple identifier */ pToken->nType = JX9_TK_ID; } }else{ sxi32 c; /* Non-alpha stream */ if( pStream->zText[0] == '#' || ( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '/') ){ pStream->zText++; /* Inline comments */ while( pStream->zText < pStream->zEnd && pStream->zText[0] != '\n' ){ pStream->zText++; } /* Tell the upper-layer to ignore this token */ return SXERR_CONTINUE; }else if( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '*' ){ pStream->zText += 2; /* Block comment */ while( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '*' ){ if( &pStream->zText[1] >= pStream->zEnd || pStream->zText[1] == '/' ){ break; } } if( pStream->zText[0] == '\n' ){ pStream->nLine++; } pStream->zText++; } pStream->zText += 2; /* Tell the upper-layer to ignore this token */ return SXERR_CONTINUE; }else if( SyisDigit(pStream->zText[0]) ){ pStream->zText++; /* Decimal digit stream */ while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } /* Mark the token as integer until we encounter a real number */ pToken->nType = JX9_TK_INTEGER; if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( c == '.' ){ /* Real number */ pStream->zText++; while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( c=='e' || c=='E' ){ pStream->zText++; if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ pStream->zText++; } while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } } } } pToken->nType = JX9_TK_REAL; }else if( c=='e' || c=='E' ){ SXUNUSED(pUserData); /* Prevent compiler warning */ SXUNUSED(pCtxData); pStream->zText++; if( pStream->zText < pStream->zEnd ){ c = pStream->zText[0]; if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ pStream->zText++; } while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ pStream->zText++; } } pToken->nType = JX9_TK_REAL; }else if( c == 'x' || c == 'X' ){ /* Hex digit stream */ pStream->zText++; while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisHex(pStream->zText[0]) ){ pStream->zText++; } }else if(c == 'b' || c == 'B' ){ /* Binary digit stream */ pStream->zText++; while( pStream->zText < pStream->zEnd && (pStream->zText[0] == '0' || pStream->zText[0] == '1') ){ pStream->zText++; } } } /* Record token length */ pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); return SXRET_OK; } c = pStream->zText[0]; pStream->zText++; /* Advance the stream cursor */ /* Assume we are dealing with an operator*/ pToken->nType = JX9_TK_OP; switch(c){ case '$': pToken->nType = JX9_TK_DOLLAR; break; case '{': pToken->nType = JX9_TK_OCB; break; case '}': pToken->nType = JX9_TK_CCB; break; case '(': pToken->nType = JX9_TK_LPAREN; break; case '[': pToken->nType |= JX9_TK_OSB; break; /* Bitwise operation here, since the square bracket token '[' * is a potential operator [i.e: subscripting] */ case ']': pToken->nType = JX9_TK_CSB; break; case ')': { SySet *pTokSet = pStream->pSet; /* Assemble type cast operators [i.e: (int), (float), (bool)...] */ if( pTokSet->nUsed >= 2 ){ SyToken *pTmp; /* Peek the last recongnized token */ pTmp = (SyToken *)SySetPeek(pTokSet); if( pTmp->nType & JX9_TK_KEYWORD ){ sxi32 nID = SX_PTR_TO_INT(pTmp->pUserData); if( (sxu32)nID & (JX9_TKWRD_INT|JX9_TKWRD_FLOAT|JX9_TKWRD_STRING|JX9_TKWRD_BOOL) ){ pTmp = (SyToken *)SySetAt(pTokSet, pTokSet->nUsed - 2); if( pTmp->nType & JX9_TK_LPAREN ){ /* Merge the three tokens '(' 'TYPE' ')' into a single one */ const char * zTypeCast = "(int)"; if( nID & JX9_TKWRD_FLOAT ){ zTypeCast = "(float)"; }else if( nID & JX9_TKWRD_BOOL ){ zTypeCast = "(bool)"; }else if( nID & JX9_TKWRD_STRING ){ zTypeCast = "(string)"; } /* Reflect the change */ pToken->nType = JX9_TK_OP; SyStringInitFromBuf(&pToken->sData, zTypeCast, SyStrlen(zTypeCast)); /* Save the instance associated with the type cast operator */ pToken->pUserData = (void *)jx9ExprExtractOperator(&pToken->sData, 0); /* Remove the two previous tokens */ pTokSet->nUsed -= 2; return SXRET_OK; } } } } pToken->nType = JX9_TK_RPAREN; break; } case '\'':{ /* Single quoted string */ pStr->zString++; while( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '\'' ){ if( pStream->zText[-1] != '\\' ){ break; }else{ const unsigned char *zPtr = &pStream->zText[-2]; sxi32 i = 1; while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ zPtr--; i++; } if((i&1)==0){ break; } } } if( pStream->zText[0] == '\n' ){ pStream->nLine++; } pStream->zText++; } /* Record token length and type */ pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); pToken->nType = JX9_TK_SSTR; /* Jump the trailing single quote */ pStream->zText++; return SXRET_OK; } case '"':{ sxi32 iNest; /* Double quoted string */ pStr->zString++; while( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '{' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '$'){ iNest = 1; pStream->zText++; /* TICKET 1433-40: Hnadle braces'{}' in double quoted string where everything is allowed */ while(pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '{' ){ iNest++; }else if (pStream->zText[0] == '}' ){ iNest--; if( iNest <= 0 ){ pStream->zText++; break; } }else if( pStream->zText[0] == '\n' ){ pStream->nLine++; } pStream->zText++; } if( pStream->zText >= pStream->zEnd ){ break; } } if( pStream->zText[0] == '"' ){ if( pStream->zText[-1] != '\\' ){ break; }else{ const unsigned char *zPtr = &pStream->zText[-2]; sxi32 i = 1; while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ zPtr--; i++; } if((i&1)==0){ break; } } } if( pStream->zText[0] == '\n' ){ pStream->nLine++; } pStream->zText++; } /* Record token length and type */ pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); pToken->nType = JX9_TK_DSTR; /* Jump the trailing quote */ pStream->zText++; return SXRET_OK; } case ':': pToken->nType = JX9_TK_COLON; /* Single colon */ break; case ',': pToken->nType |= JX9_TK_COMMA; break; /* Comma is also an operator */ case ';': pToken->nType = JX9_TK_SEMI; break; /* Handle combined operators [i.e: +=, ===, !=== ...] */ case '=': pToken->nType |= JX9_TK_EQUAL; if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '=' ){ pToken->nType &= ~JX9_TK_EQUAL; /* Current operator: == */ pStream->zText++; if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: === */ pStream->zText++; } } } break; case '!': if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: != */ pStream->zText++; if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: !== */ pStream->zText++; } } break; case '&': pToken->nType |= JX9_TK_AMPER; if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '&' ){ pToken->nType &= ~JX9_TK_AMPER; /* Current operator: && */ pStream->zText++; }else if( pStream->zText[0] == '=' ){ pToken->nType &= ~JX9_TK_AMPER; /* Current operator: &= */ pStream->zText++; } } case '.': if( pStream->zText < pStream->zEnd && (pStream->zText[0] == '.' || pStream->zText[0] == '=') ){ /* Concatenation operator: '..' or '.=' */ pStream->zText++; } break; case '|': if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '|' ){ /* Current operator: || */ pStream->zText++; }else if( pStream->zText[0] == '=' ){ /* Current operator: |= */ pStream->zText++; } } break; case '+': if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '+' ){ /* Current operator: ++ */ pStream->zText++; }else if( pStream->zText[0] == '=' ){ /* Current operator: += */ pStream->zText++; } } break; case '-': if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '-' ){ /* Current operator: -- */ pStream->zText++; }else if( pStream->zText[0] == '=' ){ /* Current operator: -= */ pStream->zText++; }else if( pStream->zText[0] == '>' ){ /* Current operator: -> */ pStream->zText++; } } break; case '*': if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: *= */ pStream->zText++; } break; case '/': if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: /= */ pStream->zText++; } break; case '%': if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: %= */ pStream->zText++; } break; case '^': if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: ^= */ pStream->zText++; } break; case '<': if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '<' ){ /* Current operator: << */ pStream->zText++; if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '=' ){ /* Current operator: <<= */ pStream->zText++; }else if( pStream->zText[0] == '<' ){ /* Current Token: <<< */ pStream->zText++; /* This may be the beginning of a Heredoc/Nowdoc string, try to delimit it */ rc = LexExtractNowdoc(&(*pStream), &(*pToken)); if( rc == SXRET_OK ){ /* Here/Now doc successfuly extracted */ return SXRET_OK; } } } }else if( pStream->zText[0] == '>' ){ /* Current operator: <> */ pStream->zText++; }else if( pStream->zText[0] == '=' ){ /* Current operator: <= */ pStream->zText++; } } break; case '>': if( pStream->zText < pStream->zEnd ){ if( pStream->zText[0] == '>' ){ /* Current operator: >> */ pStream->zText++; if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ /* Current operator: >>= */ pStream->zText++; } }else if( pStream->zText[0] == '=' ){ /* Current operator: >= */ pStream->zText++; } } break; default: break; } if( pStr->nByte <= 0 ){ /* Record token length */ pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); } if( pToken->nType & JX9_TK_OP ){ const jx9_expr_op *pOp; /* Check if the extracted token is an operator */ pOp = jx9ExprExtractOperator(pStr, (SyToken *)SySetPeek(pStream->pSet)); if( pOp == 0 ){ /* Not an operator */ pToken->nType &= ~JX9_TK_OP; if( pToken->nType <= 0 ){ pToken->nType = JX9_TK_OTHER; } }else{ /* Save the instance associated with this operator for later processing */ pToken->pUserData = (void *)pOp; } } } /* Tell the upper-layer to save the extracted token for later processing */ return SXRET_OK; } /***** This file contains automatically generated code ****** ** ** The code in this file has been automatically generated by ** ** $Header: /sqlite/sqlite/tool/mkkeywordhash.c,v 1.38 2011/12/21 01:00:46 $ ** ** The code in this file implements a function that determines whether ** or not a given identifier is really a JX9 keyword. The same thing ** might be implemented more directly using a hand-written hash table. ** But by using this automatically generated code, the size of the code ** is substantially reduced. This is important for embedded applications ** on platforms with limited memory. */ /* Hash score: 35 */ static sxu32 keywordCode(const char *z, int n) { /* zText[] encodes 188 bytes of keywords in 128 bytes */ /* printegereturnconstaticaselseifloatincludefaultDIEXITcontinue */ /* diewhileASPRINTbooleanbreakforeachfunctionimportstringswitch */ /* uplink */ static const char zText[127] = { 'p','r','i','n','t','e','g','e','r','e','t','u','r','n','c','o','n','s', 't','a','t','i','c','a','s','e','l','s','e','i','f','l','o','a','t','i', 'n','c','l','u','d','e','f','a','u','l','t','D','I','E','X','I','T','c', 'o','n','t','i','n','u','e','d','i','e','w','h','i','l','e','A','S','P', 'R','I','N','T','b','o','o','l','e','a','n','b','r','e','a','k','f','o', 'r','e','a','c','h','f','u','n','c','t','i','o','n','i','m','p','o','r', 't','s','t','r','i','n','g','s','w','i','t','c','h','u','p','l','i','n', 'k', }; static const unsigned char aHash[59] = { 0, 0, 0, 0, 15, 0, 30, 0, 0, 2, 19, 18, 0, 0, 10, 3, 12, 0, 28, 29, 23, 0, 13, 22, 0, 0, 14, 24, 25, 31, 11, 0, 0, 0, 0, 1, 5, 0, 0, 20, 0, 27, 9, 0, 0, 0, 8, 0, 0, 26, 6, 0, 0, 17, 0, 0, 0, 0, 0, }; static const unsigned char aNext[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 21, 7, 0, 0, 0, 0, 0, }; static const unsigned char aLen[31] = { 5, 7, 3, 6, 5, 6, 4, 2, 6, 4, 2, 5, 7, 7, 3, 4, 8, 3, 5, 2, 5, 4, 7, 5, 3, 7, 8, 6, 6, 6, 6, }; static const sxu16 aOffset[31] = { 0, 2, 2, 8, 14, 17, 22, 23, 25, 25, 29, 30, 35, 40, 47, 49, 53, 61, 64, 69, 71, 76, 76, 83, 88, 88, 95, 103, 109, 115, 121, }; static const sxu32 aCode[31] = { JX9_TKWRD_PRINT, JX9_TKWRD_INT, JX9_TKWRD_INT, JX9_TKWRD_RETURN, JX9_TKWRD_CONST, JX9_TKWRD_STATIC, JX9_TKWRD_CASE, JX9_TKWRD_AS, JX9_TKWRD_ELIF, JX9_TKWRD_ELSE, JX9_TKWRD_IF, JX9_TKWRD_FLOAT, JX9_TKWRD_INCLUDE, JX9_TKWRD_DEFAULT, JX9_TKWRD_DIE, JX9_TKWRD_EXIT, JX9_TKWRD_CONTINUE, JX9_TKWRD_DIE, JX9_TKWRD_WHILE, JX9_TKWRD_AS, JX9_TKWRD_PRINT, JX9_TKWRD_BOOL, JX9_TKWRD_BOOL, JX9_TKWRD_BREAK, JX9_TKWRD_FOR, JX9_TKWRD_FOREACH, JX9_TKWRD_FUNCTION, JX9_TKWRD_IMPORT, JX9_TKWRD_STRING, JX9_TKWRD_SWITCH, JX9_TKWRD_UPLINK, }; int h, i; if( n<2 ) return JX9_TK_ID; h = (((int)z[0]*4) ^ ((int)z[n-1]*3) ^ n) % 59; for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){ if( (int)aLen[i]==n && SyMemcmp(&zText[aOffset[i]],z,n)==0 ){ /* JX9_TKWRD_PRINT */ /* JX9_TKWRD_INT */ /* JX9_TKWRD_INT */ /* JX9_TKWRD_RETURN */ /* JX9_TKWRD_CONST */ /* JX9_TKWRD_STATIC */ /* JX9_TKWRD_CASE */ /* JX9_TKWRD_AS */ /* JX9_TKWRD_ELIF */ /* JX9_TKWRD_ELSE */ /* JX9_TKWRD_IF */ /* JX9_TKWRD_FLOAT */ /* JX9_TKWRD_INCLUDE */ /* JX9_TKWRD_DEFAULT */ /* JX9_TKWRD_DIE */ /* JX9_TKWRD_EXIT */ /* JX9_TKWRD_CONTINUE */ /* JX9_TKWRD_DIE */ /* JX9_TKWRD_WHILE */ /* JX9_TKWRD_AS */ /* JX9_TKWRD_PRINT */ /* JX9_TKWRD_BOOL */ /* JX9_TKWRD_BOOL */ /* JX9_TKWRD_BREAK */ /* JX9_TKWRD_FOR */ /* JX9_TKWRD_FOREACH */ /* JX9_TKWRD_FUNCTION */ /* JX9_TKWRD_IMPORT */ /* JX9_TKWRD_STRING */ /* JX9_TKWRD_SWITCH */ /* JX9_TKWRD_UPLINK */ return aCode[i]; } } return JX9_TK_ID; } /* * Extract a heredoc/nowdoc text from a raw JX9 input. * According to the JX9 language reference manual: * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier * is provided, then a newline. The string itself follows, and then the same identifier again * to close the quotation. * The closing identifier must begin in the first column of the line. Also, the identifier must * follow the same naming rules as any other label in JX9: it must contain only alphanumeric * characters and underscores, and must start with a non-digit character or underscore. * Heredoc text behaves just like a double-quoted string, without the double quotes. * This means that quotes in a heredoc do not need to be escaped, but the escape codes listed * above can still be used. Variables are expanded, but the same care must be taken when expressing * complex variables inside a heredoc as with strings. * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. * The construct is ideal for embedding JX9 code or other large blocks of text without the need * for escaping. It shares some features in common with the SGML construct, in that * it declares a block of text which is not for parsing. * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier which follows * is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc identifiers also apply to nowdoc * identifiers, especially those regarding the appearance of the closing identifier. */ static sxi32 LexExtractNowdoc(SyStream *pStream, SyToken *pToken) { const unsigned char *zIn = pStream->zText; const unsigned char *zEnd = pStream->zEnd; const unsigned char *zPtr; SyString sDelim; SyString sStr; /* Jump leading white spaces */ while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ zIn++; } if( zIn >= zEnd ){ /* A simple symbol, return immediately */ return SXERR_CONTINUE; } if( zIn[0] == '\'' || zIn[0] == '"' ){ zIn++; } if( zIn[0] < 0xc0 && !SyisAlphaNum(zIn[0]) && zIn[0] != '_' ){ /* Invalid delimiter, return immediately */ return SXERR_CONTINUE; } /* Isolate the identifier */ sDelim.zString = (const char *)zIn; for(;;){ zPtr = zIn; /* Skip alphanumeric stream */ while( zPtr < zEnd && zPtr[0] < 0xc0 && (SyisAlphaNum(zPtr[0]) || zPtr[0] == '_') ){ zPtr++; } if( zPtr < zEnd && zPtr[0] >= 0xc0 ){ zPtr++; /* UTF-8 stream */ while( zPtr < zEnd && ((zPtr[0] & 0xc0) == 0x80) ){ zPtr++; } } if( zPtr == zIn ){ /* Not an UTF-8 or alphanumeric stream */ break; } /* Synchronize pointers */ zIn = zPtr; } /* Get the identifier length */ sDelim.nByte = (sxu32)((const char *)zIn-sDelim.zString); if( zIn[0] == '"' || zIn[0] == '\'' ){ /* Jump the trailing single quote */ zIn++; } /* Jump trailing white spaces */ while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ zIn++; } if( sDelim.nByte <= 0 || zIn >= zEnd || zIn[0] != '\n' ){ /* Invalid syntax */ return SXERR_CONTINUE; } pStream->nLine++; /* Increment line counter */ zIn++; /* Isolate the delimited string */ sStr.zString = (const char *)zIn; /* Go and found the closing delimiter */ for(;;){ /* Synchronize with the next line */ while( zIn < zEnd && zIn[0] != '\n' ){ zIn++; } if( zIn >= zEnd ){ /* End of the input reached, break immediately */ pStream->zText = pStream->zEnd; break; } pStream->nLine++; /* Increment line counter */ zIn++; if( (sxu32)(zEnd - zIn) >= sDelim.nByte && SyMemcmp((const void *)sDelim.zString, (const void *)zIn, sDelim.nByte) == 0 ){ zPtr = &zIn[sDelim.nByte]; while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ zPtr++; } if( zPtr >= zEnd ){ /* End of input */ pStream->zText = zPtr; break; } if( zPtr[0] == ';' ){ const unsigned char *zCur = zPtr; zPtr++; while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ zPtr++; } if( zPtr >= zEnd || zPtr[0] == '\n' ){ /* Closing delimiter found, break immediately */ pStream->zText = zCur; /* Keep the semi-colon */ break; } }else if( zPtr[0] == '\n' ){ /* Closing delimiter found, break immediately */ pStream->zText = zPtr; /* Synchronize with the stream cursor */ break; } /* Synchronize pointers and continue searching */ zIn = zPtr; } } /* For(;;) */ /* Get the delimited string length */ sStr.nByte = (sxu32)((const char *)zIn-sStr.zString); /* Record token type and length */ pToken->nType = JX9_TK_NOWDOC; SyStringDupPtr(&pToken->sData, &sStr); /* Remove trailing white spaces */ SyStringRightTrim(&pToken->sData); /* All done */ return SXRET_OK; } /* * Tokenize a raw jx9 input. * This is the public tokenizer called by most code generator routines. */ JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput,sxu32 nLen,SySet *pOut) { SyLex sLexer; sxi32 rc; /* Initialize the lexer */ rc = SyLexInit(&sLexer, &(*pOut),jx9TokenizeInput,0); if( rc != SXRET_OK ){ return rc; } /* Tokenize input */ rc = SyLexTokenizeInput(&sLexer, zInput, nLen, 0, 0, 0); /* Release the lexer */ SyLexRelease(&sLexer); /* Tokenization result */ return rc; } /* * ---------------------------------------------------------- * File: jx9_lib.c * MD5: a684fb6677b1ab0110d03536f1280c50 * ---------------------------------------------------------- */ /* * 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: lib.c v5.1 Win7 2012-08-08 04:19 stable $ */ /* * Symisc Run-Time API: A modern thread safe replacement of the standard libc * Copyright (C) Symisc Systems 2007-2012, http://www.symisc.net/ * * The Symisc Run-Time API is an independent project developed by symisc systems * internally as a secure replacement of the standard libc. * The library is re-entrant, thread-safe and platform independent. */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif #if defined(__WINNT__) #include #else #include #endif #if defined(JX9_ENABLE_THREADS) /* SyRunTimeApi: sxmutex.c */ #if defined(__WINNT__) struct SyMutex { CRITICAL_SECTION sMutex; sxu32 nType; /* Mutex type, one of SXMUTEX_TYPE_* */ }; /* Preallocated static mutex */ static SyMutex aStaticMutexes[] = { {{0}, SXMUTEX_TYPE_STATIC_1}, {{0}, SXMUTEX_TYPE_STATIC_2}, {{0}, SXMUTEX_TYPE_STATIC_3}, {{0}, SXMUTEX_TYPE_STATIC_4}, {{0}, SXMUTEX_TYPE_STATIC_5}, {{0}, SXMUTEX_TYPE_STATIC_6} }; static BOOL winMutexInit = FALSE; static LONG winMutexLock = 0; static sxi32 WinMutexGlobaInit(void) { LONG rc; rc = InterlockedCompareExchange(&winMutexLock, 1, 0); if ( rc == 0 ){ sxu32 n; for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ InitializeCriticalSection(&aStaticMutexes[n].sMutex); } winMutexInit = TRUE; }else{ /* Someone else is doing this for us */ while( winMutexInit == FALSE ){ Sleep(1); } } return SXRET_OK; } static void WinMutexGlobalRelease(void) { LONG rc; rc = InterlockedCompareExchange(&winMutexLock, 0, 1); if( rc == 1 ){ /* The first to decrement to zero does the actual global release */ if( winMutexInit == TRUE ){ sxu32 n; for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ DeleteCriticalSection(&aStaticMutexes[n].sMutex); } winMutexInit = FALSE; } } } static SyMutex * WinMutexNew(int nType) { SyMutex *pMutex = 0; if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ /* Allocate a new mutex */ pMutex = (SyMutex *)HeapAlloc(GetProcessHeap(), 0, sizeof(SyMutex)); if( pMutex == 0 ){ return 0; } InitializeCriticalSection(&pMutex->sMutex); }else{ /* Use a pre-allocated static mutex */ if( nType > SXMUTEX_TYPE_STATIC_6 ){ nType = SXMUTEX_TYPE_STATIC_6; } pMutex = &aStaticMutexes[nType - 3]; } pMutex->nType = nType; return pMutex; } static void WinMutexRelease(SyMutex *pMutex) { if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ DeleteCriticalSection(&pMutex->sMutex); HeapFree(GetProcessHeap(), 0, pMutex); } } static void WinMutexEnter(SyMutex *pMutex) { EnterCriticalSection(&pMutex->sMutex); } static sxi32 WinMutexTryEnter(SyMutex *pMutex) { #ifdef _WIN32_WINNT BOOL rc; /* Only WindowsNT platforms */ rc = TryEnterCriticalSection(&pMutex->sMutex); if( rc ){ return SXRET_OK; }else{ return SXERR_BUSY; } #else return SXERR_NOTIMPLEMENTED; #endif } static void WinMutexLeave(SyMutex *pMutex) { LeaveCriticalSection(&pMutex->sMutex); } /* Export Windows mutex interfaces */ static const SyMutexMethods sWinMutexMethods = { WinMutexGlobaInit, /* xGlobalInit() */ WinMutexGlobalRelease, /* xGlobalRelease() */ WinMutexNew, /* xNew() */ WinMutexRelease, /* xRelease() */ WinMutexEnter, /* xEnter() */ WinMutexTryEnter, /* xTryEnter() */ WinMutexLeave /* xLeave() */ }; JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) { return &sWinMutexMethods; } #elif defined(__UNIXES__) #include struct SyMutex { pthread_mutex_t sMutex; sxu32 nType; }; static SyMutex * UnixMutexNew(int nType) { static SyMutex aStaticMutexes[] = { {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_1}, {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_2}, {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_3}, {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_4}, {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_5}, {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_6} }; SyMutex *pMutex; if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ pthread_mutexattr_t sRecursiveAttr; /* Allocate a new mutex */ pMutex = (SyMutex *)malloc(sizeof(SyMutex)); if( pMutex == 0 ){ return 0; } if( nType == SXMUTEX_TYPE_RECURSIVE ){ pthread_mutexattr_init(&sRecursiveAttr); pthread_mutexattr_settype(&sRecursiveAttr, PTHREAD_MUTEX_RECURSIVE); } pthread_mutex_init(&pMutex->sMutex, nType == SXMUTEX_TYPE_RECURSIVE ? &sRecursiveAttr : 0 ); if( nType == SXMUTEX_TYPE_RECURSIVE ){ pthread_mutexattr_destroy(&sRecursiveAttr); } }else{ /* Use a pre-allocated static mutex */ if( nType > SXMUTEX_TYPE_STATIC_6 ){ nType = SXMUTEX_TYPE_STATIC_6; } pMutex = &aStaticMutexes[nType - 3]; } pMutex->nType = nType; return pMutex; } static void UnixMutexRelease(SyMutex *pMutex) { if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ pthread_mutex_destroy(&pMutex->sMutex); free(pMutex); } } static void UnixMutexEnter(SyMutex *pMutex) { pthread_mutex_lock(&pMutex->sMutex); } static void UnixMutexLeave(SyMutex *pMutex) { pthread_mutex_unlock(&pMutex->sMutex); } /* Export pthread mutex interfaces */ static const SyMutexMethods sPthreadMutexMethods = { 0, /* xGlobalInit() */ 0, /* xGlobalRelease() */ UnixMutexNew, /* xNew() */ UnixMutexRelease, /* xRelease() */ UnixMutexEnter, /* xEnter() */ 0, /* xTryEnter() */ UnixMutexLeave /* xLeave() */ }; JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) { return &sPthreadMutexMethods; } #else /* Host application must register their own mutex subsystem if the target * platform is not an UNIX-like or windows systems. */ struct SyMutex { sxu32 nType; }; static SyMutex * DummyMutexNew(int nType) { static SyMutex sMutex; SXUNUSED(nType); return &sMutex; } static void DummyMutexRelease(SyMutex *pMutex) { SXUNUSED(pMutex); } static void DummyMutexEnter(SyMutex *pMutex) { SXUNUSED(pMutex); } static void DummyMutexLeave(SyMutex *pMutex) { SXUNUSED(pMutex); } /* Export the dummy mutex interfaces */ static const SyMutexMethods sDummyMutexMethods = { 0, /* xGlobalInit() */ 0, /* xGlobalRelease() */ DummyMutexNew, /* xNew() */ DummyMutexRelease, /* xRelease() */ DummyMutexEnter, /* xEnter() */ 0, /* xTryEnter() */ DummyMutexLeave /* xLeave() */ }; JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) { return &sDummyMutexMethods; } #endif /* __WINNT__ */ #endif /* JX9_ENABLE_THREADS */ static void * SyOSHeapAlloc(sxu32 nByte) { void *pNew; #if defined(__WINNT__) pNew = HeapAlloc(GetProcessHeap(), 0, nByte); #else pNew = malloc((size_t)nByte); #endif return pNew; } static void * SyOSHeapRealloc(void *pOld, sxu32 nByte) { void *pNew; #if defined(__WINNT__) pNew = HeapReAlloc(GetProcessHeap(), 0, pOld, nByte); #else pNew = realloc(pOld, (size_t)nByte); #endif return pNew; } static void SyOSHeapFree(void *pPtr) { #if defined(__WINNT__) HeapFree(GetProcessHeap(), 0, pPtr); #else free(pPtr); #endif } /* SyRunTimeApi:sxstr.c */ JX9_PRIVATE sxu32 SyStrlen(const char *zSrc) { register const char *zIn = zSrc; #if defined(UNTRUST) if( zIn == 0 ){ return 0; } #endif for(;;){ if( !zIn[0] ){ break; } zIn++; if( !zIn[0] ){ break; } zIn++; if( !zIn[0] ){ break; } zIn++; if( !zIn[0] ){ break; } zIn++; } return (sxu32)(zIn - zSrc); } JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos) { const char *zIn = zStr; const char *zEnd; zEnd = &zIn[nLen]; for(;;){ if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; } return SXERR_NOTFOUND; } #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos) { const char *zIn = zStr; const char *zEnd; zEnd = &zIn[nLen - 1]; for( ;; ){ if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; } return SXERR_NOTFOUND; } #endif /* JX9_DISABLE_BUILTIN_FUNC */ JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos) { const char *zIn = zSrc; const char *zPtr; const char *zEnd; sxi32 c; zEnd = &zSrc[nLen]; for(;;){ if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; } return SXERR_NOTFOUND; } #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen) { const unsigned char *zP = (const unsigned char *)zLeft; const unsigned char *zQ = (const unsigned char *)zRight; if( SX_EMPTY_STR(zP) || SX_EMPTY_STR(zQ) ){ return SX_EMPTY_STR(zP) ? (SX_EMPTY_STR(zQ) ? 0 : -1) :1; } if( nLen <= 0 ){ return 0; } for(;;){ if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; } return (sxi32)(zP[0] - zQ[0]); } #endif JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen) { register unsigned char *p = (unsigned char *)zLeft; register unsigned char *q = (unsigned char *)zRight; if( SX_EMPTY_STR(p) || SX_EMPTY_STR(q) ){ return SX_EMPTY_STR(p)? SX_EMPTY_STR(q) ? 0 : -1 :1; } for(;;){ if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; } return (sxi32)(SyCharToLower(p[0]) - SyCharToLower(q[0])); } JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen) { unsigned char *zBuf = (unsigned char *)zDest; unsigned char *zIn = (unsigned char *)zSrc; unsigned char *zEnd; #if defined(UNTRUST) if( zSrc == (const char *)zDest ){ return 0; } #endif if( nLen <= 0 ){ nLen = SyStrlen(zSrc); } zEnd = &zBuf[nDestLen - 1]; /* reserve a room for the null terminator */ for(;;){ if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; } zBuf[0] = 0; return (sxu32)(zBuf-(unsigned char *)zDest); } /* SyRunTimeApi:sxmem.c */ JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize) { register unsigned char *zSrc = (unsigned char *)pSrc; unsigned char *zEnd; #if defined(UNTRUST) if( zSrc == 0 || nSize <= 0 ){ return ; } #endif zEnd = &zSrc[nSize]; for(;;){ if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; } } JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize) { sxi32 rc; if( nSize <= 0 ){ return 0; } if( pB1 == 0 || pB2 == 0 ){ return pB1 != 0 ? 1 : (pB2 == 0 ? 0 : -1); } SX_MACRO_FAST_CMP(pB1, pB2, nSize, rc); return rc; } JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen) { if( pSrc == 0 || pDest == 0 ){ return 0; } if( pSrc == (const void *)pDest ){ return nLen; } SX_MACRO_FAST_MEMCPY(pSrc, pDest, nLen); return nLen; } static void * MemOSAlloc(sxu32 nBytes) { sxu32 *pChunk; pChunk = (sxu32 *)SyOSHeapAlloc(nBytes + sizeof(sxu32)); if( pChunk == 0 ){ return 0; } pChunk[0] = nBytes; return (void *)&pChunk[1]; } static void * MemOSRealloc(void *pOld, sxu32 nBytes) { sxu32 *pOldChunk; sxu32 *pChunk; pOldChunk = (sxu32 *)(((char *)pOld)-sizeof(sxu32)); if( pOldChunk[0] >= nBytes ){ return pOld; } pChunk = (sxu32 *)SyOSHeapRealloc(pOldChunk, nBytes + sizeof(sxu32)); if( pChunk == 0 ){ return 0; } pChunk[0] = nBytes; return (void *)&pChunk[1]; } static void MemOSFree(void *pBlock) { void *pChunk; pChunk = (void *)(((char *)pBlock)-sizeof(sxu32)); SyOSHeapFree(pChunk); } static sxu32 MemOSChunkSize(void *pBlock) { sxu32 *pChunk; pChunk = (sxu32 *)(((char *)pBlock)-sizeof(sxu32)); return pChunk[0]; } /* Export OS allocation methods */ static const SyMemMethods sOSAllocMethods = { MemOSAlloc, MemOSRealloc, MemOSFree, MemOSChunkSize, 0, 0, 0 }; static void * MemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte) { SyMemBlock *pBlock; sxi32 nRetry = 0; /* Append an extra block so we can tracks allocated chunks and avoid memory * leaks. */ nByte += sizeof(SyMemBlock); for(;;){ pBlock = (SyMemBlock *)pBackend->pMethods->xAlloc(nByte); if( pBlock != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ break; } nRetry++; } if( pBlock == 0 ){ return 0; } pBlock->pNext = pBlock->pPrev = 0; /* Link to the list of already tracked blocks */ MACRO_LD_PUSH(pBackend->pBlocks, pBlock); #if defined(UNTRUST) pBlock->nGuard = SXMEM_BACKEND_MAGIC; #endif pBackend->nBlock++; return (void *)&pBlock[1]; } JX9_PRIVATE void * SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte) { void *pChunk; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) ){ return 0; } #endif if( pBackend->pMutexMethods ){ SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); } pChunk = MemBackendAlloc(&(*pBackend), nByte); if( pBackend->pMutexMethods ){ SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); } return pChunk; } static void * MemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) { SyMemBlock *pBlock, *pNew, *pPrev, *pNext; sxu32 nRetry = 0; if( pOld == 0 ){ return MemBackendAlloc(&(*pBackend), nByte); } pBlock = (SyMemBlock *)(((char *)pOld) - sizeof(SyMemBlock)); #if defined(UNTRUST) if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ return 0; } #endif nByte += sizeof(SyMemBlock); pPrev = pBlock->pPrev; pNext = pBlock->pNext; for(;;){ pNew = (SyMemBlock *)pBackend->pMethods->xRealloc(pBlock, nByte); if( pNew != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ break; } nRetry++; } if( pNew == 0 ){ return 0; } if( pNew != pBlock ){ if( pPrev == 0 ){ pBackend->pBlocks = pNew; }else{ pPrev->pNext = pNew; } if( pNext ){ pNext->pPrev = pNew; } #if defined(UNTRUST) pNew->nGuard = SXMEM_BACKEND_MAGIC; #endif } return (void *)&pNew[1]; } JX9_PRIVATE void * SyMemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) { void *pChunk; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) ){ return 0; } #endif if( pBackend->pMutexMethods ){ SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); } pChunk = MemBackendRealloc(&(*pBackend), pOld, nByte); if( pBackend->pMutexMethods ){ SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); } return pChunk; } static sxi32 MemBackendFree(SyMemBackend *pBackend, void * pChunk) { SyMemBlock *pBlock; pBlock = (SyMemBlock *)(((char *)pChunk) - sizeof(SyMemBlock)); #if defined(UNTRUST) if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ return SXERR_CORRUPT; } #endif /* Unlink from the list of active blocks */ if( pBackend->nBlock > 0 ){ /* Release the block */ #if defined(UNTRUST) /* Mark as stale block */ pBlock->nGuard = 0x635B; #endif MACRO_LD_REMOVE(pBackend->pBlocks, pBlock); pBackend->nBlock--; pBackend->pMethods->xFree(pBlock); } return SXRET_OK; } JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void * pChunk) { sxi32 rc; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) ){ return SXERR_CORRUPT; } #endif if( pChunk == 0 ){ return SXRET_OK; } if( pBackend->pMutexMethods ){ SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); } rc = MemBackendFree(&(*pBackend), pChunk); if( pBackend->pMutexMethods ){ SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); } return rc; } #if defined(JX9_ENABLE_THREADS) JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods) { SyMutex *pMutex; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) || pMethods == 0 || pMethods->xNew == 0){ return SXERR_CORRUPT; } #endif pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); if( pMutex == 0 ){ return SXERR_OS; } /* Attach the mutex to the memory backend */ pBackend->pMutex = pMutex; pBackend->pMutexMethods = pMethods; return SXRET_OK; } JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend) { #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) ){ return SXERR_CORRUPT; } #endif if( pBackend->pMutex == 0 ){ /* There is no mutex subsystem at all */ return SXRET_OK; } SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex); pBackend->pMutexMethods = 0; pBackend->pMutex = 0; return SXRET_OK; } #endif /* * Memory pool allocator */ #define SXMEM_POOL_MAGIC 0xDEAD #define SXMEM_POOL_MAXALLOC (1<<(SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR)) #define SXMEM_POOL_MINALLOC (1<<(SXMEM_POOL_INCR)) static sxi32 MemPoolBucketAlloc(SyMemBackend *pBackend, sxu32 nBucket) { char *zBucket, *zBucketEnd; SyMemHeader *pHeader; sxu32 nBucketSize; /* Allocate one big block first */ zBucket = (char *)MemBackendAlloc(&(*pBackend), SXMEM_POOL_MAXALLOC); if( zBucket == 0 ){ return SXERR_MEM; } zBucketEnd = &zBucket[SXMEM_POOL_MAXALLOC]; /* Divide the big block into mini bucket pool */ nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); pBackend->apPool[nBucket] = pHeader = (SyMemHeader *)zBucket; for(;;){ if( &zBucket[nBucketSize] >= zBucketEnd ){ break; } pHeader->pNext = (SyMemHeader *)&zBucket[nBucketSize]; /* Advance the cursor to the next available chunk */ pHeader = pHeader->pNext; zBucket += nBucketSize; } pHeader->pNext = 0; return SXRET_OK; } static void * MemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte) { SyMemHeader *pBucket, *pNext; sxu32 nBucketSize; sxu32 nBucket; if( nByte + sizeof(SyMemHeader) >= SXMEM_POOL_MAXALLOC ){ /* Allocate a big chunk directly */ pBucket = (SyMemHeader *)MemBackendAlloc(&(*pBackend), nByte+sizeof(SyMemHeader)); if( pBucket == 0 ){ return 0; } /* Record as big block */ pBucket->nBucket = (sxu32)(SXMEM_POOL_MAGIC << 16) | SXU16_HIGH; return (void *)(pBucket+1); } /* Locate the appropriate bucket */ nBucket = 0; nBucketSize = SXMEM_POOL_MINALLOC; while( nByte + sizeof(SyMemHeader) > nBucketSize ){ nBucketSize <<= 1; nBucket++; } pBucket = pBackend->apPool[nBucket]; if( pBucket == 0 ){ sxi32 rc; rc = MemPoolBucketAlloc(&(*pBackend), nBucket); if( rc != SXRET_OK ){ return 0; } pBucket = pBackend->apPool[nBucket]; } /* Remove from the free list */ pNext = pBucket->pNext; pBackend->apPool[nBucket] = pNext; /* Record bucket&magic number */ pBucket->nBucket = (SXMEM_POOL_MAGIC << 16) | nBucket; return (void *)&pBucket[1]; } JX9_PRIVATE void * SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte) { void *pChunk; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) ){ return 0; } #endif if( pBackend->pMutexMethods ){ SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); } pChunk = MemBackendPoolAlloc(&(*pBackend), nByte); if( pBackend->pMutexMethods ){ SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); } return pChunk; } static sxi32 MemBackendPoolFree(SyMemBackend *pBackend, void * pChunk) { SyMemHeader *pHeader; sxu32 nBucket; /* Get the corresponding bucket */ pHeader = (SyMemHeader *)(((char *)pChunk) - sizeof(SyMemHeader)); /* Sanity check to avoid misuse */ if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ return SXERR_CORRUPT; } nBucket = pHeader->nBucket & 0xFFFF; if( nBucket == SXU16_HIGH ){ /* Free the big block */ MemBackendFree(&(*pBackend), pHeader); }else{ /* Return to the free list */ pHeader->pNext = pBackend->apPool[nBucket & 0x0f]; pBackend->apPool[nBucket & 0x0f] = pHeader; } return SXRET_OK; } JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void * pChunk) { sxi32 rc; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) || pChunk == 0 ){ return SXERR_CORRUPT; } #endif if( pBackend->pMutexMethods ){ SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); } rc = MemBackendPoolFree(&(*pBackend), pChunk); if( pBackend->pMutexMethods ){ SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); } return rc; } #if 0 static void * MemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) { sxu32 nBucket, nBucketSize; SyMemHeader *pHeader; void * pNew; if( pOld == 0 ){ /* Allocate a new pool */ pNew = MemBackendPoolAlloc(&(*pBackend), nByte); return pNew; } /* Get the corresponding bucket */ pHeader = (SyMemHeader *)(((char *)pOld) - sizeof(SyMemHeader)); /* Sanity check to avoid misuse */ if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ return 0; } nBucket = pHeader->nBucket & 0xFFFF; if( nBucket == SXU16_HIGH ){ /* Big block */ return MemBackendRealloc(&(*pBackend), pHeader, nByte); } nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); if( nBucketSize >= nByte + sizeof(SyMemHeader) ){ /* The old bucket can honor the requested size */ return pOld; } /* Allocate a new pool */ pNew = MemBackendPoolAlloc(&(*pBackend), nByte); if( pNew == 0 ){ return 0; } /* Copy the old data into the new block */ SyMemcpy(pOld, pNew, nBucketSize); /* Free the stale block */ MemBackendPoolFree(&(*pBackend), pOld); return pNew; } JX9_PRIVATE void * SyMemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) { void *pChunk; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) ){ return 0; } #endif if( pBackend->pMutexMethods ){ SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); } pChunk = MemBackendPoolRealloc(&(*pBackend), pOld, nByte); if( pBackend->pMutexMethods ){ SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); } return pChunk; } #endif JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void * pUserData) { #if defined(UNTRUST) if( pBackend == 0 ){ return SXERR_EMPTY; } #endif /* Zero the allocator first */ SyZero(&(*pBackend), sizeof(SyMemBackend)); pBackend->xMemError = xMemErr; pBackend->pUserData = pUserData; /* Switch to the OS memory allocator */ pBackend->pMethods = &sOSAllocMethods; if( pBackend->pMethods->xInit ){ /* Initialize the backend */ if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ return SXERR_ABORT; } } #if defined(UNTRUST) pBackend->nMagic = SXMEM_BACKEND_MAGIC; #endif return SXRET_OK; } JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void * pUserData) { #if defined(UNTRUST) if( pBackend == 0 || pMethods == 0){ return SXERR_EMPTY; } #endif if( pMethods->xAlloc == 0 || pMethods->xRealloc == 0 || pMethods->xFree == 0 || pMethods->xChunkSize == 0 ){ /* mandatory methods are missing */ return SXERR_INVALID; } /* Zero the allocator first */ SyZero(&(*pBackend), sizeof(SyMemBackend)); pBackend->xMemError = xMemErr; pBackend->pUserData = pUserData; /* Switch to the host application memory allocator */ pBackend->pMethods = pMethods; if( pBackend->pMethods->xInit ){ /* Initialize the backend */ if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ return SXERR_ABORT; } } #if defined(UNTRUST) pBackend->nMagic = SXMEM_BACKEND_MAGIC; #endif return SXRET_OK; } JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent) { sxu8 bInheritMutex; #if defined(UNTRUST) if( pBackend == 0 || SXMEM_BACKEND_CORRUPT(pParent) ){ return SXERR_CORRUPT; } #endif /* Zero the allocator first */ SyZero(&(*pBackend), sizeof(SyMemBackend)); pBackend->pMethods = pParent->pMethods; pBackend->xMemError = pParent->xMemError; pBackend->pUserData = pParent->pUserData; bInheritMutex = pParent->pMutexMethods ? TRUE : FALSE; if( bInheritMutex ){ pBackend->pMutexMethods = pParent->pMutexMethods; /* Create a private mutex */ pBackend->pMutex = pBackend->pMutexMethods->xNew(SXMUTEX_TYPE_FAST); if( pBackend->pMutex == 0){ return SXERR_OS; } } #if defined(UNTRUST) pBackend->nMagic = SXMEM_BACKEND_MAGIC; #endif return SXRET_OK; } static sxi32 MemBackendRelease(SyMemBackend *pBackend) { SyMemBlock *pBlock, *pNext; pBlock = pBackend->pBlocks; for(;;){ if( pBackend->nBlock == 0 ){ break; } pNext = pBlock->pNext; pBackend->pMethods->xFree(pBlock); pBlock = pNext; pBackend->nBlock--; /* LOOP ONE */ if( pBackend->nBlock == 0 ){ break; } pNext = pBlock->pNext; pBackend->pMethods->xFree(pBlock); pBlock = pNext; pBackend->nBlock--; /* LOOP TWO */ if( pBackend->nBlock == 0 ){ break; } pNext = pBlock->pNext; pBackend->pMethods->xFree(pBlock); pBlock = pNext; pBackend->nBlock--; /* LOOP THREE */ if( pBackend->nBlock == 0 ){ break; } pNext = pBlock->pNext; pBackend->pMethods->xFree(pBlock); pBlock = pNext; pBackend->nBlock--; /* LOOP FOUR */ } if( pBackend->pMethods->xRelease ){ pBackend->pMethods->xRelease(pBackend->pMethods->pUserData); } pBackend->pMethods = 0; pBackend->pBlocks = 0; #if defined(UNTRUST) pBackend->nMagic = 0x2626; #endif return SXRET_OK; } JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend) { sxi32 rc; #if defined(UNTRUST) if( SXMEM_BACKEND_CORRUPT(pBackend) ){ return SXERR_INVALID; } #endif if( pBackend->pMutexMethods ){ SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); } rc = MemBackendRelease(&(*pBackend)); if( pBackend->pMutexMethods ){ SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex); } return rc; } JX9_PRIVATE void * SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize) { void *pNew; #if defined(UNTRUST) if( pSrc == 0 || nSize <= 0 ){ return 0; } #endif pNew = SyMemBackendAlloc(&(*pBackend), nSize); if( pNew ){ SyMemcpy(pSrc, pNew, nSize); } return pNew; } JX9_PRIVATE char * SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize) { char *zDest; zDest = (char *)SyMemBackendAlloc(&(*pBackend), nSize + 1); if( zDest ){ Systrcpy(zDest, nSize+1, zSrc, nSize); } return zDest; } JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize) { #if defined(UNTRUST) if( pBlob == 0 || pBuffer == 0 || nSize < 1 ){ return SXERR_EMPTY; } #endif pBlob->pBlob = pBuffer; pBlob->mByte = nSize; pBlob->nByte = 0; pBlob->pAllocator = 0; pBlob->nFlags = SXBLOB_LOCKED|SXBLOB_STATIC; return SXRET_OK; } JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator) { #if defined(UNTRUST) if( pBlob == 0 ){ return SXERR_EMPTY; } #endif pBlob->pBlob = 0; pBlob->mByte = pBlob->nByte = 0; pBlob->pAllocator = &(*pAllocator); pBlob->nFlags = 0; return SXRET_OK; } JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte) { #if defined(UNTRUST) if( pBlob == 0 ){ return SXERR_EMPTY; } #endif pBlob->pBlob = (void *)pData; pBlob->nByte = nByte; pBlob->mByte = 0; pBlob->nFlags |= SXBLOB_RDONLY; return SXRET_OK; } #ifndef SXBLOB_MIN_GROWTH #define SXBLOB_MIN_GROWTH 16 #endif static sxi32 BlobPrepareGrow(SyBlob *pBlob, sxu32 *pByte) { sxu32 nByte; void *pNew; nByte = *pByte; if( pBlob->nFlags & (SXBLOB_LOCKED|SXBLOB_STATIC) ){ if ( SyBlobFreeSpace(pBlob) < nByte ){ *pByte = SyBlobFreeSpace(pBlob); if( (*pByte) == 0 ){ return SXERR_SHORT; } } return SXRET_OK; } if( pBlob->nFlags & SXBLOB_RDONLY ){ /* Make a copy of the read-only item */ if( pBlob->nByte > 0 ){ pNew = SyMemBackendDup(pBlob->pAllocator, pBlob->pBlob, pBlob->nByte); if( pNew == 0 ){ return SXERR_MEM; } pBlob->pBlob = pNew; pBlob->mByte = pBlob->nByte; }else{ pBlob->pBlob = 0; pBlob->mByte = 0; } /* Remove the read-only flag */ pBlob->nFlags &= ~SXBLOB_RDONLY; } if( SyBlobFreeSpace(pBlob) >= nByte ){ return SXRET_OK; } if( pBlob->mByte > 0 ){ nByte = nByte + pBlob->mByte * 2 + SXBLOB_MIN_GROWTH; }else if ( nByte < SXBLOB_MIN_GROWTH ){ nByte = SXBLOB_MIN_GROWTH; } pNew = SyMemBackendRealloc(pBlob->pAllocator, pBlob->pBlob, nByte); if( pNew == 0 ){ return SXERR_MEM; } pBlob->pBlob = pNew; pBlob->mByte = nByte; return SXRET_OK; } JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize) { sxu8 *zBlob; sxi32 rc; if( nSize < 1 ){ return SXRET_OK; } rc = BlobPrepareGrow(&(*pBlob), &nSize); if( SXRET_OK != rc ){ return rc; } if( pData ){ zBlob = (sxu8 *)pBlob->pBlob ; zBlob = &zBlob[pBlob->nByte]; pBlob->nByte += nSize; SX_MACRO_FAST_MEMCPY(pData, zBlob, nSize); } return SXRET_OK; } JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob) { sxi32 rc; sxu32 n; n = pBlob->nByte; rc = SyBlobAppend(&(*pBlob), (const void *)"\0", sizeof(char)); if (rc == SXRET_OK ){ pBlob->nByte = n; } return rc; } JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest) { sxi32 rc = SXRET_OK; if( pSrc->nByte > 0 ){ rc = SyBlobAppend(&(*pDest), pSrc->pBlob, pSrc->nByte); } return rc; } JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob) { pBlob->nByte = 0; if( pBlob->nFlags & SXBLOB_RDONLY ){ /* Read-only (Not malloced chunk) */ pBlob->pBlob = 0; pBlob->mByte = 0; pBlob->nFlags &= ~SXBLOB_RDONLY; } return SXRET_OK; } JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen) { if( nNewLen < pBlob->nByte ){ pBlob->nByte = nNewLen; } return SXRET_OK; } JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob) { if( (pBlob->nFlags & (SXBLOB_STATIC|SXBLOB_RDONLY)) == 0 && pBlob->mByte > 0 ){ SyMemBackendFree(pBlob->pAllocator, pBlob->pBlob); } pBlob->pBlob = 0; pBlob->nByte = pBlob->mByte = 0; pBlob->nFlags = 0; return SXRET_OK; } #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft) { const char *zIn = (const char *)pBlob; const char *zEnd; sxi32 rc; if( pLen > nLen ){ return SXERR_NOTFOUND; } zEnd = &zIn[nLen-pLen]; for(;;){ if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; } return SXERR_NOTFOUND; } #endif /* JX9_DISABLE_BUILTIN_FUNC */ /* SyRunTimeApi:sxds.c */ JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize) { pSet->nSize = 0 ; pSet->nUsed = 0; pSet->nCursor = 0; pSet->eSize = ElemSize; pSet->pAllocator = pAllocator; pSet->pBase = 0; pSet->pUserData = 0; return SXRET_OK; } JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem) { unsigned char *zbase; if( pSet->nUsed >= pSet->nSize ){ void *pNew; if( pSet->pAllocator == 0 ){ return SXERR_LOCKED; } if( pSet->nSize <= 0 ){ pSet->nSize = 4; } pNew = SyMemBackendRealloc(pSet->pAllocator, pSet->pBase, pSet->eSize * pSet->nSize * 2); if( pNew == 0 ){ return SXERR_MEM; } pSet->pBase = pNew; pSet->nSize <<= 1; } zbase = (unsigned char *)pSet->pBase; SX_MACRO_FAST_MEMCPY(pItem, &zbase[pSet->nUsed * pSet->eSize], pSet->eSize); pSet->nUsed++; return SXRET_OK; } JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem) { if( pSet->nSize > 0 ){ return SXERR_LOCKED; } if( nItem < 8 ){ nItem = 8; } pSet->pBase = SyMemBackendAlloc(pSet->pAllocator, pSet->eSize * nItem); if( pSet->pBase == 0 ){ return SXERR_MEM; } pSet->nSize = nItem; return SXRET_OK; } JX9_PRIVATE sxi32 SySetReset(SySet *pSet) { pSet->nUsed = 0; pSet->nCursor = 0; return SXRET_OK; } JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet) { pSet->nCursor = 0; return SXRET_OK; } JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry) { register unsigned char *zSrc; if( pSet->nCursor >= pSet->nUsed ){ /* Reset cursor */ pSet->nCursor = 0; return SXERR_EOF; } zSrc = (unsigned char *)SySetBasePtr(pSet); if( ppEntry ){ *ppEntry = (void *)&zSrc[pSet->nCursor * pSet->eSize]; } pSet->nCursor++; return SXRET_OK; } JX9_PRIVATE sxi32 SySetRelease(SySet *pSet) { sxi32 rc = SXRET_OK; if( pSet->pAllocator && pSet->pBase ){ rc = SyMemBackendFree(pSet->pAllocator, pSet->pBase); } pSet->pBase = 0; pSet->nUsed = 0; pSet->nCursor = 0; return rc; } JX9_PRIVATE void * SySetPeek(SySet *pSet) { const char *zBase; if( pSet->nUsed <= 0 ){ return 0; } zBase = (const char *)pSet->pBase; return (void *)&zBase[(pSet->nUsed - 1) * pSet->eSize]; } JX9_PRIVATE void * SySetPop(SySet *pSet) { const char *zBase; void *pData; if( pSet->nUsed <= 0 ){ return 0; } zBase = (const char *)pSet->pBase; pSet->nUsed--; pData = (void *)&zBase[pSet->nUsed * pSet->eSize]; return pData; } JX9_PRIVATE void * SySetAt(SySet *pSet, sxu32 nIdx) { const char *zBase; if( nIdx >= pSet->nUsed ){ /* Out of range */ return 0; } zBase = (const char *)pSet->pBase; return (void *)&zBase[nIdx * pSet->eSize]; } /* Private hash entry */ struct SyHashEntry_Pr { const void *pKey; /* Hash key */ sxu32 nKeyLen; /* Key length */ void *pUserData; /* User private data */ /* Private fields */ sxu32 nHash; SyHash *pHash; SyHashEntry_Pr *pNext, *pPrev; /* Next and previous entry in the list */ SyHashEntry_Pr *pNextCollide, *pPrevCollide; /* Collision list */ }; #define INVALID_HASH(H) ((H)->apBucket == 0) JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp) { SyHashEntry_Pr **apNew; #if defined(UNTRUST) if( pHash == 0 ){ return SXERR_EMPTY; } #endif /* Allocate a new table */ apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(&(*pAllocator), sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); if( apNew == 0 ){ return SXERR_MEM; } SyZero((void *)apNew, sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); pHash->pAllocator = &(*pAllocator); pHash->xHash = xHash ? xHash : SyBinHash; pHash->xCmp = xCmp ? xCmp : SyMemcmp; pHash->pCurrent = pHash->pList = 0; pHash->nEntry = 0; pHash->apBucket = apNew; pHash->nBucketSize = SXHASH_BUCKET_SIZE; return SXRET_OK; } JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash) { SyHashEntry_Pr *pEntry, *pNext; #if defined(UNTRUST) if( INVALID_HASH(pHash) ){ return SXERR_EMPTY; } #endif pEntry = pHash->pList; for(;;){ if( pHash->nEntry == 0 ){ break; } pNext = pEntry->pNext; SyMemBackendPoolFree(pHash->pAllocator, pEntry); pEntry = pNext; pHash->nEntry--; } if( pHash->apBucket ){ SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket); } pHash->apBucket = 0; pHash->nBucketSize = 0; pHash->pAllocator = 0; return SXRET_OK; } static SyHashEntry_Pr * HashGetEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen) { SyHashEntry_Pr *pEntry; sxu32 nHash; nHash = pHash->xHash(pKey, nKeyLen); pEntry = pHash->apBucket[nHash & (pHash->nBucketSize - 1)]; for(;;){ if( pEntry == 0 ){ break; } if( pEntry->nHash == nHash && pEntry->nKeyLen == nKeyLen && pHash->xCmp(pEntry->pKey, pKey, nKeyLen) == 0 ){ return pEntry; } pEntry = pEntry->pNextCollide; } /* Entry not found */ return 0; } JX9_PRIVATE SyHashEntry * SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen) { SyHashEntry_Pr *pEntry; #if defined(UNTRUST) if( INVALID_HASH(pHash) ){ return 0; } #endif if( pHash->nEntry < 1 || nKeyLen < 1 ){ /* Don't bother hashing, return immediately */ return 0; } pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen); if( pEntry == 0 ){ return 0; } return (SyHashEntry *)pEntry; } static sxi32 HashDeleteEntry(SyHash *pHash, SyHashEntry_Pr *pEntry, void **ppUserData) { sxi32 rc; if( pEntry->pPrevCollide == 0 ){ pHash->apBucket[pEntry->nHash & (pHash->nBucketSize - 1)] = pEntry->pNextCollide; }else{ pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; } if( pEntry->pNextCollide ){ pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; } MACRO_LD_REMOVE(pHash->pList, pEntry); pHash->nEntry--; if( ppUserData ){ /* Write a pointer to the user data */ *ppUserData = pEntry->pUserData; } /* Release the entry */ rc = SyMemBackendPoolFree(pHash->pAllocator, pEntry); return rc; } JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData) { SyHashEntry_Pr *pEntry; sxi32 rc; #if defined(UNTRUST) if( INVALID_HASH(pHash) ){ return SXERR_CORRUPT; } #endif pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen); if( pEntry == 0 ){ return SXERR_NOTFOUND; } rc = HashDeleteEntry(&(*pHash), pEntry, ppUserData); return rc; } JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32 (*xStep)(SyHashEntry *, void *), void *pUserData) { SyHashEntry_Pr *pEntry; sxi32 rc; sxu32 n; #if defined(UNTRUST) if( INVALID_HASH(pHash) || xStep == 0){ return 0; } #endif pEntry = pHash->pList; for( n = 0 ; n < pHash->nEntry ; n++ ){ /* Invoke the callback */ rc = xStep((SyHashEntry *)pEntry, pUserData); if( rc != SXRET_OK ){ return rc; } /* Point to the next entry */ pEntry = pEntry->pNext; } return SXRET_OK; } static sxi32 HashGrowTable(SyHash *pHash) { sxu32 nNewSize = pHash->nBucketSize * 2; SyHashEntry_Pr *pEntry; SyHashEntry_Pr **apNew; sxu32 n, iBucket; /* Allocate a new larger table */ apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(pHash->pAllocator, nNewSize * sizeof(SyHashEntry_Pr *)); if( apNew == 0 ){ /* Not so fatal, simply a performance hit */ return SXRET_OK; } /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(SyHashEntry_Pr *)); /* Rehash all entries */ for( n = 0, pEntry = pHash->pList; n < pHash->nEntry ; n++ ){ pEntry->pNextCollide = pEntry->pPrevCollide = 0; /* Install in the new bucket */ iBucket = pEntry->nHash & (nNewSize - 1); pEntry->pNextCollide = apNew[iBucket]; if( apNew[iBucket] != 0 ){ apNew[iBucket]->pPrevCollide = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; } /* Release the old table and reflect the change */ SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket); pHash->apBucket = apNew; pHash->nBucketSize = nNewSize; return SXRET_OK; } static sxi32 HashInsert(SyHash *pHash, SyHashEntry_Pr *pEntry) { sxu32 iBucket = pEntry->nHash & (pHash->nBucketSize - 1); /* Insert the entry in its corresponding bcuket */ pEntry->pNextCollide = pHash->apBucket[iBucket]; if( pHash->apBucket[iBucket] != 0 ){ pHash->apBucket[iBucket]->pPrevCollide = pEntry; } pHash->apBucket[iBucket] = pEntry; /* Link to the entry list */ MACRO_LD_PUSH(pHash->pList, pEntry); if( pHash->nEntry == 0 ){ pHash->pCurrent = pHash->pList; } pHash->nEntry++; return SXRET_OK; } JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData) { SyHashEntry_Pr *pEntry; sxi32 rc; #if defined(UNTRUST) if( INVALID_HASH(pHash) || pKey == 0 ){ return SXERR_CORRUPT; } #endif if( pHash->nEntry >= pHash->nBucketSize * SXHASH_FILL_FACTOR ){ rc = HashGrowTable(&(*pHash)); if( rc != SXRET_OK ){ return rc; } } /* Allocate a new hash entry */ pEntry = (SyHashEntry_Pr *)SyMemBackendPoolAlloc(pHash->pAllocator, sizeof(SyHashEntry_Pr)); if( pEntry == 0 ){ return SXERR_MEM; } /* Zero the entry */ SyZero(pEntry, sizeof(SyHashEntry_Pr)); pEntry->pHash = pHash; pEntry->pKey = pKey; pEntry->nKeyLen = nKeyLen; pEntry->pUserData = pUserData; pEntry->nHash = pHash->xHash(pEntry->pKey, pEntry->nKeyLen); /* Finally insert the entry in its corresponding bucket */ rc = HashInsert(&(*pHash), pEntry); return rc; } /* SyRunTimeApi:sxutils.c */ JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail) { const char *zCur, *zEnd; #ifdef UNTRUST if( SX_EMPTY_STR(zSrc) ){ return SXERR_EMPTY; } #endif zEnd = &zSrc[nLen]; /* Jump leading white spaces */ while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisSpace(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ zSrc++; } zCur = zSrc; if( pReal ){ *pReal = FALSE; } for(;;){ if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; }; if( zSrc < zEnd && zSrc > zCur ){ int c = zSrc[0]; if( c == '.' ){ zSrc++; if( pReal ){ *pReal = TRUE; } if( pzTail ){ while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && (zSrc[0] == 'e' || zSrc[0] == 'E') ){ zSrc++; if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ zSrc++; } while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ zSrc++; } } } }else if( c == 'e' || c == 'E' ){ zSrc++; if( pReal ){ *pReal = TRUE; } if( pzTail ){ if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ zSrc++; } while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ zSrc++; } } } } if( pzTail ){ /* Point to the non numeric part */ *pzTail = zSrc; } return zSrc > zCur ? SXRET_OK /* String prefix is numeric */ : SXERR_INVALID /* Not a digit stream */; } #define SXINT32_MIN_STR "2147483648" #define SXINT32_MAX_STR "2147483647" #define SXINT64_MIN_STR "9223372036854775808" #define SXINT64_MAX_STR "9223372036854775807" JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) { int isNeg = FALSE; const char *zEnd; sxi32 nVal = 0; sxi16 i; #if defined(UNTRUST) if( SX_EMPTY_STR(zSrc) ){ if( pOutVal ){ *(sxi32 *)pOutVal = 0; } return SXERR_EMPTY; } #endif zEnd = &zSrc[nLen]; while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ isNeg = (zSrc[0] == '-') ? TRUE :FALSE; zSrc++; } /* Skip leading zero */ while(zSrc < zEnd && zSrc[0] == '0' ){ zSrc++; } i = 10; if( (sxu32)(zEnd-zSrc) >= 10 ){ /* Handle overflow */ i = SyMemcmp(zSrc, (isNeg == TRUE) ? SXINT32_MIN_STR : SXINT32_MAX_STR, nLen) <= 0 ? 10 : 9; } for(;;){ if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; } /* Skip trailing spaces */ while(zSrc < zEnd && SyisSpace(zSrc[0])){ zSrc++; } if( zRest ){ *zRest = (char *)zSrc; } if( pOutVal ){ if( isNeg == TRUE && nVal != 0 ){ nVal = -nVal; } *(sxi32 *)pOutVal = nVal; } return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; } JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) { int isNeg = FALSE; const char *zEnd; sxi64 nVal; sxi16 i; #if defined(UNTRUST) if( SX_EMPTY_STR(zSrc) ){ if( pOutVal ){ *(sxi32 *)pOutVal = 0; } return SXERR_EMPTY; } #endif zEnd = &zSrc[nLen]; while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ isNeg = (zSrc[0] == '-') ? TRUE :FALSE; zSrc++; } /* Skip leading zero */ while(zSrc < zEnd && zSrc[0] == '0' ){ zSrc++; } i = 19; if( (sxu32)(zEnd-zSrc) >= 19 ){ i = SyMemcmp(zSrc, isNeg ? SXINT64_MIN_STR : SXINT64_MAX_STR, 19) <= 0 ? 19 : 18 ; } nVal = 0; for(;;){ if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; } /* Skip trailing spaces */ while(zSrc < zEnd && SyisSpace(zSrc[0])){ zSrc++; } if( zRest ){ *zRest = (char *)zSrc; } if( pOutVal ){ if( isNeg == TRUE && nVal != 0 ){ nVal = -nVal; } *(sxi64 *)pOutVal = nVal; } return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; } JX9_PRIVATE sxi32 SyHexToint(sxi32 c) { switch(c){ case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'A': case 'a': return 10; case 'B': case 'b': return 11; case 'C': case 'c': return 12; case 'D': case 'd': return 13; case 'E': case 'e': return 14; case 'F': case 'f': return 15; } return -1; } JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) { const char *zIn, *zEnd; int isNeg = FALSE; sxi64 nVal = 0; #if defined(UNTRUST) if( SX_EMPTY_STR(zSrc) ){ if( pOutVal ){ *(sxi32 *)pOutVal = 0; } return SXERR_EMPTY; } #endif zEnd = &zSrc[nLen]; while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && ( *zSrc == '-' || *zSrc == '+' ) ){ isNeg = (zSrc[0] == '-') ? TRUE :FALSE; zSrc++; } if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'x' || zSrc[1] == 'X') ){ /* Bypass hex prefix */ zSrc += sizeof(char) * 2; } /* Skip leading zero */ while(zSrc < zEnd && zSrc[0] == '0' ){ zSrc++; } zIn = zSrc; for(;;){ if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; } while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zRest ){ *zRest = zSrc; } if( pOutVal ){ if( isNeg == TRUE && nVal != 0 ){ nVal = -nVal; } *(sxi64 *)pOutVal = nVal; } return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; } JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) { const char *zIn, *zEnd; int isNeg = FALSE; sxi64 nVal = 0; int c; #if defined(UNTRUST) if( SX_EMPTY_STR(zSrc) ){ if( pOutVal ){ *(sxi32 *)pOutVal = 0; } return SXERR_EMPTY; } #endif zEnd = &zSrc[nLen]; while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ isNeg = (zSrc[0] == '-') ? TRUE :FALSE; zSrc++; } /* Skip leading zero */ while(zSrc < zEnd && zSrc[0] == '0' ){ zSrc++; } zIn = zSrc; for(;;){ if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; } /* Skip trailing spaces */ while(zSrc < zEnd && SyisSpace(zSrc[0])){ zSrc++; } if( zRest ){ *zRest = zSrc; } if( pOutVal ){ if( isNeg == TRUE && nVal != 0 ){ nVal = -nVal; } *(sxi64 *)pOutVal = nVal; } return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; } JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) { const char *zIn, *zEnd; int isNeg = FALSE; sxi64 nVal = 0; int c; #if defined(UNTRUST) if( SX_EMPTY_STR(zSrc) ){ if( pOutVal ){ *(sxi32 *)pOutVal = 0; } return SXERR_EMPTY; } #endif zEnd = &zSrc[nLen]; while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ isNeg = (zSrc[0] == '-') ? TRUE :FALSE; zSrc++; } if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'b' || zSrc[1] == 'B') ){ /* Bypass binary prefix */ zSrc += sizeof(char) * 2; } /* Skip leading zero */ while(zSrc < zEnd && zSrc[0] == '0' ){ zSrc++; } zIn = zSrc; for(;;){ if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; } /* Skip trailing spaces */ while(zSrc < zEnd && SyisSpace(zSrc[0])){ zSrc++; } if( zRest ){ *zRest = zSrc; } if( pOutVal ){ if( isNeg == TRUE && nVal != 0 ){ nVal = -nVal; } *(sxi64 *)pOutVal = nVal; } return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; } JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) { #define SXDBL_DIG 15 #define SXDBL_MAX_EXP 308 #define SXDBL_MIN_EXP_PLUS 307 static const sxreal aTab[] = { 10, 1.0e2, 1.0e4, 1.0e8, 1.0e16, 1.0e32, 1.0e64, 1.0e128, 1.0e256 }; sxu8 neg = FALSE; sxreal Val = 0.0; const char *zEnd; sxi32 Lim, exp; sxreal *p = 0; #ifdef UNTRUST if( SX_EMPTY_STR(zSrc) ){ if( pOutVal ){ *(sxreal *)pOutVal = 0.0; } return SXERR_EMPTY; } #endif zEnd = &zSrc[nLen]; while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zSrc < zEnd && (zSrc[0] == '-' || zSrc[0] == '+' ) ){ neg = zSrc[0] == '-' ? TRUE : FALSE ; zSrc++; } Lim = SXDBL_DIG ; for(;;){ if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; } if( zSrc < zEnd && ( zSrc[0] == '.' || zSrc[0] == ',' ) ){ sxreal dec = 1.0; zSrc++; for(;;){ if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; } Val /= dec; } if( neg == TRUE && Val != 0.0 ) { Val = -Val ; } if( Lim <= 0 ){ /* jump overflow digit */ while( zSrc < zEnd ){ if( zSrc[0] == 'e' || zSrc[0] == 'E' ){ break; } zSrc++; } } neg = FALSE; if( zSrc < zEnd && ( zSrc[0] == 'e' || zSrc[0] == 'E' ) ){ zSrc++; if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+') ){ neg = zSrc[0] == '-' ? TRUE : FALSE ; zSrc++; } exp = 0; while( zSrc < zEnd && SyisDigit(zSrc[0]) && exp < SXDBL_MAX_EXP ){ exp = exp * 10 + (zSrc[0] - '0'); zSrc++; } if( neg ){ if( exp > SXDBL_MIN_EXP_PLUS ) exp = SXDBL_MIN_EXP_PLUS ; }else if ( exp > SXDBL_MAX_EXP ){ exp = SXDBL_MAX_EXP; } for( p = (sxreal *)aTab ; exp ; exp >>= 1 , p++ ){ if( exp & 01 ){ if( neg ){ Val /= *p ; }else{ Val *= *p; } } } } while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ zSrc++; } if( zRest ){ *zRest = zSrc; } if( pOutVal ){ *(sxreal *)pOutVal = Val; } return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; } /* SyRunTimeApi:sxlib.c */ JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen) { register unsigned char *zIn = (unsigned char *)pSrc; unsigned char *zEnd; sxu32 nH = 5381; zEnd = &zIn[nLen]; for(;;){ if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; } return nH; } #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) { static const unsigned char zBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; unsigned char *zIn = (unsigned char *)zSrc; unsigned char z64[4]; sxu32 i; sxi32 rc; #if defined(UNTRUST) if( SX_EMPTY_STR(zSrc) || xConsumer == 0){ return SXERR_EMPTY; } #endif for(i = 0; i + 2 < nLen; i += 3){ z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; z64[2] = zBase64[( ((zIn[i+1] & 0x0F) << 2) | (zIn[i + 2] >> 6) ) & 0x3F]; z64[3] = zBase64[ zIn[i + 2] & 0x3F]; rc = xConsumer((const void *)z64, sizeof(z64), pUserData); if( rc != SXRET_OK ){return SXERR_ABORT;} } if ( i+1 < nLen ){ z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; z64[2] = zBase64[(zIn[i+1] & 0x0F) << 2 ]; z64[3] = '='; rc = xConsumer((const void *)z64, sizeof(z64), pUserData); if( rc != SXRET_OK ){return SXERR_ABORT;} }else if( i < nLen ){ z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; z64[1] = zBase64[(zIn[i] & 0x03) << 4]; z64[2] = '='; z64[3] = '='; rc = xConsumer((const void *)z64, sizeof(z64), pUserData); if( rc != SXRET_OK ){return SXERR_ABORT;} } return SXRET_OK; } JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) { static const sxu32 aBase64Trans[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 }; sxu32 n, w, x, y, z; sxi32 rc; unsigned char zOut[10]; #if defined(UNTRUST) if( SX_EMPTY_STR(zB64) || xConsumer == 0 ){ return SXERR_EMPTY; } #endif while(nLen > 0 && zB64[nLen - 1] == '=' ){ nLen--; } for( n = 0 ; n+3>4) & 0x03); zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); zOut[2] = ((y<<6) & 0xC0) | (z & 0x3F); rc = xConsumer((const void *)zOut, sizeof(unsigned char)*3, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT;} } if( n+2 < nLen ){ w = aBase64Trans[zB64[n] & 0x7F]; x = aBase64Trans[zB64[n+1] & 0x7F]; y = aBase64Trans[zB64[n+2] & 0x7F]; zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); rc = xConsumer((const void *)zOut, sizeof(unsigned char)*2, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT;} }else if( n+1 < nLen ){ w = aBase64Trans[zB64[n] & 0x7F]; x = aBase64Trans[zB64[n+1] & 0x7F]; zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); rc = xConsumer((const void *)zOut, sizeof(unsigned char)*1, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT;} } return SXRET_OK; } #endif /* JX9_DISABLE_BUILTIN_FUNC */ #define INVALID_LEXER(LEX) ( LEX == 0 || LEX->xTokenizer == 0 ) JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData) { SyStream *pStream; #if defined (UNTRUST) if ( pLex == 0 || xTokenizer == 0 ){ return SXERR_CORRUPT; } #endif pLex->pTokenSet = 0; /* Initialize lexer fields */ if( pSet ){ if ( SySetElemSize(pSet) != sizeof(SyToken) ){ return SXERR_INVALID; } pLex->pTokenSet = pSet; } pStream = &pLex->sStream; pLex->xTokenizer = xTokenizer; pLex->pUserData = pUserData; pStream->nLine = 1; pStream->nIgn = 0; pStream->zText = pStream->zEnd = 0; pStream->pSet = pSet; return SXRET_OK; } JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp) { const unsigned char *zCur; SyStream *pStream; SyToken sToken; sxi32 rc; #if defined (UNTRUST) if ( INVALID_LEXER(pLex) || zInput == 0 ){ return SXERR_CORRUPT; } #endif pStream = &pLex->sStream; /* Point to the head of the input */ pStream->zText = pStream->zInput = (const unsigned char *)zInput; /* Point to the end of the input */ pStream->zEnd = &pStream->zInput[nLen]; for(;;){ if( pStream->zText >= pStream->zEnd ){ /* End of the input reached */ break; } zCur = pStream->zText; /* Call the tokenizer callback */ rc = pLex->xTokenizer(pStream, &sToken, pLex->pUserData, pCtxData); if( rc != SXRET_OK && rc != SXERR_CONTINUE ){ /* Tokenizer callback request an operation abort */ if( rc == SXERR_ABORT ){ return SXERR_ABORT; } break; } if( rc == SXERR_CONTINUE ){ /* Request to ignore this token */ pStream->nIgn++; }else if( pLex->pTokenSet ){ /* Put the token in the set */ rc = SySetPut(pLex->pTokenSet, (const void *)&sToken); if( rc != SXRET_OK ){ break; } } if( zCur >= pStream->zText ){ /* Automatic advance of the stream cursor */ pStream->zText = &zCur[1]; } } if( xSort && pLex->pTokenSet ){ SyToken *aToken = (SyToken *)SySetBasePtr(pLex->pTokenSet); /* Sort the extrated tokens */ if( xCmp == 0 ){ /* Use a default comparison function */ xCmp = SyMemcmp; } xSort(aToken, SySetUsed(pLex->pTokenSet), sizeof(SyToken), xCmp); } return SXRET_OK; } JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex) { sxi32 rc = SXRET_OK; #if defined (UNTRUST) if ( INVALID_LEXER(pLex) ){ return SXERR_CORRUPT; } #else SXUNUSED(pLex); /* Prevent compiler warning */ #endif return rc; } #ifndef JX9_DISABLE_BUILTIN_FUNC #define SAFE_HTTP(C) (SyisAlphaNum(c) || c == '_' || c == '-' || c == '$' || c == '.' ) JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) { unsigned char *zIn = (unsigned char *)zSrc; unsigned char zHex[3] = { '%', 0, 0 }; unsigned char zOut[2]; unsigned char *zCur, *zEnd; sxi32 c; sxi32 rc; #ifdef UNTRUST if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ return SXERR_EMPTY; } #endif rc = SXRET_OK; zEnd = &zIn[nLen]; zCur = zIn; for(;;){ if( zCur >= zEnd ){ if( zCur != zIn ){ rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData); } break; } c = zCur[0]; if( SAFE_HTTP(c) ){ zCur++; continue; } if( zCur != zIn && SXRET_OK != (rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData))){ break; } if( c == ' ' ){ zOut[0] = '+'; rc = xConsumer((const void *)zOut, sizeof(unsigned char), pUserData); }else{ zHex[1] = "0123456789ABCDEF"[(c >> 4) & 0x0F]; zHex[2] = "0123456789ABCDEF"[c & 0x0F]; rc = xConsumer(zHex, sizeof(zHex), pUserData); } if( SXRET_OK != rc ){ break; } zIn = &zCur[1]; zCur = zIn ; } return rc == SXRET_OK ? SXRET_OK : SXERR_ABORT; } #endif /* JX9_DISABLE_BUILTIN_FUNC */ static sxi32 SyAsciiToHex(sxi32 c) { if( c >= 'a' && c <= 'f' ){ c += 10 - 'a'; return c; } if( c >= '0' && c <= '9' ){ c -= '0'; return c; } if( c >= 'A' && c <= 'F') { c += 10 - 'A'; return c; } return 0; } JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8) { static const sxu8 Utf8Trans[] = { 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 }; const char *zIn = zSrc; const char *zEnd; const char *zCur; sxu8 *zOutPtr; sxu8 zOut[10]; sxi32 c, d; sxi32 rc; #if defined(UNTRUST) if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ return SXERR_EMPTY; } #endif rc = SXRET_OK; zEnd = &zSrc[nLen]; zCur = zIn; for(;;){ while(zCur < zEnd && zCur[0] != '%' && zCur[0] != '+' ){ zCur++; } if( zCur != zIn ){ /* Consume input */ rc = xConsumer(zIn, (unsigned int)(zCur-zIn), pUserData); if( rc != SXRET_OK ){ /* User consumer routine request an operation abort */ break; } } if( zCur >= zEnd ){ rc = SXRET_OK; break; } /* Decode unsafe HTTP characters */ zOutPtr = zOut; if( zCur[0] == '+' ){ *zOutPtr++ = ' '; zCur++; }else{ if( &zCur[2] >= zEnd ){ rc = SXERR_OVERFLOW; break; } c = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); zCur += 3; if( c < 0x000C0 ){ *zOutPtr++ = (sxu8)c; }else{ c = Utf8Trans[c-0xC0]; while( zCur[0] == '%' ){ d = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); if( (d&0xC0) != 0x80 ){ break; } c = (c<<6) + (0x3f & d); zCur += 3; } if( bUTF8 == FALSE ){ *zOutPtr++ = (sxu8)c; }else{ SX_WRITE_UTF8(zOutPtr, c); } } } /* Consume the decoded characters */ rc = xConsumer((const void *)zOut, (unsigned int)(zOutPtr-zOut), pUserData); if( rc != SXRET_OK ){ break; } /* Synchronize pointers */ zIn = zCur; } return rc; } #ifndef JX9_DISABLE_BUILTIN_FUNC static const char *zEngDay[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; static const char *zEngMonth[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static const char * GetDay(sxi32 i) { return zEngDay[ i % 7 ]; } static const char * GetMonth(sxi32 i) { return zEngMonth[ i % 12 ]; } JX9_PRIVATE const char * SyTimeGetDay(sxi32 iDay) { return GetDay(iDay); } JX9_PRIVATE const char * SyTimeGetMonth(sxi32 iMonth) { return GetMonth(iMonth); } #endif /* JX9_DISABLE_BUILTIN_FUNC */ /* SyRunTimeApi: sxfmt.c */ #define SXFMT_BUFSIZ 1024 /* Conversion buffer size */ /* ** Conversion types fall into various categories as defined by the ** following enumeration. */ #define SXFMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ #define SXFMT_FLOAT 2 /* Floating point.%f */ #define SXFMT_EXP 3 /* Exponentional notation.%e and %E */ #define SXFMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ #define SXFMT_SIZE 5 /* Total number of characters processed so far.%n */ #define SXFMT_STRING 6 /* Strings.%s */ #define SXFMT_PERCENT 7 /* Percent symbol.%% */ #define SXFMT_CHARX 8 /* Characters.%c */ #define SXFMT_ERROR 9 /* Used to indicate no such conversion type */ /* Extension by Symisc Systems */ #define SXFMT_RAWSTR 13 /* %z Pointer to raw string (SyString *) */ #define SXFMT_UNUSED 15 /* ** Allowed values for SyFmtInfo.flags */ #define SXFLAG_SIGNED 0x01 #define SXFLAG_UNSIGNED 0x02 /* Allowed values for SyFmtConsumer.nType */ #define SXFMT_CONS_PROC 1 /* Consumer is a procedure */ #define SXFMT_CONS_STR 2 /* Consumer is a managed string */ #define SXFMT_CONS_FILE 5 /* Consumer is an open File */ #define SXFMT_CONS_BLOB 6 /* Consumer is a BLOB */ /* ** Each builtin conversion character (ex: the 'd' in "%d") is described ** by an instance of the following structure */ typedef struct SyFmtInfo SyFmtInfo; struct SyFmtInfo { char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */ sxu8 base; /* The base for radix conversion */ int flags; /* One or more of SXFLAG_ constants below */ sxu8 type; /* Conversion paradigm */ char *charset; /* The character set for conversion */ char *prefix; /* Prefix on non-zero values in alt format */ }; typedef struct SyFmtConsumer SyFmtConsumer; struct SyFmtConsumer { sxu32 nLen; /* Total output length */ sxi32 nType; /* Type of the consumer see below */ sxi32 rc; /* Consumer return value;Abort processing if rc != SXRET_OK */ union{ struct{ ProcConsumer xUserConsumer; void *pUserData; }sFunc; SyBlob *pBlob; }uConsumer; }; #ifndef SX_OMIT_FLOATINGPOINT static int getdigit(sxlongreal *val, int *cnt) { sxlongreal d; int digit; if( (*cnt)++ >= 16 ){ return '0'; } digit = (int)*val; d = digit; *val = (*val - d)*10.0; return digit + '0' ; } #endif /* SX_OMIT_FLOATINGPOINT */ /* * The following routine was taken from the SQLITE2 source tree and was * extended by Symisc Systems to fit its need. * Status: Public Domain */ static sxi32 InternFormat(ProcConsumer xConsumer, void *pUserData, const char *zFormat, va_list ap) { /* * The following table is searched linearly, so it is good to put the most frequently * used conversion types first. */ static const SyFmtInfo aFmt[] = { { 'd', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, { 's', 0, 0, SXFMT_STRING, 0, 0 }, { 'c', 0, 0, SXFMT_CHARX, 0, 0 }, { 'x', 16, 0, SXFMT_RADIX, "0123456789abcdef", "x0" }, { 'X', 16, 0, SXFMT_RADIX, "0123456789ABCDEF", "X0" }, /* -- Extensions by Symisc Systems -- */ { 'z', 0, 0, SXFMT_RAWSTR, 0, 0 }, /* Pointer to a raw string (SyString *) */ { 'B', 2, 0, SXFMT_RADIX, "01", "b0"}, /* -- End of Extensions -- */ { 'o', 8, 0, SXFMT_RADIX, "01234567", "0" }, { 'u', 10, 0, SXFMT_RADIX, "0123456789", 0 }, #ifndef SX_OMIT_FLOATINGPOINT { 'f', 0, SXFLAG_SIGNED, SXFMT_FLOAT, 0, 0 }, { 'e', 0, SXFLAG_SIGNED, SXFMT_EXP, "e", 0 }, { 'E', 0, SXFLAG_SIGNED, SXFMT_EXP, "E", 0 }, { 'g', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "e", 0 }, { 'G', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "E", 0 }, #endif { 'i', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, { 'n', 0, 0, SXFMT_SIZE, 0, 0 }, { '%', 0, 0, SXFMT_PERCENT, 0, 0 }, { 'p', 10, 0, SXFMT_RADIX, "0123456789", 0 } }; int c; /* Next character in the format string */ char *bufpt; /* Pointer to the conversion buffer */ int precision; /* Precision of the current field */ int length; /* Length of the field */ int idx; /* A general purpose loop counter */ int width; /* Width of the current field */ sxu8 flag_leftjustify; /* True if "-" flag is present */ sxu8 flag_plussign; /* True if "+" flag is present */ sxu8 flag_blanksign; /* True if " " flag is present */ sxu8 flag_alternateform; /* True if "#" flag is present */ sxu8 flag_zeropad; /* True if field width constant starts with zero */ sxu8 flag_long; /* True if "l" flag is present */ sxi64 longvalue; /* Value for integer types */ const SyFmtInfo *infop; /* Pointer to the appropriate info structure */ char buf[SXFMT_BUFSIZ]; /* Conversion buffer */ char prefix; /* Prefix character."+" or "-" or " " or '\0'.*/ sxu8 errorflag = 0; /* True if an error is encountered */ sxu8 xtype; /* Conversion paradigm */ char *zExtra; static char spaces[] = " "; #define etSPACESIZE ((int)sizeof(spaces)-1) #ifndef SX_OMIT_FLOATINGPOINT sxlongreal realvalue; /* Value for real types */ int exp; /* exponent of real numbers */ double rounder; /* Used for rounding floating point values */ sxu8 flag_dp; /* True if decimal point should be shown */ sxu8 flag_rtz; /* True if trailing zeros should be removed */ sxu8 flag_exp; /* True to force display of the exponent */ int nsd; /* Number of significant digits returned */ #endif int rc; length = 0; bufpt = 0; for(; (c=(*zFormat))!=0; ++zFormat){ if( c!='%' ){ unsigned int amt; bufpt = (char *)zFormat; amt = 1; while( (c=(*++zFormat))!='%' && c!=0 ) amt++; rc = xConsumer((const void *)bufpt, amt, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } if( c==0 ){ return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; } } if( (c=(*++zFormat))==0 ){ errorflag = 1; rc = xConsumer("%", sizeof("%")-1, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; } /* Find out what flags are present */ flag_leftjustify = flag_plussign = flag_blanksign = flag_alternateform = flag_zeropad = 0; do{ switch( c ){ case '-': flag_leftjustify = 1; c = 0; break; case '+': flag_plussign = 1; c = 0; break; case ' ': flag_blanksign = 1; c = 0; break; case '#': flag_alternateform = 1; c = 0; break; case '0': flag_zeropad = 1; c = 0; break; default: break; } }while( c==0 && (c=(*++zFormat))!=0 ); /* Get the field width */ width = 0; if( c=='*' ){ width = va_arg(ap, int); if( width<0 ){ flag_leftjustify = 1; width = -width; } c = *++zFormat; }else{ while( c>='0' && c<='9' ){ width = width*10 + c - '0'; c = *++zFormat; } } if( width > SXFMT_BUFSIZ-10 ){ width = SXFMT_BUFSIZ-10; } /* Get the precision */ precision = -1; if( c=='.' ){ precision = 0; c = *++zFormat; if( c=='*' ){ precision = va_arg(ap, int); if( precision<0 ) precision = -precision; c = *++zFormat; }else{ while( c>='0' && c<='9' ){ precision = precision*10 + c - '0'; c = *++zFormat; } } } /* Get the conversion type modifier */ flag_long = 0; if( c=='l' || c == 'q' /* BSD quad (expect a 64-bit integer) */ ){ flag_long = (c == 'q') ? 2 : 1; c = *++zFormat; if( c == 'l' ){ /* Standard printf emulation 'lld' (expect a 64bit integer) */ flag_long = 2; } } /* Fetch the info entry for the field */ infop = 0; xtype = SXFMT_ERROR; for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ if( c==aFmt[idx].fmttype ){ infop = &aFmt[idx]; xtype = infop->type; break; } } zExtra = 0; /* ** At this point, variables are initialized as follows: ** ** flag_alternateform TRUE if a '#' is present. ** flag_plussign TRUE if a '+' is present. ** flag_leftjustify TRUE if a '-' is present or if the ** field width was negative. ** flag_zeropad TRUE if the width began with 0. ** flag_long TRUE if the letter 'l' (ell) or 'q'(BSD quad) prefixed ** the conversion character. ** flag_blanksign TRUE if a ' ' is present. ** width The specified field width.This is ** always non-negative.Zero is the default. ** precision The specified precision.The default ** is -1. ** xtype The object of the conversion. ** infop Pointer to the appropriate info struct. */ switch( xtype ){ case SXFMT_RADIX: if( flag_long > 0 ){ if( flag_long > 1 ){ /* BSD quad: expect a 64-bit integer */ longvalue = va_arg(ap, sxi64); }else{ longvalue = va_arg(ap, sxlong); } }else{ if( infop->flags & SXFLAG_SIGNED ){ longvalue = va_arg(ap, sxi32); }else{ longvalue = va_arg(ap, sxu32); } } /* Limit the precision to prevent overflowing buf[] during conversion */ if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; #if 1 /* For the format %#x, the value zero is printed "0" not "0x0". ** I think this is stupid.*/ if( longvalue==0 ) flag_alternateform = 0; #else /* More sensible: turn off the prefix for octal (to prevent "00"), ** but leave the prefix for hex.*/ if( longvalue==0 && infop->base==8 ) flag_alternateform = 0; #endif if( infop->flags & SXFLAG_SIGNED ){ if( longvalue<0 ){ longvalue = -longvalue; /* Ticket 1433-003 */ if( longvalue < 0 ){ /* Overflow */ longvalue= 0x7FFFFFFFFFFFFFFF; } prefix = '-'; }else if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; }else{ if( longvalue<0 ){ longvalue = -longvalue; /* Ticket 1433-003 */ if( longvalue < 0 ){ /* Overflow */ longvalue= 0x7FFFFFFFFFFFFFFF; } } prefix = 0; } if( flag_zeropad && precisioncharset; base = infop->base; do{ /* Convert to ascii */ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } length = (int)(&buf[SXFMT_BUFSIZ-1]-bufpt); for(idx=precision-length; idx>0; idx--){ *(--bufpt) = '0'; /* Zero pad */ } if( prefix ) *(--bufpt) = prefix; /* Add sign */ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ char *pre, x; pre = infop->prefix; if( *bufpt!=pre[0] ){ for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x; } } length = (int)(&buf[SXFMT_BUFSIZ-1]-bufpt); break; case SXFMT_FLOAT: case SXFMT_EXP: case SXFMT_GENERIC: #ifndef SX_OMIT_FLOATINGPOINT realvalue = va_arg(ap, double); if( precision<0 ) precision = 6; /* Set default precision */ if( precision>SXFMT_BUFSIZ-40) precision = SXFMT_BUFSIZ-40; if( realvalue<0.0 ){ realvalue = -realvalue; prefix = '-'; }else{ if( flag_plussign ) prefix = '+'; else if( flag_blanksign ) prefix = ' '; else prefix = 0; } if( infop->type==SXFMT_GENERIC && precision>0 ) precision--; rounder = 0.0; #if 0 /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); #else /* It makes more sense to use 0.5 */ for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); #endif if( infop->type==SXFMT_FLOAT ) realvalue += rounder; /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ exp = 0; if( realvalue>0.0 ){ while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } if( exp>350 || exp<-350 ){ bufpt = "NaN"; length = 3; break; } } bufpt = buf; /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ flag_exp = xtype==SXFMT_EXP; if( xtype!=SXFMT_FLOAT ){ realvalue += rounder; if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } } if( xtype==SXFMT_GENERIC ){ flag_rtz = !flag_alternateform; if( exp<-4 || exp>precision ){ xtype = SXFMT_EXP; }else{ precision = precision - exp; xtype = SXFMT_FLOAT; } }else{ flag_rtz = 0; } /* ** The "exp+precision" test causes output to be of type etEXP if ** the precision is too large to fit in buf[]. */ nsd = 0; if( xtype==SXFMT_FLOAT && exp+precision0 || flag_alternateform); if( prefix ) *(bufpt++) = prefix; /* Sign */ if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */ else for(; exp>=0; exp--) *(bufpt++) = (char)getdigit(&realvalue, &nsd); if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */ for(exp++; exp<0 && precision>0; precision--, exp++){ *(bufpt++) = '0'; } while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd); *(bufpt--) = 0; /* Null terminate */ if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; } bufpt++; /* point to next free slot */ }else{ /* etEXP or etGENERIC */ flag_dp = (precision>0 || flag_alternateform); if( prefix ) *(bufpt++) = prefix; /* Sign */ *(bufpt++) = (char)getdigit(&realvalue, &nsd); /* First digit */ if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */ while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd); bufpt--; /* point to last digit */ if( flag_rtz && flag_dp ){ /* Remove tail zeros */ while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; } bufpt++; /* point to next free slot */ if( exp || flag_exp ){ *(bufpt++) = infop->charset[0]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */ else { *(bufpt++) = '+'; } if( exp>=100 ){ *(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */ exp %= 100; } *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */ *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */ } } /* The converted number is in buf[] and zero terminated.Output it. ** Note that the number is in the usual order, not reversed as with ** integer conversions.*/ length = (int)(bufpt-buf); bufpt = buf; /* Special case: Add leading zeros if the flag_zeropad flag is ** set and we are not left justified */ if( flag_zeropad && !flag_leftjustify && length < width){ int i; int nPad = width - length; for(i=width; i>=nPad; i--){ bufpt[i] = bufpt[i-nPad]; } i = prefix!=0; while( nPad-- ) bufpt[i++] = '0'; length = width; } #else bufpt = " "; length = (int)sizeof(" ") - 1; #endif /* SX_OMIT_FLOATINGPOINT */ break; case SXFMT_SIZE:{ int *pSize = va_arg(ap, int *); *pSize = ((SyFmtConsumer *)pUserData)->nLen; length = width = 0; } break; case SXFMT_PERCENT: buf[0] = '%'; bufpt = buf; length = 1; break; case SXFMT_CHARX: c = va_arg(ap, int); buf[0] = (char)c; /* Limit the precision to prevent overflowing buf[] during conversion */ if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; if( precision>=0 ){ for(idx=1; idx=0 && precisionzString == 0 ){ bufpt = " "; length = (int)sizeof(char); break; } bufpt = (char *)pStr->zString; length = (int)pStr->nByte; break; } case SXFMT_ERROR: buf[0] = '?'; bufpt = buf; length = (int)sizeof(char); if( c==0 ) zFormat--; break; }/* End switch over the format type */ /* ** The text of the conversion is pointed to by "bufpt" and is ** "length" characters long.The field width is "width".Do ** the output. */ if( !flag_leftjustify ){ register int nspace; nspace = width-length; if( nspace>0 ){ while( nspace>=etSPACESIZE ){ rc = xConsumer(spaces, etSPACESIZE, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } nspace -= etSPACESIZE; } if( nspace>0 ){ rc = xConsumer(spaces, (unsigned int)nspace, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } } } } if( length>0 ){ rc = xConsumer(bufpt, (unsigned int)length, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } } if( flag_leftjustify ){ register int nspace; nspace = width-length; if( nspace>0 ){ while( nspace>=etSPACESIZE ){ rc = xConsumer(spaces, etSPACESIZE, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } nspace -= etSPACESIZE; } if( nspace>0 ){ rc = xConsumer(spaces, (unsigned int)nspace, pUserData); if( rc != SXRET_OK ){ return SXERR_ABORT; /* Consumer routine request an operation abort */ } } } } }/* End for loop over the format string */ return errorflag ? SXERR_FORMAT : SXRET_OK; } static sxi32 FormatConsumer(const void *pSrc, unsigned int nLen, void *pData) { SyFmtConsumer *pConsumer = (SyFmtConsumer *)pData; sxi32 rc = SXERR_ABORT; switch(pConsumer->nType){ case SXFMT_CONS_PROC: /* User callback */ rc = pConsumer->uConsumer.sFunc.xUserConsumer(pSrc, nLen, pConsumer->uConsumer.sFunc.pUserData); break; case SXFMT_CONS_BLOB: /* Blob consumer */ rc = SyBlobAppend(pConsumer->uConsumer.pBlob, pSrc, (sxu32)nLen); break; default: /* Unknown consumer */ break; } /* Update total number of bytes consumed so far */ pConsumer->nLen += nLen; pConsumer->rc = rc; return rc; } static sxi32 FormatMount(sxi32 nType, void *pConsumer, ProcConsumer xUserCons, void *pUserData, sxu32 *pOutLen, const char *zFormat, va_list ap) { SyFmtConsumer sCons; sCons.nType = nType; sCons.rc = SXRET_OK; sCons.nLen = 0; if( pOutLen ){ *pOutLen = 0; } switch(nType){ case SXFMT_CONS_PROC: #if defined(UNTRUST) if( xUserCons == 0 ){ return SXERR_EMPTY; } #endif sCons.uConsumer.sFunc.xUserConsumer = xUserCons; sCons.uConsumer.sFunc.pUserData = pUserData; break; case SXFMT_CONS_BLOB: sCons.uConsumer.pBlob = (SyBlob *)pConsumer; break; default: return SXERR_UNKNOWN; } InternFormat(FormatConsumer, &sCons, zFormat, ap); if( pOutLen ){ *pOutLen = sCons.nLen; } return sCons.rc; } JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...) { va_list ap; sxi32 rc; #if defined(UNTRUST) if( SX_EMPTY_STR(zFormat) ){ return SXERR_EMPTY; } #endif va_start(ap, zFormat); rc = FormatMount(SXFMT_CONS_PROC, 0, xConsumer, pData, 0, zFormat, ap); va_end(ap); return rc; } JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...) { va_list ap; sxu32 n; #if defined(UNTRUST) if( SX_EMPTY_STR(zFormat) ){ return 0; } #endif va_start(ap, zFormat); FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); va_end(ap); return n; } JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap) { sxu32 n = 0; /* cc warning */ #if defined(UNTRUST) if( SX_EMPTY_STR(zFormat) ){ return 0; } #endif FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); return n; } JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...) { SyBlob sBlob; va_list ap; sxu32 n; #if defined(UNTRUST) if( SX_EMPTY_STR(zFormat) ){ return 0; } #endif if( SXRET_OK != SyBlobInitFromBuf(&sBlob, zBuf, nLen - 1) ){ return 0; } va_start(ap, zFormat); FormatMount(SXFMT_CONS_BLOB, &sBlob, 0, 0, 0, zFormat, ap); va_end(ap); n = SyBlobLength(&sBlob); /* Append the null terminator */ sBlob.mByte++; SyBlobAppend(&sBlob, "\0", sizeof(char)); return n; } #ifndef JX9_DISABLE_BUILTIN_FUNC /* * Zip File Format: * * Byte order: Little-endian * * [Local file header + Compressed data [+ Extended local header]?]* * [Central directory]* * [End of central directory record] * * Local file header:* * Offset Length Contents * 0 4 bytes Local file header signature (0x04034b50) * 4 2 bytes Version needed to extract * 6 2 bytes General purpose bit flag * 8 2 bytes Compression method * 10 2 bytes Last mod file time * 12 2 bytes Last mod file date * 14 4 bytes CRC-32 * 18 4 bytes Compressed size (n) * 22 4 bytes Uncompressed size * 26 2 bytes Filename length (f) * 28 2 bytes Extra field length (e) * 30 (f)bytes Filename * (e)bytes Extra field * (n)bytes Compressed data * * Extended local header:* * Offset Length Contents * 0 4 bytes Extended Local file header signature (0x08074b50) * 4 4 bytes CRC-32 * 8 4 bytes Compressed size * 12 4 bytes Uncompressed size * * Extra field:?(if any) * Offset Length Contents * 0 2 bytes Header ID (0x001 until 0xfb4a) see extended appnote from Info-zip * 2 2 bytes Data size (g) * (g) bytes (g) bytes of extra field * * Central directory:* * Offset Length Contents * 0 4 bytes Central file header signature (0x02014b50) * 4 2 bytes Version made by * 6 2 bytes Version needed to extract * 8 2 bytes General purpose bit flag * 10 2 bytes Compression method * 12 2 bytes Last mod file time * 14 2 bytes Last mod file date * 16 4 bytes CRC-32 * 20 4 bytes Compressed size * 24 4 bytes Uncompressed size * 28 2 bytes Filename length (f) * 30 2 bytes Extra field length (e) * 32 2 bytes File comment length (c) * 34 2 bytes Disk number start * 36 2 bytes Internal file attributes * 38 4 bytes External file attributes * 42 4 bytes Relative offset of local header * 46 (f)bytes Filename * (e)bytes Extra field * (c)bytes File comment * * End of central directory record: * Offset Length Contents * 0 4 bytes End of central dir signature (0x06054b50) * 4 2 bytes Number of this disk * 6 2 bytes Number of the disk with the start of the central directory * 8 2 bytes Total number of entries in the central dir on this disk * 10 2 bytes Total number of entries in the central dir * 12 4 bytes Size of the central directory * 16 4 bytes Offset of start of central directory with respect to the starting disk number * 20 2 bytes zipfile comment length (c) * 22 (c)bytes zipfile comment * * compression method: (2 bytes) * 0 - The file is stored (no compression) * 1 - The file is Shrunk * 2 - The file is Reduced with compression factor 1 * 3 - The file is Reduced with compression factor 2 * 4 - The file is Reduced with compression factor 3 * 5 - The file is Reduced with compression factor 4 * 6 - The file is Imploded * 7 - Reserved for Tokenizing compression algorithm * 8 - The file is Deflated */ #define SXMAKE_ZIP_WORKBUF (SXU16_HIGH/2) /* 32KB Initial working buffer size */ #define SXMAKE_ZIP_EXTRACT_VER 0x000a /* Version needed to extract */ #define SXMAKE_ZIP_VER 0x003 /* Version made by */ #define SXZIP_CENTRAL_MAGIC 0x02014b50 #define SXZIP_END_CENTRAL_MAGIC 0x06054b50 #define SXZIP_LOCAL_MAGIC 0x04034b50 /*#define SXZIP_CRC32_START 0xdebb20e3*/ #define SXZIP_LOCAL_HDRSZ 30 /* Local header size */ #define SXZIP_LOCAL_EXT_HDRZ 16 /* Extended local header(footer) size */ #define SXZIP_CENTRAL_HDRSZ 46 /* Central directory header size */ #define SXZIP_END_CENTRAL_HDRSZ 22 /* End of central directory header size */ #define SXARCHIVE_HASH_SIZE 64 /* Starting hash table size(MUST BE POWER OF 2)*/ static sxi32 SyLittleEndianUnpack32(sxu32 *uNB, const unsigned char *buf, sxu32 Len) { if( Len < sizeof(sxu32) ){ return SXERR_SHORT; } *uNB = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); return SXRET_OK; } static sxi32 SyLittleEndianUnpack16(sxu16 *pOut, const unsigned char *zBuf, sxu32 nLen) { if( nLen < sizeof(sxu16) ){ return SXERR_SHORT; } *pOut = zBuf[0] + (zBuf[1] <<8); return SXRET_OK; } /* * Archive hashtable manager */ static sxi32 ArchiveHashGetEntry(SyArchive *pArch, const char *zName, sxu32 nLen, SyArchiveEntry **ppEntry) { SyArchiveEntry *pBucketEntry; SyString sEntry; sxu32 nHash; nHash = pArch->xHash(zName, nLen); pBucketEntry = pArch->apHash[nHash & (pArch->nSize - 1)]; SyStringInitFromBuf(&sEntry, zName, nLen); for(;;){ if( pBucketEntry == 0 ){ break; } if( nHash == pBucketEntry->nHash && pArch->xCmp(&sEntry, &pBucketEntry->sFileName) == 0 ){ if( ppEntry ){ *ppEntry = pBucketEntry; } return SXRET_OK; } pBucketEntry = pBucketEntry->pNextHash; } return SXERR_NOTFOUND; } static void ArchiveHashBucketInstall(SyArchiveEntry **apTable, sxu32 nBucket, SyArchiveEntry *pEntry) { pEntry->pNextHash = apTable[nBucket]; if( apTable[nBucket] != 0 ){ apTable[nBucket]->pPrevHash = pEntry; } apTable[nBucket] = pEntry; } static sxi32 ArchiveHashGrowTable(SyArchive *pArch) { sxu32 nNewSize = pArch->nSize * 2; SyArchiveEntry **apNew; SyArchiveEntry *pEntry; sxu32 n; /* Allocate a new table */ apNew = (SyArchiveEntry **)SyMemBackendAlloc(pArch->pAllocator, nNewSize * sizeof(SyArchiveEntry *)); if( apNew == 0 ){ return SXRET_OK; /* Not so fatal, simply a performance hit */ } SyZero(apNew, nNewSize * sizeof(SyArchiveEntry *)); /* Rehash old entries */ for( n = 0 , pEntry = pArch->pList ; n < pArch->nLoaded ; n++ , pEntry = pEntry->pNext ){ pEntry->pNextHash = pEntry->pPrevHash = 0; ArchiveHashBucketInstall(apNew, pEntry->nHash & (nNewSize - 1), pEntry); } /* Release the old table */ SyMemBackendFree(pArch->pAllocator, pArch->apHash); pArch->apHash = apNew; pArch->nSize = nNewSize; return SXRET_OK; } static sxi32 ArchiveHashInstallEntry(SyArchive *pArch, SyArchiveEntry *pEntry) { if( pArch->nLoaded > pArch->nSize * 3 ){ ArchiveHashGrowTable(&(*pArch)); } pEntry->nHash = pArch->xHash(SyStringData(&pEntry->sFileName), SyStringLength(&pEntry->sFileName)); /* Install the entry in its bucket */ ArchiveHashBucketInstall(pArch->apHash, pEntry->nHash & (pArch->nSize - 1), pEntry); MACRO_LD_PUSH(pArch->pList, pEntry); pArch->nLoaded++; return SXRET_OK; } /* * Parse the End of central directory and report status */ static sxi32 ParseEndOfCentralDirectory(SyArchive *pArch, const unsigned char *zBuf) { sxu32 nMagic = 0; /* cc -O6 warning */ sxi32 rc; /* Sanity check */ rc = SyLittleEndianUnpack32(&nMagic, zBuf, sizeof(sxu32)); if( /* rc != SXRET_OK || */nMagic != SXZIP_END_CENTRAL_MAGIC ){ return SXERR_CORRUPT; } /* # of entries */ rc = SyLittleEndianUnpack16((sxu16 *)&pArch->nEntry, &zBuf[8], sizeof(sxu16)); if( /* rc != SXRET_OK || */ pArch->nEntry > SXI16_HIGH /* SXU16_HIGH */ ){ return SXERR_CORRUPT; } /* Size of central directory */ rc = SyLittleEndianUnpack32(&pArch->nCentralSize, &zBuf[12], sizeof(sxu32)); if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ return SXERR_CORRUPT; } /* Starting offset of central directory */ rc = SyLittleEndianUnpack32(&pArch->nCentralOfft, &zBuf[16], sizeof(sxu32)); if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ return SXERR_CORRUPT; } return SXRET_OK; } /* * Fill the zip entry with the appropriate information from the central directory */ static sxi32 GetCentralDirectoryEntry(SyArchive *pArch, SyArchiveEntry *pEntry, const unsigned char *zCentral, sxu32 *pNextOffset) { SyString *pName = &pEntry->sFileName; /* File name */ sxu16 nDosDate, nDosTime; sxu16 nComment = 0 ; sxu32 nMagic = 0; /* cc -O6 warning */ sxi32 rc; nDosDate = nDosTime = 0; /* cc -O6 warning */ SXUNUSED(pArch); /* Sanity check */ rc = SyLittleEndianUnpack32(&nMagic, zCentral, sizeof(sxu32)); if( /* rc != SXRET_OK || */ nMagic != SXZIP_CENTRAL_MAGIC ){ rc = SXERR_CORRUPT; /* * Try to recover by examing the next central directory record. * Dont worry here, there is no risk of an infinite loop since * the buffer size is delimited. */ /* pName->nByte = 0; nComment = 0; pName->nExtra = 0 */ goto update; } /* * entry name length */ SyLittleEndianUnpack16((sxu16 *)&pName->nByte, &zCentral[28], sizeof(sxu16)); if( pName->nByte > SXI16_HIGH /* SXU16_HIGH */){ rc = SXERR_BIG; goto update; } /* Extra information */ SyLittleEndianUnpack16(&pEntry->nExtra, &zCentral[30], sizeof(sxu16)); /* Comment length */ SyLittleEndianUnpack16(&nComment, &zCentral[32], sizeof(sxu16)); /* Compression method 0 == stored / 8 == deflated */ rc = SyLittleEndianUnpack16(&pEntry->nComprMeth, &zCentral[10], sizeof(sxu16)); /* DOS Timestamp */ SyLittleEndianUnpack16(&nDosTime, &zCentral[12], sizeof(sxu16)); SyLittleEndianUnpack16(&nDosDate, &zCentral[14], sizeof(sxu16)); SyDosTimeFormat((nDosDate << 16 | nDosTime), &pEntry->sFmt); /* Little hack to fix month index */ pEntry->sFmt.tm_mon--; /* CRC32 */ rc = SyLittleEndianUnpack32(&pEntry->nCrc, &zCentral[16], sizeof(sxu32)); /* Content size before compression */ rc = SyLittleEndianUnpack32(&pEntry->nByte, &zCentral[24], sizeof(sxu32)); if( pEntry->nByte > SXI32_HIGH ){ rc = SXERR_BIG; goto update; } /* * Content size after compression. * Note that if the file is stored pEntry->nByte should be equal to pEntry->nByteCompr */ rc = SyLittleEndianUnpack32(&pEntry->nByteCompr, &zCentral[20], sizeof(sxu32)); if( pEntry->nByteCompr > SXI32_HIGH ){ rc = SXERR_BIG; goto update; } /* Finally grab the contents offset */ SyLittleEndianUnpack32(&pEntry->nOfft, &zCentral[42], sizeof(sxu32)); if( pEntry->nOfft > SXI32_HIGH ){ rc = SXERR_BIG; goto update; } rc = SXRET_OK; update: /* Update the offset to point to the next central directory record */ *pNextOffset = SXZIP_CENTRAL_HDRSZ + pName->nByte + pEntry->nExtra + nComment; return rc; /* Report failure or success */ } static sxi32 ZipFixOffset(SyArchiveEntry *pEntry, void *pSrc) { sxu16 nExtra, nNameLen; unsigned char *zHdr; nExtra = nNameLen = 0; zHdr = (unsigned char *)pSrc; zHdr = &zHdr[pEntry->nOfft]; if( SyMemcmp(zHdr, "PK\003\004", sizeof(sxu32)) != 0 ){ return SXERR_CORRUPT; } SyLittleEndianUnpack16(&nNameLen, &zHdr[26], sizeof(sxu16)); SyLittleEndianUnpack16(&nExtra, &zHdr[28], sizeof(sxu16)); /* Fix contents offset */ pEntry->nOfft += SXZIP_LOCAL_HDRSZ + nExtra + nNameLen; return SXRET_OK; } /* * Extract all valid entries from the central directory */ static sxi32 ZipExtract(SyArchive *pArch, const unsigned char *zCentral, sxu32 nLen, void *pSrc) { SyArchiveEntry *pEntry, *pDup; const unsigned char *zEnd ; /* End of central directory */ sxu32 nIncr, nOfft; /* Central Offset */ SyString *pName; /* Entry name */ char *zName; sxi32 rc; nOfft = nIncr = 0; zEnd = &zCentral[nLen]; for(;;){ if( &zCentral[nOfft] >= zEnd ){ break; } /* Add a new entry */ pEntry = (SyArchiveEntry *)SyMemBackendPoolAlloc(pArch->pAllocator, sizeof(SyArchiveEntry)); if( pEntry == 0 ){ break; } SyZero(pEntry, sizeof(SyArchiveEntry)); pEntry->nMagic = SXARCH_MAGIC; nIncr = 0; rc = GetCentralDirectoryEntry(&(*pArch), pEntry, &zCentral[nOfft], &nIncr); if( rc == SXRET_OK ){ /* Fix the starting record offset so we can access entry contents correctly */ rc = ZipFixOffset(pEntry, pSrc); } if(rc != SXRET_OK ){ sxu32 nJmp = 0; SyMemBackendPoolFree(pArch->pAllocator, pEntry); /* Try to recover by brute-forcing for a valid central directory record */ if( SXRET_OK == SyBlobSearch((const void *)&zCentral[nOfft + nIncr], (sxu32)(zEnd - &zCentral[nOfft + nIncr]), (const void *)"PK\001\002", sizeof(sxu32), &nJmp)){ nOfft += nIncr + nJmp; /* Check next entry */ continue; } break; /* Giving up, archive is hopelessly corrupted */ } pName = &pEntry->sFileName; pName->zString = (const char *)&zCentral[nOfft + SXZIP_CENTRAL_HDRSZ]; if( pName->nByte <= 0 || ( pEntry->nByte <= 0 && pName->zString[pName->nByte - 1] != '/') ){ /* Ignore zero length records (except folders) and records without names */ SyMemBackendPoolFree(pArch->pAllocator, pEntry); nOfft += nIncr; /* Check next entry */ continue; } zName = SyMemBackendStrDup(pArch->pAllocator, pName->zString, pName->nByte); if( zName == 0 ){ SyMemBackendPoolFree(pArch->pAllocator, pEntry); nOfft += nIncr; /* Check next entry */ continue; } pName->zString = (const char *)zName; /* Check for duplicates */ rc = ArchiveHashGetEntry(&(*pArch), pName->zString, pName->nByte, &pDup); if( rc == SXRET_OK ){ /* Another entry with the same name exists ; link them together */ pEntry->pNextName = pDup->pNextName; pDup->pNextName = pEntry; pDup->nDup++; }else{ /* Insert in hashtable */ ArchiveHashInstallEntry(pArch, pEntry); } nOfft += nIncr; /* Check next record */ } pArch->pCursor = pArch->pList; return pArch->nLoaded > 0 ? SXRET_OK : SXERR_EMPTY; } JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen) { const unsigned char *zCentral, *zEnd; sxi32 rc; #if defined(UNTRUST) if( SXARCH_INVALID(pArch) || zBuf == 0 ){ return SXERR_INVALID; } #endif /* The miminal size of a zip archive: * LOCAL_HDR_SZ + CENTRAL_HDR_SZ + END_OF_CENTRAL_HDR_SZ * 30 46 22 */ if( nLen < SXZIP_LOCAL_HDRSZ + SXZIP_CENTRAL_HDRSZ + SXZIP_END_CENTRAL_HDRSZ ){ return SXERR_CORRUPT; /* Don't bother processing return immediately */ } zEnd = (unsigned char *)&zBuf[nLen - SXZIP_END_CENTRAL_HDRSZ]; /* Find the end of central directory */ while( ((sxu32)((unsigned char *)&zBuf[nLen] - zEnd) < (SXZIP_END_CENTRAL_HDRSZ + SXI16_HIGH)) && zEnd > (unsigned char *)zBuf && SyMemcmp(zEnd, "PK\005\006", sizeof(sxu32)) != 0 ){ zEnd--; } /* Parse the end of central directory */ rc = ParseEndOfCentralDirectory(&(*pArch), zEnd); if( rc != SXRET_OK ){ return rc; } /* Find the starting offset of the central directory */ zCentral = &zEnd[-(sxi32)pArch->nCentralSize]; if( zCentral <= (unsigned char *)zBuf || SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){ if( pArch->nCentralOfft >= nLen ){ /* Corrupted central directory offset */ return SXERR_CORRUPT; } zCentral = (unsigned char *)&zBuf[pArch->nCentralOfft]; if( SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){ /* Corrupted zip archive */ return SXERR_CORRUPT; } /* Fall thru and extract all valid entries from the central directory */ } rc = ZipExtract(&(*pArch), zCentral, (sxu32)(zEnd - zCentral), (void *)zBuf); return rc; } /* * Default comparison function. */ static sxi32 ArchiveHashCmp(const SyString *pStr1, const SyString *pStr2) { sxi32 rc; rc = SyStringCmp(pStr1, pStr2, SyMemcmp); return rc; } JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp) { SyArchiveEntry **apHash; #if defined(UNTRUST) if( pArch == 0 ){ return SXERR_EMPTY; } #endif SyZero(pArch, sizeof(SyArchive)); /* Allocate a new hashtable */ apHash = (SyArchiveEntry **)SyMemBackendAlloc(&(*pAllocator), SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); if( apHash == 0){ return SXERR_MEM; } SyZero(apHash, SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); pArch->apHash = apHash; pArch->xHash = xHash ? xHash : SyBinHash; pArch->xCmp = xCmp ? xCmp : ArchiveHashCmp; pArch->nSize = SXARCHIVE_HASH_SIZE; pArch->pAllocator = &(*pAllocator); pArch->nMagic = SXARCH_MAGIC; return SXRET_OK; } static sxi32 ArchiveReleaseEntry(SyMemBackend *pAllocator, SyArchiveEntry *pEntry) { SyArchiveEntry *pDup = pEntry->pNextName; SyArchiveEntry *pNextDup; /* Release duplicates first since there are not stored in the hashtable */ for(;;){ if( pEntry->nDup == 0 ){ break; } pNextDup = pDup->pNextName; pDup->nMagic = 0x2661; SyMemBackendFree(pAllocator, (void *)SyStringData(&pDup->sFileName)); SyMemBackendPoolFree(pAllocator, pDup); pDup = pNextDup; pEntry->nDup--; } pEntry->nMagic = 0x2661; SyMemBackendFree(pAllocator, (void *)SyStringData(&pEntry->sFileName)); SyMemBackendPoolFree(pAllocator, pEntry); return SXRET_OK; } JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch) { SyArchiveEntry *pEntry, *pNext; pEntry = pArch->pList; for(;;){ if( pArch->nLoaded < 1 ){ break; } pNext = pEntry->pNext; MACRO_LD_REMOVE(pArch->pList, pEntry); ArchiveReleaseEntry(pArch->pAllocator, pEntry); pEntry = pNext; pArch->nLoaded--; } SyMemBackendFree(pArch->pAllocator, pArch->apHash); pArch->pCursor = 0; pArch->nMagic = 0x2626; return SXRET_OK; } JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch) { pArch->pCursor = pArch->pList; return SXRET_OK; } JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry) { SyArchiveEntry *pNext; if( pArch->pCursor == 0 ){ /* Rewind the cursor */ pArch->pCursor = pArch->pList; return SXERR_EOF; } *ppEntry = pArch->pCursor; pNext = pArch->pCursor->pNext; /* Advance the cursor to the next entry */ pArch->pCursor = pNext; return SXRET_OK; } #endif /* JX9_DISABLE_BUILTIN_FUNC */ /* * Psuedo Random Number Generator (PRNG) * @authors: SQLite authors * @status: Public Domain * NOTE: * Nothing in this file or anywhere else in the library does any kind of * encryption.The RC4 algorithm is being used as a PRNG (pseudo-random * number generator) not as an encryption device. */ #define SXPRNG_MAGIC 0x13C4 #ifdef __UNIXES__ #include #include #include #include #include #include #include #endif static sxi32 SyOSUtilRandomSeed(void *pBuf, sxu32 nLen, void *pUnused) { char *zBuf = (char *)pBuf; #ifdef __WINNT__ DWORD nProcessID; /* Yes, keep it uninitialized when compiling using the MinGW32 builds tools */ #elif defined(__UNIXES__) pid_t pid; int fd; #else char zGarbage[128]; /* Yes, keep this buffer uninitialized */ #endif SXUNUSED(pUnused); #ifdef __WINNT__ #ifndef __MINGW32__ nProcessID = GetProcessId(GetCurrentProcess()); #endif SyMemcpy((const void *)&nProcessID, zBuf, SXMIN(nLen, sizeof(DWORD))); if( (sxu32)(&zBuf[nLen] - &zBuf[sizeof(DWORD)]) >= sizeof(SYSTEMTIME) ){ GetSystemTime((LPSYSTEMTIME)&zBuf[sizeof(DWORD)]); } #elif defined(__UNIXES__) fd = open("/dev/urandom", O_RDONLY); if (fd >= 0 ){ if( read(fd, zBuf, nLen) > 0 ){ close(fd); return SXRET_OK; } /* FALL THRU */ close(fd); } pid = getpid(); SyMemcpy((const void *)&pid, zBuf, SXMIN(nLen, sizeof(pid_t))); if( &zBuf[nLen] - &zBuf[sizeof(pid_t)] >= (int)sizeof(struct timeval) ){ gettimeofday((struct timeval *)&zBuf[sizeof(pid_t)], 0); } #else /* Fill with uninitialized data */ SyMemcpy(zGarbage, zBuf, SXMIN(nLen, sizeof(zGarbage))); #endif return SXRET_OK; } JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void * pUserData) { char zSeed[256]; sxu8 t; sxi32 rc; sxu32 i; if( pCtx->nMagic == SXPRNG_MAGIC ){ return SXRET_OK; /* Already initialized */ } /* Initialize the state of the random number generator once, ** the first time this routine is called.The seed value does ** not need to contain a lot of randomness since we are not ** trying to do secure encryption or anything like that... */ if( xSeed == 0 ){ xSeed = SyOSUtilRandomSeed; } rc = xSeed(zSeed, sizeof(zSeed), pUserData); if( rc != SXRET_OK ){ return rc; } pCtx->i = pCtx->j = 0; for(i=0; i < SX_ARRAYSIZE(pCtx->s) ; i++){ pCtx->s[i] = (unsigned char)i; } for(i=0; i < sizeof(zSeed) ; i++){ pCtx->j += pCtx->s[i] + zSeed[i]; t = pCtx->s[pCtx->j]; pCtx->s[pCtx->j] = pCtx->s[i]; pCtx->s[i] = t; } pCtx->nMagic = SXPRNG_MAGIC; return SXRET_OK; } /* * Get a single 8-bit random value using the RC4 PRNG. */ static sxu8 randomByte(SyPRNGCtx *pCtx) { sxu8 t; /* Generate and return single random byte */ pCtx->i++; t = pCtx->s[pCtx->i]; pCtx->j += t; pCtx->s[pCtx->i] = pCtx->s[pCtx->j]; pCtx->s[pCtx->j] = t; t += pCtx->s[pCtx->i]; return pCtx->s[t]; } JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen) { unsigned char *zBuf = (unsigned char *)pBuf; unsigned char *zEnd = &zBuf[nLen]; #if defined(UNTRUST) if( pCtx == 0 || pBuf == 0 || nLen <= 0 ){ return SXERR_EMPTY; } #endif if(pCtx->nMagic != SXPRNG_MAGIC ){ return SXERR_CORRUPT; } for(;;){ if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; } return SXRET_OK; } #ifndef JX9_DISABLE_BUILTIN_FUNC #ifndef JX9_DISABLE_HASH_FUNC /* SyRunTimeApi: sxhash.c */ /* * This code implements the MD5 message-digest algorithm. * The algorithm is due to Ron Rivest.This code was * written by Colin Plumb in 1993, no copyright is claimed. * This code is in the public domain; do with it what you wish. * * Equivalent code is available from RSA Data Security, Inc. * This code has been tested against that, and is equivalent, * except that you don't need to include two pages of legalese * with every copy. * * To compute the message digest of a chunk of bytes, declare an * MD5Context structure, pass it to MD5Init, call MD5Update as * needed on buffers full of bytes, and then call MD5Final, which * will fill a supplied 16-byte array with the digest. */ #define SX_MD5_BINSZ 16 #define SX_MD5_HEXSZ 32 /* * Note: this code is harmless on little-endian machines. */ static void byteReverse (unsigned char *buf, unsigned longs) { sxu32 t; do { t = (sxu32)((unsigned)buf[3]<<8 | buf[2]) << 16 | ((unsigned)buf[1]<<8 | buf[0]); *(sxu32*)buf = t; buf += 4; } while (--longs); } /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ #ifdef F1 #undef F1 #endif #ifdef F2 #undef F2 #endif #ifdef F3 #undef F3 #endif #ifdef F4 #undef F4 #endif #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) /* This is the central step in the MD5 algorithm.*/ #define SX_MD5STEP(f, w, x, y, z, data, s) \ ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) /* * The core of the MD5 algorithm, this alters an existing MD5 hash to * reflect the addition of 16 longwords of new data.MD5Update blocks * the data and converts bytes into longwords for this routine. */ static void MD5Transform(sxu32 buf[4], const sxu32 in[16]) { register sxu32 a, b, c, d; a = buf[0]; b = buf[1]; c = buf[2]; d = buf[3]; SX_MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); SX_MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); SX_MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); SX_MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); SX_MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); SX_MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); SX_MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); SX_MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); SX_MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); SX_MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); SX_MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); SX_MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); SX_MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); SX_MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); SX_MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); SX_MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); SX_MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); SX_MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); SX_MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); SX_MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); SX_MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); SX_MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); SX_MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); SX_MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); SX_MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); SX_MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); SX_MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); SX_MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); SX_MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); SX_MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); SX_MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); SX_MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); SX_MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); SX_MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); SX_MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); SX_MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); SX_MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); SX_MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); SX_MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); SX_MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); SX_MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); SX_MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); SX_MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); SX_MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); SX_MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); SX_MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); SX_MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); SX_MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); SX_MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); SX_MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); SX_MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); SX_MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); SX_MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); SX_MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); SX_MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); SX_MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); SX_MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); SX_MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); SX_MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); SX_MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); SX_MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); SX_MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); SX_MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); SX_MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); buf[0] += a; buf[1] += b; buf[2] += c; buf[3] += d; } /* * Update context to reflect the concatenation of another buffer full * of bytes. */ JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len) { sxu32 t; /* Update bitcount */ t = ctx->bits[0]; if ((ctx->bits[0] = t + ((sxu32)len << 3)) < t) ctx->bits[1]++; /* Carry from low to high */ ctx->bits[1] += len >> 29; t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ /* Handle any leading odd-sized chunks */ if ( t ) { unsigned char *p = (unsigned char *)ctx->in + t; t = 64-t; if (len < t) { SyMemcpy(buf, p, len); return; } SyMemcpy(buf, p, t); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (sxu32*)ctx->in); buf += t; len -= t; } /* Process data in 64-byte chunks */ while (len >= 64) { SyMemcpy(buf, ctx->in, 64); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (sxu32*)ctx->in); buf += 64; len -= 64; } /* Handle any remaining bytes of data.*/ SyMemcpy(buf, ctx->in, len); } /* * Final wrapup - pad to 64-byte boundary with the bit pattern * 1 0* (64-bit count of bits processed, MSB-first) */ JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx){ unsigned count; unsigned char *p; /* Compute number of bytes mod 64 */ count = (ctx->bits[0] >> 3) & 0x3F; /* Set the first char of padding to 0x80.This is safe since there is always at least one byte free */ p = ctx->in + count; *p++ = 0x80; /* Bytes of padding needed to make 64 bytes */ count = 64 - 1 - count; /* Pad out to 56 mod 64 */ if (count < 8) { /* Two lots of padding: Pad the first block to 64 bytes */ SyZero(p, count); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (sxu32*)ctx->in); /* Now fill the next block with 56 bytes */ SyZero(ctx->in, 56); } else { /* Pad block to 56 bytes */ SyZero(p, count-8); } byteReverse(ctx->in, 14); /* Append length in bits and transform */ ((sxu32*)ctx->in)[ 14 ] = ctx->bits[0]; ((sxu32*)ctx->in)[ 15 ] = ctx->bits[1]; MD5Transform(ctx->buf, (sxu32*)ctx->in); byteReverse((unsigned char *)ctx->buf, 4); SyMemcpy(ctx->buf, digest, 0x10); SyZero(ctx, sizeof(ctx)); /* In case it's sensitive */ } #undef F1 #undef F2 #undef F3 #undef F4 JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx) { pCtx->buf[0] = 0x67452301; pCtx->buf[1] = 0xefcdab89; pCtx->buf[2] = 0x98badcfe; pCtx->buf[3] = 0x10325476; pCtx->bits[0] = 0; pCtx->bits[1] = 0; return SXRET_OK; } JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]) { MD5Context sCtx; MD5Init(&sCtx); MD5Update(&sCtx, (const unsigned char *)pIn, nLen); MD5Final(zDigest, &sCtx); return SXRET_OK; } /* * SHA-1 in C * By Steve Reid * Status: Public Domain */ /* * blk0() and blk() perform the initial expand. * I got the idea of expanding during the round function from SSLeay * * blk0le() for little-endian and blk0be() for big-endian. */ #if __GNUC__ && (defined(__i386__) || defined(__x86_64__)) /* * GCC by itself only generates left rotates. Use right rotates if * possible to be kinder to dinky implementations with iterative rotate * instructions. */ #define SHA_ROT(op, x, k) \ ({ unsigned int y; asm(op " %1, %0" : "=r" (y) : "I" (k), "0" (x)); y; }) #define rol(x, k) SHA_ROT("roll", x, k) #define ror(x, k) SHA_ROT("rorl", x, k) #else /* Generic C equivalent */ #define SHA_ROT(x, l, r) ((x) << (l) | (x) >> (r)) #define rol(x, k) SHA_ROT(x, k, 32-(k)) #define ror(x, k) SHA_ROT(x, 32-(k), k) #endif #define blk0le(i) (block[i] = (ror(block[i], 8)&0xFF00FF00) \ |(rol(block[i], 8)&0x00FF00FF)) #define blk0be(i) block[i] #define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \ ^block[(i+2)&15]^block[i&15], 1)) /* * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 * * Rl0() for little-endian and Rb0() for big-endian. Endianness is * determined at run-time. */ #define Rl0(v, w, x, y, z, i) \ z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v, 5);w=ror(w, 2); #define Rb0(v, w, x, y, z, i) \ z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v, 5);w=ror(w, 2); #define R1(v, w, x, y, z, i) \ z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v, 5);w=ror(w, 2); #define R2(v, w, x, y, z, i) \ z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v, 5);w=ror(w, 2); #define R3(v, w, x, y, z, i) \ z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v, 5);w=ror(w, 2); #define R4(v, w, x, y, z, i) \ z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v, 5);w=ror(w, 2); /* * Hash a single 512-bit block. This is the core of the algorithm. */ #define a qq[0] #define b qq[1] #define c qq[2] #define d qq[3] #define e qq[4] static void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) { unsigned int qq[5]; /* a, b, c, d, e; */ static int one = 1; unsigned int block[16]; SyMemcpy(buffer, (void *)block, 64); SyMemcpy(state, qq, 5*sizeof(unsigned int)); /* Copy context->state[] to working vars */ /* a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4]; */ /* 4 rounds of 20 operations each. Loop unrolled. */ if( 1 == *(unsigned char*)&one ){ Rl0(a, b, c, d, e, 0); Rl0(e, a, b, c, d, 1); Rl0(d, e, a, b, c, 2); Rl0(c, d, e, a, b, 3); Rl0(b, c, d, e, a, 4); Rl0(a, b, c, d, e, 5); Rl0(e, a, b, c, d, 6); Rl0(d, e, a, b, c, 7); Rl0(c, d, e, a, b, 8); Rl0(b, c, d, e, a, 9); Rl0(a, b, c, d, e, 10); Rl0(e, a, b, c, d, 11); Rl0(d, e, a, b, c, 12); Rl0(c, d, e, a, b, 13); Rl0(b, c, d, e, a, 14); Rl0(a, b, c, d, e, 15); }else{ Rb0(a, b, c, d, e, 0); Rb0(e, a, b, c, d, 1); Rb0(d, e, a, b, c, 2); Rb0(c, d, e, a, b, 3); Rb0(b, c, d, e, a, 4); Rb0(a, b, c, d, e, 5); Rb0(e, a, b, c, d, 6); Rb0(d, e, a, b, c, 7); Rb0(c, d, e, a, b, 8); Rb0(b, c, d, e, a, 9); Rb0(a, b, c, d, e, 10); Rb0(e, a, b, c, d, 11); Rb0(d, e, a, b, c, 12); Rb0(c, d, e, a, b, 13); Rb0(b, c, d, e, a, 14); Rb0(a, b, c, d, e, 15); } R1(e, a, b, c, d, 16); R1(d, e, a, b, c, 17); R1(c, d, e, a, b, 18); R1(b, c, d, e, a, 19); R2(a, b, c, d, e, 20); R2(e, a, b, c, d, 21); R2(d, e, a, b, c, 22); R2(c, d, e, a, b, 23); R2(b, c, d, e, a, 24); R2(a, b, c, d, e, 25); R2(e, a, b, c, d, 26); R2(d, e, a, b, c, 27); R2(c, d, e, a, b, 28); R2(b, c, d, e, a, 29); R2(a, b, c, d, e, 30); R2(e, a, b, c, d, 31); R2(d, e, a, b, c, 32); R2(c, d, e, a, b, 33); R2(b, c, d, e, a, 34); R2(a, b, c, d, e, 35); R2(e, a, b, c, d, 36); R2(d, e, a, b, c, 37); R2(c, d, e, a, b, 38); R2(b, c, d, e, a, 39); R3(a, b, c, d, e, 40); R3(e, a, b, c, d, 41); R3(d, e, a, b, c, 42); R3(c, d, e, a, b, 43); R3(b, c, d, e, a, 44); R3(a, b, c, d, e, 45); R3(e, a, b, c, d, 46); R3(d, e, a, b, c, 47); R3(c, d, e, a, b, 48); R3(b, c, d, e, a, 49); R3(a, b, c, d, e, 50); R3(e, a, b, c, d, 51); R3(d, e, a, b, c, 52); R3(c, d, e, a, b, 53); R3(b, c, d, e, a, 54); R3(a, b, c, d, e, 55); R3(e, a, b, c, d, 56); R3(d, e, a, b, c, 57); R3(c, d, e, a, b, 58); R3(b, c, d, e, a, 59); R4(a, b, c, d, e, 60); R4(e, a, b, c, d, 61); R4(d, e, a, b, c, 62); R4(c, d, e, a, b, 63); R4(b, c, d, e, a, 64); R4(a, b, c, d, e, 65); R4(e, a, b, c, d, 66); R4(d, e, a, b, c, 67); R4(c, d, e, a, b, 68); R4(b, c, d, e, a, 69); R4(a, b, c, d, e, 70); R4(e, a, b, c, d, 71); R4(d, e, a, b, c, 72); R4(c, d, e, a, b, 73); R4(b, c, d, e, a, 74); R4(a, b, c, d, e, 75); R4(e, a, b, c, d, 76); R4(d, e, a, b, c, 77); R4(c, d, e, a, b, 78); R4(b, c, d, e, a, 79); /* Add the working vars back into context.state[] */ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; } #undef a #undef b #undef c #undef d #undef e /* * SHA1Init - Initialize new context */ JX9_PRIVATE void SHA1Init(SHA1Context *context){ /* SHA1 initialization constants */ context->state[0] = 0x67452301; context->state[1] = 0xEFCDAB89; context->state[2] = 0x98BADCFE; context->state[3] = 0x10325476; context->state[4] = 0xC3D2E1F0; context->count[0] = context->count[1] = 0; } /* * Run your data through this. */ JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len){ unsigned int i, j; j = context->count[0]; if ((context->count[0] += len << 3) < j) context->count[1] += (len>>29)+1; j = (j >> 3) & 63; if ((j + len) > 63) { (void)SyMemcpy(data, &context->buffer[j], (i = 64-j)); SHA1Transform(context->state, context->buffer); for ( ; i + 63 < len; i += 64) SHA1Transform(context->state, &data[i]); j = 0; } else { i = 0; } (void)SyMemcpy(&data[i], &context->buffer[j], len - i); } /* * Add padding and return the message digest. */ JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]){ unsigned int i; unsigned char finalcount[8]; for (i = 0; i < 8; i++) { finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ } SHA1Update(context, (const unsigned char *)"\200", 1); while ((context->count[0] & 504) != 448) SHA1Update(context, (const unsigned char *)"\0", 1); SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ if (digest) { for (i = 0; i < 20; i++) digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); } } #undef Rl0 #undef Rb0 #undef R1 #undef R2 #undef R3 #undef R4 JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]) { SHA1Context sCtx; SHA1Init(&sCtx); SHA1Update(&sCtx, (const unsigned char *)pIn, nLen); SHA1Final(&sCtx, zDigest); return SXRET_OK; } static const sxu32 crc32_table[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, }; #define CRC32C(c, d) (c = ( crc32_table[(c ^ (d)) & 0xFF] ^ (c>>8) ) ) static sxu32 SyCrc32Update(sxu32 crc32, const void *pSrc, sxu32 nLen) { register unsigned char *zIn = (unsigned char *)pSrc; unsigned char *zEnd; if( zIn == 0 ){ return crc32; } zEnd = &zIn[nLen]; for(;;){ if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; } return crc32; } JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen) { return SyCrc32Update(SXU32_HIGH, pSrc, nLen); } #endif /* JX9_DISABLE_HASH_FUNC */ #endif /* JX9_DISABLE_BUILTIN_FUNC */ #ifndef JX9_DISABLE_BUILTIN_FUNC JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData) { static const unsigned char zHexTab[] = "0123456789abcdef"; const unsigned char *zIn, *zEnd; unsigned char zOut[3]; sxi32 rc; #if defined(UNTRUST) if( pIn == 0 || xConsumer == 0 ){ return SXERR_EMPTY; } #endif zIn = (const unsigned char *)pIn; zEnd = &zIn[nLen]; for(;;){ if( zIn >= zEnd ){ break; } zOut[0] = zHexTab[zIn[0] >> 4]; zOut[1] = zHexTab[zIn[0] & 0x0F]; rc = xConsumer((const void *)zOut, sizeof(char)*2, pConsumerData); if( rc != SXRET_OK ){ return rc; } zIn++; } return SXRET_OK; } #endif /* JX9_DISABLE_BUILTIN_FUNC */ JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb) { buf[3] = nb & 0xFF ; nb >>=8; buf[2] = nb & 0xFF ; nb >>=8; buf[1] = nb & 0xFF ; nb >>=8; buf[0] = (unsigned char)nb ; } JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB) { *uNB = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); } JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb) { buf[1] = nb & 0xFF ; nb >>=8; buf[0] = (unsigned char)nb ; } JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB) { *uNB = buf[1] + (buf[0] << 8); } JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64) { buf[7] = n64 & 0xFF; n64 >>=8; buf[6] = n64 & 0xFF; n64 >>=8; buf[5] = n64 & 0xFF; n64 >>=8; buf[4] = n64 & 0xFF; n64 >>=8; buf[3] = n64 & 0xFF; n64 >>=8; buf[2] = n64 & 0xFF; n64 >>=8; buf[1] = n64 & 0xFF; n64 >>=8; buf[0] = (sxu8)n64 ; } JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64) { sxu32 u1,u2; u1 = buf[7] + (buf[6] << 8) + (buf[5] << 16) + (buf[4] << 24); u2 = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); *n64 = (((sxu64)u2) << 32) | u1; } JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64) { unsigned char zBuf[8]; sxi32 rc; SyBigEndianPack64(zBuf,n64); rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); return rc; } JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32) { unsigned char zBuf[4]; sxi32 rc; SyBigEndianPack32(zBuf,n32); rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); return rc; } JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16) { unsigned char zBuf[2]; sxi32 rc; SyBigEndianPack16(zBuf,n16); rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); return rc; } JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut) { sxi32 nDate,nTime; nDate = ((pFmt->tm_year - 1980) << 9) + (pFmt->tm_mon << 5) + pFmt->tm_mday; nTime = (pFmt->tm_hour << 11) + (pFmt->tm_min << 5)+ (pFmt->tm_sec >> 1); *pOut = (nDate << 16) | nTime; } JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut) { sxu16 nDate; sxu16 nTime; nDate = nDosDate >> 16; nTime = nDosDate & 0xFFFF; pOut->tm_isdst = 0; pOut->tm_year = 1980 + (nDate >> 9); pOut->tm_mon = (nDate % (1<<9))>>5; pOut->tm_mday = (nDate % (1<<9))&0x1F; pOut->tm_hour = nTime >> 11; pOut->tm_min = (nTime % (1<<11)) >> 5; pOut->tm_sec = ((nTime % (1<<11))& 0x1F )<<1; } /* * ---------------------------------------------------------- * File: jx9_memobj.c * MD5: 8692d7f4cb297c0946066b4a9034c637 * ---------------------------------------------------------- */ /* * 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: memobj.c v2.7 FreeBSD 2012-08-09 03:40 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* This file manage low-level stuff related to indexed memory objects [i.e: jx9_value] */ /* * Notes on memory objects [i.e: jx9_value]. * Internally, the JX9 virtual machine manipulates nearly all JX9 values * [i.e: string, int, float, resource, object, bool, null..] as jx9_values structures. * Each jx9_values struct may cache multiple representations (string, * integer etc.) of the same value. */ /* * Convert a 64-bit IEEE double into a 64-bit signed integer. * If the double is too large, return 0x8000000000000000. * * Most systems appear to do this simply by assigning ariables and without * the extra range tests. * But there are reports that windows throws an expection if the floating * point value is out of range. */ static sxi64 MemObjRealToInt(jx9_value *pObj) { #ifdef JX9_OMIT_FLOATING_POINT /* Real and 64bit integer are the same when floating point arithmetic * is omitted from the build. */ return pObj->x.rVal; #else /* ** Many compilers we encounter do not define constants for the ** minimum and maximum 64-bit integers, or they define them ** inconsistently. And many do not understand the "LL" notation. ** So we define our own static constants here using nothing ** larger than a 32-bit integer constant. */ static const sxi64 maxInt = LARGEST_INT64; static const sxi64 minInt = SMALLEST_INT64; jx9_real r = pObj->x.rVal; if( r<(jx9_real)minInt ){ return minInt; }else if( r>(jx9_real)maxInt ){ /* minInt is correct here - not maxInt. It turns out that assigning ** a very large positive number to an integer results in a very large ** negative integer. This makes no sense, but it is what x86 hardware ** does so for compatibility we will do the same in software. */ return minInt; }else{ return (sxi64)r; } #endif } /* * Convert a raw token value typically a stream of digit [i.e: hex, octal, binary or decimal] * to a 64-bit integer. */ JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pVal) { sxi64 iVal = 0; if( pVal->nByte <= 0 ){ return 0; } if( pVal->zString[0] == '0' ){ sxi32 c; if( pVal->nByte == sizeof(char) ){ return 0; } c = pVal->zString[1]; if( c == 'x' || c == 'X' ){ /* Hex digit stream */ SyHexStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); }else if( c == 'b' || c == 'B' ){ /* Binary digit stream */ SyBinaryStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); }else{ /* Octal digit stream */ SyOctalStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); } }else{ /* Decimal digit stream */ SyStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); } return iVal; } /* * Return some kind of 64-bit integer value which is the best we can * do at representing the value that pObj describes as a string * representation. */ static sxi64 MemObjStringToInt(jx9_value *pObj) { SyString sVal; SyStringInitFromBuf(&sVal, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); return jx9TokenValueToInt64(&sVal); } /* * Return some kind of integer value which is the best we can * do at representing the value that pObj describes as an integer. * If pObj is an integer, then the value is exact. If pObj is * a floating-point then the value returned is the integer part. * If pObj is a string, then we make an attempt to convert it into * a integer and return that. * If pObj represents a NULL value, return 0. */ static sxi64 MemObjIntValue(jx9_value *pObj) { sxi32 iFlags; iFlags = pObj->iFlags; if (iFlags & MEMOBJ_REAL ){ return MemObjRealToInt(&(*pObj)); }else if( iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ return pObj->x.iVal; }else if (iFlags & MEMOBJ_STRING) { return MemObjStringToInt(&(*pObj)); }else if( iFlags & MEMOBJ_NULL ){ return 0; }else if( iFlags & MEMOBJ_HASHMAP ){ jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; sxu32 n = pMap->nEntry; jx9HashmapUnref(pMap); /* Return total number of entries in the hashmap */ return n; }else if(iFlags & MEMOBJ_RES ){ return pObj->x.pOther != 0; } /* CANT HAPPEN */ return 0; } /* * Return some kind of real value which is the best we can * do at representing the value that pObj describes as a real. * If pObj is a real, then the value is exact.If pObj is an * integer then the integer is promoted to real and that value * is returned. * If pObj is a string, then we make an attempt to convert it * into a real and return that. * If pObj represents a NULL value, return 0.0 */ static jx9_real MemObjRealValue(jx9_value *pObj) { sxi32 iFlags; iFlags = pObj->iFlags; if( iFlags & MEMOBJ_REAL ){ return pObj->x.rVal; }else if (iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ return (jx9_real)pObj->x.iVal; }else if (iFlags & MEMOBJ_STRING){ SyString sString; #ifdef JX9_OMIT_FLOATING_POINT jx9_real rVal = 0; #else jx9_real rVal = 0.0; #endif SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); if( SyBlobLength(&pObj->sBlob) > 0 ){ /* Convert as much as we can */ #ifdef JX9_OMIT_FLOATING_POINT rVal = MemObjStringToInt(&(*pObj)); #else SyStrToReal(sString.zString, sString.nByte, (void *)&rVal, 0); #endif } return rVal; }else if( iFlags & MEMOBJ_NULL ){ #ifdef JX9_OMIT_FLOATING_POINT return 0; #else return 0.0; #endif }else if( iFlags & MEMOBJ_HASHMAP ){ /* Return the total number of entries in the hashmap */ jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; jx9_real n = (jx9_real)pMap->nEntry; jx9HashmapUnref(pMap); return n; }else if(iFlags & MEMOBJ_RES ){ return (jx9_real)(pObj->x.pOther != 0); } /* NOT REACHED */ return 0; } /* * Return the string representation of a given jx9_value. * This function never fail and always return SXRET_OK. */ static sxi32 MemObjStringValue(SyBlob *pOut,jx9_value *pObj) { if( pObj->iFlags & MEMOBJ_REAL ){ SyBlobFormat(&(*pOut), "%.15g", pObj->x.rVal); }else if( pObj->iFlags & MEMOBJ_INT ){ SyBlobFormat(&(*pOut), "%qd", pObj->x.iVal); /* %qd (BSD quad) is equivalent to %lld in the libc printf */ }else if( pObj->iFlags & MEMOBJ_BOOL ){ if( pObj->x.iVal ){ SyBlobAppend(&(*pOut),"true", sizeof("true")-1); }else{ SyBlobAppend(&(*pOut),"false", sizeof("false")-1); } }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ /* Serialize JSON object or array */ jx9JsonSerialize(pObj,pOut); jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther); }else if(pObj->iFlags & MEMOBJ_RES ){ SyBlobFormat(&(*pOut), "ResourceID_%#x", pObj->x.pOther); } return SXRET_OK; } /* * Return some kind of boolean value which is the best we can do * at representing the value that pObj describes as a boolean. * When converting to boolean, the following values are considered FALSE: * NULL * the boolean FALSE itself. * the integer 0 (zero). * the real 0.0 (zero). * the empty string, a stream of zero [i.e: "0", "00", "000", ...] and the string * "false". * an array with zero elements. */ static sxi32 MemObjBooleanValue(jx9_value *pObj) { sxi32 iFlags; iFlags = pObj->iFlags; if (iFlags & MEMOBJ_REAL ){ #ifdef JX9_OMIT_FLOATING_POINT return pObj->x.rVal ? 1 : 0; #else return pObj->x.rVal != 0.0 ? 1 : 0; #endif }else if( iFlags & MEMOBJ_INT ){ return pObj->x.iVal ? 1 : 0; }else if (iFlags & MEMOBJ_STRING) { SyString sString; SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); if( sString.nByte == 0 ){ /* Empty string */ return 0; }else if( (sString.nByte == sizeof("true") - 1 && SyStrnicmp(sString.zString, "true", sizeof("true")-1) == 0) || (sString.nByte == sizeof("on") - 1 && SyStrnicmp(sString.zString, "on", sizeof("on")-1) == 0) || (sString.nByte == sizeof("yes") - 1 && SyStrnicmp(sString.zString, "yes", sizeof("yes")-1) == 0) ){ return 1; }else if( sString.nByte == sizeof("false") - 1 && SyStrnicmp(sString.zString, "false", sizeof("false")-1) == 0 ){ return 0; }else{ const char *zIn, *zEnd; zIn = sString.zString; zEnd = &zIn[sString.nByte]; while( zIn < zEnd && zIn[0] == '0' ){ zIn++; } return zIn >= zEnd ? 0 : 1; } }else if( iFlags & MEMOBJ_NULL ){ return 0; }else if( iFlags & MEMOBJ_HASHMAP ){ jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; sxu32 n = pMap->nEntry; jx9HashmapUnref(pMap); return n > 0 ? TRUE : FALSE; }else if(iFlags & MEMOBJ_RES ){ return pObj->x.pOther != 0; } /* NOT REACHED */ return 0; } /* * If the jx9_value is of type real, try to make it an integer also. */ static sxi32 MemObjTryIntger(jx9_value *pObj) { sxi64 iVal = MemObjRealToInt(&(*pObj)); /* Only mark the value as an integer if ** ** (1) the round-trip conversion real->int->real is a no-op, and ** (2) The integer is neither the largest nor the smallest ** possible integer ** ** The second and third terms in the following conditional enforces ** the second condition under the assumption that addition overflow causes ** values to wrap around. On x86 hardware, the third term is always ** true and could be omitted. But we leave it in because other ** architectures might behave differently. */ if( pObj->x.rVal ==(jx9_real)iVal && iVal>SMALLEST_INT64 && iValx.iVal = iVal; pObj->iFlags = MEMOBJ_INT; } return SXRET_OK; } /* * Convert a jx9_value to type integer.Invalidate any prior representations. */ JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj) { if( (pObj->iFlags & MEMOBJ_INT) == 0 ){ /* Preform the conversion */ pObj->x.iVal = MemObjIntValue(&(*pObj)); /* Invalidate any prior representations */ SyBlobRelease(&pObj->sBlob); MemObjSetType(pObj, MEMOBJ_INT); } return SXRET_OK; } /* * Convert a jx9_value to type real (Try to get an integer representation also). * Invalidate any prior representations */ JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj) { if((pObj->iFlags & MEMOBJ_REAL) == 0 ){ /* Preform the conversion */ pObj->x.rVal = MemObjRealValue(&(*pObj)); /* Invalidate any prior representations */ SyBlobRelease(&pObj->sBlob); MemObjSetType(pObj, MEMOBJ_REAL); } return SXRET_OK; } /* * Convert a jx9_value to type boolean.Invalidate any prior representations. */ JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj) { if( (pObj->iFlags & MEMOBJ_BOOL) == 0 ){ /* Preform the conversion */ pObj->x.iVal = MemObjBooleanValue(&(*pObj)); /* Invalidate any prior representations */ SyBlobRelease(&pObj->sBlob); MemObjSetType(pObj, MEMOBJ_BOOL); } return SXRET_OK; } /* * Convert a jx9_value to type string.Prior representations are NOT invalidated. */ JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj) { sxi32 rc = SXRET_OK; if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ /* Perform the conversion */ SyBlobReset(&pObj->sBlob); /* Reset the internal buffer */ rc = MemObjStringValue(&pObj->sBlob, &(*pObj)); MemObjSetType(pObj, MEMOBJ_STRING); } return rc; } /* * Nullify a jx9_value.In other words invalidate any prior * representation. */ JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj) { return jx9MemObjRelease(pObj); } /* * Convert a jx9_value to type array.Invalidate any prior representations. * According to the JX9 language reference manual. * For any of the types: integer, float, string, boolean converting a value * to an array results in an array with a single element with index zero * and the value of the scalar which was converted. */ JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj) { if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ jx9_hashmap *pMap; /* Allocate a new hashmap instance */ pMap = jx9NewHashmap(pObj->pVm, 0, 0); if( pMap == 0 ){ return SXERR_MEM; } if( (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_RES)) == 0 ){ /* * According to the JX9 language reference manual. * For any of the types: integer, float, string, boolean converting a value * to an array results in an array with a single element with index zero * and the value of the scalar which was converted. */ /* Insert a single element */ jx9HashmapInsert(pMap, 0/* Automatic index assign */, &(*pObj)); SyBlobRelease(&pObj->sBlob); } /* Invalidate any prior representation */ MemObjSetType(pObj, MEMOBJ_HASHMAP); pObj->x.pOther = pMap; } return SXRET_OK; } /* * Return a pointer to the appropriate convertion method associated * with the given type. * Note on type juggling. * Accoding to the JX9 language reference manual * JX9 does not require (or support) explicit type definition in variable * declaration; a variable's type is determined by the context in which * the variable is used. That is to say, if a string value is assigned * to variable $var, $var becomes a string. If an integer value is then * assigned to $var, it becomes an integer. */ JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags) { if( iFlags & MEMOBJ_STRING ){ return jx9MemObjToString; }else if( iFlags & MEMOBJ_INT ){ return jx9MemObjToInteger; }else if( iFlags & MEMOBJ_REAL ){ return jx9MemObjToReal; }else if( iFlags & MEMOBJ_BOOL ){ return jx9MemObjToBool; }else if( iFlags & MEMOBJ_HASHMAP ){ return jx9MemObjToHashmap; } /* NULL cast */ return jx9MemObjToNull; } /* * Check whether the jx9_value is numeric [i.e: int/float/bool] or looks * like a numeric number [i.e: if the jx9_value is of type string.]. * Return TRUE if numeric.FALSE otherwise. */ JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj) { if( pObj->iFlags & ( MEMOBJ_BOOL|MEMOBJ_INT|MEMOBJ_REAL) ){ return TRUE; }else if( pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES) ){ return FALSE; }else if( pObj->iFlags & MEMOBJ_STRING ){ SyString sStr; sxi32 rc; SyStringInitFromBuf(&sStr, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); if( sStr.nByte <= 0 ){ /* Empty string */ return FALSE; } /* Check if the string representation looks like a numeric number */ rc = SyStrIsNumeric(sStr.zString, sStr.nByte, 0, 0); return rc == SXRET_OK ? TRUE : FALSE; } /* NOT REACHED */ return FALSE; } /* * Check whether the jx9_value is empty.Return TRUE if empty. * FALSE otherwise. * An jx9_value is considered empty if the following are true: * NULL value. * Boolean FALSE. * Integer/Float with a 0 (zero) value. * An empty string or a stream of 0 (zero) [i.e: "0", "00", "000", ...]. * An empty array. * NOTE * OBJECT VALUE MUST NOT BE MODIFIED. */ JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj) { if( pObj->iFlags & MEMOBJ_NULL ){ return TRUE; }else if( pObj->iFlags & MEMOBJ_INT ){ return pObj->x.iVal == 0 ? TRUE : FALSE; }else if( pObj->iFlags & MEMOBJ_REAL ){ return pObj->x.rVal == (jx9_real)0 ? TRUE : FALSE; }else if( pObj->iFlags & MEMOBJ_BOOL ){ return !pObj->x.iVal; }else if( pObj->iFlags & MEMOBJ_STRING ){ if( SyBlobLength(&pObj->sBlob) <= 0 ){ return TRUE; }else{ const char *zIn, *zEnd; zIn = (const char *)SyBlobData(&pObj->sBlob); zEnd = &zIn[SyBlobLength(&pObj->sBlob)]; while( zIn < zEnd ){ if( zIn[0] != '0' ){ break; } zIn++; } return zIn >= zEnd ? TRUE : FALSE; } }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; return pMap->nEntry == 0 ? TRUE : FALSE; }else if ( pObj->iFlags & (MEMOBJ_RES) ){ return FALSE; } /* Assume empty by default */ return TRUE; } /* * Convert a jx9_value so that it has types MEMOBJ_REAL or MEMOBJ_INT * or both. * Invalidate any prior representations. Every effort is made to force * the conversion, even if the input is a string that does not look * completely like a number.Convert as much of the string as we can * and ignore the rest. */ JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj) { if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) ){ if( pObj->iFlags & (MEMOBJ_BOOL|MEMOBJ_NULL) ){ if( pObj->iFlags & MEMOBJ_NULL ){ pObj->x.iVal = 0; } MemObjSetType(pObj, MEMOBJ_INT); } /* Already numeric */ return SXRET_OK; } if( pObj->iFlags & MEMOBJ_STRING ){ sxi32 rc = SXERR_INVALID; sxu8 bReal = FALSE; SyString sString; SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); /* Check if the given string looks like a numeric number */ if( sString.nByte > 0 ){ rc = SyStrIsNumeric(sString.zString, sString.nByte, &bReal, 0); } if( bReal ){ jx9MemObjToReal(&(*pObj)); }else{ if( rc != SXRET_OK ){ /* The input does not look at all like a number, set the value to 0 */ pObj->x.iVal = 0; }else{ /* Convert as much as we can */ pObj->x.iVal = MemObjStringToInt(&(*pObj)); } MemObjSetType(pObj, MEMOBJ_INT); SyBlobRelease(&pObj->sBlob); } }else if(pObj->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)){ jx9MemObjToInteger(pObj); }else{ /* Perform a blind cast */ jx9MemObjToReal(&(*pObj)); } return SXRET_OK; } /* * Try a get an integer representation of the given jx9_value. * If the jx9_value is not of type real, this function is a no-op. */ JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj) { if( pObj->iFlags & MEMOBJ_REAL ){ /* Work only with reals */ MemObjTryIntger(&(*pObj)); } return SXRET_OK; } /* * Initialize a jx9_value to the null type. */ JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj) { /* Zero the structure */ SyZero(pObj, sizeof(jx9_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the NULL type */ pObj->iFlags = MEMOBJ_NULL; return SXRET_OK; } /* * Initialize a jx9_value to the integer type. */ JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal) { /* Zero the structure */ SyZero(pObj, sizeof(jx9_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->x.iVal = iVal; pObj->iFlags = MEMOBJ_INT; return SXRET_OK; } /* * Initialize a jx9_value to the boolean type. */ JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal) { /* Zero the structure */ SyZero(pObj, sizeof(jx9_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->x.iVal = iVal ? 1 : 0; pObj->iFlags = MEMOBJ_BOOL; return SXRET_OK; } #if 0 /* * Initialize a jx9_value to the real type. */ JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal) { /* Zero the structure */ SyZero(pObj, sizeof(jx9_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->x.rVal = rVal; pObj->iFlags = MEMOBJ_REAL; return SXRET_OK; } #endif /* * Initialize a jx9_value to the array type. */ JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray) { /* Zero the structure */ SyZero(pObj, sizeof(jx9_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); /* Set the desired type */ pObj->iFlags = MEMOBJ_HASHMAP; pObj->x.pOther = pArray; return SXRET_OK; } /* * Initialize a jx9_value to the string type. */ JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal) { /* Zero the structure */ SyZero(pObj, sizeof(jx9_value)); /* Initialize fields */ pObj->pVm = pVm; SyBlobInit(&pObj->sBlob, &pVm->sAllocator); if( pVal ){ /* Append contents */ SyBlobAppend(&pObj->sBlob, (const void *)pVal->zString, pVal->nByte); } /* Set the desired type */ pObj->iFlags = MEMOBJ_STRING; return SXRET_OK; } /* * Append some contents to the internal buffer of a given jx9_value. * If the given jx9_value is not of type string, this function * invalidate any prior representation and set the string type. * Then a simple append operation is performed. */ JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen) { sxi32 rc; if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ /* Invalidate any prior representation */ jx9MemObjRelease(pObj); MemObjSetType(pObj, MEMOBJ_STRING); } /* Append contents */ rc = SyBlobAppend(&pObj->sBlob, zData, nLen); return rc; } #if 0 /* * Format and append some contents to the internal buffer of a given jx9_value. * If the given jx9_value is not of type string, this function invalidate * any prior representation and set the string type. * Then a simple format and append operation is performed. */ JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap) { sxi32 rc; if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ /* Invalidate any prior representation */ jx9MemObjRelease(pObj); MemObjSetType(pObj, MEMOBJ_STRING); } /* Format and append contents */ rc = SyBlobFormatAp(&pObj->sBlob, zFormat, ap); return rc; } #endif /* * Duplicate the contents of a jx9_value. */ JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest) { jx9_hashmap *pMap = 0; sxi32 rc; if( pSrc->iFlags & MEMOBJ_HASHMAP ){ /* Increment reference count */ ((jx9_hashmap *)pSrc->x.pOther)->iRef++; } if( pDest->iFlags & MEMOBJ_HASHMAP ){ pMap = (jx9_hashmap *)pDest->x.pOther; } SyMemcpy((const void *)&(*pSrc), &(*pDest), sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32))); rc = SXRET_OK; if( SyBlobLength(&pSrc->sBlob) > 0 ){ SyBlobReset(&pDest->sBlob); rc = SyBlobDup(&pSrc->sBlob, &pDest->sBlob); }else{ if( SyBlobLength(&pDest->sBlob) > 0 ){ SyBlobRelease(&pDest->sBlob); } } if( pMap ){ jx9HashmapUnref(pMap); } return rc; } /* * Duplicate the contents of a jx9_value but do not copy internal * buffer contents, simply point to it. */ JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest) { SyMemcpy((const void *)&(*pSrc), &(*pDest), sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32))); if( pSrc->iFlags & MEMOBJ_HASHMAP ){ /* Increment reference count */ ((jx9_hashmap *)pSrc->x.pOther)->iRef++; } if( SyBlobLength(&pDest->sBlob) > 0 ){ SyBlobRelease(&pDest->sBlob); } if( SyBlobLength(&pSrc->sBlob) > 0 ){ SyBlobReadOnly(&pDest->sBlob, SyBlobData(&pSrc->sBlob), SyBlobLength(&pSrc->sBlob)); } return SXRET_OK; } /* * Invalidate any prior representation of a given jx9_value. */ JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj) { if( (pObj->iFlags & MEMOBJ_NULL) == 0 ){ if( pObj->iFlags & MEMOBJ_HASHMAP ){ jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther); } /* Release the internal buffer */ SyBlobRelease(&pObj->sBlob); /* Invalidate any prior representation */ pObj->iFlags = MEMOBJ_NULL; } return SXRET_OK; } /* * Compare two jx9_values. * Return 0 if the values are equals, > 0 if pObj1 is greater than pObj2 * or < 0 if pObj2 is greater than pObj1. * Type comparison table taken from the JX9 language reference manual. * Comparisons of $x with JX9 functions Expression * gettype() empty() is_null() isset() boolean : if($x) * $x = ""; string TRUE FALSE TRUE FALSE * $x = null NULL TRUE TRUE FALSE FALSE * var $x; NULL TRUE TRUE FALSE FALSE * $x is undefined NULL TRUE TRUE FALSE FALSE * $x = array(); array TRUE FALSE TRUE FALSE * $x = false; boolean TRUE FALSE TRUE FALSE * $x = true; boolean FALSE FALSE TRUE TRUE * $x = 1; integer FALSE FALSE TRUE TRUE * $x = 42; integer FALSE FALSE TRUE TRUE * $x = 0; integer TRUE FALSE TRUE FALSE * $x = -1; integer FALSE FALSE TRUE TRUE * $x = "1"; string FALSE FALSE TRUE TRUE * $x = "0"; string TRUE FALSE TRUE FALSE * $x = "-1"; string FALSE FALSE TRUE TRUE * $x = "jx9"; string FALSE FALSE TRUE TRUE * $x = "true"; string FALSE FALSE TRUE TRUE * $x = "false"; string FALSE FALSE TRUE TRUE * Loose comparisons with == * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" "" * TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE * FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE TRUE FALSE TRUE * 1 TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE * 0 FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE TRUE * -1 TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE * "1" TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE * "0" FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE * "-1" TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE * NULL FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE * array() FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE * "jx9" TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE * "" FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE * Strict comparisons with === * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" "" * TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE * FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE * 1 FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE * 0 FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE * -1 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE * "1" FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE * "0" FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE * "-1" FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE * NULL FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE * array() FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE * "jx9" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE * "" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE */ JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest) { sxi32 iComb; sxi32 rc; if( bStrict ){ sxi32 iF1, iF2; /* Strict comparisons with === */ iF1 = pObj1->iFlags; iF2 = pObj2->iFlags; if( iF1 != iF2 ){ /* Not of the same type */ return 1; } } /* Combine flag together */ iComb = pObj1->iFlags|pObj2->iFlags; if( iComb & (MEMOBJ_RES|MEMOBJ_BOOL) ){ /* Convert to boolean: Keep in mind FALSE < TRUE */ if( (pObj1->iFlags & MEMOBJ_BOOL) == 0 ){ jx9MemObjToBool(pObj1); } if( (pObj2->iFlags & MEMOBJ_BOOL) == 0 ){ jx9MemObjToBool(pObj2); } return (sxi32)((pObj1->x.iVal != 0) - (pObj2->x.iVal != 0)); }else if( iComb & MEMOBJ_NULL ){ if( (pObj1->iFlags & MEMOBJ_NULL) == 0 ){ return 1; } if( (pObj2->iFlags & MEMOBJ_NULL) == 0 ){ return -1; } }else if ( iComb & MEMOBJ_HASHMAP ){ /* Hashmap aka 'array' comparison */ if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ /* Array is always greater */ return -1; } if( (pObj2->iFlags & MEMOBJ_HASHMAP) == 0 ){ /* Array is always greater */ return 1; } /* Perform the comparison */ rc = jx9HashmapCmp((jx9_hashmap *)pObj1->x.pOther, (jx9_hashmap *)pObj2->x.pOther, bStrict); return rc; }else if ( iComb & MEMOBJ_STRING ){ SyString s1, s2; /* Perform a strict string comparison.*/ if( (pObj1->iFlags&MEMOBJ_STRING) == 0 ){ jx9MemObjToString(pObj1); } if( (pObj2->iFlags&MEMOBJ_STRING) == 0 ){ jx9MemObjToString(pObj2); } SyStringInitFromBuf(&s1, SyBlobData(&pObj1->sBlob), SyBlobLength(&pObj1->sBlob)); SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob)); /* * Strings are compared using memcmp(). If one value is an exact prefix of the * other, then the shorter value is less than the longer value. */ rc = SyMemcmp((const void *)s1.zString, (const void *)s2.zString, SXMIN(s1.nByte, s2.nByte)); if( rc == 0 ){ if( s1.nByte != s2.nByte ){ rc = s1.nByte < s2.nByte ? -1 : 1; } } return rc; }else if( iComb & (MEMOBJ_INT|MEMOBJ_REAL) ){ /* Perform a numeric comparison if one of the operand is numeric(integer or real) */ if( (pObj1->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ jx9MemObjToNumeric(pObj1); } if( (pObj2->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ jx9MemObjToNumeric(pObj2); } if( (pObj1->iFlags & pObj2->iFlags & MEMOBJ_INT) == 0) { jx9_real r1, r2; /* Compare as reals */ if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ jx9MemObjToReal(pObj1); } r1 = pObj1->x.rVal; if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ jx9MemObjToReal(pObj2); } r2 = pObj2->x.rVal; if( r1 > r2 ){ return 1; }else if( r1 < r2 ){ return -1; } return 0; }else{ /* Integer comparison */ if( pObj1->x.iVal > pObj2->x.iVal ){ return 1; }else if( pObj1->x.iVal < pObj2->x.iVal ){ return -1; } return 0; } } /* NOT REACHED */ SXUNUSED(iNest); return 0; } /* * Perform an addition operation of two jx9_values. * The reason this function is implemented here rather than 'vm.c' * is that the '+' operator is overloaded. * That is, the '+' operator is used for arithmetic operation and also * used for operation on arrays [i.e: union]. When used with an array * The + operator returns the right-hand array appended to the left-hand array. * For keys that exist in both arrays, the elements from the left-hand array * will be used, and the matching elements from the right-hand array will * be ignored. * This function take care of handling all the scenarios. */ JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore) { if( ((pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP) == 0 ){ /* Arithemtic operation */ jx9MemObjToNumeric(pObj1); jx9MemObjToNumeric(pObj2); if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_REAL ){ /* Floating point arithmetic */ jx9_real a, b; if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ jx9MemObjToReal(pObj1); } if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ jx9MemObjToReal(pObj2); } a = pObj1->x.rVal; b = pObj2->x.rVal; pObj1->x.rVal = a+b; MemObjSetType(pObj1, MEMOBJ_REAL); /* Try to get an integer representation also */ MemObjTryIntger(&(*pObj1)); }else{ /* Integer arithmetic */ sxi64 a, b; a = pObj1->x.iVal; b = pObj2->x.iVal; pObj1->x.iVal = a+b; MemObjSetType(pObj1, MEMOBJ_INT); } }else{ if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP ){ jx9_hashmap *pMap; sxi32 rc; if( bAddStore ){ /* Do not duplicate the hashmap, use the left one since its an add&store operation. */ if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ /* Force a hashmap cast */ rc = jx9MemObjToHashmap(pObj1); if( rc != SXRET_OK ){ jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array"); return rc; } } /* Point to the structure that describe the hashmap */ pMap = (jx9_hashmap *)pObj1->x.pOther; }else{ /* Create a new hashmap */ pMap = jx9NewHashmap(pObj1->pVm, 0, 0); if( pMap == 0){ jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array"); return SXERR_MEM; } } if( !bAddStore ){ if(pObj1->iFlags & MEMOBJ_HASHMAP ){ /* Perform a hashmap duplication */ jx9HashmapDup((jx9_hashmap *)pObj1->x.pOther, pMap); }else{ if((pObj1->iFlags & MEMOBJ_NULL) == 0 ){ /* Simple insertion */ jx9HashmapInsert(pMap, 0, pObj1); } } } /* Perform the union */ if(pObj2->iFlags & MEMOBJ_HASHMAP ){ jx9HashmapUnion(pMap, (jx9_hashmap *)pObj2->x.pOther); }else{ if((pObj2->iFlags & MEMOBJ_NULL) == 0 ){ /* Simple insertion */ jx9HashmapInsert(pMap, 0, pObj2); } } /* Reflect the change */ if( pObj1->iFlags & MEMOBJ_STRING ){ SyBlobRelease(&pObj1->sBlob); } pObj1->x.pOther = pMap; MemObjSetType(pObj1, MEMOBJ_HASHMAP); } } return SXRET_OK; } /* * Return a printable representation of the type of a given * jx9_value. */ JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal) { const char *zType = ""; if( pVal->iFlags & MEMOBJ_NULL ){ zType = "null"; }else if( pVal->iFlags & MEMOBJ_INT ){ zType = "int"; }else if( pVal->iFlags & MEMOBJ_REAL ){ zType = "float"; }else if( pVal->iFlags & MEMOBJ_STRING ){ zType = "string"; }else if( pVal->iFlags & MEMOBJ_BOOL ){ zType = "bool"; }else if( pVal->iFlags & MEMOBJ_HASHMAP ){ jx9_hashmap *pMap = (jx9_hashmap *)pVal->x.pOther; if( pMap->iFlags & HASHMAP_JSON_OBJECT ){ zType = "JSON Object"; }else{ zType = "JSON Array"; } }else if( pVal->iFlags & MEMOBJ_RES ){ zType = "resource"; } return zType; } /* * Dump a jx9_value [i.e: get a printable representation of it's type and contents.]. * Store the dump in the given blob. */ JX9_PRIVATE sxi32 jx9MemObjDump( SyBlob *pOut, /* Store the dump here */ jx9_value *pObj /* Dump this */ ) { sxi32 rc = SXRET_OK; const char *zType; /* Get value type first */ zType = jx9MemObjTypeDump(pObj); SyBlobAppend(&(*pOut), zType, SyStrlen(zType)); if((pObj->iFlags & MEMOBJ_NULL) == 0 ){ SyBlobAppend(&(*pOut), "(", sizeof(char)); if( pObj->iFlags & MEMOBJ_HASHMAP ){ jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; SyBlobFormat(pOut,"%u ",pMap->nEntry); /* Dump hashmap entries */ rc = jx9JsonSerialize(pObj,pOut); }else{ SyBlob *pContents = &pObj->sBlob; /* Get a printable representation of the contents */ if((pObj->iFlags & MEMOBJ_STRING) == 0 ){ MemObjStringValue(&(*pOut), &(*pObj)); }else{ /* Append length first */ SyBlobFormat(&(*pOut), "%u '", SyBlobLength(&pObj->sBlob)); if( SyBlobLength(pContents) > 0 ){ SyBlobAppend(&(*pOut), SyBlobData(pContents), SyBlobLength(pContents)); } SyBlobAppend(&(*pOut), "'", sizeof(char)); } } SyBlobAppend(&(*pOut), ")", sizeof(char)); } #ifdef __WINNT__ SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n")-1); #else SyBlobAppend(&(*pOut), "\n", sizeof(char)); #endif return rc; } /* * ---------------------------------------------------------- * File: jx9_parse.c * MD5: d8fcac4c6cd7672f0103c0bf4a4b61fc * ---------------------------------------------------------- */ /* * 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: parse.c v1.2 FreeBSD 2012-12-11 00:46 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* Expression parser for the Jx9 programming language */ /* Operators associativity */ #define EXPR_OP_ASSOC_LEFT 0x01 /* Left associative operator */ #define EXPR_OP_ASSOC_RIGHT 0x02 /* Right associative operator */ #define EXPR_OP_NON_ASSOC 0x04 /* Non-associative operator */ /* * Operators table * This table is sorted by operators priority (highest to lowest) according * the JX9 language reference manual. * JX9 implements all the 60 JX9 operators and have introduced the eq and ne operators. * The operators precedence table have been improved dramatically so that you can do same * amazing things now such as array dereferencing, on the fly function call, anonymous function * as array values, object member access on instantiation and so on. * Refer to the following page for a full discussion on these improvements: * http://jx9.symisc.net/features.html */ static const jx9_expr_op aOpTable[] = { /* Postfix operators */ /* Precedence 2(Highest), left-associative */ { {".", sizeof(char)}, EXPR_OP_DOT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_MEMBER }, { {"[", sizeof(char)}, EXPR_OP_SUBSCRIPT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_LOAD_IDX}, /* Precedence 3, non-associative */ { {"++", sizeof(char)*2}, EXPR_OP_INCR, 3, EXPR_OP_NON_ASSOC , JX9_OP_INCR}, { {"--", sizeof(char)*2}, EXPR_OP_DECR, 3, EXPR_OP_NON_ASSOC , JX9_OP_DECR}, /* Unary operators */ /* Precedence 4, right-associative */ { {"-", sizeof(char)}, EXPR_OP_UMINUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UMINUS }, { {"+", sizeof(char)}, EXPR_OP_UPLUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UPLUS }, { {"~", sizeof(char)}, EXPR_OP_BITNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_BITNOT }, { {"!", sizeof(char)}, EXPR_OP_LOGNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_LNOT }, /* Cast operators */ { {"(int)", sizeof("(int)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_INT }, { {"(bool)", sizeof("(bool)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_BOOL }, { {"(string)", sizeof("(string)")-1}, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_STR }, { {"(float)", sizeof("(float)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_REAL }, { {"(array)", sizeof("(array)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */ { {"(object)", sizeof("(object)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */ /* Binary operators */ /* Precedence 7, left-associative */ { {"*", sizeof(char)}, EXPR_OP_MUL, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MUL}, { {"/", sizeof(char)}, EXPR_OP_DIV, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_DIV}, { {"%", sizeof(char)}, EXPR_OP_MOD, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MOD}, /* Precedence 8, left-associative */ { {"+", sizeof(char)}, EXPR_OP_ADD, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_ADD}, { {"-", sizeof(char)}, EXPR_OP_SUB, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_SUB}, { {"..", sizeof(char)*2},EXPR_OP_DDOT, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_CAT}, /* Precedence 9, left-associative */ { {"<<", sizeof(char)*2}, EXPR_OP_SHL, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHL}, { {">>", sizeof(char)*2}, EXPR_OP_SHR, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHR}, /* Precedence 10, non-associative */ { {"<", sizeof(char)}, EXPR_OP_LT, 10, EXPR_OP_NON_ASSOC, JX9_OP_LT}, { {">", sizeof(char)}, EXPR_OP_GT, 10, EXPR_OP_NON_ASSOC, JX9_OP_GT}, { {"<=", sizeof(char)*2}, EXPR_OP_LE, 10, EXPR_OP_NON_ASSOC, JX9_OP_LE}, { {">=", sizeof(char)*2}, EXPR_OP_GE, 10, EXPR_OP_NON_ASSOC, JX9_OP_GE}, { {"<>", sizeof(char)*2}, EXPR_OP_NE, 10, EXPR_OP_NON_ASSOC, JX9_OP_NEQ}, /* Precedence 11, non-associative */ { {"==", sizeof(char)*2}, EXPR_OP_EQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_EQ}, { {"!=", sizeof(char)*2}, EXPR_OP_NE, 11, EXPR_OP_NON_ASSOC, JX9_OP_NEQ}, { {"===", sizeof(char)*3}, EXPR_OP_TEQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_TEQ}, { {"!==", sizeof(char)*3}, EXPR_OP_TNE, 11, EXPR_OP_NON_ASSOC, JX9_OP_TNE}, /* Precedence 12, left-associative */ { {"&", sizeof(char)}, EXPR_OP_BAND, 12, EXPR_OP_ASSOC_LEFT, JX9_OP_BAND}, /* Binary operators */ /* Precedence 13, left-associative */ { {"^", sizeof(char)}, EXPR_OP_XOR, 13, EXPR_OP_ASSOC_LEFT, JX9_OP_BXOR}, /* Precedence 14, left-associative */ { {"|", sizeof(char)}, EXPR_OP_BOR, 14, EXPR_OP_ASSOC_LEFT, JX9_OP_BOR}, /* Precedence 15, left-associative */ { {"&&", sizeof(char)*2}, EXPR_OP_LAND, 15, EXPR_OP_ASSOC_LEFT, JX9_OP_LAND}, /* Precedence 16, left-associative */ { {"||", sizeof(char)*2}, EXPR_OP_LOR, 16, EXPR_OP_ASSOC_LEFT, JX9_OP_LOR}, /* Ternary operator */ /* Precedence 17, left-associative */ { {"?", sizeof(char)}, EXPR_OP_QUESTY, 17, EXPR_OP_ASSOC_LEFT, 0}, /* Combined binary operators */ /* Precedence 18, right-associative */ { {"=", sizeof(char)}, EXPR_OP_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_STORE}, { {"+=", sizeof(char)*2}, EXPR_OP_ADD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_ADD_STORE }, { {"-=", sizeof(char)*2}, EXPR_OP_SUB_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SUB_STORE }, { {".=", sizeof(char)*2}, EXPR_OP_DOT_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_CAT_STORE }, { {"*=", sizeof(char)*2}, EXPR_OP_MUL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MUL_STORE }, { {"/=", sizeof(char)*2}, EXPR_OP_DIV_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_DIV_STORE }, { {"%=", sizeof(char)*2}, EXPR_OP_MOD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MOD_STORE }, { {"&=", sizeof(char)*2}, EXPR_OP_AND_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BAND_STORE }, { {"|=", sizeof(char)*2}, EXPR_OP_OR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BOR_STORE }, { {"^=", sizeof(char)*2}, EXPR_OP_XOR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BXOR_STORE }, { {"<<=", sizeof(char)*3}, EXPR_OP_SHL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHL_STORE }, { {">>=", sizeof(char)*3}, EXPR_OP_SHR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHR_STORE }, /* Precedence 22, left-associative [Lowest operator] */ { {",", sizeof(char)}, EXPR_OP_COMMA, 22, EXPR_OP_ASSOC_LEFT, 0}, /* IMP-0139-COMMA: Symisc eXtension */ }; /* Function call operator need special handling */ static const jx9_expr_op sFCallOp = {{"(", sizeof(char)}, EXPR_OP_FUNC_CALL, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_CALL}; /* * Check if the given token is a potential operator or not. * This function is called by the lexer each time it extract a token that may * look like an operator. * Return a structure [i.e: jx9_expr_op instnace ] that describe the operator on success. * Otherwise NULL. * Note that the function take care of handling ambiguity [i.e: whether we are dealing with * a binary minus or unary minus.] */ JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast) { sxu32 n = 0; sxi32 rc; /* Do a linear lookup on the operators table */ for(;;){ if( n >= SX_ARRAYSIZE(aOpTable) ){ break; } rc = SyStringCmp(pStr, &aOpTable[n].sOp, SyMemcmp); if( rc == 0 ){ if( aOpTable[n].sOp.nByte != sizeof(char) || (aOpTable[n].iOp != EXPR_OP_UMINUS && aOpTable[n].iOp != EXPR_OP_UPLUS) || pLast == 0 ){ if( aOpTable[n].iOp == EXPR_OP_SUBSCRIPT && (pLast == 0 || (pLast->nType & (JX9_TK_ID|JX9_TK_CSB/*]*/|JX9_TK_RPAREN/*)*/)) == 0) ){ /* JSON Array not subscripting, return NULL */ return 0; } /* There is no ambiguity here, simply return the first operator seen */ return &aOpTable[n]; } /* Handle ambiguity */ if( pLast->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_COLON/*:*/|JX9_TK_COMMA/*, '*/) ){ /* Unary opertors have prcedence here over binary operators */ return &aOpTable[n]; } if( pLast->nType & JX9_TK_OP ){ const jx9_expr_op *pOp = (const jx9_expr_op *)pLast->pUserData; /* Ticket 1433-31: Handle the '++', '--' operators case */ if( pOp->iOp != EXPR_OP_INCR && pOp->iOp != EXPR_OP_DECR ){ /* Unary opertors have prcedence here over binary operators */ return &aOpTable[n]; } } } ++n; /* Next operator in the table */ } /* No such operator */ return 0; } /* * Delimit a set of token stream. * This function take care of handling the nesting level and stops when it hit * the end of the input or the ending token is found and the nesting level is zero. */ JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn,SyToken *pEnd,sxu32 nTokStart,sxu32 nTokEnd,SyToken **ppEnd) { SyToken *pCur = pIn; sxi32 iNest = 1; for(;;){ if( pCur >= pEnd ){ break; } if( pCur->nType & nTokStart ){ /* Increment nesting level */ iNest++; }else if( pCur->nType & nTokEnd ){ /* Decrement nesting level */ iNest--; if( iNest <= 0 ){ break; } } /* Advance cursor */ pCur++; } /* Point to the end of the chunk */ *ppEnd = pCur; } /* * Retrun TRUE if the given ID represent a language construct [i.e: print, print..]. FALSE otherwise. * Note on reserved keywords. * According to the JX9 language reference manual: * These words have special meaning in JX9. Some of them represent things which look like * functions, some look like constants, and so on--but they're not, really: they are language * constructs. You cannot use any of the following words as constants, object names, function * or method names. Using them as variable names is generally OK, but could lead to confusion. */ JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID) { if( nKeyID == JX9_TKWRD_PRINT || nKeyID == JX9_TKWRD_EXIT || nKeyID == JX9_TKWRD_DIE || nKeyID == JX9_TKWRD_INCLUDE|| nKeyID == JX9_TKWRD_IMPORT ){ return TRUE; } /* Not a language construct */ return FALSE; } /* * Point to the next expression that should be evaluated shortly. * The cursor stops when it hit a comma ', ' or a semi-colon and the nesting * level is zero. */ JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart,SyToken *pEnd,SyToken **ppNext) { SyToken *pCur = pStart; sxi32 iNest = 0; if( pCur >= pEnd || (pCur->nType & JX9_TK_SEMI/*';'*/) ){ /* Last expression */ return SXERR_EOF; } while( pCur < pEnd ){ if( (pCur->nType & (JX9_TK_COMMA/*','*/|JX9_TK_SEMI/*';'*/)) && iNest <= 0){ break; } if( pCur->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OSB/*'['*/|JX9_TK_OCB/*'{'*/) ){ iNest++; }else if( pCur->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*']'*/|JX9_TK_CCB/*'}*/) ){ iNest--; } pCur++; } *ppNext = pCur; return SXRET_OK; } /* * Collect and assemble tokens holding annonymous functions/closure body. * When errors, JX9 take care of generating the appropriate error message. * Note on annonymous functions. * According to the JX9 language reference manual: * Anonymous functions, also known as closures, allow the creation of functions * which have no specified name. They are most useful as the value of callback * parameters, but they have many other uses. * Closures may also inherit variables from the parent scope. Any such variables * must be declared in the function header. Inheriting variables from the parent * scope is not the same as using global variables. Global variables exist in the global scope * which is the same no matter what function is executing. The parent scope of a closure is the * function in which the closure was declared (not necessarily the function it was called from). * * Some example: * $greet = function($name) * { * printf("Hello %s\r\n", $name); * }; * $greet('World'); * $greet('JX9'); * * $double = function($a) { * return $a * 2; * }; * // This is our range of numbers * $numbers = range(1, 5); * // Use the Annonymous function as a callback here to * // double the size of each element in our * // range * $new_numbers = array_map($double, $numbers); * print implode(' ', $new_numbers); */ static sxi32 ExprAssembleAnnon(jx9_gen_state *pGen,SyToken **ppCur, SyToken *pEnd) { SyToken *pIn = *ppCur; sxu32 nLine; sxi32 rc; /* Jump the 'function' keyword */ nLine = pIn->nLine; pIn++; if( pIn < pEnd && (pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) ){ pIn++; } if( pIn >= pEnd || (pIn->nType & JX9_TK_LPAREN) == 0 ){ /* Syntax error */ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing opening parenthesis '(' while declaring annonymous function"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } goto Synchronize; } pIn++; /* Jump the leading parenthesis '(' */ jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_LPAREN/*'('*/, JX9_TK_RPAREN/*')'*/, &pIn); if( pIn >= pEnd || &pIn[1] >= pEnd ){ /* Syntax error */ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } goto Synchronize; } pIn++; /* Jump the trailing parenthesis */ if( pIn->nType & JX9_TK_OCB /*'{'*/ ){ pIn++; /* Jump the leading curly '{' */ jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_OCB/*'{'*/, JX9_TK_CCB/*'}'*/, &pIn); if( pIn < pEnd ){ pIn++; } }else{ /* Syntax error */ rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function, missing '{'"); if( rc == SXERR_ABORT ){ return SXERR_ABORT; } } rc = SXRET_OK; Synchronize: /* Synchronize pointers */ *ppCur = pIn; return rc; } /* * Make sure we are dealing with a valid expression tree. * This function check for balanced parenthesis, braces, brackets and so on. * When errors, JX9 take care of generating the appropriate error message. * Return SXRET_OK on success. Any other return value indicates syntax error. */ static sxi32 ExprVerifyNodes(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nNode) { sxi32 iParen, iSquare, iBraces; sxi32 i, rc; if( nNode > 0 && apNode[0]->pOp && (apNode[0]->pOp->iOp == EXPR_OP_ADD || apNode[0]->pOp->iOp == EXPR_OP_SUB) ){ /* Fix and mark as an unary not binary plus/minus operator */ apNode[0]->pOp = jx9ExprExtractOperator(&apNode[0]->pStart->sData, 0); apNode[0]->pStart->pUserData = (void *)apNode[0]->pOp; } iParen = iSquare = iBraces = 0; for( i = 0 ; i < nNode ; ++i ){ if( apNode[i]->pStart->nType & JX9_TK_LPAREN /*'('*/){ if( i > 0 && ( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral || (apNode[i - 1]->pStart->nType & (JX9_TK_ID|JX9_TK_KEYWORD|JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*]*/))) ){ /* Ticket 1433-033: Take care to ignore alpha-stream [i.e: or, xor] operators followed by an opening parenthesis */ if( (apNode[i - 1]->pStart->nType & JX9_TK_OP) == 0 ){ /* We are dealing with a postfix [i.e: function call] operator * not a simple left parenthesis. Mark the node. */ apNode[i]->pStart->nType |= JX9_TK_OP; apNode[i]->pStart->pUserData = (void *)&sFCallOp; /* Function call operator */ apNode[i]->pOp = &sFCallOp; } } iParen++; }else if( apNode[i]->pStart->nType & JX9_TK_RPAREN/*')*/){ if( iParen <= 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ')'"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } iParen--; }else if( apNode[i]->pStart->nType & JX9_TK_OSB /*'['*/ && apNode[i]->xCode == 0 ){ iSquare++; }else if (apNode[i]->pStart->nType & JX9_TK_CSB /*']'*/){ if( iSquare <= 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ']'"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } iSquare--; }else if( apNode[i]->pStart->nType & JX9_TK_OCB /*'{'*/ && apNode[i]->xCode == 0 ){ iBraces++; }else if (apNode[i]->pStart->nType & JX9_TK_CCB /*'}'*/){ if( iBraces <= 0 ){ rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token '}'"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } iBraces--; }else if( apNode[i]->pStart->nType & JX9_TK_OP ){ const jx9_expr_op *pOp = (const jx9_expr_op *)apNode[i]->pOp; if( i > 0 && (pOp->iOp == EXPR_OP_UMINUS || pOp->iOp == EXPR_OP_UPLUS)){ if( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral ){ sxi32 iExprOp = EXPR_OP_SUB; /* Binary minus */ sxu32 n = 0; if( pOp->iOp == EXPR_OP_UPLUS ){ iExprOp = EXPR_OP_ADD; /* Binary plus */ } /* * TICKET 1433-013: This is a fix around an obscure bug when the user uses * a variable name which is an alpha-stream operator [i.e: $and, $xor, $eq..]. */ while( n < SX_ARRAYSIZE(aOpTable) && aOpTable[n].iOp != iExprOp ){ ++n; } pOp = &aOpTable[n]; /* Mark as binary '+' or '-', not an unary */ apNode[i]->pOp = pOp; apNode[i]->pStart->pUserData = (void *)pOp; } } } } if( iParen != 0 || iSquare != 0 || iBraces != 0){ rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[0]->pStart->nLine, "Syntax error, mismatched '(', '[' or '{'"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } return SXRET_OK; } /* * Extract a single expression node from the input. * On success store the freshly extractd node in ppNode. * When errors, JX9 take care of generating the appropriate error message. * An expression node can be a variable [i.e: $var], an operator [i.e: ++] * an annonymous function [i.e: function(){ return "Hello"; }, a double/single * quoted string, a heredoc/nowdoc, a literal [i.e: JX9_EOL], a namespace path * [i.e: namespaces\path\to..], a array/list [i.e: array(4, 5, 6)] and so on. */ static sxi32 ExprExtractNode(jx9_gen_state *pGen, jx9_expr_node **ppNode) { jx9_expr_node *pNode; SyToken *pCur; sxi32 rc; /* Allocate a new node */ pNode = (jx9_expr_node *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_expr_node)); if( pNode == 0 ){ /* If the supplied memory subsystem is so sick that we are unable to allocate * a tiny chunk of memory, there is no much we can do here. */ return SXERR_MEM; } /* Zero the structure */ SyZero(pNode, sizeof(jx9_expr_node)); SySetInit(&pNode->aNodeArgs, &pGen->pVm->sAllocator, sizeof(jx9_expr_node **)); /* Point to the head of the token stream */ pCur = pNode->pStart = pGen->pIn; /* Start collecting tokens */ if( pCur->nType & JX9_TK_OP ){ /* Point to the instance that describe this operator */ pNode->pOp = (const jx9_expr_op *)pCur->pUserData; /* Advance the stream cursor */ pCur++; }else if( pCur->nType & JX9_TK_DOLLAR ){ /* Isolate variable */ pCur++; /* Jump the dollar sign */ if( pCur >= pGen->pEnd ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"Invalid variable name"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); return rc; } pCur++; /* Jump the variable name */ pNode->xCode = jx9CompileVariable; }else if( pCur->nType & JX9_TK_OCB /* '{' */ ){ /* JSON Object, assemble tokens */ pCur++; jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OCB /* '[' */, JX9_TK_CCB /* ']' */, &pCur); if( pCur < pGen->pEnd ){ pCur++; }else{ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Object: Missing closing braces '}'"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); return rc; } pNode->xCode = jx9CompileJsonObject; }else if( pCur->nType & JX9_TK_OSB /* '[' */ && !(pCur->nType & JX9_TK_OP) ){ /* JSON Array, assemble tokens */ pCur++; jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OSB /* '[' */, JX9_TK_CSB /* ']' */, &pCur); if( pCur < pGen->pEnd ){ pCur++; }else{ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Array: Missing closing square bracket ']'"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); return rc; } pNode->xCode = jx9CompileJsonArray; }else if( pCur->nType & JX9_TK_KEYWORD ){ int nKeyword = SX_PTR_TO_INT(pCur->pUserData); if( nKeyword == JX9_TKWRD_FUNCTION ){ /* Annonymous function */ if( &pCur[1] >= pGen->pEnd ){ /* Assume a literal */ pCur++; pNode->xCode = jx9CompileLiteral; }else{ /* Assemble annonymous functions body */ rc = ExprAssembleAnnon(&(*pGen), &pCur, pGen->pEnd); if( rc != SXRET_OK ){ SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); return rc; } pNode->xCode = jx9CompileAnnonFunc; } }else if( jx9IsLangConstruct(nKeyword) && &pCur[1] < pGen->pEnd ){ /* Language constructs [i.e: print,die...] require special handling */ jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_LPAREN|JX9_TK_OCB|JX9_TK_OSB, JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB, &pCur); pNode->xCode = jx9CompileLangConstruct; }else{ /* Assume a literal */ pCur++; pNode->xCode = jx9CompileLiteral; } }else if( pCur->nType & (JX9_TK_ID) ){ /* Constants, function name, namespace path, object name... */ pCur++; pNode->xCode = jx9CompileLiteral; }else{ if( (pCur->nType & (JX9_TK_LPAREN|JX9_TK_RPAREN|JX9_TK_COMMA|JX9_TK_CSB|JX9_TK_OCB|JX9_TK_CCB|JX9_TK_COLON)) == 0 ){ /* Point to the code generator routine */ pNode->xCode = jx9GetNodeHandler(pCur->nType); if( pNode->xCode == 0 ){ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Syntax error: Unexpected token '%z'", &pNode->pStart->sData); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); return rc; } } /* Advance the stream cursor */ pCur++; } /* Point to the end of the token stream */ pNode->pEnd = pCur; /* Save the node for later processing */ *ppNode = pNode; /* Synchronize cursors */ pGen->pIn = pCur; return SXRET_OK; } /* * Free an expression tree. */ static void ExprFreeTree(jx9_gen_state *pGen, jx9_expr_node *pNode) { if( pNode->pLeft ){ /* Release the left tree */ ExprFreeTree(&(*pGen), pNode->pLeft); } if( pNode->pRight ){ /* Release the right tree */ ExprFreeTree(&(*pGen), pNode->pRight); } if( pNode->pCond ){ /* Release the conditional tree used by the ternary operator */ ExprFreeTree(&(*pGen), pNode->pCond); } if( SySetUsed(&pNode->aNodeArgs) > 0 ){ jx9_expr_node **apArg; sxu32 n; /* Release node arguments */ apArg = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); for( n = 0 ; n < SySetUsed(&pNode->aNodeArgs) ; ++n ){ ExprFreeTree(&(*pGen), apArg[n]); } SySetRelease(&pNode->aNodeArgs); } /* Finally, release this node */ SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); } /* * Free an expression tree. * This function is a wrapper around ExprFreeTree() defined above. */ JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet) { jx9_expr_node **apNode; sxu32 n; apNode = (jx9_expr_node **)SySetBasePtr(pNodeSet); for( n = 0 ; n < SySetUsed(pNodeSet) ; ++n ){ if( apNode[n] ){ ExprFreeTree(&(*pGen), apNode[n]); } } return SXRET_OK; } /* * Check if the given node is a modifialbe l/r-value. * Return TRUE if modifiable.FALSE otherwise. */ static int ExprIsModifiableValue(jx9_expr_node *pNode) { sxi32 iExprOp; if( pNode->pOp == 0 ){ return pNode->xCode == jx9CompileVariable ? TRUE : FALSE; } iExprOp = pNode->pOp->iOp; if( iExprOp == EXPR_OP_DOT /*'.' */ ){ return TRUE; } if( iExprOp == EXPR_OP_SUBSCRIPT/*'[]'*/ ){ if( pNode->pLeft->pOp ) { if( pNode->pLeft->pOp->iOp != EXPR_OP_SUBSCRIPT /*'['*/ && pNode->pLeft->pOp->iOp != EXPR_OP_DOT /*'.'*/){ return FALSE; } }else if( pNode->pLeft->xCode != jx9CompileVariable ){ return FALSE; } return TRUE; } /* Not a modifiable l or r-value */ return FALSE; } /* Forward declaration */ static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken); /* Macro to check if the given node is a terminal */ #define NODE_ISTERM(NODE) (apNode[NODE] && (!apNode[NODE]->pOp || apNode[NODE]->pLeft )) /* * Buid an expression tree for each given function argument. * When errors, JX9 take care of generating the appropriate error message. */ static sxi32 ExprProcessFuncArguments(jx9_gen_state *pGen, jx9_expr_node *pOp, jx9_expr_node **apNode, sxi32 nToken) { sxi32 iNest, iCur, iNode; sxi32 rc; /* Process function arguments from left to right */ iCur = 0; for(;;){ if( iCur >= nToken ){ /* No more arguments to process */ break; } iNode = iCur; iNest = 0; while( iCur < nToken ){ if( apNode[iCur] ){ if( (apNode[iCur]->pStart->nType & JX9_TK_COMMA) && apNode[iCur]->pLeft == 0 && iNest <= 0 ){ break; }else if( apNode[iCur]->pStart->nType & (JX9_TK_LPAREN|JX9_TK_OSB|JX9_TK_OCB) ){ iNest++; }else if( apNode[iCur]->pStart->nType & (JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB) ){ iNest--; } } iCur++; } if( iCur > iNode ){ ExprMakeTree(&(*pGen), &apNode[iNode], iCur-iNode); if( apNode[iNode] ){ /* Put a pointer to the root of the tree in the arguments set */ SySetPut(&pOp->aNodeArgs, (const void *)&apNode[iNode]); }else{ /* Empty function argument */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Empty function argument"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } }else{ rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } /* Jump trailing comma */ if( iCur < nToken && apNode[iCur] && (apNode[iCur]->pStart->nType & JX9_TK_COMMA) ){ iCur++; if( iCur >= nToken ){ /* missing function argument */ rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } } } return SXRET_OK; } /* * Create an expression tree from an array of tokens. * If successful, the root of the tree is stored in apNode[0]. * When errors, JX9 take care of generating the appropriate error message. */ static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken) { sxi32 i, iLeft, iRight; jx9_expr_node *pNode; sxi32 iCur; sxi32 rc; if( nToken <= 0 || (nToken == 1 && apNode[0]->xCode) ){ /* TICKET 1433-17: self evaluating node */ return SXRET_OK; } /* Process expressions enclosed in parenthesis first */ for( iCur = 0 ; iCur < nToken ; ++iCur ){ sxi32 iNest; /* Note that, we use strict comparison here '!=' instead of the bitwise and '&' operator * since the LPAREN token can also be an operator [i.e: Function call]. */ if( apNode[iCur] == 0 || apNode[iCur]->pStart->nType != JX9_TK_LPAREN ){ continue; } iNest = 1; iLeft = iCur; /* Find the closing parenthesis */ iCur++; while( iCur < nToken ){ if( apNode[iCur] ){ if( apNode[iCur]->pStart->nType & JX9_TK_RPAREN /* ')' */){ /* Decrement nesting level */ iNest--; if( iNest <= 0 ){ break; } }else if( apNode[iCur]->pStart->nType & JX9_TK_LPAREN /* '(' */ ){ /* Increment nesting level */ iNest++; } } iCur++; } if( iCur - iLeft > 1 ){ /* Recurse and process this expression */ rc = ExprMakeTree(&(*pGen), &apNode[iLeft + 1], iCur - iLeft - 1); if( rc != SXRET_OK ){ return rc; } } /* Free the left and right nodes */ ExprFreeTree(&(*pGen), apNode[iLeft]); ExprFreeTree(&(*pGen), apNode[iCur]); apNode[iLeft] = 0; apNode[iCur] = 0; } /* Handle postfix [i.e: function call, member access] operators with precedence 2 */ iLeft = -1; for( iCur = 0 ; iCur < nToken ; ++iCur ){ if( apNode[iCur] == 0 ){ continue; } pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iPrec == 2 && pNode->pLeft == 0 ){ if( pNode->pOp->iOp == EXPR_OP_FUNC_CALL ){ /* Collect function arguments */ sxi32 iPtr = 0; sxi32 nFuncTok = 0; while( nFuncTok + iCur < nToken ){ if( apNode[nFuncTok+iCur] ){ if( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_LPAREN /*'('*/ ){ iPtr++; }else if ( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_RPAREN /*')'*/){ iPtr--; if( iPtr <= 0 ){ break; } } } nFuncTok++; } if( nFuncTok + iCur >= nToken ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Missing right parenthesis ')'"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } if( iLeft < 0 || !NODE_ISTERM(iLeft) /*|| ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2)*/ ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Invalid function name"); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } if( nFuncTok > 1 ){ /* Process function arguments */ rc = ExprProcessFuncArguments(&(*pGen), pNode, &apNode[iCur+1], nFuncTok-1); if( rc != SXRET_OK ){ return rc; } } /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; apNode[iLeft] = 0; for( iPtr = 1; iPtr <= nFuncTok ; iPtr++ ){ apNode[iCur+iPtr] = 0; } }else if (pNode->pOp->iOp == EXPR_OP_SUBSCRIPT ){ /* Subscripting */ sxi32 iArrTok = iCur + 1; sxi32 iNest = 1; if( iLeft >= 0 && (apNode[iLeft]->xCode == jx9CompileVariable || (apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* postfix */) ) ){ /* Collect index tokens */ while( iArrTok < nToken ){ if( apNode[iArrTok] ){ if( apNode[iArrTok]->pStart->nType & JX9_TK_OSB /*'['*/){ /* Increment nesting level */ iNest++; }else if( apNode[iArrTok]->pStart->nType & JX9_TK_CSB /*']'*/){ /* Decrement nesting level */ iNest--; if( iNest <= 0 ){ break; } } } ++iArrTok; } if( iArrTok > iCur + 1 ){ /* Recurse and process this expression */ rc = ExprMakeTree(&(*pGen), &apNode[iCur+1], iArrTok - iCur - 1); if( rc != SXRET_OK ){ return rc; } /* Link the node to it's index */ SySetPut(&pNode->aNodeArgs, (const void *)&apNode[iCur+1]); } /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; pNode->pRight = 0; apNode[iLeft] = 0; for( iNest = iCur + 1 ; iNest <= iArrTok ; ++iNest ){ apNode[iNest] = 0; } } }else{ /* Member access operators [i.e: '.' ] */ iRight = iCur + 1; while( iRight < nToken && apNode[iRight] == 0 ){ iRight++; } if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid member name", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; if( pNode->pLeft->pOp == 0 && pNode->pLeft->xCode != jx9CompileVariable ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Expecting a variable as left operand", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } pNode->pRight = apNode[iRight]; apNode[iLeft] = apNode[iRight] = 0; } } iLeft = iCur; } /* Handle post/pre icrement/decrement [i.e: ++/--] operators with precedence 3 */ iLeft = -1; for( iCur = 0 ; iCur < nToken ; ++iCur ){ if( apNode[iCur] == 0 ){ continue; } pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ if( iLeft >= 0 && ((apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* Postfix */) || apNode[iLeft]->xCode == jx9CompileVariable) ){ /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; apNode[iLeft] = 0; } } iLeft = iCur; } iLeft = -1; for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ if( apNode[iCur] == 0 ){ continue; } pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ if( iLeft < 0 || (apNode[iLeft]->pOp == 0 && apNode[iLeft]->xCode != jx9CompileVariable) || ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2 /* Postfix */) ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z' operator needs l-value", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; apNode[iLeft] = 0; /* Mark as pre-increment/decrement node */ pNode->iFlags |= EXPR_NODE_PRE_INCR; } iLeft = iCur; } /* Handle right associative unary and cast operators [i.e: !, (string), ~...] with precedence 4 */ iLeft = 0; for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ if( apNode[iCur] ){ pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iPrec == 4 && pNode->pLeft == 0){ if( iLeft > 0 ){ /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; apNode[iLeft] = 0; if( pNode->pLeft && pNode->pLeft->pOp && pNode->pLeft->pOp->iPrec > 4 ){ if( pNode->pLeft->pLeft == 0 || pNode->pLeft->pRight == 0 ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pLeft->pStart->nLine, "'%z': Missing operand", &pNode->pLeft->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } } }else{ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing operand", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } } /* Save terminal position */ iLeft = iCur; } } /* Process left and non-associative binary operators [i.e: *, /, &&, ||...]*/ for( i = 7 ; i < 17 ; i++ ){ iLeft = -1; for( iCur = 0 ; iCur < nToken ; ++iCur ){ if( apNode[iCur] == 0 ){ continue; } pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iPrec == i && pNode->pLeft == 0 ){ /* Get the right node */ iRight = iCur + 1; while( iRight < nToken && apNode[iRight] == 0 ){ iRight++; } if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; pNode->pRight = apNode[iRight]; apNode[iLeft] = apNode[iRight] = 0; } iLeft = iCur; } } /* Handle the ternary operator. (expr1) ? (expr2) : (expr3) * Note that we do not need a precedence loop here since * we are dealing with a single operator. */ iLeft = -1; for( iCur = 0 ; iCur < nToken ; ++iCur ){ if( apNode[iCur] == 0 ){ continue; } pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iOp == EXPR_OP_QUESTY && pNode->pLeft == 0 ){ sxi32 iNest = 1; if( iLeft < 0 || !NODE_ISTERM(iLeft) ){ /* Missing condition */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Syntax error", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } /* Get the right node */ iRight = iCur + 1; while( iRight < nToken ){ if( apNode[iRight] ){ if( apNode[iRight]->pOp && apNode[iRight]->pOp->iOp == EXPR_OP_QUESTY && apNode[iRight]->pCond == 0){ /* Increment nesting level */ ++iNest; }else if( apNode[iRight]->pStart->nType & JX9_TK_COLON /*:*/ ){ /* Decrement nesting level */ --iNest; if( iNest <= 0 ){ break; } } } iRight++; } if( iRight > iCur + 1 ){ /* Recurse and process the then expression */ rc = ExprMakeTree(&(*pGen), &apNode[iCur + 1], iRight - iCur - 1); if( rc != SXRET_OK ){ return rc; } /* Link the node to the tree */ pNode->pLeft = apNode[iCur + 1]; }else{ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'then' expression", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } apNode[iCur + 1] = 0; if( iRight + 1 < nToken ){ /* Recurse and process the else expression */ rc = ExprMakeTree(&(*pGen), &apNode[iRight + 1], nToken - iRight - 1); if( rc != SXRET_OK ){ return rc; } /* Link the node to the tree */ pNode->pRight = apNode[iRight + 1]; apNode[iRight + 1] = apNode[iRight] = 0; }else{ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'else' expression", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } /* Point to the condition */ pNode->pCond = apNode[iLeft]; apNode[iLeft] = 0; break; } iLeft = iCur; } /* Process right associative binary operators [i.e: '=', '+=', '/='] * Note: All right associative binary operators have precedence 18 * so there is no need for a precedence loop here. */ iRight = -1; for( iCur = nToken - 1 ; iCur >= 0 ; iCur--){ if( apNode[iCur] == 0 ){ continue; } pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iPrec == 18 && pNode->pLeft == 0 ){ /* Get the left node */ iLeft = iCur - 1; while( iLeft >= 0 && apNode[iLeft] == 0 ){ iLeft--; } if( iLeft < 0 || iRight < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } if( ExprIsModifiableValue(apNode[iLeft]) == FALSE ){ if( pNode->pOp->iVmOp != JX9_OP_STORE ){ /* Left operand must be a modifiable l-value */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Left operand must be a modifiable l-value", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } } /* Link the node to the tree (Reverse) */ pNode->pLeft = apNode[iRight]; pNode->pRight = apNode[iLeft]; apNode[iLeft] = apNode[iRight] = 0; } iRight = iCur; } /* Process the lowest precedence operator (22, comma) */ iLeft = -1; for( iCur = 0 ; iCur < nToken ; ++iCur ){ if( apNode[iCur] == 0 ){ continue; } pNode = apNode[iCur]; if( pNode->pOp && pNode->pOp->iPrec == 22 /* ',' */ && pNode->pLeft == 0 ){ /* Get the right node */ iRight = iCur + 1; while( iRight < nToken && apNode[iRight] == 0 ){ iRight++; } if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ /* Syntax error */ rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } /* Link the node to the tree */ pNode->pLeft = apNode[iLeft]; pNode->pRight = apNode[iRight]; apNode[iLeft] = apNode[iRight] = 0; } iLeft = iCur; } /* Point to the root of the expression tree */ for( iCur = 1 ; iCur < nToken ; ++iCur ){ if( apNode[iCur] ){ if( (apNode[iCur]->pOp || apNode[iCur]->xCode ) && apNode[0] != 0){ rc = jx9GenCompileError(pGen, E_ERROR, apNode[iCur]->pStart->nLine, "Unexpected token '%z'", &apNode[iCur]->pStart->sData); if( rc != SXERR_ABORT ){ rc = SXERR_SYNTAX; } return rc; } apNode[0] = apNode[iCur]; apNode[iCur] = 0; } } return SXRET_OK; } /* * Build an expression tree from the freshly extracted raw tokens. * If successful, the root of the tree is stored in ppRoot. * When errors, JX9 take care of generating the appropriate error message. * This is the public interface used by the most code generator routines. */ JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot) { jx9_expr_node **apNode; jx9_expr_node *pNode; sxi32 rc; /* Reset node container */ SySetReset(pExprNode); pNode = 0; /* Prevent compiler warning */ /* Extract nodes one after one until we hit the end of the input */ while( pGen->pIn < pGen->pEnd ){ rc = ExprExtractNode(&(*pGen), &pNode); if( rc != SXRET_OK ){ return rc; } /* Save the extracted node */ SySetPut(pExprNode, (const void *)&pNode); } if( SySetUsed(pExprNode) < 1 ){ /* Empty expression [i.e: A semi-colon;] */ *ppRoot = 0; return SXRET_OK; } apNode = (jx9_expr_node **)SySetBasePtr(pExprNode); /* Make sure we are dealing with valid nodes */ rc = ExprVerifyNodes(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode)); if( rc != SXRET_OK ){ /* Don't worry about freeing memory, upper layer will * cleanup the mess left behind. */ *ppRoot = 0; return rc; } /* Build the tree */ rc = ExprMakeTree(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode)); if( rc != SXRET_OK ){ /* Something goes wrong [i.e: Syntax error] */ *ppRoot = 0; return rc; } /* Point to the root of the tree */ *ppRoot = apNode[0]; return SXRET_OK; } /* * ---------------------------------------------------------- * File: jx9_vfs.c * MD5: 8b73046a366acaf6aa7227c2133e16c0 * ---------------------------------------------------------- */ /* * 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: vfs.c v2.1 Ubuntu 2012-12-13 00:013 stable $ */ #ifndef JX9_AMALGAMATION #include "jx9Int.h" #endif /* * This file implement a virtual file systems (VFS) for the JX9 engine. */ /* * Given a string containing the path of a file or directory, this function * return the parent directory's path. */ JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen) { const char *zEnd = &zPath[nByte - 1]; int c, d; c = d = '/'; #ifdef __WINNT__ d = '\\'; #endif while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ zEnd--; } *pLen = (int)(zEnd-zPath); #ifdef __WINNT__ if( (*pLen) == (int)sizeof(char) && zPath[0] == '/' ){ /* Normalize path on windows */ return "\\"; } #endif if( zEnd == zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d) ){ /* No separator, return "." as the current directory */ *pLen = sizeof(char); return "."; } if( (*pLen) == 0 ){ *pLen = sizeof(char); #ifdef __WINNT__ return "\\"; #else return "/"; #endif } return zPath; } /* * Omit the vfs layer implementation from the built if the JX9_DISABLE_BUILTIN_FUNC directive is defined. */ #ifndef JX9_DISABLE_BUILTIN_FUNC /* * bool chdir(string $directory) * Change the current directory. * Parameters * $directory * The new current directory * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_chdir(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xChdir == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xChdir(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool chroot(string $directory) * Change the root directory. * Parameters * $directory * The path to change the root directory to * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_chroot(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xChroot == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xChroot(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * string getcwd(void) * Gets the current working directory. * Parameters * None * Return * Returns the current working directory on success, or FALSE on failure. */ static int jx9Vfs_getcwd(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_vfs *pVfs; int rc; /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xGetcwd == 0 ){ SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } jx9_result_string(pCtx, "", 0); /* Perform the requested operation */ rc = pVfs->xGetcwd(pCtx); if( rc != JX9_OK ){ /* Error, return FALSE */ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * bool rmdir(string $directory) * Removes directory. * Parameters * $directory * The path to the directory * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_rmdir(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xRmdir == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xRmdir(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool is_dir(string $filename) * Tells whether the given filename is a directory. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_is_dir(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xIsdir == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xIsdir(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool mkdir(string $pathname[, int $mode = 0777]) * Make a directory. * Parameters * $pathname * The directory path. * $mode * The mode is 0777 by default, which means the widest possible access. * Note: * mode is ignored on Windows. * Note that you probably want to specify the mode as an octal number, which means * it should have a leading zero. The mode is also modified by the current umask * which you can change using umask(). * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_mkdir(jx9_context *pCtx, int nArg, jx9_value **apArg) { int iRecursive = 0; const char *zPath; jx9_vfs *pVfs; int iMode, rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xMkdir == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); #ifdef __WINNT__ iMode = 0; #else /* Assume UNIX */ iMode = 0777; #endif if( nArg > 1 ){ iMode = jx9_value_to_int(apArg[1]); if( nArg > 2 ){ iRecursive = jx9_value_to_bool(apArg[2]); } } /* Perform the requested operation */ rc = pVfs->xMkdir(zPath, iMode, iRecursive); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool rename(string $oldname, string $newname) * Attempts to rename oldname to newname. * Parameters * $oldname * Old name. * $newname * New name. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_rename(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zOld, *zNew; jx9_vfs *pVfs; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xRename == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ zOld = jx9_value_to_string(apArg[0], 0); zNew = jx9_value_to_string(apArg[1], 0); rc = pVfs->xRename(zOld, zNew); /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK ); return JX9_OK; } /* * string realpath(string $path) * Returns canonicalized absolute pathname. * Parameters * $path * Target path. * Return * Canonicalized absolute pathname on success. or FALSE on failure. */ static int jx9Vfs_realpath(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xRealpath == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Set an empty string untnil the underlying OS interface change that */ jx9_result_string(pCtx, "", 0); /* Perform the requested operation */ zPath = jx9_value_to_string(apArg[0], 0); rc = pVfs->xRealpath(zPath, pCtx); if( rc != JX9_OK ){ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * int sleep(int $seconds) * Delays the program execution for the given number of seconds. * Parameters * $seconds * Halt time in seconds. * Return * Zero on success or FALSE on failure. */ static int jx9Vfs_sleep(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_vfs *pVfs; int rc, nSleep; if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xSleep == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Amount to sleep */ nSleep = jx9_value_to_int(apArg[0]); if( nSleep < 0 ){ /* Invalid value, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation (Microseconds) */ rc = pVfs->xSleep((unsigned int)(nSleep * SX_USEC_PER_SEC)); if( rc != JX9_OK ){ /* Return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Return zero */ jx9_result_int(pCtx, 0); } return JX9_OK; } /* * void usleep(int $micro_seconds) * Delays program execution for the given number of micro seconds. * Parameters * $micro_seconds * Halt time in micro seconds. A micro second is one millionth of a second. * Return * None. */ static int jx9Vfs_usleep(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_vfs *pVfs; int nSleep; if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){ /* Missing/Invalid argument, return immediately */ return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xSleep == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS", jx9_function_name(pCtx) ); return JX9_OK; } /* Amount to sleep */ nSleep = jx9_value_to_int(apArg[0]); if( nSleep < 0 ){ /* Invalid value, return immediately */ return JX9_OK; } /* Perform the requested operation (Microseconds) */ pVfs->xSleep((unsigned int)nSleep); return JX9_OK; } /* * bool unlink (string $filename) * Delete a file. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_unlink(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xUnlink == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xUnlink(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool chmod(string $filename, int $mode) * Attempts to change the mode of the specified file to that given in mode. * Parameters * $filename * Path to the file. * $mode * Mode (Must be an integer) * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_chmod(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int iMode; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xChmod == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Extract the mode */ iMode = jx9_value_to_int(apArg[1]); /* Perform the requested operation */ rc = pVfs->xChmod(zPath, iMode); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool chown(string $filename, string $user) * Attempts to change the owner of the file filename to user user. * Parameters * $filename * Path to the file. * $user * Username. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_chown(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath, *zUser; jx9_vfs *pVfs; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xChown == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Extract the user */ zUser = jx9_value_to_string(apArg[1], 0); /* Perform the requested operation */ rc = pVfs->xChown(zPath, zUser); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool chgrp(string $filename, string $group) * Attempts to change the group of the file filename to group. * Parameters * $filename * Path to the file. * $group * groupname. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_chgrp(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath, *zGroup; jx9_vfs *pVfs; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xChgrp == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Extract the user */ zGroup = jx9_value_to_string(apArg[1], 0); /* Perform the requested operation */ rc = pVfs->xChgrp(zPath, zGroup); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * int64 disk_free_space(string $directory) * Returns available space on filesystem or disk partition. * Parameters * $directory * A directory of the filesystem or disk partition. * Return * Returns the number of available bytes as a 64-bit integer or FALSE on failure. */ static int jx9Vfs_disk_free_space(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_int64 iSize; jx9_vfs *pVfs; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xFreeSpace == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ iSize = pVfs->xFreeSpace(zPath); /* IO return value */ jx9_result_int64(pCtx, iSize); return JX9_OK; } /* * int64 disk_total_space(string $directory) * Returns the total size of a filesystem or disk partition. * Parameters * $directory * A directory of the filesystem or disk partition. * Return * Returns the number of available bytes as a 64-bit integer or FALSE on failure. */ static int jx9Vfs_disk_total_space(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_int64 iSize; jx9_vfs *pVfs; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xTotalSpace == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ iSize = pVfs->xTotalSpace(zPath); /* IO return value */ jx9_result_int64(pCtx, iSize); return JX9_OK; } /* * bool file_exists(string $filename) * Checks whether a file or directory exists. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_file_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xFileExists == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xFileExists(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * int64 file_size(string $filename) * Gets the size for the given file. * Parameters * $filename * Path to the file. * Return * File size on success or FALSE on failure. */ static int jx9Vfs_file_size(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_int64 iSize; jx9_vfs *pVfs; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xFileSize == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ iSize = pVfs->xFileSize(zPath); /* IO return value */ jx9_result_int64(pCtx, iSize); return JX9_OK; } /* * int64 fileatime(string $filename) * Gets the last access time of the given file. * Parameters * $filename * Path to the file. * Return * File atime on success or FALSE on failure. */ static int jx9Vfs_file_atime(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_int64 iTime; jx9_vfs *pVfs; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xFileAtime == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ iTime = pVfs->xFileAtime(zPath); /* IO return value */ jx9_result_int64(pCtx, iTime); return JX9_OK; } /* * int64 filemtime(string $filename) * Gets file modification time. * Parameters * $filename * Path to the file. * Return * File mtime on success or FALSE on failure. */ static int jx9Vfs_file_mtime(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_int64 iTime; jx9_vfs *pVfs; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xFileMtime == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ iTime = pVfs->xFileMtime(zPath); /* IO return value */ jx9_result_int64(pCtx, iTime); return JX9_OK; } /* * int64 filectime(string $filename) * Gets inode change time of file. * Parameters * $filename * Path to the file. * Return * File ctime on success or FALSE on failure. */ static int jx9Vfs_file_ctime(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_int64 iTime; jx9_vfs *pVfs; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xFileCtime == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ iTime = pVfs->xFileCtime(zPath); /* IO return value */ jx9_result_int64(pCtx, iTime); return JX9_OK; } /* * bool is_file(string $filename) * Tells whether the filename is a regular file. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_is_file(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xIsfile == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xIsfile(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool is_link(string $filename) * Tells whether the filename is a symbolic link. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_is_link(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xIslink == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xIslink(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool is_readable(string $filename) * Tells whether a file exists and is readable. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_is_readable(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xReadable == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xReadable(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool is_writable(string $filename) * Tells whether the filename is writable. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_is_writable(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xWritable == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xWritable(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool is_executable(string $filename) * Tells whether the filename is executable. * Parameters * $filename * Path to the file. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_is_executable(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xExecutable == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xExecutable(zPath); /* IO return value */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * string filetype(string $filename) * Gets file type. * Parameters * $filename * Path to the file. * Return * The type of the file. Possible values are fifo, char, dir, block, link * file, socket and unknown. */ static int jx9Vfs_filetype(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; jx9_vfs *pVfs; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return 'unknown' */ jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xFiletype == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the desired directory */ zPath = jx9_value_to_string(apArg[0], 0); /* Set the empty string as the default return value */ jx9_result_string(pCtx, "", 0); /* Perform the requested operation */ pVfs->xFiletype(zPath, pCtx); return JX9_OK; } /* * array stat(string $filename) * Gives information about a file. * Parameters * $filename * Path to the file. * Return * An associative array on success holding the following entries on success * 0 dev device number * 1 ino inode number (zero on windows) * 2 mode inode protection mode * 3 nlink number of links * 4 uid userid of owner (zero on windows) * 5 gid groupid of owner (zero on windows) * 6 rdev device type, if inode device * 7 size size in bytes * 8 atime time of last access (Unix timestamp) * 9 mtime time of last modification (Unix timestamp) * 10 ctime time of last inode change (Unix timestamp) * 11 blksize blocksize of filesystem IO (zero on windows) * 12 blocks number of 512-byte blocks allocated. * Note: * FALSE is returned on failure. */ static int jx9Vfs_stat(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pArray, *pValue; const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xStat == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Create the array and the working value */ pArray = jx9_context_new_array(pCtx); pValue = jx9_context_new_scalar(pCtx); if( pArray == 0 || pValue == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xStat(zPath, pArray, pValue); if( rc != JX9_OK ){ /* IO error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Return the associative array */ jx9_result_value(pCtx, pArray); } /* Don't worry about freeing memory here, everything will be released * automatically as soon we return from this function. */ return JX9_OK; } /* * array lstat(string $filename) * Gives information about a file or symbolic link. * Parameters * $filename * Path to the file. * Return * An associative array on success holding the following entries on success * 0 dev device number * 1 ino inode number (zero on windows) * 2 mode inode protection mode * 3 nlink number of links * 4 uid userid of owner (zero on windows) * 5 gid groupid of owner (zero on windows) * 6 rdev device type, if inode device * 7 size size in bytes * 8 atime time of last access (Unix timestamp) * 9 mtime time of last modification (Unix timestamp) * 10 ctime time of last inode change (Unix timestamp) * 11 blksize blocksize of filesystem IO (zero on windows) * 12 blocks number of 512-byte blocks allocated. * Note: * FALSE is returned on failure. */ static int jx9Vfs_lstat(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pArray, *pValue; const char *zPath; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xlStat == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Create the array and the working value */ pArray = jx9_context_new_array(pCtx); pValue = jx9_context_new_scalar(pCtx); if( pArray == 0 || pValue == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zPath = jx9_value_to_string(apArg[0], 0); /* Perform the requested operation */ rc = pVfs->xlStat(zPath, pArray, pValue); if( rc != JX9_OK ){ /* IO error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Return the associative array */ jx9_result_value(pCtx, pArray); } /* Don't worry about freeing memory here, everything will be released * automatically as soon we return from this function. */ return JX9_OK; } /* * string getenv(string $varname) * Gets the value of an environment variable. * Parameters * $varname * The variable name. * Return * Returns the value of the environment variable varname, or FALSE if the environment * variable varname does not exist. */ static int jx9Vfs_getenv(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zEnv; jx9_vfs *pVfs; int iLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xGetenv == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the environment variable */ zEnv = jx9_value_to_string(apArg[0], &iLen); /* Set a boolean FALSE as the default return value */ jx9_result_bool(pCtx, 0); if( iLen < 1 ){ /* Empty string */ return JX9_OK; } /* Perform the requested operation */ pVfs->xGetenv(zEnv, pCtx); return JX9_OK; } /* * bool putenv(string $settings) * Set the value of an environment variable. * Parameters * $setting * The setting, like "FOO=BAR" * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_putenv(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zName, *zValue; char *zSettings, *zEnd; jx9_vfs *pVfs; int iLen, rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the setting variable */ zSettings = (char *)jx9_value_to_string(apArg[0], &iLen); if( iLen < 1 ){ /* Empty string, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Parse the setting */ zEnd = &zSettings[iLen]; zValue = 0; zName = zSettings; while( zSettings < zEnd ){ if( zSettings[0] == '=' ){ /* Null terminate the name */ zSettings[0] = 0; zValue = &zSettings[1]; break; } zSettings++; } /* Install the environment variable in the $_Env array */ if( zValue == 0 || zName[0] == 0 || zValue >= zEnd || zName >= zValue ){ /* Invalid settings, retun FALSE */ jx9_result_bool(pCtx, 0); if( zSettings < zEnd ){ zSettings[0] = '='; } return JX9_OK; } jx9_vm_config(pCtx->pVm, JX9_VM_CONFIG_ENV_ATTR, zName, zValue, (int)(zEnd-zValue)); /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xSetenv == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); zSettings[0] = '='; return JX9_OK; } /* Perform the requested operation */ rc = pVfs->xSetenv(zName, zValue); jx9_result_bool(pCtx, rc == JX9_OK ); zSettings[0] = '='; return JX9_OK; } /* * bool touch(string $filename[, int64 $time = time()[, int64 $atime]]) * Sets access and modification time of file. * Note: On windows * If the file does not exists, it will not be created. * Parameters * $filename * The name of the file being touched. * $time * The touch time. If time is not supplied, the current system time is used. * $atime * If present, the access time of the given filename is set to the value of atime. * Otherwise, it is set to the value passed to the time parameter. If neither are * present, the current system time is used. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_touch(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_int64 nTime, nAccess; const char *zFile; jx9_vfs *pVfs; int rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xTouch == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ nTime = nAccess = -1; zFile = jx9_value_to_string(apArg[0], 0); if( nArg > 1 ){ nTime = jx9_value_to_int64(apArg[1]); if( nArg > 2 ){ nAccess = jx9_value_to_int64(apArg[1]); }else{ nAccess = nTime; } } rc = pVfs->xTouch(zFile, nTime, nAccess); /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * Path processing functions that do not need access to the VFS layer * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* * string dirname(string $path) * Returns parent directory's path. * Parameters * $path * Target path. * On Windows, both slash (/) and backslash (\) are used as directory separator character. * In other environments, it is the forward slash (/). * Return * The path of the parent directory. If there are no slashes in path, a dot ('.') * is returned, indicating the current directory. */ static int jx9Builtin_dirname(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath, *zDir; int iLen, iDirlen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Point to the target path */ zPath = jx9_value_to_string(apArg[0], &iLen); if( iLen < 1 ){ /* Reuturn "." */ jx9_result_string(pCtx, ".", sizeof(char)); return JX9_OK; } /* Perform the requested operation */ zDir = jx9ExtractDirName(zPath, iLen, &iDirlen); /* Return directory name */ jx9_result_string(pCtx, zDir, iDirlen); return JX9_OK; } /* * string basename(string $path[, string $suffix ]) * Returns trailing name component of path. * Parameters * $path * Target path. * On Windows, both slash (/) and backslash (\) are used as directory separator character. * In other environments, it is the forward slash (/). * $suffix * If the name component ends in suffix this will also be cut off. * Return * The base name of the given path. */ static int jx9Builtin_basename(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath, *zBase, *zEnd; int c, d, iLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } c = d = '/'; #ifdef __WINNT__ d = '\\'; #endif /* Point to the target path */ zPath = jx9_value_to_string(apArg[0], &iLen); if( iLen < 1 ){ /* Empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Perform the requested operation */ zEnd = &zPath[iLen - 1]; /* Ignore trailing '/' */ while( zEnd > zPath && ( (int)zEnd[0] == c || (int)zEnd[0] == d ) ){ zEnd--; } iLen = (int)(&zEnd[1]-zPath); while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ zEnd--; } zBase = (zEnd > zPath) ? &zEnd[1] : zPath; zEnd = &zPath[iLen]; if( nArg > 1 && jx9_value_is_string(apArg[1]) ){ const char *zSuffix; int nSuffix; /* Strip suffix */ zSuffix = jx9_value_to_string(apArg[1], &nSuffix); if( nSuffix > 0 && nSuffix < iLen && SyMemcmp(&zEnd[-nSuffix], zSuffix, nSuffix) == 0 ){ zEnd -= nSuffix; } } /* Store the basename */ jx9_result_string(pCtx, zBase, (int)(zEnd-zBase)); return JX9_OK; } /* * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) * Returns information about a file path. * Parameter * $path * The path to be parsed. * $options * If present, specifies a specific element to be returned; one of * PATHINFO_DIRNAME, PATHINFO_BASENAME, PATHINFO_EXTENSION or PATHINFO_FILENAME. * Return * If the options parameter is not passed, an associative array containing the following * elements is returned: dirname, basename, extension (if any), and filename. * If options is present, returns a string containing the requested element. */ typedef struct path_info path_info; struct path_info { SyString sDir; /* Directory [i.e: /var/www] */ SyString sBasename; /* Basename [i.e httpd.conf] */ SyString sExtension; /* File extension [i.e xml, pdf..] */ SyString sFilename; /* Filename */ }; /* * Extract path fields. */ static sxi32 ExtractPathInfo(const char *zPath, int nByte, path_info *pOut) { const char *zPtr, *zEnd = &zPath[nByte - 1]; SyString *pCur; int c, d; c = d = '/'; #ifdef __WINNT__ d = '\\'; #endif /* Zero the structure */ SyZero(pOut, sizeof(path_info)); /* Handle special case */ if( nByte == sizeof(char) && ( (int)zPath[0] == c || (int)zPath[0] == d ) ){ #ifdef __WINNT__ SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char)); #else SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char)); #endif return SXRET_OK; } /* Extract the basename */ while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ zEnd--; } zPtr = (zEnd > zPath) ? &zEnd[1] : zPath; zEnd = &zPath[nByte]; /* dirname */ pCur = &pOut->sDir; SyStringInitFromBuf(pCur, zPath, zPtr-zPath); if( pCur->nByte > 1 ){ SyStringTrimTrailingChar(pCur, '/'); #ifdef __WINNT__ SyStringTrimTrailingChar(pCur, '\\'); #endif }else if( (int)zPath[0] == c || (int)zPath[0] == d ){ #ifdef __WINNT__ SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char)); #else SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char)); #endif } /* basename/filename */ pCur = &pOut->sBasename; SyStringInitFromBuf(pCur, zPtr, zEnd-zPtr); SyStringTrimLeadingChar(pCur, '/'); #ifdef __WINNT__ SyStringTrimLeadingChar(pCur, '\\'); #endif SyStringDupPtr(&pOut->sFilename, pCur); if( pCur->nByte > 0 ){ /* extension */ zEnd--; while( zEnd > pCur->zString /*basename*/ && zEnd[0] != '.' ){ zEnd--; } if( zEnd > pCur->zString ){ zEnd++; /* Jump leading dot */ SyStringInitFromBuf(&pOut->sExtension, zEnd, &zPath[nByte]-zEnd); /* Fix filename */ pCur = &pOut->sFilename; if( pCur->nByte > SyStringLength(&pOut->sExtension) ){ pCur->nByte -= 1 + SyStringLength(&pOut->sExtension); } } } return SXRET_OK; } /* * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) * See block comment above. */ static int jx9Builtin_pathinfo(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zPath; path_info sInfo; SyString *pComp; int iLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid argument, return the empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Point to the target path */ zPath = jx9_value_to_string(apArg[0], &iLen); if( iLen < 1 ){ /* Empty string */ jx9_result_string(pCtx, "", 0); return JX9_OK; } /* Extract path info */ ExtractPathInfo(zPath, iLen, &sInfo); if( nArg > 1 && jx9_value_is_int(apArg[1]) ){ /* Return path component */ int nComp = jx9_value_to_int(apArg[1]); switch(nComp){ case 1: /* PATHINFO_DIRNAME */ pComp = &sInfo.sDir; if( pComp->nByte > 0 ){ jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); }else{ /* Expand the empty string */ jx9_result_string(pCtx, "", 0); } break; case 2: /*PATHINFO_BASENAME*/ pComp = &sInfo.sBasename; if( pComp->nByte > 0 ){ jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); }else{ /* Expand the empty string */ jx9_result_string(pCtx, "", 0); } break; case 3: /*PATHINFO_EXTENSION*/ pComp = &sInfo.sExtension; if( pComp->nByte > 0 ){ jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); }else{ /* Expand the empty string */ jx9_result_string(pCtx, "", 0); } break; case 4: /*PATHINFO_FILENAME*/ pComp = &sInfo.sFilename; if( pComp->nByte > 0 ){ jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); }else{ /* Expand the empty string */ jx9_result_string(pCtx, "", 0); } break; default: /* Expand the empty string */ jx9_result_string(pCtx, "", 0); break; } }else{ /* Return an associative array */ jx9_value *pArray, *pValue; pArray = jx9_context_new_array(pCtx); pValue = jx9_context_new_scalar(pCtx); if( pArray == 0 || pValue == 0 ){ /* Out of mem, return NULL */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* dirname */ pComp = &sInfo.sDir; if( pComp->nByte > 0 ){ jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); /* Perform the insertion */ jx9_array_add_strkey_elem(pArray, "dirname", pValue); /* Will make it's own copy */ } /* Reset the string cursor */ jx9_value_reset_string_cursor(pValue); /* basername */ pComp = &sInfo.sBasename; if( pComp->nByte > 0 ){ jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); /* Perform the insertion */ jx9_array_add_strkey_elem(pArray, "basename", pValue); /* Will make it's own copy */ } /* Reset the string cursor */ jx9_value_reset_string_cursor(pValue); /* extension */ pComp = &sInfo.sExtension; if( pComp->nByte > 0 ){ jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); /* Perform the insertion */ jx9_array_add_strkey_elem(pArray, "extension", pValue); /* Will make it's own copy */ } /* Reset the string cursor */ jx9_value_reset_string_cursor(pValue); /* filename */ pComp = &sInfo.sFilename; if( pComp->nByte > 0 ){ jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); /* Perform the insertion */ jx9_array_add_strkey_elem(pArray, "filename", pValue); /* Will make it's own copy */ } /* Return the created 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; } /* * Globbing implementation extracted from the sqlite3 source tree. * Original author: D. Richard Hipp (http://www.sqlite.org) * Status: Public Domain */ typedef unsigned char u8; /* An array to map all upper-case characters into their corresponding ** lower-case character. ** ** SQLite only considers US-ASCII (or EBCDIC) characters. We do not ** handle case conversions for the UTF character set since the tables ** involved are nearly as big or bigger than SQLite itself. */ static const unsigned char sqlite3UpperToLower[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; #define GlogUpperToLower(A) if( A<0x80 ){ A = sqlite3UpperToLower[A]; } /* ** Assuming zIn points to the first byte of a UTF-8 character, ** advance zIn to point to the first byte of the next UTF-8 character. */ #define SQLITE_SKIP_UTF8(zIn) { \ if( (*(zIn++))>=0xc0 ){ \ while( (*zIn & 0xc0)==0x80 ){ zIn++; } \ } \ } /* ** Compare two UTF-8 strings for equality where the first string can ** potentially be a "glob" expression. Return true (1) if they ** are the same and false (0) if they are different. ** ** Globbing rules: ** ** '*' Matches any sequence of zero or more characters. ** ** '?' Matches exactly one character. ** ** [...] Matches one character from the enclosed list of ** characters. ** ** [^...] Matches one character not in the enclosed list. ** ** With the [...] and [^...] matching, a ']' character can be included ** in the list by making it the first character after '[' or '^'. A ** range of characters can be specified using '-'. Example: ** "[a-z]" matches any single lower-case letter. To match a '-', make ** it the last character in the list. ** ** This routine is usually quick, but can be N**2 in the worst case. ** ** Hints: to match '*' or '?', put them in "[]". Like this: ** ** abc[*]xyz Matches "abc*xyz" only */ static int patternCompare( const u8 *zPattern, /* The glob pattern */ const u8 *zString, /* The string to compare against the glob */ const int esc, /* The escape character */ int noCase ){ int c, c2; int invert; int seen; u8 matchOne = '?'; u8 matchAll = '*'; u8 matchSet = '['; int prevEscape = 0; /* True if the previous character was 'escape' */ if( !zPattern || !zString ) return 0; while( (c = jx9Utf8Read(zPattern, 0, &zPattern))!=0 ){ if( !prevEscape && c==matchAll ){ while( (c= jx9Utf8Read(zPattern, 0, &zPattern)) == matchAll || c == matchOne ){ if( c==matchOne && jx9Utf8Read(zString, 0, &zString)==0 ){ return 0; } } if( c==0 ){ return 1; }else if( c==esc ){ c = jx9Utf8Read(zPattern, 0, &zPattern); if( c==0 ){ return 0; } }else if( c==matchSet ){ if( (esc==0) || (matchSet<0x80) ) return 0; while( *zString && patternCompare(&zPattern[-1], zString, esc, noCase)==0 ){ SQLITE_SKIP_UTF8(zString); } return *zString!=0; } while( (c2 = jx9Utf8Read(zString, 0, &zString))!=0 ){ if( noCase ){ GlogUpperToLower(c2); GlogUpperToLower(c); while( c2 != 0 && c2 != c ){ c2 = jx9Utf8Read(zString, 0, &zString); GlogUpperToLower(c2); } }else{ while( c2 != 0 && c2 != c ){ c2 = jx9Utf8Read(zString, 0, &zString); } } if( c2==0 ) return 0; if( patternCompare(zPattern, zString, esc, noCase) ) return 1; } return 0; }else if( !prevEscape && c==matchOne ){ if( jx9Utf8Read(zString, 0, &zString)==0 ){ return 0; } }else if( c==matchSet ){ int prior_c = 0; if( esc == 0 ) return 0; seen = 0; invert = 0; c = jx9Utf8Read(zString, 0, &zString); if( c==0 ) return 0; c2 = jx9Utf8Read(zPattern, 0, &zPattern); if( c2=='^' ){ invert = 1; c2 = jx9Utf8Read(zPattern, 0, &zPattern); } if( c2==']' ){ if( c==']' ) seen = 1; c2 = jx9Utf8Read(zPattern, 0, &zPattern); } while( c2 && c2!=']' ){ if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ c2 = jx9Utf8Read(zPattern, 0, &zPattern); if( c>=prior_c && c<=c2 ) seen = 1; prior_c = 0; }else{ if( c==c2 ){ seen = 1; } prior_c = c2; } c2 = jx9Utf8Read(zPattern, 0, &zPattern); } if( c2==0 || (seen ^ invert)==0 ){ return 0; } }else if( esc==c && !prevEscape ){ prevEscape = 1; }else{ c2 = jx9Utf8Read(zString, 0, &zString); if( noCase ){ GlogUpperToLower(c); GlogUpperToLower(c2); } if( c!=c2 ){ return 0; } prevEscape = 0; } } return *zString==0; } /* * Wrapper around patternCompare() defined above. * See block comment above for more information. */ static int Glob(const unsigned char *zPattern, const unsigned char *zString, int iEsc, int CaseCompare) { int rc; if( iEsc < 0 ){ iEsc = '\\'; } rc = patternCompare(zPattern, zString, iEsc, CaseCompare); return rc; } /* * bool fnmatch(string $pattern, string $string[, int $flags = 0 ]) * Match filename against a pattern. * Parameters * $pattern * The shell wildcard pattern. * $string * The tested string. * $flags * A list of possible flags: * FNM_NOESCAPE Disable backslash escaping. * FNM_PATHNAME Slash in string only matches slash in the given pattern. * FNM_PERIOD Leading period in string must be exactly matched by period in the given pattern. * FNM_CASEFOLD Caseless match. * Return * TRUE if there is a match, FALSE otherwise. */ static int jx9Builtin_fnmatch(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zPattern; int iEsc = '\\'; int noCase = 0; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the pattern and the string */ zPattern = jx9_value_to_string(apArg[0], 0); zString = jx9_value_to_string(apArg[1], 0); /* Extract the flags if avaialble */ if( nArg > 2 && jx9_value_is_int(apArg[2]) ){ rc = jx9_value_to_int(apArg[2]); if( rc & 0x01 /*FNM_NOESCAPE*/){ iEsc = 0; } if( rc & 0x08 /*FNM_CASEFOLD*/){ noCase = 1; } } /* Go globbing */ rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, noCase); /* Globbing result */ jx9_result_bool(pCtx, rc); return JX9_OK; } /* * bool strglob(string $pattern, string $string) * Match string against a pattern. * Parameters * $pattern * The shell wildcard pattern. * $string * The tested string. * Return * TRUE if there is a match, FALSE otherwise. */ static int jx9Builtin_strglob(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zString, *zPattern; int iEsc = '\\'; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the pattern and the string */ zPattern = jx9_value_to_string(apArg[0], 0); zString = jx9_value_to_string(apArg[1], 0); /* Go globbing */ rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, 0); /* Globbing result */ jx9_result_bool(pCtx, rc); return JX9_OK; } /* * bool link(string $target, string $link) * Create a hard link. * Parameters * $target * Target of the link. * $link * The link name. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_link(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zTarget, *zLink; jx9_vfs *pVfs; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xLink == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the given arguments */ zTarget = jx9_value_to_string(apArg[0], 0); zLink = jx9_value_to_string(apArg[1], 0); /* Perform the requested operation */ rc = pVfs->xLink(zTarget, zLink, 0/*Not a symbolic link */); /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK ); return JX9_OK; } /* * bool symlink(string $target, string $link) * Creates a symbolic link. * Parameters * $target * Target of the link. * $link * The link name. * Return * TRUE on success or FALSE on failure. */ static int jx9Vfs_symlink(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zTarget, *zLink; jx9_vfs *pVfs; int rc; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xLink == 0 ){ /* IO routine not implemented, return NULL */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", jx9_function_name(pCtx) ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the given arguments */ zTarget = jx9_value_to_string(apArg[0], 0); zLink = jx9_value_to_string(apArg[1], 0); /* Perform the requested operation */ rc = pVfs->xLink(zTarget, zLink, 1/*A symbolic link */); /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK ); return JX9_OK; } /* * int umask([ int $mask ]) * Changes the current umask. * Parameters * $mask * The new umask. * Return * umask() without arguments simply returns the current umask. * Otherwise the old umask is returned. */ static int jx9Vfs_umask(jx9_context *pCtx, int nArg, jx9_value **apArg) { int iOld, iNew; jx9_vfs *pVfs; /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xUmask == 0 ){ /* IO routine not implemented, return -1 */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS", jx9_function_name(pCtx) ); jx9_result_int(pCtx, 0); return JX9_OK; } iNew = 0; if( nArg > 0 ){ iNew = jx9_value_to_int(apArg[0]); } /* Perform the requested operation */ iOld = pVfs->xUmask(iNew); /* Old mask */ jx9_result_int(pCtx, iOld); return JX9_OK; } /* * string sys_get_temp_dir() * Returns directory path used for temporary files. * Parameters * None * Return * Returns the path of the temporary directory. */ static int jx9Vfs_sys_get_temp_dir(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_vfs *pVfs; /* Set the empty string as the default return value */ jx9_result_string(pCtx, "", 0); /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xTempDir == 0 ){ SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* IO routine not implemented, return "" */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS", jx9_function_name(pCtx) ); return JX9_OK; } /* Perform the requested operation */ pVfs->xTempDir(pCtx); return JX9_OK; } /* * string get_current_user() * Returns the name of the current working user. * Parameters * None * Return * Returns the name of the current working user. */ static int jx9Vfs_get_current_user(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_vfs *pVfs; /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xUsername == 0 ){ SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* IO routine not implemented */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS", jx9_function_name(pCtx) ); /* Set a dummy username */ jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); return JX9_OK; } /* Perform the requested operation */ pVfs->xUsername(pCtx); return JX9_OK; } /* * int64 getmypid() * Gets process ID. * Parameters * None * Return * Returns the process ID. */ static int jx9Vfs_getmypid(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_int64 nProcessId; jx9_vfs *pVfs; /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xProcessId == 0 ){ SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* IO routine not implemented, return -1 */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS", jx9_function_name(pCtx) ); jx9_result_int(pCtx, -1); return JX9_OK; } /* Perform the requested operation */ nProcessId = (jx9_int64)pVfs->xProcessId(); /* Set the result */ jx9_result_int64(pCtx, nProcessId); return JX9_OK; } /* * int getmyuid() * Get user ID. * Parameters * None * Return * Returns the user ID. */ static int jx9Vfs_getmyuid(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_vfs *pVfs; int nUid; /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xUid == 0 ){ SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* IO routine not implemented, return -1 */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS", jx9_function_name(pCtx) ); jx9_result_int(pCtx, -1); return JX9_OK; } /* Perform the requested operation */ nUid = pVfs->xUid(); /* Set the result */ jx9_result_int(pCtx, nUid); return JX9_OK; } /* * int getmygid() * Get group ID. * Parameters * None * Return * Returns the group ID. */ static int jx9Vfs_getmygid(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_vfs *pVfs; int nGid; /* Point to the underlying vfs */ pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); if( pVfs == 0 || pVfs->xGid == 0 ){ SXUNUSED(nArg); /* cc warning */ SXUNUSED(apArg); /* IO routine not implemented, return -1 */ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying VFS", jx9_function_name(pCtx) ); jx9_result_int(pCtx, -1); return JX9_OK; } /* Perform the requested operation */ nGid = pVfs->xGid(); /* Set the result */ jx9_result_int(pCtx, nGid); return JX9_OK; } #ifdef __WINNT__ #include #elif defined(__UNIXES__) #include #endif /* * string uname([ string $mode = "a" ]) * Returns information about the host operating system. * Parameters * $mode * mode is a single character that defines what information is returned: * 'a': This is the default. Contains all modes in the sequence "s n r v m". * 's': Operating system name. eg. FreeBSD. * 'n': Host name. eg. localhost.example.com. * 'r': Release name. eg. 5.1.2-RELEASE. * 'v': Version information. Varies a lot between operating systems. * 'm': Machine type. eg. i386. * Return * OS description as a string. */ static int jx9Vfs_uname(jx9_context *pCtx, int nArg, jx9_value **apArg) { #if defined(__WINNT__) const char *zName = "Microsoft Windows"; OSVERSIONINFOW sVer; #elif defined(__UNIXES__) struct utsname sName; #endif const char *zMode = "a"; if( nArg > 0 && jx9_value_is_string(apArg[0]) ){ /* Extract the desired mode */ zMode = jx9_value_to_string(apArg[0], 0); } #if defined(__WINNT__) sVer.dwOSVersionInfoSize = sizeof(sVer); if( TRUE != GetVersionExW(&sVer)){ jx9_result_string(pCtx, zName, -1); return JX9_OK; } if( sVer.dwPlatformId == VER_PLATFORM_WIN32_NT ){ if( sVer.dwMajorVersion <= 4 ){ zName = "Microsoft Windows NT"; }else if( sVer.dwMajorVersion == 5 ){ switch(sVer.dwMinorVersion){ case 0: zName = "Microsoft Windows 2000"; break; case 1: zName = "Microsoft Windows XP"; break; case 2: zName = "Microsoft Windows Server 2003"; break; } }else if( sVer.dwMajorVersion == 6){ switch(sVer.dwMinorVersion){ case 0: zName = "Microsoft Windows Vista"; break; case 1: zName = "Microsoft Windows 7"; break; case 2: zName = "Microsoft Windows 8"; break; default: break; } } } switch(zMode[0]){ case 's': /* Operating system name */ jx9_result_string(pCtx, zName, -1/* Compute length automatically*/); break; case 'n': /* Host name */ jx9_result_string(pCtx, "localhost", (int)sizeof("localhost")-1); break; case 'r': case 'v': /* Version information. */ jx9_result_string_format(pCtx, "%u.%u build %u", sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber ); break; case 'm': /* Machine name */ jx9_result_string(pCtx, "x86", (int)sizeof("x86")-1); break; default: jx9_result_string_format(pCtx, "%s localhost %u.%u build %u x86", zName, sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber ); break; } #elif defined(__UNIXES__) if( uname(&sName) != 0 ){ jx9_result_string(pCtx, "Unix", (int)sizeof("Unix")-1); return JX9_OK; } switch(zMode[0]){ case 's': /* Operating system name */ jx9_result_string(pCtx, sName.sysname, -1/* Compute length automatically*/); break; case 'n': /* Host name */ jx9_result_string(pCtx, sName.nodename, -1/* Compute length automatically*/); break; case 'r': /* Release information */ jx9_result_string(pCtx, sName.release, -1/* Compute length automatically*/); break; case 'v': /* Version information. */ jx9_result_string(pCtx, sName.version, -1/* Compute length automatically*/); break; case 'm': /* Machine name */ jx9_result_string(pCtx, sName.machine, -1/* Compute length automatically*/); break; default: jx9_result_string_format(pCtx, "%s %s %s %s %s", sName.sysname, sName.release, sName.version, sName.nodename, sName.machine ); break; } #else jx9_result_string(pCtx, "Host Operating System/localhost", (int)sizeof("Host Operating System/localhost")-1); #endif return JX9_OK; } /* * Section: * IO stream implementation. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ typedef struct io_private io_private; struct io_private { const jx9_io_stream *pStream; /* Underlying IO device */ void *pHandle; /* IO handle */ /* Unbuffered IO */ SyBlob sBuffer; /* Working buffer */ sxu32 nOfft; /* Current read offset */ sxu32 iMagic; /* Sanity check to avoid misuse */ }; #define IO_PRIVATE_MAGIC 0xFEAC14 /* Make sure we are dealing with a valid io_private instance */ #define IO_PRIVATE_INVALID(IO) ( IO == 0 || IO->iMagic != IO_PRIVATE_MAGIC ) /* Forward declaration */ static void ResetIOPrivate(io_private *pDev); /* * bool ftruncate(resource $handle, int64 $size) * Truncates a file to a given length. * Parameters * $handle * The file pointer. * Note: * The handle must be open for writing. * $size * The size to truncate to. * Return * TRUE on success or FALSE on failure. */ static int jx9Builtin_ftruncate(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; int rc; if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xTrunc == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ rc = pStream->xTrunc(pDev->pHandle, jx9_value_to_int64(apArg[1])); if( rc == JX9_OK ){ /* Discard buffered data */ ResetIOPrivate(pDev); } /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * int fseek(resource $handle, int $offset[, int $whence = SEEK_SET ]) * Seeks on a file pointer. * Parameters * $handle * A file system pointer resource that is typically created using fopen(). * $offset * The offset. * To move to a position before the end-of-file, you need to pass a negative * value in offset and set whence to SEEK_END. * whence * whence values are: * SEEK_SET - Set position equal to offset bytes. * SEEK_CUR - Set position to current location plus offset. * SEEK_END - Set position to end-of-file plus offset. * Return * 0 on success, -1 on failure */ static int jx9Builtin_fseek(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; jx9_int64 iOfft; int whence; int rc; if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_int(pCtx, -1); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_int(pCtx, -1); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xSeek == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_int(pCtx, -1); return JX9_OK; } /* Extract the offset */ iOfft = jx9_value_to_int64(apArg[1]); whence = 0;/* SEEK_SET */ if( nArg > 2 && jx9_value_is_int(apArg[2]) ){ whence = jx9_value_to_int(apArg[2]); } /* Perform the requested operation */ rc = pStream->xSeek(pDev->pHandle, iOfft, whence); if( rc == JX9_OK ){ /* Ignore buffered data */ ResetIOPrivate(pDev); } /* IO result */ jx9_result_int(pCtx, rc == JX9_OK ? 0 : - 1); return JX9_OK; } /* * int64 ftell(resource $handle) * Returns the current position of the file read/write pointer. * Parameters * $handle * The file pointer. * Return * Returns the position of the file pointer referenced by handle * as an integer; i.e., its offset into the file stream. * FALSE is returned on failure. */ static int jx9Builtin_ftell(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; jx9_int64 iOfft; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xTell == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ iOfft = pStream->xTell(pDev->pHandle); /* IO result */ jx9_result_int64(pCtx, iOfft); return JX9_OK; } /* * bool rewind(resource $handle) * Rewind the position of a file pointer. * Parameters * $handle * The file pointer. * Return * TRUE on success or FALSE on failure. */ static int jx9Builtin_rewind(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; int rc; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xSeek == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ rc = pStream->xSeek(pDev->pHandle, 0, 0/*SEEK_SET*/); if( rc == JX9_OK ){ /* Ignore buffered data */ ResetIOPrivate(pDev); } /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool fflush(resource $handle) * Flushes the output to a file. * Parameters * $handle * The file pointer. * Return * TRUE on success or FALSE on failure. */ static int jx9Builtin_fflush(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; int rc; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xSync == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ rc = pStream->xSync(pDev->pHandle); /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * bool feof(resource $handle) * Tests for end-of-file on a file pointer. * Parameters * $handle * The file pointer. * Return * Returns TRUE if the file pointer is at EOF.FALSE otherwise */ static int jx9Builtin_feof(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; int rc; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 1); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 1); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 1); return JX9_OK; } rc = SXERR_EOF; /* Perform the requested operation */ if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ /* Data is available */ rc = JX9_OK; }else{ char zBuf[4096]; jx9_int64 n; /* Perform a buffered read */ n = pStream->xRead(pDev->pHandle, zBuf, sizeof(zBuf)); if( n > 0 ){ /* Copy buffered data */ SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n); rc = JX9_OK; } } /* EOF or not */ jx9_result_bool(pCtx, rc == SXERR_EOF); return JX9_OK; } /* * Read n bytes from the underlying IO stream device. * Return total numbers of bytes readen on success. A number < 1 on failure * [i.e: IO error ] or EOF. */ static jx9_int64 StreamRead(io_private *pDev, void *pBuf, jx9_int64 nLen) { const jx9_io_stream *pStream = pDev->pStream; char *zBuf = (char *)pBuf; jx9_int64 n, nRead; n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; if( n > 0 ){ if( n > nLen ){ n = nLen; } /* Copy the buffered data */ SyMemcpy(SyBlobDataAt(&pDev->sBuffer, pDev->nOfft), pBuf, (sxu32)n); /* Update the read offset */ pDev->nOfft += (sxu32)n; if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ /* Reset the working buffer so that we avoid excessive memory allocation */ SyBlobReset(&pDev->sBuffer); pDev->nOfft = 0; } nLen -= n; if( nLen < 1 ){ /* All done */ return n; } /* Advance the cursor */ zBuf += n; } /* Read without buffering */ nRead = pStream->xRead(pDev->pHandle, zBuf, nLen); if( nRead > 0 ){ n += nRead; }else if( n < 1 ){ /* EOF or IO error */ return nRead; } return n; } /* * Extract a single line from the buffered input. */ static sxi32 GetLine(io_private *pDev, jx9_int64 *pLen, const char **pzLine) { const char *zIn, *zEnd, *zPtr; zIn = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); zEnd = &zIn[SyBlobLength(&pDev->sBuffer)-pDev->nOfft]; zPtr = zIn; while( zIn < zEnd ){ if( zIn[0] == '\n' ){ /* Line found */ zIn++; /* Include the line ending as requested by the JX9 specification */ *pLen = (jx9_int64)(zIn-zPtr); *pzLine = zPtr; return SXRET_OK; } zIn++; } /* No line were found */ return SXERR_NOTFOUND; } /* * Read a single line from the underlying IO stream device. */ static jx9_int64 StreamReadLine(io_private *pDev, const char **pzData, jx9_int64 nMaxLen) { const jx9_io_stream *pStream = pDev->pStream; char zBuf[8192]; jx9_int64 n; sxi32 rc; n = 0; if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ /* Reset the working buffer so that we avoid excessive memory allocation */ SyBlobReset(&pDev->sBuffer); pDev->nOfft = 0; } if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ /* Check if there is a line */ rc = GetLine(pDev, &n, pzData); if( rc == SXRET_OK ){ /* Got line, update the cursor */ pDev->nOfft += (sxu32)n; return n; } } /* Perform the read operation until a new line is extracted or length * limit is reached. */ for(;;){ n = pStream->xRead(pDev->pHandle, zBuf, (nMaxLen > 0 && nMaxLen < sizeof(zBuf)) ? nMaxLen : sizeof(zBuf)); if( n < 1 ){ /* EOF or IO error */ break; } /* Append the data just read */ SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n); /* Try to extract a line */ rc = GetLine(pDev, &n, pzData); if( rc == SXRET_OK ){ /* Got one, return immediately */ pDev->nOfft += (sxu32)n; return n; } if( nMaxLen > 0 && (SyBlobLength(&pDev->sBuffer) - pDev->nOfft >= nMaxLen) ){ /* Read limit reached, return the available data */ *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; /* Reset the working buffer */ SyBlobReset(&pDev->sBuffer); pDev->nOfft = 0; return n; } } if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ /* Read limit reached, return the available data */ *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; /* Reset the working buffer */ SyBlobReset(&pDev->sBuffer); pDev->nOfft = 0; } return n; } /* * Open an IO stream handle. * Notes on stream: * According to the JX9 reference manual. * In its simplest definition, a stream is a resource object which exhibits streamable behavior. * That is, it can be read from or written to in a linear fashion, and may be able to fseek() * to an arbitrary locations within the stream. * A wrapper is additional code which tells the stream how to handle specific protocols/encodings. * For example, the http wrapper knows how to translate a URL into an HTTP/1.0 request for a file * on a remote server. * A stream is referenced as: scheme://target * scheme(string) - The name of the wrapper to be used. Examples include: file, http... * If no wrapper is specified, the function default is used (typically file://). * target - Depends on the wrapper used. For filesystem related streams this is typically a path * and filename of the desired file. For network related streams this is typically a hostname, often * with a path appended. * * Note that JX9 IO streams looks like JX9 streams but their implementation differ greately. * Please refer to the official documentation for a full discussion. * This function return a handle on success. Otherwise null. */ JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile, int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew) { void *pHandle = 0; /* cc warning */ SyString sFile; int rc; if( pStream == 0 ){ /* No such stream device */ return 0; } SyStringInitFromBuf(&sFile, zFile, SyStrlen(zFile)); if( use_include ){ if( sFile.zString[0] == '/' || #ifdef __WINNT__ (sFile.nByte > 2 && sFile.zString[1] == ':' && (sFile.zString[2] == '\\' || sFile.zString[2] == '/') ) || #endif (sFile.nByte > 1 && sFile.zString[0] == '.' && sFile.zString[1] == '/') || (sFile.nByte > 2 && sFile.zString[0] == '.' && sFile.zString[1] == '.' && sFile.zString[2] == '/') ){ /* Open the file directly */ rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle); }else{ SyString *pPath; SyBlob sWorker; #ifdef __WINNT__ static const int c = '\\'; #else static const int c = '/'; #endif /* Init the path builder working buffer */ SyBlobInit(&sWorker, &pVm->sAllocator); /* Build a path from the set of include path */ SySetResetCursor(&pVm->aPaths); rc = SXERR_IO; while( SXRET_OK == SySetGetNextEntry(&pVm->aPaths, (void **)&pPath) ){ /* Build full path */ SyBlobFormat(&sWorker, "%z%c%z", pPath, c, &sFile); /* Append null terminator */ if( SXRET_OK != SyBlobNullAppend(&sWorker) ){ continue; } /* Try to open the file */ rc = pStream->xOpen((const char *)SyBlobData(&sWorker), iFlags, pResource, &pHandle); if( rc == JX9_OK ){ if( bPushInclude ){ /* Mark as included */ jx9VmPushFilePath(pVm, (const char *)SyBlobData(&sWorker), SyBlobLength(&sWorker), FALSE, pNew); } break; } /* Reset the working buffer */ SyBlobReset(&sWorker); /* Check the next path */ } SyBlobRelease(&sWorker); } if( rc == JX9_OK ){ if( bPushInclude ){ /* Mark as included */ jx9VmPushFilePath(pVm, sFile.zString, sFile.nByte, FALSE, pNew); } } }else{ /* Open the URI direcly */ rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle); } if( rc != JX9_OK ){ /* IO error */ return 0; } /* Return the file handle */ return pHandle; } /* * Read the whole contents of an open IO stream handle [i.e local file/URL..] * Store the read data in the given BLOB (last argument). * The read operation is stopped when he hit the EOF or an IO error occurs. */ JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut) { jx9_int64 nRead; char zBuf[8192]; /* 8K */ int rc; /* Perform the requested operation */ for(;;){ nRead = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); if( nRead < 1 ){ /* EOF or IO error */ break; } /* Append contents */ rc = SyBlobAppend(pOut, zBuf, (sxu32)nRead); if( rc != SXRET_OK ){ break; } } return SyBlobLength(pOut) > 0 ? SXRET_OK : -1; } /* * Close an open IO stream handle [i.e local file/URI..]. */ JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle) { if( pStream->xClose ){ pStream->xClose(pHandle); } } /* * string fgetc(resource $handle) * Gets a character from the given file pointer. * Parameters * $handle * The file pointer. * Return * Returns a string containing a single character read from the file * pointed to by handle. Returns FALSE on EOF. * WARNING * This operation is extremely slow.Avoid using it. */ static int jx9Builtin_fgetc(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; int c, n; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ n = (int)StreamRead(pDev, (void *)&c, sizeof(char)); /* IO result */ if( n < 1 ){ /* EOF or error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Return the string holding the character */ jx9_result_string(pCtx, (const char *)&c, sizeof(char)); } return JX9_OK; } /* * string fgets(resource $handle[, int64 $length ]) * Gets line from file pointer. * Parameters * $handle * The file pointer. * $length * Reading ends when length - 1 bytes have been read, on a newline * (which is included in the return value), or on EOF (whichever comes first). * If no length is specified, it will keep reading from the stream until it reaches * the end of the line. * Return * Returns a string of up to length - 1 bytes read from the file pointed to by handle. * If there is no more data to read in the file pointer, then FALSE is returned. * If an error occurs, FALSE is returned. */ static int jx9Builtin_fgets(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; const char *zLine; io_private *pDev; jx9_int64 n, nLen; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = -1; if( nArg > 1 ){ /* Maximum data to read */ nLen = jx9_value_to_int64(apArg[1]); } /* Perform the requested operation */ n = StreamReadLine(pDev, &zLine, nLen); if( n < 1 ){ /* EOF or IO error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Return the freshly extracted line */ jx9_result_string(pCtx, zLine, (int)n); } return JX9_OK; } /* * string fread(resource $handle, int64 $length) * Binary-safe file read. * Parameters * $handle * The file pointer. * $length * Up to length number of bytes read. * Return * The data readen on success or FALSE on failure. */ static int jx9Builtin_fread(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; jx9_int64 nRead; void *pBuf; int nLen; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = 4096; if( nArg > 1 ){ nLen = jx9_value_to_int(apArg[1]); if( nLen < 1 ){ /* Invalid length, set a default length */ nLen = 4096; } } /* Allocate enough buffer */ pBuf = jx9_context_alloc_chunk(pCtx, (unsigned int)nLen, FALSE, FALSE); if( pBuf == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ nRead = StreamRead(pDev, pBuf, (jx9_int64)nLen); if( nRead < 1 ){ /* Nothing read, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Make a copy of the data just read */ jx9_result_string(pCtx, (const char *)pBuf, (int)nRead); } /* Release the buffer */ jx9_context_free_chunk(pCtx, pBuf); return JX9_OK; } /* * array fgetcsv(resource $handle [, int $length = 0 * [, string $delimiter = ', '[, string $enclosure = '"'[, string $escape='\\']]]]) * Gets line from file pointer and parse for CSV fields. * Parameters * $handle * The file pointer. * $length * Reading ends when length - 1 bytes have been read, on a newline * (which is included in the return value), or on EOF (whichever comes first). * If no length is specified, it will keep reading from the stream until it reaches * the end of the line. * $delimiter * Set the field delimiter (one character only). * $enclosure * Set the field enclosure character (one character only). * $escape * Set the escape character (one character only). Defaults as a backslash (\) * Return * Returns a string of up to length - 1 bytes read from the file pointed to by handle. * If there is no more data to read in the file pointer, then FALSE is returned. * If an error occurs, FALSE is returned. */ static int jx9Builtin_fgetcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; const char *zLine; io_private *pDev; jx9_int64 n, nLen; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = -1; if( nArg > 1 ){ /* Maximum data to read */ nLen = jx9_value_to_int64(apArg[1]); } /* Perform the requested operation */ n = StreamReadLine(pDev, &zLine, nLen); if( n < 1 ){ /* EOF or IO error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ jx9_value *pArray; int delim = ','; /* Delimiter */ int encl = '"' ; /* Enclosure */ int escape = '\\'; /* Escape character */ if( nArg > 2 ){ const char *zPtr; int i; if( jx9_value_is_string(apArg[2]) ){ /* Extract the delimiter */ zPtr = jx9_value_to_string(apArg[2], &i); if( i > 0 ){ delim = zPtr[0]; } } if( nArg > 3 ){ if( jx9_value_is_string(apArg[3]) ){ /* Extract the enclosure */ zPtr = jx9_value_to_string(apArg[3], &i); if( i > 0 ){ encl = zPtr[0]; } } if( nArg > 4 ){ if( jx9_value_is_string(apArg[4]) ){ /* Extract the escape character */ zPtr = jx9_value_to_string(apArg[4], &i); if( i > 0 ){ escape = zPtr[0]; } } } } } /* Create our array */ pArray = jx9_context_new_array(pCtx); if( pArray == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_null(pCtx); return JX9_OK; } /* Parse the raw input */ jx9ProcessCsv(zLine, (int)n, delim, encl, escape, jx9CsvConsumer, pArray); /* Return the freshly created array */ jx9_result_value(pCtx, pArray); } return JX9_OK; } /* * string fgetss(resource $handle [, int $length [, string $allowable_tags ]]) * Gets line from file pointer and strip HTML tags. * Parameters * $handle * The file pointer. * $length * Reading ends when length - 1 bytes have been read, on a newline * (which is included in the return value), or on EOF (whichever comes first). * If no length is specified, it will keep reading from the stream until it reaches * the end of the line. * $allowable_tags * You can use the optional second parameter to specify tags which should not be stripped. * Return * Returns a string of up to length - 1 bytes read from the file pointed to by * handle, with all HTML and JX9 code stripped. If an error occurs, returns FALSE. */ static int jx9Builtin_fgetss(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; const char *zLine; io_private *pDev; jx9_int64 n, nLen; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } nLen = -1; if( nArg > 1 ){ /* Maximum data to read */ nLen = jx9_value_to_int64(apArg[1]); } /* Perform the requested operation */ n = StreamReadLine(pDev, &zLine, nLen); if( n < 1 ){ /* EOF or IO error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ const char *zTaglist = 0; int nTaglen = 0; if( nArg > 2 && jx9_value_is_string(apArg[2]) ){ /* Allowed tag */ zTaglist = jx9_value_to_string(apArg[2], &nTaglen); } /* Process data just read */ jx9StripTagsFromString(pCtx, zLine, (int)n, zTaglist, nTaglen); } return JX9_OK; } /* * string readdir(resource $dir_handle) * Read entry from directory handle. * Parameter * $dir_handle * The directory handle resource previously opened with opendir(). * Return * Returns the filename on success or FALSE on failure. */ static int jx9Builtin_readdir(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; int rc; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xReadDir == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } jx9_result_bool(pCtx, 0); /* Perform the requested operation */ rc = pStream->xReadDir(pDev->pHandle, pCtx); if( rc != JX9_OK ){ /* Return FALSE */ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * void rewinddir(resource $dir_handle) * Rewind directory handle. * Parameter * $dir_handle * The directory handle resource previously opened with opendir(). * Return * FALSE on failure. */ static int jx9Builtin_rewinddir(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xRewindDir == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ pStream->xRewindDir(pDev->pHandle); return JX9_OK; } /* Forward declaration */ static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut); static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev); /* * void closedir(resource $dir_handle) * Close directory handle. * Parameter * $dir_handle * The directory handle resource previously opened with opendir(). * Return * FALSE on failure. */ static int jx9Builtin_closedir(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xCloseDir == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ pStream->xCloseDir(pDev->pHandle); /* Release the private stucture */ ReleaseIOPrivate(pCtx, pDev); jx9MemObjRelease(apArg[0]); return JX9_OK; } /* * resource opendir(string $path[, resource $context]) * Open directory handle. * Parameters * $path * The directory path that is to be opened. * $context * A context stream resource. * Return * A directory handle resource on success, or FALSE on failure. */ static int jx9Builtin_opendir(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; const char *zPath; io_private *pDev; int iLen, rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a directory path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the target path */ zPath = jx9_value_to_string(apArg[0], &iLen); /* Try to extract a stream */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zPath, iLen); if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "No stream device is associated with the given path(%s)", zPath); jx9_result_bool(pCtx, 0); return JX9_OK; } if( pStream->xOpenDir == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device", jx9_function_name(pCtx), pStream->zName ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Allocate a new IO private instance */ pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); if( pDev == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Initialize the structure */ InitIOPrivate(pCtx->pVm, pStream, pDev); /* Open the target directory */ rc = pStream->xOpenDir(zPath, nArg > 1 ? apArg[1] : 0, &pDev->pHandle); if( rc != JX9_OK ){ /* IO error, return FALSE */ ReleaseIOPrivate(pCtx, pDev); jx9_result_bool(pCtx, 0); }else{ /* Return the handle as a resource */ jx9_result_resource(pCtx, pDev); } return JX9_OK; } /* * int readfile(string $filename[, bool $use_include_path = false [, resource $context ]]) * Reads a file and writes it to the output buffer. * Parameters * $filename * The filename being read. * $use_include_path * You can use the optional second parameter and set it to * TRUE, if you want to search for the file in the include_path, too. * $context * A context stream resource. * Return * The number of bytes read from the file on success or FALSE on failure. */ static int jx9Builtin_readfile(jx9_context *pCtx, int nArg, jx9_value **apArg) { int use_include = FALSE; const jx9_io_stream *pStream; jx9_int64 n, nRead; const char *zFile; char zBuf[8192]; void *pHandle; int rc, nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } if( nArg > 1 ){ use_include = jx9_value_to_bool(apArg[1]); } /* Try to open the file in read-only mode */ pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); if( pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ nRead = 0; for(;;){ n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); if( n < 1 ){ /* EOF or IO error, break immediately */ break; } /* Output data */ rc = jx9_context_output(pCtx, zBuf, (int)n); if( rc == JX9_ABORT ){ break; } /* Increment counter */ nRead += n; } /* Close the stream */ jx9StreamCloseHandle(pStream, pHandle); /* Total number of bytes readen */ jx9_result_int64(pCtx, nRead); return JX9_OK; } /* * string file_get_contents(string $filename[, bool $use_include_path = false * [, resource $context [, int $offset = -1 [, int $maxlen ]]]]) * Reads entire file into a string. * Parameters * $filename * The filename being read. * $use_include_path * You can use the optional second parameter and set it to * TRUE, if you want to search for the file in the include_path, too. * $context * A context stream resource. * $offset * The offset where the reading starts on the original stream. * $maxlen * Maximum length of data read. The default is to read until end of file * is reached. Note that this parameter is applied to the stream processed by the filters. * Return * The function returns the read data or FALSE on failure. */ static int jx9Builtin_file_get_contents(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; jx9_int64 n, nRead, nMaxlen; int use_include = FALSE; const char *zFile; char zBuf[8192]; void *pHandle; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } nMaxlen = -1; if( nArg > 1 ){ use_include = jx9_value_to_bool(apArg[1]); } /* Try to open the file in read-only mode */ pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); if( pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } if( nArg > 3 ){ /* Extract the offset */ n = jx9_value_to_int64(apArg[3]); if( n > 0 ){ if( pStream->xSeek ){ /* Seek to the desired offset */ pStream->xSeek(pHandle, n, 0/*SEEK_SET*/); } } if( nArg > 4 ){ /* Maximum data to read */ nMaxlen = jx9_value_to_int64(apArg[4]); } } /* Perform the requested operation */ nRead = 0; for(;;){ n = pStream->xRead(pHandle, zBuf, (nMaxlen > 0 && (nMaxlen < sizeof(zBuf))) ? nMaxlen : sizeof(zBuf)); if( n < 1 ){ /* EOF or IO error, break immediately */ break; } /* Append data */ jx9_result_string(pCtx, zBuf, (int)n); /* Increment read counter */ nRead += n; if( nMaxlen > 0 && nRead >= nMaxlen ){ /* Read limit reached */ break; } } /* Close the stream */ jx9StreamCloseHandle(pStream, pHandle); /* Check if we have read something */ if( jx9_context_result_buf_length(pCtx) < 1 ){ /* Nothing read, return FALSE */ jx9_result_bool(pCtx, 0); } return JX9_OK; } /* * int file_put_contents(string $filename, mixed $data[, int $flags = 0[, resource $context]]) * Write a string to a file. * Parameters * $filename * Path to the file where to write the data. * $data * The data to write(Must be a string). * $flags * The value of flags can be any combination of the following * flags, joined with the binary OR (|) operator. * FILE_USE_INCLUDE_PATH Search for filename in the include directory. See include_path for more information. * FILE_APPEND If file filename already exists, append the data to the file instead of overwriting it. * LOCK_EX Acquire an exclusive lock on the file while proceeding to the writing. * context * A context stream resource. * Return * The function returns the number of bytes that were written to the file, or FALSE on failure. */ static int jx9Builtin_file_put_contents(jx9_context *pCtx, int nArg, jx9_value **apArg) { int use_include = FALSE; const jx9_io_stream *pStream; const char *zFile; const char *zData; int iOpenFlags; void *pHandle; int iFlags; int nLen; if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Data to write */ zData = jx9_value_to_string(apArg[1], &nLen); if( nLen < 1 ){ /* Nothing to write, return immediately */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Try to open the file in read-write mode */ iOpenFlags = JX9_IO_OPEN_CREATE|JX9_IO_OPEN_RDWR|JX9_IO_OPEN_TRUNC; /* Extract the flags */ iFlags = 0; if( nArg > 2 ){ iFlags = jx9_value_to_int(apArg[2]); if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/){ use_include = TRUE; } if( iFlags & 0x08 /* FILE_APPEND */){ /* If the file already exists, append the data to the file * instead of overwriting it. */ iOpenFlags &= ~JX9_IO_OPEN_TRUNC; /* Append mode */ iOpenFlags |= JX9_IO_OPEN_APPEND; } } pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, iOpenFlags, use_include, nArg > 3 ? apArg[3] : 0, FALSE, FALSE); if( pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } if( pStream->xWrite ){ jx9_int64 n; if( (iFlags & 0x01/* LOCK_EX */) && pStream->xLock ){ /* Try to acquire an exclusive lock */ pStream->xLock(pHandle, 1/* LOCK_EX */); } /* Perform the write operation */ n = pStream->xWrite(pHandle, (const void *)zData, nLen); if( n < 1 ){ /* IO error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Total number of bytes written */ jx9_result_int64(pCtx, n); } }else{ /* Read-only stream */ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "Read-only stream(%s): Cannot perform write operation", pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); } /* Close the handle */ jx9StreamCloseHandle(pStream, pHandle); return JX9_OK; } /* * array file(string $filename[, int $flags = 0[, resource $context]]) * Reads entire file into an array. * Parameters * $filename * The filename being read. * $flags * The optional parameter flags can be one, or more, of the following constants: * FILE_USE_INCLUDE_PATH * Search for the file in the include_path. * FILE_IGNORE_NEW_LINES * Do not add newline at the end of each array element * FILE_SKIP_EMPTY_LINES * Skip empty lines * $context * A context stream resource. * Return * The function returns the read data or FALSE on failure. */ static int jx9Builtin_file(jx9_context *pCtx, int nArg, jx9_value **apArg) { const char *zFile, *zPtr, *zEnd, *zBuf; jx9_value *pArray, *pLine; const jx9_io_stream *pStream; int use_include = 0; io_private *pDev; jx9_int64 n; int iFlags; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Allocate a new IO private instance */ pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); if( pDev == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Initialize the structure */ InitIOPrivate(pCtx->pVm, pStream, pDev); iFlags = 0; if( nArg > 1 ){ iFlags = jx9_value_to_int(apArg[1]); } if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/ ){ use_include = TRUE; } /* Create the array and the working value */ pArray = jx9_context_new_array(pCtx); pLine = jx9_context_new_scalar(pCtx); if( pArray == 0 || pLine == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Try to open the file in read-only mode */ pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); if( pDev->pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); /* Don't worry about freeing memory, everything will be released automatically * as soon we return from this function. */ return JX9_OK; } /* Perform the requested operation */ for(;;){ /* Try to extract a line */ n = StreamReadLine(pDev, &zBuf, -1); if( n < 1 ){ /* EOF or IO error */ break; } /* Reset the cursor */ jx9_value_reset_string_cursor(pLine); /* Remove line ending if requested by the caller */ zPtr = zBuf; zEnd = &zBuf[n]; if( iFlags & 0x02 /* FILE_IGNORE_NEW_LINES */ ){ /* Ignore trailig lines */ while( zPtr < zEnd && (zEnd[-1] == '\n' #ifdef __WINNT__ || zEnd[-1] == '\r' #endif )){ n--; zEnd--; } } if( iFlags & 0x04 /* FILE_SKIP_EMPTY_LINES */ ){ /* Ignore empty lines */ while( zPtr < zEnd && (unsigned char)zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) ){ zPtr++; } if( zPtr >= zEnd ){ /* Empty line */ continue; } } jx9_value_string(pLine, zBuf, (int)(zEnd-zBuf)); /* Insert line */ jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pLine); } /* Close the stream */ jx9StreamCloseHandle(pStream, pDev->pHandle); /* Release the io_private instance */ ReleaseIOPrivate(pCtx, pDev); /* Return the created array */ jx9_result_value(pCtx, pArray); return JX9_OK; } /* * bool copy(string $source, string $dest[, resource $context ] ) * Makes a copy of the file source to dest. * Parameters * $source * Path to the source file. * $dest * The destination path. If dest is a URL, the copy operation * may fail if the wrapper does not support overwriting of existing files. * $context * A context stream resource. * Return * TRUE on success or FALSE on failure. */ static int jx9Builtin_copy(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pSin, *pSout; const char *zFile; char zBuf[8192]; void *pIn, *pOut; jx9_int64 n; int nLen; if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1])){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a source and a destination path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the source name */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pSin = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pSin == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Try to open the source file in a read-only mode */ pIn = jx9StreamOpenHandle(pCtx->pVm, pSin, zFile, JX9_IO_OPEN_RDONLY, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0); if( pIn == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening source: '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the destination name */ zFile = jx9_value_to_string(apArg[1], &nLen); /* Point to the target IO stream device */ pSout = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pSout == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); jx9StreamCloseHandle(pSin, pIn); return JX9_OK; } if( pSout->xWrite == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pSin->zName ); jx9_result_bool(pCtx, 0); jx9StreamCloseHandle(pSin, pIn); return JX9_OK; } /* Try to open the destination file in a read-write mode */ pOut = jx9StreamOpenHandle(pCtx->pVm, pSout, zFile, JX9_IO_OPEN_CREATE|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_RDWR, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0); if( pOut == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening destination: '%s'", zFile); jx9_result_bool(pCtx, 0); jx9StreamCloseHandle(pSin, pIn); return JX9_OK; } /* Perform the requested operation */ for(;;){ /* Read from source */ n = pSin->xRead(pIn, zBuf, sizeof(zBuf)); if( n < 1 ){ /* EOF or IO error, break immediately */ break; } /* Write to dest */ n = pSout->xWrite(pOut, zBuf, n); if( n < 1 ){ /* IO error, break immediately */ break; } } /* Close the streams */ jx9StreamCloseHandle(pSin, pIn); jx9StreamCloseHandle(pSout, pOut); /* Return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * array fstat(resource $handle) * Gets information about a file using an open file pointer. * Parameters * $handle * The file pointer. * Return * Returns an array with the statistics of the file or FALSE on failure. */ static int jx9Builtin_fstat(jx9_context *pCtx, int nArg, jx9_value **apArg) { jx9_value *pArray, *pValue; const jx9_io_stream *pStream; io_private *pDev; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /* Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xStat == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Create the array and the working value */ pArray = jx9_context_new_array(pCtx); pValue = jx9_context_new_scalar(pCtx); if( pArray == 0 || pValue == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ pStream->xStat(pDev->pHandle, pArray, pValue); /* Return the freshly created array */ jx9_result_value(pCtx, pArray); /* Don't worry about freeing memory here, everything will be * released automatically as soon we return from this function. */ return JX9_OK; } /* * int fwrite(resource $handle, string $string[, int $length]) * Writes the contents of string to the file stream pointed to by handle. * Parameters * $handle * The file pointer. * $string * The string that is to be written. * $length * If the length argument is given, writing will stop after length bytes have been written * or the end of string is reached, whichever comes first. * Return * Returns the number of bytes written, or FALSE on error. */ static int jx9Builtin_fwrite(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; const char *zString; io_private *pDev; int nLen, n; if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /* Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xWrite == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the data to write */ zString = jx9_value_to_string(apArg[1], &nLen); if( nArg > 2 ){ /* Maximum data length to write */ n = jx9_value_to_int(apArg[2]); if( n >= 0 && n < nLen ){ nLen = n; } } if( nLen < 1 ){ /* Nothing to write */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ n = (int)pStream->xWrite(pDev->pHandle, (const void *)zString, nLen); if( n < 0 ){ /* IO error, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* #Bytes written */ jx9_result_int(pCtx, n); } return JX9_OK; } /* * bool flock(resource $handle, int $operation) * Portable advisory file locking. * Parameters * $handle * The file pointer. * $operation * operation is one of the following: * LOCK_SH to acquire a shared lock (reader). * LOCK_EX to acquire an exclusive lock (writer). * LOCK_UN to release a lock (shared or exclusive). * Return * Returns TRUE on success or FALSE on failure. */ static int jx9Builtin_flock(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; int nLock; int rc; if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xLock == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Requested lock operation */ nLock = jx9_value_to_int(apArg[1]); /* Lock operation */ rc = pStream->xLock(pDev->pHandle, nLock); /* IO result */ jx9_result_bool(pCtx, rc == JX9_OK); return JX9_OK; } /* * int fpassthru(resource $handle) * Output all remaining data on a file pointer. * Parameters * $handle * The file pointer. * Return * Total number of characters read from handle and passed through * to the output on success or FALSE on failure. */ static int jx9Builtin_fpassthru(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; jx9_int64 n, nRead; char zBuf[8192]; int rc; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Perform the requested operation */ nRead = 0; for(;;){ n = StreamRead(pDev, zBuf, sizeof(zBuf)); if( n < 1 ){ /* Error or EOF */ break; } /* Increment the read counter */ nRead += n; /* Output data */ rc = jx9_context_output(pCtx, zBuf, (int)nRead /* FIXME: 64-bit issues */); if( rc == JX9_ABORT ){ /* Consumer callback request an operation abort */ break; } } /* Total number of bytes readen */ jx9_result_int64(pCtx, nRead); return JX9_OK; } /* CSV reader/writer private data */ struct csv_data { int delimiter; /* Delimiter. Default ', ' */ int enclosure; /* Enclosure. Default '"'*/ io_private *pDev; /* Open stream handle */ int iCount; /* Counter */ }; /* * The following callback is used by the fputcsv() function inorder to iterate * throw array entries and output CSV data based on the current key and it's * associated data. */ static int csv_write_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData) { struct csv_data *pData = (struct csv_data *)pUserData; const char *zData; int nLen, c2; sxu32 n; /* Point to the raw data */ zData = jx9_value_to_string(pValue, &nLen); if( nLen < 1 ){ /* Nothing to write */ return JX9_OK; } if( pData->iCount > 0 ){ /* Write the delimiter */ pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->delimiter, sizeof(char)); } n = 1; c2 = 0; if( SyByteFind(zData, (sxu32)nLen, pData->delimiter, 0) == SXRET_OK || SyByteFind(zData, (sxu32)nLen, pData->enclosure, &n) == SXRET_OK ){ c2 = 1; if( n == 0 ){ c2 = 2; } /* Write the enclosure */ pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); if( c2 > 1 ){ pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); } } /* Write the data */ if( pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)zData, (jx9_int64)nLen) < 1 ){ SXUNUSED(pKey); /* cc warning */ return JX9_ABORT; } if( c2 > 0 ){ /* Write the enclosure */ pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); if( c2 > 1 ){ pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); } } pData->iCount++; return JX9_OK; } /* * int fputcsv(resource $handle, array $fields[, string $delimiter = ', '[, string $enclosure = '"' ]]) * Format line as CSV and write to file pointer. * Parameters * $handle * Open file handle. * $fields * An array of values. * $delimiter * The optional delimiter parameter sets the field delimiter (one character only). * $enclosure * The optional enclosure parameter sets the field enclosure (one character only). */ static int jx9Builtin_fputcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; struct csv_data sCsv; io_private *pDev; char *zEol; int eolen; if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Missing/Invalid arguments"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 || pStream->xWrite == 0){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Set default csv separator */ sCsv.delimiter = ','; sCsv.enclosure = '"'; sCsv.pDev = pDev; sCsv.iCount = 0; if( nArg > 2 ){ /* User delimiter */ const char *z; int n; z = jx9_value_to_string(apArg[2], &n); if( n > 0 ){ sCsv.delimiter = z[0]; } if( nArg > 3 ){ z = jx9_value_to_string(apArg[3], &n); if( n > 0 ){ sCsv.enclosure = z[0]; } } } /* Iterate throw array entries and write csv data */ jx9_array_walk(apArg[1], csv_write_callback, &sCsv); /* Write a line ending */ #ifdef __WINNT__ zEol = "\r\n"; eolen = (int)sizeof("\r\n")-1; #else /* Assume UNIX LF */ zEol = "\n"; eolen = (int)sizeof(char); #endif pDev->pStream->xWrite(pDev->pHandle, (const void *)zEol, eolen); return JX9_OK; } /* * fprintf, vfprintf private data. * An instance of the following structure is passed to the formatted * input consumer callback defined below. */ typedef struct fprintf_data fprintf_data; struct fprintf_data { io_private *pIO; /* IO stream */ jx9_int64 nCount; /* Total number of bytes written */ }; /* * Callback [i.e: Formatted input consumer] for the fprintf function. */ static int fprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) { fprintf_data *pFdata = (fprintf_data *)pUserData; jx9_int64 n; /* Write the formatted data */ n = pFdata->pIO->pStream->xWrite(pFdata->pIO->pHandle, (const void *)zInput, nLen); if( n < 1 ){ SXUNUSED(pCtx); /* cc warning */ /* IO error, abort immediately */ return SXERR_ABORT; } /* Increment counter */ pFdata->nCount += n; return JX9_OK; } /* * int fprintf(resource $handle, string $format[, mixed $args [, mixed $... ]]) * Write a formatted string to a stream. * Parameters * $handle * The file pointer. * $format * String format (see sprintf()). * Return * The length of the written string. */ static int jx9Builtin_fprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) { fprintf_data sFdata; const char *zFormat; io_private *pDev; int nLen; if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ /* Missing/Invalid arguments, return zero */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments"); jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_int(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device", jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream" ); jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the string format */ zFormat = jx9_value_to_string(apArg[1], &nLen); if( nLen < 1 ){ /* Empty string, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Prepare our private data */ sFdata.nCount = 0; sFdata.pIO = pDev; /* Format the string */ jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, nArg - 1, &apArg[1], (void *)&sFdata, FALSE); /* Return total number of bytes written */ jx9_result_int64(pCtx, sFdata.nCount); return JX9_OK; } /* * int vfprintf(resource $handle, string $format, array $args) * Write a formatted string to a stream. * Parameters * $handle * The file pointer. * $format * String format (see sprintf()). * $args * User arguments. * Return * The length of the written string. */ static int jx9Builtin_vfprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) { fprintf_data sFdata; const char *zFormat; jx9_hashmap *pMap; io_private *pDev; SySet sArg; int n, nLen; if( nArg < 3 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) || !jx9_value_is_json_array(apArg[2]) ){ /* Missing/Invalid arguments, return zero */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments"); jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_int(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device", jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream" ); jx9_result_int(pCtx, 0); return JX9_OK; } /* Extract the string format */ zFormat = jx9_value_to_string(apArg[1], &nLen); if( nLen < 1 ){ /* Empty string, return zero */ jx9_result_int(pCtx, 0); return JX9_OK; } /* Point to hashmap */ pMap = (jx9_hashmap *)apArg[2]->x.pOther; /* Extract arguments from the hashmap */ n = jx9HashmapValuesToSet(pMap, &sArg); /* Prepare our private data */ sFdata.nCount = 0; sFdata.pIO = pDev; /* Format the string */ jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&sFdata, TRUE); /* Return total number of bytes written*/ jx9_result_int64(pCtx, sFdata.nCount); SySetRelease(&sArg); return JX9_OK; } /* * Convert open modes (string passed to the fopen() function) [i.e: 'r', 'w+', 'a', ...] into JX9 flags. * According to the JX9 reference manual: * The mode parameter specifies the type of access you require to the stream. It may be any of the following * 'r' Open for reading only; place the file pointer at the beginning of the file. * 'r+' Open for reading and writing; place the file pointer at the beginning of the file. * 'w' Open for writing only; place the file pointer at the beginning of the file and truncate the file * to zero length. If the file does not exist, attempt to create it. * 'w+' Open for reading and writing; place the file pointer at the beginning of the file and truncate * the file to zero length. If the file does not exist, attempt to create it. * 'a' Open for writing only; place the file pointer at the end of the file. If the file does not * exist, attempt to create it. * 'a+' Open for reading and writing; place the file pointer at the end of the file. If the file does * not exist, attempt to create it. * 'x' Create and open for writing only; place the file pointer at the beginning of the file. If the file * already exists, * the fopen() call will fail by returning FALSE and generating an error of level E_WARNING. If the file * does not exist attempt to create it. This is equivalent to specifying O_EXCL|O_CREAT flags for * the underlying open(2) system call. * 'x+' Create and open for reading and writing; otherwise it has the same behavior as 'x'. * 'c' Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated * (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer * is positioned on the beginning of the file. * This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file * as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can * be used after the lock is requested). * 'c+' Open the file for reading and writing; otherwise it has the same behavior as 'c'. */ static int StrModeToFlags(jx9_context *pCtx, const char *zMode, int nLen) { const char *zEnd = &zMode[nLen]; int iFlag = 0; int c; if( nLen < 1 ){ /* Open in a read-only mode */ return JX9_IO_OPEN_RDONLY; } c = zMode[0]; if( c == 'r' || c == 'R' ){ /* Read-only access */ iFlag = JX9_IO_OPEN_RDONLY; zMode++; /* Advance */ if( zMode < zEnd ){ c = zMode[0]; if( c == '+' || c == 'w' || c == 'W' ){ /* Read+Write access */ iFlag = JX9_IO_OPEN_RDWR; } } }else if( c == 'w' || c == 'W' ){ /* Overwrite mode. * If the file does not exists, try to create it */ iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_CREATE; zMode++; /* Advance */ if( zMode < zEnd ){ c = zMode[0]; if( c == '+' || c == 'r' || c == 'R' ){ /* Read+Write access */ iFlag &= ~JX9_IO_OPEN_WRONLY; iFlag |= JX9_IO_OPEN_RDWR; } } }else if( c == 'a' || c == 'A' ){ /* Append mode (place the file pointer at the end of the file). * Create the file if it does not exists. */ iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_APPEND|JX9_IO_OPEN_CREATE; zMode++; /* Advance */ if( zMode < zEnd ){ c = zMode[0]; if( c == '+' ){ /* Read-Write access */ iFlag &= ~JX9_IO_OPEN_WRONLY; iFlag |= JX9_IO_OPEN_RDWR; } } }else if( c == 'x' || c == 'X' ){ /* Exclusive access. * If the file already exists, return immediately with a failure code. * Otherwise create a new file. */ iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_EXCL; zMode++; /* Advance */ if( zMode < zEnd ){ c = zMode[0]; if( c == '+' || c == 'r' || c == 'R' ){ /* Read-Write access */ iFlag &= ~JX9_IO_OPEN_WRONLY; iFlag |= JX9_IO_OPEN_RDWR; } } }else if( c == 'c' || c == 'C' ){ /* Overwrite mode.Create the file if it does not exists.*/ iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_CREATE; zMode++; /* Advance */ if( zMode < zEnd ){ c = zMode[0]; if( c == '+' ){ /* Read-Write access */ iFlag &= ~JX9_IO_OPEN_WRONLY; iFlag |= JX9_IO_OPEN_RDWR; } } }else{ /* Invalid mode. Assume a read only open */ jx9_context_throw_error(pCtx, JX9_CTX_NOTICE, "Invalid open mode, JX9 is assuming a Read-Only open"); iFlag = JX9_IO_OPEN_RDONLY; } while( zMode < zEnd ){ c = zMode[0]; if( c == 'b' || c == 'B' ){ iFlag &= ~JX9_IO_OPEN_TEXT; iFlag |= JX9_IO_OPEN_BINARY; }else if( c == 't' || c == 'T' ){ iFlag &= ~JX9_IO_OPEN_BINARY; iFlag |= JX9_IO_OPEN_TEXT; } zMode++; } return iFlag; } /* * Initialize the IO private structure. */ static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut) { pOut->pStream = pStream; SyBlobInit(&pOut->sBuffer, &pVm->sAllocator); pOut->nOfft = 0; /* Set the magic number */ pOut->iMagic = IO_PRIVATE_MAGIC; } /* * Release the IO private structure. */ static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev) { SyBlobRelease(&pDev->sBuffer); pDev->iMagic = 0x2126; /* Invalid magic number so we can detetct misuse */ /* Release the whole structure */ jx9_context_free_chunk(pCtx, pDev); } /* * Reset the IO private structure. */ static void ResetIOPrivate(io_private *pDev) { SyBlobReset(&pDev->sBuffer); pDev->nOfft = 0; } /* Forward declaration */ static int is_jx9_stream(const jx9_io_stream *pStream); /* * resource fopen(string $filename, string $mode [, bool $use_include_path = false[, resource $context ]]) * Open a file, a URL or any other IO stream. * Parameters * $filename * If filename is of the form "scheme://...", it is assumed to be a URL and JX9 will search * for a protocol handler (also known as a wrapper) for that scheme. If no scheme is given * then a regular file is assumed. * $mode * The mode parameter specifies the type of access you require to the stream * See the block comment associated with the StrModeToFlags() for the supported * modes. * $use_include_path * You can use the optional second parameter and set it to * TRUE, if you want to search for the file in the include_path, too. * $context * A context stream resource. * Return * File handle on success or FALSE on failure. */ static int jx9Builtin_fopen(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; const char *zUri, *zMode; jx9_value *pResource; io_private *pDev; int iLen, imLen; int iOpenFlags; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path or URL"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the URI and the desired access mode */ zUri = jx9_value_to_string(apArg[0], &iLen); if( nArg > 1 ){ zMode = jx9_value_to_string(apArg[1], &imLen); }else{ /* Set a default read-only mode */ zMode = "r"; imLen = (int)sizeof(char); } /* Try to extract a stream */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zUri, iLen); if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "No stream device is associated with the given URI(%s)", zUri); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Allocate a new IO private instance */ pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); if( pDev == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } pResource = 0; if( nArg > 3 ){ pResource = apArg[3]; }else if( is_jx9_stream(pStream) ){ /* TICKET 1433-80: The jx9:// stream need a jx9_value to access the underlying * virtual machine. */ pResource = apArg[0]; } /* Initialize the structure */ InitIOPrivate(pCtx->pVm, pStream, pDev); /* Convert open mode to JX9 flags */ iOpenFlags = StrModeToFlags(pCtx, zMode, imLen); /* Try to get a handle */ pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zUri, iOpenFlags, nArg > 2 ? jx9_value_to_bool(apArg[2]) : FALSE, pResource, FALSE, 0); if( pDev->pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zUri); jx9_result_bool(pCtx, 0); jx9_context_free_chunk(pCtx, pDev); return JX9_OK; } /* All done, return the io_private instance as a resource */ jx9_result_resource(pCtx, pDev); return JX9_OK; } /* * bool fclose(resource $handle) * Closes an open file pointer * Parameters * $handle * The file pointer. * Return * TRUE on success or FALSE on failure. */ static int jx9Builtin_fclose(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; io_private *pDev; jx9_vm *pVm; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract our private data */ pDev = (io_private *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid io_private instance */ if( IO_PRIVATE_INVALID(pDev) ){ /*Expecting an IO handle */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the target IO stream device */ pStream = pDev->pStream; if( pStream == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" ); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the VM that own this context */ pVm = pCtx->pVm; /* TICKET 1433-62: Keep the STDIN/STDOUT/STDERR handles open */ if( pDev != pVm->pStdin && pDev != pVm->pStdout && pDev != pVm->pStderr ){ /* Perform the requested operation */ jx9StreamCloseHandle(pStream, pDev->pHandle); /* Release the IO private structure */ ReleaseIOPrivate(pCtx, pDev); /* Invalidate the resource handle */ jx9_value_release(apArg[0]); } /* Return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } #if !defined(JX9_DISABLE_HASH_FUNC) /* * MD5/SHA1 digest consumer. */ static int vfsHashConsumer(const void *pData, unsigned int nLen, void *pUserData) { /* Append hex chunk verbatim */ jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); return SXRET_OK; } /* * string md5_file(string $uri[, bool $raw_output = false ]) * Calculates the md5 hash of a given file. * Parameters * $uri * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) * $raw_output * When TRUE, returns the digest in raw binary format with a length of 16. * Return * Return the MD5 digest on success or FALSE on failure. */ static int jx9Builtin_md5_file(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; unsigned char zDigest[16]; int raw_output = FALSE; const char *zFile; MD5Context sCtx; char zBuf[8192]; void *pHandle; jx9_int64 n; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } if( nArg > 1 ){ raw_output = jx9_value_to_bool(apArg[1]); } /* Try to open the file in read-only mode */ pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); if( pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Init the MD5 context */ MD5Init(&sCtx); /* Perform the requested operation */ for(;;){ n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); if( n < 1 ){ /* EOF or IO error, break immediately */ break; } MD5Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n); } /* Close the stream */ jx9StreamCloseHandle(pStream, pHandle); /* Extract the digest */ MD5Final(zDigest, &sCtx); if( raw_output ){ /* Output raw digest */ jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest)); }else{ /* Perform a binary to hex conversion */ SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx); } return JX9_OK; } /* * string sha1_file(string $uri[, bool $raw_output = false ]) * Calculates the SHA1 hash of a given file. * Parameters * $uri * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) * $raw_output * When TRUE, returns the digest in raw binary format with a length of 20. * Return * Return the SHA1 digest on success or FALSE on failure. */ static int jx9Builtin_sha1_file(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; unsigned char zDigest[20]; int raw_output = FALSE; const char *zFile; SHA1Context sCtx; char zBuf[8192]; void *pHandle; jx9_int64 n; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } if( nArg > 1 ){ raw_output = jx9_value_to_bool(apArg[1]); } /* Try to open the file in read-only mode */ pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); if( pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Init the SHA1 context */ SHA1Init(&sCtx); /* Perform the requested operation */ for(;;){ n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); if( n < 1 ){ /* EOF or IO error, break immediately */ break; } SHA1Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n); } /* Close the stream */ jx9StreamCloseHandle(pStream, pHandle); /* Extract the digest */ SHA1Final(&sCtx, zDigest); if( raw_output ){ /* Output raw digest */ jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest)); }else{ /* Perform a binary to hex conversion */ SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx); } return JX9_OK; } #endif /* JX9_DISABLE_HASH_FUNC */ /* * array parse_ini_file(string $filename[, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]] ) * Parse a configuration file. * Parameters * $filename * The filename of the ini file being parsed. * $process_sections * By setting the process_sections parameter to TRUE, you get a multidimensional array * with the section names and settings included. * The default for process_sections is FALSE. * $scanner_mode * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. * If INI_SCANNER_RAW is supplied, then option values will not be parsed. * Return * The settings are returned as an associative array on success. * Otherwise is returned. */ static int jx9Builtin_parse_ini_file(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; const char *zFile; SyBlob sContents; void *pHandle; int nLen; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Try to open the file in read-only mode */ pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); if( pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } SyBlobInit(&sContents, &pCtx->pVm->sAllocator); /* Read the whole file */ jx9StreamReadWholeFile(pHandle, pStream, &sContents); if( SyBlobLength(&sContents) < 1 ){ /* Empty buffer, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Process the raw INI buffer */ jx9ParseIniString(pCtx, (const char *)SyBlobData(&sContents), SyBlobLength(&sContents), nArg > 1 ? jx9_value_to_bool(apArg[1]) : 0); } /* Close the stream */ jx9StreamCloseHandle(pStream, pHandle); /* Release the working buffer */ SyBlobRelease(&sContents); return JX9_OK; } /* * Section: * ZIP archive processing. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ typedef struct zip_raw_data zip_raw_data; struct zip_raw_data { int iType; /* Where the raw data is stored */ union raw_data{ struct mmap_data{ void *pMap; /* Memory mapped data */ jx9_int64 nSize; /* Map size */ const jx9_vfs *pVfs; /* Underlying vfs */ }mmap; SyBlob sBlob; /* Memory buffer */ }raw; }; #define ZIP_RAW_DATA_MMAPED 1 /* Memory mapped ZIP raw data */ #define ZIP_RAW_DATA_MEMBUF 2 /* ZIP raw data stored in a dynamically * allocated memory chunk. */ /* * mixed zip_open(string $filename) * Opens a new zip archive for reading. * Parameters * $filename * The file name of the ZIP archive to open. * Return * A resource handle for later use with zip_read() and zip_close() or FALSE on failure. */ static int jx9Builtin_zip_open(jx9_context *pCtx, int nArg, jx9_value **apArg) { const jx9_io_stream *pStream; SyArchive *pArchive; zip_raw_data *pRaw; const char *zFile; SyBlob *pContents; void *pHandle; int nLen; sxi32 rc; if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ /* Missing/Invalid arguments, return FALSE */ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the file path */ zFile = jx9_value_to_string(apArg[0], &nLen); /* Point to the target IO stream device */ pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); if( pStream == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); jx9_result_bool(pCtx, 0); return JX9_OK; } /* Create an in-memory archive */ pArchive = (SyArchive *)jx9_context_alloc_chunk(pCtx, sizeof(SyArchive)+sizeof(zip_raw_data), TRUE, FALSE); if( pArchive == 0 ){ jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "JX9 is running out of memory"); jx9_result_bool(pCtx, 0); return JX9_OK; } pRaw = (zip_raw_data *)&pArchive[1]; /* Initialize the archive */ SyArchiveInit(pArchive, &pCtx->pVm->sAllocator, 0, 0); /* Extract the default stream */ if( pStream == pCtx->pVm->pDefStream /* file:// stream*/){ const jx9_vfs *pVfs; /* Try to get a memory view of the whole file since ZIP files * tends to be very big this days, this is a huge performance win. */ pVfs = jx9ExportBuiltinVfs(); if( pVfs && pVfs->xMmap ){ rc = pVfs->xMmap(zFile, &pRaw->raw.mmap.pMap, &pRaw->raw.mmap.nSize); if( rc == JX9_OK ){ /* Nice, Extract the whole archive */ rc = SyZipExtractFromBuf(pArchive, (const char *)pRaw->raw.mmap.pMap, (sxu32)pRaw->raw.mmap.nSize); if( rc != SXRET_OK ){ if( pVfs->xUnmap ){ pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize); } /* Release the allocated chunk */ jx9_context_free_chunk(pCtx, pArchive); /* Something goes wrong with this ZIP archive, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Archive successfully opened */ pRaw->iType = ZIP_RAW_DATA_MMAPED; pRaw->raw.mmap.pVfs = pVfs; goto success; } } /* FALL THROUGH */ } /* Try to open the file in read-only mode */ pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); if( pHandle == 0 ){ jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); jx9_result_bool(pCtx, 0); return JX9_OK; } pContents = &pRaw->raw.sBlob; SyBlobInit(pContents, &pCtx->pVm->sAllocator); /* Read the whole file */ jx9StreamReadWholeFile(pHandle, pStream, pContents); /* Assume an invalid ZIP file */ rc = SXERR_INVALID; if( SyBlobLength(pContents) > 0 ){ /* Extract archive entries */ rc = SyZipExtractFromBuf(pArchive, (const char *)SyBlobData(pContents), SyBlobLength(pContents)); } pRaw->iType = ZIP_RAW_DATA_MEMBUF; /* Close the stream */ jx9StreamCloseHandle(pStream, pHandle); if( rc != SXRET_OK ){ /* Release the working buffer */ SyBlobRelease(pContents); /* Release the allocated chunk */ jx9_context_free_chunk(pCtx, pArchive); /* Something goes wrong with this ZIP archive, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } success: /* Reset the loop cursor */ SyArchiveResetLoopCursor(pArchive); /* Return the in-memory archive as a resource handle */ jx9_result_resource(pCtx, pArchive); return JX9_OK; } /* * void zip_close(resource $zip) * Close an in-memory ZIP archive. * Parameters * $zip * A ZIP file previously opened with zip_open(). * Return * null. */ static int jx9Builtin_zip_close(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchive *pArchive; zip_raw_data *pRaw; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); return JX9_OK; } /* Point to the in-memory archive */ pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid ZIP archive */ if( SXARCH_INVALID(pArchive) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); return JX9_OK; } /* Release the archive */ SyArchiveRelease(pArchive); pRaw = (zip_raw_data *)&pArchive[1]; if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ SyBlobRelease(&pRaw->raw.sBlob); }else{ const jx9_vfs *pVfs = pRaw->raw.mmap.pVfs; if( pVfs->xUnmap ){ /* Unmap the memory view */ pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize); } } /* Release the memory chunk */ jx9_context_free_chunk(pCtx, pArchive); return JX9_OK; } /* * mixed zip_read(resource $zip) * Reads the next entry from an in-memory ZIP archive. * Parameters * $zip * A ZIP file previously opened with zip_open(). * Return * A directory entry resource for later use with the zip_entry_... functions * or FALSE if there are no more entries to read, or an error code if an error occurred. */ static int jx9Builtin_zip_read(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pNext = 0; /* cc warning */ SyArchive *pArchive; sxi32 rc; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the in-memory archive */ pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid ZIP archive */ if( SXARCH_INVALID(pArchive) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Extract the next entry */ rc = SyArchiveGetNextEntry(pArchive, &pNext); if( rc != SXRET_OK ){ /* No more entries in the central directory, return FALSE */ jx9_result_bool(pCtx, 0); }else{ /* Return as a resource handle */ jx9_result_resource(pCtx, pNext); /* Point to the ZIP raw data */ pNext->pUserData = (void *)&pArchive[1]; } return JX9_OK; } /* * bool zip_entry_open(resource $zip, resource $zip_entry[, string $mode ]) * Open a directory entry for reading * Parameters * $zip * A ZIP file previously opened with zip_open(). * $zip_entry * A directory entry returned by zip_read(). * $mode * Not used * Return * A directory entry resource for later use with the zip_entry_... functions * or FALSE if there are no more entries to read, or an error code if an error occurred. */ static int jx9Builtin_zip_entry_open(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; SyArchive *pArchive; if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_resource(apArg[1]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Point to the in-memory archive */ pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); /* Make sure we are dealing with a valid ZIP archive */ if( SXARCH_INVALID(pArchive) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[1]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* All done. Actually this function is a no-op, return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * bool zip_entry_close(resource $zip_entry) * Close a directory entry. * Parameters * $zip_entry * A directory entry returned by zip_read(). * Return * Returns TRUE on success or FALSE on failure. */ static int jx9Builtin_zip_entry_close(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Reset the read cursor */ pEntry->nReadCount = 0; /*All done. Actually this function is a no-op, return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * string zip_entry_name(resource $zip_entry) * Retrieve the name of a directory entry. * Parameters * $zip_entry * A directory entry returned by zip_read(). * Return * The name of the directory entry. */ static int jx9Builtin_zip_entry_name(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; SyString *pName; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return entry name */ pName = &pEntry->sFileName; jx9_result_string(pCtx, pName->zString, (int)pName->nByte); return JX9_OK; } /* * int64 zip_entry_filesize(resource $zip_entry) * Retrieve the actual file size of a directory entry. * Parameters * $zip_entry * A directory entry returned by zip_read(). * Return * The size of the directory entry. */ static int jx9Builtin_zip_entry_filesize(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return entry size */ jx9_result_int64(pCtx, (jx9_int64)pEntry->nByte); return JX9_OK; } /* * int64 zip_entry_compressedsize(resource $zip_entry) * Retrieve the compressed size of a directory entry. * Parameters * $zip_entry * A directory entry returned by zip_read(). * Return * The compressed size. */ static int jx9Builtin_zip_entry_compressedsize(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Return entry compressed size */ jx9_result_int64(pCtx, (jx9_int64)pEntry->nByteCompr); return JX9_OK; } /* * string zip_entry_read(resource $zip_entry[, int $length]) * Reads from an open directory entry. * Parameters * $zip_entry * A directory entry returned by zip_read(). * $length * The number of bytes to return. If not specified, this function * will attempt to read 1024 bytes. * Return * Returns the data read, or FALSE if the end of the file is reached. */ static int jx9Builtin_zip_entry_read(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; zip_raw_data *pRaw; const char *zData; int iLength; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } zData = 0; if( pEntry->nReadCount >= pEntry->nByteCompr ){ /* No more data to read, return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Set a default read length */ iLength = 1024; if( nArg > 1 ){ iLength = jx9_value_to_int(apArg[1]); if( iLength < 1 ){ iLength = 1024; } } if( (sxu32)iLength > pEntry->nByteCompr - pEntry->nReadCount ){ iLength = (int)(pEntry->nByteCompr - pEntry->nReadCount); } /* Return the entry contents */ pRaw = (zip_raw_data *)pEntry->pUserData; if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ zData = (const char *)SyBlobDataAt(&pRaw->raw.sBlob, (pEntry->nOfft+pEntry->nReadCount)); }else{ const char *zMap = (const char *)pRaw->raw.mmap.pMap; /* Memory mmaped chunk */ zData = &zMap[pEntry->nOfft+pEntry->nReadCount]; } /* Increment the read counter */ pEntry->nReadCount += iLength; /* Return the raw data */ jx9_result_string(pCtx, zData, iLength); return JX9_OK; } /* * bool zip_entry_reset_cursor(resource $zip_entry) * Reset the read cursor of an open directory entry. * Parameters * $zip_entry * A directory entry returned by zip_read(). * Return * TRUE on success, FALSE on failure. */ static int jx9Builtin_zip_entry_reset_cursor(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Reset the cursor */ pEntry->nReadCount = 0; /* Return TRUE */ jx9_result_bool(pCtx, 1); return JX9_OK; } /* * string zip_entry_compressionmethod(resource $zip_entry) * Retrieve the compression method of a directory entry. * Parameters * $zip_entry * A directory entry returned by zip_read(). * Return * The compression method on success or FALSE on failure. */ static int jx9Builtin_zip_entry_compressionmethod(jx9_context *pCtx, int nArg, jx9_value **apArg) { SyArchiveEntry *pEntry; if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ /* Missing/Invalid arguments */ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } /* Make sure we are dealing with a valid ZIP archive entry */ pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); if( SXARCH_ENTRY_INVALID(pEntry) ){ jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); /* return FALSE */ jx9_result_bool(pCtx, 0); return JX9_OK; } switch(pEntry->nComprMeth){ case 0: /* No compression;entry is stored */ jx9_result_string(pCtx, "stored", (int)sizeof("stored")-1); break; case 8: /* Entry is deflated (Default compression algorithm) */ jx9_result_string(pCtx, "deflate", (int)sizeof("deflate")-1); break; /* Exotic compression algorithms */ case 1: jx9_result_string(pCtx, "shrunk", (int)sizeof("shrunk")-1); break; case 2: case 3: case 4: case 5: /* Entry is reduced */ jx9_result_string(pCtx, "reduced", (int)sizeof("reduced")-1); break; case 6: /* Entry is imploded */ jx9_result_string(pCtx, "implode", (int)sizeof("implode")-1); break; default: jx9_result_string(pCtx, "unknown", (int)sizeof("unknown")-1); break; } return JX9_OK; } #endif /* #ifndef JX9_DISABLE_BUILTIN_FUNC*/ /* NULL VFS [i.e: a no-op VFS]*/ static const jx9_vfs null_vfs = { "null_vfs", JX9_VFS_VERSION, 0, /* int (*xChdir)(const char *) */ 0, /* int (*xChroot)(const char *); */ 0, /* int (*xGetcwd)(jx9_context *) */ 0, /* int (*xMkdir)(const char *, int, int) */ 0, /* int (*xRmdir)(const char *) */ 0, /* int (*xIsdir)(const char *) */ 0, /* int (*xRename)(const char *, const char *) */ 0, /*int (*xRealpath)(const char *, jx9_context *)*/ 0, /* int (*xSleep)(unsigned int) */ 0, /* int (*xUnlink)(const char *) */ 0, /* int (*xFileExists)(const char *) */ 0, /*int (*xChmod)(const char *, int)*/ 0, /*int (*xChown)(const char *, const char *)*/ 0, /*int (*xChgrp)(const char *, const char *)*/ 0, /* jx9_int64 (*xFreeSpace)(const char *) */ 0, /* jx9_int64 (*xTotalSpace)(const char *) */ 0, /* jx9_int64 (*xFileSize)(const char *) */ 0, /* jx9_int64 (*xFileAtime)(const char *) */ 0, /* jx9_int64 (*xFileMtime)(const char *) */ 0, /* jx9_int64 (*xFileCtime)(const char *) */ 0, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ 0, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ 0, /* int (*xIsfile)(const char *) */ 0, /* int (*xIslink)(const char *) */ 0, /* int (*xReadable)(const char *) */ 0, /* int (*xWritable)(const char *) */ 0, /* int (*xExecutable)(const char *) */ 0, /* int (*xFiletype)(const char *, jx9_context *) */ 0, /* int (*xGetenv)(const char *, jx9_context *) */ 0, /* int (*xSetenv)(const char *, const char *) */ 0, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ 0, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ 0, /* void (*xUnmap)(void *, jx9_int64); */ 0, /* int (*xLink)(const char *, const char *, int) */ 0, /* int (*xUmask)(int) */ 0, /* void (*xTempDir)(jx9_context *) */ 0, /* unsigned int (*xProcessId)(void) */ 0, /* int (*xUid)(void) */ 0, /* int (*xGid)(void) */ 0, /* void (*xUsername)(jx9_context *) */ 0 /* int (*xExec)(const char *, jx9_context *) */ }; #ifndef JX9_DISABLE_BUILTIN_FUNC #ifndef JX9_DISABLE_DISK_IO #ifdef __WINNT__ /* * Windows VFS implementation for the JX9 engine. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* What follows here is code that is specific to windows systems. */ #include /* ** Convert a UTF-8 string to microsoft unicode (UTF-16?). ** ** Space to hold the returned string is obtained from HeapAlloc(). ** Taken from the sqlite3 source tree ** status: Public Domain */ static WCHAR *jx9utf8ToUnicode(const char *zFilename){ int nChar; WCHAR *zWideFilename; nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0); zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, nChar*sizeof(zWideFilename[0])); if( zWideFilename == 0 ){ return 0; } nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar); if( nChar==0 ){ HeapFree(GetProcessHeap(), 0, zWideFilename); return 0; } return zWideFilename; } /* ** Convert a UTF-8 filename into whatever form the underlying ** operating system wants filenames in.Space to hold the result ** is obtained from HeapAlloc() and must be freed by the calling ** function. ** Taken from the sqlite3 source tree ** status: Public Domain */ static void *jx9convertUtf8Filename(const char *zFilename){ void *zConverted; zConverted = jx9utf8ToUnicode(zFilename); return zConverted; } /* ** Convert microsoft unicode to UTF-8. Space to hold the returned string is ** obtained from HeapAlloc(). ** Taken from the sqlite3 source tree ** status: Public Domain */ static char *jx9unicodeToUtf8(const WCHAR *zWideFilename){ char *zFilename; int nByte; nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); zFilename = (char *)HeapAlloc(GetProcessHeap(), 0, nByte); if( zFilename == 0 ){ return 0; } nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, 0, 0); if( nByte == 0 ){ HeapFree(GetProcessHeap(), 0, zFilename); return 0; } return zFilename; } /* int (*xchdir)(const char *) */ static int WinVfs_chdir(const char *zPath) { void * pConverted; BOOL rc; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } rc = SetCurrentDirectoryW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); return rc ? JX9_OK : -1; } /* int (*xGetcwd)(jx9_context *) */ static int WinVfs_getcwd(jx9_context *pCtx) { WCHAR zDir[2048]; char *zConverted; DWORD rc; /* Get the current directory */ rc = GetCurrentDirectoryW(sizeof(zDir), zDir); if( rc < 1 ){ return -1; } zConverted = jx9unicodeToUtf8(zDir); if( zConverted == 0 ){ return -1; } jx9_result_string(pCtx, zConverted, -1/*Compute length automatically*/); /* Will make it's own copy */ HeapFree(GetProcessHeap(), 0, zConverted); return JX9_OK; } /* int (*xMkdir)(const char *, int, int) */ static int WinVfs_mkdir(const char *zPath, int mode, int recursive) { void * pConverted; BOOL rc; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } mode= 0; /* MSVC warning */ recursive = 0; rc = CreateDirectoryW((LPCWSTR)pConverted, 0); HeapFree(GetProcessHeap(), 0, pConverted); return rc ? JX9_OK : -1; } /* int (*xRmdir)(const char *) */ static int WinVfs_rmdir(const char *zPath) { void * pConverted; BOOL rc; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } rc = RemoveDirectoryW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); return rc ? JX9_OK : -1; } /* int (*xIsdir)(const char *) */ static int WinVfs_isdir(const char *zPath) { void * pConverted; DWORD dwAttr; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } dwAttr = GetFileAttributesW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( dwAttr == INVALID_FILE_ATTRIBUTES ){ return -1; } return (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? JX9_OK : -1; } /* int (*xRename)(const char *, const char *) */ static int WinVfs_Rename(const char *zOld, const char *zNew) { void *pOld, *pNew; BOOL rc = 0; pOld = jx9convertUtf8Filename(zOld); if( pOld == 0 ){ return -1; } pNew = jx9convertUtf8Filename(zNew); if( pNew ){ rc = MoveFileW((LPCWSTR)pOld, (LPCWSTR)pNew); } HeapFree(GetProcessHeap(), 0, pOld); if( pNew ){ HeapFree(GetProcessHeap(), 0, pNew); } return rc ? JX9_OK : - 1; } /* int (*xRealpath)(const char *, jx9_context *) */ static int WinVfs_Realpath(const char *zPath, jx9_context *pCtx) { WCHAR zTemp[2048]; void *pPath; char *zReal; DWORD n; pPath = jx9convertUtf8Filename(zPath); if( pPath == 0 ){ return -1; } n = GetFullPathNameW((LPCWSTR)pPath, 0, 0, 0); if( n > 0 ){ if( n >= sizeof(zTemp) ){ n = sizeof(zTemp) - 1; } GetFullPathNameW((LPCWSTR)pPath, n, zTemp, 0); } HeapFree(GetProcessHeap(), 0, pPath); if( !n ){ return -1; } zReal = jx9unicodeToUtf8(zTemp); if( zReal == 0 ){ return -1; } jx9_result_string(pCtx, zReal, -1); /* Will make it's own copy */ HeapFree(GetProcessHeap(), 0, zReal); return JX9_OK; } /* int (*xSleep)(unsigned int) */ static int WinVfs_Sleep(unsigned int uSec) { Sleep(uSec/1000/*uSec per Millisec */); return JX9_OK; } /* int (*xUnlink)(const char *) */ static int WinVfs_unlink(const char *zPath) { void * pConverted; BOOL rc; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } rc = DeleteFileW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); return rc ? JX9_OK : - 1; } /* jx9_int64 (*xFreeSpace)(const char *) */ static jx9_int64 WinVfs_DiskFreeSpace(const char *zPath) { #ifdef _WIN32_WCE /* GetDiskFreeSpace is not supported under WINCE */ SXUNUSED(zPath); return 0; #else DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters; void * pConverted; WCHAR *p; BOOL rc; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return 0; } p = (WCHAR *)pConverted; for(;*p;p++){ if( *p == '\\' || *p == '/'){ *p = '\0'; break; } } rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters); if( !rc ){ return 0; } return (jx9_int64)dwFreeClusters * dwSectPerClust * dwBytesPerSect; #endif } /* jx9_int64 (*xTotalSpace)(const char *) */ static jx9_int64 WinVfs_DiskTotalSpace(const char *zPath) { #ifdef _WIN32_WCE /* GetDiskFreeSpace is not supported under WINCE */ SXUNUSED(zPath); return 0; #else DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters; void * pConverted; WCHAR *p; BOOL rc; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return 0; } p = (WCHAR *)pConverted; for(;*p;p++){ if( *p == '\\' || *p == '/'){ *p = '\0'; break; } } rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters); if( !rc ){ return 0; } return (jx9_int64)dwTotalClusters * dwSectPerClust * dwBytesPerSect; #endif } /* int (*xFileExists)(const char *) */ static int WinVfs_FileExists(const char *zPath) { void * pConverted; DWORD dwAttr; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } dwAttr = GetFileAttributesW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( dwAttr == INVALID_FILE_ATTRIBUTES ){ return -1; } return JX9_OK; } /* Open a file in a read-only mode */ static HANDLE OpenReadOnly(LPCWSTR pPath) { DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; DWORD dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; DWORD dwAccess = GENERIC_READ; DWORD dwCreate = OPEN_EXISTING; HANDLE pHandle; pHandle = CreateFileW(pPath, dwAccess, dwShare, 0, dwCreate, dwType, 0); if( pHandle == INVALID_HANDLE_VALUE){ return 0; } return pHandle; } /* jx9_int64 (*xFileSize)(const char *) */ static jx9_int64 WinVfs_FileSize(const char *zPath) { DWORD dwLow, dwHigh; void * pConverted; jx9_int64 nSize; HANDLE pHandle; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } /* Open the file in read-only mode */ pHandle = OpenReadOnly((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( pHandle ){ dwLow = GetFileSize(pHandle, &dwHigh); nSize = dwHigh; nSize <<= 32; nSize += dwLow; CloseHandle(pHandle); }else{ nSize = -1; } return nSize; } #define TICKS_PER_SECOND 10000000 #define EPOCH_DIFFERENCE 11644473600LL /* Convert Windows timestamp to UNIX timestamp */ static jx9_int64 convertWindowsTimeToUnixTime(LPFILETIME pTime) { jx9_int64 input, temp; input = pTime->dwHighDateTime; input <<= 32; input += pTime->dwLowDateTime; temp = input / TICKS_PER_SECOND; /*convert from 100ns intervals to seconds*/ temp = temp - EPOCH_DIFFERENCE; /*subtract number of seconds between epochs*/ return temp; } /* Convert UNIX timestamp to Windows timestamp */ static void convertUnixTimeToWindowsTime(jx9_int64 nUnixtime, LPFILETIME pOut) { jx9_int64 result = EPOCH_DIFFERENCE; result += nUnixtime; result *= 10000000LL; pOut->dwHighDateTime = (DWORD)(nUnixtime>>32); pOut->dwLowDateTime = (DWORD)nUnixtime; } /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ static int WinVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time) { FILETIME sTouch, sAccess; void *pConverted; void *pHandle; BOOL rc = 0; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } pHandle = OpenReadOnly((LPCWSTR)pConverted); if( pHandle ){ if( touch_time < 0 ){ GetSystemTimeAsFileTime(&sTouch); }else{ convertUnixTimeToWindowsTime(touch_time, &sTouch); } if( access_time < 0 ){ /* Use the touch time */ sAccess = sTouch; /* Structure assignment */ }else{ convertUnixTimeToWindowsTime(access_time, &sAccess); } rc = SetFileTime(pHandle, &sTouch, &sAccess, 0); /* Close the handle */ CloseHandle(pHandle); } HeapFree(GetProcessHeap(), 0, pConverted); return rc ? JX9_OK : -1; } /* jx9_int64 (*xFileAtime)(const char *) */ static jx9_int64 WinVfs_FileAtime(const char *zPath) { BY_HANDLE_FILE_INFORMATION sInfo; void * pConverted; jx9_int64 atime; HANDLE pHandle; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } /* Open the file in read-only mode */ pHandle = OpenReadOnly((LPCWSTR)pConverted); if( pHandle ){ BOOL rc; rc = GetFileInformationByHandle(pHandle, &sInfo); if( rc ){ atime = convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime); }else{ atime = -1; } CloseHandle(pHandle); }else{ atime = -1; } HeapFree(GetProcessHeap(), 0, pConverted); return atime; } /* jx9_int64 (*xFileMtime)(const char *) */ static jx9_int64 WinVfs_FileMtime(const char *zPath) { BY_HANDLE_FILE_INFORMATION sInfo; void * pConverted; jx9_int64 mtime; HANDLE pHandle; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } /* Open the file in read-only mode */ pHandle = OpenReadOnly((LPCWSTR)pConverted); if( pHandle ){ BOOL rc; rc = GetFileInformationByHandle(pHandle, &sInfo); if( rc ){ mtime = convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime); }else{ mtime = -1; } CloseHandle(pHandle); }else{ mtime = -1; } HeapFree(GetProcessHeap(), 0, pConverted); return mtime; } /* jx9_int64 (*xFileCtime)(const char *) */ static jx9_int64 WinVfs_FileCtime(const char *zPath) { BY_HANDLE_FILE_INFORMATION sInfo; void * pConverted; jx9_int64 ctime; HANDLE pHandle; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } /* Open the file in read-only mode */ pHandle = OpenReadOnly((LPCWSTR)pConverted); if( pHandle ){ BOOL rc; rc = GetFileInformationByHandle(pHandle, &sInfo); if( rc ){ ctime = convertWindowsTimeToUnixTime(&sInfo.ftCreationTime); }else{ ctime = -1; } CloseHandle(pHandle); }else{ ctime = -1; } HeapFree(GetProcessHeap(), 0, pConverted); return ctime; } /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ static int WinVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) { BY_HANDLE_FILE_INFORMATION sInfo; void *pConverted; HANDLE pHandle; BOOL rc; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } /* Open the file in read-only mode */ pHandle = OpenReadOnly((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( pHandle == 0 ){ return -1; } rc = GetFileInformationByHandle(pHandle, &sInfo); CloseHandle(pHandle); if( !rc ){ return -1; } /* dev */ jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber); jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ /* ino */ jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ /* mode */ jx9_value_int(pWorker, 0); jx9_array_add_strkey_elem(pArray, "mode", pWorker); /* nlink */ jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks); jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ /* uid, gid, rdev */ jx9_value_int(pWorker, 0); jx9_array_add_strkey_elem(pArray, "uid", pWorker); jx9_array_add_strkey_elem(pArray, "gid", pWorker); jx9_array_add_strkey_elem(pArray, "rdev", pWorker); /* size */ jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ /* atime */ jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ /* mtime */ jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ /* ctime */ jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ /* blksize, blocks */ jx9_value_int(pWorker, 0); jx9_array_add_strkey_elem(pArray, "blksize", pWorker); jx9_array_add_strkey_elem(pArray, "blocks", pWorker); return JX9_OK; } /* int (*xIsfile)(const char *) */ static int WinVfs_isfile(const char *zPath) { void * pConverted; DWORD dwAttr; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } dwAttr = GetFileAttributesW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( dwAttr == INVALID_FILE_ATTRIBUTES ){ return -1; } return (dwAttr & (FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE)) ? JX9_OK : -1; } /* int (*xIslink)(const char *) */ static int WinVfs_islink(const char *zPath) { void * pConverted; DWORD dwAttr; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } dwAttr = GetFileAttributesW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( dwAttr == INVALID_FILE_ATTRIBUTES ){ return -1; } return (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) ? JX9_OK : -1; } /* int (*xWritable)(const char *) */ static int WinVfs_iswritable(const char *zPath) { void * pConverted; DWORD dwAttr; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } dwAttr = GetFileAttributesW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( dwAttr == INVALID_FILE_ATTRIBUTES ){ return -1; } if( (dwAttr & (FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL)) == 0 ){ /* Not a regular file */ return -1; } if( dwAttr & FILE_ATTRIBUTE_READONLY ){ /* Read-only file */ return -1; } /* File is writable */ return JX9_OK; } /* int (*xExecutable)(const char *) */ static int WinVfs_isexecutable(const char *zPath) { void * pConverted; DWORD dwAttr; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } dwAttr = GetFileAttributesW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( dwAttr == INVALID_FILE_ATTRIBUTES ){ return -1; } if( (dwAttr & FILE_ATTRIBUTE_NORMAL) == 0 ){ /* Not a regular file */ return -1; } /* File is executable */ return JX9_OK; } /* int (*xFiletype)(const char *, jx9_context *) */ static int WinVfs_Filetype(const char *zPath, jx9_context *pCtx) { void * pConverted; DWORD dwAttr; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ /* Expand 'unknown' */ jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); return -1; } dwAttr = GetFileAttributesW((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( dwAttr == INVALID_FILE_ATTRIBUTES ){ /* Expand 'unknown' */ jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); return -1; } if(dwAttr & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE) ){ jx9_result_string(pCtx, "file", sizeof("file")-1); }else if(dwAttr & FILE_ATTRIBUTE_DIRECTORY){ jx9_result_string(pCtx, "dir", sizeof("dir")-1); }else if(dwAttr & FILE_ATTRIBUTE_REPARSE_POINT){ jx9_result_string(pCtx, "link", sizeof("link")-1); }else if(dwAttr & (FILE_ATTRIBUTE_DEVICE)){ jx9_result_string(pCtx, "block", sizeof("block")-1); }else{ jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); } return JX9_OK; } /* int (*xGetenv)(const char *, jx9_context *) */ static int WinVfs_Getenv(const char *zVar, jx9_context *pCtx) { char zValue[1024]; DWORD n; /* * According to MSDN * If lpBuffer is not large enough to hold the data, the return * value is the buffer size, in characters, required to hold the * string and its terminating null character and the contents * of lpBuffer are undefined. */ n = sizeof(zValue); SyMemcpy("Undefined", zValue, sizeof("Undefined")-1); /* Extract the environment value */ n = GetEnvironmentVariableA(zVar, zValue, sizeof(zValue)); if( !n ){ /* No such variable*/ return -1; } jx9_result_string(pCtx, zValue, (int)n); return JX9_OK; } /* int (*xSetenv)(const char *, const char *) */ static int WinVfs_Setenv(const char *zName, const char *zValue) { BOOL rc; rc = SetEnvironmentVariableA(zName, zValue); return rc ? JX9_OK : -1; } /* int (*xMmap)(const char *, void **, jx9_int64 *) */ static int WinVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize) { DWORD dwSizeLow, dwSizeHigh; HANDLE pHandle, pMapHandle; void *pConverted, *pView; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } pHandle = OpenReadOnly((LPCWSTR)pConverted); HeapFree(GetProcessHeap(), 0, pConverted); if( pHandle == 0 ){ return -1; } /* Get the file size */ dwSizeLow = GetFileSize(pHandle, &dwSizeHigh); /* Create the mapping */ pMapHandle = CreateFileMappingW(pHandle, 0, PAGE_READONLY, dwSizeHigh, dwSizeLow, 0); if( pMapHandle == 0 ){ CloseHandle(pHandle); return -1; } *pSize = ((jx9_int64)dwSizeHigh << 32) | dwSizeLow; /* Obtain the view */ pView = MapViewOfFile(pMapHandle, FILE_MAP_READ, 0, 0, (SIZE_T)(*pSize)); if( pView ){ /* Let the upper layer point to the view */ *ppMap = pView; } /* Close the handle * According to MSDN it's OK the close the HANDLES. */ CloseHandle(pMapHandle); CloseHandle(pHandle); return pView ? JX9_OK : -1; } /* void (*xUnmap)(void *, jx9_int64) */ static void WinVfs_Unmap(void *pView, jx9_int64 nSize) { nSize = 0; /* Compiler warning */ UnmapViewOfFile(pView); } /* void (*xTempDir)(jx9_context *) */ static void WinVfs_TempDir(jx9_context *pCtx) { CHAR zTemp[1024]; DWORD n; n = GetTempPathA(sizeof(zTemp), zTemp); if( n < 1 ){ /* Assume the default windows temp directory */ jx9_result_string(pCtx, "C:\\Windows\\Temp", -1/*Compute length automatically*/); }else{ jx9_result_string(pCtx, zTemp, (int)n); } } /* unsigned int (*xProcessId)(void) */ static unsigned int WinVfs_ProcessId(void) { DWORD nID = 0; #ifndef __MINGW32__ nID = GetProcessId(GetCurrentProcess()); #endif /* __MINGW32__ */ return (unsigned int)nID; } /* Export the windows vfs */ static const jx9_vfs sWinVfs = { "Windows_vfs", JX9_VFS_VERSION, WinVfs_chdir, /* int (*xChdir)(const char *) */ 0, /* int (*xChroot)(const char *); */ WinVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */ WinVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */ WinVfs_rmdir, /* int (*xRmdir)(const char *) */ WinVfs_isdir, /* int (*xIsdir)(const char *) */ WinVfs_Rename, /* int (*xRename)(const char *, const char *) */ WinVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/ WinVfs_Sleep, /* int (*xSleep)(unsigned int) */ WinVfs_unlink, /* int (*xUnlink)(const char *) */ WinVfs_FileExists, /* int (*xFileExists)(const char *) */ 0, /*int (*xChmod)(const char *, int)*/ 0, /*int (*xChown)(const char *, const char *)*/ 0, /*int (*xChgrp)(const char *, const char *)*/ WinVfs_DiskFreeSpace, /* jx9_int64 (*xFreeSpace)(const char *) */ WinVfs_DiskTotalSpace, /* jx9_int64 (*xTotalSpace)(const char *) */ WinVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */ WinVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */ WinVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */ WinVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */ WinVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ WinVfs_Stat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ WinVfs_isfile, /* int (*xIsfile)(const char *) */ WinVfs_islink, /* int (*xIslink)(const char *) */ WinVfs_isfile, /* int (*xReadable)(const char *) */ WinVfs_iswritable, /* int (*xWritable)(const char *) */ WinVfs_isexecutable, /* int (*xExecutable)(const char *) */ WinVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */ WinVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */ WinVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */ WinVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ WinVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ WinVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */ 0, /* int (*xLink)(const char *, const char *, int) */ 0, /* int (*xUmask)(int) */ WinVfs_TempDir, /* void (*xTempDir)(jx9_context *) */ WinVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ 0, /* int (*xUid)(void) */ 0, /* int (*xGid)(void) */ 0, /* void (*xUsername)(jx9_context *) */ 0 /* int (*xExec)(const char *, jx9_context *) */ }; /* Windows file IO */ #ifndef INVALID_SET_FILE_POINTER # define INVALID_SET_FILE_POINTER ((DWORD)-1) #endif /* int (*xOpen)(const char *, int, jx9_value *, void **) */ static int WinFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle) { DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; DWORD dwAccess = GENERIC_READ; DWORD dwShare, dwCreate; void *pConverted; HANDLE pHandle; pConverted = jx9convertUtf8Filename(zPath); if( pConverted == 0 ){ return -1; } /* Set the desired flags according to the open mode */ if( iOpenMode & JX9_IO_OPEN_CREATE ){ /* Open existing file, or create if it doesn't exist */ dwCreate = OPEN_ALWAYS; if( iOpenMode & JX9_IO_OPEN_TRUNC ){ /* If the specified file exists and is writable, the function overwrites the file */ dwCreate = CREATE_ALWAYS; } }else if( iOpenMode & JX9_IO_OPEN_EXCL ){ /* Creates a new file, only if it does not already exist. * If the file exists, it fails. */ dwCreate = CREATE_NEW; }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){ /* Opens a file and truncates it so that its size is zero bytes * The file must exist. */ dwCreate = TRUNCATE_EXISTING; }else{ /* Opens a file, only if it exists. */ dwCreate = OPEN_EXISTING; } if( iOpenMode & JX9_IO_OPEN_RDWR ){ /* Read+Write access */ dwAccess |= GENERIC_WRITE; }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){ /* Write only access */ dwAccess = GENERIC_WRITE; } if( iOpenMode & JX9_IO_OPEN_APPEND ){ /* Append mode */ dwAccess = FILE_APPEND_DATA; } if( iOpenMode & JX9_IO_OPEN_TEMP ){ /* File is temporary */ dwType = FILE_ATTRIBUTE_TEMPORARY; } dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; pHandle = CreateFileW((LPCWSTR)pConverted, dwAccess, dwShare, 0, dwCreate, dwType, 0); HeapFree(GetProcessHeap(), 0, pConverted); if( pHandle == INVALID_HANDLE_VALUE){ SXUNUSED(pResource); /* MSVC warning */ return -1; } /* Make the handle accessible to the upper layer */ *ppHandle = (void *)pHandle; return JX9_OK; } /* An instance of the following structure is used to record state information * while iterating throw directory entries. */ typedef struct WinDir_Info WinDir_Info; struct WinDir_Info { HANDLE pDirHandle; void *pPath; WIN32_FIND_DATAW sInfo; int rc; }; /* int (*xOpenDir)(const char *, jx9_value *, void **) */ static int WinDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle) { WinDir_Info *pDirInfo; void *pConverted; char *zPrep; sxu32 n; /* Prepare the path */ n = SyStrlen(zPath); zPrep = (char *)HeapAlloc(GetProcessHeap(), 0, n+sizeof("\\*")+4); if( zPrep == 0 ){ return -1; } SyMemcpy((const void *)zPath, zPrep, n); zPrep[n] = '\\'; zPrep[n+1] = '*'; zPrep[n+2] = 0; pConverted = jx9convertUtf8Filename(zPrep); HeapFree(GetProcessHeap(), 0, zPrep); if( pConverted == 0 ){ return -1; } /* Allocate a new instance */ pDirInfo = (WinDir_Info *)HeapAlloc(GetProcessHeap(), 0, sizeof(WinDir_Info)); if( pDirInfo == 0 ){ pResource = 0; /* Compiler warning */ return -1; } pDirInfo->rc = SXRET_OK; pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pConverted, &pDirInfo->sInfo); if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ /* Cannot open directory */ HeapFree(GetProcessHeap(), 0, pConverted); HeapFree(GetProcessHeap(), 0, pDirInfo); return -1; } /* Save the path */ pDirInfo->pPath = pConverted; /* Save our structure */ *ppHandle = pDirInfo; return JX9_OK; } /* void (*xCloseDir)(void *) */ static void WinDir_Close(void *pUserData) { WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; if( pDirInfo->pDirHandle != INVALID_HANDLE_VALUE ){ FindClose(pDirInfo->pDirHandle); } HeapFree(GetProcessHeap(), 0, pDirInfo->pPath); HeapFree(GetProcessHeap(), 0, pDirInfo); } /* void (*xClose)(void *); */ static void WinFile_Close(void *pUserData) { HANDLE pHandle = (HANDLE)pUserData; CloseHandle(pHandle); } /* int (*xReadDir)(void *, jx9_context *) */ static int WinDir_Read(void *pUserData, jx9_context *pCtx) { WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; LPWIN32_FIND_DATAW pData; char *zName; BOOL rc; sxu32 n; if( pDirInfo->rc != SXRET_OK ){ /* No more entry to process */ return -1; } pData = &pDirInfo->sInfo; for(;;){ zName = jx9unicodeToUtf8(pData->cFileName); if( zName == 0 ){ /* Out of memory */ return -1; } n = SyStrlen(zName); /* Ignore '.' && '..' */ if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ break; } HeapFree(GetProcessHeap(), 0, zName); rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo); if( !rc ){ return -1; } } /* Return the current file name */ jx9_result_string(pCtx, zName, -1); HeapFree(GetProcessHeap(), 0, zName); /* Point to the next entry */ rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo); if( !rc ){ pDirInfo->rc = SXERR_EOF; } return JX9_OK; } /* void (*xRewindDir)(void *) */ static void WinDir_RewindDir(void *pUserData) { WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; FindClose(pDirInfo->pDirHandle); pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pDirInfo->pPath, &pDirInfo->sInfo); if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ pDirInfo->rc = SXERR_EOF; }else{ pDirInfo->rc = SXRET_OK; } } /* jx9_int64 (*xRead)(void *, void *, jx9_int64); */ static jx9_int64 WinFile_Read(void *pOS, void *pBuffer, jx9_int64 nDatatoRead) { HANDLE pHandle = (HANDLE)pOS; DWORD nRd; BOOL rc; rc = ReadFile(pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0); if( !rc ){ /* EOF or IO error */ return -1; } return (jx9_int64)nRd; } /* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */ static jx9_int64 WinFile_Write(void *pOS, const void *pBuffer, jx9_int64 nWrite) { const char *zData = (const char *)pBuffer; HANDLE pHandle = (HANDLE)pOS; jx9_int64 nCount; DWORD nWr; BOOL rc; nWr = 0; nCount = 0; for(;;){ if( nWrite < 1 ){ break; } rc = WriteFile(pHandle, zData, (DWORD)nWrite, &nWr, 0); if( !rc ){ /* IO error */ break; } nWrite -= nWr; nCount += nWr; zData += nWr; } if( nWrite > 0 ){ return -1; } return nCount; } /* int (*xSeek)(void *, jx9_int64, int) */ static int WinFile_Seek(void *pUserData, jx9_int64 iOfft, int whence) { HANDLE pHandle = (HANDLE)pUserData; DWORD dwMove, dwNew; LONG nHighOfft; switch(whence){ case 1:/*SEEK_CUR*/ dwMove = FILE_CURRENT; break; case 2: /* SEEK_END */ dwMove = FILE_END; break; case 0: /* SEEK_SET */ default: dwMove = FILE_BEGIN; break; } nHighOfft = (LONG)(iOfft >> 32); dwNew = SetFilePointer(pHandle, (LONG)iOfft, &nHighOfft, dwMove); if( dwNew == INVALID_SET_FILE_POINTER ){ return -1; } return JX9_OK; } /* int (*xLock)(void *, int) */ static int WinFile_Lock(void *pUserData, int lock_type) { HANDLE pHandle = (HANDLE)pUserData; static DWORD dwLo = 0, dwHi = 0; /* xx: MT-SAFE */ OVERLAPPED sDummy; BOOL rc; SyZero(&sDummy, sizeof(sDummy)); /* Get the file size */ if( lock_type < 1 ){ /* Unlock the file */ rc = UnlockFileEx(pHandle, 0, dwLo, dwHi, &sDummy); }else{ DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; /* Shared non-blocking lock by default*/ /* Lock the file */ if( lock_type == 1 /* LOCK_EXCL */ ){ dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; } dwLo = GetFileSize(pHandle, &dwHi); rc = LockFileEx(pHandle, dwFlags, 0, dwLo, dwHi, &sDummy); } return rc ? JX9_OK : -1 /* Lock error */; } /* jx9_int64 (*xTell)(void *) */ static jx9_int64 WinFile_Tell(void *pUserData) { HANDLE pHandle = (HANDLE)pUserData; DWORD dwNew; dwNew = SetFilePointer(pHandle, 0, 0, FILE_CURRENT/* SEEK_CUR */); if( dwNew == INVALID_SET_FILE_POINTER ){ return -1; } return (jx9_int64)dwNew; } /* int (*xTrunc)(void *, jx9_int64) */ static int WinFile_Trunc(void *pUserData, jx9_int64 nOfft) { HANDLE pHandle = (HANDLE)pUserData; LONG HighOfft; DWORD dwNew; BOOL rc; HighOfft = (LONG)(nOfft >> 32); dwNew = SetFilePointer(pHandle, (LONG)nOfft, &HighOfft, FILE_BEGIN); if( dwNew == INVALID_SET_FILE_POINTER ){ return -1; } rc = SetEndOfFile(pHandle); return rc ? JX9_OK : -1; } /* int (*xSync)(void *); */ static int WinFile_Sync(void *pUserData) { HANDLE pHandle = (HANDLE)pUserData; BOOL rc; rc = FlushFileBuffers(pHandle); return rc ? JX9_OK : - 1; } /* int (*xStat)(void *, jx9_value *, jx9_value *) */ static int WinFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker) { BY_HANDLE_FILE_INFORMATION sInfo; HANDLE pHandle = (HANDLE)pUserData; BOOL rc; rc = GetFileInformationByHandle(pHandle, &sInfo); if( !rc ){ return -1; } /* dev */ jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber); jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ /* ino */ jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ /* mode */ jx9_value_int(pWorker, 0); jx9_array_add_strkey_elem(pArray, "mode", pWorker); /* nlink */ jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks); jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ /* uid, gid, rdev */ jx9_value_int(pWorker, 0); jx9_array_add_strkey_elem(pArray, "uid", pWorker); jx9_array_add_strkey_elem(pArray, "gid", pWorker); jx9_array_add_strkey_elem(pArray, "rdev", pWorker); /* size */ jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ /* atime */ jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ /* mtime */ jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ /* ctime */ jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ /* blksize, blocks */ jx9_value_int(pWorker, 0); jx9_array_add_strkey_elem(pArray, "blksize", pWorker); jx9_array_add_strkey_elem(pArray, "blocks", pWorker); return JX9_OK; } /* Export the file:// stream */ static const jx9_io_stream sWinFileStream = { "file", /* Stream name */ JX9_IO_STREAM_VERSION, WinFile_Open, /* xOpen */ WinDir_Open, /* xOpenDir */ WinFile_Close, /* xClose */ WinDir_Close, /* xCloseDir */ WinFile_Read, /* xRead */ WinDir_Read, /* xReadDir */ WinFile_Write, /* xWrite */ WinFile_Seek, /* xSeek */ WinFile_Lock, /* xLock */ WinDir_RewindDir, /* xRewindDir */ WinFile_Tell, /* xTell */ WinFile_Trunc, /* xTrunc */ WinFile_Sync, /* xSeek */ WinFile_Stat /* xStat */ }; #elif defined(__UNIXES__) /* * UNIX VFS implementation for the JX9 engine. * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* int (*xchdir)(const char *) */ static int UnixVfs_chdir(const char *zPath) { int rc; rc = chdir(zPath); return rc == 0 ? JX9_OK : -1; } /* int (*xGetcwd)(jx9_context *) */ static int UnixVfs_getcwd(jx9_context *pCtx) { char zBuf[4096]; char *zDir; /* Get the current directory */ zDir = getcwd(zBuf, sizeof(zBuf)); if( zDir == 0 ){ return -1; } jx9_result_string(pCtx, zDir, -1/*Compute length automatically*/); return JX9_OK; } /* int (*xMkdir)(const char *, int, int) */ static int UnixVfs_mkdir(const char *zPath, int mode, int recursive) { int rc; rc = mkdir(zPath, mode); recursive = 0; /* cc warning */ return rc == 0 ? JX9_OK : -1; } /* int (*xRmdir)(const char *) */ static int UnixVfs_rmdir(const char *zPath) { int rc; rc = rmdir(zPath); return rc == 0 ? JX9_OK : -1; } /* int (*xIsdir)(const char *) */ static int UnixVfs_isdir(const char *zPath) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } rc = S_ISDIR(st.st_mode); return rc ? JX9_OK : -1 ; } /* int (*xRename)(const char *, const char *) */ static int UnixVfs_Rename(const char *zOld, const char *zNew) { int rc; rc = rename(zOld, zNew); return rc == 0 ? JX9_OK : -1; } /* int (*xRealpath)(const char *, jx9_context *) */ static int UnixVfs_Realpath(const char *zPath, jx9_context *pCtx) { #ifndef JX9_UNIX_OLD_LIBC char *zReal; zReal = realpath(zPath, 0); if( zReal == 0 ){ return -1; } jx9_result_string(pCtx, zReal, -1/*Compute length automatically*/); /* Release the allocated buffer */ free(zReal); return JX9_OK; #else zPath = 0; /* cc warning */ pCtx = 0; return -1; #endif } /* int (*xSleep)(unsigned int) */ static int UnixVfs_Sleep(unsigned int uSec) { usleep(uSec); return JX9_OK; } /* int (*xUnlink)(const char *) */ static int UnixVfs_unlink(const char *zPath) { int rc; rc = unlink(zPath); return rc == 0 ? JX9_OK : -1 ; } /* int (*xFileExists)(const char *) */ static int UnixVfs_FileExists(const char *zPath) { int rc; rc = access(zPath, F_OK); return rc == 0 ? JX9_OK : -1; } /* jx9_int64 (*xFileSize)(const char *) */ static jx9_int64 UnixVfs_FileSize(const char *zPath) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } return (jx9_int64)st.st_size; } /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ static int UnixVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time) { struct utimbuf ut; int rc; ut.actime = (time_t)access_time; ut.modtime = (time_t)touch_time; rc = utime(zPath, &ut); if( rc != 0 ){ return -1; } return JX9_OK; } /* jx9_int64 (*xFileAtime)(const char *) */ static jx9_int64 UnixVfs_FileAtime(const char *zPath) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } return (jx9_int64)st.st_atime; } /* jx9_int64 (*xFileMtime)(const char *) */ static jx9_int64 UnixVfs_FileMtime(const char *zPath) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } return (jx9_int64)st.st_mtime; } /* jx9_int64 (*xFileCtime)(const char *) */ static jx9_int64 UnixVfs_FileCtime(const char *zPath) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } return (jx9_int64)st.st_ctime; } /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ static int UnixVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } /* dev */ jx9_value_int64(pWorker, (jx9_int64)st.st_dev); jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ /* ino */ jx9_value_int64(pWorker, (jx9_int64)st.st_ino); jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ /* mode */ jx9_value_int(pWorker, (int)st.st_mode); jx9_array_add_strkey_elem(pArray, "mode", pWorker); /* nlink */ jx9_value_int(pWorker, (int)st.st_nlink); jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ /* uid, gid, rdev */ jx9_value_int(pWorker, (int)st.st_uid); jx9_array_add_strkey_elem(pArray, "uid", pWorker); jx9_value_int(pWorker, (int)st.st_gid); jx9_array_add_strkey_elem(pArray, "gid", pWorker); jx9_value_int(pWorker, (int)st.st_rdev); jx9_array_add_strkey_elem(pArray, "rdev", pWorker); /* size */ jx9_value_int64(pWorker, (jx9_int64)st.st_size); jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ /* atime */ jx9_value_int64(pWorker, (jx9_int64)st.st_atime); jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ /* mtime */ jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ /* ctime */ jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ /* blksize, blocks */ jx9_value_int(pWorker, (int)st.st_blksize); jx9_array_add_strkey_elem(pArray, "blksize", pWorker); jx9_value_int(pWorker, (int)st.st_blocks); jx9_array_add_strkey_elem(pArray, "blocks", pWorker); return JX9_OK; } /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ static int UnixVfs_lStat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) { struct stat st; int rc; rc = lstat(zPath, &st); if( rc != 0 ){ return -1; } /* dev */ jx9_value_int64(pWorker, (jx9_int64)st.st_dev); jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ /* ino */ jx9_value_int64(pWorker, (jx9_int64)st.st_ino); jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ /* mode */ jx9_value_int(pWorker, (int)st.st_mode); jx9_array_add_strkey_elem(pArray, "mode", pWorker); /* nlink */ jx9_value_int(pWorker, (int)st.st_nlink); jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ /* uid, gid, rdev */ jx9_value_int(pWorker, (int)st.st_uid); jx9_array_add_strkey_elem(pArray, "uid", pWorker); jx9_value_int(pWorker, (int)st.st_gid); jx9_array_add_strkey_elem(pArray, "gid", pWorker); jx9_value_int(pWorker, (int)st.st_rdev); jx9_array_add_strkey_elem(pArray, "rdev", pWorker); /* size */ jx9_value_int64(pWorker, (jx9_int64)st.st_size); jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ /* atime */ jx9_value_int64(pWorker, (jx9_int64)st.st_atime); jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ /* mtime */ jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ /* ctime */ jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ /* blksize, blocks */ jx9_value_int(pWorker, (int)st.st_blksize); jx9_array_add_strkey_elem(pArray, "blksize", pWorker); jx9_value_int(pWorker, (int)st.st_blocks); jx9_array_add_strkey_elem(pArray, "blocks", pWorker); return JX9_OK; } /* int (*xChmod)(const char *, int) */ static int UnixVfs_Chmod(const char *zPath, int mode) { int rc; rc = chmod(zPath, (mode_t)mode); return rc == 0 ? JX9_OK : - 1; } /* int (*xChown)(const char *, const char *) */ static int UnixVfs_Chown(const char *zPath, const char *zUser) { #ifndef JX9_UNIX_STATIC_BUILD struct passwd *pwd; uid_t uid; int rc; pwd = getpwnam(zUser); /* Try getting UID for username */ if (pwd == 0) { return -1; } uid = pwd->pw_uid; rc = chown(zPath, uid, -1); return rc == 0 ? JX9_OK : -1; #else SXUNUSED(zPath); SXUNUSED(zUser); return -1; #endif /* JX9_UNIX_STATIC_BUILD */ } /* int (*xChgrp)(const char *, const char *) */ static int UnixVfs_Chgrp(const char *zPath, const char *zGroup) { #ifndef JX9_UNIX_STATIC_BUILD struct group *group; gid_t gid; int rc; group = getgrnam(zGroup); if (group == 0) { return -1; } gid = group->gr_gid; rc = chown(zPath, -1, gid); return rc == 0 ? JX9_OK : -1; #else SXUNUSED(zPath); SXUNUSED(zGroup); return -1; #endif /* JX9_UNIX_STATIC_BUILD */ } /* int (*xIsfile)(const char *) */ static int UnixVfs_isfile(const char *zPath) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } rc = S_ISREG(st.st_mode); return rc ? JX9_OK : -1 ; } /* int (*xIslink)(const char *) */ static int UnixVfs_islink(const char *zPath) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ return -1; } rc = S_ISLNK(st.st_mode); return rc ? JX9_OK : -1 ; } /* int (*xReadable)(const char *) */ static int UnixVfs_isreadable(const char *zPath) { int rc; rc = access(zPath, R_OK); return rc == 0 ? JX9_OK : -1; } /* int (*xWritable)(const char *) */ static int UnixVfs_iswritable(const char *zPath) { int rc; rc = access(zPath, W_OK); return rc == 0 ? JX9_OK : -1; } /* int (*xExecutable)(const char *) */ static int UnixVfs_isexecutable(const char *zPath) { int rc; rc = access(zPath, X_OK); return rc == 0 ? JX9_OK : -1; } /* int (*xFiletype)(const char *, jx9_context *) */ static int UnixVfs_Filetype(const char *zPath, jx9_context *pCtx) { struct stat st; int rc; rc = stat(zPath, &st); if( rc != 0 ){ /* Expand 'unknown' */ jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); return -1; } if(S_ISREG(st.st_mode) ){ jx9_result_string(pCtx, "file", sizeof("file")-1); }else if(S_ISDIR(st.st_mode)){ jx9_result_string(pCtx, "dir", sizeof("dir")-1); }else if(S_ISLNK(st.st_mode)){ jx9_result_string(pCtx, "link", sizeof("link")-1); }else if(S_ISBLK(st.st_mode)){ jx9_result_string(pCtx, "block", sizeof("block")-1); }else if(S_ISSOCK(st.st_mode)){ jx9_result_string(pCtx, "socket", sizeof("socket")-1); }else if(S_ISFIFO(st.st_mode)){ jx9_result_string(pCtx, "fifo", sizeof("fifo")-1); }else{ jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); } return JX9_OK; } /* int (*xGetenv)(const char *, jx9_context *) */ static int UnixVfs_Getenv(const char *zVar, jx9_context *pCtx) { char *zEnv; zEnv = getenv(zVar); if( zEnv == 0 ){ return -1; } jx9_result_string(pCtx, zEnv, -1/*Compute length automatically*/); return JX9_OK; } /* int (*xSetenv)(const char *, const char *) */ static int UnixVfs_Setenv(const char *zName, const char *zValue) { int rc; rc = setenv(zName, zValue, 1); return rc == 0 ? JX9_OK : -1; } /* int (*xMmap)(const char *, void **, jx9_int64 *) */ static int UnixVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize) { struct stat st; void *pMap; int fd; int rc; /* Open the file in a read-only mode */ fd = open(zPath, O_RDONLY); if( fd < 0 ){ return -1; } /* stat the handle */ fstat(fd, &st); /* Obtain a memory view of the whole file */ pMap = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE|MAP_FILE, fd, 0); rc = JX9_OK; if( pMap == MAP_FAILED ){ rc = -1; }else{ /* Point to the memory view */ *ppMap = pMap; *pSize = (jx9_int64)st.st_size; } close(fd); return rc; } /* void (*xUnmap)(void *, jx9_int64) */ static void UnixVfs_Unmap(void *pView, jx9_int64 nSize) { munmap(pView, (size_t)nSize); } /* void (*xTempDir)(jx9_context *) */ static void UnixVfs_TempDir(jx9_context *pCtx) { static const char *azDirs[] = { "/var/tmp", "/usr/tmp", "/usr/local/tmp" }; unsigned int i; struct stat buf; const char *zDir; zDir = getenv("TMPDIR"); if( zDir && zDir[0] != 0 && !access(zDir, 07) ){ jx9_result_string(pCtx, zDir, -1); return; } for(i=0; ipw_name, -1); #else jx9_result_string(pCtx, "Unknown", -1); #endif /* JX9_UNIX_STATIC_BUILD */ return; } /* int (*xLink)(const char *, const char *, int) */ static int UnixVfs_link(const char *zSrc, const char *zTarget, int is_sym) { int rc; if( is_sym ){ /* Symbolic link */ rc = symlink(zSrc, zTarget); }else{ /* Hard link */ rc = link(zSrc, zTarget); } return rc == 0 ? JX9_OK : -1; } /* int (*xChroot)(const char *) */ static int UnixVfs_chroot(const char *zRootDir) { int rc; rc = chroot(zRootDir); return rc == 0 ? JX9_OK : -1; } /* Export the UNIX vfs */ static const jx9_vfs sUnixVfs = { "Unix_vfs", JX9_VFS_VERSION, UnixVfs_chdir, /* int (*xChdir)(const char *) */ UnixVfs_chroot, /* int (*xChroot)(const char *); */ UnixVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */ UnixVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */ UnixVfs_rmdir, /* int (*xRmdir)(const char *) */ UnixVfs_isdir, /* int (*xIsdir)(const char *) */ UnixVfs_Rename, /* int (*xRename)(const char *, const char *) */ UnixVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/ UnixVfs_Sleep, /* int (*xSleep)(unsigned int) */ UnixVfs_unlink, /* int (*xUnlink)(const char *) */ UnixVfs_FileExists, /* int (*xFileExists)(const char *) */ UnixVfs_Chmod, /*int (*xChmod)(const char *, int)*/ UnixVfs_Chown, /*int (*xChown)(const char *, const char *)*/ UnixVfs_Chgrp, /*int (*xChgrp)(const char *, const char *)*/ 0, /* jx9_int64 (*xFreeSpace)(const char *) */ 0, /* jx9_int64 (*xTotalSpace)(const char *) */ UnixVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */ UnixVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */ UnixVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */ UnixVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */ UnixVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ UnixVfs_lStat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ UnixVfs_isfile, /* int (*xIsfile)(const char *) */ UnixVfs_islink, /* int (*xIslink)(const char *) */ UnixVfs_isreadable, /* int (*xReadable)(const char *) */ UnixVfs_iswritable, /* int (*xWritable)(const char *) */ UnixVfs_isexecutable, /* int (*xExecutable)(const char *) */ UnixVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */ UnixVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */ UnixVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */ UnixVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ UnixVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ UnixVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */ UnixVfs_link, /* int (*xLink)(const char *, const char *, int) */ UnixVfs_Umask, /* int (*xUmask)(int) */ UnixVfs_TempDir, /* void (*xTempDir)(jx9_context *) */ UnixVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ UnixVfs_uid, /* int (*xUid)(void) */ UnixVfs_gid, /* int (*xGid)(void) */ UnixVfs_Username, /* void (*xUsername)(jx9_context *) */ 0 /* int (*xExec)(const char *, jx9_context *) */ }; /* UNIX File IO */ #define JX9_UNIX_OPEN_MODE 0640 /* Default open mode */ /* int (*xOpen)(const char *, int, jx9_value *, void **) */ static int UnixFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle) { int iOpen = O_RDONLY; int fd; /* Set the desired flags according to the open mode */ if( iOpenMode & JX9_IO_OPEN_CREATE ){ /* Open existing file, or create if it doesn't exist */ iOpen = O_CREAT; if( iOpenMode & JX9_IO_OPEN_TRUNC ){ /* If the specified file exists and is writable, the function overwrites the file */ iOpen |= O_TRUNC; SXUNUSED(pResource); /* cc warning */ } }else if( iOpenMode & JX9_IO_OPEN_EXCL ){ /* Creates a new file, only if it does not already exist. * If the file exists, it fails. */ iOpen = O_CREAT|O_EXCL; }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){ /* Opens a file and truncates it so that its size is zero bytes * The file must exist. */ iOpen = O_RDWR|O_TRUNC; } if( iOpenMode & JX9_IO_OPEN_RDWR ){ /* Read+Write access */ iOpen &= ~O_RDONLY; iOpen |= O_RDWR; }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){ /* Write only access */ iOpen &= ~O_RDONLY; iOpen |= O_WRONLY; } if( iOpenMode & JX9_IO_OPEN_APPEND ){ /* Append mode */ iOpen |= O_APPEND; } #ifdef O_TEMP if( iOpenMode & JX9_IO_OPEN_TEMP ){ /* File is temporary */ iOpen |= O_TEMP; } #endif /* Open the file now */ fd = open(zPath, iOpen, JX9_UNIX_OPEN_MODE); if( fd < 0 ){ /* IO error */ return -1; } /* Save the handle */ *ppHandle = SX_INT_TO_PTR(fd); return JX9_OK; } /* int (*xOpenDir)(const char *, jx9_value *, void **) */ static int UnixDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle) { DIR *pDir; /* Open the target directory */ pDir = opendir(zPath); if( pDir == 0 ){ pResource = 0; /* Compiler warning */ return -1; } /* Save our structure */ *ppHandle = pDir; return JX9_OK; } /* void (*xCloseDir)(void *) */ static void UnixDir_Close(void *pUserData) { closedir((DIR *)pUserData); } /* void (*xClose)(void *); */ static void UnixFile_Close(void *pUserData) { close(SX_PTR_TO_INT(pUserData)); } /* int (*xReadDir)(void *, jx9_context *) */ static int UnixDir_Read(void *pUserData, jx9_context *pCtx) { DIR *pDir = (DIR *)pUserData; struct dirent *pEntry; char *zName = 0; /* cc warning */ sxu32 n = 0; for(;;){ pEntry = readdir(pDir); if( pEntry == 0 ){ /* No more entries to process */ return -1; } zName = pEntry->d_name; n = SyStrlen(zName); /* Ignore '.' && '..' */ if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ break; } /* Next entry */ } /* Return the current file name */ jx9_result_string(pCtx, zName, (int)n); return JX9_OK; } /* void (*xRewindDir)(void *) */ static void UnixDir_Rewind(void *pUserData) { rewinddir((DIR *)pUserData); } /* jx9_int64 (*xRead)(void *, void *, jx9_int64); */ static jx9_int64 UnixFile_Read(void *pUserData, void *pBuffer, jx9_int64 nDatatoRead) { ssize_t nRd; nRd = read(SX_PTR_TO_INT(pUserData), pBuffer, (size_t)nDatatoRead); if( nRd < 1 ){ /* EOF or IO error */ return -1; } return (jx9_int64)nRd; } /* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */ static jx9_int64 UnixFile_Write(void *pUserData, const void *pBuffer, jx9_int64 nWrite) { const char *zData = (const char *)pBuffer; int fd = SX_PTR_TO_INT(pUserData); jx9_int64 nCount; ssize_t nWr; nCount = 0; for(;;){ if( nWrite < 1 ){ break; } nWr = write(fd, zData, (size_t)nWrite); if( nWr < 1 ){ /* IO error */ break; } nWrite -= nWr; nCount += nWr; zData += nWr; } if( nWrite > 0 ){ return -1; } return nCount; } /* int (*xSeek)(void *, jx9_int64, int) */ static int UnixFile_Seek(void *pUserData, jx9_int64 iOfft, int whence) { off_t iNew; switch(whence){ case 1:/*SEEK_CUR*/ whence = SEEK_CUR; break; case 2: /* SEEK_END */ whence = SEEK_END; break; case 0: /* SEEK_SET */ default: whence = SEEK_SET; break; } iNew = lseek(SX_PTR_TO_INT(pUserData), (off_t)iOfft, whence); if( iNew < 0 ){ return -1; } return JX9_OK; } /* int (*xLock)(void *, int) */ static int UnixFile_Lock(void *pUserData, int lock_type) { int fd = SX_PTR_TO_INT(pUserData); int rc = JX9_OK; /* cc warning */ if( lock_type < 0 ){ /* Unlock the file */ rc = flock(fd, LOCK_UN); }else{ if( lock_type == 1 ){ /* Exculsive lock */ rc = flock(fd, LOCK_EX); }else{ /* Shared lock */ rc = flock(fd, LOCK_SH); } } return !rc ? JX9_OK : -1; } /* jx9_int64 (*xTell)(void *) */ static jx9_int64 UnixFile_Tell(void *pUserData) { off_t iNew; iNew = lseek(SX_PTR_TO_INT(pUserData), 0, SEEK_CUR); return (jx9_int64)iNew; } /* int (*xTrunc)(void *, jx9_int64) */ static int UnixFile_Trunc(void *pUserData, jx9_int64 nOfft) { int rc; rc = ftruncate(SX_PTR_TO_INT(pUserData), (off_t)nOfft); if( rc != 0 ){ return -1; } return JX9_OK; } /* int (*xSync)(void *); */ static int UnixFile_Sync(void *pUserData) { int rc; rc = fsync(SX_PTR_TO_INT(pUserData)); return rc == 0 ? JX9_OK : - 1; } /* int (*xStat)(void *, jx9_value *, jx9_value *) */ static int UnixFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker) { struct stat st; int rc; rc = fstat(SX_PTR_TO_INT(pUserData), &st); if( rc != 0 ){ return -1; } /* dev */ jx9_value_int64(pWorker, (jx9_int64)st.st_dev); jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ /* ino */ jx9_value_int64(pWorker, (jx9_int64)st.st_ino); jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ /* mode */ jx9_value_int(pWorker, (int)st.st_mode); jx9_array_add_strkey_elem(pArray, "mode", pWorker); /* nlink */ jx9_value_int(pWorker, (int)st.st_nlink); jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ /* uid, gid, rdev */ jx9_value_int(pWorker, (int)st.st_uid); jx9_array_add_strkey_elem(pArray, "uid", pWorker); jx9_value_int(pWorker, (int)st.st_gid); jx9_array_add_strkey_elem(pArray, "gid", pWorker); jx9_value_int(pWorker, (int)st.st_rdev); jx9_array_add_strkey_elem(pArray, "rdev", pWorker); /* size */ jx9_value_int64(pWorker, (jx9_int64)st.st_size); jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ /* atime */ jx9_value_int64(pWorker, (jx9_int64)st.st_atime); jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ /* mtime */ jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ /* ctime */ jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ /* blksize, blocks */ jx9_value_int(pWorker, (int)st.st_blksize); jx9_array_add_strkey_elem(pArray, "blksize", pWorker); jx9_value_int(pWorker, (int)st.st_blocks); jx9_array_add_strkey_elem(pArray, "blocks", pWorker); return JX9_OK; } /* Export the file:// stream */ static const jx9_io_stream sUnixFileStream = { "file", /* Stream name */ JX9_IO_STREAM_VERSION, UnixFile_Open, /* xOpen */ UnixDir_Open, /* xOpenDir */ UnixFile_Close, /* xClose */ UnixDir_Close, /* xCloseDir */ UnixFile_Read, /* xRead */ UnixDir_Read, /* xReadDir */ UnixFile_Write, /* xWrite */ UnixFile_Seek, /* xSeek */ UnixFile_Lock, /* xLock */ UnixDir_Rewind, /* xRewindDir */ UnixFile_Tell, /* xTell */ UnixFile_Trunc, /* xTrunc */ UnixFile_Sync, /* xSeek */ UnixFile_Stat /* xStat */ }; #endif /* __WINNT__/__UNIXES__ */ #endif /* JX9_DISABLE_DISK_IO */ #endif /* JX9_DISABLE_BUILTIN_FUNC */ /* * Export the builtin vfs. * Return a pointer to the builtin vfs if available. * Otherwise return the null_vfs [i.e: a no-op vfs] instead. * Note: * The built-in vfs is always available for Windows/UNIX systems. * Note: * If the engine is compiled with the JX9_DISABLE_DISK_IO/JX9_DISABLE_BUILTIN_FUNC * directives defined then this function return the null_vfs instead. */ JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void) { #ifndef JX9_DISABLE_BUILTIN_FUNC #ifdef JX9_DISABLE_DISK_IO return &null_vfs; #else #ifdef __WINNT__ return &sWinVfs; #elif defined(__UNIXES__) return &sUnixVfs; #else return &null_vfs; #endif /* __WINNT__/__UNIXES__ */ #endif /*JX9_DISABLE_DISK_IO*/ #else return &null_vfs; #endif /* JX9_DISABLE_BUILTIN_FUNC */ } #ifndef JX9_DISABLE_BUILTIN_FUNC #ifndef JX9_DISABLE_DISK_IO /* * The following defines are mostly used by the UNIX built and have * no particular meaning on windows. */ #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO #define STDERR_FILENO 2 #endif /* * jx9:// Accessing various I/O streams * According to the JX9 langage reference manual * JX9 provides a number of miscellaneous I/O streams that allow access to JX9's own input * and output streams, the standard input, output and error file descriptors. * jx9://stdin, jx9://stdout and jx9://stderr: * Allow direct access to the corresponding input or output stream of the JX9 process. * The stream references a duplicate file descriptor, so if you open jx9://stdin and later * close it, you close only your copy of the descriptor-the actual stream referenced by STDIN is unaffected. * jx9://stdin is read-only, whereas jx9://stdout and jx9://stderr are write-only. * jx9://output * jx9://output is a write-only stream that allows you to write to the output buffer * mechanism in the same way as print and print. */ typedef struct jx9_stream_data jx9_stream_data; /* Supported IO streams */ #define JX9_IO_STREAM_STDIN 1 /* jx9://stdin */ #define JX9_IO_STREAM_STDOUT 2 /* jx9://stdout */ #define JX9_IO_STREAM_STDERR 3 /* jx9://stderr */ #define JX9_IO_STREAM_OUTPUT 4 /* jx9://output */ /* The following structure is the private data associated with the jx9:// stream */ struct jx9_stream_data { jx9_vm *pVm; /* VM that own this instance */ int iType; /* Stream type */ union{ void *pHandle; /* Stream handle */ jx9_output_consumer sConsumer; /* VM output consumer */ }x; }; /* * Allocate a new instance of the jx9_stream_data structure. */ static jx9_stream_data * JX9StreamDataInit(jx9_vm *pVm, int iType) { jx9_stream_data *pData; if( pVm == 0 ){ return 0; } /* Allocate a new instance */ pData = (jx9_stream_data *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(jx9_stream_data)); if( pData == 0 ){ return 0; } /* Zero the structure */ SyZero(pData, sizeof(jx9_stream_data)); /* Initialize fields */ pData->iType = iType; if( iType == JX9_IO_STREAM_OUTPUT ){ /* Point to the default VM consumer routine. */ pData->x.sConsumer = pVm->sVmConsumer; }else{ #ifdef __WINNT__ DWORD nChannel; switch(iType){ case JX9_IO_STREAM_STDOUT: nChannel = STD_OUTPUT_HANDLE; break; case JX9_IO_STREAM_STDERR: nChannel = STD_ERROR_HANDLE; break; default: nChannel = STD_INPUT_HANDLE; break; } pData->x.pHandle = GetStdHandle(nChannel); #else /* Assume an UNIX system */ int ifd = STDIN_FILENO; switch(iType){ case JX9_IO_STREAM_STDOUT: ifd = STDOUT_FILENO; break; case JX9_IO_STREAM_STDERR: ifd = STDERR_FILENO; break; default: break; } pData->x.pHandle = SX_INT_TO_PTR(ifd); #endif } pData->pVm = pVm; return pData; } /* * Implementation of the jx9:// IO streams routines * Authors: * Symisc Systems, devel@symisc.net. * Copyright (C) Symisc Systems, http://jx9.symisc.net * Status: * Stable. */ /* int (*xOpen)(const char *, int, jx9_value *, void **) */ static int JX9StreamData_Open(const char *zName, int iMode, jx9_value *pResource, void ** ppHandle) { jx9_stream_data *pData; SyString sStream; SyStringInitFromBuf(&sStream, zName, SyStrlen(zName)); /* Trim leading and trailing white spaces */ SyStringFullTrim(&sStream); /* Stream to open */ if( SyStrnicmp(sStream.zString, "stdin", sizeof("stdin")-1) == 0 ){ iMode = JX9_IO_STREAM_STDIN; }else if( SyStrnicmp(sStream.zString, "output", sizeof("output")-1) == 0 ){ iMode = JX9_IO_STREAM_OUTPUT; }else if( SyStrnicmp(sStream.zString, "stdout", sizeof("stdout")-1) == 0 ){ iMode = JX9_IO_STREAM_STDOUT; }else if( SyStrnicmp(sStream.zString, "stderr", sizeof("stderr")-1) == 0 ){ iMode = JX9_IO_STREAM_STDERR; }else{ /* unknown stream name */ return -1; } /* Create our handle */ pData = JX9StreamDataInit(pResource?pResource->pVm:0, iMode); if( pData == 0 ){ return -1; } /* Make the handle public */ *ppHandle = (void *)pData; return JX9_OK; } /* jx9_int64 (*xRead)(void *, void *, jx9_int64) */ static jx9_int64 JX9StreamData_Read(void *pHandle, void *pBuffer, jx9_int64 nDatatoRead) { jx9_stream_data *pData = (jx9_stream_data *)pHandle; if( pData == 0 ){ return -1; } if( pData->iType != JX9_IO_STREAM_STDIN ){ /* Forbidden */ return -1; } #ifdef __WINNT__ { DWORD nRd; BOOL rc; rc = ReadFile(pData->x.pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0); if( !rc ){ /* IO error */ return -1; } return (jx9_int64)nRd; } #elif defined(__UNIXES__) { ssize_t nRd; int fd; fd = SX_PTR_TO_INT(pData->x.pHandle); nRd = read(fd, pBuffer, (size_t)nDatatoRead); if( nRd < 1 ){ return -1; } return (jx9_int64)nRd; } #else return -1; #endif } /* jx9_int64 (*xWrite)(void *, const void *, jx9_int64) */ static jx9_int64 JX9StreamData_Write(void *pHandle, const void *pBuf, jx9_int64 nWrite) { jx9_stream_data *pData = (jx9_stream_data *)pHandle; if( pData == 0 ){ return -1; } if( pData->iType == JX9_IO_STREAM_STDIN ){ /* Forbidden */ return -1; }else if( pData->iType == JX9_IO_STREAM_OUTPUT ){ jx9_output_consumer *pCons = &pData->x.sConsumer; int rc; /* Call the vm output consumer */ rc = pCons->xConsumer(pBuf, (unsigned int)nWrite, pCons->pUserData); if( rc == JX9_ABORT ){ return -1; } return nWrite; } #ifdef __WINNT__ { DWORD nWr; BOOL rc; rc = WriteFile(pData->x.pHandle, pBuf, (DWORD)nWrite, &nWr, 0); if( !rc ){ /* IO error */ return -1; } return (jx9_int64)nWr; } #elif defined(__UNIXES__) { ssize_t nWr; int fd; fd = SX_PTR_TO_INT(pData->x.pHandle); nWr = write(fd, pBuf, (size_t)nWrite); if( nWr < 1 ){ return -1; } return (jx9_int64)nWr; } #else return -1; #endif } /* void (*xClose)(void *) */ static void JX9StreamData_Close(void *pHandle) { jx9_stream_data *pData = (jx9_stream_data *)pHandle; jx9_vm *pVm; if( pData == 0 ){ return; } pVm = pData->pVm; /* Free the instance */ SyMemBackendFree(&pVm->sAllocator, pData); } /* Export the jx9:// stream */ static const jx9_io_stream sjx9Stream = { "jx9", JX9_IO_STREAM_VERSION, JX9StreamData_Open, /* xOpen */ 0, /* xOpenDir */ JX9StreamData_Close, /* xClose */ 0, /* xCloseDir */ JX9StreamData_Read, /* xRead */ 0, /* xReadDir */ JX9StreamData_Write, /* xWrite */ 0, /* xSeek */ 0, /* xLock */ 0, /* xRewindDir */ 0, /* xTell */ 0, /* xTrunc */ 0, /* xSeek */ 0 /* xStat */ }; #endif /* JX9_DISABLE_DISK_IO */ /* * Return TRUE if we are dealing with the jx9:// stream. * FALSE otherwise. */ static int is_jx9_stream(const jx9_io_stream *pStream) { #ifndef JX9_DISABLE_DISK_IO return pStream == &sjx9Stream; #else SXUNUSED(pStream); /* cc warning */ return 0; #endif /* JX9_DISABLE_DISK_IO */ } #endif /* JX9_DISABLE_BUILTIN_FUNC */ /* * Export the IO routines defined above and the built-in IO streams * [i.e: file://, jx9://]. * Note: * If the engine is compiled with the JX9_DISABLE_BUILTIN_FUNC directive * defined then this function is a no-op. */ JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm) { #ifndef JX9_DISABLE_BUILTIN_FUNC /* VFS functions */ static const jx9_builtin_func aVfsFunc[] = { {"chdir", jx9Vfs_chdir }, {"chroot", jx9Vfs_chroot }, {"getcwd", jx9Vfs_getcwd }, {"rmdir", jx9Vfs_rmdir }, {"is_dir", jx9Vfs_is_dir }, {"mkdir", jx9Vfs_mkdir }, {"rename", jx9Vfs_rename }, {"realpath", jx9Vfs_realpath}, {"sleep", jx9Vfs_sleep }, {"usleep", jx9Vfs_usleep }, {"unlink", jx9Vfs_unlink }, {"delete", jx9Vfs_unlink }, {"chmod", jx9Vfs_chmod }, {"chown", jx9Vfs_chown }, {"chgrp", jx9Vfs_chgrp }, {"disk_free_space", jx9Vfs_disk_free_space }, {"disk_total_space", jx9Vfs_disk_total_space}, {"file_exists", jx9Vfs_file_exists }, {"filesize", jx9Vfs_file_size }, {"fileatime", jx9Vfs_file_atime }, {"filemtime", jx9Vfs_file_mtime }, {"filectime", jx9Vfs_file_ctime }, {"is_file", jx9Vfs_is_file }, {"is_link", jx9Vfs_is_link }, {"is_readable", jx9Vfs_is_readable }, {"is_writable", jx9Vfs_is_writable }, {"is_executable", jx9Vfs_is_executable}, {"filetype", jx9Vfs_filetype }, {"stat", jx9Vfs_stat }, {"lstat", jx9Vfs_lstat }, {"getenv", jx9Vfs_getenv }, {"setenv", jx9Vfs_putenv }, {"putenv", jx9Vfs_putenv }, {"touch", jx9Vfs_touch }, {"link", jx9Vfs_link }, {"symlink", jx9Vfs_symlink }, {"umask", jx9Vfs_umask }, {"sys_get_temp_dir", jx9Vfs_sys_get_temp_dir }, {"get_current_user", jx9Vfs_get_current_user }, {"getpid", jx9Vfs_getmypid }, {"getuid", jx9Vfs_getmyuid }, {"getgid", jx9Vfs_getmygid }, {"uname", jx9Vfs_uname}, /* Path processing */ {"dirname", jx9Builtin_dirname }, {"basename", jx9Builtin_basename }, {"pathinfo", jx9Builtin_pathinfo }, {"strglob", jx9Builtin_strglob }, {"fnmatch", jx9Builtin_fnmatch }, /* ZIP processing */ {"zip_open", jx9Builtin_zip_open }, {"zip_close", jx9Builtin_zip_close}, {"zip_read", jx9Builtin_zip_read }, {"zip_entry_open", jx9Builtin_zip_entry_open }, {"zip_entry_close", jx9Builtin_zip_entry_close}, {"zip_entry_name", jx9Builtin_zip_entry_name }, {"zip_entry_filesize", jx9Builtin_zip_entry_filesize }, {"zip_entry_compressedsize", jx9Builtin_zip_entry_compressedsize }, {"zip_entry_read", jx9Builtin_zip_entry_read }, {"zip_entry_reset_cursor", jx9Builtin_zip_entry_reset_cursor}, {"zip_entry_compressionmethod", jx9Builtin_zip_entry_compressionmethod} }; /* IO stream functions */ static const jx9_builtin_func aIOFunc[] = { {"ftruncate", jx9Builtin_ftruncate }, {"fseek", jx9Builtin_fseek }, {"ftell", jx9Builtin_ftell }, {"rewind", jx9Builtin_rewind }, {"fflush", jx9Builtin_fflush }, {"feof", jx9Builtin_feof }, {"fgetc", jx9Builtin_fgetc }, {"fgets", jx9Builtin_fgets }, {"fread", jx9Builtin_fread }, {"fgetcsv", jx9Builtin_fgetcsv}, {"fgetss", jx9Builtin_fgetss }, {"readdir", jx9Builtin_readdir}, {"rewinddir", jx9Builtin_rewinddir }, {"closedir", jx9Builtin_closedir}, {"opendir", jx9Builtin_opendir }, {"readfile", jx9Builtin_readfile}, {"file_get_contents", jx9Builtin_file_get_contents}, {"file_put_contents", jx9Builtin_file_put_contents}, {"file", jx9Builtin_file }, {"copy", jx9Builtin_copy }, {"fstat", jx9Builtin_fstat }, {"fwrite", jx9Builtin_fwrite }, {"fputs", jx9Builtin_fwrite }, {"flock", jx9Builtin_flock }, {"fclose", jx9Builtin_fclose }, {"fopen", jx9Builtin_fopen }, {"fpassthru", jx9Builtin_fpassthru }, {"fputcsv", jx9Builtin_fputcsv }, {"fprintf", jx9Builtin_fprintf }, #if !defined(JX9_DISABLE_HASH_FUNC) {"md5_file", jx9Builtin_md5_file}, {"sha1_file", jx9Builtin_sha1_file}, #endif /* JX9_DISABLE_HASH_FUNC */ {"parse_ini_file", jx9Builtin_parse_ini_file}, {"vfprintf", jx9Builtin_vfprintf} }; const jx9_io_stream *pFileStream = 0; sxu32 n = 0; /* Register the functions defined above */ for( n = 0 ; n < SX_ARRAYSIZE(aVfsFunc) ; ++n ){ jx9_create_function(&(*pVm), aVfsFunc[n].zName, aVfsFunc[n].xFunc, (void *)pVm->pEngine->pVfs); } for( n = 0 ; n < SX_ARRAYSIZE(aIOFunc) ; ++n ){ jx9_create_function(&(*pVm), aIOFunc[n].zName, aIOFunc[n].xFunc, pVm); } #ifndef JX9_DISABLE_DISK_IO /* Register the file stream if available */ #ifdef __WINNT__ pFileStream = &sWinFileStream; #elif defined(__UNIXES__) pFileStream = &sUnixFileStream; #endif /* Install the jx9:// stream */ jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, &sjx9Stream); #endif /* JX9_DISABLE_DISK_IO */ if( pFileStream ){ /* Install the file:// stream */ jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, pFileStream); } #else SXUNUSED(pVm); /* cc warning */ #endif /* JX9_DISABLE_BUILTIN_FUNC */ return SXRET_OK; } /* * Export the STDIN handle. */ JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm) { #ifndef JX9_DISABLE_BUILTIN_FUNC #ifndef JX9_DISABLE_DISK_IO if( pVm->pStdin == 0 ){ io_private *pIn; /* Allocate an IO private instance */ pIn = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); if( pIn == 0 ){ return 0; } InitIOPrivate(pVm, &sjx9Stream, pIn); /* Initialize the handle */ pIn->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDIN); /* Install the STDIN stream */ pVm->pStdin = pIn; return pIn; }else{ /* NULL or STDIN */ return pVm->pStdin; } #else return 0; #endif #else SXUNUSED(pVm); /* cc warning */ return 0; #endif } /* * Export the STDOUT handle. */ JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm) { #ifndef JX9_DISABLE_BUILTIN_FUNC #ifndef JX9_DISABLE_DISK_IO if( pVm->pStdout == 0 ){ io_private *pOut; /* Allocate an IO private instance */ pOut = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); if( pOut == 0 ){ return 0; } InitIOPrivate(pVm, &sjx9Stream, pOut); /* Initialize the handle */ pOut->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDOUT); /* Install the STDOUT stream */ pVm->pStdout = pOut; return pOut; }else{ /* NULL or STDOUT */ return pVm->pStdout; } #else return 0; #endif #else SXUNUSED(pVm); /* cc warning */ return 0; #endif } /* * Export the STDERR handle. */ JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm) { #ifndef JX9_DISABLE_BUILTIN_FUNC #ifndef JX9_DISABLE_DISK_IO if( pVm->pStderr == 0 ){ io_private *pErr; /* Allocate an IO private instance */ pErr = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); if( pErr == 0 ){ return 0; } InitIOPrivate(pVm, &sjx9Stream, pErr); /* Initialize the handle */ pErr->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDERR); /* Install the STDERR stream */ pVm->pStderr = pErr; return pErr; }else{ /* NULL or STDERR */ return pVm->pStderr; } #else return 0; #endif #else SXUNUSED(pVm); /* cc warning */ return 0; #endif } /* * ---------------------------------------------------------- * File: jx9_vm.c * MD5: beca4be65a9a49c932c356d7680034c9 * ---------------------------------------------------------- */ /* * 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; } /* * ---------------------------------------------------------- * File: lhash_kv.c * MD5: 581b07ce2984fd95740677285d8a11d3 * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: lhash_kv.c v1.7 Solaris 2013-01-14 12:56 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* * This file implements disk based hashtable using the linear hashing algorithm. * This implementation is the one decribed in the paper: * LINEAR HASHING : A NEW TOOL FOR FILE AND TABLE ADDRESSING. Witold Litwin. I. N. Ft. I. A.. 78 150 Le Chesnay, France. * Plus a smart extension called Virtual Bucket Table. (contact devel@symisc.net for additional information). */ /* Magic number identifying a valid storage image */ #define L_HASH_MAGIC 0xFA782DCB /* * Magic word to hash to identify a valid hash function. */ #define L_HASH_WORD "chm@symisc" /* * Cell size on disk. */ #define L_HASH_CELL_SZ (4/*Hash*/+4/*Key*/+8/*Data*/+2/* Offset of the next cell */+8/*Overflow*/) /* * Primary page (not overflow pages) header size on disk. */ #define L_HASH_PAGE_HDR_SZ (2/* Cell offset*/+2/* Free block offset*/+8/*Slave page number*/) /* * The maximum amount of payload (in bytes) that can be stored locally for * a database entry. If the entry contains more data than this, the * extra goes onto overflow pages. */ #define L_HASH_MX_PAYLOAD(PageSize) (PageSize-(L_HASH_PAGE_HDR_SZ+L_HASH_CELL_SZ)) /* * Maxium free space on a single page. */ #define L_HASH_MX_FREE_SPACE(PageSize) (PageSize - (L_HASH_PAGE_HDR_SZ)) /* ** The maximum number of bytes of payload allowed on a single overflow page. */ #define L_HASH_OVERFLOW_SIZE(PageSize) (PageSize-8) /* Forward declaration */ typedef struct lhash_kv_engine lhash_kv_engine; typedef struct lhpage lhpage; /* * Each record in the database is identified either in-memory or in * disk by an instance of the following structure. */ typedef struct lhcell lhcell; struct lhcell { /* Disk-data (Big-Endian) */ sxu32 nHash; /* Hash of the key: 4 bytes */ sxu32 nKey; /* Key length: 4 bytes */ sxu64 nData; /* Data length: 8 bytes */ sxu16 iNext; /* Offset of the next cell: 2 bytes */ pgno iOvfl; /* Overflow page number if any: 8 bytes */ /* In-memory data only */ lhpage *pPage; /* Page this cell belongs */ sxu16 iStart; /* Offset of this cell */ pgno iDataPage; /* Data page number when overflow */ sxu16 iDataOfft; /* Offset of the data in iDataPage */ SyBlob sKey; /* Record key for fast lookup (Kept in-memory if < 256KB ) */ lhcell *pNext,*pPrev; /* Linked list of the loaded memory cells */ lhcell *pNextCol,*pPrevCol; /* Collison chain */ }; /* ** Each database page has a header that is an instance of this ** structure. */ typedef struct lhphdr lhphdr; struct lhphdr { sxu16 iOfft; /* Offset of the first cell */ sxu16 iFree; /* Offset of the first free block*/ pgno iSlave; /* Slave page number */ }; /* * Each loaded primary disk page is represented in-memory using * an instance of the following structure. */ struct lhpage { lhash_kv_engine *pHash; /* KV Storage engine that own this page */ unqlite_page *pRaw; /* Raw page contents */ lhphdr sHdr; /* Processed page header */ lhcell **apCell; /* Cell buckets */ lhcell *pList,*pFirst; /* Linked list of cells */ sxu32 nCell; /* Total number of cells */ sxu32 nCellSize; /* apCell[] size */ lhpage *pMaster; /* Master page in case we are dealing with a slave page */ lhpage *pSlave; /* List of slave pages */ lhpage *pNextSlave; /* Next slave page on the list */ sxi32 iSlave; /* Total number of slave pages */ sxu16 nFree; /* Amount of free space available in the page */ }; /* * A Bucket map record which is used to map logical bucket number to real * bucket number is represented by an instance of the following structure. */ typedef struct lhash_bmap_rec lhash_bmap_rec; struct lhash_bmap_rec { pgno iLogic; /* Logical bucket number */ pgno iReal; /* Real bucket number */ lhash_bmap_rec *pNext,*pPrev; /* Link to other bucket map */ lhash_bmap_rec *pNextCol,*pPrevCol; /* Collision links */ }; typedef struct lhash_bmap_page lhash_bmap_page; struct lhash_bmap_page { pgno iNum; /* Page number where this entry is stored */ sxu16 iPtr; /* Offset to start reading/writing from */ sxu32 nRec; /* Total number of records in this page */ pgno iNext; /* Next map page */ }; /* * An in memory linear hash implemenation is represented by in an isntance * of the following structure. */ struct lhash_kv_engine { const unqlite_kv_io *pIo; /* IO methods: Must be first */ /* Private fields */ SyMemBackend sAllocator; /* Private memory backend */ ProcHash xHash; /* Default hash function */ ProcCmp xCmp; /* Default comparison function */ unqlite_page *pHeader; /* Page one to identify a valid implementation */ lhash_bmap_rec **apMap; /* Buckets map records */ sxu32 nBuckRec; /* Total number of bucket map records */ sxu32 nBuckSize; /* apMap[] size */ lhash_bmap_rec *pList; /* List of bucket map records */ lhash_bmap_rec *pFirst; /* First record*/ lhash_bmap_page sPageMap; /* Primary bucket map */ int iPageSize; /* Page size */ pgno nFreeList; /* List of free pages */ pgno split_bucket; /* Current split bucket: MUST BE A POWER OF TWO */ pgno max_split_bucket; /* Maximum split bucket: MUST BE A POWER OF TWO */ pgno nmax_split_nucket; /* Next maximum split bucket (1 << nMsb): In-memory only */ sxu32 nMagic; /* Magic number to identify a valid linear hash disk database */ }; /* * Given a logical bucket number, return the record associated with it. */ static lhash_bmap_rec * lhMapFindBucket(lhash_kv_engine *pEngine,pgno iLogic) { lhash_bmap_rec *pRec; if( pEngine->nBuckRec < 1 ){ /* Don't bother */ return 0; } pRec = pEngine->apMap[iLogic & (pEngine->nBuckSize - 1)]; for(;;){ if( pRec == 0 ){ break; } if( pRec->iLogic == iLogic ){ return pRec; } /* Point to the next entry */ pRec = pRec->pNextCol; } /* No such record */ return 0; } /* * Install a new bucket map record. */ static int lhMapInstallBucket(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal) { lhash_bmap_rec *pRec; sxu32 iBucket; /* Allocate a new instance */ pRec = (lhash_bmap_rec *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhash_bmap_rec)); if( pRec == 0 ){ return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pRec,sizeof(lhash_bmap_rec)); /* Fill in the structure */ pRec->iLogic = iLogic; pRec->iReal = iReal; iBucket = iLogic & (pEngine->nBuckSize - 1); pRec->pNextCol = pEngine->apMap[iBucket]; if( pEngine->apMap[iBucket] ){ pEngine->apMap[iBucket]->pPrevCol = pRec; } pEngine->apMap[iBucket] = pRec; /* Link */ if( pEngine->pFirst == 0 ){ pEngine->pFirst = pEngine->pList = pRec; }else{ MACRO_LD_PUSH(pEngine->pList,pRec); } pEngine->nBuckRec++; if( (pEngine->nBuckRec >= pEngine->nBuckSize * 3) && pEngine->nBuckRec < 100000 ){ /* Allocate a new larger table */ sxu32 nNewSize = pEngine->nBuckSize << 1; lhash_bmap_rec *pEntry; lhash_bmap_rec **apNew; sxu32 n; apNew = (lhash_bmap_rec **)SyMemBackendAlloc(&pEngine->sAllocator, nNewSize * sizeof(lhash_bmap_rec *)); if( apNew ){ /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(lhash_bmap_rec *)); /* Rehash all entries */ n = 0; pEntry = pEngine->pList; for(;;){ /* Loop one */ if( n >= pEngine->nBuckRec ){ break; } pEntry->pNextCol = pEntry->pPrevCol = 0; /* Install in the new bucket */ iBucket = pEntry->iLogic & (nNewSize - 1); pEntry->pNextCol = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevCol = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; } /* Release the old table and reflect the change */ SyMemBackendFree(&pEngine->sAllocator,(void *)pEngine->apMap); pEngine->apMap = apNew; pEngine->nBuckSize = nNewSize; } } return UNQLITE_OK; } /* * Process a raw bucket map record. */ static int lhMapLoadPage(lhash_kv_engine *pEngine,lhash_bmap_page *pMap,const unsigned char *zRaw) { const unsigned char *zEnd = &zRaw[pEngine->iPageSize]; const unsigned char *zPtr = zRaw; pgno iLogic,iReal; sxu32 n; int rc; if( pMap->iPtr == 0 ){ /* Read the map header */ SyBigEndianUnpack64(zRaw,&pMap->iNext); zRaw += 8; SyBigEndianUnpack32(zRaw,&pMap->nRec); zRaw += 4; }else{ /* Mostly page one of the database */ zRaw += pMap->iPtr; } /* Start processing */ for( n = 0; n < pMap->nRec ; ++n ){ if( zRaw >= zEnd ){ break; } /* Extract the logical and real bucket number */ SyBigEndianUnpack64(zRaw,&iLogic); zRaw += 8; SyBigEndianUnpack64(zRaw,&iReal); zRaw += 8; /* Install the record in the map */ rc = lhMapInstallBucket(pEngine,iLogic,iReal); if( rc != UNQLITE_OK ){ return rc; } } pMap->iPtr = (sxu16)(zRaw-zPtr); /* All done */ return UNQLITE_OK; } /* * Allocate a new cell instance. */ static lhcell * lhNewCell(lhash_kv_engine *pEngine,lhpage *pPage) { lhcell *pCell; pCell = (lhcell *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhcell)); if( pCell == 0 ){ return 0; } /* Zero the structure */ SyZero(pCell,sizeof(lhcell)); /* Fill in the structure */ SyBlobInit(&pCell->sKey,&pEngine->sAllocator); pCell->pPage = pPage; return pCell; } /* * Discard a cell from the page table. */ static void lhCellDiscard(lhcell *pCell) { lhpage *pPage = pCell->pPage->pMaster; if( pCell->pPrevCol ){ pCell->pPrevCol->pNextCol = pCell->pNextCol; }else{ pPage->apCell[pCell->nHash & (pPage->nCellSize - 1)] = pCell->pNextCol; } if( pCell->pNextCol ){ pCell->pNextCol->pPrevCol = pCell->pPrevCol; } MACRO_LD_REMOVE(pPage->pList,pCell); if( pCell == pPage->pFirst ){ pPage->pFirst = pCell->pPrev; } pPage->nCell--; /* Release the cell */ SyBlobRelease(&pCell->sKey); SyMemBackendPoolFree(&pPage->pHash->sAllocator,pCell); } /* * Install a cell in the page table. */ static int lhInstallCell(lhcell *pCell) { lhpage *pPage = pCell->pPage->pMaster; sxu32 iBucket; if( pPage->nCell < 1 ){ sxu32 nTableSize = 32; /* Must be a power of two */ lhcell **apTable; /* Allocate a new cell table */ apTable = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nTableSize * sizeof(lhcell *)); if( apTable == 0 ){ return UNQLITE_NOMEM; } /* Zero the new table */ SyZero((void *)apTable, nTableSize * sizeof(lhcell *)); /* Install it */ pPage->apCell = apTable; pPage->nCellSize = nTableSize; } iBucket = pCell->nHash & (pPage->nCellSize - 1); pCell->pNextCol = pPage->apCell[iBucket]; if( pPage->apCell[iBucket] ){ pPage->apCell[iBucket]->pPrevCol = pCell; } pPage->apCell[iBucket] = pCell; if( pPage->pFirst == 0 ){ pPage->pFirst = pPage->pList = pCell; }else{ MACRO_LD_PUSH(pPage->pList,pCell); } pPage->nCell++; if( (pPage->nCell >= pPage->nCellSize * 3) && pPage->nCell < 100000 ){ /* Allocate a new larger table */ sxu32 nNewSize = pPage->nCellSize << 1; lhcell *pEntry; lhcell **apNew; sxu32 n; apNew = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nNewSize * sizeof(lhcell *)); if( apNew ){ /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(lhcell *)); /* Rehash all entries */ n = 0; pEntry = pPage->pList; for(;;){ /* Loop one */ if( n >= pPage->nCell ){ break; } pEntry->pNextCol = pEntry->pPrevCol = 0; /* Install in the new bucket */ iBucket = pEntry->nHash & (nNewSize - 1); pEntry->pNextCol = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevCol = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; } /* Release the old table and reflect the change */ SyMemBackendFree(&pPage->pHash->sAllocator,(void *)pPage->apCell); pPage->apCell = apNew; pPage->nCellSize = nNewSize; } } return UNQLITE_OK; } /* * Private data of lhKeyCmp(). */ struct lhash_key_cmp { const char *zIn; /* Start of the stream */ const char *zEnd; /* End of the stream */ ProcCmp xCmp; /* Comparison function */ }; /* * Comparsion callback for large key > 256 KB */ static int lhKeyCmp(const void *pData,sxu32 nLen,void *pUserData) { struct lhash_key_cmp *pCmp = (struct lhash_key_cmp *)pUserData; int rc; if( pCmp->zIn >= pCmp->zEnd ){ if( nLen > 0 ){ return UNQLITE_ABORT; } return UNQLITE_OK; } /* Perform the comparison */ rc = pCmp->xCmp((const void *)pCmp->zIn,pData,nLen); if( rc != 0 ){ /* Abort comparison */ return UNQLITE_ABORT; } /* Advance the cursor */ pCmp->zIn += nLen; return UNQLITE_OK; } /* Forward declaration */ static int lhConsumeCellkey(lhcell *pCell,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData,int offt_only); /* * given a key, return the cell associated with it on success. NULL otherwise. */ static lhcell * lhFindCell( lhpage *pPage, /* Target page */ const void *pKey, /* Lookup key */ sxu32 nByte, /* Key length */ sxu32 nHash /* Hash of the key */ ) { lhcell *pEntry; if( pPage->nCell < 1 ){ /* Don't bother hashing */ return 0; } /* Point to the corresponding bucket */ pEntry = pPage->apCell[nHash & (pPage->nCellSize - 1)]; for(;;){ if( pEntry == 0 ){ break; } if( pEntry->nHash == nHash && pEntry->nKey == nByte ){ if( SyBlobLength(&pEntry->sKey) < 1 ){ /* Large key (> 256 KB) are not kept in-memory */ struct lhash_key_cmp sCmp; int rc; /* Fill-in the structure */ sCmp.zIn = (const char *)pKey; sCmp.zEnd = &sCmp.zIn[nByte]; sCmp.xCmp = pPage->pHash->xCmp; /* Fetch the key from disk and perform the comparison */ rc = lhConsumeCellkey(pEntry,lhKeyCmp,&sCmp,0); if( rc == UNQLITE_OK ){ /* Cell found */ return pEntry; } }else if ( pPage->pHash->xCmp(pKey,SyBlobData(&pEntry->sKey),nByte) == 0 ){ /* Cell found */ return pEntry; } } /* Point to the next entry */ pEntry = pEntry->pNextCol; } /* No such entry */ return 0; } /* * Parse a raw cell fetched from disk. */ static int lhParseOneCell(lhpage *pPage,const unsigned char *zRaw,const unsigned char *zEnd,lhcell **ppOut) { sxu16 iNext,iOfft; sxu32 iHash,nKey; lhcell *pCell; sxu64 nData; int rc; /* Offset this cell is stored */ iOfft = (sxu16)(zRaw - (const unsigned char *)pPage->pRaw->zData); /* 4 byte hash number */ SyBigEndianUnpack32(zRaw,&iHash); zRaw += 4; /* 4 byte key length */ SyBigEndianUnpack32(zRaw,&nKey); zRaw += 4; /* 8 byte data length */ SyBigEndianUnpack64(zRaw,&nData); zRaw += 8; /* 2 byte offset of the next cell */ SyBigEndianUnpack16(zRaw,&iNext); /* Perform a sanity check */ if( iNext > 0 && &pPage->pRaw->zData[iNext] >= zEnd ){ return UNQLITE_CORRUPT; } zRaw += 2; pCell = lhNewCell(pPage->pHash,pPage); if( pCell == 0 ){ return UNQLITE_NOMEM; } /* Fill in the structure */ pCell->iNext = iNext; pCell->nKey = nKey; pCell->nData = nData; pCell->nHash = iHash; /* Overflow page if any */ SyBigEndianUnpack64(zRaw,&pCell->iOvfl); zRaw += 8; /* Cell offset */ pCell->iStart = iOfft; /* Consume the key */ rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,pCell->nKey > 262144 /* 256 KB */? 1 : 0); if( rc != UNQLITE_OK ){ /* TICKET: 14-32-chm@symisc.net: Key too large for memory */ SyBlobRelease(&pCell->sKey); } /* Finally install the cell */ rc = lhInstallCell(pCell); if( rc != UNQLITE_OK ){ return rc; } if( ppOut ){ *ppOut = pCell; } return UNQLITE_OK; } /* * Compute the total number of free space on a given page. */ static int lhPageFreeSpace(lhpage *pPage) { const unsigned char *zEnd,*zRaw = pPage->pRaw->zData; lhphdr *pHdr = &pPage->sHdr; sxu16 iNext,iAmount; sxu16 nFree = 0; if( pHdr->iFree < 1 ){ /* Don't bother processing, the page is full */ pPage->nFree = 0; return UNQLITE_OK; } /* Point to first free block */ zEnd = &zRaw[pPage->pHash->iPageSize]; zRaw += pHdr->iFree; for(;;){ /* Offset of the next free block */ SyBigEndianUnpack16(zRaw,&iNext); zRaw += 2; /* Available space on this block */ SyBigEndianUnpack16(zRaw,&iAmount); nFree += iAmount; if( iNext < 1 ){ /* No more free blocks */ break; } /* Point to the next free block*/ zRaw = &pPage->pRaw->zData[iNext]; if( zRaw >= zEnd ){ /* Corrupt page */ return UNQLITE_CORRUPT; } } /* Save the amount of free space */ pPage->nFree = nFree; return UNQLITE_OK; } /* * Given a primary page, load all its cell. */ static int lhLoadCells(lhpage *pPage) { const unsigned char *zEnd,*zRaw = pPage->pRaw->zData; lhphdr *pHdr = &pPage->sHdr; lhcell *pCell = 0; /* cc warning */ int rc; /* Calculate the amount of free space available first */ rc = lhPageFreeSpace(pPage); if( rc != UNQLITE_OK ){ return rc; } if( pHdr->iOfft < 1 ){ /* Don't bother processing, the page is empty */ return UNQLITE_OK; } /* Point to first cell */ zRaw += pHdr->iOfft; zEnd = &zRaw[pPage->pHash->iPageSize]; for(;;){ /* Parse a single cell */ rc = lhParseOneCell(pPage,zRaw,zEnd,&pCell); if( rc != UNQLITE_OK ){ return rc; } if( pCell->iNext < 1 ){ /* No more cells */ break; } /* Point to the next cell */ zRaw = &pPage->pRaw->zData[pCell->iNext]; if( zRaw >= zEnd ){ /* Corrupt page */ return UNQLITE_CORRUPT; } } /* All done */ return UNQLITE_OK; } /* * Given a page, parse its raw headers. */ static int lhParsePageHeader(lhpage *pPage) { const unsigned char *zRaw = pPage->pRaw->zData; lhphdr *pHdr = &pPage->sHdr; /* Offset of the first cell */ SyBigEndianUnpack16(zRaw,&pHdr->iOfft); zRaw += 2; /* Offset of the first free block */ SyBigEndianUnpack16(zRaw,&pHdr->iFree); zRaw += 2; /* Slave page number */ SyBigEndianUnpack64(zRaw,&pHdr->iSlave); /* All done */ return UNQLITE_OK; } /* * Allocate a new page instance. */ static lhpage * lhNewPage( lhash_kv_engine *pEngine, /* KV store which own this instance */ unqlite_page *pRaw, /* Raw page contents */ lhpage *pMaster /* Master page in case we are dealing with a slave page */ ) { lhpage *pPage; /* Allocate a new instance */ pPage = (lhpage *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhpage)); if( pPage == 0 ){ return 0; } /* Zero the structure */ SyZero(pPage,sizeof(lhpage)); /* Fill-in the structure */ pPage->pHash = pEngine; pPage->pRaw = pRaw; pPage->pMaster = pMaster ? pMaster /* Slave page */ : pPage /* Master page */ ; if( pPage->pMaster != pPage ){ /* Slave page, attach it to its master */ pPage->pNextSlave = pMaster->pSlave; pMaster->pSlave = pPage; pMaster->iSlave++; } /* Save this instance for future fast lookup */ pRaw->pUserData = pPage; /* All done */ return pPage; } /* * Load a primary and its associated slave pages from disk. */ static int lhLoadPage(lhash_kv_engine *pEngine,pgno pnum,lhpage *pMaster,lhpage **ppOut,int iNest) { unqlite_page *pRaw; lhpage *pPage = 0; /* cc warning */ int rc; /* Aquire the page from the pager first */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pnum,&pRaw); if( rc != UNQLITE_OK ){ return rc; } if( pRaw->pUserData ){ /* The page is already parsed and loaded in memory. Point to it */ pPage = (lhpage *)pRaw->pUserData; }else{ /* Allocate a new page */ pPage = lhNewPage(pEngine,pRaw,pMaster); if( pPage == 0 ){ return UNQLITE_NOMEM; } /* Process the page */ rc = lhParsePageHeader(pPage); if( rc == UNQLITE_OK ){ /* Load cells */ rc = lhLoadCells(pPage); } if( rc != UNQLITE_OK ){ pEngine->pIo->xPageUnref(pPage->pRaw); /* pPage will be released inside this call */ return rc; } if( pPage->sHdr.iSlave > 0 && iNest < 128 ){ if( pMaster == 0 ){ pMaster = pPage; } /* Slave page. Not a fatal error if something goes wrong here */ lhLoadPage(pEngine,pPage->sHdr.iSlave,pMaster,0,iNest++); } } if( ppOut ){ *ppOut = pPage; } return UNQLITE_OK; } /* * Given a cell, Consume its key by invoking the given callback for each extracted chunk. */ static int lhConsumeCellkey( lhcell *pCell, /* Target cell */ int (*xConsumer)(const void *,unsigned int,void *), /* Consumer callback */ void *pUserData, /* Last argument to xConsumer() */ int offt_only ) { lhpage *pPage = pCell->pPage; const unsigned char *zRaw = pPage->pRaw->zData; const unsigned char *zPayload; int rc; /* Point to the payload area */ zPayload = &zRaw[pCell->iStart]; if( pCell->iOvfl == 0 ){ /* Best scenario, consume the key directly without any overflow page */ zPayload += L_HASH_CELL_SZ; rc = xConsumer((const void *)zPayload,pCell->nKey,pUserData); if( rc != UNQLITE_OK ){ rc = UNQLITE_ABORT; } }else{ lhash_kv_engine *pEngine = pPage->pHash; sxu32 nByte,nData = pCell->nKey; unqlite_page *pOvfl; int data_offset = 0; pgno iOvfl; /* Overflow page */ iOvfl = pCell->iOvfl; /* Total usable bytes in an overflow page */ nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize); for(;;){ if( iOvfl == 0 || nData < 1 ){ /* no more overflow page */ break; } /* Point to the overflow page */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl); if( rc != UNQLITE_OK ){ return rc; } zPayload = &pOvfl->zData[8]; /* Point to the raw content */ if( !data_offset ){ /* Get the data page and offset */ SyBigEndianUnpack64(zPayload,&pCell->iDataPage); zPayload += 8; SyBigEndianUnpack16(zPayload,&pCell->iDataOfft); zPayload += 2; if( offt_only ){ /* Key too large, grab the data offset and return */ pEngine->pIo->xPageUnref(pOvfl); return UNQLITE_OK; } data_offset = 1; } /* Consume the key */ if( nData <= nByte ){ rc = xConsumer((const void *)zPayload,nData,pUserData); if( rc != UNQLITE_OK ){ pEngine->pIo->xPageUnref(pOvfl); return UNQLITE_ABORT; } nData = 0; }else{ rc = xConsumer((const void *)zPayload,nByte,pUserData); if( rc != UNQLITE_OK ){ pEngine->pIo->xPageUnref(pOvfl); return UNQLITE_ABORT; } nData -= nByte; } /* Next overflow page in the chain */ SyBigEndianUnpack64(pOvfl->zData,&iOvfl); /* Unref the page */ pEngine->pIo->xPageUnref(pOvfl); } rc = UNQLITE_OK; } return rc; } /* * Given a cell, Consume its data by invoking the given callback for each extracted chunk. */ static int lhConsumeCellData( lhcell *pCell, /* Target cell */ int (*xConsumer)(const void *,unsigned int,void *), /* Data consumer callback */ void *pUserData /* Last argument to xConsumer() */ ) { lhpage *pPage = pCell->pPage; const unsigned char *zRaw = pPage->pRaw->zData; const unsigned char *zPayload; int rc; /* Point to the payload area */ zPayload = &zRaw[pCell->iStart]; if( pCell->iOvfl == 0 ){ /* Best scenario, consume the data directly without any overflow page */ zPayload += L_HASH_CELL_SZ + pCell->nKey; rc = xConsumer((const void *)zPayload,(sxu32)pCell->nData,pUserData); if( rc != UNQLITE_OK ){ rc = UNQLITE_ABORT; } }else{ lhash_kv_engine *pEngine = pPage->pHash; sxu64 nData = pCell->nData; unqlite_page *pOvfl; int fix_offset = 0; sxu32 nByte; pgno iOvfl; /* Overflow page where data is stored */ iOvfl = pCell->iDataPage; for(;;){ if( iOvfl == 0 || nData < 1 ){ /* no more overflow page */ break; } /* Point to the overflow page */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl); if( rc != UNQLITE_OK ){ return rc; } /* Point to the raw content */ zPayload = pOvfl->zData; if( !fix_offset ){ /* Point to the data */ zPayload += pCell->iDataOfft; nByte = pEngine->iPageSize - pCell->iDataOfft; fix_offset = 1; }else{ zPayload += 8; /* Total usable bytes in an overflow page */ nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize); } /* Consume the data */ if( nData <= (sxu64)nByte ){ rc = xConsumer((const void *)zPayload,(unsigned int)nData,pUserData); if( rc != UNQLITE_OK ){ pEngine->pIo->xPageUnref(pOvfl); return UNQLITE_ABORT; } nData = 0; }else{ if( nByte > 0 ){ rc = xConsumer((const void *)zPayload,nByte,pUserData); if( rc != UNQLITE_OK ){ pEngine->pIo->xPageUnref(pOvfl); return UNQLITE_ABORT; } nData -= nByte; } } /* Next overflow page in the chain */ SyBigEndianUnpack64(pOvfl->zData,&iOvfl); /* Unref the page */ pEngine->pIo->xPageUnref(pOvfl); } rc = UNQLITE_OK; } return rc; } /* * Read the linear hash header (Page one of the database). */ static int lhash_read_header(lhash_kv_engine *pEngine,unqlite_page *pHeader) { const unsigned char *zRaw = pHeader->zData; lhash_bmap_page *pMap; sxu32 nHash; int rc; pEngine->pHeader = pHeader; /* 4 byte magic number */ SyBigEndianUnpack32(zRaw,&pEngine->nMagic); zRaw += 4; if( pEngine->nMagic != L_HASH_MAGIC ){ /* Corrupt implementation */ return UNQLITE_CORRUPT; } /* 4 byte hash value to identify a valid hash function */ SyBigEndianUnpack32(zRaw,&nHash); zRaw += 4; /* Sanity check */ if( pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1) != nHash ){ /* Different hash function */ pEngine->pIo->xErr(pEngine->pIo->pHandle,"Invalid hash function"); return UNQLITE_INVALID; } /* List of free pages */ SyBigEndianUnpack64(zRaw,&pEngine->nFreeList); zRaw += 8; /* Current split bucket */ SyBigEndianUnpack64(zRaw,&pEngine->split_bucket); zRaw += 8; /* Maximum split bucket */ SyBigEndianUnpack64(zRaw,&pEngine->max_split_bucket); zRaw += 8; /* Next generation */ pEngine->nmax_split_nucket = pEngine->max_split_bucket << 1; /* Initialiaze the bucket map */ pMap = &pEngine->sPageMap; /* Fill in the structure */ pMap->iNum = pHeader->pgno; /* Next page in the bucket map */ SyBigEndianUnpack64(zRaw,&pMap->iNext); zRaw += 8; /* Total number of records in the bucket map (This page only) */ SyBigEndianUnpack32(zRaw,&pMap->nRec); zRaw += 4; pMap->iPtr = (sxu16)(zRaw - pHeader->zData); /* Load the map in memory */ rc = lhMapLoadPage(pEngine,pMap,pHeader->zData); if( rc != UNQLITE_OK ){ return rc; } /* Load the bucket map chain if any */ for(;;){ pgno iNext = pMap->iNext; unqlite_page *pPage; if( iNext == 0 ){ /* No more map pages */ break; } /* Point to the target page */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pPage); if( rc != UNQLITE_OK ){ return rc; } /* Fill in the structure */ pMap->iNum = iNext; pMap->iPtr = 0; /* Load the map in memory */ rc = lhMapLoadPage(pEngine,pMap,pPage->zData); if( rc != UNQLITE_OK ){ return rc; } } /* All done */ return UNQLITE_OK; } /* * Perform a record lookup. */ static int lhRecordLookup( lhash_kv_engine *pEngine, /* KV storage engine */ const void *pKey, /* Lookup key */ sxu32 nByte, /* Key length */ lhcell **ppCell /* OUT: Target cell on success */ ) { lhash_bmap_rec *pRec; lhpage *pPage; lhcell *pCell; pgno iBucket; sxu32 nHash; int rc; /* Acquire the first page (hash Header) so that everything gets loaded autmatically */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); if( rc != UNQLITE_OK ){ return rc; } /* Compute the hash of the key first */ nHash = pEngine->xHash(pKey,nByte); /* Extract the logical (i.e. not real) page number */ iBucket = nHash & (pEngine->nmax_split_nucket - 1); if( iBucket >= (pEngine->split_bucket + pEngine->max_split_bucket) ){ /* Low mask */ iBucket = nHash & (pEngine->max_split_bucket - 1); } /* Map the logical bucket number to real page number */ pRec = lhMapFindBucket(pEngine,iBucket); if( pRec == 0 ){ /* No such entry */ return UNQLITE_NOTFOUND; } /* Load the master page and it's slave page in-memory */ rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0); if( rc != UNQLITE_OK ){ /* IO error, unlikely scenario */ return rc; } /* Lookup for the cell */ pCell = lhFindCell(pPage,pKey,nByte,nHash); if( pCell == 0 ){ /* No such entry */ return UNQLITE_NOTFOUND; } if( ppCell ){ *ppCell = pCell; } return UNQLITE_OK; } /* * Acquire a new page either from the free list or ask the pager * for a new one. */ static int lhAcquirePage(lhash_kv_engine *pEngine,unqlite_page **ppOut) { unqlite_page *pPage; int rc; if( pEngine->nFreeList != 0 ){ /* Acquire one from the free list */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pEngine->nFreeList,&pPage); if( rc == UNQLITE_OK ){ /* Point to the next free page */ SyBigEndianUnpack64(pPage->zData,&pEngine->nFreeList); /* Update the database header */ rc = pEngine->pIo->xWrite(pEngine->pHeader); if( rc != UNQLITE_OK ){ return rc; } SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList); /* Tell the pager do not journal this page */ pEngine->pIo->xDontJournal(pPage); /* Return to the caller */ *ppOut = pPage; /* All done */ return UNQLITE_OK; } } /* Acquire a new page */ rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pPage); if( rc != UNQLITE_OK ){ return rc; } /* Point to the target page */ *ppOut = pPage; return UNQLITE_OK; } /* * Write a bucket map record to disk. */ static int lhMapWriteRecord(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal) { lhash_bmap_page *pMap = &pEngine->sPageMap; unqlite_page *pPage = 0; int rc; if( pMap->iPtr > (pEngine->iPageSize - 16) /* 8 byte logical bucket number + 8 byte real bucket number */ ){ unqlite_page *pOld; /* Point to the old page */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pOld); if( rc != UNQLITE_OK ){ return rc; } /* Acquire a new page */ rc = lhAcquirePage(pEngine,&pPage); if( rc != UNQLITE_OK ){ return rc; } /* Reflect the change */ pMap->iNext = 0; pMap->iNum = pPage->pgno; pMap->nRec = 0; pMap->iPtr = 8/* Next page number */+4/* Total records in the map*/; /* Link this page */ rc = pEngine->pIo->xWrite(pOld); if( rc != UNQLITE_OK ){ return rc; } if( pOld->pgno == pEngine->pHeader->pgno ){ /* First page (Hash header) */ SyBigEndianPack64(&pOld->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/],pPage->pgno); }else{ /* Link the new page */ SyBigEndianPack64(pOld->zData,pPage->pgno); /* Unref */ pEngine->pIo->xPageUnref(pOld); } /* Assume the last bucket map page */ rc = pEngine->pIo->xWrite(pPage); if( rc != UNQLITE_OK ){ return rc; } SyBigEndianPack64(pPage->zData,0); /* Next bucket map page on the list */ } if( pPage == 0){ /* Point to the current map page */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pPage); if( rc != UNQLITE_OK ){ return rc; } } /* Make page writable */ rc = pEngine->pIo->xWrite(pPage); if( rc != UNQLITE_OK ){ return rc; } /* Write the data */ SyBigEndianPack64(&pPage->zData[pMap->iPtr],iLogic); pMap->iPtr += 8; SyBigEndianPack64(&pPage->zData[pMap->iPtr],iReal); pMap->iPtr += 8; /* Install the bucket map */ rc = lhMapInstallBucket(pEngine,iLogic,iReal); if( rc == UNQLITE_OK ){ /* Total number of records */ pMap->nRec++; if( pPage->pgno == pEngine->pHeader->pgno ){ /* Page one: Always writable */ SyBigEndianPack32( &pPage->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/+8/*Next map page*/], pMap->nRec); }else{ /* Make page writable */ rc = pEngine->pIo->xWrite(pPage); if( rc != UNQLITE_OK ){ return rc; } SyBigEndianPack32(&pPage->zData[8],pMap->nRec); } } return rc; } /* * Defragment a page. */ static int lhPageDefragment(lhpage *pPage) { lhash_kv_engine *pEngine = pPage->pHash; unsigned char *zTmp,*zPtr,*zEnd,*zPayload; lhcell *pCell; /* Get a temporary page from the pager. This opertaion never fail */ zTmp = pEngine->pIo->xTmpPage(pEngine->pIo->pHandle); /* Move the target cells to the beginning */ pCell = pPage->pMaster->pList; /* Write the slave page number */ SyBigEndianPack64(&zTmp[2/*Offset of the first cell */+2/*Offset of the first free block */],pPage->sHdr.iSlave); zPtr = &zTmp[L_HASH_PAGE_HDR_SZ]; /* Offset to start writing from */ zEnd = &zTmp[pEngine->iPageSize]; pPage->sHdr.iOfft = 0; /* Offset of the first cell */ for(;;){ if( pCell == 0 ){ /* No more cells */ break; } if( pCell->pPage->pRaw->pgno == pPage->pRaw->pgno ){ /* Cell payload if locally stored */ zPayload = 0; if( pCell->iOvfl == 0 ){ zPayload = &pCell->pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ]; } /* Move the cell */ pCell->iNext = pPage->sHdr.iOfft; pCell->iStart = (sxu16)(zPtr - zTmp); /* Offset where this cell start */ pPage->sHdr.iOfft = pCell->iStart; /* Write the cell header */ /* 4 byte hash number */ SyBigEndianPack32(zPtr,pCell->nHash); zPtr += 4; /* 4 byte ley length */ SyBigEndianPack32(zPtr,pCell->nKey); zPtr += 4; /* 8 byte data length */ SyBigEndianPack64(zPtr,pCell->nData); zPtr += 8; /* 2 byte offset of the next cell */ SyBigEndianPack16(zPtr,pCell->iNext); zPtr += 2; /* 8 byte overflow page number */ SyBigEndianPack64(zPtr,pCell->iOvfl); zPtr += 8; if( zPayload ){ /* Local payload */ SyMemcpy((const void *)zPayload,zPtr,(sxu32)(pCell->nKey + pCell->nData)); zPtr += pCell->nKey + pCell->nData; } if( zPtr >= zEnd ){ /* Can't happen */ break; } } /* Point to the next page */ pCell = pCell->pNext; } /* Mark the free block */ pPage->nFree = (sxu16)(zEnd - zPtr); /* Block length */ if( pPage->nFree > 3 ){ pPage->sHdr.iFree = (sxu16)(zPtr - zTmp); /* Offset of the free block */ /* Mark the block */ SyBigEndianPack16(zPtr,0); /* Offset of the next free block */ SyBigEndianPack16(&zPtr[2],pPage->nFree); /* Block length */ }else{ /* Block of length less than 4 bytes are simply discarded */ pPage->nFree = 0; pPage->sHdr.iFree = 0; } /* Reflect the change */ SyBigEndianPack16(zTmp,pPage->sHdr.iOfft); /* Offset of the first cell */ SyBigEndianPack16(&zTmp[2],pPage->sHdr.iFree); /* Offset of the first free block */ SyMemcpy((const void *)zTmp,pPage->pRaw->zData,pEngine->iPageSize); /* All done */ return UNQLITE_OK; } /* ** Allocate nByte bytes of space on a page. ** ** Return the index into pPage->pRaw->zData[] of the first byte of ** the new allocation. Or return 0 if there is not enough free ** space on the page to satisfy the allocation request. ** ** If the page contains nBytes of free space but does not contain ** nBytes of contiguous free space, then this routine automatically ** calls defragementPage() to consolidate all free space before ** allocating the new chunk. */ static int lhAllocateSpace(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft) { const unsigned char *zEnd,*zPtr; sxu16 iNext,iBlksz,nByte; unsigned char *zPrev; int rc; if( (sxu64)pPage->nFree < nAmount ){ /* Don't bother looking for a free chunk */ return UNQLITE_FULL; } if( pPage->nCell < 10 && ((int)nAmount >= (pPage->pHash->iPageSize / 2)) ){ /* Big chunk need an overflow page for its data */ return UNQLITE_FULL; } zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree]; zEnd = &pPage->pRaw->zData[pPage->pHash->iPageSize]; nByte = (sxu16)nAmount; zPrev = 0; iBlksz = 0; /* cc warning */ /* Perform the lookup */ for(;;){ if( zPtr >= zEnd ){ return UNQLITE_FULL; } /* Offset of the next free block */ SyBigEndianUnpack16(zPtr,&iNext); /* Block size */ SyBigEndianUnpack16(&zPtr[2],&iBlksz); if( iBlksz >= nByte ){ /* Got one */ break; } zPrev = (unsigned char *)zPtr; if( iNext == 0 ){ /* No more free blocks, defragment the page */ rc = lhPageDefragment(pPage); if( rc == UNQLITE_OK && pPage->nFree >= nByte) { /* Free blocks are merged together */ iNext = 0; zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree]; iBlksz = pPage->nFree; zPrev = 0; break; }else{ return UNQLITE_FULL; } } /* Point to the next free block */ zPtr = &pPage->pRaw->zData[iNext]; } /* Acquire writer lock on this page */ rc = pPage->pHash->pIo->xWrite(pPage->pRaw); if( rc != UNQLITE_OK ){ return rc; } /* Save block offset */ *pOfft = (sxu16)(zPtr - pPage->pRaw->zData); /* Fix pointers */ if( iBlksz >= nByte && (iBlksz - nByte) > 3 ){ unsigned char *zBlock = &pPage->pRaw->zData[(*pOfft) + nByte]; /* Create a new block */ zPtr = zBlock; SyBigEndianPack16(zBlock,iNext); /* Offset of the next block */ SyBigEndianPack16(&zBlock[2],iBlksz-nByte); /* Block size*/ /* Offset of the new block */ iNext = (sxu16)(zPtr - pPage->pRaw->zData); iBlksz = nByte; } /* Fix offsets */ if( zPrev ){ SyBigEndianPack16(zPrev,iNext); }else{ /* First block */ pPage->sHdr.iFree = iNext; /* Reflect on the page header */ SyBigEndianPack16(&pPage->pRaw->zData[2/* Offset of the first cell1*/],iNext); } /* All done */ pPage->nFree -= iBlksz; return UNQLITE_OK; } /* * Write the cell header into the corresponding offset. */ static int lhCellWriteHeader(lhcell *pCell) { lhpage *pPage = pCell->pPage; unsigned char *zRaw = pPage->pRaw->zData; /* Seek to the desired location */ zRaw += pCell->iStart; /* 4 byte hash number */ SyBigEndianPack32(zRaw,pCell->nHash); zRaw += 4; /* 4 byte key length */ SyBigEndianPack32(zRaw,pCell->nKey); zRaw += 4; /* 8 byte data length */ SyBigEndianPack64(zRaw,pCell->nData); zRaw += 8; /* 2 byte offset of the next cell */ pCell->iNext = pPage->sHdr.iOfft; SyBigEndianPack16(zRaw,pCell->iNext); zRaw += 2; /* 8 byte overflow page number */ SyBigEndianPack64(zRaw,pCell->iOvfl); /* Update the page header */ pPage->sHdr.iOfft = pCell->iStart; /* pEngine->pIo->xWrite() has been successfully called on this page */ SyBigEndianPack16(pPage->pRaw->zData,pCell->iStart); /* All done */ return UNQLITE_OK; } /* * Write local payload. */ static int lhCellWriteLocalPayload(lhcell *pCell, const void *pKey,sxu32 nKeylen, const void *pData,unqlite_int64 nDatalen ) { /* A writer lock have been acquired on this page */ lhpage *pPage = pCell->pPage; unsigned char *zRaw = pPage->pRaw->zData; /* Seek to the desired location */ zRaw += pCell->iStart + L_HASH_CELL_SZ; /* Write the key */ SyMemcpy(pKey,(void *)zRaw,nKeylen); zRaw += nKeylen; if( nDatalen > 0 ){ /* Write the Data */ SyMemcpy(pData,(void *)zRaw,(sxu32)nDatalen); } return UNQLITE_OK; } /* * Allocate as much overflow page we need to store the cell payload. */ static int lhCellWriteOvflPayload(lhcell *pCell,const void *pKey,sxu32 nKeylen,...) { lhpage *pPage = pCell->pPage; lhash_kv_engine *pEngine = pPage->pHash; unqlite_page *pOvfl,*pFirst,*pNew; const unsigned char *zPtr,*zEnd; unsigned char *zRaw,*zRawEnd; sxu32 nAvail; va_list ap; int rc; /* Acquire a new overflow page */ rc = lhAcquirePage(pEngine,&pOvfl); if( rc != UNQLITE_OK ){ return rc; } /* Acquire a writer lock */ rc = pEngine->pIo->xWrite(pOvfl); if( rc != UNQLITE_OK ){ return rc; } pFirst = pOvfl; /* Link */ pCell->iOvfl = pOvfl->pgno; /* Update the cell header */ SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4/*Hash*/ + 4/*Key*/ + 8/*Data*/ + 2 /*Next cell*/],pCell->iOvfl); /* Start the write process */ zPtr = (const unsigned char *)pKey; zEnd = &zPtr[nKeylen]; SyBigEndianPack64(pOvfl->zData,0); /* Next overflow page on the chain */ zRaw = &pOvfl->zData[8/* Next ovfl page*/ + 8 /* Data page */ + 2 /* Data offset*/]; zRawEnd = &pOvfl->zData[pEngine->iPageSize]; pNew = pOvfl; /* Write the key */ for(;;){ if( zPtr >= zEnd ){ break; } if( zRaw >= zRawEnd ){ /* Acquire a new page */ rc = lhAcquirePage(pEngine,&pNew); if( rc != UNQLITE_OK ){ return rc; } rc = pEngine->pIo->xWrite(pNew); if( rc != UNQLITE_OK ){ return rc; } /* Link */ SyBigEndianPack64(pOvfl->zData,pNew->pgno); pEngine->pIo->xPageUnref(pOvfl); SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ pOvfl = pNew; zRaw = &pNew->zData[8]; zRawEnd = &pNew->zData[pEngine->iPageSize]; } nAvail = (sxu32)(zRawEnd-zRaw); nKeylen = (sxu32)(zEnd-zPtr); if( nKeylen > nAvail ){ nKeylen = nAvail; } SyMemcpy((const void *)zPtr,(void *)zRaw,nKeylen); /* Synchronize pointers */ zPtr += nKeylen; zRaw += nKeylen; } rc = UNQLITE_OK; va_start(ap,nKeylen); pCell->iDataPage = pNew->pgno; pCell->iDataOfft = (sxu16)(zRaw-pNew->zData); /* Write the data page and its offset */ SyBigEndianPack64(&pFirst->zData[8/*Next ovfl*/],pCell->iDataPage); SyBigEndianPack16(&pFirst->zData[8/*Next ovfl*/+8/*Data page*/],pCell->iDataOfft); /* Write data */ for(;;){ const void *pData; sxu32 nDatalen; sxu64 nData; pData = va_arg(ap,const void *); nData = va_arg(ap,sxu64); if( pData == 0 ){ /* No more chunks */ break; } /* Write this chunk */ zPtr = (const unsigned char *)pData; zEnd = &zPtr[nData]; for(;;){ if( zPtr >= zEnd ){ break; } if( zRaw >= zRawEnd ){ /* Acquire a new page */ rc = lhAcquirePage(pEngine,&pNew); if( rc != UNQLITE_OK ){ va_end(ap); return rc; } rc = pEngine->pIo->xWrite(pNew); if( rc != UNQLITE_OK ){ va_end(ap); return rc; } /* Link */ SyBigEndianPack64(pOvfl->zData,pNew->pgno); pEngine->pIo->xPageUnref(pOvfl); SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ pOvfl = pNew; zRaw = &pNew->zData[8]; zRawEnd = &pNew->zData[pEngine->iPageSize]; } nAvail = (sxu32)(zRawEnd-zRaw); nDatalen = (sxu32)(zEnd-zPtr); if( nDatalen > nAvail ){ nDatalen = nAvail; } SyMemcpy((const void *)zPtr,(void *)zRaw,nDatalen); /* Synchronize pointers */ zPtr += nDatalen; zRaw += nDatalen; } } /* Unref the overflow page */ pEngine->pIo->xPageUnref(pOvfl); va_end(ap); return UNQLITE_OK; } /* * Restore a page to the free list. */ static int lhRestorePage(lhash_kv_engine *pEngine,unqlite_page *pPage) { int rc; rc = pEngine->pIo->xWrite(pEngine->pHeader); if( rc != UNQLITE_OK ){ return rc; } rc = pEngine->pIo->xWrite(pPage); if( rc != UNQLITE_OK ){ return rc; } /* Link to the list of free page */ SyBigEndianPack64(pPage->zData,pEngine->nFreeList); pEngine->nFreeList = pPage->pgno; SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList); /* All done */ return UNQLITE_OK; } /* * Restore cell space and mark it as a free block. */ static int lhRestoreSpace(lhpage *pPage,sxu16 iOfft,sxu16 nByte) { unsigned char *zRaw; if( nByte < 4 ){ /* At least 4 bytes of freespace must form a valid block */ return UNQLITE_OK; } /* pEngine->pIo->xWrite() has been successfully called on this page */ zRaw = &pPage->pRaw->zData[iOfft]; /* Mark as a free block */ SyBigEndianPack16(zRaw,pPage->sHdr.iFree); /* Offset of the next free block */ zRaw += 2; SyBigEndianPack16(zRaw,nByte); /* Link */ SyBigEndianPack16(&pPage->pRaw->zData[2/* offset of the first cell */],iOfft); pPage->sHdr.iFree = iOfft; pPage->nFree += nByte; return UNQLITE_OK; } /* Forward declaration */ static lhcell * lhFindSibeling(lhcell *pCell); /* * Unlink a cell. */ static int lhUnlinkCell(lhcell *pCell) { lhash_kv_engine *pEngine = pCell->pPage->pHash; lhpage *pPage = pCell->pPage; sxu16 nByte = L_HASH_CELL_SZ; lhcell *pPrev; int rc; rc = pEngine->pIo->xWrite(pPage->pRaw); if( rc != UNQLITE_OK ){ return rc; } /* Bring the link */ pPrev = lhFindSibeling(pCell); if( pPrev ){ pPrev->iNext = pCell->iNext; /* Fix offsets in the page header */ SyBigEndianPack16(&pPage->pRaw->zData[pPrev->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext); }else{ /* First entry on this page (either master or slave) */ pPage->sHdr.iOfft = pCell->iNext; /* Update the page header */ SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext); } /* Restore cell space */ if( pCell->iOvfl == 0 ){ nByte += (sxu16)(pCell->nData + pCell->nKey); } lhRestoreSpace(pPage,pCell->iStart,nByte); /* Discard the cell from the in-memory hashtable */ lhCellDiscard(pCell); return UNQLITE_OK; } /* * Remove a cell and its paylod (key + data). */ static int lhRecordRemove(lhcell *pCell) { lhash_kv_engine *pEngine = pCell->pPage->pHash; int rc; if( pCell->iOvfl > 0){ /* Discard overflow pages */ unqlite_page *pOvfl; pgno iNext = pCell->iOvfl; for(;;){ /* Point to the overflow page */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pOvfl); if( rc != UNQLITE_OK ){ return rc; } /* Next page on the chain */ SyBigEndianUnpack64(pOvfl->zData,&iNext); /* Restore the page to the free list */ rc = lhRestorePage(pEngine,pOvfl); if( rc != UNQLITE_OK ){ return rc; } /* Unref */ pEngine->pIo->xPageUnref(pOvfl); if( iNext == 0 ){ break; } } } /* Unlink the cell */ rc = lhUnlinkCell(pCell); return rc; } /* * Find cell sibeling. */ static lhcell * lhFindSibeling(lhcell *pCell) { lhpage *pPage = pCell->pPage->pMaster; lhcell *pEntry; pEntry = pPage->pFirst; while( pEntry ){ if( pEntry->pPage == pCell->pPage && pEntry->iNext == pCell->iStart ){ /* Sibeling found */ return pEntry; } /* Point to the previous entry */ pEntry = pEntry->pPrev; } /* Last inserted cell */ return 0; } /* * Move a cell to a new location with its new data. */ static int lhMoveLocalCell( lhcell *pCell, sxu16 iOfft, const void *pData, unqlite_int64 nData ) { sxu16 iKeyOfft = pCell->iStart + L_HASH_CELL_SZ; lhpage *pPage = pCell->pPage; lhcell *pSibeling; pSibeling = lhFindSibeling(pCell); if( pSibeling ){ /* Fix link */ SyBigEndianPack16(&pPage->pRaw->zData[pSibeling->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext); pSibeling->iNext = pCell->iNext; }else{ /* First cell, update page header only */ SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext); pPage->sHdr.iOfft = pCell->iNext; } /* Set the new offset */ pCell->iStart = iOfft; pCell->nData = (sxu64)nData; /* Write the cell payload */ lhCellWriteLocalPayload(pCell,(const void *)&pPage->pRaw->zData[iKeyOfft],pCell->nKey,pData,nData); /* Finally write the cell header */ lhCellWriteHeader(pCell); /* All done */ return UNQLITE_OK; } /* * Overwrite an existing record. */ static int lhRecordOverwrite( lhcell *pCell, const void *pData,unqlite_int64 nByte ) { lhash_kv_engine *pEngine = pCell->pPage->pHash; unsigned char *zRaw,*zRawEnd,*zPayload; const unsigned char *zPtr,*zEnd; unqlite_page *pOvfl,*pOld,*pNew; lhpage *pPage = pCell->pPage; sxu32 nAvail; pgno iOvfl; int rc; /* Acquire a writer lock on this page */ rc = pEngine->pIo->xWrite(pPage->pRaw); if( rc != UNQLITE_OK ){ return rc; } if( pCell->iOvfl == 0 ){ /* Local payload, try to deal with the free space issues */ zPayload = &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey]; if( pCell->nData == (sxu64)nByte ){ /* Best scenario, simply a memcpy operation */ SyMemcpy(pData,(void *)zPayload,(sxu32)nByte); }else if( (sxu64)nByte < pCell->nData ){ /* Shorter data, not so ugly */ SyMemcpy(pData,(void *)zPayload,(sxu32)nByte); /* Update the cell header */ SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],nByte); /* Restore freespace */ lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ + pCell->nKey + nByte),(sxu16)(pCell->nData - nByte)); /* New data size */ pCell->nData = (sxu64)nByte; }else{ sxu16 iOfft = 0; /* cc warning */ /* Check if another chunk is available for this cell */ rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + nByte,&iOfft); if( rc != UNQLITE_OK ){ /* Transfer the payload to an overflow page */ rc = lhCellWriteOvflPayload(pCell,&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey,pData,nByte,(const void *)0); if( rc != UNQLITE_OK ){ return rc; } /* Update the cell header */ SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],(sxu64)nByte); /* Restore freespace */ lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData)); /* New data size */ pCell->nData = (sxu64)nByte; }else{ sxu16 iOldOfft = pCell->iStart; sxu32 iOld = (sxu32)pCell->nData; /* Space is available, transfer the cell */ lhMoveLocalCell(pCell,iOfft,pData,nByte); /* Restore cell space */ lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld)); } } return UNQLITE_OK; } /* Point to the overflow page */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl); if( rc != UNQLITE_OK ){ return rc; } /* Relase all old overflow pages first */ SyBigEndianUnpack64(pOvfl->zData,&iOvfl); pOld = pOvfl; for(;;){ if( iOvfl == 0 ){ /* No more overflow pages on the chain */ break; } /* Point to the target page */ if( UNQLITE_OK != pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOld) ){ /* Not so fatal if something goes wrong here */ break; } /* Next overflow page to be released */ SyBigEndianUnpack64(pOld->zData,&iOvfl); if( pOld != pOvfl ){ /* xx: chm is maniac */ /* Restore the page to the free list */ lhRestorePage(pEngine,pOld); /* Unref */ pEngine->pIo->xPageUnref(pOld); } } /* Point to the data offset */ zRaw = &pOvfl->zData[pCell->iDataOfft]; zRawEnd = &pOvfl->zData[pEngine->iPageSize]; /* The data to be stored */ zPtr = (const unsigned char *)pData; zEnd = &zPtr[nByte]; /* Start the overwrite process */ /* Acquire a writer lock */ rc = pEngine->pIo->xWrite(pOvfl); if( rc != UNQLITE_OK ){ return rc; } SyBigEndianPack64(pOvfl->zData,0); for(;;){ sxu32 nLen; if( zPtr >= zEnd ){ break; } if( zRaw >= zRawEnd ){ /* Acquire a new page */ rc = lhAcquirePage(pEngine,&pNew); if( rc != UNQLITE_OK ){ return rc; } rc = pEngine->pIo->xWrite(pNew); if( rc != UNQLITE_OK ){ return rc; } /* Link */ SyBigEndianPack64(pOvfl->zData,pNew->pgno); pEngine->pIo->xPageUnref(pOvfl); SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ pOvfl = pNew; zRaw = &pNew->zData[8]; zRawEnd = &pNew->zData[pEngine->iPageSize]; } nAvail = (sxu32)(zRawEnd-zRaw); nLen = (sxu32)(zEnd-zPtr); if( nLen > nAvail ){ nLen = nAvail; } SyMemcpy((const void *)zPtr,(void *)zRaw,nLen); /* Synchronize pointers */ zPtr += nLen; zRaw += nLen; } /* Unref the last overflow page */ pEngine->pIo->xPageUnref(pOvfl); /* Finally, update the cell header */ pCell->nData = (sxu64)nByte; SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData); /* All done */ return UNQLITE_OK; } /* * Append data to an existing record. */ static int lhRecordAppend( lhcell *pCell, const void *pData,unqlite_int64 nByte ) { lhash_kv_engine *pEngine = pCell->pPage->pHash; const unsigned char *zPtr,*zEnd; lhpage *pPage = pCell->pPage; unsigned char *zRaw,*zRawEnd; unqlite_page *pOvfl,*pNew; sxu64 nDatalen; sxu32 nAvail; pgno iOvfl; int rc; if( pCell->nData + nByte < pCell->nData ){ /* Overflow */ pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); return UNQLITE_LIMIT; } /* Acquire a writer lock on this page */ rc = pEngine->pIo->xWrite(pPage->pRaw); if( rc != UNQLITE_OK ){ return rc; } if( pCell->iOvfl == 0 ){ sxu16 iOfft = 0; /* cc warning */ /* Local payload, check for a bigger place */ rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + pCell->nData + nByte,&iOfft); if( rc != UNQLITE_OK ){ /* Transfer the payload to an overflow page */ rc = lhCellWriteOvflPayload(pCell, &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey, (const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],pCell->nData, pData,nByte, (const void *)0); if( rc != UNQLITE_OK ){ return rc; } /* Update the cell header */ SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData + nByte); /* Restore freespace */ lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData)); /* New data size */ pCell->nData += nByte; }else{ sxu16 iOldOfft = pCell->iStart; sxu32 iOld = (sxu32)pCell->nData; SyBlob sWorker; SyBlobInit(&sWorker,&pEngine->sAllocator); /* Copy the old data */ rc = SyBlobAppend(&sWorker,(const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],(sxu32)pCell->nData); if( rc == SXRET_OK ){ /* Append the new data */ rc = SyBlobAppend(&sWorker,pData,(sxu32)nByte); } if( rc != UNQLITE_OK ){ SyBlobRelease(&sWorker); return rc; } /* Space is available, transfer the cell */ lhMoveLocalCell(pCell,iOfft,SyBlobData(&sWorker),(unqlite_int64)SyBlobLength(&sWorker)); /* Restore cell space */ lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld)); /* All done */ SyBlobRelease(&sWorker); } return UNQLITE_OK; } /* Point to the overflow page which hold the data */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl); if( rc != UNQLITE_OK ){ return rc; } /* Next overflow page in the chain */ SyBigEndianUnpack64(pOvfl->zData,&iOvfl); /* Point to the end of the chunk */ zRaw = &pOvfl->zData[pCell->iDataOfft]; zRawEnd = &pOvfl->zData[pEngine->iPageSize]; nDatalen = pCell->nData; nAvail = (sxu32)(zRawEnd - zRaw); for(;;){ if( zRaw >= zRawEnd ){ if( iOvfl == 0 ){ /* Cant happen */ pEngine->pIo->xErr(pEngine->pIo->pHandle,"Corrupt overflow page"); return UNQLITE_CORRUPT; } rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pNew); if( rc != UNQLITE_OK ){ return rc; } /* Next overflow page on the chain */ SyBigEndianUnpack64(pNew->zData,&iOvfl); /* Unref the previous overflow page */ pEngine->pIo->xPageUnref(pOvfl); /* Point to the new chunk */ zRaw = &pNew->zData[8]; zRawEnd = &pNew->zData[pCell->pPage->pHash->iPageSize]; nAvail = L_HASH_OVERFLOW_SIZE(pCell->pPage->pHash->iPageSize); pOvfl = pNew; } if( (sxu64)nAvail > nDatalen ){ zRaw += nDatalen; break; }else{ nDatalen -= nAvail; } zRaw += nAvail; } /* Start the append process */ zPtr = (const unsigned char *)pData; zEnd = &zPtr[nByte]; /* Acquire a writer lock */ rc = pEngine->pIo->xWrite(pOvfl); if( rc != UNQLITE_OK ){ return rc; } for(;;){ sxu32 nLen; if( zPtr >= zEnd ){ break; } if( zRaw >= zRawEnd ){ /* Acquire a new page */ rc = lhAcquirePage(pEngine,&pNew); if( rc != UNQLITE_OK ){ return rc; } rc = pEngine->pIo->xWrite(pNew); if( rc != UNQLITE_OK ){ return rc; } /* Link */ SyBigEndianPack64(pOvfl->zData,pNew->pgno); pEngine->pIo->xPageUnref(pOvfl); SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ pOvfl = pNew; zRaw = &pNew->zData[8]; zRawEnd = &pNew->zData[pEngine->iPageSize]; } nAvail = (sxu32)(zRawEnd-zRaw); nLen = (sxu32)(zEnd-zPtr); if( nLen > nAvail ){ nLen = nAvail; } SyMemcpy((const void *)zPtr,(void *)zRaw,nLen); /* Synchronize pointers */ zPtr += nLen; zRaw += nLen; } /* Unref the last overflow page */ pEngine->pIo->xPageUnref(pOvfl); /* Finally, update the cell header */ pCell->nData += nByte; SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData); /* All done */ return UNQLITE_OK; } /* * A write privilege have been acquired on this page. * Mark it as an empty page (No cells). */ static int lhSetEmptyPage(lhpage *pPage) { unsigned char *zRaw = pPage->pRaw->zData; lhphdr *pHeader = &pPage->sHdr; sxu16 nByte; int rc; /* Acquire a writer lock */ rc = pPage->pHash->pIo->xWrite(pPage->pRaw); if( rc != UNQLITE_OK ){ return rc; } /* Offset of the first cell */ SyBigEndianPack16(zRaw,0); zRaw += 2; /* Offset of the first free block */ pHeader->iFree = L_HASH_PAGE_HDR_SZ; SyBigEndianPack16(zRaw,L_HASH_PAGE_HDR_SZ); zRaw += 2; /* Slave page number */ SyBigEndianPack64(zRaw,0); zRaw += 8; /* Fill the free block */ SyBigEndianPack16(zRaw,0); /* Offset of the next free block */ zRaw += 2; nByte = (sxu16)L_HASH_MX_FREE_SPACE(pPage->pHash->iPageSize); SyBigEndianPack16(zRaw,nByte); pPage->nFree = nByte; /* Do not add this page to the hot dirty list */ pPage->pHash->pIo->xDontMkHot(pPage->pRaw); return UNQLITE_OK; } /* Forward declaration */ static int lhSlaveStore( lhpage *pPage, const void *pKey,sxu32 nKeyLen, const void *pData,unqlite_int64 nDataLen, sxu32 nHash ); /* * Store a cell and its payload in a given page. */ static int lhStoreCell( lhpage *pPage, /* Target page */ const void *pKey,sxu32 nKeyLen, /* Payload: Key */ const void *pData,unqlite_int64 nDataLen, /* Payload: Data */ sxu32 nHash, /* Hash of the key */ int auto_append /* Auto append a slave page if full */ ) { lhash_kv_engine *pEngine = pPage->pHash; int iNeedOvfl = 0; /* Need overflow page for this cell and its payload*/ lhcell *pCell; sxu16 nOfft; int rc; /* Acquire a writer lock on this page first */ rc = pEngine->pIo->xWrite(pPage->pRaw); if( rc != UNQLITE_OK ){ return rc; } /* Check for a free block */ rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ+nKeyLen+nDataLen,&nOfft); if( rc != UNQLITE_OK ){ /* Check for a free block to hold a single cell only (without payload) */ rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft); if( rc != UNQLITE_OK ){ if( !auto_append ){ /* A split must be done */ return UNQLITE_FULL; }else{ /* Store this record in a slave page */ rc = lhSlaveStore(pPage,pKey,nKeyLen,pData,nDataLen,nHash); return rc; } } iNeedOvfl = 1; } /* Allocate a new cell instance */ pCell = lhNewCell(pEngine,pPage); if( pCell == 0 ){ pEngine->pIo->xErr(pEngine->pIo->pHandle,"KV store is running out of memory"); return UNQLITE_NOMEM; } /* Fill-in the structure */ pCell->iStart = nOfft; pCell->nKey = nKeyLen; pCell->nData = (sxu64)nDataLen; pCell->nHash = nHash; if( nKeyLen < 262144 /* 256 KB */ ){ /* Keep the key in-memory for fast lookup */ SyBlobAppend(&pCell->sKey,pKey,nKeyLen); } /* Link the cell */ rc = lhInstallCell(pCell); if( rc != UNQLITE_OK ){ return rc; } /* Write the payload */ if( iNeedOvfl ){ rc = lhCellWriteOvflPayload(pCell,pKey,nKeyLen,pData,nDataLen,(const void *)0); if( rc != UNQLITE_OK ){ lhCellDiscard(pCell); return rc; } }else{ lhCellWriteLocalPayload(pCell,pKey,nKeyLen,pData,nDataLen); } /* Finally, Write the cell header */ lhCellWriteHeader(pCell); /* All done */ return UNQLITE_OK; } /* * Find a slave page capable of hosting the given amount. */ static int lhFindSlavePage(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft,lhpage **ppSlave) { lhash_kv_engine *pEngine = pPage->pHash; lhpage *pMaster = pPage->pMaster; lhpage *pSlave = pMaster->pSlave; unqlite_page *pRaw; lhpage *pNew; sxu16 iOfft; sxi32 i; int rc; /* Look for an already attached slave page */ for( i = 0 ; i < pMaster->iSlave ; ++i ){ /* Find a free chunk big enough */ sxu16 size = (sxu16)(L_HASH_CELL_SZ + nAmount); rc = lhAllocateSpace(pSlave,size,&iOfft); if( rc != UNQLITE_OK ){ /* A space for cell header only */ size = L_HASH_CELL_SZ; rc = lhAllocateSpace(pSlave,size,&iOfft); } if( rc == UNQLITE_OK ){ /* All done */ if( pOfft ){ *pOfft = iOfft; }else{ rc = lhRestoreSpace(pSlave, iOfft, size); } *ppSlave = pSlave; return rc; } /* Point to the next slave page */ pSlave = pSlave->pNextSlave; } /* Acquire a new slave page */ rc = lhAcquirePage(pEngine,&pRaw); if( rc != UNQLITE_OK ){ return rc; } /* Last slave page */ pSlave = pMaster->pSlave; if( pSlave == 0 ){ /* First slave page */ pSlave = pMaster; } /* Initialize the page */ pNew = lhNewPage(pEngine,pRaw,pMaster); if( pNew == 0 ){ return UNQLITE_NOMEM; } /* Mark as an empty page */ rc = lhSetEmptyPage(pNew); if( rc != UNQLITE_OK ){ goto fail; } if( pOfft ){ /* Look for a free block */ if( UNQLITE_OK != lhAllocateSpace(pNew,L_HASH_CELL_SZ+nAmount,&iOfft) ){ /* Cell header only */ lhAllocateSpace(pNew,L_HASH_CELL_SZ,&iOfft); /* Never fail */ } *pOfft = iOfft; } /* Link this page to the previous slave page */ rc = pEngine->pIo->xWrite(pSlave->pRaw); if( rc != UNQLITE_OK ){ goto fail; } /* Reflect in the page header */ SyBigEndianPack64(&pSlave->pRaw->zData[2/*Cell offset*/+2/*Free block offset*/],pRaw->pgno); pSlave->sHdr.iSlave = pRaw->pgno; /* All done */ *ppSlave = pNew; return UNQLITE_OK; fail: pEngine->pIo->xPageUnref(pNew->pRaw); /* pNew will be released in this call */ return rc; } /* * Perform a store operation in a slave page. */ static int lhSlaveStore( lhpage *pPage, /* Master page */ const void *pKey,sxu32 nKeyLen, /* Payload: key */ const void *pData,unqlite_int64 nDataLen, /* Payload: data */ sxu32 nHash /* Hash of the key */ ) { lhpage *pSlave; int rc; /* Find a slave page */ rc = lhFindSlavePage(pPage,nKeyLen + nDataLen,0,&pSlave); if( rc != UNQLITE_OK ){ return rc; } /* Perform the insertion in the slave page */ rc = lhStoreCell(pSlave,pKey,nKeyLen,pData,nDataLen,nHash,1); return rc; } /* * Transfer a cell to a new page (either a master or slave). */ static int lhTransferCell(lhcell *pTarget,lhpage *pPage) { lhcell *pCell; sxu16 nOfft; int rc; /* Check for a free block to hold a single cell only */ rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft); if( rc != UNQLITE_OK ){ /* Store in a slave page */ rc = lhFindSlavePage(pPage,L_HASH_CELL_SZ,&nOfft,&pPage); if( rc != UNQLITE_OK ){ return rc; } } /* Allocate a new cell instance */ pCell = lhNewCell(pPage->pHash,pPage); if( pCell == 0 ){ return UNQLITE_NOMEM; } /* Fill-in the structure */ pCell->iStart = nOfft; pCell->nData = pTarget->nData; pCell->nKey = pTarget->nKey; pCell->iOvfl = pTarget->iOvfl; pCell->iDataOfft = pTarget->iDataOfft; pCell->iDataPage = pTarget->iDataPage; pCell->nHash = pTarget->nHash; SyBlobDup(&pTarget->sKey,&pCell->sKey); /* Link the cell */ rc = lhInstallCell(pCell); if( rc != UNQLITE_OK ){ return rc; } /* Finally, Write the cell header */ lhCellWriteHeader(pCell); /* All done */ return UNQLITE_OK; } /* * Perform a page split. */ static int lhPageSplit( lhpage *pOld, /* Page to be split */ lhpage *pNew, /* New page */ pgno split_bucket, /* Current split bucket */ pgno high_mask /* High mask (Max split bucket - 1) */ ) { lhcell *pCell,*pNext; SyBlob sWorker; pgno iBucket; int rc; SyBlobInit(&sWorker,&pOld->pHash->sAllocator); /* Perform the split */ pCell = pOld->pList; for( ;; ){ if( pCell == 0 ){ /* No more cells */ break; } /* Obtain the new logical bucket */ iBucket = pCell->nHash & high_mask; pNext = pCell->pNext; if( iBucket != split_bucket){ rc = UNQLITE_OK; if( pCell->iOvfl ){ /* Transfer the cell only */ rc = lhTransferCell(pCell,pNew); }else{ /* Transfer the cell and its payload */ SyBlobReset(&sWorker); if( SyBlobLength(&pCell->sKey) < 1 ){ /* Consume the key */ rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,0); if( rc != UNQLITE_OK ){ goto fail; } } /* Consume the data (Very small data < 65k) */ rc = lhConsumeCellData(pCell,unqliteDataConsumer,&sWorker); if( rc != UNQLITE_OK ){ goto fail; } /* Perform the transfer */ rc = lhStoreCell( pNew, SyBlobData(&pCell->sKey),(int)SyBlobLength(&pCell->sKey), SyBlobData(&sWorker),SyBlobLength(&sWorker), pCell->nHash, 1 ); } if( rc != UNQLITE_OK ){ goto fail; } /* Discard the cell from the old page */ lhUnlinkCell(pCell); } /* Point to the next cell */ pCell = pNext; } /* All done */ rc = UNQLITE_OK; fail: SyBlobRelease(&sWorker); return rc; } /* * Perform the infamous linear hash split operation. */ static int lhSplit(lhpage *pTarget,int *pRetry) { lhash_kv_engine *pEngine = pTarget->pHash; lhash_bmap_rec *pRec; lhpage *pOld,*pNew; unqlite_page *pRaw; int rc; /* Get the real page number of the bucket to split */ pRec = lhMapFindBucket(pEngine,pEngine->split_bucket); if( pRec == 0 ){ /* Can't happen */ return UNQLITE_CORRUPT; } /* Load the page to be split */ rc = lhLoadPage(pEngine,pRec->iReal,0,&pOld,0); if( rc != UNQLITE_OK ){ return rc; } /* Request a new page */ rc = lhAcquirePage(pEngine,&pRaw); if( rc != UNQLITE_OK ){ return rc; } /* Initialize the page */ pNew = lhNewPage(pEngine,pRaw,0); if( pNew == 0 ){ return UNQLITE_NOMEM; } /* Mark as an empty page */ rc = lhSetEmptyPage(pNew); if( rc != UNQLITE_OK ){ goto fail; } /* Install and write the logical map record */ rc = lhMapWriteRecord(pEngine, pEngine->split_bucket + pEngine->max_split_bucket, pRaw->pgno ); if( rc != UNQLITE_OK ){ goto fail; } if( pTarget->pRaw->pgno == pOld->pRaw->pgno ){ *pRetry = 1; } /* Perform the split */ rc = lhPageSplit(pOld,pNew,pEngine->split_bucket,pEngine->nmax_split_nucket - 1); if( rc != UNQLITE_OK ){ goto fail; } /* Update the database header */ pEngine->split_bucket++; /* Acquire a writer lock on the first page */ rc = pEngine->pIo->xWrite(pEngine->pHeader); if( rc != UNQLITE_OK ){ return rc; } if( pEngine->split_bucket >= pEngine->max_split_bucket ){ /* Increment the generation number */ pEngine->split_bucket = 0; pEngine->max_split_bucket = pEngine->nmax_split_nucket; pEngine->nmax_split_nucket <<= 1; if( !pEngine->nmax_split_nucket ){ /* If this happen to your installation, please tell us */ pEngine->pIo->xErr(pEngine->pIo->pHandle,"Database page (64-bit integer) limit reached"); return UNQLITE_LIMIT; } /* Reflect in the page header */ SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket); SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/+8/*Split bucket*/],pEngine->max_split_bucket); }else{ /* Modify only the split bucket */ SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket); } /* All done */ return UNQLITE_OK; fail: pEngine->pIo->xPageUnref(pNew->pRaw); return rc; } /* * Store a record in the target page. */ static int lhRecordInstall( lhpage *pPage, /* Target page */ sxu32 nHash, /* Hash of the key */ const void *pKey,sxu32 nKeyLen, /* Payload: Key */ const void *pData,unqlite_int64 nDataLen /* Payload: Data */ ) { int rc; rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,0); if( rc == UNQLITE_FULL ){ int do_retry = 0; /* Split */ rc = lhSplit(pPage,&do_retry); if( rc == UNQLITE_OK ){ if( do_retry ){ /* Re-calculate logical bucket number */ return SXERR_RETRY; } /* Perform the store */ rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1); } } return rc; } /* * Insert a record (Either overwrite or append operation) in our database. */ static int lh_record_insert( unqlite_kv_engine *pKv, /* KV store */ const void *pKey,sxu32 nKeyLen, /* Payload: Key */ const void *pData,unqlite_int64 nDataLen, /* Payload: data */ int is_append /* True for an append operation */ ) { lhash_kv_engine *pEngine = (lhash_kv_engine *)pKv; lhash_bmap_rec *pRec; unqlite_page *pRaw; lhpage *pPage; lhcell *pCell; pgno iBucket; sxu32 nHash; int iCnt; int rc; /* Acquire the first page (DB hash Header) so that everything gets loaded automatically */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); if( rc != UNQLITE_OK ){ return rc; } iCnt = 0; /* Compute the hash of the key first */ nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); retry: /* Extract the logical bucket number */ iBucket = nHash & (pEngine->nmax_split_nucket - 1); if( iBucket >= pEngine->split_bucket + pEngine->max_split_bucket ){ /* Low mask */ iBucket = nHash & (pEngine->max_split_bucket - 1); } /* Map the logical bucket number to real page number */ pRec = lhMapFindBucket(pEngine,iBucket); if( pRec == 0 ){ /* Request a new page */ rc = lhAcquirePage(pEngine,&pRaw); if( rc != UNQLITE_OK ){ return rc; } /* Initialize the page */ pPage = lhNewPage(pEngine,pRaw,0); if( pPage == 0 ){ return UNQLITE_NOMEM; } /* Mark as an empty page */ rc = lhSetEmptyPage(pPage); if( rc != UNQLITE_OK ){ pEngine->pIo->xPageUnref(pRaw); /* pPage will be released during this call */ return rc; } /* Store the cell */ rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1); if( rc == UNQLITE_OK ){ /* Install and write the logical map record */ rc = lhMapWriteRecord(pEngine,iBucket,pRaw->pgno); } pEngine->pIo->xPageUnref(pRaw); return rc; }else{ /* Load the page */ rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0); if( rc != UNQLITE_OK ){ /* IO error, unlikely scenario */ return rc; } /* Do not add this page to the hot dirty list */ pEngine->pIo->xDontMkHot(pPage->pRaw); /* Lookup for the cell */ pCell = lhFindCell(pPage,pKey,(sxu32)nKeyLen,nHash); if( pCell == 0 ){ /* Create the record */ rc = lhRecordInstall(pPage,nHash,pKey,nKeyLen,pData,nDataLen); if( rc == SXERR_RETRY && iCnt++ < 2 ){ rc = UNQLITE_OK; goto retry; } }else{ if( is_append ){ /* Append operation */ rc = lhRecordAppend(pCell,pData,nDataLen); }else{ /* Overwrite old value */ rc = lhRecordOverwrite(pCell,pData,nDataLen); } } pEngine->pIo->xPageUnref(pPage->pRaw); } return rc; } /* * Replace method. */ static int lhash_kv_replace( unqlite_kv_engine *pKv, const void *pKey,int nKeyLen, const void *pData,unqlite_int64 nDataLen ) { int rc; rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,0); return rc; } /* * Append method. */ static int lhash_kv_append( unqlite_kv_engine *pKv, const void *pKey,int nKeyLen, const void *pData,unqlite_int64 nDataLen ) { int rc; rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,1); return rc; } /* * Write the hash header (Page one). */ static int lhash_write_header(lhash_kv_engine *pEngine,unqlite_page *pHeader) { unsigned char *zRaw = pHeader->zData; lhash_bmap_page *pMap; pEngine->pHeader = pHeader; /* 4 byte magic number */ SyBigEndianPack32(zRaw,pEngine->nMagic); zRaw += 4; /* 4 byte hash value to identify a valid hash function */ SyBigEndianPack32(zRaw,pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1)); zRaw += 4; /* List of free pages: Empty */ SyBigEndianPack64(zRaw,0); zRaw += 8; /* Current split bucket */ SyBigEndianPack64(zRaw,pEngine->split_bucket); zRaw += 8; /* Maximum split bucket */ SyBigEndianPack64(zRaw,pEngine->max_split_bucket); zRaw += 8; /* Initialize the bucket map */ pMap = &pEngine->sPageMap; /* Fill in the structure */ pMap->iNum = pHeader->pgno; /* Next page in the bucket map */ SyBigEndianPack64(zRaw,0); zRaw += 8; /* Total number of records in the bucket map */ SyBigEndianPack32(zRaw,0); zRaw += 4; pMap->iPtr = (sxu16)(zRaw - pHeader->zData); /* All done */ return UNQLITE_OK; } /* * Exported: xOpen() method. */ static int lhash_kv_open(unqlite_kv_engine *pEngine,pgno dbSize) { lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; unqlite_page *pHeader; int rc; if( dbSize < 1 ){ /* A new database, create the header */ rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pHeader); if( rc != UNQLITE_OK ){ return rc; } /* Acquire a writer lock */ rc = pEngine->pIo->xWrite(pHeader); if( rc != UNQLITE_OK ){ return rc; } /* Write the hash header */ rc = lhash_write_header(pHash,pHeader); if( rc != UNQLITE_OK ){ return rc; } }else{ /* Acquire the page one of the database */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,&pHeader); if( rc != UNQLITE_OK ){ return rc; } /* Read the database header */ rc = lhash_read_header(pHash,pHeader); if( rc != UNQLITE_OK ){ return rc; } } return UNQLITE_OK; } /* * Release a master or slave page. (xUnpin callback). */ static void lhash_page_release(void *pUserData) { lhpage *pPage = (lhpage *)pUserData; lhash_kv_engine *pEngine = pPage->pHash; lhcell *pNext,*pCell = pPage->pList; unqlite_page *pRaw = pPage->pRaw; sxu32 n; /* Drop in-memory cells */ for( n = 0 ; n < pPage->nCell ; ++n ){ pNext = pCell->pNext; SyBlobRelease(&pCell->sKey); /* Release the cell instance */ SyMemBackendPoolFree(&pEngine->sAllocator,(void *)pCell); /* Point to the next entry */ pCell = pNext; } if( pPage->apCell ){ /* Release the cell table */ SyMemBackendFree(&pEngine->sAllocator,(void *)pPage->apCell); } /* Finally, release the whole page */ SyMemBackendPoolFree(&pEngine->sAllocator,pPage); pRaw->pUserData = 0; } /* * Default hash function (DJB). */ static sxu32 lhash_bin_hash(const void *pSrc,sxu32 nLen) { register unsigned char *zIn = (unsigned char *)pSrc; unsigned char *zEnd; sxu32 nH = 5381; if( nLen > 2048 /* 2K */ ){ nLen = 2048; } zEnd = &zIn[nLen]; for(;;){ if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; } return nH; } /* * Exported: xInit() method. * Initialize the Key value storage engine. */ static int lhash_kv_init(unqlite_kv_engine *pEngine,int iPageSize) { lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; int rc; /* This structure is always zeroed, go to the initialization directly */ SyMemBackendInitFromParent(&pHash->sAllocator,unqliteExportMemBackend()); pHash->iPageSize = iPageSize; /* Default hash function */ pHash->xHash = lhash_bin_hash; /* Default comparison function */ pHash->xCmp = SyMemcmp; /* Allocate a new record map */ pHash->nBuckSize = 32; pHash->apMap = (lhash_bmap_rec **)SyMemBackendAlloc(&pHash->sAllocator,pHash->nBuckSize *sizeof(lhash_bmap_rec *)); if( pHash->apMap == 0 ){ rc = UNQLITE_NOMEM; goto err; } /* Zero the table */ SyZero(pHash->apMap,pHash->nBuckSize * sizeof(lhash_bmap_rec *)); /* Linear hashing components */ pHash->split_bucket = 0; /* Logical not real bucket number */ pHash->max_split_bucket = 1; pHash->nmax_split_nucket = 2; pHash->nMagic = L_HASH_MAGIC; /* Install the cache unpin and reload callbacks */ pHash->pIo->xSetUnpin(pHash->pIo->pHandle,lhash_page_release); pHash->pIo->xSetReload(pHash->pIo->pHandle,lhash_page_release); return UNQLITE_OK; err: SyMemBackendRelease(&pHash->sAllocator); return rc; } /* * Exported: xRelease() method. * Release the Key value storage engine. */ static void lhash_kv_release(unqlite_kv_engine *pEngine) { lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; /* Release the private memory backend */ SyMemBackendRelease(&pHash->sAllocator); } /* * Exported: xConfig() method. * Configure the linear hash KV store. */ static int lhash_kv_config(unqlite_kv_engine *pEngine,int op,va_list ap) { lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; int rc = UNQLITE_OK; switch(op){ case UNQLITE_KV_CONFIG_HASH_FUNC: { /* Default hash function */ if( pHash->nBuckRec > 0 ){ /* Locked operation */ rc = UNQLITE_LOCKED; }else{ ProcHash xHash = va_arg(ap,ProcHash); if( xHash ){ pHash->xHash = xHash; } } break; } case UNQLITE_KV_CONFIG_CMP_FUNC: { /* Default comparison function */ ProcCmp xCmp = va_arg(ap,ProcCmp); if( xCmp ){ pHash->xCmp = xCmp; } break; } default: /* Unknown OP */ rc = UNQLITE_UNKNOWN; break; } return rc; } /* * Each public cursor is identified by an instance of this structure. */ typedef struct lhash_kv_cursor lhash_kv_cursor; struct lhash_kv_cursor { unqlite_kv_engine *pStore; /* Must be first */ /* Private fields */ int iState; /* Current state of the cursor */ int is_first; /* True to read the database header */ lhcell *pCell; /* Current cell we are processing */ unqlite_page *pRaw; /* Raw disk page */ lhash_bmap_rec *pRec; /* Logical to real bucket map */ }; /* * Possible state of the cursor */ #define L_HASH_CURSOR_STATE_NEXT_PAGE 1 /* Next page in the list */ #define L_HASH_CURSOR_STATE_CELL 2 /* Processing Cell */ #define L_HASH_CURSOR_STATE_DONE 3 /* Cursor does not point to anything */ /* * Initialize the cursor. */ static void lhInitCursor(unqlite_kv_cursor *pPtr) { lhash_kv_engine *pEngine = (lhash_kv_engine *)pPtr->pStore; lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; /* Init */ pCur->iState = L_HASH_CURSOR_STATE_NEXT_PAGE; pCur->pCell = 0; pCur->pRec = pEngine->pFirst; pCur->pRaw = 0; pCur->is_first = 1; } /* * Point to the next page on the database. */ static int lhCursorNextPage(lhash_kv_cursor *pPtr) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; lhash_bmap_rec *pRec; lhpage *pPage; int rc; for(;;){ pRec = pCur->pRec; if( pRec == 0 ){ pCur->iState = L_HASH_CURSOR_STATE_DONE; return UNQLITE_DONE; } if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){ /* Unref this page */ pCur->pStore->pIo->xPageUnref(pPtr->pRaw); pPtr->pRaw = 0; } /* Advance the map cursor */ pCur->pRec = pRec->pPrev; /* Not a bug, reverse link */ /* Load the next page on the list */ rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0); if( rc != UNQLITE_OK ){ return rc; } if( pPage->pList ){ /* Reflect the change */ pCur->pCell = pPage->pList; pCur->iState = L_HASH_CURSOR_STATE_CELL; pCur->pRaw = pPage->pRaw; break; } /* Empty page, discard this page and continue */ pPage->pHash->pIo->xPageUnref(pPage->pRaw); } return UNQLITE_OK; } /* * Point to the previous page on the database. */ static int lhCursorPrevPage(lhash_kv_cursor *pPtr) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; lhash_bmap_rec *pRec; lhpage *pPage; int rc; for(;;){ pRec = pCur->pRec; if( pRec == 0 ){ pCur->iState = L_HASH_CURSOR_STATE_DONE; return UNQLITE_DONE; } if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){ /* Unref this page */ pCur->pStore->pIo->xPageUnref(pPtr->pRaw); pPtr->pRaw = 0; } /* Advance the map cursor */ pCur->pRec = pRec->pNext; /* Not a bug, reverse link */ /* Load the previous page on the list */ rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0); if( rc != UNQLITE_OK ){ return rc; } if( pPage->pFirst ){ /* Reflect the change */ pCur->pCell = pPage->pFirst; pCur->iState = L_HASH_CURSOR_STATE_CELL; pCur->pRaw = pPage->pRaw; break; } /* Discard this page and continue */ pPage->pHash->pIo->xPageUnref(pPage->pRaw); } return UNQLITE_OK; } /* * Is a valid cursor. */ static int lhCursorValid(unqlite_kv_cursor *pPtr) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; return (pCur->iState == L_HASH_CURSOR_STATE_CELL) && pCur->pCell; } /* * Point to the first record. */ static int lhCursorFirst(unqlite_kv_cursor *pCursor) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore; int rc; if( pCur->is_first ){ /* Read the database header first */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); if( rc != UNQLITE_OK ){ return rc; } pCur->is_first = 0; } /* Point to the first map record */ pCur->pRec = pEngine->pFirst; /* Load the cells */ rc = lhCursorNextPage(pCur); return rc; } /* * Point to the last record. */ static int lhCursorLast(unqlite_kv_cursor *pCursor) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore; int rc; if( pCur->is_first ){ /* Read the database header first */ rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); if( rc != UNQLITE_OK ){ return rc; } pCur->is_first = 0; } /* Point to the last map record */ pCur->pRec = pEngine->pList; /* Load the cells */ rc = lhCursorPrevPage(pCur); return rc; } /* * Reset the cursor. */ static void lhCursorReset(unqlite_kv_cursor *pCursor) { lhCursorFirst(pCursor); } /* * Point to the next record. */ static int lhCursorNext(unqlite_kv_cursor *pCursor) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhcell *pCell; int rc; if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ /* Load the cells of the next page */ rc = lhCursorNextPage(pCur); return rc; } pCell = pCur->pCell; pCur->pCell = pCell->pNext; if( pCur->pCell == 0 ){ /* Load the cells of the next page */ rc = lhCursorNextPage(pCur); return rc; } return UNQLITE_OK; } /* * Point to the previous record. */ static int lhCursorPrev(unqlite_kv_cursor *pCursor) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhcell *pCell; int rc; if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ /* Load the cells of the previous page */ rc = lhCursorPrevPage(pCur); return rc; } pCell = pCur->pCell; pCur->pCell = pCell->pPrev; if( pCur->pCell == 0 ){ /* Load the cells of the previous page */ rc = lhCursorPrevPage(pCur); return rc; } return UNQLITE_OK; } /* * Return key length. */ static int lhCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhcell *pCell; if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ /* Invalid state */ return UNQLITE_INVALID; } /* Point to the target cell */ pCell = pCur->pCell; /* Return key length */ *pLen = (int)pCell->nKey; return UNQLITE_OK; } /* * Return data length. */ static int lhCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhcell *pCell; if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ /* Invalid state */ return UNQLITE_INVALID; } /* Point to the target cell */ pCell = pCur->pCell; /* Return data length */ *pLen = (unqlite_int64)pCell->nData; return UNQLITE_OK; } /* * Consume the key. */ static int lhCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhcell *pCell; int rc; if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ /* Invalid state */ return UNQLITE_INVALID; } /* Point to the target cell */ pCell = pCur->pCell; if( SyBlobLength(&pCell->sKey) > 0 ){ /* Consume the key directly */ rc = xConsumer(SyBlobData(&pCell->sKey),SyBlobLength(&pCell->sKey),pUserData); }else{ /* Very large key */ rc = lhConsumeCellkey(pCell,xConsumer,pUserData,0); } return rc; } /* * Consume the data. */ static int lhCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhcell *pCell; int rc; if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ /* Invalid state */ return UNQLITE_INVALID; } /* Point to the target cell */ pCell = pCur->pCell; /* Consume the data */ rc = lhConsumeCellData(pCell,xConsumer,pUserData); return rc; } /* * Find a partiuclar record. */ static int lhCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; int rc; /* Perform a lookup */ rc = lhRecordLookup((lhash_kv_engine *)pCur->pStore,pKey,nByte,&pCur->pCell); if( rc != UNQLITE_OK ){ SXUNUSED(iPos); pCur->pCell = 0; pCur->iState = L_HASH_CURSOR_STATE_DONE; return rc; } pCur->iState = L_HASH_CURSOR_STATE_CELL; return UNQLITE_OK; } /* * Remove a particular record. */ static int lhCursorDelete(unqlite_kv_cursor *pCursor) { lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; lhcell *pCell; int rc; if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ /* Invalid state */ return UNQLITE_INVALID; } /* Point to the target cell */ pCell = pCur->pCell; /* Point to the next entry */ pCur->pCell = pCell->pNext; /* Perform the deletion */ rc = lhRecordRemove(pCell); return rc; } /* * Export the linear-hash storage engine. */ UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void) { static const unqlite_kv_methods sDiskStore = { "hash", /* zName */ sizeof(lhash_kv_engine), /* szKv */ sizeof(lhash_kv_cursor), /* szCursor */ 1, /* iVersion */ lhash_kv_init, /* xInit */ lhash_kv_release, /* xRelease */ lhash_kv_config, /* xConfig */ lhash_kv_open, /* xOpen */ lhash_kv_replace, /* xReplace */ lhash_kv_append, /* xAppend */ lhInitCursor, /* xCursorInit */ lhCursorSeek, /* xSeek */ lhCursorFirst, /* xFirst */ lhCursorLast, /* xLast */ lhCursorValid, /* xValid */ lhCursorNext, /* xNext */ lhCursorPrev, /* xPrev */ lhCursorDelete, /* xDelete */ lhCursorKeyLength, /* xKeyLength */ lhCursorKey, /* xKey */ lhCursorDataLength, /* xDataLength */ lhCursorData, /* xData */ lhCursorReset, /* xReset */ 0 /* xRelease */ }; return &sDiskStore; } /* * ---------------------------------------------------------- * File: mem_kv.c * MD5: 32e2610c95f53038114d9566f0d0489e * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: mem_kv.c v1.7 Win7 2012-11-28 01:41 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* * This file implements an in-memory key value storage engine for unQLite. * Note that this storage engine does not support transactions. * * Normaly, I (chm@symisc.net) planned to implement a red-black tree * which is suitable for this kind of operation, but due to the lack * of time, I decided to implement a tunned hashtable which everybody * know works very well for this kind of operation. * Again, I insist on a red-black tree implementation for future version * of Unqlite. */ /* Forward declaration */ typedef struct mem_hash_kv_engine mem_hash_kv_engine; /* * Each record is storead in an instance of the following structure. */ typedef struct mem_hash_record mem_hash_record; struct mem_hash_record { mem_hash_kv_engine *pEngine; /* Storage engine */ sxu32 nHash; /* Hash of the key */ const void *pKey; /* Key */ sxu32 nKeyLen; /* Key size (Max 1GB) */ const void *pData; /* Data */ sxu32 nDataLen; /* Data length (Max 4GB) */ mem_hash_record *pNext,*pPrev; /* Link to other records */ mem_hash_record *pNextHash,*pPrevHash; /* Collision link */ }; /* * Each in-memory KV engine is represented by an instance * of the following structure. */ struct mem_hash_kv_engine { const unqlite_kv_io *pIo; /* IO methods: MUST be first */ /* Private data */ SyMemBackend sAlloc; /* Private memory allocator */ ProcHash xHash; /* Default hash function */ ProcCmp xCmp; /* Default comparison function */ sxu32 nRecord; /* Total number of records */ sxu32 nBucket; /* Bucket size: Must be a power of two */ mem_hash_record **apBucket; /* Hash bucket */ mem_hash_record *pFirst; /* First inserted entry */ mem_hash_record *pLast; /* Last inserted entry */ }; /* * Allocate a new hash record. */ static mem_hash_record * MemHashNewRecord( mem_hash_kv_engine *pEngine, const void *pKey,int nKey, const void *pData,unqlite_int64 nData, sxu32 nHash ) { SyMemBackend *pAlloc = &pEngine->sAlloc; mem_hash_record *pRecord; void *pDupData; sxu32 nByte; char *zPtr; /* Total number of bytes to alloc */ nByte = sizeof(mem_hash_record) + nKey; /* Allocate a new instance */ pRecord = (mem_hash_record *)SyMemBackendAlloc(pAlloc,nByte); if( pRecord == 0 ){ return 0; } pDupData = (void *)SyMemBackendAlloc(pAlloc,(sxu32)nData); if( pDupData == 0 ){ SyMemBackendFree(pAlloc,pRecord); return 0; } zPtr = (char *)pRecord; zPtr += sizeof(mem_hash_record); /* Zero the structure */ SyZero(pRecord,sizeof(mem_hash_record)); /* Fill in the structure */ pRecord->pEngine = pEngine; pRecord->nDataLen = (sxu32)nData; pRecord->nKeyLen = (sxu32)nKey; pRecord->nHash = nHash; SyMemcpy(pKey,zPtr,pRecord->nKeyLen); pRecord->pKey = (const void *)zPtr; SyMemcpy(pData,pDupData,pRecord->nDataLen); pRecord->pData = pDupData; /* All done */ return pRecord; } /* * Install a given record in the hashtable. */ static void MemHashLinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pRecord) { sxu32 nBucket = pRecord->nHash & (pEngine->nBucket - 1); pRecord->pNextHash = pEngine->apBucket[nBucket]; if( pEngine->apBucket[nBucket] ){ pEngine->apBucket[nBucket]->pPrevHash = pRecord; } pEngine->apBucket[nBucket] = pRecord; if( pEngine->pFirst == 0 ){ pEngine->pFirst = pEngine->pLast = pRecord; }else{ MACRO_LD_PUSH(pEngine->pLast,pRecord); } pEngine->nRecord++; } /* * Unlink a given record from the hashtable. */ static void MemHashUnlinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pEntry) { sxu32 nBucket = pEntry->nHash & (pEngine->nBucket - 1); SyMemBackend *pAlloc = &pEngine->sAlloc; if( pEntry->pPrevHash == 0 ){ pEngine->apBucket[nBucket] = pEntry->pNextHash; }else{ pEntry->pPrevHash->pNextHash = pEntry->pNextHash; } if( pEntry->pNextHash ){ pEntry->pNextHash->pPrevHash = pEntry->pPrevHash; } MACRO_LD_REMOVE(pEngine->pLast,pEntry); if( pEntry == pEngine->pFirst ){ pEngine->pFirst = pEntry->pPrev; } pEngine->nRecord--; /* Release the entry */ SyMemBackendFree(pAlloc,(void *)pEntry->pData); SyMemBackendFree(pAlloc,pEntry); /* Key is also stored here */ } /* * Perform a lookup for a given entry. */ static mem_hash_record * MemHashGetEntry( mem_hash_kv_engine *pEngine, const void *pKey,int nKeyLen ) { mem_hash_record *pEntry; sxu32 nHash,nBucket; /* Hash the entry */ nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); nBucket = nHash & (pEngine->nBucket - 1); pEntry = pEngine->apBucket[nBucket]; for(;;){ if( pEntry == 0 ){ break; } if( pEntry->nHash == nHash && pEntry->nKeyLen == (sxu32)nKeyLen && pEngine->xCmp(pEntry->pKey,pKey,pEntry->nKeyLen) == 0 ){ return pEntry; } pEntry = pEntry->pNextHash; } /* No such entry */ return 0; } /* * Rehash all the entries in the given table. */ static int MemHashGrowTable(mem_hash_kv_engine *pEngine) { sxu32 nNewSize = pEngine->nBucket << 1; mem_hash_record *pEntry; mem_hash_record **apNew; sxu32 n,iBucket; /* Allocate a new larger table */ apNew = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc, nNewSize * sizeof(mem_hash_record *)); if( apNew == 0 ){ /* Not so fatal, simply a performance hit */ return UNQLITE_OK; } /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(mem_hash_record *)); /* Rehash all entries */ n = 0; pEntry = pEngine->pLast; for(;;){ /* Loop one */ if( n >= pEngine->nRecord ){ break; } pEntry->pNextHash = pEntry->pPrevHash = 0; /* Install in the new bucket */ iBucket = pEntry->nHash & (nNewSize - 1); pEntry->pNextHash = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevHash = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; /* Loop two */ if( n >= pEngine->nRecord ){ break; } pEntry->pNextHash = pEntry->pPrevHash = 0; /* Install in the new bucket */ iBucket = pEntry->nHash & (nNewSize - 1); pEntry->pNextHash = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevHash = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; /* Loop three */ if( n >= pEngine->nRecord ){ break; } pEntry->pNextHash = pEntry->pPrevHash = 0; /* Install in the new bucket */ iBucket = pEntry->nHash & (nNewSize - 1); pEntry->pNextHash = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevHash = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; /* Loop four */ if( n >= pEngine->nRecord ){ break; } pEntry->pNextHash = pEntry->pPrevHash = 0; /* Install in the new bucket */ iBucket = pEntry->nHash & (nNewSize - 1); pEntry->pNextHash = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevHash = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; } /* Release the old table and reflect the change */ SyMemBackendFree(&pEngine->sAlloc,(void *)pEngine->apBucket); pEngine->apBucket = apNew; pEngine->nBucket = nNewSize; return UNQLITE_OK; } /* * Exported Interfaces. */ /* * Each public cursor is identified by an instance of this structure. */ typedef struct mem_hash_cursor mem_hash_cursor; struct mem_hash_cursor { unqlite_kv_engine *pStore; /* Must be first */ /* Private fields */ mem_hash_record *pCur; /* Current hash record */ }; /* * Initialize the cursor. */ static void MemHashInitCursor(unqlite_kv_cursor *pCursor) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; /* Point to the first inserted entry */ pMem->pCur = pEngine->pFirst; } /* * Point to the first entry. */ static int MemHashCursorFirst(unqlite_kv_cursor *pCursor) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; pMem->pCur = pEngine->pFirst; return UNQLITE_OK; } /* * Point to the last entry. */ static int MemHashCursorLast(unqlite_kv_cursor *pCursor) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; pMem->pCur = pEngine->pLast; return UNQLITE_OK; } /* * is a Valid Cursor. */ static int MemHashCursorValid(unqlite_kv_cursor *pCursor) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; return pMem->pCur != 0 ? 1 : 0; } /* * Point to the next entry. */ static int MemHashCursorNext(unqlite_kv_cursor *pCursor) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; if( pMem->pCur == 0){ return UNQLITE_EOF; } pMem->pCur = pMem->pCur->pPrev; /* Reverse link: Not a Bug */ return UNQLITE_OK; } /* * Point to the previous entry. */ static int MemHashCursorPrev(unqlite_kv_cursor *pCursor) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; if( pMem->pCur == 0){ return UNQLITE_EOF; } pMem->pCur = pMem->pCur->pNext; /* Reverse link: Not a Bug */ return UNQLITE_OK; } /* * Return key length. */ static int MemHashCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; if( pMem->pCur == 0){ return UNQLITE_EOF; } *pLen = (int)pMem->pCur->nKeyLen; return UNQLITE_OK; } /* * Return data length. */ static int MemHashCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; if( pMem->pCur == 0 ){ return UNQLITE_EOF; } *pLen = pMem->pCur->nDataLen; return UNQLITE_OK; } /* * Consume the key. */ static int MemHashCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; int rc; if( pMem->pCur == 0){ return UNQLITE_EOF; } /* Invoke the callback */ rc = xConsumer(pMem->pCur->pKey,pMem->pCur->nKeyLen,pUserData); /* Callback result */ return rc; } /* * Consume the data. */ static int MemHashCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; int rc; if( pMem->pCur == 0){ return UNQLITE_EOF; } /* Invoke the callback */ rc = xConsumer(pMem->pCur->pData,pMem->pCur->nDataLen,pUserData); /* Callback result */ return rc; } /* * Reset the cursor. */ static void MemHashCursorReset(unqlite_kv_cursor *pCursor) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; pMem->pCur = ((mem_hash_kv_engine *)pCursor->pStore)->pFirst; } /* * Remove a particular record. */ static int MemHashCursorDelete(unqlite_kv_cursor *pCursor) { mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; mem_hash_record *pNext; if( pMem->pCur == 0 ){ /* Cursor does not point to anything */ return UNQLITE_NOTFOUND; } pNext = pMem->pCur->pPrev; /* Perform the deletion */ MemHashUnlinkRecord(pMem->pCur->pEngine,pMem->pCur); /* Point to the next entry */ pMem->pCur = pNext; return UNQLITE_OK; } /* * Find a particular record. */ static int MemHashCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; /* Perform the lookup */ pMem->pCur = MemHashGetEntry(pEngine,pKey,nByte); if( pMem->pCur == 0 ){ if( iPos != UNQLITE_CURSOR_MATCH_EXACT ){ /* noop; */ } /* No such record */ return UNQLITE_NOTFOUND; } return UNQLITE_OK; } /* * Builtin hash function. */ static sxu32 MemHashFunc(const void *pSrc,sxu32 nLen) { register unsigned char *zIn = (unsigned char *)pSrc; unsigned char *zEnd; sxu32 nH = 5381; zEnd = &zIn[nLen]; for(;;){ if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; } return nH; } /* Default bucket size */ #define MEM_HASH_BUCKET_SIZE 64 /* Default fill factor */ #define MEM_HASH_FILL_FACTOR 4 /* or 3 */ /* * Initialize the in-memory storage engine. */ static int MemHashInit(unqlite_kv_engine *pKvEngine,int iPageSize) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; /* Note that this instance is already zeroed */ /* Memory backend */ SyMemBackendInitFromParent(&pEngine->sAlloc,unqliteExportMemBackend()); //#if defined(UNQLITE_ENABLE_THREADS) // /* Already protected by the upper layers */ // SyMemBackendDisbaleMutexing(&pEngine->sAlloc); //#endif /* Default hash & comparison function */ pEngine->xHash = MemHashFunc; pEngine->xCmp = SyMemcmp; /* Allocate a new bucket */ pEngine->apBucket = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); if( pEngine->apBucket == 0 ){ SXUNUSED(iPageSize); /* cc warning */ return UNQLITE_NOMEM; } /* Zero the bucket */ SyZero(pEngine->apBucket,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); pEngine->nRecord = 0; pEngine->nBucket = MEM_HASH_BUCKET_SIZE; return UNQLITE_OK; } /* * Release the in-memory storage engine. */ static void MemHashRelease(unqlite_kv_engine *pKvEngine) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; /* Release the private memory backend */ SyMemBackendRelease(&pEngine->sAlloc); } /* * Configure the in-memory storage engine. */ static int MemHashConfigure(unqlite_kv_engine *pKvEngine,int iOp,va_list ap) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; int rc = UNQLITE_OK; switch(iOp){ case UNQLITE_KV_CONFIG_HASH_FUNC:{ /* Use a default hash function */ if( pEngine->nRecord > 0 ){ rc = UNQLITE_LOCKED; }else{ ProcHash xHash = va_arg(ap,ProcHash); if( xHash ){ pEngine->xHash = xHash; } } break; } case UNQLITE_KV_CONFIG_CMP_FUNC: { /* Default comparison function */ ProcCmp xCmp = va_arg(ap,ProcCmp); if( xCmp ){ pEngine->xCmp = xCmp; } break; } default: /* Unknown configuration option */ rc = UNQLITE_UNKNOWN; } return rc; } /* * Replace method. */ static int MemHashReplace( unqlite_kv_engine *pKv, const void *pKey,int nKeyLen, const void *pData,unqlite_int64 nDataLen ) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; mem_hash_record *pRecord; if( nDataLen > SXU32_HIGH ){ /* Database limit */ pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); return UNQLITE_LIMIT; } /* Fetch the record first */ pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); if( pRecord == 0 ){ /* Allocate a new record */ pRecord = MemHashNewRecord(pEngine, pKey,nKeyLen, pData,nDataLen, pEngine->xHash(pKey,nKeyLen) ); if( pRecord == 0 ){ return UNQLITE_NOMEM; } /* Link the entry */ MemHashLinkRecord(pEngine,pRecord); if( (pEngine->nRecord >= pEngine->nBucket * MEM_HASH_FILL_FACTOR) && pEngine->nRecord < 100000 ){ /* Rehash the table */ MemHashGrowTable(pEngine); } }else{ sxu32 nData = (sxu32)nDataLen; void *pNew; /* Replace an existing record */ if( nData == pRecord->nDataLen ){ /* No need to free the old chunk */ pNew = (void *)pRecord->pData; }else{ pNew = SyMemBackendAlloc(&pEngine->sAlloc,nData); if( pNew == 0 ){ return UNQLITE_NOMEM; } /* Release the old data */ SyMemBackendFree(&pEngine->sAlloc,(void *)pRecord->pData); } /* Reflect the change */ pRecord->nDataLen = nData; SyMemcpy(pData,pNew,nData); pRecord->pData = pNew; } return UNQLITE_OK; } /* * Append method. */ static int MemHashAppend( unqlite_kv_engine *pKv, const void *pKey,int nKeyLen, const void *pData,unqlite_int64 nDataLen ) { mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; mem_hash_record *pRecord; if( nDataLen > SXU32_HIGH ){ /* Database limit */ pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); return UNQLITE_LIMIT; } /* Fetch the record first */ pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); if( pRecord == 0 ){ /* Allocate a new record */ pRecord = MemHashNewRecord(pEngine, pKey,nKeyLen, pData,nDataLen, pEngine->xHash(pKey,nKeyLen) ); if( pRecord == 0 ){ return UNQLITE_NOMEM; } /* Link the entry */ MemHashLinkRecord(pEngine,pRecord); if( pEngine->nRecord * MEM_HASH_FILL_FACTOR >= pEngine->nBucket && pEngine->nRecord < 100000 ){ /* Rehash the table */ MemHashGrowTable(pEngine); } }else{ unqlite_int64 nNew = pRecord->nDataLen + nDataLen; void *pOld = (void *)pRecord->pData; sxu32 nData; char *zNew; /* Append data to the existing record */ if( nNew > SXU32_HIGH ){ /* Overflow */ pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); return UNQLITE_LIMIT; } nData = (sxu32)nNew; /* Allocate bigger chunk */ zNew = (char *)SyMemBackendRealloc(&pEngine->sAlloc,pOld,nData); if( zNew == 0 ){ return UNQLITE_NOMEM; } /* Reflect the change */ SyMemcpy(pData,&zNew[pRecord->nDataLen],(sxu32)nDataLen); pRecord->pData = (const void *)zNew; pRecord->nDataLen = nData; } return UNQLITE_OK; } /* * Export the in-memory storage engine. */ UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void) { static const unqlite_kv_methods sMemStore = { "mem", /* zName */ sizeof(mem_hash_kv_engine), /* szKv */ sizeof(mem_hash_cursor), /* szCursor */ 1, /* iVersion */ MemHashInit, /* xInit */ MemHashRelease, /* xRelease */ MemHashConfigure, /* xConfig */ 0, /* xOpen */ MemHashReplace, /* xReplace */ MemHashAppend, /* xAppend */ MemHashInitCursor, /* xCursorInit */ MemHashCursorSeek, /* xSeek */ MemHashCursorFirst, /* xFirst */ MemHashCursorLast, /* xLast */ MemHashCursorValid, /* xValid */ MemHashCursorNext, /* xNext */ MemHashCursorPrev, /* xPrev */ MemHashCursorDelete, /* xDelete */ MemHashCursorKeyLength, /* xKeyLength */ MemHashCursorKey, /* xKey */ MemHashCursorDataLength, /* xDataLength */ MemHashCursorData, /* xData */ MemHashCursorReset, /* xReset */ 0 /* xRelease */ }; return &sMemStore; } /* * ---------------------------------------------------------- * File: os.c * MD5: e7ad243c3cd9e6aac5fba406eedb7766 * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: os.c v1.0 FreeBSD 2012-11-12 21:27 devel $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* OS interfaces abstraction layers: Mostly SQLite3 source tree */ /* ** The following routines are convenience wrappers around methods ** of the unqlite_file object. This is mostly just syntactic sugar. All ** of this would be completely automatic if UnQLite were coded using ** C++ instead of plain old C. */ UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset) { return id->pMethods->xRead(id, pBuf, amt, offset); } UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset) { return id->pMethods->xWrite(id, pBuf, amt, offset); } UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size) { return id->pMethods->xTruncate(id, size); } UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags) { return id->pMethods->xSync(id, flags); } UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize) { return id->pMethods->xFileSize(id, pSize); } UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType) { return id->pMethods->xLock(id, lockType); } UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType) { return id->pMethods->xUnlock(id, lockType); } UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut) { return id->pMethods->xCheckReservedLock(id, pResOut); } UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id) { if( id->pMethods->xSectorSize ){ return id->pMethods->xSectorSize(id); } return UNQLITE_DEFAULT_SECTOR_SIZE; } /* ** The next group of routines are convenience wrappers around the ** VFS methods. */ UNQLITE_PRIVATE int unqliteOsOpen( unqlite_vfs *pVfs, SyMemBackend *pAlloc, const char *zPath, unqlite_file **ppOut, unsigned int flags ) { unqlite_file *pFile; int rc; *ppOut = 0; if( zPath == 0 ){ /* May happen if dealing with an in-memory database */ return SXERR_EMPTY; } /* Allocate a new instance */ pFile = (unqlite_file *)SyMemBackendAlloc(pAlloc,sizeof(unqlite_file)+pVfs->szOsFile); if( pFile == 0 ){ return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pFile,sizeof(unqlite_file)+pVfs->szOsFile); /* Invoke the xOpen method of the underlying VFS */ rc = pVfs->xOpen(pVfs, zPath, pFile, flags); if( rc != UNQLITE_OK ){ SyMemBackendFree(pAlloc,pFile); pFile = 0; } *ppOut = pFile; return rc; } UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId) { int rc = UNQLITE_OK; if( pId ){ rc = pId->pMethods->xClose(pId); SyMemBackendFree(pAlloc,pId); } return rc; } UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync){ return pVfs->xDelete(pVfs, zPath, dirSync); } UNQLITE_PRIVATE int unqliteOsAccess( unqlite_vfs *pVfs, const char *zPath, int flags, int *pResOut ){ return pVfs->xAccess(pVfs, zPath, flags, pResOut); } /* * ---------------------------------------------------------- * File: os_unix.c * MD5: 5efd57d03f8fb988d081c5bcf5cc2998 * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: os_unix.c v1.3 FreeBSD 2013-04-05 01:10 devel $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* * Omit the whole layer from the build if compiling for platforms other than Unix (Linux, BSD, Solaris, OS X, etc.). * Note: Mostly SQLite3 source tree. */ #if defined(__UNIXES__) /** This file contains the VFS implementation for unix-like operating systems ** include Linux, MacOSX, *BSD, QNX, VxWorks, AIX, HPUX, and others. ** ** There are actually several different VFS implementations in this file. ** The differences are in the way that file locking is done. The default ** implementation uses Posix Advisory Locks. Alternative implementations ** use flock(), dot-files, various proprietary locking schemas, or simply ** skip locking all together. ** ** This source file is organized into divisions where the logic for various ** subfunctions is contained within the appropriate division. PLEASE ** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed ** in the correct division and should be clearly labeled. ** */ /* ** standard include files. */ #include #include #include #include #include #include #include #include #include #if defined(__APPLE__) # include #endif /* ** Allowed values of unixFile.fsFlags */ #define UNQLITE_FSFLAGS_IS_MSDOS 0x1 /* ** Default permissions when creating a new file */ #ifndef UNQLITE_DEFAULT_FILE_PERMISSIONS # define UNQLITE_DEFAULT_FILE_PERMISSIONS 0644 #endif /* ** Default permissions when creating auto proxy dir */ #ifndef UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS # define UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS 0755 #endif /* ** Maximum supported path-length. */ #define MAX_PATHNAME 512 /* ** Only set the lastErrno if the error code is a real error and not ** a normal expected return code of UNQLITE_BUSY or UNQLITE_OK */ #define IS_LOCK_ERROR(x) ((x != UNQLITE_OK) && (x != UNQLITE_BUSY)) /* Forward references */ typedef struct unixInodeInfo unixInodeInfo; /* An i-node */ typedef struct UnixUnusedFd UnixUnusedFd; /* An unused file descriptor */ /* ** Sometimes, after a file handle is closed by SQLite, the file descriptor ** cannot be closed immediately. In these cases, instances of the following ** structure are used to store the file descriptor while waiting for an ** opportunity to either close or reuse it. */ struct UnixUnusedFd { int fd; /* File descriptor to close */ int flags; /* Flags this file descriptor was opened with */ UnixUnusedFd *pNext; /* Next unused file descriptor on same file */ }; /* ** The unixFile structure is subclass of unqlite3_file specific to the unix ** VFS implementations. */ typedef struct unixFile unixFile; struct unixFile { const unqlite_io_methods *pMethod; /* Always the first entry */ unixInodeInfo *pInode; /* Info about locks on this inode */ int h; /* The file descriptor */ int dirfd; /* File descriptor for the directory */ unsigned char eFileLock; /* The type of lock held on this fd */ int lastErrno; /* The unix errno from last I/O error */ void *lockingContext; /* Locking style specific state */ UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */ int fileFlags; /* Miscellanous flags */ const char *zPath; /* Name of the file */ unsigned fsFlags; /* cached details from statfs() */ }; /* ** The following macros define bits in unixFile.fileFlags */ #define UNQLITE_WHOLE_FILE_LOCKING 0x0001 /* Use whole-file locking */ /* ** Define various macros that are missing from some systems. */ #ifndef O_LARGEFILE # define O_LARGEFILE 0 #endif #ifndef O_NOFOLLOW # define O_NOFOLLOW 0 #endif #ifndef O_BINARY # define O_BINARY 0 #endif /* ** Helper functions to obtain and relinquish the global mutex. The ** global mutex is used to protect the unixInodeInfo and ** vxworksFileId objects used by this file, all of which may be ** shared by multiple threads. ** ** Function unixMutexHeld() is used to assert() that the global mutex ** is held when required. This function is only used as part of assert() ** statements. e.g. ** ** unixEnterMutex() ** assert( unixMutexHeld() ); ** unixEnterLeave() */ static void unixEnterMutex(void){ #ifdef UNQLITE_ENABLE_THREADS const SyMutexMethods *pMutexMethods = SyMutexExportMethods(); if( pMutexMethods ){ SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */ SyMutexEnter(pMutexMethods,pMutex); } #endif /* UNQLITE_ENABLE_THREADS */ } static void unixLeaveMutex(void){ #ifdef UNQLITE_ENABLE_THREADS const SyMutexMethods *pMutexMethods = SyMutexExportMethods(); if( pMutexMethods ){ SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */ SyMutexLeave(pMutexMethods,pMutex); } #endif /* UNQLITE_ENABLE_THREADS */ } /* ** This routine translates a standard POSIX errno code into something ** useful to the clients of the unqlite3 functions. Specifically, it is ** intended to translate a variety of "try again" errors into UNQLITE_BUSY ** and a variety of "please close the file descriptor NOW" errors into ** UNQLITE_IOERR ** ** Errors during initialization of locks, or file system support for locks, ** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately. */ static int unqliteErrorFromPosixError(int posixError, int unqliteIOErr) { switch (posixError) { case 0: return UNQLITE_OK; case EAGAIN: case ETIMEDOUT: case EBUSY: case EINTR: case ENOLCK: /* random NFS retry error, unless during file system support * introspection, in which it actually means what it says */ return UNQLITE_BUSY; case EACCES: /* EACCES is like EAGAIN during locking operations, but not any other time*/ return UNQLITE_BUSY; case EPERM: return UNQLITE_PERM; case EDEADLK: return UNQLITE_IOERR; #if EOPNOTSUPP!=ENOTSUP case EOPNOTSUPP: /* something went terribly awry, unless during file system support * introspection, in which it actually means what it says */ #endif #ifdef ENOTSUP case ENOTSUP: /* invalid fd, unless during file system support introspection, in which * it actually means what it says */ #endif case EIO: case EBADF: case EINVAL: case ENOTCONN: case ENODEV: case ENXIO: case ENOENT: case ESTALE: case ENOSYS: /* these should force the client to close the file and reconnect */ default: return unqliteIOErr; } } /****************************************************************************** *************************** Posix Advisory Locking **************************** ** ** POSIX advisory locks are broken by design. ANSI STD 1003.1 (1996) ** section 6.5.2.2 lines 483 through 490 specify that when a process ** sets or clears a lock, that operation overrides any prior locks set ** by the same process. It does not explicitly say so, but this implies ** that it overrides locks set by the same process using a different ** file descriptor. Consider this test case: ** ** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644); ** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644); ** ** Suppose ./file1 and ./file2 are really the same file (because ** one is a hard or symbolic link to the other) then if you set ** an exclusive lock on fd1, then try to get an exclusive lock ** on fd2, it works. I would have expected the second lock to ** fail since there was already a lock on the file due to fd1. ** But not so. Since both locks came from the same process, the ** second overrides the first, even though they were on different ** file descriptors opened on different file names. ** ** This means that we cannot use POSIX locks to synchronize file access ** among competing threads of the same process. POSIX locks will work fine ** to synchronize access for threads in separate processes, but not ** threads within the same process. ** ** To work around the problem, SQLite has to manage file locks internally ** on its own. Whenever a new database is opened, we have to find the ** specific inode of the database file (the inode is determined by the ** st_dev and st_ino fields of the stat structure that fstat() fills in) ** and check for locks already existing on that inode. When locks are ** created or removed, we have to look at our own internal record of the ** locks to see if another thread has previously set a lock on that same ** inode. ** ** (Aside: The use of inode numbers as unique IDs does not work on VxWorks. ** For VxWorks, we have to use the alternative unique ID system based on ** canonical filename and implemented in the previous division.) ** ** There is one locking structure ** per inode, so if the same inode is opened twice, both unixFile structures ** point to the same locking structure. The locking structure keeps ** a reference count (so we will know when to delete it) and a "cnt" ** field that tells us its internal lock status. cnt==0 means the ** file is unlocked. cnt==-1 means the file has an exclusive lock. ** cnt>0 means there are cnt shared locks on the file. ** ** Any attempt to lock or unlock a file first checks the locking ** structure. The fcntl() system call is only invoked to set a ** POSIX lock if the internal lock structure transitions between ** a locked and an unlocked state. ** ** But wait: there are yet more problems with POSIX advisory locks. ** ** If you close a file descriptor that points to a file that has locks, ** all locks on that file that are owned by the current process are ** released. To work around this problem, each unixInodeInfo object ** maintains a count of the number of pending locks on that inode. ** When an attempt is made to close an unixFile, if there are ** other unixFile open on the same inode that are holding locks, the call ** to close() the file descriptor is deferred until all of the locks clear. ** The unixInodeInfo structure keeps a list of file descriptors that need to ** be closed and that list is walked (and cleared) when the last lock ** clears. ** ** Yet another problem: LinuxThreads do not play well with posix locks. ** ** Many older versions of linux use the LinuxThreads library which is ** not posix compliant. Under LinuxThreads, a lock created by thread ** A cannot be modified or overridden by a different thread B. ** Only thread A can modify the lock. Locking behavior is correct ** if the appliation uses the newer Native Posix Thread Library (NPTL) ** on linux - with NPTL a lock created by thread A can override locks ** in thread B. But there is no way to know at compile-time which ** threading library is being used. So there is no way to know at ** compile-time whether or not thread A can override locks on thread B. ** One has to do a run-time check to discover the behavior of the ** current process. ** */ /* ** An instance of the following structure serves as the key used ** to locate a particular unixInodeInfo object. */ struct unixFileId { dev_t dev; /* Device number */ ino_t ino; /* Inode number */ }; /* ** An instance of the following structure is allocated for each open ** inode. Or, on LinuxThreads, there is one of these structures for ** each inode opened by each thread. ** ** A single inode can have multiple file descriptors, so each unixFile ** structure contains a pointer to an instance of this object and this ** object keeps a count of the number of unixFile pointing to it. */ struct unixInodeInfo { struct unixFileId fileId; /* The lookup key */ int nShared; /* Number of SHARED locks held */ int eFileLock; /* One of SHARED_LOCK, RESERVED_LOCK etc. */ int nRef; /* Number of pointers to this structure */ int nLock; /* Number of outstanding file locks */ UnixUnusedFd *pUnused; /* Unused file descriptors to close */ unixInodeInfo *pNext; /* List of all unixInodeInfo objects */ unixInodeInfo *pPrev; /* .... doubly linked */ }; static unixInodeInfo *inodeList = 0; /* * Local memory allocation stuff. */ void * unqlite_malloc(unsigned int nByte) { SyMemBackend *pAlloc; void *p; pAlloc = (SyMemBackend *)unqliteExportMemBackend(); p = SyMemBackendAlloc(pAlloc,nByte); return p; } void unqlite_free(void *p) { SyMemBackend *pAlloc; pAlloc = (SyMemBackend *)unqliteExportMemBackend(); SyMemBackendFree(pAlloc,p); } /* ** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. ** If all such file descriptors are closed without error, the list is ** cleared and UNQLITE_OK returned. ** ** Otherwise, if an error occurs, then successfully closed file descriptor ** entries are removed from the list, and UNQLITE_IOERR_CLOSE returned. ** not deleted and UNQLITE_IOERR_CLOSE returned. */ static int closePendingFds(unixFile *pFile){ int rc = UNQLITE_OK; unixInodeInfo *pInode = pFile->pInode; UnixUnusedFd *pError = 0; UnixUnusedFd *p; UnixUnusedFd *pNext; for(p=pInode->pUnused; p; p=pNext){ pNext = p->pNext; if( close(p->fd) ){ pFile->lastErrno = errno; rc = UNQLITE_IOERR; p->pNext = pError; pError = p; }else{ unqlite_free(p); } } pInode->pUnused = pError; return rc; } /* ** Release a unixInodeInfo structure previously allocated by findInodeInfo(). ** ** The mutex entered using the unixEnterMutex() function must be held ** when this function is called. */ static void releaseInodeInfo(unixFile *pFile){ unixInodeInfo *pInode = pFile->pInode; if( pInode ){ pInode->nRef--; if( pInode->nRef==0 ){ closePendingFds(pFile); if( pInode->pPrev ){ pInode->pPrev->pNext = pInode->pNext; }else{ inodeList = pInode->pNext; } if( pInode->pNext ){ pInode->pNext->pPrev = pInode->pPrev; } unqlite_free(pInode); } } } /* ** Given a file descriptor, locate the unixInodeInfo object that ** describes that file descriptor. Create a new one if necessary. The ** return value might be uninitialized if an error occurs. ** ** The mutex entered using the unixEnterMutex() function must be held ** when this function is called. ** ** Return an appropriate error code. */ static int findInodeInfo( unixFile *pFile, /* Unix file with file desc used in the key */ unixInodeInfo **ppInode /* Return the unixInodeInfo object here */ ){ int rc; /* System call return code */ int fd; /* The file descriptor for pFile */ struct unixFileId fileId; /* Lookup key for the unixInodeInfo */ struct stat statbuf; /* Low-level file information */ unixInodeInfo *pInode = 0; /* Candidate unixInodeInfo object */ /* Get low-level information about the file that we can used to ** create a unique name for the file. */ fd = pFile->h; rc = fstat(fd, &statbuf); if( rc!=0 ){ pFile->lastErrno = errno; #ifdef EOVERFLOW if( pFile->lastErrno==EOVERFLOW ) return UNQLITE_NOTIMPLEMENTED; #endif return UNQLITE_IOERR; } #ifdef __APPLE__ /* On OS X on an msdos filesystem, the inode number is reported ** incorrectly for zero-size files. See ticket #3260. To work ** around this problem (we consider it a bug in OS X, not SQLite) ** we always increase the file size to 1 by writing a single byte ** prior to accessing the inode number. The one byte written is ** an ASCII 'S' character which also happens to be the first byte ** in the header of every SQLite database. In this way, if there ** is a race condition such that another thread has already populated ** the first page of the database, no damage is done. */ if( statbuf.st_size==0 && (pFile->fsFlags & UNQLITE_FSFLAGS_IS_MSDOS)!=0 ){ rc = write(fd, "S", 1); if( rc!=1 ){ pFile->lastErrno = errno; return UNQLITE_IOERR; } rc = fstat(fd, &statbuf); if( rc!=0 ){ pFile->lastErrno = errno; return UNQLITE_IOERR; } } #endif SyZero(&fileId,sizeof(fileId)); fileId.dev = statbuf.st_dev; fileId.ino = statbuf.st_ino; pInode = inodeList; while( pInode && SyMemcmp((const void *)&fileId,(const void *)&pInode->fileId, sizeof(fileId)) ){ pInode = pInode->pNext; } if( pInode==0 ){ pInode = (unixInodeInfo *)unqlite_malloc( sizeof(*pInode) ); if( pInode==0 ){ return UNQLITE_NOMEM; } SyZero(pInode,sizeof(*pInode)); SyMemcpy((const void *)&fileId,(void *)&pInode->fileId,sizeof(fileId)); pInode->nRef = 1; pInode->pNext = inodeList; pInode->pPrev = 0; if( inodeList ) inodeList->pPrev = pInode; inodeList = pInode; }else{ pInode->nRef++; } *ppInode = pInode; return UNQLITE_OK; } /* ** This routine checks if there is a RESERVED lock held on the specified ** file by this or any other process. If such a lock is held, set *pResOut ** to a non-zero value otherwise *pResOut is set to zero. The return value ** is set to UNQLITE_OK unless an I/O error occurs during lock checking. */ static int unixCheckReservedLock(unqlite_file *id, int *pResOut){ int rc = UNQLITE_OK; int reserved = 0; unixFile *pFile = (unixFile*)id; unixEnterMutex(); /* Because pFile->pInode is shared across threads */ /* Check if a thread in this process holds such a lock */ if( pFile->pInode->eFileLock>SHARED_LOCK ){ reserved = 1; } /* Otherwise see if some other process holds it. */ if( !reserved ){ struct flock lock; lock.l_whence = SEEK_SET; lock.l_start = RESERVED_BYTE; lock.l_len = 1; lock.l_type = F_WRLCK; if (-1 == fcntl(pFile->h, F_GETLK, &lock)) { int tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); pFile->lastErrno = tErrno; } else if( lock.l_type!=F_UNLCK ){ reserved = 1; } } unixLeaveMutex(); *pResOut = reserved; return rc; } /* ** Lock the file with the lock specified by parameter eFileLock - one ** of the following: ** ** (1) SHARED_LOCK ** (2) RESERVED_LOCK ** (3) PENDING_LOCK ** (4) EXCLUSIVE_LOCK ** ** Sometimes when requesting one lock state, additional lock states ** are inserted in between. The locking might fail on one of the later ** transitions leaving the lock state different from what it started but ** still short of its goal. The following chart shows the allowed ** transitions and the inserted intermediate states: ** ** UNLOCKED -> SHARED ** SHARED -> RESERVED ** SHARED -> (PENDING) -> EXCLUSIVE ** RESERVED -> (PENDING) -> EXCLUSIVE ** PENDING -> EXCLUSIVE ** ** This routine will only increase a lock. Use the unqliteOsUnlock() ** routine to lower a locking level. */ static int unixLock(unqlite_file *id, int eFileLock){ /* The following describes the implementation of the various locks and ** lock transitions in terms of the POSIX advisory shared and exclusive ** lock primitives (called read-locks and write-locks below, to avoid ** confusion with SQLite lock names). The algorithms are complicated ** slightly in order to be compatible with unixdows systems simultaneously ** accessing the same database file, in case that is ever required. ** ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved ** byte', each single bytes at well known offsets, and the 'shared byte ** range', a range of 510 bytes at a well known offset. ** ** To obtain a SHARED lock, a read-lock is obtained on the 'pending ** byte'. If this is successful, a random byte from the 'shared byte ** range' is read-locked and the lock on the 'pending byte' released. ** ** A process may only obtain a RESERVED lock after it has a SHARED lock. ** A RESERVED lock is implemented by grabbing a write-lock on the ** 'reserved byte'. ** ** A process may only obtain a PENDING lock after it has obtained a ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock ** on the 'pending byte'. This ensures that no new SHARED locks can be ** obtained, but existing SHARED locks are allowed to persist. A process ** does not have to obtain a RESERVED lock on the way to a PENDING lock. ** This property is used by the algorithm for rolling back a journal file ** after a crash. ** ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is ** implemented by obtaining a write-lock on the entire 'shared byte ** range'. Since all other locks require a read-lock on one of the bytes ** within this range, this ensures that no other locks are held on the ** database. ** ** The reason a single byte cannot be used instead of the 'shared byte ** range' is that some versions of unixdows do not support read-locks. By ** locking a random byte from a range, concurrent SHARED locks may exist ** even if the locking primitive used is always a write-lock. */ int rc = UNQLITE_OK; unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode = pFile->pInode; struct flock lock; int s = 0; int tErrno = 0; /* If there is already a lock of this type or more restrictive on the ** unixFile, do nothing. Don't use the end_lock: exit path, as ** unixEnterMutex() hasn't been called yet. */ if( pFile->eFileLock>=eFileLock ){ return UNQLITE_OK; } /* This mutex is needed because pFile->pInode is shared across threads */ unixEnterMutex(); pInode = pFile->pInode; /* If some thread using this PID has a lock via a different unixFile* ** handle that precludes the requested lock, return BUSY. */ if( (pFile->eFileLock!=pInode->eFileLock && (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) ){ rc = UNQLITE_BUSY; goto end_lock; } /* If a SHARED lock is requested, and some thread using this PID already ** has a SHARED or RESERVED lock, then increment reference counts and ** return UNQLITE_OK. */ if( eFileLock==SHARED_LOCK && (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ pFile->eFileLock = SHARED_LOCK; pInode->nShared++; pInode->nLock++; goto end_lock; } /* A PENDING lock is needed before acquiring a SHARED lock and before ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will ** be released. */ lock.l_len = 1L; lock.l_whence = SEEK_SET; if( eFileLock==SHARED_LOCK || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLockh, F_SETLK, &lock); if( s==(-1) ){ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } goto end_lock; } } /* If control gets to this point, then actually go ahead and make ** operating system calls for the specified lock. */ if( eFileLock==SHARED_LOCK ){ /* Now get the read-lock */ lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; if( (s = fcntl(pFile->h, F_SETLK, &lock))==(-1) ){ tErrno = errno; } /* Drop the temporary PENDING lock */ lock.l_start = PENDING_BYTE; lock.l_len = 1L; lock.l_type = F_UNLCK; if( fcntl(pFile->h, F_SETLK, &lock)!=0 ){ if( s != -1 ){ /* This could happen with a network mount */ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } goto end_lock; } } if( s==(-1) ){ rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } }else{ pFile->eFileLock = SHARED_LOCK; pInode->nLock++; pInode->nShared = 1; } }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){ /* We are trying for an exclusive lock but another thread in this ** same process is still holding a shared lock. */ rc = UNQLITE_BUSY; }else{ /* The request was for a RESERVED or EXCLUSIVE lock. It is ** assumed that there is a SHARED or greater lock on the file ** already. */ lock.l_type = F_WRLCK; switch( eFileLock ){ case RESERVED_LOCK: lock.l_start = RESERVED_BYTE; break; case EXCLUSIVE_LOCK: lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; break; default: /* Can't happen */ break; } s = fcntl(pFile->h, F_SETLK, &lock); if( s==(-1) ){ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } } } if( rc==UNQLITE_OK ){ pFile->eFileLock = eFileLock; pInode->eFileLock = eFileLock; }else if( eFileLock==EXCLUSIVE_LOCK ){ pFile->eFileLock = PENDING_LOCK; pInode->eFileLock = PENDING_LOCK; } end_lock: unixLeaveMutex(); return rc; } /* ** Add the file descriptor used by file handle pFile to the corresponding ** pUnused list. */ static void setPendingFd(unixFile *pFile){ unixInodeInfo *pInode = pFile->pInode; UnixUnusedFd *p = pFile->pUnused; p->pNext = pInode->pUnused; pInode->pUnused = p; pFile->h = -1; pFile->pUnused = 0; } /* ** Lower the locking level on file descriptor pFile to eFileLock. eFileLock ** must be either NO_LOCK or SHARED_LOCK. ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. ** ** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED ** the byte range is divided into 2 parts and the first part is unlocked then ** set to a read lock, then the other part is simply unlocked. This works ** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to ** remove the write lock on a region when a read lock is set. */ static int _posixUnlock(unqlite_file *id, int eFileLock, int handleNFSUnlock){ unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode; struct flock lock; int rc = UNQLITE_OK; int h; int tErrno; /* Error code from system call errors */ if( pFile->eFileLock<=eFileLock ){ return UNQLITE_OK; } unixEnterMutex(); h = pFile->h; pInode = pFile->pInode; if( pFile->eFileLock>SHARED_LOCK ){ /* downgrading to a shared lock on NFS involves clearing the write lock ** before establishing the readlock - to avoid a race condition we downgrade ** the lock in 2 blocks, so that part of the range will be covered by a ** write lock until the rest is covered by a read lock: ** 1: [WWWWW] ** 2: [....W] ** 3: [RRRRW] ** 4: [RRRR.] */ if( eFileLock==SHARED_LOCK ){ if( handleNFSUnlock ){ off_t divSize = SHARED_SIZE - 1; lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; lock.l_len = divSize; if( fcntl(h, F_SETLK, &lock)==(-1) ){ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } goto end_unlock; } lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; lock.l_len = divSize; if( fcntl(h, F_SETLK, &lock)==(-1) ){ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } goto end_unlock; } lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST+divSize; lock.l_len = SHARED_SIZE-divSize; if( fcntl(h, F_SETLK, &lock)==(-1) ){ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } goto end_unlock; } }else{ lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; if( fcntl(h, F_SETLK, &lock)==(-1) ){ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } goto end_unlock; } } } lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = PENDING_BYTE; lock.l_len = 2L; if( fcntl(h, F_SETLK, &lock)!=(-1) ){ pInode->eFileLock = SHARED_LOCK; }else{ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } goto end_unlock; } } if( eFileLock==NO_LOCK ){ /* Decrement the shared lock counter. Release the lock using an ** OS call only when all threads in this same process have released ** the lock. */ pInode->nShared--; if( pInode->nShared==0 ){ lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = lock.l_len = 0L; if( fcntl(h, F_SETLK, &lock)!=(-1) ){ pInode->eFileLock = NO_LOCK; }else{ tErrno = errno; rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); if( IS_LOCK_ERROR(rc) ){ pFile->lastErrno = tErrno; } pInode->eFileLock = NO_LOCK; pFile->eFileLock = NO_LOCK; } } /* Decrement the count of locks against this same file. When the ** count reaches zero, close any other file descriptors whose close ** was deferred because of outstanding locks. */ pInode->nLock--; if( pInode->nLock==0 ){ int rc2 = closePendingFds(pFile); if( rc==UNQLITE_OK ){ rc = rc2; } } } end_unlock: unixLeaveMutex(); if( rc==UNQLITE_OK ) pFile->eFileLock = eFileLock; return rc; } /* ** Lower the locking level on file descriptor pFile to eFileLock. eFileLock ** must be either NO_LOCK or SHARED_LOCK. ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. */ static int unixUnlock(unqlite_file *id, int eFileLock){ return _posixUnlock(id, eFileLock, 0); } /* ** This function performs the parts of the "close file" operation ** common to all locking schemes. It closes the directory and file ** handles, if they are valid, and sets all fields of the unixFile ** structure to 0. ** */ static int closeUnixFile(unqlite_file *id){ unixFile *pFile = (unixFile*)id; if( pFile ){ if( pFile->dirfd>=0 ){ int err = close(pFile->dirfd); if( err ){ pFile->lastErrno = errno; return UNQLITE_IOERR; }else{ pFile->dirfd=-1; } } if( pFile->h>=0 ){ int err = close(pFile->h); if( err ){ pFile->lastErrno = errno; return UNQLITE_IOERR; } } unqlite_free(pFile->pUnused); SyZero(pFile,sizeof(unixFile)); } return UNQLITE_OK; } /* ** Close a file. */ static int unixClose(unqlite_file *id){ int rc = UNQLITE_OK; if( id ){ unixFile *pFile = (unixFile *)id; unixUnlock(id, NO_LOCK); unixEnterMutex(); if( pFile->pInode && pFile->pInode->nLock ){ /* If there are outstanding locks, do not actually close the file just ** yet because that would clear those locks. Instead, add the file ** descriptor to pInode->pUnused list. It will be automatically closed ** when the last lock is cleared. */ setPendingFd(pFile); } releaseInodeInfo(pFile); rc = closeUnixFile(id); unixLeaveMutex(); } return rc; } /************** End of the posix advisory lock implementation ***************** ******************************************************************************/ /* ** ** The next division contains implementations for all methods of the ** unqlite_file object other than the locking methods. The locking ** methods were defined in divisions above (one locking method per ** division). Those methods that are common to all locking modes ** are gather together into this division. */ /* ** Seek to the offset passed as the second argument, then read cnt ** bytes into pBuf. Return the number of bytes actually read. ** ** NB: If you define USE_PREAD or USE_PREAD64, then it might also ** be necessary to define _XOPEN_SOURCE to be 500. This varies from ** one system to another. Since SQLite does not define USE_PREAD ** any form by default, we will not attempt to define _XOPEN_SOURCE. ** See tickets #2741 and #2681. ** ** To avoid stomping the errno value on a failed read the lastErrno value ** is set before returning. */ static int seekAndRead(unixFile *id, unqlite_int64 offset, void *pBuf, int cnt){ int got; #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) unqlite_int64 newOffset; #endif #if defined(USE_PREAD) got = pread(id->h, pBuf, cnt, offset); #elif defined(USE_PREAD64) got = pread64(id->h, pBuf, cnt, offset); #else newOffset = lseek(id->h, offset, SEEK_SET); if( newOffset!=offset ){ if( newOffset == -1 ){ ((unixFile*)id)->lastErrno = errno; }else{ ((unixFile*)id)->lastErrno = 0; } return -1; } got = read(id->h, pBuf, cnt); #endif if( got<0 ){ ((unixFile*)id)->lastErrno = errno; } return got; } /* ** Read data from a file into a buffer. Return UNQLITE_OK if all ** bytes were read successfully and UNQLITE_IOERR if anything goes ** wrong. */ static int unixRead( unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset ){ unixFile *pFile = (unixFile *)id; int got; got = seekAndRead(pFile, offset, pBuf, (int)amt); if( got==(int)amt ){ return UNQLITE_OK; }else if( got<0 ){ /* lastErrno set by seekAndRead */ return UNQLITE_IOERR; }else{ pFile->lastErrno = 0; /* not a system error */ /* Unread parts of the buffer must be zero-filled */ SyZero(&((char*)pBuf)[got],(sxu32)amt-got); return UNQLITE_IOERR; } } /* ** Seek to the offset in id->offset then read cnt bytes into pBuf. ** Return the number of bytes actually read. Update the offset. ** ** To avoid stomping the errno value on a failed write the lastErrno value ** is set before returning. */ static int seekAndWrite(unixFile *id, unqlite_int64 offset, const void *pBuf, unqlite_int64 cnt){ int got; #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) unqlite_int64 newOffset; #endif #if defined(USE_PREAD) got = pwrite(id->h, pBuf, cnt, offset); #elif defined(USE_PREAD64) got = pwrite64(id->h, pBuf, cnt, offset); #else newOffset = lseek(id->h, offset, SEEK_SET); if( newOffset!=offset ){ if( newOffset == -1 ){ ((unixFile*)id)->lastErrno = errno; }else{ ((unixFile*)id)->lastErrno = 0; } return -1; } got = write(id->h, pBuf, cnt); #endif if( got<0 ){ ((unixFile*)id)->lastErrno = errno; } return got; } /* ** Write data from a buffer into a file. Return UNQLITE_OK on success ** or some other error code on failure. */ static int unixWrite( unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset ){ unixFile *pFile = (unixFile*)id; int wrote = 0; while( amt>0 && (wrote = seekAndWrite(pFile, offset, pBuf, amt))>0 ){ amt -= wrote; offset += wrote; pBuf = &((char*)pBuf)[wrote]; } if( amt>0 ){ if( wrote<0 ){ /* lastErrno set by seekAndWrite */ return UNQLITE_IOERR; }else{ pFile->lastErrno = 0; /* not a system error */ return UNQLITE_FULL; } } return UNQLITE_OK; } /* ** We do not trust systems to provide a working fdatasync(). Some do. ** Others do no. To be safe, we will stick with the (slower) fsync(). ** If you know that your system does support fdatasync() correctly, ** then simply compile with -Dfdatasync=fdatasync */ #if !defined(fdatasync) && !defined(__linux__) # define fdatasync fsync #endif /* ** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not ** the F_FULLFSYNC macro is defined. F_FULLFSYNC is currently ** only available on Mac OS X. But that could change. */ #ifdef F_FULLFSYNC # define HAVE_FULLFSYNC 1 #else # define HAVE_FULLFSYNC 0 #endif /* ** The fsync() system call does not work as advertised on many ** unix systems. The following procedure is an attempt to make ** it work better. ** ** ** SQLite sets the dataOnly flag if the size of the file is unchanged. ** The idea behind dataOnly is that it should only write the file content ** to disk, not the inode. We only set dataOnly if the file size is ** unchanged since the file size is part of the inode. However, ** Ted Ts'o tells us that fdatasync() will also write the inode if the ** file size has changed. The only real difference between fdatasync() ** and fsync(), Ted tells us, is that fdatasync() will not flush the ** inode if the mtime or owner or other inode attributes have changed. ** We only care about the file size, not the other file attributes, so ** as far as SQLite is concerned, an fdatasync() is always adequate. ** So, we always use fdatasync() if it is available, regardless of ** the value of the dataOnly flag. */ static int full_fsync(int fd, int fullSync, int dataOnly){ int rc; #if HAVE_FULLFSYNC SXUNUSED(dataOnly); #else SXUNUSED(fullSync); SXUNUSED(dataOnly); #endif /* If we compiled with the UNQLITE_NO_SYNC flag, then syncing is a ** no-op */ #if HAVE_FULLFSYNC if( fullSync ){ rc = fcntl(fd, F_FULLFSYNC, 0); }else{ rc = 1; } /* If the FULLFSYNC failed, fall back to attempting an fsync(). ** It shouldn't be possible for fullfsync to fail on the local ** file system (on OSX), so failure indicates that FULLFSYNC ** isn't supported for this file system. So, attempt an fsync ** and (for now) ignore the overhead of a superfluous fcntl call. ** It'd be better to detect fullfsync support once and avoid ** the fcntl call every time sync is called. */ if( rc ) rc = fsync(fd); #elif defined(__APPLE__) /* fdatasync() on HFS+ doesn't yet flush the file size if it changed correctly ** so currently we default to the macro that redefines fdatasync to fsync */ rc = fsync(fd); #else rc = fdatasync(fd); #endif /* ifdef UNQLITE_NO_SYNC elif HAVE_FULLFSYNC */ if( rc!= -1 ){ rc = 0; } return rc; } /* ** Make sure all writes to a particular file are committed to disk. ** ** If dataOnly==0 then both the file itself and its metadata (file ** size, access time, etc) are synced. If dataOnly!=0 then only the ** file data is synced. ** ** Under Unix, also make sure that the directory entry for the file ** has been created by fsync-ing the directory that contains the file. ** If we do not do this and we encounter a power failure, the directory ** entry for the journal might not exist after we reboot. The next ** SQLite to access the file will not know that the journal exists (because ** the directory entry for the journal was never created) and the transaction ** will not roll back - possibly leading to database corruption. */ static int unixSync(unqlite_file *id, int flags){ int rc; unixFile *pFile = (unixFile*)id; int isDataOnly = (flags&UNQLITE_SYNC_DATAONLY); int isFullsync = (flags&0x0F)==UNQLITE_SYNC_FULL; rc = full_fsync(pFile->h, isFullsync, isDataOnly); if( rc ){ pFile->lastErrno = errno; return UNQLITE_IOERR; } if( pFile->dirfd>=0 ){ int err; #ifndef UNQLITE_DISABLE_DIRSYNC /* The directory sync is only attempted if full_fsync is ** turned off or unavailable. If a full_fsync occurred above, ** then the directory sync is superfluous. */ if( (!HAVE_FULLFSYNC || !isFullsync) && full_fsync(pFile->dirfd,0,0) ){ /* ** We have received multiple reports of fsync() returning ** errors when applied to directories on certain file systems. ** A failed directory sync is not a big deal. So it seems ** better to ignore the error. Ticket #1657 */ /* pFile->lastErrno = errno; */ /* return UNQLITE_IOERR; */ } #endif err = close(pFile->dirfd); /* Only need to sync once, so close the */ if( err==0 ){ /* directory when we are done */ pFile->dirfd = -1; }else{ pFile->lastErrno = errno; rc = UNQLITE_IOERR; } } return rc; } /* ** Truncate an open file to a specified size */ static int unixTruncate(unqlite_file *id, sxi64 nByte){ unixFile *pFile = (unixFile *)id; int rc; rc = ftruncate(pFile->h, (off_t)nByte); if( rc ){ pFile->lastErrno = errno; return UNQLITE_IOERR; }else{ return UNQLITE_OK; } } /* ** Determine the current size of a file in bytes */ static int unixFileSize(unqlite_file *id,sxi64 *pSize){ int rc; struct stat buf; rc = fstat(((unixFile*)id)->h, &buf); if( rc!=0 ){ ((unixFile*)id)->lastErrno = errno; return UNQLITE_IOERR; } *pSize = buf.st_size; /* When opening a zero-size database, the findInodeInfo() procedure ** writes a single byte into that file in order to work around a bug ** in the OS-X msdos filesystem. In order to avoid problems with upper ** layers, we need to report this file size as zero even though it is ** really 1. Ticket #3260. */ if( *pSize==1 ) *pSize = 0; return UNQLITE_OK; } /* ** Return the sector size in bytes of the underlying block device for ** the specified file. This is almost always 512 bytes, but may be ** larger for some devices. ** ** SQLite code assumes this function cannot fail. It also assumes that ** if two files are created in the same file-system directory (i.e. ** a database and its journal file) that the sector size will be the ** same for both. */ static int unixSectorSize(unqlite_file *NotUsed){ SXUNUSED(NotUsed); return UNQLITE_DEFAULT_SECTOR_SIZE; } /* ** This vector defines all the methods that can operate on an ** unqlite_file for Windows systems. */ static const unqlite_io_methods unixIoMethod = { 1, /* iVersion */ unixClose, /* xClose */ unixRead, /* xRead */ unixWrite, /* xWrite */ unixTruncate, /* xTruncate */ unixSync, /* xSync */ unixFileSize, /* xFileSize */ unixLock, /* xLock */ unixUnlock, /* xUnlock */ unixCheckReservedLock, /* xCheckReservedLock */ unixSectorSize, /* xSectorSize */ }; /**************************************************************************** **************************** unqlite_vfs methods **************************** ** ** This division contains the implementation of methods on the ** unqlite_vfs object. */ /* ** Initialize the contents of the unixFile structure pointed to by pId. */ static int fillInUnixFile( unqlite_vfs *pVfs, /* Pointer to vfs object */ int h, /* Open file descriptor of file being opened */ int dirfd, /* Directory file descriptor */ unqlite_file *pId, /* Write to the unixFile structure here */ const char *zFilename, /* Name of the file being opened */ int noLock, /* Omit locking if true */ int isDelete /* Delete on close if true */ ){ const unqlite_io_methods *pLockingStyle = &unixIoMethod; unixFile *pNew = (unixFile *)pId; int rc = UNQLITE_OK; /* Parameter isDelete is only used on vxworks. Express this explicitly ** here to prevent compiler warnings about unused parameters. */ SXUNUSED(isDelete); SXUNUSED(noLock); SXUNUSED(pVfs); pNew->h = h; pNew->dirfd = dirfd; pNew->fileFlags = 0; pNew->zPath = zFilename; unixEnterMutex(); rc = findInodeInfo(pNew, &pNew->pInode); if( rc!=UNQLITE_OK ){ /* If an error occured in findInodeInfo(), close the file descriptor ** immediately, before releasing the mutex. findInodeInfo() may fail ** in two scenarios: ** ** (a) A call to fstat() failed. ** (b) A malloc failed. ** ** Scenario (b) may only occur if the process is holding no other ** file descriptors open on the same file. If there were other file ** descriptors on this file, then no malloc would be required by ** findInodeInfo(). If this is the case, it is quite safe to close ** handle h - as it is guaranteed that no posix locks will be released ** by doing so. ** ** If scenario (a) caused the error then things are not so safe. The ** implicit assumption here is that if fstat() fails, things are in ** such bad shape that dropping a lock or two doesn't matter much. */ close(h); h = -1; } unixLeaveMutex(); pNew->lastErrno = 0; if( rc!=UNQLITE_OK ){ if( dirfd>=0 ) close(dirfd); /* silent leak if fail, already in error */ if( h>=0 ) close(h); }else{ pNew->pMethod = pLockingStyle; } return rc; } /* ** Open a file descriptor to the directory containing file zFilename. ** If successful, *pFd is set to the opened file descriptor and ** UNQLITE_OK is returned. If an error occurs, either UNQLITE_NOMEM ** or UNQLITE_CANTOPEN is returned and *pFd is set to an undefined ** value. ** ** If UNQLITE_OK is returned, the caller is responsible for closing ** the file descriptor *pFd using close(). */ static int openDirectory(const char *zFilename, int *pFd){ sxu32 ii; int fd = -1; char zDirname[MAX_PATHNAME+1]; sxu32 n; n = Systrcpy(zDirname,sizeof(zDirname),zFilename,0); for(ii=n; ii>1 && zDirname[ii]!='/'; ii--); if( ii>0 ){ zDirname[ii] = '\0'; fd = open(zDirname, O_RDONLY|O_BINARY, 0); if( fd>=0 ){ #ifdef FD_CLOEXEC fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); #endif } } *pFd = fd; return (fd>=0?UNQLITE_OK: UNQLITE_IOERR ); } /* ** Search for an unused file descriptor that was opened on the database ** file (not a journal or master-journal file) identified by pathname ** zPath with UNQLITE_OPEN_XXX flags matching those passed as the second ** argument to this function. ** ** Such a file descriptor may exist if a database connection was closed ** but the associated file descriptor could not be closed because some ** other file descriptor open on the same file is holding a file-lock. ** Refer to comments in the unixClose() function and the lengthy comment ** describing "Posix Advisory Locking" at the start of this file for ** further details. Also, ticket #4018. ** ** If a suitable file descriptor is found, then it is returned. If no ** such file descriptor is located, -1 is returned. */ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ UnixUnusedFd *pUnused = 0; struct stat sStat; /* Results of stat() call */ /* A stat() call may fail for various reasons. If this happens, it is ** almost certain that an open() call on the same path will also fail. ** For this reason, if an error occurs in the stat() call here, it is ** ignored and -1 is returned. The caller will try to open a new file ** descriptor on the same path, fail, and return an error to SQLite. ** ** Even if a subsequent open() call does succeed, the consequences of ** not searching for a resusable file descriptor are not dire. */ if( 0==stat(zPath, &sStat) ){ unixInodeInfo *pInode; unixEnterMutex(); pInode = inodeList; while( pInode && (pInode->fileId.dev!=sStat.st_dev || pInode->fileId.ino!=sStat.st_ino) ){ pInode = pInode->pNext; } if( pInode ){ UnixUnusedFd **pp; for(pp=&pInode->pUnused; *pp && (*pp)->flags!=flags; pp=&((*pp)->pNext)); pUnused = *pp; if( pUnused ){ *pp = pUnused->pNext; } } unixLeaveMutex(); } return pUnused; } /* ** This function is called by unixOpen() to determine the unix permissions ** to create new files with. If no error occurs, then UNQLITE_OK is returned ** and a value suitable for passing as the third argument to open(2) is ** written to *pMode. If an IO error occurs, an SQLite error code is ** returned and the value of *pMode is not modified. ** ** If the file being opened is a temporary file, it is always created with ** the octal permissions 0600 (read/writable by owner only). If the file ** is a database or master journal file, it is created with the permissions ** mask UNQLITE_DEFAULT_FILE_PERMISSIONS. ** ** Finally, if the file being opened is a WAL or regular journal file, then ** this function queries the file-system for the permissions on the ** corresponding database file and sets *pMode to this value. Whenever ** possible, WAL and journal files are created using the same permissions ** as the associated database file. */ static int findCreateFileMode( const char *zPath, /* Path of file (possibly) being created */ int flags, /* Flags passed as 4th argument to xOpen() */ mode_t *pMode /* OUT: Permissions to open file with */ ){ int rc = UNQLITE_OK; /* Return Code */ if( flags & UNQLITE_OPEN_TEMP_DB ){ *pMode = 0600; SXUNUSED(zPath); }else{ *pMode = UNQLITE_DEFAULT_FILE_PERMISSIONS; } return rc; } /* ** Open the file zPath. ** ** Previously, the SQLite OS layer used three functions in place of this ** one: ** ** unqliteOsOpenReadWrite(); ** unqliteOsOpenReadOnly(); ** unqliteOsOpenExclusive(); ** ** These calls correspond to the following combinations of flags: ** ** ReadWrite() -> (READWRITE | CREATE) ** ReadOnly() -> (READONLY) ** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE) ** ** The old OpenExclusive() accepted a boolean argument - "delFlag". If ** true, the file was configured to be automatically deleted when the ** file handle closed. To achieve the same effect using this new ** interface, add the DELETEONCLOSE flag to those specified above for ** OpenExclusive(). */ static int unixOpen( unqlite_vfs *pVfs, /* The VFS for which this is the xOpen method */ const char *zPath, /* Pathname of file to be opened */ unqlite_file *pFile, /* The file descriptor to be filled in */ unsigned int flags /* Input flags to control the opening */ ){ unixFile *p = (unixFile *)pFile; int fd = -1; /* File descriptor returned by open() */ int dirfd = -1; /* Directory file descriptor */ int openFlags = 0; /* Flags to pass to open() */ int noLock; /* True to omit locking primitives */ int rc = UNQLITE_OK; /* Function Return Code */ UnixUnusedFd *pUnused; int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE); int isDelete = (flags & UNQLITE_OPEN_TEMP_DB); int isCreate = (flags & UNQLITE_OPEN_CREATE); int isReadonly = (flags & UNQLITE_OPEN_READONLY); int isReadWrite = (flags & UNQLITE_OPEN_READWRITE); /* If creating a master or main-file journal, this function will open ** a file-descriptor on the directory too. The first time unixSync() ** is called the directory file descriptor will be fsync()ed and close()d. */ #ifndef UNQLITE_DISABLE_DIRSYNC int isOpenDirectory = isCreate; #endif const char *zName = zPath; SyZero(p,sizeof(unixFile)); pUnused = findReusableFd(zName, flags); if( pUnused ){ fd = pUnused->fd; }else{ pUnused = unqlite_malloc(sizeof(*pUnused)); if( !pUnused ){ return UNQLITE_NOMEM; } } p->pUnused = pUnused; /* Determine the value of the flags parameter passed to POSIX function ** open(). These must be calculated even if open() is not called, as ** they may be stored as part of the file handle and used by the ** 'conch file' locking functions later on. */ if( isReadonly ) openFlags |= O_RDONLY; if( isReadWrite ) openFlags |= O_RDWR; if( isCreate ) openFlags |= O_CREAT; if( isExclusive ) openFlags |= (O_EXCL|O_NOFOLLOW); openFlags |= (O_LARGEFILE|O_BINARY); if( fd<0 ){ mode_t openMode; /* Permissions to create file with */ rc = findCreateFileMode(zName, flags, &openMode); if( rc!=UNQLITE_OK ){ return rc; } fd = open(zName, openFlags, openMode); if( fd<0 ){ rc = UNQLITE_IOERR; goto open_finished; } } if( p->pUnused ){ p->pUnused->fd = fd; p->pUnused->flags = flags; } if( isDelete ){ unlink(zName); } #ifndef UNQLITE_DISABLE_DIRSYNC if( isOpenDirectory ){ rc = openDirectory(zPath, &dirfd); if( rc!=UNQLITE_OK ){ /* It is safe to close fd at this point, because it is guaranteed not ** to be open on a database file. If it were open on a database file, ** it would not be safe to close as this would release any locks held ** on the file by this process. */ close(fd); /* silently leak if fail, already in error */ goto open_finished; } } #endif #ifdef FD_CLOEXEC fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); #endif noLock = 0; #if defined(__APPLE__) struct statfs fsInfo; if( fstatfs(fd, &fsInfo) == -1 ){ ((unixFile*)pFile)->lastErrno = errno; if( dirfd>=0 ) close(dirfd); /* silently leak if fail, in error */ close(fd); /* silently leak if fail, in error */ return UNQLITE_IOERR; } if (0 == SyStrncmp("msdos", fsInfo.f_fstypename, 5)) { ((unixFile*)pFile)->fsFlags |= UNQLITE_FSFLAGS_IS_MSDOS; } #endif rc = fillInUnixFile(pVfs, fd, dirfd, pFile, zPath, noLock, isDelete); open_finished: if( rc!=UNQLITE_OK ){ unqlite_free(p->pUnused); } return rc; } /* ** Delete the file at zPath. If the dirSync argument is true, fsync() ** the directory after deleting the file. */ static int unixDelete( unqlite_vfs *NotUsed, /* VFS containing this as the xDelete method */ const char *zPath, /* Name of file to be deleted */ int dirSync /* If true, fsync() directory after deleting file */ ){ int rc = UNQLITE_OK; SXUNUSED(NotUsed); if( unlink(zPath)==(-1) && errno!=ENOENT ){ return UNQLITE_IOERR; } #ifndef UNQLITE_DISABLE_DIRSYNC if( dirSync ){ int fd; rc = openDirectory(zPath, &fd); if( rc==UNQLITE_OK ){ if( fsync(fd) ) { rc = UNQLITE_IOERR; } if( close(fd) && !rc ){ rc = UNQLITE_IOERR; } } } #endif return rc; } /* ** Sleep for a little while. Return the amount of time slept. ** The argument is the number of microseconds we want to sleep. ** The return value is the number of microseconds of sleep actually ** requested from the underlying operating system, a number which ** might be greater than or equal to the argument, but not less ** than the argument. */ static int unixSleep(unqlite_vfs *NotUsed, int microseconds) { #if defined(HAVE_USLEEP) && HAVE_USLEEP usleep(microseconds); SXUNUSED(NotUsed); return microseconds; #else int seconds = (microseconds+999999)/1000000; SXUNUSED(NotUsed); sleep(seconds); return seconds*1000000; #endif } /* * Export the current system time. */ static int unixCurrentTime(unqlite_vfs *pVfs,Sytm *pOut) { struct tm *pTm; time_t tt; SXUNUSED(pVfs); time(&tt); pTm = gmtime(&tt); if( pTm ){ /* Yes, it can fail */ STRUCT_TM_TO_SYTM(pTm,pOut); } return UNQLITE_OK; } /* ** Test the existance of or access permissions of file zPath. The ** test performed depends on the value of flags: ** ** UNQLITE_ACCESS_EXISTS: Return 1 if the file exists ** UNQLITE_ACCESS_READWRITE: Return 1 if the file is read and writable. ** UNQLITE_ACCESS_READONLY: Return 1 if the file is readable. ** ** Otherwise return 0. */ static int unixAccess( unqlite_vfs *NotUsed, /* The VFS containing this xAccess method */ const char *zPath, /* Path of the file to examine */ int flags, /* What do we want to learn about the zPath file? */ int *pResOut /* Write result boolean here */ ){ int amode = 0; SXUNUSED(NotUsed); switch( flags ){ case UNQLITE_ACCESS_EXISTS: amode = F_OK; break; case UNQLITE_ACCESS_READWRITE: amode = W_OK|R_OK; break; case UNQLITE_ACCESS_READ: amode = R_OK; break; default: /* Can't happen */ break; } *pResOut = (access(zPath, amode)==0); if( flags==UNQLITE_ACCESS_EXISTS && *pResOut ){ struct stat buf; if( 0==stat(zPath, &buf) && buf.st_size==0 ){ *pResOut = 0; } } return UNQLITE_OK; } /* ** Turn a relative pathname into a full pathname. The relative path ** is stored as a nul-terminated string in the buffer pointed to by ** zPath. ** ** zOut points to a buffer of at least unqlite_vfs.mxPathname bytes ** (in this case, MAX_PATHNAME bytes). The full-path is written to ** this buffer before returning. */ static int unixFullPathname( unqlite_vfs *pVfs, /* Pointer to vfs object */ const char *zPath, /* Possibly relative input path */ int nOut, /* Size of output buffer in bytes */ char *zOut /* Output buffer */ ){ if( zPath[0]=='/' ){ Systrcpy(zOut,(sxu32)nOut,zPath,0); SXUNUSED(pVfs); }else{ sxu32 nCwd; zOut[nOut-1] = '\0'; if( getcwd(zOut, nOut-1)==0 ){ return UNQLITE_IOERR; } nCwd = SyStrlen(zOut); SyBufferFormat(&zOut[nCwd],(sxu32)nOut-nCwd,"/%s",zPath); } return UNQLITE_OK; } /* * Export the Unix Vfs. */ UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void) { static const unqlite_vfs sUnixvfs = { "Unix", /* Vfs name */ 1, /* Vfs structure version */ sizeof(unixFile), /* szOsFile */ MAX_PATHNAME, /* mxPathName */ unixOpen, /* xOpen */ unixDelete, /* xDelete */ unixAccess, /* xAccess */ unixFullPathname, /* xFullPathname */ 0, /* xTmp */ unixSleep, /* xSleep */ unixCurrentTime, /* xCurrentTime */ 0, /* xGetLastError */ }; return &sUnixvfs; } #endif /* __UNIXES__ */ /* * ---------------------------------------------------------- * File: os_win.c * MD5: ab70fb386c21b39a08b0eb776a8391ab * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: os_win.c v1.2 Win7 2012-11-10 12:10 devel $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* Omit the whole layer from the build if compiling for platforms other than Windows */ #ifdef __WINNT__ /* This file contains code that is specific to windows. (Mostly SQLite3 source tree) */ #include /* ** Some microsoft compilers lack this definition. */ #ifndef INVALID_FILE_ATTRIBUTES # define INVALID_FILE_ATTRIBUTES ((DWORD)-1) #endif /* ** WinCE lacks native support for file locking so we have to fake it ** with some code of our own. */ #ifdef __WIN_CE__ typedef struct winceLock { int nReaders; /* Number of reader locks obtained */ BOOL bPending; /* Indicates a pending lock has been obtained */ BOOL bReserved; /* Indicates a reserved lock has been obtained */ BOOL bExclusive; /* Indicates an exclusive lock has been obtained */ } winceLock; #define AreFileApisANSI() 1 #define FormatMessageW(a,b,c,d,e,f,g) 0 #endif /* ** The winFile structure is a subclass of unqlite_file* specific to the win32 ** portability layer. */ typedef struct winFile winFile; struct winFile { const unqlite_io_methods *pMethod; /*** Must be first ***/ unqlite_vfs *pVfs; /* The VFS used to open this file */ HANDLE h; /* Handle for accessing the file */ sxu8 locktype; /* Type of lock currently held on this file */ short sharedLockByte; /* Randomly chosen byte used as a shared lock */ DWORD lastErrno; /* The Windows errno from the last I/O error */ DWORD sectorSize; /* Sector size of the device file is on */ int szChunk; /* Chunk size */ #ifdef __WIN_CE__ WCHAR *zDeleteOnClose; /* Name of file to delete when closing */ HANDLE hMutex; /* Mutex used to control access to shared lock */ HANDLE hShared; /* Shared memory segment used for locking */ winceLock local; /* Locks obtained by this instance of winFile */ winceLock *shared; /* Global shared lock memory for the file */ #endif }; /* ** Convert a UTF-8 string to microsoft unicode (UTF-16?). ** ** Space to hold the returned string is obtained from HeapAlloc(). */ static WCHAR *utf8ToUnicode(const char *zFilename){ int nChar; WCHAR *zWideFilename; nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0); zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nChar*sizeof(zWideFilename[0]) ); if( zWideFilename==0 ){ return 0; } nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar); if( nChar==0 ){ HeapFree(GetProcessHeap(),0,zWideFilename); zWideFilename = 0; } return zWideFilename; } /* ** Convert microsoft unicode to UTF-8. Space to hold the returned string is ** obtained from malloc(). */ static char *unicodeToUtf8(const WCHAR *zWideFilename){ int nByte; char *zFilename; nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); zFilename = (char *)HeapAlloc(GetProcessHeap(),0,nByte ); if( zFilename==0 ){ return 0; } nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, 0, 0); if( nByte == 0 ){ HeapFree(GetProcessHeap(),0,zFilename); zFilename = 0; } return zFilename; } /* ** Convert an ansi string to microsoft unicode, based on the ** current codepage settings for file apis. ** ** Space to hold the returned string is obtained ** from malloc. */ static WCHAR *mbcsToUnicode(const char *zFilename){ int nByte; WCHAR *zMbcsFilename; int codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, 0,0)*sizeof(WCHAR); zMbcsFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zMbcsFilename[0]) ); if( zMbcsFilename==0 ){ return 0; } nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte); if( nByte==0 ){ HeapFree(GetProcessHeap(),0,zMbcsFilename); zMbcsFilename = 0; } return zMbcsFilename; } /* ** Convert multibyte character string to UTF-8. Space to hold the ** returned string is obtained from malloc(). */ char *unqlite_win32_mbcs_to_utf8(const char *zFilename){ char *zFilenameUtf8; WCHAR *zTmpWide; zTmpWide = mbcsToUnicode(zFilename); if( zTmpWide==0 ){ return 0; } zFilenameUtf8 = unicodeToUtf8(zTmpWide); HeapFree(GetProcessHeap(),0,zTmpWide); return zFilenameUtf8; } /* ** Some microsoft compilers lack this definition. */ #ifndef INVALID_SET_FILE_POINTER # define INVALID_SET_FILE_POINTER ((DWORD)-1) #endif /* ** Move the current position of the file handle passed as the first ** argument to offset iOffset within the file. If successful, return 0. ** Otherwise, set pFile->lastErrno and return non-zero. */ static int seekWinFile(winFile *pFile, unqlite_int64 iOffset){ LONG upperBits; /* Most sig. 32 bits of new offset */ LONG lowerBits; /* Least sig. 32 bits of new offset */ DWORD dwRet; /* Value returned by SetFilePointer() */ upperBits = (LONG)((iOffset>>32) & 0x7fffffff); lowerBits = (LONG)(iOffset & 0xffffffff); /* API oddity: If successful, SetFilePointer() returns a dword ** containing the lower 32-bits of the new file-offset. Or, if it fails, ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine ** whether an error has actually occured, it is also necessary to call ** GetLastError(). */ dwRet = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN); if( (dwRet==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR) ){ pFile->lastErrno = GetLastError(); return 1; } return 0; } /* ** Close a file. ** ** It is reported that an attempt to close a handle might sometimes ** fail. This is a very unreasonable result, but windows is notorious ** for being unreasonable so I do not doubt that it might happen. If ** the close fails, we pause for 100 milliseconds and try again. As ** many as MX_CLOSE_ATTEMPT attempts to close the handle are made before ** giving up and returning an error. */ #define MX_CLOSE_ATTEMPT 3 static int winClose(unqlite_file *id) { int rc, cnt = 0; winFile *pFile = (winFile*)id; do{ rc = CloseHandle(pFile->h); }while( rc==0 && ++cnt < MX_CLOSE_ATTEMPT && (Sleep(100), 1) ); return rc ? UNQLITE_OK : UNQLITE_IOERR; } /* ** Read data from a file into a buffer. Return UNQLITE_OK if all ** bytes were read successfully and UNQLITE_IOERR if anything goes ** wrong. */ static int winRead( unqlite_file *id, /* File to read from */ void *pBuf, /* Write content into this buffer */ unqlite_int64 amt, /* Number of bytes to read */ unqlite_int64 offset /* Begin reading at this offset */ ){ winFile *pFile = (winFile*)id; /* file handle */ DWORD nRead; /* Number of bytes actually read from file */ if( seekWinFile(pFile, offset) ){ return UNQLITE_FULL; } if( !ReadFile(pFile->h, pBuf, (DWORD)amt, &nRead, 0) ){ pFile->lastErrno = GetLastError(); return UNQLITE_IOERR; } if( nRead<(DWORD)amt ){ /* Unread parts of the buffer must be zero-filled */ SyZero(&((char*)pBuf)[nRead],(sxu32)(amt-nRead)); return UNQLITE_IOERR; } return UNQLITE_OK; } /* ** Write data from a buffer into a file. Return UNQLITE_OK on success ** or some other error code on failure. */ static int winWrite( unqlite_file *id, /* File to write into */ const void *pBuf, /* The bytes to be written */ unqlite_int64 amt, /* Number of bytes to write */ unqlite_int64 offset /* Offset into the file to begin writing at */ ){ int rc; /* True if error has occured, else false */ winFile *pFile = (winFile*)id; /* File handle */ rc = seekWinFile(pFile, offset); if( rc==0 ){ sxu8 *aRem = (sxu8 *)pBuf; /* Data yet to be written */ unqlite_int64 nRem = amt; /* Number of bytes yet to be written */ DWORD nWrite; /* Bytes written by each WriteFile() call */ while( nRem>0 && WriteFile(pFile->h, aRem, (DWORD)nRem, &nWrite, 0) && nWrite>0 ){ aRem += nWrite; nRem -= nWrite; } if( nRem>0 ){ pFile->lastErrno = GetLastError(); rc = 1; } } if( rc ){ if( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ){ return UNQLITE_FULL; } return UNQLITE_IOERR; } return UNQLITE_OK; } /* ** Truncate an open file to a specified size */ static int winTruncate(unqlite_file *id, unqlite_int64 nByte){ winFile *pFile = (winFile*)id; /* File handle object */ int rc = UNQLITE_OK; /* Return code for this function */ /* If the user has configured a chunk-size for this file, truncate the ** file so that it consists of an integer number of chunks (i.e. the ** actual file size after the operation may be larger than the requested ** size). */ if( pFile->szChunk ){ nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; } /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */ if( seekWinFile(pFile, nByte) ){ rc = UNQLITE_IOERR; }else if( 0==SetEndOfFile(pFile->h) ){ pFile->lastErrno = GetLastError(); rc = UNQLITE_IOERR; } return rc; } /* ** Make sure all writes to a particular file are committed to disk. */ static int winSync(unqlite_file *id, int flags){ winFile *pFile = (winFile*)id; SXUNUSED(flags); /* MSVC warning */ if( FlushFileBuffers(pFile->h) ){ return UNQLITE_OK; }else{ pFile->lastErrno = GetLastError(); return UNQLITE_IOERR; } } /* ** Determine the current size of a file in bytes */ static int winFileSize(unqlite_file *id, unqlite_int64 *pSize){ DWORD upperBits; DWORD lowerBits; winFile *pFile = (winFile*)id; DWORD error; lowerBits = GetFileSize(pFile->h, &upperBits); if( (lowerBits == INVALID_FILE_SIZE) && ((error = GetLastError()) != NO_ERROR) ) { pFile->lastErrno = error; return UNQLITE_IOERR; } *pSize = (((unqlite_int64)upperBits)<<32) + lowerBits; return UNQLITE_OK; } /* ** LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. */ #ifndef LOCKFILE_FAIL_IMMEDIATELY # define LOCKFILE_FAIL_IMMEDIATELY 1 #endif /* ** Acquire a reader lock. */ static int getReadLock(winFile *pFile){ int res; OVERLAPPED ovlp; ovlp.Offset = SHARED_FIRST; ovlp.OffsetHigh = 0; ovlp.hEvent = 0; res = LockFileEx(pFile->h, LOCKFILE_FAIL_IMMEDIATELY,0, SHARED_SIZE, 0, &ovlp); if( res == 0 ){ pFile->lastErrno = GetLastError(); } return res; } /* ** Undo a readlock */ static int unlockReadLock(winFile *pFile){ int res; res = UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); if( res == 0 ){ pFile->lastErrno = GetLastError(); } return res; } /* ** Lock the file with the lock specified by parameter locktype - one ** of the following: ** ** (1) SHARED_LOCK ** (2) RESERVED_LOCK ** (3) PENDING_LOCK ** (4) EXCLUSIVE_LOCK ** ** Sometimes when requesting one lock state, additional lock states ** are inserted in between. The locking might fail on one of the later ** transitions leaving the lock state different from what it started but ** still short of its goal. The following chart shows the allowed ** transitions and the inserted intermediate states: ** ** UNLOCKED -> SHARED ** SHARED -> RESERVED ** SHARED -> (PENDING) -> EXCLUSIVE ** RESERVED -> (PENDING) -> EXCLUSIVE ** PENDING -> EXCLUSIVE ** ** This routine will only increase a lock. The winUnlock() routine ** erases all locks at once and returns us immediately to locking level 0. ** It is not possible to lower the locking level one step at a time. You ** must go straight to locking level 0. */ static int winLock(unqlite_file *id, int locktype){ int rc = UNQLITE_OK; /* Return code from subroutines */ int res = 1; /* Result of a windows lock call */ int newLocktype; /* Set pFile->locktype to this value before exiting */ int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */ winFile *pFile = (winFile*)id; DWORD error = NO_ERROR; /* If there is already a lock of this type or more restrictive on the ** OsFile, do nothing. */ if( pFile->locktype>=locktype ){ return UNQLITE_OK; } /* Make sure the locking sequence is correct assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK ); assert( locktype!=PENDING_LOCK ); assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK ); */ /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of ** the PENDING_LOCK byte is temporary. */ newLocktype = pFile->locktype; if( (pFile->locktype==NO_LOCK) || ( (locktype==EXCLUSIVE_LOCK) && (pFile->locktype==RESERVED_LOCK)) ){ int cnt = 3; while( cnt-->0 && (res = LockFile(pFile->h, PENDING_BYTE, 0, 1, 0))==0 ){ /* Try 3 times to get the pending lock. The pending lock might be ** held by another reader process who will release it momentarily. */ Sleep(1); } gotPendingLock = res; if( !res ){ error = GetLastError(); } } /* Acquire a shared lock */ if( locktype==SHARED_LOCK && res ){ /* assert( pFile->locktype==NO_LOCK ); */ res = getReadLock(pFile); if( res ){ newLocktype = SHARED_LOCK; }else{ error = GetLastError(); } } /* Acquire a RESERVED lock */ if( locktype==RESERVED_LOCK && res ){ /* assert( pFile->locktype==SHARED_LOCK ); */ res = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); if( res ){ newLocktype = RESERVED_LOCK; }else{ error = GetLastError(); } } /* Acquire a PENDING lock */ if( locktype==EXCLUSIVE_LOCK && res ){ newLocktype = PENDING_LOCK; gotPendingLock = 0; } /* Acquire an EXCLUSIVE lock */ if( locktype==EXCLUSIVE_LOCK && res ){ /* assert( pFile->locktype>=SHARED_LOCK ); */ res = unlockReadLock(pFile); res = LockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); if( res ){ newLocktype = EXCLUSIVE_LOCK; }else{ error = GetLastError(); getReadLock(pFile); } } /* If we are holding a PENDING lock that ought to be released, then ** release it now. */ if( gotPendingLock && locktype==SHARED_LOCK ){ UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0); } /* Update the state of the lock has held in the file descriptor then ** return the appropriate result code. */ if( res ){ rc = UNQLITE_OK; }else{ pFile->lastErrno = error; rc = UNQLITE_BUSY; } pFile->locktype = (sxu8)newLocktype; return rc; } /* ** This routine checks if there is a RESERVED lock held on the specified ** file by this or any other process. If such a lock is held, return ** non-zero, otherwise zero. */ static int winCheckReservedLock(unqlite_file *id, int *pResOut){ int rc; winFile *pFile = (winFile*)id; if( pFile->locktype>=RESERVED_LOCK ){ rc = 1; }else{ rc = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); if( rc ){ UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); } rc = !rc; } *pResOut = rc; return UNQLITE_OK; } /* ** Lower the locking level on file descriptor id to locktype. locktype ** must be either NO_LOCK or SHARED_LOCK. ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. ** ** It is not possible for this routine to fail if the second argument ** is NO_LOCK. If the second argument is SHARED_LOCK then this routine ** might return UNQLITE_IOERR; */ static int winUnlock(unqlite_file *id, int locktype){ int type; winFile *pFile = (winFile*)id; int rc = UNQLITE_OK; type = pFile->locktype; if( type>=EXCLUSIVE_LOCK ){ UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); if( locktype==SHARED_LOCK && !getReadLock(pFile) ){ /* This should never happen. We should always be able to ** reacquire the read lock */ rc = UNQLITE_IOERR; } } if( type>=RESERVED_LOCK ){ UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); } if( locktype==NO_LOCK && type>=SHARED_LOCK ){ unlockReadLock(pFile); } if( type>=PENDING_LOCK ){ UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0); } pFile->locktype = (sxu8)locktype; return rc; } /* ** Return the sector size in bytes of the underlying block device for ** the specified file. This is almost always 512 bytes, but may be ** larger for some devices. ** */ static int winSectorSize(unqlite_file *id){ return (int)(((winFile*)id)->sectorSize); } /* ** This vector defines all the methods that can operate on an ** unqlite_file for Windows systems. */ static const unqlite_io_methods winIoMethod = { 1, /* iVersion */ winClose, /* xClose */ winRead, /* xRead */ winWrite, /* xWrite */ winTruncate, /* xTruncate */ winSync, /* xSync */ winFileSize, /* xFileSize */ winLock, /* xLock */ winUnlock, /* xUnlock */ winCheckReservedLock, /* xCheckReservedLock */ winSectorSize, /* xSectorSize */ }; /* * Windows VFS Methods. */ /* ** Convert a UTF-8 filename into whatever form the underlying ** operating system wants filenames in. Space to hold the result ** is obtained from malloc and must be freed by the calling ** function. */ static void *convertUtf8Filename(const char *zFilename) { void *zConverted; zConverted = utf8ToUnicode(zFilename); /* caller will handle out of memory */ return zConverted; } /* ** Delete the named file. ** ** Note that windows does not allow a file to be deleted if some other ** process has it open. Sometimes a virus scanner or indexing program ** will open a journal file shortly after it is created in order to do ** whatever it does. While this other process is holding the ** file open, we will be unable to delete it. To work around this ** problem, we delay 100 milliseconds and try to delete again. Up ** to MX_DELETION_ATTEMPTs deletion attempts are run before giving ** up and returning an error. */ #define MX_DELETION_ATTEMPTS 5 static int winDelete( unqlite_vfs *pVfs, /* Not used on win32 */ const char *zFilename, /* Name of file to delete */ int syncDir /* Not used on win32 */ ){ int cnt = 0; DWORD rc; DWORD error = 0; void *zConverted; zConverted = convertUtf8Filename(zFilename); if( zConverted==0 ){ SXUNUSED(pVfs); SXUNUSED(syncDir); return UNQLITE_NOMEM; } do{ DeleteFileW((LPCWSTR)zConverted); }while( ( ((rc = GetFileAttributesW((LPCWSTR)zConverted)) != INVALID_FILE_ATTRIBUTES) || ((error = GetLastError()) == ERROR_ACCESS_DENIED)) && (++cnt < MX_DELETION_ATTEMPTS) && (Sleep(100), 1) ); HeapFree(GetProcessHeap(),0,zConverted); return ( (rc == INVALID_FILE_ATTRIBUTES) && (error == ERROR_FILE_NOT_FOUND)) ? UNQLITE_OK : UNQLITE_IOERR; } /* ** Check the existance and status of a file. */ static int winAccess( unqlite_vfs *pVfs, /* Not used */ const char *zFilename, /* Name of file to check */ int flags, /* Type of test to make on this file */ int *pResOut /* OUT: Result */ ){ WIN32_FILE_ATTRIBUTE_DATA sAttrData; DWORD attr; int rc = 0; void *zConverted; SXUNUSED(pVfs); zConverted = convertUtf8Filename(zFilename); if( zConverted==0 ){ return UNQLITE_NOMEM; } SyZero(&sAttrData,sizeof(sAttrData)); if( GetFileAttributesExW((WCHAR*)zConverted, GetFileExInfoStandard, &sAttrData) ){ /* For an UNQLITE_ACCESS_EXISTS query, treat a zero-length file ** as if it does not exist. */ if( flags==UNQLITE_ACCESS_EXISTS && sAttrData.nFileSizeHigh==0 && sAttrData.nFileSizeLow==0 ){ attr = INVALID_FILE_ATTRIBUTES; }else{ attr = sAttrData.dwFileAttributes; } }else{ if( GetLastError()!=ERROR_FILE_NOT_FOUND ){ HeapFree(GetProcessHeap(),0,zConverted); return UNQLITE_IOERR; }else{ attr = INVALID_FILE_ATTRIBUTES; } } HeapFree(GetProcessHeap(),0,zConverted); switch( flags ){ case UNQLITE_ACCESS_READWRITE: rc = (attr & FILE_ATTRIBUTE_READONLY)==0; break; case UNQLITE_ACCESS_READ: case UNQLITE_ACCESS_EXISTS: default: rc = attr!=INVALID_FILE_ATTRIBUTES; break; } *pResOut = rc; return UNQLITE_OK; } /* ** Turn a relative pathname into a full pathname. Write the full ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname ** bytes in size. */ static int winFullPathname( unqlite_vfs *pVfs, /* Pointer to vfs object */ const char *zRelative, /* Possibly relative input path */ int nFull, /* Size of output buffer in bytes */ char *zFull /* Output buffer */ ){ int nByte; void *zConverted; WCHAR *zTemp; char *zOut; SXUNUSED(nFull); zConverted = convertUtf8Filename(zRelative); if( zConverted == 0 ){ return UNQLITE_NOMEM; } nByte = GetFullPathNameW((WCHAR*)zConverted, 0, 0, 0) + 3; zTemp = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zTemp[0]) ); if( zTemp==0 ){ HeapFree(GetProcessHeap(),0,zConverted); return UNQLITE_NOMEM; } GetFullPathNameW((WCHAR*)zConverted, nByte, zTemp, 0); HeapFree(GetProcessHeap(),0,zConverted); zOut = unicodeToUtf8(zTemp); HeapFree(GetProcessHeap(),0,zTemp); if( zOut == 0 ){ return UNQLITE_NOMEM; } Systrcpy(zFull,(sxu32)pVfs->mxPathname,zOut,0); HeapFree(GetProcessHeap(),0,zOut); return UNQLITE_OK; } /* ** Get the sector size of the device used to store ** file. */ static int getSectorSize( unqlite_vfs *pVfs, const char *zRelative /* UTF-8 file name */ ){ DWORD bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE; char zFullpath[MAX_PATH+1]; int rc; DWORD dwRet = 0; DWORD dwDummy; /* ** We need to get the full path name of the file ** to get the drive letter to look up the sector ** size. */ rc = winFullPathname(pVfs, zRelative, MAX_PATH, zFullpath); if( rc == UNQLITE_OK ) { void *zConverted = convertUtf8Filename(zFullpath); if( zConverted ){ /* trim path to just drive reference */ WCHAR *p = (WCHAR *)zConverted; for(;*p;p++){ if( *p == '\\' ){ *p = '\0'; break; } } dwRet = GetDiskFreeSpaceW((WCHAR*)zConverted, &dwDummy, &bytesPerSector, &dwDummy, &dwDummy); HeapFree(GetProcessHeap(),0,zConverted); } if( !dwRet ){ bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE; } } return (int) bytesPerSector; } /* ** Sleep for a little while. Return the amount of time slept. */ static int winSleep(unqlite_vfs *pVfs, int microsec){ Sleep((microsec+999)/1000); SXUNUSED(pVfs); return ((microsec+999)/1000)*1000; } /* * Export the current system time. */ static int winCurrentTime(unqlite_vfs *pVfs,Sytm *pOut) { SYSTEMTIME sSys; SXUNUSED(pVfs); GetSystemTime(&sSys); SYSTEMTIME_TO_SYTM(&sSys,pOut); return UNQLITE_OK; } /* ** The idea is that this function works like a combination of ** GetLastError() and FormatMessage() on windows (or errno and ** strerror_r() on unix). After an error is returned by an OS ** function, UnQLite calls this function with zBuf pointing to ** a buffer of nBuf bytes. The OS layer should populate the ** buffer with a nul-terminated UTF-8 encoded error message ** describing the last IO error to have occurred within the calling ** thread. ** ** If the error message is too large for the supplied buffer, ** it should be truncated. The return value of xGetLastError ** is zero if the error message fits in the buffer, or non-zero ** otherwise (if the message was truncated). If non-zero is returned, ** then it is not necessary to include the nul-terminator character ** in the output buffer. */ static int winGetLastError(unqlite_vfs *pVfs, int nBuf, char *zBuf) { /* FormatMessage returns 0 on failure. Otherwise it ** returns the number of TCHARs written to the output ** buffer, excluding the terminating null char. */ DWORD error = GetLastError(); WCHAR *zTempWide = 0; DWORD dwLen; char *zOut = 0; SXUNUSED(pVfs); dwLen = FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, 0, (LPWSTR) &zTempWide, 0, 0 ); if( dwLen > 0 ){ /* allocate a buffer and convert to UTF8 */ zOut = unicodeToUtf8(zTempWide); /* free the system buffer allocated by FormatMessage */ LocalFree(zTempWide); } if( 0 == dwLen ){ Systrcpy(zBuf,(sxu32)nBuf,"OS Error",sizeof("OS Error")-1); }else{ /* copy a maximum of nBuf chars to output buffer */ Systrcpy(zBuf,(sxu32)nBuf,zOut,0 /* Compute input length automatically */); /* free the UTF8 buffer */ HeapFree(GetProcessHeap(),0,zOut); } return 0; } /* ** Open a file. */ static int winOpen( unqlite_vfs *pVfs, /* Not used */ const char *zName, /* Name of the file (UTF-8) */ unqlite_file *id, /* Write the UnQLite file handle here */ unsigned int flags /* Open mode flags */ ){ HANDLE h; DWORD dwDesiredAccess; DWORD dwShareMode; DWORD dwCreationDisposition; DWORD dwFlagsAndAttributes = 0; winFile *pFile = (winFile*)id; void *zConverted; /* Filename in OS encoding */ const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE); int isDelete = (flags & UNQLITE_OPEN_TEMP_DB); int isCreate = (flags & UNQLITE_OPEN_CREATE); int isReadWrite = (flags & UNQLITE_OPEN_READWRITE); pFile->h = INVALID_HANDLE_VALUE; /* Convert the filename to the system encoding. */ zConverted = convertUtf8Filename(zUtf8Name); if( zConverted==0 ){ return UNQLITE_NOMEM; } if( isReadWrite ){ dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; }else{ dwDesiredAccess = GENERIC_READ; } /* UNQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is ** created. */ if( isExclusive ){ /* Creates a new file, only if it does not already exist. */ /* If the file exists, it fails. */ dwCreationDisposition = CREATE_NEW; }else if( isCreate ){ /* Open existing file, or create if it doesn't exist */ dwCreationDisposition = OPEN_ALWAYS; }else{ /* Opens a file, only if it exists. */ dwCreationDisposition = OPEN_EXISTING; } dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; if( isDelete ){ dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY | FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_DELETE_ON_CLOSE; }else{ dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; } h = CreateFileW((WCHAR*)zConverted, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL ); if( h==INVALID_HANDLE_VALUE ){ pFile->lastErrno = GetLastError(); HeapFree(GetProcessHeap(),0,zConverted); return UNQLITE_IOERR; } SyZero(pFile,sizeof(*pFile)); pFile->pMethod = &winIoMethod; pFile->h = h; pFile->lastErrno = NO_ERROR; pFile->pVfs = pVfs; pFile->sectorSize = getSectorSize(pVfs, zUtf8Name); HeapFree(GetProcessHeap(),0,zConverted); return UNQLITE_OK; } /* * Export the Windows Vfs. */ UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void) { static const unqlite_vfs sWinvfs = { "Windows", /* Vfs name */ 1, /* Vfs structure version */ sizeof(winFile), /* szOsFile */ MAX_PATH, /* mxPathName */ winOpen, /* xOpen */ winDelete, /* xDelete */ winAccess, /* xAccess */ winFullPathname, /* xFullPathname */ 0, /* xTmp */ winSleep, /* xSleep */ winCurrentTime, /* xCurrentTime */ winGetLastError, /* xGetLastError */ }; return &sWinvfs; } #endif /* __WINNT__ */ /* * ---------------------------------------------------------- * File: pager.c * MD5: 57ff77347402fbf6892af589ff8a5df7 * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: pager.c v1.1 Win7 2012-11-29 03:46 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* ** This file implements the pager and the transaction manager for UnQLite (Mostly inspired from the SQLite3 Source tree). ** ** The Pager.eState variable stores the current 'state' of a pager. A ** pager may be in any one of the seven states shown in the following ** state diagram. ** ** OPEN <------+------+ ** | | | ** V | | ** +---------> READER-------+ | ** | | | ** | V | ** |<-------WRITER_LOCKED--------->| ** | | | ** | V | ** |<------WRITER_CACHEMOD-------->| ** | | | ** | V | ** |<-------WRITER_DBMOD---------->| ** | | | ** | V | ** +<------WRITER_FINISHED-------->+ ** ** OPEN: ** ** The pager starts up in this state. Nothing is guaranteed in this ** state - the file may or may not be locked and the database size is ** unknown. The database may not be read or written. ** ** * No read or write transaction is active. ** * Any lock, or no lock at all, may be held on the database file. ** * The dbSize and dbOrigSize variables may not be trusted. ** ** READER: ** ** In this state all the requirements for reading the database in ** rollback mode are met. Unless the pager is (or recently ** was) in exclusive-locking mode, a user-level read transaction is ** open. The database size is known in this state. ** ** * A read transaction may be active (but a write-transaction cannot). ** * A SHARED or greater lock is held on the database file. ** * The dbSize variable may be trusted (even if a user-level read ** transaction is not active). The dbOrigSize variables ** may not be trusted at this point. ** * Even if a read-transaction is not open, it is guaranteed that ** there is no hot-journal in the file-system. ** ** WRITER_LOCKED: ** ** The pager moves to this state from READER when a write-transaction ** is first opened on the database. In WRITER_LOCKED state, all locks ** required to start a write-transaction are held, but no actual ** modifications to the cache or database have taken place. ** ** In rollback mode, a RESERVED or (if the transaction was opened with ** EXCLUSIVE flag) EXCLUSIVE lock is obtained on the database file when ** moving to this state, but the journal file is not written to or opened ** to in this state. If the transaction is committed or rolled back while ** in WRITER_LOCKED state, all that is required is to unlock the database ** file. ** ** * A write transaction is active. ** * If the connection is open in rollback-mode, a RESERVED or greater ** lock is held on the database file. ** * The dbSize and dbOrigSize variables are all valid. ** * The contents of the pager cache have not been modified. ** * The journal file may or may not be open. ** * Nothing (not even the first header) has been written to the journal. ** ** WRITER_CACHEMOD: ** ** A pager moves from WRITER_LOCKED state to this state when a page is ** first modified by the upper layer. In rollback mode the journal file ** is opened (if it is not already open) and a header written to the ** start of it. The database file on disk has not been modified. ** ** * A write transaction is active. ** * A RESERVED or greater lock is held on the database file. ** * The journal file is open and the first header has been written ** to it, but the header has not been synced to disk. ** * The contents of the page cache have been modified. ** ** WRITER_DBMOD: ** ** The pager transitions from WRITER_CACHEMOD into WRITER_DBMOD state ** when it modifies the contents of the database file. ** ** * A write transaction is active. ** * An EXCLUSIVE or greater lock is held on the database file. ** * The journal file is open and the first header has been written ** and synced to disk. ** * The contents of the page cache have been modified (and possibly ** written to disk). ** ** WRITER_FINISHED: ** ** A rollback-mode pager changes to WRITER_FINISHED state from WRITER_DBMOD ** state after the entire transaction has been successfully written into the ** database file. In this state the transaction may be committed simply ** by finalizing the journal file. Once in WRITER_FINISHED state, it is ** not possible to modify the database further. At this point, the upper ** layer must either commit or rollback the transaction. ** ** * A write transaction is active. ** * An EXCLUSIVE or greater lock is held on the database file. ** * All writing and syncing of journal and database data has finished. ** If no error occured, all that remains is to finalize the journal to ** commit the transaction. If an error did occur, the caller will need ** to rollback the transaction. ** ** */ #define PAGER_OPEN 0 #define PAGER_READER 1 #define PAGER_WRITER_LOCKED 2 #define PAGER_WRITER_CACHEMOD 3 #define PAGER_WRITER_DBMOD 4 #define PAGER_WRITER_FINISHED 5 /* ** Journal files begin with the following magic string. The data ** was obtained from /dev/random. It is used only as a sanity check. ** ** NOTE: These values must be different from the one used by SQLite3 ** to avoid journal file collision. ** */ static const unsigned char aJournalMagic[] = { 0xa6, 0xe8, 0xcd, 0x2b, 0x1c, 0x92, 0xdb, 0x9f, }; /* ** The journal header size for this pager. This is usually the same ** size as a single disk sector. See also setSectorSize(). */ #define JOURNAL_HDR_SZ(pPager) (pPager->iSectorSize) /* * Database page handle. * Each raw disk page is represented in memory by an instance * of the following structure. */ typedef struct Page Page; struct Page { /* Must correspond to unqlite_page */ unsigned char *zData; /* Content of this page */ void *pUserData; /* Extra content */ pgno pgno; /* Page number for this page */ /********************************************************************** ** Elements above are public. All that follows is private to pcache.c ** and should not be accessed by other modules. */ Pager *pPager; /* The pager this page is part of */ int flags; /* Page flags defined below */ int nRef; /* Number of users of this page */ Page *pNext, *pPrev; /* A list of all pages */ Page *pDirtyNext; /* Next element in list of dirty pages */ Page *pDirtyPrev; /* Previous element in list of dirty pages */ Page *pNextCollide,*pPrevCollide; /* Collission chain */ Page *pNextHot,*pPrevHot; /* Hot dirty pages chain */ }; /* Bit values for Page.flags */ #define PAGE_DIRTY 0x002 /* Page has changed */ #define PAGE_NEED_SYNC 0x004 /* fsync the rollback journal before ** writing this page to the database */ #define PAGE_DONT_WRITE 0x008 /* Dont write page content to disk */ #define PAGE_NEED_READ 0x010 /* Content is unread */ #define PAGE_IN_JOURNAL 0x020 /* Page written to the journal */ #define PAGE_HOT_DIRTY 0x040 /* Hot dirty page */ #define PAGE_DONT_MAKE_HOT 0x080 /* Dont make this page Hot. In other words, * do not link it to the hot dirty list. */ /* * Each active database pager is represented by an instance of * the following structure. */ struct Pager { SyMemBackend *pAllocator; /* Memory backend */ unqlite *pDb; /* DB handle that own this instance */ unqlite_kv_engine *pEngine; /* Underlying KV storage engine */ char *zFilename; /* Name of the database file */ char *zJournal; /* Name of the journal file */ unqlite_vfs *pVfs; /* Underlying virtual file system */ unqlite_file *pfd,*pjfd; /* File descriptors for database and journal */ pgno dbSize; /* Number of pages in the file */ pgno dbOrigSize; /* dbSize before the current change */ sxi64 dbByteSize; /* Database size in bytes */ void *pMmap; /* Read-only Memory view (mmap) of the whole file if requested (UNQLITE_OPEN_MMAP). */ sxu32 nRec; /* Number of pages written to the journal */ SyPRNGCtx sPrng; /* PRNG Context */ sxu32 cksumInit; /* Quasi-random value added to every checksum */ sxu32 iOpenFlags; /* Flag passed to unqlite_open() after processing */ sxi64 iJournalOfft; /* Journal offset we are reading from */ int (*xBusyHandler)(void *); /* Busy handler */ void *pBusyHandlerArg; /* First arg to xBusyHandler() */ void (*xPageUnpin)(void *); /* Page Unpin callback */ void (*xPageReload)(void *); /* Page Reload callback */ Bitvec *pVec; /* Bitmap */ Page *pHeader; /* Page one of the database (Unqlite header) */ Sytm tmCreate; /* Database creation time */ SyString sKv; /* Underlying Key/Value storage engine name */ int iState; /* Pager state */ int iLock; /* Lock state */ sxi32 iFlags; /* Control flags (see below) */ int is_mem; /* True for an in-memory database */ int is_rdonly; /* True for a read-only database */ int no_jrnl; /* TRUE to omit journaling */ int iPageSize; /* Page size in bytes (default 4K) */ int iSectorSize; /* Size of a single sector on disk */ unsigned char *zTmpPage; /* Temporary page */ Page *pFirstDirty; /* First dirty pages */ Page *pDirty; /* Transient list of dirty pages */ Page *pAll; /* List of all pages */ Page *pHotDirty; /* List of hot dirty pages */ Page *pFirstHot; /* First hot dirty page */ sxu32 nHot; /* Total number of hot dirty pages */ Page **apHash; /* Page table */ sxu32 nSize; /* apHash[] size: Must be a power of two */ sxu32 nPage; /* Total number of page loaded in memory */ sxu32 nCacheMax; /* Maximum page to cache*/ }; /* Control flags */ #define PAGER_CTRL_COMMIT_ERR 0x001 /* Commit error */ #define PAGER_CTRL_DIRTY_COMMIT 0x002 /* Dirty commit has been applied */ /* ** Read a 32-bit integer from the given file descriptor. ** All values are stored on disk as big-endian. */ static int ReadInt32(unqlite_file *pFd,sxu32 *pOut,sxi64 iOfft) { unsigned char zBuf[4]; int rc; rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft); if( rc != UNQLITE_OK ){ return rc; } SyBigEndianUnpack32(zBuf,pOut); return UNQLITE_OK; } /* ** Read a 64-bit integer from the given file descriptor. ** All values are stored on disk as big-endian. */ static int ReadInt64(unqlite_file *pFd,sxu64 *pOut,sxi64 iOfft) { unsigned char zBuf[8]; int rc; rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft); if( rc != UNQLITE_OK ){ return rc; } SyBigEndianUnpack64(zBuf,pOut); return UNQLITE_OK; } /* ** Write a 32-bit integer into the given file descriptor. */ static int WriteInt32(unqlite_file *pFd,sxu32 iNum,sxi64 iOfft) { unsigned char zBuf[4]; int rc; SyBigEndianPack32(zBuf,iNum); rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft); return rc; } /* ** Write a 64-bit integer into the given file descriptor. */ static int WriteInt64(unqlite_file *pFd,sxu64 iNum,sxi64 iOfft) { unsigned char zBuf[8]; int rc; SyBigEndianPack64(zBuf,iNum); rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft); return rc; } /* ** The maximum allowed sector size. 64KiB. If the xSectorsize() method ** returns a value larger than this, then MAX_SECTOR_SIZE is used instead. ** This could conceivably cause corruption following a power failure on ** such a system. This is currently an undocumented limit. */ #define MAX_SECTOR_SIZE 0x10000 /* ** Get the size of a single sector on disk. ** The sector size will be used used to determine the size ** and alignment of journal header and within created journal files. ** ** The default sector size is set to 512. */ static int GetSectorSize(unqlite_file *pFd) { int iSectorSize = UNQLITE_DEFAULT_SECTOR_SIZE; if( pFd ){ iSectorSize = unqliteOsSectorSize(pFd); if( iSectorSize < 32 ){ iSectorSize = 512; } if( iSectorSize > MAX_SECTOR_SIZE ){ iSectorSize = MAX_SECTOR_SIZE; } } return iSectorSize; } /* Hash function for page number */ #define PAGE_HASH(PNUM) (PNUM) /* * Fetch a page from the cache. */ static Page * pager_fetch_page(Pager *pPager,pgno page_num) { Page *pEntry; if( pPager->nPage < 1 ){ /* Don't bother hashing */ return 0; } /* Perform the lookup */ pEntry = pPager->apHash[PAGE_HASH(page_num) & (pPager->nSize - 1)]; for(;;){ if( pEntry == 0 ){ break; } if( pEntry->pgno == page_num ){ return pEntry; } /* Point to the next entry in the colission chain */ pEntry = pEntry->pNextCollide; } /* No such page */ return 0; } /* * Allocate and initialize a new page. */ static Page * pager_alloc_page(Pager *pPager,pgno num_page) { Page *pNew; pNew = (Page *)SyMemBackendPoolAlloc(pPager->pAllocator,sizeof(Page)+pPager->iPageSize); if( pNew == 0 ){ return 0; } /* Zero the structure */ SyZero(pNew,sizeof(Page)+pPager->iPageSize); /* Page data */ pNew->zData = (unsigned char *)&pNew[1]; /* Fill in the structure */ pNew->pPager = pPager; pNew->nRef = 1; pNew->pgno = num_page; return pNew; } /* * Increment the reference count of a given page. */ static void page_ref(Page *pPage) { if( pPage->pPager->pAllocator->pMutexMethods ){ SyMutexEnter(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); } pPage->nRef++; if( pPage->pPager->pAllocator->pMutexMethods ){ SyMutexLeave(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); } } /* * Release an in-memory page after its reference count reach zero. */ static int pager_release_page(Pager *pPager,Page *pPage) { int rc = UNQLITE_OK; if( !(pPage->flags & PAGE_DIRTY)){ /* Invoke the unpin callback if available */ if( pPager->xPageUnpin && pPage->pUserData ){ pPager->xPageUnpin(pPage->pUserData); } pPage->pUserData = 0; SyMemBackendPoolFree(pPager->pAllocator,pPage); }else{ /* Dirty page, it will be released later when a dirty commit * or the final commit have been applied. */ rc = UNQLITE_LOCKED; } return rc; } /* Forward declaration */ static int pager_unlink_page(Pager *pPager,Page *pPage); /* * Decrement the reference count of a given page. */ static void page_unref(Page *pPage) { int nRef; if( pPage->pPager->pAllocator->pMutexMethods ){ SyMutexEnter(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); } nRef = pPage->nRef--; if( pPage->pPager->pAllocator->pMutexMethods ){ SyMutexLeave(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); } if( nRef == 0){ Pager *pPager = pPage->pPager; if( !(pPage->flags & PAGE_DIRTY) ){ pager_unlink_page(pPager,pPage); /* Release the page */ pager_release_page(pPager,pPage); }else{ if( pPage->flags & PAGE_DONT_MAKE_HOT ){ /* Do not add this page to the hot dirty list */ return; } if( !(pPage->flags & PAGE_HOT_DIRTY) ){ /* Add to the hot dirty list */ pPage->pPrevHot = 0; if( pPager->pFirstHot == 0 ){ pPager->pFirstHot = pPager->pHotDirty = pPage; }else{ pPage->pNextHot = pPager->pHotDirty; if( pPager->pHotDirty ){ pPager->pHotDirty->pPrevHot = pPage; } pPager->pHotDirty = pPage; } pPager->nHot++; pPage->flags |= PAGE_HOT_DIRTY; } } } } /* * Link a freshly created page to the list of active page. */ static int pager_link_page(Pager *pPager,Page *pPage) { sxu32 nBucket; /* Install in the corresponding bucket */ nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1); pPage->pNextCollide = pPager->apHash[nBucket]; if( pPager->apHash[nBucket] ){ pPager->apHash[nBucket]->pPrevCollide = pPage; } pPager->apHash[nBucket] = pPage; /* Link to the list of active pages */ MACRO_LD_PUSH(pPager->pAll,pPage); pPager->nPage++; if( (pPager->nPage >= pPager->nSize * 4) && pPager->nPage < 100000 ){ /* Grow the hashtable */ sxu32 nNewSize = pPager->nSize << 1; Page *pEntry,**apNew; sxu32 n; apNew = (Page **)SyMemBackendAlloc(pPager->pAllocator, nNewSize * sizeof(Page *)); if( apNew ){ sxu32 iBucket; /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(Page *)); /* Rehash all entries */ n = 0; pEntry = pPager->pAll; for(;;){ /* Loop one */ if( n >= pPager->nPage ){ break; } pEntry->pNextCollide = pEntry->pPrevCollide = 0; /* Install in the new bucket */ iBucket = PAGE_HASH(pEntry->pgno) & (nNewSize - 1); pEntry->pNextCollide = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevCollide = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; } /* Release the old table and reflect the change */ SyMemBackendFree(pPager->pAllocator,(void *)pPager->apHash); pPager->apHash = apNew; pPager->nSize = nNewSize; } } return UNQLITE_OK; } /* * Unlink a page from the list of active pages. */ static int pager_unlink_page(Pager *pPager,Page *pPage) { if( pPage->pNextCollide ){ pPage->pNextCollide->pPrevCollide = pPage->pPrevCollide; } if( pPage->pPrevCollide ){ pPage->pPrevCollide->pNextCollide = pPage->pNextCollide; }else{ sxu32 nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1); pPager->apHash[nBucket] = pPage->pNextCollide; } MACRO_LD_REMOVE(pPager->pAll,pPage); pPager->nPage--; return UNQLITE_OK; } /* * Update the content of a cached page. */ static int pager_fill_page(Pager *pPager,pgno iNum,void *pContents) { Page *pPage; /* Fetch the page from the catch */ pPage = pager_fetch_page(pPager,iNum); if( pPage == 0 ){ return SXERR_NOTFOUND; } /* Reflect the change */ SyMemcpy(pContents,pPage->zData,pPager->iPageSize); return UNQLITE_OK; } /* * Read the content of a page from disk. */ static int pager_get_page_contents(Pager *pPager,Page *pPage,int noContent) { int rc = UNQLITE_OK; if( pPager->is_mem || noContent || pPage->pgno >= pPager->dbSize ){ /* Do not bother reading, zero the page contents only */ SyZero(pPage->zData,pPager->iPageSize); return UNQLITE_OK; } if( (pPager->iOpenFlags & UNQLITE_OPEN_MMAP) && (pPager->pMmap /* Paranoid edition */) ){ unsigned char *zMap = (unsigned char *)pPager->pMmap; pPage->zData = &zMap[pPage->pgno * pPager->iPageSize]; }else{ /* Read content */ rc = unqliteOsRead(pPager->pfd,pPage->zData,pPager->iPageSize,pPage->pgno * pPager->iPageSize); } return rc; } /* * Add a page to the dirty list. */ static void pager_page_to_dirty_list(Pager *pPager,Page *pPage) { if( pPage->flags & PAGE_DIRTY ){ /* Already set */ return; } /* Mark the page as dirty */ pPage->flags |= PAGE_DIRTY|PAGE_NEED_SYNC|PAGE_IN_JOURNAL; /* Link to the list */ pPage->pDirtyPrev = 0; pPage->pDirtyNext = pPager->pDirty; if( pPager->pDirty ){ pPager->pDirty->pDirtyPrev = pPage; } pPager->pDirty = pPage; if( pPager->pFirstDirty == 0 ){ pPager->pFirstDirty = pPage; } } /* * Merge sort. * The merge sort implementation is based on the one used by * the PH7 Embeddable PHP Engine (http://ph7.symisc.net/). */ /* ** Inputs: ** a: A sorted, null-terminated linked list. (May be null). ** b: A sorted, null-terminated linked list. (May be null). ** cmp: A pointer to the comparison function. ** ** Return Value: ** A pointer to the head of a sorted list containing the elements ** of both a and b. ** ** Side effects: ** The "next", "prev" pointers for elements in the lists a and b are ** changed. */ static Page * page_merge_dirty(Page *pA, Page *pB) { Page result, *pTail; /* Prevent compiler warning */ result.pDirtyNext = result.pDirtyPrev = 0; pTail = &result; while( pA && pB ){ if( pA->pgno < pB->pgno ){ pTail->pDirtyPrev = pA; pA->pDirtyNext = pTail; pTail = pA; pA = pA->pDirtyPrev; }else{ pTail->pDirtyPrev = pB; pB->pDirtyNext = pTail; pTail = pB; pB = pB->pDirtyPrev; } } if( pA ){ pTail->pDirtyPrev = pA; pA->pDirtyNext = pTail; }else if( pB ){ pTail->pDirtyPrev = pB; pB->pDirtyNext = pTail; }else{ pTail->pDirtyPrev = pTail->pDirtyNext = 0; } return result.pDirtyPrev; } /* ** Inputs: ** Map: Input hashmap ** cmp: A comparison function. ** ** Return Value: ** Sorted hashmap. ** ** Side effects: ** The "next" pointers for elements in list are changed. */ #define N_SORT_BUCKET 32 static Page * pager_get_dirty_pages(Pager *pPager) { Page *a[N_SORT_BUCKET], *p, *pIn; sxu32 i; if( pPager->pFirstDirty == 0 ){ /* Don't bother sorting, the list is already empty */ return 0; } SyZero(a, sizeof(a)); /* Point to the first inserted entry */ pIn = pPager->pFirstDirty; while( pIn ){ p = pIn; pIn = p->pDirtyPrev; p->pDirtyPrev = 0; for(i=0; ipDirtyNext = 0; return p; } /* * See block comment above. */ static Page * page_merge_hot(Page *pA, Page *pB) { Page result, *pTail; /* Prevent compiler warning */ result.pNextHot = result.pPrevHot = 0; pTail = &result; while( pA && pB ){ if( pA->pgno < pB->pgno ){ pTail->pPrevHot = pA; pA->pNextHot = pTail; pTail = pA; pA = pA->pPrevHot; }else{ pTail->pPrevHot = pB; pB->pNextHot = pTail; pTail = pB; pB = pB->pPrevHot; } } if( pA ){ pTail->pPrevHot = pA; pA->pNextHot = pTail; }else if( pB ){ pTail->pPrevHot = pB; pB->pNextHot = pTail; }else{ pTail->pPrevHot = pTail->pNextHot = 0; } return result.pPrevHot; } /* ** Inputs: ** Map: Input hashmap ** cmp: A comparison function. ** ** Return Value: ** Sorted hashmap. ** ** Side effects: ** The "next" pointers for elements in list are changed. */ #define N_SORT_BUCKET 32 static Page * pager_get_hot_pages(Pager *pPager) { Page *a[N_SORT_BUCKET], *p, *pIn; sxu32 i; if( pPager->pFirstHot == 0 ){ /* Don't bother sorting, the list is already empty */ return 0; } SyZero(a, sizeof(a)); /* Point to the first inserted entry */ pIn = pPager->pFirstHot; while( pIn ){ p = pIn; pIn = p->pPrevHot; p->pPrevHot = 0; for(i=0; ipNextHot = 0; return p; } /* ** The format for the journal header is as follows: ** - 8 bytes: Magic identifying journal format. ** - 4 bytes: Number of records in journal. ** - 4 bytes: Random number used for page hash. ** - 8 bytes: Initial database page count. ** - 4 bytes: Sector size used by the process that wrote this journal. ** - 4 bytes: Database page size. ** ** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space. */ /* ** Open the journal file and extract its header information. ** ** If the header is read successfully, *pNRec is set to the number of ** page records following this header and *pDbSize is set to the size of the ** database before the transaction began, in pages. Also, pPager->cksumInit ** is set to the value read from the journal header. UNQLITE_OK is returned ** in this case. ** ** If the journal header file appears to be corrupted, UNQLITE_DONE is ** returned and *pNRec and *PDbSize are undefined. If JOURNAL_HDR_SZ bytes ** cannot be read from the journal file an error code is returned. */ static int pager_read_journal_header( Pager *pPager, /* Pager object */ sxu32 *pNRec, /* OUT: Value read from the nRec field */ pgno *pDbSize /* OUT: Value of original database size field */ ) { sxu32 iPageSize,iSectorSize; unsigned char zMagic[8]; sxi64 iHdrOfft; sxi64 iSize; int rc; /* Offset to start reading from */ iHdrOfft = 0; /* Get the size of the journal */ rc = unqliteOsFileSize(pPager->pjfd,&iSize); if( rc != UNQLITE_OK ){ return UNQLITE_DONE; } /* If the journal file is too small, return UNQLITE_DONE. */ if( 32 /* Minimum sector size */> iSize ){ return UNQLITE_DONE; } /* Make sure we are dealing with a valid journal */ rc = unqliteOsRead(pPager->pjfd,zMagic,sizeof(zMagic),iHdrOfft); if( rc != UNQLITE_OK ){ return rc; } if( SyMemcmp(zMagic,aJournalMagic,sizeof(zMagic)) != 0 ){ return UNQLITE_DONE; } iHdrOfft += sizeof(zMagic); /* Read the first three 32-bit fields of the journal header: The nRec ** field, the checksum-initializer and the database size at the start ** of the transaction. Return an error code if anything goes wrong. */ rc = ReadInt32(pPager->pjfd,pNRec,iHdrOfft); if( rc != UNQLITE_OK ){ return rc; } iHdrOfft += 4; rc = ReadInt32(pPager->pjfd,&pPager->cksumInit,iHdrOfft); if( rc != UNQLITE_OK ){ return rc; } iHdrOfft += 4; rc = ReadInt64(pPager->pjfd,pDbSize,iHdrOfft); if( rc != UNQLITE_OK ){ return rc; } iHdrOfft += 8; /* Read the page-size and sector-size journal header fields. */ rc = ReadInt32(pPager->pjfd,&iSectorSize,iHdrOfft); if( rc != UNQLITE_OK ){ return rc; } iHdrOfft += 4; rc = ReadInt32(pPager->pjfd,&iPageSize,iHdrOfft); if( rc != UNQLITE_OK ){ return rc; } /* Check that the values read from the page-size and sector-size fields ** are within range. To be 'in range', both values need to be a power ** of two greater than or equal to 512 or 32, and not greater than their ** respective compile time maximum limits. */ if( iPageSize < UNQLITE_MIN_PAGE_SIZE || iSectorSize<32 || iPageSize > UNQLITE_MAX_PAGE_SIZE || iSectorSize>MAX_SECTOR_SIZE || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 ){ /* If the either the page-size or sector-size in the journal-header is ** invalid, then the process that wrote the journal-header must have ** crashed before the header was synced. In this case stop reading ** the journal file here. */ return UNQLITE_DONE; } /* Update the assumed sector-size to match the value used by ** the process that created this journal. If this journal was ** created by a process other than this one, then this routine ** is being called from within pager_playback(). The local value ** of Pager.sectorSize is restored at the end of that routine. */ pPager->iSectorSize = iSectorSize; pPager->iPageSize = iPageSize; /* Ready to rollback */ pPager->iJournalOfft = JOURNAL_HDR_SZ(pPager); /* All done */ return UNQLITE_OK; } /* * Write the journal header in the given memory buffer. * The given buffer is big enough to hold the whole header. */ static int pager_write_journal_header(Pager *pPager,unsigned char *zBuf) { unsigned char *zPtr = zBuf; /* 8 bytes magic number */ SyMemcpy(aJournalMagic,zPtr,sizeof(aJournalMagic)); zPtr += sizeof(aJournalMagic); /* 4 bytes: Number of records in journal. */ SyBigEndianPack32(zPtr,0); zPtr += 4; /* 4 bytes: Random number used to compute page checksum. */ SyBigEndianPack32(zPtr,pPager->cksumInit); zPtr += 4; /* 8 bytes: Initial database page count. */ SyBigEndianPack64(zPtr,pPager->dbOrigSize); zPtr += 8; /* 4 bytes: Sector size used by the process that wrote this journal. */ SyBigEndianPack32(zPtr,(sxu32)pPager->iSectorSize); zPtr += 4; /* 4 bytes: Database page size. */ SyBigEndianPack32(zPtr,(sxu32)pPager->iPageSize); return UNQLITE_OK; } /* ** Parameter aData must point to a buffer of pPager->pageSize bytes ** of data. Compute and return a checksum based ont the contents of the ** page of data and the current value of pPager->cksumInit. ** ** This is not a real checksum. It is really just the sum of the ** random initial value (pPager->cksumInit) and every 200th byte ** of the page data, starting with byte offset (pPager->pageSize%200). ** Each byte is interpreted as an 8-bit unsigned integer. ** ** Changing the formula used to compute this checksum results in an ** incompatible journal file format. ** ** If journal corruption occurs due to a power failure, the most likely ** scenario is that one end or the other of the record will be changed. ** It is much less likely that the two ends of the journal record will be ** correct and the middle be corrupt. Thus, this "checksum" scheme, ** though fast and simple, catches the mostly likely kind of corruption. */ static sxu32 pager_cksum(Pager *pPager,const unsigned char *zData) { sxu32 cksum = pPager->cksumInit; /* Checksum value to return */ int i = pPager->iPageSize-200; /* Loop counter */ while( i>0 ){ cksum += zData[i]; i -= 200; } return cksum; } /* ** Read a single page from the journal file opened on file descriptor ** jfd. Playback this one page. Update the offset to read from. */ static int pager_play_back_one_page(Pager *pPager,sxi64 *pOfft,unsigned char *zTmp) { unsigned char *zData = zTmp; sxi64 iOfft; /* Offset to read from */ pgno iNum; /* Pager number */ sxu32 ckSum; /* Sanity check */ int rc; /* Offset to start reading from */ iOfft = *pOfft; /* Database page number */ rc = ReadInt64(pPager->pjfd,&iNum,iOfft); if( rc != UNQLITE_OK ){ return rc; } iOfft += 8; /* Page data */ rc = unqliteOsRead(pPager->pjfd,zData,pPager->iPageSize,iOfft); if( rc != UNQLITE_OK ){ return rc; } iOfft += pPager->iPageSize; /* Page cksum */ rc = ReadInt32(pPager->pjfd,&ckSum,iOfft); if( rc != UNQLITE_OK ){ return rc; } iOfft += 4; /* Synchronize pointers */ *pOfft = iOfft; /* Make sure we are dealing with a valid page */ if( ckSum != pager_cksum(pPager,zData) ){ /* Ignore that page */ return SXERR_IGNORE; } if( iNum >= pPager->dbSize ){ /* Ignore that page */ return UNQLITE_OK; } /* playback */ rc = unqliteOsWrite(pPager->pfd,zData,pPager->iPageSize,iNum * pPager->iPageSize); if( rc == UNQLITE_OK ){ /* Flush the cache */ pager_fill_page(pPager,iNum,zData); } return rc; } /* ** Playback the journal and thus restore the database file to ** the state it was in before we started making changes. ** ** The journal file format is as follows: ** ** (1) 8 byte prefix. A copy of aJournalMagic[]. ** (2) 4 byte big-endian integer which is the number of valid page records ** in the journal. ** (3) 4 byte big-endian integer which is the initial value for the ** sanity checksum. ** (4) 8 byte integer which is the number of pages to truncate the ** database to during a rollback. ** (5) 4 byte big-endian integer which is the sector size. The header ** is this many bytes in size. ** (6) 4 byte big-endian integer which is the page size. ** (7) zero padding out to the next sector size. ** (8) Zero or more pages instances, each as follows: ** + 4 byte page number. ** + pPager->pageSize bytes of data. ** + 4 byte checksum ** ** When we speak of the journal header, we mean the first 7 items above. ** Each entry in the journal is an instance of the 8th item. ** ** Call the value from the second bullet "nRec". nRec is the number of ** valid page entries in the journal. In most cases, you can compute the ** value of nRec from the size of the journal file. But if a power ** failure occurred while the journal was being written, it could be the ** case that the size of the journal file had already been increased but ** the extra entries had not yet made it safely to disk. In such a case, ** the value of nRec computed from the file size would be too large. For ** that reason, we always use the nRec value in the header. ** ** If the file opened as the journal file is not a well-formed ** journal file then all pages up to the first corrupted page are rolled ** back (or no pages if the journal header is corrupted). The journal file ** is then deleted and SQLITE_OK returned, just as if no corruption had ** been encountered. ** ** If an I/O or malloc() error occurs, the journal-file is not deleted ** and an error code is returned. ** */ static int pager_playback(Pager *pPager) { unsigned char *zTmp = 0; /* cc warning */ sxu32 n,nRec; sxi64 iOfft; int rc; /* Read the journal header*/ rc = pager_read_journal_header(pPager,&nRec,&pPager->dbSize); if( rc != UNQLITE_OK ){ if( rc == UNQLITE_DONE ){ goto end_playback; } unqliteGenErrorFormat(pPager->pDb,"IO error while reading journal file '%s' header",pPager->zJournal); return rc; } /* Truncate the database back to its original size */ rc = unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize); if( rc != UNQLITE_OK ){ unqliteGenError(pPager->pDb,"IO error while truncating database file"); return rc; } /* Allocate a temporary page */ zTmp = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize); if( zTmp == 0 ){ unqliteGenOutofMem(pPager->pDb); return UNQLITE_NOMEM; } SyZero((void *)zTmp,(sxu32)pPager->iPageSize); /* Copy original pages out of the journal and back into the ** database file and/or page cache. */ iOfft = pPager->iJournalOfft; for( n = 0 ; n < nRec ; ++n ){ rc = pager_play_back_one_page(pPager,&iOfft,zTmp); if( rc != UNQLITE_OK ){ if( rc != SXERR_IGNORE ){ unqliteGenError(pPager->pDb,"Page playback error"); goto end_playback; } } } end_playback: /* Release the temp page */ SyMemBackendFree(pPager->pAllocator,(void *)zTmp); if( rc == UNQLITE_OK ){ /* Sync the database file */ unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL); } if( rc == UNQLITE_DONE ){ rc = UNQLITE_OK; } /* Return to the caller */ return rc; } /* ** Unlock the database file to level eLock, which must be either NO_LOCK ** or SHARED_LOCK. Regardless of whether or not the call to xUnlock() ** succeeds, set the Pager.iLock variable to match the (attempted) new lock. ** ** Except, if Pager.iLock is set to NO_LOCK when this function is ** called, do not modify it. See the comment above the #define of ** NO_LOCK for an explanation of this. */ static int pager_unlock_db(Pager *pPager, int eLock) { int rc = UNQLITE_OK; if( pPager->iLock != NO_LOCK ){ rc = unqliteOsUnlock(pPager->pfd,eLock); pPager->iLock = eLock; } return rc; } /* ** Lock the database file to level eLock, which must be either SHARED_LOCK, ** RESERVED_LOCK or EXCLUSIVE_LOCK. If the caller is successful, set the ** Pager.eLock variable to the new locking state. ** ** Except, if Pager.eLock is set to NO_LOCK when this function is ** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. ** See the comment above the #define of NO_LOCK for an explanation ** of this. */ static int pager_lock_db(Pager *pPager, int eLock){ int rc = UNQLITE_OK; if( pPager->iLock < eLock || pPager->iLock == NO_LOCK ){ rc = unqliteOsLock(pPager->pfd, eLock); if( rc==UNQLITE_OK ){ pPager->iLock = eLock; }else{ unqliteGenError(pPager->pDb, rc == UNQLITE_BUSY ? "Another process or thread hold the requested lock" : "Error while requesting database lock" ); } } return rc; } /* ** Try to obtain a lock of type locktype on the database file. If ** a similar or greater lock is already held, this function is a no-op ** (returning UNQLITE_OK immediately). ** ** Otherwise, attempt to obtain the lock using unqliteOsLock(). Invoke ** the busy callback if the lock is currently not available. Repeat ** until the busy callback returns false or until the attempt to ** obtain the lock succeeds. ** ** Return UNQLITE_OK on success and an error code if we cannot obtain ** the lock. If the lock is obtained successfully, set the Pager.state ** variable to locktype before returning. */ static int pager_wait_on_lock(Pager *pPager, int locktype){ int rc; /* Return code */ do { rc = pager_lock_db(pPager,locktype); }while( rc==UNQLITE_BUSY && pPager->xBusyHandler && pPager->xBusyHandler(pPager->pBusyHandlerArg) ); return rc; } /* ** This function is called after transitioning from PAGER_OPEN to ** PAGER_SHARED state. It tests if there is a hot journal present in ** the file-system for the given pager. A hot journal is one that ** needs to be played back. According to this function, a hot-journal ** file exists if the following criteria are met: ** ** * The journal file exists in the file system, and ** * No process holds a RESERVED or greater lock on the database file, and ** * The database file itself is greater than 0 bytes in size, and ** * The first byte of the journal file exists and is not 0x00. ** ** If the current size of the database file is 0 but a journal file ** exists, that is probably an old journal left over from a prior ** database with the same name. In this case the journal file is ** just deleted using OsDelete, *pExists is set to 0 and UNQLITE_OK ** is returned. ** ** If a hot-journal file is found to exist, *pExists is set to 1 and ** UNQLITE_OK returned. If no hot-journal file is present, *pExists is ** set to 0 and UNQLITE_OK returned. If an IO error occurs while trying ** to determine whether or not a hot-journal file exists, the IO error ** code is returned and the value of *pExists is undefined. */ static int pager_has_hot_journal(Pager *pPager, int *pExists) { unqlite_vfs *pVfs = pPager->pVfs; int rc = UNQLITE_OK; /* Return code */ int exists = 1; /* True if a journal file is present */ *pExists = 0; rc = unqliteOsAccess(pVfs, pPager->zJournal, UNQLITE_ACCESS_EXISTS, &exists); if( rc==UNQLITE_OK && exists ){ int locked = 0; /* True if some process holds a RESERVED lock */ /* Race condition here: Another process might have been holding the ** the RESERVED lock and have a journal open at the unqliteOsAccess() ** call above, but then delete the journal and drop the lock before ** we get to the following unqliteOsCheckReservedLock() call. If that ** is the case, this routine might think there is a hot journal when ** in fact there is none. This results in a false-positive which will ** be dealt with by the playback routine. */ rc = unqliteOsCheckReservedLock(pPager->pfd, &locked); if( rc==UNQLITE_OK && !locked ){ sxi64 n = 0; /* Size of db file in bytes */ /* Check the size of the database file. If it consists of 0 pages, ** then delete the journal file. See the header comment above for ** the reasoning here. Delete the obsolete journal file under ** a RESERVED lock to avoid race conditions. */ rc = unqliteOsFileSize(pPager->pfd,&n); if( rc==UNQLITE_OK ){ if( n < 1 ){ if( pager_lock_db(pPager, RESERVED_LOCK)==UNQLITE_OK ){ unqliteOsDelete(pVfs, pPager->zJournal, 0); pager_unlock_db(pPager, SHARED_LOCK); } }else{ /* The journal file exists and no other connection has a reserved ** or greater lock on the database file. */ *pExists = 1; } } } } return rc; } /* * Rollback a journal file. (See block-comment above). */ static int pager_journal_rollback(Pager *pPager,int check_hot) { int rc; if( check_hot ){ int iExists = 0; /* cc warning */ /* Check if the journal file exists */ rc = pager_has_hot_journal(pPager,&iExists); if( rc != UNQLITE_OK ){ /* IO error */ return rc; } if( !iExists ){ /* Journal file does not exists */ return UNQLITE_OK; } } if( pPager->is_rdonly ){ unqliteGenErrorFormat(pPager->pDb, "Cannot rollback journal file '%s' due to a read-only database handle",pPager->zJournal); return UNQLITE_READ_ONLY; } /* Get an EXCLUSIVE lock on the database file. At this point it is ** important that a RESERVED lock is not obtained on the way to the ** EXCLUSIVE lock. If it were, another process might open the ** database file, detect the RESERVED lock, and conclude that the ** database is safe to read while this process is still rolling the ** hot-journal back. ** ** Because the intermediate RESERVED lock is not requested, any ** other process attempting to access the database file will get to ** this point in the code and fail to obtain its own EXCLUSIVE lock ** on the database file. ** ** Unless the pager is in locking_mode=exclusive mode, the lock is ** downgraded to SHARED_LOCK before this function returns. */ /* Open the journal file */ rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal,&pPager->pjfd,UNQLITE_OPEN_READWRITE); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: '%s'",pPager->zJournal); goto fail; } rc = pager_lock_db(pPager,EXCLUSIVE_LOCK); if( rc != UNQLITE_OK ){ unqliteGenError(pPager->pDb,"Cannot acquire an exclusive lock on the database while journal rollback"); goto fail; } /* Sync the journal file */ unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); /* Finally rollback the database */ rc = pager_playback(pPager); /* Switch back to shared lock */ pager_unlock_db(pPager,SHARED_LOCK); fail: /* Close the journal handle */ unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); pPager->pjfd = 0; if( rc == UNQLITE_OK ){ /* Delete the journal file */ unqliteOsDelete(pPager->pVfs,pPager->zJournal,TRUE); } return rc; } /* * Write the unqlite header (First page). (Big-Endian) */ static int pager_write_db_header(Pager *pPager) { unsigned char *zRaw = pPager->pHeader->zData; unqlite_kv_engine *pEngine = pPager->pEngine; sxu32 nDos; sxu16 nLen; /* Database signature */ SyMemcpy(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1); zRaw += sizeof(UNQLITE_DB_SIG)-1; /* Database magic number */ SyBigEndianPack32(zRaw,UNQLITE_DB_MAGIC); zRaw += 4; /* 4 byte magic number */ /* Database creation time */ SyZero(&pPager->tmCreate,sizeof(Sytm)); if( pPager->pVfs->xCurrentTime ){ pPager->pVfs->xCurrentTime(pPager->pVfs,&pPager->tmCreate); } /* DOS time format (4 bytes) */ SyTimeFormatToDos(&pPager->tmCreate,&nDos); SyBigEndianPack32(zRaw,nDos); zRaw += 4; /* 4 byte DOS time */ /* Sector size */ SyBigEndianPack32(zRaw,(sxu32)pPager->iSectorSize); zRaw += 4; /* 4 byte sector size */ /* Page size */ SyBigEndianPack32(zRaw,(sxu32)pPager->iPageSize); zRaw += 4; /* 4 byte page size */ /* Key value storage engine */ nLen = (sxu16)SyStrlen(pEngine->pIo->pMethods->zName); SyBigEndianPack16(zRaw,nLen); /* 2 byte storage engine name */ zRaw += 2; SyMemcpy((const void *)pEngine->pIo->pMethods->zName,(void *)zRaw,nLen); zRaw += nLen; /* All rest are meta-data available to the host application */ return UNQLITE_OK; } /* * Read the unqlite header (first page). (Big-Endian) */ static int pager_extract_header(Pager *pPager,const unsigned char *zRaw,sxu32 nByte) { const unsigned char *zEnd = &zRaw[nByte]; sxu32 nDos,iMagic; sxu16 nLen; char *zKv; /* Database signature */ if( SyMemcmp(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1) != 0 ){ /* Corrupt database */ return UNQLITE_CORRUPT; } zRaw += sizeof(UNQLITE_DB_SIG)-1; /* Database magic number */ SyBigEndianUnpack32(zRaw,&iMagic); zRaw += 4; /* 4 byte magic number */ if( iMagic != UNQLITE_DB_MAGIC ){ /* Corrupt database */ return UNQLITE_CORRUPT; } /* Database creation time */ SyBigEndianUnpack32(zRaw,&nDos); zRaw += 4; /* 4 byte DOS time format */ SyDosTimeFormat(nDos,&pPager->tmCreate); /* Sector size */ SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iSectorSize); zRaw += 4; /* 4 byte sector size */ /* Page size */ SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iPageSize); zRaw += 4; /* 4 byte page size */ /* Check that the values read from the page-size and sector-size fields ** are within range. To be 'in range', both values need to be a power ** of two greater than or equal to 512 or 32, and not greater than their ** respective compile time maximum limits. */ if( pPager->iPageSizeiSectorSize<32 || pPager->iPageSize>UNQLITE_MAX_PAGE_SIZE || pPager->iSectorSize>MAX_SECTOR_SIZE || ((pPager->iPageSize<-1)&pPager->iPageSize)!=0 || ((pPager->iSectorSize-1)&pPager->iSectorSize)!=0 ){ return UNQLITE_CORRUPT; } /* Key value storage engine */ SyBigEndianUnpack16(zRaw,&nLen); /* 2 byte storage engine length */ zRaw += 2; if( nLen > (sxu16)(zEnd - zRaw) ){ nLen = (sxu16)(zEnd - zRaw); } zKv = (char *)SyMemBackendDup(pPager->pAllocator,(const char *)zRaw,nLen); if( zKv == 0 ){ return UNQLITE_NOMEM; } SyStringInitFromBuf(&pPager->sKv,zKv,nLen); return UNQLITE_OK; } /* * Read the database header. */ static int pager_read_db_header(Pager *pPager) { unsigned char zRaw[UNQLITE_MIN_PAGE_SIZE]; /* Minimum page size */ sxi64 n = 0; /* Size of db file in bytes */ int rc; /* Get the file size first */ rc = unqliteOsFileSize(pPager->pfd,&n); if( rc != UNQLITE_OK ){ return rc; } pPager->dbByteSize = n; if( n > 0 ){ unqlite_kv_methods *pMethods; SyString *pKv; pgno nPage; if( n < UNQLITE_MIN_PAGE_SIZE ){ /* A valid unqlite database must be at least 512 bytes long */ unqliteGenError(pPager->pDb,"Malformed database image"); return UNQLITE_CORRUPT; } /* Read the database header */ rc = unqliteOsRead(pPager->pfd,zRaw,sizeof(zRaw),0); if( rc != UNQLITE_OK ){ unqliteGenError(pPager->pDb,"IO error while reading database header"); return rc; } /* Extract the header */ rc = pager_extract_header(pPager,zRaw,sizeof(zRaw)); if( rc != UNQLITE_OK ){ unqliteGenError(pPager->pDb,rc == UNQLITE_NOMEM ? "Unqlite is running out of memory" : "Malformed database image"); return rc; } /* Update pager state */ nPage = (pgno)(n / pPager->iPageSize); if( nPage==0 && n>0 ){ nPage = 1; } pPager->dbSize = nPage; /* Laod the target Key/Value storage engine */ pKv = &pPager->sKv; pMethods = unqliteFindKVStore(pKv->zString,pKv->nByte); if( pMethods == 0 ){ unqliteGenErrorFormat(pPager->pDb,"No such Key/Value storage engine '%z'",pKv); return UNQLITE_NOTIMPLEMENTED; } /* Install the new KV storage engine */ rc = unqlitePagerRegisterKvEngine(pPager,pMethods); if( rc != UNQLITE_OK ){ return rc; } }else{ /* Set a default page and sector size */ pPager->iSectorSize = GetSectorSize(pPager->pfd); pPager->iPageSize = unqliteGetPageSize(); SyStringInitFromBuf(&pPager->sKv,pPager->pEngine->pIo->pMethods->zName,SyStrlen(pPager->pEngine->pIo->pMethods->zName)); pPager->dbSize = 0; } /* Allocate a temporary page size */ pPager->zTmpPage = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize); if( pPager->zTmpPage == 0 ){ unqliteGenOutofMem(pPager->pDb); return UNQLITE_NOMEM; } SyZero(pPager->zTmpPage,(sxu32)pPager->iPageSize); return UNQLITE_OK; } /* * Write the database header. */ static int pager_create_header(Pager *pPager) { Page *pHeader; int rc; /* Allocate a new page */ pHeader = pager_alloc_page(pPager,0); if( pHeader == 0 ){ return UNQLITE_NOMEM; } pPager->pHeader = pHeader; /* Link the page */ pager_link_page(pPager,pHeader); /* Add to the dirty list */ pager_page_to_dirty_list(pPager,pHeader); /* Write the database header */ rc = pager_write_db_header(pPager); return rc; } /* ** This function is called to obtain a shared lock on the database file. ** It is illegal to call unqlitePagerAcquire() until after this function ** has been successfully called. If a shared-lock is already held when ** this function is called, it is a no-op. ** ** The following operations are also performed by this function. ** ** 1) If the pager is currently in PAGER_OPEN state (no lock held ** on the database file), then an attempt is made to obtain a ** SHARED lock on the database file. Immediately after obtaining ** the SHARED lock, the file-system is checked for a hot-journal, ** which is played back if present. ** ** If everything is successful, UNQLITE_OK is returned. If an IO error ** occurs while locking the database, checking for a hot-journal file or ** rolling back a journal file, the IO error code is returned. */ static int pager_shared_lock(Pager *pPager) { int rc = UNQLITE_OK; if( pPager->iState == PAGER_OPEN ){ unqlite_kv_methods *pMethods; /* Open the target database */ rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zFilename,&pPager->pfd,pPager->iOpenFlags); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pPager->pDb, "IO error while opening the target database file: %s",pPager->zFilename ); return rc; } /* Try to obtain a shared lock */ rc = pager_wait_on_lock(pPager,SHARED_LOCK); if( rc == UNQLITE_OK ){ if( pPager->iLock <= SHARED_LOCK ){ /* Rollback any hot journal */ rc = pager_journal_rollback(pPager,1); if( rc != UNQLITE_OK ){ return rc; } } /* Read the database header */ rc = pager_read_db_header(pPager); if( rc != UNQLITE_OK ){ return rc; } if(pPager->dbSize > 0 ){ if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){ const jx9_vfs *pVfs = jx9ExportBuiltinVfs(); /* Obtain a read-only memory view of the whole file */ if( pVfs && pVfs->xMmap ){ int vr; vr = pVfs->xMmap(pPager->zFilename,&pPager->pMmap,&pPager->dbByteSize); if( vr != JX9_OK ){ /* Generate a warning */ unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database"); pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP; } }else{ /* Generate a warning */ unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database"); pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP; } } } /* Update the pager state */ pPager->iState = PAGER_READER; /* Invoke the xOpen methods if available */ pMethods = pPager->pEngine->pIo->pMethods; if( pMethods->xOpen ){ rc = pMethods->xOpen(pPager->pEngine,pPager->dbSize); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pPager->pDb, "xOpen() method of the underlying KV engine '%z' failed", &pPager->sKv ); pager_unlock_db(pPager,NO_LOCK); pPager->iState = PAGER_OPEN; return rc; } } }else if( rc == UNQLITE_BUSY ){ unqliteGenError(pPager->pDb,"Another process or thread have a reserved or exclusive lock on this database"); } } return rc; } /* ** Begin a write-transaction on the specified pager object. If a ** write-transaction has already been opened, this function is a no-op. */ UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager) { int rc; /* Obtain a shared lock on the database first */ rc = pager_shared_lock(pPager); if( rc != UNQLITE_OK ){ return rc; } if( pPager->iState >= PAGER_WRITER_LOCKED ){ return UNQLITE_OK; } if( pPager->is_rdonly ){ unqliteGenError(pPager->pDb,"Read-only database"); /* Read only database */ return UNQLITE_READ_ONLY; } /* Obtain a reserved lock on the database */ rc = pager_wait_on_lock(pPager,RESERVED_LOCK); if( rc == UNQLITE_OK ){ /* Create the bitvec */ pPager->pVec = unqliteBitvecCreate(pPager->pAllocator,pPager->dbSize); if( pPager->pVec == 0 ){ unqliteGenOutofMem(pPager->pDb); rc = UNQLITE_NOMEM; goto fail; } /* Change to the WRITER_LOCK state */ pPager->iState = PAGER_WRITER_LOCKED; pPager->dbOrigSize = pPager->dbSize; pPager->iJournalOfft = 0; pPager->nRec = 0; if( pPager->dbSize < 1 ){ /* Write the database header */ rc = pager_create_header(pPager); if( rc != UNQLITE_OK ){ goto fail; } pPager->dbSize = 1; } }else if( rc == UNQLITE_BUSY ){ unqliteGenError(pPager->pDb,"Another process or thread have a reserved lock on this database"); } return rc; fail: /* Downgrade to shared lock */ pager_unlock_db(pPager,SHARED_LOCK); return rc; } /* ** This function is called at the start of every write transaction. ** There must already be a RESERVED or EXCLUSIVE lock on the database ** file when this routine is called. ** */ static int unqliteOpenJournal(Pager *pPager) { unsigned char *zHeader; int rc = UNQLITE_OK; if( pPager->is_mem || pPager->no_jrnl ){ /* Journaling is omitted for this database */ goto finish; } if( pPager->iState >= PAGER_WRITER_CACHEMOD ){ /* Already opened */ return UNQLITE_OK; } /* Delete any previously journal with the same name */ unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); /* Open the journal file */ rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal, &pPager->pjfd,UNQLITE_OPEN_CREATE|UNQLITE_OPEN_READWRITE); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: %s",pPager->zJournal); return rc; } /* Write the journal header */ zHeader = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iSectorSize); if( zHeader == 0 ){ rc = UNQLITE_NOMEM; goto fail; } pager_write_journal_header(pPager,zHeader); /* Perform the disk write */ rc = unqliteOsWrite(pPager->pjfd,zHeader,pPager->iSectorSize,0); /* Offset to start writing from */ pPager->iJournalOfft = pPager->iSectorSize; /* All done, journal will be synced later */ SyMemBackendFree(pPager->pAllocator,zHeader); finish: if( rc == UNQLITE_OK ){ pPager->iState = PAGER_WRITER_CACHEMOD; return UNQLITE_OK; } fail: /* Unlink the journal file if something goes wrong */ unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); unqliteOsDelete(pPager->pVfs,pPager->zJournal,0); pPager->pjfd = 0; return rc; } /* ** Sync the journal. In other words, make sure all the pages that have ** been written to the journal have actually reached the surface of the ** disk and can be restored in the event of a hot-journal rollback. * * This routine try also to obtain an exlusive lock on the database. */ static int unqliteFinalizeJournal(Pager *pPager,int *pRetry,int close_jrnl) { int rc; *pRetry = 0; /* Grab the exclusive lock first */ rc = pager_lock_db(pPager,EXCLUSIVE_LOCK); if( rc != UNQLITE_OK ){ /* Retry the excusive lock process */ *pRetry = 1; rc = UNQLITE_OK; } if( pPager->no_jrnl ){ /* Journaling is omitted, return immediately */ return UNQLITE_OK; } /* Write the total number of database records */ rc = WriteInt32(pPager->pjfd,pPager->nRec,8 /* sizeof(aJournalRec) */); if( rc != UNQLITE_OK ){ if( pPager->nRec > 0 ){ return rc; }else{ /* Not so fatal */ rc = UNQLITE_OK; } } /* Sync the journal and close it */ rc = unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); if( close_jrnl ){ /* close the journal file */ if( UNQLITE_OK != unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd) ){ if( rc != UNQLITE_OK /* unqliteOsSync */ ){ return rc; } } pPager->pjfd = 0; } if( (*pRetry) == 1 ){ if( pager_lock_db(pPager,EXCLUSIVE_LOCK) == UNQLITE_OK ){ /* Got exclusive lock */ *pRetry = 0; } } return UNQLITE_OK; } /* * Mark a single data page as writeable. The page is written into the * main journal as required. */ static int page_write(Pager *pPager,Page *pPage) { int rc; if( !pPager->is_mem && !pPager->no_jrnl ){ /* Write the page to the transaction journal */ if( pPage->pgno < pPager->dbOrigSize && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){ sxu32 cksum; if( pPager->nRec == SXU32_HIGH ){ /* Journal Limit reached */ unqliteGenError(pPager->pDb,"Journal record limit reached, commit your changes"); return UNQLITE_LIMIT; } /* Write the page number */ rc = WriteInt64(pPager->pjfd,pPage->pgno,pPager->iJournalOfft); if( rc != UNQLITE_OK ){ return rc; } /* Write the raw page */ /** CODEC */ rc = unqliteOsWrite(pPager->pjfd,pPage->zData,pPager->iPageSize,pPager->iJournalOfft + 8); if( rc != UNQLITE_OK ){ return rc; } /* Compute the checksum */ cksum = pager_cksum(pPager,pPage->zData); rc = WriteInt32(pPager->pjfd,cksum,pPager->iJournalOfft + 8 + pPager->iPageSize); if( rc != UNQLITE_OK ){ return rc; } /* Update the journal offset */ pPager->iJournalOfft += 8 /* page num */ + pPager->iPageSize + 4 /* cksum */; pPager->nRec++; /* Mark as journalled */ unqliteBitvecSet(pPager->pVec,pPage->pgno); } } /* Add the page to the dirty list */ pager_page_to_dirty_list(pPager,pPage); /* Update the database size and return. */ if( (1 + pPage->pgno) > pPager->dbSize ){ pPager->dbSize = 1 + pPage->pgno; if( pPager->dbSize == SXU64_HIGH ){ unqliteGenError(pPager->pDb,"Database maximum page limit (64-bit) reached"); return UNQLITE_LIMIT; } } return UNQLITE_OK; } /* ** The argument is the first in a linked list of dirty pages connected ** by the PgHdr.pDirty pointer. This function writes each one of the ** in-memory pages in the list to the database file. The argument may ** be NULL, representing an empty list. In this case this function is ** a no-op. ** ** The pager must hold at least a RESERVED lock when this function ** is called. Before writing anything to the database file, this lock ** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, ** UNQLITE_BUSY is returned and no data is written to the database file. */ static int pager_write_dirty_pages(Pager *pPager,Page *pDirty) { int rc = UNQLITE_OK; Page *pNext; for(;;){ if( pDirty == 0 ){ break; } /* Point to the next dirty page */ pNext = pDirty->pDirtyPrev; /* Not a bug: Reverse link */ if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){ rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize); if( rc != UNQLITE_OK ){ /* A rollback should be done */ break; } } /* Remove stale flags */ pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); if( pDirty->nRef < 1 ){ /* Unlink the page now it is unused */ pager_unlink_page(pPager,pDirty); /* Release the page */ pager_release_page(pPager,pDirty); } /* Point to the next page */ pDirty = pNext; } pPager->pDirty = pPager->pFirstDirty = 0; pPager->pHotDirty = pPager->pFirstHot = 0; pPager->nHot = 0; return rc; } /* ** The argument is the first in a linked list of hot dirty pages connected ** by the PgHdr.pHotDirty pointer. This function writes each one of the ** in-memory pages in the list to the database file. The argument may ** be NULL, representing an empty list. In this case this function is ** a no-op. ** ** The pager must hold at least a RESERVED lock when this function ** is called. Before writing anything to the database file, this lock ** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, ** UNQLITE_BUSY is returned and no data is written to the database file. */ static int pager_write_hot_dirty_pages(Pager *pPager,Page *pDirty) { int rc = UNQLITE_OK; Page *pNext; for(;;){ if( pDirty == 0 ){ break; } /* Point to the next page */ pNext = pDirty->pPrevHot; /* Not a bug: Reverse link */ if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){ rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize); if( rc != UNQLITE_OK ){ break; } } /* Remove stale flags */ pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); /* Unlink from the list of dirty pages */ if( pDirty->pDirtyPrev ){ pDirty->pDirtyPrev->pDirtyNext = pDirty->pDirtyNext; }else{ pPager->pDirty = pDirty->pDirtyNext; } if( pDirty->pDirtyNext ){ pDirty->pDirtyNext->pDirtyPrev = pDirty->pDirtyPrev; }else{ pPager->pFirstDirty = pDirty->pDirtyPrev; } /* Discard */ pager_unlink_page(pPager,pDirty); /* Release the page */ pager_release_page(pPager,pDirty); /* Next hot page */ pDirty = pNext; } return rc; } /* * Commit a transaction: Phase one. */ static int pager_commit_phase1(Pager *pPager) { int get_excl = 0; Page *pDirty; int rc; /* If no database changes have been made, return early. */ if( pPager->iState < PAGER_WRITER_CACHEMOD ){ return UNQLITE_OK; } if( pPager->is_mem ){ /* An in-memory database */ return UNQLITE_OK; } if( pPager->is_rdonly ){ /* Read-Only DB */ unqliteGenError(pPager->pDb,"Read-Only database"); return UNQLITE_READ_ONLY; } /* Finalize the journal file */ rc = unqliteFinalizeJournal(pPager,&get_excl,1); if( rc != UNQLITE_OK ){ return rc; } /* Get the dirty pages */ pDirty = pager_get_dirty_pages(pPager); if( get_excl ){ /* Wait one last time for the exclusive lock */ rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK); if( rc != UNQLITE_OK ){ unqliteGenError(pPager->pDb,"Cannot obtain an Exclusive lock on the target database"); return rc; } } if( pPager->iFlags & PAGER_CTRL_DIRTY_COMMIT ){ /* Sync the database first if a dirty commit have been applied */ unqliteOsSync(pPager->pfd,UNQLITE_SYNC_NORMAL); } /* Write the dirty pages */ rc = pager_write_dirty_pages(pPager,pDirty); if( rc != UNQLITE_OK ){ /* Rollback your DB */ pPager->iFlags |= PAGER_CTRL_COMMIT_ERR; pPager->pFirstDirty = pDirty; unqliteGenError(pPager->pDb,"IO error while writing dirty pages, rollback your database"); return rc; } /* release all pages */ { Page *p; while (1) { p = pPager->pAll; if (p == 0) { break; } pager_unlink_page(pPager, p); pager_release_page(pPager, p); } } /* If the file on disk is not the same size as the database image, * then use unqliteOsTruncate to grow or shrink the file here. */ if( pPager->dbSize != pPager->dbOrigSize ){ unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize); } /* Sync the database file */ unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL); /* Remove stale flags */ pPager->iJournalOfft = 0; pPager->nRec = 0; return UNQLITE_OK; } /* * Commit a transaction: Phase two. */ static int pager_commit_phase2(Pager *pPager) { if( !pPager->is_mem ){ if( pPager->iState == PAGER_OPEN ){ return UNQLITE_OK; } if( pPager->iState != PAGER_READER ){ if( !pPager->no_jrnl ){ /* Finally, unlink the journal file */ unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); } /* Downgrade to shared lock */ pager_unlock_db(pPager,SHARED_LOCK); pPager->iState = PAGER_READER; if( pPager->pVec ){ unqliteBitvecDestroy(pPager->pVec); pPager->pVec = 0; } } } return UNQLITE_OK; } /* * Perform a dirty commit. */ static int pager_dirty_commit(Pager *pPager) { int get_excl = 0; Page *pHot; int rc; /* Finalize the journal file without closing it */ rc = unqliteFinalizeJournal(pPager,&get_excl,0); if( rc != UNQLITE_OK ){ /* It's not a fatal error if something goes wrong here since * its not the final commit. */ return UNQLITE_OK; } /* Point to the list of hot pages */ pHot = pager_get_hot_pages(pPager); if( pHot == 0 ){ return UNQLITE_OK; } if( get_excl ){ /* Wait one last time for the exclusive lock */ rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK); if( rc != UNQLITE_OK ){ /* Not so fatal, will try another time */ return UNQLITE_OK; } } /* Tell that a dirty commit happen */ pPager->iFlags |= PAGER_CTRL_DIRTY_COMMIT; /* Write the hot pages now */ rc = pager_write_hot_dirty_pages(pPager,pHot); if( rc != UNQLITE_OK ){ pPager->iFlags |= PAGER_CTRL_COMMIT_ERR; unqliteGenError(pPager->pDb,"IO error while writing hot dirty pages, rollback your database"); return rc; } pPager->pFirstHot = pPager->pHotDirty = 0; pPager->nHot = 0; /* No need to sync the database file here, since the journal is already * open here and this is not the final commit. */ return UNQLITE_OK; } /* ** Commit a transaction and sync the database file for the pager pPager. ** ** This routine ensures that: ** ** * the journal is synced, ** * all dirty pages are written to the database file, ** * the database file is truncated (if required), and ** * the database file synced. ** * the journal file is deleted. */ UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager) { int rc; /* Commit: Phase One */ rc = pager_commit_phase1(pPager); if( rc != UNQLITE_OK ){ goto fail; } /* Commit: Phase Two */ rc = pager_commit_phase2(pPager); if( rc != UNQLITE_OK ){ goto fail; } /* Remove stale flags */ pPager->iFlags &= ~PAGER_CTRL_COMMIT_ERR; /* All done */ return UNQLITE_OK; fail: /* Disable the auto-commit flag */ pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; return rc; } /* * Reset the pager to its initial state. This is caused by * a rollback operation. */ static int pager_reset_state(Pager *pPager,int bResetKvEngine) { unqlite_kv_engine *pEngine = pPager->pEngine; Page *pNext,*pPtr = pPager->pAll; const unqlite_kv_io *pIo; int rc; /* Remove stale flags */ pPager->iFlags &= ~(PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT); pPager->iJournalOfft = 0; pPager->nRec = 0; /* Database original size */ pPager->dbSize = pPager->dbOrigSize; /* Discard all in-memory pages */ for(;;){ if( pPtr == 0 ){ break; } pNext = pPtr->pNext; /* Reverse link */ /* Remove stale flags */ pPtr->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); /* Release the page */ pager_release_page(pPager,pPtr); /* Point to the next page */ pPtr = pNext; } pPager->pAll = 0; pPager->nPage = 0; pPager->pDirty = pPager->pFirstDirty = 0; pPager->pHotDirty = pPager->pFirstHot = 0; pPager->nHot = 0; if( pPager->apHash ){ /* Zero the table */ SyZero((void *)pPager->apHash,sizeof(Page *) * pPager->nSize); } if( pPager->pVec ){ unqliteBitvecDestroy(pPager->pVec); pPager->pVec = 0; } /* Switch back to shared lock */ pager_unlock_db(pPager,SHARED_LOCK); pPager->iState = PAGER_READER; if( bResetKvEngine ){ /* Reset the underlying KV engine */ pIo = pEngine->pIo; if( pIo->pMethods->xRelease ){ /* Call the release callback */ pIo->pMethods->xRelease(pEngine); } /* Zero the structure */ SyZero(pEngine,(sxu32)pIo->pMethods->szKv); /* Fill in */ pEngine->pIo = pIo; if( pIo->pMethods->xInit ){ /* Call the init method */ rc = pIo->pMethods->xInit(pEngine,pPager->iPageSize); if( rc != UNQLITE_OK ){ return rc; } } if( pIo->pMethods->xOpen ){ /* Call the xOpen method */ rc = pIo->pMethods->xOpen(pEngine,pPager->dbSize); if( rc != UNQLITE_OK ){ return rc; } } } /* All done */ return UNQLITE_OK; } /* ** If a write transaction is open, then all changes made within the ** transaction are reverted and the current write-transaction is closed. ** The pager falls back to PAGER_READER state if successful. ** ** Otherwise, in rollback mode, this function performs two functions: ** ** 1) It rolls back the journal file, restoring all database file and ** in-memory cache pages to the state they were in when the transaction ** was opened, and ** ** 2) It finalizes the journal file, so that it is not used for hot ** rollback at any point in the future (i.e. deletion). ** ** Finalization of the journal file (task 2) is only performed if the ** rollback is successful. ** */ UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine) { int rc = UNQLITE_OK; if( pPager->iState < PAGER_WRITER_LOCKED ){ /* A write transaction must be opened */ return UNQLITE_OK; } if( pPager->is_mem ){ /* As of this release 1.1.6: Transactions are not supported for in-memory databases */ return UNQLITE_OK; } if( pPager->is_rdonly ){ /* Read-Only DB */ unqliteGenError(pPager->pDb,"Read-Only database"); return UNQLITE_READ_ONLY; } if( pPager->iState >= PAGER_WRITER_CACHEMOD ){ if( !pPager->no_jrnl ){ /* Close any outstanding joural file */ if( pPager->pjfd ){ /* Sync the journal file */ unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); } unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); pPager->pjfd = 0; if( pPager->iFlags & (PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT) ){ /* Perform the rollback */ rc = pager_journal_rollback(pPager,0); if( rc != UNQLITE_OK ){ /* Set the auto-commit flag */ pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; return rc; } } } /* Unlink the journal file */ unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); /* Reset the pager state */ rc = pager_reset_state(pPager,bResetKvEngine); if( rc != UNQLITE_OK ){ /* Mostly an unlikely scenario */ pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; /* Set the auto-commit flag */ unqliteGenError(pPager->pDb,"Error while reseting pager to its initial state"); return rc; } }else{ /* Downgrade to shared lock */ pager_unlock_db(pPager,SHARED_LOCK); pPager->iState = PAGER_READER; } return UNQLITE_OK; } /* * Mark a data page as non writeable. */ static int unqlitePagerDontWrite(unqlite_page *pMyPage) { Page *pPage = (Page *)pMyPage; if( pPage->pgno > 0 /* Page 0 is always writeable */ ){ pPage->flags |= PAGE_DONT_WRITE; } return UNQLITE_OK; } /* ** Mark a data page as writeable. This routine must be called before ** making changes to a page. The caller must check the return value ** of this function and be careful not to change any page data unless ** this routine returns UNQLITE_OK. */ static int unqlitePageWrite(unqlite_page *pMyPage) { Page *pPage = (Page *)pMyPage; Pager *pPager = pPage->pPager; int rc; /* Begin the write transaction */ rc = unqlitePagerBegin(pPager); if( rc != UNQLITE_OK ){ return rc; } if( pPager->iState == PAGER_WRITER_LOCKED ){ /* The journal file needs to be opened. Higher level routines have already ** obtained the necessary locks to begin the write-transaction, but the ** rollback journal might not yet be open. Open it now if this is the case. */ rc = unqliteOpenJournal(pPager); if( rc != UNQLITE_OK ){ return rc; } } if( pPager->nHot > 127 ){ /* Write hot dirty pages */ rc = pager_dirty_commit(pPager); if( rc != UNQLITE_OK ){ /* A rollback must be done */ unqliteGenError(pPager->pDb,"Please perform a rollback"); return rc; } } /* Write the page to the journal file */ rc = page_write(pPager,pPage); return rc; } /* ** Acquire a reference to page number pgno in pager pPager (a page ** reference has type unqlite_page*). If the requested reference is ** successfully obtained, it is copied to *ppPage and UNQLITE_OK returned. ** ** If the requested page is already in the cache, it is returned. ** Otherwise, a new page object is allocated and populated with data ** read from the database file. */ static int unqlitePagerAcquire( Pager *pPager, /* The pager open on the database file */ pgno pgno, /* Page number to fetch */ unqlite_page **ppPage, /* OUT: Acquired page */ int fetchOnly, /* Cache lookup only */ int noContent /* Do not bother reading content from disk if true */ ) { Page *pPage; int rc; /* Acquire a shared lock (if not yet done) on the database and rollback any hot-journal if present */ rc = pager_shared_lock(pPager); if( rc != UNQLITE_OK ){ return rc; } /* Fetch the page from the cache */ pPage = pager_fetch_page(pPager,pgno); if( fetchOnly ){ if( ppPage ){ *ppPage = (unqlite_page *)pPage; } return pPage ? UNQLITE_OK : UNQLITE_NOTFOUND; } if( pPage == 0 ){ /* Allocate a new page */ pPage = pager_alloc_page(pPager,pgno); if( pPage == 0 ){ unqliteGenOutofMem(pPager->pDb); return UNQLITE_NOMEM; } /* Read page contents */ rc = pager_get_page_contents(pPager,pPage,noContent); if( rc != UNQLITE_OK ){ SyMemBackendPoolFree(pPager->pAllocator,pPage); return rc; } /* Link the page */ pager_link_page(pPager,pPage); }else{ if( ppPage ){ page_ref(pPage); } } /* All done, page is loaded in memeory */ if( ppPage ){ *ppPage = (unqlite_page *)pPage; } return UNQLITE_OK; } /* * Return true if we are dealing with an in-memory database. */ static int unqliteInMemory(const char *zFilename) { sxu32 n; if( SX_EMPTY_STR(zFilename) ){ /* NULL or the empty string means an in-memory database */ return TRUE; } n = SyStrlen(zFilename); if( n == sizeof(":mem:") - 1 && SyStrnicmp(zFilename,":mem:",sizeof(":mem:") - 1) == 0 ){ return TRUE; } if( n == sizeof(":memory:") - 1 && SyStrnicmp(zFilename,":memory:",sizeof(":memory:") - 1) == 0 ){ return TRUE; } return FALSE; } /* * Allocate a new KV cursor. */ UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut) { unqlite_kv_methods *pMethods; unqlite_kv_cursor *pCur; sxu32 nByte; /* Storage engine methods */ pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods; if( pMethods->szCursor < 1 ){ /* Implementation does not supprt cursors */ unqliteGenErrorFormat(pDb,"Storage engine '%s' does not support cursors",pMethods->zName); return UNQLITE_NOTIMPLEMENTED; } nByte = pMethods->szCursor; if( nByte < sizeof(unqlite_kv_cursor) ){ nByte += sizeof(unqlite_kv_cursor); } pCur = (unqlite_kv_cursor *)SyMemBackendPoolAlloc(&pDb->sMem,nByte); if( pCur == 0 ){ unqliteGenOutofMem(pDb); return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pCur,nByte); /* Save the cursor */ pCur->pStore = pDb->sDB.pPager->pEngine; /* Invoke the initialization callback if any */ if( pMethods->xCursorInit ){ pMethods->xCursorInit(pCur); } /* All done */ *ppOut = pCur; return UNQLITE_OK; } /* * Release a cursor. */ UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur) { unqlite_kv_methods *pMethods; /* Storage engine methods */ pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods; /* Invoke the release callback if available */ if( pMethods->xCursorRelease ){ pMethods->xCursorRelease(pCur); } /* Finally, free the whole instance */ SyMemBackendPoolFree(&pDb->sMem,pCur); return UNQLITE_OK; } /* * Release the underlying KV storage engine and invoke * its associated callbacks if available. */ static void pager_release_kv_engine(Pager *pPager) { unqlite_kv_engine *pEngine = pPager->pEngine; unqlite_db *pStorage = &pPager->pDb->sDB; if( pStorage->pCursor ){ /* Release the associated cursor */ unqliteReleaseCursor(pPager->pDb,pStorage->pCursor); pStorage->pCursor = 0; } if( pEngine->pIo->pMethods->xRelease ){ pEngine->pIo->pMethods->xRelease(pEngine); } /* Release the whole instance */ SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine->pIo); SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine); pPager->pEngine = 0; } /* Forward declaration */ static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo); /* * Allocate, initialize and register a new KV storage engine * within this database instance. */ UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods) { unqlite_db *pStorage = &pPager->pDb->sDB; unqlite *pDb = pPager->pDb; unqlite_kv_engine *pEngine; unqlite_kv_io *pIo; sxu32 nByte; int rc; if( pPager->pEngine ){ if( pMethods == pPager->pEngine->pIo->pMethods ){ /* Ticket 1432: Same implementation */ return UNQLITE_OK; } /* Release the old KV engine */ pager_release_kv_engine(pPager); } /* Allocate a new KV engine instance */ nByte = (sxu32)pMethods->szKv; pEngine = (unqlite_kv_engine *)SyMemBackendAlloc(&pDb->sMem,nByte); if( pEngine == 0 ){ unqliteGenOutofMem(pDb); return UNQLITE_NOMEM; } pIo = (unqlite_kv_io *)SyMemBackendAlloc(&pDb->sMem,sizeof(unqlite_kv_io)); if( pIo == 0 ){ SyMemBackendFree(&pDb->sMem,pEngine); unqliteGenOutofMem(pDb); return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pIo,sizeof(unqlite_io_methods)); SyZero(pEngine,nByte); /* Populate the IO structure */ pager_kv_io_init(pPager,pMethods,pIo); pEngine->pIo = pIo; /* Invoke the init callback if avaialble */ if( pMethods->xInit ){ rc = pMethods->xInit(pEngine,unqliteGetPageSize()); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pDb, "xInit() method of the underlying KV engine '%z' failed",&pPager->sKv); goto fail; } pEngine->pIo = pIo; } pPager->pEngine = pEngine; /* Allocate a new cursor */ rc = unqliteInitCursor(pDb,&pStorage->pCursor); if( rc != UNQLITE_OK ){ goto fail; } return UNQLITE_OK; fail: SyMemBackendFree(&pDb->sMem,pEngine); SyMemBackendFree(&pDb->sMem,pIo); return rc; } /* * Return the underlying KV storage engine instance. */ UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb) { return pDb->sDB.pPager->pEngine; } /* * Allocate and initialize a new Pager object. The pager should * eventually be freed by passing it to unqlitePagerClose(). * * The zFilename argument is the path to the database file to open. * If zFilename is NULL or ":memory:" then all information is held * in cache. It is never written to disk. This can be used to implement * an in-memory database. */ UNQLITE_PRIVATE int unqlitePagerOpen( unqlite_vfs *pVfs, /* The virtual file system to use */ unqlite *pDb, /* Database handle */ const char *zFilename, /* Name of the database file to open */ unsigned int iFlags /* flags controlling this file */ ) { unqlite_kv_methods *pMethods = 0; int is_mem,rd_only,no_jrnl; Pager *pPager; sxu32 nByte; sxu32 nLen; int rc; /* Select the appropriate KV storage subsytem */ if( (iFlags & UNQLITE_OPEN_IN_MEMORY) || unqliteInMemory(zFilename) ){ /* An in-memory database, record that */ pMethods = unqliteFindKVStore("mem",sizeof("mem") - 1); /* Always available */ iFlags |= UNQLITE_OPEN_IN_MEMORY; }else{ /* Install the default key value storage subsystem [i.e. Linear Hash] */ pMethods = unqliteFindKVStore("hash",sizeof("hash")-1); if( pMethods == 0 ){ /* Use the b+tree storage backend if the linear hash storage is not available */ pMethods = unqliteFindKVStore("btree",sizeof("btree")-1); } } if( pMethods == 0 ){ /* Can't happen */ unqliteGenError(pDb,"Cannot install a default Key/Value storage engine"); return UNQLITE_NOTIMPLEMENTED; } is_mem = (iFlags & UNQLITE_OPEN_IN_MEMORY) != 0; rd_only = (iFlags & UNQLITE_OPEN_READONLY) != 0; no_jrnl = (iFlags & UNQLITE_OPEN_OMIT_JOURNALING) != 0; rc = UNQLITE_OK; if( is_mem ){ /* Omit journaling for in-memory database */ no_jrnl = 1; } /* Total number of bytes to allocate */ nByte = sizeof(Pager); nLen = 0; if( !is_mem ){ nLen = SyStrlen(zFilename); nByte += pVfs->mxPathname + nLen + sizeof(char) /* null terminator */; } /* Allocate */ pPager = (Pager *)SyMemBackendAlloc(&pDb->sMem,nByte); if( pPager == 0 ){ return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pPager,nByte); /* Fill-in the structure */ pPager->pAllocator = &pDb->sMem; pPager->pDb = pDb; pDb->sDB.pPager = pPager; /* Allocate page table */ pPager->nSize = 128; /* Must be a power of two */ nByte = pPager->nSize * sizeof(Page *); pPager->apHash = (Page **)SyMemBackendAlloc(pPager->pAllocator,nByte); if( pPager->apHash == 0 ){ rc = UNQLITE_NOMEM; goto fail; } SyZero(pPager->apHash,nByte); pPager->is_mem = is_mem; pPager->no_jrnl = no_jrnl; pPager->is_rdonly = rd_only; pPager->iOpenFlags = iFlags; pPager->pVfs = pVfs; SyRandomnessInit(&pPager->sPrng,0,0); SyRandomness(&pPager->sPrng,(void *)&pPager->cksumInit,sizeof(sxu32)); /* Unlimited cache size */ pPager->nCacheMax = SXU32_HIGH; /* Copy filename and journal name */ if( !is_mem ){ pPager->zFilename = (char *)&pPager[1]; rc = UNQLITE_OK; if( pVfs->xFullPathname ){ rc = pVfs->xFullPathname(pVfs,zFilename,pVfs->mxPathname + nLen,pPager->zFilename); } if( rc != UNQLITE_OK ){ /* Simple filename copy */ SyMemcpy(zFilename,pPager->zFilename,nLen); pPager->zFilename[nLen] = 0; rc = UNQLITE_OK; }else{ nLen = SyStrlen(pPager->zFilename); } pPager->zJournal = (char *) SyMemBackendAlloc(pPager->pAllocator,nLen + sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) + sizeof(char)); if( pPager->zJournal == 0 ){ rc = UNQLITE_NOMEM; goto fail; } /* Copy filename */ SyMemcpy(pPager->zFilename,pPager->zJournal,nLen); /* Copy journal suffix */ SyMemcpy(UNQLITE_JOURNAL_FILE_SUFFIX,&pPager->zJournal[nLen],sizeof(UNQLITE_JOURNAL_FILE_SUFFIX)-1); /* Append the nul terminator to the journal path */ pPager->zJournal[nLen + ( sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) - 1)] = 0; } /* Finally, register the selected KV engine */ rc = unqlitePagerRegisterKvEngine(pPager,pMethods); if( rc != UNQLITE_OK ){ goto fail; } /* Set the pager state */ if( pPager->is_mem ){ pPager->iState = PAGER_WRITER_FINISHED; pPager->iLock = EXCLUSIVE_LOCK; }else{ pPager->iState = PAGER_OPEN; pPager->iLock = NO_LOCK; } /* All done, ready for processing */ return UNQLITE_OK; fail: SyMemBackendFree(&pDb->sMem,pPager); return rc; } /* * Set a cache limit. Note that, this is a simple hint, the pager is not * forced to honor this limit. */ UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage) { if( mxPage < 256 ){ return UNQLITE_INVALID; } pPager->nCacheMax = mxPage; return UNQLITE_OK; } /* * Shutdown the page cache. Free all memory and close the database file. */ UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager) { /* Release the KV engine */ pager_release_kv_engine(pPager); if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){ const jx9_vfs *pVfs = jx9ExportBuiltinVfs(); if( pVfs && pVfs->xUnmap && pPager->pMmap ){ pVfs->xUnmap(pPager->pMmap,pPager->dbByteSize); } } if( !pPager->is_mem && pPager->iState >= PAGER_OPEN ){ /* Release all lock on this database handle. The issue is * discussed at https://github.com/symisc/unqlite/issues/74. */ pager_unlock_db(pPager,NO_LOCK); /* Close the file */ unqliteOsCloseFree(pPager->pAllocator,pPager->pfd); } if( pPager->pVec ){ unqliteBitvecDestroy(pPager->pVec); pPager->pVec = 0; } return UNQLITE_OK; } /* * Generate a random string. */ UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen) { static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */ sxu32 i; /* Generate a binary string first */ SyRandomness(&pPager->sPrng,zBuf,nLen); /* Turn the binary string into english based alphabet */ for( i = 0 ; i < nLen ; ++i ){ zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)]; } } /* * Generate a random number. */ UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager) { sxu32 iNum; SyRandomness(&pPager->sPrng,(void *)&iNum,sizeof(iNum)); return iNum; } /* Exported KV IO Methods */ /* * Refer to [unqlitePagerAcquire()] */ static int unqliteKvIoPageGet(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage) { int rc; rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,0,0); return rc; } /* * Refer to [unqlitePagerAcquire()] */ static int unqliteKvIoPageLookup(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage) { int rc; rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,1,0); return rc; } /* * Refer to [unqlitePagerAcquire()] */ static int unqliteKvIoNewPage(unqlite_kv_handle pHandle,unqlite_page **ppPage) { Pager *pPager = (Pager *)pHandle; int rc; /* * Acquire a reader-lock first so that pPager->dbSize get initialized. */ rc = pager_shared_lock(pPager); if( rc == UNQLITE_OK ){ rc = unqlitePagerAcquire(pPager,pPager->dbSize == 0 ? /* Page 0 is reserved */ 1 : pPager->dbSize ,ppPage,0,0); } return rc; } /* * Refer to [unqlitePageWrite()] */ static int unqliteKvIopageWrite(unqlite_page *pPage) { int rc; if( pPage == 0 ){ /* TICKET 1433-0348 */ return UNQLITE_OK; } rc = unqlitePageWrite(pPage); return rc; } /* * Refer to [unqlitePagerDontWrite()] */ static int unqliteKvIoPageDontWrite(unqlite_page *pPage) { int rc; if( pPage == 0 ){ /* TICKET 1433-0348 */ return UNQLITE_OK; } rc = unqlitePagerDontWrite(pPage); return rc; } /* * Refer to [unqliteBitvecSet()] */ static int unqliteKvIoPageDontJournal(unqlite_page *pRaw) { Page *pPage = (Page *)pRaw; Pager *pPager; if( pPage == 0 ){ /* TICKET 1433-0348 */ return UNQLITE_OK; } pPager = pPage->pPager; if( pPager->iState >= PAGER_WRITER_LOCKED ){ if( !pPager->no_jrnl && pPager->pVec && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){ unqliteBitvecSet(pPager->pVec,pPage->pgno); } } return UNQLITE_OK; } /* * Do not add a page to the hot dirty list. */ static int unqliteKvIoPageDontMakeHot(unqlite_page *pRaw) { Page *pPage = (Page *)pRaw; if( pPage == 0 ){ /* TICKET 1433-0348 */ return UNQLITE_OK; } pPage->flags |= PAGE_DONT_MAKE_HOT; /* Remove from hot dirty list if it is already there */ if( pPage->flags & PAGE_HOT_DIRTY ){ Pager *pPager = pPage->pPager; if( pPage->pNextHot ){ pPage->pNextHot->pPrevHot = pPage->pPrevHot; } if( pPage->pPrevHot ){ pPage->pPrevHot->pNextHot = pPage->pNextHot; } if( pPager->pFirstHot == pPage ){ pPager->pFirstHot = pPage->pPrevHot; } if( pPager->pHotDirty == pPage ){ pPager->pHotDirty = pPage->pNextHot; } pPager->nHot--; pPage->flags &= ~PAGE_HOT_DIRTY; } return UNQLITE_OK; } /* * Refer to [page_ref()] */ static int unqliteKvIopage_ref(unqlite_page *pPage) { if( pPage ){ page_ref((Page *)pPage); } return UNQLITE_OK; } /* * Refer to [page_unref()] */ static int unqliteKvIoPageUnRef(unqlite_page *pPage) { if( pPage ){ page_unref((Page *)pPage); } return UNQLITE_OK; } /* * Refer to the declaration of the [Pager] structure */ static int unqliteKvIoReadOnly(unqlite_kv_handle pHandle) { return ((Pager *)pHandle)->is_rdonly; } /* * Refer to the declaration of the [Pager] structure */ static int unqliteKvIoPageSize(unqlite_kv_handle pHandle) { return ((Pager *)pHandle)->iPageSize; } /* * Refer to the declaration of the [Pager] structure */ static unsigned char * unqliteKvIoTempPage(unqlite_kv_handle pHandle) { return ((Pager *)pHandle)->zTmpPage; } /* * Set a page unpin callback. * Refer to the declaration of the [Pager] structure */ static void unqliteKvIoPageUnpin(unqlite_kv_handle pHandle,void (*xPageUnpin)(void *)) { Pager *pPager = (Pager *)pHandle; pPager->xPageUnpin = xPageUnpin; } /* * Set a page reload callback. * Refer to the declaration of the [Pager] structure */ static void unqliteKvIoPageReload(unqlite_kv_handle pHandle,void (*xPageReload)(void *)) { Pager *pPager = (Pager *)pHandle; pPager->xPageReload = xPageReload; } /* * Log an error. * Refer to the declaration of the [Pager] structure */ static void unqliteKvIoErr(unqlite_kv_handle pHandle,const char *zErr) { Pager *pPager = (Pager *)pHandle; unqliteGenError(pPager->pDb,zErr); } /* * Init an instance of the [unqlite_kv_io] structure. */ static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo) { pIo->pHandle = pPager; pIo->pMethods = pMethods; pIo->xGet = unqliteKvIoPageGet; pIo->xLookup = unqliteKvIoPageLookup; pIo->xNew = unqliteKvIoNewPage; pIo->xWrite = unqliteKvIopageWrite; pIo->xDontWrite = unqliteKvIoPageDontWrite; pIo->xDontJournal = unqliteKvIoPageDontJournal; pIo->xDontMkHot = unqliteKvIoPageDontMakeHot; pIo->xPageRef = unqliteKvIopage_ref; pIo->xPageUnref = unqliteKvIoPageUnRef; pIo->xPageSize = unqliteKvIoPageSize; pIo->xReadOnly = unqliteKvIoReadOnly; pIo->xTmpPage = unqliteKvIoTempPage; pIo->xSetUnpin = unqliteKvIoPageUnpin; pIo->xSetReload = unqliteKvIoPageReload; pIo->xErr = unqliteKvIoErr; return UNQLITE_OK; } /* * ---------------------------------------------------------- * File: unqlite_vm.c * MD5: 2a0c56efb2ab87d3e52d0d7c3147c53b * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: unqlite_vm.c v1.0 Win7 2013-01-29 23:37 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* This file deals with low level stuff related to the unQLite Virtual Machine */ /* Record ID as a hash value */ #define COL_RECORD_HASH(RID) (RID) /* * Fetch a record from a given collection. */ static unqlite_col_record * CollectionCacheFetchRecord( unqlite_col *pCol, /* Target collection */ jx9_int64 nId /* Unique record ID */ ) { unqlite_col_record *pEntry; if( pCol->nRec < 1 ){ /* Don't bother hashing */ return 0; } pEntry = pCol->apRecord[COL_RECORD_HASH(nId) & (pCol->nRecSize - 1)]; for(;;){ if( pEntry == 0 ){ break; } if( pEntry->nId == nId ){ /* Record found */ return pEntry; } /* Point to the next entry */ pEntry = pEntry->pNextCol; } /* No such record */ return 0; } /* * Install a freshly created record in a given collection. */ static int CollectionCacheInstallRecord( unqlite_col *pCol, /* Target collection */ jx9_int64 nId, /* Unique record ID */ jx9_value *pValue /* JSON value */ ) { unqlite_col_record *pRecord; sxu32 iBucket; /* Fetch the record first */ pRecord = CollectionCacheFetchRecord(pCol,nId); if( pRecord ){ /* Record already installed, overwrite its old value */ jx9MemObjStore(pValue,&pRecord->sValue); return UNQLITE_OK; } /* Allocate a new instance */ pRecord = (unqlite_col_record *)SyMemBackendPoolAlloc(&pCol->pVm->sAlloc,sizeof(unqlite_col_record)); if( pRecord == 0 ){ return UNQLITE_NOMEM; } /* Zero the structure */ SyZero(pRecord,sizeof(unqlite_col_record)); /* Fill in the structure */ jx9MemObjInit(pCol->pVm->pJx9Vm,&pRecord->sValue); jx9MemObjStore(pValue,&pRecord->sValue); pRecord->nId = nId; pRecord->pCol = pCol; /* Install in the corresponding bucket */ iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1); pRecord->pNextCol = pCol->apRecord[iBucket]; if( pCol->apRecord[iBucket] ){ pCol->apRecord[iBucket]->pPrevCol = pRecord; } pCol->apRecord[iBucket] = pRecord; /* Link */ MACRO_LD_PUSH(pCol->pList,pRecord); pCol->nRec++; if( (pCol->nRec >= pCol->nRecSize * 3) && pCol->nRec < 100000 ){ /* Allocate a new larger table */ sxu32 nNewSize = pCol->nRecSize << 1; unqlite_col_record *pEntry; unqlite_col_record **apNew; sxu32 n; apNew = (unqlite_col_record **)SyMemBackendAlloc(&pCol->pVm->sAlloc, nNewSize * sizeof(unqlite_col_record *)); if( apNew ){ /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(unqlite_col_record *)); /* Rehash all entries */ n = 0; pEntry = pCol->pList; for(;;){ /* Loop one */ if( n >= pCol->nRec ){ break; } pEntry->pNextCol = pEntry->pPrevCol = 0; /* Install in the new bucket */ iBucket = COL_RECORD_HASH(pEntry->nId) & (nNewSize - 1); pEntry->pNextCol = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevCol = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; } /* Release the old table and reflect the change */ SyMemBackendFree(&pCol->pVm->sAlloc,(void *)pCol->apRecord); pCol->apRecord = apNew; pCol->nRecSize = nNewSize; } } /* All done */ return UNQLITE_OK; } /* * Remove a record from the collection table. */ UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord( unqlite_col *pCol, /* Target collection */ jx9_int64 nId /* Unique record ID */ ) { unqlite_col_record *pRecord; /* Fetch the record first */ pRecord = CollectionCacheFetchRecord(pCol,nId); if( pRecord == 0 ){ /* No such record */ return UNQLITE_NOTFOUND; } if( pRecord->pPrevCol ){ pRecord->pPrevCol->pNextCol = pRecord->pNextCol; }else{ sxu32 iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1); pCol->apRecord[iBucket] = pRecord->pNextCol; } if( pRecord->pNextCol ){ pRecord->pNextCol->pPrevCol = pRecord->pPrevCol; } /* Unlink */ MACRO_LD_REMOVE(pCol->pList,pRecord); pCol->nRec--; return UNQLITE_OK; } /* * Discard a collection and its records. */ static int CollectionCacheRelease(unqlite_col *pCol) { unqlite_col_record *pNext,*pRec = pCol->pList; unqlite_vm *pVm = pCol->pVm; sxu32 n; /* Discard all records */ for( n = 0 ; n < pCol->nRec ; ++n ){ pNext = pRec->pNext; jx9MemObjRelease(&pRec->sValue); SyMemBackendPoolFree(&pVm->sAlloc,(void *)pRec); /* Point to the next record */ pRec = pNext; } SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord); pCol->nRec = pCol->nRecSize = 0; pCol->pList = 0; return UNQLITE_OK; } /* * Install a freshly created collection in the unqlite VM. */ static int unqliteVmInstallCollection( unqlite_vm *pVm, /* Target VM */ unqlite_col *pCol /* Collection to install */ ) { SyString *pName = &pCol->sName; sxu32 iBucket; /* Hash the collection name */ pCol->nHash = SyBinHash((const void *)pName->zString,pName->nByte); /* Install it in the corresponding bucket */ iBucket = pCol->nHash & (pVm->iColSize - 1); pCol->pNextCol = pVm->apCol[iBucket]; if( pVm->apCol[iBucket] ){ pVm->apCol[iBucket]->pPrevCol = pCol; } pVm->apCol[iBucket] = pCol; /* Link to the list of active collections */ MACRO_LD_PUSH(pVm->pCol,pCol); pVm->iCol++; if( (pVm->iCol >= pVm->iColSize * 4) && pVm->iCol < 10000 ){ /* Grow the hashtable */ sxu32 nNewSize = pVm->iColSize << 1; unqlite_col *pEntry; unqlite_col **apNew; sxu32 n; apNew = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc, nNewSize * sizeof(unqlite_col *)); if( apNew ){ /* Zero the new table */ SyZero((void *)apNew, nNewSize * sizeof(unqlite_col *)); /* Rehash all entries */ n = 0; pEntry = pVm->pCol; for(;;){ /* Loop one */ if( n >= pVm->iCol ){ break; } pEntry->pNextCol = pEntry->pPrevCol = 0; /* Install in the new bucket */ iBucket = pEntry->nHash & (nNewSize - 1); pEntry->pNextCol = apNew[iBucket]; if( apNew[iBucket] ){ apNew[iBucket]->pPrevCol = pEntry; } apNew[iBucket] = pEntry; /* Point to the next entry */ pEntry = pEntry->pNext; n++; } /* Release the old table and reflect the change */ SyMemBackendFree(&pVm->sAlloc,(void *)pVm->apCol); pVm->apCol = apNew; pVm->iColSize = nNewSize; } } return UNQLITE_OK; } /* * Fetch a collection from the target VM. */ static unqlite_col * unqliteVmFetchCollection( unqlite_vm *pVm, /* Target VM */ SyString *pName /* Lookup name */ ) { unqlite_col *pCol; sxu32 nHash; if( pVm->iCol < 1 ){ /* Don't bother hashing */ return 0; } nHash = SyBinHash((const void *)pName->zString,pName->nByte); /* Perform the lookup */ pCol = pVm->apCol[nHash & ( pVm->iColSize - 1)]; for(;;){ if( pCol == 0 ){ break; } if( nHash == pCol->nHash && SyStringCmp(pName,&pCol->sName,SyMemcmp) == 0 ){ /* Collection found */ return pCol; } /* Point to the next entry */ pCol = pCol->pNextCol; } /* No such collection */ return 0; } /* * Write and/or alter collection binary header. */ static int CollectionSetHeader( unqlite_kv_engine *pEngine, /* Underlying KV storage engine */ unqlite_col *pCol, /* Target collection */ jx9_int64 iRec, /* Last record ID */ jx9_int64 iTotal, /* Total number of records in this collection */ jx9_value *pSchema /* Collection schema */ ) { SyBlob *pHeader = &pCol->sHeader; unqlite_kv_methods *pMethods; int iWrite = 0; int rc; if( pEngine == 0 ){ /* Default storage engine */ pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); } pMethods = pEngine->pIo->pMethods; if( SyBlobLength(pHeader) < 1 ){ Sytm *pCreate = &pCol->sCreation; /* Creation time */ unqlite_vfs *pVfs; sxu32 iDos; /* Magic number */ rc = SyBlobAppendBig16(pHeader,UNQLITE_COLLECTION_MAGIC); if( rc != UNQLITE_OK ){ return rc; } /* Initial record ID */ rc = SyBlobAppendBig64(pHeader,0); if( rc != UNQLITE_OK ){ return rc; } /* Total records in the collection */ rc = SyBlobAppendBig64(pHeader,0); if( rc != UNQLITE_OK ){ return rc; } pVfs = (unqlite_vfs *)unqliteExportBuiltinVfs(); /* Creation time of the collection */ if( pVfs->xCurrentTime ){ /* Get the creation time */ pVfs->xCurrentTime(pVfs,pCreate); }else{ /* Zero the structure */ SyZero(pCreate,sizeof(Sytm)); } /* Convert to DOS time */ SyTimeFormatToDos(pCreate,&iDos); rc = SyBlobAppendBig32(pHeader,iDos); if( rc != UNQLITE_OK ){ return rc; } /* Offset to start writing collection schema */ pCol->nSchemaOfft = SyBlobLength(pHeader); iWrite = 1; }else{ unsigned char *zBinary = (unsigned char *)SyBlobData(pHeader); /* Header update */ if( iRec >= 0 ){ /* Update record ID */ SyBigEndianPack64(&zBinary[2/* Magic number*/],(sxu64)iRec); iWrite = 1; } if( iTotal >= 0 ){ /* Total records */ SyBigEndianPack64(&zBinary[2/* Magic number*/+8/* Record ID*/],(sxu64)iTotal); iWrite = 1; } if( pSchema ){ /* Collection Schema */ SyBlobTruncate(pHeader,pCol->nSchemaOfft); /* Encode the schema to FastJson */ rc = FastJsonEncode(pSchema,pHeader,0); if( rc != UNQLITE_OK ){ return rc; } /* Copy the collection schema */ jx9MemObjStore(pSchema,&pCol->sSchema); iWrite = 1; } } if( iWrite ){ SyString *pId = &pCol->sName; /* Reflect the disk and/or in-memory image */ rc = pMethods->xReplace(pEngine, (const void *)pId->zString,pId->nByte, SyBlobData(pHeader),SyBlobLength(pHeader) ); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pCol->pVm->pDb, "Cannot save collection '%z' header in the underlying storage engine", pId ); return rc; } } return UNQLITE_OK; } /* * Load a binary collection from disk. */ static int CollectionLoadHeader(unqlite_col *pCol) { SyBlob *pHeader = &pCol->sHeader; unsigned char *zRaw,*zEnd; sxu16 nMagic; sxu32 iDos; int rc; SyBlobReset(pHeader); /* Read the binary header */ rc = unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pHeader); if( rc != UNQLITE_OK ){ return rc; } /* Perform a sanity check */ if( SyBlobLength(pHeader) < (2 /* magic */ + 8 /* record_id */ + 8 /* total_records */+ 4 /* DOS creation time*/) ){ return UNQLITE_CORRUPT; } zRaw = (unsigned char *)SyBlobData(pHeader); zEnd = &zRaw[SyBlobLength(pHeader)]; /* Extract the magic number */ SyBigEndianUnpack16(zRaw,&nMagic); if( nMagic != UNQLITE_COLLECTION_MAGIC ){ return UNQLITE_CORRUPT; } zRaw += 2; /* sizeof(sxu16) */ /* Extract the record ID */ SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nLastid); zRaw += 8; /* sizeof(sxu64) */ /* Total records in the collection */ SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nTotRec); /* Extract the collection creation date (DOS) */ zRaw += 8; /* sizeof(sxu64) */ SyBigEndianUnpack32(zRaw,&iDos); SyDosTimeFormat(iDos,&pCol->sCreation); zRaw += 4; /* Check for a collection schema */ pCol->nSchemaOfft = (sxu32)(zRaw - (unsigned char *)SyBlobData(pHeader)); if( zRaw < zEnd ){ /* Decode the FastJson value */ FastJsonDecode((const void *)zRaw,(sxu32)(zEnd-zRaw),&pCol->sSchema,0,0); } return UNQLITE_OK; } /* * Load or create a binary collection. */ static int unqliteVmLoadCollection( unqlite_vm *pVm, /* Target VM */ const char *zName, /* Collection name */ sxu32 nByte, /* zName length */ int iFlag, /* Control flag */ unqlite_col **ppOut /* OUT: in-memory collection */ ) { unqlite_kv_methods *pMethods; unqlite_kv_engine *pEngine; unqlite_kv_cursor *pCursor; unqlite *pDb = pVm->pDb; unqlite_col *pCol = 0; /* cc warning */ int rc = SXERR_MEM; char *zDup = 0; /* Point to the underlying KV store */ pEngine = unqlitePagerGetKvEngine(pVm->pDb); pMethods = pEngine->pIo->pMethods; /* Allocate a new cursor */ rc = unqliteInitCursor(pDb,&pCursor); if( rc != UNQLITE_OK ){ return rc; } if( (iFlag & UNQLITE_VM_COLLECTION_CREATE) == 0 ){ /* Seek to the desired location */ rc = pMethods->xSeek(pCursor,(const void *)zName,(int)nByte,UNQLITE_CURSOR_MATCH_EXACT); if( rc != UNQLITE_OK && (iFlag & UNQLITE_VM_COLLECTION_EXISTS) == 0){ unqliteGenErrorFormat(pDb,"Collection '%.*s' not defined in the underlying database",nByte,zName); unqliteReleaseCursor(pDb,pCursor); return rc; } else if((iFlag & UNQLITE_VM_COLLECTION_EXISTS)){ unqliteReleaseCursor(pDb,pCursor); return rc; } } /* Allocate a new instance */ pCol = (unqlite_col *)SyMemBackendPoolAlloc(&pVm->sAlloc,sizeof(unqlite_col)); if( pCol == 0 ){ unqliteGenOutofMem(pDb); rc = UNQLITE_NOMEM; goto fail; } SyZero(pCol,sizeof(unqlite_col)); /* Fill in the structure */ SyBlobInit(&pCol->sWorker,&pVm->sAlloc); SyBlobInit(&pCol->sHeader,&pVm->sAlloc); pCol->pVm = pVm; pCol->pCursor = pCursor; /* Duplicate collection name */ zDup = SyMemBackendStrDup(&pVm->sAlloc,zName,nByte); if( zDup == 0 ){ unqliteGenOutofMem(pDb); rc = UNQLITE_NOMEM; goto fail; } pCol->nRecSize = 64; /* Must be a power of two */ pCol->apRecord = (unqlite_col_record **)SyMemBackendAlloc(&pVm->sAlloc,pCol->nRecSize * sizeof(unqlite_col_record *)); if( pCol->apRecord == 0 ){ unqliteGenOutofMem(pDb); rc = UNQLITE_NOMEM; goto fail; } /* Zero the table */ SyZero((void *)pCol->apRecord,pCol->nRecSize * sizeof(unqlite_col_record *)); SyStringInitFromBuf(&pCol->sName,zDup,nByte); jx9MemObjInit(pVm->pJx9Vm,&pCol->sSchema); if( iFlag & UNQLITE_VM_COLLECTION_CREATE ){ /* Create a new collection */ if( pMethods->xReplace == 0 ){ /* Read-only KV engine: Generate an error message and return */ unqliteGenErrorFormat(pDb, "Cannot create new collection '%z' due to a read-only Key/Value storage engine", &pCol->sName ); rc = UNQLITE_ABORT; /* Abort VM execution */ goto fail; } /* Write the collection header */ rc = CollectionSetHeader(pEngine,pCol,0,0,0); if( rc != UNQLITE_OK ){ rc = UNQLITE_ABORT; /* Abort VM execution */ goto fail; } }else{ /* Read the collection header */ rc = CollectionLoadHeader(pCol); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pDb,"Corrupt collection '%z' header",&pCol->sName); goto fail; } } /* Finally install the collection */ unqliteVmInstallCollection(pVm,pCol); /* All done */ if( ppOut ){ *ppOut = pCol; } return UNQLITE_OK; fail: unqliteReleaseCursor(pDb,pCursor); if( zDup ){ SyMemBackendFree(&pVm->sAlloc,zDup); } if( pCol ){ if( pCol->apRecord ){ SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord); } SyBlobRelease(&pCol->sHeader); SyBlobRelease(&pCol->sWorker); jx9MemObjRelease(&pCol->sSchema); SyMemBackendPoolFree(&pVm->sAlloc,pCol); } return rc; } /* * Fetch a collection. */ UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch( unqlite_vm *pVm, /* Target VM */ SyString *pName, /* Lookup key */ int iFlag /* Control flag */ ) { unqlite_col *pCol = 0; /* cc warning */ int rc; /* Check if the collection is already loaded in memory */ pCol = unqliteVmFetchCollection(pVm,pName); if( pCol ){ /* Already loaded in memory*/ return pCol; } if( (iFlag & UNQLITE_VM_AUTO_LOAD) == 0 ){ return 0; } /* Ask the storage engine for the collection */ rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,0,&pCol); /* Return to the caller */ return rc == UNQLITE_OK ? pCol : 0; } /* * Return the unique ID of the last inserted record. */ UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol) { return pCol->nLastid == 0 ? 0 : (pCol->nLastid - 1); } /* * Return the current record ID. */ UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol) { return pCol->nCurid; } /* * Return the total number of records in a given collection. */ UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol) { return pCol->nTotRec; } /* * Reset the record cursor. */ UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol) { pCol->nCurid = 0; } /* * Fetch a record by its unique ID. */ UNQLITE_PRIVATE int unqliteCollectionFetchRecordById( unqlite_col *pCol, /* Target collection */ jx9_int64 nId, /* Unique record ID */ jx9_value *pValue /* OUT: record value */ ) { SyBlob *pWorker = &pCol->sWorker; unqlite_col_record *pRec; int rc; jx9_value_null(pValue); /* Perform a cache lookup first */ pRec = CollectionCacheFetchRecord(pCol,nId); if( pRec ){ /* Copy record value */ jx9MemObjStore(&pRec->sValue,pValue); return UNQLITE_OK; } /* Reset the working buffer */ SyBlobReset(pWorker); /* Generate the unique ID */ SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId); /* Reset the cursor */ unqlite_kv_cursor_reset(pCol->pCursor); /* Seek the cursor to the desired location */ rc = unqlite_kv_cursor_seek(pCol->pCursor, SyBlobData(pWorker),SyBlobLength(pWorker), UNQLITE_CURSOR_MATCH_EXACT ); if( rc != UNQLITE_OK ){ return rc; } /* Consume the binary JSON */ SyBlobReset(pWorker); unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pWorker); if( SyBlobLength(pWorker) < 1 ){ unqliteGenErrorFormat(pCol->pVm->pDb, "Empty record '%qd'",nId ); jx9_value_null(pValue); }else{ /* Decode the binary JSON */ rc = FastJsonDecode(SyBlobData(pWorker),SyBlobLength(pWorker),pValue,0,0); if( rc == UNQLITE_OK ){ /* Install the record in the cache */ CollectionCacheInstallRecord(pCol,nId,pValue); } } return rc; } /* * Fetch the next record from a given collection. */ UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue) { int rc; for(;;){ if( pCol->nCurid >= pCol->nLastid ){ /* No more records, reset the record cursor ID */ pCol->nCurid = 0; /* Return to the caller */ return SXERR_EOF; } rc = unqliteCollectionFetchRecordById(pCol,pCol->nCurid,pValue); /* Increment the record ID */ pCol->nCurid++; /* Lookup result */ if( rc == UNQLITE_OK || rc != UNQLITE_NOTFOUND ){ break; } } return rc; } /* * Judge a collection whether exists */ UNQLITE_PRIVATE int unqliteExistsCollection( unqlite_vm *pVm, /* Target VM */ SyString *pName /* Collection name */ ) { unqlite_col *pCol; int rc; /* Perform a lookup first */ pCol = unqliteVmFetchCollection(pVm,pName); if( pCol ){ /* Already loaded in memory*/ return UNQLITE_OK; } rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_EXISTS,0); return rc; } /* * Create a new collection. */ UNQLITE_PRIVATE int unqliteCreateCollection( unqlite_vm *pVm, /* Target VM */ SyString *pName /* Collection name */ ) { unqlite_col *pCol; int rc; /* Perform a lookup first */ pCol = unqliteCollectionFetch(pVm,pName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ return UNQLITE_EXISTS; } /* Now, safely create the collection */ rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_CREATE,0); return rc; } /* * Set a schema (JSON object) for a given collection. */ UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue) { int rc; if( !jx9_value_is_json_object(pValue) ){ /* Must be a JSON object */ return SXERR_INVALID; } rc = CollectionSetHeader(0,pCol,-1,-1,pValue); return rc; } /* * Perform a store operation on a given collection. */ static int CollectionStore( unqlite_col *pCol, /* Target collection */ jx9_value *pValue /* JSON value to be stored */ ) { SyBlob *pWorker = &pCol->sWorker; unqlite_kv_methods *pMethods; unqlite_kv_engine *pEngine; sxu32 nKeyLen; int rc; /* Point to the underlying KV store */ pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); pMethods = pEngine->pIo->pMethods; if( pCol->nTotRec >= SXI64_HIGH ){ /* Collection limit reached. No more records */ unqliteGenErrorFormat(pCol->pVm->pDb, "Collection '%z': Records limit reached", &pCol->sName ); return UNQLITE_LIMIT; } if( pMethods->xReplace == 0 ){ unqliteGenErrorFormat(pCol->pVm->pDb, "Cannot store record into collection '%z' due to a read-only Key/Value storage engine", &pCol->sName ); return UNQLITE_READ_ONLY; } /* Reset the working buffer */ SyBlobReset(pWorker); if( jx9_value_is_json_object(pValue) ){ jx9_value sId; /* If the given type is a JSON object, then add the special __id field */ jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,pCol->nLastid); jx9_array_add_strkey_elem(pValue,"__id",&sId); jx9MemObjRelease(&sId); } /* Prepare the unique ID for this record */ SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,pCol->nLastid); nKeyLen = SyBlobLength(pWorker); if( nKeyLen < 1 ){ unqliteGenOutofMem(pCol->pVm->pDb); return UNQLITE_NOMEM; } /* Turn to FastJson */ rc = FastJsonEncode(pValue,pWorker,0); if( rc != UNQLITE_OK ){ return rc; } /* Finally perform the insertion */ rc = pMethods->xReplace( pEngine, SyBlobData(pWorker),nKeyLen, SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen ); if( rc == UNQLITE_OK ){ /* Save the value in the cache */ CollectionCacheInstallRecord(pCol,pCol->nLastid,pValue); /* Increment the unique __id */ pCol->nLastid++; pCol->nTotRec++; /* Reflect the change */ rc = CollectionSetHeader(0,pCol,pCol->nLastid,pCol->nTotRec,0); } if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pCol->pVm->pDb, "IO error while storing record into collection '%z'", &pCol->sName ); return rc; } return UNQLITE_OK; } /* * Perform a update operation on a given collection. */ static int CollectionUpdate( unqlite_col *pCol, /* Target collection */ jx9_int64 nId, /* Record ID */ jx9_value *pValue /* JSON value to be stored */ ) { SyBlob *pWorker = &pCol->sWorker; unqlite_kv_methods *pMethods; unqlite_kv_engine *pEngine; sxu32 nKeyLen; int rc; /* Point to the underlying KV store */ pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); pMethods = pEngine->pIo->pMethods; if( pCol->nTotRec >= SXI64_HIGH ){ /* Collection limit reached. No more records */ unqliteGenErrorFormat(pCol->pVm->pDb, "Collection '%z': Records limit reached", &pCol->sName ); return UNQLITE_LIMIT; } if( pMethods->xReplace == 0 ){ unqliteGenErrorFormat(pCol->pVm->pDb, "Cannot store record into collection '%z' due to a read-only Key/Value storage engine", &pCol->sName ); return UNQLITE_READ_ONLY; } /* Reset the working buffer */ SyBlobReset(pWorker); /* Prepare the unique ID for this record */ SyBlobFormat(pWorker,"%z_%qd",&pCol->sName, nId); /* Reset the cursor */ unqlite_kv_cursor_reset(pCol->pCursor); /* Seek the cursor to the desired location */ rc = unqlite_kv_cursor_seek(pCol->pCursor, SyBlobData(pWorker),SyBlobLength(pWorker), UNQLITE_CURSOR_MATCH_EXACT ); if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pCol->pVm->pDb, "No record to update in collection '%z'", &pCol->sName ); return rc; } if( jx9_value_is_json_object(pValue) ){ jx9_value sId; /* If the given type is a JSON object, then add the special __id field */ jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,nId); jx9_array_add_strkey_elem(pValue,"__id",&sId); jx9MemObjRelease(&sId); } nKeyLen = SyBlobLength(pWorker); if( nKeyLen < 1 ){ unqliteGenOutofMem(pCol->pVm->pDb); return UNQLITE_NOMEM; } /* Turn to FastJson */ rc = FastJsonEncode(pValue,pWorker,0); if( rc != UNQLITE_OK ){ return rc; } /* Finally perform the insertion */ rc = pMethods->xReplace( pEngine, SyBlobData(pWorker),nKeyLen, SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen ); if( rc == UNQLITE_OK ){ /* Save the value in the cache */ CollectionCacheInstallRecord(pCol,nId,pValue); } if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pCol->pVm->pDb, "IO error while storing record into collection '%z'", &pCol->sName ); return rc; } return UNQLITE_OK; } /* * Array walker callback (Refer to jx9_array_walk()). */ static int CollectionRecordArrayWalker(jx9_value *pKey,jx9_value *pData,void *pUserData) { unqlite_col *pCol = (unqlite_col *)pUserData; int rc; /* Perform the insertion */ rc = CollectionStore(pCol,pData); if( rc != UNQLITE_OK ){ SXUNUSED(pKey); /* cc warning */ } return rc; } /* * Perform a store operation on a given collection. */ UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag) { int rc; if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){ /* Iterate over the array and store its members in the collection */ rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol); SXUNUSED(iFlag); /* cc warning */ }else{ rc = CollectionStore(pCol,pValue); } return rc; } /* * Drop a record from a given collection. */ UNQLITE_PRIVATE int unqliteCollectionDropRecord( unqlite_col *pCol, /* Target collection */ jx9_int64 nId, /* Unique ID of the record to be droped */ int wr_header, /* True to alter collection header */ int log_err /* True to log error */ ) { SyBlob *pWorker = &pCol->sWorker; int rc; /* Reset the working buffer */ SyBlobReset(pWorker); /* Prepare the unique ID for this record */ SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId); /* Reset the cursor */ unqlite_kv_cursor_reset(pCol->pCursor); /* Seek the cursor to the desired location */ rc = unqlite_kv_cursor_seek(pCol->pCursor, SyBlobData(pWorker),SyBlobLength(pWorker), UNQLITE_CURSOR_MATCH_EXACT ); if( rc != UNQLITE_OK ){ return rc; } /* Remove the record from the storage engine */ rc = unqlite_kv_cursor_delete_entry(pCol->pCursor); /* Finally, Remove the record from the cache */ unqliteCollectionCacheRemoveRecord(pCol,nId); if( rc == UNQLITE_OK ){ pCol->nTotRec--; if( wr_header ){ /* Relect in the collection header */ rc = CollectionSetHeader(0,pCol,-1,pCol->nTotRec,0); } }else if( rc == UNQLITE_NOTIMPLEMENTED ){ if( log_err ){ unqliteGenErrorFormat(pCol->pVm->pDb, "Cannot delete record from collection '%z' due to a read-only Key/Value storage engine", &pCol->sName ); } } return rc; } /* * Update a given record with new data */ UNQLITE_PRIVATE int unqliteCollectionUpdateRecord(unqlite_col *pCol,jx9_int64 nId, jx9_value *pValue,int iFlag) { int rc; if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){ /* Iterate over the array and store its members in the collection */ rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol); SXUNUSED(iFlag); /* cc warning */ }else{ rc = CollectionUpdate(pCol,nId,pValue); } return rc; } /* * Drop a collection from the KV storage engine and the underlying * unqlite VM. */ UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol) { unqlite_vm *pVm = pCol->pVm; jx9_int64 nId; int rc; /* Reset the cursor */ unqlite_kv_cursor_reset(pCol->pCursor); /* Seek the cursor to the desired location */ rc = unqlite_kv_cursor_seek(pCol->pCursor, SyStringData(&pCol->sName),SyStringLength(&pCol->sName), UNQLITE_CURSOR_MATCH_EXACT ); if( rc == UNQLITE_OK ){ /* Remove the record from the storage engine */ rc = unqlite_kv_cursor_delete_entry(pCol->pCursor); } if( rc != UNQLITE_OK ){ unqliteGenErrorFormat(pCol->pVm->pDb, "Cannot remove collection '%z' due to a read-only Key/Value storage engine", &pCol->sName ); return rc; } /* Drop collection records */ for( nId = 0 ; nId < pCol->nLastid ; ++nId ){ unqliteCollectionDropRecord(pCol,nId,0,0); } /* Cleanup */ CollectionCacheRelease(pCol); SyBlobRelease(&pCol->sHeader); SyBlobRelease(&pCol->sWorker); SyMemBackendFree(&pVm->sAlloc,(void *)SyStringData(&pCol->sName)); unqliteReleaseCursor(pVm->pDb,pCol->pCursor); /* Unlink */ if( pCol->pPrevCol ){ pCol->pPrevCol->pNextCol = pCol->pNextCol; }else{ sxu32 iBucket = pCol->nHash & (pVm->iColSize - 1); pVm->apCol[iBucket] = pCol->pNextCol; } if( pCol->pNextCol ){ pCol->pNextCol->pPrevCol = pCol->pPrevCol; } MACRO_LD_REMOVE(pVm->pCol,pCol); pVm->iCol--; SyMemBackendPoolFree(&pVm->sAlloc,pCol); return UNQLITE_OK; } /* * ---------------------------------------------------------- * File: unqlite_jx9.c * MD5: 8fddc15b667e85d7b5df5367132518fb * ---------------------------------------------------------- */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ * Version 1.1.6 * 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://unqlite.org/licensing.html */ /* $SymiscID: unql_jx9.c v1.2 FreeBSD 2013-01-24 22:45 stable $ */ #ifndef UNQLITE_AMALGAMATION #include "unqliteInt.h" #endif /* * This file implements UnQLite functions (db_exists(), db_create(), db_put(), db_get(), etc.) for the * underlying Jx9 Virtual Machine. */ /* * string db_version(void) * Return the current version of the unQLite database engine. * Parameter * None * Return * unQLite version number (string). */ static int unqliteBuiltin_db_version(jx9_context *pCtx,int argc,jx9_value **argv) { SXUNUSED(argc); /* cc warning */ SXUNUSED(argv); jx9_result_string(pCtx,UNQLITE_VERSION,(int)sizeof(UNQLITE_VERSION)-1); return JX9_OK; } /* * string db_errlog(void) * Return the database error log. * Parameter * None * Return * Database error log (string). */ static int unqliteBuiltin_db_errlog(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_vm *pVm; SyBlob *pErr; SXUNUSED(argc); /* cc warning */ SXUNUSED(argv); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Point to the error log */ pErr = &pVm->pDb->sErr; /* Return the log */ jx9_result_string(pCtx,(const char *)SyBlobData(pErr),(int)SyBlobLength(pErr)); return JX9_OK; } /* * string db_copyright(void) * string db_credits(void) * Return the unQLite database engine copyright notice. * Parameter * None * Return * Copyright notice. */ static int unqliteBuiltin_db_credits(jx9_context *pCtx,int argc,jx9_value **argv) { SXUNUSED(argc); /* cc warning */ SXUNUSED(argv); jx9_result_string(pCtx,UNQLITE_COPYRIGHT,(int)sizeof(UNQLITE_COPYRIGHT)-1); return JX9_OK; } /* * string db_sig(void) * Return the unQLite database engine unique signature. * Parameter * None * Return * unQLite signature. */ static int unqliteBuiltin_db_sig(jx9_context *pCtx,int argc,jx9_value **argv) { SXUNUSED(argc); /* cc warning */ SXUNUSED(argv); jx9_result_string(pCtx,UNQLITE_IDENT,sizeof(UNQLITE_IDENT)-1); return JX9_OK; } /* * bool collection_exists(string $name) * bool db_exits(string $name) * Check if a given collection exists in the underlying database. * Parameter * name: Lookup name * Return * TRUE if the collection exits. FALSE otherwise. */ static int unqliteBuiltin_collection_exists(jx9_context *pCtx,int argc,jx9_value **argv) { const char *zName; unqlite_vm *pVm; SyString sName; int nByte; int rc; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Perform the lookup */ rc = unqliteExistsCollection(pVm, &sName); /* Lookup result */ jx9_result_bool(pCtx, rc == UNQLITE_OK ? 1 : 0); return JX9_OK; } /* * bool collection_create(string $name) * bool db_create(string $name) * Create a new collection. * Parameter * name: Collection name * Return * TRUE if the collection was successfuly created. FALSE otherwise. */ static int unqliteBuiltin_collection_create(jx9_context *pCtx,int argc,jx9_value **argv) { const char *zName; unqlite_vm *pVm; SyString sName; int nByte; int rc; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Try to create the collection */ rc = unqliteCreateCollection(pVm,&sName); /* Return the result to the caller */ jx9_result_bool(pCtx,rc == UNQLITE_OK ? 1 : 0); return JX9_OK; } /* * value db_fetch(string $col_name) * value db_get(string $col_name) * Fetch the current record from a given collection and advance * the record cursor. * Parameter * col_name: Collection name * Return * Record content success. NULL on failure (No more records to retrieve). */ static int unqliteBuiltin_db_fetch_next(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; int rc; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return null */ jx9_result_null(pCtx); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return null */ jx9_result_null(pCtx); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ /* Fetch the current record */ jx9_value *pValue; pValue = jx9_context_new_scalar(pCtx); if( pValue == 0 ){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); jx9_result_null(pCtx); return JX9_OK; }else{ rc = unqliteCollectionFetchNextRecord(pCol,pValue); if( rc == UNQLITE_OK ){ jx9_result_value(pCtx,pValue); /* pValue will be automatically released as soon we return from this function */ }else{ /* Return null */ jx9_result_null(pCtx); } } }else{ /* No such collection, return null */ jx9_result_null(pCtx); } return JX9_OK; } /* * value db_fetch_by_id(string $col_name,int64 $record_id) * value db_get_by_id(string $col_name,int64 $record_id) * Fetch a record using its unique ID from a given collection. * Parameter * col_name: Collection name * record_id: Record number (__id field of a JSON object) * Return * Record content success. NULL on failure (No such record). */ static int unqliteBuiltin_db_fetch_by_id(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; jx9_int64 nId; int nByte; int rc; /* Extract collection name */ if( argc < 2 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or record ID"); /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } /* Extract the record ID */ nId = jx9_value_to_int(argv[1]); SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ /* Fetch the desired record */ jx9_value *pValue; pValue = jx9_context_new_scalar(pCtx); if( pValue == 0 ){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); jx9_result_null(pCtx); return JX9_OK; }else{ rc = unqliteCollectionFetchRecordById(pCol,nId,pValue); if( rc == UNQLITE_OK ){ jx9_result_value(pCtx,pValue); /* pValue will be automatically released as soon we return from this function */ }else{ /* No such record, return null */ jx9_result_null(pCtx); } } }else{ /* No such collection, return null */ jx9_result_null(pCtx); } return JX9_OK; } /* * array db_fetch_all(string $col_name,[callback filter_callback]) * array db_get_all(string $col_name,[callback filter_callback]) * Retrieve all records of a given collection and apply the given * callback if available to filter records. * Parameter * col_name: Collection name * Return * Contents of the collection (JSON array) on success. NULL on failure. */ static int unqliteBuiltin_db_fetch_all(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; int rc; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return NULL */ jx9_result_null(pCtx); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ jx9_value *pValue,*pArray,*pCallback = 0; jx9_value sResult; /* Callback result */ /* Allocate an empty scalar value and an empty JSON array */ pArray = jx9_context_new_array(pCtx); pValue = jx9_context_new_scalar(pCtx); jx9MemObjInit(pCtx->pVm,&sResult); if( pValue == 0 || pArray == 0 ){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); jx9_result_null(pCtx); return JX9_OK; } if( argc > 1 && jx9_value_is_callable(argv[1]) ){ pCallback = argv[1]; } unqliteCollectionResetRecordCursor(pCol); /* Fetch collection records one after one */ while( UNQLITE_OK == unqliteCollectionFetchNextRecord(pCol,pValue) ){ if( pCallback ){ jx9_value *apArg[2]; /* Invoke the filter callback */ apArg[0] = pValue; rc = jx9VmCallUserFunction(pCtx->pVm,pCallback,1,apArg,&sResult); if( rc == JX9_OK ){ int iResult; /* Callback result */ /* Extract callback result */ iResult = jx9_value_to_bool(&sResult); if( !iResult ){ /* Discard the result */ unqliteCollectionCacheRemoveRecord(pCol,unqliteCollectionCurrentRecordId(pCol) - 1); continue; } } } /* Put the value in the JSON array */ jx9_array_add_elem(pArray,0,pValue); /* Release the value */ jx9_value_null(pValue); } jx9MemObjRelease(&sResult); /* Finally, return our array */ jx9_result_value(pCtx,pArray); /* pValue will be automatically released as soon we return from * this foreign function. */ }else{ /* No such collection, return null */ jx9_result_null(pCtx); } return JX9_OK; } /* * int64 db_last_record_id(string $col_name) * Return the ID of the last inserted record. * Parameter * col_name: Collection name * Return * Record ID (64-bit integer) on success. FALSE on failure. */ static int unqliteBuiltin_db_last_record_id(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ jx9_result_int64(pCtx,unqliteCollectionLastRecordId(pCol)); }else{ /* No such collection, return FALSE */ jx9_result_bool(pCtx,0); } return JX9_OK; } /* * inr64 db_current_record_id(string $col_name) * Return the current record ID. * Parameter * col_name: Collection name * Return * Current record ID (64-bit integer) on success. FALSE on failure. */ static int unqliteBuiltin_db_current_record_id(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ jx9_result_int64(pCtx,unqliteCollectionCurrentRecordId(pCol)); }else{ /* No such collection, return FALSE */ jx9_result_bool(pCtx,0); } return JX9_OK; } /* * bool db_reset_record_cursor(string $col_name) * Reset the record ID cursor. * Parameter * col_name: Collection name * Return * TRUE on success. FALSE on failure. */ static int unqliteBuiltin_db_reset_record_cursor(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ unqliteCollectionResetRecordCursor(pCol); jx9_result_bool(pCtx,1); }else{ /* No such collection */ jx9_result_bool(pCtx,0); } return JX9_OK; } /* * int64 db_total_records(string $col_name) * Return the total number of inserted records in the given collection. * Parameter * col_name: Collection name * Return * Total number of records on success. FALSE on failure. */ static int unqliteBuiltin_db_total_records(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ unqlite_int64 nRec; nRec = unqliteCollectionTotalRecords(pCol); jx9_result_int64(pCtx,nRec); }else{ /* No such collection */ jx9_result_bool(pCtx,0); } return JX9_OK; } /* * string db_creation_date(string $col_name) * Return the creation date of the given collection. * Parameter * col_name: Collection name * Return * Creation date on success. FALSE on failure. */ static int unqliteBuiltin_db_creation_date(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ Sytm *pTm = &pCol->sCreation; jx9_result_string_format(pCtx,"%d-%d-%d %02d:%02d:%02d", pTm->tm_year,pTm->tm_mon,pTm->tm_mday, pTm->tm_hour,pTm->tm_min,pTm->tm_sec ); }else{ /* No such collection */ jx9_result_bool(pCtx,0); } return JX9_OK; } /* * bool db_store(string $col_name,...) * bool db_put(string $col_name,...) * Store one or more JSON values in a given collection. * Parameter * col_name: Collection name * Return * TRUE on success. FALSE on failure. */ static int unqliteBuiltin_db_store(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; int rc; int i; /* Extract collection name */ if( argc < 2 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol == 0 ){ jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } /* Store the given values */ for( i = 1 ; i < argc ; ++i ){ rc = unqliteCollectionPut(pCol,argv[i],0); if( rc != UNQLITE_OK){ jx9_context_throw_error_format(pCtx,JX9_CTX_ERR, "Error while storing record %d in collection '%z'",i,&sName ); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } } /* All done, return TRUE */ jx9_result_bool(pCtx,1); return JX9_OK; } /* * bool db_update_record(string $col_name, int_64 record_id, object $json_object) * Update a given record with new json object * Parameter * col_name: Collection name * record_id: ID of the record * json_object: New Record data * Return * TRUE on success. FALSE on failure. */ static int unqliteBuiltin_db_update_record(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; jx9_int64 nId; int nByte; int rc; /* Extract collection name */ if( argc < 2 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol == 0 ){ jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } /* Update a record with the given value */ nId = jx9_value_to_int64(argv[1]); rc = unqliteCollectionUpdateRecord(pCol, nId, argv[2], 0); /* All done, return TRUE */ jx9_result_bool(pCtx,rc == UNQLITE_OK); return JX9_OK; } /* * bool db_drop_collection(string $col_name) * bool collection_delete(string $col_name) * Remove a given collection from the database. * Parameter * col_name: Collection name * Return * TRUE on success. FALSE on failure. */ static int unqliteBuiltin_db_drop_col(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; int rc; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol == 0 ){ jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } /* Drop the collection */ rc = unqliteDropCollection(pCol); /* Processing result */ jx9_result_bool(pCtx,rc == UNQLITE_OK); return JX9_OK; } /* * bool db_drop_record(string $col_name,int64 record_id) * Remove a given record from a collection. * Parameter * col_name: Collection name. * record_id: ID of the record. * Return * TRUE on success. FALSE on failure. */ static int unqliteBuiltin_db_drop_record(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; jx9_int64 nId; int nByte; int rc; /* Extract collection name */ if( argc < 2 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol == 0 ){ jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } /* Extract the record ID */ nId = jx9_value_to_int64(argv[1]); /* Drop the record */ rc = unqliteCollectionDropRecord(pCol,nId,1,1); /* Processing result */ jx9_result_bool(pCtx,rc == UNQLITE_OK); return JX9_OK; } /* * bool db_set_schema(string $col_name, object $json_object) * Set a schema for a given collection. * Parameter * col_name: Collection name. * json_object: Collection schema (Must be a JSON object). * Return * TRUE on success. FALSE on failure. */ static int unqliteBuiltin_db_set_schema(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; int rc; /* Extract collection name */ if( argc < 2 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } if( !jx9_value_is_json_object(argv[1]) ){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection scheme"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ rc = UNQLITE_NOOP; pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ /* Set the collection scheme */ rc = unqliteCollectionSetSchema(pCol,argv[1]); }else{ jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING, "No such collection '%z'", &sName ); } /* Processing result */ jx9_result_bool(pCtx,rc == UNQLITE_OK); return JX9_OK; } /* * object db_get_schema(string $col_name) * Return the schema associated with a given collection. * Parameter * col_name: Collection name * Return * Collection schema on success. null otherwise. */ static int unqliteBuiltin_db_get_schema(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_col *pCol; const char *zName; unqlite_vm *pVm; SyString sName; int nByte; /* Extract collection name */ if( argc < 1 ){ /* Missing arguments */ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } zName = jx9_value_to_string(argv[0],&nByte); if( nByte < 1){ jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); /* Return false */ jx9_result_bool(pCtx,0); return JX9_OK; } SyStringInitFromBuf(&sName,zName,nByte); pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Fetch the collection */ pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); if( pCol ){ /* Return the collection schema */ jx9_result_value(pCtx,&pCol->sSchema); }else{ jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING, "No such collection '%z'", &sName ); jx9_result_null(pCtx); } return JX9_OK; } /* * bool db_begin(void) * Manually begin a write transaction. * Parameter * None * Return * TRUE on success. FALSE otherwise. */ static int unqliteBuiltin_db_begin(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_vm *pVm; unqlite *pDb; int rc; SXUNUSED(argc); /* cc warning */ SXUNUSED(argv); /* Point to the unqlite Vm */ pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Point to the underlying database handle */ pDb = pVm->pDb; /* Begin the transaction */ rc = unqlitePagerBegin(pDb->sDB.pPager); /* result */ jx9_result_bool(pCtx,rc == UNQLITE_OK ); return JX9_OK; } /* * bool db_commit(void) * Manually commit a transaction. * Parameter * None * Return * TRUE if the transaction was successfuly commited. FALSE otherwise. */ static int unqliteBuiltin_db_commit(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_vm *pVm; unqlite *pDb; int rc; SXUNUSED(argc); /* cc warning */ SXUNUSED(argv); /* Point to the unqlite Vm */ pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Point to the underlying database handle */ pDb = pVm->pDb; /* Commit the transaction if any */ rc = unqlitePagerCommit(pDb->sDB.pPager); /* Commit result */ jx9_result_bool(pCtx,rc == UNQLITE_OK ); return JX9_OK; } /* * bool db_rollback(void) * Manually rollback a transaction. * Parameter * None * Return * TRUE if the transaction was successfuly rolled back. FALSE otherwise */ static int unqliteBuiltin_db_rollback(jx9_context *pCtx,int argc,jx9_value **argv) { unqlite_vm *pVm; unqlite *pDb; int rc; SXUNUSED(argc); /* cc warning */ SXUNUSED(argv); /* Point to the unqlite Vm */ pVm = (unqlite_vm *)jx9_context_user_data(pCtx); /* Point to the underlying database handle */ pDb = pVm->pDb; /* Rollback the transaction if any */ rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE); /* Rollback result */ jx9_result_bool(pCtx,rc == UNQLITE_OK ); return JX9_OK; } /* * Register all the UnQLite foreign functions defined above. */ UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm) { static const jx9_builtin_func aBuiltin[] = { { "db_version" , unqliteBuiltin_db_version }, { "db_copyright", unqliteBuiltin_db_credits }, { "db_credits" , unqliteBuiltin_db_credits }, { "db_sig" , unqliteBuiltin_db_sig }, { "db_errlog", unqliteBuiltin_db_errlog }, { "collection_exists", unqliteBuiltin_collection_exists }, { "db_exists", unqliteBuiltin_collection_exists }, { "collection_create", unqliteBuiltin_collection_create }, { "db_create", unqliteBuiltin_collection_create }, { "db_fetch", unqliteBuiltin_db_fetch_next }, { "db_get", unqliteBuiltin_db_fetch_next }, { "db_fetch_by_id", unqliteBuiltin_db_fetch_by_id }, { "db_get_by_id", unqliteBuiltin_db_fetch_by_id }, { "db_fetch_all", unqliteBuiltin_db_fetch_all }, { "db_get_all", unqliteBuiltin_db_fetch_all }, { "db_last_record_id", unqliteBuiltin_db_last_record_id }, { "db_current_record_id", unqliteBuiltin_db_current_record_id }, { "db_reset_record_cursor", unqliteBuiltin_db_reset_record_cursor }, { "db_total_records", unqliteBuiltin_db_total_records }, { "db_creation_date", unqliteBuiltin_db_creation_date }, { "db_store", unqliteBuiltin_db_store }, { "db_update_record", unqliteBuiltin_db_update_record }, { "db_put", unqliteBuiltin_db_store }, { "db_drop_collection", unqliteBuiltin_db_drop_col }, { "collection_delete", unqliteBuiltin_db_drop_col }, { "db_drop_record", unqliteBuiltin_db_drop_record }, { "db_set_schema", unqliteBuiltin_db_set_schema }, { "db_get_schema", unqliteBuiltin_db_get_schema }, { "db_begin", unqliteBuiltin_db_begin }, { "db_commit", unqliteBuiltin_db_commit }, { "db_rollback", unqliteBuiltin_db_rollback }, }; int rc = UNQLITE_OK; sxu32 n; /* Register the unQLite functions defined above in the Jx9 call table */ for( n = 0 ; n < SX_ARRAYSIZE(aBuiltin) ; ++n ){ rc = jx9_create_function(pVm->pJx9Vm,aBuiltin[n].zName,aBuiltin[n].xFunc,pVm); } return rc; } /* END-OF-IMPLEMENTATION: unqlite@embedded@symisc 34-09-46 */ /* * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. * Copyright (C) 2012-2019, Symisc Systems http://unqlite.org/ * Version 1.1.9 * 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://unqlite.org/licensing.html */ /* * Copyright (C) 2012, 2019 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */