freeswitch/libs/libblade/src/unqlite.c

60240 lines
1.8 MiB
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef _WIN32
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#endif
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-2016, Symisc Systems http://unqlite.org/
* Copyright (C) 2014, Yuras Shumovich <shumovichy@gmail.com>
* Version 1.1.7
* 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, 2016 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.7 Win10 2106-12-02 00:04:12 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: d26e9847c6587edbbb183d0115d172cb
* ----------------------------------------------------------
*/
/* 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-2016, 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, 2016 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.2 Win10 2106-12-02 00:04:12 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.7"
/*
* 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 1001007
/*
* 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.7"
/*
* 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-2016, http://unqlite.org/"
/* Make sure we can call this stuff from C++ */
#ifdef __cplusplus
extern "C" {
#endif
/* Forward declaration to public objects */
typedef struct unqlite_io_methods unqlite_io_methods;
typedef struct unqlite_kv_methods unqlite_kv_methods;
typedef struct unqlite_kv_engine unqlite_kv_engine;
typedef struct jx9_io_stream unqlite_io_stream;
typedef struct jx9_context unqlite_context;
typedef struct jx9_value unqlite_value;
typedef struct unqlite_vfs unqlite_vfs;
typedef struct unqlite_vm unqlite_vm;
typedef struct unqlite unqlite;
/*
* ------------------------------
* Compile time directives
* ------------------------------
* For most purposes, UnQLite can be built just fine using the default compilation options.
* However, if required, the compile-time options documented below can be used to omit UnQLite
* features (resulting in a smaller compiled library size) or to change the default values
* of some parameters.
* Every effort has been made to ensure that the various combinations of compilation options
* work harmoniously and produce a working library.
*
* UNQLITE_ENABLE_THREADS
* This option controls whether or not code is included in UnQLite to enable it to operate
* safely in a multithreaded environment. The default is not. All mutexing code is omitted
* and it is unsafe to use UnQLite in a multithreaded program. When compiled with the
* UNQLITE_ENABLE_THREADS directive enabled, UnQLite can be used in a multithreaded program
* and it is safe to share the same virtual machine and engine handle between two or more threads.
* The value of UNQLITE_ENABLE_THREADS can be determined at run-time using the unqlite_lib_is_threadsafe()
* interface.
* When UnQLite has been compiled with threading support then the threading mode can be altered
* at run-time using the unqlite_lib_config() interface together with one of these verbs:
* UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE
* UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI
* Platforms others than Windows and UNIX systems must install their own mutex subsystem via
* unqlite_lib_config() with a configuration verb set to UNQLITE_LIB_CONFIG_USER_MUTEX.
* Otherwise the library is not threadsafe.
* Note that you must link UnQLite with the POSIX threads library under UNIX systems (i.e: -lpthread).
*
* Options To Omit/Enable Features
*
* The following options can be used to reduce the size of the compiled library by omitting optional
* features. This is probably only useful in embedded systems where space is especially tight, as even
* with all features included the UnQLite library is relatively small. Don't forget to tell your
* compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler
* to optimize for size usually has a much larger impact on library footprint than employing
* any of these compile-time options.
*
* JX9_DISABLE_BUILTIN_FUNC
* Jx9 is shipped with more than 312 built-in functions suitable for most purposes like
* string and INI processing, ZIP extracting, Base64 encoding/decoding, JSON encoding/decoding
* and so forth.
* If this directive is enabled, then all built-in Jx9 functions are omitted from the build.
* Note that special functions such as db_create(), db_store(), db_fetch(), etc. are not omitted
* from the build and are not affected by this directive.
*
* JX9_ENABLE_MATH_FUNC
* If this directive is enabled, built-in math functions such as sqrt(), abs(), log(), ceil(), etc.
* are included in the build. Note that you may need to link UnQLite with the math library in same
* Linux/BSD flavor (i.e: -lm).
*
* JX9_DISABLE_DISK_IO
* If this directive is enabled, built-in VFS functions such as chdir(), mkdir(), chroot(), unlink(),
* sleep(), etc. are omitted from the build.
*
* UNQLITE_ENABLE_JX9_HASH_IO
* If this directive is enabled, built-in hash functions such as md5(), sha1(), md5_file(), crc32(), etc.
* are included in the build.
*/
/* Symisc public definitions */
#if !defined(SYMISC_STANDARD_DEFS)
#define SYMISC_STANDARD_DEFS
#if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE)
/* Windows Systems */
#if !defined(__WINNT__)
#define __WINNT__
#endif
/*
* Determine if we are dealing with WindowsCE - which has a much
* reduced API.
*/
#if defined(_WIN32_WCE)
#ifndef __WIN_CE__
#define __WIN_CE__
#endif /* __WIN_CE__ */
#endif /* _WIN32_WCE */
#else
/*
* By default we will assume that we are compiling on a UNIX systems.
* Otherwise the OS_OTHER directive must be defined.
*/
#if !defined(OS_OTHER)
#if !defined(__UNIXES__)
#define __UNIXES__
#endif /* __UNIXES__ */
#else
#endif /* OS_OTHER */
#endif /* __WINNT__/__UNIXES__ */
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */
typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */
#else
typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */
typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */
#endif /* _MSC_VER */
/* Signature of the consumer routine */
typedef int (*ProcConsumer)(const void *, unsigned int, void *);
/* Forward reference */
typedef struct SyMutexMethods SyMutexMethods;
typedef struct SyMemMethods SyMemMethods;
typedef struct SyString SyString;
typedef struct syiovec syiovec;
typedef struct SyMutex SyMutex;
typedef struct Sytm Sytm;
/* Scatter and gather array. */
struct syiovec
{
#if defined (__WINNT__)
/* Same fields type and offset as WSABUF structure defined one winsock2 header */
unsigned long nLen;
char *pBase;
#else
void *pBase;
unsigned long nLen;
#endif
};
struct SyString
{
const char *zString; /* Raw string (may not be null terminated) */
unsigned int nByte; /* Raw string length */
};
/* Time structure. */
struct Sytm
{
int tm_sec; /* seconds (0 - 60) */
int tm_min; /* minutes (0 - 59) */
int tm_hour; /* hours (0 - 23) */
int tm_mday; /* day of month (1 - 31) */
int tm_mon; /* month of year (0 - 11) */
int tm_year; /* year + 1900 */
int tm_wday; /* day of week (Sunday = 0) */
int tm_yday; /* day of year (0 - 365) */
int tm_isdst; /* is summer time in effect? */
char *tm_zone; /* abbreviation of timezone name */
long tm_gmtoff; /* offset from UTC in seconds */
};
/* Convert a tm structure (struct tm *) found in <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);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* _UNQLITE_H_ */
/*
* ----------------------------------------------------------
* File: jx9.h
* MD5: d23a1e182f596794001533e1d6aa16a0
* ----------------------------------------------------------
*/
/* This file was automatically generated. Do not edit (except for compile time directive)! */
#ifndef _JX9H_
#define _JX9H_
/*
* Symisc Jx9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
* Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
* Version 1.7.2
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://jx9.symisc.net/
*/
/*
* Copyright (C) 2012, 2013 Symisc Systems. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Redistributions in any form must be accompanied by information on
* how to obtain complete source code for the JX9 engine and any
* accompanying software that uses the JX9 engine software.
* The source code must either be included in the distribution
* or be available for no more than the cost of distribution plus
* a nominal fee, and must be freely redistributable under reasonable
* conditions. For an executable file, complete source code means
* the source code for all modules it contains.It does not include
* source code for modules or files that typically accompany the major
* components of the operating system on which the executable file runs.
*
* THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
* NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* $SymiscID: jx9.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable <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/"
/* Forward declaration to public objects */
typedef struct jx9_io_stream jx9_io_stream;
typedef struct jx9_context jx9_context;
typedef struct jx9_value jx9_value;
typedef struct jx9_vfs jx9_vfs;
typedef struct jx9_vm jx9_vm;
typedef struct jx9 jx9;
#include "unqlite.h"
#if !defined( UNQLITE_ENABLE_JX9_HASH_FUNC )
#define JX9_DISABLE_HASH_FUNC
#endif /* UNQLITE_ENABLE_JX9_HASH_FUNC */
#ifdef UNQLITE_ENABLE_THREADS
#define JX9_ENABLE_THREADS
#endif /* UNQLITE_ENABLE_THREADS */
/* Standard JX9 return values */
#define JX9_OK SXRET_OK /* Successful result */
/* beginning-of-error-codes */
#define JX9_NOMEM UNQLITE_NOMEM /* Out of memory */
#define JX9_ABORT UNQLITE_ABORT /* Foreign Function request operation abort/Another thread have released this instance */
#define JX9_IO_ERR UNQLITE_IOERR /* IO error */
#define JX9_CORRUPT UNQLITE_CORRUPT /* Corrupt pointer/Unknown configuration option */
#define JX9_LOOKED UNQLITE_LOCKED /* Forbidden Operation */
#define JX9_COMPILE_ERR UNQLITE_COMPILE_ERR /* Compilation error */
#define JX9_VM_ERR UNQLITE_VM_ERR /* Virtual machine error */
/* end-of-error-codes */
/*
* If compiling for a processor that lacks floating point
* support, substitute integer for floating-point.
*/
#ifdef JX9_OMIT_FLOATING_POINT
typedef sxi64 jx9_real;
#else
typedef double jx9_real;
#endif
typedef sxi64 jx9_int64;
/*
* Engine Configuration Commands.
*
* The following set of constants are the available configuration verbs that can
* be used by the host-application to configure the JX9 engine.
* These constants must be passed as the second argument to the [jx9_config()]
* interface.
* Each options require a variable number of arguments.
* The [jx9_config()] interface will return JX9_OK on success, any other
* return value indicates failure.
* For a full discussion on the configuration verbs and their expected
* parameters, please refer to this page:
* http://jx9.symisc.net/c_api_func.html#jx9_config
*/
#define JX9_CONFIG_ERR_ABORT 1 /* RESERVED FOR FUTURE USE */
#define JX9_CONFIG_ERR_LOG 2 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */
/*
* Virtual Machine Configuration Commands.
*
* The following set of constants are the available configuration verbs that can
* be used by the host-application to configure the JX9 Virtual machine.
* These constants must be passed as the second argument to the [jx9_vm_config()]
* interface.
* Each options require a variable number of arguments.
* The [jx9_vm_config()] interface will return JX9_OK on success, any other return
* value indicates failure.
* There are many options but the most importants are: JX9_VM_CONFIG_OUTPUT which install
* a VM output consumer callback, JX9_VM_CONFIG_HTTP_REQUEST which parse and register
* a HTTP request and JX9_VM_CONFIG_ARGV_ENTRY which populate the $argv array.
* For a full discussion on the configuration verbs and their expected parameters, please
* refer to this page:
* http://jx9.symisc.net/c_api_func.html#jx9_vm_config
*/
#define JX9_VM_CONFIG_OUTPUT UNQLITE_VM_CONFIG_OUTPUT /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */
#define JX9_VM_CONFIG_IMPORT_PATH UNQLITE_VM_CONFIG_IMPORT_PATH /* ONE ARGUMENT: const char *zIncludePath */
#define JX9_VM_CONFIG_ERR_REPORT UNQLITE_VM_CONFIG_ERR_REPORT /* NO ARGUMENTS: Report all run-time errors in the VM output */
#define JX9_VM_CONFIG_RECURSION_DEPTH UNQLITE_VM_CONFIG_RECURSION_DEPTH /* ONE ARGUMENT: int nMaxDepth */
#define JX9_VM_OUTPUT_LENGTH UNQLITE_VM_OUTPUT_LENGTH /* ONE ARGUMENT: unsigned int *pLength */
#define JX9_VM_CONFIG_CREATE_VAR UNQLITE_VM_CONFIG_CREATE_VAR /* TWO ARGUMENTS: const char *zName, jx9_value *pValue */
#define JX9_VM_CONFIG_HTTP_REQUEST UNQLITE_VM_CONFIG_HTTP_REQUEST /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */
#define JX9_VM_CONFIG_SERVER_ATTR UNQLITE_VM_CONFIG_SERVER_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */
#define JX9_VM_CONFIG_ENV_ATTR UNQLITE_VM_CONFIG_ENV_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */
#define JX9_VM_CONFIG_EXEC_VALUE UNQLITE_VM_CONFIG_EXEC_VALUE /* ONE ARGUMENT: jx9_value **ppValue */
#define JX9_VM_CONFIG_IO_STREAM UNQLITE_VM_CONFIG_IO_STREAM /* ONE ARGUMENT: const jx9_io_stream *pStream */
#define JX9_VM_CONFIG_ARGV_ENTRY UNQLITE_VM_CONFIG_ARGV_ENTRY /* ONE ARGUMENT: const char *zValue */
#define JX9_VM_CONFIG_EXTRACT_OUTPUT UNQLITE_VM_CONFIG_EXTRACT_OUTPUT /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */
/*
* Global Library Configuration Commands.
*
* The following set of constants are the available configuration verbs that can
* be used by the host-application to configure the whole library.
* These constants must be passed as the first argument to the [jx9_lib_config()]
* interface.
* Each options require a variable number of arguments.
* The [jx9_lib_config()] interface will return JX9_OK on success, any other return
* value indicates failure.
* Notes:
* The default configuration is recommended for most applications and so the call to
* [jx9_lib_config()] is usually not necessary. It is provided to support rare
* applications with unusual needs.
* The [jx9_lib_config()] interface is not threadsafe. The application must insure that
* no other [jx9_*()] interfaces are invoked by other threads while [jx9_lib_config()]
* is running. Furthermore, [jx9_lib_config()] may only be invoked prior to library
* initialization using [jx9_lib_init()] or [jx9_init()] or after shutdown
* by [jx9_lib_shutdown()]. If [jx9_lib_config()] is called after [jx9_lib_init()]
* or [jx9_init()] and before [jx9_lib_shutdown()] then it will return jx9LOCKED.
* For a full discussion on the configuration verbs and their expected parameters, please
* refer to this page:
* http://jx9.symisc.net/c_api_func.html#Global_Library_Management_Interfaces
*/
#define JX9_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */
#define JX9_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */
#define JX9_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */
#define JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */
#define JX9_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */
#define JX9_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const jx9_vfs *pVfs */
/*
* Call Context - Error Message Serverity Level.
*/
#define JX9_CTX_ERR UNQLITE_CTX_ERR /* Call context error such as unexpected number of arguments, invalid types and so on. */
#define JX9_CTX_WARNING UNQLITE_CTX_WARNING /* Call context Warning */
#define JX9_CTX_NOTICE UNQLITE_CTX_NOTICE /* Call context Notice */
/* Current VFS structure version*/
#define JX9_VFS_VERSION 2
/*
* JX9 Virtual File System (VFS).
*
* An instance of the jx9_vfs object defines the interface between the JX9 core
* and the underlying operating system. The "vfs" in the name of the object stands
* for "virtual file system". The vfs is used to implement JX9 system functions
* such as mkdir(), chdir(), stat(), get_user_name() and many more.
* The value of the iVersion field is initially 2 but may be larger in future versions
* of JX9.
* Additional fields may be appended to this object when the iVersion value is increased.
* Only a single vfs can be registered within the JX9 core. Vfs registration is done
* using the jx9_lib_config() interface with a configuration verb set to JX9_LIB_CONFIG_VFS.
* Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users does not have to
* worry about registering and installing a vfs since JX9 come with a built-in vfs for these
* platforms which implement most the methods defined below.
* Host-application running on exotic systems (ie: Other than Windows and UNIX systems) must
* register their own vfs in order to be able to use and call JX9 system functions.
* Also note that the jx9_compile_file() interface depend on the xMmap() method of the underlying
* vfs which mean that this method must be available (Always the case using the built-in VFS)
* in order to use this interface.
* Developers wishing to implement their own vfs an contact symisc systems to obtain
* the JX9 VFS C/C++ Specification manual.
*/
struct jx9_vfs
{
const char *zName; /* Underlying VFS name [i.e: FreeBSD/Linux/Windows...] */
int iVersion; /* Current VFS structure version [default 2] */
/* Directory functions */
int (*xChdir)(const char *); /* Change directory */
int (*xChroot)(const char *); /* Change the root directory */
int (*xGetcwd)(jx9_context *); /* Get the current working directory */
int (*xMkdir)(const char *, int, int); /* Make directory */
int (*xRmdir)(const char *); /* Remove directory */
int (*xIsdir)(const char *); /* Tells whether the filename is a directory */
int (*xRename)(const char *, const char *); /* Renames a file or directory */
int (*xRealpath)(const char *, jx9_context *); /* Return canonicalized absolute pathname*/
/* Systems functions */
int (*xSleep)(unsigned int); /* Delay execution in microseconds */
int (*xUnlink)(const char *); /* Deletes a file */
int (*xFileExists)(const char *); /* Checks whether a file or directory exists */
int (*xChmod)(const char *, int); /* Changes file mode */
int (*xChown)(const char *, const char *); /* Changes file owner */
int (*xChgrp)(const char *, const char *); /* Changes file group */
jx9_int64 (*xFreeSpace)(const char *); /* Available space on filesystem or disk partition */
jx9_int64 (*xTotalSpace)(const char *); /* Total space on filesystem or disk partition */
jx9_int64 (*xFileSize)(const char *); /* Gets file size */
jx9_int64 (*xFileAtime)(const char *); /* Gets last access time of file */
jx9_int64 (*xFileMtime)(const char *); /* Gets file modification time */
jx9_int64 (*xFileCtime)(const char *); /* Gets inode change time of file */
int (*xStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */
int (*xlStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */
int (*xIsfile)(const char *); /* Tells whether the filename is a regular file */
int (*xIslink)(const char *); /* Tells whether the filename is a symbolic link */
int (*xReadable)(const char *); /* Tells whether a file exists and is readable */
int (*xWritable)(const char *); /* Tells whether the filename is writable */
int (*xExecutable)(const char *); /* Tells whether the filename is executable */
int (*xFiletype)(const char *, jx9_context *); /* Gets file type [i.e: fifo, dir, file..] */
int (*xGetenv)(const char *, jx9_context *); /* Gets the value of an environment variable */
int (*xSetenv)(const char *, const char *); /* Sets the value of an environment variable */
int (*xTouch)(const char *, jx9_int64, jx9_int64); /* Sets access and modification time of file */
int (*xMmap)(const char *, void **, jx9_int64 *); /* Read-only memory map of the whole file */
void (*xUnmap)(void *, jx9_int64); /* Unmap a memory view */
int (*xLink)(const char *, const char *, int); /* Create hard or symbolic link */
int (*xUmask)(int); /* Change the current umask */
void (*xTempDir)(jx9_context *); /* Get path of the temporary directory */
unsigned int (*xProcessId)(void); /* Get running process ID */
int (*xUid)(void); /* user ID of the process */
int (*xGid)(void); /* group ID of the process */
void (*xUsername)(jx9_context *); /* Running username */
int (*xExec)(const char *, jx9_context *); /* Execute an external program */
};
/* Current JX9 IO stream structure version. */
#define JX9_IO_STREAM_VERSION 1
/*
* Possible open mode flags that can be passed to the xOpen() routine
* of the underlying IO stream device .
* Refer to the JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html)
* for additional information.
*/
#define JX9_IO_OPEN_RDONLY 0x001 /* Read-only open */
#define JX9_IO_OPEN_WRONLY 0x002 /* Write-only open */
#define JX9_IO_OPEN_RDWR 0x004 /* Read-write open. */
#define JX9_IO_OPEN_CREATE 0x008 /* If the file does not exist it will be created */
#define JX9_IO_OPEN_TRUNC 0x010 /* Truncate the file to zero length */
#define JX9_IO_OPEN_APPEND 0x020 /* Append mode.The file offset is positioned at the end of the file */
#define JX9_IO_OPEN_EXCL 0x040 /* Ensure that this call creates the file, the file must not exist before */
#define JX9_IO_OPEN_BINARY 0x080 /* Simple hint: Data is binary */
#define JX9_IO_OPEN_TEMP 0x100 /* Simple hint: Temporary file */
#define JX9_IO_OPEN_TEXT 0x200 /* Simple hint: Data is textual */
/*
* JX9 IO Stream Device.
*
* An instance of the jx9_io_stream object defines the interface between the JX9 core
* and the underlying stream device.
* A stream is a smart mechanism for generalizing file, network, data compression
* and other IO operations which share a common set of functions using an abstracted
* unified interface.
* A stream device is additional code which tells the stream how to handle specific
* protocols/encodings. For example, the http device knows how to translate a URL
* into an HTTP/1.1 request for a file on a remote server.
* JX9 come with two built-in IO streams device:
* The file:// stream which perform very efficient disk IO and the jx9:// stream
* which is a special stream that allow access various I/O streams (See the JX9 official
* documentation for more information on this stream).
* A stream is referenced as: scheme://target
* scheme(string) - The name of the wrapper to be used. Examples include: file, http, https, ftp,
* ftps, compress.zlib, compress.bz2, and jx9. If no wrapper is specified, the function default
* is used (typically file://).
* target - Depends on the device used. For filesystem related streams this is typically a path
* and filename of the desired file.For network related streams this is typically a hostname, often
* with a path appended.
* IO stream devices are registered using a call to jx9_vm_config() with a configuration verb
* set to JX9_VM_CONFIG_IO_STREAM.
* Currently the JX9 development team is working on the implementation of the http:// and ftp://
* IO stream protocols. These devices will be available in the next major release of the JX9 engine.
* Developers wishing to implement their own IO stream devices must understand and follow
* The JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html).
*/
struct jx9_io_stream
{
const char *zName; /* Underlying stream name [i.e: file/http/zip/jx9, ..] */
int iVersion; /* IO stream structure version [default 1]*/
int (*xOpen)(const char *, int, jx9_value *, void **); /* Open handle*/
int (*xOpenDir)(const char *, jx9_value *, void **); /* Open directory handle */
void (*xClose)(void *); /* Close file handle */
void (*xCloseDir)(void *); /* Close directory handle */
jx9_int64 (*xRead)(void *, void *, jx9_int64); /* Read from the open stream */
int (*xReadDir)(void *, jx9_context *); /* Read entry from directory handle */
jx9_int64 (*xWrite)(void *, const void *, jx9_int64); /* Write to the open stream */
int (*xSeek)(void *, jx9_int64, int); /* Seek on the open stream */
int (*xLock)(void *, int); /* Lock/Unlock the open stream */
void (*xRewindDir)(void *); /* Rewind directory handle */
jx9_int64 (*xTell)(void *); /* Current position of the stream read/write pointer */
int (*xTrunc)(void *, jx9_int64); /* Truncates the open stream to a given length */
int (*xSync)(void *); /* Flush open stream data */
int (*xStat)(void *, jx9_value *, jx9_value *); /* Stat an open stream handle */
};
/*
* C-API-REF: Please refer to the official documentation for interfaces
* purpose and expected parameters.
*/
/* Engine Handling Interfaces */
JX9_PRIVATE int jx9_init(jx9 **ppEngine);
/*JX9_PRIVATE int jx9_config(jx9 *pEngine, int nConfigOp, ...);*/
JX9_PRIVATE int jx9_release(jx9 *pEngine);
/* Compile Interfaces */
JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm);
JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm);
/* Virtual Machine Handling Interfaces */
JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...);
/*JX9_PRIVATE int jx9_vm_exec(jx9_vm *pVm, int *pExitStatus);*/
/*JX9_PRIVATE jx9_value * jx9_vm_extract_variable(jx9_vm *pVm,const char *zVarname);*/
/*JX9_PRIVATE int jx9_vm_reset(jx9_vm *pVm);*/
JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm);
/*JX9_PRIVATE int jx9_vm_dump_v2(jx9_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData);*/
/* In-process Extending Interfaces */
JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData);
/*JX9_PRIVATE int jx9_delete_function(jx9_vm *pVm, const char *zName);*/
JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData);
/*JX9_PRIVATE int jx9_delete_constant(jx9_vm *pVm, const char *zName);*/
/* Foreign Function Parameter Values */
JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue);
JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue);
JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue);
JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue);
JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen);
JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue);
JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict);
/* Setting The Result Of A Foreign Function */
JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue);
JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue);
JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool);
JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value);
JX9_PRIVATE int jx9_result_null(jx9_context *pCtx);
JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen);
JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...);
JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue);
JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData);
/* Call Context Handling Interfaces */
JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen);
/*JX9_PRIVATE int jx9_context_output_format(jx9_context *pCtx, const char *zFormat, ...);*/
JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr);
JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...);
JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx);
JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen);
JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx);
JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData);
JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx);
JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx);
JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx);
JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx);
/* Call Context Memory Management Interfaces */
JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease);
JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte);
JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk);
/* On Demand Dynamically Typed Value Object allocation interfaces */
JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm);
JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm);
JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue);
JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx);
JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx);
JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue);
/* Dynamically Typed Value Object Management Interfaces */
JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue);
JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue);
JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool);
JX9_PRIVATE int jx9_value_null(jx9_value *pVal);
JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value);
JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen);
JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...);
JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal);
JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData);
JX9_PRIVATE int jx9_value_release(jx9_value *pVal);
/* JSON Array/Object Management Interfaces */
JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte);
JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData);
JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue);
JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue);
JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray);
/* Dynamically Typed Value Object Query Interfaces */
JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal);
JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal);
/* Global Library Management Interfaces */
/*JX9_PRIVATE int jx9_lib_init(void);*/
JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...);
JX9_PRIVATE int jx9_lib_shutdown(void);
/*JX9_PRIVATE int jx9_lib_is_threadsafe(void);*/
/*JX9_PRIVATE const char * jx9_lib_version(void);*/
JX9_PRIVATE const char * jx9_lib_signature(void);
/*JX9_PRIVATE const char * jx9_lib_ident(void);*/
/*JX9_PRIVATE const char * jx9_lib_copyright(void);*/
#endif /* _JX9H_ */
/*
* ----------------------------------------------------------
* File: jx9Int.h
* MD5: fb8dffc8ba1425a139091aa145067e16
* ----------------------------------------------------------
*/
/*
* Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
* Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
* Version 1.7.2
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://jx9.symisc.net/
*/
/* $SymiscID: jx9Int.h v1.9 FreeBSD 2012-08-13 23:25 devel <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_ABORT = -1, /* deadliness error should halt script execution. */
E_ERROR = 1, /* Fatal run-time errors. These indicate errors that can not be recovered
* from, such as a memory allocation problem. Execution of the script is
* halted.
* The only fatal error under JX9 is an out-of-memory. All others erros
* even a call to undefined function will not halt script execution.
*/
E_WARNING , /* Run-time warnings (non-fatal errors). Execution of the script is not halted. */
E_PARSE , /* Compile-time parse errors. Parse errors should only be generated by the parser.*/
E_NOTICE , /* Run-time notices. Indicate that the script encountered something that could
* indicate an error, but could also happen in the normal course of running a script.
*/
};
/*
* Each VM instruction resulting from compiling a JX9 script is represented
* by one of the following OP codes.
* The program consists of a linear sequence of operations. Each operation
* has an opcode and 3 operands.Operands P1 is an integer.
* Operand P2 is an unsigned integer and operand P3 is a memory address.
* Few opcodes use all 3 operands.
*/
enum jx9_vm_op {
JX9_OP_DONE = 1, /* Done */
JX9_OP_HALT, /* Halt */
JX9_OP_LOAD, /* Load memory object */
JX9_OP_LOADC, /* Load constant */
JX9_OP_LOAD_IDX, /* Load array entry */
JX9_OP_LOAD_MAP, /* Load hashmap('array') */
JX9_OP_NOOP, /* NOOP */
JX9_OP_JMP, /* Unconditional jump */
JX9_OP_JZ, /* Jump on zero (FALSE jump) */
JX9_OP_JNZ, /* Jump on non-zero (TRUE jump) */
JX9_OP_POP, /* Stack POP */
JX9_OP_CAT, /* Concatenation */
JX9_OP_CVT_INT, /* Integer cast */
JX9_OP_CVT_STR, /* String cast */
JX9_OP_CVT_REAL, /* Float cast */
JX9_OP_CALL, /* Function call */
JX9_OP_UMINUS, /* Unary minus '-'*/
JX9_OP_UPLUS, /* Unary plus '+'*/
JX9_OP_BITNOT, /* Bitwise not '~' */
JX9_OP_LNOT, /* Logical not '!' */
JX9_OP_MUL, /* Multiplication '*' */
JX9_OP_DIV, /* Division '/' */
JX9_OP_MOD, /* Modulus '%' */
JX9_OP_ADD, /* Add '+' */
JX9_OP_SUB, /* Sub '-' */
JX9_OP_SHL, /* Left shift '<<' */
JX9_OP_SHR, /* Right shift '>>' */
JX9_OP_LT, /* Less than '<' */
JX9_OP_LE, /* Less or equal '<=' */
JX9_OP_GT, /* Greater than '>' */
JX9_OP_GE, /* Greater or equal '>=' */
JX9_OP_EQ, /* Equal '==' */
JX9_OP_NEQ, /* Not equal '!=' */
JX9_OP_TEQ, /* Type equal '===' */
JX9_OP_TNE, /* Type not equal '!==' */
JX9_OP_BAND, /* Bitwise and '&' */
JX9_OP_BXOR, /* Bitwise xor '^' */
JX9_OP_BOR, /* Bitwise or '|' */
JX9_OP_LAND, /* Logical and '&&','and' */
JX9_OP_LOR, /* Logical or '||','or' */
JX9_OP_LXOR, /* Logical xor 'xor' */
JX9_OP_STORE, /* Store Object */
JX9_OP_STORE_IDX, /* Store indexed object */
JX9_OP_PULL, /* Stack pull */
JX9_OP_SWAP, /* Stack swap */
JX9_OP_YIELD, /* Stack yield */
JX9_OP_CVT_BOOL, /* Boolean cast */
JX9_OP_CVT_NUMC, /* Numeric (integer, real or both) type cast */
JX9_OP_INCR, /* Increment ++ */
JX9_OP_DECR, /* Decrement -- */
JX9_OP_ADD_STORE, /* Add and store '+=' */
JX9_OP_SUB_STORE, /* Sub and store '-=' */
JX9_OP_MUL_STORE, /* Mul and store '*=' */
JX9_OP_DIV_STORE, /* Div and store '/=' */
JX9_OP_MOD_STORE, /* Mod and store '%=' */
JX9_OP_CAT_STORE, /* Cat and store '.=' */
JX9_OP_SHL_STORE, /* Shift left and store '>>=' */
JX9_OP_SHR_STORE, /* Shift right and store '<<=' */
JX9_OP_BAND_STORE, /* Bitand and store '&=' */
JX9_OP_BOR_STORE, /* Bitor and store '|=' */
JX9_OP_BXOR_STORE, /* Bitxor and store '^=' */
JX9_OP_CONSUME, /* Consume VM output */
JX9_OP_MEMBER, /* Object member run-time access */
JX9_OP_UPLINK, /* Run-Time frame link */
JX9_OP_CVT_NULL, /* NULL cast */
JX9_OP_CVT_ARRAY, /* Array cast */
JX9_OP_FOREACH_INIT, /* For each init */
JX9_OP_FOREACH_STEP, /* For each step */
JX9_OP_SWITCH /* Switch operation */
};
/* -- END-OF INSTRUCTIONS -- */
/*
* Expression Operators ID.
*/
enum jx9_expr_id {
EXPR_OP_DOT, /* Member access */
EXPR_OP_DC, /* :: */
EXPR_OP_SUBSCRIPT, /* []: Subscripting */
EXPR_OP_FUNC_CALL, /* func_call() */
EXPR_OP_INCR, /* ++ */
EXPR_OP_DECR, /* -- */
EXPR_OP_BITNOT, /* ~ */
EXPR_OP_UMINUS, /* Unary minus */
EXPR_OP_UPLUS, /* Unary plus */
EXPR_OP_TYPECAST, /* Type cast [i.e: (int), (float), (string)...] */
EXPR_OP_ALT, /* @ */
EXPR_OP_INSTOF, /* instanceof */
EXPR_OP_LOGNOT, /* logical not ! */
EXPR_OP_MUL, /* Multiplication */
EXPR_OP_DIV, /* division */
EXPR_OP_MOD, /* Modulus */
EXPR_OP_ADD, /* Addition */
EXPR_OP_SUB, /* Substraction */
EXPR_OP_DDOT, /* Concatenation */
EXPR_OP_SHL, /* Left shift */
EXPR_OP_SHR, /* Right shift */
EXPR_OP_LT, /* Less than */
EXPR_OP_LE, /* Less equal */
EXPR_OP_GT, /* Greater than */
EXPR_OP_GE, /* Greater equal */
EXPR_OP_EQ, /* Equal == */
EXPR_OP_NE, /* Not equal != <> */
EXPR_OP_TEQ, /* Type equal === */
EXPR_OP_TNE, /* Type not equal !== */
EXPR_OP_SEQ, /* String equal 'eq' */
EXPR_OP_SNE, /* String not equal 'ne' */
EXPR_OP_BAND, /* Biwise and '&' */
EXPR_OP_REF, /* Reference operator '&' */
EXPR_OP_XOR, /* bitwise xor '^' */
EXPR_OP_BOR, /* bitwise or '|' */
EXPR_OP_LAND, /* Logical and '&&','and' */
EXPR_OP_LOR, /* Logical or '||','or'*/
EXPR_OP_LXOR, /* Logical xor 'xor' */
EXPR_OP_QUESTY, /* Ternary operator '?' */
EXPR_OP_ASSIGN, /* Assignment '=' */
EXPR_OP_ADD_ASSIGN, /* Combined operator: += */
EXPR_OP_SUB_ASSIGN, /* Combined operator: -= */
EXPR_OP_MUL_ASSIGN, /* Combined operator: *= */
EXPR_OP_DIV_ASSIGN, /* Combined operator: /= */
EXPR_OP_MOD_ASSIGN, /* Combined operator: %= */
EXPR_OP_DOT_ASSIGN, /* Combined operator: .= */
EXPR_OP_AND_ASSIGN, /* Combined operator: &= */
EXPR_OP_OR_ASSIGN, /* Combined operator: |= */
EXPR_OP_XOR_ASSIGN, /* Combined operator: ^= */
EXPR_OP_SHL_ASSIGN, /* Combined operator: <<= */
EXPR_OP_SHR_ASSIGN, /* Combined operator: >>= */
EXPR_OP_COMMA /* Comma expression */
};
/*
* Lexer token codes
* The following set of constants are the tokens recognized
* by the lexer when processing JX9 input.
* Important: Token values MUST BE A POWER OF TWO.
*/
#define JX9_TK_INTEGER 0x0000001 /* Integer */
#define JX9_TK_REAL 0x0000002 /* Real number */
#define JX9_TK_NUM (JX9_TK_INTEGER|JX9_TK_REAL) /* Numeric token, either integer or real */
#define JX9_TK_KEYWORD 0x0000004 /* Keyword [i.e: while, for, if, foreach...] */
#define JX9_TK_ID 0x0000008 /* Alphanumeric or UTF-8 stream */
#define JX9_TK_DOLLAR 0x0000010 /* '$' Dollar sign */
#define JX9_TK_OP 0x0000020 /* Operator [i.e: +, *, /...] */
#define JX9_TK_OCB 0x0000040 /* Open curly brace'{' */
#define JX9_TK_CCB 0x0000080 /* Closing curly brace'}' */
#define JX9_TK_DOT 0x0000100 /* Dot . */
#define JX9_TK_LPAREN 0x0000200 /* Left parenthesis '(' */
#define JX9_TK_RPAREN 0x0000400 /* Right parenthesis ')' */
#define JX9_TK_OSB 0x0000800 /* Open square bracket '[' */
#define JX9_TK_CSB 0x0001000 /* Closing square bracket ']' */
#define JX9_TK_DSTR 0x0002000 /* Double quoted string "$str" */
#define JX9_TK_SSTR 0x0004000 /* Single quoted string 'str' */
#define JX9_TK_NOWDOC 0x0010000 /* Nowdoc <<< */
#define JX9_TK_COMMA 0x0020000 /* Comma ',' */
#define JX9_TK_SEMI 0x0040000 /* Semi-colon ";" */
#define JX9_TK_BSTR 0x0080000 /* Backtick quoted string [i.e: Shell command `date`] */
#define JX9_TK_COLON 0x0100000 /* single Colon ':' */
#define JX9_TK_AMPER 0x0200000 /* Ampersand '&' */
#define JX9_TK_EQUAL 0x0400000 /* Equal '=' */
#define JX9_TK_OTHER 0x1000000 /* Other symbols */
/*
* JX9 keyword.
* These words have special meaning in JX9. Some of them represent things which look like
* functions, some look like constants, and so on, but they're not, really: they are language constructs.
* You cannot use any of the following words as constants, object names, function or method names.
* Using them as variable names is generally OK, but could lead to confusion.
*/
#define JX9_TKWRD_SWITCH 1 /* switch */
#define JX9_TKWRD_PRINT 2 /* print */
#define JX9_TKWRD_ELIF 0x4000000 /* elseif: MUST BE A POWER OF TWO */
#define JX9_TKWRD_ELSE 0x8000000 /* else: MUST BE A POWER OF TWO */
#define JX9_TKWRD_IF 3 /* if */
#define JX9_TKWRD_STATIC 4 /* static */
#define JX9_TKWRD_CASE 5 /* case */
#define JX9_TKWRD_FUNCTION 6 /* function */
#define JX9_TKWRD_CONST 7 /* const */
/* The number '8' is reserved for JX9_TK_ID */
#define JX9_TKWRD_WHILE 9 /* while */
#define JX9_TKWRD_DEFAULT 10 /* default */
#define JX9_TKWRD_AS 11 /* as */
#define JX9_TKWRD_CONTINUE 12 /* continue */
#define JX9_TKWRD_EXIT 13 /* exit */
#define JX9_TKWRD_DIE 14 /* die */
#define JX9_TKWRD_IMPORT 15 /* import */
#define JX9_TKWRD_INCLUDE 16 /* include */
#define JX9_TKWRD_FOR 17 /* for */
#define JX9_TKWRD_FOREACH 18 /* foreach */
#define JX9_TKWRD_RETURN 19 /* return */
#define JX9_TKWRD_BREAK 20 /* break */
#define JX9_TKWRD_UPLINK 21 /* uplink */
#define JX9_TKWRD_BOOL 0x8000 /* bool: MUST BE A POWER OF TWO */
#define JX9_TKWRD_INT 0x10000 /* int: MUST BE A POWER OF TWO */
#define JX9_TKWRD_FLOAT 0x20000 /* float: MUST BE A POWER OF TWO */
#define JX9_TKWRD_STRING 0x40000 /* string: MUST BE A POWER OF TWO */
/* api.c */
JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap);
JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName);
JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName);
/* json.c function prototypes */
JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut);
JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte);
/* memobj.c function prototypes */
JX9_PRIVATE sxi32 jx9MemObjDump(SyBlob *pOut, jx9_value *pObj);
JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal);
JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore);
JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest);
JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal);
JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray);
#if 0
/* Not used in the current release of the JX9 engine */
JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal);
#endif
JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal);
JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal);
JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen);
#if 0
/* Not used in the current release of the JX9 engine */
JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap);
#endif
JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest);
JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest);
JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj);
JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags);
JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj);
JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj);
JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pData);
/* lex.c function prototypes */
JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput, sxu32 nLen, SySet *pOut);
/* vm.c function prototypes */
JX9_PRIVATE void jx9VmReleaseContextValue(jx9_context *pCtx, jx9_value *pValue);
JX9_PRIVATE sxi32 jx9VmInitFuncState(jx9_vm *pVm, jx9_vm_func *pFunc, const char *zName, sxu32 nByte,
sxi32 iFlags, void *pUserData);
JX9_PRIVATE sxi32 jx9VmInstallUserFunction(jx9_vm *pVm, jx9_vm_func *pFunc, SyString *pName);
JX9_PRIVATE sxi32 jx9VmRegisterConstant(jx9_vm *pVm, const SyString *pName, ProcConstant xExpand, void *pUserData);
JX9_PRIVATE sxi32 jx9VmInstallForeignFunction(jx9_vm *pVm, const SyString *pName, ProcHostFunction xFunc, void *pUserData);
JX9_PRIVATE sxi32 jx9VmBlobConsumer(const void *pSrc, unsigned int nLen, void *pUserData);
JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIndex);
JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex);
JX9_PRIVATE sxi32 jx9VmOutputConsume(jx9_vm *pVm, SyString *pString);
JX9_PRIVATE sxi32 jx9VmOutputConsumeAp(jx9_vm *pVm, const char *zFormat, va_list ap);
JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap);
JX9_PRIVATE sxi32 jx9VmThrowError(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zMessage);
JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData);
JX9_PRIVATE sxi32 jx9VmDump(jx9_vm *pVm, ProcConsumer xConsumer, void *pUserData);
JX9_PRIVATE sxi32 jx9VmInit(jx9_vm *pVm, jx9 *pEngine);
JX9_PRIVATE sxi32 jx9VmConfigure(jx9_vm *pVm, sxi32 nOp, va_list ap);
JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm);
JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar);
JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm);
JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm);
JX9_PRIVATE sxi32 jx9VmMakeReady(jx9_vm *pVm);
JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm);
JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm);
JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm);
JX9_PRIVATE VmInstr *jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex);
JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm);
JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer);
JX9_PRIVATE sxi32 jx9VmEmitInstr(jx9_vm *pVm, sxi32 iOp, sxi32 iP1, sxu32 iP2, void *p3, sxu32 *pIndex);
JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm);
JX9_PRIVATE sxi32 jx9VmCallUserFunction(jx9_vm *pVm, jx9_value *pFunc, int nArg, jx9_value **apArg, jx9_value *pResult);
JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp(jx9_vm *pVm, jx9_value *pFunc, jx9_value *pResult, ...);
JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm, sxu32 nObjIdx);
JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen);
JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue);
JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew);
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(jx9_vm *pVm, const char **pzDevice, int nByte);
#endif /* JX9_DISABLE_BUILTIN_FUNC */
JX9_PRIVATE int jx9Utf8Read(
const unsigned char *z, /* First byte of UTF-8 character */
const unsigned char *zTerm, /* Pretend this byte is 0x00 */
const unsigned char **pzNext /* Write first byte past UTF-8 char here */
);
/* parse.c function prototypes */
JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID);
JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot);
JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart, SyToken *pEnd, SyToken **ppNext);
JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn, SyToken *pEnd, sxu32 nTokStart, sxu32 nTokEnd, SyToken **ppEnd);
JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast);
JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet);
/* compile.c function prototypes */
JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType);
JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen, sxi32 iCompileFlag);
JX9_PRIVATE sxi32 jx9InitCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData);
JX9_PRIVATE sxi32 jx9ResetCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData);
JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...);
JX9_PRIVATE sxi32 jx9CompileScript(jx9_vm *pVm, SyString *pScript, sxi32 iFlags);
/* constant.c function prototypes */
JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm);
/* builtin.c function prototypes */
JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm);
/* hashmap.c function prototypes */
JX9_PRIVATE jx9_hashmap * jx9NewHashmap(jx9_vm *pVm, sxu32 (*xIntHash)(sxi64), sxu32 (*xBlobHash)(const void *, sxu32));
JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm);
JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS);
JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap);
JX9_PRIVATE sxi32 jx9HashmapLookup(jx9_hashmap *pMap, jx9_value *pKey, jx9_hashmap_node **ppNode);
JX9_PRIVATE sxi32 jx9HashmapInsert(jx9_hashmap *pMap, jx9_value *pKey, jx9_value *pVal);
JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight);
JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest);
JX9_PRIVATE sxi32 jx9HashmapCmp(jx9_hashmap *pLeft, jx9_hashmap *pRight, int bStrict);
JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap);
JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap);
JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode);
JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore);
JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode, jx9_value *pKey);
JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm);
JX9_PRIVATE sxi32 jx9HashmapWalk(jx9_hashmap *pMap, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData);
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut);
/* builtin.c function prototypes */
JX9_PRIVATE sxi32 jx9InputFormat(int (*xConsumer)(jx9_context *, const char *, int, void *),
jx9_context *pCtx, const char *zIn, int nByte, int nArg, jx9_value **apArg, void *pUserData, int vf);
JX9_PRIVATE sxi32 jx9ProcessCsv(const char *zInput, int nByte, int delim, int encl,
int escape, sxi32 (*xConsumer)(const char *, int, void *), void *pUserData);
JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData);
JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen);
JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection);
#endif
/* vfs.c */
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile,
int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew);
JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut);
JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle);
#endif /* JX9_DISABLE_BUILTIN_FUNC */
JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen);
JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm);
JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void);
JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm);
JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm);
JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm);
/* lib.c function prototypes */
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp);
JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch);
JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch);
JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry);
JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen);
#endif /* JX9_DISABLE_BUILTIN_FUNC */
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData);
#endif /* JX9_DISABLE_BUILTIN_FUNC */
#ifndef JX9_DISABLE_BUILTIN_FUNC
#ifndef JX9_DISABLE_HASH_FUNC
JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen);
JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len);
JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx);
JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx);
JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]);
JX9_PRIVATE void SHA1Init(SHA1Context *context);
JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len);
JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]);
JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]);
#endif
#endif /* JX9_DISABLE_BUILTIN_FUNC */
JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen);
JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void *pUserData);
JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...);
JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap);
JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...);
JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...);
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE const char *SyTimeGetMonth(sxi32 iMonth);
JX9_PRIVATE const char *SyTimeGetDay(sxi32 iDay);
#endif /* JX9_DISABLE_BUILTIN_FUNC */
JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8);
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData);
#endif
JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex);
JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp);
JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData);
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData);
JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData);
#endif /* JX9_DISABLE_BUILTIN_FUNC */
JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen);
JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
JX9_PRIVATE sxi32 SyHexToint(sxi32 c);
JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail);
JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData);
JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32(*xStep)(SyHashEntry *, void *), void *pUserData);
JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData);
JX9_PRIVATE SyHashEntry *SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen);
JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash);
JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp);
JX9_PRIVATE void *SySetAt(SySet *pSet, sxu32 nIdx);
JX9_PRIVATE void *SySetPop(SySet *pSet);
JX9_PRIVATE void *SySetPeek(SySet *pSet);
JX9_PRIVATE sxi32 SySetRelease(SySet *pSet);
JX9_PRIVATE sxi32 SySetReset(SySet *pSet);
JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet);
JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry);
JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem);
JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem);
JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize);
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft);
#endif
JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob);
JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob);
JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen);
JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest);
JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob);
JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize);
JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte);
JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator);
JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize);
JX9_PRIVATE char *SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize);
JX9_PRIVATE void *SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize);
JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend);
JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void *pUserData);
JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void *pUserData);
JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent);
#if 0
/* Not used in the current release of the JX9 engine */
JX9_PRIVATE void *SyMemBackendPoolRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte);
#endif
JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void *pChunk);
JX9_PRIVATE void *SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte);
JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void *pChunk);
JX9_PRIVATE void *SyMemBackendRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte);
JX9_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte);
JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen);
JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize);
JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize);
JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen);
JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen);
#if !defined(JX9_DISABLE_BUILTIN_FUNC) || defined(__APPLE__)
JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen);
#endif
JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos);
#ifndef JX9_DISABLE_BUILTIN_FUNC
JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos);
#endif
JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos);
JX9_PRIVATE sxu32 SyStrlen(const char *zSrc);
#if defined(JX9_ENABLE_THREADS)
JX9_PRIVATE const SyMutexMethods *SyMutexExportMethods(void);
JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods);
JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend);
#endif
JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb);
JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB);
JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb);
JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB);
JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64);
JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64);
JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64);
JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32);
JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16);
JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut);
JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut);
#endif /* __JX9INT_H__ */
/*
* ----------------------------------------------------------
* File: unqliteInt.h
* MD5: 325816ce05f6adbaab2c39a41875dedd
* ----------------------------------------------------------
*/
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
* Version 1.1.6
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://unqlite.org/licensing.html
*/
/* $SymiscID: unqliteInt.h v1.7 FreeBSD 2012-11-02 11:25 devel <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_EXISTS 0x002 /* Exists old collection */
#define UNQLITE_VM_AUTO_LOAD 0x004 /* Auto load a collection from the vfs */
/* Forward declaration */
typedef struct unqlite_col_record unqlite_col_record;
typedef struct unqlite_col unqlite_col;
/*
* Each an in-memory collection record is stored in an instance
* of the following structure.
*/
struct unqlite_col_record
{
unqlite_col *pCol; /* Collecion this record belong */
jx9_int64 nId; /* Unique record ID */
jx9_value sValue; /* In-memory value of the record */
unqlite_col_record *pNextCol,*pPrevCol; /* Collision chain */
unqlite_col_record *pNext,*pPrev; /* Linked list of records */
};
/*
* Magic number to identify a valid collection on disk.
*/
#define UNQLITE_COLLECTION_MAGIC 0x611E /* sizeof(unsigned short) 2 bytes */
/*
* A loaded collection is identified by an instance of the following structure.
*/
struct unqlite_col
{
unqlite_vm *pVm; /* VM that own this instance */
SyString sName; /* ID of the collection */
sxu32 nHash; /* sName hash */
jx9_value sSchema; /* Collection schema */
sxu32 nSchemaOfft; /* Shema offset in sHeader */
SyBlob sWorker; /* General purpose working buffer */
SyBlob sHeader; /* Collection binary header */
jx9_int64 nLastid; /* Last collection record ID */
jx9_int64 nCurid; /* Current record ID */
jx9_int64 nTotRec; /* Total number of records in the collection */
int iFlags; /* Control flags (see below) */
unqlite_col_record **apRecord; /* Hashtable of loaded records */
unqlite_col_record *pList; /* Linked list of records */
sxu32 nRec; /* Total number of records in apRecord[] */
sxu32 nRecSize; /* apRecord[] size */
Sytm sCreation; /* Colleation creation time */
unqlite_kv_cursor *pCursor; /* Cursor pointing to the raw binary data */
unqlite_col *pNext,*pPrev; /* Next and previous collection in the chain */
unqlite_col *pNextCol,*pPrevCol; /* Collision chain */
};
/*
* Each unQLite Virtual Machine resulting from successful compilation of
* a Jx9 script is represented by an instance of the following structure.
*/
struct unqlite_vm
{
unqlite *pDb; /* Database handle that own this instance */
SyMemBackend sAlloc; /* Private memory allocator */
#if defined(UNQLITE_ENABLE_THREADS)
SyMutex *pMutex; /* Recursive mutex associated with this VM. */
#endif
unqlite_col **apCol; /* Table of loaded collections */
unqlite_col *pCol; /* List of loaded collections */
sxu32 iCol; /* Total number of loaded collections */
sxu32 iColSize; /* apCol[] size */
jx9_vm *pJx9Vm; /* Compiled Jx9 script*/
unqlite_vm *pNext,*pPrev; /* Linked list of active unQLite VM */
sxu32 nMagic; /* Magic number to avoid misuse */
};
/*
* Database signature to identify a valid database image.
*/
#define UNQLITE_DB_SIG "unqlite"
/*
* Database magic number (4 bytes).
*/
#define UNQLITE_DB_MAGIC 0xDB7C2712
/*
* Maximum page size in bytes.
*/
#ifdef UNQLITE_MAX_PAGE_SIZE
# undef UNQLITE_MAX_PAGE_SIZE
#endif
#define UNQLITE_MAX_PAGE_SIZE 65536 /* 65K */
/*
* Minimum page size in bytes.
*/
#ifdef UNQLITE_MIN_PAGE_SIZE
# undef UNQLITE_MIN_PAGE_SIZE
#endif
#define UNQLITE_MIN_PAGE_SIZE 512
/*
* The default size of a database page.
*/
#ifndef UNQLITE_DEFAULT_PAGE_SIZE
# undef UNQLITE_DEFAULT_PAGE_SIZE
#endif
# define UNQLITE_DEFAULT_PAGE_SIZE 4096 /* 4K */
/* Forward declaration */
typedef struct Bitvec Bitvec;
/* Private library functions */
/* api.c */
UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void);
UNQLITE_PRIVATE int unqliteDataConsumer(
const void *pOut, /* Data to consume */
unsigned int nLen, /* Data length */
void *pUserData /* User private data */
);
UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore(
const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */
sxu32 nByte /* zName length */
);
UNQLITE_PRIVATE int unqliteGetPageSize(void);
UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr);
UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...);
UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb);
/* unql_vm.c */
UNQLITE_PRIVATE int unqliteExistsCollection(unqlite_vm *pVm, SyString *pName);
UNQLITE_PRIVATE int unqliteCreateCollection(unqlite_vm *pVm,SyString *pName);
UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol);
UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol);
UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(unqlite_col *pCol,jx9_int64 nId);
UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol);
UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol);
UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue);
UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(unqlite_col *pCol,jx9_int64 nId,jx9_value *pValue);
UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(unqlite_vm *pVm,SyString *pCol,int iFlag);
UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue);
UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag);
UNQLITE_PRIVATE int unqliteCollectionDropRecord(unqlite_col *pCol,jx9_int64 nId,int wr_header,int log_err);
UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol);
/* unql_jx9.c */
UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm);
/* fastjson.c */
UNQLITE_PRIVATE sxi32 FastJsonEncode(
jx9_value *pValue, /* Value to encode */
SyBlob *pOut, /* Store encoded value here */
int iNest /* Nesting limit */
);
UNQLITE_PRIVATE sxi32 FastJsonDecode(
const void *pIn, /* Binary JSON */
sxu32 nByte, /* Chunk delimiter */
jx9_value *pOut, /* Decoded value */
const unsigned char **pzPtr,
int iNest /* Nesting limit */
);
/* vfs.c [io_win.c, io_unix.c ] */
UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void);
/* mem_kv.c */
UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void);
/* lhash_kv.c */
UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void);
/* os.c */
UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset);
UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset);
UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size);
UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags);
UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize);
UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType);
UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType);
UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut);
UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id);
UNQLITE_PRIVATE int unqliteOsOpen(
unqlite_vfs *pVfs,
SyMemBackend *pAlloc,
const char *zPath,
unqlite_file **ppOut,
unsigned int flags
);
UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId);
UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync);
UNQLITE_PRIVATE int unqliteOsAccess(unqlite_vfs *pVfs,const char *zPath,int flags,int *pResOut);
/* bitmap.c */
UNQLITE_PRIVATE Bitvec *unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize);
UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i);
UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i);
UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p);
/* pager.c */
UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut);
UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur);
UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage);
UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager);
UNQLITE_PRIVATE int unqlitePagerOpen(
unqlite_vfs *pVfs, /* The virtual file system to use */
unqlite *pDb, /* Database handle */
const char *zFilename, /* Name of the database file to open */
unsigned int iFlags /* flags controlling this file */
);
UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods);
UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb);
UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager);
UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager);
UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine);
UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen);
UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager);
#endif /* __UNQLITEINT_H__ */
/*
* ----------------------------------------------------------
* File: api.c
* MD5: d79e8404e50dacd0ea75635c1ebe553a
* ----------------------------------------------------------
*/
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
* Version 1.1.6
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://unqlite.org/licensing.html
*/
/* $SymiscID: api.c v2.0 FreeBSD 2012-11-08 23:07 stable <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 */
}
if( sUnqlMPGlobal.pVfs == 0 ){
/* Point to the built-in vfs */
pVfs = unqliteExportBuiltinVfs();
/* Install it */
unqlite_lib_config(UNQLITE_LIB_CONFIG_VFS, pVfs);
}
#if defined(UNQLITE_ENABLE_THREADS)
if( sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_SINGLE ){
pMutexMethods = sUnqlMPGlobal.pMutexMethods;
if( pMutexMethods == 0 ){
/* Use the built-in mutex subsystem */
pMutexMethods = SyMutexExportMethods();
if( pMutexMethods == 0 ){
return UNQLITE_CORRUPT; /* Can't happen */
}
/* Install the mutex subsystem */
rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MUTEX, pMutexMethods);
if( rc != UNQLITE_OK ){
return rc;
}
}
/* Obtain a static mutex so we can initialize the library without calling malloc() */
pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1);
if( pMaster == 0 ){
return UNQLITE_CORRUPT; /* Can't happen */
}
}
/* Lock the master mutex */
rc = UNQLITE_OK;
SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){
#endif
if( sUnqlMPGlobal.sAllocator.pMethods == 0 ){
/* Install a memory subsystem */
rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */
if( rc != UNQLITE_OK ){
/* If we are unable to initialize the memory backend, there is no much we can do here.*/
goto End;
}
}
#if defined(UNQLITE_ENABLE_THREADS)
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){
/* Protect the memory allocation subsystem */
rc = SyMemBackendMakeThreadSafe(&sUnqlMPGlobal.sAllocator, sUnqlMPGlobal.pMutexMethods);
if( rc != UNQLITE_OK ){
goto End;
}
}
#endif
SySetInit(&sUnqlMPGlobal.kv_storage,&sUnqlMPGlobal.sAllocator,sizeof(unqlite_kv_methods *));
/* Install the built-in Key Value storage engines */
pMethods = unqliteExportMemKvStorage(); /* In-memory storage */
unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods);
/* Default disk key/value storage engine */
pMethods = unqliteExportDiskKvStorage(); /* Disk storage */
unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods);
/* Default page size */
if( sUnqlMPGlobal.iPageSize < UNQLITE_MIN_PAGE_SIZE ){
unqlite_lib_config(UNQLITE_LIB_CONFIG_PAGE_SIZE,UNQLITE_DEFAULT_PAGE_SIZE);
}
/* Our library is initialized, set the magic number */
sUnqlMPGlobal.nMagic = UNQLITE_LIB_MAGIC;
rc = UNQLITE_OK;
#if defined(UNQLITE_ENABLE_THREADS)
} /* sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC */
#endif
End:
#if defined(UNQLITE_ENABLE_THREADS)
/* Unlock the master mutex */
SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
#endif
return rc;
}
/* Forward declaration */
static int unqliteVmRelease(unqlite_vm *pVm);
/*
* Release a single instance of an unqlite database handle.
*/
static int unqliteDbRelease(unqlite *pDb)
{
unqlite_db *pStore = &pDb->sDB;
unqlite_vm *pVm,*pNext;
int rc = UNQLITE_OK;
if( (pDb->iFlags & UNQLITE_FL_DISABLE_AUTO_COMMIT) == 0 ){
/* Commit any outstanding transaction */
rc = unqlitePagerCommit(pStore->pPager);
if( rc != UNQLITE_OK ){
/* Rollback the transaction */
rc = unqlitePagerRollback(pStore->pPager,FALSE);
}
}else{
/* Rollback any outstanding transaction */
rc = unqlitePagerRollback(pStore->pPager,FALSE);
}
/* Close the pager */
unqlitePagerClose(pStore->pPager);
/* Release any active VM's */
pVm = pDb->pVms;
for(;;){
if( pDb->iVm < 1 ){
break;
}
/* Point to the next entry */
pNext = pVm->pNext;
unqliteVmRelease(pVm);
pVm = pNext;
pDb->iVm--;
}
/* Release the Jx9 handle */
jx9_release(pStore->pJx9);
/* Set a dummy magic number */
pDb->nMagic = 0x7250;
/* Release the whole memory subsystem */
SyMemBackendRelease(&pDb->sMem);
/* Commit or rollback result */
return rc;
}
/*
* Release all resources consumed by the library.
* Note: This call is not thread safe. Refer to [unqlite_lib_shutdown()].
*/
static void unqliteCoreShutdown(void)
{
unqlite *pDb, *pNext;
/* Release all active databases handles */
pDb = sUnqlMPGlobal.pDB;
for(;;){
if( sUnqlMPGlobal.nDB < 1 ){
break;
}
pNext = pDb->pNext;
unqliteDbRelease(pDb);
pDb = pNext;
sUnqlMPGlobal.nDB--;
}
/* Release the storage methods container */
SySetRelease(&sUnqlMPGlobal.kv_storage);
#if defined(UNQLITE_ENABLE_THREADS)
/* Release the mutex subsystem */
if( sUnqlMPGlobal.pMutexMethods ){
if( sUnqlMPGlobal.pMutex ){
SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex);
sUnqlMPGlobal.pMutex = 0;
}
if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){
sUnqlMPGlobal.pMutexMethods->xGlobalRelease();
}
sUnqlMPGlobal.pMutexMethods = 0;
}
sUnqlMPGlobal.nThreadingLevel = 0;
#endif
if( sUnqlMPGlobal.sAllocator.pMethods ){
/* Release the memory backend */
SyMemBackendRelease(&sUnqlMPGlobal.sAllocator);
}
sUnqlMPGlobal.nMagic = 0x1764;
/* Finally, shutdown the Jx9 library */
jx9_lib_shutdown();
}
/*
* [CAPIREF: unqlite_lib_init()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_lib_init(void)
{
int rc;
rc = unqliteCoreInitialize();
return rc;
}
/*
* [CAPIREF: unqlite_lib_shutdown()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_lib_shutdown(void)
{
if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){
/* Already shut */
return UNQLITE_OK;
}
unqliteCoreShutdown();
return UNQLITE_OK;
}
/*
* [CAPIREF: unqlite_lib_is_threadsafe()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_lib_is_threadsafe(void)
{
if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){
return 0;
}
#if defined(UNQLITE_ENABLE_THREADS)
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){
/* Muli-threading support is enabled */
return 1;
}else{
/* Single-threading */
return 0;
}
#else
return 0;
#endif
}
/*
*
* [CAPIREF: unqlite_lib_version()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
const char * unqlite_lib_version(void)
{
return UNQLITE_VERSION;
}
/*
*
* [CAPIREF: unqlite_lib_signature()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
const char * unqlite_lib_signature(void)
{
return UNQLITE_SIG;
}
/*
*
* [CAPIREF: unqlite_lib_ident()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
const char * unqlite_lib_ident(void)
{
return UNQLITE_IDENT;
}
/*
*
* [CAPIREF: unqlite_lib_copyright()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
const char * unqlite_lib_copyright(void)
{
return UNQLITE_COPYRIGHT;
}
/*
* Remove harmfull and/or stale flags passed to the [unqlite_open()] interface.
*/
static unsigned int unqliteSanityzeFlag(unsigned int iFlags)
{
iFlags &= ~UNQLITE_OPEN_EXCLUSIVE; /* Reserved flag */
if( iFlags & UNQLITE_OPEN_TEMP_DB ){
/* Omit journaling for temporary database */
iFlags |= UNQLITE_OPEN_OMIT_JOURNALING|UNQLITE_OPEN_CREATE;
}
if( (iFlags & (UNQLITE_OPEN_READONLY|UNQLITE_OPEN_READWRITE)) == 0 ){
/* Auto-append the R+W flag */
iFlags |= UNQLITE_OPEN_READWRITE;
}
if( iFlags & UNQLITE_OPEN_CREATE ){
iFlags &= ~(UNQLITE_OPEN_MMAP|UNQLITE_OPEN_READONLY);
/* Auto-append the R+W flag */
iFlags |= UNQLITE_OPEN_READWRITE;
}else{
if( iFlags & UNQLITE_OPEN_READONLY ){
iFlags &= ~UNQLITE_OPEN_READWRITE;
}else if( iFlags & UNQLITE_OPEN_READWRITE ){
iFlags &= ~UNQLITE_OPEN_MMAP;
}
}
return iFlags;
}
/*
* This routine does the work of initializing a database handle on behalf
* of [unqlite_open()].
*/
static int unqliteInitDatabase(
unqlite *pDB, /* Database handle */
SyMemBackend *pParent, /* Master memory backend */
const char *zFilename, /* Target database */
unsigned int iFlags /* Open flags */
)
{
unqlite_db *pStorage = &pDB->sDB;
int rc;
/* 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;
}
break;
}
default:
/* Unknown configuration option */
rc = UNQLITE_UNKNOWN;
break;
}
return rc;
}
/*
* Export the global (master) memory allocator to submodules.
*/
UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void)
{
return &sUnqlMPGlobal.sAllocator;
}
/*
* [CAPIREF: unqlite_open()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode)
{
unqlite *pHandle;
int rc;
#if defined(UNTRUST)
if( ppDB == 0 ){
return UNQLITE_CORRUPT;
}
#endif
*ppDB = 0;
/* One-time automatic library initialization */
rc = unqliteCoreInitialize();
if( rc != UNQLITE_OK ){
return rc;
}
/* Allocate a new database handle */
pHandle = (unqlite *)SyMemBackendPoolAlloc(&sUnqlMPGlobal.sAllocator, sizeof(unqlite));
if( pHandle == 0 ){
return UNQLITE_NOMEM;
}
/* Zero the structure */
SyZero(pHandle,sizeof(unqlite));
if( iMode < 1 ){
/* Assume a read-only database */
iMode = UNQLITE_OPEN_READONLY|UNQLITE_OPEN_MMAP;
}
/* Init the database */
rc = unqliteInitDatabase(pHandle,&sUnqlMPGlobal.sAllocator,zFilename,iMode);
if( rc != UNQLITE_OK ){
goto Release;
}
#if defined(UNQLITE_ENABLE_THREADS)
if( !(iMode & UNQLITE_OPEN_NOMUTEX) && (sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE) ){
/* Associate a recursive mutex with this instance */
pHandle->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE);
if( pHandle->pMutex == 0 ){
rc = UNQLITE_NOMEM;
goto Release;
}
}
#endif
/* Link to the list of active DB handles */
#if defined(UNQLITE_ENABLE_THREADS)
/* Enter the global mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
#endif
MACRO_LD_PUSH(sUnqlMPGlobal.pDB,pHandle);
sUnqlMPGlobal.nDB++;
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave the global mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
#endif
/* Set the magic number to identify a valid DB handle */
pHandle->nMagic = UNQLITE_DB_MAGIC;
/* Make the handle available to the caller */
*ppDB = pHandle;
return UNQLITE_OK;
Release:
SyMemBackendRelease(&pHandle->sMem);
SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pHandle);
return rc;
}
/*
* [CAPIREF: unqlite_config()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_config(unqlite *pDb,int nConfigOp,...)
{
va_list ap;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
va_start(ap, nConfigOp);
rc = unqliteConfigure(&(*pDb),nConfigOp, ap);
va_end(ap);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_close()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_close(unqlite *pDb)
{
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Release the database handle */
rc = unqliteDbRelease(pDb);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
/* Release DB mutex */
SyMutexRelease(sUnqlMPGlobal.pMutexMethods, pDb->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
#if defined(UNQLITE_ENABLE_THREADS)
/* Enter the global mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
#endif
/* Unlink from the list of active database handles */
MACRO_LD_REMOVE(sUnqlMPGlobal.pDB, pDb);
sUnqlMPGlobal.nDB--;
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave the global mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
#endif
/* Release the memory chunk allocated to this handle */
SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pDb);
return rc;
}
/*
* [CAPIREF: unqlite_compile()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut)
{
jx9_vm *pVm;
int rc;
if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT;
}
#endif
/* Compile the Jx9 script first */
rc = jx9_compile(pDb->sDB.pJx9,zJx9,nByte,&pVm);
if( rc == JX9_OK ){
/* Allocate a new unqlite VM instance */
rc = unqliteInitVm(pDb,pVm,ppOut);
if( rc != UNQLITE_OK ){
/* Release the Jx9 VM */
jx9_vm_release(pVm);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_compile_file()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut)
{
jx9_vm *pVm;
int rc;
if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT;
}
#endif
/* Compile the Jx9 script first */
rc = jx9_compile_file(pDb->sDB.pJx9,zPath,&pVm);
if( rc == JX9_OK ){
/* Allocate a new unqlite VM instance */
rc = unqliteInitVm(pDb,pVm,ppOut);
if( rc != UNQLITE_OK ){
/* Release the Jx9 VM */
jx9_vm_release(pVm);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* Configure an unqlite virtual machine (Mostly Jx9 VM) instance.
*/
static int unqliteVmConfig(unqlite_vm *pVm,sxi32 iOp,va_list ap)
{
int rc;
rc = jx9VmConfigure(pVm->pJx9Vm,iOp,ap);
return rc;
}
/*
* [CAPIREF: unqlite_vm_config()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_vm_config(unqlite_vm *pVm,int iOp,...)
{
va_list ap;
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
va_start(ap,iOp);
rc = unqliteVmConfig(pVm,iOp,ap);
va_end(ap);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_vm_exec()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_vm_exec(unqlite_vm *pVm)
{
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Execute the Jx9 bytecode program */
rc = jx9VmByteCodeExec(pVm->pJx9Vm);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_vm_release()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_vm_release(unqlite_vm *pVm)
{
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Release the VM */
rc = unqliteVmRelease(pVm);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave VM mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
/* Release VM mutex */
SyMutexRelease(sUnqlMPGlobal.pMutexMethods,pVm->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
if( rc == UNQLITE_OK ){
unqlite *pDb = pVm->pDb;
/* Unlink from the list of active VM's */
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
MACRO_LD_REMOVE(pDb->pVms, pVm);
pDb->iVm--;
/* Release the memory chunk allocated to this instance */
SyMemBackendPoolFree(&pDb->sMem,pVm);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
}
return rc;
}
/*
* [CAPIREF: unqlite_vm_reset()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_vm_reset(unqlite_vm *pVm)
{
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Reset the Jx9 VM */
rc = jx9VmReset(pVm->pJx9Vm);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_vm_dump()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData)
{
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Dump the Jx9 VM */
rc = jx9VmDump(pVm->pJx9Vm,xConsumer,pUserData);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_vm_extract_variable()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname)
{
unqlite_value *pValue;
SyString sVariable;
if( UNQLITE_VM_MISUSE(pVm) ){
return 0;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return 0; /* Another thread have released this instance */
}
#endif
/* Extract the target variable */
SyStringInitFromBuf(&sVariable,zVarname,SyStrlen(zVarname));
pValue = jx9VmExtractVariable(pVm->pJx9Vm,&sVariable);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return pValue;
}
/*
* [CAPIREF: unqlite_create_function()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_create_function(unqlite_vm *pVm, const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData)
{
SyString sName;
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
/* Remove leading and trailing white spaces */
SyStringFullTrim(&sName);
/* Ticket 1433-003: NULL values are not allowed */
if( sName.nByte < 1 || xFunc == 0 ){
return UNQLITE_INVALID;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Install the foreign function */
rc = jx9VmInstallForeignFunction(pVm->pJx9Vm,&sName,xFunc,pUserData);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_delete_function()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_delete_function(unqlite_vm *pVm, const char *zName)
{
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Unlink the foreign function */
rc = jx9DeleteFunction(pVm->pJx9Vm,zName);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_create_constant()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData)
{
SyString sName;
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
/* Remove leading and trailing white spaces */
SyStringFullTrim(&sName);
if( sName.nByte < 1 ){
/* Empty constant name */
return UNQLITE_INVALID;
}
/* TICKET 1433-003: NULL pointer is harmless operation */
if( xExpand == 0 ){
return UNQLITE_INVALID;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Install the foreign constant */
rc = jx9VmRegisterConstant(pVm->pJx9Vm,&sName,xExpand,pUserData);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_delete_constant()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_delete_constant(unqlite_vm *pVm, const char *zName)
{
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Unlink the foreign constant */
rc = Jx9DeleteConstant(pVm->pJx9Vm,zName);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_value_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_int(unqlite_value *pVal, int iValue)
{
return jx9_value_int(pVal,iValue);
}
/*
* [CAPIREF: unqlite_value_int64()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_int64(unqlite_value *pVal,unqlite_int64 iValue)
{
return jx9_value_int64(pVal,iValue);
}
/*
* [CAPIREF: unqlite_value_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_bool(unqlite_value *pVal, int iBool)
{
return jx9_value_bool(pVal,iBool);
}
/*
* [CAPIREF: unqlite_value_null()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_null(unqlite_value *pVal)
{
return jx9_value_null(pVal);
}
/*
* [CAPIREF: unqlite_value_double()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_double(unqlite_value *pVal, double Value)
{
return jx9_value_double(pVal,Value);
}
/*
* [CAPIREF: unqlite_value_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen)
{
return jx9_value_string(pVal,zString,nLen);
}
/*
* [CAPIREF: unqlite_value_string_format()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...)
{
va_list ap;
int rc;
if((pVal->iFlags & MEMOBJ_STRING) == 0 ){
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
MemObjSetType(pVal, MEMOBJ_STRING);
}
va_start(ap, zFormat);
rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap);
va_end(ap);
return UNQLITE_OK;
}
/*
* [CAPIREF: unqlite_value_reset_string_cursor()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_reset_string_cursor(unqlite_value *pVal)
{
return jx9_value_reset_string_cursor(pVal);
}
/*
* [CAPIREF: unqlite_value_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_resource(unqlite_value *pVal,void *pUserData)
{
return jx9_value_resource(pVal,pUserData);
}
/*
* [CAPIREF: unqlite_value_release()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_release(unqlite_value *pVal)
{
return jx9_value_release(pVal);
}
/*
* [CAPIREF: unqlite_value_to_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_to_int(unqlite_value *pValue)
{
return jx9_value_to_int(pValue);
}
/*
* [CAPIREF: unqlite_value_to_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_to_bool(unqlite_value *pValue)
{
return jx9_value_to_bool(pValue);
}
/*
* [CAPIREF: unqlite_value_to_int64()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue)
{
return jx9_value_to_int64(pValue);
}
/*
* [CAPIREF: unqlite_value_to_double()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
double unqlite_value_to_double(unqlite_value *pValue)
{
return jx9_value_to_double(pValue);
}
/*
* [CAPIREF: unqlite_value_to_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen)
{
return jx9_value_to_string(pValue,pLen);
}
/*
* [CAPIREF: unqlite_value_to_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void * unqlite_value_to_resource(unqlite_value *pValue)
{
return jx9_value_to_resource(pValue);
}
/*
* [CAPIREF: unqlite_value_compare()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict)
{
return jx9_value_compare(pLeft,pRight,bStrict);
}
/*
* [CAPIREF: unqlite_result_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_int(unqlite_context *pCtx, int iValue)
{
return jx9_result_int(pCtx,iValue);
}
/*
* [CAPIREF: unqlite_result_int64()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue)
{
return jx9_result_int64(pCtx,iValue);
}
/*
* [CAPIREF: unqlite_result_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_bool(unqlite_context *pCtx, int iBool)
{
return jx9_result_bool(pCtx,iBool);
}
/*
* [CAPIREF: unqlite_result_double()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_double(unqlite_context *pCtx, double Value)
{
return jx9_result_double(pCtx,Value);
}
/*
* [CAPIREF: unqlite_result_null()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_null(unqlite_context *pCtx)
{
return jx9_result_null(pCtx);
}
/*
* [CAPIREF: unqlite_result_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen)
{
return jx9_result_string(pCtx,zString,nLen);
}
/*
* [CAPIREF: unqlite_result_string_format()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...)
{
jx9_value *p;
va_list ap;
int rc;
p = pCtx->pRet;
if( (p->iFlags & MEMOBJ_STRING) == 0 ){
/* Invalidate any prior representation */
jx9MemObjRelease(p);
MemObjSetType(p, MEMOBJ_STRING);
}
/* Format the given string */
va_start(ap, zFormat);
rc = SyBlobFormatAp(&p->sBlob, zFormat, ap);
va_end(ap);
return rc;
}
/*
* [CAPIREF: unqlite_result_value()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue)
{
return jx9_result_value(pCtx,pValue);
}
/*
* [CAPIREF: unqlite_result_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_result_resource(unqlite_context *pCtx, void *pUserData)
{
return jx9_result_resource(pCtx,pUserData);
}
/*
* [CAPIREF: unqlite_value_is_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_int(unqlite_value *pVal)
{
return jx9_value_is_int(pVal);
}
/*
* [CAPIREF: unqlite_value_is_float()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_float(unqlite_value *pVal)
{
return jx9_value_is_float(pVal);
}
/*
* [CAPIREF: unqlite_value_is_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_bool(unqlite_value *pVal)
{
return jx9_value_is_bool(pVal);
}
/*
* [CAPIREF: unqlite_value_is_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_string(unqlite_value *pVal)
{
return jx9_value_is_string(pVal);
}
/*
* [CAPIREF: unqlite_value_is_null()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_null(unqlite_value *pVal)
{
return jx9_value_is_null(pVal);
}
/*
* [CAPIREF: unqlite_value_is_numeric()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_numeric(unqlite_value *pVal)
{
return jx9_value_is_numeric(pVal);
}
/*
* [CAPIREF: unqlite_value_is_callable()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_callable(unqlite_value *pVal)
{
return jx9_value_is_callable(pVal);
}
/*
* [CAPIREF: unqlite_value_is_scalar()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_scalar(unqlite_value *pVal)
{
return jx9_value_is_scalar(pVal);
}
/*
* [CAPIREF: unqlite_value_is_json_array()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_json_array(unqlite_value *pVal)
{
return jx9_value_is_json_array(pVal);
}
/*
* [CAPIREF: unqlite_value_is_json_object()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_json_object(unqlite_value *pVal)
{
return jx9_value_is_json_object(pVal);
}
/*
* [CAPIREF: unqlite_value_is_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_resource(unqlite_value *pVal)
{
return jx9_value_is_resource(pVal);
}
/*
* [CAPIREF: unqlite_value_is_empty()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_value_is_empty(unqlite_value *pVal)
{
return jx9_value_is_empty(pVal);
}
/*
* [CAPIREF: unqlite_array_fetch()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte)
{
return jx9_array_fetch(pArray,zKey,nByte);
}
/*
* [CAPIREF: unqlite_array_walk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData)
{
return jx9_array_walk(pArray,xWalk,pUserData);
}
/*
* [CAPIREF: unqlite_array_add_elem()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue)
{
return jx9_array_add_elem(pArray,pKey,pValue);
}
/*
* [CAPIREF: unqlite_array_add_strkey_elem()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue)
{
return jx9_array_add_strkey_elem(pArray,zKey,pValue);
}
/*
* [CAPIREF: unqlite_array_count()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_array_count(unqlite_value *pArray)
{
return (int)jx9_array_count(pArray);
}
/*
* [CAPIREF: unqlite_vm_new_scalar()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm)
{
unqlite_value *pValue;
if( UNQLITE_VM_MISUSE(pVm) ){
return 0;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return 0; /* Another thread have released this instance */
}
#endif
pValue = jx9_new_scalar(pVm->pJx9Vm);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return pValue;
}
/*
* [CAPIREF: unqlite_vm_new_array()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm)
{
unqlite_value *pValue;
if( UNQLITE_VM_MISUSE(pVm) ){
return 0;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return 0; /* Another thread have released this instance */
}
#endif
pValue = jx9_new_array(pVm->pJx9Vm);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return pValue;
}
/*
* [CAPIREF: unqlite_vm_release_value()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue)
{
int rc;
if( UNQLITE_VM_MISUSE(pVm) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_VM_RELEASE(pVm) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
rc = jx9_release_value(pVm->pJx9Vm,pValue);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_context_output()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen)
{
return jx9_context_output(pCtx,zString,nLen);
}
/*
* [CAPIREF: unqlite_context_output_format()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...)
{
va_list ap;
int rc;
va_start(ap, zFormat);
rc = jx9VmOutputConsumeAp(pCtx->pVm,zFormat, ap);
va_end(ap);
return rc;
}
/*
* [CAPIREF: unqlite_context_throw_error()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr)
{
return jx9_context_throw_error(pCtx,iErr,zErr);
}
/*
* [CAPIREF: unqlite_context_throw_error_format()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...)
{
va_list ap;
int rc;
if( zFormat == 0){
return JX9_OK;
}
va_start(ap, zFormat);
rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap);
va_end(ap);
return rc;
}
/*
* [CAPIREF: unqlite_context_random_num()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unsigned int unqlite_context_random_num(unqlite_context *pCtx)
{
return jx9_context_random_num(pCtx);
}
/*
* [CAPIREF: unqlite_context_random_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen)
{
return jx9_context_random_string(pCtx,zBuf,nBuflen);
}
/*
* [CAPIREF: unqlite_context_user_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void * unqlite_context_user_data(unqlite_context *pCtx)
{
return jx9_context_user_data(pCtx);
}
/*
* [CAPIREF: unqlite_context_push_aux_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData)
{
return jx9_context_push_aux_data(pCtx,pUserData);
}
/*
* [CAPIREF: unqlite_context_peek_aux_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void * unqlite_context_peek_aux_data(unqlite_context *pCtx)
{
return jx9_context_peek_aux_data(pCtx);
}
/*
* [CAPIREF: unqlite_context_pop_aux_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void * unqlite_context_pop_aux_data(unqlite_context *pCtx)
{
return jx9_context_pop_aux_data(pCtx);
}
/*
* [CAPIREF: unqlite_context_result_buf_length()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx)
{
return jx9_context_result_buf_length(pCtx);
}
/*
* [CAPIREF: unqlite_function_name()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
const char * unqlite_function_name(unqlite_context *pCtx)
{
return jx9_function_name(pCtx);
}
/*
* [CAPIREF: unqlite_context_new_scalar()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx)
{
return jx9_context_new_scalar(pCtx);
}
/*
* [CAPIREF: unqlite_context_new_array()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
unqlite_value * unqlite_context_new_array(unqlite_context *pCtx)
{
return jx9_context_new_array(pCtx);
}
/*
* [CAPIREF: unqlite_context_release_value()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue)
{
jx9_context_release_value(pCtx,pValue);
}
/*
* [CAPIREF: unqlite_context_alloc_chunk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease)
{
return jx9_context_alloc_chunk(pCtx,nByte,ZeroChunk,AutoRelease);
}
/*
* [CAPIREF: unqlite_context_realloc_chunk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte)
{
return jx9_context_realloc_chunk(pCtx,pChunk,nByte);
}
/*
* [CAPIREF: unqlite_context_free_chunk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk)
{
jx9_context_free_chunk(pCtx,pChunk);
}
/*
* [CAPIREF: unqlite_kv_store()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen)
{
unqlite_kv_engine *pEngine;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
if( pEngine->pIo->pMethods->xReplace == 0 ){
/* Storage engine does not implement such method */
unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine");
rc = UNQLITE_NOTIMPLEMENTED;
}else{
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
unqliteGenError(pDb,"Empty key");
rc = UNQLITE_EMPTY;
}else{
/* Perform the requested operation */
rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,pData,nDataLen);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_store_fmt()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...)
{
unqlite_kv_engine *pEngine;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
if( pEngine->pIo->pMethods->xReplace == 0 ){
/* Storage engine does not implement such method */
unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine");
rc = UNQLITE_NOTIMPLEMENTED;
}else{
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
unqliteGenError(pDb,"Empty key");
rc = UNQLITE_EMPTY;
}else{
SyBlob sWorker; /* Working buffer */
va_list ap;
SyBlobInit(&sWorker,&pDb->sMem);
/* Format the data */
va_start(ap,zFormat);
SyBlobFormatAp(&sWorker,zFormat,ap);
va_end(ap);
/* Perform the requested operation */
rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker));
/* Clean up */
SyBlobRelease(&sWorker);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_append()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen)
{
unqlite_kv_engine *pEngine;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
if( pEngine->pIo->pMethods->xAppend == 0 ){
/* Storage engine does not implement such method */
unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine");
rc = UNQLITE_NOTIMPLEMENTED;
}else{
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
unqliteGenError(pDb,"Empty key");
rc = UNQLITE_EMPTY;
}else{
/* Perform the requested operation */
rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,pData,nDataLen);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_append_fmt()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...)
{
unqlite_kv_engine *pEngine;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
if( pEngine->pIo->pMethods->xAppend == 0 ){
/* Storage engine does not implement such method */
unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine");
rc = UNQLITE_NOTIMPLEMENTED;
}else{
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
unqliteGenError(pDb,"Empty key");
rc = UNQLITE_EMPTY;
}else{
SyBlob sWorker; /* Working buffer */
va_list ap;
SyBlobInit(&sWorker,&pDb->sMem);
/* Format the data */
va_start(ap,zFormat);
SyBlobFormatAp(&sWorker,zFormat,ap);
va_end(ap);
/* Perform the requested operation */
rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker));
/* Clean up */
SyBlobRelease(&sWorker);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_fetch()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 *pBufLen)
{
unqlite_kv_methods *pMethods;
unqlite_kv_engine *pEngine;
unqlite_kv_cursor *pCur;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
pMethods = pEngine->pIo->pMethods;
pCur = pDb->sDB.pCursor;
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
unqliteGenError(pDb,"Empty key");
rc = UNQLITE_EMPTY;
}else{
/* Seek to the record position */
rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT);
}
if( rc == UNQLITE_OK ){
if( pBuf == 0 ){
/* Data length only */
rc = pMethods->xDataLength(pCur,pBufLen);
}else{
SyBlob sBlob;
/* Initialize the data consumer */
SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)*pBufLen);
/* Consume the data */
rc = pMethods->xData(pCur,unqliteDataConsumer,&sBlob);
/* Data length */
*pBufLen = (unqlite_int64)SyBlobLength(&sBlob);
/* Cleanup */
SyBlobRelease(&sBlob);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_fetch_callback()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey,int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
{
unqlite_kv_methods *pMethods;
unqlite_kv_engine *pEngine;
unqlite_kv_cursor *pCur;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
pMethods = pEngine->pIo->pMethods;
pCur = pDb->sDB.pCursor;
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
unqliteGenError(pDb,"Empty key");
rc = UNQLITE_EMPTY;
}else{
/* Seek to the record position */
rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT);
}
if( rc == UNQLITE_OK && xConsumer ){
/* Consume the data directly */
rc = pMethods->xData(pCur,xConsumer,pUserData);
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_delete()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen)
{
unqlite_kv_methods *pMethods;
unqlite_kv_engine *pEngine;
unqlite_kv_cursor *pCur;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
pMethods = pEngine->pIo->pMethods;
pCur = pDb->sDB.pCursor;
if( pMethods->xDelete == 0 ){
/* Storage engine does not implement such method */
unqliteGenError(pDb,"xDelete() method not implemented in the underlying storage engine");
rc = UNQLITE_NOTIMPLEMENTED;
}else{
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
unqliteGenError(pDb,"Empty key");
rc = UNQLITE_EMPTY;
}else{
/* Seek to the record position */
rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT);
}
if( rc == UNQLITE_OK ){
/* Exact match found, delete the entry */
rc = pMethods->xDelete(pCur);
}
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_config()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_config(unqlite *pDb,int iOp,...)
{
unqlite_kv_engine *pEngine;
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Point to the underlying storage engine */
pEngine = unqlitePagerGetKvEngine(pDb);
if( pEngine->pIo->pMethods->xConfig == 0 ){
/* Storage engine does not implements such method */
unqliteGenError(pDb,"xConfig() method not implemented in the underlying storage engine");
rc = UNQLITE_NOTIMPLEMENTED;
}else{
va_list ap;
/* Configure the storage engine */
va_start(ap,iOp);
rc = pEngine->pIo->pMethods->xConfig(pEngine,iOp,ap);
va_end(ap);
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_init()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut)
{
int rc;
if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0 /* Noop */){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Allocate a new cursor */
rc = unqliteInitCursor(pDb,ppOut);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_release()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur)
{
int rc;
if( UNQLITE_DB_MISUSE(pDb) || pCur == 0 /* Noop */){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Release the cursor */
rc = unqliteReleaseCursor(pDb,pCur);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_first_entry()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Check if the requested method is implemented by the underlying storage engine */
if( pCursor->pStore->pIo->pMethods->xFirst == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
/* Seek to the first entry */
rc = pCursor->pStore->pIo->pMethods->xFirst(pCursor);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_last_entry()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Check if the requested method is implemented by the underlying storage engine */
if( pCursor->pStore->pIo->pMethods->xLast == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
/* Seek to the last entry */
rc = pCursor->pStore->pIo->pMethods->xLast(pCursor);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_valid_entry()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Check if the requested method is implemented by the underlying storage engine */
if( pCursor->pStore->pIo->pMethods->xValid == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
rc = pCursor->pStore->pIo->pMethods->xValid(pCursor);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_next_entry()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Check if the requested method is implemented by the underlying storage engine */
if( pCursor->pStore->pIo->pMethods->xNext == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
/* Seek to the next entry */
rc = pCursor->pStore->pIo->pMethods->xNext(pCursor);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_prev_entry()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Check if the requested method is implemented by the underlying storage engine */
if( pCursor->pStore->pIo->pMethods->xPrev == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
/* Seek to the previous entry */
rc = pCursor->pStore->pIo->pMethods->xPrev(pCursor);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_delete_entry()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Check if the requested method is implemented by the underlying storage engine */
if( pCursor->pStore->pIo->pMethods->xDelete == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
/* Delete the entry */
rc = pCursor->pStore->pIo->pMethods->xDelete(pCursor);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_reset()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor)
{
int rc = UNQLITE_OK;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Check if the requested method is implemented by the underlying storage engine */
if( pCursor->pStore->pIo->pMethods->xReset == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
/* Reset */
pCursor->pStore->pIo->pMethods->xReset(pCursor);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_seek()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos)
{
int rc = UNQLITE_OK;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
if( nKeyLen < 0 ){
/* Assume a null terminated string and compute it's length */
nKeyLen = SyStrlen((const char *)pKey);
}
if( !nKeyLen ){
rc = UNQLITE_EMPTY;
}else{
/* Seek to the desired location */
rc = pCursor->pStore->pIo->pMethods->xSeek(pCursor,pKey,nKeyLen,iPos);
}
return rc;
}
/*
* Default data consumer callback. That is, all retrieved is redirected to this
* routine which store the output in an internal blob.
*/
UNQLITE_PRIVATE int unqliteDataConsumer(
const void *pOut, /* Data to consume */
unsigned int nLen, /* Data length */
void *pUserData /* User private data */
)
{
sxi32 rc;
/* Store the output in an internal BLOB */
rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen);
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_data_callback()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Consume the key directly */
rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,xConsumer,pUserData);
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_key()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
if( pBuf == 0 ){
/* Key length only */
rc = pCursor->pStore->pIo->pMethods->xKeyLength(pCursor,pnByte);
}else{
SyBlob sBlob;
if( (*pnByte) < 0 ){
return UNQLITE_CORRUPT;
}
/* Initialize the data consumer */
SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte));
/* Consume the key */
rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,unqliteDataConsumer,&sBlob);
/* Key length */
*pnByte = SyBlobLength(&sBlob);
/* Cleanup */
SyBlobRelease(&sBlob);
}
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_data_callback()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
/* Consume the data directly */
rc = pCursor->pStore->pIo->pMethods->xData(pCursor,xConsumer,pUserData);
return rc;
}
/*
* [CAPIREF: unqlite_kv_cursor_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnByte)
{
int rc;
#ifdef UNTRUST
if( pCursor == 0 ){
return UNQLITE_CORRUPT;
}
#endif
if( pBuf == 0 ){
/* Data length only */
rc = pCursor->pStore->pIo->pMethods->xDataLength(pCursor,pnByte);
}else{
SyBlob sBlob;
if( (*pnByte) < 0 ){
return UNQLITE_CORRUPT;
}
/* Initialize the data consumer */
SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte));
/* Consume the data */
rc = pCursor->pStore->pIo->pMethods->xData(pCursor,unqliteDataConsumer,&sBlob);
/* Data length */
*pnByte = SyBlobLength(&sBlob);
/* Cleanup */
SyBlobRelease(&sBlob);
}
return rc;
}
/*
* [CAPIREF: unqlite_begin()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_begin(unqlite *pDb)
{
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Begin the write transaction */
rc = unqlitePagerBegin(pDb->sDB.pPager);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_commit()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_commit(unqlite *pDb)
{
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Commit the transaction */
rc = unqlitePagerCommit(pDb->sDB.pPager);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_rollback()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
int unqlite_rollback(unqlite *pDb)
{
int rc;
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Rollback the transaction */
rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: unqlite_util_load_mmaped_file()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize)
{
const jx9_vfs *pVfs;
int rc;
if( SX_EMPTY_STR(zFile) || ppMap == 0 || pFileSize == 0){
/* Sanity check */
return UNQLITE_CORRUPT;
}
*ppMap = 0;
/* Extract the Jx9 Vfs */
pVfs = jx9ExportBuiltinVfs();
/*
* Check if the underlying vfs implement the memory map routines
* [i.e: mmap() under UNIX/MapViewOfFile() under windows].
*/
if( pVfs == 0 || pVfs->xMmap == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
/* Try to get a read-only memory view of the whole file */
rc = pVfs->xMmap(zFile,ppMap,pFileSize);
}
return rc;
}
/*
* [CAPIREF: unqlite_util_release_mmaped_file()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize)
{
const jx9_vfs *pVfs;
int rc = UNQLITE_OK;
if( pMap == 0 ){
return UNQLITE_OK;
}
/* Extract the Jx9 Vfs */
pVfs = jx9ExportBuiltinVfs();
if( pVfs == 0 || pVfs->xUnmap == 0 ){
rc = UNQLITE_NOTIMPLEMENTED;
}else{
pVfs->xUnmap(pMap,iFileSize);
}
return rc;
}
/*
* [CAPIREF: unqlite_util_random_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size)
{
if( UNQLITE_DB_MISUSE(pDb) ){
return UNQLITE_CORRUPT;
}
if( zBuf == 0 || buf_size < 3 ){
/* Buffer must be long enough to hold three bytes */
return UNQLITE_INVALID;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return UNQLITE_ABORT; /* Another thread have released this instance */
}
#endif
/* Generate the random string */
unqlitePagerRandomString(pDb->sDB.pPager,zBuf,buf_size);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return UNQLITE_OK;
}
/*
* [CAPIREF: unqlite_util_random_num()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb)
{
sxu32 iNum;
if( UNQLITE_DB_MISUSE(pDb) ){
return 0;
}
#if defined(UNQLITE_ENABLE_THREADS)
/* Acquire DB mutex */
SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
UNQLITE_THRD_DB_RELEASE(pDb) ){
return 0; /* Another thread have released this instance */
}
#endif
/* Generate the random number */
iNum = unqlitePagerRandomNum(pDb->sDB.pPager);
#if defined(UNQLITE_ENABLE_THREADS)
/* Leave DB mutex */
SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
#endif
return iNum;
}
/*
* ----------------------------------------------------------
* File: bitvec.c
* MD5: 7e3376710d8454ebcf8c77baacca880f
* ----------------------------------------------------------
*/
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
* Version 1.1.6
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://unqlite.org/licensing.html
*/
/* $SymiscID: bitvec.c v1.0 Win7 2013-02-27 15:16 stable <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 */
}
if( sJx9MPGlobal.pVfs == 0 ){
/* Point to the built-in vfs */
pVfs = jx9ExportBuiltinVfs();
/* Install it */
jx9_lib_config(JX9_LIB_CONFIG_VFS, pVfs);
}
#if defined(JX9_ENABLE_THREADS)
if( sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_SINGLE ){
pMutexMethods = sJx9MPGlobal.pMutexMethods;
if( pMutexMethods == 0 ){
/* Use the built-in mutex subsystem */
pMutexMethods = SyMutexExportMethods();
if( pMutexMethods == 0 ){
return JX9_CORRUPT; /* Can't happen */
}
/* Install the mutex subsystem */
rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MUTEX, pMutexMethods);
if( rc != JX9_OK ){
return rc;
}
}
/* Obtain a static mutex so we can initialize the library without calling malloc() */
pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1);
if( pMaster == 0 ){
return JX9_CORRUPT; /* Can't happen */
}
}
/* Lock the master mutex */
rc = JX9_OK;
SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){
#endif
if( sJx9MPGlobal.sAllocator.pMethods == 0 ){
/* Install a memory subsystem */
rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */
if( rc != JX9_OK ){
/* If we are unable to initialize the memory backend, there is no much we can do here.*/
goto End;
}
}
#if defined(JX9_ENABLE_THREADS)
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){
/* Protect the memory allocation subsystem */
rc = SyMemBackendMakeThreadSafe(&sJx9MPGlobal.sAllocator, sJx9MPGlobal.pMutexMethods);
if( rc != JX9_OK ){
goto End;
}
}
#endif
/* Our library is initialized, set the magic number */
sJx9MPGlobal.nMagic = JX9_LIB_MAGIC;
rc = JX9_OK;
#if defined(JX9_ENABLE_THREADS)
} /* sJx9MPGlobal.nMagic != JX9_LIB_MAGIC */
#endif
End:
#if defined(JX9_ENABLE_THREADS)
/* Unlock the master mutex */
SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
#endif
return rc;
}
/*
* Release an active JX9 engine and it's associated active virtual machines.
*/
static sxi32 EngineRelease(jx9 *pEngine)
{
jx9_vm *pVm, *pNext;
/* Release all active VM */
pVm = pEngine->pVms;
for(;;){
if( pEngine->iVm < 1 ){
break;
}
pNext = pVm->pNext;
jx9VmRelease(pVm);
pVm = pNext;
pEngine->iVm--;
}
/* Set a dummy magic number */
pEngine->nMagic = 0x7635;
/* Release the private memory subsystem */
SyMemBackendRelease(&pEngine->sAllocator);
return JX9_OK;
}
/*
* Release all resources consumed by the library.
* If JX9 is already shut when this routine is invoked then this
* routine is a harmless no-op.
* Note: This call is not thread safe. Refer to [jx9_lib_shutdown()].
*/
static void JX9CoreShutdown(void)
{
jx9 *pEngine, *pNext;
/* Release all active engines first */
pEngine = sJx9MPGlobal.pEngines;
for(;;){
if( sJx9MPGlobal.nEngine < 1 ){
break;
}
pNext = pEngine->pNext;
EngineRelease(pEngine);
pEngine = pNext;
sJx9MPGlobal.nEngine--;
}
#if defined(JX9_ENABLE_THREADS)
/* Release the mutex subsystem */
if( sJx9MPGlobal.pMutexMethods ){
if( sJx9MPGlobal.pMutex ){
SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex);
sJx9MPGlobal.pMutex = 0;
}
if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){
sJx9MPGlobal.pMutexMethods->xGlobalRelease();
}
sJx9MPGlobal.pMutexMethods = 0;
}
sJx9MPGlobal.nThreadingLevel = 0;
#endif
if( sJx9MPGlobal.sAllocator.pMethods ){
/* Release the memory backend */
SyMemBackendRelease(&sJx9MPGlobal.sAllocator);
}
sJx9MPGlobal.nMagic = 0x1928;
}
/*
* [CAPIREF: jx9_lib_shutdown()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_lib_shutdown(void)
{
if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){
/* Already shut */
return JX9_OK;
}
JX9CoreShutdown();
return JX9_OK;
}
/*
* [CAPIREF: jx9_lib_signature()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE const char * jx9_lib_signature(void)
{
return JX9_SIG;
}
/*
* [CAPIREF: jx9_init()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_init(jx9 **ppEngine)
{
jx9 *pEngine;
int rc;
#if defined(UNTRUST)
if( ppEngine == 0 ){
return JX9_CORRUPT;
}
#endif
*ppEngine = 0;
/* One-time automatic library initialization */
rc = Jx9CoreInitialize();
if( rc != JX9_OK ){
return rc;
}
/* Allocate a new engine */
pEngine = (jx9 *)SyMemBackendPoolAlloc(&sJx9MPGlobal.sAllocator, sizeof(jx9));
if( pEngine == 0 ){
return JX9_NOMEM;
}
/* Zero the structure */
SyZero(pEngine, sizeof(jx9));
/* Initialize engine fields */
pEngine->nMagic = JX9_ENGINE_MAGIC;
rc = SyMemBackendInitFromParent(&pEngine->sAllocator, &sJx9MPGlobal.sAllocator);
if( rc != JX9_OK ){
goto Release;
}
//#if defined(JX9_ENABLE_THREADS)
// SyMemBackendDisbaleMutexing(&pEngine->sAllocator);
//#endif
/* Default configuration */
SyBlobInit(&pEngine->xConf.sErrConsumer, &pEngine->sAllocator);
/* Install a default compile-time error consumer routine */
pEngine->xConf.xErr = jx9VmBlobConsumer;
pEngine->xConf.pErrData = &pEngine->xConf.sErrConsumer;
/* Built-in vfs */
pEngine->pVfs = sJx9MPGlobal.pVfs;
#if defined(JX9_ENABLE_THREADS)
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){
/* Associate a recursive mutex with this instance */
pEngine->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE);
if( pEngine->pMutex == 0 ){
rc = JX9_NOMEM;
goto Release;
}
}
#endif
/* Link to the list of active engines */
#if defined(JX9_ENABLE_THREADS)
/* Enter the global mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
#endif
MACRO_LD_PUSH(sJx9MPGlobal.pEngines, pEngine);
sJx9MPGlobal.nEngine++;
#if defined(JX9_ENABLE_THREADS)
/* Leave the global mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
#endif
/* Write a pointer to the new instance */
*ppEngine = pEngine;
return JX9_OK;
Release:
SyMemBackendRelease(&pEngine->sAllocator);
SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator,pEngine);
return rc;
}
/*
* [CAPIREF: jx9_release()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_release(jx9 *pEngine)
{
int rc;
if( JX9_ENGINE_MISUSE(pEngine) ){
return JX9_CORRUPT;
}
#if defined(JX9_ENABLE_THREADS)
/* Acquire engine mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_ENGINE_RELEASE(pEngine) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
/* Release the engine */
rc = EngineRelease(&(*pEngine));
#if defined(JX9_ENABLE_THREADS)
/* Leave engine mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
/* Release engine mutex */
SyMutexRelease(sJx9MPGlobal.pMutexMethods, pEngine->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
#if defined(JX9_ENABLE_THREADS)
/* Enter the global mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
#endif
/* Unlink from the list of active engines */
MACRO_LD_REMOVE(sJx9MPGlobal.pEngines, pEngine);
sJx9MPGlobal.nEngine--;
#if defined(JX9_ENABLE_THREADS)
/* Leave the global mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
#endif
/* Release the memory chunk allocated to this engine */
SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator, pEngine);
return rc;
}
/*
* Compile a raw JX9 script.
* To execute a JX9 code, it must first be compiled into a bytecode program using this routine.
* If something goes wrong [i.e: compile-time error], your error log [i.e: error consumer callback]
* should display the appropriate error message and this function set ppVm to null and return
* an error code that is different from JX9_OK. Otherwise when the script is successfully compiled
* ppVm should hold the JX9 bytecode and it's safe to call [jx9_vm_exec(), jx9_vm_reset(), etc.].
* This API does not actually evaluate the JX9 code. It merely compile and prepares the JX9 script
* for evaluation.
*/
static sxi32 ProcessScript(
jx9 *pEngine, /* Running JX9 engine */
jx9_vm **ppVm, /* OUT: A pointer to the virtual machine */
SyString *pScript, /* Raw JX9 script to compile */
sxi32 iFlags, /* Compile-time flags */
const char *zFilePath /* File path if script come from a file. NULL otherwise */
)
{
jx9_vm *pVm;
int rc;
/* Allocate a new virtual machine */
pVm = (jx9_vm *)SyMemBackendPoolAlloc(&pEngine->sAllocator, sizeof(jx9_vm));
if( pVm == 0 ){
/* If the supplied memory subsystem is so sick that we are unable to allocate
* a tiny chunk of memory, there is no much we can do here. */
if( ppVm ){
*ppVm = 0;
}
return JX9_NOMEM;
}
if( iFlags < 0 ){
/* Default compile-time flags */
iFlags = 0;
}
/* Initialize the Virtual Machine */
rc = jx9VmInit(pVm, &(*pEngine));
if( rc != JX9_OK ){
SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
if( ppVm ){
*ppVm = 0;
}
return JX9_VM_ERR;
}
if( zFilePath ){
/* Push processed file path */
jx9VmPushFilePath(pVm, zFilePath, -1, TRUE, 0);
}
/* Reset the error message consumer */
SyBlobReset(&pEngine->xConf.sErrConsumer);
/* Compile the script */
jx9CompileScript(pVm, &(*pScript), iFlags);
if( pVm->sCodeGen.nErr > 0 || pVm == 0){
sxu32 nErr = pVm->sCodeGen.nErr;
/* Compilation error or null ppVm pointer, release this VM */
SyMemBackendRelease(&pVm->sAllocator);
SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
if( ppVm ){
*ppVm = 0;
}
return nErr > 0 ? JX9_COMPILE_ERR : JX9_OK;
}
/* Prepare the virtual machine for bytecode execution */
rc = jx9VmMakeReady(pVm);
if( rc != JX9_OK ){
goto Release;
}
/* Install local import path which is the current directory */
jx9_vm_config(pVm, JX9_VM_CONFIG_IMPORT_PATH, "./");
#if defined(JX9_ENABLE_THREADS)
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){
/* Associate a recursive mutex with this instance */
pVm->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE);
if( pVm->pMutex == 0 ){
goto Release;
}
}
#endif
/* Script successfully compiled, link to the list of active virtual machines */
MACRO_LD_PUSH(pEngine->pVms, pVm);
pEngine->iVm++;
/* Point to the freshly created VM */
*ppVm = pVm;
/* Ready to execute JX9 bytecode */
return JX9_OK;
Release:
SyMemBackendRelease(&pVm->sAllocator);
SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
*ppVm = 0;
return JX9_VM_ERR;
}
/*
* [CAPIREF: jx9_compile()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm)
{
SyString sScript;
int rc;
if( JX9_ENGINE_MISUSE(pEngine) ){
return JX9_CORRUPT;
}
if( zSource == 0 ){
/* Empty Jx9 statement ';' */
zSource = ";";
nLen = (int)sizeof(char);
}
if( nLen < 0 ){
/* Compute input length automatically */
nLen = (int)SyStrlen(zSource);
}
SyStringInitFromBuf(&sScript, zSource, nLen);
#if defined(JX9_ENABLE_THREADS)
/* Acquire engine mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_ENGINE_RELEASE(pEngine) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
/* Compile the script */
rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,0,0);
#if defined(JX9_ENABLE_THREADS)
/* Leave engine mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
/* Compilation result */
return rc;
}
/*
* [CAPIREF: jx9_compile_file()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm)
{
const jx9_vfs *pVfs;
int rc;
if( ppOutVm ){
*ppOutVm = 0;
}
rc = JX9_OK; /* cc warning */
if( JX9_ENGINE_MISUSE(pEngine) || SX_EMPTY_STR(zFilePath) ){
return JX9_CORRUPT;
}
#if defined(JX9_ENABLE_THREADS)
/* Acquire engine mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_ENGINE_RELEASE(pEngine) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
/*
* Check if the underlying vfs implement the memory map
* [i.e: mmap() under UNIX/MapViewOfFile() under windows] function.
*/
pVfs = pEngine->pVfs;
if( pVfs == 0 || pVfs->xMmap == 0 ){
/* Memory map routine not implemented */
rc = JX9_IO_ERR;
}else{
void *pMapView = 0; /* cc warning */
jx9_int64 nSize = 0; /* cc warning */
SyString sScript;
/* Try to get a memory view of the whole file */
rc = pVfs->xMmap(zFilePath, &pMapView, &nSize);
if( rc != JX9_OK ){
/* Assume an IO error */
rc = JX9_IO_ERR;
}else{
/* Compile the file */
SyStringInitFromBuf(&sScript, pMapView, nSize);
rc = ProcessScript(&(*pEngine), ppOutVm, &sScript,0,zFilePath);
/* Release the memory view of the whole file */
if( pVfs->xUnmap ){
pVfs->xUnmap(pMapView, nSize);
}
}
}
#if defined(JX9_ENABLE_THREADS)
/* Leave engine mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
/* Compilation result */
return rc;
}
/*
* [CAPIREF: jx9_vm_config()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...)
{
va_list ap;
int rc;
/* Ticket 1433-002: NULL VM is harmless operation */
if ( JX9_VM_MISUSE(pVm) ){
return JX9_CORRUPT;
}
#if defined(JX9_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_VM_RELEASE(pVm) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
/* Confiugure the virtual machine */
va_start(ap, iConfigOp);
rc = jx9VmConfigure(&(*pVm), iConfigOp, ap);
va_end(ap);
#if defined(JX9_ENABLE_THREADS)
/* Leave VM mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
return rc;
}
/*
* [CAPIREF: jx9_vm_release()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm)
{
jx9 *pEngine;
int rc;
/* Ticket 1433-002: NULL VM is harmless operation */
if ( JX9_VM_MISUSE(pVm) ){
return JX9_CORRUPT;
}
#if defined(JX9_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_VM_RELEASE(pVm) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
pEngine = pVm->pEngine;
rc = jx9VmRelease(&(*pVm));
#if defined(JX9_ENABLE_THREADS)
/* Leave VM mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
/* Release VM mutex */
SyMutexRelease(sJx9MPGlobal.pMutexMethods, pVm->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
if( rc == JX9_OK ){
/* Unlink from the list of active VM */
#if defined(JX9_ENABLE_THREADS)
/* Acquire engine mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_ENGINE_RELEASE(pEngine) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
MACRO_LD_REMOVE(pEngine->pVms, pVm);
pEngine->iVm--;
/* Release the memory chunk allocated to this VM */
SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
#if defined(JX9_ENABLE_THREADS)
/* Leave engine mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
}
return rc;
}
/*
* [CAPIREF: jx9_create_function()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData)
{
SyString sName;
int rc;
/* Ticket 1433-002: NULL VM is harmless operation */
if ( JX9_VM_MISUSE(pVm) ){
return JX9_CORRUPT;
}
SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
/* Remove leading and trailing white spaces */
SyStringFullTrim(&sName);
/* Ticket 1433-003: NULL values are not allowed */
if( sName.nByte < 1 || xFunc == 0 ){
return JX9_CORRUPT;
}
#if defined(JX9_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_VM_RELEASE(pVm) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
/* Install the foreign function */
rc = jx9VmInstallForeignFunction(&(*pVm), &sName, xFunc, pUserData);
#if defined(JX9_ENABLE_THREADS)
/* Leave VM mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
return rc;
}
JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName)
{
jx9_user_func *pFunc = 0; /* cc warning */
int rc;
/* Perform the deletion */
rc = SyHashDeleteEntry(&pVm->hHostFunction, (const void *)zName, SyStrlen(zName), (void **)&pFunc);
if( rc == JX9_OK ){
/* Release internal fields */
SySetRelease(&pFunc->aAux);
SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName));
SyMemBackendPoolFree(&pVm->sAllocator, pFunc);
}
return rc;
}
/*
* [CAPIREF: jx9_create_constant()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData)
{
SyString sName;
int rc;
/* Ticket 1433-002: NULL VM is harmless operation */
if ( JX9_VM_MISUSE(pVm) ){
return JX9_CORRUPT;
}
SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
/* Remove leading and trailing white spaces */
SyStringFullTrim(&sName);
if( sName.nByte < 1 ){
/* Empty constant name */
return JX9_CORRUPT;
}
/* TICKET 1433-003: NULL pointer is harmless operation */
if( xExpand == 0 ){
return JX9_CORRUPT;
}
#if defined(JX9_ENABLE_THREADS)
/* Acquire VM mutex */
SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
JX9_THRD_VM_RELEASE(pVm) ){
return JX9_ABORT; /* Another thread have released this instance */
}
#endif
/* Perform the registration */
rc = jx9VmRegisterConstant(&(*pVm), &sName, xExpand, pUserData);
#if defined(JX9_ENABLE_THREADS)
/* Leave VM mutex */
SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
#endif
return rc;
}
JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName)
{
jx9_constant *pCons;
int rc;
/* Query the constant hashtable */
rc = SyHashDeleteEntry(&pVm->hConstant, (const void *)zName, SyStrlen(zName), (void **)&pCons);
if( rc == JX9_OK ){
/* Perform the deletion */
SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pCons->sName));
SyMemBackendPoolFree(&pVm->sAllocator, pCons);
}
return rc;
}
/*
* [CAPIREF: jx9_new_scalar()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm)
{
jx9_value *pObj;
/* Ticket 1433-002: NULL VM is harmless operation */
if ( JX9_VM_MISUSE(pVm) ){
return 0;
}
/* Allocate a new scalar variable */
pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value));
if( pObj == 0 ){
return 0;
}
/* Nullify the new scalar */
jx9MemObjInit(pVm, pObj);
return pObj;
}
/*
* [CAPIREF: jx9_new_array()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm)
{
jx9_hashmap *pMap;
jx9_value *pObj;
/* Ticket 1433-002: NULL VM is harmless operation */
if ( JX9_VM_MISUSE(pVm) ){
return 0;
}
/* Create a new hashmap first */
pMap = jx9NewHashmap(&(*pVm), 0, 0);
if( pMap == 0 ){
return 0;
}
/* Associate a new jx9_value with this hashmap */
pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value));
if( pObj == 0 ){
jx9HashmapRelease(pMap, TRUE);
return 0;
}
jx9MemObjInitFromArray(pVm, pObj, pMap);
return pObj;
}
/*
* [CAPIREF: jx9_release_value()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue)
{
/* Ticket 1433-002: NULL VM is a harmless operation */
if ( JX9_VM_MISUSE(pVm) ){
return JX9_CORRUPT;
}
if( pValue ){
/* Release the value */
jx9MemObjRelease(pValue);
SyMemBackendPoolFree(&pVm->sAllocator, pValue);
}
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_to_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue)
{
int rc;
rc = jx9MemObjToInteger(pValue);
if( rc != JX9_OK ){
return 0;
}
return (int)pValue->x.iVal;
}
/*
* [CAPIREF: jx9_value_to_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue)
{
int rc;
rc = jx9MemObjToBool(pValue);
if( rc != JX9_OK ){
return 0;
}
return (int)pValue->x.iVal;
}
/*
* [CAPIREF: jx9_value_to_int64()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue)
{
int rc;
rc = jx9MemObjToInteger(pValue);
if( rc != JX9_OK ){
return 0;
}
return pValue->x.iVal;
}
/*
* [CAPIREF: jx9_value_to_double()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue)
{
int rc;
rc = jx9MemObjToReal(pValue);
if( rc != JX9_OK ){
return (double)0;
}
return (double)pValue->x.rVal;
}
/*
* [CAPIREF: jx9_value_to_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen)
{
jx9MemObjToString(pValue);
if( SyBlobLength(&pValue->sBlob) > 0 ){
SyBlobNullAppend(&pValue->sBlob);
if( pLen ){
*pLen = (int)SyBlobLength(&pValue->sBlob);
}
return (const char *)SyBlobData(&pValue->sBlob);
}else{
/* Return the empty string */
if( pLen ){
*pLen = 0;
}
return "";
}
}
/*
* [CAPIREF: jx9_value_to_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue)
{
if( (pValue->iFlags & MEMOBJ_RES) == 0 ){
/* Not a resource, return NULL */
return 0;
}
return pValue->x.pOther;
}
/*
* [CAPIREF: jx9_value_compare()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict)
{
int rc;
if( pLeft == 0 || pRight == 0 ){
/* TICKET 1433-24: NULL values is harmless operation */
return 1;
}
/* Perform the comparison */
rc = jx9MemObjCmp(&(*pLeft), &(*pRight), bStrict, 0);
/* Comparison result */
return rc;
}
/*
* [CAPIREF: jx9_result_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue)
{
return jx9_value_int(pCtx->pRet, iValue);
}
/*
* [CAPIREF: jx9_result_int64()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue)
{
return jx9_value_int64(pCtx->pRet, iValue);
}
/*
* [CAPIREF: jx9_result_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool)
{
return jx9_value_bool(pCtx->pRet, iBool);
}
/*
* [CAPIREF: jx9_result_double()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value)
{
return jx9_value_double(pCtx->pRet, Value);
}
/*
* [CAPIREF: jx9_result_null()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_null(jx9_context *pCtx)
{
/* Invalidate any prior representation and set the NULL flag */
jx9MemObjRelease(pCtx->pRet);
return JX9_OK;
}
/*
* [CAPIREF: jx9_result_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen)
{
return jx9_value_string(pCtx->pRet, zString, nLen);
}
/*
* [CAPIREF: jx9_result_string_format()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...)
{
jx9_value *p;
va_list ap;
int rc;
p = pCtx->pRet;
if( (p->iFlags & MEMOBJ_STRING) == 0 ){
/* Invalidate any prior representation */
jx9MemObjRelease(p);
MemObjSetType(p, MEMOBJ_STRING);
}
/* Format the given string */
va_start(ap, zFormat);
rc = SyBlobFormatAp(&p->sBlob, zFormat, ap);
va_end(ap);
return rc;
}
/*
* [CAPIREF: jx9_result_value()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue)
{
int rc = JX9_OK;
if( pValue == 0 ){
jx9MemObjRelease(pCtx->pRet);
}else{
rc = jx9MemObjStore(pValue, pCtx->pRet);
}
return rc;
}
/*
* [CAPIREF: jx9_result_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData)
{
return jx9_value_resource(pCtx->pRet, pUserData);
}
/*
* [CAPIREF: jx9_context_new_scalar()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx)
{
jx9_value *pVal;
pVal = jx9_new_scalar(pCtx->pVm);
if( pVal ){
/* Record value address so it can be freed automatically
* when the calling function returns.
*/
SySetPut(&pCtx->sVar, (const void *)&pVal);
}
return pVal;
}
/*
* [CAPIREF: jx9_context_new_array()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx)
{
jx9_value *pVal;
pVal = jx9_new_array(pCtx->pVm);
if( pVal ){
/* Record value address so it can be freed automatically
* when the calling function returns.
*/
SySetPut(&pCtx->sVar, (const void *)&pVal);
}
return pVal;
}
/*
* [CAPIREF: jx9_context_release_value()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue)
{
jx9VmReleaseContextValue(&(*pCtx), pValue);
}
/*
* [CAPIREF: jx9_context_alloc_chunk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease)
{
void *pChunk;
pChunk = SyMemBackendAlloc(&pCtx->pVm->sAllocator, nByte);
if( pChunk ){
if( ZeroChunk ){
/* Zero the memory chunk */
SyZero(pChunk, nByte);
}
if( AutoRelease ){
jx9_aux_data sAux;
/* Track the chunk so that it can be released automatically
* upon this context is destroyed.
*/
sAux.pAuxData = pChunk;
SySetPut(&pCtx->sChunk, (const void *)&sAux);
}
}
return pChunk;
}
/*
* Check if the given chunk address is registered in the call context
* chunk container.
* Return TRUE if registered.FALSE otherwise.
* Refer to [jx9_context_realloc_chunk(), jx9_context_free_chunk()].
*/
static jx9_aux_data * ContextFindChunk(jx9_context *pCtx, void *pChunk)
{
jx9_aux_data *aAux, *pAux;
sxu32 n;
if( SySetUsed(&pCtx->sChunk) < 1 ){
/* Don't bother processing, the container is empty */
return 0;
}
/* Perform the lookup */
aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk);
for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){
pAux = &aAux[n];
if( pAux->pAuxData == pChunk ){
/* Chunk found */
return pAux;
}
}
/* No such allocated chunk */
return 0;
}
/*
* [CAPIREF: jx9_context_realloc_chunk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte)
{
jx9_aux_data *pAux;
void *pNew;
pNew = SyMemBackendRealloc(&pCtx->pVm->sAllocator, pChunk, nByte);
if( pNew ){
pAux = ContextFindChunk(pCtx, pChunk);
if( pAux ){
pAux->pAuxData = pNew;
}
}
return pNew;
}
/*
* [CAPIREF: jx9_context_free_chunk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk)
{
jx9_aux_data *pAux;
if( pChunk == 0 ){
/* TICKET-1433-93: NULL chunk is a harmless operation */
return;
}
pAux = ContextFindChunk(pCtx, pChunk);
if( pAux ){
/* Mark as destroyed */
pAux->pAuxData = 0;
}
SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk);
}
/*
* [CAPIREF: jx9_array_fetch()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte)
{
jx9_hashmap_node *pNode;
jx9_value *pValue;
jx9_value skey;
int rc;
/* Make sure we are dealing with a valid hashmap */
if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
return 0;
}
if( nByte < 0 ){
nByte = (int)SyStrlen(zKey);
}
/* Convert the key to a jx9_value */
jx9MemObjInit(pArray->pVm, &skey);
jx9MemObjStringAppend(&skey, zKey, (sxu32)nByte);
/* Perform the lookup */
rc = jx9HashmapLookup((jx9_hashmap *)pArray->x.pOther, &skey, &pNode);
jx9MemObjRelease(&skey);
if( rc != JX9_OK ){
/* No such entry */
return 0;
}
/* Extract the target value */
pValue = (jx9_value *)SySetAt(&pArray->pVm->aMemObj, pNode->nValIdx);
return pValue;
}
/*
* [CAPIREF: jx9_array_walk()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *pValue, jx9_value *, void *), void *pUserData)
{
int rc;
if( xWalk == 0 ){
return JX9_CORRUPT;
}
/* Make sure we are dealing with a valid hashmap */
if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
return JX9_CORRUPT;
}
/* Start the walk process */
rc = jx9HashmapWalk((jx9_hashmap *)pArray->x.pOther, xWalk, pUserData);
return rc != JX9_OK ? JX9_ABORT /* User callback request an operation abort*/ : JX9_OK;
}
/*
* [CAPIREF: jx9_array_add_elem()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue)
{
int rc;
/* Make sure we are dealing with a valid hashmap */
if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
return JX9_CORRUPT;
}
/* Perform the insertion */
rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &(*pKey), &(*pValue));
return rc;
}
/*
* [CAPIREF: jx9_array_add_strkey_elem()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue)
{
int rc;
/* Make sure we are dealing with a valid hashmap */
if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
return JX9_CORRUPT;
}
/* Perform the insertion */
if( SX_EMPTY_STR(zKey) ){
/* Empty key, assign an automatic index */
rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, 0, &(*pValue));
}else{
jx9_value sKey;
jx9MemObjInitFromString(pArray->pVm, &sKey, 0);
jx9MemObjStringAppend(&sKey, zKey, (sxu32)SyStrlen(zKey));
rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &sKey, &(*pValue));
jx9MemObjRelease(&sKey);
}
return rc;
}
/*
* [CAPIREF: jx9_array_count()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray)
{
jx9_hashmap *pMap;
/* Make sure we are dealing with a valid hashmap */
if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
return 0;
}
/* Point to the internal representation of the hashmap */
pMap = (jx9_hashmap *)pArray->x.pOther;
return pMap->nEntry;
}
/*
* [CAPIREF: jx9_context_output()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen)
{
SyString sData;
int rc;
if( nLen < 0 ){
nLen = (int)SyStrlen(zString);
}
SyStringInitFromBuf(&sData, zString, nLen);
rc = jx9VmOutputConsume(pCtx->pVm, &sData);
return rc;
}
/*
* [CAPIREF: jx9_context_throw_error()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr)
{
int rc = JX9_OK;
if( zErr ){
rc = jx9VmThrowError(pCtx->pVm, &pCtx->pFunc->sName, iErr, zErr);
}
return rc;
}
/*
* [CAPIREF: jx9_context_throw_error_format()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...)
{
va_list ap;
int rc;
if( zFormat == 0){
return JX9_OK;
}
va_start(ap, zFormat);
rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap);
va_end(ap);
return rc;
}
/*
* [CAPIREF: jx9_context_random_num()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx)
{
sxu32 n;
n = jx9VmRandomNum(pCtx->pVm);
return n;
}
/*
* [CAPIREF: jx9_context_random_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen)
{
if( nBuflen < 3 ){
return JX9_CORRUPT;
}
jx9VmRandomString(pCtx->pVm, zBuf, nBuflen);
return JX9_OK;
}
/*
* [CAPIREF: jx9_context_user_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx)
{
return pCtx->pFunc->pUserData;
}
/*
* [CAPIREF: jx9_context_push_aux_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData)
{
jx9_aux_data sAux;
int rc;
sAux.pAuxData = pUserData;
rc = SySetPut(&pCtx->pFunc->aAux, (const void *)&sAux);
return rc;
}
/*
* [CAPIREF: jx9_context_peek_aux_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx)
{
jx9_aux_data *pAux;
pAux = (jx9_aux_data *)SySetPeek(&pCtx->pFunc->aAux);
return pAux ? pAux->pAuxData : 0;
}
/*
* [CAPIREF: jx9_context_pop_aux_data()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx)
{
jx9_aux_data *pAux;
pAux = (jx9_aux_data *)SySetPop(&pCtx->pFunc->aAux);
return pAux ? pAux->pAuxData : 0;
}
/*
* [CAPIREF: jx9_context_result_buf_length()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx)
{
return SyBlobLength(&pCtx->pRet->sBlob);
}
/*
* [CAPIREF: jx9_function_name()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx)
{
SyString *pName;
pName = &pCtx->pFunc->sName;
return pName->zString;
}
/*
* [CAPIREF: jx9_value_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue)
{
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
pVal->x.iVal = (jx9_int64)iValue;
MemObjSetType(pVal, MEMOBJ_INT);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_int64()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue)
{
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
pVal->x.iVal = iValue;
MemObjSetType(pVal, MEMOBJ_INT);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool)
{
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
pVal->x.iVal = iBool ? 1 : 0;
MemObjSetType(pVal, MEMOBJ_BOOL);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_null()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_null(jx9_value *pVal)
{
/* Invalidate any prior representation and set the NULL flag */
jx9MemObjRelease(pVal);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_double()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value)
{
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
pVal->x.rVal = (jx9_real)Value;
MemObjSetType(pVal, MEMOBJ_REAL);
/* Try to get an integer representation also */
jx9MemObjTryInteger(pVal);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen)
{
if((pVal->iFlags & MEMOBJ_STRING) == 0 ){
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
MemObjSetType(pVal, MEMOBJ_STRING);
}
if( zString ){
if( nLen < 0 ){
/* Compute length automatically */
nLen = (int)SyStrlen(zString);
}
SyBlobAppend(&pVal->sBlob, (const void *)zString, (sxu32)nLen);
}
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_string_format()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...)
{
va_list ap;
int rc;
if((pVal->iFlags & MEMOBJ_STRING) == 0 ){
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
MemObjSetType(pVal, MEMOBJ_STRING);
}
va_start(ap, zFormat);
rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap);
va_end(ap);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_reset_string_cursor()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal)
{
/* Reset the string cursor */
SyBlobReset(&pVal->sBlob);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData)
{
/* Invalidate any prior representation */
jx9MemObjRelease(pVal);
/* Reflect the new type */
pVal->x.pOther = pUserData;
MemObjSetType(pVal, MEMOBJ_RES);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_release()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_release(jx9_value *pVal)
{
jx9MemObjRelease(pVal);
return JX9_OK;
}
/*
* [CAPIREF: jx9_value_is_int()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_INT) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_float()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_REAL) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_bool()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_BOOL) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_string()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_STRING) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_null()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_NULL) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_numeric()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal)
{
int rc;
rc = jx9MemObjIsNumeric(pVal);
return rc;
}
/*
* [CAPIREF: jx9_value_is_callable()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal)
{
int rc;
rc = jx9VmIsCallable(pVal->pVm, pVal);
return rc;
}
/*
* [CAPIREF: jx9_value_is_scalar()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_SCALAR) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_json_array()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_HASHMAP) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_json_object()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal)
{
jx9_hashmap *pMap;
if( (pVal->iFlags & MEMOBJ_HASHMAP) == 0 ){
return FALSE;
}
pMap = (jx9_hashmap *)pVal->x.pOther;
if( (pMap->iFlags & HASHMAP_JSON_OBJECT) == 0 ){
return FALSE;
}
return TRUE;
}
/*
* [CAPIREF: jx9_value_is_resource()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal)
{
return (pVal->iFlags & MEMOBJ_RES) ? TRUE : FALSE;
}
/*
* [CAPIREF: jx9_value_is_empty()]
* Please refer to the official documentation for function purpose and expected parameters.
*/
JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal)
{
int rc;
rc = jx9MemObjIsEmpty(pVal);
return rc;
}
/*
* ----------------------------------------------------------
* File: jx9_builtin.c
* MD5: 97ae6ddf8ded9fe14634060675e12f80
* ----------------------------------------------------------
*/
/*
* Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
* Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
* Version 1.7.2
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://jx9.symisc.net/
*/
/* $SymiscID: builtin.c v1.7 Win7 2012-12-13 00:01 stable <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));
}
else
{
jx9MemObjStringAppend(pObj, "", 0);
}
zIn++;
if( zIn < zEnd ){
if( zIn[0] == '\\' ){
/* A literal backslash */
jx9MemObjStringAppend(pObj, "\\", sizeof(char));
}else if( zIn[0] == '\'' ){
/* A single quote */
jx9MemObjStringAppend(pObj, "'", sizeof(char));
}else{
/* verbatim copy */
zIn--;
jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2);
zIn++;
}
}
/* Advance the stream cursor */
zIn++;
}
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
if( pStr->nByte < 1024 ){
/* Install in the literal table */
GenStateInstallLiteral(pGen, pObj, nIdx);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string.
* According to the JX9 language reference manual
* When a string is specified in double quotes or with heredoc, variables are parsed within it.
* There are two types of syntax: a simple one and a complex one. The simple syntax is the most
* common and convenient. It provides a way to embed a variable, an array value, or an object
* property in a string with a minimum of effort.
* Simple syntax
* If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible
* to form a valid variable name. Enclose the variable name in curly braces to explicitly specify
* the end of the name.
* Similarly, an array index or an object property can be parsed. With array indices, the closing
* square bracket (]) marks the end of the index. The same rules apply to object properties
* as to simple variables.
* Complex (curly) syntax
* This isn't called complex because the syntax is complex, but because it allows for the use
* of complex expressions.
* Any scalar variable, array element or object property with a string representation can be
* included via this syntax. Simply write the expression the same way as it would appear outside
* the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only
* be recognised when the $ immediately follows the {. Use {\$ to get a literal {$
*/
static sxi32 GenStateProcessStringExpression(
jx9_gen_state *pGen, /* Code generator state */
const char *zIn, /* Raw expression */
const char *zEnd /* End of the expression */
)
{
SyToken *pTmpIn, *pTmpEnd;
SySet sToken;
sxi32 rc;
/* Initialize the token set */
SySetInit(&sToken, &pGen->pVm->sAllocator, sizeof(SyToken));
/* Preallocate some slots */
SySetAlloc(&sToken, 0x08);
/* Tokenize the text */
jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken);
/* Swap delimiter */
pTmpIn = pGen->pIn;
pTmpEnd = pGen->pEnd;
pGen->pIn = (SyToken *)SySetBasePtr(&sToken);
pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)];
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Restore token stream */
pGen->pIn = pTmpIn;
pGen->pEnd = pTmpEnd;
/* Release the token set */
SySetRelease(&sToken);
/* Compilation result */
return rc;
}
/*
* Reserve a new constant for a double quoted/heredoc string.
*/
static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount)
{
jx9_value *pConstObj;
sxu32 nIdx = 0;
/* Reserve a new constant */
pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pConstObj == 0 ){
GenStateOutOfMem(&(*pGen));
return 0;
}
(*pCount)++;
jx9MemObjInitFromString(pGen->pVm, pConstObj, 0);
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
return pConstObj;
}
/*
* Compile a double quoted/heredoc string.
* According to the JX9 language reference manual
* Heredoc
* A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier
* is provided, then a newline. The string itself follows, and then the same identifier again
* to close the quotation.
* The closing identifier must begin in the first column of the line. Also, the identifier must
* follow the same naming rules as any other label in JX9: it must contain only alphanumeric
* characters and underscores, and must start with a non-digit character or underscore.
* Warning
* It is very important to note that the line with the closing identifier must contain
* no other characters, except possibly a semicolon (;). That means especially that the identifier
* may not be indented, and there may not be any spaces or tabs before or after the semicolon.
* It's also important to realize that the first character before the closing identifier must
* be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X.
* The closing delimiter (possibly followed by a semicolon) must also be followed by a newline.
* If this rule is broken and the closing identifier is not "clean", it will not be considered a closing
* identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before
* the end of the current file, a parse error will result at the last line.
* Heredocs can not be used for initializing object properties.
* Double quoted
* If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters:
* Escaped characters Sequence Meaning
* \n linefeed (LF or 0x0A (10) in ASCII)
* \r carriage return (CR or 0x0D (13) in ASCII)
* \t horizontal tab (HT or 0x09 (9) in ASCII)
* \v vertical tab (VT or 0x0B (11) in ASCII)
* \f form feed (FF or 0x0C (12) in ASCII)
* \\ backslash
* \$ dollar sign
* \" double-quote
* \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation
* \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation
* As in single quoted strings, escaping any other character will result in the backslash being printed too.
* The most important feature of double-quoted strings is the fact that variable names will be expanded.
* See string parsing for details.
*/
static sxi32 GenStateCompileString(jx9_gen_state *pGen)
{
SyString *pStr = &pGen->pIn->sData; /* Raw token value */
const char *zIn, *zCur, *zEnd;
jx9_value *pObj = 0;
sxi32 iCons;
sxi32 rc;
/* Delimit the string */
zIn = pStr->zString;
zEnd = &zIn[pStr->nByte];
if( zIn > zEnd ){
/* Empty string, load NULL */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
return SXRET_OK;
}
zCur = 0;
/* Compile the node */
iCons = 0;
for(;;){
zCur = zIn;
while( zIn < zEnd && zIn[0] != '\\' ){
if(zIn[0] == '$' && &zIn[1] < zEnd &&
(((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){
break;
}
zIn++;
}
if( zIn > zCur ){
if( pObj == 0 ){
pObj = GenStateNewStrObj(&(*pGen), &iCons);
if( pObj == 0 ){
return SXERR_ABORT;
}
}
jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur));
}
else
{
if( pObj == 0 ){
pObj = GenStateNewStrObj(&(*pGen), &iCons);
if( pObj == 0 ){
return SXERR_ABORT;
}
}
jx9MemObjStringAppend(pObj, "", 0);
}
if( zIn >= zEnd ){
break;
}
if( zIn[0] == '\\' ){
const char *zPtr = 0;
sxu32 n;
zIn++;
if( zIn >= zEnd ){
break;
}
if( pObj == 0 ){
pObj = GenStateNewStrObj(&(*pGen), &iCons);
if( pObj == 0 ){
return SXERR_ABORT;
}
}
n = sizeof(char); /* size of conversion */
switch( zIn[0] ){
case '$':
/* Dollar sign */
jx9MemObjStringAppend(pObj, "$", sizeof(char));
break;
case '\\':
/* A literal backslash */
jx9MemObjStringAppend(pObj, "\\", sizeof(char));
break;
case 'a':
/* The "alert" character (BEL)[ctrl+g] ASCII code 7 */
jx9MemObjStringAppend(pObj, "\a", sizeof(char));
break;
case 'b':
/* Backspace (BS)[ctrl+h] ASCII code 8 */
jx9MemObjStringAppend(pObj, "\b", sizeof(char));
break;
case 'f':
/* Form-feed (FF)[ctrl+l] ASCII code 12 */
jx9MemObjStringAppend(pObj, "\f", sizeof(char));
break;
case 'n':
/* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */
jx9MemObjStringAppend(pObj, "\n", sizeof(char));
break;
case 'r':
/* Carriage return (CR)[ctrl+m] ASCII code 13 */
jx9MemObjStringAppend(pObj, "\r", sizeof(char));
break;
case 't':
/* Horizontal tab (HT)[ctrl+i] ASCII code 9 */
jx9MemObjStringAppend(pObj, "\t", sizeof(char));
break;
case 'v':
/* Vertical tab(VT)[ctrl+k] ASCII code 11 */
jx9MemObjStringAppend(pObj, "\v", sizeof(char));
break;
case '\'':
/* Single quote */
jx9MemObjStringAppend(pObj, "'", sizeof(char));
break;
case '"':
/* Double quote */
jx9MemObjStringAppend(pObj, "\"", sizeof(char));
break;
case '0':
/* NUL byte */
jx9MemObjStringAppend(pObj, "\0", sizeof(char));
break;
case 'x':
if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){
int c;
/* Hex digit */
c = SyHexToint(zIn[1]) << 4;
if( &zIn[2] < zEnd ){
c += SyHexToint(zIn[2]);
}
/* Output char */
jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char));
n += sizeof(char) * 2;
}else{
/* Output literal character */
jx9MemObjStringAppend(pObj, "x", sizeof(char));
}
break;
case 'o':
if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){
/* Octal digit stream */
int c;
c = 0;
zIn++;
for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){
if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){
break;
}
c = c * 8 + (zPtr[0] - '0');
}
if ( c > 0 ){
jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char));
}
n = (sxu32)(zPtr-zIn);
}else{
/* Output literal character */
jx9MemObjStringAppend(pObj, "o", sizeof(char));
}
break;
default:
/* Output without a slash */
jx9MemObjStringAppend(pObj, zIn, sizeof(char));
break;
}
/* Advance the stream cursor */
zIn += n;
continue;
}
if( zIn[0] == '{' ){
/* Curly syntax */
const char *zExpr;
sxi32 iNest = 1;
zIn++;
zExpr = zIn;
/* Synchronize with the next closing curly braces */
while( zIn < zEnd ){
if( zIn[0] == '{' ){
/* Increment nesting level */
iNest++;
}else if(zIn[0] == '}' ){
/* Decrement nesting level */
iNest--;
if( iNest <= 0 ){
break;
}
}
zIn++;
}
/* Process the expression */
rc = GenStateProcessStringExpression(&(*pGen),zExpr,zIn);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
++iCons;
}
if( zIn < zEnd ){
/* Jump the trailing curly */
zIn++;
}
}else{
/* Simple syntax */
const char *zExpr = zIn;
/* Assemble variable name */
for(;;){
/* Jump leading dollars */
while( zIn < zEnd && zIn[0] == '$' ){
zIn++;
}
for(;;){
while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){
zIn++;
}
if((unsigned char)zIn[0] >= 0xc0 ){
/* UTF-8 stream */
zIn++;
while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){
zIn++;
}
continue;
}
break;
}
if( zIn >= zEnd ){
break;
}
if( zIn[0] == '[' ){
sxi32 iSquare = 1;
zIn++;
while( zIn < zEnd ){
if( zIn[0] == '[' ){
iSquare++;
}else if (zIn[0] == ']' ){
iSquare--;
if( iSquare <= 0 ){
break;
}
}
zIn++;
}
if( zIn < zEnd ){
zIn++;
}
break;
}else if( zIn[0] == '.' ){
/* Member access operator '.' */
zIn++;
}else{
break;
}
}
/* Process the expression */
rc = GenStateProcessStringExpression(&(*pGen),zExpr, zIn);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
++iCons;
}
}
/* Invalidate the previously used constant */
pObj = 0;
}/*for(;;)*/
if( iCons > 1 ){
/* Concatenate all compiled constants */
jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a double quoted string.
* See the block-comment above for more information.
*/
JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag)
{
sxi32 rc;
rc = GenStateCompileString(&(*pGen));
SXUNUSED(iCompileFlag); /* cc warning */
/* Compilation result */
return rc;
}
/*
* Compile a literal which is an identifier(name) for simple values.
*/
JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
SyToken *pToken = pGen->pIn;
jx9_value *pObj;
SyString *pStr;
sxu32 nIdx;
/* Extract token value */
pStr = &pToken->sData;
/* Deal with the reserved literals [i.e: null, false, true, ...] first */
if( pStr->nByte == sizeof("NULL") - 1 ){
if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){
/* NULL constant are always indexed at 0 */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
return SXRET_OK;
}else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){
/* TRUE constant are always indexed at 1 */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1, 0, 0);
return SXRET_OK;
}
}else if (pStr->nByte == sizeof("FALSE") - 1 &&
SyStrnicmp(pStr->zString, "false", sizeof("FALSE")-1) == 0 ){
/* FALSE constant are always indexed at 2 */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 2, 0, 0);
return SXRET_OK;
}else if(pStr->nByte == sizeof("__LINE__") - 1 &&
SyMemcmp(pStr->zString, "__LINE__", sizeof("__LINE__")-1) == 0 ){
/* TICKET 1433-004: __LINE__ constant must be resolved at compile time, not run time */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
SXUNUSED(iCompileFlag); /* cc warning */
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine);
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
return SXRET_OK;
}else if( pStr->nByte == sizeof("__FUNCTION__") - 1 &&
SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-1) == 0 ){
GenBlock *pBlock = pGen->pCurrent;
/* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time, not run time */
while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){
/* Point to the upper block */
pBlock = pBlock->pParent;
}
if( pBlock == 0 ){
/* Called in the global scope, load NULL */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
}else{
/* Extract the target function/method */
jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData;
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName);
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
}
return SXRET_OK;
}
/* Query literal table */
if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){
jx9_value *pObj;
/* Unknown literal, install it in the literal table */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData);
GenStateInstallLiteral(&(*pGen), pObj, nIdx);
}
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile an array entry whether it is a key or a value.
*/
static sxi32 GenStateCompileJSONEntry(
jx9_gen_state *pGen, /* Code generator state */
SyToken *pIn, /* Token stream */
SyToken *pEnd, /* End of the token stream */
sxi32 iFlags, /* Compilation flags */
sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */
)
{
SyToken *pTmpIn, *pTmpEnd;
sxi32 rc;
/* Swap token stream */
SWAP_DELIMITER(pGen, pIn, pEnd);
/* Compile the expression*/
rc = jx9CompileExpr(&(*pGen), iFlags, xValidator);
/* Restore token stream */
RE_SWAP_DELIMITER(pGen);
return rc;
}
/*
* Compile a Jx9 JSON Array.
*/
JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag)
{
sxi32 nPair = 0;
SyToken *pCur;
sxi32 rc;
pGen->pIn++; /* Jump the open square bracket '['*/
pGen->pEnd--;
SXUNUSED(iCompileFlag); /* cc warning */
for(;;){
/* Jump leading commas */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){
pGen->pIn++;
}
pCur = pGen->pIn;
if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){
/* No more entry to process */
break;
}
/* Compile entry */
rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
nPair++;
}
/* Emit the load map instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Node validator for a given JSON key.
*/
static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot)
{
sxi32 rc = SXRET_OK;
if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString
&& pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){
/* Unexpected expression */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0,
"JSON Object: Unexpected expression, key must be of type string, literal or simple variable");
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
return rc;
}
/*
* Compile a Jx9 JSON Object
*/
JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag)
{
SyToken *pKey, *pCur;
sxi32 nPair = 0;
sxi32 rc;
pGen->pIn++; /* Jump the open querly braces '{'*/
pGen->pEnd--;
SXUNUSED(iCompileFlag); /* cc warning */
for(;;){
/* Jump leading commas */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){
pGen->pIn++;
}
pCur = pGen->pIn;
if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){
/* No more entry to process */
break;
}
/* Compile the key */
pKey = pCur;
while( pCur < pGen->pIn ){
if( pCur->nType & JX9_TK_COLON /*':'*/ ){
break;
}
pCur++;
}
rc = SXERR_EMPTY;
if( (pCur->nType & JX9_TK_COLON) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ABORT, pCur->nLine, "JSON Object: Missing colon string \":\"");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
if( pCur < pGen->pIn ){
if( &pCur[1] >= pGen->pIn ){
/* Missing value */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Compile the expression holding the key */
rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur,
EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */,
GenStateJSONObjectKeyNodeValidator /* Node validator callback */
);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pCur++; /* Jump the double colon ':' */
}else if( pKey == pCur ){
/* Key is omitted, emit an error */
jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key");
pCur++; /* Jump the double colon ':' */
}else{
/* Reset back the cursor and point to the entry value */
pCur = pKey;
}
/* Compile indice value */
rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
nPair++;
}
/* Emit the load map instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a function [i.e: print, exit(), include(), ...] which is a langauge
* construct.
*/
JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
SyString *pName;
sxu32 nKeyID;
sxi32 rc;
/* Name of the language construct [i.e: print, die...]*/
pName = &pGen->pIn->sData;
nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
pGen->pIn++; /* Jump the language construct keyword */
if( nKeyID == JX9_TKWRD_PRINT ){
SyToken *pTmp, *pNext = 0;
/* Compile arguments one after one */
pTmp = pGen->pEnd;
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0);
while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){
if( pGen->pIn < pNext ){
pGen->pEnd = pNext;
rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( rc != SXERR_EMPTY ){
/* Ticket 1433-008: Optimization #1: Consume input directly
* without the overhead of a function call.
* This is a very powerful optimization that improve
* performance greatly.
*/
jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0);
}
}
/* Jump trailing commas */
while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){
pNext++;
}
pGen->pIn = pNext;
}
/* Restore token stream */
pGen->pEnd = pTmp;
}else{
sxi32 nArg = 0;
sxu32 nIdx = 0;
rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nArg = 1;
}
if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){
jx9_value *pObj;
/* Emit the call instruction */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
SXUNUSED(iCompileFlag); /* cc warning */
return GenStateOutOfMem(pGen);
}
jx9MemObjInitFromString(pGen->pVm, pObj, pName);
/* Install in the literal table */
GenStateInstallLiteral(&(*pGen), pObj, nIdx);
}
/* Emit the call instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0);
}
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile a node holding a variable declaration.
* According to the J9X language reference
* Variables in JX9 are represented by a dollar sign followed by the name of the variable.
* The variable name is case-sensitive.
* Variable names follow the same rules as other labels in JX9. A valid variable name
* starts with a letter, underscore or any UTF-8 stream, followed by any number of letters
* numbers, or underscores.
* By default, variables are always assigned by value unless the target value is a JSON
* array or a JSON object which is passed by reference.
*/
JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
sxu32 nLine = pGen->pIn->nLine;
SyHashEntry *pEntry;
SyString *pName;
char *zName = 0;
sxi32 iP1;
void *p3;
sxi32 rc;
pGen->pIn++; /* Jump the dollar sign '$' */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
/* Invalid variable name */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Extract variable name */
pName = &pGen->pIn->sData;
/* Advance the stream cursor */
pGen->pIn++;
pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte);
if( pEntry == 0 ){
/* Duplicate name */
zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
if( zName == 0 ){
return GenStateOutOfMem(pGen);
}
/* Install in the hashtable */
SyHashInsert(&pGen->hVar, zName, pName->nByte, zName);
}else{
/* Name already available */
zName = (char *)pEntry->pUserData;
}
p3 = (void *)zName;
iP1 = 0;
if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){
if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){
/* Read-only load.In other words do not create the variable if inexistant */
iP1 = 1;
}
}
/* Emit the load instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/* Forward declaration */
static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc);
/*
* Compile an annoynmous function or a closure.
* According to the JX9 language reference
* Anonymous functions, also known as closures, allow the creation of functions
* which have no specified name. They are most useful as the value of callback
* parameters, but they have many other uses. Closures can also be used as
* the values of variables; Assigning a closure to a variable uses the same
* syntax as any other assignment, including the trailing semicolon:
* Example Anonymous function variable assignment example
* $greet = function($name)
* {
* printf("Hello %s\r\n", $name);
* };
* $greet('World');
* $greet('JX9');
* Note that the implementation of annoynmous function and closure under
* JX9 is completely different from the one used by the engine.
*/
JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag)
{
jx9_vm_func *pAnnonFunc; /* Annonymous function body */
char zName[512]; /* Unique lambda name */
static int iCnt = 1; /* There is no worry about thread-safety here, because only
* one thread is allowed to compile the script.
*/
jx9_value *pObj;
SyString sName;
sxu32 nIdx;
sxu32 nLen;
sxi32 rc;
pGen->pIn++; /* Jump the 'function' keyword */
if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){
pGen->pIn++;
}
/* Reserve a constant for the lambda */
pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
if( pObj == 0 ){
GenStateOutOfMem(pGen);
SXUNUSED(iCompileFlag); /* cc warning */
return SXERR_ABORT;
}
/* Generate a unique name */
nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++);
/* Make sure the generated name is unique */
while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){
nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++);
}
SyStringInitFromBuf(&sName, zName, nLen);
jx9MemObjInitFromString(pGen->pVm, pObj, &sName);
/* Compile the lambda body */
rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Emit the load constant instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
/* Node successfully compiled */
return SXRET_OK;
}
/*
* Compile the 'continue' statement.
* According to the JX9 language reference
* continue is used within looping structures to skip the rest of the current loop iteration
* and continue execution at the condition evaluation and then the beginning of the next
* iteration.
* Note: Note that in JX9 the switch statement is considered a looping structure for
* the purposes of continue.
* continue accepts an optional numeric argument which tells it how many levels
* of enclosing loops it should skip to the end of.
* Note:
* continue 0; and continue 1; is the same as running continue;.
*/
static sxi32 jx9CompileContinue(jx9_gen_state *pGen)
{
GenBlock *pLoop; /* Target loop */
sxi32 iLevel; /* How many nesting loop to skip */
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
iLevel = 0;
/* Jump the 'continue' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){
/* optional numeric argument which tells us how many levels
* of enclosing loops we should skip to the end of.
*/
iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData);
if( iLevel < 2 ){
iLevel = 0;
}
pGen->pIn++; /* Jump the optional numeric argument */
}
/* Point to the target loop */
pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel);
if( pLoop == 0 ){
/* Illegal continue */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}else{
sxu32 nInstrIdx = 0;
/* Emit the unconditional jump to the beginning of the target loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx);
if( pLoop->bPostContinue == TRUE ){
JumpFixup sJumpFix;
/* Post-continue */
sJumpFix.nJumpType = JX9_OP_JMP;
sJumpFix.nInstrIdx = nInstrIdx;
SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix);
}
}
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Not so fatal, emit a warning only */
jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement");
}
/* Statement successfully compiled */
return SXRET_OK;
}
/*
* Compile the 'break' statement.
* According to the JX9 language reference
* break ends execution of the current for, foreach, while, do-while or switch
* structure.
* break accepts an optional numeric argument which tells it how many nested
* enclosing structures are to be broken out of.
*/
static sxi32 jx9CompileBreak(jx9_gen_state *pGen)
{
GenBlock *pLoop; /* Target loop */
sxi32 iLevel; /* How many nesting loop to skip */
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
iLevel = 0;
/* Jump the 'break' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){
/* optional numeric argument which tells us how many levels
* of enclosing loops we should skip to the end of.
*/
iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData);
if( iLevel < 2 ){
iLevel = 0;
}
pGen->pIn++; /* Jump the optional numeric argument */
}
/* Extract the target loop */
pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel);
if( pLoop == 0 ){
/* Illegal break */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}else{
sxu32 nInstrIdx;
rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx);
if( rc == SXRET_OK ){
/* Fix the jump later when the jump destination is resolved */
GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx);
}
}
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Not so fatal, emit a warning only */
jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement");
}
/* Statement successfully compiled */
return SXRET_OK;
}
/* Forward declaration */
static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags);
/*
* Compile a JX9 block.
* A block is simply one or more JX9 statements and expressions to compile
* optionally delimited by braces {}.
* Return SXRET_OK on success. Any other return value indicates failure
* and this function takes care of generating the appropriate error
* message.
*/
static sxi32 jx9CompileBlock(
jx9_gen_state *pGen /* Code generator state */
)
{
sxi32 rc;
if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){
sxu32 nLine = pGen->pIn->nLine;
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(pGen->pVm), 0, 0);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
pGen->pIn++;
/* Compile until we hit the closing braces '}' */
for(;;){
if( pGen->pIn >= pGen->pEnd ){
/* No more token to process. Missing closing braces */
jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'");
break;
}
if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){
/* Closing braces found, break immediately*/
pGen->pIn++;
break;
}
/* Compile a single statement */
rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
GenStateLeaveBlock(&(*pGen), 0);
}else{
/* Compile a single statement */
rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
/* Jump trailing semi-colons ';' */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the gentle 'while' statement.
* According to the JX9 language reference
* while loops are the simplest type of loop in JX9.They behave just like their C counterparts.
* The basic form of a while statement is:
* while (expr)
* statement
* The meaning of a while statement is simple. It tells JX9 to execute the nested statement(s)
* repeatedly, as long as the while expression evaluates to TRUE. The value of the expression
* is checked each time at the beginning of the loop, so even if this value changes during
* the execution of the nested statement(s), execution will not stop until the end of the iteration
* (each time JX9 runs the statements in the loop is one iteration). Sometimes, if the while
* expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once.
* Like with the if statement, you can group multiple statements within the same while loop by surrounding
* a group of statements with curly braces, or by using the alternate syntax:
* while (expr):
* statement
* endwhile;
*/
static sxi32 jx9CompileWhile(jx9_gen_state *pGen)
{
GenBlock *pWhileBlock = 0;
SyToken *pTmp, *pEnd = 0;
sxu32 nFalseJump;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'while' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Delimit the condition */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Update token stream */
while(pGen->pIn < pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
/* Synchronize pointers */
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
/* Emit the false jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump);
/* Compile the loop body */
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Emit the unconditional jump to the start of the loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';' so we can avoid
* compiling this erroneous block.
*/
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the complex and powerful 'for' statement.
* According to the JX9 language reference
* for loops are the most complex loops in JX9. They behave like their C counterparts.
* The syntax of a for loop is:
* for (expr1; expr2; expr3)
* statement
* The first expression (expr1) is evaluated (executed) once unconditionally at
* the beginning of the loop.
* In the beginning of each iteration, expr2 is evaluated. If it evaluates to
* TRUE, the loop continues and the nested statement(s) are executed. If it evaluates
* to FALSE, the execution of the loop ends.
* At the end of each iteration, expr3 is evaluated (executed).
* Each of the expressions can be empty or contain multiple expressions separated by commas.
* In expr2, all expressions separated by a comma are evaluated but the result is taken
* from the last part. expr2 being empty means the loop should be run indefinitely
* (JX9 implicitly considers it as TRUE, like C). This may not be as useless as you might
* think, since often you'd want to end the loop using a conditional break statement instead
* of using the for truth expression.
*/
static sxi32 jx9CompileFor(jx9_gen_state *pGen)
{
SyToken *pTmp, *pPostStart, *pEnd = 0;
GenBlock *pForBlock = 0;
sxu32 nFalseJump;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'for' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Delimit the init-expr;condition;post-expr */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
/* Synchronize */
pGen->pIn = pEnd;
if( pGen->pIn < pGen->pEnd ){
pGen->pIn++;
}
return SXRET_OK;
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile initialization expressions if available */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Pop operand lvalues */
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
}
if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
"for: Expected ';' after initialization expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the trailing ';' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Deffer continue jumps */
pForBlock->bPostContinue = TRUE;
/* Compile the condition */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
/* Emit the false jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump);
}
if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
"for: Expected ';' after conditionals expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
/* Jump the trailing ';' */
pGen->pIn++;
/* Save the post condition stream */
pPostStart = pGen->pIn;
/* Compile the loop body */
pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */
pGen->pEnd = pTmp;
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Fix post-continue jumps */
if( SySetUsed(&pForBlock->aPostContFix) > 0 ){
JumpFixup *aPost;
VmInstr *pInstr;
sxu32 nJumpDest;
sxu32 n;
aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix);
nJumpDest = jx9VmInstrLength(pGen->pVm);
for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){
pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx);
if( pInstr ){
/* Fix jump */
pInstr->iP2 = nJumpDest;
}
}
}
/* compile the post-expressions if available */
while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){
pPostStart++;
}
if( pPostStart < pEnd ){
SyToken *pTmpIn, *pTmpEnd;
SWAP_DELIMITER(pGen, pPostStart, pEnd);
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( pGen->pIn < pGen->pEnd ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
return SXRET_OK;
}
RE_SWAP_DELIMITER(pGen);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY){
/* Pop operand lvalue */
jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
}
}
/* Emit the unconditional jump to the start of the loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
}
/* Expression tree validator callback used by the 'foreach' statement.
* Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...]
* are allowed.
*/
static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot)
{
sxi32 rc = SXRET_OK; /* Assume a valid expression tree */
if( pRoot->xCode != jx9CompileVariable ){
/* Unexpected expression */
rc = jx9GenCompileError(&(*pGen),
E_ERROR,
pRoot->pStart? pRoot->pStart->nLine : 0,
"foreach: Expecting a variable name"
);
if( rc != SXERR_ABORT ){
rc = SXERR_INVALID;
}
}
return rc;
}
/*
* Compile the 'foreach' statement.
* According to the JX9 language reference
* The foreach construct simply gives an easy way to iterate over arrays. foreach works
* only on arrays (and objects), and will issue an error when you try to use it on a variable
* with a different data type or an uninitialized variable. There are two syntaxes; the second
* is a minor but useful extension of the first:
* foreach (json_array_json_object as $value)
* statement
* foreach (json_array_json_objec as $key,$value)
* statement
* The first form loops over the array given by array_expression. On each loop, the value
* of the current element is assigned to $value and the internal array pointer is advanced
* by one (so on the next loop, you'll be looking at the next element).
* The second form does the same thing, except that the current element's key will be assigned
* to the variable $key on each loop.
* Note:
* When foreach first starts executing, the internal array pointer is automatically reset to the
* first element of the array. This means that you do not need to call reset() before a foreach loop.
*/
static sxi32 jx9CompileForeach(jx9_gen_state *pGen)
{
SyToken *pCur, *pTmp, *pEnd = 0;
GenBlock *pForeachBlock = 0;
jx9_foreach_info *pInfo;
sxu32 nFalseJump;
VmInstr *pInstr;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'foreach' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Delimit the expression */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
/* Synchronize */
pGen->pIn = pEnd;
if( pGen->pIn < pGen->pEnd ){
pGen->pIn++;
}
return SXRET_OK;
}
/* Compile the array expression */
pCur = pGen->pIn;
while( pCur < pEnd ){
if( pCur->nType & JX9_TK_KEYWORD ){
sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData);
if( nKeywrd == JX9_TKWRD_AS ){
/* Break with the first 'as' found */
break;
}
}
/* Advance the stream cursor */
pCur++;
}
if( pCur <= pGen->pIn ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
"foreach: Missing array/object expression");
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pCur;
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Update token stream */
while(pGen->pIn < pCur ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
pGen->pIn++;
}
pCur++; /* Jump the 'as' keyword */
pGen->pIn = pCur;
if( pGen->pIn >= pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
/* Create the foreach context */
pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info));
if( pInfo == 0 ){
jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory");
return SXERR_ABORT;
}
/* Zero the structure */
SyZero(pInfo, sizeof(jx9_foreach_info));
/* Initialize structure fields */
SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *));
/* Check if we have a key field */
while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){
pCur++;
}
if( pCur < pEnd ){
/* Compile the expression holding the key name */
if( pGen->pIn >= pCur ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key");
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
}else{
pGen->pEnd = pCur;
rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
pInstr = jx9VmPopInstr(pGen->pVm);
if( pInstr->p3 ){
/* Record key name */
SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3));
}
pInfo->iFlags |= JX9_4EACH_STEP_KEY;
}
pGen->pIn = &pCur[1]; /* Jump the arrow */
}
pGen->pEnd = pEnd;
if( pGen->pIn >= pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value");
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Compile the expression holding the value name */
rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
pInstr = jx9VmPopInstr(pGen->pVm);
if( pInstr->p3 ){
/* Record value name */
SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3));
}
/* Emit the 'FOREACH_INIT' instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump);
/* Record the first instruction to execute */
pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm);
/* Emit the FOREACH_STEP instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump);
/* Compile the loop body */
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
/* Emit the unconditional jump to the start of the loop */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0);
/* Fix all jumps now the destination is resolved */
GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';' so we can avoid
* compiling this erroneous block.
*/
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the infamous if/elseif/else if/else statements.
* According to the JX9 language reference
* The if construct is one of the most important features of many languages JX9 included.
* It allows for conditional execution of code fragments. JX9 features an if structure
* that is similar to that of C:
* if (expr)
* statement
* else construct:
* Often you'd want to execute a statement if a certain condition is met, and a different
* statement if the condition is not met. This is what else is for. else extends an if statement
* to execute a statement in case the expression in the if statement evaluates to FALSE.
* For example, the following code would display a is greater than b if $a is greater than
* $b, and a is NOT greater than b otherwise.
* The else statement is only executed if the if expression evaluated to FALSE, and if there
* were any elseif expressions - only if they evaluated to FALSE as well
* elseif
* elseif, as its name suggests, is a combination of if and else. Like else, it extends
* an if statement to execute a different statement in case the original if expression evaluates
* to FALSE. However, unlike else, it will execute that alternative expression only if the elseif
* conditional expression evaluates to TRUE. For example, the following code would display a is bigger
* than b, a equal to b or a is smaller than b:
* if ($a > $b) {
* print "a is bigger than b";
* } elseif ($a == $b) {
* print "a is equal to b";
* } else {
* print "a is smaller than b";
* }
*/
static sxi32 jx9CompileIf(jx9_gen_state *pGen)
{
SyToken *pToken, *pTmp, *pEnd = 0;
GenBlock *pCondBlock = 0;
sxu32 nJumpIdx;
sxu32 nKeyID;
sxi32 rc;
/* Jump the 'if' keyword */
pGen->pIn++;
pToken = pGen->pIn;
/* Create the conditional block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(pGen->pVm), 0, &pCondBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Process as many [if/else if/elseif/else] blocks as we can */
for(;;){
if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
if( pToken >= pGen->pEnd ){
pToken--;
}
rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pToken++;
/* Delimit the condition */
jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){
/* Syntax error */
if( pToken >= pGen->pEnd ){
pToken--;
}
rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Swap token streams */
SWAP_TOKEN_STREAM(pGen, pToken, pEnd);
/* Compile the condition */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Update token stream */
while(pGen->pIn < pEnd ){
jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
pGen->pIn++;
}
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Emit the false jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx);
/* Compile the body */
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
break;
}
/* Ensure that the keyword ID is 'else if' or 'else' */
nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){
break;
}
/* Emit the unconditional jump */
jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx);
/* Save the instruction index so we can fix it later when the jump destination is resolved */
GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx);
if( nKeyID & JX9_TKWRD_ELSE ){
pToken = &pGen->pIn[1];
if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 ||
SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){
break;
}
pGen->pIn++; /* Jump the 'else' keyword */
}
pGen->pIn++; /* Jump the 'elseif/if' keyword */
/* Synchronize cursors */
pToken = pGen->pIn;
/* Fix the false jump */
GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm));
} /* For(;;) */
/* Fix the false jump */
GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm));
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) &&
(SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){
/* Compile the else block */
pGen->pIn++;
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
nJumpIdx = jx9VmInstrLength(pGen->pVm);
/* Fix all unconditional jumps now the destination is resolved */
GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx);
/* Release the conditional block */
GenStateLeaveBlock(pGen, 0);
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block.
*/
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the return statement.
* According to the JX9 language reference
* If called from within a function, the return() statement immediately ends execution
* of the current function, and returns its argument as the value of the function call.
* return() will also end the execution of an eval() statement or script file.
* If called from the global scope, then execution of the current script file is ended.
* If the current script file was include()ed or require()ed, then control is passed back
* to the calling file. Furthermore, if the current script file was include()ed, then the value
* given to return() will be returned as the value of the include() call. If return() is called
* from within the main script file, then script execution end.
* Note that since return() is a language construct and not a function, the parentheses
* surrounding its arguments are not required. It is common to leave them out, and you actually
* should do so as JX9 has less work to do in this case.
* Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead..
*/
static sxi32 jx9CompileReturn(jx9_gen_state *pGen)
{
sxi32 nRet = 0; /* TRUE if there is a return value */
sxi32 rc;
/* Jump the 'return' keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nRet = 1;
}
}
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, nRet, 0, 0, 0);
return SXRET_OK;
}
/*
* Compile the die/exit language construct.
* The role of these constructs is to terminate execution of the script.
* Shutdown functions will always be executed even if exit() is called.
*/
static sxi32 jx9CompileHalt(jx9_gen_state *pGen)
{
sxi32 nExpr = 0;
sxi32 rc;
/* Jump the die/exit keyword */
pGen->pIn++;
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nExpr = 1;
}
}
/* Emit the HALT instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0);
return SXRET_OK;
}
/*
* Compile the static statement.
* According to the JX9 language reference
* Another important feature of variable scoping is the static variable.
* A static variable exists only in a local function scope, but it does not lose its value
* when program execution leaves this scope.
* Static variables also provide one way to deal with recursive functions.
*/
static sxi32 jx9CompileStatic(jx9_gen_state *pGen)
{
jx9_vm_func_static_var sStatic; /* Structure describing the static variable */
jx9_vm_func *pFunc; /* Enclosing function */
GenBlock *pBlock;
SyString *pName;
char *zDup;
sxu32 nLine;
sxi32 rc;
/* Jump the static keyword */
nLine = pGen->pIn->nLine;
pGen->pIn++;
/* Extract the enclosing function if any */
pBlock = pGen->pCurrent;
while( pBlock ){
if( pBlock->iFlags & GEN_BLOCK_FUNC){
break;
}
/* Point to the upper block */
pBlock = pBlock->pParent;
}
if( pBlock == 0 ){
/* Static statement, called outside of a function body, treat it as a simple variable. */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
goto Synchronize;
}
/* Compile the expression holding the variable */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if( rc != SXERR_EMPTY ){
/* Emit the POP instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
}
return SXRET_OK;
}
pFunc = (jx9_vm_func *)pBlock->pUserData;
/* Make sure we are dealing with a valid statement */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd ||
(pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++;
/* Extract variable name */
pName = &pGen->pIn->sData;
pGen->pIn++; /* Jump the var name */
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData);
goto Synchronize;
}
/* Initialize the structure describing the static variable */
SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
sStatic.nIdx = SXU32_HIGH; /* Not yet created */
/* Duplicate variable name */
zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
if( zDup == 0 ){
jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory");
return SXERR_ABORT;
}
SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte);
/* Check if we have an expression to compile */
if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){
SySet *pInstrContainer;
pGen->pIn++; /* Jump the equal '=' sign */
/* Swap bytecode container */
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode);
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
/* Restore default bytecode container */
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
}
/* Finally save the compiled static variable in the appropriate container */
SySetPut(&pFunc->aStatic, (const void *)&sStatic);
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous
* statement.
*/
while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the 'const' statement.
* According to the JX9 language reference
* A constant is an identifier (name) for a simple value. As the name suggests, that value
* cannot change during the execution of the script (except for magic constants, which aren't actually constants).
* A constant is case-sensitive by default. By convention, constant identifiers are always uppercase.
* The name of a constant follows the same rules as any label in JX9. A valid constant name starts
* with a letter or underscore, followed by any number of letters, numbers, or underscores.
* As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
* Syntax
* You can define a constant by using the define()-function or by using the const keyword outside
* a object definition. Once a constant is defined, it can never be changed or undefined.
* You can get the value of a constant by simply specifying its name. Unlike with variables
* you should not prepend a constant with a $. You can also use the function constant() to read
* a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants()
* to get a list of all defined constants.
*/
static sxi32 jx9CompileConstant(jx9_gen_state *pGen)
{
SySet *pConsCode, *pInstrContainer;
sxu32 nLine = pGen->pIn->nLine;
SyString *pName;
sxi32 rc;
pGen->pIn++; /* Jump the 'const' keyword */
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
/* Invalid constant name */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Peek constant name */
pName = &pGen->pIn->sData;
/* Make sure the constant name isn't reserved */
if( GenStateIsReservedID(pName) ){
/* Reserved constant */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName);
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++;
if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){
/* Invalid statement*/
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++; /*Jump the equal sign */
/* Allocate a new constant value container */
pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet));
if( pConsCode == 0 ){
return GenStateOutOfMem(pGen);
}
SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
/* Swap bytecode container */
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, pConsCode);
/* Compile constant value */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
if( rc == SXERR_ABORT ){
/* Don't worry about freeing memory, everything will be released shortly */
return SXERR_ABORT;
}
SySetSetUserData(pConsCode, pGen->pVm);
/* Register the constant */
rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode);
if( rc != SXRET_OK ){
SySetRelease(pConsCode);
SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode);
}
return SXRET_OK;
Synchronize:
/* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */
while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Compile the uplink construct.
* According to the JX9 language reference
* In JX9 global variables must be declared uplink inside a function if they are going
* to be used in that function.
* Example #1 Using global
* $a = 1;
* $b = 2;
* function Sum()
* {
* uplink $a, $b;
* $b = $a + $b;
* }
* Sum();
* print $b;
* ?>
* The above script will output 3. By declaring $a and $b global within the function
* all references to either variable will refer to the global version. There is no limit
* to the number of global variables that can be manipulated by a function.
*/
static sxi32 jx9CompileUplink(jx9_gen_state *pGen)
{
SyToken *pTmp, *pNext = 0;
sxi32 nExpr;
sxi32 rc;
/* Jump the 'uplink' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){
/* Nothing to process */
return SXRET_OK;
}
pTmp = pGen->pEnd;
nExpr = 0;
while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){
if( pGen->pIn < pNext ){
pGen->pEnd = pNext;
if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}else{
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd ){
/* Emit a warning */
jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name");
}else{
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}else if(rc != SXERR_EMPTY ){
nExpr++;
}
}
}
}
/* Next expression in the stream */
pGen->pIn = pNext;
/* Jump trailing commas */
while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){
pGen->pIn++;
}
}
/* Restore token stream */
pGen->pEnd = pTmp;
if( nExpr > 0 ){
/* Emit the uplink instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0);
}
return SXRET_OK;
}
/*
* Compile a switch block.
* (See block-comment below for more information)
*/
static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart)
{
sxi32 rc = SXRET_OK;
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
pGen->pIn++;
/* First instruction to execute in this block. */
*pBlockStart = jx9VmInstrLength(pGen->pVm);
/* Compile the block until we hit a case/default/endswitch keyword
* or the '}' token */
for(;;){
if( pGen->pIn >= pGen->pEnd ){
/* No more input to process */
break;
}
rc = SXRET_OK;
if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){
rc = SXERR_EOF;
break;
}
}else{
sxi32 nKwrd;
/* Extract the keyword */
nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){
break;
}
}
/* Compile block */
rc = jx9CompileBlock(&(*pGen));
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
return rc;
}
/*
* Compile a case eXpression.
* (See block-comment below for more information)
*/
static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_case_expr *pExpr)
{
SySet *pInstrContainer;
SyToken *pEnd, *pTmp;
sxi32 iNest = 0;
sxi32 rc;
/* Delimit the expression */
pEnd = pGen->pIn;
while( pEnd < pGen->pEnd ){
if( pEnd->nType & JX9_TK_LPAREN /*(*/ ){
/* Increment nesting level */
iNest++;
}else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){
/* Decrement nesting level */
iNest--;
}else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){
break;
}
pEnd++;
}
if( pGen->pIn >= pEnd ){
rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}
/* Swap token stream */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode);
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
/* Update token stream */
pGen->pIn = pEnd;
pGen->pEnd = pTmp;
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
/*
* Compile the smart switch statement.
* According to the JX9 language reference manual
* The switch statement is similar to a series of IF statements on the same expression.
* In many occasions, you may want to compare the same variable (or expression) with many
* different values, and execute a different piece of code depending on which value it equals to.
* This is exactly what the switch statement is for.
* Note: Note that unlike some other languages, the continue statement applies to switch and acts
* similar to break. If you have a switch inside a loop and wish to continue to the next iteration
* of the outer loop, use continue 2.
* Note that switch/case does loose comparision.
* It is important to understand how the switch statement is executed in order to avoid mistakes.
* The switch statement executes line by line (actually, statement by statement).
* In the beginning, no code is executed. Only when a case statement is found with a value that
* matches the value of the switch expression does JX9 begin to execute the statements.
* JX9 continues to execute the statements until the end of the switch block, or the first time
* it sees a break statement. If you don't write a break statement at the end of a case's statement list.
* In a switch statement, the condition is evaluated only once and the result is compared to each
* case statement. In an elseif statement, the condition is evaluated again. If your condition
* is more complicated than a simple compare and/or is in a tight loop, a switch may be faster.
* The statement list for a case can also be empty, which simply passes control into the statement
* list for the next case.
* The case expression may be any expression that evaluates to a simple type, that is, integer
* or floating-point numbers and strings.
*/
static sxi32 jx9CompileSwitch(jx9_gen_state *pGen)
{
GenBlock *pSwitchBlock;
SyToken *pTmp, *pEnd;
jx9_switch *pSwitch;
sxu32 nLine;
sxi32 rc;
nLine = pGen->pIn->nLine;
/* Jump the 'switch' keyword */
pGen->pIn++;
if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
/* Syntax error */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
goto Synchronize;
}
/* Jump the left parenthesis '(' */
pGen->pIn++;
pEnd = 0; /* cc warning */
/* Create the loop block */
rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH,
jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock);
if( rc != SXRET_OK ){
return SXERR_ABORT;
}
/* Delimit the condition */
jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
/* Empty expression */
rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword");
if( rc == SXERR_ABORT ){
/* Error count limit reached, abort immediately */
return SXERR_ABORT;
}
}
/* Swap token streams */
pTmp = pGen->pEnd;
pGen->pEnd = pEnd;
/* Compile the expression */
rc = jx9CompileExpr(&(*pGen), 0, 0);
if( rc == SXERR_ABORT ){
/* Expression handler request an operation abort [i.e: Out-of-memory] */
return SXERR_ABORT;
}
/* Update token stream */
while(pGen->pIn < pEnd ){
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
"Switch: Unexpected token '%z'", &pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
pGen->pIn++;
}
pGen->pIn = &pEnd[1];
pGen->pEnd = pTmp;
if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd ||
(pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){
pTmp = pGen->pIn;
if( pTmp >= pGen->pEnd ){
pTmp--;
}
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
goto Synchronize;
}
pGen->pIn++; /* Jump the leading curly braces/colons */
/* Create the switch blocks container */
pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch));
if( pSwitch == 0 ){
/* Abort compilation */
return GenStateOutOfMem(pGen);
}
/* Zero the structure */
SyZero(pSwitch, sizeof(jx9_switch));
/* Initialize fields */
SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr));
/* Emit the switch instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0);
/* Compile case blocks */
for(;;){
sxu32 nKwrd;
if( pGen->pIn >= pGen->pEnd ){
/* No more input to process */
break;
}
if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'",
&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* FALL THROUGH */
}
/* Block compiled */
break;
}
/* Extract the keyword */
nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
if( nKwrd == JX9_TKWRD_DEFAULT ){
/*
* Accroding to the JX9 language reference manual
* A special case is the default case. This case matches anything
* that wasn't matched by the other cases.
*/
if( pSwitch->nDefault > 0 ){
/* Default case already compiled */
rc = jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled");
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
pGen->pIn++; /* Jump the 'default' keyword */
/* Compile the default block */
rc = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault);
if( rc == SXERR_ABORT){
return SXERR_ABORT;
}else if( rc == SXERR_EOF ){
break;
}
}else if( nKwrd == JX9_TKWRD_CASE ){
jx9_case_expr sCase;
/* Standard case block */
pGen->pIn++; /* Jump the 'case' keyword */
/* initialize the structure */
SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
/* Compile the case expression */
rc = GenStateCompileCaseExpr(pGen, &sCase);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
/* Compile the case block */
rc = GenStateCompileSwitchBlock(pGen,&sCase.nStart);
/* Insert in the switch container */
SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase);
if( rc == SXERR_ABORT){
return SXERR_ABORT;
}else if( rc == SXERR_EOF ){
break;
}
}else{
/* Unexpected token */
rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'",
&pGen->pIn->sData);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
break;
}
}
/* Fix all jumps now the destination is resolved */
pSwitch->nOut = jx9VmInstrLength(pGen->pVm);
GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm));
/* Release the loop block */
GenStateLeaveBlock(pGen, 0);
if( pGen->pIn < pGen->pEnd ){
/* Jump the trailing curly braces */
pGen->pIn++;
}
/* Statement successfully compiled */
return SXRET_OK;
Synchronize:
/* Synchronize with the first semi-colon */
while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
pGen->pIn++;
}
return SXRET_OK;
}
/*
* Process default argument values. That is, a function may define C++-style default value
* as follows:
* function makecoffee($type = "cappuccino")
* {
* return "Making a cup of $type.\n";
* }
* Some features:
* 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous
* functions, array member, ..]
* 2 -) Full type hinting: (Arguments are automatically casted to the desired type)
* Example:
* function a(string $a){} function b(int $a, string $c, float $d){}
* 3 -) Function overloading!!
* Example:
* function foo($a) {
* return $a.JX9_EOL;
* }
* function foo($a, $b) {
* return $a + $b;
* }
* print foo(5); // Prints "5"
* print foo(5, 2); // Prints "7"
* // Same arg
* function foo(string $a)
* {
* print "a is a string\n";
* dump($a);
* }
* function foo(int $a)
* {
* print "a is integer\n";
* dump($a);
* }
* function foo(array $a)
* {
* print "a is an array\n";
* dump($a);
* }
* foo('This is a great feature'); // a is a string [first foo]
* foo(52); // a is integer [second foo]
* foo(array(14, __TIME__, __DATE__)); // a is an array [third foo]
* Please refer to the official documentation for more information on the powerful extension
* introduced by the JX9 engine.
*/
static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd)
{
SyToken *pTmpIn, *pTmpEnd;
SySet *pInstrContainer;
sxi32 rc;
/* Swap token stream */
SWAP_DELIMITER(pGen, pIn, pEnd);
pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode);
/* Compile the expression holding the argument value */
rc = jx9CompileExpr(&(*pGen), 0, 0);
/* Emit the done instruction */
jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
RE_SWAP_DELIMITER(pGen);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
return SXRET_OK;
}
/*
* Collect function arguments one after one.
* According to the JX9 language reference manual.
* Information may be passed to functions via the argument list, which is a comma-delimited
* list of expressions.
* JX9 supports passing arguments by value (the default), passing by reference
* and default argument values. Variable-length argument lists are also supported,
* see also the function references for func_num_args(), func_get_arg(), and func_get_args()
* for more information.
* Example #1 Passing arrays to functions
* <?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 ){
close(fd);
return SXRET_OK;
}
/* FALL THRU */
close(fd);
}
pid = getpid();
SyMemcpy((const void *)&pid, zBuf, SXMIN(nLen, sizeof(pid_t)));
if( &zBuf[nLen] - &zBuf[sizeof(pid_t)] >= (int)sizeof(struct timeval) ){
gettimeofday((struct timeval *)&zBuf[sizeof(pid_t)], 0);
}
#else
/* Fill with uninitialized data */
SyMemcpy(zGarbage, zBuf, SXMIN(nLen, sizeof(zGarbage)));
#endif
return SXRET_OK;
}
JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void * pUserData)
{
char zSeed[256];
sxu8 t;
sxi32 rc;
sxu32 i;
if( pCtx->nMagic == SXPRNG_MAGIC ){
return SXRET_OK; /* Already initialized */
}
/* Initialize the state of the random number generator once,
** the first time this routine is called.The seed value does
** not need to contain a lot of randomness since we are not
** trying to do secure encryption or anything like that...
*/
if( xSeed == 0 ){
xSeed = SyOSUtilRandomSeed;
}
rc = xSeed(zSeed, sizeof(zSeed), pUserData);
if( rc != SXRET_OK ){
return rc;
}
pCtx->i = pCtx->j = 0;
for(i=0; i < SX_ARRAYSIZE(pCtx->s) ; i++){
pCtx->s[i] = (unsigned char)i;
}
for(i=0; i < sizeof(zSeed) ; i++){
pCtx->j += pCtx->s[i] + zSeed[i];
t = pCtx->s[pCtx->j];
pCtx->s[pCtx->j] = pCtx->s[i];
pCtx->s[i] = t;
}
pCtx->nMagic = SXPRNG_MAGIC;
return SXRET_OK;
}
/*
* Get a single 8-bit random value using the RC4 PRNG.
*/
static sxu8 randomByte(SyPRNGCtx *pCtx)
{
sxu8 t;
/* Generate and return single random byte */
pCtx->i++;
t = pCtx->s[pCtx->i];
pCtx->j += t;
pCtx->s[pCtx->i] = pCtx->s[pCtx->j];
pCtx->s[pCtx->j] = t;
t += pCtx->s[pCtx->i];
return pCtx->s[t];
}
JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen)
{
unsigned char *zBuf = (unsigned char *)pBuf;
unsigned char *zEnd = &zBuf[nLen];
#if defined(UNTRUST)
if( pCtx == 0 || pBuf == 0 || nLen <= 0 ){
return SXERR_EMPTY;
}
#endif
if(pCtx->nMagic != SXPRNG_MAGIC ){
return SXERR_CORRUPT;
}
for(;;){
if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
}
return SXRET_OK;
}
#ifndef JX9_DISABLE_BUILTIN_FUNC
#ifndef JX9_DISABLE_HASH_FUNC
/* SyRunTimeApi: sxhash.c */
/*
* This code implements the MD5 message-digest algorithm.
* The algorithm is due to Ron Rivest.This code was
* written by Colin Plumb in 1993, no copyright is claimed.
* This code is in the public domain; do with it what you wish.
*
* Equivalent code is available from RSA Data Security, Inc.
* This code has been tested against that, and is equivalent,
* except that you don't need to include two pages of legalese
* with every copy.
*
* To compute the message digest of a chunk of bytes, declare an
* MD5Context structure, pass it to MD5Init, call MD5Update as
* needed on buffers full of bytes, and then call MD5Final, which
* will fill a supplied 16-byte array with the digest.
*/
#define SX_MD5_BINSZ 16
#define SX_MD5_HEXSZ 32
/*
* Note: this code is harmless on little-endian machines.
*/
static void byteReverse (unsigned char *buf, unsigned longs)
{
sxu32 t;
do {
t = (sxu32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
((unsigned)buf[1]<<8 | buf[0]);
*(sxu32*)buf = t;
buf += 4;
} while (--longs);
}
/* The four core functions - F1 is optimized somewhat */
/* #define F1(x, y, z) (x & y | ~x & z) */
#ifdef F1
#undef F1
#endif
#ifdef F2
#undef F2
#endif
#ifdef F3
#undef F3
#endif
#ifdef F4
#undef F4
#endif
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
/* This is the central step in the MD5 algorithm.*/
#define SX_MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w = w<<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_RES|MEMOBJ_BOOL) ){
/* Convert to boolean: Keep in mind FALSE < TRUE */
if( (pObj1->iFlags & MEMOBJ_BOOL) == 0 ){
jx9MemObjToBool(pObj1);
}
if( (pObj2->iFlags & MEMOBJ_BOOL) == 0 ){
jx9MemObjToBool(pObj2);
}
return (sxi32)((pObj1->x.iVal != 0) - (pObj2->x.iVal != 0));
}else if( iComb & MEMOBJ_NULL ){
if( (pObj1->iFlags & MEMOBJ_NULL) == 0 ){
return 1;
}
if( (pObj2->iFlags & MEMOBJ_NULL) == 0 ){
return -1;
}
}else if ( iComb & MEMOBJ_HASHMAP ){
/* Hashmap aka 'array' comparison */
if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){
/* Array is always greater */
return -1;
}
if( (pObj2->iFlags & MEMOBJ_HASHMAP) == 0 ){
/* Array is always greater */
return 1;
}
/* Perform the comparison */
rc = jx9HashmapCmp((jx9_hashmap *)pObj1->x.pOther, (jx9_hashmap *)pObj2->x.pOther, bStrict);
return rc;
}else if ( iComb & MEMOBJ_STRING ){
SyString s1, s2;
/* Perform a strict string comparison.*/
if( (pObj1->iFlags&MEMOBJ_STRING) == 0 ){
jx9MemObjToString(pObj1);
}
if( (pObj2->iFlags&MEMOBJ_STRING) == 0 ){
jx9MemObjToString(pObj2);
}
SyStringInitFromBuf(&s1, SyBlobData(&pObj1->sBlob), SyBlobLength(&pObj1->sBlob));
SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob));
/*
* Strings are compared using memcmp(). If one value is an exact prefix of the
* other, then the shorter value is less than the longer value.
*/
rc = SyMemcmp((const void *)s1.zString, (const void *)s2.zString, SXMIN(s1.nByte, s2.nByte));
if( rc == 0 ){
if( s1.nByte != s2.nByte ){
rc = s1.nByte < s2.nByte ? -1 : 1;
}
}
return rc;
}else if( iComb & (MEMOBJ_INT|MEMOBJ_REAL) ){
/* Perform a numeric comparison if one of the operand is numeric(integer or real) */
if( (pObj1->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){
jx9MemObjToNumeric(pObj1);
}
if( (pObj2->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){
jx9MemObjToNumeric(pObj2);
}
if( (pObj1->iFlags & pObj2->iFlags & MEMOBJ_INT) == 0) {
jx9_real r1, r2;
/* Compare as reals */
if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){
jx9MemObjToReal(pObj1);
}
r1 = pObj1->x.rVal;
if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){
jx9MemObjToReal(pObj2);
}
r2 = pObj2->x.rVal;
if( r1 > r2 ){
return 1;
}else if( r1 < r2 ){
return -1;
}
return 0;
}else{
/* Integer comparison */
if( pObj1->x.iVal > pObj2->x.iVal ){
return 1;
}else if( pObj1->x.iVal < pObj2->x.iVal ){
return -1;
}
return 0;
}
}
/* NOT REACHED */
SXUNUSED(iNest);
return 0;
}
/*
* Perform an addition operation of two jx9_values.
* The reason this function is implemented here rather than 'vm.c'
* is that the '+' operator is overloaded.
* That is, the '+' operator is used for arithmetic operation and also
* used for operation on arrays [i.e: union]. When used with an array
* The + operator returns the right-hand array appended to the left-hand array.
* For keys that exist in both arrays, the elements from the left-hand array
* will be used, and the matching elements from the right-hand array will
* be ignored.
* This function take care of handling all the scenarios.
*/
JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore)
{
if( ((pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP) == 0 ){
/* Arithemtic operation */
jx9MemObjToNumeric(pObj1);
jx9MemObjToNumeric(pObj2);
if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_REAL ){
/* Floating point arithmetic */
jx9_real a, b;
if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){
jx9MemObjToReal(pObj1);
}
if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){
jx9MemObjToReal(pObj2);
}
a = pObj1->x.rVal;
b = pObj2->x.rVal;
pObj1->x.rVal = a+b;
MemObjSetType(pObj1, MEMOBJ_REAL);
/* Try to get an integer representation also */
MemObjTryIntger(&(*pObj1));
}else{
/* Integer arithmetic */
sxi64 a, b;
a = pObj1->x.iVal;
b = pObj2->x.iVal;
pObj1->x.iVal = a+b;
MemObjSetType(pObj1, MEMOBJ_INT);
}
}else{
if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP ){
jx9_hashmap *pMap;
sxi32 rc;
if( bAddStore ){
/* Do not duplicate the hashmap, use the left one since its an add&store operation.
*/
if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){
/* Force a hashmap cast */
rc = jx9MemObjToHashmap(pObj1);
if( rc != SXRET_OK ){
jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array");
return rc;
}
}
/* Point to the structure that describe the hashmap */
pMap = (jx9_hashmap *)pObj1->x.pOther;
}else{
/* Create a new hashmap */
pMap = jx9NewHashmap(pObj1->pVm, 0, 0);
if( pMap == 0){
jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array");
return SXERR_MEM;
}
}
if( !bAddStore ){
if(pObj1->iFlags & MEMOBJ_HASHMAP ){
/* Perform a hashmap duplication */
jx9HashmapDup((jx9_hashmap *)pObj1->x.pOther, pMap);
}else{
if((pObj1->iFlags & MEMOBJ_NULL) == 0 ){
/* Simple insertion */
jx9HashmapInsert(pMap, 0, pObj1);
}
}
}
/* Perform the union */
if(pObj2->iFlags & MEMOBJ_HASHMAP ){
jx9HashmapUnion(pMap, (jx9_hashmap *)pObj2->x.pOther);
}else{
if((pObj2->iFlags & MEMOBJ_NULL) == 0 ){
/* Simple insertion */
jx9HashmapInsert(pMap, 0, pObj2);
}
}
/* Reflect the change */
if( pObj1->iFlags & MEMOBJ_STRING ){
SyBlobRelease(&pObj1->sBlob);
}
pObj1->x.pOther = pMap;
MemObjSetType(pObj1, MEMOBJ_HASHMAP);
}
}
return SXRET_OK;
}
/*
* Return a printable representation of the type of a given
* jx9_value.
*/
JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal)
{
const char *zType = "";
if( pVal->iFlags & MEMOBJ_NULL ){
zType = "null";
}else if( pVal->iFlags & MEMOBJ_INT ){
zType = "int";
}else if( pVal->iFlags & MEMOBJ_REAL ){
zType = "float";
}else if( pVal->iFlags & MEMOBJ_STRING ){
zType = "string";
}else if( pVal->iFlags & MEMOBJ_BOOL ){
zType = "bool";
}else if( pVal->iFlags & MEMOBJ_HASHMAP ){
jx9_hashmap *pMap = (jx9_hashmap *)pVal->x.pOther;
if( pMap->iFlags & HASHMAP_JSON_OBJECT ){
zType = "JSON Object";
}else{
zType = "JSON Array";
}
}else if( pVal->iFlags & MEMOBJ_RES ){
zType = "resource";
}
return zType;
}
/*
* Dump a jx9_value [i.e: get a printable representation of it's type and contents.].
* Store the dump in the given blob.
*/
JX9_PRIVATE sxi32 jx9MemObjDump(
SyBlob *pOut, /* Store the dump here */
jx9_value *pObj /* Dump this */
)
{
sxi32 rc = SXRET_OK;
const char *zType;
/* Get value type first */
zType = jx9MemObjTypeDump(pObj);
SyBlobAppend(&(*pOut), zType, SyStrlen(zType));
if((pObj->iFlags & MEMOBJ_NULL) == 0 ){
SyBlobAppend(&(*pOut), "(", sizeof(char));
if( pObj->iFlags & MEMOBJ_HASHMAP ){
jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther;
SyBlobFormat(pOut,"%u ",pMap->nEntry);
/* Dump hashmap entries */
rc = jx9JsonSerialize(pObj,pOut);
}else{
SyBlob *pContents = &pObj->sBlob;
/* Get a printable representation of the contents */
if((pObj->iFlags & MEMOBJ_STRING) == 0 ){
MemObjStringValue(&(*pOut), &(*pObj));
}else{
/* Append length first */
SyBlobFormat(&(*pOut), "%u '", SyBlobLength(&pObj->sBlob));
if( SyBlobLength(pContents) > 0 ){
SyBlobAppend(&(*pOut), SyBlobData(pContents), SyBlobLength(pContents));
}
SyBlobAppend(&(*pOut), "'", sizeof(char));
}
}
SyBlobAppend(&(*pOut), ")", sizeof(char));
}
#ifdef __WINNT__
SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n")-1);
#else
SyBlobAppend(&(*pOut), "\n", sizeof(char));
#endif
return rc;
}
/*
* ----------------------------------------------------------
* File: jx9_parse.c
* MD5: d8fcac4c6cd7672f0103c0bf4a4b61fc
* ----------------------------------------------------------
*/
/*
* Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
* Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
* Version 1.7.2
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://jx9.symisc.net/
*/
/* $SymiscID: parse.c v1.2 FreeBSD 2012-12-11 00:46 stable <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 — Dumps information about a variable
* Parameters
* One or more expression to dump.
* Returns
* Nothing.
*/
static int vm_builtin_dump(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
SyBlob sDump; /* Generated dump is stored here */
int i;
SyBlobInit(&sDump,&pCtx->pVm->sAllocator);
/* Dump one or more expressions */
for( i = 0 ; i < nArg ; i++ ){
jx9_value *pObj = apArg[i];
/* Reset the working buffer */
SyBlobReset(&sDump);
/* Dump the given expression */
jx9MemObjDump(&sDump,pObj);
/* Output */
if( SyBlobLength(&sDump) > 0 ){
jx9_context_output(pCtx, (const char *)SyBlobData(&sDump), (int)SyBlobLength(&sDump));
}
}
/* Release the working buffer */
SyBlobRelease(&sDump);
return SXRET_OK;
}
/*
* Section:
* Version, Credits and Copyright related functions.
* Authors:
* Symisc Systems, devel@symisc.net.
* Copyright (C) Symisc Systems, http://jx9.symisc.net
* Stable.
*/
/*
* string jx9_version(void)
* string jx9_credits(void)
* Returns the running version of the jx9 version.
* Parameters
* None
* Return
* Current jx9 version.
*/
static int vm_builtin_jx9_version(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
SXUNUSED(nArg);
SXUNUSED(apArg); /* cc warning */
/* Current engine version, signature and cipyright notice */
jx9_result_string_format(pCtx,"%s %s, %s",JX9_VERSION,JX9_SIG,JX9_COPYRIGHT);
return JX9_OK;
}
/*
* Section:
* URL related routines.
* Authors:
* Symisc Systems, devel@symisc.net.
* Copyright (C) Symisc Systems, http://jx9.symisc.net
* Status:
* Stable.
*/
/* Forward declaration */
static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen);
/*
* value parse_url(string $url [, int $component = -1 ])
* Parse a URL and return its fields.
* Parameters
* $url
* The URL to parse.
* $component
* Specify one of JX9_URL_SCHEME, JX9_URL_HOST, JX9_URL_PORT, JX9_URL_USER
* JX9_URL_PASS, JX9_URL_PATH, JX9_URL_QUERY or JX9_URL_FRAGMENT to retrieve
* just a specific URL component as a string (except when JX9_URL_PORT is given
* in which case the return value will be an integer).
* Return
* If the component parameter is omitted, an associative array is returned.
* At least one element will be present within the array. Potential keys within
* this array are:
* scheme - e.g. http
* host
* port
* user
* pass
* path
* query - after the question mark ?
* fragment - after the hashmark #
* Note:
* FALSE is returned on failure.
* This function work with relative URL unlike the one shipped
* with the standard JX9 engine.
*/
static int vm_builtin_parse_url(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
const char *zStr; /* Input string */
SyString *pComp; /* Pointer to the URI component */
SyhttpUri sURI; /* Parse of the given URI */
int nLen;
sxi32 rc;
if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
/* Missing/Invalid arguments, return FALSE */
jx9_result_bool(pCtx, 0);
return JX9_OK;
}
/* Extract the given URI */
zStr = jx9_value_to_string(apArg[0], &nLen);
if( nLen < 1 ){
/* Nothing to process, return FALSE */
jx9_result_bool(pCtx, 0);
return JX9_OK;
}
/* Get a parse */
rc = VmHttpSplitURI(&sURI, zStr, (sxu32)nLen);
if( rc != SXRET_OK ){
/* Malformed input, return FALSE */
jx9_result_bool(pCtx, 0);
return JX9_OK;
}
if( nArg > 1 ){
int nComponent = jx9_value_to_int(apArg[1]);
/* Refer to constant.c for constants values */
switch(nComponent){
case 1: /* JX9_URL_SCHEME */
pComp = &sURI.sScheme;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
}
break;
case 2: /* JX9_URL_HOST */
pComp = &sURI.sHost;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
}
break;
case 3: /* JX9_URL_PORT */
pComp = &sURI.sPort;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
int iPort = 0;
/* Cast the value to integer */
SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0);
jx9_result_int(pCtx, iPort);
}
break;
case 4: /* JX9_URL_USER */
pComp = &sURI.sUser;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
}
break;
case 5: /* JX9_URL_PASS */
pComp = &sURI.sPass;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
}
break;
case 7: /* JX9_URL_QUERY */
pComp = &sURI.sQuery;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
}
break;
case 8: /* JX9_URL_FRAGMENT */
pComp = &sURI.sFragment;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
}
break;
case 6: /* JX9_URL_PATH */
pComp = &sURI.sPath;
if( pComp->nByte < 1 ){
/* No available value, return NULL */
jx9_result_null(pCtx);
}else{
jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
}
break;
default:
/* No such entry, return NULL */
jx9_result_null(pCtx);
break;
}
}else{
jx9_value *pArray, *pValue;
/* Return an associative array */
pArray = jx9_context_new_array(pCtx); /* Empty array */
pValue = jx9_context_new_scalar(pCtx); /* Array value */
if( pArray == 0 || pValue == 0 ){
/* Out of memory */
jx9_context_throw_error(pCtx, JX9_CTX_ERR, "jx9 engine is running out of memory");
/* Return false */
jx9_result_bool(pCtx, 0);
return JX9_OK;
}
/* Fill the array */
pComp = &sURI.sScheme;
if( pComp->nByte > 0 ){
jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
jx9_array_add_strkey_elem(pArray, "scheme", pValue); /* Will make it's own copy */
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pValue);
pComp = &sURI.sHost;
if( pComp->nByte > 0 ){
jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
jx9_array_add_strkey_elem(pArray, "host", pValue); /* Will make it's own copy */
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pValue);
pComp = &sURI.sPort;
if( pComp->nByte > 0 ){
int iPort = 0;/* cc warning */
/* Convert to integer */
SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0);
jx9_value_int(pValue, iPort);
jx9_array_add_strkey_elem(pArray, "port", pValue); /* Will make it's own copy */
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pValue);
pComp = &sURI.sUser;
if( pComp->nByte > 0 ){
jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
jx9_array_add_strkey_elem(pArray, "user", pValue); /* Will make it's own copy */
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pValue);
pComp = &sURI.sPass;
if( pComp->nByte > 0 ){
jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
jx9_array_add_strkey_elem(pArray, "pass", pValue); /* Will make it's own copy */
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pValue);
pComp = &sURI.sPath;
if( pComp->nByte > 0 ){
jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
jx9_array_add_strkey_elem(pArray, "path", pValue); /* Will make it's own copy */
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pValue);
pComp = &sURI.sQuery;
if( pComp->nByte > 0 ){
jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
jx9_array_add_strkey_elem(pArray, "query", pValue); /* Will make it's own copy */
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pValue);
pComp = &sURI.sFragment;
if( pComp->nByte > 0 ){
jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
jx9_array_add_strkey_elem(pArray, "fragment", pValue); /* Will make it's own copy */
}
/* Return the created array */
jx9_result_value(pCtx, pArray);
/* NOTE:
* Don't worry about freeing 'pValue', everything will be released
* automatically as soon we return from this function.
*/
}
/* All done */
return JX9_OK;
}
/*
* Section:
* Array related routines.
* Authors:
* Symisc Systems, devel@symisc.net.
* Copyright (C) Symisc Systems, http://jx9.symisc.net
* Status:
* Stable.
* Note 2012-5-21 01:04:15:
* Array related functions that need access to the underlying
* virtual machine are implemented here rather than 'hashmap.c'
*/
/*
* The [extract()] function store it's state information in an instance
* of the following structure.
*/
typedef struct extract_aux_data extract_aux_data;
struct extract_aux_data
{
jx9_vm *pVm; /* VM that own this instance */
int iCount; /* Number of variables successfully imported */
const char *zPrefix; /* Prefix name */
int Prefixlen; /* Prefix length */
int iFlags; /* Control flags */
char zWorker[1024]; /* Working buffer */
};
/* Forward declaration */
static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData);
/*
* int extract(array $var_array[, int $extract_type = EXTR_OVERWRITE[, string $prefix = NULL ]])
* Import variables into the current symbol table from an array.
* Parameters
* $var_array
* An associative array. This function treats keys as variable names and values
* as variable values. For each key/value pair it will create a variable in the current symbol
* table, subject to extract_type and prefix parameters.
* You must use an associative array; a numerically indexed array will not produce results
* unless you use EXTR_PREFIX_ALL or EXTR_PREFIX_INVALID.
* $extract_type
* The way invalid/numeric keys and collisions are treated is determined by the extract_type.
* It can be one of the following values:
* EXTR_OVERWRITE
* If there is a collision, overwrite the existing variable.
* EXTR_SKIP
* If there is a collision, don't overwrite the existing variable.
* EXTR_PREFIX_SAME
* If there is a collision, prefix the variable name with prefix.
* EXTR_PREFIX_ALL
* Prefix all variable names with prefix.
* EXTR_PREFIX_INVALID
* Only prefix invalid/numeric variable names with prefix.
* EXTR_IF_EXISTS
* Only overwrite the variable if it already exists in the current symbol table
* otherwise do nothing.
* This is useful for defining a list of valid variables and then extracting only those
* variables you have defined out of $_REQUEST, for example.
* EXTR_PREFIX_IF_EXISTS
* Only create prefixed variable names if the non-prefixed version of the same variable exists in
* the current symbol table.
* $prefix
* Note that prefix is only required if extract_type is EXTR_PREFIX_SAME, EXTR_PREFIX_ALL
* EXTR_PREFIX_INVALID or EXTR_PREFIX_IF_EXISTS. If the prefixed result is not a valid variable name
* it is not imported into the symbol table. Prefixes are automatically separated from the array key by an
* underscore character.
* Return
* Returns the number of variables successfully imported into the symbol table.
*/
static int vm_builtin_extract(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
extract_aux_data sAux;
jx9_hashmap *pMap;
if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){
/* Missing/Invalid arguments, return 0 */
jx9_result_int(pCtx, 0);
return JX9_OK;
}
/* Point to the target hashmap */
pMap = (jx9_hashmap *)apArg[0]->x.pOther;
if( pMap->nEntry < 1 ){
/* Empty map, return 0 */
jx9_result_int(pCtx, 0);
return JX9_OK;
}
/* Prepare the aux data */
SyZero(&sAux, sizeof(extract_aux_data)-sizeof(sAux.zWorker));
if( nArg > 1 ){
sAux.iFlags = jx9_value_to_int(apArg[1]);
if( nArg > 2 ){
sAux.zPrefix = jx9_value_to_string(apArg[2], &sAux.Prefixlen);
}
}
sAux.pVm = pCtx->pVm;
/* Invoke the worker callback */
jx9HashmapWalk(pMap, VmExtractCallback, &sAux);
/* Number of variables successfully imported */
jx9_result_int(pCtx, sAux.iCount);
return JX9_OK;
}
/*
* Worker callback for the [extract()] function defined
* below.
*/
static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData)
{
extract_aux_data *pAux = (extract_aux_data *)pUserData;
int iFlags = pAux->iFlags;
jx9_vm *pVm = pAux->pVm;
jx9_value *pObj;
SyString sVar;
if( (iFlags & 0x10/* EXTR_PREFIX_INVALID */) && (pKey->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL|MEMOBJ_REAL))){
iFlags |= 0x08; /*EXTR_PREFIX_ALL*/
}
/* Perform a string cast */
jx9MemObjToString(pKey);
if( SyBlobLength(&pKey->sBlob) < 1 ){
/* Unavailable variable name */
return SXRET_OK;
}
sVar.nByte = 0; /* cc warning */
if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/ ) && pAux->Prefixlen > 0 ){
sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker, sizeof(pAux->zWorker), "%.*s_%.*s",
pAux->Prefixlen, pAux->zPrefix,
SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob)
);
}else{
sVar.nByte = (sxu32) SyMemcpy(SyBlobData(&pKey->sBlob), pAux->zWorker,
SXMIN(SyBlobLength(&pKey->sBlob), sizeof(pAux->zWorker)));
}
sVar.zString = pAux->zWorker;
/* Try to extract the variable */
pObj = VmExtractMemObj(pVm, &sVar, TRUE, FALSE);
if( pObj ){
/* Collision */
if( iFlags & 0x02 /* EXTR_SKIP */ ){
return SXRET_OK;
}
if( iFlags & 0x04 /* EXTR_PREFIX_SAME */ ){
if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/) || pAux->Prefixlen < 1){
/* Already prefixed */
return SXRET_OK;
}
sVar.nByte = SyBufferFormat(
pAux->zWorker, sizeof(pAux->zWorker),
"%.*s_%.*s",
pAux->Prefixlen, pAux->zPrefix,
SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob)
);
pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE);
}
}else{
/* Create the variable */
pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE);
}
if( pObj ){
/* Overwrite the old value */
jx9MemObjStore(pValue, pObj);
/* Increment counter */
pAux->iCount++;
}
return SXRET_OK;
}
/*
* Compile and evaluate a JX9 chunk at run-time.
* Refer to the include language construct implementation for more
* information.
*/
static sxi32 VmEvalChunk(
jx9_vm *pVm, /* Underlying Virtual Machine */
jx9_context *pCtx, /* Call Context */
SyString *pChunk, /* JX9 chunk to evaluate */
int iFlags, /* Compile flag */
int bTrueReturn /* TRUE to return execution result */
)
{
SySet *pByteCode, aByteCode;
ProcConsumer xErr = 0;
void *pErrData = 0;
/* Initialize bytecode container */
SySetInit(&aByteCode, &pVm->sAllocator, sizeof(VmInstr));
SySetAlloc(&aByteCode, 0x20);
/* Reset the code generator */
if( bTrueReturn ){
/* Included file, log compile-time errors */
xErr = pVm->pEngine->xConf.xErr;
pErrData = pVm->pEngine->xConf.pErrData;
}
jx9ResetCodeGenerator(pVm, xErr, pErrData);
/* Swap bytecode container */
pByteCode = pVm->pByteContainer;
pVm->pByteContainer = &aByteCode;
/* Compile the chunk */
jx9CompileScript(pVm, pChunk, iFlags);
if( pVm->sCodeGen.nErr > 0 ){
/* Compilation error, return false */
if( pCtx ){
jx9_result_bool(pCtx, 0);
}
}else{
jx9_value sResult; /* Return value */
if( SXRET_OK != jx9VmEmitInstr(pVm, JX9_OP_DONE, 0, 0, 0, 0) ){
/* Out of memory */
if( pCtx ){
jx9_result_bool(pCtx, 0);
}
goto Cleanup;
}
if( bTrueReturn ){
/* Assume a boolean true return value */
jx9MemObjInitFromBool(pVm, &sResult, 1);
}else{
/* Assume a null return value */
jx9MemObjInit(pVm, &sResult);
}
/* Execute the compiled chunk */
VmLocalExec(pVm, &aByteCode, &sResult);
if( pCtx ){
/* Set the execution result */
jx9_result_value(pCtx, &sResult);
}
jx9MemObjRelease(&sResult);
}
Cleanup:
/* Cleanup the mess left behind */
pVm->pByteContainer = pByteCode;
SySetRelease(&aByteCode);
return SXRET_OK;
}
/*
* Check if a file path is already included.
*/
static int VmIsIncludedFile(jx9_vm *pVm, SyString *pFile)
{
SyString *aEntries;
sxu32 n;
aEntries = (SyString *)SySetBasePtr(&pVm->aIncluded);
/* Perform a linear search */
for( n = 0 ; n < SySetUsed(&pVm->aIncluded) ; ++n ){
if( SyStringCmp(pFile, &aEntries[n], SyMemcmp) == 0 ){
/* Already included */
return TRUE;
}
}
return FALSE;
}
/*
* Push a file path in the appropriate VM container.
*/
JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew)
{
SyString sPath;
char *zDup;
#ifdef __WINNT__
char *zCur;
#endif
sxi32 rc;
if( nLen < 0 ){
nLen = SyStrlen(zPath);
}
/* Duplicate the file path first */
zDup = SyMemBackendStrDup(&pVm->sAllocator, zPath, nLen);
if( zDup == 0 ){
return SXERR_MEM;
}
#ifdef __WINNT__
/* Normalize path on windows
* Example:
* Path/To/File.jx9
* becomes
* path\to\file.jx9
*/
zCur = zDup;
while( zCur[0] != 0 ){
if( zCur[0] == '/' ){
zCur[0] = '\\';
}else if( (unsigned char)zCur[0] < 0xc0 && SyisUpper(zCur[0]) ){
int c = SyToLower(zCur[0]);
zCur[0] = (char)c; /* MSVC stupidity */
}
zCur++;
}
#endif
/* Install the file path */
SyStringInitFromBuf(&sPath, zDup, nLen);
if( !bMain ){
if( VmIsIncludedFile(&(*pVm), &sPath) ){
/* Already included */
*pNew = 0;
}else{
/* Insert in the corresponding container */
rc = SySetPut(&pVm->aIncluded, (const void *)&sPath);
if( rc != SXRET_OK ){
SyMemBackendFree(&pVm->sAllocator, zDup);
return rc;
}
*pNew = 1;
}
}
SySetPut(&pVm->aFiles, (const void *)&sPath);
return SXRET_OK;
}
/*
* Compile and Execute a JX9 script at run-time.
* SXRET_OK is returned on sucessful evaluation.Any other return values
* indicates failure.
* Note that the JX9 script to evaluate can be a local or remote file.In
* either cases the [jx9StreamReadWholeFile()] function handle all the underlying
* operations.
* If the [jJX9_DISABLE_BUILTIN_FUNC] compile-time directive is defined, then
* this function is a no-op.
* Refer to the implementation of the include(), import() language
* constructs for more information.
*/
static sxi32 VmExecIncludedFile(
jx9_context *pCtx, /* Call Context */
SyString *pPath, /* Script path or URL*/
int IncludeOnce /* TRUE if called from import() or require_once() */
)
{
sxi32 rc;
#ifndef JX9_DISABLE_BUILTIN_FUNC
const jx9_io_stream *pStream;
SyBlob sContents;
void *pHandle;
jx9_vm *pVm;
int isNew;
/* Initialize fields */
pVm = pCtx->pVm;
SyBlobInit(&sContents, &pVm->sAllocator);
isNew = 0;
/* Extract the associated stream */
pStream = jx9VmGetStreamDevice(pVm, &pPath->zString, pPath->nByte);
/*
* Open the file or the URL [i.e: http://jx9.symisc.net/example/hello.jx9.txt"]
* in a read-only mode.
*/
pHandle = jx9StreamOpenHandle(pVm, pStream,pPath->zString, JX9_IO_OPEN_RDONLY, TRUE, 0, TRUE, &isNew);
if( pHandle == 0 ){
return SXERR_IO;
}
rc = SXRET_OK; /* Stupid cc warning */
if( IncludeOnce && !isNew ){
/* Already included */
rc = SXERR_EXISTS;
}else{
/* Read the whole file contents */
rc = jx9StreamReadWholeFile(pHandle, pStream, &sContents);
if( rc == SXRET_OK ){
SyString sScript;
/* Compile and execute the script */
SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents));
VmEvalChunk(pCtx->pVm, &(*pCtx), &sScript, 0, TRUE);
}
}
/* Pop from the set of included file */
(void)SySetPop(&pVm->aFiles);
/* Close the handle */
jx9StreamCloseHandle(pStream, pHandle);
/* Release the working buffer */
SyBlobRelease(&sContents);
#else
pCtx = 0; /* cc warning */
pPath = 0;
IncludeOnce = 0;
rc = SXERR_IO;
#endif /* JX9_DISABLE_BUILTIN_FUNC */
return rc;
}
/* * include:
* According to the JX9 reference manual.
* The include() function includes and evaluates the specified file.
* Files are included based on the file path given or, if none is given
* the include_path specified.If the file isn't found in the include_path
* include() will finally check in the calling script's own directory
* and the current working directory before failing. The include()
* construct will emit a warning if it cannot find a file; this is different
* behavior from require(), which will emit a fatal error.
* If a path is defined — whether absolute (starting with a drive letter
* or \ on Windows, or / on Unix/Linux systems) or relative to the current
* directory (starting with . or ..) — the include_path will be ignored altogether.
* For example, if a filename begins with ../, the parser will look in the parent
* directory to find the requested file.
* When a file is included, the code it contains inherits the variable scope
* of the line on which the include occurs. Any variables available at that line
* in the calling file will be available within the called file, from that point forward.
* However, all functions and objectes defined in the included file have the global scope.
*/
static int vm_builtin_include(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
SyString sFile;
sxi32 rc;
if( nArg < 1 ){
/* Nothing to evaluate, return NULL */
jx9_result_null(pCtx);
return SXRET_OK;
}
/* File to include */
sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte);
if( sFile.nByte < 1 ){
/* Empty string, return NULL */
jx9_result_null(pCtx);
return SXRET_OK;
}
/* Open, compile and execute the desired script */
rc = VmExecIncludedFile(&(*pCtx), &sFile, FALSE);
if( rc != SXRET_OK ){
/* Emit a warning and return false */
jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile);
jx9_result_bool(pCtx, 0);
}
return SXRET_OK;
}
/*
* import:
* According to the JX9 reference manual.
* The import() statement includes and evaluates the specified file during
* the execution of the script. This is a behavior similar to the include()
* statement, with the only difference being that if the code from a file has already
* been included, it will not be included again. As the name suggests, it will be included
* just once.
*/
static int vm_builtin_import(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
SyString sFile;
sxi32 rc;
if( nArg < 1 ){
/* Nothing to evaluate, return NULL */
jx9_result_null(pCtx);
return SXRET_OK;
}
/* File to include */
sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte);
if( sFile.nByte < 1 ){
/* Empty string, return NULL */
jx9_result_null(pCtx);
return SXRET_OK;
}
/* Open, compile and execute the desired script */
rc = VmExecIncludedFile(&(*pCtx), &sFile, TRUE);
if( rc == SXERR_EXISTS ){
/* File already included, return TRUE */
jx9_result_bool(pCtx, 1);
return SXRET_OK;
}
if( rc != SXRET_OK ){
/* Emit a warning and return false */
jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile);
jx9_result_bool(pCtx, 0);
}
return SXRET_OK;
}
/*
* Section:
* Command line arguments processing.
* Authors:
* Symisc Systems, devel@symisc.net.
* Copyright (C) Symisc Systems, http://jx9.symisc.net
* Status:
* Stable.
*/
/*
* Check if a short option argument [i.e: -c] is available in the command
* line string. Return a pointer to the start of the stream on success.
* NULL otherwise.
*/
static const char * VmFindShortOpt(int c, const char *zIn, const char *zEnd)
{
while( zIn < zEnd ){
if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == c ){
/* Got one */
return &zIn[1];
}
/* Advance the cursor */
zIn++;
}
/* No such option */
return 0;
}
/*
* Check if a long option argument [i.e: --opt] is available in the command
* line string. Return a pointer to the start of the stream on success.
* NULL otherwise.
*/
static const char * VmFindLongOpt(const char *zLong, int nByte, const char *zIn, const char *zEnd)
{
const char *zOpt;
while( zIn < zEnd ){
if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == '-' ){
zIn += 2;
zOpt = zIn;
while( zIn < zEnd && !SyisSpace(zIn[0]) ){
if( zIn[0] == '=' /* --opt=val */){
break;
}
zIn++;
}
/* Test */
if( (int)(zIn-zOpt) == nByte && SyMemcmp(zOpt, zLong, nByte) == 0 ){
/* Got one, return it's value */
return zIn;
}
}else{
zIn++;
}
}
/* No such option */
return 0;
}
/*
* Long option [i.e: --opt] arguments private data structure.
*/
struct getopt_long_opt
{
const char *zArgIn, *zArgEnd; /* Command line arguments */
jx9_value *pWorker; /* Worker variable*/
jx9_value *pArray; /* getopt() return value */
jx9_context *pCtx; /* Call Context */
};
/* Forward declaration */
static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData);
/*
* Extract short or long argument option values.
*/
static void VmExtractOptArgValue(
jx9_value *pArray, /* getopt() return value */
jx9_value *pWorker, /* Worker variable */
const char *zArg, /* Argument stream */
const char *zArgEnd, /* End of the argument stream */
int need_val, /* TRUE to fetch option argument */
jx9_context *pCtx, /* Call Context */
const char *zName /* Option name */)
{
jx9_value_bool(pWorker, 0);
if( !need_val ){
/*
* Option does not need arguments.
* Insert the option name and a boolean FALSE.
*/
jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */
}else{
const char *zCur;
/* Extract option argument */
zArg++;
if( zArg < zArgEnd && zArg[0] == '=' ){
zArg++;
}
while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){
zArg++;
}
if( zArg >= zArgEnd || zArg[0] == '-' ){
/*
* Argument not found.
* Insert the option name and a boolean FALSE.
*/
jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */
return;
}
/* Delimit the value */
zCur = zArg;
if( zArg[0] == '\'' || zArg[0] == '"' ){
int d = zArg[0];
/* Delimt the argument */
zArg++;
zCur = zArg;
while( zArg < zArgEnd ){
if( zArg[0] == d && zArg[-1] != '\\' ){
/* Delimiter found, exit the loop */
break;
}
zArg++;
}
/* Save the value */
jx9_value_string(pWorker, zCur, (int)(zArg-zCur));
if( zArg < zArgEnd ){ zArg++; }
}else{
while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){
zArg++;
}
/* Save the value */
jx9_value_string(pWorker, zCur, (int)(zArg-zCur));
}
/*
* Check if we are dealing with multiple values.
* If so, create an array to hold them, rather than a scalar variable.
*/
while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){
zArg++;
}
if( zArg < zArgEnd && zArg[0] != '-' ){
jx9_value *pOptArg; /* Array of option arguments */
pOptArg = jx9_context_new_array(pCtx);
if( pOptArg == 0 ){
jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
}else{
/* Insert the first value */
jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */
for(;;){
if( zArg >= zArgEnd || zArg[0] == '-' ){
/* No more value */
break;
}
/* Delimit the value */
zCur = zArg;
if( zArg < zArgEnd && zArg[0] == '\\' ){
zArg++;
zCur = zArg;
}
while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){
zArg++;
}
/* Reset the string cursor */
jx9_value_reset_string_cursor(pWorker);
/* Save the value */
jx9_value_string(pWorker, zCur, (int)(zArg-zCur));
/* Insert */
jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */
/* Jump trailing white spaces */
while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){
zArg++;
}
}
/* Insert the option arg array */
jx9_array_add_strkey_elem(pArray, (const char *)zName, pOptArg); /* Will make it's own copy */
/* Safely release */
jx9_context_release_value(pCtx, pOptArg);
}
}else{
/* Single value */
jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */
}
}
}
/*
* array getopt(string $options[, array $longopts ])
* Gets options from the command line argument list.
* Parameters
* $options
* Each character in this string will be used as option characters
* and matched against options passed to the script starting with
* a single hyphen (-). For example, an option string "x" recognizes
* an option -x. Only a-z, A-Z and 0-9 are allowed.
* $longopts
* An array of options. Each element in this array will be used as option
* strings and matched against options passed to the script starting with
* two hyphens (--). For example, an longopts element "opt" recognizes an
* option --opt.
* Return
* This function will return an array of option / argument pairs or FALSE
* on failure.
*/
static int vm_builtin_getopt(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
const char *zIn, *zEnd, *zArg, *zArgIn, *zArgEnd;
struct getopt_long_opt sLong;
jx9_value *pArray, *pWorker;
SyBlob *pArg;
int nByte;
if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
/* Missing/Invalid arguments, return FALSE */
jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Missing/Invalid option arguments");
jx9_result_bool(pCtx, 0);
return JX9_OK;
}
/* Extract option arguments */
zIn = jx9_value_to_string(apArg[0], &nByte);
zEnd = &zIn[nByte];
/* Point to the string representation of the $argv[] array */
pArg = &pCtx->pVm->sArgv;
/* Create a new empty array and a worker variable */
pArray = jx9_context_new_array(pCtx);
pWorker = jx9_context_new_scalar(pCtx);
if( pArray == 0 || pWorker == 0 ){
jx9_context_throw_error(pCtx,JX9_CTX_ERR, "JX9 is running out of memory");
jx9_result_bool(pCtx, 0);
return JX9_OK;
}
if( SyBlobLength(pArg) < 1 ){
/* Empty command line, return the empty array*/
jx9_result_value(pCtx, pArray);
/* Everything will be released automatically when we return
* from this function.
*/
return JX9_OK;
}
zArgIn = (const char *)SyBlobData(pArg);
zArgEnd = &zArgIn[SyBlobLength(pArg)];
/* Fill the long option structure */
sLong.pArray = pArray;
sLong.pWorker = pWorker;
sLong.zArgIn = zArgIn;
sLong.zArgEnd = zArgEnd;
sLong.pCtx = pCtx;
/* Start processing */
while( zIn < zEnd ){
int c = zIn[0];
int need_val = 0;
/* Advance the stream cursor */
zIn++;
/* Ignore non-alphanum characters */
if( !SyisAlphaNum(c) ){
continue;
}
if( zIn < zEnd && zIn[0] == ':' ){
zIn++;
need_val = 1;
if( zIn < zEnd && zIn[0] == ':' ){
zIn++;
}
}
/* Find option */
zArg = VmFindShortOpt(c, zArgIn, zArgEnd);
if( zArg == 0 ){
/* No such option */
continue;
}
/* Extract option argument value */
VmExtractOptArgValue(pArray, pWorker, zArg, zArgEnd, need_val, pCtx, (const char *)&c);
}
if( nArg > 1 && jx9_value_is_json_array(apArg[1]) && jx9_array_count(apArg[1]) > 0 ){
/* Process long options */
jx9_array_walk(apArg[1], VmProcessLongOpt, &sLong);
}
/* Return the option array */
jx9_result_value(pCtx, pArray);
/*
* Don't worry about freeing memory, everything will be released
* automatically as soon we return from this foreign function.
*/
return JX9_OK;
}
/*
* Array walker callback used for processing long options values.
*/
static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData)
{
struct getopt_long_opt *pOpt = (struct getopt_long_opt *)pUserData;
const char *zArg, *zOpt, *zEnd;
int need_value = 0;
int nByte;
/* Value must be of type string */
if( !jx9_value_is_string(pValue) ){
/* Simply ignore */
return JX9_OK;
}
zOpt = jx9_value_to_string(pValue, &nByte);
if( nByte < 1 ){
/* Empty string, ignore */
return JX9_OK;
}
zEnd = &zOpt[nByte - 1];
if( zEnd[0] == ':' ){
char *zTerm;
/* Try to extract a value */
need_value = 1;
while( zEnd >= zOpt && zEnd[0] == ':' ){
zEnd--;
}
if( zOpt >= zEnd ){
/* Empty string, ignore */
SXUNUSED(pKey);
return JX9_OK;
}
zEnd++;
zTerm = (char *)zEnd;
zTerm[0] = 0;
}else{
zEnd = &zOpt[nByte];
}
/* Find the option */
zArg = VmFindLongOpt(zOpt, (int)(zEnd-zOpt), pOpt->zArgIn, pOpt->zArgEnd);
if( zArg == 0 ){
/* No such option, return immediately */
return JX9_OK;
}
/* Try to extract a value */
VmExtractOptArgValue(pOpt->pArray, pOpt->pWorker, zArg, pOpt->zArgEnd, need_value, pOpt->pCtx, zOpt);
return JX9_OK;
}
/*
* int utf8_encode(string $input)
* UTF-8 encoding.
* This function encodes the string data to UTF-8, and returns the encoded version.
* UTF-8 is a standard mechanism used by Unicode for encoding wide character values
* into a byte stream. UTF-8 is transparent to plain ASCII characters, is self-synchronized
* (meaning it is possible for a program to figure out where in the bytestream characters start)
* and can be used with normal string comparison functions for sorting and such.
* Notes on UTF-8 (According to SQLite3 authors):
* Byte-0 Byte-1 Byte-2 Byte-3 Value
* 0xxxxxxx 00000000 00000000 0xxxxxxx
* 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx
* 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx
* 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx
* Parameters
* $input
* String to encode or NULL on failure.
* Return
* An UTF-8 encoded string.
*/
static int vm_builtin_utf8_encode(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
const unsigned char *zIn, *zEnd;
int nByte, c, e;
if( nArg < 1 ){
/* Missing arguments, return null */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Extract the target string */
zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte);
if( nByte < 1 ){
/* Empty string, return null */
jx9_result_null(pCtx);
return JX9_OK;
}
zEnd = &zIn[nByte];
/* Start the encoding process */
for(;;){
if( zIn >= zEnd ){
/* End of input */
break;
}
c = zIn[0];
/* Advance the stream cursor */
zIn++;
/* Encode */
if( c<0x00080 ){
e = (c&0xFF);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
}else if( c<0x00800 ){
e = 0xC0 + ((c>>6)&0x1F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
e = 0x80 + (c & 0x3F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
}else if( c<0x10000 ){
e = 0xE0 + ((c>>12)&0x0F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
e = 0x80 + ((c>>6) & 0x3F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
e = 0x80 + (c & 0x3F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
}else{
e = 0xF0 + ((c>>18) & 0x07);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
e = 0x80 + ((c>>12) & 0x3F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
e = 0x80 + ((c>>6) & 0x3F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
e = 0x80 + (c & 0x3F);
jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
}
}
/* All done */
return JX9_OK;
}
/*
* UTF-8 decoding routine extracted from the sqlite3 source tree.
* Original author: D. Richard Hipp (http://www.sqlite.org)
* Status: Public Domain
*/
/*
** This lookup table is used to help decode the first byte of
** a multi-byte UTF8 character.
*/
static const unsigned char UtfTrans1[] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
};
/*
** Translate a single UTF-8 character. Return the unicode value.
**
** During translation, assume that the byte that zTerm points
** is a 0x00.
**
** Write a pointer to the next unread byte back into *pzNext.
**
** Notes On Invalid UTF-8:
**
** * This routine never allows a 7-bit character (0x00 through 0x7f) to
** be encoded as a multi-byte character. Any multi-byte character that
** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd.
**
** * This routine never allows a UTF16 surrogate value to be encoded.
** If a multi-byte character attempts to encode a value between
** 0xd800 and 0xe000 then it is rendered as 0xfffd.
**
** * Bytes in the range of 0x80 through 0xbf which occur as the first
** byte of a character are interpreted as single-byte characters
** and rendered as themselves even though they are technically
** invalid characters.
**
** * This routine accepts an infinite number of different UTF8 encodings
** for unicode values 0x80 and greater. It do not change over-length
** encodings to 0xfffd as some systems recommend.
*/
#define READ_UTF8(zIn, zTerm, c) \
c = *(zIn++); \
if( c>=0xc0 ){ \
c = UtfTrans1[c-0xc0]; \
while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
c = (c<<6) + (0x3f & *(zIn++)); \
} \
if( c<0x80 \
|| (c&0xFFFFF800)==0xD800 \
|| (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
}
JX9_PRIVATE int jx9Utf8Read(
const unsigned char *z, /* First byte of UTF-8 character */
const unsigned char *zTerm, /* Pretend this byte is 0x00 */
const unsigned char **pzNext /* Write first byte past UTF-8 char here */
){
int c;
READ_UTF8(z, zTerm, c);
*pzNext = z;
return c;
}
/*
* string utf8_decode(string $data)
* This function decodes data, assumed to be UTF-8 encoded, to unicode.
* Parameters
* data
* An UTF-8 encoded string.
* Return
* Unicode decoded string or NULL on failure.
*/
static int vm_builtin_utf8_decode(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
const unsigned char *zIn, *zEnd;
int nByte, c;
if( nArg < 1 ){
/* Missing arguments, return null */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Extract the target string */
zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte);
if( nByte < 1 ){
/* Empty string, return null */
jx9_result_null(pCtx);
return JX9_OK;
}
zEnd = &zIn[nByte];
/* Start the decoding process */
while( zIn < zEnd ){
c = jx9Utf8Read(zIn, zEnd, &zIn);
if( c == 0x0 ){
break;
}
jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
}
return JX9_OK;
}
/*
* string json_encode(mixed $value)
* Returns a string containing the JSON representation of value.
* Parameters
* $value
* The value being encoded. Can be any type except a resource.
* Return
* Returns a JSON encoded string on success. FALSE otherwise
*/
static int vm_builtin_json_encode(jx9_context *pCtx,int nArg,jx9_value **apArg)
{
SyBlob sBlob;
if( nArg < 1 ){
/* Missing arguments, return FALSE */
jx9_result_bool(pCtx, 0);
return JX9_OK;
}
/* Init the working buffer */
SyBlobInit(&sBlob,&pCtx->pVm->sAllocator);
/* Perform the encoding operation */
jx9JsonSerialize(apArg[0],&sBlob);
/* Return the serialized value */
jx9_result_string(pCtx,(const char *)SyBlobData(&sBlob),(int)SyBlobLength(&sBlob));
/* Cleanup */
SyBlobRelease(&sBlob);
/* All done */
return JX9_OK;
}
/*
* mixed json_decode(string $json)
* Takes a JSON encoded string and converts it into a JX9 variable.
* Parameters
* $json
* The json string being decoded.
* Return
* The value encoded in json in appropriate JX9 type. Values true, false and null (case-insensitive)
* are returned as TRUE, FALSE and NULL respectively. NULL is returned if the json cannot be decoded
* or if the encoded data is deeper than the recursion limit.
*/
static int vm_builtin_json_decode(jx9_context *pCtx, int nArg, jx9_value **apArg)
{
const char *zJSON;
int nByte;
if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
/* Missing/Invalid arguments, return NULL */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Extract the JSON string */
zJSON = jx9_value_to_string(apArg[0], &nByte);
if( nByte < 1 ){
/* Empty string, return NULL */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Decode the raw JSON */
jx9JsonDecode(pCtx,zJSON,nByte);
return JX9_OK;
}
/* Table of built-in VM functions. */
static const jx9_builtin_func aVmFunc[] = {
/* JSON Encoding/Decoding */
{ "json_encode", vm_builtin_json_encode },
{ "json_decode", vm_builtin_json_decode },
/* Functions calls */
{ "func_num_args" , vm_builtin_func_num_args },
{ "func_get_arg" , vm_builtin_func_get_arg },
{ "func_get_args" , vm_builtin_func_get_args },
{ "function_exists", vm_builtin_func_exists },
{ "is_callable" , vm_builtin_is_callable },
{ "get_defined_functions", vm_builtin_get_defined_func },
/* Constants management */
{ "defined", vm_builtin_defined },
{ "get_defined_constants", vm_builtin_get_defined_constants },
/* Random numbers/strings generators */
{ "rand", vm_builtin_rand },
{ "rand_str", vm_builtin_rand_str },
{ "getrandmax", vm_builtin_getrandmax },
/* Language constructs functions */
{ "print", vm_builtin_print },
{ "exit", vm_builtin_exit },
{ "die", vm_builtin_exit },
/* Variable handling functions */
{ "gettype", vm_builtin_gettype },
{ "get_resource_type", vm_builtin_get_resource_type},
/* Variable dumping */
{ "dump", vm_builtin_dump },
/* Release info */
{"jx9_version", vm_builtin_jx9_version },
{"jx9_credits", vm_builtin_jx9_version },
{"jx9_info", vm_builtin_jx9_version },
{"jx9_copyright", vm_builtin_jx9_version },
/* hashmap */
{"extract", vm_builtin_extract },
/* URL related function */
{"parse_url", vm_builtin_parse_url },
/* UTF-8 encoding/decoding */
{"utf8_encode", vm_builtin_utf8_encode},
{"utf8_decode", vm_builtin_utf8_decode},
/* Command line processing */
{"getopt", vm_builtin_getopt },
/* Files/URI inclusion facility */
{ "include", vm_builtin_include },
{ "import", vm_builtin_import }
};
/*
* Register the built-in VM functions defined above.
*/
static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm)
{
sxi32 rc;
sxu32 n;
for( n = 0 ; n < SX_ARRAYSIZE(aVmFunc) ; ++n ){
/* Note that these special functions have access
* to the underlying virtual machine as their
* private data.
*/
rc = jx9_create_function(&(*pVm), aVmFunc[n].zName, aVmFunc[n].xFunc, &(*pVm));
if( rc != SXRET_OK ){
return rc;
}
}
return SXRET_OK;
}
#ifndef JX9_DISABLE_BUILTIN_FUNC
/*
* Extract the IO stream device associated with a given scheme.
* Return a pointer to an instance of jx9_io_stream when the scheme
* have an associated IO stream registered with it. NULL otherwise.
* If no scheme:// is avalilable then the file:// scheme is assumed.
* For more information on how to register IO stream devices, please
* refer to the official documentation.
*/
JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(
jx9_vm *pVm, /* Target VM */
const char **pzDevice, /* Full path, URI, ... */
int nByte /* *pzDevice length*/
)
{
const char *zIn, *zEnd, *zCur, *zNext;
jx9_io_stream **apStream, *pStream;
SyString sDev, sCur;
sxu32 n, nEntry;
int rc;
/* Check if a scheme [i.e: file://, http://, zip://...] is available */
zNext = zCur = zIn = *pzDevice;
zEnd = &zIn[nByte];
while( zIn < zEnd ){
if( zIn < &zEnd[-3]/*://*/ && zIn[0] == ':' && zIn[1] == '/' && zIn[2] == '/' ){
/* Got one */
zNext = &zIn[sizeof("://")-1];
break;
}
/* Advance the cursor */
zIn++;
}
if( zIn >= zEnd ){
/* No such scheme, return the default stream */
return pVm->pDefStream;
}
SyStringInitFromBuf(&sDev, zCur, zIn-zCur);
/* Remove leading and trailing white spaces */
SyStringFullTrim(&sDev);
/* Perform a linear lookup on the installed stream devices */
apStream = (jx9_io_stream **)SySetBasePtr(&pVm->aIOstream);
nEntry = SySetUsed(&pVm->aIOstream);
for( n = 0 ; n < nEntry ; n++ ){
pStream = apStream[n];
SyStringInitFromBuf(&sCur, pStream->zName, SyStrlen(pStream->zName));
/* Perfrom a case-insensitive comparison */
rc = SyStringCmp(&sDev, &sCur, SyStrnicmp);
if( rc == 0 ){
/* Stream device found */
*pzDevice = zNext;
return pStream;
}
}
/* No such stream, return NULL */
return 0;
}
#endif /* JX9_DISABLE_BUILTIN_FUNC */
/*
* Section:
* HTTP/URI related routines.
* Authors:
* Symisc Systems, devel@symisc.net.
* Copyright (C) Symisc Systems, http://jx9.symisc.net
* Status:
* Stable.
*/
/*
* URI Parser: Split an URI into components [i.e: Host, Path, Query, ...].
* URI syntax: [method:/][/[user[:pwd]@]host[:port]/][document]
* This almost, but not quite, RFC1738 URI syntax.
* This routine is not a validator, it does not check for validity
* nor decode URI parts, the only thing this routine does is splitting
* the input to its fields.
* Upper layer are responsible of decoding and validating URI parts.
* On success, this function populate the "SyhttpUri" structure passed
* as the first argument. Otherwise SXERR_* is returned when a malformed
* input is encountered.
*/
static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen)
{
const char *zEnd = &zUri[nLen];
sxu8 bHostOnly = FALSE;
sxu8 bIPv6 = FALSE ;
const char *zCur;
SyString *pComp;
sxu32 nPos = 0;
sxi32 rc;
/* Zero the structure first */
SyZero(pOut, sizeof(SyhttpUri));
/* Remove leading and trailing white spaces */
SyStringInitFromBuf(&pOut->sRaw, zUri, nLen);
SyStringFullTrim(&pOut->sRaw);
/* Find the first '/' separator */
rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos);
if( rc != SXRET_OK ){
/* Assume a host name only */
zCur = zEnd;
bHostOnly = TRUE;
goto ProcessHost;
}
zCur = &zUri[nPos];
if( zUri != zCur && zCur[-1] == ':' ){
/* Extract a scheme:
* Not that we can get an invalid scheme here.
* Fortunately the caller can discard any URI by comparing this scheme with its
* registered schemes and will report the error as soon as his comparison function
* fail.
*/
pComp = &pOut->sScheme;
SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri - 1));
SyStringLeftTrim(pComp);
}
if( zCur[1] != '/' ){
if( zCur == zUri || zCur[-1] == ':' ){
/* No authority */
goto PathSplit;
}
/* There is something here , we will assume its an authority
* and someone has forgot the two prefix slashes "//",
* sooner or later we will detect if we are dealing with a malicious
* user or not, but now assume we are dealing with an authority
* and let the caller handle all the validation process.
*/
goto ProcessHost;
}
zUri = &zCur[2];
zCur = zEnd;
rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos);
if( rc == SXRET_OK ){
zCur = &zUri[nPos];
}
ProcessHost:
/* Extract user information if present */
rc = SyByteFind(zUri, (sxu32)(zCur - zUri), '@', &nPos);
if( rc == SXRET_OK ){
if( nPos > 0 ){
sxu32 nPassOfft; /* Password offset */
pComp = &pOut->sUser;
SyStringInitFromBuf(pComp, zUri, nPos);
/* Extract the password if available */
rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPassOfft);
if( rc == SXRET_OK && nPassOfft < nPos){
pComp->nByte = nPassOfft;
pComp = &pOut->sPass;
pComp->zString = &zUri[nPassOfft+sizeof(char)];
pComp->nByte = nPos - nPassOfft - 1;
}
/* Update the cursor */
zUri = &zUri[nPos+1];
}else{
zUri++;
}
}
pComp = &pOut->sHost;
while( zUri < zCur && SyisSpace(zUri[0])){
zUri++;
}
SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri));
if( pComp->zString[0] == '[' ){
/* An IPv6 Address: Make a simple naive test
*/
zUri++; pComp->zString++; pComp->nByte = 0;
while( ((unsigned char)zUri[0] < 0xc0 && SyisHex(zUri[0])) || zUri[0] == ':' ){
zUri++; pComp->nByte++;
}
if( zUri[0] != ']' ){
return SXERR_CORRUPT; /* Malformed IPv6 address */
}
zUri++;
bIPv6 = TRUE;
}
/* Extract a port number if available */
rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPos);
if( rc == SXRET_OK ){
if( bIPv6 == FALSE ){
pComp->nByte = (sxu32)(&zUri[nPos] - zUri);
}
pComp = &pOut->sPort;
SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zCur - &zUri[nPos+1]));
}
if( bHostOnly == TRUE ){
return SXRET_OK;
}
PathSplit:
zUri = zCur;
pComp = &pOut->sPath;
SyStringInitFromBuf(pComp, zUri, (sxu32)(zEnd-zUri));
if( pComp->nByte == 0 ){
return SXRET_OK; /* Empty path */
}
if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '?', &nPos) ){
pComp->nByte = nPos; /* Update path length */
pComp = &pOut->sQuery;
SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1]));
}
if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '#', &nPos) ){
/* Update path or query length */
if( pComp == &pOut->sPath ){
pComp->nByte = nPos;
}else{
if( &zUri[nPos] < (char *)SyStringData(pComp) ){
/* Malformed syntax : Query must be present before fragment */
return SXERR_SYNTAX;
}
pComp->nByte -= (sxu32)(zEnd - &zUri[nPos]);
}
pComp = &pOut->sFragment;
SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1]))
}
return SXRET_OK;
}
/*
* Extract a single line from a raw HTTP request.
* Return SXRET_OK on success, SXERR_EOF when end of input
* and SXERR_MORE when more input is needed.
*/
static sxi32 VmGetNextLine(SyString *pCursor, SyString *pCurrent)
{
const char *zIn;
sxu32 nPos;
/* Jump leading white spaces */
SyStringLeftTrim(pCursor);
if( pCursor->nByte < 1 ){
SyStringInitFromBuf(pCurrent, 0, 0);
return SXERR_EOF; /* End of input */
}
zIn = SyStringData(pCursor);
if( SXRET_OK != SyByteListFind(pCursor->zString, pCursor->nByte, "\r\n", &nPos) ){
/* Line not found, tell the caller to read more input from source */
SyStringDupPtr(pCurrent, pCursor);
return SXERR_MORE;
}
pCurrent->zString = zIn;
pCurrent->nByte = nPos;
/* advance the cursor so we can call this routine again */
pCursor->zString = &zIn[nPos];
pCursor->nByte -= nPos;
return SXRET_OK;
}
/*
* Split a single MIME header into a name value pair.
* This function return SXRET_OK, SXERR_CONTINUE on success.
* Otherwise SXERR_NEXT is returned when a malformed header
* is encountered.
* Note: This function handle also mult-line headers.
*/
static sxi32 VmHttpProcessOneHeader(SyhttpHeader *pHdr, SyhttpHeader *pLast, const char *zLine, sxu32 nLen)
{
SyString *pName;
sxu32 nPos;
sxi32 rc;
if( nLen < 1 ){
return SXERR_NEXT;
}
/* Check for multi-line header */
if( pLast && (zLine[-1] == ' ' || zLine[-1] == '\t') ){
SyString *pTmp = &pLast->sValue;
SyStringFullTrim(pTmp);
if( pTmp->nByte == 0 ){
SyStringInitFromBuf(pTmp, zLine, nLen);
}else{
/* Update header value length */
pTmp->nByte = (sxu32)(&zLine[nLen] - pTmp->zString);
}
/* Simply tell the caller to reset its states and get another line */
return SXERR_CONTINUE;
}
/* Split the header */
pName = &pHdr->sName;
rc = SyByteFind(zLine, nLen, ':', &nPos);
if(rc != SXRET_OK ){
return SXERR_NEXT; /* Malformed header;Check the next entry */
}
SyStringInitFromBuf(pName, zLine, nPos);
SyStringFullTrim(pName);
/* Extract a header value */
SyStringInitFromBuf(&pHdr->sValue, &zLine[nPos + 1], nLen - nPos - 1);
/* Remove leading and trailing whitespaces */
SyStringFullTrim(&pHdr->sValue);
return SXRET_OK;
}
/*
* Extract all MIME headers associated with a HTTP request.
* After processing the first line of a HTTP request, the following
* routine is called in order to extract MIME headers.
* This function return SXRET_OK on success, SXERR_MORE when it needs
* more inputs.
* Note: Any malformed header is simply discarded.
*/
static sxi32 VmHttpExtractHeaders(SyString *pRequest, SySet *pOut)
{
SyhttpHeader *pLast = 0;
SyString sCurrent;
SyhttpHeader sHdr;
sxu8 bEol;
sxi32 rc;
if( SySetUsed(pOut) > 0 ){
pLast = (SyhttpHeader *)SySetAt(pOut, SySetUsed(pOut)-1);
}
bEol = FALSE;
for(;;){
SyZero(&sHdr, sizeof(SyhttpHeader));
/* Extract a single line from the raw HTTP request */
rc = VmGetNextLine(pRequest, &sCurrent);
if(rc != SXRET_OK ){
if( sCurrent.nByte < 1 ){
break;
}
bEol = TRUE;
}
/* Process the header */
if( SXRET_OK == VmHttpProcessOneHeader(&sHdr, pLast, sCurrent.zString, sCurrent.nByte)){
if( SXRET_OK != SySetPut(pOut, (const void *)&sHdr) ){
break;
}
/* Retrieve the last parsed header so we can handle multi-line header
* in case we face one of them.
*/
pLast = (SyhttpHeader *)SySetPeek(pOut);
}
if( bEol ){
break;
}
} /* for(;;) */
return SXRET_OK;
}
/*
* Process the first line of a HTTP request.
* This routine perform the following operations
* 1) Extract the HTTP method.
* 2) Split the request URI to it's fields [ie: host, path, query, ...].
* 3) Extract the HTTP protocol version.
*/
static sxi32 VmHttpProcessFirstLine(
SyString *pRequest, /* Raw HTTP request */
sxi32 *pMethod, /* OUT: HTTP method */
SyhttpUri *pUri, /* OUT: Parse of the URI */
sxi32 *pProto /* OUT: HTTP protocol */
)
{
static const char *azMethods[] = { "get", "post", "head", "put"};
static const sxi32 aMethods[] = { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PUT};
const char *zIn, *zEnd, *zPtr;
SyString sLine;
sxu32 nLen;
sxi32 rc;
/* Extract the first line and update the pointer */
rc = VmGetNextLine(pRequest, &sLine);
if( rc != SXRET_OK ){
return rc;
}
if ( sLine.nByte < 1 ){
/* Empty HTTP request */
return SXERR_EMPTY;
}
/* Delimit the line and ignore trailing and leading white spaces */
zIn = sLine.zString;
zEnd = &zIn[sLine.nByte];
while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
zIn++;
}
/* Extract the HTTP method */
zPtr = zIn;
while( zIn < zEnd && !SyisSpace(zIn[0]) ){
zIn++;
}
*pMethod = HTTP_METHOD_OTHR;
if( zIn > zPtr ){
sxu32 i;
nLen = (sxu32)(zIn-zPtr);
for( i = 0 ; i < SX_ARRAYSIZE(azMethods) ; ++i ){
if( SyStrnicmp(azMethods[i], zPtr, nLen) == 0 ){
*pMethod = aMethods[i];
break;
}
}
}
/* Jump trailing white spaces */
while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
zIn++;
}
/* Extract the request URI */
zPtr = zIn;
while( zIn < zEnd && !SyisSpace(zIn[0]) ){
zIn++;
}
if( zIn > zPtr ){
nLen = (sxu32)(zIn-zPtr);
/* Split raw URI to it's fields */
VmHttpSplitURI(pUri, zPtr, nLen);
}
/* Jump trailing white spaces */
while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
zIn++;
}
/* Extract the HTTP version */
zPtr = zIn;
while( zIn < zEnd && !SyisSpace(zIn[0]) ){
zIn++;
}
*pProto = HTTP_PROTO_11; /* HTTP/1.1 */
rc = 1;
if( zIn > zPtr ){
rc = SyStrnicmp(zPtr, "http/1.0", (sxu32)(zIn-zPtr));
}
if( !rc ){
*pProto = HTTP_PROTO_10; /* HTTP/1.0 */
}
return SXRET_OK;
}
/*
* Tokenize, decode and split a raw query encoded as: "x-www-form-urlencoded"
* into a name value pair.
* Note that this encoding is implicit in GET based requests.
* After the tokenization process, register the decoded queries
* in the $_GET/$_POST/$_REQUEST superglobals arrays.
*/
static sxi32 VmHttpSplitEncodedQuery(
jx9_vm *pVm, /* Target VM */
SyString *pQuery, /* Raw query to decode */
SyBlob *pWorker, /* Working buffer */
int is_post /* TRUE if we are dealing with a POST request */
)
{
const char *zEnd = &pQuery->zString[pQuery->nByte];
const char *zIn = pQuery->zString;
jx9_value *pGet, *pRequest;
SyString sName, sValue;
const char *zPtr;
sxu32 nBlobOfft;
/* Extract superglobals */
if( is_post ){
/* $_POST superglobal */
pGet = VmExtractSuper(&(*pVm), "_POST", sizeof("_POST")-1);
}else{
/* $_GET superglobal */
pGet = VmExtractSuper(&(*pVm), "_GET", sizeof("_GET")-1);
}
pRequest = VmExtractSuper(&(*pVm), "_REQUEST", sizeof("_REQUEST")-1);
/* Split up the raw query */
for(;;){
/* Jump leading white spaces */
while(zIn < zEnd && SyisSpace(zIn[0]) ){
zIn++;
}
if( zIn >= zEnd ){
break;
}
zPtr = zIn;
while( zPtr < zEnd && zPtr[0] != '=' && zPtr[0] != '&' && zPtr[0] != ';' ){
zPtr++;
}
/* Reset the working buffer */
SyBlobReset(pWorker);
/* Decode the entry */
SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE);
/* Save the entry */
sName.nByte = SyBlobLength(pWorker);
sValue.zString = 0;
sValue.nByte = 0;
if( zPtr < zEnd && zPtr[0] == '=' ){
zPtr++;
zIn = zPtr;
/* Store field value */
while( zPtr < zEnd && zPtr[0] != '&' && zPtr[0] != ';' ){
zPtr++;
}
if( zPtr > zIn ){
/* Decode the value */
nBlobOfft = SyBlobLength(pWorker);
SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE);
sValue.zString = (const char *)SyBlobDataAt(pWorker, nBlobOfft);
sValue.nByte = SyBlobLength(pWorker) - nBlobOfft;
}
/* Synchronize pointers */
zIn = zPtr;
}
sName.zString = (const char *)SyBlobData(pWorker);
/* Install the decoded query in the $_GET/$_REQUEST array */
if( pGet && (pGet->iFlags & MEMOBJ_HASHMAP) ){
VmHashmapInsert((jx9_hashmap *)pGet->x.pOther,
sName.zString, (int)sName.nByte,
sValue.zString, (int)sValue.nByte
);
}
if( pRequest && (pRequest->iFlags & MEMOBJ_HASHMAP) ){
VmHashmapInsert((jx9_hashmap *)pRequest->x.pOther,
sName.zString, (int)sName.nByte,
sValue.zString, (int)sValue.nByte
);
}
/* Advance the pointer */
zIn = &zPtr[1];
}
/* All done*/
return SXRET_OK;
}
/*
* Extract MIME header value from the given set.
* Return header value on success. NULL otherwise.
*/
static SyString * VmHttpExtractHeaderValue(SySet *pSet, const char *zMime, sxu32 nByte)
{
SyhttpHeader *aMime, *pMime;
SyString sMime;
sxu32 n;
SyStringInitFromBuf(&sMime, zMime, nByte);
/* Point to the MIME entries */
aMime = (SyhttpHeader *)SySetBasePtr(pSet);
/* Perform the lookup */
for( n = 0 ; n < SySetUsed(pSet) ; ++n ){
pMime = &aMime[n];
if( SyStringCmp(&sMime, &pMime->sName, SyStrnicmp) == 0 ){
/* Header found, return it's associated value */
return &pMime->sValue;
}
}
/* No such MIME header */
return 0;
}
/*
* Tokenize and decode a raw "Cookie:" MIME header into a name value pair
* and insert it's fields [i.e name, value] in the $_COOKIE superglobal.
*/
static sxi32 VmHttpPorcessCookie(jx9_vm *pVm, SyBlob *pWorker, const char *zIn, sxu32 nByte)
{
const char *zPtr, *zDelimiter, *zEnd = &zIn[nByte];
SyString sName, sValue;
jx9_value *pCookie;
sxu32 nOfft;
/* Make sure the $_COOKIE superglobal is available */
pCookie = VmExtractSuper(&(*pVm), "_COOKIE", sizeof("_COOKIE")-1);
if( pCookie == 0 || (pCookie->iFlags & MEMOBJ_HASHMAP) == 0 ){
/* $_COOKIE superglobal not available */
return SXERR_NOTFOUND;
}
for(;;){
/* Jump leading white spaces */
while( zIn < zEnd && SyisSpace(zIn[0]) ){
zIn++;
}
if( zIn >= zEnd ){
break;
}
/* Reset the working buffer */
SyBlobReset(pWorker);
zDelimiter = zIn;
/* Delimit the name[=value]; pair */
while( zDelimiter < zEnd && zDelimiter[0] != ';' ){
zDelimiter++;
}
zPtr = zIn;
while( zPtr < zDelimiter && zPtr[0] != '=' ){
zPtr++;
}
/* Decode the cookie */
SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE);
sName.nByte = SyBlobLength(pWorker);
zPtr++;
sValue.zString = 0;
sValue.nByte = 0;
if( zPtr < zDelimiter ){
/* Got a Cookie value */
nOfft = SyBlobLength(pWorker);
SyUriDecode(zPtr, (sxu32)(zDelimiter-zPtr), jx9VmBlobConsumer, pWorker, TRUE);
SyStringInitFromBuf(&sValue, SyBlobDataAt(pWorker, nOfft), SyBlobLength(pWorker)-nOfft);
}
/* Synchronize pointers */
zIn = &zDelimiter[1];
/* Perform the insertion */
sName.zString = (const char *)SyBlobData(pWorker);
VmHashmapInsert((jx9_hashmap *)pCookie->x.pOther,
sName.zString, (int)sName.nByte,
sValue.zString, (int)sValue.nByte
);
}
return SXRET_OK;
}
/*
* Process a full HTTP request and populate the appropriate arrays
* such as $_SERVER, $_GET, $_POST, $_COOKIE, $_REQUEST, ... with the information
* extracted from the raw HTTP request. As an extension Symisc introduced
* the $_HEADER array which hold a copy of the processed HTTP MIME headers
* and their associated values. [i.e: $_HEADER['Server'], $_HEADER['User-Agent'], ...].
* This function return SXRET_OK on success. Any other return value indicates
* a malformed HTTP request.
*/
static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte)
{
SyString *pName, *pValue, sRequest; /* Raw HTTP request */
jx9_value *pHeaderArray; /* $_HEADER superglobal (Symisc eXtension to the JX9 specification)*/
SyhttpHeader *pHeader; /* MIME header */
SyhttpUri sUri; /* Parse of the raw URI*/
SyBlob sWorker; /* General purpose working buffer */
SySet sHeader; /* MIME headers set */
sxi32 iMethod; /* HTTP method [i.e: GET, POST, HEAD...]*/
sxi32 iVer; /* HTTP protocol version */
sxi32 rc;
SyStringInitFromBuf(&sRequest, zRequest, nByte);
SySetInit(&sHeader, &pVm->sAllocator, sizeof(SyhttpHeader));
SyBlobInit(&sWorker, &pVm->sAllocator);
/* Ignore leading and trailing white spaces*/
SyStringFullTrim(&sRequest);
/* Process the first line */
rc = VmHttpProcessFirstLine(&sRequest, &iMethod, &sUri, &iVer);
if( rc != SXRET_OK ){
return rc;
}
/* Process MIME headers */
VmHttpExtractHeaders(&sRequest, &sHeader);
/*
* Setup $_SERVER environments
*/
/* 'SERVER_PROTOCOL': Name and revision of the information protocol via which the page was requested */
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"SERVER_PROTOCOL",
iVer == HTTP_PROTO_10 ? "HTTP/1.0" : "HTTP/1.1",
sizeof("HTTP/1.1")-1
);
/* 'REQUEST_METHOD': Which request method was used to access the page */
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"REQUEST_METHOD",
iMethod == HTTP_METHOD_GET ? "GET" :
(iMethod == HTTP_METHOD_POST ? "POST":
(iMethod == HTTP_METHOD_PUT ? "PUT" :
(iMethod == HTTP_METHOD_HEAD ? "HEAD" : "OTHER"))),
-1 /* Compute attribute length automatically */
);
if( SyStringLength(&sUri.sQuery) > 0 && iMethod == HTTP_METHOD_GET ){
pValue = &sUri.sQuery;
/* 'QUERY_STRING': The query string, if any, via which the page was accessed */
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"QUERY_STRING",
pValue->zString,
pValue->nByte
);
/* Decoded the raw query */
VmHttpSplitEncodedQuery(&(*pVm), pValue, &sWorker, FALSE);
}
/* REQUEST_URI: The URI which was given in order to access this page; for instance, '/index.html' */
pValue = &sUri.sRaw;
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"REQUEST_URI",
pValue->zString,
pValue->nByte
);
/*
* 'PATH_INFO'
* 'ORIG_PATH_INFO'
* Contains any client-provided pathname information trailing the actual script filename but preceding
* the query string, if available. For instance, if the current script was accessed via the URL
* http://www.example.com/jx9/path_info.jx9/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain
* /some/stuff.
*/
pValue = &sUri.sPath;
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"PATH_INFO",
pValue->zString,
pValue->nByte
);
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"ORIG_PATH_INFO",
pValue->zString,
pValue->nByte
);
/* 'HTTP_ACCEPT': Contents of the Accept: header from the current request, if there is one */
pValue = VmHttpExtractHeaderValue(&sHeader, "Accept", sizeof("Accept")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_ACCEPT",
pValue->zString,
pValue->nByte
);
}
/* 'HTTP_ACCEPT_CHARSET': Contents of the Accept-Charset: header from the current request, if there is one. */
pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Charset", sizeof("Accept-Charset")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_ACCEPT_CHARSET",
pValue->zString,
pValue->nByte
);
}
/* 'HTTP_ACCEPT_ENCODING': Contents of the Accept-Encoding: header from the current request, if there is one. */
pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Encoding", sizeof("Accept-Encoding")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_ACCEPT_ENCODING",
pValue->zString,
pValue->nByte
);
}
/* 'HTTP_ACCEPT_LANGUAGE': Contents of the Accept-Language: header from the current request, if there is one */
pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Language", sizeof("Accept-Language")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_ACCEPT_LANGUAGE",
pValue->zString,
pValue->nByte
);
}
/* 'HTTP_CONNECTION': Contents of the Connection: header from the current request, if there is one. */
pValue = VmHttpExtractHeaderValue(&sHeader, "Connection", sizeof("Connection")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_CONNECTION",
pValue->zString,
pValue->nByte
);
}
/* 'HTTP_HOST': Contents of the Host: header from the current request, if there is one. */
pValue = VmHttpExtractHeaderValue(&sHeader, "Host", sizeof("Host")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_HOST",
pValue->zString,
pValue->nByte
);
}
/* 'HTTP_REFERER': Contents of the Referer: header from the current request, if there is one. */
pValue = VmHttpExtractHeaderValue(&sHeader, "Referer", sizeof("Referer")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_REFERER",
pValue->zString,
pValue->nByte
);
}
/* 'HTTP_USER_AGENT': Contents of the Referer: header from the current request, if there is one. */
pValue = VmHttpExtractHeaderValue(&sHeader, "User-Agent", sizeof("User-Agent")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"HTTP_USER_AGENT",
pValue->zString,
pValue->nByte
);
}
/* 'JX9_AUTH_DIGEST': When doing Digest HTTP authentication this variable is set to the 'Authorization'
* header sent by the client (which you should then use to make the appropriate validation).
*/
pValue = VmHttpExtractHeaderValue(&sHeader, "Authorization", sizeof("Authorization")-1);
if( pValue ){
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"JX9_AUTH_DIGEST",
pValue->zString,
pValue->nByte
);
jx9_vm_config(pVm,
JX9_VM_CONFIG_SERVER_ATTR,
"JX9_AUTH",
pValue->zString,
pValue->nByte
);
}
/* Install all clients HTTP headers in the $_HEADER superglobal */
pHeaderArray = VmExtractSuper(&(*pVm), "_HEADER", sizeof("_HEADER")-1);
/* Iterate throw the available MIME headers*/
SySetResetCursor(&sHeader);
pHeader = 0; /* stupid cc warning */
while( SXRET_OK == SySetGetNextEntry(&sHeader, (void **)&pHeader) ){
pName = &pHeader->sName;
pValue = &pHeader->sValue;
if( pHeaderArray && (pHeaderArray->iFlags & MEMOBJ_HASHMAP)){
/* Insert the MIME header and it's associated value */
VmHashmapInsert((jx9_hashmap *)pHeaderArray->x.pOther,
pName->zString, (int)pName->nByte,
pValue->zString, (int)pValue->nByte
);
}
if( pName->nByte == sizeof("Cookie")-1 && SyStrnicmp(pName->zString, "Cookie", sizeof("Cookie")-1) == 0
&& pValue->nByte > 0){
/* Process the name=value pair and insert them in the $_COOKIE superglobal array */
VmHttpPorcessCookie(&(*pVm), &sWorker, pValue->zString, pValue->nByte);
}
}
if( iMethod == HTTP_METHOD_POST ){
/* Extract raw POST data */
pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Type", sizeof("Content-Type") - 1);
if( pValue && pValue->nByte >= sizeof("application/x-www-form-urlencoded") - 1 &&
SyMemcmp("application/x-www-form-urlencoded", pValue->zString, pValue->nByte) == 0 ){
/* Extract POST data length */
pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Length", sizeof("Content-Length") - 1);
if( pValue ){
sxi32 iLen = 0; /* POST data length */
SyStrToInt32(pValue->zString, pValue->nByte, (void *)&iLen, 0);
if( iLen > 0 ){
/* Remove leading and trailing white spaces */
SyStringFullTrim(&sRequest);
if( (int)sRequest.nByte > iLen ){
sRequest.nByte = (sxu32)iLen;
}
/* Decode POST data now */
VmHttpSplitEncodedQuery(&(*pVm), &sRequest, &sWorker, TRUE);
}
}
}
}
/* All done, clean-up the mess left behind */
SySetRelease(&sHeader);
SyBlobRelease(&sWorker);
return SXRET_OK;
}
/*
* ----------------------------------------------------------
* File: lhash_kv.c
* MD5: 581b07ce2984fd95740677285d8a11d3
* ----------------------------------------------------------
*/
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
* Version 1.1.6
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://unqlite.org/licensing.html
*/
/* $SymiscID: lhash_kv.c v1.7 Solaris 2013-01-14 12:56 stable <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);
iBlksz = nByte;
}
/* Fix offsets */
if( zPrev ){
SyBigEndianPack16(zPrev,iNext);
}else{
/* First block */
pPage->sHdr.iFree = iNext;
/* Reflect on the page header */
SyBigEndianPack16(&pPage->pRaw->zData[2/* Offset of the first cell1*/],iNext);
}
/* All done */
pPage->nFree -= iBlksz;
return UNQLITE_OK;
}
/*
* Write the cell header into the corresponding offset.
*/
static int lhCellWriteHeader(lhcell *pCell)
{
lhpage *pPage = pCell->pPage;
unsigned char *zRaw = pPage->pRaw->zData;
/* Seek to the desired location */
zRaw += pCell->iStart;
/* 4 byte hash number */
SyBigEndianPack32(zRaw,pCell->nHash);
zRaw += 4;
/* 4 byte key length */
SyBigEndianPack32(zRaw,pCell->nKey);
zRaw += 4;
/* 8 byte data length */
SyBigEndianPack64(zRaw,pCell->nData);
zRaw += 8;
/* 2 byte offset of the next cell */
pCell->iNext = pPage->sHdr.iOfft;
SyBigEndianPack16(zRaw,pCell->iNext);
zRaw += 2;
/* 8 byte overflow page number */
SyBigEndianPack64(zRaw,pCell->iOvfl);
/* Update the page header */
pPage->sHdr.iOfft = pCell->iStart;
/* pEngine->pIo->xWrite() has been successfully called on this page */
SyBigEndianPack16(pPage->pRaw->zData,pCell->iStart);
/* All done */
return UNQLITE_OK;
}
/*
* Write local payload.
*/
static int lhCellWriteLocalPayload(lhcell *pCell,
const void *pKey,sxu32 nKeylen,
const void *pData,unqlite_int64 nDatalen
)
{
/* A writer lock have been acquired on this page */
lhpage *pPage = pCell->pPage;
unsigned char *zRaw = pPage->pRaw->zData;
/* Seek to the desired location */
zRaw += pCell->iStart + L_HASH_CELL_SZ;
/* Write the key */
SyMemcpy(pKey,(void *)zRaw,nKeylen);
zRaw += nKeylen;
if( nDatalen > 0 ){
/* Write the Data */
SyMemcpy(pData,(void *)zRaw,(sxu32)nDatalen);
}
return UNQLITE_OK;
}
/*
* Allocate as much overflow page we need to store the cell payload.
*/
static int lhCellWriteOvflPayload(lhcell *pCell,const void *pKey,sxu32 nKeylen,...)
{
lhpage *pPage = pCell->pPage;
lhash_kv_engine *pEngine = pPage->pHash;
unqlite_page *pOvfl,*pFirst,*pNew;
const unsigned char *zPtr,*zEnd;
unsigned char *zRaw,*zRawEnd;
sxu32 nAvail;
va_list ap;
int rc;
/* Acquire a new overflow page */
rc = lhAcquirePage(pEngine,&pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
/* Acquire a writer lock */
rc = pEngine->pIo->xWrite(pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
pFirst = pOvfl;
/* Link */
pCell->iOvfl = pOvfl->pgno;
/* Update the cell header */
SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4/*Hash*/ + 4/*Key*/ + 8/*Data*/ + 2 /*Next cell*/],pCell->iOvfl);
/* Start the write process */
zPtr = (const unsigned char *)pKey;
zEnd = &zPtr[nKeylen];
SyBigEndianPack64(pOvfl->zData,0); /* Next overflow page on the chain */
zRaw = &pOvfl->zData[8/* Next ovfl page*/ + 8 /* Data page */ + 2 /* Data offset*/];
zRawEnd = &pOvfl->zData[pEngine->iPageSize];
pNew = pOvfl;
/* Write the key */
for(;;){
if( zPtr >= zEnd ){
break;
}
if( zRaw >= zRawEnd ){
/* Acquire a new page */
rc = lhAcquirePage(pEngine,&pNew);
if( rc != UNQLITE_OK ){
return rc;
}
rc = pEngine->pIo->xWrite(pNew);
if( rc != UNQLITE_OK ){
return rc;
}
/* Link */
SyBigEndianPack64(pOvfl->zData,pNew->pgno);
pEngine->pIo->xPageUnref(pOvfl);
SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
pOvfl = pNew;
zRaw = &pNew->zData[8];
zRawEnd = &pNew->zData[pEngine->iPageSize];
}
nAvail = (sxu32)(zRawEnd-zRaw);
nKeylen = (sxu32)(zEnd-zPtr);
if( nKeylen > nAvail ){
nKeylen = nAvail;
}
SyMemcpy((const void *)zPtr,(void *)zRaw,nKeylen);
/* Synchronize pointers */
zPtr += nKeylen;
zRaw += nKeylen;
}
rc = UNQLITE_OK;
va_start(ap,nKeylen);
pCell->iDataPage = pNew->pgno;
pCell->iDataOfft = (sxu16)(zRaw-pNew->zData);
/* Write the data page and its offset */
SyBigEndianPack64(&pFirst->zData[8/*Next ovfl*/],pCell->iDataPage);
SyBigEndianPack16(&pFirst->zData[8/*Next ovfl*/+8/*Data page*/],pCell->iDataOfft);
/* Write data */
for(;;){
const void *pData;
sxu32 nDatalen;
sxu64 nData;
pData = va_arg(ap,const void *);
nData = va_arg(ap,sxu64);
if( pData == 0 ){
/* No more chunks */
break;
}
/* Write this chunk */
zPtr = (const unsigned char *)pData;
zEnd = &zPtr[nData];
for(;;){
if( zPtr >= zEnd ){
break;
}
if( zRaw >= zRawEnd ){
/* Acquire a new page */
rc = lhAcquirePage(pEngine,&pNew);
if( rc != UNQLITE_OK ){
va_end(ap);
return rc;
}
rc = pEngine->pIo->xWrite(pNew);
if( rc != UNQLITE_OK ){
va_end(ap);
return rc;
}
/* Link */
SyBigEndianPack64(pOvfl->zData,pNew->pgno);
pEngine->pIo->xPageUnref(pOvfl);
SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
pOvfl = pNew;
zRaw = &pNew->zData[8];
zRawEnd = &pNew->zData[pEngine->iPageSize];
}
nAvail = (sxu32)(zRawEnd-zRaw);
nDatalen = (sxu32)(zEnd-zPtr);
if( nDatalen > nAvail ){
nDatalen = nAvail;
}
SyMemcpy((const void *)zPtr,(void *)zRaw,nDatalen);
/* Synchronize pointers */
zPtr += nDatalen;
zRaw += nDatalen;
}
}
/* Unref the overflow page */
pEngine->pIo->xPageUnref(pOvfl);
va_end(ap);
return UNQLITE_OK;
}
/*
* Restore a page to the free list.
*/
static int lhRestorePage(lhash_kv_engine *pEngine,unqlite_page *pPage)
{
int rc;
rc = pEngine->pIo->xWrite(pEngine->pHeader);
if( rc != UNQLITE_OK ){
return rc;
}
rc = pEngine->pIo->xWrite(pPage);
if( rc != UNQLITE_OK ){
return rc;
}
/* Link to the list of free page */
SyBigEndianPack64(pPage->zData,pEngine->nFreeList);
pEngine->nFreeList = pPage->pgno;
SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList);
/* All done */
return UNQLITE_OK;
}
/*
* Restore cell space and mark it as a free block.
*/
static int lhRestoreSpace(lhpage *pPage,sxu16 iOfft,sxu16 nByte)
{
unsigned char *zRaw;
if( nByte < 4 ){
/* At least 4 bytes of freespace must form a valid block */
return UNQLITE_OK;
}
/* pEngine->pIo->xWrite() has been successfully called on this page */
zRaw = &pPage->pRaw->zData[iOfft];
/* Mark as a free block */
SyBigEndianPack16(zRaw,pPage->sHdr.iFree); /* Offset of the next free block */
zRaw += 2;
SyBigEndianPack16(zRaw,nByte);
/* Link */
SyBigEndianPack16(&pPage->pRaw->zData[2/* offset of the first cell */],iOfft);
pPage->sHdr.iFree = iOfft;
pPage->nFree += nByte;
return UNQLITE_OK;
}
/* Forward declaration */
static lhcell * lhFindSibeling(lhcell *pCell);
/*
* Unlink a cell.
*/
static int lhUnlinkCell(lhcell *pCell)
{
lhash_kv_engine *pEngine = pCell->pPage->pHash;
lhpage *pPage = pCell->pPage;
sxu16 nByte = L_HASH_CELL_SZ;
lhcell *pPrev;
int rc;
rc = pEngine->pIo->xWrite(pPage->pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
/* Bring the link */
pPrev = lhFindSibeling(pCell);
if( pPrev ){
pPrev->iNext = pCell->iNext;
/* Fix offsets in the page header */
SyBigEndianPack16(&pPage->pRaw->zData[pPrev->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext);
}else{
/* First entry on this page (either master or slave) */
pPage->sHdr.iOfft = pCell->iNext;
/* Update the page header */
SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext);
}
/* Restore cell space */
if( pCell->iOvfl == 0 ){
nByte += (sxu16)(pCell->nData + pCell->nKey);
}
lhRestoreSpace(pPage,pCell->iStart,nByte);
/* Discard the cell from the in-memory hashtable */
lhCellDiscard(pCell);
return UNQLITE_OK;
}
/*
* Remove a cell and its paylod (key + data).
*/
static int lhRecordRemove(lhcell *pCell)
{
lhash_kv_engine *pEngine = pCell->pPage->pHash;
int rc;
if( pCell->iOvfl > 0){
/* Discard overflow pages */
unqlite_page *pOvfl;
pgno iNext = pCell->iOvfl;
for(;;){
/* Point to the overflow page */
rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
/* Next page on the chain */
SyBigEndianUnpack64(pOvfl->zData,&iNext);
/* Restore the page to the free list */
rc = lhRestorePage(pEngine,pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
/* Unref */
pEngine->pIo->xPageUnref(pOvfl);
if( iNext == 0 ){
break;
}
}
}
/* Unlink the cell */
rc = lhUnlinkCell(pCell);
return rc;
}
/*
* Find cell sibeling.
*/
static lhcell * lhFindSibeling(lhcell *pCell)
{
lhpage *pPage = pCell->pPage->pMaster;
lhcell *pEntry;
pEntry = pPage->pFirst;
while( pEntry ){
if( pEntry->pPage == pCell->pPage && pEntry->iNext == pCell->iStart ){
/* Sibeling found */
return pEntry;
}
/* Point to the previous entry */
pEntry = pEntry->pPrev;
}
/* Last inserted cell */
return 0;
}
/*
* Move a cell to a new location with its new data.
*/
static int lhMoveLocalCell(
lhcell *pCell,
sxu16 iOfft,
const void *pData,
unqlite_int64 nData
)
{
sxu16 iKeyOfft = pCell->iStart + L_HASH_CELL_SZ;
lhpage *pPage = pCell->pPage;
lhcell *pSibeling;
pSibeling = lhFindSibeling(pCell);
if( pSibeling ){
/* Fix link */
SyBigEndianPack16(&pPage->pRaw->zData[pSibeling->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext);
pSibeling->iNext = pCell->iNext;
}else{
/* First cell, update page header only */
SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext);
pPage->sHdr.iOfft = pCell->iNext;
}
/* Set the new offset */
pCell->iStart = iOfft;
pCell->nData = (sxu64)nData;
/* Write the cell payload */
lhCellWriteLocalPayload(pCell,(const void *)&pPage->pRaw->zData[iKeyOfft],pCell->nKey,pData,nData);
/* Finally write the cell header */
lhCellWriteHeader(pCell);
/* All done */
return UNQLITE_OK;
}
/*
* Overwrite an existing record.
*/
static int lhRecordOverwrite(
lhcell *pCell,
const void *pData,unqlite_int64 nByte
)
{
lhash_kv_engine *pEngine = pCell->pPage->pHash;
unsigned char *zRaw,*zRawEnd,*zPayload;
const unsigned char *zPtr,*zEnd;
unqlite_page *pOvfl,*pOld,*pNew;
lhpage *pPage = pCell->pPage;
sxu32 nAvail;
pgno iOvfl;
int rc;
/* Acquire a writer lock on this page */
rc = pEngine->pIo->xWrite(pPage->pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
if( pCell->iOvfl == 0 ){
/* Local payload, try to deal with the free space issues */
zPayload = &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey];
if( pCell->nData == (sxu64)nByte ){
/* Best scenario, simply a memcpy operation */
SyMemcpy(pData,(void *)zPayload,(sxu32)nByte);
}else if( (sxu64)nByte < pCell->nData ){
/* Shorter data, not so ugly */
SyMemcpy(pData,(void *)zPayload,(sxu32)nByte);
/* Update the cell header */
SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],nByte);
/* Restore freespace */
lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ + pCell->nKey + nByte),(sxu16)(pCell->nData - nByte));
/* New data size */
pCell->nData = (sxu64)nByte;
}else{
sxu16 iOfft = 0; /* cc warning */
/* Check if another chunk is available for this cell */
rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + nByte,&iOfft);
if( rc != UNQLITE_OK ){
/* Transfer the payload to an overflow page */
rc = lhCellWriteOvflPayload(pCell,&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey,pData,nByte,(const void *)0);
if( rc != UNQLITE_OK ){
return rc;
}
/* Update the cell header */
SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],(sxu64)nByte);
/* Restore freespace */
lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData));
/* New data size */
pCell->nData = (sxu64)nByte;
}else{
sxu16 iOldOfft = pCell->iStart;
sxu32 iOld = (sxu32)pCell->nData;
/* Space is available, transfer the cell */
lhMoveLocalCell(pCell,iOfft,pData,nByte);
/* Restore cell space */
lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld));
}
}
return UNQLITE_OK;
}
/* Point to the overflow page */
rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
/* Relase all old overflow pages first */
SyBigEndianUnpack64(pOvfl->zData,&iOvfl);
pOld = pOvfl;
for(;;){
if( iOvfl == 0 ){
/* No more overflow pages on the chain */
break;
}
/* Point to the target page */
if( UNQLITE_OK != pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOld) ){
/* Not so fatal if something goes wrong here */
break;
}
/* Next overflow page to be released */
SyBigEndianUnpack64(pOld->zData,&iOvfl);
if( pOld != pOvfl ){ /* xx: chm is maniac */
/* Restore the page to the free list */
lhRestorePage(pEngine,pOld);
/* Unref */
pEngine->pIo->xPageUnref(pOld);
}
}
/* Point to the data offset */
zRaw = &pOvfl->zData[pCell->iDataOfft];
zRawEnd = &pOvfl->zData[pEngine->iPageSize];
/* The data to be stored */
zPtr = (const unsigned char *)pData;
zEnd = &zPtr[nByte];
/* Start the overwrite process */
/* Acquire a writer lock */
rc = pEngine->pIo->xWrite(pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
SyBigEndianPack64(pOvfl->zData,0);
for(;;){
sxu32 nLen;
if( zPtr >= zEnd ){
break;
}
if( zRaw >= zRawEnd ){
/* Acquire a new page */
rc = lhAcquirePage(pEngine,&pNew);
if( rc != UNQLITE_OK ){
return rc;
}
rc = pEngine->pIo->xWrite(pNew);
if( rc != UNQLITE_OK ){
return rc;
}
/* Link */
SyBigEndianPack64(pOvfl->zData,pNew->pgno);
pEngine->pIo->xPageUnref(pOvfl);
SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
pOvfl = pNew;
zRaw = &pNew->zData[8];
zRawEnd = &pNew->zData[pEngine->iPageSize];
}
nAvail = (sxu32)(zRawEnd-zRaw);
nLen = (sxu32)(zEnd-zPtr);
if( nLen > nAvail ){
nLen = nAvail;
}
SyMemcpy((const void *)zPtr,(void *)zRaw,nLen);
/* Synchronize pointers */
zPtr += nLen;
zRaw += nLen;
}
/* Unref the last overflow page */
pEngine->pIo->xPageUnref(pOvfl);
/* Finally, update the cell header */
pCell->nData = (sxu64)nByte;
SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData);
/* All done */
return UNQLITE_OK;
}
/*
* Append data to an existing record.
*/
static int lhRecordAppend(
lhcell *pCell,
const void *pData,unqlite_int64 nByte
)
{
lhash_kv_engine *pEngine = pCell->pPage->pHash;
const unsigned char *zPtr,*zEnd;
lhpage *pPage = pCell->pPage;
unsigned char *zRaw,*zRawEnd;
unqlite_page *pOvfl,*pNew;
sxu64 nDatalen;
sxu32 nAvail;
pgno iOvfl;
int rc;
if( pCell->nData + nByte < pCell->nData ){
/* Overflow */
pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow");
return UNQLITE_LIMIT;
}
/* Acquire a writer lock on this page */
rc = pEngine->pIo->xWrite(pPage->pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
if( pCell->iOvfl == 0 ){
sxu16 iOfft = 0; /* cc warning */
/* Local payload, check for a bigger place */
rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + pCell->nData + nByte,&iOfft);
if( rc != UNQLITE_OK ){
/* Transfer the payload to an overflow page */
rc = lhCellWriteOvflPayload(pCell,
&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey,
(const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],pCell->nData,
pData,nByte,
(const void *)0);
if( rc != UNQLITE_OK ){
return rc;
}
/* Update the cell header */
SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData + nByte);
/* Restore freespace */
lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData));
/* New data size */
pCell->nData += nByte;
}else{
sxu16 iOldOfft = pCell->iStart;
sxu32 iOld = (sxu32)pCell->nData;
SyBlob sWorker;
SyBlobInit(&sWorker,&pEngine->sAllocator);
/* Copy the old data */
rc = SyBlobAppend(&sWorker,(const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],(sxu32)pCell->nData);
if( rc == SXRET_OK ){
/* Append the new data */
rc = SyBlobAppend(&sWorker,pData,(sxu32)nByte);
}
if( rc != UNQLITE_OK ){
SyBlobRelease(&sWorker);
return rc;
}
/* Space is available, transfer the cell */
lhMoveLocalCell(pCell,iOfft,SyBlobData(&sWorker),(unqlite_int64)SyBlobLength(&sWorker));
/* Restore cell space */
lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld));
/* All done */
SyBlobRelease(&sWorker);
}
return UNQLITE_OK;
}
/* Point to the overflow page which hold the data */
rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
/* Next overflow page in the chain */
SyBigEndianUnpack64(pOvfl->zData,&iOvfl);
/* Point to the end of the chunk */
zRaw = &pOvfl->zData[pCell->iDataOfft];
zRawEnd = &pOvfl->zData[pEngine->iPageSize];
nDatalen = pCell->nData;
nAvail = (sxu32)(zRawEnd - zRaw);
for(;;){
if( zRaw >= zRawEnd ){
if( iOvfl == 0 ){
/* Cant happen */
pEngine->pIo->xErr(pEngine->pIo->pHandle,"Corrupt overflow page");
return UNQLITE_CORRUPT;
}
rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pNew);
if( rc != UNQLITE_OK ){
return rc;
}
/* Next overflow page on the chain */
SyBigEndianUnpack64(pNew->zData,&iOvfl);
/* Unref the previous overflow page */
pEngine->pIo->xPageUnref(pOvfl);
/* Point to the new chunk */
zRaw = &pNew->zData[8];
zRawEnd = &pNew->zData[pCell->pPage->pHash->iPageSize];
nAvail = L_HASH_OVERFLOW_SIZE(pCell->pPage->pHash->iPageSize);
pOvfl = pNew;
}
if( (sxu64)nAvail > nDatalen ){
zRaw += nDatalen;
break;
}else{
nDatalen -= nAvail;
}
zRaw += nAvail;
}
/* Start the append process */
zPtr = (const unsigned char *)pData;
zEnd = &zPtr[nByte];
/* Acquire a writer lock */
rc = pEngine->pIo->xWrite(pOvfl);
if( rc != UNQLITE_OK ){
return rc;
}
for(;;){
sxu32 nLen;
if( zPtr >= zEnd ){
break;
}
if( zRaw >= zRawEnd ){
/* Acquire a new page */
rc = lhAcquirePage(pEngine,&pNew);
if( rc != UNQLITE_OK ){
return rc;
}
rc = pEngine->pIo->xWrite(pNew);
if( rc != UNQLITE_OK ){
return rc;
}
/* Link */
SyBigEndianPack64(pOvfl->zData,pNew->pgno);
pEngine->pIo->xPageUnref(pOvfl);
SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
pOvfl = pNew;
zRaw = &pNew->zData[8];
zRawEnd = &pNew->zData[pEngine->iPageSize];
}
nAvail = (sxu32)(zRawEnd-zRaw);
nLen = (sxu32)(zEnd-zPtr);
if( nLen > nAvail ){
nLen = nAvail;
}
SyMemcpy((const void *)zPtr,(void *)zRaw,nLen);
/* Synchronize pointers */
zPtr += nLen;
zRaw += nLen;
}
/* Unref the last overflow page */
pEngine->pIo->xPageUnref(pOvfl);
/* Finally, update the cell header */
pCell->nData += nByte;
SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData);
/* All done */
return UNQLITE_OK;
}
/*
* A write privilege have been acquired on this page.
* Mark it as an empty page (No cells).
*/
static int lhSetEmptyPage(lhpage *pPage)
{
unsigned char *zRaw = pPage->pRaw->zData;
lhphdr *pHeader = &pPage->sHdr;
sxu16 nByte;
int rc;
/* Acquire a writer lock */
rc = pPage->pHash->pIo->xWrite(pPage->pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
/* Offset of the first cell */
SyBigEndianPack16(zRaw,0);
zRaw += 2;
/* Offset of the first free block */
pHeader->iFree = L_HASH_PAGE_HDR_SZ;
SyBigEndianPack16(zRaw,L_HASH_PAGE_HDR_SZ);
zRaw += 2;
/* Slave page number */
SyBigEndianPack64(zRaw,0);
zRaw += 8;
/* Fill the free block */
SyBigEndianPack16(zRaw,0); /* Offset of the next free block */
zRaw += 2;
nByte = (sxu16)L_HASH_MX_FREE_SPACE(pPage->pHash->iPageSize);
SyBigEndianPack16(zRaw,nByte);
pPage->nFree = nByte;
/* Do not add this page to the hot dirty list */
pPage->pHash->pIo->xDontMkHot(pPage->pRaw);
return UNQLITE_OK;
}
/* Forward declaration */
static int lhSlaveStore(
lhpage *pPage,
const void *pKey,sxu32 nKeyLen,
const void *pData,unqlite_int64 nDataLen,
sxu32 nHash
);
/*
* Store a cell and its payload in a given page.
*/
static int lhStoreCell(
lhpage *pPage, /* Target page */
const void *pKey,sxu32 nKeyLen, /* Payload: Key */
const void *pData,unqlite_int64 nDataLen, /* Payload: Data */
sxu32 nHash, /* Hash of the key */
int auto_append /* Auto append a slave page if full */
)
{
lhash_kv_engine *pEngine = pPage->pHash;
int iNeedOvfl = 0; /* Need overflow page for this cell and its payload*/
lhcell *pCell;
sxu16 nOfft;
int rc;
/* Acquire a writer lock on this page first */
rc = pEngine->pIo->xWrite(pPage->pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
/* Check for a free block */
rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ+nKeyLen+nDataLen,&nOfft);
if( rc != UNQLITE_OK ){
/* Check for a free block to hold a single cell only (without payload) */
rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft);
if( rc != UNQLITE_OK ){
if( !auto_append ){
/* A split must be done */
return UNQLITE_FULL;
}else{
/* Store this record in a slave page */
rc = lhSlaveStore(pPage,pKey,nKeyLen,pData,nDataLen,nHash);
return rc;
}
}
iNeedOvfl = 1;
}
/* Allocate a new cell instance */
pCell = lhNewCell(pEngine,pPage);
if( pCell == 0 ){
pEngine->pIo->xErr(pEngine->pIo->pHandle,"KV store is running out of memory");
return UNQLITE_NOMEM;
}
/* Fill-in the structure */
pCell->iStart = nOfft;
pCell->nKey = nKeyLen;
pCell->nData = (sxu64)nDataLen;
pCell->nHash = nHash;
if( nKeyLen < 262144 /* 256 KB */ ){
/* Keep the key in-memory for fast lookup */
SyBlobAppend(&pCell->sKey,pKey,nKeyLen);
}
/* Link the cell */
rc = lhInstallCell(pCell);
if( rc != UNQLITE_OK ){
return rc;
}
/* Write the payload */
if( iNeedOvfl ){
rc = lhCellWriteOvflPayload(pCell,pKey,nKeyLen,pData,nDataLen,(const void *)0);
if( rc != UNQLITE_OK ){
lhCellDiscard(pCell);
return rc;
}
}else{
lhCellWriteLocalPayload(pCell,pKey,nKeyLen,pData,nDataLen);
}
/* Finally, Write the cell header */
lhCellWriteHeader(pCell);
/* All done */
return UNQLITE_OK;
}
/*
* Find a slave page capable of hosting the given amount.
*/
static int lhFindSlavePage(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft,lhpage **ppSlave)
{
lhash_kv_engine *pEngine = pPage->pHash;
lhpage *pMaster = pPage->pMaster;
lhpage *pSlave = pMaster->pSlave;
unqlite_page *pRaw;
lhpage *pNew;
sxu16 iOfft;
sxi32 i;
int rc;
/* Look for an already attached slave page */
for( i = 0 ; i < pMaster->iSlave ; ++i ){
/* Find a free chunk big enough */
sxu16 size = (sxu16)(L_HASH_CELL_SZ + nAmount);
rc = lhAllocateSpace(pSlave,size,&iOfft);
if( rc != UNQLITE_OK ){
/* A space for cell header only */
size = L_HASH_CELL_SZ;
rc = lhAllocateSpace(pSlave,size,&iOfft);
}
if( rc == UNQLITE_OK ){
/* All done */
if( pOfft ){
*pOfft = iOfft;
}else{
rc = lhRestoreSpace(pSlave, iOfft, size);
}
*ppSlave = pSlave;
return rc;
}
/* Point to the next slave page */
pSlave = pSlave->pNextSlave;
}
/* Acquire a new slave page */
rc = lhAcquirePage(pEngine,&pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
/* Last slave page */
pSlave = pMaster->pSlave;
if( pSlave == 0 ){
/* First slave page */
pSlave = pMaster;
}
/* Initialize the page */
pNew = lhNewPage(pEngine,pRaw,pMaster);
if( pNew == 0 ){
return UNQLITE_NOMEM;
}
/* Mark as an empty page */
rc = lhSetEmptyPage(pNew);
if( rc != UNQLITE_OK ){
goto fail;
}
if( pOfft ){
/* Look for a free block */
if( UNQLITE_OK != lhAllocateSpace(pNew,L_HASH_CELL_SZ+nAmount,&iOfft) ){
/* Cell header only */
lhAllocateSpace(pNew,L_HASH_CELL_SZ,&iOfft); /* Never fail */
}
*pOfft = iOfft;
}
/* Link this page to the previous slave page */
rc = pEngine->pIo->xWrite(pSlave->pRaw);
if( rc != UNQLITE_OK ){
goto fail;
}
/* Reflect in the page header */
SyBigEndianPack64(&pSlave->pRaw->zData[2/*Cell offset*/+2/*Free block offset*/],pRaw->pgno);
pSlave->sHdr.iSlave = pRaw->pgno;
/* All done */
*ppSlave = pNew;
return UNQLITE_OK;
fail:
pEngine->pIo->xPageUnref(pNew->pRaw); /* pNew will be released in this call */
return rc;
}
/*
* Perform a store operation in a slave page.
*/
static int lhSlaveStore(
lhpage *pPage, /* Master page */
const void *pKey,sxu32 nKeyLen, /* Payload: key */
const void *pData,unqlite_int64 nDataLen, /* Payload: data */
sxu32 nHash /* Hash of the key */
)
{
lhpage *pSlave;
int rc;
/* Find a slave page */
rc = lhFindSlavePage(pPage,nKeyLen + nDataLen,0,&pSlave);
if( rc != UNQLITE_OK ){
return rc;
}
/* Perform the insertion in the slave page */
rc = lhStoreCell(pSlave,pKey,nKeyLen,pData,nDataLen,nHash,1);
return rc;
}
/*
* Transfer a cell to a new page (either a master or slave).
*/
static int lhTransferCell(lhcell *pTarget,lhpage *pPage)
{
lhcell *pCell;
sxu16 nOfft;
int rc;
/* Check for a free block to hold a single cell only */
rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft);
if( rc != UNQLITE_OK ){
/* Store in a slave page */
rc = lhFindSlavePage(pPage,L_HASH_CELL_SZ,&nOfft,&pPage);
if( rc != UNQLITE_OK ){
return rc;
}
}
/* Allocate a new cell instance */
pCell = lhNewCell(pPage->pHash,pPage);
if( pCell == 0 ){
return UNQLITE_NOMEM;
}
/* Fill-in the structure */
pCell->iStart = nOfft;
pCell->nData = pTarget->nData;
pCell->nKey = pTarget->nKey;
pCell->iOvfl = pTarget->iOvfl;
pCell->iDataOfft = pTarget->iDataOfft;
pCell->iDataPage = pTarget->iDataPage;
pCell->nHash = pTarget->nHash;
SyBlobDup(&pTarget->sKey,&pCell->sKey);
/* Link the cell */
rc = lhInstallCell(pCell);
if( rc != UNQLITE_OK ){
return rc;
}
/* Finally, Write the cell header */
lhCellWriteHeader(pCell);
/* All done */
return UNQLITE_OK;
}
/*
* Perform a page split.
*/
static int lhPageSplit(
lhpage *pOld, /* Page to be split */
lhpage *pNew, /* New page */
pgno split_bucket, /* Current split bucket */
pgno high_mask /* High mask (Max split bucket - 1) */
)
{
lhcell *pCell,*pNext;
SyBlob sWorker;
pgno iBucket;
int rc;
SyBlobInit(&sWorker,&pOld->pHash->sAllocator);
/* Perform the split */
pCell = pOld->pList;
for( ;; ){
if( pCell == 0 ){
/* No more cells */
break;
}
/* Obtain the new logical bucket */
iBucket = pCell->nHash & high_mask;
pNext = pCell->pNext;
if( iBucket != split_bucket){
rc = UNQLITE_OK;
if( pCell->iOvfl ){
/* Transfer the cell only */
rc = lhTransferCell(pCell,pNew);
}else{
/* Transfer the cell and its payload */
SyBlobReset(&sWorker);
if( SyBlobLength(&pCell->sKey) < 1 ){
/* Consume the key */
rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,0);
if( rc != UNQLITE_OK ){
goto fail;
}
}
/* Consume the data (Very small data < 65k) */
rc = lhConsumeCellData(pCell,unqliteDataConsumer,&sWorker);
if( rc != UNQLITE_OK ){
goto fail;
}
/* Perform the transfer */
rc = lhStoreCell(
pNew,
SyBlobData(&pCell->sKey),(int)SyBlobLength(&pCell->sKey),
SyBlobData(&sWorker),SyBlobLength(&sWorker),
pCell->nHash,
1
);
}
if( rc != UNQLITE_OK ){
goto fail;
}
/* Discard the cell from the old page */
lhUnlinkCell(pCell);
}
/* Point to the next cell */
pCell = pNext;
}
/* All done */
rc = UNQLITE_OK;
fail:
SyBlobRelease(&sWorker);
return rc;
}
/*
* Perform the infamous linear hash split operation.
*/
static int lhSplit(lhpage *pTarget,int *pRetry)
{
lhash_kv_engine *pEngine = pTarget->pHash;
lhash_bmap_rec *pRec;
lhpage *pOld,*pNew;
unqlite_page *pRaw;
int rc;
/* Get the real page number of the bucket to split */
pRec = lhMapFindBucket(pEngine,pEngine->split_bucket);
if( pRec == 0 ){
/* Can't happen */
return UNQLITE_CORRUPT;
}
/* Load the page to be split */
rc = lhLoadPage(pEngine,pRec->iReal,0,&pOld,0);
if( rc != UNQLITE_OK ){
return rc;
}
/* Request a new page */
rc = lhAcquirePage(pEngine,&pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
/* Initialize the page */
pNew = lhNewPage(pEngine,pRaw,0);
if( pNew == 0 ){
return UNQLITE_NOMEM;
}
/* Mark as an empty page */
rc = lhSetEmptyPage(pNew);
if( rc != UNQLITE_OK ){
goto fail;
}
/* Install and write the logical map record */
rc = lhMapWriteRecord(pEngine,
pEngine->split_bucket + pEngine->max_split_bucket,
pRaw->pgno
);
if( rc != UNQLITE_OK ){
goto fail;
}
if( pTarget->pRaw->pgno == pOld->pRaw->pgno ){
*pRetry = 1;
}
/* Perform the split */
rc = lhPageSplit(pOld,pNew,pEngine->split_bucket,pEngine->nmax_split_nucket - 1);
if( rc != UNQLITE_OK ){
goto fail;
}
/* Update the database header */
pEngine->split_bucket++;
/* Acquire a writer lock on the first page */
rc = pEngine->pIo->xWrite(pEngine->pHeader);
if( rc != UNQLITE_OK ){
return rc;
}
if( pEngine->split_bucket >= pEngine->max_split_bucket ){
/* Increment the generation number */
pEngine->split_bucket = 0;
pEngine->max_split_bucket = pEngine->nmax_split_nucket;
pEngine->nmax_split_nucket <<= 1;
if( !pEngine->nmax_split_nucket ){
/* If this happen to your installation, please tell us <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 ){
int do_retry = 0;
/* Split */
rc = lhSplit(pPage,&do_retry);
if( rc == UNQLITE_OK ){
if( do_retry ){
/* Re-calculate logical bucket number */
return SXERR_RETRY;
}
/* Perform the store */
rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1);
}
}
return rc;
}
/*
* Insert a record (Either overwrite or append operation) in our database.
*/
static int lh_record_insert(
unqlite_kv_engine *pKv, /* KV store */
const void *pKey,sxu32 nKeyLen, /* Payload: Key */
const void *pData,unqlite_int64 nDataLen, /* Payload: data */
int is_append /* True for an append operation */
)
{
lhash_kv_engine *pEngine = (lhash_kv_engine *)pKv;
lhash_bmap_rec *pRec;
unqlite_page *pRaw;
lhpage *pPage;
lhcell *pCell;
pgno iBucket;
sxu32 nHash;
int iCnt;
int rc;
/* Acquire the first page (DB hash Header) so that everything gets loaded autmatically */
rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0);
if( rc != UNQLITE_OK ){
return rc;
}
iCnt = 0;
/* Compute the hash of the key first */
nHash = pEngine->xHash(pKey,(sxu32)nKeyLen);
retry:
/* Extract the logical bucket number */
iBucket = nHash & (pEngine->nmax_split_nucket - 1);
if( iBucket >= pEngine->split_bucket + pEngine->max_split_bucket ){
/* Low mask */
iBucket = nHash & (pEngine->max_split_bucket - 1);
}
/* Map the logical bucket number to real page number */
pRec = lhMapFindBucket(pEngine,iBucket);
if( pRec == 0 ){
/* Request a new page */
rc = lhAcquirePage(pEngine,&pRaw);
if( rc != UNQLITE_OK ){
return rc;
}
/* Initialize the page */
pPage = lhNewPage(pEngine,pRaw,0);
if( pPage == 0 ){
return UNQLITE_NOMEM;
}
/* Mark as an empty page */
rc = lhSetEmptyPage(pPage);
if( rc != UNQLITE_OK ){
pEngine->pIo->xPageUnref(pRaw); /* pPage will be released during this call */
return rc;
}
/* Store the cell */
rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1);
if( rc == UNQLITE_OK ){
/* Install and write the logical map record */
rc = lhMapWriteRecord(pEngine,iBucket,pRaw->pgno);
}
pEngine->pIo->xPageUnref(pRaw);
return rc;
}else{
/* Load the page */
rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0);
if( rc != UNQLITE_OK ){
/* IO error, unlikely scenario */
return rc;
}
/* Do not add this page to the hot dirty list */
pEngine->pIo->xDontMkHot(pPage->pRaw);
/* Lookup for the cell */
pCell = lhFindCell(pPage,pKey,(sxu32)nKeyLen,nHash);
if( pCell == 0 ){
/* Create the record */
rc = lhRecordInstall(pPage,nHash,pKey,nKeyLen,pData,nDataLen);
if( rc == SXERR_RETRY && iCnt++ < 2 ){
rc = UNQLITE_OK;
goto retry;
}
}else{
if( is_append ){
/* Append operation */
rc = lhRecordAppend(pCell,pData,nDataLen);
}else{
/* Overwrite old value */
rc = lhRecordOverwrite(pCell,pData,nDataLen);
}
}
pEngine->pIo->xPageUnref(pPage->pRaw);
}
return rc;
}
/*
* Replace method.
*/
static int lhash_kv_replace(
unqlite_kv_engine *pKv,
const void *pKey,int nKeyLen,
const void *pData,unqlite_int64 nDataLen
)
{
int rc;
rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,0);
return rc;
}
/*
* Append method.
*/
static int lhash_kv_append(
unqlite_kv_engine *pKv,
const void *pKey,int nKeyLen,
const void *pData,unqlite_int64 nDataLen
)
{
int rc;
rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,1);
return rc;
}
/*
* Write the hash header (Page one).
*/
static int lhash_write_header(lhash_kv_engine *pEngine,unqlite_page *pHeader)
{
unsigned char *zRaw = pHeader->zData;
lhash_bmap_page *pMap;
pEngine->pHeader = pHeader;
/* 4 byte magic number */
SyBigEndianPack32(zRaw,pEngine->nMagic);
zRaw += 4;
/* 4 byte hash value to identify a valid hash function */
SyBigEndianPack32(zRaw,pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1));
zRaw += 4;
/* List of free pages: Empty */
SyBigEndianPack64(zRaw,0);
zRaw += 8;
/* Current split bucket */
SyBigEndianPack64(zRaw,pEngine->split_bucket);
zRaw += 8;
/* Maximum split bucket */
SyBigEndianPack64(zRaw,pEngine->max_split_bucket);
zRaw += 8;
/* 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.
*/
void * unqlite_malloc(unsigned int nByte)
{
SyMemBackend *pAlloc;
void *p;
pAlloc = (SyMemBackend *)unqliteExportMemBackend();
p = SyMemBackendAlloc(pAlloc,nByte);
return p;
}
void unqlite_free(void *p)
{
SyMemBackend *pAlloc;
pAlloc = (SyMemBackend *)unqliteExportMemBackend();
SyMemBackendFree(pAlloc,p);
}
/*
** Close all file descriptors accumuated in the unixInodeInfo->pUnused list.
** If all such file descriptors are closed without error, the list is
** cleared and UNQLITE_OK returned.
**
** Otherwise, if an error occurs, then successfully closed file descriptor
** entries are removed from the list, and UNQLITE_IOERR_CLOSE returned.
** not deleted and UNQLITE_IOERR_CLOSE returned.
*/
static int closePendingFds(unixFile *pFile){
int rc = UNQLITE_OK;
unixInodeInfo *pInode = pFile->pInode;
UnixUnusedFd *pError = 0;
UnixUnusedFd *p;
UnixUnusedFd *pNext;
for(p=pInode->pUnused; p; p=pNext){
pNext = p->pNext;
if( close(p->fd) ){
pFile->lastErrno = errno;
rc = UNQLITE_IOERR;
p->pNext = pError;
pError = p;
}else{
unqlite_free(p);
}
}
pInode->pUnused = pError;
return rc;
}
/*
** Release a unixInodeInfo structure previously allocated by findInodeInfo().
**
** The mutex entered using the unixEnterMutex() function must be held
** when this function is called.
*/
static void releaseInodeInfo(unixFile *pFile){
unixInodeInfo *pInode = pFile->pInode;
if( pInode ){
pInode->nRef--;
if( pInode->nRef==0 ){
closePendingFds(pFile);
if( pInode->pPrev ){
pInode->pPrev->pNext = pInode->pNext;
}else{
inodeList = pInode->pNext;
}
if( pInode->pNext ){
pInode->pNext->pPrev = pInode->pPrev;
}
unqlite_free(pInode);
}
}
}
/*
** Given a file descriptor, locate the unixInodeInfo object that
** describes that file descriptor. Create a new one if necessary. The
** return value might be uninitialized if an error occurs.
**
** The mutex entered using the unixEnterMutex() function must be held
** when this function is called.
**
** Return an appropriate error code.
*/
static int findInodeInfo(
unixFile *pFile, /* Unix file with file desc used in the key */
unixInodeInfo **ppInode /* Return the unixInodeInfo object here */
){
int rc; /* System call return code */
int fd; /* The file descriptor for pFile */
struct unixFileId fileId; /* Lookup key for the unixInodeInfo */
struct stat statbuf; /* Low-level file information */
unixInodeInfo *pInode = 0; /* Candidate unixInodeInfo object */
/* Get low-level information about the file that we can used to
** create a unique name for the file.
*/
fd = pFile->h;
rc = fstat(fd, &statbuf);
if( rc!=0 ){
pFile->lastErrno = errno;
#ifdef EOVERFLOW
if( pFile->lastErrno==EOVERFLOW ) return UNQLITE_NOTIMPLEMENTED;
#endif
return UNQLITE_IOERR;
}
#ifdef __APPLE__
/* On OS X on an msdos filesystem, the inode number is reported
** incorrectly for zero-size files. See ticket #3260. To work
** around this problem (we consider it a bug in OS X, not SQLite)
** we always increase the file size to 1 by writing a single byte
** prior to accessing the inode number. The one byte written is
** an ASCII 'S' character which also happens to be the first byte
** in the header of every SQLite database. In this way, if there
** is a race condition such that another thread has already populated
** the first page of the database, no damage is done.
*/
if( statbuf.st_size==0 && (pFile->fsFlags & UNQLITE_FSFLAGS_IS_MSDOS)!=0 ){
rc = write(fd, "S", 1);
if( rc!=1 ){
pFile->lastErrno = errno;
return UNQLITE_IOERR;
}
rc = fstat(fd, &statbuf);
if( rc!=0 ){
pFile->lastErrno = errno;
return UNQLITE_IOERR;
}
}
#endif
SyZero(&fileId,sizeof(fileId));
fileId.dev = statbuf.st_dev;
fileId.ino = statbuf.st_ino;
pInode = inodeList;
while( pInode && SyMemcmp((const void *)&fileId,(const void *)&pInode->fileId, sizeof(fileId)) ){
pInode = pInode->pNext;
}
if( pInode==0 ){
pInode = (unixInodeInfo *)unqlite_malloc( sizeof(*pInode) );
if( pInode==0 ){
return UNQLITE_NOMEM;
}
SyZero(pInode,sizeof(*pInode));
SyMemcpy((const void *)&fileId,(void *)&pInode->fileId,sizeof(fileId));
pInode->nRef = 1;
pInode->pNext = inodeList;
pInode->pPrev = 0;
if( inodeList ) inodeList->pPrev = pInode;
inodeList = pInode;
}else{
pInode->nRef++;
}
*ppInode = pInode;
return UNQLITE_OK;
}
/*
** This routine checks if there is a RESERVED lock held on the specified
** file by this or any other process. If such a lock is held, set *pResOut
** to a non-zero value otherwise *pResOut is set to zero. The return value
** is set to UNQLITE_OK unless an I/O error occurs during lock checking.
*/
static int unixCheckReservedLock(unqlite_file *id, int *pResOut){
int rc = UNQLITE_OK;
int reserved = 0;
unixFile *pFile = (unixFile*)id;
unixEnterMutex(); /* Because pFile->pInode is shared across threads */
/* Check if a thread in this process holds such a lock */
if( pFile->pInode->eFileLock>SHARED_LOCK ){
reserved = 1;
}
/* Otherwise see if some other process holds it.
*/
if( !reserved ){
struct flock lock;
lock.l_whence = SEEK_SET;
lock.l_start = RESERVED_BYTE;
lock.l_len = 1;
lock.l_type = F_WRLCK;
if (-1 == fcntl(pFile->h, F_GETLK, &lock)) {
int tErrno = errno;
rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
pFile->lastErrno = tErrno;
} else if( lock.l_type!=F_UNLCK ){
reserved = 1;
}
}
unixLeaveMutex();
*pResOut = reserved;
return rc;
}
/*
** Lock the file with the lock specified by parameter eFileLock - one
** of the following:
**
** (1) SHARED_LOCK
** (2) RESERVED_LOCK
** (3) PENDING_LOCK
** (4) EXCLUSIVE_LOCK
**
** Sometimes when requesting one lock state, additional lock states
** are inserted in between. The locking might fail on one of the later
** transitions leaving the lock state different from what it started but
** still short of its goal. The following chart shows the allowed
** transitions and the inserted intermediate states:
**
** UNLOCKED -> SHARED
** SHARED -> RESERVED
** SHARED -> (PENDING) -> EXCLUSIVE
** RESERVED -> (PENDING) -> EXCLUSIVE
** PENDING -> EXCLUSIVE
**
** This routine will only increase a lock. Use the unqliteOsUnlock()
** routine to lower a locking level.
*/
static int unixLock(unqlite_file *id, int eFileLock){
/* The following describes the implementation of the various locks and
** lock transitions in terms of the POSIX advisory shared and exclusive
** lock primitives (called read-locks and write-locks below, to avoid
** confusion with SQLite lock names). The algorithms are complicated
** slightly in order to be compatible with unixdows systems simultaneously
** accessing the same database file, in case that is ever required.
**
** Symbols defined in os.h indentify the 'pending byte' and the 'reserved
** byte', each single bytes at well known offsets, and the 'shared byte
** range', a range of 510 bytes at a well known offset.
**
** To obtain a SHARED lock, a read-lock is obtained on the 'pending
** byte'. If this is successful, a random byte from the 'shared byte
** range' is read-locked and the lock on the 'pending byte' released.
**
** A process may only obtain a RESERVED lock after it has a SHARED lock.
** A RESERVED lock is implemented by grabbing a write-lock on the
** 'reserved byte'.
**
** A process may only obtain a PENDING lock after it has obtained a
** SHARED lock. A PENDING lock is implemented by obtaining a write-lock
** on the 'pending byte'. This ensures that no new SHARED locks can be
** obtained, but existing SHARED locks are allowed to persist. A process
** does not have to obtain a RESERVED lock on the way to a PENDING lock.
** This property is used by the algorithm for rolling back a journal file
** after a crash.
**
** An EXCLUSIVE lock, obtained after a PENDING lock is held, is
** implemented by obtaining a write-lock on the entire 'shared byte
** range'. Since all other locks require a read-lock on one of the bytes
** within this range, this ensures that no other locks are held on the
** database.
**
** The reason a single byte cannot be used instead of the 'shared byte
** range' is that some versions of unixdows do not support read-locks. By
** locking a random byte from a range, concurrent SHARED locks may exist
** even if the locking primitive used is always a write-lock.
*/
int rc = UNQLITE_OK;
unixFile *pFile = (unixFile*)id;
unixInodeInfo *pInode = pFile->pInode;
struct flock lock;
int s = 0;
int tErrno = 0;
/* If there is already a lock of this type or more restrictive on the
** unixFile, do nothing. Don't use the end_lock: exit path, as
** unixEnterMutex() hasn't been called yet.
*/
if( pFile->eFileLock>=eFileLock ){
return UNQLITE_OK;
}
/* This mutex is needed because pFile->pInode is shared across threads
*/
unixEnterMutex();
pInode = pFile->pInode;
/* If some thread using this PID has a lock via a different unixFile*
** handle that precludes the requested lock, return BUSY.
*/
if( (pFile->eFileLock!=pInode->eFileLock &&
(pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK))
){
rc = UNQLITE_BUSY;
goto end_lock;
}
/* If a SHARED lock is requested, and some thread using this PID already
** has a SHARED or RESERVED lock, then increment reference counts and
** return UNQLITE_OK.
*/
if( eFileLock==SHARED_LOCK &&
(pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){
pFile->eFileLock = SHARED_LOCK;
pInode->nShared++;
pInode->nLock++;
goto end_lock;
}
/* A PENDING lock is needed before acquiring a SHARED lock and before
** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will
** be released.
*/
lock.l_len = 1L;
lock.l_whence = SEEK_SET;
if( eFileLock==SHARED_LOCK
|| (eFileLock==EXCLUSIVE_LOCK && pFile->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)
{
if( pPage->pPager->pAllocator->pMutexMethods ){
SyMutexEnter(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex);
}
pPage->nRef++;
if( pPage->pPager->pAllocator->pMutexMethods ){
SyMutexLeave(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex);
}
}
/*
* Release an in-memory page after its reference count reach zero.
*/
static int pager_release_page(Pager *pPager,Page *pPage)
{
int rc = UNQLITE_OK;
if( !(pPage->flags & PAGE_DIRTY)){
/* Invoke the unpin callback if available */
if( pPager->xPageUnpin && pPage->pUserData ){
pPager->xPageUnpin(pPage->pUserData);
}
pPage->pUserData = 0;
SyMemBackendPoolFree(pPager->pAllocator,pPage);
}else{
/* Dirty page, it will be released later when a dirty commit
* or the final commit have been applied.
*/
rc = UNQLITE_LOCKED;
}
return rc;
}
/* Forward declaration */
static int pager_unlink_page(Pager *pPager,Page *pPage);
/*
* Decrement the reference count of a given page.
*/
static void page_unref(Page *pPage)
{
int nRef;
if( pPage->pPager->pAllocator->pMutexMethods ){
SyMutexEnter(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex);
}
nRef = pPage->nRef--;
if( pPage->pPager->pAllocator->pMutexMethods ){
SyMutexLeave(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex);
}
if( nRef == 0){
Pager *pPager = pPage->pPager;
if( !(pPage->flags & PAGE_DIRTY) ){
pager_unlink_page(pPager,pPage);
/* Release the page */
pager_release_page(pPager,pPage);
}else{
if( pPage->flags & PAGE_DONT_MAKE_HOT ){
/* Do not add this page to the hot dirty list */
return;
}
if( !(pPage->flags & PAGE_HOT_DIRTY) ){
/* Add to the hot dirty list */
pPage->pPrevHot = 0;
if( pPager->pFirstHot == 0 ){
pPager->pFirstHot = pPager->pHotDirty = pPage;
}else{
pPage->pNextHot = pPager->pHotDirty;
if( pPager->pHotDirty ){
pPager->pHotDirty->pPrevHot = pPage;
}
pPager->pHotDirty = pPage;
}
pPager->nHot++;
pPage->flags |= PAGE_HOT_DIRTY;
}
}
}
}
/*
* Link a freshly created page to the list of active page.
*/
static int pager_link_page(Pager *pPager,Page *pPage)
{
sxu32 nBucket;
/* Install in the corresponding bucket */
nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1);
pPage->pNextCollide = pPager->apHash[nBucket];
if( pPager->apHash[nBucket] ){
pPager->apHash[nBucket]->pPrevCollide = pPage;
}
pPager->apHash[nBucket] = pPage;
/* Link to the list of active pages */
MACRO_LD_PUSH(pPager->pAll,pPage);
pPager->nPage++;
if( (pPager->nPage >= pPager->nSize * 4) && pPager->nPage < 100000 ){
/* Grow the hashtable */
sxu32 nNewSize = pPager->nSize << 1;
Page *pEntry,**apNew;
sxu32 n;
apNew = (Page **)SyMemBackendAlloc(pPager->pAllocator, nNewSize * sizeof(Page *));
if( apNew ){
sxu32 iBucket;
/* Zero the new table */
SyZero((void *)apNew, nNewSize * sizeof(Page *));
/* Rehash all entries */
n = 0;
pEntry = pPager->pAll;
for(;;){
/* Loop one */
if( n >= pPager->nPage ){
break;
}
pEntry->pNextCollide = pEntry->pPrevCollide = 0;
/* Install in the new bucket */
iBucket = PAGE_HASH(pEntry->pgno) & (nNewSize - 1);
pEntry->pNextCollide = apNew[iBucket];
if( apNew[iBucket] ){
apNew[iBucket]->pPrevCollide = pEntry;
}
apNew[iBucket] = pEntry;
/* Point to the next entry */
pEntry = pEntry->pNext;
n++;
}
/* Release the old table and reflect the change */
SyMemBackendFree(pPager->pAllocator,(void *)pPager->apHash);
pPager->apHash = apNew;
pPager->nSize = nNewSize;
}
}
return UNQLITE_OK;
}
/*
* Unlink a page from the list of active pages.
*/
static int pager_unlink_page(Pager *pPager,Page *pPage)
{
if( pPage->pNextCollide ){
pPage->pNextCollide->pPrevCollide = pPage->pPrevCollide;
}
if( pPage->pPrevCollide ){
pPage->pPrevCollide->pNextCollide = pPage->pNextCollide;
}else{
sxu32 nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1);
pPager->apHash[nBucket] = pPage->pNextCollide;
}
MACRO_LD_REMOVE(pPager->pAll,pPage);
pPager->nPage--;
return UNQLITE_OK;
}
/*
* Update the content of a cached page.
*/
static int pager_fill_page(Pager *pPager,pgno iNum,void *pContents)
{
Page *pPage;
/* Fetch the page from the catch */
pPage = pager_fetch_page(pPager,iNum);
if( pPage == 0 ){
return SXERR_NOTFOUND;
}
/* Reflect the change */
SyMemcpy(pContents,pPage->zData,pPager->iPageSize);
return UNQLITE_OK;
}
/*
* Read the content of a page from disk.
*/
static int pager_get_page_contents(Pager *pPager,Page *pPage,int noContent)
{
int rc = UNQLITE_OK;
if( pPager->is_mem || noContent || pPage->pgno >= pPager->dbSize ){
/* Do not bother reading, zero the page contents only */
SyZero(pPage->zData,pPager->iPageSize);
return UNQLITE_OK;
}
if( (pPager->iOpenFlags & UNQLITE_OPEN_MMAP) && (pPager->pMmap /* Paranoid edition */) ){
unsigned char *zMap = (unsigned char *)pPager->pMmap;
pPage->zData = &zMap[pPage->pgno * pPager->iPageSize];
}else{
/* Read content */
rc = unqliteOsRead(pPager->pfd,pPage->zData,pPager->iPageSize,pPage->pgno * pPager->iPageSize);
}
return rc;
}
/*
* Add a page to the dirty list.
*/
static void pager_page_to_dirty_list(Pager *pPager,Page *pPage)
{
if( pPage->flags & PAGE_DIRTY ){
/* Already set */
return;
}
/* Mark the page as dirty */
pPage->flags |= PAGE_DIRTY|PAGE_NEED_SYNC|PAGE_IN_JOURNAL;
/* Link to the list */
pPage->pDirtyPrev = 0;
pPage->pDirtyNext = pPager->pDirty;
if( pPager->pDirty ){
pPager->pDirty->pDirtyPrev = pPage;
}
pPager->pDirty = pPage;
if( pPager->pFirstDirty == 0 ){
pPager->pFirstDirty = pPage;
}
}
/*
* Merge sort.
* The merge sort implementation is based on the one used by
* the PH7 Embeddable PHP Engine (http://ph7.symisc.net/).
*/
/*
** Inputs:
** a: A sorted, null-terminated linked list. (May be null).
** b: A sorted, null-terminated linked list. (May be null).
** cmp: A pointer to the comparison function.
**
** Return Value:
** A pointer to the head of a sorted list containing the elements
** of both a and b.
**
** Side effects:
** The "next", "prev" pointers for elements in the lists a and b are
** changed.
*/
static Page * page_merge_dirty(Page *pA, Page *pB)
{
Page result, *pTail;
/* Prevent compiler warning */
result.pDirtyNext = result.pDirtyPrev = 0;
pTail = &result;
while( pA && pB ){
if( pA->pgno < pB->pgno ){
pTail->pDirtyPrev = pA;
pA->pDirtyNext = pTail;
pTail = pA;
pA = pA->pDirtyPrev;
}else{
pTail->pDirtyPrev = pB;
pB->pDirtyNext = pTail;
pTail = pB;
pB = pB->pDirtyPrev;
}
}
if( pA ){
pTail->pDirtyPrev = pA;
pA->pDirtyNext = pTail;
}else if( pB ){
pTail->pDirtyPrev = pB;
pB->pDirtyNext = pTail;
}else{
pTail->pDirtyPrev = pTail->pDirtyNext = 0;
}
return result.pDirtyPrev;
}
/*
** Inputs:
** Map: Input hashmap
** cmp: A comparison function.
**
** Return Value:
** Sorted hashmap.
**
** Side effects:
** The "next" pointers for elements in list are changed.
*/
#define N_SORT_BUCKET 32
static Page * pager_get_dirty_pages(Pager *pPager)
{
Page *a[N_SORT_BUCKET], *p, *pIn;
sxu32 i;
if( pPager->pFirstDirty == 0 ){
/* Don't bother sorting, the list is already empty */
return 0;
}
SyZero(a, sizeof(a));
/* Point to the first inserted entry */
pIn = pPager->pFirstDirty;
while( pIn ){
p = pIn;
pIn = p->pDirtyPrev;
p->pDirtyPrev = 0;
for(i=0; 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;
/* Remove from hot dirty list if it is already there */
if( pPage->flags & PAGE_HOT_DIRTY ){
Pager *pPager = pPage->pPager;
if( pPage->pNextHot ){
pPage->pNextHot->pPrevHot = pPage->pPrevHot;
}
if( pPage->pPrevHot ){
pPage->pPrevHot->pNextHot = pPage->pNextHot;
}
if( pPager->pFirstHot == pPage ){
pPager->pFirstHot = pPage->pPrevHot;
}
if( pPager->pHotDirty == pPage ){
pPager->pHotDirty = pPage->pNextHot;
}
pPager->nHot--;
pPage->flags &= ~PAGE_HOT_DIRTY;
}
return UNQLITE_OK;
}
/*
* Refer to [page_ref()]
*/
static int unqliteKvIopage_ref(unqlite_page *pPage)
{
if( pPage ){
page_ref((Page *)pPage);
}
return UNQLITE_OK;
}
/*
* Refer to [page_unref()]
*/
static int unqliteKvIoPageUnRef(unqlite_page *pPage)
{
if( pPage ){
page_unref((Page *)pPage);
}
return UNQLITE_OK;
}
/*
* Refer to the declaration of the [Pager] structure
*/
static int unqliteKvIoReadOnly(unqlite_kv_handle pHandle)
{
return ((Pager *)pHandle)->is_rdonly;
}
/*
* Refer to the declaration of the [Pager] structure
*/
static int unqliteKvIoPageSize(unqlite_kv_handle pHandle)
{
return ((Pager *)pHandle)->iPageSize;
}
/*
* Refer to the declaration of the [Pager] structure
*/
static unsigned char * unqliteKvIoTempPage(unqlite_kv_handle pHandle)
{
return ((Pager *)pHandle)->zTmpPage;
}
/*
* Set a page unpin callback.
* Refer to the declaration of the [Pager] structure
*/
static void unqliteKvIoPageUnpin(unqlite_kv_handle pHandle,void (*xPageUnpin)(void *))
{
Pager *pPager = (Pager *)pHandle;
pPager->xPageUnpin = xPageUnpin;
}
/*
* Set a page reload callback.
* Refer to the declaration of the [Pager] structure
*/
static void unqliteKvIoPageReload(unqlite_kv_handle pHandle,void (*xPageReload)(void *))
{
Pager *pPager = (Pager *)pHandle;
pPager->xPageReload = xPageReload;
}
/*
* Log an error.
* Refer to the declaration of the [Pager] structure
*/
static void unqliteKvIoErr(unqlite_kv_handle pHandle,const char *zErr)
{
Pager *pPager = (Pager *)pHandle;
unqliteGenError(pPager->pDb,zErr);
}
/*
* Init an instance of the [unqlite_kv_io] structure.
*/
static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo)
{
pIo->pHandle = pPager;
pIo->pMethods = pMethods;
pIo->xGet = unqliteKvIoPageGet;
pIo->xLookup = unqliteKvIoPageLookup;
pIo->xNew = unqliteKvIoNewPage;
pIo->xWrite = unqliteKvIopageWrite;
pIo->xDontWrite = unqliteKvIoPageDontWrite;
pIo->xDontJournal = unqliteKvIoPageDontJournal;
pIo->xDontMkHot = unqliteKvIoPageDontMakeHot;
pIo->xPageRef = unqliteKvIopage_ref;
pIo->xPageUnref = unqliteKvIoPageUnRef;
pIo->xPageSize = unqliteKvIoPageSize;
pIo->xReadOnly = unqliteKvIoReadOnly;
pIo->xTmpPage = unqliteKvIoTempPage;
pIo->xSetUnpin = unqliteKvIoPageUnpin;
pIo->xSetReload = unqliteKvIoPageReload;
pIo->xErr = unqliteKvIoErr;
return UNQLITE_OK;
}
/*
* ----------------------------------------------------------
* File: unqlite_vm.c
* MD5: 2a0c56efb2ab87d3e52d0d7c3147c53b
* ----------------------------------------------------------
*/
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
* Version 1.1.6
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://unqlite.org/licensing.html
*/
/* $SymiscID: unqlite_vm.c v1.0 Win7 2013-01-29 23:37 stable <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->pNextCol = pEntry->pPrevCol = 0;
/* Install in the new bucket */
iBucket = COL_RECORD_HASH(pEntry->nId) & (nNewSize - 1);
pEntry->pNextCol = apNew[iBucket];
if( apNew[iBucket] ){
apNew[iBucket]->pPrevCol = pEntry;
}
apNew[iBucket] = pEntry;
/* Point to the next entry */
pEntry = pEntry->pNext;
n++;
}
/* Release the old table and reflect the change */
SyMemBackendFree(&pCol->pVm->sAlloc,(void *)pCol->apRecord);
pCol->apRecord = apNew;
pCol->nRecSize = nNewSize;
}
}
/* All done */
return UNQLITE_OK;
}
/*
* Remove a record from the collection table.
*/
UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(
unqlite_col *pCol, /* Target collection */
jx9_int64 nId /* Unique record ID */
)
{
unqlite_col_record *pRecord;
/* Fetch the record first */
pRecord = CollectionCacheFetchRecord(pCol,nId);
if( pRecord == 0 ){
/* No such record */
return UNQLITE_NOTFOUND;
}
if( pRecord->pPrevCol ){
pRecord->pPrevCol->pNextCol = pRecord->pNextCol;
}else{
sxu32 iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1);
pCol->apRecord[iBucket] = pRecord->pNextCol;
}
if( pRecord->pNextCol ){
pRecord->pNextCol->pPrevCol = pRecord->pPrevCol;
}
/* Unlink */
MACRO_LD_REMOVE(pCol->pList,pRecord);
pCol->nRec--;
return UNQLITE_OK;
}
/*
* Discard a collection and its records.
*/
static int CollectionCacheRelease(unqlite_col *pCol)
{
unqlite_col_record *pNext,*pRec = pCol->pList;
unqlite_vm *pVm = pCol->pVm;
sxu32 n;
/* Discard all records */
for( n = 0 ; n < pCol->nRec ; ++n ){
pNext = pRec->pNext;
jx9MemObjRelease(&pRec->sValue);
SyMemBackendPoolFree(&pVm->sAlloc,(void *)pRec);
/* Point to the next record */
pRec = pNext;
}
SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord);
pCol->nRec = pCol->nRecSize = 0;
pCol->pList = 0;
return UNQLITE_OK;
}
/*
* Install a freshly created collection in the unqlite VM.
*/
static int unqliteVmInstallCollection(
unqlite_vm *pVm, /* Target VM */
unqlite_col *pCol /* Collection to install */
)
{
SyString *pName = &pCol->sName;
sxu32 iBucket;
/* Hash the collection name */
pCol->nHash = SyBinHash((const void *)pName->zString,pName->nByte);
/* Install it in the corresponding bucket */
iBucket = pCol->nHash & (pVm->iColSize - 1);
pCol->pNextCol = pVm->apCol[iBucket];
if( pVm->apCol[iBucket] ){
pVm->apCol[iBucket]->pPrevCol = pCol;
}
pVm->apCol[iBucket] = pCol;
/* Link to the list of active collections */
MACRO_LD_PUSH(pVm->pCol,pCol);
pVm->iCol++;
if( (pVm->iCol >= pVm->iColSize * 4) && pVm->iCol < 10000 ){
/* Grow the hashtable */
sxu32 nNewSize = pVm->iColSize << 1;
unqlite_col *pEntry;
unqlite_col **apNew;
sxu32 n;
apNew = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc, nNewSize * sizeof(unqlite_col *));
if( apNew ){
/* Zero the new table */
SyZero((void *)apNew, nNewSize * sizeof(unqlite_col *));
/* Rehash all entries */
n = 0;
pEntry = pVm->pCol;
for(;;){
/* Loop one */
if( n >= pVm->iCol ){
break;
}
pEntry->pNextCol = pEntry->pPrevCol = 0;
/* Install in the new bucket */
iBucket = pEntry->nHash & (nNewSize - 1);
pEntry->pNextCol = apNew[iBucket];
if( apNew[iBucket] ){
apNew[iBucket]->pPrevCol = pEntry;
}
apNew[iBucket] = pEntry;
/* Point to the next entry */
pEntry = pEntry->pNext;
n++;
}
/* Release the old table and reflect the change */
SyMemBackendFree(&pVm->sAlloc,(void *)pVm->apCol);
pVm->apCol = apNew;
pVm->iColSize = nNewSize;
}
}
return UNQLITE_OK;
}
/*
* Fetch a collection from the target VM.
*/
static unqlite_col * unqliteVmFetchCollection(
unqlite_vm *pVm, /* Target VM */
SyString *pName /* Lookup name */
)
{
unqlite_col *pCol;
sxu32 nHash;
if( pVm->iCol < 1 ){
/* Don't bother hashing */
return 0;
}
nHash = SyBinHash((const void *)pName->zString,pName->nByte);
/* Perform the lookup */
pCol = pVm->apCol[nHash & ( pVm->iColSize - 1)];
for(;;){
if( pCol == 0 ){
break;
}
if( nHash == pCol->nHash && SyStringCmp(pName,&pCol->sName,SyMemcmp) == 0 ){
/* Collection found */
return pCol;
}
/* Point to the next entry */
pCol = pCol->pNextCol;
}
/* No such collection */
return 0;
}
/*
* Write and/or alter collection binary header.
*/
static int CollectionSetHeader(
unqlite_kv_engine *pEngine, /* Underlying KV storage engine */
unqlite_col *pCol, /* Target collection */
jx9_int64 iRec, /* Last record ID */
jx9_int64 iTotal, /* Total number of records in this collection */
jx9_value *pSchema /* Collection schema */
)
{
SyBlob *pHeader = &pCol->sHeader;
unqlite_kv_methods *pMethods;
int iWrite = 0;
int rc;
if( pEngine == 0 ){
/* Default storage engine */
pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb);
}
pMethods = pEngine->pIo->pMethods;
if( SyBlobLength(pHeader) < 1 ){
Sytm *pCreate = &pCol->sCreation; /* Creation time */
unqlite_vfs *pVfs;
sxu32 iDos;
/* Magic number */
rc = SyBlobAppendBig16(pHeader,UNQLITE_COLLECTION_MAGIC);
if( rc != UNQLITE_OK ){
return rc;
}
/* Initial record ID */
rc = SyBlobAppendBig64(pHeader,0);
if( rc != UNQLITE_OK ){
return rc;
}
/* Total records in the collection */
rc = SyBlobAppendBig64(pHeader,0);
if( rc != UNQLITE_OK ){
return rc;
}
pVfs = (unqlite_vfs *)unqliteExportBuiltinVfs();
/* Creation time of the collection */
if( pVfs->xCurrentTime ){
/* Get the creation time */
pVfs->xCurrentTime(pVfs,pCreate);
}else{
/* Zero the structure */
SyZero(pCreate,sizeof(Sytm));
}
/* Convert to DOS time */
SyTimeFormatToDos(pCreate,&iDos);
rc = SyBlobAppendBig32(pHeader,iDos);
if( rc != UNQLITE_OK ){
return rc;
}
/* Offset to start writing collection schema */
pCol->nSchemaOfft = SyBlobLength(pHeader);
iWrite = 1;
}else{
unsigned char *zBinary = (unsigned char *)SyBlobData(pHeader);
/* Header update */
if( iRec >= 0 ){
/* Update record ID */
SyBigEndianPack64(&zBinary[2/* Magic number*/],(sxu64)iRec);
iWrite = 1;
}
if( iTotal >= 0 ){
/* Total records */
SyBigEndianPack64(&zBinary[2/* Magic number*/+8/* Record ID*/],(sxu64)iTotal);
iWrite = 1;
}
if( pSchema ){
/* Collection Schema */
SyBlobTruncate(pHeader,pCol->nSchemaOfft);
/* Encode the schema to FastJson */
rc = FastJsonEncode(pSchema,pHeader,0);
if( rc != UNQLITE_OK ){
return rc;
}
/* Copy the collection schema */
jx9MemObjStore(pSchema,&pCol->sSchema);
iWrite = 1;
}
}
if( iWrite ){
SyString *pId = &pCol->sName;
/* Reflect the disk and/or in-memory image */
rc = pMethods->xReplace(pEngine,
(const void *)pId->zString,pId->nByte,
SyBlobData(pHeader),SyBlobLength(pHeader)
);
if( rc != UNQLITE_OK ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"Cannot save collection '%z' header in the underlying storage engine",
pId
);
return rc;
}
}
return UNQLITE_OK;
}
/*
* Load a binary collection from disk.
*/
static int CollectionLoadHeader(unqlite_col *pCol)
{
SyBlob *pHeader = &pCol->sHeader;
unsigned char *zRaw,*zEnd;
sxu16 nMagic;
sxu32 iDos;
int rc;
SyBlobReset(pHeader);
/* Read the binary header */
rc = unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pHeader);
if( rc != UNQLITE_OK ){
return rc;
}
/* Perform a sanity check */
if( SyBlobLength(pHeader) < (2 /* magic */ + 8 /* record_id */ + 8 /* total_records */+ 4 /* DOS creation time*/) ){
return UNQLITE_CORRUPT;
}
zRaw = (unsigned char *)SyBlobData(pHeader);
zEnd = &zRaw[SyBlobLength(pHeader)];
/* Extract the magic number */
SyBigEndianUnpack16(zRaw,&nMagic);
if( nMagic != UNQLITE_COLLECTION_MAGIC ){
return UNQLITE_CORRUPT;
}
zRaw += 2; /* sizeof(sxu16) */
/* Extract the record ID */
SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nLastid);
zRaw += 8; /* sizeof(sxu64) */
/* Total records in the collection */
SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nTotRec);
/* Extract the collection creation date (DOS) */
zRaw += 8; /* sizeof(sxu64) */
SyBigEndianUnpack32(zRaw,&iDos);
SyDosTimeFormat(iDos,&pCol->sCreation);
zRaw += 4;
/* Check for a collection schema */
pCol->nSchemaOfft = (sxu32)(zRaw - (unsigned char *)SyBlobData(pHeader));
if( zRaw < zEnd ){
/* Decode the FastJson value */
FastJsonDecode((const void *)zRaw,(sxu32)(zEnd-zRaw),&pCol->sSchema,0,0);
}
return UNQLITE_OK;
}
/*
* Load or create a binary collection.
*/
static int unqliteVmLoadCollection(
unqlite_vm *pVm, /* Target VM */
const char *zName, /* Collection name */
sxu32 nByte, /* zName length */
int iFlag, /* Control flag */
unqlite_col **ppOut /* OUT: in-memory collection */
)
{
unqlite_kv_methods *pMethods;
unqlite_kv_engine *pEngine;
unqlite_kv_cursor *pCursor;
unqlite *pDb = pVm->pDb;
unqlite_col *pCol = 0; /* cc warning */
int rc = SXERR_MEM;
char *zDup = 0;
/* Point to the underlying KV store */
pEngine = unqlitePagerGetKvEngine(pVm->pDb);
pMethods = pEngine->pIo->pMethods;
/* Allocate a new cursor */
rc = unqliteInitCursor(pDb,&pCursor);
if( rc != UNQLITE_OK ){
return rc;
}
if( (iFlag & UNQLITE_VM_COLLECTION_CREATE) == 0 ){
/* Seek to the desired location */
rc = pMethods->xSeek(pCursor,(const void *)zName,(int)nByte,UNQLITE_CURSOR_MATCH_EXACT);
if( rc != UNQLITE_OK && (iFlag & UNQLITE_VM_COLLECTION_EXISTS) == 0){
unqliteGenErrorFormat(pDb,"Collection '%.*s' not defined in the underlying database",nByte,zName);
unqliteReleaseCursor(pDb,pCursor);
return rc;
}
else if((iFlag & UNQLITE_VM_COLLECTION_EXISTS)){
unqliteReleaseCursor(pDb,pCursor);
return rc;
}
}
/* Allocate a new instance */
pCol = (unqlite_col *)SyMemBackendPoolAlloc(&pVm->sAlloc,sizeof(unqlite_col));
if( pCol == 0 ){
unqliteGenOutofMem(pDb);
rc = UNQLITE_NOMEM;
goto fail;
}
SyZero(pCol,sizeof(unqlite_col));
/* Fill in the structure */
SyBlobInit(&pCol->sWorker,&pVm->sAlloc);
SyBlobInit(&pCol->sHeader,&pVm->sAlloc);
pCol->pVm = pVm;
pCol->pCursor = pCursor;
/* Duplicate collection name */
zDup = SyMemBackendStrDup(&pVm->sAlloc,zName,nByte);
if( zDup == 0 ){
unqliteGenOutofMem(pDb);
rc = UNQLITE_NOMEM;
goto fail;
}
pCol->nRecSize = 64; /* Must be a power of two */
pCol->apRecord = (unqlite_col_record **)SyMemBackendAlloc(&pVm->sAlloc,pCol->nRecSize * sizeof(unqlite_col_record *));
if( pCol->apRecord == 0 ){
unqliteGenOutofMem(pDb);
rc = UNQLITE_NOMEM;
goto fail;
}
/* Zero the table */
SyZero((void *)pCol->apRecord,pCol->nRecSize * sizeof(unqlite_col_record *));
SyStringInitFromBuf(&pCol->sName,zDup,nByte);
jx9MemObjInit(pVm->pJx9Vm,&pCol->sSchema);
if( iFlag & UNQLITE_VM_COLLECTION_CREATE ){
/* Create a new collection */
if( pMethods->xReplace == 0 ){
/* Read-only KV engine: Generate an error message and return */
unqliteGenErrorFormat(pDb,
"Cannot create new collection '%z' due to a read-only Key/Value storage engine",
&pCol->sName
);
rc = UNQLITE_ABORT; /* Abort VM execution */
goto fail;
}
/* Write the collection header */
rc = CollectionSetHeader(pEngine,pCol,0,0,0);
if( rc != UNQLITE_OK ){
rc = UNQLITE_ABORT; /* Abort VM execution */
goto fail;
}
}else{
/* Read the collection header */
rc = CollectionLoadHeader(pCol);
if( rc != UNQLITE_OK ){
unqliteGenErrorFormat(pDb,"Corrupt collection '%z' header",&pCol->sName);
goto fail;
}
}
/* Finally install the collection */
unqliteVmInstallCollection(pVm,pCol);
/* All done */
if( ppOut ){
*ppOut = pCol;
}
return UNQLITE_OK;
fail:
unqliteReleaseCursor(pDb,pCursor);
if( zDup ){
SyMemBackendFree(&pVm->sAlloc,zDup);
}
if( pCol ){
if( pCol->apRecord ){
SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord);
}
SyBlobRelease(&pCol->sHeader);
SyBlobRelease(&pCol->sWorker);
jx9MemObjRelease(&pCol->sSchema);
SyMemBackendPoolFree(&pVm->sAlloc,pCol);
}
return rc;
}
/*
* Fetch a collection.
*/
UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(
unqlite_vm *pVm, /* Target VM */
SyString *pName, /* Lookup key */
int iFlag /* Control flag */
)
{
unqlite_col *pCol = 0; /* cc warning */
int rc;
/* Check if the collection is already loaded in memory */
pCol = unqliteVmFetchCollection(pVm,pName);
if( pCol ){
/* Already loaded in memory*/
return pCol;
}
if( (iFlag & UNQLITE_VM_AUTO_LOAD) == 0 ){
return 0;
}
/* Ask the storage engine for the collection */
rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,0,&pCol);
/* Return to the caller */
return rc == UNQLITE_OK ? pCol : 0;
}
/*
* Return the unique ID of the last inserted record.
*/
UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol)
{
return pCol->nLastid == 0 ? 0 : (pCol->nLastid - 1);
}
/*
* Return the current record ID.
*/
UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol)
{
return pCol->nCurid;
}
/*
* Return the total number of records in a given collection.
*/
UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol)
{
return pCol->nTotRec;
}
/*
* Reset the record cursor.
*/
UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol)
{
pCol->nCurid = 0;
}
/*
* Fetch a record by its unique ID.
*/
UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(
unqlite_col *pCol, /* Target collection */
jx9_int64 nId, /* Unique record ID */
jx9_value *pValue /* OUT: record value */
)
{
SyBlob *pWorker = &pCol->sWorker;
unqlite_col_record *pRec;
int rc;
jx9_value_null(pValue);
/* Perform a cache lookup first */
pRec = CollectionCacheFetchRecord(pCol,nId);
if( pRec ){
/* Copy record value */
jx9MemObjStore(&pRec->sValue,pValue);
return UNQLITE_OK;
}
/* Reset the working buffer */
SyBlobReset(pWorker);
/* Generate the unique ID */
SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId);
/* Reset the cursor */
unqlite_kv_cursor_reset(pCol->pCursor);
/* Seek the cursor to the desired location */
rc = unqlite_kv_cursor_seek(pCol->pCursor,
SyBlobData(pWorker),SyBlobLength(pWorker),
UNQLITE_CURSOR_MATCH_EXACT
);
if( rc != UNQLITE_OK ){
return rc;
}
/* Consume the binary JSON */
SyBlobReset(pWorker);
unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pWorker);
if( SyBlobLength(pWorker) < 1 ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"Empty record '%qd'",nId
);
jx9_value_null(pValue);
}else{
/* Decode the binary JSON */
rc = FastJsonDecode(SyBlobData(pWorker),SyBlobLength(pWorker),pValue,0,0);
if( rc == UNQLITE_OK ){
/* Install the record in the cache */
CollectionCacheInstallRecord(pCol,nId,pValue);
}
}
return rc;
}
/*
* Fetch the next record from a given collection.
*/
UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue)
{
int rc;
for(;;){
if( pCol->nCurid >= pCol->nLastid ){
/* No more records, reset the record cursor ID */
pCol->nCurid = 0;
/* Return to the caller */
return SXERR_EOF;
}
rc = unqliteCollectionFetchRecordById(pCol,pCol->nCurid,pValue);
/* Increment the record ID */
pCol->nCurid++;
/* Lookup result */
if( rc == UNQLITE_OK || rc != UNQLITE_NOTFOUND ){
break;
}
}
return rc;
}
/*
* Judge a collection whether exists
*/
UNQLITE_PRIVATE int unqliteExistsCollection(
unqlite_vm *pVm, /* Target VM */
SyString *pName /* Collection name */
)
{
unqlite_col *pCol;
int rc;
/* Perform a lookup first */
pCol = unqliteVmFetchCollection(pVm,pName);
if( pCol ){
/* Already loaded in memory*/
return UNQLITE_OK;
}
rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_EXISTS,0);
return rc;
}
/*
* Create a new collection.
*/
UNQLITE_PRIVATE int unqliteCreateCollection(
unqlite_vm *pVm, /* Target VM */
SyString *pName /* Collection name */
)
{
unqlite_col *pCol;
int rc;
/* Perform a lookup first */
pCol = unqliteCollectionFetch(pVm,pName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
return UNQLITE_EXISTS;
}
/* Now, safely create the collection */
rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_CREATE,0);
return rc;
}
/*
* Set a schema (JSON object) for a given collection.
*/
UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue)
{
int rc;
if( !jx9_value_is_json_object(pValue) ){
/* Must be a JSON object */
return SXERR_INVALID;
}
rc = CollectionSetHeader(0,pCol,-1,-1,pValue);
return rc;
}
/*
* Perform a store operation on a given collection.
*/
static int CollectionStore(
unqlite_col *pCol, /* Target collection */
jx9_value *pValue /* JSON value to be stored */
)
{
SyBlob *pWorker = &pCol->sWorker;
unqlite_kv_methods *pMethods;
unqlite_kv_engine *pEngine;
sxu32 nKeyLen;
int rc;
/* Point to the underlying KV store */
pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb);
pMethods = pEngine->pIo->pMethods;
if( pCol->nTotRec >= SXI64_HIGH ){
/* Collection limit reached. No more records */
unqliteGenErrorFormat(pCol->pVm->pDb,
"Collection '%z': Records limit reached",
&pCol->sName
);
return UNQLITE_LIMIT;
}
if( pMethods->xReplace == 0 ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"Cannot store record into collection '%z' due to a read-only Key/Value storage engine",
&pCol->sName
);
return UNQLITE_READ_ONLY;
}
/* Reset the working buffer */
SyBlobReset(pWorker);
if( jx9_value_is_json_object(pValue) ){
jx9_value sId;
/* If the given type is a JSON object, then add the special __id field */
jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,pCol->nLastid);
jx9_array_add_strkey_elem(pValue,"__id",&sId);
jx9MemObjRelease(&sId);
}
/* Prepare the unique ID for this record */
SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,pCol->nLastid);
nKeyLen = SyBlobLength(pWorker);
if( nKeyLen < 1 ){
unqliteGenOutofMem(pCol->pVm->pDb);
return UNQLITE_NOMEM;
}
/* Turn to FastJson */
rc = FastJsonEncode(pValue,pWorker,0);
if( rc != UNQLITE_OK ){
return rc;
}
/* Finally perform the insertion */
rc = pMethods->xReplace(
pEngine,
SyBlobData(pWorker),nKeyLen,
SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen
);
if( rc == UNQLITE_OK ){
/* Save the value in the cache */
CollectionCacheInstallRecord(pCol,pCol->nLastid,pValue);
/* Increment the unique __id */
pCol->nLastid++;
pCol->nTotRec++;
/* Reflect the change */
rc = CollectionSetHeader(0,pCol,pCol->nLastid,pCol->nTotRec,0);
}
if( rc != UNQLITE_OK ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"IO error while storing record into collection '%z'",
&pCol->sName
);
return rc;
}
return UNQLITE_OK;
}
/*
* Perform a update operation on a given collection.
*/
static int CollectionUpdate(
unqlite_col *pCol, /* Target collection */
jx9_int64 nId, /* Record ID */
jx9_value *pValue /* JSON value to be stored */
)
{
SyBlob *pWorker = &pCol->sWorker;
unqlite_kv_methods *pMethods;
unqlite_kv_engine *pEngine;
sxu32 nKeyLen;
int rc;
/* Point to the underlying KV store */
pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb);
pMethods = pEngine->pIo->pMethods;
if( pCol->nTotRec >= SXI64_HIGH ){
/* Collection limit reached. No more records */
unqliteGenErrorFormat(pCol->pVm->pDb,
"Collection '%z': Records limit reached",
&pCol->sName
);
return UNQLITE_LIMIT;
}
if( pMethods->xReplace == 0 ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"Cannot store record into collection '%z' due to a read-only Key/Value storage engine",
&pCol->sName
);
return UNQLITE_READ_ONLY;
}
/* Reset the working buffer */
SyBlobReset(pWorker);
/* Prepare the unique ID for this record */
SyBlobFormat(pWorker,"%z_%qd",&pCol->sName, nId);
/* Reset the cursor */
unqlite_kv_cursor_reset(pCol->pCursor);
/* Seek the cursor to the desired location */
rc = unqlite_kv_cursor_seek(pCol->pCursor,
SyBlobData(pWorker),SyBlobLength(pWorker),
UNQLITE_CURSOR_MATCH_EXACT
);
if( rc != UNQLITE_OK ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"No record to update in collection '%z'",
&pCol->sName
);
return rc;
}
if( jx9_value_is_json_object(pValue) ){
jx9_value sId;
/* If the given type is a JSON object, then add the special __id field */
jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,nId);
jx9_array_add_strkey_elem(pValue,"__id",&sId);
jx9MemObjRelease(&sId);
}
nKeyLen = SyBlobLength(pWorker);
if( nKeyLen < 1 ){
unqliteGenOutofMem(pCol->pVm->pDb);
return UNQLITE_NOMEM;
}
/* Turn to FastJson */
rc = FastJsonEncode(pValue,pWorker,0);
if( rc != UNQLITE_OK ){
return rc;
}
/* Finally perform the insertion */
rc = pMethods->xReplace(
pEngine,
SyBlobData(pWorker),nKeyLen,
SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen
);
if( rc == UNQLITE_OK ){
/* Save the value in the cache */
CollectionCacheInstallRecord(pCol,nId,pValue);
}
if( rc != UNQLITE_OK ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"IO error while storing record into collection '%z'",
&pCol->sName
);
return rc;
}
return UNQLITE_OK;
}
/*
* Array walker callback (Refer to jx9_array_walk()).
*/
static int CollectionRecordArrayWalker(jx9_value *pKey,jx9_value *pData,void *pUserData)
{
unqlite_col *pCol = (unqlite_col *)pUserData;
int rc;
/* Perform the insertion */
rc = CollectionStore(pCol,pData);
if( rc != UNQLITE_OK ){
SXUNUSED(pKey); /* cc warning */
}
return rc;
}
/*
* Perform a store operation on a given collection.
*/
UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag)
{
int rc;
if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){
/* Iterate over the array and store its members in the collection */
rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol);
SXUNUSED(iFlag); /* cc warning */
}else{
rc = CollectionStore(pCol,pValue);
}
return rc;
}
/*
* Drop a record from a given collection.
*/
UNQLITE_PRIVATE int unqliteCollectionDropRecord(
unqlite_col *pCol, /* Target collection */
jx9_int64 nId, /* Unique ID of the record to be droped */
int wr_header, /* True to alter collection header */
int log_err /* True to log error */
)
{
SyBlob *pWorker = &pCol->sWorker;
int rc;
/* Reset the working buffer */
SyBlobReset(pWorker);
/* Prepare the unique ID for this record */
SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId);
/* Reset the cursor */
unqlite_kv_cursor_reset(pCol->pCursor);
/* Seek the cursor to the desired location */
rc = unqlite_kv_cursor_seek(pCol->pCursor,
SyBlobData(pWorker),SyBlobLength(pWorker),
UNQLITE_CURSOR_MATCH_EXACT
);
if( rc != UNQLITE_OK ){
return rc;
}
/* Remove the record from the storage engine */
rc = unqlite_kv_cursor_delete_entry(pCol->pCursor);
/* Finally, Remove the record from the cache */
unqliteCollectionCacheRemoveRecord(pCol,nId);
if( rc == UNQLITE_OK ){
pCol->nTotRec--;
if( wr_header ){
/* Relect in the collection header */
rc = CollectionSetHeader(0,pCol,-1,pCol->nTotRec,0);
}
}else if( rc == UNQLITE_NOTIMPLEMENTED ){
if( log_err ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"Cannot delete record from collection '%z' due to a read-only Key/Value storage engine",
&pCol->sName
);
}
}
return rc;
}
/*
* Update a given record with new data
*/
UNQLITE_PRIVATE int unqliteCollectionUpdateRecord(unqlite_col *pCol,jx9_int64 nId, jx9_value *pValue,int iFlag)
{
int rc;
if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){
/* Iterate over the array and store its members in the collection */
rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol);
SXUNUSED(iFlag); /* cc warning */
}else{
rc = CollectionUpdate(pCol,nId,pValue);
}
return rc;
}
/*
* Drop a collection from the KV storage engine and the underlying
* unqlite VM.
*/
UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol)
{
unqlite_vm *pVm = pCol->pVm;
jx9_int64 nId;
int rc;
/* Reset the cursor */
unqlite_kv_cursor_reset(pCol->pCursor);
/* Seek the cursor to the desired location */
rc = unqlite_kv_cursor_seek(pCol->pCursor,
SyStringData(&pCol->sName),SyStringLength(&pCol->sName),
UNQLITE_CURSOR_MATCH_EXACT
);
if( rc == UNQLITE_OK ){
/* Remove the record from the storage engine */
rc = unqlite_kv_cursor_delete_entry(pCol->pCursor);
}
if( rc != UNQLITE_OK ){
unqliteGenErrorFormat(pCol->pVm->pDb,
"Cannot remove collection '%z' due to a read-only Key/Value storage engine",
&pCol->sName
);
return rc;
}
/* Drop collection records */
for( nId = 0 ; nId < pCol->nLastid ; ++nId ){
unqliteCollectionDropRecord(pCol,nId,0,0);
}
/* Cleanup */
CollectionCacheRelease(pCol);
SyBlobRelease(&pCol->sHeader);
SyBlobRelease(&pCol->sWorker);
SyMemBackendFree(&pVm->sAlloc,(void *)SyStringData(&pCol->sName));
unqliteReleaseCursor(pVm->pDb,pCol->pCursor);
/* Unlink */
if( pCol->pPrevCol ){
pCol->pPrevCol->pNextCol = pCol->pNextCol;
}else{
sxu32 iBucket = pCol->nHash & (pVm->iColSize - 1);
pVm->apCol[iBucket] = pCol->pNextCol;
}
if( pCol->pNextCol ){
pCol->pNextCol->pPrevCol = pCol->pPrevCol;
}
MACRO_LD_REMOVE(pVm->pCol,pCol);
pVm->iCol--;
SyMemBackendPoolFree(&pVm->sAlloc,pCol);
return UNQLITE_OK;
}
/*
* ----------------------------------------------------------
* File: unqlite_jx9.c
* MD5: 8fddc15b667e85d7b5df5367132518fb
* ----------------------------------------------------------
*/
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
* Version 1.1.6
* For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://unqlite.org/licensing.html
*/
/* $SymiscID: unql_jx9.c v1.2 FreeBSD 2013-01-24 22:45 stable <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)
{
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
int rc;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Perform the lookup */
rc = unqliteExistsCollection(pVm, &sName);
/* Lookup result */
jx9_result_bool(pCtx, rc == UNQLITE_OK ? 1 : 0);
return JX9_OK;
}
/*
* bool collection_create(string $name)
* bool db_create(string $name)
* Create a new collection.
* Parameter
* name: Collection name
* Return
* TRUE if the collection was successfuly created. FALSE otherwise.
*/
static int unqliteBuiltin_collection_create(jx9_context *pCtx,int argc,jx9_value **argv)
{
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
int rc;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Try to create the collection */
rc = unqliteCreateCollection(pVm,&sName);
/* Return the result to the caller */
jx9_result_bool(pCtx,rc == UNQLITE_OK ? 1 : 0);
return JX9_OK;
}
/*
* value db_fetch(string $col_name)
* value db_get(string $col_name)
* Fetch the current record from a given collection and advance
* the record cursor.
* Parameter
* col_name: Collection name
* Return
* Record content success. NULL on failure (No more records to retrieve).
*/
static int unqliteBuiltin_db_fetch_next(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
int rc;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return null */
jx9_result_null(pCtx);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return null */
jx9_result_null(pCtx);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
/* Fetch the current record */
jx9_value *pValue;
pValue = jx9_context_new_scalar(pCtx);
if( pValue == 0 ){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory");
jx9_result_null(pCtx);
return JX9_OK;
}else{
rc = unqliteCollectionFetchNextRecord(pCol,pValue);
if( rc == UNQLITE_OK ){
jx9_result_value(pCtx,pValue);
/* pValue will be automatically released as soon we return from this function */
}else{
/* Return null */
jx9_result_null(pCtx);
}
}
}else{
/* No such collection, return null */
jx9_result_null(pCtx);
}
return JX9_OK;
}
/*
* value db_fetch_by_id(string $col_name,int64 $record_id)
* value db_get_by_id(string $col_name,int64 $record_id)
* Fetch a record using its unique ID from a given collection.
* Parameter
* col_name: Collection name
* record_id: Record number (__id field of a JSON object)
* Return
* Record content success. NULL on failure (No such record).
*/
static int unqliteBuiltin_db_fetch_by_id(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
jx9_int64 nId;
int nByte;
int rc;
/* Extract collection name */
if( argc < 2 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or record ID");
/* Return NULL */
jx9_result_null(pCtx);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return NULL */
jx9_result_null(pCtx);
return JX9_OK;
}
/* Extract the record ID */
nId = jx9_value_to_int(argv[1]);
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
/* Fetch the desired record */
jx9_value *pValue;
pValue = jx9_context_new_scalar(pCtx);
if( pValue == 0 ){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory");
jx9_result_null(pCtx);
return JX9_OK;
}else{
rc = unqliteCollectionFetchRecordById(pCol,nId,pValue);
if( rc == UNQLITE_OK ){
jx9_result_value(pCtx,pValue);
/* pValue will be automatically released as soon we return from this function */
}else{
/* No such record, return null */
jx9_result_null(pCtx);
}
}
}else{
/* No such collection, return null */
jx9_result_null(pCtx);
}
return JX9_OK;
}
/*
* array db_fetch_all(string $col_name,[callback filter_callback])
* array db_get_all(string $col_name,[callback filter_callback])
* Retrieve all records of a given collection and apply the given
* callback if available to filter records.
* Parameter
* col_name: Collection name
* Return
* Contents of the collection (JSON array) on success. NULL on failure.
*/
static int unqliteBuiltin_db_fetch_all(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
int rc;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return NULL */
jx9_result_null(pCtx);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return NULL */
jx9_result_null(pCtx);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
jx9_value *pValue,*pArray,*pCallback = 0;
jx9_value sResult; /* Callback result */
/* Allocate an empty scalar value and an empty JSON array */
pArray = jx9_context_new_array(pCtx);
pValue = jx9_context_new_scalar(pCtx);
jx9MemObjInit(pCtx->pVm,&sResult);
if( pValue == 0 || pArray == 0 ){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory");
jx9_result_null(pCtx);
return JX9_OK;
}
if( argc > 1 && jx9_value_is_callable(argv[1]) ){
pCallback = argv[1];
}
unqliteCollectionResetRecordCursor(pCol);
/* Fetch collection records one after one */
while( UNQLITE_OK == unqliteCollectionFetchNextRecord(pCol,pValue) ){
if( pCallback ){
jx9_value *apArg[2];
/* Invoke the filter callback */
apArg[0] = pValue;
rc = jx9VmCallUserFunction(pCtx->pVm,pCallback,1,apArg,&sResult);
if( rc == JX9_OK ){
int iResult; /* Callback result */
/* Extract callback result */
iResult = jx9_value_to_bool(&sResult);
if( !iResult ){
/* Discard the result */
unqliteCollectionCacheRemoveRecord(pCol,unqliteCollectionCurrentRecordId(pCol) - 1);
continue;
}
}
}
/* Put the value in the JSON array */
jx9_array_add_elem(pArray,0,pValue);
/* Release the value */
jx9_value_null(pValue);
}
jx9MemObjRelease(&sResult);
/* Finally, return our array */
jx9_result_value(pCtx,pArray);
/* pValue will be automatically released as soon we return from
* this foreign function.
*/
}else{
/* No such collection, return null */
jx9_result_null(pCtx);
}
return JX9_OK;
}
/*
* int64 db_last_record_id(string $col_name)
* Return the ID of the last inserted record.
* Parameter
* col_name: Collection name
* Return
* Record ID (64-bit integer) on success. FALSE on failure.
*/
static int unqliteBuiltin_db_last_record_id(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
jx9_result_int64(pCtx,unqliteCollectionLastRecordId(pCol));
}else{
/* No such collection, return FALSE */
jx9_result_bool(pCtx,0);
}
return JX9_OK;
}
/*
* inr64 db_current_record_id(string $col_name)
* Return the current record ID.
* Parameter
* col_name: Collection name
* Return
* Current record ID (64-bit integer) on success. FALSE on failure.
*/
static int unqliteBuiltin_db_current_record_id(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
jx9_result_int64(pCtx,unqliteCollectionCurrentRecordId(pCol));
}else{
/* No such collection, return FALSE */
jx9_result_bool(pCtx,0);
}
return JX9_OK;
}
/*
* bool db_reset_record_cursor(string $col_name)
* Reset the record ID cursor.
* Parameter
* col_name: Collection name
* Return
* TRUE on success. FALSE on failure.
*/
static int unqliteBuiltin_db_reset_record_cursor(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
unqliteCollectionResetRecordCursor(pCol);
jx9_result_bool(pCtx,1);
}else{
/* No such collection */
jx9_result_bool(pCtx,0);
}
return JX9_OK;
}
/*
* int64 db_total_records(string $col_name)
* Return the total number of inserted records in the given collection.
* Parameter
* col_name: Collection name
* Return
* Total number of records on success. FALSE on failure.
*/
static int unqliteBuiltin_db_total_records(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
unqlite_int64 nRec;
nRec = unqliteCollectionTotalRecords(pCol);
jx9_result_int64(pCtx,nRec);
}else{
/* No such collection */
jx9_result_bool(pCtx,0);
}
return JX9_OK;
}
/*
* string db_creation_date(string $col_name)
* Return the creation date of the given collection.
* Parameter
* col_name: Collection name
* Return
* Creation date on success. FALSE on failure.
*/
static int unqliteBuiltin_db_creation_date(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
Sytm *pTm = &pCol->sCreation;
jx9_result_string_format(pCtx,"%d-%d-%d %02d:%02d:%02d",
pTm->tm_year,pTm->tm_mon,pTm->tm_mday,
pTm->tm_hour,pTm->tm_min,pTm->tm_sec
);
}else{
/* No such collection */
jx9_result_bool(pCtx,0);
}
return JX9_OK;
}
/*
* bool db_store(string $col_name,...)
* bool db_put(string $col_name,...)
* Store one or more JSON values in a given collection.
* Parameter
* col_name: Collection name
* Return
* TRUE on success. FALSE on failure.
*/
static int unqliteBuiltin_db_store(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
int rc;
int i;
/* Extract collection name */
if( argc < 2 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol == 0 ){
jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName);
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
/* Store the given values */
for( i = 1 ; i < argc ; ++i ){
rc = unqliteCollectionPut(pCol,argv[i],0);
if( rc != UNQLITE_OK){
jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,
"Error while storing record %d in collection '%z'",i,&sName
);
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
}
/* All done, return TRUE */
jx9_result_bool(pCtx,1);
return JX9_OK;
}
/*
* bool db_update_record(string $col_name, int_64 record_id, object $json_object)
* Update a given record with new json object
* Parameter
* col_name: Collection name
* record_id: ID of the record
* json_object: New Record data
* Return
* TRUE on success. FALSE on failure.
*/
static int unqliteBuiltin_db_update_record(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
jx9_int64 nId;
int nByte;
int rc;
/* Extract collection name */
if( argc < 2 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol == 0 ){
jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName);
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
/* Update a record with the given value */
nId = jx9_value_to_int64(argv[1]);
rc = unqliteCollectionUpdateRecord(pCol, nId, argv[2], 0);
/* All done, return TRUE */
jx9_result_bool(pCtx,rc == UNQLITE_OK);
return JX9_OK;
}
/*
* bool db_drop_collection(string $col_name)
* bool collection_delete(string $col_name)
* Remove a given collection from the database.
* Parameter
* col_name: Collection name
* Return
* TRUE on success. FALSE on failure.
*/
static int unqliteBuiltin_db_drop_col(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
int rc;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol == 0 ){
jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName);
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
/* Drop the collection */
rc = unqliteDropCollection(pCol);
/* Processing result */
jx9_result_bool(pCtx,rc == UNQLITE_OK);
return JX9_OK;
}
/*
* bool db_drop_record(string $col_name,int64 record_id)
* Remove a given record from a collection.
* Parameter
* col_name: Collection name.
* record_id: ID of the record.
* Return
* TRUE on success. FALSE on failure.
*/
static int unqliteBuiltin_db_drop_record(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
jx9_int64 nId;
int nByte;
int rc;
/* Extract collection name */
if( argc < 2 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol == 0 ){
jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName);
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
/* Extract the record ID */
nId = jx9_value_to_int64(argv[1]);
/* Drop the record */
rc = unqliteCollectionDropRecord(pCol,nId,1,1);
/* Processing result */
jx9_result_bool(pCtx,rc == UNQLITE_OK);
return JX9_OK;
}
/*
* bool db_set_schema(string $col_name, object $json_object)
* Set a schema for a given collection.
* Parameter
* col_name: Collection name.
* json_object: Collection schema (Must be a JSON object).
* Return
* TRUE on success. FALSE on failure.
*/
static int unqliteBuiltin_db_set_schema(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
int rc;
/* Extract collection name */
if( argc < 2 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
if( !jx9_value_is_json_object(argv[1]) ){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection scheme");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
rc = UNQLITE_NOOP;
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
/* Set the collection scheme */
rc = unqliteCollectionSetSchema(pCol,argv[1]);
}else{
jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING,
"No such collection '%z'",
&sName
);
}
/* Processing result */
jx9_result_bool(pCtx,rc == UNQLITE_OK);
return JX9_OK;
}
/*
* object db_get_schema(string $col_name)
* Return the schema associated with a given collection.
* Parameter
* col_name: Collection name
* Return
* Collection schema on success. null otherwise.
*/
static int unqliteBuiltin_db_get_schema(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_col *pCol;
const char *zName;
unqlite_vm *pVm;
SyString sName;
int nByte;
/* Extract collection name */
if( argc < 1 ){
/* Missing arguments */
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
zName = jx9_value_to_string(argv[0],&nByte);
if( nByte < 1){
jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
/* Return false */
jx9_result_bool(pCtx,0);
return JX9_OK;
}
SyStringInitFromBuf(&sName,zName,nByte);
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Fetch the collection */
pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
if( pCol ){
/* Return the collection schema */
jx9_result_value(pCtx,&pCol->sSchema);
}else{
jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING,
"No such collection '%z'",
&sName
);
jx9_result_null(pCtx);
}
return JX9_OK;
}
/*
* bool db_begin(void)
* Manually begin a write transaction.
* Parameter
* None
* Return
* TRUE on success. FALSE otherwise.
*/
static int unqliteBuiltin_db_begin(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_vm *pVm;
unqlite *pDb;
int rc;
SXUNUSED(argc); /* cc warning */
SXUNUSED(argv);
/* Point to the unqlite Vm */
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Point to the underlying database handle */
pDb = pVm->pDb;
/* Begin the transaction */
rc = unqlitePagerBegin(pDb->sDB.pPager);
/* result */
jx9_result_bool(pCtx,rc == UNQLITE_OK );
return JX9_OK;
}
/*
* bool db_commit(void)
* Manually commit a transaction.
* Parameter
* None
* Return
* TRUE if the transaction was successfuly commited. FALSE otherwise.
*/
static int unqliteBuiltin_db_commit(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_vm *pVm;
unqlite *pDb;
int rc;
SXUNUSED(argc); /* cc warning */
SXUNUSED(argv);
/* Point to the unqlite Vm */
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Point to the underlying database handle */
pDb = pVm->pDb;
/* Commit the transaction if any */
rc = unqlitePagerCommit(pDb->sDB.pPager);
/* Commit result */
jx9_result_bool(pCtx,rc == UNQLITE_OK );
return JX9_OK;
}
/*
* bool db_rollback(void)
* Manually rollback a transaction.
* Parameter
* None
* Return
* TRUE if the transaction was successfuly rolled back. FALSE otherwise
*/
static int unqliteBuiltin_db_rollback(jx9_context *pCtx,int argc,jx9_value **argv)
{
unqlite_vm *pVm;
unqlite *pDb;
int rc;
SXUNUSED(argc); /* cc warning */
SXUNUSED(argv);
/* Point to the unqlite Vm */
pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
/* Point to the underlying database handle */
pDb = pVm->pDb;
/* Rollback the transaction if any */
rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE);
/* Rollback result */
jx9_result_bool(pCtx,rc == UNQLITE_OK );
return JX9_OK;
}
/*
* Register all the UnQLite foreign functions defined above.
*/
UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm)
{
static const jx9_builtin_func aBuiltin[] = {
{ "db_version" , unqliteBuiltin_db_version },
{ "db_copyright", unqliteBuiltin_db_credits },
{ "db_credits" , unqliteBuiltin_db_credits },
{ "db_sig" , unqliteBuiltin_db_sig },
{ "db_errlog", unqliteBuiltin_db_errlog },
{ "collection_exists", unqliteBuiltin_collection_exists },
{ "db_exists", unqliteBuiltin_collection_exists },
{ "collection_create", unqliteBuiltin_collection_create },
{ "db_create", unqliteBuiltin_collection_create },
{ "db_fetch", unqliteBuiltin_db_fetch_next },
{ "db_get", unqliteBuiltin_db_fetch_next },
{ "db_fetch_by_id", unqliteBuiltin_db_fetch_by_id },
{ "db_get_by_id", unqliteBuiltin_db_fetch_by_id },
{ "db_fetch_all", unqliteBuiltin_db_fetch_all },
{ "db_get_all", unqliteBuiltin_db_fetch_all },
{ "db_last_record_id", unqliteBuiltin_db_last_record_id },
{ "db_current_record_id", unqliteBuiltin_db_current_record_id },
{ "db_reset_record_cursor", unqliteBuiltin_db_reset_record_cursor },
{ "db_total_records", unqliteBuiltin_db_total_records },
{ "db_creation_date", unqliteBuiltin_db_creation_date },
{ "db_store", unqliteBuiltin_db_store },
{ "db_update_record", unqliteBuiltin_db_update_record },
{ "db_put", unqliteBuiltin_db_store },
{ "db_drop_collection", unqliteBuiltin_db_drop_col },
{ "collection_delete", unqliteBuiltin_db_drop_col },
{ "db_drop_record", unqliteBuiltin_db_drop_record },
{ "db_set_schema", unqliteBuiltin_db_set_schema },
{ "db_get_schema", unqliteBuiltin_db_get_schema },
{ "db_begin", unqliteBuiltin_db_begin },
{ "db_commit", unqliteBuiltin_db_commit },
{ "db_rollback", unqliteBuiltin_db_rollback },
};
int rc = UNQLITE_OK;
sxu32 n;
/* Register the unQLite functions defined above in the Jx9 call table */
for( n = 0 ; n < SX_ARRAYSIZE(aBuiltin) ; ++n ){
rc = jx9_create_function(pVm->pJx9Vm,aBuiltin[n].zName,aBuiltin[n].xFunc,pVm);
}
return rc;
}
/* END-OF-IMPLEMENTATION: unqlite@embedded@symisc 34-09-46 */
/*
* Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
* Copyright (C) 2012-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.
*/
#ifndef _WIN32
#pragma GCC diagnostic pop
#endif