unqlitego/unqlite.c
2014-02-05 18:01:50 +09:00

59959 lines
1.8 MiB
Raw Blame History

/*
* 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
*/
/*
* Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>].
* 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.6 Unix|Win32/64 2013-05-21 22:39:15 stable <chm@symisc.net> $
*/
/* This file is an amalgamation of many separate C source files from unqlite version 1.1.6
* 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: <unqlite.h>" 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: <unqlite.h>
*/
/*
* ----------------------------------------------------------
* File: unqlite.h
* MD5: df5da92eec0f513e6daaeee18dff981a
* ----------------------------------------------------------
*/
/* 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-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
*/
/*
* Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>].
* 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.1 UNIX|WIN32/64 2012-11-02 02:10 stable <chm@symisc.net> $ */
#include <stdarg.h> /* 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.6"
/*
* 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 1001006
/*
* 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.6"
/*
* 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:b172a1e2c3f62fb35c8e1fb2795121f82356cad6"
/*
* 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 <chm@symisc.net>] 2012-2013, http://unqlite.org/"
/* 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 <time.h> 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 importants 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);
#endif /* _UNQLITE_H_ */
/*
* ----------------------------------------------------------
* File: jx9.h
* MD5: 8a8548429796b64f4afad5fab7312e93
* ----------------------------------------------------------
*/
/* 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 <chm@symisc.net> $ */
#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/"
/* Make sure we can call this stuff from C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* 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);*/
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* _JX9H_ */
/*
* ----------------------------------------------------------
* File: jx9Int.h
* MD5: 539aa6619fb06376acda88ba66311be9
* ----------------------------------------------------------
*/
/*
* 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 <chm@symisc.net> $ */
#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 <ctype.h>
#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_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);
#if defined(JX9_ENABLE_THREADS)
JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods);
JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend);
#endif
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 <chm@symisc.net> $ */
#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_OVERWRITE 0x002 /* Overwrite 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 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: 5d020de5ba84f99af867d524f4f99769
* ----------------------------------------------------------
*/
/*
* 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 <chm@symisc.net> $ */
#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 */
}
/* 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;
/* Initialiaze the memory subsystem */
SyMemBackendInitFromParent(&pDB->sMem,pParent);
#if defined(UNQLITE_ENABLE_THREADS)
/* No need for internal mutexes */
SyMemBackendDisbaleMutexing(&pDB->sMem);
#endif
SyBlobInit(&pDB->sErr,&pDB->sMem);
/* Sanityze 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;
}
}
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 <chm@symisc.net> $ */
#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 <chm@symisc.net> $ */
#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 <chm@symisc.net> $ */
#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 */
}
/* 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 <chm@symisc.net> $ */
#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 <stdlib.h> /* abs */
#include <math.h>
/*
* 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<LARGEST_INT64-1 ){
r = (double)((jx9_int64)(r+0.5));
}else if( n==0 && r<0 && (-r)<LARGEST_INT64-1 ){
r = -(double)((jx9_int64)((-r)+0.5));
}else{
char zBuf[256];
sxu32 nLen;
nLen = SyBufferFormat(zBuf, sizeof(zBuf), "%.*f", n, r);
/* Convert the string to real number */
SyStrToReal(zBuf, nLen, (void *)&r, 0);
}
/* Return thr rounded value */
jx9_result_double(pCtx, r);
return JX9_OK;
}
/*
* string dechex(int $number)
* Decimal to hexadecimal.
* Parameters
* $number
* Decimal value to convert
* Return
* Hexadecimal string representation of number
*/
static int jx9Builtin_dechex(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
int iVal;
if( nArg < 1 ){
/* Missing arguments, return null */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Extract the given number */
iVal = jx9_value_to_int(apArg[0]);
/* Format */
jx9_result_string_format(pCtx, "%x", iVal);
return JX9_OK;
}
/*
* string decoct(int $number)
* Decimal to Octal.
* Parameters
* $number
* Decimal value to convert
* Return
* Octal string representation of number
*/
static int jx9Builtin_decoct(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
int iVal;
if( nArg < 1 ){
/* Missing arguments, return null */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Extract the given number */
iVal = jx9_value_to_int(apArg[0]);
/* Format */
jx9_result_string_format(pCtx, "%o", iVal);
return JX9_OK;
}
/*
* string decbin(int $number)
* Decimal to binary.
* Parameters
* $number
* Decimal value to convert
* Return
* Binary string representation of number
*/
static int jx9Builtin_decbin(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
int iVal;
if( nArg < 1 ){
/* Missing arguments, return null */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Extract the given number */
iVal = jx9_value_to_int(apArg[0]);
/* Format */
jx9_result_string_format(pCtx, "%B", iVal);
return JX9_OK;
}
/*
* int64 hexdec(string $hex_string)
* Hexadecimal to decimal.
* Parameters
* $hex_string
* The hexadecimal string to convert
* Return
* The decimal representation of hex_string
*/
static int jx9Builtin_hexdec(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
const char *zString, *zEnd;
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);
/* Delimit the string */
zEnd = &zString[nLen];
/* Ignore non hex-stream */
while( zString < zEnd ){
if( (unsigned char)zString[0] >= 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) ==> '&amp;'
* '"' (double quote) ==> '&quot;' when ENT_NOQUOTES is not set.
* "'" (single quote) ==> '&#039;' only when ENT_QUOTES is set.
* '<' (less than) ==> '&lt;'
* '>' (greater than) ==> '&gt;'
* 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 '&amp;' */
jx9_result_string(pCtx, "&amp;", (int)sizeof("&amp;")-1);
}else if( c == '<' ){
/* Expand '&lt;' */
jx9_result_string(pCtx, "&lt;", (int)sizeof("&lt;")-1);
}else if( c == '>' ){
/* Expand '&gt;' */
jx9_result_string(pCtx, "&gt;", (int)sizeof("&gt;")-1);
}else if( c == '\'' ){
if( iFlags & 0x02 /*ENT_QUOTES*/ ){
/* Expand '&#039;' */
jx9_result_string(pCtx, "&#039;", (int)sizeof("&#039;")-1);
}else{
/* Leave the single quote untouched */
jx9_result_string(pCtx, "'", (int)sizeof(char));
}
}else if( c == '"' ){
if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){
/* Expand '&quot;' */
jx9_result_string(pCtx, "&quot;", (int)sizeof("&quot;")-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("&amp;")-1 && SyStrnicmp(zIn, "&amp;", sizeof("&amp;")-1) == 0 ){
/* &amp; ==> '&' */
jx9_result_string(pCtx, "&", (int)sizeof(char));
nJump = (int)sizeof("&amp;")-1;
}else if( nLen >= (int)sizeof("&lt;")-1 && SyStrnicmp(zIn, "&lt;", sizeof("&lt;")-1) == 0 ){
/* &lt; ==> < */
jx9_result_string(pCtx, "<", (int)sizeof(char));
nJump = (int)sizeof("&lt;")-1;
}else if( nLen >= (int)sizeof("&gt;")-1 && SyStrnicmp(zIn, "&gt;", sizeof("&gt;")-1) == 0 ){
/* &gt; ==> '>' */
jx9_result_string(pCtx, ">", (int)sizeof(char));
nJump = (int)sizeof("&gt;")-1;
}else if( nLen >= (int)sizeof("&quot;")-1 && SyStrnicmp(zIn, "&quot;", sizeof("&quot;")-1) == 0 ){
/* &quot; ==> '"' */
if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){
jx9_result_string(pCtx, "\"", (int)sizeof(char));
}else{
/* Leave untouched */
jx9_result_string(pCtx, "&quot;", (int)sizeof("&quot;")-1);
}
nJump = (int)sizeof("&quot;")-1;
}else if( nLen >= (int)sizeof("&#039;")-1 && SyStrnicmp(zIn, "&#039;", sizeof("&#039;")-1) == 0 ){
/* &#039; ==> ''' */
if( iFlags & 0x02 /*ENT_QUOTES*/ ){
/* Expand ''' */
jx9_result_string(pCtx, "'", (int)sizeof(char));
}else{
/* Leave untouched */
jx9_result_string(pCtx, "&#039;", (int)sizeof("&#039;")-1);
}
nJump = (int)sizeof("&#039;")-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[] = {
"&lt;", "<", "&gt;", ">", "&amp;", "&", "&quot;", "\"", "&#39;", "'",
"&#33;", "!", "&#36;", "$", "&#35;", "#", "&#37;", "%", "&#40;", "(",
"&#41;", ")", "&#123;", "{", "&#125;", "}", "&#61;", "=", "&#43;", "+",
"&#63;", "?", "&#91;", "[", "&#93;", "]", "&#64;", "@", "&#44;", ","
};
/*
* 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: '<' ==> '&lt;"] */
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, "<br>", (int)sizeof("<br>")-1);
}else{
jx9_result_string(pCtx, "<br/>", (int)sizeof("<br/>")-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 && precision<length ){
length = precision;
}
if( flag_zeropad ){
/* zero-padding works on strings too */
for(idx = 0 ; idx < etSPACESIZE ; ++idx ){
spaces[idx] = '0';
}
}
break;
case JX9_FMT_RADIX:
pArg = NEXT_ARG;
if( pArg == 0 ){
iVal = 0;
}else{
iVal = jx9_value_to_int64(pArg);
}
/* Limit the precision to prevent overflowing buf[] during conversion */
if( precision>JX9_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 && precision<width-(prefix!=0) ){
precision = width-(prefix!=0);
}
zBuf = &zWorker[JX9_FMT_BUFSIZ-1];
{
register char *cset; /* Use registers for speed */
register int base;
cset = pInfo->charset;
base = pInfo->base;
do{ /* Convert to ascii */
*(--zBuf) = cset[iVal%base];
iVal = iVal/base;
}while( iVal>0 );
}
length = &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 = &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+precision<JX9_FMT_BUFSIZ-30 ){
flag_dp = (precision>0 || 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 <time.h>
#ifdef __WINNT__
/* GetSystemTime() */
#include <Windows.h>
#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 <sys/time.h>
#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 <chm@symisc.net> $ */
#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 <![CDATA[ ]]>
* construct, in that it declares a block of text which is not for parsing.
* A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier
* which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc
* identifiers also apply to nowdoc identifiers, especially those regarding the appearance
* of the closing identifier.
*/
static sxi32 jx9CompileNowdoc(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
SyString *pStr = &pGen->pIn->sData; /* Constant string literal */
jx9_value *pObj;
sxu32 nIdx;
nIdx = 0; /* Prevent compiler warning */
if( pStr->nByte <= 0 ){
/* Empty string, load NULL */
jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC, 0, 0, 0, 0);
return SXRET_OK;
}
/* Reserve a new constant */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "JX9 engine is running out of memory");
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
/* No processing is done here, simply a memcpy() operation */
jx9MemObjInitFromString(pGen->pVm, pObj, pStr);
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a single quoted string.
* According to the JX9 language reference manual:
*
* The simplest way to specify a string is to enclose it in single quotes (the character ' ).
* To specify a literal single quote, escape it with a backslash (\). To specify a literal
* backslash, double it (\\). All other instances of backslash will be treated as a literal
* backslash: this means that the other escape sequences you might be used to, such as \r
* or \n, will be output literally as specified rather than having any special meaning.
*
*/
JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag)
{
SyString *pStr = &pGen->pIn->sData; /* Constant string literal */
const char *zIn, *zCur, *zEnd;
jx9_value *pObj;
sxu32 nIdx;
nIdx = 0; /* Prevent compiler warning */
/* Delimit the string */
zIn = pStr->zString;
zEnd = &zIn[pStr->nByte];
if( zIn >= zEnd ){
/* Empty string, load NULL */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
return SXRET_OK;
}
if( SXRET_OK == GenStateFindLiteral(&(*pGen), pStr, &nIdx) ){
/* Already processed, emit the load constant instruction
* and return.
*/
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
return SXRET_OK;
}
/* Reserve a new constant */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
jx9GenCompileError(&(*pGen), E_ERROR, 1, "JX9 engine is running out of memory");
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
jx9MemObjInitFromString(pGen->pVm, pObj, 0);
/* Compile the node */
for(;;){
if( zIn >= zEnd ){
/* End of input */
break;
}
zCur = zIn;
while( zIn < zEnd && zIn[0] != '\\' ){
zIn++;
}
if( zIn > zCur ){
/* Append raw contents*/
jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur));
}
zIn++;
if( zIn < zEnd ){
if( zIn[0] == '\\' ){
/* A literal backslash */
jx9MemObjStringAppend(pObj, "\\", sizeof(char));
}else if( zIn[0] == '\'' ){
/* A single quote */
jx9MemObjStringAppend(pObj, "'", sizeof(char));
}else{
/* verbatim copy */
zIn--;
jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2);
zIn++;
}
}
/* Advance the stream cursor */
zIn++;
}
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
if( pStr->nByte < 1024 ){
/* Install in the literal table */
GenStateInstallLiteral(pGen, pObj, nIdx);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string.
* According to the JX9 language reference manual
* When a string is specified in double quotes or with heredoc, variables are parsed within it.
* There are two types of syntax: a simple one and a complex one. The simple syntax is the most
* common and convenient. It provides a way to embed a variable, an array value, or an object
* property in a string with a minimum of effort.
* Simple syntax
* If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible
* to form a valid variable name. Enclose the variable name in curly braces to explicitly specify
* the end of the name.
* Similarly, an array index or an object property can be parsed. With array indices, the closing
* square bracket (]) marks the end of the index. The same rules apply to object properties
* as to simple variables.
* Complex (curly) syntax
* This isn't called complex because the syntax is complex, but because it allows for the use
* of complex expressions.
* Any scalar variable, array element or object property with a string representation can be
* included via this syntax. Simply write the expression the same way as it would appear outside
* the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only
* be recognised when the $ immediately follows the {. Use {\$ to get a literal {$
*/
static sxi32 GenStateProcessStringExpression(
jx9_gen_state *pGen, /* Code generator state */
const char *zIn, /* Raw expression */
const char *zEnd /* End of the expression */
)
{
SyToken *pTmpIn, *pTmpEnd;
SySet sToken;
sxi32 rc;
/* Initialize the token set */
SySetInit(&sToken, &pGen->pVm->sAllocator, sizeof(SyToken));
/* Preallocate some slots */
SySetAlloc(&sToken, 0x08);
/* Tokenize the text */
jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken);
/* Swap delimiter */
pTmpIn = pGen->pIn;
pTmpEnd = pGen->pEnd;
pGen->pIn = (SyToken *)SySetBasePtr(&sToken);
pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)];
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Restore token stream */
pGen->pIn = pTmpIn;
pGen->pEnd = pTmpEnd;
/* Release the token set */
SySetRelease(&sToken);
/* Compilation result */
return rc;
}
/*
* Reserve a new constant for a double quoted/heredoc string.
*/
static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount)
{
jx9_value *pConstObj;
sxu32 nIdx = 0;
/* Reserve a new constant */
pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pConstObj == 0 ){
GenStateOutOfMem(&(*pGen));
return 0;
}
(*pCount)++;
jx9MemObjInitFromString(pGen->pVm, pConstObj, 0);
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
return pConstObj;
}
/*
* Compile a double quoted/heredoc string.
* According to the JX9 language reference manual
* Heredoc
* A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier
* is provided, then a newline. The string itself follows, and then the same identifier again
* to close the quotation.
* The closing identifier must begin in the first column of the line. Also, the identifier must
* follow the same naming rules as any other label in JX9: it must contain only alphanumeric
* characters and underscores, and must start with a non-digit character or underscore.
* Warning
* It is very important to note that the line with the closing identifier must contain
* no other characters, except possibly a semicolon (;). That means especially that the identifier
* may not be indented, and there may not be any spaces or tabs before or after the semicolon.
* It's also important to realize that the first character before the closing identifier must
* be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X.
* The closing delimiter (possibly followed by a semicolon) must also be followed by a newline.
* If this rule is broken and the closing identifier is not "clean", it will not be considered a closing
* identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before
* the end of the current file, a parse error will result at the last line.
* Heredocs can not be used for initializing object properties.
* Double quoted
* If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters:
* Escaped characters Sequence Meaning
* \n linefeed (LF or 0x0A (10) in ASCII)
* \r carriage return (CR or 0x0D (13) in ASCII)
* \t horizontal tab (HT or 0x09 (9) in ASCII)
* \v vertical tab (VT or 0x0B (11) in ASCII)
* \f form feed (FF or 0x0C (12) in ASCII)
* \\ backslash
* \$ dollar sign
* \" double-quote
* \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation
* \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation
* As in single quoted strings, escaping any other character will result in the backslash being printed too.
* The most important feature of double-quoted strings is the fact that variable names will be expanded.
* See string parsing for details.
*/
static sxi32 GenStateCompileString(jx9_gen_state *pGen)
{
SyString *pStr = &pGen->pIn->sData; /* Raw token value */
const char *zIn, *zCur, *zEnd;
jx9_value *pObj = 0;
sxi32 iCons;
sxi32 rc;
/* Delimit the string */
zIn = pStr->zString;
zEnd = &zIn[pStr->nByte];
if( zIn >= zEnd ){
/* Empty string, load NULL */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
return SXRET_OK;
}
zCur = 0;
/* Compile the node */
iCons = 0;
for(;;){
zCur = zIn;
while( zIn < zEnd && zIn[0] != '\\' ){
if(zIn[0] == '$' && &zIn[1] < zEnd &&
(((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){
break;
}
zIn++;
}
if( zIn > zCur ){
if( pObj == 0 ){
pObj = GenStateNewStrObj(&(*pGen), &iCons);
if( pObj == 0 ){
return SXERR_ABORT;
}
}
jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur));
}
if( zIn >= zEnd ){
break;
}
if( zIn[0] == '\\' ){
const char *zPtr = 0;
sxu32 n;
zIn++;
if( zIn >= zEnd ){
break;
}
if( pObj == 0 ){
pObj = GenStateNewStrObj(&(*pGen), &iCons);
if( pObj == 0 ){
return SXERR_ABORT;
}
}
n = sizeof(char); /* size of conversion */
switch( zIn[0] ){
case '$':
/* Dollar sign */
jx9MemObjStringAppend(pObj, "$", sizeof(char));
break;
case '\\':
/* A literal backslash */
jx9MemObjStringAppend(pObj, "\\", sizeof(char));
break;
case 'a':
/* The "alert" character (BEL)[ctrl+g] ASCII code 7 */
jx9MemObjStringAppend(pObj, "\a", sizeof(char));
break;
case 'b':
/* Backspace (BS)[ctrl+h] ASCII code 8 */
jx9MemObjStringAppend(pObj, "\b", sizeof(char));
break;
case 'f':
/* Form-feed (FF)[ctrl+l] ASCII code 12 */
jx9MemObjStringAppend(pObj, "\f", sizeof(char));
break;
case 'n':
/* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */
jx9MemObjStringAppend(pObj, "\n", sizeof(char));
break;
case 'r':
/* Carriage return (CR)[ctrl+m] ASCII code 13 */
jx9MemObjStringAppend(pObj, "\r", sizeof(char));
break;
case 't':
/* Horizontal tab (HT)[ctrl+i] ASCII code 9 */
jx9MemObjStringAppend(pObj, "\t", sizeof(char));
break;
case 'v':
/* Vertical tab(VT)[ctrl+k] ASCII code 11 */
jx9MemObjStringAppend(pObj, "\v", sizeof(char));
break;
case '\'':
/* Single quote */
jx9MemObjStringAppend(pObj, "'", sizeof(char));
break;
case '"':
/* Double quote */
jx9MemObjStringAppend(pObj, "\"", sizeof(char));
break;
case '0':
/* NUL byte */
jx9MemObjStringAppend(pObj, "\0", sizeof(char));
break;
case 'x':
if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){
int c;
/* Hex digit */
c = SyHexToint(zIn[1]) << 4;
if( &zIn[2] < zEnd ){
c += SyHexToint(zIn[2]);
}
/* Output char */
jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char));
n += sizeof(char) * 2;
}else{
/* Output literal character */
jx9MemObjStringAppend(pObj, "x", sizeof(char));
}
break;
case 'o':
if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){
/* Octal digit stream */
int c;
c = 0;
zIn++;
for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){
if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){
break;
}
c = c * 8 + (zPtr[0] - '0');
}
if ( c > 0 ){
jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char));
}
n = (sxu32)(zPtr-zIn);
}else{
/* Output literal character */
jx9MemObjStringAppend(pObj, "o", sizeof(char));
}
break;
default:
/* Output without a slash */
jx9MemObjStringAppend(pObj, zIn, sizeof(char));
break;
}
/* Advance the stream cursor */
zIn += n;
continue;
}
if( zIn[0] == '{' ){
/* Curly syntax */
const char *zExpr;
sxi32 iNest = 1;
zIn++;
zExpr = zIn;
/* Synchronize with the next closing curly braces */
while( zIn < zEnd ){
if( zIn[0] == '{' ){
/* Increment nesting level */
iNest++;
}else if(zIn[0] == '}' ){
/* Decrement nesting level */
iNest--;
if( iNest <= 0 ){
break;
}
}
zIn++;
}
/* Process the expression */
rc = GenStateProcessStringExpression(&(*pGen),zExpr,zIn);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
++iCons;
}
if( zIn < zEnd ){
/* Jump the trailing curly */
zIn++;
}
}else{
/* Simple syntax */
const char *zExpr = zIn;
/* Assemble variable name */
for(;;){
/* Jump leading dollars */
while( zIn < zEnd && zIn[0] == '$' ){
zIn++;
}
for(;;){
while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){
zIn++;
}
if((unsigned char)zIn[0] >= 0xc0 ){
/* UTF-8 stream */
zIn++;
while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){
zIn++;
}
continue;
}
break;
}
if( zIn >= zEnd ){
break;
}
if( zIn[0] == '[' ){
sxi32 iSquare = 1;
zIn++;
while( zIn < zEnd ){
if( zIn[0] == '[' ){
iSquare++;
}else if (zIn[0] == ']' ){
iSquare--;
if( iSquare <= 0 ){
break;
}
}
zIn++;
}
if( zIn < zEnd ){
zIn++;
}
break;
}else if( zIn[0] == '.' ){
/* Member access operator '.' */
zIn++;
}else{
break;
}
}
/* Process the expression */
rc = GenStateProcessStringExpression(&(*pGen),zExpr, zIn);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
++iCons;
}
}
/* Invalidate the previously used constant */
pObj = 0;
}/*for(;;)*/
if( iCons > 1 ){
/* Concatenate all compiled constants */
jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a double quoted string.
* See the block-comment above for more information.
*/
JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag)
{
sxi32 rc;
rc = GenStateCompileString(&(*pGen));
SXUNUSED(iCompileFlag); /* cc warning */
/* Compilation result */
return rc;
}
/*
* Compile a literal which is an identifier(name) for simple values.
*/
JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
SyToken *pToken = pGen->pIn;
jx9_value *pObj;
SyString *pStr;
sxu32 nIdx;
/* Extract token value */
pStr = &pToken->sData;
/* Deal with the reserved literals [i.e: null, false, true, ...] first */
if( pStr->nByte == sizeof("NULL") - 1 ){
if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){
/* NULL constant are always indexed at 0 */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
return SXRET_OK;
}else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){
/* TRUE constant are always indexed at 1 */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1, 0, 0);
return SXRET_OK;
}
}else if (pStr->nByte == sizeof("FALSE") - 1 &&
SyStrnicmp(pStr->zString, "false", sizeof("FALSE")-1) == 0 ){
/* FALSE constant are always indexed at 2 */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 2, 0, 0);
return SXRET_OK;
}else if(pStr->nByte == sizeof("__LINE__") - 1 &&
SyMemcmp(pStr->zString, "__LINE__", sizeof("__LINE__")-1) == 0 ){
/* TICKET 1433-004: __LINE__ constant must be resolved at compile time, not run time */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
SXUNUSED(iCompileFlag); /* cc warning */
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine);
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
return SXRET_OK;
}else if( pStr->nByte == sizeof("__FUNCTION__") - 1 &&
SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-1) == 0 ){
GenBlock *pBlock = pGen->pCurrent;
/* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time, not run time */
while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){
/* Point to the upper block */
pBlock = pBlock->pParent;
}
if( pBlock == 0 ){
/* Called in the global scope, load NULL */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
}else{
/* Extract the target function/method */
jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData;
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName);
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
}
return SXRET_OK;
}
/* Query literal table */
if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){
jx9_value *pObj;
/* Unknown literal, install it in the literal table */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData);
GenStateInstallLiteral(&(*pGen), pObj, nIdx);
}
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile an array entry whether it is a key or a value.
*/
static sxi32 GenStateCompileJSONEntry(
jx9_gen_state *pGen, /* Code generator state */
SyToken *pIn, /* Token stream */
SyToken *pEnd, /* End of the token stream */
sxi32 iFlags, /* Compilation flags */
sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */
)
{
SyToken *pTmpIn, *pTmpEnd;
sxi32 rc;
/* Swap token stream */
SWAP_DELIMITER(pGen, pIn, pEnd);
/* Compile the expression*/
rc = jx9CompileExpr(&(*pGen), iFlags, xValidator);
/* Restore token stream */
RE_SWAP_DELIMITER(pGen);
return rc;
}
/*
* Compile a Jx9 JSON Array.
*/
JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag)
{
sxi32 nPair = 0;
SyToken *pCur;
sxi32 rc;
pGen->pIn++; /* Jump the open square bracket '['*/
pGen->pEnd--;
SXUNUSED(iCompileFlag); /* cc warning */
for(;;){
/* Jump leading commas */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){
pGen->pIn++;
}
pCur = pGen->pIn;
if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){
/* No more entry to process */
break;
}
/* Compile entry */
rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
nPair++;
}
/* Emit the load map instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Node validator for a given JSON key.
*/
static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot)
{
sxi32 rc = SXRET_OK;
if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString
&& pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){
/* Unexpected expression */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0,
"JSON Object: Unexpected expression, key must be of type string, literal or simple variable");
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
return rc;
}
/*
* Compile a Jx9 JSON Object
*/
JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag)
{
SyToken *pKey, *pCur;
sxi32 nPair = 0;
sxi32 rc;
pGen->pIn++; /* Jump the open querly braces '{'*/
pGen->pEnd--;
SXUNUSED(iCompileFlag); /* cc warning */
for(;;){
/* Jump leading commas */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){
pGen->pIn++;
}
pCur = pGen->pIn;
if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){
/* No more entry to process */
break;
}
/* Compile the key */
pKey = pCur;
while( pCur < pGen->pIn ){
if( pCur->nType & JX9_TK_COLON /*':'*/ ){
break;
}
pCur++;
}
rc = SXERR_EMPTY;
if( pCur < pGen->pIn ){
if( &pCur[1] >= pGen->pIn ){
/* Missing value */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Compile the expression holding the key */
rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur,
EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */,
GenStateJSONObjectKeyNodeValidator /* Node validator callback */
);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pCur++; /* Jump the double colon ':' */
}else if( pKey == pCur ){
/* Key is omitted, emit an error */
jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key");
pCur++; /* Jump the double colon ':' */
}else{
/* Reset back the cursor and point to the entry value */
pCur = pKey;
}
/* Compile indice value */
rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
nPair++;
}
/* Emit the load map instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a function [i.e: print, exit(), include(), ...] which is a langauge
* construct.
*/
JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
SyString *pName;
sxu32 nKeyID;
sxi32 rc;
/* Name of the language construct [i.e: print, die...]*/
pName = &pGen->pIn->sData;
nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
pGen->pIn++; /* Jump the language construct keyword */
if( nKeyID == JX9_TKWRD_PRINT ){
SyToken *pTmp, *pNext = 0;
/* Compile arguments one after one */
pTmp = pGen->pEnd;
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0);
while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){
if( pGen->pIn < pNext ){
pGen->pEnd = pNext;
rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
/* Ticket 1433-008: Optimization #1: Consume input directly
* without the overhead of a function call.
* This is a very powerful optimization that improve
* performance greatly.
*/
jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0);
}
}
/* Jump trailing commas */
while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){
pNext++;
}
pGen->pIn = pNext;
}
/* Restore token stream */
pGen->pEnd = pTmp;
}else{
sxi32 nArg = 0;
sxu32 nIdx = 0;
rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nArg = 1;
}
if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){
jx9_value *pObj;
/* Emit the call instruction */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
SXUNUSED(iCompileFlag); /* cc warning */
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromString(pGen->pVm, pObj, pName);
/* Install in the literal table */
GenStateInstallLiteral(&(*pGen), pObj, nIdx);
}
/* Emit the call instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a node holding a variable declaration.
* According to the J9X language reference
* Variables in JX9 are represented by a dollar sign followed by the name of the variable.
* The variable name is case-sensitive.
* Variable names follow the same rules as other labels in JX9. A valid variable name
* starts with a letter, underscore or any UTF-8 stream, followed by any number of letters
* numbers, or underscores.
* By default, variables are always assigned by value unless the target value is a JSON
* array or a JSON object which is passed by reference.
*/
JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
sxu32 nLine = pGen->pIn->nLine;
SyHashEntry *pEntry;
SyString *pName;
char *zName = 0;
sxi32 iP1;
void *p3;
sxi32 rc;
pGen->pIn++; /* Jump the dollar sign '$' */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
/* Invalid variable name */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Extract variable name */
pName = &pGen->pIn->sData;
/* Advance the stream cursor */
pGen->pIn++;
pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte);
if( pEntry == 0 ){
/* Duplicate name */
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
if( zName == 0 ){
return GenStateOutOfMem(pGen);
}
/* Install in the hashtable */
SyHashInsert(&pGen->hVar, zName, pName->nByte, zName);
}else{
/* Name already available */
zName = (char *)pEntry->pUserData;
}
p3 = (void *)zName;
iP1 = 0;
if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){
if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){
/* Read-only load.In other words do not create the variable if inexistant */
iP1 = 1;
}
}
/* Emit the load instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/* Forward declaration */
static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc);
/*
* Compile an annoynmous function or a closure.
* According to the JX9 language reference
* Anonymous functions, also known as closures, allow the creation of functions
* which have no specified name. They are most useful as the value of callback
* parameters, but they have many other uses. Closures can also be used as
* the values of variables; Assigning a closure to a variable uses the same
* syntax as any other assignment, including the trailing semicolon:
* Example Anonymous function variable assignment example
* $greet = function($name)
* {
* printf("Hello %s\r\n", $name);
* };
* $greet('World');
* $greet('JX9');
* Note that the implementation of annoynmous function and closure under
* JX9 is completely different from the one used by the engine.
*/
JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
jx9_vm_func *pAnnonFunc; /* Annonymous function body */
char zName[512]; /* Unique lambda name */
static int iCnt = 1; /* There is no worry about thread-safety here, because only
* one thread is allowed to compile the script.
*/
jx9_value *pObj;
SyString sName;
sxu32 nIdx;
sxu32 nLen;
sxi32 rc;
pGen->pIn++; /* Jump the 'function' keyword */
if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){
pGen->pIn++;
}
/* Reserve a constant for the lambda */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
GenStateOutOfMem(pGen);
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
/* Generate a unique name */
nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++);
/* Make sure the generated name is unique */
while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){
nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++);
}
SyStringInitFromBuf(&sName, zName, nLen);
jx9MemObjInitFromString(pGen->pVm, pObj, &sName);
/* Compile the lambda body */
rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile the 'continue' statement.
* According to the JX9 language reference
* continue is used within looping structures to skip the rest of the current loop iteration
* and continue execution at the condition evaluation and then the beginning of the next
* iteration.
* Note: Note that in JX9 the switch statement is considered a looping structure for
* the purposes of continue.
* continue accepts an optional numeric argument which tells it how many levels
* of enclosing loops it should skip to the end of.
* Note:
* continue 0; and continue 1; is the same as running continue;.
*/
static sxi32 jx9CompileContinue(jx9_gen_state *pGen)
{
GenBlock *pLoop; /* Target loop */
sxi32 iLevel; /* How many nesting loop to skip */
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
iLevel = 0;
/* Jump the 'continue' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){
/* optional numeric argument which tells us how many levels
* of enclosing loops we should skip to the end of.
*/
iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData);
if( iLevel < 2 ){
iLevel = 0;
}
pGen->pIn++; /* Jump the optional numeric argument */
}
/* Point to the target loop */
pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel);
if( pLoop == 0 ){
/* Illegal continue */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}else{
sxu32 nInstrIdx = 0;
/* Emit the unconditional jump to the beginning of the target loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx);
if( pLoop->bPostContinue == TRUE ){
JumpFixup sJumpFix;
/* Post-continue */
sJumpFix.nJumpType = JX9_OP_JMP;
sJumpFix.nInstrIdx = nInstrIdx;
SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix);
}
}
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Not so fatal, emit a warning only */
jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement");
}
/* Statement successfully compiled */
return SXRET_OK;
}
/*
* Compile the 'break' statement.
* According to the JX9 language reference
* break ends execution of the current for, foreach, while, do-while or switch
* structure.
* break accepts an optional numeric argument which tells it how many nested
* enclosing structures are to be broken out of.
*/
static sxi32 jx9CompileBreak(jx9_gen_state *pGen)
{
GenBlock *pLoop; /* Target loop */
sxi32 iLevel; /* How many nesting loop to skip */
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
iLevel = 0;
/* Jump the 'break' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){
/* optional numeric argument which tells us how many levels
* of enclosing loops we should skip to the end of.
*/
iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData);
if( iLevel < 2 ){
iLevel = 0;
}
pGen->pIn++; /* Jump the optional numeric argument */
}
/* Extract the target loop */
pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel);
if( pLoop == 0 ){
/* Illegal break */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}else{
sxu32 nInstrIdx;
rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx);
if( rc == SXRET_OK ){
/* Fix the jump later when the jump destination is resolved */
GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx);
}
}
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Not so fatal, emit a warning only */
jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement");
}
/* Statement successfully compiled */
return SXRET_OK;
}
/* Forward declaration */
static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags);
/*
* Compile a JX9 block.
* A block is simply one or more JX9 statements and expressions to compile
* optionally delimited by braces {}.
* Return SXRET_OK on success. Any other return value indicates failure
* and this function takes care of generating the appropriate error
* message.
*/
static sxi32 jx9CompileBlock(
jx9_gen_state *pGen /* Code generator state */
)
{
sxi32 rc;
if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){
sxu32 nLine = pGen->pIn->nLine;
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(pGen->pVm), 0, 0);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
pGen->pIn++;
/* Compile until we hit the closing braces '}' */
for(;;){
if( pGen->pIn >= pGen->pEnd ){
/* No more token to process. Missing closing braces */
jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'");
break;
}
if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){
/* Closing braces found, break immediately*/
pGen->pIn++;
break;
}
/* Compile a single statement */
rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
GenStateLeaveBlock(&(*pGen), 0);
}else{
/* Compile a single statement */
rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
/* Jump trailing semi-colons ';' */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the gentle 'while' statement.
* According to the JX9 language reference
* while loops are the simplest type of loop in JX9.They behave just like their C counterparts.
* The basic form of a while statement is:
* while (expr)
* statement
* The meaning of a while statement is simple. It tells JX9 to execute the nested statement(s)
* repeatedly, as long as the while expression evaluates to TRUE. The value of the expression
* is checked each time at the beginning of the loop, so even if this value changes during
* the execution of the nested statement(s), execution will not stop until the end of the iteration
* (each time JX9 runs the statements in the loop is one iteration). Sometimes, if the while
* expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once.
* Like with the if statement, you can group multiple statements within the same while loop by surrounding
* a group of statements with curly braces, or by using the alternate syntax:
* while (expr):
* statement
* endwhile;
*/
static sxi32 jx9CompileWhile(jx9_gen_state *pGen)
{
GenBlock *pWhileBlock = 0;
SyToken *pTmp, *pEnd = 0;
sxu32 nFalseJump;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'while' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Delimit the condition */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Update token stream */
while(pGen->pIn < pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
/* Synchronize pointers */
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
/* Emit the false jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump);
/* Compile the loop body */
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Emit the unconditional jump to the start of the loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';' so we can avoid
* compiling this erroneous block.
*/
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the complex and powerful 'for' statement.
* According to the JX9 language reference
* for loops are the most complex loops in JX9. They behave like their C counterparts.
* The syntax of a for loop is:
* for (expr1; expr2; expr3)
* statement
* The first expression (expr1) is evaluated (executed) once unconditionally at
* the beginning of the loop.
* In the beginning of each iteration, expr2 is evaluated. If it evaluates to
* TRUE, the loop continues and the nested statement(s) are executed. If it evaluates
* to FALSE, the execution of the loop ends.
* At the end of each iteration, expr3 is evaluated (executed).
* Each of the expressions can be empty or contain multiple expressions separated by commas.
* In expr2, all expressions separated by a comma are evaluated but the result is taken
* from the last part. expr2 being empty means the loop should be run indefinitely
* (JX9 implicitly considers it as TRUE, like C). This may not be as useless as you might
* think, since often you'd want to end the loop using a conditional break statement instead
* of using the for truth expression.
*/
static sxi32 jx9CompileFor(jx9_gen_state *pGen)
{
SyToken *pTmp, *pPostStart, *pEnd = 0;
GenBlock *pForBlock = 0;
sxu32 nFalseJump;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'for' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Delimit the init-expr;condition;post-expr */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
/* Synchronize */
pGen->pIn = pEnd;
if( pGen->pIn < pGen->pEnd ){
pGen->pIn++;
}
return SXRET_OK;
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile initialization expressions if available */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Pop operand lvalues */
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
}
if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
"for: Expected ';' after initialization expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the trailing ';' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Deffer continue jumps */
pForBlock->bPostContinue = TRUE;
/* Compile the condition */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
/* Emit the false jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump);
}
if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
"for: Expected ';' after conditionals expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the trailing ';' */
pGen->pIn++;
/* Save the post condition stream */
pPostStart = pGen->pIn;
/* Compile the loop body */
pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */
pGen->pEnd = pTmp;
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Fix post-continue jumps */
if( SySetUsed(&pForBlock->aPostContFix) > 0 ){
JumpFixup *aPost;
VmInstr *pInstr;
sxu32 nJumpDest;
sxu32 n;
aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix);
nJumpDest = jx9VmInstrLength(pGen->pVm);
for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){
pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx);
if( pInstr ){
/* Fix jump */
pInstr->iP2 = nJumpDest;
}
}
}
/* compile the post-expressions if available */
while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){
pPostStart++;
}
if( pPostStart < pEnd ){
SyToken *pTmpIn, *pTmpEnd;
SWAP_DELIMITER(pGen, pPostStart, pEnd);
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( pGen->pIn < pGen->pEnd ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
RE_SWAP_DELIMITER(pGen);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY){
/* Pop operand lvalue */
jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
}
}
/* Emit the unconditional jump to the start of the loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
}
/* Expression tree validator callback used by the 'foreach' statement.
* Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...]
* are allowed.
*/
static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot)
{
sxi32 rc = SXRET_OK; /* Assume a valid expression tree */
if( pRoot->xCode != jx9CompileVariable ){
/* Unexpected expression */
rc = jx9GenCompileError(&(*pGen),
E_ERROR,
pRoot->pStart? pRoot->pStart->nLine : 0,
"foreach: Expecting a variable name"
);
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
return rc;
}
/*
* Compile the 'foreach' statement.
* According to the JX9 language reference
* The foreach construct simply gives an easy way to iterate over arrays. foreach works
* only on arrays (and objects), and will issue an error when you try to use it on a variable
* with a different data type or an uninitialized variable. There are two syntaxes; the second
* is a minor but useful extension of the first:
* foreach (json_array_json_object as $value)
* statement
* foreach (json_array_json_objec as $key,$value)
* statement
* The first form loops over the array given by array_expression. On each loop, the value
* of the current element is assigned to $value and the internal array pointer is advanced
* by one (so on the next loop, you'll be looking at the next element).
* The second form does the same thing, except that the current element's key will be assigned
* to the variable $key on each loop.
* Note:
* When foreach first starts executing, the internal array pointer is automatically reset to the
* first element of the array. This means that you do not need to call reset() before a foreach loop.
*/
static sxi32 jx9CompileForeach(jx9_gen_state *pGen)
{
SyToken *pCur, *pTmp, *pEnd = 0;
GenBlock *pForeachBlock = 0;
jx9_foreach_info *pInfo;
sxu32 nFalseJump;
VmInstr *pInstr;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'foreach' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Delimit the expression */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
/* Synchronize */
pGen->pIn = pEnd;
if( pGen->pIn < pGen->pEnd ){
pGen->pIn++;
}
return SXRET_OK;
}
/* Compile the array expression */
pCur = pGen->pIn;
while( pCur < pEnd ){
if( pCur->nType & JX9_TK_KEYWORD ){
sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData);
if( nKeywrd == JX9_TKWRD_AS ){
/* Break with the first 'as' found */
break;
}
}
/* Advance the stream cursor */
pCur++;
}
if( pCur <= pGen->pIn ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
"foreach: Missing array/object expression");
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pCur;
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Update token stream */
while(pGen->pIn < pCur ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
pGen->pIn++;
}
pCur++; /* Jump the 'as' keyword */
pGen->pIn = pCur;
if( pGen->pIn >= pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
/* Create the foreach context */
pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info));
if( pInfo == 0 ){
jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory");
return SXERR_ABORT;
}
/* Zero the structure */
SyZero(pInfo, sizeof(jx9_foreach_info));
/* Initialize structure fields */
SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *));
/* Check if we have a key field */
while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){
pCur++;
}
if( pCur < pEnd ){
/* Compile the expression holding the key name */
if( pGen->pIn >= pCur ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key");
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
}else{
pGen->pEnd = pCur;
rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
pInstr = jx9VmPopInstr(pGen->pVm);
if( pInstr->p3 ){
/* Record key name */
SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3));
}
pInfo->iFlags |= JX9_4EACH_STEP_KEY;
}
pGen->pIn = &pCur[1]; /* Jump the arrow */
}
pGen->pEnd = pEnd;
if( pGen->pIn >= pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value");
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Compile the expression holding the value name */
rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
pInstr = jx9VmPopInstr(pGen->pVm);
if( pInstr->p3 ){
/* Record value name */
SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3));
}
/* Emit the 'FOREACH_INIT' instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump);
/* Record the first instruction to execute */
pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm);
/* Emit the FOREACH_STEP instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump);
/* Compile the loop body */
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
/* Emit the unconditional jump to the start of the loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';' so we can avoid
* compiling this erroneous block.
*/
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the infamous if/elseif/else if/else statements.
* According to the JX9 language reference
* The if construct is one of the most important features of many languages JX9 included.
* It allows for conditional execution of code fragments. JX9 features an if structure
* that is similar to that of C:
* if (expr)
* statement
* else construct:
* Often you'd want to execute a statement if a certain condition is met, and a different
* statement if the condition is not met. This is what else is for. else extends an if statement
* to execute a statement in case the expression in the if statement evaluates to FALSE.
* For example, the following code would display a is greater than b if $a is greater than
* $b, and a is NOT greater than b otherwise.
* The else statement is only executed if the if expression evaluated to FALSE, and if there
* were any elseif expressions - only if they evaluated to FALSE as well
* elseif
* elseif, as its name suggests, is a combination of if and else. Like else, it extends
* an if statement to execute a different statement in case the original if expression evaluates
* to FALSE. However, unlike else, it will execute that alternative expression only if the elseif
* conditional expression evaluates to TRUE. For example, the following code would display a is bigger
* than b, a equal to b or a is smaller than b:
* if ($a > $b) {
* print "a is bigger than b";
* } elseif ($a == $b) {
* print "a is equal to b";
* } else {
* print "a is smaller than b";
* }
*/
static sxi32 jx9CompileIf(jx9_gen_state *pGen)
{
SyToken *pToken, *pTmp, *pEnd = 0;
GenBlock *pCondBlock = 0;
sxu32 nJumpIdx;
sxu32 nKeyID;
sxi32 rc;
/* Jump the 'if' keyword */
pGen->pIn++;
pToken = pGen->pIn;
/* Create the conditional block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(pGen->pVm), 0, &pCondBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Process as many [if/else if/elseif/else] blocks as we can */
for(;;){
if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
if( pToken >= pGen->pEnd ){
pToken--;
}
rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pToken++;
/* Delimit the condition */
jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){
/* Syntax error */
if( pToken >= pGen->pEnd ){
pToken--;
}
rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Swap token streams */
SWAP_TOKEN_STREAM(pGen, pToken, pEnd);
/* Compile the condition */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Update token stream */
while(pGen->pIn < pEnd ){
jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
pGen->pIn++;
}
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Emit the false jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx);
/* Compile the body */
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
break;
}
/* Ensure that the keyword ID is 'else if' or 'else' */
nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){
break;
}
/* Emit the unconditional jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx);
if( nKeyID & JX9_TKWRD_ELSE ){
pToken = &pGen->pIn[1];
if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 ||
SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){
break;
}
pGen->pIn++; /* Jump the 'else' keyword */
}
pGen->pIn++; /* Jump the 'elseif/if' keyword */
/* Synchronize cursors */
pToken = pGen->pIn;
/* Fix the false jump */
GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm));
} /* For(;;) */
/* Fix the false jump */
GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm));
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) &&
(SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){
/* Compile the else block */
pGen->pIn++;
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
nJumpIdx = jx9VmInstrLength(pGen->pVm);
/* Fix all unconditional jumps now the destination is resolved */
GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx);
/* Release the conditional block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block.
*/
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the return statement.
* According to the JX9 language reference
* If called from within a function, the return() statement immediately ends execution
* of the current function, and returns its argument as the value of the function call.
* return() will also end the execution of an eval() statement or script file.
* If called from the global scope, then execution of the current script file is ended.
* If the current script file was include()ed or require()ed, then control is passed back
* to the calling file. Furthermore, if the current script file was include()ed, then the value
* given to return() will be returned as the value of the include() call. If return() is called
* from within the main script file, then script execution end.
* Note that since return() is a language construct and not a function, the parentheses
* surrounding its arguments are not required. It is common to leave them out, and you actually
* should do so as JX9 has less work to do in this case.
* Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead..
*/
static sxi32 jx9CompileReturn(jx9_gen_state *pGen)
{
sxi32 nRet = 0; /* TRUE if there is a return value */
sxi32 rc;
/* Jump the 'return' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nRet = 1;
}
}
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, nRet, 0, 0, 0);
return SXRET_OK;
}
/*
* Compile the die/exit language construct.
* The role of these constructs is to terminate execution of the script.
* Shutdown functions will always be executed even if exit() is called.
*/
static sxi32 jx9CompileHalt(jx9_gen_state *pGen)
{
sxi32 nExpr = 0;
sxi32 rc;
/* Jump the die/exit keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nExpr = 1;
}
}
/* Emit the HALT instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0);
return SXRET_OK;
}
/*
* Compile the static statement.
* According to the JX9 language reference
* Another important feature of variable scoping is the static variable.
* A static variable exists only in a local function scope, but it does not lose its value
* when program execution leaves this scope.
* Static variables also provide one way to deal with recursive functions.
*/
static sxi32 jx9CompileStatic(jx9_gen_state *pGen)
{
jx9_vm_func_static_var sStatic; /* Structure describing the static variable */
jx9_vm_func *pFunc; /* Enclosing function */
GenBlock *pBlock;
SyString *pName;
char *zDup;
sxu32 nLine;
sxi32 rc;
/* Jump the static keyword */
nLine = pGen->pIn->nLine;
pGen->pIn++;
/* Extract the enclosing function if any */
pBlock = pGen->pCurrent;
while( pBlock ){
if( pBlock->iFlags & GEN_BLOCK_FUNC){
break;
}
/* Point to the upper block */
pBlock = pBlock->pParent;
}
if( pBlock == 0 ){
/* Static statement, called outside of a function body, treat it as a simple variable. */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
goto Synchronize;
}
/* Compile the expression holding the variable */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
/* Emit the POP instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
}
return SXRET_OK;
}
pFunc = (jx9_vm_func *)pBlock->pUserData;
/* Make sure we are dealing with a valid statement */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd ||
(pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++;
/* Extract variable name */
pName = &pGen->pIn->sData;
pGen->pIn++; /* Jump the var name */
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData);
goto Synchronize;
}
/* Initialize the structure describing the static variable */
SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
sStatic.nIdx = SXU32_HIGH; /* Not yet created */
/* Duplicate variable name */
zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
if( zDup == 0 ){
jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory");
return SXERR_ABORT;
}
SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte);
/* Check if we have an expression to compile */
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){
SySet *pInstrContainer;
pGen->pIn++; /* Jump the equal '=' sign */
/* Swap bytecode container */
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode);
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
/* Restore default bytecode container */
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
}
/* Finally save the compiled static variable in the appropriate container */
SySetPut(&pFunc->aStatic, (const void *)&sStatic);
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous
* statement.
*/
while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the 'const' statement.
* According to the JX9 language reference
* A constant is an identifier (name) for a simple value. As the name suggests, that value
* cannot change during the execution of the script (except for magic constants, which aren't actually constants).
* A constant is case-sensitive by default. By convention, constant identifiers are always uppercase.
* The name of a constant follows the same rules as any label in JX9. A valid constant name starts
* with a letter or underscore, followed by any number of letters, numbers, or underscores.
* As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
* Syntax
* You can define a constant by using the define()-function or by using the const keyword outside
* a object definition. Once a constant is defined, it can never be changed or undefined.
* You can get the value of a constant by simply specifying its name. Unlike with variables
* you should not prepend a constant with a $. You can also use the function constant() to read
* a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants()
* to get a list of all defined constants.
*/
static sxi32 jx9CompileConstant(jx9_gen_state *pGen)
{
SySet *pConsCode, *pInstrContainer;
sxu32 nLine = pGen->pIn->nLine;
SyString *pName;
sxi32 rc;
pGen->pIn++; /* Jump the 'const' keyword */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
/* Invalid constant name */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Peek constant name */
pName = &pGen->pIn->sData;
/* Make sure the constant name isn't reserved */
if( GenStateIsReservedID(pName) ){
/* Reserved constant */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName);
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++;
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){
/* Invalid statement*/
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++; /*Jump the equal sign */
/* Allocate a new constant value container */
pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet));
if( pConsCode == 0 ){
return GenStateOutOfMem(pGen);
}
SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
/* Swap bytecode container */
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, pConsCode);
/* Compile constant value */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
SySetSetUserData(pConsCode, pGen->pVm);
/* Register the constant */
rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode);
if( rc != SXRET_OK ){
SySetRelease(pConsCode);
SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode);
}
return SXRET_OK;
Synchronize:
/* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */
while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the uplink construct.
* According to the JX9 language reference
* In JX9 global variables must be declared uplink inside a function if they are going
* to be used in that function.
* Example #1 Using global
* $a = 1;
* $b = 2;
* function Sum()
* {
* uplink $a, $b;
* $b = $a + $b;
* }
* Sum();
* print $b;
* ?>
* The above script will output 3. By declaring $a and $b global within the function
* all references to either variable will refer to the global version. There is no limit
* to the number of global variables that can be manipulated by a function.
*/
static sxi32 jx9CompileUplink(jx9_gen_state *pGen)
{
SyToken *pTmp, *pNext = 0;
sxi32 nExpr;
sxi32 rc;
/* Jump the 'uplink' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){
/* Nothing to process */
return SXRET_OK;
}
pTmp = pGen->pEnd;
nExpr = 0;
while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){
if( pGen->pIn < pNext ){
pGen->pEnd = pNext;
if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}else{
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd ){
/* Emit a warning */
jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name");
}else{
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nExpr++;
}
}
}
}
/* Next expression in the stream */
pGen->pIn = pNext;
/* Jump trailing commas */
while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){
pGen->pIn++;
}
}
/* Restore token stream */
pGen->pEnd = pTmp;
if( nExpr > 0 ){
/* Emit the uplink instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0);
}
return SXRET_OK;
}
/*
* Compile a switch block.
* (See block-comment below for more information)
*/
static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart)
{
sxi32 rc = SXRET_OK;
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
pGen->pIn++;
/* First instruction to execute in this block. */
*pBlockStart = jx9VmInstrLength(pGen->pVm);
/* Compile the block until we hit a case/default/endswitch keyword
* or the '}' token */
for(;;){
if( pGen->pIn >= pGen->pEnd ){
/* No more input to process */
break;
}
rc = SXRET_OK;
if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){
rc = SXERR_EOF;
break;
}
}else{
sxi32 nKwrd;
/* Extract the keyword */
nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){
break;
}
}
/* Compile block */
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
return rc;
}
/*
* Compile a case eXpression.
* (See block-comment below for more information)
*/
static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_case_expr *pExpr)
{
SySet *pInstrContainer;
SyToken *pEnd, *pTmp;
sxi32 iNest = 0;
sxi32 rc;
/* Delimit the expression */
pEnd = pGen->pIn;
while( pEnd < pGen->pEnd ){
if( pEnd->nType & JX9_TK_LPAREN /*(*/ ){
/* Increment nesting level */
iNest++;
}else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){
/* Decrement nesting level */
iNest--;
}else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){
break;
}
pEnd++;
}
if( pGen->pIn >= pEnd ){
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}
/* Swap token stream */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode);
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
/* Update token stream */
pGen->pIn = pEnd;
pGen->pEnd = pTmp;
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
/*
* Compile the smart switch statement.
* According to the JX9 language reference manual
* The switch statement is similar to a series of IF statements on the same expression.
* In many occasions, you may want to compare the same variable (or expression) with many
* different values, and execute a different piece of code depending on which value it equals to.
* This is exactly what the switch statement is for.
* Note: Note that unlike some other languages, the continue statement applies to switch and acts
* similar to break. If you have a switch inside a loop and wish to continue to the next iteration
* of the outer loop, use continue 2.
* Note that switch/case does loose comparision.
* It is important to understand how the switch statement is executed in order to avoid mistakes.
* The switch statement executes line by line (actually, statement by statement).
* In the beginning, no code is executed. Only when a case statement is found with a value that
* matches the value of the switch expression does JX9 begin to execute the statements.
* JX9 continues to execute the statements until the end of the switch block, or the first time
* it sees a break statement. If you don't write a break statement at the end of a case's statement list.
* In a switch statement, the condition is evaluated only once and the result is compared to each
* case statement. In an elseif statement, the condition is evaluated again. If your condition
* is more complicated than a simple compare and/or is in a tight loop, a switch may be faster.
* The statement list for a case can also be empty, which simply passes control into the statement
* list for the next case.
* The case expression may be any expression that evaluates to a simple type, that is, integer
* or floating-point numbers and strings.
*/
static sxi32 jx9CompileSwitch(jx9_gen_state *pGen)
{
GenBlock *pSwitchBlock;
SyToken *pTmp, *pEnd;
jx9_switch *pSwitch;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'switch' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
pEnd = 0; /* cc warning */
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH,
jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Delimit the condition */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Update token stream */
while(pGen->pIn < pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
"Switch: Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd ||
(pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){
pTmp = pGen->pIn;
if( pTmp >= pGen->pEnd ){
pTmp--;
}
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++; /* Jump the leading curly braces/colons */
/* Create the switch blocks container */
pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch));
if( pSwitch == 0 ){
/* Abort compilation */
return GenStateOutOfMem(pGen);
}
/* Zero the structure */
SyZero(pSwitch, sizeof(jx9_switch));
/* Initialize fields */
SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr));
/* Emit the switch instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0);
/* Compile case blocks */
for(;;){
sxu32 nKwrd;
if( pGen->pIn >= pGen->pEnd ){
/* No more input to process */
break;
}
if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'",
&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* FALL THROUGH */
}
/* Block compiled */
break;
}
/* Extract the keyword */
nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
if( nKwrd == JX9_TKWRD_DEFAULT ){
/*
* Accroding to the JX9 language reference manual
* A special case is the default case. This case matches anything
* that wasn't matched by the other cases.
*/
if( pSwitch->nDefault > 0 ){
/* Default case already compiled */
rc = jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
pGen->pIn++; /* Jump the 'default' keyword */
/* Compile the default block */
rc = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault);
if( rc == SXERR_ABORT){
return SXERR_ABORT;
}else if( rc == SXERR_EOF ){
break;
}
}else if( nKwrd == JX9_TKWRD_CASE ){
jx9_case_expr sCase;
/* Standard case block */
pGen->pIn++; /* Jump the 'case' keyword */
/* initialize the structure */
SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
/* Compile the case expression */
rc = GenStateCompileCaseExpr(pGen, &sCase);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Compile the case block */
rc = GenStateCompileSwitchBlock(pGen,&sCase.nStart);
/* Insert in the switch container */
SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase);
if( rc == SXERR_ABORT){
return SXERR_ABORT;
}else if( rc == SXERR_EOF ){
break;
}
}else{
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'",
&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
break;
}
}
/* Fix all jumps now the destination is resolved */
pSwitch->nOut = jx9VmInstrLength(pGen->pVm);
GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
if( pGen->pIn < pGen->pEnd ){
/* Jump the trailing curly braces */
pGen->pIn++;
}
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Process default argument values. That is, a function may define C++-style default value
* as follows:
* function makecoffee($type = "cappuccino")
* {
* return "Making a cup of $type.\n";
* }
* Some features:
* 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous
* functions, array member, ..]
* 2 -) Full type hinting: (Arguments are automatically casted to the desired type)
* Example:
* function a(string $a){} function b(int $a, string $c, float $d){}
* 3 -) Function overloading!!
* Example:
* function foo($a) {
* return $a.JX9_EOL;
* }
* function foo($a, $b) {
* return $a + $b;
* }
* print foo(5); // Prints "5"
* print foo(5, 2); // Prints "7"
* // Same arg
* function foo(string $a)
* {
* print "a is a string\n";
* dump($a);
* }
* function foo(int $a)
* {
* print "a is integer\n";
* dump($a);
* }
* function foo(array $a)
* {
* print "a is an array\n";
* dump($a);
* }
* foo('This is a great feature'); // a is a string [first foo]
* foo(52); // a is integer [second foo]
* foo(array(14, __TIME__, __DATE__)); // a is an array [third foo]
* Please refer to the official documentation for more information on the powerful extension
* introduced by the JX9 engine.
*/
static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd)
{
SyToken *pTmpIn, *pTmpEnd;
SySet *pInstrContainer;
sxi32 rc;
/* Swap token stream */
SWAP_DELIMITER(pGen, pIn, pEnd);
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode);
/* Compile the expression holding the argument value */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
RE_SWAP_DELIMITER(pGen);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
/*
* Collect function arguments one after one.
* According to the JX9 language reference manual.
* Information may be passed to functions via the argument list, which is a comma-delimited
* list of expressions.
* JX9 supports passing arguments by value (the default), passing by reference
* and default argument values. Variable-length argument lists are also supported,
* see also the function references for func_num_args(), func_get_arg(), and func_get_args()
* for more information.
* Example #1 Passing arrays to functions
* <?jx9
* function takes_array($input)
* {
* print "$input[0] + $input[1] = ", $input[0]+$input[1];
* }
* ?>
* Making arguments be passed by reference
* By default, function arguments are passed by value (so that if the value of the argument
* within the function is changed, it does not get changed outside of the function).
* To allow a function to modify its arguments, they must be passed by reference.
* To have an argument to a function always passed by reference, prepend an ampersand (&)
* to the argument name in the function definition:
* Example #2 Passing function parameters by reference
* <?jx9
* function add_some_extra(&$string)
* {
* $string .= 'and something extra.';
* }
* $str = 'This is a string, ';
* add_some_extra($str);
* print $str; // outputs 'This is a string, and something extra.'
* ?>
*
* 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 <pEnd ){
pEnd = pEnd2;
}
}
if( pEnd > 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 <chm@symisc.net> $ */
#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 <Windows.h>
#elif defined(__UNIXES__)
#include <sys/utsname.h>
#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 <time.h>
#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 <chm@symisc.net> $ */
#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.
* <?jx9
* $a = array("a" => "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:
* <?jx9
* $a = array("a" => "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; i<N_SORT_BUCKET-1; i++){
if( a[i]==0 ){
a[i] = p;
break;
}else{
p = HashmapNodeMerge(a[i], p, xCmp, pCmpData);
a[i] = 0;
}
}
if( i==N_SORT_BUCKET-1 ){
/* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list.
* But that is impossible.
*/
a[i] = HashmapNodeMerge(a[i], p, xCmp, pCmpData);
}
}
p = a[0];
for(i=1; i<N_SORT_BUCKET; i++){
p = HashmapNodeMerge(p, a[i], xCmp, pCmpData);
}
p->pNext = 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 <chm@symisc.net> $ */
#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 <chm@symisc.net> $ */
#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 <chm@symisc.net> $
**
** 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 <![CDATA[ ]]> 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 <chm@symisc.net> $ */
/*
* 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 <Windows.h>
#else
#include <stdlib.h>
#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 <pthread.h>
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<nLen ; n += 4){
w = aBase64Trans[zB64[n] & 0x7F];
x = aBase64Trans[zB64[n+1] & 0x7F];
y = aBase64Trans[zB64[n+2] & 0x7F];
z = aBase64Trans[zB64[n+3] & 0x7F];
zOut[0] = ((w<<2) & 0xFC) | ((x>>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 && precision<width-(prefix!=0) ){
precision = width-(prefix!=0);
}
bufpt = &buf[SXFMT_BUFSIZ-1];
{
register char *cset; /* Use registers for speed */
register int base;
cset = infop->charset;
base = infop->base;
do{ /* Convert to ascii */
*(--bufpt) = cset[longvalue%base];
longvalue = longvalue/base;
}while( longvalue>0 );
}
length = &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 = &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+precision<SXFMT_BUFSIZ-30 ){
flag_dp = (precision>0 || 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 = 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<precision; idx++) buf[idx] = (char)c;
length = precision;
}else{
length =1;
}
bufpt = buf;
break;
case SXFMT_STRING:
bufpt = va_arg(ap, char*);
if( bufpt==0 ){
bufpt = " ";
length = (int)sizeof(" ")-1;
break;
}
length = precision;
if( precision < 0 ){
/* Symisc extension */
length = (int)SyStrlen(bufpt);
}
if( precision>=0 && precision<length ) length = precision;
break;
case SXFMT_RAWSTR:{
/* Symisc extension */
SyString *pStr = va_arg(ap, SyString *);
if( pStr == 0 || pStr->zString == 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 <http://www.sqlite.org/>
* @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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#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 ){
return SXRET_OK;
}
/* FALL THRU */
}
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<<s | 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 <steve@edmweb.com>
* 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 <chm@symisc.net> $ */
#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 && iVal<LARGEST_INT64 ){
pObj->x.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_NULL|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_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 <chm@symisc.net> $ */
#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 <chm@symisc.net> $ */
#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 <Windows.h>
#elif defined(__UNIXES__)
#include <sys/utsname.h>
#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 <Windows.h>
/*
** 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 <sys/types.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/file.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <utime.h>
#include <stdio.h>
#include <stdlib.h>
/* 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; i<sizeof(azDirs)/sizeof(azDirs[0]); i++){
zDir=azDirs[i];
if( zDir==0 ) continue;
if( stat(zDir, &buf) ) continue;
if( !S_ISDIR(buf.st_mode) ) continue;
if( access(zDir, 07) ) continue;
/* Got one */
jx9_result_string(pCtx, zDir, -1);
return;
}
/* Default temp dir */
jx9_result_string(pCtx, "/tmp", (int)sizeof("/tmp")-1);
}
/* unsigned int (*xProcessId)(void) */
static unsigned int UnixVfs_ProcessId(void)
{
return (unsigned int)getpid();
}
/* int (*xUid)(void) */
static int UnixVfs_uid(void)
{
return (int)getuid();
}
/* int (*xGid)(void) */
static int UnixVfs_gid(void)
{
return (int)getgid();
}
/* int (*xUmask)(int) */
static int UnixVfs_Umask(int new_mask)
{
int old_mask;
old_mask = umask(new_mask);
return old_mask;
}
/* void (*xUsername)(jx9_context *) */
static void UnixVfs_Username(jx9_context *pCtx)
{
#ifndef JX9_UNIX_STATIC_BUILD
struct passwd *pwd;
uid_t uid;
uid = getuid();
pwd = getpwuid(uid); /* Try getting UID for username */
if (pwd == 0) {
return;
}
/* Return the username */
jx9_result_string(pCtx, pwd->pw_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 <chm@symisc.net> $ */
#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 pNos<pTos.
* 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_LE P1 P2 P3
*
* Pop the top two elements from the stack. If the second element (the top of stack)
* is less than or equal to the first (next on stack), then jump to instruction P2.
* Otherwise continue to the next instruction. In other words, jump if pNos<pTos.
* 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_LT:
case JX9_OP_LE: {
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_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 pNos<pTos.
* 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_GE P1 P2 P3
*
* Pop the top two elements from the stack. If the second element (the top of stack)
* is greater than or equal to the first (next on stack), then jump to instruction P2.
* Otherwise continue to the next instruction. In other words, jump if pNos<pTos.
* 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_GT:
case JX9_OP_GE: {
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_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 <20> 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 <20> whether absolute (starting with a drive letter
* or \ on Windows, or / on Unix/Linux systems) or relative to the current
* directory (starting with . or ..) <20> 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: 1c9e0b9759c2c53c78e1e04b44dac494
* ----------------------------------------------------------
*/
/*
* 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 <chm@symisc.net> $ */
#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 begining */
pCell = pPage->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);
}
/* 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 -= nByte;
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,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,
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,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 */
rc = lhAllocateSpace(pSlave,L_HASH_CELL_SZ+nAmount,&iOfft);
if( rc != UNQLITE_OK ){
/* A space for cell header only */
rc = lhAllocateSpace(pSlave,L_HASH_CELL_SZ,&iOfft);
}
if( rc == UNQLITE_OK ){
/* All done */
if( pOfft ){
*pOfft = iOfft;
}
*ppSlave = pSlave;
return UNQLITE_OK;
}
/* 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(lhash_kv_engine *pEngine)
{
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;
}
/* 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 <chm@symisc.net> */
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 ){
/* Split */
rc = lhSplit(pPage->pHash);
if( rc == UNQLITE_OK ){
/* 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 rc;
/* Acquire the first page (DB 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,(sxu32)nKeyLen);
/* 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);
}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;
/* Initialiaze 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());
#if defined(UNQLITE_ENABLE_THREADS)
/* Already protected by the upper layers */
SyMemBackendDisbaleMutexing(&pHash->sAllocator);
#endif
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 <chm@symisc.net> $ */
#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 <chm@symisc.net> $ */
#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 <chm@symisc.net> $ */
#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 <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#if defined(__APPLE__)
# include <sys/mount.h>
#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.
*/
static void * unqlite_malloc(sxu32 nByte)
{
SyMemBackend *pAlloc;
void *p;
pAlloc = (SyMemBackend *)unqliteExportMemBackend();
p = SyMemBackendAlloc(pAlloc,nByte);
return p;
}
static 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->eFileLock<PENDING_LOCK)
){
lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK);
lock.l_start = PENDING_BYTE;
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;
}
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.
*/
int isOpenDirectory = isCreate ;
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);
}
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;
}
}
#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 <chm@symisc.net> $ */
#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 <Windows.h>
/*
** 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 <chm@symisc.net> $ */
#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)
{
pPage->nRef++;
}
/*
* 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)
{
pPage->nRef--;
if( pPage->nRef < 1 ){
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; i<N_SORT_BUCKET-1; i++){
if( a[i]==0 ){
a[i] = p;
break;
}else{
p = page_merge_dirty(a[i], p);
a[i] = 0;
}
}
if( i==N_SORT_BUCKET-1 ){
/* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list.
* But that is impossible.
*/
a[i] = page_merge_dirty(a[i], p);
}
}
p = a[0];
for(i=1; i<N_SORT_BUCKET; i++){
p = page_merge_dirty(p,a[i]);
}
p->pDirtyNext = 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; i<N_SORT_BUCKET-1; i++){
if( a[i]==0 ){
a[i] = p;
break;
}else{
p = page_merge_hot(a[i], p);
a[i] = 0;
}
}
if( i==N_SORT_BUCKET-1 ){
/* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list.
* But that is impossible.
*/
a[i] = page_merge_hot(a[i], p);
}
}
p = a[0];
for(i=1; i<N_SORT_BUCKET; i++){
p = page_merge_hot(p,a[i]);
}
p->pNextHot = 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->iPageSize<UNQLITE_MIN_PAGE_SIZE || pPager->iSectorSize<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 ){
/* Synce 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;
}
/* 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 shraed 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 termniator */;
}
/* 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 */
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;
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: 358e7f319791c11c8262b81573d3a90e
* ----------------------------------------------------------
*/
/*
* 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 <chm@symisc.net> $ */
#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->pNext = 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,(unqlite_int64)nByte,UNQLITE_CURSOR_MATCH_EXACT);
if( rc != UNQLITE_OK ){
unqliteGenErrorFormat(pDb,"Collection '%.*s' not defined in the underlying database",nByte,zName);
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;
}
/*
* 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;
}
/*
* 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;
}
/*
* 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 <chm@symisc.net> $ */
#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)
{
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);
/* Perform the lookup */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
/* Lookup result */
jx9_result_bool(pCtx,pCol ? 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_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_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-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
*/
/*
* Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>].
* 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.
*/