summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/CMakeLists.txt20
-rw-r--r--common/storage_kyoto.cpp234
-rw-r--r--common/storage_unqlite.cpp287
-rw-r--r--common/unqlite/api.c2789
-rw-r--r--common/unqlite/bitvec.c222
-rw-r--r--common/unqlite/fastjson.c393
-rw-r--r--common/unqlite/jx9.h462
-rw-r--r--common/unqlite/jx9Int.h1705
-rw-r--r--common/unqlite/jx9_api.c1744
-rw-r--r--common/unqlite/jx9_builtin.c8297
-rw-r--r--common/unqlite/jx9_compile.c3671
-rw-r--r--common/unqlite/jx9_const.c1423
-rw-r--r--common/unqlite/jx9_hashmap.c2989
-rw-r--r--common/unqlite/jx9_json.c730
-rw-r--r--common/unqlite/jx9_lex.c758
-rw-r--r--common/unqlite/jx9_lib.c4387
-rw-r--r--common/unqlite/jx9_license.txt44
-rw-r--r--common/unqlite/jx9_memobj.c1078
-rw-r--r--common/unqlite/jx9_parse.c1177
-rw-r--r--common/unqlite/jx9_vfs.c8222
-rw-r--r--common/unqlite/jx9_vm.c7141
-rw-r--r--common/unqlite/lhash_kv.c3082
-rw-r--r--common/unqlite/license.txt25
-rw-r--r--common/unqlite/mem_kv.c678
-rw-r--r--common/unqlite/os.c117
-rw-r--r--common/unqlite/os_unix.c1769
-rw-r--r--common/unqlite/os_win.c940
-rw-r--r--common/unqlite/pager.c2808
-rw-r--r--common/unqlite/unqlite.h954
-rw-r--r--common/unqlite/unqliteInt.h319
-rw-r--r--common/unqlite/unqlite_jx9.c977
-rw-r--r--common/unqlite/unqlite_vm.c894
32 files changed, 60093 insertions, 243 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 2fef5f6..a2fd57a 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -2,20 +2,22 @@ project(akonadi2common)
2generate_flatbuffers(commands/handshake 2generate_flatbuffers(commands/handshake
3 commands/revisionupdate) 3 commands/revisionupdate)
4 4
5if (STORAGE_KYOTO) 5if (STORAGE_unqlite)
6 set(storage_SRCS storage_kyoto.cpp) 6 add_definitions(-DUNQLITE_ENABLE_THREADS)
7 set(storage_LIBS kyotocabinet) 7 file(GLOB storage_SRCS unqlite/*c)
8else (STORAGE_KYOTO) 8 set(storage_SRCS ${storage_SRCS} storage_unqlite.cpp)
9else (STORAGE_unqlite)
9 set(storage_SRCS storage_lmdb.cpp) 10 set(storage_SRCS storage_lmdb.cpp)
10 set(storage_LIBS lmdb) 11 set(storage_LIBS lmdb)
11endif (STORAGE_KYOTO) 12endif (STORAGE_unqlite)
12 13
13set(command_SRCS 14set(command_SRCS
14 commands.cpp 15 commands.cpp
15 console.cpp 16 console.cpp
16 storage_common.cpp 17 storage_common.cpp
17 ${storage_SRCS}) 18 ${storage_SRCS})
18 19
20message("We have: ${storage_SRCS}")
19add_library(${PROJECT_NAME} ${command_SRCS}) 21add_library(${PROJECT_NAME} ${command_SRCS})
20SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) 22SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
21qt5_use_modules(${PROJECT_NAME} Widgets) 23qt5_use_modules(${PROJECT_NAME} Widgets)
diff --git a/common/storage_kyoto.cpp b/common/storage_kyoto.cpp
deleted file mode 100644
index 4179c8b..0000000
--- a/common/storage_kyoto.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
1#include "storage.h"
2
3#include <iostream>
4
5#include <QAtomicInt>
6#include <QDebug>
7#include <QDir>
8#include <QFileInfo>
9#include <QReadWriteLock>
10#include <QString>
11#include <QTime>
12
13#include <kchashdb.h>
14
15//FIXME: Private::db needs to be shared process-wide for a given db; from the kc docs:
16// "It is forbidden for multible database objects in a process to open the same database at the same time."
17
18//TODO: research what can be done about this (from kc docs):
19// "To avoid data missing or corruption, it is important to close every database file by the
20// BasicDB::close method when the database is no longer in use."
21
22//TODO: research answers for max open files limit ->
23// "After that got sources of kyotocabinet and researched that for every kyoto File()
24// object special TSDKey object created, and this object create pthread_key. By default
25// one process can create limited number of this keys, and this number defined in PTHREAD_KEYS_MAX."
26// - http://stackoverflow.com/questions/22023419/kyotocabinet-and-scalajava-limit-of-db-files-open
27
28class Storage::Private
29{
30public:
31 Private(const QString &storageRoot, const QString &name, AccessMode m);
32 ~Private();
33
34 QString name;
35 kyotocabinet::TreeDB db;
36 AccessMode mode;
37 bool dbOpen;
38 bool inTransaction;
39};
40
41Storage::Private::Private(const QString &storageRoot, const QString &n, AccessMode m)
42 : name(n),
43 mode(m),
44 dbOpen(false),
45 inTransaction(false)
46{
47 QDir dir;
48 dir.mkdir(storageRoot);
49
50 //create file
51 uint32_t openMode = kyotocabinet::BasicDB::OCREATE |
52 (mode == ReadOnly ? kyotocabinet::BasicDB::OREADER
53 : kyotocabinet::BasicDB::OWRITER);
54 dbOpen = db.open((storageRoot + "/" + name + ".kch").toStdString(), openMode);
55 if (!dbOpen) {
56 std::cerr << "Could not open database: " << db.error().codename(db.error().code()) << " " << db.error().message() << std::endl;
57 // TODO: handle error
58 }
59}
60
61Storage::Private::~Private()
62{
63 if (dbOpen && inTransaction) {
64 db.end_transaction(false);
65 }
66}
67
68Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode)
69 : d(new Private(storageRoot, name, mode))
70{
71}
72
73Storage::~Storage()
74{
75 delete d;
76}
77
78bool Storage::isInTransaction() const
79{
80 return d->inTransaction;
81}
82
83bool Storage::startTransaction(AccessMode type)
84{
85 if (!d->dbOpen) {
86 return false;
87 }
88
89 if (type == ReadWrite && d->mode != ReadWrite) {
90 return false;
91 }
92
93 if (d->inTransaction) {
94 return true;
95 }
96
97 //TODO handle errors
98 d->inTransaction = d->db.begin_transaction();
99 return d->inTransaction;
100}
101
102bool Storage::commitTransaction()
103{
104 if (!d->dbOpen) {
105 return false;
106 }
107
108 if (!d->inTransaction) {
109 return false;
110 }
111
112 bool success = d->db.end_transaction(true);
113 d->inTransaction = false;
114 return success;
115}
116
117void Storage::abortTransaction()
118{
119 if (!d->dbOpen || !d->inTransaction) {
120 return;
121 }
122
123 d->db.end_transaction(false);
124 d->inTransaction = false;
125}
126
127bool Storage::write(const char *key, size_t keySize, const char *value, size_t valueSize)
128{
129 if (!d->dbOpen) {
130 return false;
131 }
132
133 bool success = d->db.set(key, keySize, value, valueSize);
134 return success;
135}
136
137bool Storage::write(const std::string &sKey, const std::string &sValue)
138{
139 if (!d->dbOpen) {
140 return false;
141 }
142
143 bool success = d->db.set(sKey, sValue);
144 return success;
145}
146
147void Storage::read(const std::string &sKey,
148 const std::function<bool(const std::string &value)> &resultHandler,
149 const std::function<void(const Storage::Error &error)> &errorHandler)
150{
151 if (!d->dbOpen) {
152 Error error(d->name.toStdString(), -1, "Not open");
153 errorHandler(error);
154 return;
155 }
156
157 std::string value;
158 if (sKey.empty()) {
159 kyotocabinet::DB::Cursor *cursor = d->db.cursor();
160 cursor->jump();
161
162 std::string key, value;
163 while (cursor->get_value(&value, true) && resultHandler(value)) {}
164
165 delete cursor;
166 return;
167 } else {
168 if (d->db.get(sKey, &value)) {
169 resultHandler(value);
170 return;
171 }
172 }
173
174 Error error(d->name.toStdString(), d->db.error().code(), d->db.error().message());
175 errorHandler(error);
176}
177
178void Storage::read(const std::string &sKey,
179 const std::function<bool(void *ptr, int size)> &resultHandler,
180 const std::function<void(const Storage::Error &error)> &errorHandler)
181{
182 if (!d->dbOpen) {
183 Error error(d->name.toStdString(), -1, "Not open");
184 errorHandler(error);
185 return;
186 }
187
188 size_t valueSize;
189 char *valueBuffer;
190 if (sKey.empty()) {
191 kyotocabinet::DB::Cursor *cursor = d->db.cursor();
192 cursor->jump();
193
194 while ((valueBuffer = cursor->get_value(&valueSize, true))) {
195 bool ok = resultHandler(valueBuffer, valueSize);
196 delete[] valueBuffer;
197 if (!ok) {
198 break;
199 }
200 }
201
202 delete cursor;
203 } else {
204 valueBuffer = d->db.get(sKey.data(), sKey.size(), &valueSize);
205 if (valueBuffer) {
206 resultHandler(valueBuffer, valueSize);
207 } else {
208 Error error(d->name.toStdString(), d->db.error().code(), d->db.error().message());
209 errorHandler(error);
210 }
211 delete[] valueBuffer;
212 }
213}
214
215qint64 Storage::diskUsage() const
216{
217 if (!d->dbOpen) {
218 return 0;
219 }
220
221 QFileInfo info(QString::fromStdString(d->db.path()));
222 return info.size();
223}
224
225void Storage::removeFromDisk() const
226{
227 if (!d->dbOpen) {
228 return;
229 }
230
231 QFileInfo info(QString::fromStdString(d->db.path()));
232 QDir dir = info.dir();
233 dir.remove(info.fileName());
234}
diff --git a/common/storage_unqlite.cpp b/common/storage_unqlite.cpp
new file mode 100644
index 0000000..5c5ef09
--- /dev/null
+++ b/common/storage_unqlite.cpp
@@ -0,0 +1,287 @@
1#include "storage.h"
2
3#include <iostream>
4
5#include <QAtomicInt>
6#include <QDebug>
7#include <QDir>
8#include <QReadWriteLock>
9#include <QString>
10#include <QTime>
11
12#include "unqlite/unqlite.h"
13
14class Storage::Private
15{
16public:
17 Private(const QString &s, const QString &name, AccessMode m);
18 ~Private();
19
20 void reportDbError(const char *functionName);
21 void reportDbError(const char *functionName, int errorCode,
22 const std::function<void(const Storage::Error &error)> &errorHandler);
23
24 QString storageRoot;
25 QString name;
26 AccessMode mode;
27
28 unqlite *db;
29 bool inTransaction;
30};
31
32Storage::Private::Private(const QString &s, const QString &n, AccessMode m)
33 : storageRoot(s),
34 name(n),
35 mode(m),
36 db(0),
37 inTransaction(false)
38{
39 const QString fullPath(storageRoot + '/' + name);
40 QDir dir;
41 dir.mkdir(storageRoot);
42
43 //create file
44 int openFlags = UNQLITE_OPEN_CREATE;
45 if (mode == ReadOnly) {
46 openFlags |= UNQLITE_OPEN_READONLY | UNQLITE_OPEN_MMAP;
47 } else {
48 openFlags |= UNQLITE_OPEN_READWRITE;
49 }
50
51 int rc = unqlite_open(&db, fullPath.toStdString().data(), openFlags);
52
53 if (rc != UNQLITE_OK) {
54 reportDbError("unqlite_open");
55 }
56}
57
58Storage::Private::~Private()
59{
60 unqlite_close(db);
61}
62
63void Storage::Private::reportDbError(const char *functionName)
64{
65 std::cerr << "ERROR: " << functionName;
66 if (db) {
67 const char *errorMessage;
68 int length;
69 /* Something goes wrong, extract database error log */
70 unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &errorMessage, &length);
71 if (length > 0) {
72 std::cerr << ": " << errorMessage;
73 }
74 }
75 std::cerr << std::endl;
76}
77
78void Storage::Private::reportDbError(const char *functionName, int errorCode,
79 const std::function<void(const Storage::Error &error)> &errorHandler)
80{
81 if (db) {
82 const char *errorMessage;
83 int length;
84 /* Something goes wrong, extract database error log */
85 unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &errorMessage, &length);
86 if (length > 0) {
87 Error error(name.toStdString(), errorCode, errorMessage);
88 errorHandler(error);
89 return;
90 }
91 }
92
93 Error error(name.toStdString(), errorCode, functionName);
94 errorHandler(error);
95}
96
97Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode)
98 : d(new Private(storageRoot, name, mode))
99{
100}
101
102Storage::~Storage()
103{
104 if (d->inTransaction) {
105 abortTransaction();
106 }
107
108 delete d;
109}
110
111bool Storage::isInTransaction() const
112{
113 return d->inTransaction;
114}
115
116bool Storage::startTransaction(AccessMode type)
117{
118 if (!d->db) {
119 return false;
120 }
121
122 if (d->inTransaction) {
123 return true;
124 }
125
126 d->inTransaction = unqlite_begin(d->db) == UNQLITE_OK;
127
128 if (!d->inTransaction) {
129 d->reportDbError("unqlite_begin");
130 }
131
132 return d->inTransaction;
133}
134
135bool Storage::commitTransaction()
136{
137 if (!d->db) {
138 return false;
139 }
140
141 if (!d->inTransaction) {
142 return true;
143 }
144
145 int rc = unqlite_commit(d->db);
146 d->inTransaction = false;
147
148 if (rc != UNQLITE_OK) {
149 d->reportDbError("unqlite_commit");
150 }
151
152 return rc == UNQLITE_OK;
153}
154
155void Storage::abortTransaction()
156{
157 if (!d->db || !d->inTransaction) {
158 return;
159 }
160
161 unqlite_rollback(d->db);
162 d->inTransaction = false;
163}
164
165bool Storage::write(const char *key, size_t keySize, const char *value, size_t valueSize)
166{
167 return write(std::string(key, keySize), std::string(value, valueSize));
168}
169
170bool Storage::write(const std::string &sKey, const std::string &sValue)
171{
172 if (!d->db) {
173 return false;
174 }
175
176 int rc = unqlite_kv_store(d->db, sKey.data(), -1, sValue.data(), sValue.size());
177
178 if (rc != UNQLITE_OK) {
179 d->reportDbError("unqlite_kv_store");
180 }
181
182 return !rc;
183}
184
185void Storage::read(const std::string &sKey,
186 const std::function<bool(const std::string &value)> &resultHandler,
187 const std::function<void(const Storage::Error &error)> &errorHandler)
188{
189 read(sKey,
190 [&](void *ptr, int size) -> bool {
191 if (ptr) {
192 const std::string resultValue(static_cast<char*>(ptr), size);
193 return resultHandler(resultValue);
194 }
195
196 return true;
197 }, errorHandler);
198}
199
200void Storage::read(const std::string &sKey,
201 const std::function<bool(void *ptr, int size)> &resultHandler,
202 const std::function<void(const Storage::Error &error)> &errorHandler)
203{
204 scan(sKey.data(), sKey.size(), [resultHandler](void *keyPtr, int keySize, void *valuePtr, int valueSize) {
205 return resultHandler(valuePtr, valueSize);
206 }, errorHandler);
207}
208
209void fetchCursorData(unqlite_kv_cursor *cursor,
210 void **keyBuffer, int *keyBufferLength, void **dataBuffer, unqlite_int64 *dataBufferLength,
211 const std::function<bool(void *keyPtr, int keySize, void *valuePtr, int valueSize)> &resultHandler)
212{
213 int keyLength = 0;
214 unqlite_int64 dataLength = 0;
215 // now fetch the data sizes
216 if (unqlite_kv_cursor_key(cursor, nullptr, &keyLength) == UNQLITE_OK &&
217 unqlite_kv_cursor_data(cursor, nullptr, &dataLength) == UNQLITE_OK) {
218 if (keyLength > *keyBufferLength) {
219 *keyBuffer = realloc(*keyBuffer, keyLength);
220 *keyBufferLength = keyLength;
221 }
222
223 if (dataLength > *dataBufferLength) {
224 *dataBuffer = realloc(*dataBuffer, dataLength);
225 *dataBufferLength = dataLength;
226 }
227
228 if (unqlite_kv_cursor_key(cursor, *keyBuffer, &keyLength) == UNQLITE_OK &&
229 unqlite_kv_cursor_data(cursor, *dataBuffer, &dataLength) == UNQLITE_OK) {
230 resultHandler(*keyBuffer, keyLength, *dataBuffer, dataLength);
231 }
232 }
233}
234
235void Storage::scan(const char *keyData, uint keySize,
236 const std::function<bool(void *keyPtr, int keySize, void *valuePtr, int valueSize)> &resultHandler,
237 const std::function<void(const Storage::Error &error)> &errorHandler)
238{
239 if (!d->db) {
240 Error error(d->name.toStdString(), -1, "Not open");
241 errorHandler(error);
242 return;
243 }
244
245 unqlite_kv_cursor *cursor;
246
247 int rc = unqlite_kv_cursor_init(d->db, &cursor);
248 if (rc != UNQLITE_OK) {
249 d->reportDbError("unqlite_kv_cursor_init", rc, errorHandler);
250 return;
251 }
252
253 void *keyBuffer = nullptr;
254 int keyBufferLength = 0;
255 void *dataBuffer = nullptr;
256 //FIXME: 64bit ints, but feeding int lenghts to the callbacks. can result in truncation
257 unqlite_int64 dataBufferLength = 0;
258 if (!keyData || keySize == 0) {
259 for (unqlite_kv_cursor_first_entry(cursor); unqlite_kv_cursor_valid_entry(cursor); unqlite_kv_cursor_next_entry(cursor)) {
260 fetchCursorData(cursor, &keyBuffer, &keyBufferLength, &dataBuffer, &dataBufferLength, resultHandler);
261 }
262 } else {
263 rc = unqlite_kv_cursor_seek(cursor, keyData, keySize, UNQLITE_CURSOR_MATCH_EXACT);
264 if (rc == UNQLITE_OK) {
265 fetchCursorData(cursor, &keyBuffer, &keyBufferLength, &dataBuffer, &dataBufferLength, resultHandler);
266 } else {
267 std::cout << "couldn't find value " << std::string(keyData, keySize) << std::endl;
268 }
269
270 }
271
272 free(keyBuffer);
273 free(dataBuffer);
274 unqlite_kv_cursor_release(d->db, cursor);
275}
276
277qint64 Storage::diskUsage() const
278{
279 QFileInfo info(d->storageRoot + '/' + d->name);
280 return info.size();
281}
282
283void Storage::removeFromDisk() const
284{
285 QFile::remove(d->storageRoot + '/' + d->name);
286}
287
diff --git a/common/unqlite/api.c b/common/unqlite/api.c
new file mode 100644
index 0000000..9915d14
--- /dev/null
+++ b/common/unqlite/api.c
@@ -0,0 +1,2789 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: api.c v2.0 FreeBSD 2012-11-08 23:07 stable <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/* This file implement the public interfaces presented to host-applications.
18 * Routines in other files are for internal use by UnQLite and should not be
19 * accessed by users of the library.
20 */
21#define UNQLITE_DB_MISUSE(DB) (DB == 0 || DB->nMagic != UNQLITE_DB_MAGIC)
22#define UNQLITE_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE)
23/* If another thread have released a working instance, the following macros
24 * evaluates to true. These macros are only used when the library
25 * is built with threading support enabled.
26 */
27#define UNQLITE_THRD_DB_RELEASE(DB) (DB->nMagic != UNQLITE_DB_MAGIC)
28#define UNQLITE_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE)
29/* IMPLEMENTATION: unqlite@embedded@symisc 118-09-4785 */
30/*
31 * All global variables are collected in the structure named "sUnqlMPGlobal".
32 * That way it is clear in the code when we are using static variable because
33 * its name start with sUnqlMPGlobal.
34 */
35static struct unqlGlobal_Data
36{
37 SyMemBackend sAllocator; /* Global low level memory allocator */
38#if defined(UNQLITE_ENABLE_THREADS)
39 const SyMutexMethods *pMutexMethods; /* Mutex methods */
40 SyMutex *pMutex; /* Global mutex */
41 sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded
42 * The threading level can be set using the [unqlite_lib_config()]
43 * interface with a configuration verb set to
44 * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE or
45 * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI
46 */
47#endif
48 SySet kv_storage; /* Installed KV storage engines */
49 int iPageSize; /* Default Page size */
50 unqlite_vfs *pVfs; /* Underlying virtual file system (Vfs) */
51 sxi32 nDB; /* Total number of active DB handles */
52 unqlite *pDB; /* List of active DB handles */
53 sxu32 nMagic; /* Sanity check against library misuse */
54}sUnqlMPGlobal = {
55 {0, 0, 0, 0, 0, 0, 0, 0, {0}},
56#if defined(UNQLITE_ENABLE_THREADS)
57 0,
58 0,
59 0,
60#endif
61 {0, 0, 0, 0, 0, 0, 0 },
62 UNQLITE_DEFAULT_PAGE_SIZE,
63 0,
64 0,
65 0,
66 0
67};
68#define UNQLITE_LIB_MAGIC 0xEA1495BA
69#define UNQLITE_LIB_MISUSE (sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC)
70/*
71 * Supported threading level.
72 * These options have meaning only when the library is compiled with multi-threading
73 * support. That is, the UNQLITE_ENABLE_THREADS compile time directive must be defined
74 * when UnQLite is built.
75 * UNQLITE_THREAD_LEVEL_SINGLE:
76 * In this mode, mutexing is disabled and the library can only be used by a single thread.
77 * UNQLITE_THREAD_LEVEL_MULTI
78 * In this mode, all mutexes including the recursive mutexes on [unqlite] objects
79 * are enabled so that the application is free to share the same database handle
80 * between different threads at the same time.
81 */
82#define UNQLITE_THREAD_LEVEL_SINGLE 1
83#define UNQLITE_THREAD_LEVEL_MULTI 2
84/*
85 * Find a Key Value storage engine from the set of installed engines.
86 * Return a pointer to the storage engine methods on success. NULL on failure.
87 */
88UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore(
89 const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */
90 sxu32 nByte /* zName length */
91 )
92{
93 unqlite_kv_methods **apStore,*pEntry;
94 sxu32 n,nMax;
95 /* Point to the set of installed engines */
96 apStore = (unqlite_kv_methods **)SySetBasePtr(&sUnqlMPGlobal.kv_storage);
97 nMax = SySetUsed(&sUnqlMPGlobal.kv_storage);
98 for( n = 0 ; n < nMax; ++n ){
99 pEntry = apStore[n];
100 if( nByte == SyStrlen(pEntry->zName) && SyStrnicmp(pEntry->zName,zName,nByte) == 0 ){
101 /* Storage engine found */
102 return pEntry;
103 }
104 }
105 /* No such entry, return NULL */
106 return 0;
107}
108/*
109 * Configure the UnQLite library.
110 * Return UNQLITE_OK on success. Any other return value indicates failure.
111 * Refer to [unqlite_lib_config()].
112 */
113static sxi32 unqliteCoreConfigure(sxi32 nOp, va_list ap)
114{
115 int rc = UNQLITE_OK;
116 switch(nOp){
117 case UNQLITE_LIB_CONFIG_PAGE_SIZE: {
118 /* Default page size: Must be a power of two */
119 int iPage = va_arg(ap,int);
120 if( iPage >= UNQLITE_MIN_PAGE_SIZE && iPage <= UNQLITE_MAX_PAGE_SIZE ){
121 if( !(iPage & (iPage - 1)) ){
122 sUnqlMPGlobal.iPageSize = iPage;
123 }else{
124 /* Invalid page size */
125 rc = UNQLITE_INVALID;
126 }
127 }else{
128 /* Invalid page size */
129 rc = UNQLITE_INVALID;
130 }
131 break;
132 }
133 case UNQLITE_LIB_CONFIG_STORAGE_ENGINE: {
134 /* Install a key value storage engine */
135 unqlite_kv_methods *pMethods = va_arg(ap,unqlite_kv_methods *);
136 /* Make sure we are delaing with a valid methods */
137 if( pMethods == 0 || SX_EMPTY_STR(pMethods->zName) || pMethods->xSeek == 0 || pMethods->xData == 0
138 || pMethods->xKey == 0 || pMethods->xDataLength == 0 || pMethods->xKeyLength == 0
139 || pMethods->szKv < (int)sizeof(unqlite_kv_engine) ){
140 rc = UNQLITE_INVALID;
141 break;
142 }
143 /* Install it */
144 rc = SySetPut(&sUnqlMPGlobal.kv_storage,(const void *)&pMethods);
145 break;
146 }
147 case UNQLITE_LIB_CONFIG_VFS:{
148 /* Install a virtual file system */
149 unqlite_vfs *pVfs = va_arg(ap,unqlite_vfs *);
150 if( pVfs ){
151 sUnqlMPGlobal.pVfs = pVfs;
152 }
153 break;
154 }
155 case UNQLITE_LIB_CONFIG_USER_MALLOC: {
156 /* Use an alternative low-level memory allocation routines */
157 const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *);
158 /* Save the memory failure callback (if available) */
159 ProcMemError xMemErr = sUnqlMPGlobal.sAllocator.xMemError;
160 void *pMemErr = sUnqlMPGlobal.sAllocator.pUserData;
161 if( pMethods == 0 ){
162 /* Use the built-in memory allocation subsystem */
163 rc = SyMemBackendInit(&sUnqlMPGlobal.sAllocator, xMemErr, pMemErr);
164 }else{
165 rc = SyMemBackendInitFromOthers(&sUnqlMPGlobal.sAllocator, pMethods, xMemErr, pMemErr);
166 }
167 break;
168 }
169 case UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK: {
170 /* Memory failure callback */
171 ProcMemError xMemErr = va_arg(ap, ProcMemError);
172 void *pUserData = va_arg(ap, void *);
173 sUnqlMPGlobal.sAllocator.xMemError = xMemErr;
174 sUnqlMPGlobal.sAllocator.pUserData = pUserData;
175 break;
176 }
177 case UNQLITE_LIB_CONFIG_USER_MUTEX: {
178#if defined(UNQLITE_ENABLE_THREADS)
179 /* Use an alternative low-level mutex subsystem */
180 const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *);
181#if defined (UNTRUST)
182 if( pMethods == 0 ){
183 rc = UNQLITE_CORRUPT;
184 }
185#endif
186 /* Sanity check */
187 if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){
188 /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */
189 rc = UNQLITE_CORRUPT;
190 break;
191 }
192 if( sUnqlMPGlobal.pMutexMethods ){
193 /* Overwrite the previous mutex subsystem */
194 SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex);
195 if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){
196 sUnqlMPGlobal.pMutexMethods->xGlobalRelease();
197 }
198 sUnqlMPGlobal.pMutex = 0;
199 }
200 /* Initialize and install the new mutex subsystem */
201 if( pMethods->xGlobalInit ){
202 rc = pMethods->xGlobalInit();
203 if ( rc != UNQLITE_OK ){
204 break;
205 }
206 }
207 /* Create the global mutex */
208 sUnqlMPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST);
209 if( sUnqlMPGlobal.pMutex == 0 ){
210 /*
211 * If the supplied mutex subsystem is so sick that we are unable to
212 * create a single mutex, there is no much we can do here.
213 */
214 if( pMethods->xGlobalRelease ){
215 pMethods->xGlobalRelease();
216 }
217 rc = UNQLITE_CORRUPT;
218 break;
219 }
220 sUnqlMPGlobal.pMutexMethods = pMethods;
221 if( sUnqlMPGlobal.nThreadingLevel == 0 ){
222 /* Set a default threading level */
223 sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI;
224 }
225#endif
226 break;
227 }
228 case UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE:
229#if defined(UNQLITE_ENABLE_THREADS)
230 /* Single thread mode (Only one thread is allowed to play with the library) */
231 sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_SINGLE;
232 jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE);
233#endif
234 break;
235 case UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI:
236#if defined(UNQLITE_ENABLE_THREADS)
237 /* Multi-threading mode (library is thread safe and database handles and virtual machines
238 * may be shared between multiple threads).
239 */
240 sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI;
241 jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_MULTI);
242#endif
243 break;
244 default:
245 /* Unknown configuration option */
246 rc = UNQLITE_CORRUPT;
247 break;
248 }
249 return rc;
250}
251/*
252 * [CAPIREF: unqlite_lib_config()]
253 * Please refer to the official documentation for function purpose and expected parameters.
254 */
255int unqlite_lib_config(int nConfigOp,...)
256{
257 va_list ap;
258 int rc;
259 if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){
260 /* Library is already initialized, this operation is forbidden */
261 return UNQLITE_LOCKED;
262 }
263 va_start(ap,nConfigOp);
264 rc = unqliteCoreConfigure(nConfigOp,ap);
265 va_end(ap);
266 return rc;
267}
268/*
269 * Global library initialization
270 * Refer to [unqlite_lib_init()]
271 * This routine must be called to initialize the memory allocation subsystem, the mutex
272 * subsystem prior to doing any serious work with the library. The first thread to call
273 * this routine does the initialization process and set the magic number so no body later
274 * can re-initialize the library. If subsequent threads call this routine before the first
275 * thread have finished the initialization process, then the subsequent threads must block
276 * until the initialization process is done.
277 */
278static sxi32 unqliteCoreInitialize(void)
279{
280 const unqlite_kv_methods *pMethods;
281 const unqlite_vfs *pVfs; /* Built-in vfs */
282#if defined(UNQLITE_ENABLE_THREADS)
283 const SyMutexMethods *pMutexMethods = 0;
284 SyMutex *pMaster = 0;
285#endif
286 int rc;
287 /*
288 * If the library is already initialized, then a call to this routine
289 * is a no-op.
290 */
291 if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){
292 return UNQLITE_OK; /* Already initialized */
293 }
294 /* Point to the built-in vfs */
295 pVfs = unqliteExportBuiltinVfs();
296 /* Install it */
297 unqlite_lib_config(UNQLITE_LIB_CONFIG_VFS, pVfs);
298#if defined(UNQLITE_ENABLE_THREADS)
299 if( sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_SINGLE ){
300 pMutexMethods = sUnqlMPGlobal.pMutexMethods;
301 if( pMutexMethods == 0 ){
302 /* Use the built-in mutex subsystem */
303 pMutexMethods = SyMutexExportMethods();
304 if( pMutexMethods == 0 ){
305 return UNQLITE_CORRUPT; /* Can't happen */
306 }
307 /* Install the mutex subsystem */
308 rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MUTEX, pMutexMethods);
309 if( rc != UNQLITE_OK ){
310 return rc;
311 }
312 }
313 /* Obtain a static mutex so we can initialize the library without calling malloc() */
314 pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1);
315 if( pMaster == 0 ){
316 return UNQLITE_CORRUPT; /* Can't happen */
317 }
318 }
319 /* Lock the master mutex */
320 rc = UNQLITE_OK;
321 SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
322 if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){
323#endif
324 if( sUnqlMPGlobal.sAllocator.pMethods == 0 ){
325 /* Install a memory subsystem */
326 rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */
327 if( rc != UNQLITE_OK ){
328 /* If we are unable to initialize the memory backend, there is no much we can do here.*/
329 goto End;
330 }
331 }
332#if defined(UNQLITE_ENABLE_THREADS)
333 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){
334 /* Protect the memory allocation subsystem */
335 rc = SyMemBackendMakeThreadSafe(&sUnqlMPGlobal.sAllocator, sUnqlMPGlobal.pMutexMethods);
336 if( rc != UNQLITE_OK ){
337 goto End;
338 }
339 }
340#endif
341 SySetInit(&sUnqlMPGlobal.kv_storage,&sUnqlMPGlobal.sAllocator,sizeof(unqlite_kv_methods *));
342 /* Install the built-in Key Value storage engines */
343 pMethods = unqliteExportMemKvStorage(); /* In-memory storage */
344 unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods);
345 /* Default disk key/value storage engine */
346 pMethods = unqliteExportDiskKvStorage(); /* Disk storage */
347 unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods);
348 /* Default page size */
349 if( sUnqlMPGlobal.iPageSize < UNQLITE_MIN_PAGE_SIZE ){
350 unqlite_lib_config(UNQLITE_LIB_CONFIG_PAGE_SIZE,UNQLITE_DEFAULT_PAGE_SIZE);
351 }
352 /* Our library is initialized, set the magic number */
353 sUnqlMPGlobal.nMagic = UNQLITE_LIB_MAGIC;
354 rc = UNQLITE_OK;
355#if defined(UNQLITE_ENABLE_THREADS)
356 } /* sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC */
357#endif
358End:
359#if defined(UNQLITE_ENABLE_THREADS)
360 /* Unlock the master mutex */
361 SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
362#endif
363 return rc;
364}
365/* Forward declaration */
366static int unqliteVmRelease(unqlite_vm *pVm);
367/*
368 * Release a single instance of an unqlite database handle.
369 */
370static int unqliteDbRelease(unqlite *pDb)
371{
372 unqlite_db *pStore = &pDb->sDB;
373 unqlite_vm *pVm,*pNext;
374 int rc = UNQLITE_OK;
375 if( (pDb->iFlags & UNQLITE_FL_DISABLE_AUTO_COMMIT) == 0 ){
376 /* Commit any outstanding transaction */
377 rc = unqlitePagerCommit(pStore->pPager);
378 if( rc != UNQLITE_OK ){
379 /* Rollback the transaction */
380 rc = unqlitePagerRollback(pStore->pPager,FALSE);
381 }
382 }else{
383 /* Rollback any outstanding transaction */
384 rc = unqlitePagerRollback(pStore->pPager,FALSE);
385 }
386 /* Close the pager */
387 unqlitePagerClose(pStore->pPager);
388 /* Release any active VM's */
389 pVm = pDb->pVms;
390 for(;;){
391 if( pDb->iVm < 1 ){
392 break;
393 }
394 /* Point to the next entry */
395 pNext = pVm->pNext;
396 unqliteVmRelease(pVm);
397 pVm = pNext;
398 pDb->iVm--;
399 }
400 /* Release the Jx9 handle */
401 jx9_release(pStore->pJx9);
402 /* Set a dummy magic number */
403 pDb->nMagic = 0x7250;
404 /* Release the whole memory subsystem */
405 SyMemBackendRelease(&pDb->sMem);
406 /* Commit or rollback result */
407 return rc;
408}
409/*
410 * Release all resources consumed by the library.
411 * Note: This call is not thread safe. Refer to [unqlite_lib_shutdown()].
412 */
413static void unqliteCoreShutdown(void)
414{
415 unqlite *pDb, *pNext;
416 /* Release all active databases handles */
417 pDb = sUnqlMPGlobal.pDB;
418 for(;;){
419 if( sUnqlMPGlobal.nDB < 1 ){
420 break;
421 }
422 pNext = pDb->pNext;
423 unqliteDbRelease(pDb);
424 pDb = pNext;
425 sUnqlMPGlobal.nDB--;
426 }
427 /* Release the storage methods container */
428 SySetRelease(&sUnqlMPGlobal.kv_storage);
429#if defined(UNQLITE_ENABLE_THREADS)
430 /* Release the mutex subsystem */
431 if( sUnqlMPGlobal.pMutexMethods ){
432 if( sUnqlMPGlobal.pMutex ){
433 SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex);
434 sUnqlMPGlobal.pMutex = 0;
435 }
436 if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){
437 sUnqlMPGlobal.pMutexMethods->xGlobalRelease();
438 }
439 sUnqlMPGlobal.pMutexMethods = 0;
440 }
441 sUnqlMPGlobal.nThreadingLevel = 0;
442#endif
443 if( sUnqlMPGlobal.sAllocator.pMethods ){
444 /* Release the memory backend */
445 SyMemBackendRelease(&sUnqlMPGlobal.sAllocator);
446 }
447 sUnqlMPGlobal.nMagic = 0x1764;
448 /* Finally, shutdown the Jx9 library */
449 jx9_lib_shutdown();
450}
451/*
452 * [CAPIREF: unqlite_lib_init()]
453 * Please refer to the official documentation for function purpose and expected parameters.
454 */
455int unqlite_lib_init(void)
456{
457 int rc;
458 rc = unqliteCoreInitialize();
459 return rc;
460}
461/*
462 * [CAPIREF: unqlite_lib_shutdown()]
463 * Please refer to the official documentation for function purpose and expected parameters.
464 */
465int unqlite_lib_shutdown(void)
466{
467 if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){
468 /* Already shut */
469 return UNQLITE_OK;
470 }
471 unqliteCoreShutdown();
472 return UNQLITE_OK;
473}
474/*
475 * [CAPIREF: unqlite_lib_is_threadsafe()]
476 * Please refer to the official documentation for function purpose and expected parameters.
477 */
478int unqlite_lib_is_threadsafe(void)
479{
480 if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){
481 return 0;
482 }
483#if defined(UNQLITE_ENABLE_THREADS)
484 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){
485 /* Muli-threading support is enabled */
486 return 1;
487 }else{
488 /* Single-threading */
489 return 0;
490 }
491#else
492 return 0;
493#endif
494}
495/*
496 *
497 * [CAPIREF: unqlite_lib_version()]
498 * Please refer to the official documentation for function purpose and expected parameters.
499 */
500const char * unqlite_lib_version(void)
501{
502 return UNQLITE_VERSION;
503}
504/*
505 *
506 * [CAPIREF: unqlite_lib_signature()]
507 * Please refer to the official documentation for function purpose and expected parameters.
508 */
509const char * unqlite_lib_signature(void)
510{
511 return UNQLITE_SIG;
512}
513/*
514 *
515 * [CAPIREF: unqlite_lib_ident()]
516 * Please refer to the official documentation for function purpose and expected parameters.
517 */
518const char * unqlite_lib_ident(void)
519{
520 return UNQLITE_IDENT;
521}
522/*
523 *
524 * [CAPIREF: unqlite_lib_copyright()]
525 * Please refer to the official documentation for function purpose and expected parameters.
526 */
527const char * unqlite_lib_copyright(void)
528{
529 return UNQLITE_COPYRIGHT;
530}
531/*
532 * Remove harmfull and/or stale flags passed to the [unqlite_open()] interface.
533 */
534static unsigned int unqliteSanityzeFlag(unsigned int iFlags)
535{
536 iFlags &= ~UNQLITE_OPEN_EXCLUSIVE; /* Reserved flag */
537 if( iFlags & UNQLITE_OPEN_TEMP_DB ){
538 /* Omit journaling for temporary database */
539 iFlags |= UNQLITE_OPEN_OMIT_JOURNALING|UNQLITE_OPEN_CREATE;
540 }
541 if( (iFlags & (UNQLITE_OPEN_READONLY|UNQLITE_OPEN_READWRITE)) == 0 ){
542 /* Auto-append the R+W flag */
543 iFlags |= UNQLITE_OPEN_READWRITE;
544 }
545 if( iFlags & UNQLITE_OPEN_CREATE ){
546 iFlags &= ~(UNQLITE_OPEN_MMAP|UNQLITE_OPEN_READONLY);
547 /* Auto-append the R+W flag */
548 iFlags |= UNQLITE_OPEN_READWRITE;
549 }else{
550 if( iFlags & UNQLITE_OPEN_READONLY ){
551 iFlags &= ~UNQLITE_OPEN_READWRITE;
552 }else if( iFlags & UNQLITE_OPEN_READWRITE ){
553 iFlags &= ~UNQLITE_OPEN_MMAP;
554 }
555 }
556 return iFlags;
557}
558/*
559 * This routine does the work of initializing a database handle on behalf
560 * of [unqlite_open()].
561 */
562static int unqliteInitDatabase(
563 unqlite *pDB, /* Database handle */
564 SyMemBackend *pParent, /* Master memory backend */
565 const char *zFilename, /* Target database */
566 unsigned int iFlags /* Open flags */
567 )
568{
569 unqlite_db *pStorage = &pDB->sDB;
570 int rc;
571 /* Initialiaze the memory subsystem */
572 SyMemBackendInitFromParent(&pDB->sMem,pParent);
573#if defined(UNQLITE_ENABLE_THREADS)
574 /* No need for internal mutexes */
575 SyMemBackendDisbaleMutexing(&pDB->sMem);
576#endif
577 SyBlobInit(&pDB->sErr,&pDB->sMem);
578 /* Sanityze flags */
579 iFlags = unqliteSanityzeFlag(iFlags);
580 /* Init the pager and the transaction manager */
581 rc = unqlitePagerOpen(sUnqlMPGlobal.pVfs,pDB,zFilename,iFlags);
582 if( rc != UNQLITE_OK ){
583 return rc;
584 }
585 /* Allocate a new Jx9 engine handle */
586 rc = jx9_init(&pStorage->pJx9);
587 if( rc != JX9_OK ){
588 return rc;
589 }
590 return UNQLITE_OK;
591}
592/*
593 * Allocate and initialize a new UnQLite Virtual Mahcine and attach it
594 * to the compiled Jx9 script.
595 */
596static int unqliteInitVm(unqlite *pDb,jx9_vm *pJx9Vm,unqlite_vm **ppOut)
597{
598 unqlite_vm *pVm;
599
600 *ppOut = 0;
601 /* Allocate a new VM instance */
602 pVm = (unqlite_vm *)SyMemBackendPoolAlloc(&pDb->sMem,sizeof(unqlite_vm));
603 if( pVm == 0 ){
604 return UNQLITE_NOMEM;
605 }
606 /* Zero the structure */
607 SyZero(pVm,sizeof(unqlite_vm));
608 /* Initialize */
609 SyMemBackendInitFromParent(&pVm->sAlloc,&pDb->sMem);
610 /* Allocate a new collection table */
611 pVm->apCol = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc,32 * sizeof(unqlite_col *));
612 if( pVm->apCol == 0 ){
613 goto fail;
614 }
615 pVm->iColSize = 32; /* Must be a power of two */
616 /* Zero the table */
617 SyZero((void *)pVm->apCol,pVm->iColSize * sizeof(unqlite_col *));
618#if defined(UNQLITE_ENABLE_THREADS)
619 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){
620 /* Associate a recursive mutex with this instance */
621 pVm->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE);
622 if( pVm->pMutex == 0 ){
623 goto fail;
624 }
625 }
626#endif
627 /* Link the VM to the list of active virtual machines */
628 pVm->pJx9Vm = pJx9Vm;
629 pVm->pDb = pDb;
630 MACRO_LD_PUSH(pDb->pVms,pVm);
631 pDb->iVm++;
632 /* Register Jx9 functions */
633 unqliteRegisterJx9Functions(pVm);
634 /* Set the magic number */
635 pVm->nMagic = JX9_VM_INIT; /* Same magic number as Jx9 */
636 /* All done */
637 *ppOut = pVm;
638 return UNQLITE_OK;
639fail:
640 SyMemBackendRelease(&pVm->sAlloc);
641 SyMemBackendPoolFree(&pDb->sMem,pVm);
642 return UNQLITE_NOMEM;
643}
644/*
645 * Release an active VM.
646 */
647static int unqliteVmRelease(unqlite_vm *pVm)
648{
649 /* Release the Jx9 VM */
650 jx9_vm_release(pVm->pJx9Vm);
651 /* Release the private memory backend */
652 SyMemBackendRelease(&pVm->sAlloc);
653 /* Upper layer will discard this VM from the list
654 * of active VM.
655 */
656 return UNQLITE_OK;
657}
658/*
659 * Return the default page size.
660 */
661UNQLITE_PRIVATE int unqliteGetPageSize(void)
662{
663 int iSize = sUnqlMPGlobal.iPageSize;
664 if( iSize < UNQLITE_MIN_PAGE_SIZE || iSize > UNQLITE_MAX_PAGE_SIZE ){
665 iSize = UNQLITE_DEFAULT_PAGE_SIZE;
666 }
667 return iSize;
668}
669/*
670 * Generate an error message.
671 */
672UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr)
673{
674 int rc;
675 /* Append the error message */
676 rc = SyBlobAppend(&pDb->sErr,(const void *)zErr,SyStrlen(zErr));
677 /* Append a new line */
678 SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char));
679 return rc;
680}
681/*
682 * Generate an error message (Printf like).
683 */
684UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...)
685{
686 va_list ap;
687 int rc;
688 va_start(ap,zFmt);
689 rc = SyBlobFormatAp(&pDb->sErr,zFmt,ap);
690 va_end(ap);
691 /* Append a new line */
692 SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char));
693 return rc;
694}
695/*
696 * Generate an error message (Out of memory).
697 */
698UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb)
699{
700 int rc;
701 rc = unqliteGenError(pDb,"unQLite is running out of memory");
702 return rc;
703}
704/*
705 * Configure a working UnQLite database handle.
706 */
707static int unqliteConfigure(unqlite *pDb,int nOp,va_list ap)
708{
709 int rc = UNQLITE_OK;
710 switch(nOp){
711 case UNQLITE_CONFIG_JX9_ERR_LOG:
712 /* Jx9 compile-time error log */
713 rc = jx9EngineConfig(pDb->sDB.pJx9,JX9_CONFIG_ERR_LOG,ap);
714 break;
715 case UNQLITE_CONFIG_MAX_PAGE_CACHE: {
716 int max_page = va_arg(ap,int);
717 /* Maximum number of page to cache (Simple hint). */
718 rc = unqlitePagerSetCachesize(pDb->sDB.pPager,max_page);
719 break;
720 }
721 case UNQLITE_CONFIG_ERR_LOG: {
722 /* Database error log if any */
723 const char **pzPtr = va_arg(ap, const char **);
724 int *pLen = va_arg(ap, int *);
725 if( pzPtr == 0 ){
726 rc = JX9_CORRUPT;
727 break;
728 }
729 /* NULL terminate the error-log buffer */
730 SyBlobNullAppend(&pDb->sErr);
731 /* Point to the error-log buffer */
732 *pzPtr = (const char *)SyBlobData(&pDb->sErr);
733 if( pLen ){
734 if( SyBlobLength(&pDb->sErr) > 1 /* NULL '\0' terminator */ ){
735 *pLen = (int)SyBlobLength(&pDb->sErr);
736 }else{
737 *pLen = 0;
738 }
739 }
740 break;
741 }
742 case UNQLITE_CONFIG_DISABLE_AUTO_COMMIT:{
743 /* Disable auto-commit */
744 pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT;
745 break;
746 }
747 case UNQLITE_CONFIG_GET_KV_NAME: {
748 /* Name of the underlying KV storage engine */
749 const char **pzPtr = va_arg(ap,const char **);
750 if( pzPtr ){
751 unqlite_kv_engine *pEngine;
752 pEngine = unqlitePagerGetKvEngine(pDb);
753 /* Point to the name */
754 *pzPtr = pEngine->pIo->pMethods->zName;
755 }
756 break;
757 }
758 default:
759 /* Unknown configuration option */
760 rc = UNQLITE_UNKNOWN;
761 break;
762 }
763 return rc;
764}
765/*
766 * Export the global (master) memory allocator to submodules.
767 */
768UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void)
769{
770 return &sUnqlMPGlobal.sAllocator;
771}
772/*
773 * [CAPIREF: unqlite_open()]
774 * Please refer to the official documentation for function purpose and expected parameters.
775 */
776int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode)
777{
778 unqlite *pHandle;
779 int rc;
780#if defined(UNTRUST)
781 if( ppDB == 0 ){
782 return UNQLITE_CORRUPT;
783 }
784#endif
785 *ppDB = 0;
786 /* One-time automatic library initialization */
787 rc = unqliteCoreInitialize();
788 if( rc != UNQLITE_OK ){
789 return rc;
790 }
791 /* Allocate a new database handle */
792 pHandle = (unqlite *)SyMemBackendPoolAlloc(&sUnqlMPGlobal.sAllocator, sizeof(unqlite));
793 if( pHandle == 0 ){
794 return UNQLITE_NOMEM;
795 }
796 /* Zero the structure */
797 SyZero(pHandle,sizeof(unqlite));
798 if( iMode < 1 ){
799 /* Assume a read-only database */
800 iMode = UNQLITE_OPEN_READONLY|UNQLITE_OPEN_MMAP;
801 }
802 /* Init the database */
803 rc = unqliteInitDatabase(pHandle,&sUnqlMPGlobal.sAllocator,zFilename,iMode);
804 if( rc != UNQLITE_OK ){
805 goto Release;
806 }
807#if defined(UNQLITE_ENABLE_THREADS)
808 if( !(iMode & UNQLITE_OPEN_NOMUTEX) && (sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE) ){
809 /* Associate a recursive mutex with this instance */
810 pHandle->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE);
811 if( pHandle->pMutex == 0 ){
812 rc = UNQLITE_NOMEM;
813 goto Release;
814 }
815 }
816#endif
817 /* Link to the list of active DB handles */
818#if defined(UNQLITE_ENABLE_THREADS)
819 /* Enter the global mutex */
820 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
821#endif
822 MACRO_LD_PUSH(sUnqlMPGlobal.pDB,pHandle);
823 sUnqlMPGlobal.nDB++;
824#if defined(UNQLITE_ENABLE_THREADS)
825 /* Leave the global mutex */
826 SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
827#endif
828 /* Set the magic number to identify a valid DB handle */
829 pHandle->nMagic = UNQLITE_DB_MAGIC;
830 /* Make the handle available to the caller */
831 *ppDB = pHandle;
832 return UNQLITE_OK;
833Release:
834 SyMemBackendRelease(&pHandle->sMem);
835 SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pHandle);
836 return rc;
837}
838/*
839 * [CAPIREF: unqlite_config()]
840 * Please refer to the official documentation for function purpose and expected parameters.
841 */
842int unqlite_config(unqlite *pDb,int nConfigOp,...)
843{
844 va_list ap;
845 int rc;
846 if( UNQLITE_DB_MISUSE(pDb) ){
847 return UNQLITE_CORRUPT;
848 }
849#if defined(UNQLITE_ENABLE_THREADS)
850 /* Acquire DB mutex */
851 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
852 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
853 UNQLITE_THRD_DB_RELEASE(pDb) ){
854 return UNQLITE_ABORT; /* Another thread have released this instance */
855 }
856#endif
857 va_start(ap, nConfigOp);
858 rc = unqliteConfigure(&(*pDb),nConfigOp, ap);
859 va_end(ap);
860#if defined(UNQLITE_ENABLE_THREADS)
861 /* Leave DB mutex */
862 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
863#endif
864 return rc;
865}
866/*
867 * [CAPIREF: unqlite_close()]
868 * Please refer to the official documentation for function purpose and expected parameters.
869 */
870int unqlite_close(unqlite *pDb)
871{
872 int rc;
873 if( UNQLITE_DB_MISUSE(pDb) ){
874 return UNQLITE_CORRUPT;
875 }
876#if defined(UNQLITE_ENABLE_THREADS)
877 /* Acquire DB mutex */
878 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
879 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
880 UNQLITE_THRD_DB_RELEASE(pDb) ){
881 return UNQLITE_ABORT; /* Another thread have released this instance */
882 }
883#endif
884 /* Release the database handle */
885 rc = unqliteDbRelease(pDb);
886#if defined(UNQLITE_ENABLE_THREADS)
887 /* Leave DB mutex */
888 SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
889 /* Release DB mutex */
890 SyMutexRelease(sUnqlMPGlobal.pMutexMethods, pDb->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
891#endif
892#if defined(UNQLITE_ENABLE_THREADS)
893 /* Enter the global mutex */
894 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
895#endif
896 /* Unlink from the list of active database handles */
897 MACRO_LD_REMOVE(sUnqlMPGlobal.pDB, pDb);
898 sUnqlMPGlobal.nDB--;
899#if defined(UNQLITE_ENABLE_THREADS)
900 /* Leave the global mutex */
901 SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */
902#endif
903 /* Release the memory chunk allocated to this handle */
904 SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pDb);
905 return rc;
906}
907/*
908 * [CAPIREF: unqlite_compile()]
909 * Please refer to the official documentation for function purpose and expected parameters.
910 */
911int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut)
912{
913 jx9_vm *pVm;
914 int rc;
915 if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){
916 return UNQLITE_CORRUPT;
917 }
918#if defined(UNQLITE_ENABLE_THREADS)
919 /* Acquire DB mutex */
920 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
921 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
922 UNQLITE_THRD_DB_RELEASE(pDb) ){
923 return UNQLITE_ABORT;
924 }
925#endif
926 /* Compile the Jx9 script first */
927 rc = jx9_compile(pDb->sDB.pJx9,zJx9,nByte,&pVm);
928 if( rc == JX9_OK ){
929 /* Allocate a new unqlite VM instance */
930 rc = unqliteInitVm(pDb,pVm,ppOut);
931 if( rc != UNQLITE_OK ){
932 /* Release the Jx9 VM */
933 jx9_vm_release(pVm);
934 }
935 }
936#if defined(UNQLITE_ENABLE_THREADS)
937 /* Leave DB mutex */
938 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
939#endif
940 return rc;
941}
942/*
943 * [CAPIREF: unqlite_compile_file()]
944 * Please refer to the official documentation for function purpose and expected parameters.
945 */
946int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut)
947{
948 jx9_vm *pVm;
949 int rc;
950 if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){
951 return UNQLITE_CORRUPT;
952 }
953#if defined(UNQLITE_ENABLE_THREADS)
954 /* Acquire DB mutex */
955 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
956 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
957 UNQLITE_THRD_DB_RELEASE(pDb) ){
958 return UNQLITE_ABORT;
959 }
960#endif
961 /* Compile the Jx9 script first */
962 rc = jx9_compile_file(pDb->sDB.pJx9,zPath,&pVm);
963 if( rc == JX9_OK ){
964 /* Allocate a new unqlite VM instance */
965 rc = unqliteInitVm(pDb,pVm,ppOut);
966 if( rc != UNQLITE_OK ){
967 /* Release the Jx9 VM */
968 jx9_vm_release(pVm);
969 }
970 }
971#if defined(UNQLITE_ENABLE_THREADS)
972 /* Leave DB mutex */
973 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
974#endif
975 return rc;
976}
977/*
978 * Configure an unqlite virtual machine (Mostly Jx9 VM) instance.
979 */
980static int unqliteVmConfig(unqlite_vm *pVm,sxi32 iOp,va_list ap)
981{
982 int rc;
983 rc = jx9VmConfigure(pVm->pJx9Vm,iOp,ap);
984 return rc;
985}
986/*
987 * [CAPIREF: unqlite_vm_config()]
988 * Please refer to the official documentation for function purpose and expected parameters.
989 */
990int unqlite_vm_config(unqlite_vm *pVm,int iOp,...)
991{
992 va_list ap;
993 int rc;
994 if( UNQLITE_VM_MISUSE(pVm) ){
995 return UNQLITE_CORRUPT;
996 }
997#if defined(UNQLITE_ENABLE_THREADS)
998 /* Acquire VM mutex */
999 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1000 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1001 UNQLITE_THRD_VM_RELEASE(pVm) ){
1002 return UNQLITE_ABORT; /* Another thread have released this instance */
1003 }
1004#endif
1005 va_start(ap,iOp);
1006 rc = unqliteVmConfig(pVm,iOp,ap);
1007 va_end(ap);
1008#if defined(UNQLITE_ENABLE_THREADS)
1009 /* Leave DB mutex */
1010 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1011#endif
1012 return rc;
1013}
1014/*
1015 * [CAPIREF: unqlite_vm_exec()]
1016 * Please refer to the official documentation for function purpose and expected parameters.
1017 */
1018int unqlite_vm_exec(unqlite_vm *pVm)
1019{
1020 int rc;
1021 if( UNQLITE_VM_MISUSE(pVm) ){
1022 return UNQLITE_CORRUPT;
1023 }
1024#if defined(UNQLITE_ENABLE_THREADS)
1025 /* Acquire VM mutex */
1026 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1027 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1028 UNQLITE_THRD_VM_RELEASE(pVm) ){
1029 return UNQLITE_ABORT; /* Another thread have released this instance */
1030 }
1031#endif
1032 /* Execute the Jx9 bytecode program */
1033 rc = jx9VmByteCodeExec(pVm->pJx9Vm);
1034#if defined(UNQLITE_ENABLE_THREADS)
1035 /* Leave DB mutex */
1036 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1037#endif
1038 return rc;
1039}
1040/*
1041 * [CAPIREF: unqlite_vm_release()]
1042 * Please refer to the official documentation for function purpose and expected parameters.
1043 */
1044int unqlite_vm_release(unqlite_vm *pVm)
1045{
1046 int rc;
1047 if( UNQLITE_VM_MISUSE(pVm) ){
1048 return UNQLITE_CORRUPT;
1049 }
1050#if defined(UNQLITE_ENABLE_THREADS)
1051 /* Acquire VM mutex */
1052 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1053 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1054 UNQLITE_THRD_VM_RELEASE(pVm) ){
1055 return UNQLITE_ABORT; /* Another thread have released this instance */
1056 }
1057#endif
1058 /* Release the VM */
1059 rc = unqliteVmRelease(pVm);
1060#if defined(UNQLITE_ENABLE_THREADS)
1061 /* Leave VM mutex */
1062 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1063 /* Release VM mutex */
1064 SyMutexRelease(sUnqlMPGlobal.pMutexMethods,pVm->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1065#endif
1066 if( rc == UNQLITE_OK ){
1067 unqlite *pDb = pVm->pDb;
1068 /* Unlink from the list of active VM's */
1069#if defined(UNQLITE_ENABLE_THREADS)
1070 /* Acquire DB mutex */
1071 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1072 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1073 UNQLITE_THRD_DB_RELEASE(pDb) ){
1074 return UNQLITE_ABORT; /* Another thread have released this instance */
1075 }
1076#endif
1077 MACRO_LD_REMOVE(pDb->pVms, pVm);
1078 pDb->iVm--;
1079 /* Release the memory chunk allocated to this instance */
1080 SyMemBackendPoolFree(&pDb->sMem,pVm);
1081#if defined(UNQLITE_ENABLE_THREADS)
1082 /* Leave DB mutex */
1083 SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1084#endif
1085 }
1086 return rc;
1087}
1088/*
1089 * [CAPIREF: unqlite_vm_reset()]
1090 * Please refer to the official documentation for function purpose and expected parameters.
1091 */
1092int unqlite_vm_reset(unqlite_vm *pVm)
1093{
1094 int rc;
1095 if( UNQLITE_VM_MISUSE(pVm) ){
1096 return UNQLITE_CORRUPT;
1097 }
1098#if defined(UNQLITE_ENABLE_THREADS)
1099 /* Acquire VM mutex */
1100 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1101 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1102 UNQLITE_THRD_VM_RELEASE(pVm) ){
1103 return UNQLITE_ABORT; /* Another thread have released this instance */
1104 }
1105#endif
1106 /* Reset the Jx9 VM */
1107 rc = jx9VmReset(pVm->pJx9Vm);
1108#if defined(UNQLITE_ENABLE_THREADS)
1109 /* Leave DB mutex */
1110 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1111#endif
1112 return rc;
1113}
1114/*
1115 * [CAPIREF: unqlite_vm_dump()]
1116 * Please refer to the official documentation for function purpose and expected parameters.
1117 */
1118int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData)
1119{
1120 int rc;
1121 if( UNQLITE_VM_MISUSE(pVm) ){
1122 return UNQLITE_CORRUPT;
1123 }
1124#if defined(UNQLITE_ENABLE_THREADS)
1125 /* Acquire VM mutex */
1126 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1127 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1128 UNQLITE_THRD_VM_RELEASE(pVm) ){
1129 return UNQLITE_ABORT; /* Another thread have released this instance */
1130 }
1131#endif
1132 /* Dump the Jx9 VM */
1133 rc = jx9VmDump(pVm->pJx9Vm,xConsumer,pUserData);
1134#if defined(UNQLITE_ENABLE_THREADS)
1135 /* Leave DB mutex */
1136 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1137#endif
1138 return rc;
1139}
1140/*
1141 * [CAPIREF: unqlite_vm_extract_variable()]
1142 * Please refer to the official documentation for function purpose and expected parameters.
1143 */
1144unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname)
1145{
1146 unqlite_value *pValue;
1147 SyString sVariable;
1148 if( UNQLITE_VM_MISUSE(pVm) ){
1149 return 0;
1150 }
1151#if defined(UNQLITE_ENABLE_THREADS)
1152 /* Acquire VM mutex */
1153 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1154 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1155 UNQLITE_THRD_VM_RELEASE(pVm) ){
1156 return 0; /* Another thread have released this instance */
1157 }
1158#endif
1159 /* Extract the target variable */
1160 SyStringInitFromBuf(&sVariable,zVarname,SyStrlen(zVarname));
1161 pValue = jx9VmExtractVariable(pVm->pJx9Vm,&sVariable);
1162#if defined(UNQLITE_ENABLE_THREADS)
1163 /* Leave DB mutex */
1164 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1165#endif
1166 return pValue;
1167}
1168/*
1169 * [CAPIREF: unqlite_create_function()]
1170 * Please refer to the official documentation for function purpose and expected parameters.
1171 */
1172int unqlite_create_function(unqlite_vm *pVm, const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData)
1173{
1174 SyString sName;
1175 int rc;
1176 if( UNQLITE_VM_MISUSE(pVm) ){
1177 return UNQLITE_CORRUPT;
1178 }
1179 SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
1180 /* Remove leading and trailing white spaces */
1181 SyStringFullTrim(&sName);
1182 /* Ticket 1433-003: NULL values are not allowed */
1183 if( sName.nByte < 1 || xFunc == 0 ){
1184 return UNQLITE_INVALID;
1185 }
1186#if defined(UNQLITE_ENABLE_THREADS)
1187 /* Acquire VM mutex */
1188 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1189 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1190 UNQLITE_THRD_VM_RELEASE(pVm) ){
1191 return UNQLITE_ABORT; /* Another thread have released this instance */
1192 }
1193#endif
1194 /* Install the foreign function */
1195 rc = jx9VmInstallForeignFunction(pVm->pJx9Vm,&sName,xFunc,pUserData);
1196#if defined(UNQLITE_ENABLE_THREADS)
1197 /* Leave DB mutex */
1198 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1199#endif
1200 return rc;
1201}
1202/*
1203 * [CAPIREF: unqlite_delete_function()]
1204 * Please refer to the official documentation for function purpose and expected parameters.
1205 */
1206int unqlite_delete_function(unqlite_vm *pVm, const char *zName)
1207{
1208 int rc;
1209 if( UNQLITE_VM_MISUSE(pVm) ){
1210 return UNQLITE_CORRUPT;
1211 }
1212#if defined(UNQLITE_ENABLE_THREADS)
1213 /* Acquire VM mutex */
1214 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1215 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1216 UNQLITE_THRD_VM_RELEASE(pVm) ){
1217 return UNQLITE_ABORT; /* Another thread have released this instance */
1218 }
1219#endif
1220 /* Unlink the foreign function */
1221 rc = jx9DeleteFunction(pVm->pJx9Vm,zName);
1222#if defined(UNQLITE_ENABLE_THREADS)
1223 /* Leave DB mutex */
1224 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1225#endif
1226 return rc;
1227}
1228/*
1229 * [CAPIREF: unqlite_create_constant()]
1230 * Please refer to the official documentation for function purpose and expected parameters.
1231 */
1232int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData)
1233{
1234 SyString sName;
1235 int rc;
1236 if( UNQLITE_VM_MISUSE(pVm) ){
1237 return UNQLITE_CORRUPT;
1238 }
1239 SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
1240 /* Remove leading and trailing white spaces */
1241 SyStringFullTrim(&sName);
1242 if( sName.nByte < 1 ){
1243 /* Empty constant name */
1244 return UNQLITE_INVALID;
1245 }
1246 /* TICKET 1433-003: NULL pointer is harmless operation */
1247 if( xExpand == 0 ){
1248 return UNQLITE_INVALID;
1249 }
1250#if defined(UNQLITE_ENABLE_THREADS)
1251 /* Acquire VM mutex */
1252 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1253 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1254 UNQLITE_THRD_VM_RELEASE(pVm) ){
1255 return UNQLITE_ABORT; /* Another thread have released this instance */
1256 }
1257#endif
1258 /* Install the foreign constant */
1259 rc = jx9VmRegisterConstant(pVm->pJx9Vm,&sName,xExpand,pUserData);
1260#if defined(UNQLITE_ENABLE_THREADS)
1261 /* Leave DB mutex */
1262 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1263#endif
1264 return rc;
1265}
1266/*
1267 * [CAPIREF: unqlite_delete_constant()]
1268 * Please refer to the official documentation for function purpose and expected parameters.
1269 */
1270int unqlite_delete_constant(unqlite_vm *pVm, const char *zName)
1271{
1272 int rc;
1273 if( UNQLITE_VM_MISUSE(pVm) ){
1274 return UNQLITE_CORRUPT;
1275 }
1276#if defined(UNQLITE_ENABLE_THREADS)
1277 /* Acquire VM mutex */
1278 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1279 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1280 UNQLITE_THRD_VM_RELEASE(pVm) ){
1281 return UNQLITE_ABORT; /* Another thread have released this instance */
1282 }
1283#endif
1284 /* Unlink the foreign constant */
1285 rc = Jx9DeleteConstant(pVm->pJx9Vm,zName);
1286#if defined(UNQLITE_ENABLE_THREADS)
1287 /* Leave DB mutex */
1288 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1289#endif
1290 return rc;
1291}
1292/*
1293 * [CAPIREF: unqlite_value_int()]
1294 * Please refer to the official documentation for function purpose and expected parameters.
1295 */
1296int unqlite_value_int(unqlite_value *pVal, int iValue)
1297{
1298 return jx9_value_int(pVal,iValue);
1299}
1300/*
1301 * [CAPIREF: unqlite_value_int64()]
1302 * Please refer to the official documentation for function purpose and expected parameters.
1303 */
1304int unqlite_value_int64(unqlite_value *pVal,unqlite_int64 iValue)
1305{
1306 return jx9_value_int64(pVal,iValue);
1307}
1308/*
1309 * [CAPIREF: unqlite_value_bool()]
1310 * Please refer to the official documentation for function purpose and expected parameters.
1311 */
1312int unqlite_value_bool(unqlite_value *pVal, int iBool)
1313{
1314 return jx9_value_bool(pVal,iBool);
1315}
1316/*
1317 * [CAPIREF: unqlite_value_null()]
1318 * Please refer to the official documentation for function purpose and expected parameters.
1319 */
1320int unqlite_value_null(unqlite_value *pVal)
1321{
1322 return jx9_value_null(pVal);
1323}
1324/*
1325 * [CAPIREF: unqlite_value_double()]
1326 * Please refer to the official documentation for function purpose and expected parameters.
1327 */
1328int unqlite_value_double(unqlite_value *pVal, double Value)
1329{
1330 return jx9_value_double(pVal,Value);
1331}
1332/*
1333 * [CAPIREF: unqlite_value_string()]
1334 * Please refer to the official documentation for function purpose and expected parameters.
1335 */
1336int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen)
1337{
1338 return jx9_value_string(pVal,zString,nLen);
1339}
1340/*
1341 * [CAPIREF: unqlite_value_string_format()]
1342 * Please refer to the official documentation for function purpose and expected parameters.
1343 */
1344int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...)
1345{
1346 va_list ap;
1347 int rc;
1348 if((pVal->iFlags & MEMOBJ_STRING) == 0 ){
1349 /* Invalidate any prior representation */
1350 jx9MemObjRelease(pVal);
1351 MemObjSetType(pVal, MEMOBJ_STRING);
1352 }
1353 va_start(ap, zFormat);
1354 rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap);
1355 va_end(ap);
1356 return UNQLITE_OK;
1357}
1358/*
1359 * [CAPIREF: unqlite_value_reset_string_cursor()]
1360 * Please refer to the official documentation for function purpose and expected parameters.
1361 */
1362int unqlite_value_reset_string_cursor(unqlite_value *pVal)
1363{
1364 return jx9_value_reset_string_cursor(pVal);
1365}
1366/*
1367 * [CAPIREF: unqlite_value_resource()]
1368 * Please refer to the official documentation for function purpose and expected parameters.
1369 */
1370int unqlite_value_resource(unqlite_value *pVal,void *pUserData)
1371{
1372 return jx9_value_resource(pVal,pUserData);
1373}
1374/*
1375 * [CAPIREF: unqlite_value_release()]
1376 * Please refer to the official documentation for function purpose and expected parameters.
1377 */
1378int unqlite_value_release(unqlite_value *pVal)
1379{
1380 return jx9_value_release(pVal);
1381}
1382/*
1383 * [CAPIREF: unqlite_value_to_int()]
1384 * Please refer to the official documentation for function purpose and expected parameters.
1385 */
1386int unqlite_value_to_int(unqlite_value *pValue)
1387{
1388 return jx9_value_to_int(pValue);
1389}
1390/*
1391 * [CAPIREF: unqlite_value_to_bool()]
1392 * Please refer to the official documentation for function purpose and expected parameters.
1393 */
1394int unqlite_value_to_bool(unqlite_value *pValue)
1395{
1396 return jx9_value_to_bool(pValue);
1397}
1398/*
1399 * [CAPIREF: unqlite_value_to_int64()]
1400 * Please refer to the official documentation for function purpose and expected parameters.
1401 */
1402unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue)
1403{
1404 return jx9_value_to_int64(pValue);
1405}
1406/*
1407 * [CAPIREF: unqlite_value_to_double()]
1408 * Please refer to the official documentation for function purpose and expected parameters.
1409 */
1410double unqlite_value_to_double(unqlite_value *pValue)
1411{
1412 return jx9_value_to_double(pValue);
1413}
1414/*
1415 * [CAPIREF: unqlite_value_to_string()]
1416 * Please refer to the official documentation for function purpose and expected parameters.
1417 */
1418const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen)
1419{
1420 return jx9_value_to_string(pValue,pLen);
1421}
1422/*
1423 * [CAPIREF: unqlite_value_to_resource()]
1424 * Please refer to the official documentation for function purpose and expected parameters.
1425 */
1426void * unqlite_value_to_resource(unqlite_value *pValue)
1427{
1428 return jx9_value_to_resource(pValue);
1429}
1430/*
1431 * [CAPIREF: unqlite_value_compare()]
1432 * Please refer to the official documentation for function purpose and expected parameters.
1433 */
1434int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict)
1435{
1436 return jx9_value_compare(pLeft,pRight,bStrict);
1437}
1438/*
1439 * [CAPIREF: unqlite_result_int()]
1440 * Please refer to the official documentation for function purpose and expected parameters.
1441 */
1442int unqlite_result_int(unqlite_context *pCtx, int iValue)
1443{
1444 return jx9_result_int(pCtx,iValue);
1445}
1446/*
1447 * [CAPIREF: unqlite_result_int64()]
1448 * Please refer to the official documentation for function purpose and expected parameters.
1449 */
1450int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue)
1451{
1452 return jx9_result_int64(pCtx,iValue);
1453}
1454/*
1455 * [CAPIREF: unqlite_result_bool()]
1456 * Please refer to the official documentation for function purpose and expected parameters.
1457 */
1458int unqlite_result_bool(unqlite_context *pCtx, int iBool)
1459{
1460 return jx9_result_bool(pCtx,iBool);
1461}
1462/*
1463 * [CAPIREF: unqlite_result_double()]
1464 * Please refer to the official documentation for function purpose and expected parameters.
1465 */
1466int unqlite_result_double(unqlite_context *pCtx, double Value)
1467{
1468 return jx9_result_double(pCtx,Value);
1469}
1470/*
1471 * [CAPIREF: unqlite_result_null()]
1472 * Please refer to the official documentation for function purpose and expected parameters.
1473 */
1474int unqlite_result_null(unqlite_context *pCtx)
1475{
1476 return jx9_result_null(pCtx);
1477}
1478/*
1479 * [CAPIREF: unqlite_result_string()]
1480 * Please refer to the official documentation for function purpose and expected parameters.
1481 */
1482int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen)
1483{
1484 return jx9_result_string(pCtx,zString,nLen);
1485}
1486/*
1487 * [CAPIREF: unqlite_result_string_format()]
1488 * Please refer to the official documentation for function purpose and expected parameters.
1489 */
1490int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...)
1491{
1492 jx9_value *p;
1493 va_list ap;
1494 int rc;
1495 p = pCtx->pRet;
1496 if( (p->iFlags & MEMOBJ_STRING) == 0 ){
1497 /* Invalidate any prior representation */
1498 jx9MemObjRelease(p);
1499 MemObjSetType(p, MEMOBJ_STRING);
1500 }
1501 /* Format the given string */
1502 va_start(ap, zFormat);
1503 rc = SyBlobFormatAp(&p->sBlob, zFormat, ap);
1504 va_end(ap);
1505 return rc;
1506}
1507/*
1508 * [CAPIREF: unqlite_result_value()]
1509 * Please refer to the official documentation for function purpose and expected parameters.
1510 */
1511int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue)
1512{
1513 return jx9_result_value(pCtx,pValue);
1514}
1515/*
1516 * [CAPIREF: unqlite_result_resource()]
1517 * Please refer to the official documentation for function purpose and expected parameters.
1518 */
1519int unqlite_result_resource(unqlite_context *pCtx, void *pUserData)
1520{
1521 return jx9_result_resource(pCtx,pUserData);
1522}
1523/*
1524 * [CAPIREF: unqlite_value_is_int()]
1525 * Please refer to the official documentation for function purpose and expected parameters.
1526 */
1527int unqlite_value_is_int(unqlite_value *pVal)
1528{
1529 return jx9_value_is_int(pVal);
1530}
1531/*
1532 * [CAPIREF: unqlite_value_is_float()]
1533 * Please refer to the official documentation for function purpose and expected parameters.
1534 */
1535int unqlite_value_is_float(unqlite_value *pVal)
1536{
1537 return jx9_value_is_float(pVal);
1538}
1539/*
1540 * [CAPIREF: unqlite_value_is_bool()]
1541 * Please refer to the official documentation for function purpose and expected parameters.
1542 */
1543int unqlite_value_is_bool(unqlite_value *pVal)
1544{
1545 return jx9_value_is_bool(pVal);
1546}
1547/*
1548 * [CAPIREF: unqlite_value_is_string()]
1549 * Please refer to the official documentation for function purpose and expected parameters.
1550 */
1551int unqlite_value_is_string(unqlite_value *pVal)
1552{
1553 return jx9_value_is_string(pVal);
1554}
1555/*
1556 * [CAPIREF: unqlite_value_is_null()]
1557 * Please refer to the official documentation for function purpose and expected parameters.
1558 */
1559int unqlite_value_is_null(unqlite_value *pVal)
1560{
1561 return jx9_value_is_null(pVal);
1562}
1563/*
1564 * [CAPIREF: unqlite_value_is_numeric()]
1565 * Please refer to the official documentation for function purpose and expected parameters.
1566 */
1567int unqlite_value_is_numeric(unqlite_value *pVal)
1568{
1569 return jx9_value_is_numeric(pVal);
1570}
1571/*
1572 * [CAPIREF: unqlite_value_is_callable()]
1573 * Please refer to the official documentation for function purpose and expected parameters.
1574 */
1575int unqlite_value_is_callable(unqlite_value *pVal)
1576{
1577 return jx9_value_is_callable(pVal);
1578}
1579/*
1580 * [CAPIREF: unqlite_value_is_scalar()]
1581 * Please refer to the official documentation for function purpose and expected parameters.
1582 */
1583int unqlite_value_is_scalar(unqlite_value *pVal)
1584{
1585 return jx9_value_is_scalar(pVal);
1586}
1587/*
1588 * [CAPIREF: unqlite_value_is_json_array()]
1589 * Please refer to the official documentation for function purpose and expected parameters.
1590 */
1591int unqlite_value_is_json_array(unqlite_value *pVal)
1592{
1593 return jx9_value_is_json_array(pVal);
1594}
1595/*
1596 * [CAPIREF: unqlite_value_is_json_object()]
1597 * Please refer to the official documentation for function purpose and expected parameters.
1598 */
1599int unqlite_value_is_json_object(unqlite_value *pVal)
1600{
1601 return jx9_value_is_json_object(pVal);
1602}
1603/*
1604 * [CAPIREF: unqlite_value_is_resource()]
1605 * Please refer to the official documentation for function purpose and expected parameters.
1606 */
1607int unqlite_value_is_resource(unqlite_value *pVal)
1608{
1609 return jx9_value_is_resource(pVal);
1610}
1611/*
1612 * [CAPIREF: unqlite_value_is_empty()]
1613 * Please refer to the official documentation for function purpose and expected parameters.
1614 */
1615int unqlite_value_is_empty(unqlite_value *pVal)
1616{
1617 return jx9_value_is_empty(pVal);
1618}
1619/*
1620 * [CAPIREF: unqlite_array_fetch()]
1621 * Please refer to the official documentation for function purpose and expected parameters.
1622 */
1623unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte)
1624{
1625 return jx9_array_fetch(pArray,zKey,nByte);
1626}
1627/*
1628 * [CAPIREF: unqlite_array_walk()]
1629 * Please refer to the official documentation for function purpose and expected parameters.
1630 */
1631int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData)
1632{
1633 return jx9_array_walk(pArray,xWalk,pUserData);
1634}
1635/*
1636 * [CAPIREF: unqlite_array_add_elem()]
1637 * Please refer to the official documentation for function purpose and expected parameters.
1638 */
1639int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue)
1640{
1641 return jx9_array_add_elem(pArray,pKey,pValue);
1642}
1643/*
1644 * [CAPIREF: unqlite_array_add_strkey_elem()]
1645 * Please refer to the official documentation for function purpose and expected parameters.
1646 */
1647int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue)
1648{
1649 return jx9_array_add_strkey_elem(pArray,zKey,pValue);
1650}
1651/*
1652 * [CAPIREF: unqlite_array_count()]
1653 * Please refer to the official documentation for function purpose and expected parameters.
1654 */
1655int unqlite_array_count(unqlite_value *pArray)
1656{
1657 return (int)jx9_array_count(pArray);
1658}
1659/*
1660 * [CAPIREF: unqlite_vm_new_scalar()]
1661 * Please refer to the official documentation for function purpose and expected parameters.
1662 */
1663unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm)
1664{
1665 unqlite_value *pValue;
1666 if( UNQLITE_VM_MISUSE(pVm) ){
1667 return 0;
1668 }
1669#if defined(UNQLITE_ENABLE_THREADS)
1670 /* Acquire VM mutex */
1671 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1672 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1673 UNQLITE_THRD_VM_RELEASE(pVm) ){
1674 return 0; /* Another thread have released this instance */
1675 }
1676#endif
1677 pValue = jx9_new_scalar(pVm->pJx9Vm);
1678#if defined(UNQLITE_ENABLE_THREADS)
1679 /* Leave DB mutex */
1680 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1681#endif
1682 return pValue;
1683}
1684/*
1685 * [CAPIREF: unqlite_vm_new_array()]
1686 * Please refer to the official documentation for function purpose and expected parameters.
1687 */
1688unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm)
1689{
1690 unqlite_value *pValue;
1691 if( UNQLITE_VM_MISUSE(pVm) ){
1692 return 0;
1693 }
1694#if defined(UNQLITE_ENABLE_THREADS)
1695 /* Acquire VM mutex */
1696 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1697 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1698 UNQLITE_THRD_VM_RELEASE(pVm) ){
1699 return 0; /* Another thread have released this instance */
1700 }
1701#endif
1702 pValue = jx9_new_array(pVm->pJx9Vm);
1703#if defined(UNQLITE_ENABLE_THREADS)
1704 /* Leave DB mutex */
1705 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1706#endif
1707 return pValue;
1708}
1709/*
1710 * [CAPIREF: unqlite_vm_release_value()]
1711 * Please refer to the official documentation for function purpose and expected parameters.
1712 */
1713int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue)
1714{
1715 int rc;
1716 if( UNQLITE_VM_MISUSE(pVm) ){
1717 return UNQLITE_CORRUPT;
1718 }
1719#if defined(UNQLITE_ENABLE_THREADS)
1720 /* Acquire VM mutex */
1721 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1722 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1723 UNQLITE_THRD_VM_RELEASE(pVm) ){
1724 return UNQLITE_ABORT; /* Another thread have released this instance */
1725 }
1726#endif
1727 rc = jx9_release_value(pVm->pJx9Vm,pValue);
1728#if defined(UNQLITE_ENABLE_THREADS)
1729 /* Leave DB mutex */
1730 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1731#endif
1732 return rc;
1733}
1734/*
1735 * [CAPIREF: unqlite_context_output()]
1736 * Please refer to the official documentation for function purpose and expected parameters.
1737 */
1738int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen)
1739{
1740 return jx9_context_output(pCtx,zString,nLen);
1741}
1742/*
1743 * [CAPIREF: unqlite_context_output_format()]
1744 * Please refer to the official documentation for function purpose and expected parameters.
1745 */
1746int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...)
1747{
1748 va_list ap;
1749 int rc;
1750 va_start(ap, zFormat);
1751 rc = jx9VmOutputConsumeAp(pCtx->pVm,zFormat, ap);
1752 va_end(ap);
1753 return rc;
1754}
1755/*
1756 * [CAPIREF: unqlite_context_throw_error()]
1757 * Please refer to the official documentation for function purpose and expected parameters.
1758 */
1759int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr)
1760{
1761 return jx9_context_throw_error(pCtx,iErr,zErr);
1762}
1763/*
1764 * [CAPIREF: unqlite_context_throw_error_format()]
1765 * Please refer to the official documentation for function purpose and expected parameters.
1766 */
1767int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...)
1768{
1769 va_list ap;
1770 int rc;
1771 if( zFormat == 0){
1772 return JX9_OK;
1773 }
1774 va_start(ap, zFormat);
1775 rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap);
1776 va_end(ap);
1777 return rc;
1778}
1779/*
1780 * [CAPIREF: unqlite_context_random_num()]
1781 * Please refer to the official documentation for function purpose and expected parameters.
1782 */
1783unsigned int unqlite_context_random_num(unqlite_context *pCtx)
1784{
1785 return jx9_context_random_num(pCtx);
1786}
1787/*
1788 * [CAPIREF: unqlite_context_random_string()]
1789 * Please refer to the official documentation for function purpose and expected parameters.
1790 */
1791int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen)
1792{
1793 return jx9_context_random_string(pCtx,zBuf,nBuflen);
1794}
1795/*
1796 * [CAPIREF: unqlite_context_user_data()]
1797 * Please refer to the official documentation for function purpose and expected parameters.
1798 */
1799void * unqlite_context_user_data(unqlite_context *pCtx)
1800{
1801 return jx9_context_user_data(pCtx);
1802}
1803/*
1804 * [CAPIREF: unqlite_context_push_aux_data()]
1805 * Please refer to the official documentation for function purpose and expected parameters.
1806 */
1807int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData)
1808{
1809 return jx9_context_push_aux_data(pCtx,pUserData);
1810}
1811/*
1812 * [CAPIREF: unqlite_context_peek_aux_data()]
1813 * Please refer to the official documentation for function purpose and expected parameters.
1814 */
1815void * unqlite_context_peek_aux_data(unqlite_context *pCtx)
1816{
1817 return jx9_context_peek_aux_data(pCtx);
1818}
1819/*
1820 * [CAPIREF: unqlite_context_pop_aux_data()]
1821 * Please refer to the official documentation for function purpose and expected parameters.
1822 */
1823void * unqlite_context_pop_aux_data(unqlite_context *pCtx)
1824{
1825 return jx9_context_pop_aux_data(pCtx);
1826}
1827/*
1828 * [CAPIREF: unqlite_context_result_buf_length()]
1829 * Please refer to the official documentation for function purpose and expected parameters.
1830 */
1831unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx)
1832{
1833 return jx9_context_result_buf_length(pCtx);
1834}
1835/*
1836 * [CAPIREF: unqlite_function_name()]
1837 * Please refer to the official documentation for function purpose and expected parameters.
1838 */
1839const char * unqlite_function_name(unqlite_context *pCtx)
1840{
1841 return jx9_function_name(pCtx);
1842}
1843/*
1844 * [CAPIREF: unqlite_context_new_scalar()]
1845 * Please refer to the official documentation for function purpose and expected parameters.
1846 */
1847unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx)
1848{
1849 return jx9_context_new_scalar(pCtx);
1850}
1851/*
1852 * [CAPIREF: unqlite_context_new_array()]
1853 * Please refer to the official documentation for function purpose and expected parameters.
1854 */
1855unqlite_value * unqlite_context_new_array(unqlite_context *pCtx)
1856{
1857 return jx9_context_new_array(pCtx);
1858}
1859/*
1860 * [CAPIREF: unqlite_context_release_value()]
1861 * Please refer to the official documentation for function purpose and expected parameters.
1862 */
1863void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue)
1864{
1865 jx9_context_release_value(pCtx,pValue);
1866}
1867/*
1868 * [CAPIREF: unqlite_context_alloc_chunk()]
1869 * Please refer to the official documentation for function purpose and expected parameters.
1870 */
1871void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease)
1872{
1873 return jx9_context_alloc_chunk(pCtx,nByte,ZeroChunk,AutoRelease);
1874}
1875/*
1876 * [CAPIREF: unqlite_context_realloc_chunk()]
1877 * Please refer to the official documentation for function purpose and expected parameters.
1878 */
1879void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte)
1880{
1881 return jx9_context_realloc_chunk(pCtx,pChunk,nByte);
1882}
1883/*
1884 * [CAPIREF: unqlite_context_free_chunk()]
1885 * Please refer to the official documentation for function purpose and expected parameters.
1886 */
1887void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk)
1888{
1889 jx9_context_free_chunk(pCtx,pChunk);
1890}
1891/*
1892 * [CAPIREF: unqlite_kv_store()]
1893 * Please refer to the official documentation for function purpose and expected parameters.
1894 */
1895int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen)
1896{
1897 unqlite_kv_engine *pEngine;
1898 int rc;
1899 if( UNQLITE_DB_MISUSE(pDb) ){
1900 return UNQLITE_CORRUPT;
1901 }
1902#if defined(UNQLITE_ENABLE_THREADS)
1903 /* Acquire DB mutex */
1904 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1905 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1906 UNQLITE_THRD_DB_RELEASE(pDb) ){
1907 return UNQLITE_ABORT; /* Another thread have released this instance */
1908 }
1909#endif
1910 /* Point to the underlying storage engine */
1911 pEngine = unqlitePagerGetKvEngine(pDb);
1912 if( pEngine->pIo->pMethods->xReplace == 0 ){
1913 /* Storage engine does not implement such method */
1914 unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine");
1915 rc = UNQLITE_NOTIMPLEMENTED;
1916 }else{
1917 if( nKeyLen < 0 ){
1918 /* Assume a null terminated string and compute it's length */
1919 nKeyLen = SyStrlen((const char *)pKey);
1920 }
1921 if( !nKeyLen ){
1922 unqliteGenError(pDb,"Empty key");
1923 rc = UNQLITE_EMPTY;
1924 }else{
1925 /* Perform the requested operation */
1926 rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,pData,nDataLen);
1927 }
1928 }
1929#if defined(UNQLITE_ENABLE_THREADS)
1930 /* Leave DB mutex */
1931 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1932#endif
1933 return rc;
1934}
1935/*
1936 * [CAPIREF: unqlite_kv_store_fmt()]
1937 * Please refer to the official documentation for function purpose and expected parameters.
1938 */
1939int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...)
1940{
1941 unqlite_kv_engine *pEngine;
1942 int rc;
1943 if( UNQLITE_DB_MISUSE(pDb) ){
1944 return UNQLITE_CORRUPT;
1945 }
1946#if defined(UNQLITE_ENABLE_THREADS)
1947 /* Acquire DB mutex */
1948 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1949 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
1950 UNQLITE_THRD_DB_RELEASE(pDb) ){
1951 return UNQLITE_ABORT; /* Another thread have released this instance */
1952 }
1953#endif
1954 /* Point to the underlying storage engine */
1955 pEngine = unqlitePagerGetKvEngine(pDb);
1956 if( pEngine->pIo->pMethods->xReplace == 0 ){
1957 /* Storage engine does not implement such method */
1958 unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine");
1959 rc = UNQLITE_NOTIMPLEMENTED;
1960 }else{
1961 if( nKeyLen < 0 ){
1962 /* Assume a null terminated string and compute it's length */
1963 nKeyLen = SyStrlen((const char *)pKey);
1964 }
1965 if( !nKeyLen ){
1966 unqliteGenError(pDb,"Empty key");
1967 rc = UNQLITE_EMPTY;
1968 }else{
1969 SyBlob sWorker; /* Working buffer */
1970 va_list ap;
1971 SyBlobInit(&sWorker,&pDb->sMem);
1972 /* Format the data */
1973 va_start(ap,zFormat);
1974 SyBlobFormatAp(&sWorker,zFormat,ap);
1975 va_end(ap);
1976 /* Perform the requested operation */
1977 rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker));
1978 /* Clean up */
1979 SyBlobRelease(&sWorker);
1980 }
1981 }
1982#if defined(UNQLITE_ENABLE_THREADS)
1983 /* Leave DB mutex */
1984 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
1985#endif
1986 return rc;
1987}
1988/*
1989 * [CAPIREF: unqlite_kv_append()]
1990 * Please refer to the official documentation for function purpose and expected parameters.
1991 */
1992int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen)
1993{
1994 unqlite_kv_engine *pEngine;
1995 int rc;
1996 if( UNQLITE_DB_MISUSE(pDb) ){
1997 return UNQLITE_CORRUPT;
1998 }
1999#if defined(UNQLITE_ENABLE_THREADS)
2000 /* Acquire DB mutex */
2001 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2002 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2003 UNQLITE_THRD_DB_RELEASE(pDb) ){
2004 return UNQLITE_ABORT; /* Another thread have released this instance */
2005 }
2006#endif
2007 /* Point to the underlying storage engine */
2008 pEngine = unqlitePagerGetKvEngine(pDb);
2009 if( pEngine->pIo->pMethods->xAppend == 0 ){
2010 /* Storage engine does not implement such method */
2011 unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine");
2012 rc = UNQLITE_NOTIMPLEMENTED;
2013 }else{
2014 if( nKeyLen < 0 ){
2015 /* Assume a null terminated string and compute it's length */
2016 nKeyLen = SyStrlen((const char *)pKey);
2017 }
2018 if( !nKeyLen ){
2019 unqliteGenError(pDb,"Empty key");
2020 rc = UNQLITE_EMPTY;
2021 }else{
2022 /* Perform the requested operation */
2023 rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,pData,nDataLen);
2024 }
2025 }
2026#if defined(UNQLITE_ENABLE_THREADS)
2027 /* Leave DB mutex */
2028 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2029#endif
2030 return rc;
2031}
2032/*
2033 * [CAPIREF: unqlite_kv_append_fmt()]
2034 * Please refer to the official documentation for function purpose and expected parameters.
2035 */
2036int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...)
2037{
2038 unqlite_kv_engine *pEngine;
2039 int rc;
2040 if( UNQLITE_DB_MISUSE(pDb) ){
2041 return UNQLITE_CORRUPT;
2042 }
2043#if defined(UNQLITE_ENABLE_THREADS)
2044 /* Acquire DB mutex */
2045 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2046 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2047 UNQLITE_THRD_DB_RELEASE(pDb) ){
2048 return UNQLITE_ABORT; /* Another thread have released this instance */
2049 }
2050#endif
2051 /* Point to the underlying storage engine */
2052 pEngine = unqlitePagerGetKvEngine(pDb);
2053 if( pEngine->pIo->pMethods->xAppend == 0 ){
2054 /* Storage engine does not implement such method */
2055 unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine");
2056 rc = UNQLITE_NOTIMPLEMENTED;
2057 }else{
2058 if( nKeyLen < 0 ){
2059 /* Assume a null terminated string and compute it's length */
2060 nKeyLen = SyStrlen((const char *)pKey);
2061 }
2062 if( !nKeyLen ){
2063 unqliteGenError(pDb,"Empty key");
2064 rc = UNQLITE_EMPTY;
2065 }else{
2066 SyBlob sWorker; /* Working buffer */
2067 va_list ap;
2068 SyBlobInit(&sWorker,&pDb->sMem);
2069 /* Format the data */
2070 va_start(ap,zFormat);
2071 SyBlobFormatAp(&sWorker,zFormat,ap);
2072 va_end(ap);
2073 /* Perform the requested operation */
2074 rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker));
2075 /* Clean up */
2076 SyBlobRelease(&sWorker);
2077 }
2078 }
2079#if defined(UNQLITE_ENABLE_THREADS)
2080 /* Leave DB mutex */
2081 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2082#endif
2083 return rc;
2084}
2085/*
2086 * [CAPIREF: unqlite_kv_fetch()]
2087 * Please refer to the official documentation for function purpose and expected parameters.
2088 */
2089int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 *pBufLen)
2090{
2091 unqlite_kv_methods *pMethods;
2092 unqlite_kv_engine *pEngine;
2093 unqlite_kv_cursor *pCur;
2094 int rc;
2095 if( UNQLITE_DB_MISUSE(pDb) ){
2096 return UNQLITE_CORRUPT;
2097 }
2098#if defined(UNQLITE_ENABLE_THREADS)
2099 /* Acquire DB mutex */
2100 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2101 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2102 UNQLITE_THRD_DB_RELEASE(pDb) ){
2103 return UNQLITE_ABORT; /* Another thread have released this instance */
2104 }
2105#endif
2106 /* Point to the underlying storage engine */
2107 pEngine = unqlitePagerGetKvEngine(pDb);
2108 pMethods = pEngine->pIo->pMethods;
2109 pCur = pDb->sDB.pCursor;
2110 if( nKeyLen < 0 ){
2111 /* Assume a null terminated string and compute it's length */
2112 nKeyLen = SyStrlen((const char *)pKey);
2113 }
2114 if( !nKeyLen ){
2115 unqliteGenError(pDb,"Empty key");
2116 rc = UNQLITE_EMPTY;
2117 }else{
2118 /* Seek to the record position */
2119 rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT);
2120 }
2121 if( rc == UNQLITE_OK ){
2122 if( pBuf == 0 ){
2123 /* Data length only */
2124 rc = pMethods->xDataLength(pCur,pBufLen);
2125 }else{
2126 SyBlob sBlob;
2127 /* Initialize the data consumer */
2128 SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)*pBufLen);
2129 /* Consume the data */
2130 rc = pMethods->xData(pCur,unqliteDataConsumer,&sBlob);
2131 /* Data length */
2132 *pBufLen = (unqlite_int64)SyBlobLength(&sBlob);
2133 /* Cleanup */
2134 SyBlobRelease(&sBlob);
2135 }
2136 }
2137#if defined(UNQLITE_ENABLE_THREADS)
2138 /* Leave DB mutex */
2139 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2140#endif
2141 return rc;
2142}
2143/*
2144 * [CAPIREF: unqlite_kv_fetch_callback()]
2145 * Please refer to the official documentation for function purpose and expected parameters.
2146 */
2147int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey,int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
2148{
2149 unqlite_kv_methods *pMethods;
2150 unqlite_kv_engine *pEngine;
2151 unqlite_kv_cursor *pCur;
2152 int rc;
2153 if( UNQLITE_DB_MISUSE(pDb) ){
2154 return UNQLITE_CORRUPT;
2155 }
2156#if defined(UNQLITE_ENABLE_THREADS)
2157 /* Acquire DB mutex */
2158 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2159 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2160 UNQLITE_THRD_DB_RELEASE(pDb) ){
2161 return UNQLITE_ABORT; /* Another thread have released this instance */
2162 }
2163#endif
2164 /* Point to the underlying storage engine */
2165 pEngine = unqlitePagerGetKvEngine(pDb);
2166 pMethods = pEngine->pIo->pMethods;
2167 pCur = pDb->sDB.pCursor;
2168 if( nKeyLen < 0 ){
2169 /* Assume a null terminated string and compute it's length */
2170 nKeyLen = SyStrlen((const char *)pKey);
2171 }
2172 if( !nKeyLen ){
2173 unqliteGenError(pDb,"Empty key");
2174 rc = UNQLITE_EMPTY;
2175 }else{
2176 /* Seek to the record position */
2177 rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT);
2178 }
2179 if( rc == UNQLITE_OK && xConsumer ){
2180 /* Consume the data directly */
2181 rc = pMethods->xData(pCur,xConsumer,pUserData);
2182 }
2183#if defined(UNQLITE_ENABLE_THREADS)
2184 /* Leave DB mutex */
2185 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2186#endif
2187 return rc;
2188}
2189/*
2190 * [CAPIREF: unqlite_kv_delete()]
2191 * Please refer to the official documentation for function purpose and expected parameters.
2192 */
2193int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen)
2194{
2195 unqlite_kv_methods *pMethods;
2196 unqlite_kv_engine *pEngine;
2197 unqlite_kv_cursor *pCur;
2198 int rc;
2199 if( UNQLITE_DB_MISUSE(pDb) ){
2200 return UNQLITE_CORRUPT;
2201 }
2202#if defined(UNQLITE_ENABLE_THREADS)
2203 /* Acquire DB mutex */
2204 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2205 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2206 UNQLITE_THRD_DB_RELEASE(pDb) ){
2207 return UNQLITE_ABORT; /* Another thread have released this instance */
2208 }
2209#endif
2210 /* Point to the underlying storage engine */
2211 pEngine = unqlitePagerGetKvEngine(pDb);
2212 pMethods = pEngine->pIo->pMethods;
2213 pCur = pDb->sDB.pCursor;
2214 if( pMethods->xDelete == 0 ){
2215 /* Storage engine does not implement such method */
2216 unqliteGenError(pDb,"xDelete() method not implemented in the underlying storage engine");
2217 rc = UNQLITE_NOTIMPLEMENTED;
2218 }else{
2219 if( nKeyLen < 0 ){
2220 /* Assume a null terminated string and compute it's length */
2221 nKeyLen = SyStrlen((const char *)pKey);
2222 }
2223 if( !nKeyLen ){
2224 unqliteGenError(pDb,"Empty key");
2225 rc = UNQLITE_EMPTY;
2226 }else{
2227 /* Seek to the record position */
2228 rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT);
2229 }
2230 if( rc == UNQLITE_OK ){
2231 /* Exact match found, delete the entry */
2232 rc = pMethods->xDelete(pCur);
2233 }
2234 }
2235#if defined(UNQLITE_ENABLE_THREADS)
2236 /* Leave DB mutex */
2237 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2238#endif
2239 return rc;
2240}
2241/*
2242 * [CAPIREF: unqlite_kv_config()]
2243 * Please refer to the official documentation for function purpose and expected parameters.
2244 */
2245int unqlite_kv_config(unqlite *pDb,int iOp,...)
2246{
2247 unqlite_kv_engine *pEngine;
2248 int rc;
2249 if( UNQLITE_DB_MISUSE(pDb) ){
2250 return UNQLITE_CORRUPT;
2251 }
2252#if defined(UNQLITE_ENABLE_THREADS)
2253 /* Acquire DB mutex */
2254 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2255 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2256 UNQLITE_THRD_DB_RELEASE(pDb) ){
2257 return UNQLITE_ABORT; /* Another thread have released this instance */
2258 }
2259#endif
2260 /* Point to the underlying storage engine */
2261 pEngine = unqlitePagerGetKvEngine(pDb);
2262 if( pEngine->pIo->pMethods->xConfig == 0 ){
2263 /* Storage engine does not implements such method */
2264 unqliteGenError(pDb,"xConfig() method not implemented in the underlying storage engine");
2265 rc = UNQLITE_NOTIMPLEMENTED;
2266 }else{
2267 va_list ap;
2268 /* Configure the storage engine */
2269 va_start(ap,iOp);
2270 rc = pEngine->pIo->pMethods->xConfig(pEngine,iOp,ap);
2271 va_end(ap);
2272 }
2273#if defined(UNQLITE_ENABLE_THREADS)
2274 /* Leave DB mutex */
2275 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2276#endif
2277 return rc;
2278}
2279/*
2280 * [CAPIREF: unqlite_kv_cursor_init()]
2281 * Please refer to the official documentation for function purpose and expected parameters.
2282 */
2283int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut)
2284{
2285 int rc;
2286 if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0 /* Noop */){
2287 return UNQLITE_CORRUPT;
2288 }
2289#if defined(UNQLITE_ENABLE_THREADS)
2290 /* Acquire DB mutex */
2291 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2292 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2293 UNQLITE_THRD_DB_RELEASE(pDb) ){
2294 return UNQLITE_ABORT; /* Another thread have released this instance */
2295 }
2296#endif
2297 /* Allocate a new cursor */
2298 rc = unqliteInitCursor(pDb,ppOut);
2299#if defined(UNQLITE_ENABLE_THREADS)
2300 /* Leave DB mutex */
2301 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2302#endif
2303 return rc;
2304}
2305/*
2306 * [CAPIREF: unqlite_kv_cursor_release()]
2307 * Please refer to the official documentation for function purpose and expected parameters.
2308 */
2309int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur)
2310{
2311 int rc;
2312 if( UNQLITE_DB_MISUSE(pDb) || pCur == 0 /* Noop */){
2313 return UNQLITE_CORRUPT;
2314 }
2315#if defined(UNQLITE_ENABLE_THREADS)
2316 /* Acquire DB mutex */
2317 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2318 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2319 UNQLITE_THRD_DB_RELEASE(pDb) ){
2320 return UNQLITE_ABORT; /* Another thread have released this instance */
2321 }
2322#endif
2323 /* Release the cursor */
2324 rc = unqliteReleaseCursor(pDb,pCur);
2325#if defined(UNQLITE_ENABLE_THREADS)
2326 /* Leave DB mutex */
2327 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2328#endif
2329 return rc;
2330}
2331/*
2332 * [CAPIREF: unqlite_kv_cursor_first_entry()]
2333 * Please refer to the official documentation for function purpose and expected parameters.
2334 */
2335int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor)
2336{
2337 int rc;
2338#ifdef UNTRUST
2339 if( pCursor == 0 ){
2340 return UNQLITE_CORRUPT;
2341 }
2342#endif
2343 /* Check if the requested method is implemented by the underlying storage engine */
2344 if( pCursor->pStore->pIo->pMethods->xFirst == 0 ){
2345 rc = UNQLITE_NOTIMPLEMENTED;
2346 }else{
2347 /* Seek to the first entry */
2348 rc = pCursor->pStore->pIo->pMethods->xFirst(pCursor);
2349 }
2350 return rc;
2351}
2352/*
2353 * [CAPIREF: unqlite_kv_cursor_last_entry()]
2354 * Please refer to the official documentation for function purpose and expected parameters.
2355 */
2356int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor)
2357{
2358 int rc;
2359#ifdef UNTRUST
2360 if( pCursor == 0 ){
2361 return UNQLITE_CORRUPT;
2362 }
2363#endif
2364 /* Check if the requested method is implemented by the underlying storage engine */
2365 if( pCursor->pStore->pIo->pMethods->xLast == 0 ){
2366 rc = UNQLITE_NOTIMPLEMENTED;
2367 }else{
2368 /* Seek to the last entry */
2369 rc = pCursor->pStore->pIo->pMethods->xLast(pCursor);
2370 }
2371 return rc;
2372}
2373/*
2374 * [CAPIREF: unqlite_kv_cursor_valid_entry()]
2375 * Please refer to the official documentation for function purpose and expected parameters.
2376 */
2377int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor)
2378{
2379 int rc;
2380#ifdef UNTRUST
2381 if( pCursor == 0 ){
2382 return UNQLITE_CORRUPT;
2383 }
2384#endif
2385 /* Check if the requested method is implemented by the underlying storage engine */
2386 if( pCursor->pStore->pIo->pMethods->xValid == 0 ){
2387 rc = UNQLITE_NOTIMPLEMENTED;
2388 }else{
2389 rc = pCursor->pStore->pIo->pMethods->xValid(pCursor);
2390 }
2391 return rc;
2392}
2393/*
2394 * [CAPIREF: unqlite_kv_cursor_next_entry()]
2395 * Please refer to the official documentation for function purpose and expected parameters.
2396 */
2397int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor)
2398{
2399 int rc;
2400#ifdef UNTRUST
2401 if( pCursor == 0 ){
2402 return UNQLITE_CORRUPT;
2403 }
2404#endif
2405 /* Check if the requested method is implemented by the underlying storage engine */
2406 if( pCursor->pStore->pIo->pMethods->xNext == 0 ){
2407 rc = UNQLITE_NOTIMPLEMENTED;
2408 }else{
2409 /* Seek to the next entry */
2410 rc = pCursor->pStore->pIo->pMethods->xNext(pCursor);
2411 }
2412 return rc;
2413}
2414/*
2415 * [CAPIREF: unqlite_kv_cursor_prev_entry()]
2416 * Please refer to the official documentation for function purpose and expected parameters.
2417 */
2418int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor)
2419{
2420 int rc;
2421#ifdef UNTRUST
2422 if( pCursor == 0 ){
2423 return UNQLITE_CORRUPT;
2424 }
2425#endif
2426 /* Check if the requested method is implemented by the underlying storage engine */
2427 if( pCursor->pStore->pIo->pMethods->xPrev == 0 ){
2428 rc = UNQLITE_NOTIMPLEMENTED;
2429 }else{
2430 /* Seek to the previous entry */
2431 rc = pCursor->pStore->pIo->pMethods->xPrev(pCursor);
2432 }
2433 return rc;
2434}
2435/*
2436 * [CAPIREF: unqlite_kv_cursor_delete_entry()]
2437 * Please refer to the official documentation for function purpose and expected parameters.
2438 */
2439int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor)
2440{
2441 int rc;
2442#ifdef UNTRUST
2443 if( pCursor == 0 ){
2444 return UNQLITE_CORRUPT;
2445 }
2446#endif
2447 /* Check if the requested method is implemented by the underlying storage engine */
2448 if( pCursor->pStore->pIo->pMethods->xDelete == 0 ){
2449 rc = UNQLITE_NOTIMPLEMENTED;
2450 }else{
2451 /* Delete the entry */
2452 rc = pCursor->pStore->pIo->pMethods->xDelete(pCursor);
2453 }
2454 return rc;
2455}
2456/*
2457 * [CAPIREF: unqlite_kv_cursor_reset()]
2458 * Please refer to the official documentation for function purpose and expected parameters.
2459 */
2460int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor)
2461{
2462 int rc = UNQLITE_OK;
2463#ifdef UNTRUST
2464 if( pCursor == 0 ){
2465 return UNQLITE_CORRUPT;
2466 }
2467#endif
2468 /* Check if the requested method is implemented by the underlying storage engine */
2469 if( pCursor->pStore->pIo->pMethods->xReset == 0 ){
2470 rc = UNQLITE_NOTIMPLEMENTED;
2471 }else{
2472 /* Reset */
2473 pCursor->pStore->pIo->pMethods->xReset(pCursor);
2474 }
2475 return rc;
2476}
2477/*
2478 * [CAPIREF: unqlite_kv_cursor_seek()]
2479 * Please refer to the official documentation for function purpose and expected parameters.
2480 */
2481int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos)
2482{
2483 int rc = UNQLITE_OK;
2484#ifdef UNTRUST
2485 if( pCursor == 0 ){
2486 return UNQLITE_CORRUPT;
2487 }
2488#endif
2489 if( nKeyLen < 0 ){
2490 /* Assume a null terminated string and compute it's length */
2491 nKeyLen = SyStrlen((const char *)pKey);
2492 }
2493 if( !nKeyLen ){
2494 rc = UNQLITE_EMPTY;
2495 }else{
2496 /* Seek to the desired location */
2497 rc = pCursor->pStore->pIo->pMethods->xSeek(pCursor,pKey,nKeyLen,iPos);
2498 }
2499 return rc;
2500}
2501/*
2502 * Default data consumer callback. That is, all retrieved is redirected to this
2503 * routine which store the output in an internal blob.
2504 */
2505UNQLITE_PRIVATE int unqliteDataConsumer(
2506 const void *pOut, /* Data to consume */
2507 unsigned int nLen, /* Data length */
2508 void *pUserData /* User private data */
2509 )
2510{
2511 sxi32 rc;
2512 /* Store the output in an internal BLOB */
2513 rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen);
2514 return rc;
2515}
2516/*
2517 * [CAPIREF: unqlite_kv_cursor_data_callback()]
2518 * Please refer to the official documentation for function purpose and expected parameters.
2519 */
2520int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
2521{
2522 int rc;
2523#ifdef UNTRUST
2524 if( pCursor == 0 ){
2525 return UNQLITE_CORRUPT;
2526 }
2527#endif
2528 /* Consume the key directly */
2529 rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,xConsumer,pUserData);
2530 return rc;
2531}
2532/*
2533 * [CAPIREF: unqlite_kv_cursor_key()]
2534 * Please refer to the official documentation for function purpose and expected parameters.
2535 */
2536int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte)
2537{
2538 int rc;
2539#ifdef UNTRUST
2540 if( pCursor == 0 ){
2541 return UNQLITE_CORRUPT;
2542 }
2543#endif
2544 if( pBuf == 0 ){
2545 /* Key length only */
2546 rc = pCursor->pStore->pIo->pMethods->xKeyLength(pCursor,pnByte);
2547 }else{
2548 SyBlob sBlob;
2549 if( (*pnByte) < 0 ){
2550 return UNQLITE_CORRUPT;
2551 }
2552 /* Initialize the data consumer */
2553 SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte));
2554 /* Consume the key */
2555 rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,unqliteDataConsumer,&sBlob);
2556 /* Key length */
2557 *pnByte = SyBlobLength(&sBlob);
2558 /* Cleanup */
2559 SyBlobRelease(&sBlob);
2560 }
2561 return rc;
2562}
2563/*
2564 * [CAPIREF: unqlite_kv_cursor_data_callback()]
2565 * Please refer to the official documentation for function purpose and expected parameters.
2566 */
2567int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
2568{
2569 int rc;
2570#ifdef UNTRUST
2571 if( pCursor == 0 ){
2572 return UNQLITE_CORRUPT;
2573 }
2574#endif
2575 /* Consume the data directly */
2576 rc = pCursor->pStore->pIo->pMethods->xData(pCursor,xConsumer,pUserData);
2577 return rc;
2578}
2579/*
2580 * [CAPIREF: unqlite_kv_cursor_data()]
2581 * Please refer to the official documentation for function purpose and expected parameters.
2582 */
2583int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnByte)
2584{
2585 int rc;
2586#ifdef UNTRUST
2587 if( pCursor == 0 ){
2588 return UNQLITE_CORRUPT;
2589 }
2590#endif
2591 if( pBuf == 0 ){
2592 /* Data length only */
2593 rc = pCursor->pStore->pIo->pMethods->xDataLength(pCursor,pnByte);
2594 }else{
2595 SyBlob sBlob;
2596 if( (*pnByte) < 0 ){
2597 return UNQLITE_CORRUPT;
2598 }
2599 /* Initialize the data consumer */
2600 SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte));
2601 /* Consume the data */
2602 rc = pCursor->pStore->pIo->pMethods->xData(pCursor,unqliteDataConsumer,&sBlob);
2603 /* Data length */
2604 *pnByte = SyBlobLength(&sBlob);
2605 /* Cleanup */
2606 SyBlobRelease(&sBlob);
2607 }
2608 return rc;
2609}
2610/*
2611 * [CAPIREF: unqlite_begin()]
2612 * Please refer to the official documentation for function purpose and expected parameters.
2613 */
2614int unqlite_begin(unqlite *pDb)
2615{
2616 int rc;
2617 if( UNQLITE_DB_MISUSE(pDb) ){
2618 return UNQLITE_CORRUPT;
2619 }
2620#if defined(UNQLITE_ENABLE_THREADS)
2621 /* Acquire DB mutex */
2622 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2623 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2624 UNQLITE_THRD_DB_RELEASE(pDb) ){
2625 return UNQLITE_ABORT; /* Another thread have released this instance */
2626 }
2627#endif
2628 /* Begin the write transaction */
2629 rc = unqlitePagerBegin(pDb->sDB.pPager);
2630#if defined(UNQLITE_ENABLE_THREADS)
2631 /* Leave DB mutex */
2632 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2633#endif
2634 return rc;
2635}
2636/*
2637 * [CAPIREF: unqlite_commit()]
2638 * Please refer to the official documentation for function purpose and expected parameters.
2639 */
2640int unqlite_commit(unqlite *pDb)
2641{
2642 int rc;
2643 if( UNQLITE_DB_MISUSE(pDb) ){
2644 return UNQLITE_CORRUPT;
2645 }
2646#if defined(UNQLITE_ENABLE_THREADS)
2647 /* Acquire DB mutex */
2648 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2649 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2650 UNQLITE_THRD_DB_RELEASE(pDb) ){
2651 return UNQLITE_ABORT; /* Another thread have released this instance */
2652 }
2653#endif
2654 /* Commit the transaction */
2655 rc = unqlitePagerCommit(pDb->sDB.pPager);
2656#if defined(UNQLITE_ENABLE_THREADS)
2657 /* Leave DB mutex */
2658 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2659#endif
2660 return rc;
2661}
2662/*
2663 * [CAPIREF: unqlite_rollback()]
2664 * Please refer to the official documentation for function purpose and expected parameters.
2665 */
2666int unqlite_rollback(unqlite *pDb)
2667{
2668 int rc;
2669 if( UNQLITE_DB_MISUSE(pDb) ){
2670 return UNQLITE_CORRUPT;
2671 }
2672#if defined(UNQLITE_ENABLE_THREADS)
2673 /* Acquire DB mutex */
2674 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2675 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2676 UNQLITE_THRD_DB_RELEASE(pDb) ){
2677 return UNQLITE_ABORT; /* Another thread have released this instance */
2678 }
2679#endif
2680 /* Rollback the transaction */
2681 rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE);
2682#if defined(UNQLITE_ENABLE_THREADS)
2683 /* Leave DB mutex */
2684 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2685#endif
2686 return rc;
2687}
2688/*
2689 * [CAPIREF: unqlite_util_load_mmaped_file()]
2690 * Please refer to the official documentation for function purpose and expected parameters.
2691 */
2692UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize)
2693{
2694 const jx9_vfs *pVfs;
2695 int rc;
2696 if( SX_EMPTY_STR(zFile) || ppMap == 0 || pFileSize == 0){
2697 /* Sanity check */
2698 return UNQLITE_CORRUPT;
2699 }
2700 *ppMap = 0;
2701 /* Extract the Jx9 Vfs */
2702 pVfs = jx9ExportBuiltinVfs();
2703 /*
2704 * Check if the underlying vfs implement the memory map routines
2705 * [i.e: mmap() under UNIX/MapViewOfFile() under windows].
2706 */
2707 if( pVfs == 0 || pVfs->xMmap == 0 ){
2708 rc = UNQLITE_NOTIMPLEMENTED;
2709 }else{
2710 /* Try to get a read-only memory view of the whole file */
2711 rc = pVfs->xMmap(zFile,ppMap,pFileSize);
2712 }
2713 return rc;
2714}
2715/*
2716 * [CAPIREF: unqlite_util_release_mmaped_file()]
2717 * Please refer to the official documentation for function purpose and expected parameters.
2718 */
2719UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize)
2720{
2721 const jx9_vfs *pVfs;
2722 int rc = UNQLITE_OK;
2723 if( pMap == 0 ){
2724 return UNQLITE_OK;
2725 }
2726 /* Extract the Jx9 Vfs */
2727 pVfs = jx9ExportBuiltinVfs();
2728 if( pVfs == 0 || pVfs->xUnmap == 0 ){
2729 rc = UNQLITE_NOTIMPLEMENTED;
2730 }else{
2731 pVfs->xUnmap(pMap,iFileSize);
2732 }
2733 return rc;
2734}
2735/*
2736 * [CAPIREF: unqlite_util_random_string()]
2737 * Please refer to the official documentation for function purpose and expected parameters.
2738 */
2739UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size)
2740{
2741 if( UNQLITE_DB_MISUSE(pDb) ){
2742 return UNQLITE_CORRUPT;
2743 }
2744 if( zBuf == 0 || buf_size < 3 ){
2745 /* Buffer must be long enough to hold three bytes */
2746 return UNQLITE_INVALID;
2747 }
2748#if defined(UNQLITE_ENABLE_THREADS)
2749 /* Acquire DB mutex */
2750 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2751 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2752 UNQLITE_THRD_DB_RELEASE(pDb) ){
2753 return UNQLITE_ABORT; /* Another thread have released this instance */
2754 }
2755#endif
2756 /* Generate the random string */
2757 unqlitePagerRandomString(pDb->sDB.pPager,zBuf,buf_size);
2758#if defined(UNQLITE_ENABLE_THREADS)
2759 /* Leave DB mutex */
2760 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2761#endif
2762 return UNQLITE_OK;
2763}
2764/*
2765 * [CAPIREF: unqlite_util_random_num()]
2766 * Please refer to the official documentation for function purpose and expected parameters.
2767 */
2768UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb)
2769{
2770 sxu32 iNum;
2771 if( UNQLITE_DB_MISUSE(pDb) ){
2772 return 0;
2773 }
2774#if defined(UNQLITE_ENABLE_THREADS)
2775 /* Acquire DB mutex */
2776 SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2777 if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE &&
2778 UNQLITE_THRD_DB_RELEASE(pDb) ){
2779 return 0; /* Another thread have released this instance */
2780 }
2781#endif
2782 /* Generate the random number */
2783 iNum = unqlitePagerRandomNum(pDb->sDB.pPager);
2784#if defined(UNQLITE_ENABLE_THREADS)
2785 /* Leave DB mutex */
2786 SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */
2787#endif
2788 return iNum;
2789}
diff --git a/common/unqlite/bitvec.c b/common/unqlite/bitvec.c
new file mode 100644
index 0000000..5b78430
--- /dev/null
+++ b/common/unqlite/bitvec.c
@@ -0,0 +1,222 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: bitvec.c v1.0 Win7 2013-02-27 15:16 stable <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17
18/** This file implements an object that represents a dynmaic
19** bitmap.
20**
21** A bitmap is used to record which pages of a database file have been
22** journalled during a transaction, or which pages have the "dont-write"
23** property. Usually only a few pages are meet either condition.
24** So the bitmap is usually sparse and has low cardinality.
25*/
26/*
27 * Actually, this is not a bitmap but a simple hashtable where page
28 * number (64-bit unsigned integers) are used as the lookup keys.
29 */
30typedef struct bitvec_rec bitvec_rec;
31struct bitvec_rec
32{
33 pgno iPage; /* Page number */
34 bitvec_rec *pNext,*pNextCol; /* Collison link */
35};
36struct Bitvec
37{
38 SyMemBackend *pAlloc; /* Memory allocator */
39 sxu32 nRec; /* Total number of records */
40 sxu32 nSize; /* Table size */
41 bitvec_rec **apRec; /* Record table */
42 bitvec_rec *pList; /* List of records */
43};
44/*
45 * Allocate a new bitvec instance.
46*/
47UNQLITE_PRIVATE Bitvec * unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize)
48{
49 bitvec_rec **apNew;
50 Bitvec *p;
51
52 p = (Bitvec *)SyMemBackendAlloc(pAlloc,sizeof(*p) );
53 if( p == 0 ){
54 SXUNUSED(iSize); /* cc warning */
55 return 0;
56 }
57 /* Zero the structure */
58 SyZero(p,sizeof(Bitvec));
59 /* Allocate a new table */
60 p->nSize = 64; /* Must be a power of two */
61 apNew = (bitvec_rec **)SyMemBackendAlloc(pAlloc,p->nSize * sizeof(bitvec_rec *));
62 if( apNew == 0 ){
63 SyMemBackendFree(pAlloc,p);
64 return 0;
65 }
66 /* Zero the new table */
67 SyZero((void *)apNew,p->nSize * sizeof(bitvec_rec *));
68 /* Fill-in */
69 p->apRec = apNew;
70 p->pAlloc = pAlloc;
71 return p;
72}
73/*
74 * Check if the given page number is already installed in the table.
75 * Return true if installed. False otherwise.
76 */
77UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i)
78{
79 bitvec_rec *pRec;
80 /* Point to the desired bucket */
81 pRec = p->apRec[i & (p->nSize - 1)];
82 for(;;){
83 if( pRec == 0 ){ break; }
84 if( pRec->iPage == i ){
85 /* Page found */
86 return 1;
87 }
88 /* Point to the next entry */
89 pRec = pRec->pNextCol;
90
91 if( pRec == 0 ){ break; }
92 if( pRec->iPage == i ){
93 /* Page found */
94 return 1;
95 }
96 /* Point to the next entry */
97 pRec = pRec->pNextCol;
98
99
100 if( pRec == 0 ){ break; }
101 if( pRec->iPage == i ){
102 /* Page found */
103 return 1;
104 }
105 /* Point to the next entry */
106 pRec = pRec->pNextCol;
107
108
109 if( pRec == 0 ){ break; }
110 if( pRec->iPage == i ){
111 /* Page found */
112 return 1;
113 }
114 /* Point to the next entry */
115 pRec = pRec->pNextCol;
116 }
117 /* No such entry */
118 return 0;
119}
120/*
121 * Install a given page number in our bitmap (Actually, our hashtable).
122 */
123UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i)
124{
125 bitvec_rec *pRec;
126 sxi32 iBuck;
127 /* Allocate a new instance */
128 pRec = (bitvec_rec *)SyMemBackendPoolAlloc(p->pAlloc,sizeof(bitvec_rec));
129 if( pRec == 0 ){
130 return UNQLITE_NOMEM;
131 }
132 /* Zero the structure */
133 SyZero(pRec,sizeof(bitvec_rec));
134 /* Fill-in */
135 pRec->iPage = i;
136 iBuck = i & (p->nSize - 1);
137 pRec->pNextCol = p->apRec[iBuck];
138 p->apRec[iBuck] = pRec;
139 pRec->pNext = p->pList;
140 p->pList = pRec;
141 p->nRec++;
142 if( p->nRec >= (p->nSize * 3) && p->nRec < 100000 ){
143 /* Grow the hashtable */
144 sxu32 nNewSize = p->nSize << 1;
145 bitvec_rec *pEntry,**apNew;
146 sxu32 n;
147 apNew = (bitvec_rec **)SyMemBackendAlloc(p->pAlloc, nNewSize * sizeof(bitvec_rec *));
148 if( apNew ){
149 sxu32 iBucket;
150 /* Zero the new table */
151 SyZero((void *)apNew, nNewSize * sizeof(bitvec_rec *));
152 /* Rehash all entries */
153 n = 0;
154 pEntry = p->pList;
155 for(;;){
156 /* Loop one */
157 if( n >= p->nRec ){
158 break;
159 }
160 pEntry->pNextCol = 0;
161 /* Install in the new bucket */
162 iBucket = pEntry->iPage & (nNewSize - 1);
163 pEntry->pNextCol = apNew[iBucket];
164 apNew[iBucket] = pEntry;
165 /* Point to the next entry */
166 pEntry = pEntry->pNext;
167 n++;
168 }
169 /* Release the old table and reflect the change */
170 SyMemBackendFree(p->pAlloc,(void *)p->apRec);
171 p->apRec = apNew;
172 p->nSize = nNewSize;
173 }
174 }
175 return UNQLITE_OK;
176}
177/*
178 * Destroy a bitvec instance. Reclaim all memory used.
179 */
180UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p)
181{
182 bitvec_rec *pNext,*pRec = p->pList;
183 SyMemBackend *pAlloc = p->pAlloc;
184
185 for(;;){
186 if( p->nRec < 1 ){
187 break;
188 }
189 pNext = pRec->pNext;
190 SyMemBackendPoolFree(pAlloc,(void *)pRec);
191 pRec = pNext;
192 p->nRec--;
193
194 if( p->nRec < 1 ){
195 break;
196 }
197 pNext = pRec->pNext;
198 SyMemBackendPoolFree(pAlloc,(void *)pRec);
199 pRec = pNext;
200 p->nRec--;
201
202
203 if( p->nRec < 1 ){
204 break;
205 }
206 pNext = pRec->pNext;
207 SyMemBackendPoolFree(pAlloc,(void *)pRec);
208 pRec = pNext;
209 p->nRec--;
210
211
212 if( p->nRec < 1 ){
213 break;
214 }
215 pNext = pRec->pNext;
216 SyMemBackendPoolFree(pAlloc,(void *)pRec);
217 pRec = pNext;
218 p->nRec--;
219 }
220 SyMemBackendFree(pAlloc,(void *)p->apRec);
221 SyMemBackendFree(pAlloc,p);
222}
diff --git a/common/unqlite/fastjson.c b/common/unqlite/fastjson.c
new file mode 100644
index 0000000..96f287f
--- /dev/null
+++ b/common/unqlite/fastjson.c
@@ -0,0 +1,393 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: fastjson.c v1.1 FreeBSD 2012-12-05 22:52 stable <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/* JSON binary encoding, decoding and stuff like that */
18#ifndef UNQLITE_FAST_JSON_NEST_LIMIT
19#if defined(__WINNT__) || defined(__UNIXES__)
20#define UNQLITE_FAST_JSON_NEST_LIMIT 64 /* Nesting limit */
21#else
22#define UNQLITE_FAST_JSON_NEST_LIMIT 32 /* Nesting limit */
23#endif
24#endif /* UNQLITE_FAST_JSON_NEST_LIMIT */
25/*
26 * JSON to Binary using the FastJSON implementation (BigEndian).
27 */
28/*
29 * FastJSON implemented binary token.
30 */
31#define FJSON_DOC_START 1 /* { */
32#define FJSON_DOC_END 2 /* } */
33#define FJSON_ARRAY_START 3 /* [ */
34#define FJSON_ARRAY_END 4 /* ] */
35#define FJSON_COLON 5 /* : */
36#define FJSON_COMMA 6 /* , */
37#define FJSON_ID 7 /* ID + 4 Bytes length */
38#define FJSON_STRING 8 /* String + 4 bytes length */
39#define FJSON_BYTE 9 /* Byte */
40#define FJSON_INT64 10 /* Integer 64 + 8 bytes */
41#define FJSON_REAL 18 /* Floating point value + 2 bytes */
42#define FJSON_NULL 23 /* NULL */
43#define FJSON_TRUE 24 /* TRUE */
44#define FJSON_FALSE 25 /* FALSE */
45/*
46 * Encode a Jx9 value to binary JSON.
47 */
48UNQLITE_PRIVATE sxi32 FastJsonEncode(
49 jx9_value *pValue, /* Value to encode */
50 SyBlob *pOut, /* Store encoded value here */
51 int iNest /* Nesting limit */
52 )
53{
54 sxi32 iType = pValue ? pValue->iFlags : MEMOBJ_NULL;
55 sxi32 rc = SXRET_OK;
56 int c;
57 if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){
58 /* Nesting limit reached */
59 return SXERR_LIMIT;
60 }
61 if( iType & (MEMOBJ_NULL|MEMOBJ_RES) ){
62 /*
63 * Resources are encoded as null also.
64 */
65 c = FJSON_NULL;
66 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
67 }else if( iType & MEMOBJ_BOOL ){
68 c = pValue->x.iVal ? FJSON_TRUE : FJSON_FALSE;
69 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
70 }else if( iType & MEMOBJ_STRING ){
71 unsigned char zBuf[sizeof(sxu32)]; /* String length */
72 c = FJSON_STRING;
73 SyBigEndianPack32(zBuf,SyBlobLength(&pValue->sBlob));
74 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
75 if( rc == SXRET_OK ){
76 rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf));
77 if( rc == SXRET_OK ){
78 rc = SyBlobAppend(pOut,SyBlobData(&pValue->sBlob),SyBlobLength(&pValue->sBlob));
79 }
80 }
81 }else if( iType & MEMOBJ_INT ){
82 unsigned char zBuf[8];
83 /* 64bit big endian integer */
84 c = FJSON_INT64;
85 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
86 if( rc == SXRET_OK ){
87 SyBigEndianPack64(zBuf,(sxu64)pValue->x.iVal);
88 rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf));
89 }
90 }else if( iType & MEMOBJ_REAL ){
91 /* Real number */
92 c = FJSON_REAL;
93 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
94 if( rc == SXRET_OK ){
95 sxu32 iOfft = SyBlobLength(pOut);
96 rc = SyBlobAppendBig16(pOut,0);
97 if( rc == SXRET_OK ){
98 unsigned char *zBlob;
99 SyBlobFormat(pOut,"%.15g",pValue->x.rVal);
100 zBlob = (unsigned char *)SyBlobDataAt(pOut,iOfft);
101 SyBigEndianPack16(zBlob,(sxu16)(SyBlobLength(pOut) - ( 2 + iOfft)));
102 }
103 }
104 }else if( iType & MEMOBJ_HASHMAP ){
105 /* A JSON object or array */
106 jx9_hashmap *pMap = (jx9_hashmap *)pValue->x.pOther;
107 jx9_hashmap_node *pNode;
108 jx9_value *pEntry;
109 /* Reset the hashmap loop cursor */
110 jx9HashmapResetLoopCursor(pMap);
111 if( pMap->iFlags & HASHMAP_JSON_OBJECT ){
112 jx9_value sKey;
113 /* A JSON object */
114 c = FJSON_DOC_START; /* { */
115 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
116 if( rc == SXRET_OK ){
117 jx9MemObjInit(pMap->pVm,&sKey);
118 /* Encode object entries */
119 while((pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){
120 /* Extract the key */
121 jx9HashmapExtractNodeKey(pNode,&sKey);
122 /* Encode it */
123 rc = FastJsonEncode(&sKey,pOut,iNest+1);
124 if( rc != SXRET_OK ){
125 break;
126 }
127 c = FJSON_COLON;
128 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
129 if( rc != SXRET_OK ){
130 break;
131 }
132 /* Extract the value */
133 pEntry = jx9HashmapGetNodeValue(pNode);
134 /* Encode it */
135 rc = FastJsonEncode(pEntry,pOut,iNest+1);
136 if( rc != SXRET_OK ){
137 break;
138 }
139 /* Delimit the entry */
140 c = FJSON_COMMA;
141 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
142 if( rc != SXRET_OK ){
143 break;
144 }
145 }
146 jx9MemObjRelease(&sKey);
147 if( rc == SXRET_OK ){
148 c = FJSON_DOC_END; /* } */
149 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
150 }
151 }
152 }else{
153 /* A JSON array */
154 c = FJSON_ARRAY_START; /* [ */
155 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
156 if( rc == SXRET_OK ){
157 /* Encode array entries */
158 while( (pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){
159 /* Extract the value */
160 pEntry = jx9HashmapGetNodeValue(pNode);
161 /* Encode it */
162 rc = FastJsonEncode(pEntry,pOut,iNest+1);
163 if( rc != SXRET_OK ){
164 break;
165 }
166 /* Delimit the entry */
167 c = FJSON_COMMA;
168 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
169 if( rc != SXRET_OK ){
170 break;
171 }
172 }
173 if( rc == SXRET_OK ){
174 c = FJSON_ARRAY_END; /* ] */
175 rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char));
176 }
177 }
178 }
179 }
180 return rc;
181}
182/*
183 * Decode a FastJSON binary blob.
184 */
185UNQLITE_PRIVATE sxi32 FastJsonDecode(
186 const void *pIn, /* Binary JSON */
187 sxu32 nByte, /* Chunk delimiter */
188 jx9_value *pOut, /* Decoded value */
189 const unsigned char **pzPtr,
190 int iNest /* Nesting limit */
191 )
192{
193 const unsigned char *zIn = (const unsigned char *)pIn;
194 const unsigned char *zEnd = &zIn[nByte];
195 sxi32 rc = SXRET_OK;
196 int c;
197 if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){
198 /* Nesting limit reached */
199 return SXERR_LIMIT;
200 }
201 c = zIn[0];
202 /* Advance the stream cursor */
203 zIn++;
204 /* Process the binary token */
205 switch(c){
206 case FJSON_NULL:
207 /* null */
208 jx9_value_null(pOut);
209 break;
210 case FJSON_FALSE:
211 /* Boolean FALSE */
212 jx9_value_bool(pOut,0);
213 break;
214 case FJSON_TRUE:
215 /* Boolean TRUE */
216 jx9_value_bool(pOut,1);
217 break;
218 case FJSON_INT64: {
219 /* 64Bit integer */
220 sxu64 iVal;
221 /* Sanity check */
222 if( &zIn[8] >= zEnd ){
223 /* Corrupt chunk */
224 rc = SXERR_CORRUPT;
225 break;
226 }
227 SyBigEndianUnpack64(zIn,&iVal);
228 /* Advance the pointer */
229 zIn += 8;
230 jx9_value_int64(pOut,(jx9_int64)iVal);
231 break;
232 }
233 case FJSON_REAL: {
234 /* Real number */
235 double iVal = 0; /* cc warning */
236 sxu16 iLen;
237 /* Sanity check */
238 if( &zIn[2] >= zEnd ){
239 /* Corrupt chunk */
240 rc = SXERR_CORRUPT;
241 break;
242 }
243 SyBigEndianUnpack16(zIn,&iLen);
244 if( &zIn[iLen] >= zEnd ){
245 /* Corrupt chunk */
246 rc = SXERR_CORRUPT;
247 break;
248 }
249 zIn += 2;
250 SyStrToReal((const char *)zIn,(sxu32)iLen,&iVal,0);
251 /* Advance the pointer */
252 zIn += iLen;
253 jx9_value_double(pOut,iVal);
254 break;
255 }
256 case FJSON_STRING: {
257 /* UTF-8/Binary chunk */
258 sxu32 iLength;
259 /* Sanity check */
260 if( &zIn[4] >= zEnd ){
261 /* Corrupt chunk */
262 rc = SXERR_CORRUPT;
263 break;
264 }
265 SyBigEndianUnpack32(zIn,&iLength);
266 if( &zIn[iLength] >= zEnd ){
267 /* Corrupt chunk */
268 rc = SXERR_CORRUPT;
269 break;
270 }
271 zIn += 4;
272 /* Invalidate any prior representation */
273 if( pOut->iFlags & MEMOBJ_STRING ){
274 /* Reset the string cursor */
275 SyBlobReset(&pOut->sBlob);
276 }
277 rc = jx9MemObjStringAppend(pOut,(const char *)zIn,iLength);
278 /* Update pointer */
279 zIn += iLength;
280 break;
281 }
282 case FJSON_ARRAY_START: {
283 /* Binary JSON array */
284 jx9_hashmap *pMap;
285 jx9_value sVal;
286 /* Allocate a new hashmap */
287 pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0);
288 if( pMap == 0 ){
289 rc = SXERR_MEM;
290 break;
291 }
292 jx9MemObjInit(pOut->pVm,&sVal);
293 jx9MemObjRelease(pOut);
294 MemObjSetType(pOut,MEMOBJ_HASHMAP);
295 pOut->x.pOther = pMap;
296 rc = SXRET_OK;
297 for(;;){
298 /* Jump leading binary commas */
299 while (zIn < zEnd && zIn[0] == FJSON_COMMA ){
300 zIn++;
301 }
302 if( zIn >= zEnd || zIn[0] == FJSON_ARRAY_END ){
303 if( zIn < zEnd ){
304 zIn++; /* Jump the trailing binary ] */
305 }
306 break;
307 }
308 /* Decode the value */
309 rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1);
310 if( rc != SXRET_OK ){
311 break;
312 }
313 /* Insert the decoded value */
314 rc = jx9HashmapInsert(pMap,0,&sVal);
315 if( rc != UNQLITE_OK ){
316 break;
317 }
318 }
319 if( rc != SXRET_OK ){
320 jx9MemObjRelease(pOut);
321 }
322 jx9MemObjRelease(&sVal);
323 break;
324 }
325 case FJSON_DOC_START: {
326 /* Binary JSON object */
327 jx9_value sVal,sKey;
328 jx9_hashmap *pMap;
329 /* Allocate a new hashmap */
330 pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0);
331 if( pMap == 0 ){
332 rc = SXERR_MEM;
333 break;
334 }
335 jx9MemObjInit(pOut->pVm,&sVal);
336 jx9MemObjInit(pOut->pVm,&sKey);
337 jx9MemObjRelease(pOut);
338 MemObjSetType(pOut,MEMOBJ_HASHMAP);
339 pOut->x.pOther = pMap;
340 rc = SXRET_OK;
341 for(;;){
342 /* Jump leading binary commas */
343 while (zIn < zEnd && zIn[0] == FJSON_COMMA ){
344 zIn++;
345 }
346 if( zIn >= zEnd || zIn[0] == FJSON_DOC_END ){
347 if( zIn < zEnd ){
348 zIn++; /* Jump the trailing binary } */
349 }
350 break;
351 }
352 /* Extract the key */
353 rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sKey,&zIn,iNest+1);
354 if( rc != UNQLITE_OK ){
355 break;
356 }
357 if( zIn >= zEnd || zIn[0] != FJSON_COLON ){
358 rc = UNQLITE_CORRUPT;
359 break;
360 }
361 zIn++; /* Jump the binary colon ':' */
362 if( zIn >= zEnd ){
363 rc = UNQLITE_CORRUPT;
364 break;
365 }
366 /* Decode the value */
367 rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1);
368 if( rc != SXRET_OK ){
369 break;
370 }
371 /* Insert the key and its associated value */
372 rc = jx9HashmapInsert(pMap,&sKey,&sVal);
373 if( rc != UNQLITE_OK ){
374 break;
375 }
376 }
377 if( rc != SXRET_OK ){
378 jx9MemObjRelease(pOut);
379 }
380 jx9MemObjRelease(&sVal);
381 jx9MemObjRelease(&sKey);
382 break;
383 }
384 default:
385 /* Corrupt data */
386 rc = SXERR_CORRUPT;
387 break;
388 }
389 if( pzPtr ){
390 *pzPtr = zIn;
391 }
392 return rc;
393}
diff --git a/common/unqlite/jx9.h b/common/unqlite/jx9.h
new file mode 100644
index 0000000..399cd9c
--- /dev/null
+++ b/common/unqlite/jx9.h
@@ -0,0 +1,462 @@
1/* This file was automatically generated. Do not edit (except for compile time directive)! */
2#ifndef _JX9H_
3#define _JX9H_
4/*
5 * Symisc Jx9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
6 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
7 * Version 1.7.2
8 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
9 * please contact Symisc Systems via:
10 * legal@symisc.net
11 * licensing@symisc.net
12 * contact@symisc.net
13 * or visit:
14 * http://jx9.symisc.net/
15 */
16/*
17 * Copyright (C) 2012, 2013 Symisc Systems. All rights reserved.
18 *
19 * Redistribution and use in source and binary forms, with or without
20 * modification, are permitted provided that the following conditions
21 * are met:
22 * 1. Redistributions of source code must retain the above copyright
23 * notice, this list of conditions and the following disclaimer.
24 * 2. Redistributions in binary form must reproduce the above copyright
25 * notice, this list of conditions and the following disclaimer in the
26 * documentation and/or other materials provided with the distribution.
27 * 3. Redistributions in any form must be accompanied by information on
28 * how to obtain complete source code for the JX9 engine and any
29 * accompanying software that uses the JX9 engine software.
30 * The source code must either be included in the distribution
31 * or be available for no more than the cost of distribution plus
32 * a nominal fee, and must be freely redistributable under reasonable
33 * conditions. For an executable file, complete source code means
34 * the source code for all modules it contains.It does not include
35 * source code for modules or files that typically accompany the major
36 * components of the operating system on which the executable file runs.
37 *
38 * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS
39 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
40 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
41 * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS
42 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
43 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
44 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
45 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
46 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
47 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
48 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
49 */
50 /* $SymiscID: jx9.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable <chm@symisc.net> $ */
51#include "unqlite.h"
52/*
53 * Compile time engine version, signature, identification in the symisc source tree
54 * and copyright notice.
55 * Each macro have an equivalent C interface associated with it that provide the same
56 * information but are associated with the library instead of the header file.
57 * Refer to [jx9_lib_version()], [jx9_lib_signature()], [jx9_lib_ident()] and
58 * [jx9_lib_copyright()] for more information.
59 */
60/*
61 * The JX9_VERSION C preprocessor macroevaluates to a string literal
62 * that is the jx9 version in the format "X.Y.Z" where X is the major
63 * version number and Y is the minor version number and Z is the release
64 * number.
65 */
66#define JX9_VERSION "1.7.2"
67/*
68 * The JX9_VERSION_NUMBER C preprocessor macro resolves to an integer
69 * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same
70 * numbers used in [JX9_VERSION].
71 */
72#define JX9_VERSION_NUMBER 1007002
73/*
74 * The JX9_SIG C preprocessor macro evaluates to a string
75 * literal which is the public signature of the jx9 engine.
76 * This signature could be included for example in a host-application
77 * generated Server MIME header as follows:
78 * Server: YourWebServer/x.x Jx9/x.x.x \r\n
79 */
80#define JX9_SIG "Jx9/1.7.2"
81/*
82 * JX9 identification in the Symisc source tree:
83 * Each particular check-in of a particular software released
84 * by symisc systems have an unique identifier associated with it.
85 * This macro hold the one associated with jx9.
86 */
87#define JX9_IDENT "jx9:d217a6e8c7f10fb35a8becb2793101fd2036aeb7"
88/*
89 * Copyright notice.
90 * If you have any questions about the licensing situation, please
91 * visit http://jx9.symisc.net/licensing.html
92 * or contact Symisc Systems via:
93 * legal@symisc.net
94 * licensing@symisc.net
95 * contact@symisc.net
96 */
97#define JX9_COPYRIGHT "Copyright (C) Symisc Systems 2012-2013, http://jx9.symisc.net/"
98
99/* Forward declaration to public objects */
100typedef struct jx9_io_stream jx9_io_stream;
101typedef struct jx9_context jx9_context;
102typedef struct jx9_value jx9_value;
103typedef struct jx9_vfs jx9_vfs;
104typedef struct jx9_vm jx9_vm;
105typedef struct jx9 jx9;
106
107#include "unqlite.h"
108
109#if !defined( UNQLITE_ENABLE_JX9_HASH_FUNC )
110#define JX9_DISABLE_HASH_FUNC
111#endif /* UNQLITE_ENABLE_JX9_HASH_FUNC */
112#ifdef UNQLITE_ENABLE_THREADS
113#define JX9_ENABLE_THREADS
114#endif /* UNQLITE_ENABLE_THREADS */
115/* Standard JX9 return values */
116#define JX9_OK SXRET_OK /* Successful result */
117/* beginning-of-error-codes */
118#define JX9_NOMEM UNQLITE_NOMEM /* Out of memory */
119#define JX9_ABORT UNQLITE_ABORT /* Foreign Function request operation abort/Another thread have released this instance */
120#define JX9_IO_ERR UNQLITE_IOERR /* IO error */
121#define JX9_CORRUPT UNQLITE_CORRUPT /* Corrupt pointer/Unknown configuration option */
122#define JX9_LOOKED UNQLITE_LOCKED /* Forbidden Operation */
123#define JX9_COMPILE_ERR UNQLITE_COMPILE_ERR /* Compilation error */
124#define JX9_VM_ERR UNQLITE_VM_ERR /* Virtual machine error */
125/* end-of-error-codes */
126/*
127 * If compiling for a processor that lacks floating point
128 * support, substitute integer for floating-point.
129 */
130#ifdef JX9_OMIT_FLOATING_POINT
131typedef sxi64 jx9_real;
132#else
133typedef double jx9_real;
134#endif
135typedef sxi64 jx9_int64;
136/*
137 * Engine Configuration Commands.
138 *
139 * The following set of constants are the available configuration verbs that can
140 * be used by the host-application to configure the JX9 engine.
141 * These constants must be passed as the second argument to the [jx9_config()]
142 * interface.
143 * Each options require a variable number of arguments.
144 * The [jx9_config()] interface will return JX9_OK on success, any other
145 * return value indicates failure.
146 * For a full discussion on the configuration verbs and their expected
147 * parameters, please refer to this page:
148 * http://jx9.symisc.net/c_api_func.html#jx9_config
149 */
150#define JX9_CONFIG_ERR_ABORT 1 /* RESERVED FOR FUTURE USE */
151#define JX9_CONFIG_ERR_LOG 2 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */
152/*
153 * Virtual Machine Configuration Commands.
154 *
155 * The following set of constants are the available configuration verbs that can
156 * be used by the host-application to configure the JX9 Virtual machine.
157 * These constants must be passed as the second argument to the [jx9_vm_config()]
158 * interface.
159 * Each options require a variable number of arguments.
160 * The [jx9_vm_config()] interface will return JX9_OK on success, any other return
161 * value indicates failure.
162 * There are many options but the most importants are: JX9_VM_CONFIG_OUTPUT which install
163 * a VM output consumer callback, JX9_VM_CONFIG_HTTP_REQUEST which parse and register
164 * a HTTP request and JX9_VM_CONFIG_ARGV_ENTRY which populate the $argv array.
165 * For a full discussion on the configuration verbs and their expected parameters, please
166 * refer to this page:
167 * http://jx9.symisc.net/c_api_func.html#jx9_vm_config
168 */
169#define JX9_VM_CONFIG_OUTPUT UNQLITE_VM_CONFIG_OUTPUT /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */
170#define JX9_VM_CONFIG_IMPORT_PATH UNQLITE_VM_CONFIG_IMPORT_PATH /* ONE ARGUMENT: const char *zIncludePath */
171#define JX9_VM_CONFIG_ERR_REPORT UNQLITE_VM_CONFIG_ERR_REPORT /* NO ARGUMENTS: Report all run-time errors in the VM output */
172#define JX9_VM_CONFIG_RECURSION_DEPTH UNQLITE_VM_CONFIG_RECURSION_DEPTH /* ONE ARGUMENT: int nMaxDepth */
173#define JX9_VM_OUTPUT_LENGTH UNQLITE_VM_OUTPUT_LENGTH /* ONE ARGUMENT: unsigned int *pLength */
174#define JX9_VM_CONFIG_CREATE_VAR UNQLITE_VM_CONFIG_CREATE_VAR /* TWO ARGUMENTS: const char *zName, jx9_value *pValue */
175#define JX9_VM_CONFIG_HTTP_REQUEST UNQLITE_VM_CONFIG_HTTP_REQUEST /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */
176#define JX9_VM_CONFIG_SERVER_ATTR UNQLITE_VM_CONFIG_SERVER_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */
177#define JX9_VM_CONFIG_ENV_ATTR UNQLITE_VM_CONFIG_ENV_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */
178#define JX9_VM_CONFIG_EXEC_VALUE UNQLITE_VM_CONFIG_EXEC_VALUE /* ONE ARGUMENT: jx9_value **ppValue */
179#define JX9_VM_CONFIG_IO_STREAM UNQLITE_VM_CONFIG_IO_STREAM /* ONE ARGUMENT: const jx9_io_stream *pStream */
180#define JX9_VM_CONFIG_ARGV_ENTRY UNQLITE_VM_CONFIG_ARGV_ENTRY /* ONE ARGUMENT: const char *zValue */
181#define JX9_VM_CONFIG_EXTRACT_OUTPUT UNQLITE_VM_CONFIG_EXTRACT_OUTPUT /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */
182/*
183 * Global Library Configuration Commands.
184 *
185 * The following set of constants are the available configuration verbs that can
186 * be used by the host-application to configure the whole library.
187 * These constants must be passed as the first argument to the [jx9_lib_config()]
188 * interface.
189 * Each options require a variable number of arguments.
190 * The [jx9_lib_config()] interface will return JX9_OK on success, any other return
191 * value indicates failure.
192 * Notes:
193 * The default configuration is recommended for most applications and so the call to
194 * [jx9_lib_config()] is usually not necessary. It is provided to support rare
195 * applications with unusual needs.
196 * The [jx9_lib_config()] interface is not threadsafe. The application must insure that
197 * no other [jx9_*()] interfaces are invoked by other threads while [jx9_lib_config()]
198 * is running. Furthermore, [jx9_lib_config()] may only be invoked prior to library
199 * initialization using [jx9_lib_init()] or [jx9_init()] or after shutdown
200 * by [jx9_lib_shutdown()]. If [jx9_lib_config()] is called after [jx9_lib_init()]
201 * or [jx9_init()] and before [jx9_lib_shutdown()] then it will return jx9LOCKED.
202 * For a full discussion on the configuration verbs and their expected parameters, please
203 * refer to this page:
204 * http://jx9.symisc.net/c_api_func.html#Global_Library_Management_Interfaces
205 */
206#define JX9_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */
207#define JX9_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */
208#define JX9_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */
209#define JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */
210#define JX9_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */
211#define JX9_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const jx9_vfs *pVfs */
212/*
213 * Call Context - Error Message Serverity Level.
214 */
215#define JX9_CTX_ERR UNQLITE_CTX_ERR /* Call context error such as unexpected number of arguments, invalid types and so on. */
216#define JX9_CTX_WARNING UNQLITE_CTX_WARNING /* Call context Warning */
217#define JX9_CTX_NOTICE UNQLITE_CTX_NOTICE /* Call context Notice */
218/* Current VFS structure version*/
219#define JX9_VFS_VERSION 2
220/*
221 * JX9 Virtual File System (VFS).
222 *
223 * An instance of the jx9_vfs object defines the interface between the JX9 core
224 * and the underlying operating system. The "vfs" in the name of the object stands
225 * for "virtual file system". The vfs is used to implement JX9 system functions
226 * such as mkdir(), chdir(), stat(), get_user_name() and many more.
227 * The value of the iVersion field is initially 2 but may be larger in future versions
228 * of JX9.
229 * Additional fields may be appended to this object when the iVersion value is increased.
230 * Only a single vfs can be registered within the JX9 core. Vfs registration is done
231 * using the jx9_lib_config() interface with a configuration verb set to JX9_LIB_CONFIG_VFS.
232 * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users does not have to
233 * worry about registering and installing a vfs since JX9 come with a built-in vfs for these
234 * platforms which implement most the methods defined below.
235 * Host-application running on exotic systems (ie: Other than Windows and UNIX systems) must
236 * register their own vfs in order to be able to use and call JX9 system functions.
237 * Also note that the jx9_compile_file() interface depend on the xMmap() method of the underlying
238 * vfs which mean that this method must be available (Always the case using the built-in VFS)
239 * in order to use this interface.
240 * Developers wishing to implement their own vfs an contact symisc systems to obtain
241 * the JX9 VFS C/C++ Specification manual.
242 */
243struct jx9_vfs
244{
245 const char *zName; /* Underlying VFS name [i.e: FreeBSD/Linux/Windows...] */
246 int iVersion; /* Current VFS structure version [default 2] */
247 /* Directory functions */
248 int (*xChdir)(const char *); /* Change directory */
249 int (*xChroot)(const char *); /* Change the root directory */
250 int (*xGetcwd)(jx9_context *); /* Get the current working directory */
251 int (*xMkdir)(const char *, int, int); /* Make directory */
252 int (*xRmdir)(const char *); /* Remove directory */
253 int (*xIsdir)(const char *); /* Tells whether the filename is a directory */
254 int (*xRename)(const char *, const char *); /* Renames a file or directory */
255 int (*xRealpath)(const char *, jx9_context *); /* Return canonicalized absolute pathname*/
256 /* Systems functions */
257 int (*xSleep)(unsigned int); /* Delay execution in microseconds */
258 int (*xUnlink)(const char *); /* Deletes a file */
259 int (*xFileExists)(const char *); /* Checks whether a file or directory exists */
260 int (*xChmod)(const char *, int); /* Changes file mode */
261 int (*xChown)(const char *, const char *); /* Changes file owner */
262 int (*xChgrp)(const char *, const char *); /* Changes file group */
263 jx9_int64 (*xFreeSpace)(const char *); /* Available space on filesystem or disk partition */
264 jx9_int64 (*xTotalSpace)(const char *); /* Total space on filesystem or disk partition */
265 jx9_int64 (*xFileSize)(const char *); /* Gets file size */
266 jx9_int64 (*xFileAtime)(const char *); /* Gets last access time of file */
267 jx9_int64 (*xFileMtime)(const char *); /* Gets file modification time */
268 jx9_int64 (*xFileCtime)(const char *); /* Gets inode change time of file */
269 int (*xStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */
270 int (*xlStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */
271 int (*xIsfile)(const char *); /* Tells whether the filename is a regular file */
272 int (*xIslink)(const char *); /* Tells whether the filename is a symbolic link */
273 int (*xReadable)(const char *); /* Tells whether a file exists and is readable */
274 int (*xWritable)(const char *); /* Tells whether the filename is writable */
275 int (*xExecutable)(const char *); /* Tells whether the filename is executable */
276 int (*xFiletype)(const char *, jx9_context *); /* Gets file type [i.e: fifo, dir, file..] */
277 int (*xGetenv)(const char *, jx9_context *); /* Gets the value of an environment variable */
278 int (*xSetenv)(const char *, const char *); /* Sets the value of an environment variable */
279 int (*xTouch)(const char *, jx9_int64, jx9_int64); /* Sets access and modification time of file */
280 int (*xMmap)(const char *, void **, jx9_int64 *); /* Read-only memory map of the whole file */
281 void (*xUnmap)(void *, jx9_int64); /* Unmap a memory view */
282 int (*xLink)(const char *, const char *, int); /* Create hard or symbolic link */
283 int (*xUmask)(int); /* Change the current umask */
284 void (*xTempDir)(jx9_context *); /* Get path of the temporary directory */
285 unsigned int (*xProcessId)(void); /* Get running process ID */
286 int (*xUid)(void); /* user ID of the process */
287 int (*xGid)(void); /* group ID of the process */
288 void (*xUsername)(jx9_context *); /* Running username */
289 int (*xExec)(const char *, jx9_context *); /* Execute an external program */
290};
291/* Current JX9 IO stream structure version. */
292#define JX9_IO_STREAM_VERSION 1
293/*
294 * Possible open mode flags that can be passed to the xOpen() routine
295 * of the underlying IO stream device .
296 * Refer to the JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html)
297 * for additional information.
298 */
299#define JX9_IO_OPEN_RDONLY 0x001 /* Read-only open */
300#define JX9_IO_OPEN_WRONLY 0x002 /* Write-only open */
301#define JX9_IO_OPEN_RDWR 0x004 /* Read-write open. */
302#define JX9_IO_OPEN_CREATE 0x008 /* If the file does not exist it will be created */
303#define JX9_IO_OPEN_TRUNC 0x010 /* Truncate the file to zero length */
304#define JX9_IO_OPEN_APPEND 0x020 /* Append mode.The file offset is positioned at the end of the file */
305#define JX9_IO_OPEN_EXCL 0x040 /* Ensure that this call creates the file, the file must not exist before */
306#define JX9_IO_OPEN_BINARY 0x080 /* Simple hint: Data is binary */
307#define JX9_IO_OPEN_TEMP 0x100 /* Simple hint: Temporary file */
308#define JX9_IO_OPEN_TEXT 0x200 /* Simple hint: Data is textual */
309/*
310 * JX9 IO Stream Device.
311 *
312 * An instance of the jx9_io_stream object defines the interface between the JX9 core
313 * and the underlying stream device.
314 * A stream is a smart mechanism for generalizing file, network, data compression
315 * and other IO operations which share a common set of functions using an abstracted
316 * unified interface.
317 * A stream device is additional code which tells the stream how to handle specific
318 * protocols/encodings. For example, the http device knows how to translate a URL
319 * into an HTTP/1.1 request for a file on a remote server.
320 * JX9 come with two built-in IO streams device:
321 * The file:// stream which perform very efficient disk IO and the jx9:// stream
322 * which is a special stream that allow access various I/O streams (See the JX9 official
323 * documentation for more information on this stream).
324 * A stream is referenced as: scheme://target
325 * scheme(string) - The name of the wrapper to be used. Examples include: file, http, https, ftp,
326 * ftps, compress.zlib, compress.bz2, and jx9. If no wrapper is specified, the function default
327 * is used (typically file://).
328 * target - Depends on the device used. For filesystem related streams this is typically a path
329 * and filename of the desired file.For network related streams this is typically a hostname, often
330 * with a path appended.
331 * IO stream devices are registered using a call to jx9_vm_config() with a configuration verb
332 * set to JX9_VM_CONFIG_IO_STREAM.
333 * Currently the JX9 development team is working on the implementation of the http:// and ftp://
334 * IO stream protocols. These devices will be available in the next major release of the JX9 engine.
335 * Developers wishing to implement their own IO stream devices must understand and follow
336 * The JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html).
337 */
338struct jx9_io_stream
339{
340 const char *zName; /* Underlying stream name [i.e: file/http/zip/jx9, ..] */
341 int iVersion; /* IO stream structure version [default 1]*/
342 int (*xOpen)(const char *, int, jx9_value *, void **); /* Open handle*/
343 int (*xOpenDir)(const char *, jx9_value *, void **); /* Open directory handle */
344 void (*xClose)(void *); /* Close file handle */
345 void (*xCloseDir)(void *); /* Close directory handle */
346 jx9_int64 (*xRead)(void *, void *, jx9_int64); /* Read from the open stream */
347 int (*xReadDir)(void *, jx9_context *); /* Read entry from directory handle */
348 jx9_int64 (*xWrite)(void *, const void *, jx9_int64); /* Write to the open stream */
349 int (*xSeek)(void *, jx9_int64, int); /* Seek on the open stream */
350 int (*xLock)(void *, int); /* Lock/Unlock the open stream */
351 void (*xRewindDir)(void *); /* Rewind directory handle */
352 jx9_int64 (*xTell)(void *); /* Current position of the stream read/write pointer */
353 int (*xTrunc)(void *, jx9_int64); /* Truncates the open stream to a given length */
354 int (*xSync)(void *); /* Flush open stream data */
355 int (*xStat)(void *, jx9_value *, jx9_value *); /* Stat an open stream handle */
356};
357/*
358 * C-API-REF: Please refer to the official documentation for interfaces
359 * purpose and expected parameters.
360 */
361/* Engine Handling Interfaces */
362JX9_PRIVATE int jx9_init(jx9 **ppEngine);
363/*JX9_PRIVATE int jx9_config(jx9 *pEngine, int nConfigOp, ...);*/
364JX9_PRIVATE int jx9_release(jx9 *pEngine);
365/* Compile Interfaces */
366JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm);
367JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm);
368/* Virtual Machine Handling Interfaces */
369JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...);
370/*JX9_PRIVATE int jx9_vm_exec(jx9_vm *pVm, int *pExitStatus);*/
371/*JX9_PRIVATE jx9_value * jx9_vm_extract_variable(jx9_vm *pVm,const char *zVarname);*/
372/*JX9_PRIVATE int jx9_vm_reset(jx9_vm *pVm);*/
373JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm);
374/*JX9_PRIVATE int jx9_vm_dump_v2(jx9_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData);*/
375/* In-process Extending Interfaces */
376JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData);
377/*JX9_PRIVATE int jx9_delete_function(jx9_vm *pVm, const char *zName);*/
378JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData);
379/*JX9_PRIVATE int jx9_delete_constant(jx9_vm *pVm, const char *zName);*/
380/* Foreign Function Parameter Values */
381JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue);
382JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue);
383JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue);
384JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue);
385JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen);
386JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue);
387JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict);
388/* Setting The Result Of A Foreign Function */
389JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue);
390JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue);
391JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool);
392JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value);
393JX9_PRIVATE int jx9_result_null(jx9_context *pCtx);
394JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen);
395JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...);
396JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue);
397JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData);
398/* Call Context Handling Interfaces */
399JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen);
400/*JX9_PRIVATE int jx9_context_output_format(jx9_context *pCtx, const char *zFormat, ...);*/
401JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr);
402JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...);
403JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx);
404JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen);
405JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx);
406JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData);
407JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx);
408JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx);
409JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx);
410JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx);
411/* Call Context Memory Management Interfaces */
412JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease);
413JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte);
414JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk);
415/* On Demand Dynamically Typed Value Object allocation interfaces */
416JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm);
417JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm);
418JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue);
419JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx);
420JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx);
421JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue);
422/* Dynamically Typed Value Object Management Interfaces */
423JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue);
424JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue);
425JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool);
426JX9_PRIVATE int jx9_value_null(jx9_value *pVal);
427JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value);
428JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen);
429JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...);
430JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal);
431JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData);
432JX9_PRIVATE int jx9_value_release(jx9_value *pVal);
433/* JSON Array/Object Management Interfaces */
434JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte);
435JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData);
436JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue);
437JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue);
438JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray);
439/* Dynamically Typed Value Object Query Interfaces */
440JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal);
441JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal);
442JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal);
443JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal);
444JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal);
445JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal);
446JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal);
447JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal);
448JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal);
449JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal);
450JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal);
451JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal);
452/* Global Library Management Interfaces */
453/*JX9_PRIVATE int jx9_lib_init(void);*/
454JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...);
455JX9_PRIVATE int jx9_lib_shutdown(void);
456/*JX9_PRIVATE int jx9_lib_is_threadsafe(void);*/
457/*JX9_PRIVATE const char * jx9_lib_version(void);*/
458JX9_PRIVATE const char * jx9_lib_signature(void);
459/*JX9_PRIVATE const char * jx9_lib_ident(void);*/
460/*JX9_PRIVATE const char * jx9_lib_copyright(void);*/
461
462#endif /* _JX9H_ */
diff --git a/common/unqlite/jx9Int.h b/common/unqlite/jx9Int.h
new file mode 100644
index 0000000..3b2350a
--- /dev/null
+++ b/common/unqlite/jx9Int.h
@@ -0,0 +1,1705 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: jx9Int.h v1.9 FreeBSD 2012-08-13 23:25 devel <chm@symisc.net> $ */
14#ifndef __JX9INT_H__
15#define __JX9INT_H__
16/* Internal interface definitions for JX9. */
17#ifdef JX9_AMALGAMATION
18#ifndef JX9_PRIVATE
19/* Marker for routines not intended for external use */
20#define JX9_PRIVATE static
21#endif /* JX9_PRIVATE */
22#else
23#define JX9_PRIVATE
24#include "jx9.h"
25#endif
26#ifndef JX9_PI
27/* Value of PI */
28#define JX9_PI 3.1415926535898
29#endif
30/*
31 * Constants for the largest and smallest possible 64-bit signed integers.
32 * These macros are designed to work correctly on both 32-bit and 64-bit
33 * compilers.
34 */
35#ifndef LARGEST_INT64
36#define LARGEST_INT64 (0xffffffff|(((sxi64)0x7fffffff)<<32))
37#endif
38#ifndef SMALLEST_INT64
39#define SMALLEST_INT64 (((sxi64)-1) - LARGEST_INT64)
40#endif
41/* Forward declaration of private structures */
42typedef struct jx9_foreach_info jx9_foreach_info;
43typedef struct jx9_foreach_step jx9_foreach_step;
44typedef struct jx9_hashmap_node jx9_hashmap_node;
45typedef struct jx9_hashmap jx9_hashmap;
46/* Symisc Standard types */
47#if !defined(SYMISC_STD_TYPES)
48#define SYMISC_STD_TYPES
49#ifdef __WINNT__
50/* Disable nuisance warnings on Borland compilers */
51#if defined(__BORLANDC__)
52#pragma warn -rch /* unreachable code */
53#pragma warn -ccc /* Condition is always true or false */
54#pragma warn -aus /* Assigned value is never used */
55#pragma warn -csu /* Comparing signed and unsigned */
56#pragma warn -spa /* Suspicious pointer arithmetic */
57#endif
58#endif
59typedef signed char sxi8; /* signed char */
60typedef unsigned char sxu8; /* unsigned char */
61typedef signed short int sxi16; /* 16 bits(2 bytes) signed integer */
62typedef unsigned short int sxu16; /* 16 bits(2 bytes) unsigned integer */
63typedef int sxi32; /* 32 bits(4 bytes) integer */
64typedef unsigned int sxu32; /* 32 bits(4 bytes) unsigned integer */
65typedef long sxptr;
66typedef unsigned long sxuptr;
67typedef long sxlong;
68typedef unsigned long sxulong;
69typedef sxi32 sxofft;
70typedef sxi64 sxofft64;
71typedef long double sxlongreal;
72typedef double sxreal;
73#define SXI8_HIGH 0x7F
74#define SXU8_HIGH 0xFF
75#define SXI16_HIGH 0x7FFF
76#define SXU16_HIGH 0xFFFF
77#define SXI32_HIGH 0x7FFFFFFF
78#define SXU32_HIGH 0xFFFFFFFF
79#define SXI64_HIGH 0x7FFFFFFFFFFFFFFF
80#define SXU64_HIGH 0xFFFFFFFFFFFFFFFF
81#if !defined(TRUE)
82#define TRUE 1
83#endif
84#if !defined(FALSE)
85#define FALSE 0
86#endif
87/*
88 * The following macros are used to cast pointers to integers and
89 * integers to pointers.
90 */
91#if defined(__PTRDIFF_TYPE__)
92# define SX_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X))
93# define SX_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X))
94#elif !defined(__GNUC__)
95# define SX_INT_TO_PTR(X) ((void*)&((char*)0)[X])
96# define SX_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0))
97#else
98# define SX_INT_TO_PTR(X) ((void*)(X))
99# define SX_PTR_TO_INT(X) ((int)(X))
100#endif
101#define SXMIN(a, b) ((a < b) ? (a) : (b))
102#define SXMAX(a, b) ((a < b) ? (b) : (a))
103#endif /* SYMISC_STD_TYPES */
104/* Symisc Run-time API private definitions */
105#if !defined(SYMISC_PRIVATE_DEFS)
106#define SYMISC_PRIVATE_DEFS
107
108typedef sxi32 (*ProcRawStrCmp)(const SyString *, const SyString *);
109#define SyStringData(RAW) ((RAW)->zString)
110#define SyStringLength(RAW) ((RAW)->nByte)
111#define SyStringInitFromBuf(RAW, ZBUF, NLEN){\
112 (RAW)->zString = (const char *)ZBUF;\
113 (RAW)->nByte = (sxu32)(NLEN);\
114}
115#define SyStringUpdatePtr(RAW, NBYTES){\
116 if( NBYTES > (RAW)->nByte ){\
117 (RAW)->nByte = 0;\
118 }else{\
119 (RAW)->zString += NBYTES;\
120 (RAW)->nByte -= NBYTES;\
121 }\
122}
123#define SyStringDupPtr(RAW1, RAW2)\
124 (RAW1)->zString = (RAW2)->zString;\
125 (RAW1)->nByte = (RAW2)->nByte;
126
127#define SyStringTrimLeadingChar(RAW, CHAR)\
128 while((RAW)->nByte > 0 && (RAW)->zString[0] == CHAR ){\
129 (RAW)->zString++;\
130 (RAW)->nByte--;\
131 }
132#define SyStringTrimTrailingChar(RAW, CHAR)\
133 while((RAW)->nByte > 0 && (RAW)->zString[(RAW)->nByte - 1] == CHAR){\
134 (RAW)->nByte--;\
135 }
136#define SyStringCmp(RAW1, RAW2, xCMP)\
137 (((RAW1)->nByte == (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW1)->nByte - (RAW2)->nByte))
138
139#define SyStringCmp2(RAW1, RAW2, xCMP)\
140 (((RAW1)->nByte >= (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW2)->nByte - (RAW1)->nByte))
141
142#define SyStringCharCmp(RAW, CHAR) \
143 (((RAW)->nByte == sizeof(char)) ? ((RAW)->zString[0] == CHAR ? 0 : CHAR - (RAW)->zString[0]) : ((RAW)->zString[0] == CHAR ? 0 : (RAW)->nByte - sizeof(char)))
144
145#define SX_ADDR(PTR) ((sxptr)PTR)
146#define SX_ARRAYSIZE(X) (sizeof(X)/sizeof(X[0]))
147#define SXUNUSED(P) (P = 0)
148#define SX_EMPTY(PTR) (PTR == 0)
149#define SX_EMPTY_STR(STR) (STR == 0 || STR[0] == 0 )
150typedef struct SyMemBackend SyMemBackend;
151typedef struct SyBlob SyBlob;
152typedef struct SySet SySet;
153/* Standard function signatures */
154typedef sxi32 (*ProcCmp)(const void *, const void *, sxu32);
155typedef sxi32 (*ProcPatternMatch)(const char *, sxu32, const char *, sxu32, sxu32 *);
156typedef sxi32 (*ProcSearch)(const void *, sxu32, const void *, sxu32, ProcCmp, sxu32 *);
157typedef sxu32 (*ProcHash)(const void *, sxu32);
158typedef sxi32 (*ProcHashSum)(const void *, sxu32, unsigned char *, sxu32);
159typedef sxi32 (*ProcSort)(void *, sxu32, sxu32, ProcCmp);
160#define MACRO_LIST_PUSH(Head, Item)\
161 Item->pNext = Head;\
162 Head = Item;
163#define MACRO_LD_PUSH(Head, Item)\
164 if( Head == 0 ){\
165 Head = Item;\
166 }else{\
167 Item->pNext = Head;\
168 Head->pPrev = Item;\
169 Head = Item;\
170 }
171#define MACRO_LD_REMOVE(Head, Item)\
172 if( Head == Item ){\
173 Head = Head->pNext;\
174 }\
175 if( Item->pPrev ){ Item->pPrev->pNext = Item->pNext;}\
176 if( Item->pNext ){ Item->pNext->pPrev = Item->pPrev;}
177/*
178 * A generic dynamic set.
179 */
180struct SySet
181{
182 SyMemBackend *pAllocator; /* Memory backend */
183 void *pBase; /* Base pointer */
184 sxu32 nUsed; /* Total number of used slots */
185 sxu32 nSize; /* Total number of available slots */
186 sxu32 eSize; /* Size of a single slot */
187 sxu32 nCursor; /* Loop cursor */
188 void *pUserData; /* User private data associated with this container */
189};
190#define SySetBasePtr(S) ((S)->pBase)
191#define SySetBasePtrJump(S, OFFT) (&((char *)(S)->pBase)[OFFT*(S)->eSize])
192#define SySetUsed(S) ((S)->nUsed)
193#define SySetSize(S) ((S)->nSize)
194#define SySetElemSize(S) ((S)->eSize)
195#define SySetCursor(S) ((S)->nCursor)
196#define SySetGetAllocator(S) ((S)->pAllocator)
197#define SySetSetUserData(S, DATA) ((S)->pUserData = DATA)
198#define SySetGetUserData(S) ((S)->pUserData)
199/*
200 * A variable length containers for generic data.
201 */
202struct SyBlob
203{
204 SyMemBackend *pAllocator; /* Memory backend */
205 void *pBlob; /* Base pointer */
206 sxu32 nByte; /* Total number of used bytes */
207 sxu32 mByte; /* Total number of available bytes */
208 sxu32 nFlags; /* Blob internal flags, see below */
209};
210#define SXBLOB_LOCKED 0x01 /* Blob is locked [i.e: Cannot auto grow] */
211#define SXBLOB_STATIC 0x02 /* Not allocated from heap */
212#define SXBLOB_RDONLY 0x04 /* Read-Only data */
213
214#define SyBlobFreeSpace(BLOB) ((BLOB)->mByte - (BLOB)->nByte)
215#define SyBlobLength(BLOB) ((BLOB)->nByte)
216#define SyBlobData(BLOB) ((BLOB)->pBlob)
217#define SyBlobCurData(BLOB) ((void*)(&((char*)(BLOB)->pBlob)[(BLOB)->nByte]))
218#define SyBlobDataAt(BLOB, OFFT) ((void *)(&((char *)(BLOB)->pBlob)[OFFT]))
219#define SyBlobGetAllocator(BLOB) ((BLOB)->pAllocator)
220
221#define SXMEM_POOL_INCR 3
222#define SXMEM_POOL_NBUCKETS 12
223#define SXMEM_BACKEND_MAGIC 0xBAC3E67D
224#define SXMEM_BACKEND_CORRUPT(BACKEND) (BACKEND == 0 || BACKEND->nMagic != SXMEM_BACKEND_MAGIC)
225
226#define SXMEM_BACKEND_RETRY 3
227/* A memory backend subsystem is defined by an instance of the following structures */
228typedef union SyMemHeader SyMemHeader;
229typedef struct SyMemBlock SyMemBlock;
230struct SyMemBlock
231{
232 SyMemBlock *pNext, *pPrev; /* Chain of allocated memory blocks */
233#ifdef UNTRUST
234 sxu32 nGuard; /* magic number associated with each valid block, so we
235 * can detect misuse.
236 */
237#endif
238};
239/*
240 * Header associated with each valid memory pool block.
241 */
242union SyMemHeader
243{
244 SyMemHeader *pNext; /* Next chunk of size 1 << (nBucket + SXMEM_POOL_INCR) in the list */
245 sxu32 nBucket; /* Bucket index in aPool[] */
246};
247struct SyMemBackend
248{
249 const SyMutexMethods *pMutexMethods; /* Mutex methods */
250 const SyMemMethods *pMethods; /* Memory allocation methods */
251 SyMemBlock *pBlocks; /* List of valid memory blocks */
252 sxu32 nBlock; /* Total number of memory blocks allocated so far */
253 ProcMemError xMemError; /* Out-of memory callback */
254 void *pUserData; /* First arg to xMemError() */
255 SyMutex *pMutex; /* Per instance mutex */
256 sxu32 nMagic; /* Sanity check against misuse */
257 SyMemHeader *apPool[SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR]; /* Pool of memory chunks */
258};
259/* Mutex types */
260#define SXMUTEX_TYPE_FAST 1
261#define SXMUTEX_TYPE_RECURSIVE 2
262#define SXMUTEX_TYPE_STATIC_1 3
263#define SXMUTEX_TYPE_STATIC_2 4
264#define SXMUTEX_TYPE_STATIC_3 5
265#define SXMUTEX_TYPE_STATIC_4 6
266#define SXMUTEX_TYPE_STATIC_5 7
267#define SXMUTEX_TYPE_STATIC_6 8
268
269#define SyMutexGlobalInit(METHOD){\
270 if( (METHOD)->xGlobalInit ){\
271 (METHOD)->xGlobalInit();\
272 }\
273}
274#define SyMutexGlobalRelease(METHOD){\
275 if( (METHOD)->xGlobalRelease ){\
276 (METHOD)->xGlobalRelease();\
277 }\
278}
279#define SyMutexNew(METHOD, TYPE) (METHOD)->xNew(TYPE)
280#define SyMutexRelease(METHOD, MUTEX){\
281 if( MUTEX && (METHOD)->xRelease ){\
282 (METHOD)->xRelease(MUTEX);\
283 }\
284}
285#define SyMutexEnter(METHOD, MUTEX){\
286 if( MUTEX ){\
287 (METHOD)->xEnter(MUTEX);\
288 }\
289}
290#define SyMutexTryEnter(METHOD, MUTEX){\
291 if( MUTEX && (METHOD)->xTryEnter ){\
292 (METHOD)->xTryEnter(MUTEX);\
293 }\
294}
295#define SyMutexLeave(METHOD, MUTEX){\
296 if( MUTEX ){\
297 (METHOD)->xLeave(MUTEX);\
298 }\
299}
300/* Comparison, byte swap, byte copy macros */
301#define SX_MACRO_FAST_CMP(X1, X2, SIZE, RC){\
302 register unsigned char *r1 = (unsigned char *)X1;\
303 register unsigned char *r2 = (unsigned char *)X2;\
304 register sxu32 LEN = SIZE;\
305 for(;;){\
306 if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\
307 if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\
308 if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\
309 if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\
310 }\
311 RC = !LEN ? 0 : r1[0] - r2[0];\
312}
313#define SX_MACRO_FAST_MEMCPY(SRC, DST, SIZ){\
314 register unsigned char *xSrc = (unsigned char *)SRC;\
315 register unsigned char *xDst = (unsigned char *)DST;\
316 register sxu32 xLen = SIZ;\
317 for(;;){\
318 if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\
319 if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\
320 if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\
321 if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\
322 }\
323}
324#define SX_MACRO_BYTE_SWAP(X, Y, Z){\
325 register unsigned char *s = (unsigned char *)X;\
326 register unsigned char *d = (unsigned char *)Y;\
327 sxu32 ZLong = Z; \
328 sxi32 c; \
329 for(;;){\
330 if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\
331 if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\
332 if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\
333 if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\
334 }\
335}
336#define SX_MSEC_PER_SEC (1000) /* Millisec per seconds */
337#define SX_USEC_PER_SEC (1000000) /* Microsec per seconds */
338#define SX_NSEC_PER_SEC (1000000000) /* Nanosec per seconds */
339#endif /* SYMISC_PRIVATE_DEFS */
340/* Symisc Run-time API auxiliary definitions */
341#if !defined(SYMISC_PRIVATE_AUX_DEFS)
342#define SYMISC_PRIVATE_AUX_DEFS
343
344typedef struct SyHashEntry_Pr SyHashEntry_Pr;
345typedef struct SyHashEntry SyHashEntry;
346typedef struct SyHash SyHash;
347/*
348 * Each public hashtable entry is represented by an instance
349 * of the following structure.
350 */
351struct SyHashEntry
352{
353 const void *pKey; /* Hash key */
354 sxu32 nKeyLen; /* Key length */
355 void *pUserData; /* User private data */
356};
357#define SyHashEntryGetUserData(ENTRY) ((ENTRY)->pUserData)
358#define SyHashEntryGetKey(ENTRY) ((ENTRY)->pKey)
359/* Each active hashtable is identified by an instance of the following structure */
360struct SyHash
361{
362 SyMemBackend *pAllocator; /* Memory backend */
363 ProcHash xHash; /* Hash function */
364 ProcCmp xCmp; /* Comparison function */
365 SyHashEntry_Pr *pList, *pCurrent; /* Linked list of hash entries user for linear traversal */
366 sxu32 nEntry; /* Total number of entries */
367 SyHashEntry_Pr **apBucket; /* Hash buckets */
368 sxu32 nBucketSize; /* Current bucket size */
369};
370#define SXHASH_BUCKET_SIZE 16 /* Initial bucket size: must be a power of two */
371#define SXHASH_FILL_FACTOR 3
372/* Hash access macro */
373#define SyHashFunc(HASH) ((HASH)->xHash)
374#define SyHashCmpFunc(HASH) ((HASH)->xCmp)
375#define SyHashTotalEntry(HASH) ((HASH)->nEntry)
376#define SyHashGetPool(HASH) ((HASH)->pAllocator)
377/*
378 * An instance of the following structure define a single context
379 * for an Pseudo Random Number Generator.
380 *
381 * Nothing in this file or anywhere else in the library does any kind of
382 * encryption. The RC4 algorithm is being used as a PRNG (pseudo-random
383 * number generator) not as an encryption device.
384 * This implementation is taken from the SQLite3 source tree.
385 */
386typedef struct SyPRNGCtx SyPRNGCtx;
387struct SyPRNGCtx
388{
389 sxu8 i, j; /* State variables */
390 unsigned char s[256]; /* State variables */
391 sxu16 nMagic; /* Sanity check */
392 };
393typedef sxi32 (*ProcRandomSeed)(void *, unsigned int, void *);
394/* High resolution timer.*/
395typedef struct sytime sytime;
396struct sytime
397{
398 long tm_sec; /* seconds */
399 long tm_usec; /* microseconds */
400};
401/* Forward declaration */
402typedef struct SyStream SyStream;
403typedef struct SyToken SyToken;
404typedef struct SyLex SyLex;
405/*
406 * Tokenizer callback signature.
407 */
408typedef sxi32 (*ProcTokenizer)(SyStream *, SyToken *, void *, void *);
409/*
410 * Each token in the input is represented by an instance
411 * of the following structure.
412 */
413struct SyToken
414{
415 SyString sData; /* Token text and length */
416 sxu32 nType; /* Token type */
417 sxu32 nLine; /* Token line number */
418 void *pUserData; /* User private data associated with this token */
419};
420/*
421 * During tokenization, information about the state of the input
422 * stream is held in an instance of the following structure.
423 */
424struct SyStream
425{
426 const unsigned char *zInput; /* Complete text of the input */
427 const unsigned char *zText; /* Current input we are processing */
428 const unsigned char *zEnd; /* End of input marker */
429 sxu32 nLine; /* Total number of processed lines */
430 sxu32 nIgn; /* Total number of ignored tokens */
431 SySet *pSet; /* Token containers */
432};
433/*
434 * Each lexer is represented by an instance of the following structure.
435 */
436struct SyLex
437{
438 SyStream sStream; /* Input stream */
439 ProcTokenizer xTokenizer; /* Tokenizer callback */
440 void * pUserData; /* Third argument to xTokenizer() */
441 SySet *pTokenSet; /* Token set */
442};
443#define SyLexTotalToken(LEX) SySetTotalEntry(&(LEX)->aTokenSet)
444#define SyLexTotalLines(LEX) ((LEX)->sStream.nLine)
445#define SyLexTotalIgnored(LEX) ((LEX)->sStream.nIgn)
446#define XLEX_IN_LEN(STREAM) (sxu32)(STREAM->zEnd - STREAM->zText)
447#endif /* SYMISC_PRIVATE_AUX_DEFS */
448/*
449** Notes on UTF-8 (According to SQLite3 authors):
450**
451** Byte-0 Byte-1 Byte-2 Byte-3 Value
452** 0xxxxxxx 00000000 00000000 0xxxxxxx
453** 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx
454** 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx
455** 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx
456**
457*/
458/*
459** Assuming zIn points to the first byte of a UTF-8 character,
460** advance zIn to point to the first byte of the next UTF-8 character.
461*/
462#define SX_JMP_UTF8(zIn, zEnd)\
463 while(zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ zIn++; }
464#define SX_WRITE_UTF8(zOut, c) { \
465 if( c<0x00080 ){ \
466 *zOut++ = (sxu8)(c&0xFF); \
467 }else if( c<0x00800 ){ \
468 *zOut++ = 0xC0 + (sxu8)((c>>6)&0x1F); \
469 *zOut++ = 0x80 + (sxu8)(c & 0x3F); \
470 }else if( c<0x10000 ){ \
471 *zOut++ = 0xE0 + (sxu8)((c>>12)&0x0F); \
472 *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \
473 *zOut++ = 0x80 + (sxu8)(c & 0x3F); \
474 }else{ \
475 *zOut++ = 0xF0 + (sxu8)((c>>18) & 0x07); \
476 *zOut++ = 0x80 + (sxu8)((c>>12) & 0x3F); \
477 *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \
478 *zOut++ = 0x80 + (sxu8)(c & 0x3F); \
479 } \
480}
481/* Rely on the standard ctype */
482#include <ctype.h>
483#define SyToUpper(c) toupper(c)
484#define SyToLower(c) tolower(c)
485#define SyisUpper(c) isupper(c)
486#define SyisLower(c) islower(c)
487#define SyisSpace(c) isspace(c)
488#define SyisBlank(c) isspace(c)
489#define SyisAlpha(c) isalpha(c)
490#define SyisDigit(c) isdigit(c)
491#define SyisHex(c) isxdigit(c)
492#define SyisPrint(c) isprint(c)
493#define SyisPunct(c) ispunct(c)
494#define SyisSpec(c) iscntrl(c)
495#define SyisCtrl(c) iscntrl(c)
496#define SyisAscii(c) isascii(c)
497#define SyisAlphaNum(c) isalnum(c)
498#define SyisGraph(c) isgraph(c)
499#define SyDigToHex(c) "0123456789ABCDEF"[c & 0x0F]
500#define SyDigToInt(c) ((c < 0xc0 && SyisDigit(c))? (c - '0') : 0 )
501#define SyCharToUpper(c) ((c < 0xc0 && SyisLower(c))? SyToUpper(c) : c)
502#define SyCharToLower(c) ((c < 0xc0 && SyisUpper(c))? SyToLower(c) : c)
503/* Remove white space/NUL byte from a raw string */
504#define SyStringLeftTrim(RAW)\
505 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\
506 (RAW)->nByte--;\
507 (RAW)->zString++;\
508 }
509#define SyStringLeftTrimSafe(RAW)\
510 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && ((RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\
511 (RAW)->nByte--;\
512 (RAW)->zString++;\
513 }
514#define SyStringRightTrim(RAW)\
515 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\
516 (RAW)->nByte--;\
517 }
518#define SyStringRightTrimSafe(RAW)\
519 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \
520 (( RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\
521 (RAW)->nByte--;\
522 }
523
524#define SyStringFullTrim(RAW)\
525 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\
526 (RAW)->nByte--;\
527 (RAW)->zString++;\
528 }\
529 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\
530 (RAW)->nByte--;\
531 }
532#define SyStringFullTrimSafe(RAW)\
533 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && \
534 ( (RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\
535 (RAW)->nByte--;\
536 (RAW)->zString++;\
537 }\
538 while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \
539 ( (RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\
540 (RAW)->nByte--;\
541 }
542#ifndef JX9_DISABLE_BUILTIN_FUNC
543/*
544 * An XML raw text, CDATA, tag name and son is parsed out and stored
545 * in an instance of the following structure.
546 */
547typedef struct SyXMLRawStr SyXMLRawStr;
548struct SyXMLRawStr
549{
550 const char *zString; /* Raw text [UTF-8 ENCODED EXCEPT CDATA] [NOT NULL TERMINATED] */
551 sxu32 nByte; /* Text length */
552 sxu32 nLine; /* Line number this text occurs */
553};
554/*
555 * Event callback signatures.
556 */
557typedef sxi32 (*ProcXMLStartTagHandler)(SyXMLRawStr *, SyXMLRawStr *, sxu32, SyXMLRawStr *, void *);
558typedef sxi32 (*ProcXMLTextHandler)(SyXMLRawStr *, void *);
559typedef sxi32 (*ProcXMLEndTagHandler)(SyXMLRawStr *, SyXMLRawStr *, void *);
560typedef sxi32 (*ProcXMLPIHandler)(SyXMLRawStr *, SyXMLRawStr *, void *);
561typedef sxi32 (*ProcXMLDoctypeHandler)(SyXMLRawStr *, void *);
562typedef sxi32 (*ProcXMLSyntaxErrorHandler)(const char *, int, SyToken *, void *);
563typedef sxi32 (*ProcXMLStartDocument)(void *);
564typedef sxi32 (*ProcXMLNameSpaceStart)(SyXMLRawStr *, SyXMLRawStr *, void *);
565typedef sxi32 (*ProcXMLNameSpaceEnd)(SyXMLRawStr *, void *);
566typedef sxi32 (*ProcXMLEndDocument)(void *);
567/* XML processing control flags */
568#define SXML_ENABLE_NAMESPACE 0x01 /* Parse XML with namespace support enbaled */
569#define SXML_ENABLE_QUERY 0x02 /* Not used */
570#define SXML_OPTION_CASE_FOLDING 0x04 /* Controls whether case-folding is enabled for this XML parser */
571#define SXML_OPTION_SKIP_TAGSTART 0x08 /* Specify how many characters should be skipped in the beginning of a tag name.*/
572#define SXML_OPTION_SKIP_WHITE 0x10 /* Whether to skip values consisting of whitespace characters. */
573#define SXML_OPTION_TARGET_ENCODING 0x20 /* Default encoding: UTF-8 */
574/* XML error codes */
575enum xml_err_code{
576 SXML_ERROR_NONE = 1,
577 SXML_ERROR_NO_MEMORY,
578 SXML_ERROR_SYNTAX,
579 SXML_ERROR_NO_ELEMENTS,
580 SXML_ERROR_INVALID_TOKEN,
581 SXML_ERROR_UNCLOSED_TOKEN,
582 SXML_ERROR_PARTIAL_CHAR,
583 SXML_ERROR_TAG_MISMATCH,
584 SXML_ERROR_DUPLICATE_ATTRIBUTE,
585 SXML_ERROR_JUNK_AFTER_DOC_ELEMENT,
586 SXML_ERROR_PARAM_ENTITY_REF,
587 SXML_ERROR_UNDEFINED_ENTITY,
588 SXML_ERROR_RECURSIVE_ENTITY_REF,
589 SXML_ERROR_ASYNC_ENTITY,
590 SXML_ERROR_BAD_CHAR_REF,
591 SXML_ERROR_BINARY_ENTITY_REF,
592 SXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF,
593 SXML_ERROR_MISPLACED_XML_PI,
594 SXML_ERROR_UNKNOWN_ENCODING,
595 SXML_ERROR_INCORRECT_ENCODING,
596 SXML_ERROR_UNCLOSED_CDATA_SECTION,
597 SXML_ERROR_EXTERNAL_ENTITY_HANDLING
598};
599/* Each active XML SAX parser is represented by an instance
600 * of the following structure.
601 */
602typedef struct SyXMLParser SyXMLParser;
603struct SyXMLParser
604{
605 SyMemBackend *pAllocator; /* Memory backend */
606 void *pUserData; /* User private data forwarded varbatim by the XML parser
607 * as the last argument to the users callbacks.
608 */
609 SyHash hns; /* Namespace hashtable */
610 SySet sToken; /* XML tokens */
611 SyLex sLex; /* Lexical analyzer */
612 sxi32 nFlags; /* Control flags */
613 /* User callbacks */
614 ProcXMLStartTagHandler xStartTag; /* Start element handler */
615 ProcXMLEndTagHandler xEndTag; /* End element handler */
616 ProcXMLTextHandler xRaw; /* Raw text/CDATA handler */
617 ProcXMLDoctypeHandler xDoctype; /* DOCTYPE handler */
618 ProcXMLPIHandler xPi; /* Processing instruction (PI) handler*/
619 ProcXMLSyntaxErrorHandler xError; /* Error handler */
620 ProcXMLStartDocument xStartDoc; /* StartDoc handler */
621 ProcXMLEndDocument xEndDoc; /* EndDoc handler */
622 ProcXMLNameSpaceStart xNameSpace; /* Namespace declaration handler */
623 ProcXMLNameSpaceEnd xNameSpaceEnd; /* End namespace declaration handler */
624};
625/*
626 * --------------
627 * Archive extractor:
628 * --------------
629 * Each open ZIP/TAR archive is identified by an instance of the following structure.
630 * That is, a process can open one or more archives and manipulates them in thread safe
631 * way by simply working with pointers to the following structure.
632 * Each entry in the archive is remembered in a hashtable.
633 * Lookup is very fast and entry with the same name are chained together.
634 */
635 typedef struct SyArchiveEntry SyArchiveEntry;
636 typedef struct SyArchive SyArchive;
637 struct SyArchive
638 {
639 SyMemBackend *pAllocator; /* Memory backend */
640 SyArchiveEntry *pCursor; /* Cursor for linear traversal of archive entries */
641 SyArchiveEntry *pList; /* Pointer to the List of the loaded archive */
642 SyArchiveEntry **apHash; /* Hashtable for archive entry */
643 ProcRawStrCmp xCmp; /* Hash comparison function */
644 ProcHash xHash; /* Hash Function */
645 sxu32 nSize; /* Hashtable size */
646 sxu32 nEntry; /* Total number of entries in the zip/tar archive */
647 sxu32 nLoaded; /* Total number of entries loaded in memory */
648 sxu32 nCentralOfft; /* Central directory offset(ZIP only. Otherwise Zero) */
649 sxu32 nCentralSize; /* Central directory size(ZIP only. Otherwise Zero) */
650 void *pUserData; /* Upper layer private data */
651 sxu32 nMagic; /* Sanity check */
652
653 };
654#define SXARCH_MAGIC 0xDEAD635A
655#define SXARCH_INVALID(ARCH) (ARCH == 0 || ARCH->nMagic != SXARCH_MAGIC)
656#define SXARCH_ENTRY_INVALID(ENTRY) (ENTRY == 0 || ENTRY->nMagic != SXARCH_MAGIC)
657#define SyArchiveHashFunc(ARCH) (ARCH)->xHash
658#define SyArchiveCmpFunc(ARCH) (ARCH)->xCmp
659#define SyArchiveUserData(ARCH) (ARCH)->pUserData
660#define SyArchiveSetUserData(ARCH, DATA) (ARCH)->pUserData = DATA
661/*
662 * Each loaded archive record is identified by an instance
663 * of the following structure.
664 */
665 struct SyArchiveEntry
666 {
667 sxu32 nByte; /* Contents size before compression */
668 sxu32 nByteCompr; /* Contents size after compression */
669 sxu32 nReadCount; /* Read counter */
670 sxu32 nCrc; /* Contents CRC32 */
671 Sytm sFmt; /* Last-modification time */
672 sxu32 nOfft; /* Data offset. */
673 sxu16 nComprMeth; /* Compression method 0 == stored/8 == deflated and so on (see appnote.txt)*/
674 sxu16 nExtra; /* Extra size if any */
675 SyString sFileName; /* entry name & length */
676 sxu32 nDup; /* Total number of entries with the same name */
677 SyArchiveEntry *pNextHash, *pPrevHash; /* Hash collision chains */
678 SyArchiveEntry *pNextName; /* Next entry with the same name */
679 SyArchiveEntry *pNext, *pPrev; /* Next and previous entry in the list */
680 sxu32 nHash; /* Hash of the entry name */
681 void *pUserData; /* User data */
682 sxu32 nMagic; /* Sanity check */
683 };
684 /*
685 * Extra flags for extending the file local header
686 */
687#define SXZIP_EXTRA_TIMESTAMP 0x001 /* Extended UNIX timestamp */
688#endif /* JX9_DISABLE_BUILTIN_FUNC */
689#ifndef JX9_DISABLE_HASH_FUNC
690/* MD5 context */
691typedef struct MD5Context MD5Context;
692struct MD5Context {
693 sxu32 buf[4];
694 sxu32 bits[2];
695 unsigned char in[64];
696};
697/* SHA1 context */
698typedef struct SHA1Context SHA1Context;
699struct SHA1Context {
700 unsigned int state[5];
701 unsigned int count[2];
702 unsigned char buffer[64];
703};
704#endif /* JX9_DISABLE_HASH_FUNC */
705/* JX9 private declaration */
706/*
707 * Memory Objects.
708 * Internally, the JX9 virtual machine manipulates nearly all JX9 values
709 * [i.e: string, int, float, resource, object, bool, null] as jx9_values structures.
710 * Each jx9_values struct may cache multiple representations (string, integer etc.)
711 * of the same value.
712 */
713struct jx9_value
714{
715 union{
716 jx9_real rVal; /* Real value */
717 sxi64 iVal; /* Integer value */
718 void *pOther; /* Other values (Object, Array, Resource, Namespace, etc.) */
719 }x;
720 sxi32 iFlags; /* Control flags (see below) */
721 jx9_vm *pVm; /* VM this instance belong */
722 SyBlob sBlob; /* String values */
723 sxu32 nIdx; /* Object index in the global pool */
724};
725/* Allowed value types.
726 */
727#define MEMOBJ_STRING 0x001 /* Memory value is a UTF-8 string */
728#define MEMOBJ_INT 0x002 /* Memory value is an integer */
729#define MEMOBJ_REAL 0x004 /* Memory value is a real number */
730#define MEMOBJ_BOOL 0x008 /* Memory value is a boolean */
731#define MEMOBJ_NULL 0x020 /* Memory value is NULL */
732#define MEMOBJ_HASHMAP 0x040 /* Memory value is a hashmap (JSON representation of Array and Objects) */
733#define MEMOBJ_RES 0x100 /* Memory value is a resource [User private data] */
734/* Mask of all known types */
735#define MEMOBJ_ALL (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)
736/* Scalar variables
737 * According to the JX9 language reference manual
738 * Scalar variables are those containing an integer, float, string or boolean.
739 * Types array, object and resource are not scalar.
740 */
741#define MEMOBJ_SCALAR (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL)
742/*
743 * The following macro clear the current jx9_value type and replace
744 * it with the given one.
745 */
746#define MemObjSetType(OBJ, TYPE) ((OBJ)->iFlags = ((OBJ)->iFlags&~MEMOBJ_ALL)|TYPE)
747/* jx9_value cast method signature */
748typedef sxi32 (*ProcMemObjCast)(jx9_value *);
749/* Forward reference */
750typedef struct jx9_output_consumer jx9_output_consumer;
751typedef struct jx9_user_func jx9_user_func;
752typedef struct jx9_conf jx9_conf;
753/*
754 * An instance of the following structure store the default VM output
755 * consumer and it's private data.
756 * Client-programs can register their own output consumer callback
757 * via the [JX9_VM_CONFIG_OUTPUT] configuration directive.
758 * Please refer to the official documentation for more information
759 * on how to register an output consumer callback.
760 */
761struct jx9_output_consumer
762{
763 ProcConsumer xConsumer; /* VM output consumer routine */
764 void *pUserData; /* Third argument to xConsumer() */
765 ProcConsumer xDef; /* Default output consumer routine */
766 void *pDefData; /* Third argument to xDef() */
767};
768/*
769 * JX9 engine [i.e: jx9 instance] configuration is stored in
770 * an instance of the following structure.
771 * Please refer to the official documentation for more information
772 * on how to configure your jx9 engine instance.
773 */
774struct jx9_conf
775{
776 ProcConsumer xErr; /* Compile-time error consumer callback */
777 void *pErrData; /* Third argument to xErr() */
778 SyBlob sErrConsumer; /* Default error consumer */
779};
780/*
781 * Signature of the C function responsible of expanding constant values.
782 */
783typedef void (*ProcConstant)(jx9_value *, void *);
784/*
785 * Each registered constant [i.e: __TIME__, __DATE__, JX9_OS, INT_MAX, etc.] is stored
786 * in an instance of the following structure.
787 * Please refer to the official documentation for more information
788 * on how to create/install foreign constants.
789 */
790typedef struct jx9_constant jx9_constant;
791struct jx9_constant
792{
793 SyString sName; /* Constant name */
794 ProcConstant xExpand; /* Function responsible of expanding constant value */
795 void *pUserData; /* Last argument to xExpand() */
796};
797typedef struct jx9_aux_data jx9_aux_data;
798/*
799 * Auxiliary data associated with each foreign function is stored
800 * in a stack of the following structure.
801 * Note that automatic tracked chunks are also stored in an instance
802 * of this structure.
803 */
804struct jx9_aux_data
805{
806 void *pAuxData; /* Aux data */
807};
808/* Foreign functions signature */
809typedef int (*ProcHostFunction)(jx9_context *, int, jx9_value **);
810/*
811 * Each installed foreign function is recored in an instance of the following
812 * structure.
813 * Please refer to the official documentation for more information on how
814 * to create/install foreign functions.
815 */
816struct jx9_user_func
817{
818 jx9_vm *pVm; /* VM that own this instance */
819 SyString sName; /* Foreign function name */
820 ProcHostFunction xFunc; /* Implementation of the foreign function */
821 void *pUserData; /* User private data [Refer to the official documentation for more information]*/
822 SySet aAux; /* Stack of auxiliary data [Refer to the official documentation for more information]*/
823};
824/*
825 * The 'context' argument for an installable function. A pointer to an
826 * instance of this structure is the first argument to the routines used
827 * implement the foreign functions.
828 */
829struct jx9_context
830{
831 jx9_user_func *pFunc; /* Function information. */
832 jx9_value *pRet; /* Return value is stored here. */
833 SySet sVar; /* Container of dynamically allocated jx9_values
834 * [i.e: Garbage collection purposes.]
835 */
836 SySet sChunk; /* Track dynamically allocated chunks [jx9_aux_data instance].
837 * [i.e: Garbage collection purposes.]
838 */
839 jx9_vm *pVm; /* Virtual machine that own this context */
840 sxi32 iFlags; /* Call flags */
841};
842/* Hashmap control flags */
843#define HASHMAP_JSON_OBJECT 0x001 /* Hashmap represent JSON Object*/
844/*
845 * Each hashmap entry [i.e: array(4, 5, 6)] is recorded in an instance
846 * of the following structure.
847 */
848struct jx9_hashmap_node
849{
850 jx9_hashmap *pMap; /* Hashmap that own this instance */
851 sxi32 iType; /* Node type */
852 union{
853 sxi64 iKey; /* Int key */
854 SyBlob sKey; /* Blob key */
855 }xKey;
856 sxi32 iFlags; /* Control flags */
857 sxu32 nHash; /* Key hash value */
858 sxu32 nValIdx; /* Value stored in this node */
859 jx9_hashmap_node *pNext, *pPrev; /* Link to other entries [i.e: linear traversal] */
860 jx9_hashmap_node *pNextCollide, *pPrevCollide; /* Collision chain */
861};
862/*
863 * Each active hashmap aka array in the JX9 jargon is represented
864 * by an instance of the following structure.
865 */
866struct jx9_hashmap
867{
868 jx9_vm *pVm; /* VM that own this instance */
869 jx9_hashmap_node **apBucket; /* Hash bucket */
870 jx9_hashmap_node *pFirst; /* First inserted entry */
871 jx9_hashmap_node *pLast; /* Last inserted entry */
872 jx9_hashmap_node *pCur; /* Current entry */
873 sxu32 nSize; /* Bucket size */
874 sxu32 nEntry; /* Total number of inserted entries */
875 sxu32 (*xIntHash)(sxi64); /* Hash function for int_keys */
876 sxu32 (*xBlobHash)(const void *, sxu32); /* Hash function for blob_keys */
877 sxi32 iFlags; /* Hashmap control flags */
878 sxi64 iNextIdx; /* Next available automatically assigned index */
879 sxi32 iRef; /* Reference count */
880};
881/* An instance of the following structure is the context
882 * for the FOREACH_STEP/FOREACH_INIT VM instructions.
883 * Those instructions are used to implement the 'foreach'
884 * statement.
885 * This structure is made available to these instructions
886 * as the P3 operand.
887 */
888struct jx9_foreach_info
889{
890 SyString sKey; /* Key name. Empty otherwise*/
891 SyString sValue; /* Value name */
892 sxi32 iFlags; /* Control flags */
893 SySet aStep; /* Stack of steps [i.e: jx9_foreach_step instance] */
894};
895struct jx9_foreach_step
896{
897 sxi32 iFlags; /* Control flags (see below) */
898 /* Iterate on this map*/
899 jx9_hashmap *pMap; /* Hashmap [i.e: array in the JX9 jargon] iteration
900 * Ex: foreach(array(1, 2, 3) as $key=>$value){}
901 */
902
903};
904/* Foreach step control flags */
905#define JX9_4EACH_STEP_KEY 0x001 /* Make Key available */
906/*
907 * Each JX9 engine is identified by an instance of the following structure.
908 * Please refer to the official documentation for more information
909 * on how to configure your JX9 engine instance.
910 */
911struct jx9
912{
913 SyMemBackend sAllocator; /* Low level memory allocation subsystem */
914 const jx9_vfs *pVfs; /* Underlying Virtual File System */
915 jx9_conf xConf; /* Configuration */
916#if defined(JX9_ENABLE_THREADS)
917 SyMutex *pMutex; /* Per-engine mutex */
918#endif
919 jx9_vm *pVms; /* List of active VM */
920 sxi32 iVm; /* Total number of active VM */
921 jx9 *pNext, *pPrev; /* List of active engines */
922 sxu32 nMagic; /* Sanity check against misuse */
923};
924/* Code generation data structures */
925typedef sxi32 (*ProcErrorGen)(void *, sxi32, sxu32, const char *, ...);
926typedef struct jx9_expr_node jx9_expr_node;
927typedef struct jx9_expr_op jx9_expr_op;
928typedef struct jx9_gen_state jx9_gen_state;
929typedef struct GenBlock GenBlock;
930typedef sxi32 (*ProcLangConstruct)(jx9_gen_state *);
931typedef sxi32 (*ProcNodeConstruct)(jx9_gen_state *, sxi32);
932/*
933 * Each supported operator [i.e: +, -, ==, *, %, >>, >=, new, etc.] is represented
934 * by an instance of the following structure.
935 * The JX9 parser does not use any external tools and is 100% handcoded.
936 * That is, the JX9 parser is thread-safe , full reentrant, produce consistant
937 * compile-time errrors and at least 7 times faster than the standard JX9 parser.
938 */
939struct jx9_expr_op
940{
941 SyString sOp; /* String representation of the operator [i.e: "+", "*", "=="...] */
942 sxi32 iOp; /* Operator ID */
943 sxi32 iPrec; /* Operator precedence: 1 == Highest */
944 sxi32 iAssoc; /* Operator associativity (either left, right or non-associative) */
945 sxi32 iVmOp; /* VM OP code for this operator [i.e: JX9_OP_EQ, JX9_OP_LT, JX9_OP_MUL...]*/
946};
947/*
948 * Each expression node is parsed out and recorded
949 * in an instance of the following structure.
950 */
951struct jx9_expr_node
952{
953 const jx9_expr_op *pOp; /* Operator ID or NULL if literal, constant, variable, function or object method call */
954 jx9_expr_node *pLeft; /* Left expression tree */
955 jx9_expr_node *pRight; /* Right expression tree */
956 SyToken *pStart; /* Stream of tokens that belong to this node */
957 SyToken *pEnd; /* End of token stream */
958 sxi32 iFlags; /* Node construct flags */
959 ProcNodeConstruct xCode; /* C routine responsible of compiling this node */
960 SySet aNodeArgs; /* Node arguments. Only used by postfix operators [i.e: function call]*/
961 jx9_expr_node *pCond; /* Condition: Only used by the ternary operator '?:' */
962};
963/* Node Construct flags */
964#define EXPR_NODE_PRE_INCR 0x01 /* Pre-icrement/decrement [i.e: ++$i, --$j] node */
965/*
966 * A block of instructions is recorded in an instance of the following structure.
967 * This structure is used only during compile-time and have no meaning
968 * during bytecode execution.
969 */
970struct GenBlock
971{
972 jx9_gen_state *pGen; /* State of the code generator */
973 GenBlock *pParent; /* Upper block or NULL if global */
974 sxu32 nFirstInstr; /* First instruction to execute */
975 sxi32 iFlags; /* Block control flags (see below) */
976 SySet aJumpFix; /* Jump fixup (JumpFixup instance) */
977 void *pUserData; /* Upper layer private data */
978 /* The following two fields are used only when compiling
979 * the 'do..while()' language construct.
980 */
981 sxu8 bPostContinue; /* TRUE when compiling the do..while() statement */
982 SySet aPostContFix; /* Post-continue jump fix */
983};
984/*
985 * Code generator state is remembered in an instance of the following
986 * structure. We put the information in this structure and pass around
987 * a pointer to this structure, rather than pass around all of the
988 * information separately. This helps reduce the number of arguments
989 * to generator functions.
990 * This structure is used only during compile-time and have no meaning
991 * during bytecode execution.
992 */
993struct jx9_gen_state
994{
995 jx9_vm *pVm; /* VM that own this instance */
996 SyHash hLiteral; /* Constant string Literals table */
997 SyHash hNumLiteral; /* Numeric literals table */
998 SyHash hVar; /* Collected variable hashtable */
999 GenBlock *pCurrent; /* Current processed block */
1000 GenBlock sGlobal; /* Global block */
1001 ProcConsumer xErr; /* Error consumer callback */
1002 void *pErrData; /* Third argument to xErr() */
1003 SyToken *pIn; /* Current processed token */
1004 SyToken *pEnd; /* Last token in the stream */
1005 sxu32 nErr; /* Total number of compilation error */
1006};
1007/* Forward references */
1008typedef struct jx9_vm_func_static_var jx9_vm_func_static_var;
1009typedef struct jx9_vm_func_arg jx9_vm_func_arg;
1010typedef struct jx9_vm_func jx9_vm_func;
1011typedef struct VmFrame VmFrame;
1012/*
1013 * Each collected function argument is recorded in an instance
1014 * of the following structure.
1015 * Note that as an extension, JX9 implements full type hinting
1016 * which mean that any function can have it's own signature.
1017 * Example:
1018 * function foo(int $a, string $b, float $c, ClassInstance $d){}
1019 * This is how the powerful function overloading mechanism is
1020 * implemented.
1021 * Note that as an extension, JX9 allow function arguments to have
1022 * any complex default value associated with them unlike the standard
1023 * JX9 engine.
1024 * Example:
1025 * function foo(int $a = rand() & 1023){}
1026 * now, when foo is called without arguments [i.e: foo()] the
1027 * $a variable (first parameter) will be set to a random number
1028 * between 0 and 1023 inclusive.
1029 * Refer to the official documentation for more information on this
1030 * mechanism and other extension introduced by the JX9 engine.
1031 */
1032struct jx9_vm_func_arg
1033{
1034 SyString sName; /* Argument name */
1035 SySet aByteCode; /* Compiled default value associated with this argument */
1036 sxu32 nType; /* Type of this argument [i.e: array, int, string, float, object, etc.] */
1037 sxi32 iFlags; /* Configuration flags */
1038};
1039/*
1040 * Each static variable is parsed out and remembered in an instance
1041 * of the following structure.
1042 * Note that as an extension, JX9 allow static variable have
1043 * any complex default value associated with them unlike the standard
1044 * JX9 engine.
1045 * Example:
1046 * static $rand_str = 'JX9'.rand_str(3); // Concatenate 'JX9' with
1047 * // a random three characters(English alphabet)
1048 * dump($rand_str);
1049 * //You should see something like this
1050 * string(6 'JX9awt');
1051 */
1052struct jx9_vm_func_static_var
1053{
1054 SyString sName; /* Static variable name */
1055 SySet aByteCode; /* Compiled initialization expression */
1056 sxu32 nIdx; /* Object index in the global memory object container */
1057};
1058/* Function configuration flags */
1059#define VM_FUNC_ARG_HAS_DEF 0x001 /* Argument has default value associated with it */
1060#define VM_FUNC_ARG_IGNORE 0x002 /* Do not install argument in the current frame */
1061/*
1062 * Each user defined function is parsed out and stored in an instance
1063 * of the following structure.
1064 * JX9 introduced some powerfull extensions to the JX9 5 programming
1065 * language like function overloading, type hinting, complex default
1066 * arguments values and many more.
1067 * Please refer to the official documentation for more information.
1068 */
1069struct jx9_vm_func
1070{
1071 SySet aArgs; /* Expected arguments (jx9_vm_func_arg instance) */
1072 SySet aStatic; /* Static variable (jx9_vm_func_static_var instance) */
1073 SyString sName; /* Function name */
1074 SySet aByteCode; /* Compiled function body */
1075 sxi32 iFlags; /* VM function configuration */
1076 SyString sSignature; /* Function signature used to implement function overloading
1077 * (Refer to the official docuemntation for more information
1078 * on this powerfull feature)
1079 */
1080 void *pUserData; /* Upper layer private data associated with this instance */
1081 jx9_vm_func *pNextName; /* Next VM function with the same name as this one */
1082};
1083/* Forward reference */
1084typedef struct jx9_builtin_constant jx9_builtin_constant;
1085typedef struct jx9_builtin_func jx9_builtin_func;
1086/*
1087 * Each built-in foreign function (C function) is stored in an
1088 * instance of the following structure.
1089 * Please refer to the official documentation for more information
1090 * on how to create/install foreign functions.
1091 */
1092struct jx9_builtin_func
1093{
1094 const char *zName; /* Function name [i.e: strlen(), rand(), array_merge(), etc.]*/
1095 ProcHostFunction xFunc; /* C routine performing the computation */
1096};
1097/*
1098 * Each built-in foreign constant is stored in an instance
1099 * of the following structure.
1100 * Please refer to the official documentation for more information
1101 * on how to create/install foreign constants.
1102 */
1103struct jx9_builtin_constant
1104{
1105 const char *zName; /* Constant name */
1106 ProcConstant xExpand; /* C routine responsible of expanding constant value*/
1107};
1108/*
1109 * A single instruction of the virtual machine has an opcode
1110 * and as many as three operands.
1111 * Each VM instruction resulting from compiling a JX9 script
1112 * is stored in an instance of the following structure.
1113 */
1114typedef struct VmInstr VmInstr;
1115struct VmInstr
1116{
1117 sxu8 iOp; /* Operation to preform */
1118 sxi32 iP1; /* First operand */
1119 sxu32 iP2; /* Second operand (Often the jump destination) */
1120 void *p3; /* Third operand (Often Upper layer private data) */
1121};
1122/* Forward reference */
1123typedef struct jx9_case_expr jx9_case_expr;
1124typedef struct jx9_switch jx9_switch;
1125/*
1126 * Each compiled case block in a swicth statement is compiled
1127 * and stored in an instance of the following structure.
1128 */
1129struct jx9_case_expr
1130{
1131 SySet aByteCode; /* Compiled body of the case block */
1132 sxu32 nStart; /* First instruction to execute */
1133};
1134/*
1135 * Each compiled switch statement is parsed out and stored
1136 * in an instance of the following structure.
1137 */
1138struct jx9_switch
1139{
1140 SySet aCaseExpr; /* Compile case block */
1141 sxu32 nOut; /* First instruction to execute after this statement */
1142 sxu32 nDefault; /* First instruction to execute in the default block */
1143};
1144/* Assertion flags */
1145#define JX9_ASSERT_DISABLE 0x01 /* Disable assertion */
1146#define JX9_ASSERT_WARNING 0x02 /* Issue a warning for each failed assertion */
1147#define JX9_ASSERT_BAIL 0x04 /* Terminate execution on failed assertions */
1148#define JX9_ASSERT_QUIET_EVAL 0x08 /* Not used */
1149#define JX9_ASSERT_CALLBACK 0x10 /* Callback to call on failed assertions */
1150/*
1151 * An instance of the following structure hold the bytecode instructions
1152 * resulting from compiling a JX9 script.
1153 * This structure contains the complete state of the virtual machine.
1154 */
1155struct jx9_vm
1156{
1157 SyMemBackend sAllocator; /* Memory backend */
1158#if defined(JX9_ENABLE_THREADS)
1159 SyMutex *pMutex; /* Recursive mutex associated with this VM. */
1160#endif
1161 jx9 *pEngine; /* Interpreter that own this VM */
1162 SySet aByteCode; /* Default bytecode container */
1163 SySet *pByteContainer; /* Current bytecode container */
1164 VmFrame *pFrame; /* Stack of active frames */
1165 SyPRNGCtx sPrng; /* PRNG context */
1166 SySet aMemObj; /* Object allocation table */
1167 SySet aLitObj; /* Literals allocation table */
1168 jx9_value *aOps; /* Operand stack */
1169 SySet aFreeObj; /* Stack of free memory objects */
1170 SyHash hConstant; /* Host-application and user defined constants container */
1171 SyHash hHostFunction; /* Host-application installable functions */
1172 SyHash hFunction; /* Compiled functions */
1173 SyHash hSuper; /* Global variable */
1174 SyBlob sConsumer; /* Default VM consumer [i.e Redirect all VM output to this blob] */
1175 SyBlob sWorker; /* General purpose working buffer */
1176 SyBlob sArgv; /* $argv[] collector [refer to the [getopt()] implementation for more information] */
1177 SySet aFiles; /* Stack of processed files */
1178 SySet aPaths; /* Set of import paths */
1179 SySet aIncluded; /* Set of included files */
1180 SySet aIOstream; /* Installed IO stream container */
1181 const jx9_io_stream *pDefStream; /* Default IO stream [i.e: typically this is the 'file://' stream] */
1182 jx9_value sExec; /* Compiled script return value [Can be extracted via the JX9_VM_CONFIG_EXEC_VALUE directive]*/
1183 void *pStdin; /* STDIN IO stream */
1184 void *pStdout; /* STDOUT IO stream */
1185 void *pStderr; /* STDERR IO stream */
1186 int bErrReport; /* TRUE to report all runtime Error/Warning/Notice */
1187 int nRecursionDepth; /* Current recursion depth */
1188 int nMaxDepth; /* Maximum allowed recusion depth */
1189 sxu32 nOutputLen; /* Total number of generated output */
1190 jx9_output_consumer sVmConsumer; /* Registered output consumer callback */
1191 int iAssertFlags; /* Assertion flags */
1192 jx9_value sAssertCallback; /* Callback to call on failed assertions */
1193 sxi32 iExitStatus; /* Script exit status */
1194 jx9_gen_state sCodeGen; /* Code generator module */
1195 jx9_vm *pNext, *pPrev; /* List of active VM's */
1196 sxu32 nMagic; /* Sanity check against misuse */
1197};
1198/*
1199 * Allowed value for jx9_vm.nMagic
1200 */
1201#define JX9_VM_INIT 0xEA12CD72 /* VM correctly initialized */
1202#define JX9_VM_RUN 0xBA851227 /* VM ready to execute JX9 bytecode */
1203#define JX9_VM_EXEC 0xCDFE1DAD /* VM executing JX9 bytecode */
1204#define JX9_VM_STALE 0xDEAD2BAD /* Stale VM */
1205/*
1206 * Error codes according to the JX9 language reference manual.
1207 */
1208enum iErrCode
1209{
1210 E_ERROR = 1, /* Fatal run-time errors. These indicate errors that can not be recovered
1211 * from, such as a memory allocation problem. Execution of the script is
1212 * halted.
1213 * The only fatal error under JX9 is an out-of-memory. All others erros
1214 * even a call to undefined function will not halt script execution.
1215 */
1216 E_WARNING , /* Run-time warnings (non-fatal errors). Execution of the script is not halted. */
1217 E_PARSE , /* Compile-time parse errors. Parse errors should only be generated by the parser.*/
1218 E_NOTICE , /* Run-time notices. Indicate that the script encountered something that could
1219 * indicate an error, but could also happen in the normal course of running a script.
1220 */
1221};
1222/*
1223 * Each VM instruction resulting from compiling a JX9 script is represented
1224 * by one of the following OP codes.
1225 * The program consists of a linear sequence of operations. Each operation
1226 * has an opcode and 3 operands.Operands P1 is an integer.
1227 * Operand P2 is an unsigned integer and operand P3 is a memory address.
1228 * Few opcodes use all 3 operands.
1229 */
1230enum jx9_vm_op {
1231 JX9_OP_DONE = 1, /* Done */
1232 JX9_OP_HALT, /* Halt */
1233 JX9_OP_LOAD, /* Load memory object */
1234 JX9_OP_LOADC, /* Load constant */
1235 JX9_OP_LOAD_IDX, /* Load array entry */
1236 JX9_OP_LOAD_MAP, /* Load hashmap('array') */
1237 JX9_OP_NOOP, /* NOOP */
1238 JX9_OP_JMP, /* Unconditional jump */
1239 JX9_OP_JZ, /* Jump on zero (FALSE jump) */
1240 JX9_OP_JNZ, /* Jump on non-zero (TRUE jump) */
1241 JX9_OP_POP, /* Stack POP */
1242 JX9_OP_CAT, /* Concatenation */
1243 JX9_OP_CVT_INT, /* Integer cast */
1244 JX9_OP_CVT_STR, /* String cast */
1245 JX9_OP_CVT_REAL, /* Float cast */
1246 JX9_OP_CALL, /* Function call */
1247 JX9_OP_UMINUS, /* Unary minus '-'*/
1248 JX9_OP_UPLUS, /* Unary plus '+'*/
1249 JX9_OP_BITNOT, /* Bitwise not '~' */
1250 JX9_OP_LNOT, /* Logical not '!' */
1251 JX9_OP_MUL, /* Multiplication '*' */
1252 JX9_OP_DIV, /* Division '/' */
1253 JX9_OP_MOD, /* Modulus '%' */
1254 JX9_OP_ADD, /* Add '+' */
1255 JX9_OP_SUB, /* Sub '-' */
1256 JX9_OP_SHL, /* Left shift '<<' */
1257 JX9_OP_SHR, /* Right shift '>>' */
1258 JX9_OP_LT, /* Less than '<' */
1259 JX9_OP_LE, /* Less or equal '<=' */
1260 JX9_OP_GT, /* Greater than '>' */
1261 JX9_OP_GE, /* Greater or equal '>=' */
1262 JX9_OP_EQ, /* Equal '==' */
1263 JX9_OP_NEQ, /* Not equal '!=' */
1264 JX9_OP_TEQ, /* Type equal '===' */
1265 JX9_OP_TNE, /* Type not equal '!==' */
1266 JX9_OP_BAND, /* Bitwise and '&' */
1267 JX9_OP_BXOR, /* Bitwise xor '^' */
1268 JX9_OP_BOR, /* Bitwise or '|' */
1269 JX9_OP_LAND, /* Logical and '&&','and' */
1270 JX9_OP_LOR, /* Logical or '||','or' */
1271 JX9_OP_LXOR, /* Logical xor 'xor' */
1272 JX9_OP_STORE, /* Store Object */
1273 JX9_OP_STORE_IDX, /* Store indexed object */
1274 JX9_OP_PULL, /* Stack pull */
1275 JX9_OP_SWAP, /* Stack swap */
1276 JX9_OP_YIELD, /* Stack yield */
1277 JX9_OP_CVT_BOOL, /* Boolean cast */
1278 JX9_OP_CVT_NUMC, /* Numeric (integer, real or both) type cast */
1279 JX9_OP_INCR, /* Increment ++ */
1280 JX9_OP_DECR, /* Decrement -- */
1281 JX9_OP_ADD_STORE, /* Add and store '+=' */
1282 JX9_OP_SUB_STORE, /* Sub and store '-=' */
1283 JX9_OP_MUL_STORE, /* Mul and store '*=' */
1284 JX9_OP_DIV_STORE, /* Div and store '/=' */
1285 JX9_OP_MOD_STORE, /* Mod and store '%=' */
1286 JX9_OP_CAT_STORE, /* Cat and store '.=' */
1287 JX9_OP_SHL_STORE, /* Shift left and store '>>=' */
1288 JX9_OP_SHR_STORE, /* Shift right and store '<<=' */
1289 JX9_OP_BAND_STORE, /* Bitand and store '&=' */
1290 JX9_OP_BOR_STORE, /* Bitor and store '|=' */
1291 JX9_OP_BXOR_STORE, /* Bitxor and store '^=' */
1292 JX9_OP_CONSUME, /* Consume VM output */
1293 JX9_OP_MEMBER, /* Object member run-time access */
1294 JX9_OP_UPLINK, /* Run-Time frame link */
1295 JX9_OP_CVT_NULL, /* NULL cast */
1296 JX9_OP_CVT_ARRAY, /* Array cast */
1297 JX9_OP_FOREACH_INIT, /* For each init */
1298 JX9_OP_FOREACH_STEP, /* For each step */
1299 JX9_OP_SWITCH /* Switch operation */
1300};
1301/* -- END-OF INSTRUCTIONS -- */
1302/*
1303 * Expression Operators ID.
1304 */
1305enum jx9_expr_id {
1306 EXPR_OP_DOT, /* Member access */
1307 EXPR_OP_DC, /* :: */
1308 EXPR_OP_SUBSCRIPT, /* []: Subscripting */
1309 EXPR_OP_FUNC_CALL, /* func_call() */
1310 EXPR_OP_INCR, /* ++ */
1311 EXPR_OP_DECR, /* -- */
1312 EXPR_OP_BITNOT, /* ~ */
1313 EXPR_OP_UMINUS, /* Unary minus */
1314 EXPR_OP_UPLUS, /* Unary plus */
1315 EXPR_OP_TYPECAST, /* Type cast [i.e: (int), (float), (string)...] */
1316 EXPR_OP_ALT, /* @ */
1317 EXPR_OP_INSTOF, /* instanceof */
1318 EXPR_OP_LOGNOT, /* logical not ! */
1319 EXPR_OP_MUL, /* Multiplication */
1320 EXPR_OP_DIV, /* division */
1321 EXPR_OP_MOD, /* Modulus */
1322 EXPR_OP_ADD, /* Addition */
1323 EXPR_OP_SUB, /* Substraction */
1324 EXPR_OP_DDOT, /* Concatenation */
1325 EXPR_OP_SHL, /* Left shift */
1326 EXPR_OP_SHR, /* Right shift */
1327 EXPR_OP_LT, /* Less than */
1328 EXPR_OP_LE, /* Less equal */
1329 EXPR_OP_GT, /* Greater than */
1330 EXPR_OP_GE, /* Greater equal */
1331 EXPR_OP_EQ, /* Equal == */
1332 EXPR_OP_NE, /* Not equal != <> */
1333 EXPR_OP_TEQ, /* Type equal === */
1334 EXPR_OP_TNE, /* Type not equal !== */
1335 EXPR_OP_SEQ, /* String equal 'eq' */
1336 EXPR_OP_SNE, /* String not equal 'ne' */
1337 EXPR_OP_BAND, /* Biwise and '&' */
1338 EXPR_OP_REF, /* Reference operator '&' */
1339 EXPR_OP_XOR, /* bitwise xor '^' */
1340 EXPR_OP_BOR, /* bitwise or '|' */
1341 EXPR_OP_LAND, /* Logical and '&&','and' */
1342 EXPR_OP_LOR, /* Logical or '||','or'*/
1343 EXPR_OP_LXOR, /* Logical xor 'xor' */
1344 EXPR_OP_QUESTY, /* Ternary operator '?' */
1345 EXPR_OP_ASSIGN, /* Assignment '=' */
1346 EXPR_OP_ADD_ASSIGN, /* Combined operator: += */
1347 EXPR_OP_SUB_ASSIGN, /* Combined operator: -= */
1348 EXPR_OP_MUL_ASSIGN, /* Combined operator: *= */
1349 EXPR_OP_DIV_ASSIGN, /* Combined operator: /= */
1350 EXPR_OP_MOD_ASSIGN, /* Combined operator: %= */
1351 EXPR_OP_DOT_ASSIGN, /* Combined operator: .= */
1352 EXPR_OP_AND_ASSIGN, /* Combined operator: &= */
1353 EXPR_OP_OR_ASSIGN, /* Combined operator: |= */
1354 EXPR_OP_XOR_ASSIGN, /* Combined operator: ^= */
1355 EXPR_OP_SHL_ASSIGN, /* Combined operator: <<= */
1356 EXPR_OP_SHR_ASSIGN, /* Combined operator: >>= */
1357 EXPR_OP_COMMA /* Comma expression */
1358};
1359/*
1360 * Lexer token codes
1361 * The following set of constants are the tokens recognized
1362 * by the lexer when processing JX9 input.
1363 * Important: Token values MUST BE A POWER OF TWO.
1364 */
1365#define JX9_TK_INTEGER 0x0000001 /* Integer */
1366#define JX9_TK_REAL 0x0000002 /* Real number */
1367#define JX9_TK_NUM (JX9_TK_INTEGER|JX9_TK_REAL) /* Numeric token, either integer or real */
1368#define JX9_TK_KEYWORD 0x0000004 /* Keyword [i.e: while, for, if, foreach...] */
1369#define JX9_TK_ID 0x0000008 /* Alphanumeric or UTF-8 stream */
1370#define JX9_TK_DOLLAR 0x0000010 /* '$' Dollar sign */
1371#define JX9_TK_OP 0x0000020 /* Operator [i.e: +, *, /...] */
1372#define JX9_TK_OCB 0x0000040 /* Open curly brace'{' */
1373#define JX9_TK_CCB 0x0000080 /* Closing curly brace'}' */
1374#define JX9_TK_DOT 0x0000100 /* Dot . */
1375#define JX9_TK_LPAREN 0x0000200 /* Left parenthesis '(' */
1376#define JX9_TK_RPAREN 0x0000400 /* Right parenthesis ')' */
1377#define JX9_TK_OSB 0x0000800 /* Open square bracket '[' */
1378#define JX9_TK_CSB 0x0001000 /* Closing square bracket ']' */
1379#define JX9_TK_DSTR 0x0002000 /* Double quoted string "$str" */
1380#define JX9_TK_SSTR 0x0004000 /* Single quoted string 'str' */
1381#define JX9_TK_NOWDOC 0x0010000 /* Nowdoc <<< */
1382#define JX9_TK_COMMA 0x0020000 /* Comma ',' */
1383#define JX9_TK_SEMI 0x0040000 /* Semi-colon ";" */
1384#define JX9_TK_BSTR 0x0080000 /* Backtick quoted string [i.e: Shell command `date`] */
1385#define JX9_TK_COLON 0x0100000 /* single Colon ':' */
1386#define JX9_TK_AMPER 0x0200000 /* Ampersand '&' */
1387#define JX9_TK_EQUAL 0x0400000 /* Equal '=' */
1388#define JX9_TK_OTHER 0x1000000 /* Other symbols */
1389/*
1390 * JX9 keyword.
1391 * These words have special meaning in JX9. Some of them represent things which look like
1392 * functions, some look like constants, and so on, but they're not, really: they are language constructs.
1393 * You cannot use any of the following words as constants, object names, function or method names.
1394 * Using them as variable names is generally OK, but could lead to confusion.
1395 */
1396#define JX9_TKWRD_SWITCH 1 /* switch */
1397#define JX9_TKWRD_PRINT 2 /* print */
1398#define JX9_TKWRD_ELIF 0x4000000 /* elseif: MUST BE A POWER OF TWO */
1399#define JX9_TKWRD_ELSE 0x8000000 /* else: MUST BE A POWER OF TWO */
1400#define JX9_TKWRD_IF 3 /* if */
1401#define JX9_TKWRD_STATIC 4 /* static */
1402#define JX9_TKWRD_CASE 5 /* case */
1403#define JX9_TKWRD_FUNCTION 6 /* function */
1404#define JX9_TKWRD_CONST 7 /* const */
1405/* The number '8' is reserved for JX9_TK_ID */
1406#define JX9_TKWRD_WHILE 9 /* while */
1407#define JX9_TKWRD_DEFAULT 10 /* default */
1408#define JX9_TKWRD_AS 11 /* as */
1409#define JX9_TKWRD_CONTINUE 12 /* continue */
1410#define JX9_TKWRD_EXIT 13 /* exit */
1411#define JX9_TKWRD_DIE 14 /* die */
1412#define JX9_TKWRD_IMPORT 15 /* import */
1413#define JX9_TKWRD_INCLUDE 16 /* include */
1414#define JX9_TKWRD_FOR 17 /* for */
1415#define JX9_TKWRD_FOREACH 18 /* foreach */
1416#define JX9_TKWRD_RETURN 19 /* return */
1417#define JX9_TKWRD_BREAK 20 /* break */
1418#define JX9_TKWRD_UPLINK 21 /* uplink */
1419#define JX9_TKWRD_BOOL 0x8000 /* bool: MUST BE A POWER OF TWO */
1420#define JX9_TKWRD_INT 0x10000 /* int: MUST BE A POWER OF TWO */
1421#define JX9_TKWRD_FLOAT 0x20000 /* float: MUST BE A POWER OF TWO */
1422#define JX9_TKWRD_STRING 0x40000 /* string: MUST BE A POWER OF TWO */
1423
1424/* api.c */
1425JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap);
1426JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName);
1427JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName);
1428/* json.c function prototypes */
1429JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut);
1430JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte);
1431/* memobj.c function prototypes */
1432JX9_PRIVATE sxi32 jx9MemObjDump(SyBlob *pOut, jx9_value *pObj);
1433JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal);
1434JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore);
1435JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest);
1436JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal);
1437JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray);
1438#if 0
1439/* Not used in the current release of the JX9 engine */
1440JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal);
1441#endif
1442JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal);
1443JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal);
1444JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj);
1445JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen);
1446#if 0
1447/* Not used in the current release of the JX9 engine */
1448JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap);
1449#endif
1450JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest);
1451JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest);
1452JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj);
1453JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj);
1454JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj);
1455JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags);
1456JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj);
1457JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj);
1458JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj);
1459JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj);
1460JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj);
1461JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj);
1462JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj);
1463JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj);
1464JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pData);
1465/* lex.c function prototypes */
1466JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput, sxu32 nLen, SySet *pOut);
1467/* vm.c function prototypes */
1468JX9_PRIVATE void jx9VmReleaseContextValue(jx9_context *pCtx, jx9_value *pValue);
1469JX9_PRIVATE sxi32 jx9VmInitFuncState(jx9_vm *pVm, jx9_vm_func *pFunc, const char *zName, sxu32 nByte,
1470 sxi32 iFlags, void *pUserData);
1471JX9_PRIVATE sxi32 jx9VmInstallUserFunction(jx9_vm *pVm, jx9_vm_func *pFunc, SyString *pName);
1472JX9_PRIVATE sxi32 jx9VmRegisterConstant(jx9_vm *pVm, const SyString *pName, ProcConstant xExpand, void *pUserData);
1473JX9_PRIVATE sxi32 jx9VmInstallForeignFunction(jx9_vm *pVm, const SyString *pName, ProcHostFunction xFunc, void *pUserData);
1474JX9_PRIVATE sxi32 jx9VmBlobConsumer(const void *pSrc, unsigned int nLen, void *pUserData);
1475JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIndex);
1476JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex);
1477JX9_PRIVATE sxi32 jx9VmOutputConsume(jx9_vm *pVm, SyString *pString);
1478JX9_PRIVATE sxi32 jx9VmOutputConsumeAp(jx9_vm *pVm, const char *zFormat, va_list ap);
1479JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap);
1480JX9_PRIVATE sxi32 jx9VmThrowError(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zMessage);
1481JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData);
1482JX9_PRIVATE sxi32 jx9VmDump(jx9_vm *pVm, ProcConsumer xConsumer, void *pUserData);
1483JX9_PRIVATE sxi32 jx9VmInit(jx9_vm *pVm, jx9 *pEngine);
1484JX9_PRIVATE sxi32 jx9VmConfigure(jx9_vm *pVm, sxi32 nOp, va_list ap);
1485JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm);
1486JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar);
1487JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm);
1488JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm);
1489JX9_PRIVATE sxi32 jx9VmMakeReady(jx9_vm *pVm);
1490JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm);
1491JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm);
1492JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm);
1493JX9_PRIVATE VmInstr *jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex);
1494JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm);
1495JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer);
1496JX9_PRIVATE sxi32 jx9VmEmitInstr(jx9_vm *pVm, sxi32 iOp, sxi32 iP1, sxu32 iP2, void *p3, sxu32 *pIndex);
1497JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm);
1498JX9_PRIVATE sxi32 jx9VmCallUserFunction(jx9_vm *pVm, jx9_value *pFunc, int nArg, jx9_value **apArg, jx9_value *pResult);
1499JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp(jx9_vm *pVm, jx9_value *pFunc, jx9_value *pResult, ...);
1500JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm, sxu32 nObjIdx);
1501JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen);
1502JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue);
1503JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew);
1504#ifndef JX9_DISABLE_BUILTIN_FUNC
1505JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(jx9_vm *pVm, const char **pzDevice, int nByte);
1506#endif /* JX9_DISABLE_BUILTIN_FUNC */
1507JX9_PRIVATE int jx9Utf8Read(
1508 const unsigned char *z, /* First byte of UTF-8 character */
1509 const unsigned char *zTerm, /* Pretend this byte is 0x00 */
1510 const unsigned char **pzNext /* Write first byte past UTF-8 char here */
1511);
1512/* parse.c function prototypes */
1513JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID);
1514JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot);
1515JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart, SyToken *pEnd, SyToken **ppNext);
1516JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn, SyToken *pEnd, sxu32 nTokStart, sxu32 nTokEnd, SyToken **ppEnd);
1517JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast);
1518JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet);
1519/* compile.c function prototypes */
1520JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType);
1521JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen, sxi32 iCompileFlag);
1522JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag);
1523JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag);
1524JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen, sxi32 iCompileFlag);
1525JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen, sxi32 iCompileFlag);
1526JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag);
1527JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag);
1528JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen, sxi32 iCompileFlag);
1529JX9_PRIVATE sxi32 jx9InitCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData);
1530JX9_PRIVATE sxi32 jx9ResetCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData);
1531JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...);
1532JX9_PRIVATE sxi32 jx9CompileScript(jx9_vm *pVm, SyString *pScript, sxi32 iFlags);
1533/* constant.c function prototypes */
1534JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm);
1535/* builtin.c function prototypes */
1536JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm);
1537/* hashmap.c function prototypes */
1538JX9_PRIVATE jx9_hashmap * jx9NewHashmap(jx9_vm *pVm, sxu32 (*xIntHash)(sxi64), sxu32 (*xBlobHash)(const void *, sxu32));
1539JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm);
1540JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS);
1541JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap);
1542JX9_PRIVATE sxi32 jx9HashmapLookup(jx9_hashmap *pMap, jx9_value *pKey, jx9_hashmap_node **ppNode);
1543JX9_PRIVATE sxi32 jx9HashmapInsert(jx9_hashmap *pMap, jx9_value *pKey, jx9_value *pVal);
1544JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight);
1545JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest);
1546JX9_PRIVATE sxi32 jx9HashmapCmp(jx9_hashmap *pLeft, jx9_hashmap *pRight, int bStrict);
1547JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap);
1548JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap);
1549JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode);
1550JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore);
1551JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode, jx9_value *pKey);
1552JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm);
1553JX9_PRIVATE sxi32 jx9HashmapWalk(jx9_hashmap *pMap, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData);
1554#ifndef JX9_DISABLE_BUILTIN_FUNC
1555JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut);
1556/* builtin.c function prototypes */
1557JX9_PRIVATE sxi32 jx9InputFormat(int (*xConsumer)(jx9_context *, const char *, int, void *),
1558 jx9_context *pCtx, const char *zIn, int nByte, int nArg, jx9_value **apArg, void *pUserData, int vf);
1559JX9_PRIVATE sxi32 jx9ProcessCsv(const char *zInput, int nByte, int delim, int encl,
1560 int escape, sxi32 (*xConsumer)(const char *, int, void *), void *pUserData);
1561JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData);
1562JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen);
1563JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection);
1564#endif
1565/* vfs.c */
1566#ifndef JX9_DISABLE_BUILTIN_FUNC
1567JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile,
1568 int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew);
1569JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut);
1570JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle);
1571#endif /* JX9_DISABLE_BUILTIN_FUNC */
1572JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen);
1573JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm);
1574JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void);
1575JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm);
1576JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm);
1577JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm);
1578/* lib.c function prototypes */
1579#ifndef JX9_DISABLE_BUILTIN_FUNC
1580JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp);
1581JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch);
1582JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch);
1583JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry);
1584JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen);
1585#endif /* JX9_DISABLE_BUILTIN_FUNC */
1586#ifndef JX9_DISABLE_BUILTIN_FUNC
1587JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData);
1588#endif /* JX9_DISABLE_BUILTIN_FUNC */
1589#ifndef JX9_DISABLE_BUILTIN_FUNC
1590#ifndef JX9_DISABLE_HASH_FUNC
1591JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen);
1592JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len);
1593JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx);
1594JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx);
1595JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]);
1596JX9_PRIVATE void SHA1Init(SHA1Context *context);
1597JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len);
1598JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]);
1599JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]);
1600#endif
1601#endif /* JX9_DISABLE_BUILTIN_FUNC */
1602JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen);
1603JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void *pUserData);
1604JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...);
1605JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap);
1606JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...);
1607JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...);
1608#ifndef JX9_DISABLE_BUILTIN_FUNC
1609JX9_PRIVATE const char *SyTimeGetMonth(sxi32 iMonth);
1610JX9_PRIVATE const char *SyTimeGetDay(sxi32 iDay);
1611#endif /* JX9_DISABLE_BUILTIN_FUNC */
1612JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8);
1613#ifndef JX9_DISABLE_BUILTIN_FUNC
1614JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData);
1615#endif
1616JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex);
1617JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp);
1618JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData);
1619#ifndef JX9_DISABLE_BUILTIN_FUNC
1620JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData);
1621JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData);
1622#endif /* JX9_DISABLE_BUILTIN_FUNC */
1623JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen);
1624JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
1625JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
1626JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
1627JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
1628JX9_PRIVATE sxi32 SyHexToint(sxi32 c);
1629JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
1630JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest);
1631JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail);
1632JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData);
1633JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32(*xStep)(SyHashEntry *, void *), void *pUserData);
1634JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData);
1635JX9_PRIVATE SyHashEntry *SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen);
1636JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash);
1637JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp);
1638JX9_PRIVATE void *SySetAt(SySet *pSet, sxu32 nIdx);
1639JX9_PRIVATE void *SySetPop(SySet *pSet);
1640JX9_PRIVATE void *SySetPeek(SySet *pSet);
1641JX9_PRIVATE sxi32 SySetRelease(SySet *pSet);
1642JX9_PRIVATE sxi32 SySetReset(SySet *pSet);
1643JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet);
1644JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry);
1645JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem);
1646JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem);
1647JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize);
1648#ifndef JX9_DISABLE_BUILTIN_FUNC
1649JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft);
1650#endif
1651JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob);
1652JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob);
1653JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen);
1654JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest);
1655JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob);
1656JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize);
1657JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte);
1658JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator);
1659JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize);
1660JX9_PRIVATE char *SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize);
1661JX9_PRIVATE void *SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize);
1662JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend);
1663JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void *pUserData);
1664JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void *pUserData);
1665JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent);
1666#if 0
1667/* Not used in the current release of the JX9 engine */
1668JX9_PRIVATE void *SyMemBackendPoolRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte);
1669#endif
1670JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void *pChunk);
1671JX9_PRIVATE void *SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte);
1672JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void *pChunk);
1673JX9_PRIVATE void *SyMemBackendRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte);
1674JX9_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte);
1675JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen);
1676JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize);
1677JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize);
1678JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen);
1679JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen);
1680#if !defined(JX9_DISABLE_BUILTIN_FUNC) || defined(__APPLE__)
1681JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen);
1682#endif
1683JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos);
1684#ifndef JX9_DISABLE_BUILTIN_FUNC
1685JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos);
1686#endif
1687JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos);
1688JX9_PRIVATE sxu32 SyStrlen(const char *zSrc);
1689#if defined(JX9_ENABLE_THREADS)
1690JX9_PRIVATE const SyMutexMethods *SyMutexExportMethods(void);
1691JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods);
1692JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend);
1693#endif
1694JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb);
1695JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB);
1696JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb);
1697JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB);
1698JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64);
1699JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64);
1700JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64);
1701JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32);
1702JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16);
1703JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut);
1704JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut);
1705#endif /* __JX9INT_H__ */
diff --git a/common/unqlite/jx9_api.c b/common/unqlite/jx9_api.c
new file mode 100644
index 0000000..efad097
--- /dev/null
+++ b/common/unqlite/jx9_api.c
@@ -0,0 +1,1744 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: api.c v1.7 FreeBSD 2012-12-18 06:54 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* This file implement the public interfaces presented to host-applications.
18 * Routines in other files are for internal use by JX9 and should not be
19 * accessed by users of the library.
20 */
21#define JX9_ENGINE_MAGIC 0xF874BCD7
22#define JX9_ENGINE_MISUSE(ENGINE) (ENGINE == 0 || ENGINE->nMagic != JX9_ENGINE_MAGIC)
23#define JX9_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE)
24/* If another thread have released a working instance, the following macros
25 * evaluates to true. These macros are only used when the library
26 * is built with threading support enabled which is not the case in
27 * the default built.
28 */
29#define JX9_THRD_ENGINE_RELEASE(ENGINE) (ENGINE->nMagic != JX9_ENGINE_MAGIC)
30#define JX9_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE)
31/* IMPLEMENTATION: jx9@embedded@symisc 311-12-32 */
32/*
33 * All global variables are collected in the structure named "sJx9MPGlobal".
34 * That way it is clear in the code when we are using static variable because
35 * its name start with sJx9MPGlobal.
36 */
37static struct Jx9Global_Data
38{
39 SyMemBackend sAllocator; /* Global low level memory allocator */
40#if defined(JX9_ENABLE_THREADS)
41 const SyMutexMethods *pMutexMethods; /* Mutex methods */
42 SyMutex *pMutex; /* Global mutex */
43 sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded
44 * The threading level can be set using the [jx9_lib_config()]
45 * interface with a configuration verb set to
46 * JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE or
47 * JX9_LIB_CONFIG_THREAD_LEVEL_MULTI
48 */
49#endif
50 const jx9_vfs *pVfs; /* Underlying virtual file system */
51 sxi32 nEngine; /* Total number of active engines */
52 jx9 *pEngines; /* List of active engine */
53 sxu32 nMagic; /* Sanity check against library misuse */
54}sJx9MPGlobal = {
55 {0, 0, 0, 0, 0, 0, 0, 0, {0}},
56#if defined(JX9_ENABLE_THREADS)
57 0,
58 0,
59 0,
60#endif
61 0,
62 0,
63 0,
64 0
65};
66#define JX9_LIB_MAGIC 0xEA1495BA
67#define JX9_LIB_MISUSE (sJx9MPGlobal.nMagic != JX9_LIB_MAGIC)
68/*
69 * Supported threading level.
70 * These options have meaning only when the library is compiled with multi-threading
71 * support.That is, the JX9_ENABLE_THREADS compile time directive must be defined
72 * when JX9 is built.
73 * JX9_THREAD_LEVEL_SINGLE:
74 * In this mode, mutexing is disabled and the library can only be used by a single thread.
75 * JX9_THREAD_LEVEL_MULTI
76 * In this mode, all mutexes including the recursive mutexes on [jx9] objects
77 * are enabled so that the application is free to share the same engine
78 * between different threads at the same time.
79 */
80#define JX9_THREAD_LEVEL_SINGLE 1
81#define JX9_THREAD_LEVEL_MULTI 2
82/*
83 * Configure a running JX9 engine instance.
84 * return JX9_OK on success.Any other return
85 * value indicates failure.
86 * Refer to [jx9_config()].
87 */
88JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap)
89{
90 jx9_conf *pConf = &pEngine->xConf;
91 int rc = JX9_OK;
92 /* Perform the requested operation */
93 switch(nOp){
94 case JX9_CONFIG_ERR_LOG:{
95 /* Extract compile-time error log if any */
96 const char **pzPtr = va_arg(ap, const char **);
97 int *pLen = va_arg(ap, int *);
98 if( pzPtr == 0 ){
99 rc = JX9_CORRUPT;
100 break;
101 }
102 /* NULL terminate the error-log buffer */
103 SyBlobNullAppend(&pConf->sErrConsumer);
104 /* Point to the error-log buffer */
105 *pzPtr = (const char *)SyBlobData(&pConf->sErrConsumer);
106 if( pLen ){
107 if( SyBlobLength(&pConf->sErrConsumer) > 1 /* NULL '\0' terminator */ ){
108 *pLen = (int)SyBlobLength(&pConf->sErrConsumer);
109 }else{
110 *pLen = 0;
111 }
112 }
113 break;
114 }
115 case JX9_CONFIG_ERR_ABORT:
116 /* Reserved for future use */
117 break;
118 default:
119 /* Unknown configuration verb */
120 rc = JX9_CORRUPT;
121 break;
122 } /* Switch() */
123 return rc;
124}
125/*
126 * Configure the JX9 library.
127 * Return JX9_OK on success. Any other return value indicates failure.
128 * Refer to [jx9_lib_config()].
129 */
130static sxi32 Jx9CoreConfigure(sxi32 nOp, va_list ap)
131{
132 int rc = JX9_OK;
133 switch(nOp){
134 case JX9_LIB_CONFIG_VFS:{
135 /* Install a virtual file system */
136 const jx9_vfs *pVfs = va_arg(ap, const jx9_vfs *);
137 sJx9MPGlobal.pVfs = pVfs;
138 break;
139 }
140 case JX9_LIB_CONFIG_USER_MALLOC: {
141 /* Use an alternative low-level memory allocation routines */
142 const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *);
143 /* Save the memory failure callback (if available) */
144 ProcMemError xMemErr = sJx9MPGlobal.sAllocator.xMemError;
145 void *pMemErr = sJx9MPGlobal.sAllocator.pUserData;
146 if( pMethods == 0 ){
147 /* Use the built-in memory allocation subsystem */
148 rc = SyMemBackendInit(&sJx9MPGlobal.sAllocator, xMemErr, pMemErr);
149 }else{
150 rc = SyMemBackendInitFromOthers(&sJx9MPGlobal.sAllocator, pMethods, xMemErr, pMemErr);
151 }
152 break;
153 }
154 case JX9_LIB_CONFIG_MEM_ERR_CALLBACK: {
155 /* Memory failure callback */
156 ProcMemError xMemErr = va_arg(ap, ProcMemError);
157 void *pUserData = va_arg(ap, void *);
158 sJx9MPGlobal.sAllocator.xMemError = xMemErr;
159 sJx9MPGlobal.sAllocator.pUserData = pUserData;
160 break;
161 }
162 case JX9_LIB_CONFIG_USER_MUTEX: {
163#if defined(JX9_ENABLE_THREADS)
164 /* Use an alternative low-level mutex subsystem */
165 const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *);
166#if defined (UNTRUST)
167 if( pMethods == 0 ){
168 rc = JX9_CORRUPT;
169 }
170#endif
171 /* Sanity check */
172 if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){
173 /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */
174 rc = JX9_CORRUPT;
175 break;
176 }
177 if( sJx9MPGlobal.pMutexMethods ){
178 /* Overwrite the previous mutex subsystem */
179 SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex);
180 if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){
181 sJx9MPGlobal.pMutexMethods->xGlobalRelease();
182 }
183 sJx9MPGlobal.pMutex = 0;
184 }
185 /* Initialize and install the new mutex subsystem */
186 if( pMethods->xGlobalInit ){
187 rc = pMethods->xGlobalInit();
188 if ( rc != JX9_OK ){
189 break;
190 }
191 }
192 /* Create the global mutex */
193 sJx9MPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST);
194 if( sJx9MPGlobal.pMutex == 0 ){
195 /*
196 * If the supplied mutex subsystem is so sick that we are unable to
197 * create a single mutex, there is no much we can do here.
198 */
199 if( pMethods->xGlobalRelease ){
200 pMethods->xGlobalRelease();
201 }
202 rc = JX9_CORRUPT;
203 break;
204 }
205 sJx9MPGlobal.pMutexMethods = pMethods;
206 if( sJx9MPGlobal.nThreadingLevel == 0 ){
207 /* Set a default threading level */
208 sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI;
209 }
210#endif
211 break;
212 }
213 case JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE:
214#if defined(JX9_ENABLE_THREADS)
215 /* Single thread mode(Only one thread is allowed to play with the library) */
216 sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_SINGLE;
217#endif
218 break;
219 case JX9_LIB_CONFIG_THREAD_LEVEL_MULTI:
220#if defined(JX9_ENABLE_THREADS)
221 /* Multi-threading mode (library is thread safe and JX9 engines and virtual machines
222 * may be shared between multiple threads).
223 */
224 sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI;
225#endif
226 break;
227 default:
228 /* Unknown configuration option */
229 rc = JX9_CORRUPT;
230 break;
231 }
232 return rc;
233}
234/*
235 * [CAPIREF: jx9_lib_config()]
236 * Please refer to the official documentation for function purpose and expected parameters.
237 */
238JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...)
239{
240 va_list ap;
241 int rc;
242 if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){
243 /* Library is already initialized, this operation is forbidden */
244 return JX9_LOOKED;
245 }
246 va_start(ap, nConfigOp);
247 rc = Jx9CoreConfigure(nConfigOp, ap);
248 va_end(ap);
249 return rc;
250}
251/*
252 * Global library initialization
253 * Refer to [jx9_lib_init()]
254 * This routine must be called to initialize the memory allocation subsystem, the mutex
255 * subsystem prior to doing any serious work with the library.The first thread to call
256 * this routine does the initialization process and set the magic number so no body later
257 * can re-initialize the library.If subsequent threads call this routine before the first
258 * thread have finished the initialization process, then the subsequent threads must block
259 * until the initialization process is done.
260 */
261static sxi32 Jx9CoreInitialize(void)
262{
263 const jx9_vfs *pVfs; /* Built-in vfs */
264#if defined(JX9_ENABLE_THREADS)
265 const SyMutexMethods *pMutexMethods = 0;
266 SyMutex *pMaster = 0;
267#endif
268 int rc;
269 /*
270 * If the library is already initialized, then a call to this routine
271 * is a no-op.
272 */
273 if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){
274 return JX9_OK; /* Already initialized */
275 }
276 /* Point to the built-in vfs */
277 pVfs = jx9ExportBuiltinVfs();
278 /* Install it */
279 jx9_lib_config(JX9_LIB_CONFIG_VFS, pVfs);
280#if defined(JX9_ENABLE_THREADS)
281 if( sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_SINGLE ){
282 pMutexMethods = sJx9MPGlobal.pMutexMethods;
283 if( pMutexMethods == 0 ){
284 /* Use the built-in mutex subsystem */
285 pMutexMethods = SyMutexExportMethods();
286 if( pMutexMethods == 0 ){
287 return JX9_CORRUPT; /* Can't happen */
288 }
289 /* Install the mutex subsystem */
290 rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MUTEX, pMutexMethods);
291 if( rc != JX9_OK ){
292 return rc;
293 }
294 }
295 /* Obtain a static mutex so we can initialize the library without calling malloc() */
296 pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1);
297 if( pMaster == 0 ){
298 return JX9_CORRUPT; /* Can't happen */
299 }
300 }
301 /* Lock the master mutex */
302 rc = JX9_OK;
303 SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
304 if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){
305#endif
306 if( sJx9MPGlobal.sAllocator.pMethods == 0 ){
307 /* Install a memory subsystem */
308 rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */
309 if( rc != JX9_OK ){
310 /* If we are unable to initialize the memory backend, there is no much we can do here.*/
311 goto End;
312 }
313 }
314#if defined(JX9_ENABLE_THREADS)
315 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){
316 /* Protect the memory allocation subsystem */
317 rc = SyMemBackendMakeThreadSafe(&sJx9MPGlobal.sAllocator, sJx9MPGlobal.pMutexMethods);
318 if( rc != JX9_OK ){
319 goto End;
320 }
321 }
322#endif
323 /* Our library is initialized, set the magic number */
324 sJx9MPGlobal.nMagic = JX9_LIB_MAGIC;
325 rc = JX9_OK;
326#if defined(JX9_ENABLE_THREADS)
327 } /* sJx9MPGlobal.nMagic != JX9_LIB_MAGIC */
328#endif
329End:
330#if defined(JX9_ENABLE_THREADS)
331 /* Unlock the master mutex */
332 SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
333#endif
334 return rc;
335}
336/*
337 * Release an active JX9 engine and it's associated active virtual machines.
338 */
339static sxi32 EngineRelease(jx9 *pEngine)
340{
341 jx9_vm *pVm, *pNext;
342 /* Release all active VM */
343 pVm = pEngine->pVms;
344 for(;;){
345 if( pEngine->iVm < 1 ){
346 break;
347 }
348 pNext = pVm->pNext;
349 jx9VmRelease(pVm);
350 pVm = pNext;
351 pEngine->iVm--;
352 }
353 /* Set a dummy magic number */
354 pEngine->nMagic = 0x7635;
355 /* Release the private memory subsystem */
356 SyMemBackendRelease(&pEngine->sAllocator);
357 return JX9_OK;
358}
359/*
360 * Release all resources consumed by the library.
361 * If JX9 is already shut when this routine is invoked then this
362 * routine is a harmless no-op.
363 * Note: This call is not thread safe. Refer to [jx9_lib_shutdown()].
364 */
365static void JX9CoreShutdown(void)
366{
367 jx9 *pEngine, *pNext;
368 /* Release all active engines first */
369 pEngine = sJx9MPGlobal.pEngines;
370 for(;;){
371 if( sJx9MPGlobal.nEngine < 1 ){
372 break;
373 }
374 pNext = pEngine->pNext;
375 EngineRelease(pEngine);
376 pEngine = pNext;
377 sJx9MPGlobal.nEngine--;
378 }
379#if defined(JX9_ENABLE_THREADS)
380 /* Release the mutex subsystem */
381 if( sJx9MPGlobal.pMutexMethods ){
382 if( sJx9MPGlobal.pMutex ){
383 SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex);
384 sJx9MPGlobal.pMutex = 0;
385 }
386 if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){
387 sJx9MPGlobal.pMutexMethods->xGlobalRelease();
388 }
389 sJx9MPGlobal.pMutexMethods = 0;
390 }
391 sJx9MPGlobal.nThreadingLevel = 0;
392#endif
393 if( sJx9MPGlobal.sAllocator.pMethods ){
394 /* Release the memory backend */
395 SyMemBackendRelease(&sJx9MPGlobal.sAllocator);
396 }
397 sJx9MPGlobal.nMagic = 0x1928;
398}
399/*
400 * [CAPIREF: jx9_lib_shutdown()]
401 * Please refer to the official documentation for function purpose and expected parameters.
402 */
403JX9_PRIVATE int jx9_lib_shutdown(void)
404{
405 if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){
406 /* Already shut */
407 return JX9_OK;
408 }
409 JX9CoreShutdown();
410 return JX9_OK;
411}
412/*
413 * [CAPIREF: jx9_lib_signature()]
414 * Please refer to the official documentation for function purpose and expected parameters.
415 */
416JX9_PRIVATE const char * jx9_lib_signature(void)
417{
418 return JX9_SIG;
419}
420/*
421 * [CAPIREF: jx9_init()]
422 * Please refer to the official documentation for function purpose and expected parameters.
423 */
424JX9_PRIVATE int jx9_init(jx9 **ppEngine)
425{
426 jx9 *pEngine;
427 int rc;
428#if defined(UNTRUST)
429 if( ppEngine == 0 ){
430 return JX9_CORRUPT;
431 }
432#endif
433 *ppEngine = 0;
434 /* One-time automatic library initialization */
435 rc = Jx9CoreInitialize();
436 if( rc != JX9_OK ){
437 return rc;
438 }
439 /* Allocate a new engine */
440 pEngine = (jx9 *)SyMemBackendPoolAlloc(&sJx9MPGlobal.sAllocator, sizeof(jx9));
441 if( pEngine == 0 ){
442 return JX9_NOMEM;
443 }
444 /* Zero the structure */
445 SyZero(pEngine, sizeof(jx9));
446 /* Initialize engine fields */
447 pEngine->nMagic = JX9_ENGINE_MAGIC;
448 rc = SyMemBackendInitFromParent(&pEngine->sAllocator, &sJx9MPGlobal.sAllocator);
449 if( rc != JX9_OK ){
450 goto Release;
451 }
452#if defined(JX9_ENABLE_THREADS)
453 SyMemBackendDisbaleMutexing(&pEngine->sAllocator);
454#endif
455 /* Default configuration */
456 SyBlobInit(&pEngine->xConf.sErrConsumer, &pEngine->sAllocator);
457 /* Install a default compile-time error consumer routine */
458 pEngine->xConf.xErr = jx9VmBlobConsumer;
459 pEngine->xConf.pErrData = &pEngine->xConf.sErrConsumer;
460 /* Built-in vfs */
461 pEngine->pVfs = sJx9MPGlobal.pVfs;
462#if defined(JX9_ENABLE_THREADS)
463 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){
464 /* Associate a recursive mutex with this instance */
465 pEngine->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE);
466 if( pEngine->pMutex == 0 ){
467 rc = JX9_NOMEM;
468 goto Release;
469 }
470 }
471#endif
472 /* Link to the list of active engines */
473#if defined(JX9_ENABLE_THREADS)
474 /* Enter the global mutex */
475 SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
476#endif
477 MACRO_LD_PUSH(sJx9MPGlobal.pEngines, pEngine);
478 sJx9MPGlobal.nEngine++;
479#if defined(JX9_ENABLE_THREADS)
480 /* Leave the global mutex */
481 SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
482#endif
483 /* Write a pointer to the new instance */
484 *ppEngine = pEngine;
485 return JX9_OK;
486Release:
487 SyMemBackendRelease(&pEngine->sAllocator);
488 SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator,pEngine);
489 return rc;
490}
491/*
492 * [CAPIREF: jx9_release()]
493 * Please refer to the official documentation for function purpose and expected parameters.
494 */
495JX9_PRIVATE int jx9_release(jx9 *pEngine)
496{
497 int rc;
498 if( JX9_ENGINE_MISUSE(pEngine) ){
499 return JX9_CORRUPT;
500 }
501#if defined(JX9_ENABLE_THREADS)
502 /* Acquire engine mutex */
503 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
504 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
505 JX9_THRD_ENGINE_RELEASE(pEngine) ){
506 return JX9_ABORT; /* Another thread have released this instance */
507 }
508#endif
509 /* Release the engine */
510 rc = EngineRelease(&(*pEngine));
511#if defined(JX9_ENABLE_THREADS)
512 /* Leave engine mutex */
513 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
514 /* Release engine mutex */
515 SyMutexRelease(sJx9MPGlobal.pMutexMethods, pEngine->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
516#endif
517#if defined(JX9_ENABLE_THREADS)
518 /* Enter the global mutex */
519 SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
520#endif
521 /* Unlink from the list of active engines */
522 MACRO_LD_REMOVE(sJx9MPGlobal.pEngines, pEngine);
523 sJx9MPGlobal.nEngine--;
524#if defined(JX9_ENABLE_THREADS)
525 /* Leave the global mutex */
526 SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */
527#endif
528 /* Release the memory chunk allocated to this engine */
529 SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator, pEngine);
530 return rc;
531}
532/*
533 * Compile a raw JX9 script.
534 * To execute a JX9 code, it must first be compiled into a bytecode program using this routine.
535 * If something goes wrong [i.e: compile-time error], your error log [i.e: error consumer callback]
536 * should display the appropriate error message and this function set ppVm to null and return
537 * an error code that is different from JX9_OK. Otherwise when the script is successfully compiled
538 * ppVm should hold the JX9 bytecode and it's safe to call [jx9_vm_exec(), jx9_vm_reset(), etc.].
539 * This API does not actually evaluate the JX9 code. It merely compile and prepares the JX9 script
540 * for evaluation.
541 */
542static sxi32 ProcessScript(
543 jx9 *pEngine, /* Running JX9 engine */
544 jx9_vm **ppVm, /* OUT: A pointer to the virtual machine */
545 SyString *pScript, /* Raw JX9 script to compile */
546 sxi32 iFlags, /* Compile-time flags */
547 const char *zFilePath /* File path if script come from a file. NULL otherwise */
548 )
549{
550 jx9_vm *pVm;
551 int rc;
552 /* Allocate a new virtual machine */
553 pVm = (jx9_vm *)SyMemBackendPoolAlloc(&pEngine->sAllocator, sizeof(jx9_vm));
554 if( pVm == 0 ){
555 /* If the supplied memory subsystem is so sick that we are unable to allocate
556 * a tiny chunk of memory, there is no much we can do here. */
557 if( ppVm ){
558 *ppVm = 0;
559 }
560 return JX9_NOMEM;
561 }
562 if( iFlags < 0 ){
563 /* Default compile-time flags */
564 iFlags = 0;
565 }
566 /* Initialize the Virtual Machine */
567 rc = jx9VmInit(pVm, &(*pEngine));
568 if( rc != JX9_OK ){
569 SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
570 if( ppVm ){
571 *ppVm = 0;
572 }
573 return JX9_VM_ERR;
574 }
575 if( zFilePath ){
576 /* Push processed file path */
577 jx9VmPushFilePath(pVm, zFilePath, -1, TRUE, 0);
578 }
579 /* Reset the error message consumer */
580 SyBlobReset(&pEngine->xConf.sErrConsumer);
581 /* Compile the script */
582 jx9CompileScript(pVm, &(*pScript), iFlags);
583 if( pVm->sCodeGen.nErr > 0 || pVm == 0){
584 sxu32 nErr = pVm->sCodeGen.nErr;
585 /* Compilation error or null ppVm pointer, release this VM */
586 SyMemBackendRelease(&pVm->sAllocator);
587 SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
588 if( ppVm ){
589 *ppVm = 0;
590 }
591 return nErr > 0 ? JX9_COMPILE_ERR : JX9_OK;
592 }
593 /* Prepare the virtual machine for bytecode execution */
594 rc = jx9VmMakeReady(pVm);
595 if( rc != JX9_OK ){
596 goto Release;
597 }
598 /* Install local import path which is the current directory */
599 jx9_vm_config(pVm, JX9_VM_CONFIG_IMPORT_PATH, "./");
600#if defined(JX9_ENABLE_THREADS)
601 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){
602 /* Associate a recursive mutex with this instance */
603 pVm->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE);
604 if( pVm->pMutex == 0 ){
605 goto Release;
606 }
607 }
608#endif
609 /* Script successfully compiled, link to the list of active virtual machines */
610 MACRO_LD_PUSH(pEngine->pVms, pVm);
611 pEngine->iVm++;
612 /* Point to the freshly created VM */
613 *ppVm = pVm;
614 /* Ready to execute JX9 bytecode */
615 return JX9_OK;
616Release:
617 SyMemBackendRelease(&pVm->sAllocator);
618 SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
619 *ppVm = 0;
620 return JX9_VM_ERR;
621}
622/*
623 * [CAPIREF: jx9_compile()]
624 * Please refer to the official documentation for function purpose and expected parameters.
625 */
626JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm)
627{
628 SyString sScript;
629 int rc;
630 if( JX9_ENGINE_MISUSE(pEngine) ){
631 return JX9_CORRUPT;
632 }
633 if( zSource == 0 ){
634 /* Empty Jx9 statement ';' */
635 zSource = ";";
636 nLen = (int)sizeof(char);
637 }
638 if( nLen < 0 ){
639 /* Compute input length automatically */
640 nLen = (int)SyStrlen(zSource);
641 }
642 SyStringInitFromBuf(&sScript, zSource, nLen);
643#if defined(JX9_ENABLE_THREADS)
644 /* Acquire engine mutex */
645 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
646 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
647 JX9_THRD_ENGINE_RELEASE(pEngine) ){
648 return JX9_ABORT; /* Another thread have released this instance */
649 }
650#endif
651 /* Compile the script */
652 rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,0,0);
653#if defined(JX9_ENABLE_THREADS)
654 /* Leave engine mutex */
655 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
656#endif
657 /* Compilation result */
658 return rc;
659}
660/*
661 * [CAPIREF: jx9_compile_file()]
662 * Please refer to the official documentation for function purpose and expected parameters.
663 */
664JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm)
665{
666 const jx9_vfs *pVfs;
667 int rc;
668 if( ppOutVm ){
669 *ppOutVm = 0;
670 }
671 rc = JX9_OK; /* cc warning */
672 if( JX9_ENGINE_MISUSE(pEngine) || SX_EMPTY_STR(zFilePath) ){
673 return JX9_CORRUPT;
674 }
675#if defined(JX9_ENABLE_THREADS)
676 /* Acquire engine mutex */
677 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
678 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
679 JX9_THRD_ENGINE_RELEASE(pEngine) ){
680 return JX9_ABORT; /* Another thread have released this instance */
681 }
682#endif
683 /*
684 * Check if the underlying vfs implement the memory map
685 * [i.e: mmap() under UNIX/MapViewOfFile() under windows] function.
686 */
687 pVfs = pEngine->pVfs;
688 if( pVfs == 0 || pVfs->xMmap == 0 ){
689 /* Memory map routine not implemented */
690 rc = JX9_IO_ERR;
691 }else{
692 void *pMapView = 0; /* cc warning */
693 jx9_int64 nSize = 0; /* cc warning */
694 SyString sScript;
695 /* Try to get a memory view of the whole file */
696 rc = pVfs->xMmap(zFilePath, &pMapView, &nSize);
697 if( rc != JX9_OK ){
698 /* Assume an IO error */
699 rc = JX9_IO_ERR;
700 }else{
701 /* Compile the file */
702 SyStringInitFromBuf(&sScript, pMapView, nSize);
703 rc = ProcessScript(&(*pEngine), ppOutVm, &sScript,0,zFilePath);
704 /* Release the memory view of the whole file */
705 if( pVfs->xUnmap ){
706 pVfs->xUnmap(pMapView, nSize);
707 }
708 }
709 }
710#if defined(JX9_ENABLE_THREADS)
711 /* Leave engine mutex */
712 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
713#endif
714 /* Compilation result */
715 return rc;
716}
717/*
718 * [CAPIREF: jx9_vm_config()]
719 * Please refer to the official documentation for function purpose and expected parameters.
720 */
721JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...)
722{
723 va_list ap;
724 int rc;
725 /* Ticket 1433-002: NULL VM is harmless operation */
726 if ( JX9_VM_MISUSE(pVm) ){
727 return JX9_CORRUPT;
728 }
729#if defined(JX9_ENABLE_THREADS)
730 /* Acquire VM mutex */
731 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
732 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
733 JX9_THRD_VM_RELEASE(pVm) ){
734 return JX9_ABORT; /* Another thread have released this instance */
735 }
736#endif
737 /* Confiugure the virtual machine */
738 va_start(ap, iConfigOp);
739 rc = jx9VmConfigure(&(*pVm), iConfigOp, ap);
740 va_end(ap);
741#if defined(JX9_ENABLE_THREADS)
742 /* Leave VM mutex */
743 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
744#endif
745 return rc;
746}
747/*
748 * [CAPIREF: jx9_vm_release()]
749 * Please refer to the official documentation for function purpose and expected parameters.
750 */
751JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm)
752{
753 jx9 *pEngine;
754 int rc;
755 /* Ticket 1433-002: NULL VM is harmless operation */
756 if ( JX9_VM_MISUSE(pVm) ){
757 return JX9_CORRUPT;
758 }
759#if defined(JX9_ENABLE_THREADS)
760 /* Acquire VM mutex */
761 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
762 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
763 JX9_THRD_VM_RELEASE(pVm) ){
764 return JX9_ABORT; /* Another thread have released this instance */
765 }
766#endif
767 pEngine = pVm->pEngine;
768 rc = jx9VmRelease(&(*pVm));
769#if defined(JX9_ENABLE_THREADS)
770 /* Leave VM mutex */
771 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
772 /* Release VM mutex */
773 SyMutexRelease(sJx9MPGlobal.pMutexMethods, pVm->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
774#endif
775 if( rc == JX9_OK ){
776 /* Unlink from the list of active VM */
777#if defined(JX9_ENABLE_THREADS)
778 /* Acquire engine mutex */
779 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
780 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
781 JX9_THRD_ENGINE_RELEASE(pEngine) ){
782 return JX9_ABORT; /* Another thread have released this instance */
783 }
784#endif
785 MACRO_LD_REMOVE(pEngine->pVms, pVm);
786 pEngine->iVm--;
787 /* Release the memory chunk allocated to this VM */
788 SyMemBackendPoolFree(&pEngine->sAllocator, pVm);
789#if defined(JX9_ENABLE_THREADS)
790 /* Leave engine mutex */
791 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
792#endif
793 }
794 return rc;
795}
796/*
797 * [CAPIREF: jx9_create_function()]
798 * Please refer to the official documentation for function purpose and expected parameters.
799 */
800JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData)
801{
802 SyString sName;
803 int rc;
804 /* Ticket 1433-002: NULL VM is harmless operation */
805 if ( JX9_VM_MISUSE(pVm) ){
806 return JX9_CORRUPT;
807 }
808 SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
809 /* Remove leading and trailing white spaces */
810 SyStringFullTrim(&sName);
811 /* Ticket 1433-003: NULL values are not allowed */
812 if( sName.nByte < 1 || xFunc == 0 ){
813 return JX9_CORRUPT;
814 }
815#if defined(JX9_ENABLE_THREADS)
816 /* Acquire VM mutex */
817 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
818 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
819 JX9_THRD_VM_RELEASE(pVm) ){
820 return JX9_ABORT; /* Another thread have released this instance */
821 }
822#endif
823 /* Install the foreign function */
824 rc = jx9VmInstallForeignFunction(&(*pVm), &sName, xFunc, pUserData);
825#if defined(JX9_ENABLE_THREADS)
826 /* Leave VM mutex */
827 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
828#endif
829 return rc;
830}
831JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName)
832{
833 jx9_user_func *pFunc = 0; /* cc warning */
834 int rc;
835 /* Perform the deletion */
836 rc = SyHashDeleteEntry(&pVm->hHostFunction, (const void *)zName, SyStrlen(zName), (void **)&pFunc);
837 if( rc == JX9_OK ){
838 /* Release internal fields */
839 SySetRelease(&pFunc->aAux);
840 SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName));
841 SyMemBackendPoolFree(&pVm->sAllocator, pFunc);
842 }
843 return rc;
844}
845/*
846 * [CAPIREF: jx9_create_constant()]
847 * Please refer to the official documentation for function purpose and expected parameters.
848 */
849JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData)
850{
851 SyString sName;
852 int rc;
853 /* Ticket 1433-002: NULL VM is harmless operation */
854 if ( JX9_VM_MISUSE(pVm) ){
855 return JX9_CORRUPT;
856 }
857 SyStringInitFromBuf(&sName, zName, SyStrlen(zName));
858 /* Remove leading and trailing white spaces */
859 SyStringFullTrim(&sName);
860 if( sName.nByte < 1 ){
861 /* Empty constant name */
862 return JX9_CORRUPT;
863 }
864 /* TICKET 1433-003: NULL pointer is harmless operation */
865 if( xExpand == 0 ){
866 return JX9_CORRUPT;
867 }
868#if defined(JX9_ENABLE_THREADS)
869 /* Acquire VM mutex */
870 SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
871 if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE &&
872 JX9_THRD_VM_RELEASE(pVm) ){
873 return JX9_ABORT; /* Another thread have released this instance */
874 }
875#endif
876 /* Perform the registration */
877 rc = jx9VmRegisterConstant(&(*pVm), &sName, xExpand, pUserData);
878#if defined(JX9_ENABLE_THREADS)
879 /* Leave VM mutex */
880 SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */
881#endif
882 return rc;
883}
884JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName)
885{
886 jx9_constant *pCons;
887 int rc;
888 /* Query the constant hashtable */
889 rc = SyHashDeleteEntry(&pVm->hConstant, (const void *)zName, SyStrlen(zName), (void **)&pCons);
890 if( rc == JX9_OK ){
891 /* Perform the deletion */
892 SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pCons->sName));
893 SyMemBackendPoolFree(&pVm->sAllocator, pCons);
894 }
895 return rc;
896}
897/*
898 * [CAPIREF: jx9_new_scalar()]
899 * Please refer to the official documentation for function purpose and expected parameters.
900 */
901JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm)
902{
903 jx9_value *pObj;
904 /* Ticket 1433-002: NULL VM is harmless operation */
905 if ( JX9_VM_MISUSE(pVm) ){
906 return 0;
907 }
908 /* Allocate a new scalar variable */
909 pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value));
910 if( pObj == 0 ){
911 return 0;
912 }
913 /* Nullify the new scalar */
914 jx9MemObjInit(pVm, pObj);
915 return pObj;
916}
917/*
918 * [CAPIREF: jx9_new_array()]
919 * Please refer to the official documentation for function purpose and expected parameters.
920 */
921JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm)
922{
923 jx9_hashmap *pMap;
924 jx9_value *pObj;
925 /* Ticket 1433-002: NULL VM is harmless operation */
926 if ( JX9_VM_MISUSE(pVm) ){
927 return 0;
928 }
929 /* Create a new hashmap first */
930 pMap = jx9NewHashmap(&(*pVm), 0, 0);
931 if( pMap == 0 ){
932 return 0;
933 }
934 /* Associate a new jx9_value with this hashmap */
935 pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value));
936 if( pObj == 0 ){
937 jx9HashmapRelease(pMap, TRUE);
938 return 0;
939 }
940 jx9MemObjInitFromArray(pVm, pObj, pMap);
941 return pObj;
942}
943/*
944 * [CAPIREF: jx9_release_value()]
945 * Please refer to the official documentation for function purpose and expected parameters.
946 */
947JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue)
948{
949 /* Ticket 1433-002: NULL VM is a harmless operation */
950 if ( JX9_VM_MISUSE(pVm) ){
951 return JX9_CORRUPT;
952 }
953 if( pValue ){
954 /* Release the value */
955 jx9MemObjRelease(pValue);
956 SyMemBackendPoolFree(&pVm->sAllocator, pValue);
957 }
958 return JX9_OK;
959}
960/*
961 * [CAPIREF: jx9_value_to_int()]
962 * Please refer to the official documentation for function purpose and expected parameters.
963 */
964JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue)
965{
966 int rc;
967 rc = jx9MemObjToInteger(pValue);
968 if( rc != JX9_OK ){
969 return 0;
970 }
971 return (int)pValue->x.iVal;
972}
973/*
974 * [CAPIREF: jx9_value_to_bool()]
975 * Please refer to the official documentation for function purpose and expected parameters.
976 */
977JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue)
978{
979 int rc;
980 rc = jx9MemObjToBool(pValue);
981 if( rc != JX9_OK ){
982 return 0;
983 }
984 return (int)pValue->x.iVal;
985}
986/*
987 * [CAPIREF: jx9_value_to_int64()]
988 * Please refer to the official documentation for function purpose and expected parameters.
989 */
990JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue)
991{
992 int rc;
993 rc = jx9MemObjToInteger(pValue);
994 if( rc != JX9_OK ){
995 return 0;
996 }
997 return pValue->x.iVal;
998}
999/*
1000 * [CAPIREF: jx9_value_to_double()]
1001 * Please refer to the official documentation for function purpose and expected parameters.
1002 */
1003JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue)
1004{
1005 int rc;
1006 rc = jx9MemObjToReal(pValue);
1007 if( rc != JX9_OK ){
1008 return (double)0;
1009 }
1010 return (double)pValue->x.rVal;
1011}
1012/*
1013 * [CAPIREF: jx9_value_to_string()]
1014 * Please refer to the official documentation for function purpose and expected parameters.
1015 */
1016JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen)
1017{
1018 jx9MemObjToString(pValue);
1019 if( SyBlobLength(&pValue->sBlob) > 0 ){
1020 SyBlobNullAppend(&pValue->sBlob);
1021 if( pLen ){
1022 *pLen = (int)SyBlobLength(&pValue->sBlob);
1023 }
1024 return (const char *)SyBlobData(&pValue->sBlob);
1025 }else{
1026 /* Return the empty string */
1027 if( pLen ){
1028 *pLen = 0;
1029 }
1030 return "";
1031 }
1032}
1033/*
1034 * [CAPIREF: jx9_value_to_resource()]
1035 * Please refer to the official documentation for function purpose and expected parameters.
1036 */
1037JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue)
1038{
1039 if( (pValue->iFlags & MEMOBJ_RES) == 0 ){
1040 /* Not a resource, return NULL */
1041 return 0;
1042 }
1043 return pValue->x.pOther;
1044}
1045/*
1046 * [CAPIREF: jx9_value_compare()]
1047 * Please refer to the official documentation for function purpose and expected parameters.
1048 */
1049JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict)
1050{
1051 int rc;
1052 if( pLeft == 0 || pRight == 0 ){
1053 /* TICKET 1433-24: NULL values is harmless operation */
1054 return 1;
1055 }
1056 /* Perform the comparison */
1057 rc = jx9MemObjCmp(&(*pLeft), &(*pRight), bStrict, 0);
1058 /* Comparison result */
1059 return rc;
1060}
1061/*
1062 * [CAPIREF: jx9_result_int()]
1063 * Please refer to the official documentation for function purpose and expected parameters.
1064 */
1065JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue)
1066{
1067 return jx9_value_int(pCtx->pRet, iValue);
1068}
1069/*
1070 * [CAPIREF: jx9_result_int64()]
1071 * Please refer to the official documentation for function purpose and expected parameters.
1072 */
1073JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue)
1074{
1075 return jx9_value_int64(pCtx->pRet, iValue);
1076}
1077/*
1078 * [CAPIREF: jx9_result_bool()]
1079 * Please refer to the official documentation for function purpose and expected parameters.
1080 */
1081JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool)
1082{
1083 return jx9_value_bool(pCtx->pRet, iBool);
1084}
1085/*
1086 * [CAPIREF: jx9_result_double()]
1087 * Please refer to the official documentation for function purpose and expected parameters.
1088 */
1089JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value)
1090{
1091 return jx9_value_double(pCtx->pRet, Value);
1092}
1093/*
1094 * [CAPIREF: jx9_result_null()]
1095 * Please refer to the official documentation for function purpose and expected parameters.
1096 */
1097JX9_PRIVATE int jx9_result_null(jx9_context *pCtx)
1098{
1099 /* Invalidate any prior representation and set the NULL flag */
1100 jx9MemObjRelease(pCtx->pRet);
1101 return JX9_OK;
1102}
1103/*
1104 * [CAPIREF: jx9_result_string()]
1105 * Please refer to the official documentation for function purpose and expected parameters.
1106 */
1107JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen)
1108{
1109 return jx9_value_string(pCtx->pRet, zString, nLen);
1110}
1111/*
1112 * [CAPIREF: jx9_result_string_format()]
1113 * Please refer to the official documentation for function purpose and expected parameters.
1114 */
1115JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...)
1116{
1117 jx9_value *p;
1118 va_list ap;
1119 int rc;
1120 p = pCtx->pRet;
1121 if( (p->iFlags & MEMOBJ_STRING) == 0 ){
1122 /* Invalidate any prior representation */
1123 jx9MemObjRelease(p);
1124 MemObjSetType(p, MEMOBJ_STRING);
1125 }
1126 /* Format the given string */
1127 va_start(ap, zFormat);
1128 rc = SyBlobFormatAp(&p->sBlob, zFormat, ap);
1129 va_end(ap);
1130 return rc;
1131}
1132/*
1133 * [CAPIREF: jx9_result_value()]
1134 * Please refer to the official documentation for function purpose and expected parameters.
1135 */
1136JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue)
1137{
1138 int rc = JX9_OK;
1139 if( pValue == 0 ){
1140 jx9MemObjRelease(pCtx->pRet);
1141 }else{
1142 rc = jx9MemObjStore(pValue, pCtx->pRet);
1143 }
1144 return rc;
1145}
1146/*
1147 * [CAPIREF: jx9_result_resource()]
1148 * Please refer to the official documentation for function purpose and expected parameters.
1149 */
1150JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData)
1151{
1152 return jx9_value_resource(pCtx->pRet, pUserData);
1153}
1154/*
1155 * [CAPIREF: jx9_context_new_scalar()]
1156 * Please refer to the official documentation for function purpose and expected parameters.
1157 */
1158JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx)
1159{
1160 jx9_value *pVal;
1161 pVal = jx9_new_scalar(pCtx->pVm);
1162 if( pVal ){
1163 /* Record value address so it can be freed automatically
1164 * when the calling function returns.
1165 */
1166 SySetPut(&pCtx->sVar, (const void *)&pVal);
1167 }
1168 return pVal;
1169}
1170/*
1171 * [CAPIREF: jx9_context_new_array()]
1172 * Please refer to the official documentation for function purpose and expected parameters.
1173 */
1174JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx)
1175{
1176 jx9_value *pVal;
1177 pVal = jx9_new_array(pCtx->pVm);
1178 if( pVal ){
1179 /* Record value address so it can be freed automatically
1180 * when the calling function returns.
1181 */
1182 SySetPut(&pCtx->sVar, (const void *)&pVal);
1183 }
1184 return pVal;
1185}
1186/*
1187 * [CAPIREF: jx9_context_release_value()]
1188 * Please refer to the official documentation for function purpose and expected parameters.
1189 */
1190JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue)
1191{
1192 jx9VmReleaseContextValue(&(*pCtx), pValue);
1193}
1194/*
1195 * [CAPIREF: jx9_context_alloc_chunk()]
1196 * Please refer to the official documentation for function purpose and expected parameters.
1197 */
1198JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease)
1199{
1200 void *pChunk;
1201 pChunk = SyMemBackendAlloc(&pCtx->pVm->sAllocator, nByte);
1202 if( pChunk ){
1203 if( ZeroChunk ){
1204 /* Zero the memory chunk */
1205 SyZero(pChunk, nByte);
1206 }
1207 if( AutoRelease ){
1208 jx9_aux_data sAux;
1209 /* Track the chunk so that it can be released automatically
1210 * upon this context is destroyed.
1211 */
1212 sAux.pAuxData = pChunk;
1213 SySetPut(&pCtx->sChunk, (const void *)&sAux);
1214 }
1215 }
1216 return pChunk;
1217}
1218/*
1219 * Check if the given chunk address is registered in the call context
1220 * chunk container.
1221 * Return TRUE if registered.FALSE otherwise.
1222 * Refer to [jx9_context_realloc_chunk(), jx9_context_free_chunk()].
1223 */
1224static jx9_aux_data * ContextFindChunk(jx9_context *pCtx, void *pChunk)
1225{
1226 jx9_aux_data *aAux, *pAux;
1227 sxu32 n;
1228 if( SySetUsed(&pCtx->sChunk) < 1 ){
1229 /* Don't bother processing, the container is empty */
1230 return 0;
1231 }
1232 /* Perform the lookup */
1233 aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk);
1234 for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){
1235 pAux = &aAux[n];
1236 if( pAux->pAuxData == pChunk ){
1237 /* Chunk found */
1238 return pAux;
1239 }
1240 }
1241 /* No such allocated chunk */
1242 return 0;
1243}
1244/*
1245 * [CAPIREF: jx9_context_realloc_chunk()]
1246 * Please refer to the official documentation for function purpose and expected parameters.
1247 */
1248JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte)
1249{
1250 jx9_aux_data *pAux;
1251 void *pNew;
1252 pNew = SyMemBackendRealloc(&pCtx->pVm->sAllocator, pChunk, nByte);
1253 if( pNew ){
1254 pAux = ContextFindChunk(pCtx, pChunk);
1255 if( pAux ){
1256 pAux->pAuxData = pNew;
1257 }
1258 }
1259 return pNew;
1260}
1261/*
1262 * [CAPIREF: jx9_context_free_chunk()]
1263 * Please refer to the official documentation for function purpose and expected parameters.
1264 */
1265JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk)
1266{
1267 jx9_aux_data *pAux;
1268 if( pChunk == 0 ){
1269 /* TICKET-1433-93: NULL chunk is a harmless operation */
1270 return;
1271 }
1272 pAux = ContextFindChunk(pCtx, pChunk);
1273 if( pAux ){
1274 /* Mark as destroyed */
1275 pAux->pAuxData = 0;
1276 }
1277 SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk);
1278}
1279/*
1280 * [CAPIREF: jx9_array_fetch()]
1281 * Please refer to the official documentation for function purpose and expected parameters.
1282 */
1283JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte)
1284{
1285 jx9_hashmap_node *pNode;
1286 jx9_value *pValue;
1287 jx9_value skey;
1288 int rc;
1289 /* Make sure we are dealing with a valid hashmap */
1290 if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
1291 return 0;
1292 }
1293 if( nByte < 0 ){
1294 nByte = (int)SyStrlen(zKey);
1295 }
1296 /* Convert the key to a jx9_value */
1297 jx9MemObjInit(pArray->pVm, &skey);
1298 jx9MemObjStringAppend(&skey, zKey, (sxu32)nByte);
1299 /* Perform the lookup */
1300 rc = jx9HashmapLookup((jx9_hashmap *)pArray->x.pOther, &skey, &pNode);
1301 jx9MemObjRelease(&skey);
1302 if( rc != JX9_OK ){
1303 /* No such entry */
1304 return 0;
1305 }
1306 /* Extract the target value */
1307 pValue = (jx9_value *)SySetAt(&pArray->pVm->aMemObj, pNode->nValIdx);
1308 return pValue;
1309}
1310/*
1311 * [CAPIREF: jx9_array_walk()]
1312 * Please refer to the official documentation for function purpose and expected parameters.
1313 */
1314JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *pValue, jx9_value *, void *), void *pUserData)
1315{
1316 int rc;
1317 if( xWalk == 0 ){
1318 return JX9_CORRUPT;
1319 }
1320 /* Make sure we are dealing with a valid hashmap */
1321 if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
1322 return JX9_CORRUPT;
1323 }
1324 /* Start the walk process */
1325 rc = jx9HashmapWalk((jx9_hashmap *)pArray->x.pOther, xWalk, pUserData);
1326 return rc != JX9_OK ? JX9_ABORT /* User callback request an operation abort*/ : JX9_OK;
1327}
1328/*
1329 * [CAPIREF: jx9_array_add_elem()]
1330 * Please refer to the official documentation for function purpose and expected parameters.
1331 */
1332JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue)
1333{
1334 int rc;
1335 /* Make sure we are dealing with a valid hashmap */
1336 if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
1337 return JX9_CORRUPT;
1338 }
1339 /* Perform the insertion */
1340 rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &(*pKey), &(*pValue));
1341 return rc;
1342}
1343/*
1344 * [CAPIREF: jx9_array_add_strkey_elem()]
1345 * Please refer to the official documentation for function purpose and expected parameters.
1346 */
1347JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue)
1348{
1349 int rc;
1350 /* Make sure we are dealing with a valid hashmap */
1351 if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
1352 return JX9_CORRUPT;
1353 }
1354 /* Perform the insertion */
1355 if( SX_EMPTY_STR(zKey) ){
1356 /* Empty key, assign an automatic index */
1357 rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, 0, &(*pValue));
1358 }else{
1359 jx9_value sKey;
1360 jx9MemObjInitFromString(pArray->pVm, &sKey, 0);
1361 jx9MemObjStringAppend(&sKey, zKey, (sxu32)SyStrlen(zKey));
1362 rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &sKey, &(*pValue));
1363 jx9MemObjRelease(&sKey);
1364 }
1365 return rc;
1366}
1367/*
1368 * [CAPIREF: jx9_array_count()]
1369 * Please refer to the official documentation for function purpose and expected parameters.
1370 */
1371JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray)
1372{
1373 jx9_hashmap *pMap;
1374 /* Make sure we are dealing with a valid hashmap */
1375 if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){
1376 return 0;
1377 }
1378 /* Point to the internal representation of the hashmap */
1379 pMap = (jx9_hashmap *)pArray->x.pOther;
1380 return pMap->nEntry;
1381}
1382/*
1383 * [CAPIREF: jx9_context_output()]
1384 * Please refer to the official documentation for function purpose and expected parameters.
1385 */
1386JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen)
1387{
1388 SyString sData;
1389 int rc;
1390 if( nLen < 0 ){
1391 nLen = (int)SyStrlen(zString);
1392 }
1393 SyStringInitFromBuf(&sData, zString, nLen);
1394 rc = jx9VmOutputConsume(pCtx->pVm, &sData);
1395 return rc;
1396}
1397/*
1398 * [CAPIREF: jx9_context_throw_error()]
1399 * Please refer to the official documentation for function purpose and expected parameters.
1400 */
1401JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr)
1402{
1403 int rc = JX9_OK;
1404 if( zErr ){
1405 rc = jx9VmThrowError(pCtx->pVm, &pCtx->pFunc->sName, iErr, zErr);
1406 }
1407 return rc;
1408}
1409/*
1410 * [CAPIREF: jx9_context_throw_error_format()]
1411 * Please refer to the official documentation for function purpose and expected parameters.
1412 */
1413JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...)
1414{
1415 va_list ap;
1416 int rc;
1417 if( zFormat == 0){
1418 return JX9_OK;
1419 }
1420 va_start(ap, zFormat);
1421 rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap);
1422 va_end(ap);
1423 return rc;
1424}
1425/*
1426 * [CAPIREF: jx9_context_random_num()]
1427 * Please refer to the official documentation for function purpose and expected parameters.
1428 */
1429JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx)
1430{
1431 sxu32 n;
1432 n = jx9VmRandomNum(pCtx->pVm);
1433 return n;
1434}
1435/*
1436 * [CAPIREF: jx9_context_random_string()]
1437 * Please refer to the official documentation for function purpose and expected parameters.
1438 */
1439JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen)
1440{
1441 if( nBuflen < 3 ){
1442 return JX9_CORRUPT;
1443 }
1444 jx9VmRandomString(pCtx->pVm, zBuf, nBuflen);
1445 return JX9_OK;
1446}
1447/*
1448 * [CAPIREF: jx9_context_user_data()]
1449 * Please refer to the official documentation for function purpose and expected parameters.
1450 */
1451JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx)
1452{
1453 return pCtx->pFunc->pUserData;
1454}
1455/*
1456 * [CAPIREF: jx9_context_push_aux_data()]
1457 * Please refer to the official documentation for function purpose and expected parameters.
1458 */
1459JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData)
1460{
1461 jx9_aux_data sAux;
1462 int rc;
1463 sAux.pAuxData = pUserData;
1464 rc = SySetPut(&pCtx->pFunc->aAux, (const void *)&sAux);
1465 return rc;
1466}
1467/*
1468 * [CAPIREF: jx9_context_peek_aux_data()]
1469 * Please refer to the official documentation for function purpose and expected parameters.
1470 */
1471JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx)
1472{
1473 jx9_aux_data *pAux;
1474 pAux = (jx9_aux_data *)SySetPeek(&pCtx->pFunc->aAux);
1475 return pAux ? pAux->pAuxData : 0;
1476}
1477/*
1478 * [CAPIREF: jx9_context_pop_aux_data()]
1479 * Please refer to the official documentation for function purpose and expected parameters.
1480 */
1481JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx)
1482{
1483 jx9_aux_data *pAux;
1484 pAux = (jx9_aux_data *)SySetPop(&pCtx->pFunc->aAux);
1485 return pAux ? pAux->pAuxData : 0;
1486}
1487/*
1488 * [CAPIREF: jx9_context_result_buf_length()]
1489 * Please refer to the official documentation for function purpose and expected parameters.
1490 */
1491JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx)
1492{
1493 return SyBlobLength(&pCtx->pRet->sBlob);
1494}
1495/*
1496 * [CAPIREF: jx9_function_name()]
1497 * Please refer to the official documentation for function purpose and expected parameters.
1498 */
1499JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx)
1500{
1501 SyString *pName;
1502 pName = &pCtx->pFunc->sName;
1503 return pName->zString;
1504}
1505/*
1506 * [CAPIREF: jx9_value_int()]
1507 * Please refer to the official documentation for function purpose and expected parameters.
1508 */
1509JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue)
1510{
1511 /* Invalidate any prior representation */
1512 jx9MemObjRelease(pVal);
1513 pVal->x.iVal = (jx9_int64)iValue;
1514 MemObjSetType(pVal, MEMOBJ_INT);
1515 return JX9_OK;
1516}
1517/*
1518 * [CAPIREF: jx9_value_int64()]
1519 * Please refer to the official documentation for function purpose and expected parameters.
1520 */
1521JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue)
1522{
1523 /* Invalidate any prior representation */
1524 jx9MemObjRelease(pVal);
1525 pVal->x.iVal = iValue;
1526 MemObjSetType(pVal, MEMOBJ_INT);
1527 return JX9_OK;
1528}
1529/*
1530 * [CAPIREF: jx9_value_bool()]
1531 * Please refer to the official documentation for function purpose and expected parameters.
1532 */
1533JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool)
1534{
1535 /* Invalidate any prior representation */
1536 jx9MemObjRelease(pVal);
1537 pVal->x.iVal = iBool ? 1 : 0;
1538 MemObjSetType(pVal, MEMOBJ_BOOL);
1539 return JX9_OK;
1540}
1541/*
1542 * [CAPIREF: jx9_value_null()]
1543 * Please refer to the official documentation for function purpose and expected parameters.
1544 */
1545JX9_PRIVATE int jx9_value_null(jx9_value *pVal)
1546{
1547 /* Invalidate any prior representation and set the NULL flag */
1548 jx9MemObjRelease(pVal);
1549 return JX9_OK;
1550}
1551/*
1552 * [CAPIREF: jx9_value_double()]
1553 * Please refer to the official documentation for function purpose and expected parameters.
1554 */
1555JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value)
1556{
1557 /* Invalidate any prior representation */
1558 jx9MemObjRelease(pVal);
1559 pVal->x.rVal = (jx9_real)Value;
1560 MemObjSetType(pVal, MEMOBJ_REAL);
1561 /* Try to get an integer representation also */
1562 jx9MemObjTryInteger(pVal);
1563 return JX9_OK;
1564}
1565/*
1566 * [CAPIREF: jx9_value_string()]
1567 * Please refer to the official documentation for function purpose and expected parameters.
1568 */
1569JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen)
1570{
1571 if((pVal->iFlags & MEMOBJ_STRING) == 0 ){
1572 /* Invalidate any prior representation */
1573 jx9MemObjRelease(pVal);
1574 MemObjSetType(pVal, MEMOBJ_STRING);
1575 }
1576 if( zString ){
1577 if( nLen < 0 ){
1578 /* Compute length automatically */
1579 nLen = (int)SyStrlen(zString);
1580 }
1581 SyBlobAppend(&pVal->sBlob, (const void *)zString, (sxu32)nLen);
1582 }
1583 return JX9_OK;
1584}
1585/*
1586 * [CAPIREF: jx9_value_string_format()]
1587 * Please refer to the official documentation for function purpose and expected parameters.
1588 */
1589JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...)
1590{
1591 va_list ap;
1592 int rc;
1593 if((pVal->iFlags & MEMOBJ_STRING) == 0 ){
1594 /* Invalidate any prior representation */
1595 jx9MemObjRelease(pVal);
1596 MemObjSetType(pVal, MEMOBJ_STRING);
1597 }
1598 va_start(ap, zFormat);
1599 rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap);
1600 va_end(ap);
1601 return JX9_OK;
1602}
1603/*
1604 * [CAPIREF: jx9_value_reset_string_cursor()]
1605 * Please refer to the official documentation for function purpose and expected parameters.
1606 */
1607JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal)
1608{
1609 /* Reset the string cursor */
1610 SyBlobReset(&pVal->sBlob);
1611 return JX9_OK;
1612}
1613/*
1614 * [CAPIREF: jx9_value_resource()]
1615 * Please refer to the official documentation for function purpose and expected parameters.
1616 */
1617JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData)
1618{
1619 /* Invalidate any prior representation */
1620 jx9MemObjRelease(pVal);
1621 /* Reflect the new type */
1622 pVal->x.pOther = pUserData;
1623 MemObjSetType(pVal, MEMOBJ_RES);
1624 return JX9_OK;
1625}
1626/*
1627 * [CAPIREF: jx9_value_release()]
1628 * Please refer to the official documentation for function purpose and expected parameters.
1629 */
1630JX9_PRIVATE int jx9_value_release(jx9_value *pVal)
1631{
1632 jx9MemObjRelease(pVal);
1633 return JX9_OK;
1634}
1635/*
1636 * [CAPIREF: jx9_value_is_int()]
1637 * Please refer to the official documentation for function purpose and expected parameters.
1638 */
1639JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal)
1640{
1641 return (pVal->iFlags & MEMOBJ_INT) ? TRUE : FALSE;
1642}
1643/*
1644 * [CAPIREF: jx9_value_is_float()]
1645 * Please refer to the official documentation for function purpose and expected parameters.
1646 */
1647JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal)
1648{
1649 return (pVal->iFlags & MEMOBJ_REAL) ? TRUE : FALSE;
1650}
1651/*
1652 * [CAPIREF: jx9_value_is_bool()]
1653 * Please refer to the official documentation for function purpose and expected parameters.
1654 */
1655JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal)
1656{
1657 return (pVal->iFlags & MEMOBJ_BOOL) ? TRUE : FALSE;
1658}
1659/*
1660 * [CAPIREF: jx9_value_is_string()]
1661 * Please refer to the official documentation for function purpose and expected parameters.
1662 */
1663JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal)
1664{
1665 return (pVal->iFlags & MEMOBJ_STRING) ? TRUE : FALSE;
1666}
1667/*
1668 * [CAPIREF: jx9_value_is_null()]
1669 * Please refer to the official documentation for function purpose and expected parameters.
1670 */
1671JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal)
1672{
1673 return (pVal->iFlags & MEMOBJ_NULL) ? TRUE : FALSE;
1674}
1675/*
1676 * [CAPIREF: jx9_value_is_numeric()]
1677 * Please refer to the official documentation for function purpose and expected parameters.
1678 */
1679JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal)
1680{
1681 int rc;
1682 rc = jx9MemObjIsNumeric(pVal);
1683 return rc;
1684}
1685/*
1686 * [CAPIREF: jx9_value_is_callable()]
1687 * Please refer to the official documentation for function purpose and expected parameters.
1688 */
1689JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal)
1690{
1691 int rc;
1692 rc = jx9VmIsCallable(pVal->pVm, pVal);
1693 return rc;
1694}
1695/*
1696 * [CAPIREF: jx9_value_is_scalar()]
1697 * Please refer to the official documentation for function purpose and expected parameters.
1698 */
1699JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal)
1700{
1701 return (pVal->iFlags & MEMOBJ_SCALAR) ? TRUE : FALSE;
1702}
1703/*
1704 * [CAPIREF: jx9_value_is_json_array()]
1705 * Please refer to the official documentation for function purpose and expected parameters.
1706 */
1707JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal)
1708{
1709 return (pVal->iFlags & MEMOBJ_HASHMAP) ? TRUE : FALSE;
1710}
1711/*
1712 * [CAPIREF: jx9_value_is_json_object()]
1713 * Please refer to the official documentation for function purpose and expected parameters.
1714 */
1715JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal)
1716{
1717 jx9_hashmap *pMap;
1718 if( (pVal->iFlags & MEMOBJ_HASHMAP) == 0 ){
1719 return FALSE;
1720 }
1721 pMap = (jx9_hashmap *)pVal->x.pOther;
1722 if( (pMap->iFlags & HASHMAP_JSON_OBJECT) == 0 ){
1723 return FALSE;
1724 }
1725 return TRUE;
1726}
1727/*
1728 * [CAPIREF: jx9_value_is_resource()]
1729 * Please refer to the official documentation for function purpose and expected parameters.
1730 */
1731JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal)
1732{
1733 return (pVal->iFlags & MEMOBJ_RES) ? TRUE : FALSE;
1734}
1735/*
1736 * [CAPIREF: jx9_value_is_empty()]
1737 * Please refer to the official documentation for function purpose and expected parameters.
1738 */
1739JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal)
1740{
1741 int rc;
1742 rc = jx9MemObjIsEmpty(pVal);
1743 return rc;
1744}
diff --git a/common/unqlite/jx9_builtin.c b/common/unqlite/jx9_builtin.c
new file mode 100644
index 0000000..83e6c6e
--- /dev/null
+++ b/common/unqlite/jx9_builtin.c
@@ -0,0 +1,8297 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: builtin.c v1.7 Win7 2012-12-13 00:01 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* This file implement built-in 'foreign' functions for the JX9 engine */
18/*
19 * Section:
20 * Variable handling Functions.
21 * Authors:
22 * Symisc Systems, devel@symisc.net.
23 * Copyright (C) Symisc Systems, http://jx9.symisc.net
24 * Status:
25 * Stable.
26 */
27/*
28 * bool is_bool($var)
29 * Finds out whether a variable is a boolean.
30 * Parameters
31 * $var: The variable being evaluated.
32 * Return
33 * TRUE if var is a boolean. False otherwise.
34 */
35static int jx9Builtin_is_bool(jx9_context *pCtx, int nArg, jx9_value **apArg)
36{
37 int res = 0; /* Assume false by default */
38 if( nArg > 0 ){
39 res = jx9_value_is_bool(apArg[0]);
40 }
41 /* Query result */
42 jx9_result_bool(pCtx, res);
43 return JX9_OK;
44}
45/*
46 * bool is_float($var)
47 * bool is_real($var)
48 * bool is_double($var)
49 * Finds out whether a variable is a float.
50 * Parameters
51 * $var: The variable being evaluated.
52 * Return
53 * TRUE if var is a float. False otherwise.
54 */
55static int jx9Builtin_is_float(jx9_context *pCtx, int nArg, jx9_value **apArg)
56{
57 int res = 0; /* Assume false by default */
58 if( nArg > 0 ){
59 res = jx9_value_is_float(apArg[0]);
60 }
61 /* Query result */
62 jx9_result_bool(pCtx, res);
63 return JX9_OK;
64}
65/*
66 * bool is_int($var)
67 * bool is_integer($var)
68 * bool is_long($var)
69 * Finds out whether a variable is an integer.
70 * Parameters
71 * $var: The variable being evaluated.
72 * Return
73 * TRUE if var is an integer. False otherwise.
74 */
75static int jx9Builtin_is_int(jx9_context *pCtx, int nArg, jx9_value **apArg)
76{
77 int res = 0; /* Assume false by default */
78 if( nArg > 0 ){
79 res = jx9_value_is_int(apArg[0]);
80 }
81 /* Query result */
82 jx9_result_bool(pCtx, res);
83 return JX9_OK;
84}
85/*
86 * bool is_string($var)
87 * Finds out whether a variable is a string.
88 * Parameters
89 * $var: The variable being evaluated.
90 * Return
91 * TRUE if var is string. False otherwise.
92 */
93static int jx9Builtin_is_string(jx9_context *pCtx, int nArg, jx9_value **apArg)
94{
95 int res = 0; /* Assume false by default */
96 if( nArg > 0 ){
97 res = jx9_value_is_string(apArg[0]);
98 }
99 /* Query result */
100 jx9_result_bool(pCtx, res);
101 return JX9_OK;
102}
103/*
104 * bool is_null($var)
105 * Finds out whether a variable is NULL.
106 * Parameters
107 * $var: The variable being evaluated.
108 * Return
109 * TRUE if var is NULL. False otherwise.
110 */
111static int jx9Builtin_is_null(jx9_context *pCtx, int nArg, jx9_value **apArg)
112{
113 int res = 0; /* Assume false by default */
114 if( nArg > 0 ){
115 res = jx9_value_is_null(apArg[0]);
116 }
117 /* Query result */
118 jx9_result_bool(pCtx, res);
119 return JX9_OK;
120}
121/*
122 * bool is_numeric($var)
123 * Find out whether a variable is NULL.
124 * Parameters
125 * $var: The variable being evaluated.
126 * Return
127 * True if var is numeric. False otherwise.
128 */
129static int jx9Builtin_is_numeric(jx9_context *pCtx, int nArg, jx9_value **apArg)
130{
131 int res = 0; /* Assume false by default */
132 if( nArg > 0 ){
133 res = jx9_value_is_numeric(apArg[0]);
134 }
135 /* Query result */
136 jx9_result_bool(pCtx, res);
137 return JX9_OK;
138}
139/*
140 * bool is_scalar($var)
141 * Find out whether a variable is a scalar.
142 * Parameters
143 * $var: The variable being evaluated.
144 * Return
145 * True if var is scalar. False otherwise.
146 */
147static int jx9Builtin_is_scalar(jx9_context *pCtx, int nArg, jx9_value **apArg)
148{
149 int res = 0; /* Assume false by default */
150 if( nArg > 0 ){
151 res = jx9_value_is_scalar(apArg[0]);
152 }
153 /* Query result */
154 jx9_result_bool(pCtx, res);
155 return JX9_OK;
156}
157/*
158 * bool is_array($var)
159 * Find out whether a variable is an array.
160 * Parameters
161 * $var: The variable being evaluated.
162 * Return
163 * True if var is an array. False otherwise.
164 */
165static int jx9Builtin_is_array(jx9_context *pCtx, int nArg, jx9_value **apArg)
166{
167 int res = 0; /* Assume false by default */
168 if( nArg > 0 ){
169 res = jx9_value_is_json_array(apArg[0]);
170 }
171 /* Query result */
172 jx9_result_bool(pCtx, res);
173 return JX9_OK;
174}
175/*
176 * bool is_object($var)
177 * Find out whether a variable is an object.
178 * Parameters
179 * $var: The variable being evaluated.
180 * Return
181 * True if var is an object. False otherwise.
182 */
183static int jx9Builtin_is_object(jx9_context *pCtx, int nArg, jx9_value **apArg)
184{
185 int res = 0; /* Assume false by default */
186 if( nArg > 0 ){
187 res = jx9_value_is_json_object(apArg[0]);
188 }
189 /* Query result */
190 jx9_result_bool(pCtx, res);
191 return JX9_OK;
192}
193/*
194 * bool is_resource($var)
195 * Find out whether a variable is a resource.
196 * Parameters
197 * $var: The variable being evaluated.
198 * Return
199 * True if a resource. False otherwise.
200 */
201static int jx9Builtin_is_resource(jx9_context *pCtx, int nArg, jx9_value **apArg)
202{
203 int res = 0; /* Assume false by default */
204 if( nArg > 0 ){
205 res = jx9_value_is_resource(apArg[0]);
206 }
207 jx9_result_bool(pCtx, res);
208 return JX9_OK;
209}
210/*
211 * float floatval($var)
212 * Get float value of a variable.
213 * Parameter
214 * $var: The variable being processed.
215 * Return
216 * the float value of a variable.
217 */
218static int jx9Builtin_floatval(jx9_context *pCtx, int nArg, jx9_value **apArg)
219{
220 if( nArg < 1 ){
221 /* return 0.0 */
222 jx9_result_double(pCtx, 0);
223 }else{
224 double dval;
225 /* Perform the cast */
226 dval = jx9_value_to_double(apArg[0]);
227 jx9_result_double(pCtx, dval);
228 }
229 return JX9_OK;
230}
231/*
232 * int intval($var)
233 * Get integer value of a variable.
234 * Parameter
235 * $var: The variable being processed.
236 * Return
237 * the int value of a variable.
238 */
239static int jx9Builtin_intval(jx9_context *pCtx, int nArg, jx9_value **apArg)
240{
241 if( nArg < 1 ){
242 /* return 0 */
243 jx9_result_int(pCtx, 0);
244 }else{
245 sxi64 iVal;
246 /* Perform the cast */
247 iVal = jx9_value_to_int64(apArg[0]);
248 jx9_result_int64(pCtx, iVal);
249 }
250 return JX9_OK;
251}
252/*
253 * string strval($var)
254 * Get the string representation of a variable.
255 * Parameter
256 * $var: The variable being processed.
257 * Return
258 * the string value of a variable.
259 */
260static int jx9Builtin_strval(jx9_context *pCtx, int nArg, jx9_value **apArg)
261{
262 if( nArg < 1 ){
263 /* return NULL */
264 jx9_result_null(pCtx);
265 }else{
266 const char *zVal;
267 int iLen = 0; /* cc -O6 warning */
268 /* Perform the cast */
269 zVal = jx9_value_to_string(apArg[0], &iLen);
270 jx9_result_string(pCtx, zVal, iLen);
271 }
272 return JX9_OK;
273}
274/*
275 * bool empty($var)
276 * Determine whether a variable is empty.
277 * Parameters
278 * $var: The variable being checked.
279 * Return
280 * 0 if var has a non-empty and non-zero value.1 otherwise.
281 */
282static int jx9Builtin_empty(jx9_context *pCtx, int nArg, jx9_value **apArg)
283{
284 int res = 1; /* Assume empty by default */
285 if( nArg > 0 ){
286 res = jx9_value_is_empty(apArg[0]);
287 }
288 jx9_result_bool(pCtx, res);
289 return JX9_OK;
290
291}
292#ifndef JX9_DISABLE_BUILTIN_FUNC
293#ifdef JX9_ENABLE_MATH_FUNC
294/*
295 * Section:
296 * Math Functions.
297 * Authors:
298 * Symisc Systems, devel@symisc.net.
299 * Copyright (C) Symisc Systems, http://jx9.symisc.net
300 * Status:
301 * Stable.
302 */
303#include <stdlib.h> /* abs */
304#include <math.h>
305/*
306 * float sqrt(float $arg )
307 * Square root of the given number.
308 * Parameter
309 * The number to process.
310 * Return
311 * The square root of arg or the special value Nan of failure.
312 */
313static int jx9Builtin_sqrt(jx9_context *pCtx, int nArg, jx9_value **apArg)
314{
315 double r, x;
316 if( nArg < 1 ){
317 /* Missing argument, return 0 */
318 jx9_result_int(pCtx, 0);
319 return JX9_OK;
320 }
321 x = jx9_value_to_double(apArg[0]);
322 /* Perform the requested operation */
323 r = sqrt(x);
324 /* store the result back */
325 jx9_result_double(pCtx, r);
326 return JX9_OK;
327}
328/*
329 * float exp(float $arg )
330 * Calculates the exponent of e.
331 * Parameter
332 * The number to process.
333 * Return
334 * 'e' raised to the power of arg.
335 */
336static int jx9Builtin_exp(jx9_context *pCtx, int nArg, jx9_value **apArg)
337{
338 double r, x;
339 if( nArg < 1 ){
340 /* Missing argument, return 0 */
341 jx9_result_int(pCtx, 0);
342 return JX9_OK;
343 }
344 x = jx9_value_to_double(apArg[0]);
345 /* Perform the requested operation */
346 r = exp(x);
347 /* store the result back */
348 jx9_result_double(pCtx, r);
349 return JX9_OK;
350}
351/*
352 * float floor(float $arg )
353 * Round fractions down.
354 * Parameter
355 * The number to process.
356 * Return
357 * Returns the next lowest integer value by rounding down value if necessary.
358 */
359static int jx9Builtin_floor(jx9_context *pCtx, int nArg, jx9_value **apArg)
360{
361 double r, x;
362 if( nArg < 1 ){
363 /* Missing argument, return 0 */
364 jx9_result_int(pCtx, 0);
365 return JX9_OK;
366 }
367 x = jx9_value_to_double(apArg[0]);
368 /* Perform the requested operation */
369 r = floor(x);
370 /* store the result back */
371 jx9_result_double(pCtx, r);
372 return JX9_OK;
373}
374/*
375 * float cos(float $arg )
376 * Cosine.
377 * Parameter
378 * The number to process.
379 * Return
380 * The cosine of arg.
381 */
382static int jx9Builtin_cos(jx9_context *pCtx, int nArg, jx9_value **apArg)
383{
384 double r, x;
385 if( nArg < 1 ){
386 /* Missing argument, return 0 */
387 jx9_result_int(pCtx, 0);
388 return JX9_OK;
389 }
390 x = jx9_value_to_double(apArg[0]);
391 /* Perform the requested operation */
392 r = cos(x);
393 /* store the result back */
394 jx9_result_double(pCtx, r);
395 return JX9_OK;
396}
397/*
398 * float acos(float $arg )
399 * Arc cosine.
400 * Parameter
401 * The number to process.
402 * Return
403 * The arc cosine of arg.
404 */
405static int jx9Builtin_acos(jx9_context *pCtx, int nArg, jx9_value **apArg)
406{
407 double r, x;
408 if( nArg < 1 ){
409 /* Missing argument, return 0 */
410 jx9_result_int(pCtx, 0);
411 return JX9_OK;
412 }
413 x = jx9_value_to_double(apArg[0]);
414 /* Perform the requested operation */
415 r = acos(x);
416 /* store the result back */
417 jx9_result_double(pCtx, r);
418 return JX9_OK;
419}
420/*
421 * float cosh(float $arg )
422 * Hyperbolic cosine.
423 * Parameter
424 * The number to process.
425 * Return
426 * The hyperbolic cosine of arg.
427 */
428static int jx9Builtin_cosh(jx9_context *pCtx, int nArg, jx9_value **apArg)
429{
430 double r, x;
431 if( nArg < 1 ){
432 /* Missing argument, return 0 */
433 jx9_result_int(pCtx, 0);
434 return JX9_OK;
435 }
436 x = jx9_value_to_double(apArg[0]);
437 /* Perform the requested operation */
438 r = cosh(x);
439 /* store the result back */
440 jx9_result_double(pCtx, r);
441 return JX9_OK;
442}
443/*
444 * float sin(float $arg )
445 * Sine.
446 * Parameter
447 * The number to process.
448 * Return
449 * The sine of arg.
450 */
451static int jx9Builtin_sin(jx9_context *pCtx, int nArg, jx9_value **apArg)
452{
453 double r, x;
454 if( nArg < 1 ){
455 /* Missing argument, return 0 */
456 jx9_result_int(pCtx, 0);
457 return JX9_OK;
458 }
459 x = jx9_value_to_double(apArg[0]);
460 /* Perform the requested operation */
461 r = sin(x);
462 /* store the result back */
463 jx9_result_double(pCtx, r);
464 return JX9_OK;
465}
466/*
467 * float asin(float $arg )
468 * Arc sine.
469 * Parameter
470 * The number to process.
471 * Return
472 * The arc sine of arg.
473 */
474static int jx9Builtin_asin(jx9_context *pCtx, int nArg, jx9_value **apArg)
475{
476 double r, x;
477 if( nArg < 1 ){
478 /* Missing argument, return 0 */
479 jx9_result_int(pCtx, 0);
480 return JX9_OK;
481 }
482 x = jx9_value_to_double(apArg[0]);
483 /* Perform the requested operation */
484 r = asin(x);
485 /* store the result back */
486 jx9_result_double(pCtx, r);
487 return JX9_OK;
488}
489/*
490 * float sinh(float $arg )
491 * Hyperbolic sine.
492 * Parameter
493 * The number to process.
494 * Return
495 * The hyperbolic sine of arg.
496 */
497static int jx9Builtin_sinh(jx9_context *pCtx, int nArg, jx9_value **apArg)
498{
499 double r, x;
500 if( nArg < 1 ){
501 /* Missing argument, return 0 */
502 jx9_result_int(pCtx, 0);
503 return JX9_OK;
504 }
505 x = jx9_value_to_double(apArg[0]);
506 /* Perform the requested operation */
507 r = sinh(x);
508 /* store the result back */
509 jx9_result_double(pCtx, r);
510 return JX9_OK;
511}
512/*
513 * float ceil(float $arg )
514 * Round fractions up.
515 * Parameter
516 * The number to process.
517 * Return
518 * The next highest integer value by rounding up value if necessary.
519 */
520static int jx9Builtin_ceil(jx9_context *pCtx, int nArg, jx9_value **apArg)
521{
522 double r, x;
523 if( nArg < 1 ){
524 /* Missing argument, return 0 */
525 jx9_result_int(pCtx, 0);
526 return JX9_OK;
527 }
528 x = jx9_value_to_double(apArg[0]);
529 /* Perform the requested operation */
530 r = ceil(x);
531 /* store the result back */
532 jx9_result_double(pCtx, r);
533 return JX9_OK;
534}
535/*
536 * float tan(float $arg )
537 * Tangent.
538 * Parameter
539 * The number to process.
540 * Return
541 * The tangent of arg.
542 */
543static int jx9Builtin_tan(jx9_context *pCtx, int nArg, jx9_value **apArg)
544{
545 double r, x;
546 if( nArg < 1 ){
547 /* Missing argument, return 0 */
548 jx9_result_int(pCtx, 0);
549 return JX9_OK;
550 }
551 x = jx9_value_to_double(apArg[0]);
552 /* Perform the requested operation */
553 r = tan(x);
554 /* store the result back */
555 jx9_result_double(pCtx, r);
556 return JX9_OK;
557}
558/*
559 * float atan(float $arg )
560 * Arc tangent.
561 * Parameter
562 * The number to process.
563 * Return
564 * The arc tangent of arg.
565 */
566static int jx9Builtin_atan(jx9_context *pCtx, int nArg, jx9_value **apArg)
567{
568 double r, x;
569 if( nArg < 1 ){
570 /* Missing argument, return 0 */
571 jx9_result_int(pCtx, 0);
572 return JX9_OK;
573 }
574 x = jx9_value_to_double(apArg[0]);
575 /* Perform the requested operation */
576 r = atan(x);
577 /* store the result back */
578 jx9_result_double(pCtx, r);
579 return JX9_OK;
580}
581/*
582 * float tanh(float $arg )
583 * Hyperbolic tangent.
584 * Parameter
585 * The number to process.
586 * Return
587 * The Hyperbolic tangent of arg.
588 */
589static int jx9Builtin_tanh(jx9_context *pCtx, int nArg, jx9_value **apArg)
590{
591 double r, x;
592 if( nArg < 1 ){
593 /* Missing argument, return 0 */
594 jx9_result_int(pCtx, 0);
595 return JX9_OK;
596 }
597 x = jx9_value_to_double(apArg[0]);
598 /* Perform the requested operation */
599 r = tanh(x);
600 /* store the result back */
601 jx9_result_double(pCtx, r);
602 return JX9_OK;
603}
604/*
605 * float atan2(float $y, float $x)
606 * Arc tangent of two variable.
607 * Parameter
608 * $y = Dividend parameter.
609 * $x = Divisor parameter.
610 * Return
611 * The arc tangent of y/x in radian.
612 */
613static int jx9Builtin_atan2(jx9_context *pCtx, int nArg, jx9_value **apArg)
614{
615 double r, x, y;
616 if( nArg < 2 ){
617 /* Missing arguments, return 0 */
618 jx9_result_int(pCtx, 0);
619 return JX9_OK;
620 }
621 y = jx9_value_to_double(apArg[0]);
622 x = jx9_value_to_double(apArg[1]);
623 /* Perform the requested operation */
624 r = atan2(y, x);
625 /* store the result back */
626 jx9_result_double(pCtx, r);
627 return JX9_OK;
628}
629/*
630 * float/int64 abs(float/int64 $arg )
631 * Absolute value.
632 * Parameter
633 * The number to process.
634 * Return
635 * The absolute value of number.
636 */
637static int jx9Builtin_abs(jx9_context *pCtx, int nArg, jx9_value **apArg)
638{
639 int is_float;
640 if( nArg < 1 ){
641 /* Missing argument, return 0 */
642 jx9_result_int(pCtx, 0);
643 return JX9_OK;
644 }
645 is_float = jx9_value_is_float(apArg[0]);
646 if( is_float ){
647 double r, x;
648 x = jx9_value_to_double(apArg[0]);
649 /* Perform the requested operation */
650 r = fabs(x);
651 jx9_result_double(pCtx, r);
652 }else{
653 int r, x;
654 x = jx9_value_to_int(apArg[0]);
655 /* Perform the requested operation */
656 r = abs(x);
657 jx9_result_int(pCtx, r);
658 }
659 return JX9_OK;
660}
661/*
662 * float log(float $arg, [int/float $base])
663 * Natural logarithm.
664 * Parameter
665 * $arg: The number to process.
666 * $base: The optional logarithmic base to use. (only base-10 is supported)
667 * Return
668 * The logarithm of arg to base, if given, or the natural logarithm.
669 * Note:
670 * only Natural log and base-10 log are supported.
671 */
672static int jx9Builtin_log(jx9_context *pCtx, int nArg, jx9_value **apArg)
673{
674 double r, x;
675 if( nArg < 1 ){
676 /* Missing argument, return 0 */
677 jx9_result_int(pCtx, 0);
678 return JX9_OK;
679 }
680 x = jx9_value_to_double(apArg[0]);
681 /* Perform the requested operation */
682 if( nArg == 2 && jx9_value_is_numeric(apArg[1]) && jx9_value_to_int(apArg[1]) == 10 ){
683 /* Base-10 log */
684 r = log10(x);
685 }else{
686 r = log(x);
687 }
688 /* store the result back */
689 jx9_result_double(pCtx, r);
690 return JX9_OK;
691}
692/*
693 * float log10(float $arg )
694 * Base-10 logarithm.
695 * Parameter
696 * The number to process.
697 * Return
698 * The Base-10 logarithm of the given number.
699 */
700static int jx9Builtin_log10(jx9_context *pCtx, int nArg, jx9_value **apArg)
701{
702 double r, x;
703 if( nArg < 1 ){
704 /* Missing argument, return 0 */
705 jx9_result_int(pCtx, 0);
706 return JX9_OK;
707 }
708 x = jx9_value_to_double(apArg[0]);
709 /* Perform the requested operation */
710 r = log10(x);
711 /* store the result back */
712 jx9_result_double(pCtx, r);
713 return JX9_OK;
714}
715/*
716 * number pow(number $base, number $exp)
717 * Exponential expression.
718 * Parameter
719 * base
720 * The base to use.
721 * exp
722 * The exponent.
723 * Return
724 * base raised to the power of exp.
725 * If the result can be represented as integer it will be returned
726 * as type integer, else it will be returned as type float.
727 */
728static int jx9Builtin_pow(jx9_context *pCtx, int nArg, jx9_value **apArg)
729{
730 double r, x, y;
731 if( nArg < 1 ){
732 /* Missing argument, return 0 */
733 jx9_result_int(pCtx, 0);
734 return JX9_OK;
735 }
736 x = jx9_value_to_double(apArg[0]);
737 y = jx9_value_to_double(apArg[1]);
738 /* Perform the requested operation */
739 r = pow(x, y);
740 jx9_result_double(pCtx, r);
741 return JX9_OK;
742}
743/*
744 * float pi(void)
745 * Returns an approximation of pi.
746 * Note
747 * you can use the M_PI constant which yields identical results to pi().
748 * Return
749 * The value of pi as float.
750 */
751static int jx9Builtin_pi(jx9_context *pCtx, int nArg, jx9_value **apArg)
752{
753 SXUNUSED(nArg); /* cc warning */
754 SXUNUSED(apArg);
755 jx9_result_double(pCtx, JX9_PI);
756 return JX9_OK;
757}
758/*
759 * float fmod(float $x, float $y)
760 * Returns the floating point remainder (modulo) of the division of the arguments.
761 * Parameters
762 * $x
763 * The dividend
764 * $y
765 * The divisor
766 * Return
767 * The floating point remainder of x/y.
768 */
769static int jx9Builtin_fmod(jx9_context *pCtx, int nArg, jx9_value **apArg)
770{
771 double x, y, r;
772 if( nArg < 2 ){
773 /* Missing arguments */
774 jx9_result_double(pCtx, 0);
775 return JX9_OK;
776 }
777 /* Extract given arguments */
778 x = jx9_value_to_double(apArg[0]);
779 y = jx9_value_to_double(apArg[1]);
780 /* Perform the requested operation */
781 r = fmod(x, y);
782 /* Processing result */
783 jx9_result_double(pCtx, r);
784 return JX9_OK;
785}
786/*
787 * float hypot(float $x, float $y)
788 * Calculate the length of the hypotenuse of a right-angle triangle .
789 * Parameters
790 * $x
791 * Length of first side
792 * $y
793 * Length of first side
794 * Return
795 * Calculated length of the hypotenuse.
796 */
797static int jx9Builtin_hypot(jx9_context *pCtx, int nArg, jx9_value **apArg)
798{
799 double x, y, r;
800 if( nArg < 2 ){
801 /* Missing arguments */
802 jx9_result_double(pCtx, 0);
803 return JX9_OK;
804 }
805 /* Extract given arguments */
806 x = jx9_value_to_double(apArg[0]);
807 y = jx9_value_to_double(apArg[1]);
808 /* Perform the requested operation */
809 r = hypot(x, y);
810 /* Processing result */
811 jx9_result_double(pCtx, r);
812 return JX9_OK;
813}
814#endif /* JX9_ENABLE_MATH_FUNC */
815/*
816 * float round ( float $val [, int $precision = 0 [, int $mode = JX9_ROUND_HALF_UP ]] )
817 * Exponential expression.
818 * Parameter
819 * $val
820 * The value to round.
821 * $precision
822 * The optional number of decimal digits to round to.
823 * $mode
824 * One of JX9_ROUND_HALF_UP, JX9_ROUND_HALF_DOWN, JX9_ROUND_HALF_EVEN, or JX9_ROUND_HALF_ODD.
825 * (not supported).
826 * Return
827 * The rounded value.
828 */
829static int jx9Builtin_round(jx9_context *pCtx, int nArg, jx9_value **apArg)
830{
831 int n = 0;
832 double r;
833 if( nArg < 1 ){
834 /* Missing argument, return 0 */
835 jx9_result_int(pCtx, 0);
836 return JX9_OK;
837 }
838 /* Extract the precision if available */
839 if( nArg > 1 ){
840 n = jx9_value_to_int(apArg[1]);
841 if( n>30 ){
842 n = 30;
843 }
844 if( n<0 ){
845 n = 0;
846 }
847 }
848 r = jx9_value_to_double(apArg[0]);
849 /* If Y==0 and X will fit in a 64-bit int,
850 * handle the rounding directly.Otherwise
851 * use our own cutsom printf [i.e:SyBufferFormat()].
852 */
853 if( n==0 && r>=0 && r<LARGEST_INT64-1 ){
854 r = (double)((jx9_int64)(r+0.5));
855 }else if( n==0 && r<0 && (-r)<LARGEST_INT64-1 ){
856 r = -(double)((jx9_int64)((-r)+0.5));
857 }else{
858 char zBuf[256];
859 sxu32 nLen;
860 nLen = SyBufferFormat(zBuf, sizeof(zBuf), "%.*f", n, r);
861 /* Convert the string to real number */
862 SyStrToReal(zBuf, nLen, (void *)&r, 0);
863 }
864 /* Return thr rounded value */
865 jx9_result_double(pCtx, r);
866 return JX9_OK;
867}
868/*
869 * string dechex(int $number)
870 * Decimal to hexadecimal.
871 * Parameters
872 * $number
873 * Decimal value to convert
874 * Return
875 * Hexadecimal string representation of number
876 */
877static int jx9Builtin_dechex(jx9_context *pCtx, int nArg, jx9_value **apArg)
878{
879 int iVal;
880 if( nArg < 1 ){
881 /* Missing arguments, return null */
882 jx9_result_null(pCtx);
883 return JX9_OK;
884 }
885 /* Extract the given number */
886 iVal = jx9_value_to_int(apArg[0]);
887 /* Format */
888 jx9_result_string_format(pCtx, "%x", iVal);
889 return JX9_OK;
890}
891/*
892 * string decoct(int $number)
893 * Decimal to Octal.
894 * Parameters
895 * $number
896 * Decimal value to convert
897 * Return
898 * Octal string representation of number
899 */
900static int jx9Builtin_decoct(jx9_context *pCtx, int nArg, jx9_value **apArg)
901{
902 int iVal;
903 if( nArg < 1 ){
904 /* Missing arguments, return null */
905 jx9_result_null(pCtx);
906 return JX9_OK;
907 }
908 /* Extract the given number */
909 iVal = jx9_value_to_int(apArg[0]);
910 /* Format */
911 jx9_result_string_format(pCtx, "%o", iVal);
912 return JX9_OK;
913}
914/*
915 * string decbin(int $number)
916 * Decimal to binary.
917 * Parameters
918 * $number
919 * Decimal value to convert
920 * Return
921 * Binary string representation of number
922 */
923static int jx9Builtin_decbin(jx9_context *pCtx, int nArg, jx9_value **apArg)
924{
925 int iVal;
926 if( nArg < 1 ){
927 /* Missing arguments, return null */
928 jx9_result_null(pCtx);
929 return JX9_OK;
930 }
931 /* Extract the given number */
932 iVal = jx9_value_to_int(apArg[0]);
933 /* Format */
934 jx9_result_string_format(pCtx, "%B", iVal);
935 return JX9_OK;
936}
937/*
938 * int64 hexdec(string $hex_string)
939 * Hexadecimal to decimal.
940 * Parameters
941 * $hex_string
942 * The hexadecimal string to convert
943 * Return
944 * The decimal representation of hex_string
945 */
946static int jx9Builtin_hexdec(jx9_context *pCtx, int nArg, jx9_value **apArg)
947{
948 const char *zString, *zEnd;
949 jx9_int64 iVal;
950 int nLen;
951 if( nArg < 1 ){
952 /* Missing arguments, return -1 */
953 jx9_result_int(pCtx, -1);
954 return JX9_OK;
955 }
956 iVal = 0;
957 if( jx9_value_is_string(apArg[0]) ){
958 /* Extract the given string */
959 zString = jx9_value_to_string(apArg[0], &nLen);
960 /* Delimit the string */
961 zEnd = &zString[nLen];
962 /* Ignore non hex-stream */
963 while( zString < zEnd ){
964 if( (unsigned char)zString[0] >= 0xc0 ){
965 /* UTF-8 stream */
966 zString++;
967 while( zString < zEnd && (((unsigned char)zString[0] & 0xc0) == 0x80) ){
968 zString++;
969 }
970 }else{
971 if( SyisHex(zString[0]) ){
972 break;
973 }
974 /* Ignore */
975 zString++;
976 }
977 }
978 if( zString < zEnd ){
979 /* Cast */
980 SyHexStrToInt64(zString, (sxu32)(zEnd-zString), (void *)&iVal, 0);
981 }
982 }else{
983 /* Extract as a 64-bit integer */
984 iVal = jx9_value_to_int64(apArg[0]);
985 }
986 /* Return the number */
987 jx9_result_int64(pCtx, iVal);
988 return JX9_OK;
989}
990/*
991 * int64 bindec(string $bin_string)
992 * Binary to decimal.
993 * Parameters
994 * $bin_string
995 * The binary string to convert
996 * Return
997 * Returns the decimal equivalent of the binary number represented by the binary_string argument.
998 */
999static int jx9Builtin_bindec(jx9_context *pCtx, int nArg, jx9_value **apArg)
1000{
1001 const char *zString;
1002 jx9_int64 iVal;
1003 int nLen;
1004 if( nArg < 1 ){
1005 /* Missing arguments, return -1 */
1006 jx9_result_int(pCtx, -1);
1007 return JX9_OK;
1008 }
1009 iVal = 0;
1010 if( jx9_value_is_string(apArg[0]) ){
1011 /* Extract the given string */
1012 zString = jx9_value_to_string(apArg[0], &nLen);
1013 if( nLen > 0 ){
1014 /* Perform a binary cast */
1015 SyBinaryStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0);
1016 }
1017 }else{
1018 /* Extract as a 64-bit integer */
1019 iVal = jx9_value_to_int64(apArg[0]);
1020 }
1021 /* Return the number */
1022 jx9_result_int64(pCtx, iVal);
1023 return JX9_OK;
1024}
1025/*
1026 * int64 octdec(string $oct_string)
1027 * Octal to decimal.
1028 * Parameters
1029 * $oct_string
1030 * The octal string to convert
1031 * Return
1032 * Returns the decimal equivalent of the octal number represented by the octal_string argument.
1033 */
1034static int jx9Builtin_octdec(jx9_context *pCtx, int nArg, jx9_value **apArg)
1035{
1036 const char *zString;
1037 jx9_int64 iVal;
1038 int nLen;
1039 if( nArg < 1 ){
1040 /* Missing arguments, return -1 */
1041 jx9_result_int(pCtx, -1);
1042 return JX9_OK;
1043 }
1044 iVal = 0;
1045 if( jx9_value_is_string(apArg[0]) ){
1046 /* Extract the given string */
1047 zString = jx9_value_to_string(apArg[0], &nLen);
1048 if( nLen > 0 ){
1049 /* Perform the cast */
1050 SyOctalStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0);
1051 }
1052 }else{
1053 /* Extract as a 64-bit integer */
1054 iVal = jx9_value_to_int64(apArg[0]);
1055 }
1056 /* Return the number */
1057 jx9_result_int64(pCtx, iVal);
1058 return JX9_OK;
1059}
1060/*
1061 * string base_convert(string $number, int $frombase, int $tobase)
1062 * Convert a number between arbitrary bases.
1063 * Parameters
1064 * $number
1065 * The number to convert
1066 * $frombase
1067 * The base number is in
1068 * $tobase
1069 * The base to convert number to
1070 * Return
1071 * Number converted to base tobase
1072 */
1073static int jx9Builtin_base_convert(jx9_context *pCtx, int nArg, jx9_value **apArg)
1074{
1075 int nLen, iFbase, iTobase;
1076 const char *zNum;
1077 jx9_int64 iNum;
1078 if( nArg < 3 ){
1079 /* Return the empty string*/
1080 jx9_result_string(pCtx, "", 0);
1081 return JX9_OK;
1082 }
1083 /* Base numbers */
1084 iFbase = jx9_value_to_int(apArg[1]);
1085 iTobase = jx9_value_to_int(apArg[2]);
1086 if( jx9_value_is_string(apArg[0]) ){
1087 /* Extract the target number */
1088 zNum = jx9_value_to_string(apArg[0], &nLen);
1089 if( nLen < 1 ){
1090 /* Return the empty string*/
1091 jx9_result_string(pCtx, "", 0);
1092 return JX9_OK;
1093 }
1094 /* Base conversion */
1095 switch(iFbase){
1096 case 16:
1097 /* Hex */
1098 SyHexStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0);
1099 break;
1100 case 8:
1101 /* Octal */
1102 SyOctalStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0);
1103 break;
1104 case 2:
1105 /* Binary */
1106 SyBinaryStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0);
1107 break;
1108 default:
1109 /* Decimal */
1110 SyStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0);
1111 break;
1112 }
1113 }else{
1114 iNum = jx9_value_to_int64(apArg[0]);
1115 }
1116 switch(iTobase){
1117 case 16:
1118 /* Hex */
1119 jx9_result_string_format(pCtx, "%qx", iNum); /* Quad hex */
1120 break;
1121 case 8:
1122 /* Octal */
1123 jx9_result_string_format(pCtx, "%qo", iNum); /* Quad octal */
1124 break;
1125 case 2:
1126 /* Binary */
1127 jx9_result_string_format(pCtx, "%qB", iNum); /* Quad binary */
1128 break;
1129 default:
1130 /* Decimal */
1131 jx9_result_string_format(pCtx, "%qd", iNum); /* Quad decimal */
1132 break;
1133 }
1134 return JX9_OK;
1135}
1136/*
1137 * Section:
1138 * String handling Functions.
1139 * Authors:
1140 * Symisc Systems, devel@symisc.net.
1141 * Copyright (C) Symisc Systems, http://jx9.symisc.net
1142 * Status:
1143 * Stable.
1144 */
1145/*
1146 * string substr(string $string, int $start[, int $length ])
1147 * Return part of a string.
1148 * Parameters
1149 * $string
1150 * The input string. Must be one character or longer.
1151 * $start
1152 * If start is non-negative, the returned string will start at the start'th position
1153 * in string, counting from zero. For instance, in the string 'abcdef', the character
1154 * at position 0 is 'a', the character at position 2 is 'c', and so forth.
1155 * If start is negative, the returned string will start at the start'th character
1156 * from the end of string.
1157 * If string is less than or equal to start characters long, FALSE will be returned.
1158 * $length
1159 * If length is given and is positive, the string returned will contain at most length
1160 * characters beginning from start (depending on the length of string).
1161 * If length is given and is negative, then that many characters will be omitted from
1162 * the end of string (after the start position has been calculated when a start is negative).
1163 * If start denotes the position of this truncation or beyond, false will be returned.
1164 * If length is given and is 0, FALSE or NULL an empty string will be returned.
1165 * If length is omitted, the substring starting from start until the end of the string
1166 * will be returned.
1167 * Return
1168 * Returns the extracted part of string, or FALSE on failure or an empty string.
1169 */
1170static int jx9Builtin_substr(jx9_context *pCtx, int nArg, jx9_value **apArg)
1171{
1172 const char *zSource, *zOfft;
1173 int nOfft, nLen, nSrcLen;
1174 if( nArg < 2 ){
1175 /* return FALSE */
1176 jx9_result_bool(pCtx, 0);
1177 return JX9_OK;
1178 }
1179 /* Extract the target string */
1180 zSource = jx9_value_to_string(apArg[0], &nSrcLen);
1181 if( nSrcLen < 1 ){
1182 /* Empty string, return FALSE */
1183 jx9_result_bool(pCtx, 0);
1184 return JX9_OK;
1185 }
1186 nLen = nSrcLen; /* cc warning */
1187 /* Extract the offset */
1188 nOfft = jx9_value_to_int(apArg[1]);
1189 if( nOfft < 0 ){
1190 zOfft = &zSource[nSrcLen+nOfft];
1191 if( zOfft < zSource ){
1192 /* Invalid offset */
1193 jx9_result_bool(pCtx, 0);
1194 return JX9_OK;
1195 }
1196 nLen = (int)(&zSource[nSrcLen]-zOfft);
1197 nOfft = (int)(zOfft-zSource);
1198 }else if( nOfft >= nSrcLen ){
1199 /* Invalid offset */
1200 jx9_result_bool(pCtx, 0);
1201 return JX9_OK;
1202 }else{
1203 zOfft = &zSource[nOfft];
1204 nLen = nSrcLen - nOfft;
1205 }
1206 if( nArg > 2 ){
1207 /* Extract the length */
1208 nLen = jx9_value_to_int(apArg[2]);
1209 if( nLen == 0 ){
1210 /* Invalid length, return an empty string */
1211 jx9_result_string(pCtx, "", 0);
1212 return JX9_OK;
1213 }else if( nLen < 0 ){
1214 nLen = nSrcLen + nLen - nOfft;
1215 if( nLen < 1 ){
1216 /* Invalid length */
1217 nLen = nSrcLen - nOfft;
1218 }
1219 }
1220 if( nLen + nOfft > nSrcLen ){
1221 /* Invalid length */
1222 nLen = nSrcLen - nOfft;
1223 }
1224 }
1225 /* Return the substring */
1226 jx9_result_string(pCtx, zOfft, nLen);
1227 return JX9_OK;
1228}
1229/*
1230 * int substr_compare(string $main_str, string $str , int $offset[, int $length[, bool $case_insensitivity = false ]])
1231 * Binary safe comparison of two strings from an offset, up to length characters.
1232 * Parameters
1233 * $main_str
1234 * The main string being compared.
1235 * $str
1236 * The secondary string being compared.
1237 * $offset
1238 * The start position for the comparison. If negative, it starts counting from
1239 * the end of the string.
1240 * $length
1241 * The length of the comparison. The default value is the largest of the length
1242 * of the str compared to the length of main_str less the offset.
1243 * $case_insensitivity
1244 * If case_insensitivity is TRUE, comparison is case insensitive.
1245 * Return
1246 * Returns < 0 if main_str from position offset is less than str, > 0 if it is greater than
1247 * str, and 0 if they are equal. If offset is equal to or greater than the length of main_str
1248 * or length is set and is less than 1, substr_compare() prints a warning and returns FALSE.
1249 */
1250static int jx9Builtin_substr_compare(jx9_context *pCtx, int nArg, jx9_value **apArg)
1251{
1252 const char *zSource, *zOfft, *zSub;
1253 int nOfft, nLen, nSrcLen, nSublen;
1254 int iCase = 0;
1255 int rc;
1256 if( nArg < 3 ){
1257 /* Missing arguments, return FALSE */
1258 jx9_result_bool(pCtx, 0);
1259 return JX9_OK;
1260 }
1261 /* Extract the target string */
1262 zSource = jx9_value_to_string(apArg[0], &nSrcLen);
1263 if( nSrcLen < 1 ){
1264 /* Empty string, return FALSE */
1265 jx9_result_bool(pCtx, 0);
1266 return JX9_OK;
1267 }
1268 nLen = nSrcLen; /* cc warning */
1269 /* Extract the substring */
1270 zSub = jx9_value_to_string(apArg[1], &nSublen);
1271 if( nSublen < 1 || nSublen > nSrcLen){
1272 /* Empty string, return FALSE */
1273 jx9_result_bool(pCtx, 0);
1274 return JX9_OK;
1275 }
1276 /* Extract the offset */
1277 nOfft = jx9_value_to_int(apArg[2]);
1278 if( nOfft < 0 ){
1279 zOfft = &zSource[nSrcLen+nOfft];
1280 if( zOfft < zSource ){
1281 /* Invalid offset */
1282 jx9_result_bool(pCtx, 0);
1283 return JX9_OK;
1284 }
1285 nLen = (int)(&zSource[nSrcLen]-zOfft);
1286 nOfft = (int)(zOfft-zSource);
1287 }else if( nOfft >= nSrcLen ){
1288 /* Invalid offset */
1289 jx9_result_bool(pCtx, 0);
1290 return JX9_OK;
1291 }else{
1292 zOfft = &zSource[nOfft];
1293 nLen = nSrcLen - nOfft;
1294 }
1295 if( nArg > 3 ){
1296 /* Extract the length */
1297 nLen = jx9_value_to_int(apArg[3]);
1298 if( nLen < 1 ){
1299 /* Invalid length */
1300 jx9_result_int(pCtx, 1);
1301 return JX9_OK;
1302 }else if( nLen + nOfft > nSrcLen ){
1303 /* Invalid length */
1304 nLen = nSrcLen - nOfft;
1305 }
1306 if( nArg > 4 ){
1307 /* Case-sensitive or not */
1308 iCase = jx9_value_to_bool(apArg[4]);
1309 }
1310 }
1311 /* Perform the comparison */
1312 if( iCase ){
1313 rc = SyStrnicmp(zOfft, zSub, (sxu32)nLen);
1314 }else{
1315 rc = SyStrncmp(zOfft, zSub, (sxu32)nLen);
1316 }
1317 /* Comparison result */
1318 jx9_result_int(pCtx, rc);
1319 return JX9_OK;
1320}
1321/*
1322 * int substr_count(string $haystack, string $needle[, int $offset = 0 [, int $length ]])
1323 * Count the number of substring occurrences.
1324 * Parameters
1325 * $haystack
1326 * The string to search in
1327 * $needle
1328 * The substring to search for
1329 * $offset
1330 * The offset where to start counting
1331 * $length (NOT USED)
1332 * The maximum length after the specified offset to search for the substring.
1333 * It outputs a warning if the offset plus the length is greater than the haystack length.
1334 * Return
1335 * Toral number of substring occurrences.
1336 */
1337static int jx9Builtin_substr_count(jx9_context *pCtx, int nArg, jx9_value **apArg)
1338{
1339 const char *zText, *zPattern, *zEnd;
1340 int nTextlen, nPatlen;
1341 int iCount = 0;
1342 sxu32 nOfft;
1343 sxi32 rc;
1344 if( nArg < 2 ){
1345 /* Missing arguments */
1346 jx9_result_int(pCtx, 0);
1347 return JX9_OK;
1348 }
1349 /* Point to the haystack */
1350 zText = jx9_value_to_string(apArg[0], &nTextlen);
1351 /* Point to the neddle */
1352 zPattern = jx9_value_to_string(apArg[1], &nPatlen);
1353 if( nTextlen < 1 || nPatlen < 1 || nPatlen > nTextlen ){
1354 /* NOOP, return zero */
1355 jx9_result_int(pCtx, 0);
1356 return JX9_OK;
1357 }
1358 if( nArg > 2 ){
1359 int nOfft;
1360 /* Extract the offset */
1361 nOfft = jx9_value_to_int(apArg[2]);
1362 if( nOfft < 0 || nOfft > nTextlen ){
1363 /* Invalid offset, return zero */
1364 jx9_result_int(pCtx, 0);
1365 return JX9_OK;
1366 }
1367 /* Point to the desired offset */
1368 zText = &zText[nOfft];
1369 /* Adjust length */
1370 nTextlen -= nOfft;
1371 }
1372 /* Point to the end of the string */
1373 zEnd = &zText[nTextlen];
1374 if( nArg > 3 ){
1375 int nLen;
1376 /* Extract the length */
1377 nLen = jx9_value_to_int(apArg[3]);
1378 if( nLen < 0 || nLen > nTextlen ){
1379 /* Invalid length, return 0 */
1380 jx9_result_int(pCtx, 0);
1381 return JX9_OK;
1382 }
1383 /* Adjust pointer */
1384 nTextlen = nLen;
1385 zEnd = &zText[nTextlen];
1386 }
1387 /* Perform the search */
1388 for(;;){
1389 rc = SyBlobSearch((const void *)zText, (sxu32)(zEnd-zText), (const void *)zPattern, nPatlen, &nOfft);
1390 if( rc != SXRET_OK ){
1391 /* Pattern not found, break immediately */
1392 break;
1393 }
1394 /* Increment counter and update the offset */
1395 iCount++;
1396 zText += nOfft + nPatlen;
1397 if( zText >= zEnd ){
1398 break;
1399 }
1400 }
1401 /* Pattern count */
1402 jx9_result_int(pCtx, iCount);
1403 return JX9_OK;
1404}
1405/*
1406 * string chunk_split(string $body[, int $chunklen = 76 [, string $end = "\r\n" ]])
1407 * Split a string into smaller chunks.
1408 * Parameters
1409 * $body
1410 * The string to be chunked.
1411 * $chunklen
1412 * The chunk length.
1413 * $end
1414 * The line ending sequence.
1415 * Return
1416 * The chunked string or NULL on failure.
1417 */
1418static int jx9Builtin_chunk_split(jx9_context *pCtx, int nArg, jx9_value **apArg)
1419{
1420 const char *zIn, *zEnd, *zSep = "\r\n";
1421 int nSepLen, nChunkLen, nLen;
1422 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1423 /* Nothing to split, return null */
1424 jx9_result_null(pCtx);
1425 return JX9_OK;
1426 }
1427 /* initialize/Extract arguments */
1428 nSepLen = (int)sizeof("\r\n") - 1;
1429 nChunkLen = 76;
1430 zIn = jx9_value_to_string(apArg[0], &nLen);
1431 zEnd = &zIn[nLen];
1432 if( nArg > 1 ){
1433 /* Chunk length */
1434 nChunkLen = jx9_value_to_int(apArg[1]);
1435 if( nChunkLen < 1 ){
1436 /* Switch back to the default length */
1437 nChunkLen = 76;
1438 }
1439 if( nArg > 2 ){
1440 /* Separator */
1441 zSep = jx9_value_to_string(apArg[2], &nSepLen);
1442 if( nSepLen < 1 ){
1443 /* Switch back to the default separator */
1444 zSep = "\r\n";
1445 nSepLen = (int)sizeof("\r\n") - 1;
1446 }
1447 }
1448 }
1449 /* Perform the requested operation */
1450 if( nChunkLen > nLen ){
1451 /* Nothing to split, return the string and the separator */
1452 jx9_result_string_format(pCtx, "%.*s%.*s", nLen, zIn, nSepLen, zSep);
1453 return JX9_OK;
1454 }
1455 while( zIn < zEnd ){
1456 if( nChunkLen > (int)(zEnd-zIn) ){
1457 nChunkLen = (int)(zEnd - zIn);
1458 }
1459 /* Append the chunk and the separator */
1460 jx9_result_string_format(pCtx, "%.*s%.*s", nChunkLen, zIn, nSepLen, zSep);
1461 /* Point beyond the chunk */
1462 zIn += nChunkLen;
1463 }
1464 return JX9_OK;
1465}
1466/*
1467 * string htmlspecialchars(string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $charset]])
1468 * HTML escaping of special characters.
1469 * The translations performed are:
1470 * '&' (ampersand) ==> '&amp;'
1471 * '"' (double quote) ==> '&quot;' when ENT_NOQUOTES is not set.
1472 * "'" (single quote) ==> '&#039;' only when ENT_QUOTES is set.
1473 * '<' (less than) ==> '&lt;'
1474 * '>' (greater than) ==> '&gt;'
1475 * Parameters
1476 * $string
1477 * The string being converted.
1478 * $flags
1479 * A bitmask of one or more of the following flags, which specify how to handle quotes.
1480 * The default is ENT_COMPAT | ENT_HTML401.
1481 * ENT_COMPAT Will convert double-quotes and leave single-quotes alone.
1482 * ENT_QUOTES Will convert both double and single quotes.
1483 * ENT_NOQUOTES Will leave both double and single quotes unconverted.
1484 * ENT_IGNORE Silently discard invalid code unit sequences instead of returning an empty string.
1485 * $charset
1486 * Defines character set used in conversion. The default character set is ISO-8859-1. (Not used)
1487 * Return
1488 * The escaped string or NULL on failure.
1489 */
1490static int jx9Builtin_htmlspecialchars(jx9_context *pCtx, int nArg, jx9_value **apArg)
1491{
1492 const char *zCur, *zIn, *zEnd;
1493 int iFlags = 0x01|0x40; /* ENT_COMPAT | ENT_HTML401 */
1494 int nLen, c;
1495 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1496 /* Missing/Invalid arguments, return NULL */
1497 jx9_result_null(pCtx);
1498 return JX9_OK;
1499 }
1500 /* Extract the target string */
1501 zIn = jx9_value_to_string(apArg[0], &nLen);
1502 zEnd = &zIn[nLen];
1503 /* Extract the flags if available */
1504 if( nArg > 1 ){
1505 iFlags = jx9_value_to_int(apArg[1]);
1506 if( iFlags < 0 ){
1507 iFlags = 0x01|0x40;
1508 }
1509 }
1510 /* Perform the requested operation */
1511 for(;;){
1512 if( zIn >= zEnd ){
1513 break;
1514 }
1515 zCur = zIn;
1516 while( zIn < zEnd && zIn[0] != '&' && zIn[0] != '\'' && zIn[0] != '"' && zIn[0] != '<' && zIn[0] != '>' ){
1517 zIn++;
1518 }
1519 if( zCur < zIn ){
1520 /* Append the raw string verbatim */
1521 jx9_result_string(pCtx, zCur, (int)(zIn-zCur));
1522 }
1523 if( zIn >= zEnd ){
1524 break;
1525 }
1526 c = zIn[0];
1527 if( c == '&' ){
1528 /* Expand '&amp;' */
1529 jx9_result_string(pCtx, "&amp;", (int)sizeof("&amp;")-1);
1530 }else if( c == '<' ){
1531 /* Expand '&lt;' */
1532 jx9_result_string(pCtx, "&lt;", (int)sizeof("&lt;")-1);
1533 }else if( c == '>' ){
1534 /* Expand '&gt;' */
1535 jx9_result_string(pCtx, "&gt;", (int)sizeof("&gt;")-1);
1536 }else if( c == '\'' ){
1537 if( iFlags & 0x02 /*ENT_QUOTES*/ ){
1538 /* Expand '&#039;' */
1539 jx9_result_string(pCtx, "&#039;", (int)sizeof("&#039;")-1);
1540 }else{
1541 /* Leave the single quote untouched */
1542 jx9_result_string(pCtx, "'", (int)sizeof(char));
1543 }
1544 }else if( c == '"' ){
1545 if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){
1546 /* Expand '&quot;' */
1547 jx9_result_string(pCtx, "&quot;", (int)sizeof("&quot;")-1);
1548 }else{
1549 /* Leave the double quote untouched */
1550 jx9_result_string(pCtx, "\"", (int)sizeof(char));
1551 }
1552 }
1553 /* Ignore the unsafe HTML character */
1554 zIn++;
1555 }
1556 return JX9_OK;
1557}
1558/*
1559 * string htmlspecialchars_decode(string $string[, int $quote_style = ENT_COMPAT ])
1560 * Unescape HTML entities.
1561 * Parameters
1562 * $string
1563 * The string to decode
1564 * $quote_style
1565 * The quote style. One of the following constants:
1566 * ENT_COMPAT Will convert double-quotes and leave single-quotes alone (default)
1567 * ENT_QUOTES Will convert both double and single quotes
1568 * ENT_NOQUOTES Will leave both double and single quotes unconverted
1569 * Return
1570 * The unescaped string or NULL on failure.
1571 */
1572static int jx9Builtin_htmlspecialchars_decode(jx9_context *pCtx, int nArg, jx9_value **apArg)
1573{
1574 const char *zCur, *zIn, *zEnd;
1575 int iFlags = 0x01; /* ENT_COMPAT */
1576 int nLen, nJump;
1577 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1578 /* Missing/Invalid arguments, return NULL */
1579 jx9_result_null(pCtx);
1580 return JX9_OK;
1581 }
1582 /* Extract the target string */
1583 zIn = jx9_value_to_string(apArg[0], &nLen);
1584 zEnd = &zIn[nLen];
1585 /* Extract the flags if available */
1586 if( nArg > 1 ){
1587 iFlags = jx9_value_to_int(apArg[1]);
1588 if( iFlags < 0 ){
1589 iFlags = 0x01;
1590 }
1591 }
1592 /* Perform the requested operation */
1593 for(;;){
1594 if( zIn >= zEnd ){
1595 break;
1596 }
1597 zCur = zIn;
1598 while( zIn < zEnd && zIn[0] != '&' ){
1599 zIn++;
1600 }
1601 if( zCur < zIn ){
1602 /* Append the raw string verbatim */
1603 jx9_result_string(pCtx, zCur, (int)(zIn-zCur));
1604 }
1605 nLen = (int)(zEnd-zIn);
1606 nJump = (int)sizeof(char);
1607 if( nLen >= (int)sizeof("&amp;")-1 && SyStrnicmp(zIn, "&amp;", sizeof("&amp;")-1) == 0 ){
1608 /* &amp; ==> '&' */
1609 jx9_result_string(pCtx, "&", (int)sizeof(char));
1610 nJump = (int)sizeof("&amp;")-1;
1611 }else if( nLen >= (int)sizeof("&lt;")-1 && SyStrnicmp(zIn, "&lt;", sizeof("&lt;")-1) == 0 ){
1612 /* &lt; ==> < */
1613 jx9_result_string(pCtx, "<", (int)sizeof(char));
1614 nJump = (int)sizeof("&lt;")-1;
1615 }else if( nLen >= (int)sizeof("&gt;")-1 && SyStrnicmp(zIn, "&gt;", sizeof("&gt;")-1) == 0 ){
1616 /* &gt; ==> '>' */
1617 jx9_result_string(pCtx, ">", (int)sizeof(char));
1618 nJump = (int)sizeof("&gt;")-1;
1619 }else if( nLen >= (int)sizeof("&quot;")-1 && SyStrnicmp(zIn, "&quot;", sizeof("&quot;")-1) == 0 ){
1620 /* &quot; ==> '"' */
1621 if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){
1622 jx9_result_string(pCtx, "\"", (int)sizeof(char));
1623 }else{
1624 /* Leave untouched */
1625 jx9_result_string(pCtx, "&quot;", (int)sizeof("&quot;")-1);
1626 }
1627 nJump = (int)sizeof("&quot;")-1;
1628 }else if( nLen >= (int)sizeof("&#039;")-1 && SyStrnicmp(zIn, "&#039;", sizeof("&#039;")-1) == 0 ){
1629 /* &#039; ==> ''' */
1630 if( iFlags & 0x02 /*ENT_QUOTES*/ ){
1631 /* Expand ''' */
1632 jx9_result_string(pCtx, "'", (int)sizeof(char));
1633 }else{
1634 /* Leave untouched */
1635 jx9_result_string(pCtx, "&#039;", (int)sizeof("&#039;")-1);
1636 }
1637 nJump = (int)sizeof("&#039;")-1;
1638 }else if( nLen >= (int)sizeof(char) ){
1639 /* expand '&' */
1640 jx9_result_string(pCtx, "&", (int)sizeof(char));
1641 }else{
1642 /* No more input to process */
1643 break;
1644 }
1645 zIn += nJump;
1646 }
1647 return JX9_OK;
1648}
1649/* HTML encoding/Decoding table
1650 * Source: Symisc RunTime API.[chm@symisc.net]
1651 */
1652static const char *azHtmlEscape[] = {
1653 "&lt;", "<", "&gt;", ">", "&amp;", "&", "&quot;", "\"", "&#39;", "'",
1654 "&#33;", "!", "&#36;", "$", "&#35;", "#", "&#37;", "%", "&#40;", "(",
1655 "&#41;", ")", "&#123;", "{", "&#125;", "}", "&#61;", "=", "&#43;", "+",
1656 "&#63;", "?", "&#91;", "[", "&#93;", "]", "&#64;", "@", "&#44;", ","
1657 };
1658/*
1659 * array get_html_translation_table(void)
1660 * Returns the translation table used by htmlspecialchars() and htmlentities().
1661 * Parameters
1662 * None
1663 * Return
1664 * The translation table as an array or NULL on failure.
1665 */
1666static int jx9Builtin_get_html_translation_table(jx9_context *pCtx, int nArg, jx9_value **apArg)
1667{
1668 jx9_value *pArray, *pValue;
1669 sxu32 n;
1670 /* Element value */
1671 pValue = jx9_context_new_scalar(pCtx);
1672 if( pValue == 0 ){
1673 SXUNUSED(nArg); /* cc warning */
1674 SXUNUSED(apArg);
1675 /* Return NULL */
1676 jx9_result_null(pCtx);
1677 return JX9_OK;
1678 }
1679 /* Create a new array */
1680 pArray = jx9_context_new_array(pCtx);
1681 if( pArray == 0 ){
1682 /* Return NULL */
1683 jx9_result_null(pCtx);
1684 return JX9_OK;
1685 }
1686 /* Make the table */
1687 for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){
1688 /* Prepare the value */
1689 jx9_value_string(pValue, azHtmlEscape[n], -1 /* Compute length automatically */);
1690 /* Insert the value */
1691 jx9_array_add_strkey_elem(pArray, azHtmlEscape[n+1], pValue);
1692 /* Reset the string cursor */
1693 jx9_value_reset_string_cursor(pValue);
1694 }
1695 /*
1696 * Return the array.
1697 * Don't worry about freeing memory, everything will be automatically
1698 * released upon we return from this function.
1699 */
1700 jx9_result_value(pCtx, pArray);
1701 return JX9_OK;
1702}
1703/*
1704 * string htmlentities( string $string [, int $flags = ENT_COMPAT | ENT_HTML401]);
1705 * Convert all applicable characters to HTML entities
1706 * Parameters
1707 * $string
1708 * The input string.
1709 * $flags
1710 * A bitmask of one or more of the flags (see block-comment on jx9Builtin_htmlspecialchars())
1711 * Return
1712 * The encoded string.
1713 */
1714static int jx9Builtin_htmlentities(jx9_context *pCtx, int nArg, jx9_value **apArg)
1715{
1716 int iFlags = 0x01; /* ENT_COMPAT */
1717 const char *zIn, *zEnd;
1718 int nLen, c;
1719 sxu32 n;
1720 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1721 /* Missing/Invalid arguments, return NULL */
1722 jx9_result_null(pCtx);
1723 return JX9_OK;
1724 }
1725 /* Extract the target string */
1726 zIn = jx9_value_to_string(apArg[0], &nLen);
1727 zEnd = &zIn[nLen];
1728 /* Extract the flags if available */
1729 if( nArg > 1 ){
1730 iFlags = jx9_value_to_int(apArg[1]);
1731 if( iFlags < 0 ){
1732 iFlags = 0x01;
1733 }
1734 }
1735 /* Perform the requested operation */
1736 for(;;){
1737 if( zIn >= zEnd ){
1738 /* No more input to process */
1739 break;
1740 }
1741 c = zIn[0];
1742 /* Perform a linear lookup on the decoding table */
1743 for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){
1744 if( azHtmlEscape[n+1][0] == c ){
1745 /* Got one */
1746 break;
1747 }
1748 }
1749 if( n < SX_ARRAYSIZE(azHtmlEscape) ){
1750 /* Output the safe sequence [i.e: '<' ==> '&lt;"] */
1751 if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){
1752 /* Expand the double quote verbatim */
1753 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
1754 }else if(c == '\'' && ((iFlags & 0x02 /*ENT_QUOTES*/) == 0 || (iFlags & 0x04) /*ENT_NOQUOTES*/) ){
1755 /* expand single quote verbatim */
1756 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
1757 }else{
1758 jx9_result_string(pCtx, azHtmlEscape[n], -1/*Compute length automatically */);
1759 }
1760 }else{
1761 /* Output character verbatim */
1762 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
1763 }
1764 zIn++;
1765 }
1766 return JX9_OK;
1767}
1768/*
1769 * string html_entity_decode(string $string [, int $quote_style = ENT_COMPAT [, string $charset = 'UTF-8' ]])
1770 * Perform the reverse operation of html_entity_decode().
1771 * Parameters
1772 * $string
1773 * The input string.
1774 * $flags
1775 * A bitmask of one or more of the flags (see comment on jx9Builtin_htmlspecialchars())
1776 * Return
1777 * The decoded string.
1778 */
1779static int jx9Builtin_html_entity_decode(jx9_context *pCtx, int nArg, jx9_value **apArg)
1780{
1781 const char *zCur, *zIn, *zEnd;
1782 int iFlags = 0x01; /* ENT_COMPAT */
1783 int nLen;
1784 sxu32 n;
1785 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1786 /* Missing/Invalid arguments, return NULL */
1787 jx9_result_null(pCtx);
1788 return JX9_OK;
1789 }
1790 /* Extract the target string */
1791 zIn = jx9_value_to_string(apArg[0], &nLen);
1792 zEnd = &zIn[nLen];
1793 /* Extract the flags if available */
1794 if( nArg > 1 ){
1795 iFlags = jx9_value_to_int(apArg[1]);
1796 if( iFlags < 0 ){
1797 iFlags = 0x01;
1798 }
1799 }
1800 /* Perform the requested operation */
1801 for(;;){
1802 if( zIn >= zEnd ){
1803 /* No more input to process */
1804 break;
1805 }
1806 zCur = zIn;
1807 while( zIn < zEnd && zIn[0] != '&' ){
1808 zIn++;
1809 }
1810 if( zCur < zIn ){
1811 /* Append raw string verbatim */
1812 jx9_result_string(pCtx, zCur, (int)(zIn-zCur));
1813 }
1814 if( zIn >= zEnd ){
1815 break;
1816 }
1817 nLen = (int)(zEnd-zIn);
1818 /* Find an encoded sequence */
1819 for(n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){
1820 int iLen = (int)SyStrlen(azHtmlEscape[n]);
1821 if( nLen >= iLen && SyStrnicmp(zIn, azHtmlEscape[n], (sxu32)iLen) == 0 ){
1822 /* Got one */
1823 zIn += iLen;
1824 break;
1825 }
1826 }
1827 if( n < SX_ARRAYSIZE(azHtmlEscape) ){
1828 int c = azHtmlEscape[n+1][0];
1829 /* Output the decoded character */
1830 if( c == '\'' && ((iFlags & 0x02) == 0 /*ENT_QUOTES*/|| (iFlags & 0x04) /*ENT_NOQUOTES*/) ){
1831 /* Do not process single quotes */
1832 jx9_result_string(pCtx, azHtmlEscape[n], -1);
1833 }else if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){
1834 /* Do not process double quotes */
1835 jx9_result_string(pCtx, azHtmlEscape[n], -1);
1836 }else{
1837 jx9_result_string(pCtx, azHtmlEscape[n+1], -1); /* Compute length automatically */
1838 }
1839 }else{
1840 /* Append '&' */
1841 jx9_result_string(pCtx, "&", (int)sizeof(char));
1842 zIn++;
1843 }
1844 }
1845 return JX9_OK;
1846}
1847/*
1848 * int strlen($string)
1849 * return the length of the given string.
1850 * Parameter
1851 * string: The string being measured for length.
1852 * Return
1853 * length of the given string.
1854 */
1855static int jx9Builtin_strlen(jx9_context *pCtx, int nArg, jx9_value **apArg)
1856{
1857 int iLen = 0;
1858 if( nArg > 0 ){
1859 jx9_value_to_string(apArg[0], &iLen);
1860 }
1861 /* String length */
1862 jx9_result_int(pCtx, iLen);
1863 return JX9_OK;
1864}
1865/*
1866 * int strcmp(string $str1, string $str2)
1867 * Perform a binary safe string comparison.
1868 * Parameter
1869 * str1: The first string
1870 * str2: The second string
1871 * Return
1872 * Returns < 0 if str1 is less than str2; > 0 if str1 is greater
1873 * than str2, and 0 if they are equal.
1874 */
1875static int jx9Builtin_strcmp(jx9_context *pCtx, int nArg, jx9_value **apArg)
1876{
1877 const char *z1, *z2;
1878 int n1, n2;
1879 int res;
1880 if( nArg < 2 ){
1881 res = nArg == 0 ? 0 : 1;
1882 jx9_result_int(pCtx, res);
1883 return JX9_OK;
1884 }
1885 /* Perform the comparison */
1886 z1 = jx9_value_to_string(apArg[0], &n1);
1887 z2 = jx9_value_to_string(apArg[1], &n2);
1888 res = SyStrncmp(z1, z2, (sxu32)(SXMAX(n1, n2)));
1889 /* Comparison result */
1890 jx9_result_int(pCtx, res);
1891 return JX9_OK;
1892}
1893/*
1894 * int strncmp(string $str1, string $str2, int n)
1895 * Perform a binary safe string comparison of the first n characters.
1896 * Parameter
1897 * str1: The first string
1898 * str2: The second string
1899 * Return
1900 * Returns < 0 if str1 is less than str2; > 0 if str1 is greater
1901 * than str2, and 0 if they are equal.
1902 */
1903static int jx9Builtin_strncmp(jx9_context *pCtx, int nArg, jx9_value **apArg)
1904{
1905 const char *z1, *z2;
1906 int res;
1907 int n;
1908 if( nArg < 3 ){
1909 /* Perform a standard comparison */
1910 return jx9Builtin_strcmp(pCtx, nArg, apArg);
1911 }
1912 /* Desired comparison length */
1913 n = jx9_value_to_int(apArg[2]);
1914 if( n < 0 ){
1915 /* Invalid length */
1916 jx9_result_int(pCtx, -1);
1917 return JX9_OK;
1918 }
1919 /* Perform the comparison */
1920 z1 = jx9_value_to_string(apArg[0], 0);
1921 z2 = jx9_value_to_string(apArg[1], 0);
1922 res = SyStrncmp(z1, z2, (sxu32)n);
1923 /* Comparison result */
1924 jx9_result_int(pCtx, res);
1925 return JX9_OK;
1926}
1927/*
1928 * int strcasecmp(string $str1, string $str2, int n)
1929 * Perform a binary safe case-insensitive string comparison.
1930 * Parameter
1931 * str1: The first string
1932 * str2: The second string
1933 * Return
1934 * Returns < 0 if str1 is less than str2; > 0 if str1 is greater
1935 * than str2, and 0 if they are equal.
1936 */
1937static int jx9Builtin_strcasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg)
1938{
1939 const char *z1, *z2;
1940 int n1, n2;
1941 int res;
1942 if( nArg < 2 ){
1943 res = nArg == 0 ? 0 : 1;
1944 jx9_result_int(pCtx, res);
1945 return JX9_OK;
1946 }
1947 /* Perform the comparison */
1948 z1 = jx9_value_to_string(apArg[0], &n1);
1949 z2 = jx9_value_to_string(apArg[1], &n2);
1950 res = SyStrnicmp(z1, z2, (sxu32)(SXMAX(n1, n2)));
1951 /* Comparison result */
1952 jx9_result_int(pCtx, res);
1953 return JX9_OK;
1954}
1955/*
1956 * int strncasecmp(string $str1, string $str2, int n)
1957 * Perform a binary safe case-insensitive string comparison of the first n characters.
1958 * Parameter
1959 * $str1: The first string
1960 * $str2: The second string
1961 * $len: The length of strings to be used in the comparison.
1962 * Return
1963 * Returns < 0 if str1 is less than str2; > 0 if str1 is greater
1964 * than str2, and 0 if they are equal.
1965 */
1966static int jx9Builtin_strncasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg)
1967{
1968 const char *z1, *z2;
1969 int res;
1970 int n;
1971 if( nArg < 3 ){
1972 /* Perform a standard comparison */
1973 return jx9Builtin_strcasecmp(pCtx, nArg, apArg);
1974 }
1975 /* Desired comparison length */
1976 n = jx9_value_to_int(apArg[2]);
1977 if( n < 0 ){
1978 /* Invalid length */
1979 jx9_result_int(pCtx, -1);
1980 return JX9_OK;
1981 }
1982 /* Perform the comparison */
1983 z1 = jx9_value_to_string(apArg[0], 0);
1984 z2 = jx9_value_to_string(apArg[1], 0);
1985 res = SyStrnicmp(z1, z2, (sxu32)n);
1986 /* Comparison result */
1987 jx9_result_int(pCtx, res);
1988 return JX9_OK;
1989}
1990/*
1991 * Implode context [i.e: it's private data].
1992 * A pointer to the following structure is forwarded
1993 * verbatim to the array walker callback defined below.
1994 */
1995struct implode_data {
1996 jx9_context *pCtx; /* Call context */
1997 int bRecursive; /* TRUE if recursive implode [this is a symisc eXtension] */
1998 const char *zSep; /* Arguments separator if any */
1999 int nSeplen; /* Separator length */
2000 int bFirst; /* TRUE if first call */
2001 int nRecCount; /* Recursion count to avoid infinite loop */
2002};
2003/*
2004 * Implode walker callback for the [jx9_array_walk()] interface.
2005 * The following routine is invoked for each array entry passed
2006 * to the implode() function.
2007 */
2008static int implode_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData)
2009{
2010 struct implode_data *pData = (struct implode_data *)pUserData;
2011 const char *zData;
2012 int nLen;
2013 if( pData->bRecursive && jx9_value_is_json_array(pValue) && pData->nRecCount < 32 ){
2014 if( pData->nSeplen > 0 ){
2015 if( !pData->bFirst ){
2016 /* append the separator first */
2017 jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen);
2018 }else{
2019 pData->bFirst = 0;
2020 }
2021 }
2022 /* Recurse */
2023 pData->bFirst = 1;
2024 pData->nRecCount++;
2025 jx9HashmapWalk((jx9_hashmap *)pValue->x.pOther, implode_callback, pData);
2026 pData->nRecCount--;
2027 return JX9_OK;
2028 }
2029 /* Extract the string representation of the entry value */
2030 zData = jx9_value_to_string(pValue, &nLen);
2031 if( nLen > 0 ){
2032 if( pData->nSeplen > 0 ){
2033 if( !pData->bFirst ){
2034 /* append the separator first */
2035 jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen);
2036 }else{
2037 pData->bFirst = 0;
2038 }
2039 }
2040 jx9_result_string(pData->pCtx, zData, nLen);
2041 }else{
2042 SXUNUSED(pKey); /* cc warning */
2043 }
2044 return JX9_OK;
2045}
2046/*
2047 * string implode(string $glue, array $pieces, ...)
2048 * string implode(array $pieces, ...)
2049 * Join array elements with a string.
2050 * $glue
2051 * Defaults to an empty string. This is not the preferred usage of implode() as glue
2052 * would be the second parameter and thus, the bad prototype would be used.
2053 * $pieces
2054 * The array of strings to implode.
2055 * Return
2056 * Returns a string containing a string representation of all the array elements in the same
2057 * order, with the glue string between each element.
2058 */
2059static int jx9Builtin_implode(jx9_context *pCtx, int nArg, jx9_value **apArg)
2060{
2061 struct implode_data imp_data;
2062 int i = 1;
2063 if( nArg < 1 ){
2064 /* Missing argument, return NULL */
2065 jx9_result_null(pCtx);
2066 return JX9_OK;
2067 }
2068 /* Prepare the implode context */
2069 imp_data.pCtx = pCtx;
2070 imp_data.bRecursive = 0;
2071 imp_data.bFirst = 1;
2072 imp_data.nRecCount = 0;
2073 if( !jx9_value_is_json_array(apArg[0]) ){
2074 imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen);
2075 }else{
2076 imp_data.zSep = 0;
2077 imp_data.nSeplen = 0;
2078 i = 0;
2079 }
2080 jx9_result_string(pCtx, "", 0); /* Set an empty stirng */
2081 /* Start the 'join' process */
2082 while( i < nArg ){
2083 if( jx9_value_is_json_array(apArg[i]) ){
2084 /* Iterate throw array entries */
2085 jx9_array_walk(apArg[i], implode_callback, &imp_data);
2086 }else{
2087 const char *zData;
2088 int nLen;
2089 /* Extract the string representation of the jx9 value */
2090 zData = jx9_value_to_string(apArg[i], &nLen);
2091 if( nLen > 0 ){
2092 if( imp_data.nSeplen > 0 ){
2093 if( !imp_data.bFirst ){
2094 /* append the separator first */
2095 jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen);
2096 }else{
2097 imp_data.bFirst = 0;
2098 }
2099 }
2100 jx9_result_string(pCtx, zData, nLen);
2101 }
2102 }
2103 i++;
2104 }
2105 return JX9_OK;
2106}
2107/*
2108 * string implode_recursive(string $glue, array $pieces, ...)
2109 * Purpose
2110 * Same as implode() but recurse on arrays.
2111 * Example:
2112 * $a = array('usr', array('home', 'dean'));
2113 * print implode_recursive("/", $a);
2114 * Will output
2115 * usr/home/dean.
2116 * While the standard implode would produce.
2117 * usr/Array.
2118 * Parameter
2119 * Refer to implode().
2120 * Return
2121 * Refer to implode().
2122 */
2123static int jx9Builtin_implode_recursive(jx9_context *pCtx, int nArg, jx9_value **apArg)
2124{
2125 struct implode_data imp_data;
2126 int i = 1;
2127 if( nArg < 1 ){
2128 /* Missing argument, return NULL */
2129 jx9_result_null(pCtx);
2130 return JX9_OK;
2131 }
2132 /* Prepare the implode context */
2133 imp_data.pCtx = pCtx;
2134 imp_data.bRecursive = 1;
2135 imp_data.bFirst = 1;
2136 imp_data.nRecCount = 0;
2137 if( !jx9_value_is_json_array(apArg[0]) ){
2138 imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen);
2139 }else{
2140 imp_data.zSep = 0;
2141 imp_data.nSeplen = 0;
2142 i = 0;
2143 }
2144 jx9_result_string(pCtx, "", 0); /* Set an empty stirng */
2145 /* Start the 'join' process */
2146 while( i < nArg ){
2147 if( jx9_value_is_json_array(apArg[i]) ){
2148 /* Iterate throw array entries */
2149 jx9_array_walk(apArg[i], implode_callback, &imp_data);
2150 }else{
2151 const char *zData;
2152 int nLen;
2153 /* Extract the string representation of the jx9 value */
2154 zData = jx9_value_to_string(apArg[i], &nLen);
2155 if( nLen > 0 ){
2156 if( imp_data.nSeplen > 0 ){
2157 if( !imp_data.bFirst ){
2158 /* append the separator first */
2159 jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen);
2160 }else{
2161 imp_data.bFirst = 0;
2162 }
2163 }
2164 jx9_result_string(pCtx, zData, nLen);
2165 }
2166 }
2167 i++;
2168 }
2169 return JX9_OK;
2170}
2171/*
2172 * array explode(string $delimiter, string $string[, int $limit ])
2173 * Returns an array of strings, each of which is a substring of string
2174 * formed by splitting it on boundaries formed by the string delimiter.
2175 * Parameters
2176 * $delimiter
2177 * The boundary string.
2178 * $string
2179 * The input string.
2180 * $limit
2181 * If limit is set and positive, the returned array will contain a maximum
2182 * of limit elements with the last element containing the rest of string.
2183 * If the limit parameter is negative, all fields except the last -limit are returned.
2184 * If the limit parameter is zero, then this is treated as 1.
2185 * Returns
2186 * Returns an array of strings created by splitting the string parameter
2187 * on boundaries formed by the delimiter.
2188 * If delimiter is an empty string (""), explode() will return FALSE.
2189 * If delimiter contains a value that is not contained in string and a negative
2190 * limit is used, then an empty array will be returned, otherwise an array containing string
2191 * will be returned.
2192 * NOTE:
2193 * Negative limit is not supported.
2194 */
2195static int jx9Builtin_explode(jx9_context *pCtx, int nArg, jx9_value **apArg)
2196{
2197 const char *zDelim, *zString, *zCur, *zEnd;
2198 int nDelim, nStrlen, iLimit;
2199 jx9_value *pArray;
2200 jx9_value *pValue;
2201 sxu32 nOfft;
2202 sxi32 rc;
2203 if( nArg < 2 ){
2204 /* Missing arguments, return FALSE */
2205 jx9_result_bool(pCtx, 0);
2206 return JX9_OK;
2207 }
2208 /* Extract the delimiter */
2209 zDelim = jx9_value_to_string(apArg[0], &nDelim);
2210 if( nDelim < 1 ){
2211 /* Empty delimiter, return FALSE */
2212 jx9_result_bool(pCtx, 0);
2213 return JX9_OK;
2214 }
2215 /* Extract the string */
2216 zString = jx9_value_to_string(apArg[1], &nStrlen);
2217 if( nStrlen < 1 ){
2218 /* Empty delimiter, return FALSE */
2219 jx9_result_bool(pCtx, 0);
2220 return JX9_OK;
2221 }
2222 /* Point to the end of the string */
2223 zEnd = &zString[nStrlen];
2224 /* Create the array */
2225 pArray = jx9_context_new_array(pCtx);
2226 pValue = jx9_context_new_scalar(pCtx);
2227 if( pArray == 0 || pValue == 0 ){
2228 /* Out of memory, return FALSE */
2229 jx9_result_bool(pCtx, 0);
2230 return JX9_OK;
2231 }
2232 /* Set a defualt limit */
2233 iLimit = SXI32_HIGH;
2234 if( nArg > 2 ){
2235 iLimit = jx9_value_to_int(apArg[2]);
2236 if( iLimit < 0 ){
2237 iLimit = -iLimit;
2238 }
2239 if( iLimit == 0 ){
2240 iLimit = 1;
2241 }
2242 iLimit--;
2243 }
2244 /* Start exploding */
2245 for(;;){
2246 if( zString >= zEnd ){
2247 /* No more entry to process */
2248 break;
2249 }
2250 rc = SyBlobSearch(zString, (sxu32)(zEnd-zString), zDelim, nDelim, &nOfft);
2251 if( rc != SXRET_OK || iLimit <= (int)jx9_array_count(pArray) ){
2252 /* Limit reached, insert the rest of the string and break */
2253 if( zEnd > zString ){
2254 jx9_value_string(pValue, zString, (int)(zEnd-zString));
2255 jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue);
2256 }
2257 break;
2258 }
2259 /* Point to the desired offset */
2260 zCur = &zString[nOfft];
2261 if( zCur > zString ){
2262 /* Perform the store operation */
2263 jx9_value_string(pValue, zString, (int)(zCur-zString));
2264 jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue);
2265 }
2266 /* Point beyond the delimiter */
2267 zString = &zCur[nDelim];
2268 /* Reset the cursor */
2269 jx9_value_reset_string_cursor(pValue);
2270 }
2271 /* Return the freshly created array */
2272 jx9_result_value(pCtx, pArray);
2273 /* NOTE that every allocated jx9_value will be automatically
2274 * released as soon we return from this foregin function.
2275 */
2276 return JX9_OK;
2277}
2278/*
2279 * string trim(string $str[, string $charlist ])
2280 * Strip whitespace (or other characters) from the beginning and end of a string.
2281 * Parameters
2282 * $str
2283 * The string that will be trimmed.
2284 * $charlist
2285 * Optionally, the stripped characters can also be specified using the charlist parameter.
2286 * Simply list all characters that you want to be stripped.
2287 * With .. you can specify a range of characters.
2288 * Returns.
2289 * Thr processed string.
2290 */
2291static int jx9Builtin_trim(jx9_context *pCtx, int nArg, jx9_value **apArg)
2292{
2293 const char *zString;
2294 int nLen;
2295 if( nArg < 1 ){
2296 /* Missing arguments, return null */
2297 jx9_result_null(pCtx);
2298 return JX9_OK;
2299 }
2300 /* Extract the target string */
2301 zString = jx9_value_to_string(apArg[0], &nLen);
2302 if( nLen < 1 ){
2303 /* Empty string, return */
2304 jx9_result_string(pCtx, "", 0);
2305 return JX9_OK;
2306 }
2307 /* Start the trim process */
2308 if( nArg < 2 ){
2309 SyString sStr;
2310 /* Remove white spaces and NUL bytes */
2311 SyStringInitFromBuf(&sStr, zString, nLen);
2312 SyStringFullTrimSafe(&sStr);
2313 jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte);
2314 }else{
2315 /* Char list */
2316 const char *zList;
2317 int nListlen;
2318 zList = jx9_value_to_string(apArg[1], &nListlen);
2319 if( nListlen < 1 ){
2320 /* Return the string unchanged */
2321 jx9_result_string(pCtx, zString, nLen);
2322 }else{
2323 const char *zEnd = &zString[nLen];
2324 const char *zCur = zString;
2325 const char *zPtr;
2326 int i;
2327 /* Left trim */
2328 for(;;){
2329 if( zCur >= zEnd ){
2330 break;
2331 }
2332 zPtr = zCur;
2333 for( i = 0 ; i < nListlen ; i++ ){
2334 if( zCur < zEnd && zCur[0] == zList[i] ){
2335 zCur++;
2336 }
2337 }
2338 if( zCur == zPtr ){
2339 /* No match, break immediately */
2340 break;
2341 }
2342 }
2343 /* Right trim */
2344 zEnd--;
2345 for(;;){
2346 if( zEnd <= zCur ){
2347 break;
2348 }
2349 zPtr = zEnd;
2350 for( i = 0 ; i < nListlen ; i++ ){
2351 if( zEnd > zCur && zEnd[0] == zList[i] ){
2352 zEnd--;
2353 }
2354 }
2355 if( zEnd == zPtr ){
2356 break;
2357 }
2358 }
2359 if( zCur >= zEnd ){
2360 /* Return the empty string */
2361 jx9_result_string(pCtx, "", 0);
2362 }else{
2363 zEnd++;
2364 jx9_result_string(pCtx, zCur, (int)(zEnd-zCur));
2365 }
2366 }
2367 }
2368 return JX9_OK;
2369}
2370/*
2371 * string rtrim(string $str[, string $charlist ])
2372 * Strip whitespace (or other characters) from the end of a string.
2373 * Parameters
2374 * $str
2375 * The string that will be trimmed.
2376 * $charlist
2377 * Optionally, the stripped characters can also be specified using the charlist parameter.
2378 * Simply list all characters that you want to be stripped.
2379 * With .. you can specify a range of characters.
2380 * Returns.
2381 * Thr processed string.
2382 */
2383static int jx9Builtin_rtrim(jx9_context *pCtx, int nArg, jx9_value **apArg)
2384{
2385 const char *zString;
2386 int nLen;
2387 if( nArg < 1 ){
2388 /* Missing arguments, return null */
2389 jx9_result_null(pCtx);
2390 return JX9_OK;
2391 }
2392 /* Extract the target string */
2393 zString = jx9_value_to_string(apArg[0], &nLen);
2394 if( nLen < 1 ){
2395 /* Empty string, return */
2396 jx9_result_string(pCtx, "", 0);
2397 return JX9_OK;
2398 }
2399 /* Start the trim process */
2400 if( nArg < 2 ){
2401 SyString sStr;
2402 /* Remove white spaces and NUL bytes*/
2403 SyStringInitFromBuf(&sStr, zString, nLen);
2404 SyStringRightTrimSafe(&sStr);
2405 jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte);
2406 }else{
2407 /* Char list */
2408 const char *zList;
2409 int nListlen;
2410 zList = jx9_value_to_string(apArg[1], &nListlen);
2411 if( nListlen < 1 ){
2412 /* Return the string unchanged */
2413 jx9_result_string(pCtx, zString, nLen);
2414 }else{
2415 const char *zEnd = &zString[nLen - 1];
2416 const char *zCur = zString;
2417 const char *zPtr;
2418 int i;
2419 /* Right trim */
2420 for(;;){
2421 if( zEnd <= zCur ){
2422 break;
2423 }
2424 zPtr = zEnd;
2425 for( i = 0 ; i < nListlen ; i++ ){
2426 if( zEnd > zCur && zEnd[0] == zList[i] ){
2427 zEnd--;
2428 }
2429 }
2430 if( zEnd == zPtr ){
2431 break;
2432 }
2433 }
2434 if( zEnd <= zCur ){
2435 /* Return the empty string */
2436 jx9_result_string(pCtx, "", 0);
2437 }else{
2438 zEnd++;
2439 jx9_result_string(pCtx, zCur, (int)(zEnd-zCur));
2440 }
2441 }
2442 }
2443 return JX9_OK;
2444}
2445/*
2446 * string ltrim(string $str[, string $charlist ])
2447 * Strip whitespace (or other characters) from the beginning and end of a string.
2448 * Parameters
2449 * $str
2450 * The string that will be trimmed.
2451 * $charlist
2452 * Optionally, the stripped characters can also be specified using the charlist parameter.
2453 * Simply list all characters that you want to be stripped.
2454 * With .. you can specify a range of characters.
2455 * Returns.
2456 * The processed string.
2457 */
2458static int jx9Builtin_ltrim(jx9_context *pCtx, int nArg, jx9_value **apArg)
2459{
2460 const char *zString;
2461 int nLen;
2462 if( nArg < 1 ){
2463 /* Missing arguments, return null */
2464 jx9_result_null(pCtx);
2465 return JX9_OK;
2466 }
2467 /* Extract the target string */
2468 zString = jx9_value_to_string(apArg[0], &nLen);
2469 if( nLen < 1 ){
2470 /* Empty string, return */
2471 jx9_result_string(pCtx, "", 0);
2472 return JX9_OK;
2473 }
2474 /* Start the trim process */
2475 if( nArg < 2 ){
2476 SyString sStr;
2477 /* Remove white spaces and NUL byte */
2478 SyStringInitFromBuf(&sStr, zString, nLen);
2479 SyStringLeftTrimSafe(&sStr);
2480 jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte);
2481 }else{
2482 /* Char list */
2483 const char *zList;
2484 int nListlen;
2485 zList = jx9_value_to_string(apArg[1], &nListlen);
2486 if( nListlen < 1 ){
2487 /* Return the string unchanged */
2488 jx9_result_string(pCtx, zString, nLen);
2489 }else{
2490 const char *zEnd = &zString[nLen];
2491 const char *zCur = zString;
2492 const char *zPtr;
2493 int i;
2494 /* Left trim */
2495 for(;;){
2496 if( zCur >= zEnd ){
2497 break;
2498 }
2499 zPtr = zCur;
2500 for( i = 0 ; i < nListlen ; i++ ){
2501 if( zCur < zEnd && zCur[0] == zList[i] ){
2502 zCur++;
2503 }
2504 }
2505 if( zCur == zPtr ){
2506 /* No match, break immediately */
2507 break;
2508 }
2509 }
2510 if( zCur >= zEnd ){
2511 /* Return the empty string */
2512 jx9_result_string(pCtx, "", 0);
2513 }else{
2514 jx9_result_string(pCtx, zCur, (int)(zEnd-zCur));
2515 }
2516 }
2517 }
2518 return JX9_OK;
2519}
2520/*
2521 * string strtolower(string $str)
2522 * Make a string lowercase.
2523 * Parameters
2524 * $str
2525 * The input string.
2526 * Returns.
2527 * The lowercased string.
2528 */
2529static int jx9Builtin_strtolower(jx9_context *pCtx, int nArg, jx9_value **apArg)
2530{
2531 const char *zString, *zCur, *zEnd;
2532 int nLen;
2533 if( nArg < 1 ){
2534 /* Missing arguments, return null */
2535 jx9_result_null(pCtx);
2536 return JX9_OK;
2537 }
2538 /* Extract the target string */
2539 zString = jx9_value_to_string(apArg[0], &nLen);
2540 if( nLen < 1 ){
2541 /* Empty string, return */
2542 jx9_result_string(pCtx, "", 0);
2543 return JX9_OK;
2544 }
2545 /* Perform the requested operation */
2546 zEnd = &zString[nLen];
2547 for(;;){
2548 if( zString >= zEnd ){
2549 /* No more input, break immediately */
2550 break;
2551 }
2552 if( (unsigned char)zString[0] >= 0xc0 ){
2553 /* UTF-8 stream, output verbatim */
2554 zCur = zString;
2555 zString++;
2556 while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){
2557 zString++;
2558 }
2559 /* Append UTF-8 stream */
2560 jx9_result_string(pCtx, zCur, (int)(zString-zCur));
2561 }else{
2562 int c = zString[0];
2563 if( SyisUpper(c) ){
2564 c = SyToLower(zString[0]);
2565 }
2566 /* Append character */
2567 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
2568 /* Advance the cursor */
2569 zString++;
2570 }
2571 }
2572 return JX9_OK;
2573}
2574/*
2575 * string strtolower(string $str)
2576 * Make a string uppercase.
2577 * Parameters
2578 * $str
2579 * The input string.
2580 * Returns.
2581 * The uppercased string.
2582 */
2583static int jx9Builtin_strtoupper(jx9_context *pCtx, int nArg, jx9_value **apArg)
2584{
2585 const char *zString, *zCur, *zEnd;
2586 int nLen;
2587 if( nArg < 1 ){
2588 /* Missing arguments, return null */
2589 jx9_result_null(pCtx);
2590 return JX9_OK;
2591 }
2592 /* Extract the target string */
2593 zString = jx9_value_to_string(apArg[0], &nLen);
2594 if( nLen < 1 ){
2595 /* Empty string, return */
2596 jx9_result_string(pCtx, "", 0);
2597 return JX9_OK;
2598 }
2599 /* Perform the requested operation */
2600 zEnd = &zString[nLen];
2601 for(;;){
2602 if( zString >= zEnd ){
2603 /* No more input, break immediately */
2604 break;
2605 }
2606 if( (unsigned char)zString[0] >= 0xc0 ){
2607 /* UTF-8 stream, output verbatim */
2608 zCur = zString;
2609 zString++;
2610 while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){
2611 zString++;
2612 }
2613 /* Append UTF-8 stream */
2614 jx9_result_string(pCtx, zCur, (int)(zString-zCur));
2615 }else{
2616 int c = zString[0];
2617 if( SyisLower(c) ){
2618 c = SyToUpper(zString[0]);
2619 }
2620 /* Append character */
2621 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
2622 /* Advance the cursor */
2623 zString++;
2624 }
2625 }
2626 return JX9_OK;
2627}
2628/*
2629 * int ord(string $string)
2630 * Returns the ASCII value of the first character of string.
2631 * Parameters
2632 * $str
2633 * The input string.
2634 * Returns.
2635 * The ASCII value as an integer.
2636 */
2637static int jx9Builtin_ord(jx9_context *pCtx, int nArg, jx9_value **apArg)
2638{
2639 const char *zString;
2640 int nLen, c;
2641 if( nArg < 1 ){
2642 /* Missing arguments, return -1 */
2643 jx9_result_int(pCtx, -1);
2644 return JX9_OK;
2645 }
2646 /* Extract the target string */
2647 zString = jx9_value_to_string(apArg[0], &nLen);
2648 if( nLen < 1 ){
2649 /* Empty string, return -1 */
2650 jx9_result_int(pCtx, -1);
2651 return JX9_OK;
2652 }
2653 /* Extract the ASCII value of the first character */
2654 c = zString[0];
2655 /* Return that value */
2656 jx9_result_int(pCtx, c);
2657 return JX9_OK;
2658}
2659/*
2660 * string chr(int $ascii)
2661 * Returns a one-character string containing the character specified by ascii.
2662 * Parameters
2663 * $ascii
2664 * The ascii code.
2665 * Returns.
2666 * The specified character.
2667 */
2668static int jx9Builtin_chr(jx9_context *pCtx, int nArg, jx9_value **apArg)
2669{
2670 int c;
2671 if( nArg < 1 ){
2672 /* Missing arguments, return null */
2673 jx9_result_null(pCtx);
2674 return JX9_OK;
2675 }
2676 /* Extract the ASCII value */
2677 c = jx9_value_to_int(apArg[0]);
2678 /* Return the specified character */
2679 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
2680 return JX9_OK;
2681}
2682/*
2683 * Binary to hex consumer callback.
2684 * This callback is the default consumer used by the hash functions
2685 * [i.e: bin2hex(), md5(), sha1(), md5_file() ... ] defined below.
2686 */
2687static int HashConsumer(const void *pData, unsigned int nLen, void *pUserData)
2688{
2689 /* Append hex chunk verbatim */
2690 jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen);
2691 return SXRET_OK;
2692}
2693/*
2694 * string bin2hex(string $str)
2695 * Convert binary data into hexadecimal representation.
2696 * Parameters
2697 * $str
2698 * The input string.
2699 * Returns.
2700 * Returns the hexadecimal representation of the given string.
2701 */
2702static int jx9Builtin_bin2hex(jx9_context *pCtx, int nArg, jx9_value **apArg)
2703{
2704 const char *zString;
2705 int nLen;
2706 if( nArg < 1 ){
2707 /* Missing arguments, return null */
2708 jx9_result_null(pCtx);
2709 return JX9_OK;
2710 }
2711 /* Extract the target string */
2712 zString = jx9_value_to_string(apArg[0], &nLen);
2713 if( nLen < 1 ){
2714 /* Empty string, return */
2715 jx9_result_string(pCtx, "", 0);
2716 return JX9_OK;
2717 }
2718 /* Perform the requested operation */
2719 SyBinToHexConsumer((const void *)zString, (sxu32)nLen, HashConsumer, pCtx);
2720 return JX9_OK;
2721}
2722/* Search callback signature */
2723typedef sxi32 (*ProcStringMatch)(const void *, sxu32, const void *, sxu32, sxu32 *);
2724/*
2725 * Case-insensitive pattern match.
2726 * Brute force is the default search method used here.
2727 * This is due to the fact that brute-forcing works quite
2728 * well for short/medium texts on modern hardware.
2729 */
2730static sxi32 iPatternMatch(const void *pText, sxu32 nLen, const void *pPattern, sxu32 iPatLen, sxu32 *pOfft)
2731{
2732 const char *zpIn = (const char *)pPattern;
2733 const char *zIn = (const char *)pText;
2734 const char *zpEnd = &zpIn[iPatLen];
2735 const char *zEnd = &zIn[nLen];
2736 const char *zPtr, *zPtr2;
2737 int c, d;
2738 if( iPatLen > nLen ){
2739 /* Don't bother processing */
2740 return SXERR_NOTFOUND;
2741 }
2742 for(;;){
2743 if( zIn >= zEnd ){
2744 break;
2745 }
2746 c = SyToLower(zIn[0]);
2747 d = SyToLower(zpIn[0]);
2748 if( c == d ){
2749 zPtr = &zIn[1];
2750 zPtr2 = &zpIn[1];
2751 for(;;){
2752 if( zPtr2 >= zpEnd ){
2753 /* Pattern found */
2754 if( pOfft ){ *pOfft = (sxu32)(zIn-(const char *)pText); }
2755 return SXRET_OK;
2756 }
2757 if( zPtr >= zEnd ){
2758 break;
2759 }
2760 c = SyToLower(zPtr[0]);
2761 d = SyToLower(zPtr2[0]);
2762 if( c != d ){
2763 break;
2764 }
2765 zPtr++; zPtr2++;
2766 }
2767 }
2768 zIn++;
2769 }
2770 /* Pattern not found */
2771 return SXERR_NOTFOUND;
2772}
2773/*
2774 * string strstr(string $haystack, string $needle[, bool $before_needle = false ])
2775 * Find the first occurrence of a string.
2776 * Parameters
2777 * $haystack
2778 * The input string.
2779 * $needle
2780 * Search pattern (must be a string).
2781 * $before_needle
2782 * If TRUE, strstr() returns the part of the haystack before the first occurrence
2783 * of the needle (excluding the needle).
2784 * Return
2785 * Returns the portion of string, or FALSE if needle is not found.
2786 */
2787static int jx9Builtin_strstr(jx9_context *pCtx, int nArg, jx9_value **apArg)
2788{
2789 ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */
2790 const char *zBlob, *zPattern;
2791 int nLen, nPatLen;
2792 sxu32 nOfft;
2793 sxi32 rc;
2794 if( nArg < 2 ){
2795 /* Missing arguments, return FALSE */
2796 jx9_result_bool(pCtx, 0);
2797 return JX9_OK;
2798 }
2799 /* Extract the needle and the haystack */
2800 zBlob = jx9_value_to_string(apArg[0], &nLen);
2801 zPattern = jx9_value_to_string(apArg[1], &nPatLen);
2802 nOfft = 0; /* cc warning */
2803 if( nLen > 0 && nPatLen > 0 ){
2804 int before = 0;
2805 /* Perform the lookup */
2806 rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft);
2807 if( rc != SXRET_OK ){
2808 /* Pattern not found, return FALSE */
2809 jx9_result_bool(pCtx, 0);
2810 return JX9_OK;
2811 }
2812 /* Return the portion of the string */
2813 if( nArg > 2 ){
2814 before = jx9_value_to_int(apArg[2]);
2815 }
2816 if( before ){
2817 jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob));
2818 }else{
2819 jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft]));
2820 }
2821 }else{
2822 jx9_result_bool(pCtx, 0);
2823 }
2824 return JX9_OK;
2825}
2826/*
2827 * string stristr(string $haystack, string $needle[, bool $before_needle = false ])
2828 * Case-insensitive strstr().
2829 * Parameters
2830 * $haystack
2831 * The input string.
2832 * $needle
2833 * Search pattern (must be a string).
2834 * $before_needle
2835 * If TRUE, strstr() returns the part of the haystack before the first occurrence
2836 * of the needle (excluding the needle).
2837 * Return
2838 * Returns the portion of string, or FALSE if needle is not found.
2839 */
2840static int jx9Builtin_stristr(jx9_context *pCtx, int nArg, jx9_value **apArg)
2841{
2842 ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */
2843 const char *zBlob, *zPattern;
2844 int nLen, nPatLen;
2845 sxu32 nOfft;
2846 sxi32 rc;
2847 if( nArg < 2 ){
2848 /* Missing arguments, return FALSE */
2849 jx9_result_bool(pCtx, 0);
2850 return JX9_OK;
2851 }
2852 /* Extract the needle and the haystack */
2853 zBlob = jx9_value_to_string(apArg[0], &nLen);
2854 zPattern = jx9_value_to_string(apArg[1], &nPatLen);
2855 nOfft = 0; /* cc warning */
2856 if( nLen > 0 && nPatLen > 0 ){
2857 int before = 0;
2858 /* Perform the lookup */
2859 rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft);
2860 if( rc != SXRET_OK ){
2861 /* Pattern not found, return FALSE */
2862 jx9_result_bool(pCtx, 0);
2863 return JX9_OK;
2864 }
2865 /* Return the portion of the string */
2866 if( nArg > 2 ){
2867 before = jx9_value_to_int(apArg[2]);
2868 }
2869 if( before ){
2870 jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob));
2871 }else{
2872 jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft]));
2873 }
2874 }else{
2875 jx9_result_bool(pCtx, 0);
2876 }
2877 return JX9_OK;
2878}
2879/*
2880 * int strpos(string $haystack, string $needle [, int $offset = 0 ] )
2881 * Returns the numeric position of the first occurrence of needle in the haystack string.
2882 * Parameters
2883 * $haystack
2884 * The input string.
2885 * $needle
2886 * Search pattern (must be a string).
2887 * $offset
2888 * This optional offset parameter allows you to specify which character in haystack
2889 * to start searching. The position returned is still relative to the beginning
2890 * of haystack.
2891 * Return
2892 * Returns the position as an integer.If needle is not found, strpos() will return FALSE.
2893 */
2894static int jx9Builtin_strpos(jx9_context *pCtx, int nArg, jx9_value **apArg)
2895{
2896 ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */
2897 const char *zBlob, *zPattern;
2898 int nLen, nPatLen, nStart;
2899 sxu32 nOfft;
2900 sxi32 rc;
2901 if( nArg < 2 ){
2902 /* Missing arguments, return FALSE */
2903 jx9_result_bool(pCtx, 0);
2904 return JX9_OK;
2905 }
2906 /* Extract the needle and the haystack */
2907 zBlob = jx9_value_to_string(apArg[0], &nLen);
2908 zPattern = jx9_value_to_string(apArg[1], &nPatLen);
2909 nOfft = 0; /* cc warning */
2910 nStart = 0;
2911 /* Peek the starting offset if available */
2912 if( nArg > 2 ){
2913 nStart = jx9_value_to_int(apArg[2]);
2914 if( nStart < 0 ){
2915 nStart = -nStart;
2916 }
2917 if( nStart >= nLen ){
2918 /* Invalid offset */
2919 nStart = 0;
2920 }else{
2921 zBlob += nStart;
2922 nLen -= nStart;
2923 }
2924 }
2925 if( nLen > 0 && nPatLen > 0 ){
2926 /* Perform the lookup */
2927 rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft);
2928 if( rc != SXRET_OK ){
2929 /* Pattern not found, return FALSE */
2930 jx9_result_bool(pCtx, 0);
2931 return JX9_OK;
2932 }
2933 /* Return the pattern position */
2934 jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart));
2935 }else{
2936 jx9_result_bool(pCtx, 0);
2937 }
2938 return JX9_OK;
2939}
2940/*
2941 * int stripos(string $haystack, string $needle [, int $offset = 0 ] )
2942 * Case-insensitive strpos.
2943 * Parameters
2944 * $haystack
2945 * The input string.
2946 * $needle
2947 * Search pattern (must be a string).
2948 * $offset
2949 * This optional offset parameter allows you to specify which character in haystack
2950 * to start searching. The position returned is still relative to the beginning
2951 * of haystack.
2952 * Return
2953 * Returns the position as an integer.If needle is not found, strpos() will return FALSE.
2954 */
2955static int jx9Builtin_stripos(jx9_context *pCtx, int nArg, jx9_value **apArg)
2956{
2957 ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */
2958 const char *zBlob, *zPattern;
2959 int nLen, nPatLen, nStart;
2960 sxu32 nOfft;
2961 sxi32 rc;
2962 if( nArg < 2 ){
2963 /* Missing arguments, return FALSE */
2964 jx9_result_bool(pCtx, 0);
2965 return JX9_OK;
2966 }
2967 /* Extract the needle and the haystack */
2968 zBlob = jx9_value_to_string(apArg[0], &nLen);
2969 zPattern = jx9_value_to_string(apArg[1], &nPatLen);
2970 nOfft = 0; /* cc warning */
2971 nStart = 0;
2972 /* Peek the starting offset if available */
2973 if( nArg > 2 ){
2974 nStart = jx9_value_to_int(apArg[2]);
2975 if( nStart < 0 ){
2976 nStart = -nStart;
2977 }
2978 if( nStart >= nLen ){
2979 /* Invalid offset */
2980 nStart = 0;
2981 }else{
2982 zBlob += nStart;
2983 nLen -= nStart;
2984 }
2985 }
2986 if( nLen > 0 && nPatLen > 0 ){
2987 /* Perform the lookup */
2988 rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft);
2989 if( rc != SXRET_OK ){
2990 /* Pattern not found, return FALSE */
2991 jx9_result_bool(pCtx, 0);
2992 return JX9_OK;
2993 }
2994 /* Return the pattern position */
2995 jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart));
2996 }else{
2997 jx9_result_bool(pCtx, 0);
2998 }
2999 return JX9_OK;
3000}
3001/*
3002 * int strrpos(string $haystack, string $needle [, int $offset = 0 ] )
3003 * Find the numeric position of the last occurrence of needle in the haystack string.
3004 * Parameters
3005 * $haystack
3006 * The input string.
3007 * $needle
3008 * Search pattern (must be a string).
3009 * $offset
3010 * If specified, search will start this number of characters counted from the beginning
3011 * of the string. If the value is negative, search will instead start from that many
3012 * characters from the end of the string, searching backwards.
3013 * Return
3014 * Returns the position as an integer.If needle is not found, strrpos() will return FALSE.
3015 */
3016static int jx9Builtin_strrpos(jx9_context *pCtx, int nArg, jx9_value **apArg)
3017{
3018 const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd;
3019 ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */
3020 int nLen, nPatLen;
3021 sxu32 nOfft;
3022 sxi32 rc;
3023 if( nArg < 2 ){
3024 /* Missing arguments, return FALSE */
3025 jx9_result_bool(pCtx, 0);
3026 return JX9_OK;
3027 }
3028 /* Extract the needle and the haystack */
3029 zBlob = jx9_value_to_string(apArg[0], &nLen);
3030 zPattern = jx9_value_to_string(apArg[1], &nPatLen);
3031 /* Point to the end of the pattern */
3032 zPtr = &zBlob[nLen - 1];
3033 zEnd = &zBlob[nLen];
3034 /* Save the starting posistion */
3035 zStart = zBlob;
3036 nOfft = 0; /* cc warning */
3037 /* Peek the starting offset if available */
3038 if( nArg > 2 ){
3039 int nStart;
3040 nStart = jx9_value_to_int(apArg[2]);
3041 if( nStart < 0 ){
3042 nStart = -nStart;
3043 if( nStart >= nLen ){
3044 /* Invalid offset */
3045 jx9_result_bool(pCtx, 0);
3046 return JX9_OK;
3047 }else{
3048 nLen -= nStart;
3049 zPtr = &zBlob[nLen - 1];
3050 zEnd = &zBlob[nLen];
3051 }
3052 }else{
3053 if( nStart >= nLen ){
3054 /* Invalid offset */
3055 jx9_result_bool(pCtx, 0);
3056 return JX9_OK;
3057 }else{
3058 zBlob += nStart;
3059 nLen -= nStart;
3060 }
3061 }
3062 }
3063 if( nLen > 0 && nPatLen > 0 ){
3064 /* Perform the lookup */
3065 for(;;){
3066 if( zBlob >= zPtr ){
3067 break;
3068 }
3069 rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft);
3070 if( rc == SXRET_OK ){
3071 /* Pattern found, return it's position */
3072 jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart));
3073 return JX9_OK;
3074 }
3075 zPtr--;
3076 }
3077 /* Pattern not found, return FALSE */
3078 jx9_result_bool(pCtx, 0);
3079 }else{
3080 jx9_result_bool(pCtx, 0);
3081 }
3082 return JX9_OK;
3083}
3084/*
3085 * int strripos(string $haystack, string $needle [, int $offset = 0 ] )
3086 * Case-insensitive strrpos.
3087 * Parameters
3088 * $haystack
3089 * The input string.
3090 * $needle
3091 * Search pattern (must be a string).
3092 * $offset
3093 * If specified, search will start this number of characters counted from the beginning
3094 * of the string. If the value is negative, search will instead start from that many
3095 * characters from the end of the string, searching backwards.
3096 * Return
3097 * Returns the position as an integer.If needle is not found, strrpos() will return FALSE.
3098 */
3099static int jx9Builtin_strripos(jx9_context *pCtx, int nArg, jx9_value **apArg)
3100{
3101 const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd;
3102 ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */
3103 int nLen, nPatLen;
3104 sxu32 nOfft;
3105 sxi32 rc;
3106 if( nArg < 2 ){
3107 /* Missing arguments, return FALSE */
3108 jx9_result_bool(pCtx, 0);
3109 return JX9_OK;
3110 }
3111 /* Extract the needle and the haystack */
3112 zBlob = jx9_value_to_string(apArg[0], &nLen);
3113 zPattern = jx9_value_to_string(apArg[1], &nPatLen);
3114 /* Point to the end of the pattern */
3115 zPtr = &zBlob[nLen - 1];
3116 zEnd = &zBlob[nLen];
3117 /* Save the starting posistion */
3118 zStart = zBlob;
3119 nOfft = 0; /* cc warning */
3120 /* Peek the starting offset if available */
3121 if( nArg > 2 ){
3122 int nStart;
3123 nStart = jx9_value_to_int(apArg[2]);
3124 if( nStart < 0 ){
3125 nStart = -nStart;
3126 if( nStart >= nLen ){
3127 /* Invalid offset */
3128 jx9_result_bool(pCtx, 0);
3129 return JX9_OK;
3130 }else{
3131 nLen -= nStart;
3132 zPtr = &zBlob[nLen - 1];
3133 zEnd = &zBlob[nLen];
3134 }
3135 }else{
3136 if( nStart >= nLen ){
3137 /* Invalid offset */
3138 jx9_result_bool(pCtx, 0);
3139 return JX9_OK;
3140 }else{
3141 zBlob += nStart;
3142 nLen -= nStart;
3143 }
3144 }
3145 }
3146 if( nLen > 0 && nPatLen > 0 ){
3147 /* Perform the lookup */
3148 for(;;){
3149 if( zBlob >= zPtr ){
3150 break;
3151 }
3152 rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft);
3153 if( rc == SXRET_OK ){
3154 /* Pattern found, return it's position */
3155 jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart));
3156 return JX9_OK;
3157 }
3158 zPtr--;
3159 }
3160 /* Pattern not found, return FALSE */
3161 jx9_result_bool(pCtx, 0);
3162 }else{
3163 jx9_result_bool(pCtx, 0);
3164 }
3165 return JX9_OK;
3166}
3167/*
3168 * int strrchr(string $haystack, mixed $needle)
3169 * Find the last occurrence of a character in a string.
3170 * Parameters
3171 * $haystack
3172 * The input string.
3173 * $needle
3174 * If needle contains more than one character, only the first is used.
3175 * This behavior is different from that of strstr().
3176 * If needle is not a string, it is converted to an integer and applied
3177 * as the ordinal value of a character.
3178 * Return
3179 * This function returns the portion of string, or FALSE if needle is not found.
3180 */
3181static int jx9Builtin_strrchr(jx9_context *pCtx, int nArg, jx9_value **apArg)
3182{
3183 const char *zBlob;
3184 int nLen, c;
3185 if( nArg < 2 ){
3186 /* Missing arguments, return FALSE */
3187 jx9_result_bool(pCtx, 0);
3188 return JX9_OK;
3189 }
3190 /* Extract the haystack */
3191 zBlob = jx9_value_to_string(apArg[0], &nLen);
3192 c = 0; /* cc warning */
3193 if( nLen > 0 ){
3194 sxu32 nOfft;
3195 sxi32 rc;
3196 if( jx9_value_is_string(apArg[1]) ){
3197 const char *zPattern;
3198 zPattern = jx9_value_to_string(apArg[1], 0); /* Never fail, so there is no need to check
3199 * for NULL pointer.
3200 */
3201 c = zPattern[0];
3202 }else{
3203 /* Int cast */
3204 c = jx9_value_to_int(apArg[1]);
3205 }
3206 /* Perform the lookup */
3207 rc = SyByteFind2(zBlob, (sxu32)nLen, c, &nOfft);
3208 if( rc != SXRET_OK ){
3209 /* No such entry, return FALSE */
3210 jx9_result_bool(pCtx, 0);
3211 return JX9_OK;
3212 }
3213 /* Return the string portion */
3214 jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft]));
3215 }else{
3216 jx9_result_bool(pCtx, 0);
3217 }
3218 return JX9_OK;
3219}
3220/*
3221 * string strrev(string $string)
3222 * Reverse a string.
3223 * Parameters
3224 * $string
3225 * String to be reversed.
3226 * Return
3227 * The reversed string.
3228 */
3229static int jx9Builtin_strrev(jx9_context *pCtx, int nArg, jx9_value **apArg)
3230{
3231 const char *zIn, *zEnd;
3232 int nLen, c;
3233 if( nArg < 1 ){
3234 /* Missing arguments, return NULL */
3235 jx9_result_null(pCtx);
3236 return JX9_OK;
3237 }
3238 /* Extract the target string */
3239 zIn = jx9_value_to_string(apArg[0], &nLen);
3240 if( nLen < 1 ){
3241 /* Empty string Return null */
3242 jx9_result_null(pCtx);
3243 return JX9_OK;
3244 }
3245 /* Perform the requested operation */
3246 zEnd = &zIn[nLen - 1];
3247 for(;;){
3248 if( zEnd < zIn ){
3249 /* No more input to process */
3250 break;
3251 }
3252 /* Append current character */
3253 c = zEnd[0];
3254 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
3255 zEnd--;
3256 }
3257 return JX9_OK;
3258}
3259/*
3260 * string str_repeat(string $input, int $multiplier)
3261 * Returns input repeated multiplier times.
3262 * Parameters
3263 * $string
3264 * String to be repeated.
3265 * $multiplier
3266 * Number of time the input string should be repeated.
3267 * multiplier has to be greater than or equal to 0. If the multiplier is set
3268 * to 0, the function will return an empty string.
3269 * Return
3270 * The repeated string.
3271 */
3272static int jx9Builtin_str_repeat(jx9_context *pCtx, int nArg, jx9_value **apArg)
3273{
3274 const char *zIn;
3275 int nLen, nMul;
3276 int rc;
3277 if( nArg < 2 ){
3278 /* Missing arguments, return NULL */
3279 jx9_result_null(pCtx);
3280 return JX9_OK;
3281 }
3282 /* Extract the target string */
3283 zIn = jx9_value_to_string(apArg[0], &nLen);
3284 if( nLen < 1 ){
3285 /* Empty string.Return null */
3286 jx9_result_null(pCtx);
3287 return JX9_OK;
3288 }
3289 /* Extract the multiplier */
3290 nMul = jx9_value_to_int(apArg[1]);
3291 if( nMul < 1 ){
3292 /* Return the empty string */
3293 jx9_result_string(pCtx, "", 0);
3294 return JX9_OK;
3295 }
3296 /* Perform the requested operation */
3297 for(;;){
3298 if( nMul < 1 ){
3299 break;
3300 }
3301 /* Append the copy */
3302 rc = jx9_result_string(pCtx, zIn, nLen);
3303 if( rc != JX9_OK ){
3304 /* Out of memory, break immediately */
3305 break;
3306 }
3307 nMul--;
3308 }
3309 return JX9_OK;
3310}
3311/*
3312 * string nl2br(string $string[, bool $is_xhtml = true ])
3313 * Inserts HTML line breaks before all newlines in a string.
3314 * Parameters
3315 * $string
3316 * The input string.
3317 * $is_xhtml
3318 * Whenever to use XHTML compatible line breaks or not.
3319 * Return
3320 * The processed string.
3321 */
3322static int jx9Builtin_nl2br(jx9_context *pCtx, int nArg, jx9_value **apArg)
3323{
3324 const char *zIn, *zCur, *zEnd;
3325 int is_xhtml = 0;
3326 int nLen;
3327 if( nArg < 1 ){
3328 /* Missing arguments, return the empty string */
3329 jx9_result_string(pCtx, "", 0);
3330 return JX9_OK;
3331 }
3332 /* Extract the target string */
3333 zIn = jx9_value_to_string(apArg[0], &nLen);
3334 if( nLen < 1 ){
3335 /* Empty string, return null */
3336 jx9_result_null(pCtx);
3337 return JX9_OK;
3338 }
3339 if( nArg > 1 ){
3340 is_xhtml = jx9_value_to_bool(apArg[1]);
3341 }
3342 zEnd = &zIn[nLen];
3343 /* Perform the requested operation */
3344 for(;;){
3345 zCur = zIn;
3346 /* Delimit the string */
3347 while( zIn < zEnd && (zIn[0] != '\n'&& zIn[0] != '\r') ){
3348 zIn++;
3349 }
3350 if( zCur < zIn ){
3351 /* Output chunk verbatim */
3352 jx9_result_string(pCtx, zCur, (int)(zIn-zCur));
3353 }
3354 if( zIn >= zEnd ){
3355 /* No more input to process */
3356 break;
3357 }
3358 /* Output the HTML line break */
3359 if( is_xhtml ){
3360 jx9_result_string(pCtx, "<br>", (int)sizeof("<br>")-1);
3361 }else{
3362 jx9_result_string(pCtx, "<br/>", (int)sizeof("<br/>")-1);
3363 }
3364 zCur = zIn;
3365 /* Append trailing line */
3366 while( zIn < zEnd && (zIn[0] == '\n' || zIn[0] == '\r') ){
3367 zIn++;
3368 }
3369 if( zCur < zIn ){
3370 /* Output chunk verbatim */
3371 jx9_result_string(pCtx, zCur, (int)(zIn-zCur));
3372 }
3373 }
3374 return JX9_OK;
3375}
3376/*
3377 * Format a given string and invoke the given callback on each processed chunk.
3378 * According to the JX9 reference manual.
3379 * The format string is composed of zero or more directives: ordinary characters
3380 * (excluding %) that are copied directly to the result, and conversion
3381 * specifications, each of which results in fetching its own parameter.
3382 * This applies to both sprintf() and printf().
3383 * Each conversion specification consists of a percent sign (%), followed by one
3384 * or more of these elements, in order:
3385 * An optional sign specifier that forces a sign (- or +) to be used on a number.
3386 * By default, only the - sign is used on a number if it's negative. This specifier forces
3387 * positive numbers to have the + sign attached as well.
3388 * An optional padding specifier that says what character will be used for padding
3389 * the results to the right string size. This may be a space character or a 0 (zero character).
3390 * The default is to pad with spaces. An alternate padding character can be specified by prefixing
3391 * it with a single quote ('). See the examples below.
3392 * An optional alignment specifier that says if the result should be left-justified or right-justified.
3393 * The default is right-justified; a - character here will make it left-justified.
3394 * An optional number, a width specifier that says how many characters (minimum) this conversion
3395 * should result in.
3396 * An optional precision specifier in the form of a period (`.') followed by an optional decimal
3397 * digit string that says how many decimal digits should be displayed for floating-point numbers.
3398 * When using this specifier on a string, it acts as a cutoff point, setting a maximum character
3399 * limit to the string.
3400 * A type specifier that says what type the argument data should be treated as. Possible types:
3401 * % - a literal percent character. No argument is required.
3402 * b - the argument is treated as an integer, and presented as a binary number.
3403 * c - the argument is treated as an integer, and presented as the character with that ASCII value.
3404 * d - the argument is treated as an integer, and presented as a (signed) decimal number.
3405 * e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands
3406 * for the number of digits after the decimal point.
3407 * E - like %e but uses uppercase letter (e.g. 1.2E+2).
3408 * u - the argument is treated as an integer, and presented as an unsigned decimal number.
3409 * f - the argument is treated as a float, and presented as a floating-point number (locale aware).
3410 * F - the argument is treated as a float, and presented as a floating-point number (non-locale aware).
3411 * g - shorter of %e and %f.
3412 * G - shorter of %E and %f.
3413 * o - the argument is treated as an integer, and presented as an octal number.
3414 * s - the argument is treated as and presented as a string.
3415 * x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters).
3416 * X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters).
3417 */
3418/*
3419 * This implementation is based on the one found in the SQLite3 source tree.
3420 */
3421#define JX9_FMT_BUFSIZ 1024 /* Conversion buffer size */
3422/*
3423** Conversion types fall into various categories as defined by the
3424** following enumeration.
3425*/
3426#define JX9_FMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */
3427#define JX9_FMT_FLOAT 2 /* Floating point.%f */
3428#define JX9_FMT_EXP 3 /* Exponentional notation.%e and %E */
3429#define JX9_FMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */
3430#define JX9_FMT_SIZE 5 /* Total number of characters processed so far.%n */
3431#define JX9_FMT_STRING 6 /* Strings.%s */
3432#define JX9_FMT_PERCENT 7 /* Percent symbol.%% */
3433#define JX9_FMT_CHARX 8 /* Characters.%c */
3434#define JX9_FMT_ERROR 9 /* Used to indicate no such conversion type */
3435/*
3436** Allowed values for jx9_fmt_info.flags
3437*/
3438#define JX9_FMT_FLAG_SIGNED 0x01
3439#define JX9_FMT_FLAG_UNSIGNED 0x02
3440/*
3441** Each builtin conversion character (ex: the 'd' in "%d") is described
3442** by an instance of the following structure
3443*/
3444typedef struct jx9_fmt_info jx9_fmt_info;
3445struct jx9_fmt_info
3446{
3447 char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */
3448 sxu8 base; /* The base for radix conversion */
3449 int flags; /* One or more of JX9_FMT_FLAG_ constants below */
3450 sxu8 type; /* Conversion paradigm */
3451 char *charset; /* The character set for conversion */
3452 char *prefix; /* Prefix on non-zero values in alt format */
3453};
3454#ifndef JX9_OMIT_FLOATING_POINT
3455/*
3456** "*val" is a double such that 0.1 <= *val < 10.0
3457** Return the ascii code for the leading digit of *val, then
3458** multiply "*val" by 10.0 to renormalize.
3459**
3460** Example:
3461** input: *val = 3.14159
3462** output: *val = 1.4159 function return = '3'
3463**
3464** The counter *cnt is incremented each time. After counter exceeds
3465** 16 (the number of significant digits in a 64-bit float) '0' is
3466** always returned.
3467*/
3468static int vxGetdigit(sxlongreal *val, int *cnt)
3469{
3470 sxlongreal d;
3471 int digit;
3472
3473 if( (*cnt)++ >= 16 ){
3474 return '0';
3475 }
3476 digit = (int)*val;
3477 d = digit;
3478 *val = (*val - d)*10.0;
3479 return digit + '0' ;
3480}
3481#endif /* JX9_OMIT_FLOATING_POINT */
3482/*
3483 * The following table is searched linearly, so it is good to put the most frequently
3484 * used conversion types first.
3485 */
3486static const jx9_fmt_info aFmt[] = {
3487 { 'd', 10, JX9_FMT_FLAG_SIGNED, JX9_FMT_RADIX, "0123456789", 0 },
3488 { 's', 0, 0, JX9_FMT_STRING, 0, 0 },
3489 { 'c', 0, 0, JX9_FMT_CHARX, 0, 0 },
3490 { 'x', 16, 0, JX9_FMT_RADIX, "0123456789abcdef", "x0" },
3491 { 'X', 16, 0, JX9_FMT_RADIX, "0123456789ABCDEF", "X0" },
3492 { 'b', 2, 0, JX9_FMT_RADIX, "01", "b0"},
3493 { 'o', 8, 0, JX9_FMT_RADIX, "01234567", "0" },
3494 { 'u', 10, 0, JX9_FMT_RADIX, "0123456789", 0 },
3495 { 'f', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 },
3496 { 'F', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 },
3497 { 'e', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "e", 0 },
3498 { 'E', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "E", 0 },
3499 { 'g', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "e", 0 },
3500 { 'G', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "E", 0 },
3501 { '%', 0, 0, JX9_FMT_PERCENT, 0, 0 }
3502};
3503/*
3504 * Format a given string.
3505 * The root program. All variations call this core.
3506 * INPUTS:
3507 * xConsumer This is a pointer to a function taking four arguments
3508 * 1. A pointer to the call context.
3509 * 2. A pointer to the list of characters to be output
3510 * (Note, this list is NOT null terminated.)
3511 * 3. An integer number of characters to be output.
3512 * (Note: This number might be zero.)
3513 * 4. Upper layer private data.
3514 * zIn This is the format string, as in the usual print.
3515 * apArg This is a pointer to a list of arguments.
3516 */
3517JX9_PRIVATE sxi32 jx9InputFormat(
3518 int (*xConsumer)(jx9_context *, const char *, int, void *), /* Format consumer */
3519 jx9_context *pCtx, /* call context */
3520 const char *zIn, /* Format string */
3521 int nByte, /* Format string length */
3522 int nArg, /* Total argument of the given arguments */
3523 jx9_value **apArg, /* User arguments */
3524 void *pUserData, /* Last argument to xConsumer() */
3525 int vf /* TRUE if called from vfprintf, vsprintf context */
3526 )
3527{
3528 char spaces[] = " ";
3529#define etSPACESIZE ((int)sizeof(spaces)-1)
3530 const char *zCur, *zEnd = &zIn[nByte];
3531 char *zBuf, zWorker[JX9_FMT_BUFSIZ]; /* Working buffer */
3532 const jx9_fmt_info *pInfo; /* Pointer to the appropriate info structure */
3533 int flag_alternateform; /* True if "#" flag is present */
3534 int flag_leftjustify; /* True if "-" flag is present */
3535 int flag_blanksign; /* True if " " flag is present */
3536 int flag_plussign; /* True if "+" flag is present */
3537 int flag_zeropad; /* True if field width constant starts with zero */
3538 jx9_value *pArg; /* Current processed argument */
3539 jx9_int64 iVal;
3540 int precision; /* Precision of the current field */
3541 char *zExtra;
3542 int c, rc, n;
3543 int length; /* Length of the field */
3544 int prefix;
3545 sxu8 xtype; /* Conversion paradigm */
3546 int width; /* Width of the current field */
3547 int idx;
3548 n = (vf == TRUE) ? 0 : 1;
3549#define NEXT_ARG ( n < nArg ? apArg[n++] : 0 )
3550 /* Start the format process */
3551 for(;;){
3552 zCur = zIn;
3553 while( zIn < zEnd && zIn[0] != '%' ){
3554 zIn++;
3555 }
3556 if( zCur < zIn ){
3557 /* Consume chunk verbatim */
3558 rc = xConsumer(pCtx, zCur, (int)(zIn-zCur), pUserData);
3559 if( rc == SXERR_ABORT ){
3560 /* Callback request an operation abort */
3561 break;
3562 }
3563 }
3564 if( zIn >= zEnd ){
3565 /* No more input to process, break immediately */
3566 break;
3567 }
3568 /* Find out what flags are present */
3569 flag_leftjustify = flag_plussign = flag_blanksign =
3570 flag_alternateform = flag_zeropad = 0;
3571 zIn++; /* Jump the precent sign */
3572 do{
3573 c = zIn[0];
3574 switch( c ){
3575 case '-': flag_leftjustify = 1; c = 0; break;
3576 case '+': flag_plussign = 1; c = 0; break;
3577 case ' ': flag_blanksign = 1; c = 0; break;
3578 case '#': flag_alternateform = 1; c = 0; break;
3579 case '0': flag_zeropad = 1; c = 0; break;
3580 case '\'':
3581 zIn++;
3582 if( zIn < zEnd ){
3583 /* An alternate padding character can be specified by prefixing it with a single quote (') */
3584 c = zIn[0];
3585 for(idx = 0 ; idx < etSPACESIZE ; ++idx ){
3586 spaces[idx] = (char)c;
3587 }
3588 c = 0;
3589 }
3590 break;
3591 default: break;
3592 }
3593 }while( c==0 && (zIn++ < zEnd) );
3594 /* Get the field width */
3595 width = 0;
3596 while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){
3597 width = width*10 + (zIn[0] - '0');
3598 zIn++;
3599 }
3600 if( zIn < zEnd && zIn[0] == '$' ){
3601 /* Position specifer */
3602 if( width > 0 ){
3603 n = width;
3604 if( vf && n > 0 ){
3605 n--;
3606 }
3607 }
3608 zIn++;
3609 width = 0;
3610 if( zIn < zEnd && zIn[0] == '0' ){
3611 flag_zeropad = 1;
3612 zIn++;
3613 }
3614 while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){
3615 width = width*10 + (zIn[0] - '0');
3616 zIn++;
3617 }
3618 }
3619 if( width > JX9_FMT_BUFSIZ-10 ){
3620 width = JX9_FMT_BUFSIZ-10;
3621 }
3622 /* Get the precision */
3623 precision = -1;
3624 if( zIn < zEnd && zIn[0] == '.' ){
3625 precision = 0;
3626 zIn++;
3627 while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){
3628 precision = precision*10 + (zIn[0] - '0');
3629 zIn++;
3630 }
3631 }
3632 if( zIn >= zEnd ){
3633 /* No more input */
3634 break;
3635 }
3636 /* Fetch the info entry for the field */
3637 pInfo = 0;
3638 xtype = JX9_FMT_ERROR;
3639 c = zIn[0];
3640 zIn++; /* Jump the format specifer */
3641 for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){
3642 if( c==aFmt[idx].fmttype ){
3643 pInfo = &aFmt[idx];
3644 xtype = pInfo->type;
3645 break;
3646 }
3647 }
3648 zBuf = zWorker; /* Point to the working buffer */
3649 length = 0;
3650 zExtra = 0;
3651 /*
3652 ** At this point, variables are initialized as follows:
3653 **
3654 ** flag_alternateform TRUE if a '#' is present.
3655 ** flag_plussign TRUE if a '+' is present.
3656 ** flag_leftjustify TRUE if a '-' is present or if the
3657 ** field width was negative.
3658 ** flag_zeropad TRUE if the width began with 0.
3659 ** the conversion character.
3660 ** flag_blanksign TRUE if a ' ' is present.
3661 ** width The specified field width. This is
3662 ** always non-negative. Zero is the default.
3663 ** precision The specified precision. The default
3664 ** is -1.
3665 */
3666 switch(xtype){
3667 case JX9_FMT_PERCENT:
3668 /* A literal percent character */
3669 zWorker[0] = '%';
3670 length = (int)sizeof(char);
3671 break;
3672 case JX9_FMT_CHARX:
3673 /* The argument is treated as an integer, and presented as the character
3674 * with that ASCII value
3675 */
3676 pArg = NEXT_ARG;
3677 if( pArg == 0 ){
3678 c = 0;
3679 }else{
3680 c = jx9_value_to_int(pArg);
3681 }
3682 /* NUL byte is an acceptable value */
3683 zWorker[0] = (char)c;
3684 length = (int)sizeof(char);
3685 break;
3686 case JX9_FMT_STRING:
3687 /* the argument is treated as and presented as a string */
3688 pArg = NEXT_ARG;
3689 if( pArg == 0 ){
3690 length = 0;
3691 }else{
3692 zBuf = (char *)jx9_value_to_string(pArg, &length);
3693 }
3694 if( length < 1 ){
3695 zBuf = " ";
3696 length = (int)sizeof(char);
3697 }
3698 if( precision>=0 && precision<length ){
3699 length = precision;
3700 }
3701 if( flag_zeropad ){
3702 /* zero-padding works on strings too */
3703 for(idx = 0 ; idx < etSPACESIZE ; ++idx ){
3704 spaces[idx] = '0';
3705 }
3706 }
3707 break;
3708 case JX9_FMT_RADIX:
3709 pArg = NEXT_ARG;
3710 if( pArg == 0 ){
3711 iVal = 0;
3712 }else{
3713 iVal = jx9_value_to_int64(pArg);
3714 }
3715 /* Limit the precision to prevent overflowing buf[] during conversion */
3716 if( precision>JX9_FMT_BUFSIZ-40 ){
3717 precision = JX9_FMT_BUFSIZ-40;
3718 }
3719#if 1
3720 /* For the format %#x, the value zero is printed "0" not "0x0".
3721 ** I think this is stupid.*/
3722 if( iVal==0 ) flag_alternateform = 0;
3723#else
3724 /* More sensible: turn off the prefix for octal (to prevent "00"),
3725 ** but leave the prefix for hex.*/
3726 if( iVal==0 && pInfo->base==8 ) flag_alternateform = 0;
3727#endif
3728 if( pInfo->flags & JX9_FMT_FLAG_SIGNED ){
3729 if( iVal<0 ){
3730 iVal = -iVal;
3731 /* Ticket 1433-003 */
3732 if( iVal < 0 ){
3733 /* Overflow */
3734 iVal= 0x7FFFFFFFFFFFFFFF;
3735 }
3736 prefix = '-';
3737 }else if( flag_plussign ) prefix = '+';
3738 else if( flag_blanksign ) prefix = ' ';
3739 else prefix = 0;
3740 }else{
3741 if( iVal<0 ){
3742 iVal = -iVal;
3743 /* Ticket 1433-003 */
3744 if( iVal < 0 ){
3745 /* Overflow */
3746 iVal= 0x7FFFFFFFFFFFFFFF;
3747 }
3748 }
3749 prefix = 0;
3750 }
3751 if( flag_zeropad && precision<width-(prefix!=0) ){
3752 precision = width-(prefix!=0);
3753 }
3754 zBuf = &zWorker[JX9_FMT_BUFSIZ-1];
3755 {
3756 register char *cset; /* Use registers for speed */
3757 register int base;
3758 cset = pInfo->charset;
3759 base = pInfo->base;
3760 do{ /* Convert to ascii */
3761 *(--zBuf) = cset[iVal%base];
3762 iVal = iVal/base;
3763 }while( iVal>0 );
3764 }
3765 length = &zWorker[JX9_FMT_BUFSIZ-1]-zBuf;
3766 for(idx=precision-length; idx>0; idx--){
3767 *(--zBuf) = '0'; /* Zero pad */
3768 }
3769 if( prefix ) *(--zBuf) = (char)prefix; /* Add sign */
3770 if( flag_alternateform && pInfo->prefix ){ /* Add "0" or "0x" */
3771 char *pre, x;
3772 pre = pInfo->prefix;
3773 if( *zBuf!=pre[0] ){
3774 for(pre=pInfo->prefix; (x=(*pre))!=0; pre++) *(--zBuf) = x;
3775 }
3776 }
3777 length = &zWorker[JX9_FMT_BUFSIZ-1]-zBuf;
3778 break;
3779 case JX9_FMT_FLOAT:
3780 case JX9_FMT_EXP:
3781 case JX9_FMT_GENERIC:{
3782#ifndef JX9_OMIT_FLOATING_POINT
3783 long double realvalue;
3784 int exp; /* exponent of real numbers */
3785 double rounder; /* Used for rounding floating point values */
3786 int flag_dp; /* True if decimal point should be shown */
3787 int flag_rtz; /* True if trailing zeros should be removed */
3788 int flag_exp; /* True to force display of the exponent */
3789 int nsd; /* Number of significant digits returned */
3790 pArg = NEXT_ARG;
3791 if( pArg == 0 ){
3792 realvalue = 0;
3793 }else{
3794 realvalue = jx9_value_to_double(pArg);
3795 }
3796 if( precision<0 ) precision = 6; /* Set default precision */
3797 if( precision>JX9_FMT_BUFSIZ-40) precision = JX9_FMT_BUFSIZ-40;
3798 if( realvalue<0.0 ){
3799 realvalue = -realvalue;
3800 prefix = '-';
3801 }else{
3802 if( flag_plussign ) prefix = '+';
3803 else if( flag_blanksign ) prefix = ' ';
3804 else prefix = 0;
3805 }
3806 if( pInfo->type==JX9_FMT_GENERIC && precision>0 ) precision--;
3807 rounder = 0.0;
3808#if 0
3809 /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */
3810 for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1);
3811#else
3812 /* It makes more sense to use 0.5 */
3813 for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1);
3814#endif
3815 if( pInfo->type==JX9_FMT_FLOAT ) realvalue += rounder;
3816 /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
3817 exp = 0;
3818 if( realvalue>0.0 ){
3819 while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
3820 while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
3821 while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; }
3822 while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; }
3823 if( exp>350 || exp<-350 ){
3824 zBuf = "NaN";
3825 length = 3;
3826 break;
3827 }
3828 }
3829 zBuf = zWorker;
3830 /*
3831 ** If the field type is etGENERIC, then convert to either etEXP
3832 ** or etFLOAT, as appropriate.
3833 */
3834 flag_exp = xtype==JX9_FMT_EXP;
3835 if( xtype!=JX9_FMT_FLOAT ){
3836 realvalue += rounder;
3837 if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
3838 }
3839 if( xtype==JX9_FMT_GENERIC ){
3840 flag_rtz = !flag_alternateform;
3841 if( exp<-4 || exp>precision ){
3842 xtype = JX9_FMT_EXP;
3843 }else{
3844 precision = precision - exp;
3845 xtype = JX9_FMT_FLOAT;
3846 }
3847 }else{
3848 flag_rtz = 0;
3849 }
3850 /*
3851 ** The "exp+precision" test causes output to be of type etEXP if
3852 ** the precision is too large to fit in buf[].
3853 */
3854 nsd = 0;
3855 if( xtype==JX9_FMT_FLOAT && exp+precision<JX9_FMT_BUFSIZ-30 ){
3856 flag_dp = (precision>0 || flag_alternateform);
3857 if( prefix ) *(zBuf++) = (char)prefix; /* Sign */
3858 if( exp<0 ) *(zBuf++) = '0'; /* Digits before "." */
3859 else for(; exp>=0; exp--) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd);
3860 if( flag_dp ) *(zBuf++) = '.'; /* The decimal point */
3861 for(exp++; exp<0 && precision>0; precision--, exp++){
3862 *(zBuf++) = '0';
3863 }
3864 while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd);
3865 *(zBuf--) = 0; /* Null terminate */
3866 if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */
3867 while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0;
3868 if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0;
3869 }
3870 zBuf++; /* point to next free slot */
3871 }else{ /* etEXP or etGENERIC */
3872 flag_dp = (precision>0 || flag_alternateform);
3873 if( prefix ) *(zBuf++) = (char)prefix; /* Sign */
3874 *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); /* First digit */
3875 if( flag_dp ) *(zBuf++) = '.'; /* Decimal point */
3876 while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd);
3877 zBuf--; /* point to last digit */
3878 if( flag_rtz && flag_dp ){ /* Remove tail zeros */
3879 while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0;
3880 if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0;
3881 }
3882 zBuf++; /* point to next free slot */
3883 if( exp || flag_exp ){
3884 *(zBuf++) = pInfo->charset[0];
3885 if( exp<0 ){ *(zBuf++) = '-'; exp = -exp; } /* sign of exp */
3886 else { *(zBuf++) = '+'; }
3887 if( exp>=100 ){
3888 *(zBuf++) = (char)((exp/100)+'0'); /* 100's digit */
3889 exp %= 100;
3890 }
3891 *(zBuf++) = (char)(exp/10+'0'); /* 10's digit */
3892 *(zBuf++) = (char)(exp%10+'0'); /* 1's digit */
3893 }
3894 }
3895 /* The converted number is in buf[] and zero terminated.Output it.
3896 ** Note that the number is in the usual order, not reversed as with
3897 ** integer conversions.*/
3898 length = (int)(zBuf-zWorker);
3899 zBuf = zWorker;
3900 /* Special case: Add leading zeros if the flag_zeropad flag is
3901 ** set and we are not left justified */
3902 if( flag_zeropad && !flag_leftjustify && length < width){
3903 int i;
3904 int nPad = width - length;
3905 for(i=width; i>=nPad; i--){
3906 zBuf[i] = zBuf[i-nPad];
3907 }
3908 i = prefix!=0;
3909 while( nPad-- ) zBuf[i++] = '0';
3910 length = width;
3911 }
3912#else
3913 zBuf = " ";
3914 length = (int)sizeof(char);
3915#endif /* JX9_OMIT_FLOATING_POINT */
3916 break;
3917 }
3918 default:
3919 /* Invalid format specifer */
3920 zWorker[0] = '?';
3921 length = (int)sizeof(char);
3922 break;
3923 }
3924 /*
3925 ** The text of the conversion is pointed to by "zBuf" and is
3926 ** "length" characters long.The field width is "width".Do
3927 ** the output.
3928 */
3929 if( !flag_leftjustify ){
3930 register int nspace;
3931 nspace = width-length;
3932 if( nspace>0 ){
3933 while( nspace>=etSPACESIZE ){
3934 rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData);
3935 if( rc != SXRET_OK ){
3936 return SXERR_ABORT; /* Consumer routine request an operation abort */
3937 }
3938 nspace -= etSPACESIZE;
3939 }
3940 if( nspace>0 ){
3941 rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData);
3942 if( rc != SXRET_OK ){
3943 return SXERR_ABORT; /* Consumer routine request an operation abort */
3944 }
3945 }
3946 }
3947 }
3948 if( length>0 ){
3949 rc = xConsumer(pCtx, zBuf, (unsigned int)length, pUserData);
3950 if( rc != SXRET_OK ){
3951 return SXERR_ABORT; /* Consumer routine request an operation abort */
3952 }
3953 }
3954 if( flag_leftjustify ){
3955 register int nspace;
3956 nspace = width-length;
3957 if( nspace>0 ){
3958 while( nspace>=etSPACESIZE ){
3959 rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData);
3960 if( rc != SXRET_OK ){
3961 return SXERR_ABORT; /* Consumer routine request an operation abort */
3962 }
3963 nspace -= etSPACESIZE;
3964 }
3965 if( nspace>0 ){
3966 rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData);
3967 if( rc != SXRET_OK ){
3968 return SXERR_ABORT; /* Consumer routine request an operation abort */
3969 }
3970 }
3971 }
3972 }
3973 }/* for(;;) */
3974 return SXRET_OK;
3975}
3976/*
3977 * Callback [i.e: Formatted input consumer] of the sprintf function.
3978 */
3979static int sprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData)
3980{
3981 /* Consume directly */
3982 jx9_result_string(pCtx, zInput, nLen);
3983 SXUNUSED(pUserData); /* cc warning */
3984 return JX9_OK;
3985}
3986/*
3987 * string sprintf(string $format[, mixed $args [, mixed $... ]])
3988 * Return a formatted string.
3989 * Parameters
3990 * $format
3991 * The format string (see block comment above)
3992 * Return
3993 * A string produced according to the formatting string format.
3994 */
3995static int jx9Builtin_sprintf(jx9_context *pCtx, int nArg, jx9_value **apArg)
3996{
3997 const char *zFormat;
3998 int nLen;
3999 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
4000 /* Missing/Invalid arguments, return the empty string */
4001 jx9_result_string(pCtx, "", 0);
4002 return JX9_OK;
4003 }
4004 /* Extract the string format */
4005 zFormat = jx9_value_to_string(apArg[0], &nLen);
4006 if( nLen < 1 ){
4007 /* Empty string */
4008 jx9_result_string(pCtx, "", 0);
4009 return JX9_OK;
4010 }
4011 /* Format the string */
4012 jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, nArg, apArg, 0, FALSE);
4013 return JX9_OK;
4014}
4015/*
4016 * Callback [i.e: Formatted input consumer] of the printf function.
4017 */
4018static int printfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData)
4019{
4020 jx9_int64 *pCounter = (jx9_int64 *)pUserData;
4021 /* Call the VM output consumer directly */
4022 jx9_context_output(pCtx, zInput, nLen);
4023 /* Increment counter */
4024 *pCounter += nLen;
4025 return JX9_OK;
4026}
4027/*
4028 * int64 printf(string $format[, mixed $args[, mixed $... ]])
4029 * Output a formatted string.
4030 * Parameters
4031 * $format
4032 * See sprintf() for a description of format.
4033 * Return
4034 * The length of the outputted string.
4035 */
4036static int jx9Builtin_printf(jx9_context *pCtx, int nArg, jx9_value **apArg)
4037{
4038 jx9_int64 nCounter = 0;
4039 const char *zFormat;
4040 int nLen;
4041 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
4042 /* Missing/Invalid arguments, return 0 */
4043 jx9_result_int(pCtx, 0);
4044 return JX9_OK;
4045 }
4046 /* Extract the string format */
4047 zFormat = jx9_value_to_string(apArg[0], &nLen);
4048 if( nLen < 1 ){
4049 /* Empty string */
4050 jx9_result_int(pCtx, 0);
4051 return JX9_OK;
4052 }
4053 /* Format the string */
4054 jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, nArg, apArg, (void *)&nCounter, FALSE);
4055 /* Return the length of the outputted string */
4056 jx9_result_int64(pCtx, nCounter);
4057 return JX9_OK;
4058}
4059/*
4060 * int vprintf(string $format, array $args)
4061 * Output a formatted string.
4062 * Parameters
4063 * $format
4064 * See sprintf() for a description of format.
4065 * Return
4066 * The length of the outputted string.
4067 */
4068static int jx9Builtin_vprintf(jx9_context *pCtx, int nArg, jx9_value **apArg)
4069{
4070 jx9_int64 nCounter = 0;
4071 const char *zFormat;
4072 jx9_hashmap *pMap;
4073 SySet sArg;
4074 int nLen, n;
4075 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){
4076 /* Missing/Invalid arguments, return 0 */
4077 jx9_result_int(pCtx, 0);
4078 return JX9_OK;
4079 }
4080 /* Extract the string format */
4081 zFormat = jx9_value_to_string(apArg[0], &nLen);
4082 if( nLen < 1 ){
4083 /* Empty string */
4084 jx9_result_int(pCtx, 0);
4085 return JX9_OK;
4086 }
4087 /* Point to the hashmap */
4088 pMap = (jx9_hashmap *)apArg[1]->x.pOther;
4089 /* Extract arguments from the hashmap */
4090 n = jx9HashmapValuesToSet(pMap, &sArg);
4091 /* Format the string */
4092 jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&nCounter, TRUE);
4093 /* Return the length of the outputted string */
4094 jx9_result_int64(pCtx, nCounter);
4095 /* Release the container */
4096 SySetRelease(&sArg);
4097 return JX9_OK;
4098}
4099/*
4100 * int vsprintf(string $format, array $args)
4101 * Output a formatted string.
4102 * Parameters
4103 * $format
4104 * See sprintf() for a description of format.
4105 * Return
4106 * A string produced according to the formatting string format.
4107 */
4108static int jx9Builtin_vsprintf(jx9_context *pCtx, int nArg, jx9_value **apArg)
4109{
4110 const char *zFormat;
4111 jx9_hashmap *pMap;
4112 SySet sArg;
4113 int nLen, n;
4114 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){
4115 /* Missing/Invalid arguments, return the empty string */
4116 jx9_result_string(pCtx, "", 0);
4117 return JX9_OK;
4118 }
4119 /* Extract the string format */
4120 zFormat = jx9_value_to_string(apArg[0], &nLen);
4121 if( nLen < 1 ){
4122 /* Empty string */
4123 jx9_result_string(pCtx, "", 0);
4124 return JX9_OK;
4125 }
4126 /* Point to hashmap */
4127 pMap = (jx9_hashmap *)apArg[1]->x.pOther;
4128 /* Extract arguments from the hashmap */
4129 n = jx9HashmapValuesToSet(pMap, &sArg);
4130 /* Format the string */
4131 jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), 0, TRUE);
4132 /* Release the container */
4133 SySetRelease(&sArg);
4134 return JX9_OK;
4135}
4136/*
4137 * string size_format(int64 $size)
4138 * Return a smart string represenation of the given size [i.e: 64-bit integer]
4139 * Example:
4140 * print size_format(1*1024*1024*1024);// 1GB
4141 * print size_format(512*1024*1024); // 512 MB
4142 * print size_format(file_size(/path/to/my/file_8192)); //8KB
4143 * Parameter
4144 * $size
4145 * Entity size in bytes.
4146 * Return
4147 * Formatted string representation of the given size.
4148 */
4149static int jx9Builtin_size_format(jx9_context *pCtx, int nArg, jx9_value **apArg)
4150{
4151 /*Kilo*/ /*Mega*/ /*Giga*/ /*Tera*/ /*Peta*/ /*Exa*/ /*Zeta*/
4152 static const char zUnit[] = {"KMGTPEZ"};
4153 sxi32 nRest, i_32;
4154 jx9_int64 iSize;
4155 int c = -1; /* index in zUnit[] */
4156
4157 if( nArg < 1 ){
4158 /* Missing argument, return the empty string */
4159 jx9_result_string(pCtx, "", 0);
4160 return JX9_OK;
4161 }
4162 /* Extract the given size */
4163 iSize = jx9_value_to_int64(apArg[0]);
4164 if( iSize < 100 /* Bytes */ ){
4165 /* Don't bother formatting, return immediately */
4166 jx9_result_string(pCtx, "0.1 KB", (int)sizeof("0.1 KB")-1);
4167 return JX9_OK;
4168 }
4169 for(;;){
4170 nRest = (sxi32)(iSize & 0x3FF);
4171 iSize >>= 10;
4172 c++;
4173 if( (iSize & (~0 ^ 1023)) == 0 ){
4174 break;
4175 }
4176 }
4177 nRest /= 100;
4178 if( nRest > 9 ){
4179 nRest = 9;
4180 }
4181 if( iSize > 999 ){
4182 c++;
4183 nRest = 9;
4184 iSize = 0;
4185 }
4186 i_32 = (sxi32)iSize;
4187 /* Format */
4188 jx9_result_string_format(pCtx, "%d.%d %cB", i_32, nRest, zUnit[c]);
4189 return JX9_OK;
4190}
4191#if !defined(JX9_DISABLE_HASH_FUNC)
4192/*
4193 * string md5(string $str[, bool $raw_output = false])
4194 * Calculate the md5 hash of a string.
4195 * Parameter
4196 * $str
4197 * Input string
4198 * $raw_output
4199 * If the optional raw_output is set to TRUE, then the md5 digest
4200 * is instead returned in raw binary format with a length of 16.
4201 * Return
4202 * MD5 Hash as a 32-character hexadecimal string.
4203 */
4204static int jx9Builtin_md5(jx9_context *pCtx, int nArg, jx9_value **apArg)
4205{
4206 unsigned char zDigest[16];
4207 int raw_output = FALSE;
4208 const void *pIn;
4209 int nLen;
4210 if( nArg < 1 ){
4211 /* Missing arguments, return the empty string */
4212 jx9_result_string(pCtx, "", 0);
4213 return JX9_OK;
4214 }
4215 /* Extract the input string */
4216 pIn = (const void *)jx9_value_to_string(apArg[0], &nLen);
4217 if( nLen < 1 ){
4218 /* Empty string */
4219 jx9_result_string(pCtx, "", 0);
4220 return JX9_OK;
4221 }
4222 if( nArg > 1 && jx9_value_is_bool(apArg[1])){
4223 raw_output = jx9_value_to_bool(apArg[1]);
4224 }
4225 /* Compute the MD5 digest */
4226 SyMD5Compute(pIn, (sxu32)nLen, zDigest);
4227 if( raw_output ){
4228 /* Output raw digest */
4229 jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest));
4230 }else{
4231 /* Perform a binary to hex conversion */
4232 SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx);
4233 }
4234 return JX9_OK;
4235}
4236/*
4237 * string sha1(string $str[, bool $raw_output = false])
4238 * Calculate the sha1 hash of a string.
4239 * Parameter
4240 * $str
4241 * Input string
4242 * $raw_output
4243 * If the optional raw_output is set to TRUE, then the md5 digest
4244 * is instead returned in raw binary format with a length of 16.
4245 * Return
4246 * SHA1 Hash as a 40-character hexadecimal string.
4247 */
4248static int jx9Builtin_sha1(jx9_context *pCtx, int nArg, jx9_value **apArg)
4249{
4250 unsigned char zDigest[20];
4251 int raw_output = FALSE;
4252 const void *pIn;
4253 int nLen;
4254 if( nArg < 1 ){
4255 /* Missing arguments, return the empty string */
4256 jx9_result_string(pCtx, "", 0);
4257 return JX9_OK;
4258 }
4259 /* Extract the input string */
4260 pIn = (const void *)jx9_value_to_string(apArg[0], &nLen);
4261 if( nLen < 1 ){
4262 /* Empty string */
4263 jx9_result_string(pCtx, "", 0);
4264 return JX9_OK;
4265 }
4266 if( nArg > 1 && jx9_value_is_bool(apArg[1])){
4267 raw_output = jx9_value_to_bool(apArg[1]);
4268 }
4269 /* Compute the SHA1 digest */
4270 SySha1Compute(pIn, (sxu32)nLen, zDigest);
4271 if( raw_output ){
4272 /* Output raw digest */
4273 jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest));
4274 }else{
4275 /* Perform a binary to hex conversion */
4276 SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx);
4277 }
4278 return JX9_OK;
4279}
4280/*
4281 * int64 crc32(string $str)
4282 * Calculates the crc32 polynomial of a strin.
4283 * Parameter
4284 * $str
4285 * Input string
4286 * Return
4287 * CRC32 checksum of the given input (64-bit integer).
4288 */
4289static int jx9Builtin_crc32(jx9_context *pCtx, int nArg, jx9_value **apArg)
4290{
4291 const void *pIn;
4292 sxu32 nCRC;
4293 int nLen;
4294 if( nArg < 1 ){
4295 /* Missing arguments, return 0 */
4296 jx9_result_int(pCtx, 0);
4297 return JX9_OK;
4298 }
4299 /* Extract the input string */
4300 pIn = (const void *)jx9_value_to_string(apArg[0], &nLen);
4301 if( nLen < 1 ){
4302 /* Empty string */
4303 jx9_result_int(pCtx, 0);
4304 return JX9_OK;
4305 }
4306 /* Calculate the sum */
4307 nCRC = SyCrc32(pIn, (sxu32)nLen);
4308 /* Return the CRC32 as 64-bit integer */
4309 jx9_result_int64(pCtx, (jx9_int64)nCRC^ 0xFFFFFFFF);
4310 return JX9_OK;
4311}
4312#endif /* JX9_DISABLE_HASH_FUNC */
4313/*
4314 * Parse a CSV string and invoke the supplied callback for each processed xhunk.
4315 */
4316JX9_PRIVATE sxi32 jx9ProcessCsv(
4317 const char *zInput, /* Raw input */
4318 int nByte, /* Input length */
4319 int delim, /* Delimiter */
4320 int encl, /* Enclosure */
4321 int escape, /* Escape character */
4322 sxi32 (*xConsumer)(const char *, int, void *), /* User callback */
4323 void *pUserData /* Last argument to xConsumer() */
4324 )
4325{
4326 const char *zEnd = &zInput[nByte];
4327 const char *zIn = zInput;
4328 const char *zPtr;
4329 int isEnc;
4330 /* Start processing */
4331 for(;;){
4332 if( zIn >= zEnd ){
4333 /* No more input to process */
4334 break;
4335 }
4336 isEnc = 0;
4337 zPtr = zIn;
4338 /* Find the first delimiter */
4339 while( zIn < zEnd ){
4340 if( zIn[0] == delim && !isEnc){
4341 /* Delimiter found, break imediately */
4342 break;
4343 }else if( zIn[0] == encl ){
4344 /* Inside enclosure? */
4345 isEnc = !isEnc;
4346 }else if( zIn[0] == escape ){
4347 /* Escape sequence */
4348 zIn++;
4349 }
4350 /* Advance the cursor */
4351 zIn++;
4352 }
4353 if( zIn > zPtr ){
4354 int nByte = (int)(zIn-zPtr);
4355 sxi32 rc;
4356 /* Invoke the supllied callback */
4357 if( zPtr[0] == encl ){
4358 zPtr++;
4359 nByte-=2;
4360 }
4361 if( nByte > 0 ){
4362 rc = xConsumer(zPtr, nByte, pUserData);
4363 if( rc == SXERR_ABORT ){
4364 /* User callback request an operation abort */
4365 break;
4366 }
4367 }
4368 }
4369 /* Ignore trailing delimiter */
4370 while( zIn < zEnd && zIn[0] == delim ){
4371 zIn++;
4372 }
4373 }
4374 return SXRET_OK;
4375}
4376/*
4377 * Default consumer callback for the CSV parsing routine defined above.
4378 * All the processed input is insereted into an array passed as the last
4379 * argument to this callback.
4380 */
4381JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData)
4382{
4383 jx9_value *pArray = (jx9_value *)pUserData;
4384 jx9_value sEntry;
4385 SyString sToken;
4386 /* Insert the token in the given array */
4387 SyStringInitFromBuf(&sToken, zToken, nTokenLen);
4388 /* Remove trailing and leading white spcaces and null bytes */
4389 SyStringFullTrimSafe(&sToken);
4390 if( sToken.nByte < 1){
4391 return SXRET_OK;
4392 }
4393 jx9MemObjInitFromString(pArray->pVm, &sEntry, &sToken);
4394 jx9_array_add_elem(pArray, 0, &sEntry);
4395 jx9MemObjRelease(&sEntry);
4396 return SXRET_OK;
4397}
4398/*
4399 * array str_getcsv(string $input[, string $delimiter = ', '[, string $enclosure = '"' [, string $escape='\\']]])
4400 * Parse a CSV string into an array.
4401 * Parameters
4402 * $input
4403 * The string to parse.
4404 * $delimiter
4405 * Set the field delimiter (one character only).
4406 * $enclosure
4407 * Set the field enclosure character (one character only).
4408 * $escape
4409 * Set the escape character (one character only). Defaults as a backslash (\)
4410 * Return
4411 * An indexed array containing the CSV fields or NULL on failure.
4412 */
4413static int jx9Builtin_str_getcsv(jx9_context *pCtx, int nArg, jx9_value **apArg)
4414{
4415 const char *zInput, *zPtr;
4416 jx9_value *pArray;
4417 int delim = ','; /* Delimiter */
4418 int encl = '"' ; /* Enclosure */
4419 int escape = '\\'; /* Escape character */
4420 int nLen;
4421 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
4422 /* Missing/Invalid arguments, return NULL */
4423 jx9_result_null(pCtx);
4424 return JX9_OK;
4425 }
4426 /* Extract the raw input */
4427 zInput = jx9_value_to_string(apArg[0], &nLen);
4428 if( nArg > 1 ){
4429 int i;
4430 if( jx9_value_is_string(apArg[1]) ){
4431 /* Extract the delimiter */
4432 zPtr = jx9_value_to_string(apArg[1], &i);
4433 if( i > 0 ){
4434 delim = zPtr[0];
4435 }
4436 }
4437 if( nArg > 2 ){
4438 if( jx9_value_is_string(apArg[2]) ){
4439 /* Extract the enclosure */
4440 zPtr = jx9_value_to_string(apArg[2], &i);
4441 if( i > 0 ){
4442 encl = zPtr[0];
4443 }
4444 }
4445 if( nArg > 3 ){
4446 if( jx9_value_is_string(apArg[3]) ){
4447 /* Extract the escape character */
4448 zPtr = jx9_value_to_string(apArg[3], &i);
4449 if( i > 0 ){
4450 escape = zPtr[0];
4451 }
4452 }
4453 }
4454 }
4455 }
4456 /* Create our array */
4457 pArray = jx9_context_new_array(pCtx);
4458 if( pArray == 0 ){
4459 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
4460 jx9_result_null(pCtx);
4461 return JX9_OK;
4462 }
4463 /* Parse the raw input */
4464 jx9ProcessCsv(zInput, nLen, delim, encl, escape, jx9CsvConsumer, pArray);
4465 /* Return the freshly created array */
4466 jx9_result_value(pCtx, pArray);
4467 return JX9_OK;
4468}
4469/*
4470 * Extract a tag name from a raw HTML input and insert it in the given
4471 * container.
4472 * Refer to [strip_tags()].
4473 */
4474static sxi32 AddTag(SySet *pSet, const char *zTag, int nByte)
4475{
4476 const char *zEnd = &zTag[nByte];
4477 const char *zPtr;
4478 SyString sEntry;
4479 /* Strip tags */
4480 for(;;){
4481 while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?'
4482 || zTag[0] == '!' || zTag[0] == '-' || ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){
4483 zTag++;
4484 }
4485 if( zTag >= zEnd ){
4486 break;
4487 }
4488 zPtr = zTag;
4489 /* Delimit the tag */
4490 while(zTag < zEnd ){
4491 if( (unsigned char)zTag[0] >= 0xc0 ){
4492 /* UTF-8 stream */
4493 zTag++;
4494 SX_JMP_UTF8(zTag, zEnd);
4495 }else if( !SyisAlphaNum(zTag[0]) ){
4496 break;
4497 }else{
4498 zTag++;
4499 }
4500 }
4501 if( zTag > zPtr ){
4502 /* Perform the insertion */
4503 SyStringInitFromBuf(&sEntry, zPtr, (int)(zTag-zPtr));
4504 SyStringFullTrim(&sEntry);
4505 SySetPut(pSet, (const void *)&sEntry);
4506 }
4507 /* Jump the trailing '>' */
4508 zTag++;
4509 }
4510 return SXRET_OK;
4511}
4512/*
4513 * Check if the given HTML tag name is present in the given container.
4514 * Return SXRET_OK if present.SXERR_NOTFOUND otherwise.
4515 * Refer to [strip_tags()].
4516 */
4517static sxi32 FindTag(SySet *pSet, const char *zTag, int nByte)
4518{
4519 if( SySetUsed(pSet) > 0 ){
4520 const char *zCur, *zEnd = &zTag[nByte];
4521 SyString sTag;
4522 while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' ||
4523 ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){
4524 zTag++;
4525 }
4526 /* Delimit the tag */
4527 zCur = zTag;
4528 while(zTag < zEnd ){
4529 if( (unsigned char)zTag[0] >= 0xc0 ){
4530 /* UTF-8 stream */
4531 zTag++;
4532 SX_JMP_UTF8(zTag, zEnd);
4533 }else if( !SyisAlphaNum(zTag[0]) ){
4534 break;
4535 }else{
4536 zTag++;
4537 }
4538 }
4539 SyStringInitFromBuf(&sTag, zCur, zTag-zCur);
4540 /* Trim leading white spaces and null bytes */
4541 SyStringLeftTrimSafe(&sTag);
4542 if( sTag.nByte > 0 ){
4543 SyString *aEntry, *pEntry;
4544 sxi32 rc;
4545 sxu32 n;
4546 /* Perform the lookup */
4547 aEntry = (SyString *)SySetBasePtr(pSet);
4548 for( n = 0 ; n < SySetUsed(pSet) ; ++n ){
4549 pEntry = &aEntry[n];
4550 /* Do the comparison */
4551 rc = SyStringCmp(pEntry, &sTag, SyStrnicmp);
4552 if( !rc ){
4553 return SXRET_OK;
4554 }
4555 }
4556 }
4557 }
4558 /* No such tag */
4559 return SXERR_NOTFOUND;
4560}
4561/*
4562 * This function tries to return a string [i.e: in the call context result buffer]
4563 * with all NUL bytes, HTML and JX9 tags stripped from a given string.
4564 * Refer to [strip_tags()].
4565 */
4566JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen)
4567{
4568 const char *zEnd = &zIn[nByte];
4569 const char *zPtr, *zTag;
4570 SySet sSet;
4571 /* initialize the set of allowed tags */
4572 SySetInit(&sSet, &pCtx->pVm->sAllocator, sizeof(SyString));
4573 if( nTaglen > 0 ){
4574 /* Set of allowed tags */
4575 AddTag(&sSet, zTaglist, nTaglen);
4576 }
4577 /* Set the empty string */
4578 jx9_result_string(pCtx, "", 0);
4579 /* Start processing */
4580 for(;;){
4581 if(zIn >= zEnd){
4582 /* No more input to process */
4583 break;
4584 }
4585 zPtr = zIn;
4586 /* Find a tag */
4587 while( zIn < zEnd && zIn[0] != '<' && zIn[0] != 0 /* NUL byte */ ){
4588 zIn++;
4589 }
4590 if( zIn > zPtr ){
4591 /* Consume raw input */
4592 jx9_result_string(pCtx, zPtr, (int)(zIn-zPtr));
4593 }
4594 /* Ignore trailing null bytes */
4595 while( zIn < zEnd && zIn[0] == 0 ){
4596 zIn++;
4597 }
4598 if(zIn >= zEnd){
4599 /* No more input to process */
4600 break;
4601 }
4602 if( zIn[0] == '<' ){
4603 sxi32 rc;
4604 zTag = zIn++;
4605 /* Delimit the tag */
4606 while( zIn < zEnd && zIn[0] != '>' ){
4607 zIn++;
4608 }
4609 if( zIn < zEnd ){
4610 zIn++; /* Ignore the trailing closing tag */
4611 }
4612 /* Query the set */
4613 rc = FindTag(&sSet, zTag, (int)(zIn-zTag));
4614 if( rc == SXRET_OK ){
4615 /* Keep the tag */
4616 jx9_result_string(pCtx, zTag, (int)(zIn-zTag));
4617 }
4618 }
4619 }
4620 /* Cleanup */
4621 SySetRelease(&sSet);
4622 return SXRET_OK;
4623}
4624/*
4625 * string strip_tags(string $str[, string $allowable_tags])
4626 * Strip HTML and JX9 tags from a string.
4627 * Parameters
4628 * $str
4629 * The input string.
4630 * $allowable_tags
4631 * You can use the optional second parameter to specify tags which should not be stripped.
4632 * Return
4633 * Returns the stripped string.
4634 */
4635static int jx9Builtin_strip_tags(jx9_context *pCtx, int nArg, jx9_value **apArg)
4636{
4637 const char *zTaglist = 0;
4638 const char *zString;
4639 int nTaglen = 0;
4640 int nLen;
4641 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
4642 /* Missing/Invalid arguments, return the empty string */
4643 jx9_result_string(pCtx, "", 0);
4644 return JX9_OK;
4645 }
4646 /* Point to the raw string */
4647 zString = jx9_value_to_string(apArg[0], &nLen);
4648 if( nArg > 1 && jx9_value_is_string(apArg[1]) ){
4649 /* Allowed tag */
4650 zTaglist = jx9_value_to_string(apArg[1], &nTaglen);
4651 }
4652 /* Process input */
4653 jx9StripTagsFromString(pCtx, zString, nLen, zTaglist, nTaglen);
4654 return JX9_OK;
4655}
4656/*
4657 * array str_split(string $string[, int $split_length = 1 ])
4658 * Convert a string to an array.
4659 * Parameters
4660 * $str
4661 * The input string.
4662 * $split_length
4663 * Maximum length of the chunk.
4664 * Return
4665 * If the optional split_length parameter is specified, the returned array
4666 * will be broken down into chunks with each being split_length in length, otherwise
4667 * each chunk will be one character in length. FALSE is returned if split_length is less than 1.
4668 * If the split_length length exceeds the length of string, the entire string is returned
4669 * as the first (and only) array element.
4670 */
4671static int jx9Builtin_str_split(jx9_context *pCtx, int nArg, jx9_value **apArg)
4672{
4673 const char *zString, *zEnd;
4674 jx9_value *pArray, *pValue;
4675 int split_len;
4676 int nLen;
4677 if( nArg < 1 ){
4678 /* Missing arguments, return FALSE */
4679 jx9_result_bool(pCtx, 0);
4680 return JX9_OK;
4681 }
4682 /* Point to the target string */
4683 zString = jx9_value_to_string(apArg[0], &nLen);
4684 if( nLen < 1 ){
4685 /* Nothing to process, return FALSE */
4686 jx9_result_bool(pCtx, 0);
4687 return JX9_OK;
4688 }
4689 split_len = (int)sizeof(char);
4690 if( nArg > 1 ){
4691 /* Split length */
4692 split_len = jx9_value_to_int(apArg[1]);
4693 if( split_len < 1 ){
4694 /* Invalid length, return FALSE */
4695 jx9_result_bool(pCtx, 0);
4696 return JX9_OK;
4697 }
4698 if( split_len > nLen ){
4699 split_len = nLen;
4700 }
4701 }
4702 /* Create the array and the scalar value */
4703 pArray = jx9_context_new_array(pCtx);
4704 /*Chunk value */
4705 pValue = jx9_context_new_scalar(pCtx);
4706 if( pValue == 0 || pArray == 0 ){
4707 /* Return FALSE */
4708 jx9_result_bool(pCtx, 0);
4709 return JX9_OK;
4710 }
4711 /* Point to the end of the string */
4712 zEnd = &zString[nLen];
4713 /* Perform the requested operation */
4714 for(;;){
4715 int nMax;
4716 if( zString >= zEnd ){
4717 /* No more input to process */
4718 break;
4719 }
4720 nMax = (int)(zEnd-zString);
4721 if( nMax < split_len ){
4722 split_len = nMax;
4723 }
4724 /* Copy the current chunk */
4725 jx9_value_string(pValue, zString, split_len);
4726 /* Insert it */
4727 jx9_array_add_elem(pArray, 0, pValue); /* Will make it's own copy */
4728 /* reset the string cursor */
4729 jx9_value_reset_string_cursor(pValue);
4730 /* Update position */
4731 zString += split_len;
4732 }
4733 /*
4734 * Return the array.
4735 * Don't worry about freeing memory, everything will be automatically released
4736 * upon we return from this function.
4737 */
4738 jx9_result_value(pCtx, pArray);
4739 return JX9_OK;
4740}
4741/*
4742 * Tokenize a raw string and extract the first non-space token.
4743 * Refer to [strspn()].
4744 */
4745static sxi32 ExtractNonSpaceToken(const char **pzIn, const char *zEnd, SyString *pOut)
4746{
4747 const char *zIn = *pzIn;
4748 const char *zPtr;
4749 /* Ignore leading white spaces */
4750 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
4751 zIn++;
4752 }
4753 if( zIn >= zEnd ){
4754 /* End of input */
4755 return SXERR_EOF;
4756 }
4757 zPtr = zIn;
4758 /* Extract the token */
4759 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && !SyisSpace(zIn[0]) ){
4760 zIn++;
4761 }
4762 SyStringInitFromBuf(pOut, zPtr, zIn-zPtr);
4763 /* Synchronize pointers */
4764 *pzIn = zIn;
4765 /* Return to the caller */
4766 return SXRET_OK;
4767}
4768/*
4769 * Check if the given string contains only characters from the given mask.
4770 * return the longest match.
4771 * Refer to [strspn()].
4772 */
4773static int LongestStringMask(const char *zString, int nLen, const char *zMask, int nMaskLen)
4774{
4775 const char *zEnd = &zString[nLen];
4776 const char *zIn = zString;
4777 int i, c;
4778 for(;;){
4779 if( zString >= zEnd ){
4780 break;
4781 }
4782 /* Extract current character */
4783 c = zString[0];
4784 /* Perform the lookup */
4785 for( i = 0 ; i < nMaskLen ; i++ ){
4786 if( c == zMask[i] ){
4787 /* Character found */
4788 break;
4789 }
4790 }
4791 if( i >= nMaskLen ){
4792 /* Character not in the current mask, break immediately */
4793 break;
4794 }
4795 /* Advance cursor */
4796 zString++;
4797 }
4798 /* Longest match */
4799 return (int)(zString-zIn);
4800}
4801/*
4802 * Do the reverse operation of the previous function [i.e: LongestStringMask()].
4803 * Refer to [strcspn()].
4804 */
4805static int LongestStringMask2(const char *zString, int nLen, const char *zMask, int nMaskLen)
4806{
4807 const char *zEnd = &zString[nLen];
4808 const char *zIn = zString;
4809 int i, c;
4810 for(;;){
4811 if( zString >= zEnd ){
4812 break;
4813 }
4814 /* Extract current character */
4815 c = zString[0];
4816 /* Perform the lookup */
4817 for( i = 0 ; i < nMaskLen ; i++ ){
4818 if( c == zMask[i] ){
4819 break;
4820 }
4821 }
4822 if( i < nMaskLen ){
4823 /* Character in the current mask, break immediately */
4824 break;
4825 }
4826 /* Advance cursor */
4827 zString++;
4828 }
4829 /* Longest match */
4830 return (int)(zString-zIn);
4831}
4832/*
4833 * int strspn(string $str, string $mask[, int $start[, int $length]])
4834 * Finds the length of the initial segment of a string consisting entirely
4835 * of characters contained within a given mask.
4836 * Parameters
4837 * $str
4838 * The input string.
4839 * $mask
4840 * The list of allowable characters.
4841 * $start
4842 * The position in subject to start searching.
4843 * If start is given and is non-negative, then strspn() will begin examining
4844 * subject at the start'th position. For instance, in the string 'abcdef', the character
4845 * at position 0 is 'a', the character at position 2 is 'c', and so forth.
4846 * If start is given and is negative, then strspn() will begin examining subject at the
4847 * start'th position from the end of subject.
4848 * $length
4849 * The length of the segment from subject to examine.
4850 * If length is given and is non-negative, then subject will be examined for length
4851 * characters after the starting position.
4852 * If lengthis given and is negative, then subject will be examined from the starting
4853 * position up to length characters from the end of subject.
4854 * Return
4855 * Returns the length of the initial segment of subject which consists entirely of characters
4856 * in mask.
4857 */
4858static int jx9Builtin_strspn(jx9_context *pCtx, int nArg, jx9_value **apArg)
4859{
4860 const char *zString, *zMask, *zEnd;
4861 int iMasklen, iLen;
4862 SyString sToken;
4863 int iCount = 0;
4864 int rc;
4865 if( nArg < 2 ){
4866 /* Missing agruments, return zero */
4867 jx9_result_int(pCtx, 0);
4868 return JX9_OK;
4869 }
4870 /* Extract the target string */
4871 zString = jx9_value_to_string(apArg[0], &iLen);
4872 /* Extract the mask */
4873 zMask = jx9_value_to_string(apArg[1], &iMasklen);
4874 if( iLen < 1 || iMasklen < 1 ){
4875 /* Nothing to process, return zero */
4876 jx9_result_int(pCtx, 0);
4877 return JX9_OK;
4878 }
4879 if( nArg > 2 ){
4880 int nOfft;
4881 /* Extract the offset */
4882 nOfft = jx9_value_to_int(apArg[2]);
4883 if( nOfft < 0 ){
4884 const char *zBase = &zString[iLen + nOfft];
4885 if( zBase > zString ){
4886 iLen = (int)(&zString[iLen]-zBase);
4887 zString = zBase;
4888 }else{
4889 /* Invalid offset */
4890 jx9_result_int(pCtx, 0);
4891 return JX9_OK;
4892 }
4893 }else{
4894 if( nOfft >= iLen ){
4895 /* Invalid offset */
4896 jx9_result_int(pCtx, 0);
4897 return JX9_OK;
4898 }else{
4899 /* Update offset */
4900 zString += nOfft;
4901 iLen -= nOfft;
4902 }
4903 }
4904 if( nArg > 3 ){
4905 int iUserlen;
4906 /* Extract the desired length */
4907 iUserlen = jx9_value_to_int(apArg[3]);
4908 if( iUserlen > 0 && iUserlen < iLen ){
4909 iLen = iUserlen;
4910 }
4911 }
4912 }
4913 /* Point to the end of the string */
4914 zEnd = &zString[iLen];
4915 /* Extract the first non-space token */
4916 rc = ExtractNonSpaceToken(&zString, zEnd, &sToken);
4917 if( rc == SXRET_OK && sToken.nByte > 0 ){
4918 /* Compare against the current mask */
4919 iCount = LongestStringMask(sToken.zString, (int)sToken.nByte, zMask, iMasklen);
4920 }
4921 /* Longest match */
4922 jx9_result_int(pCtx, iCount);
4923 return JX9_OK;
4924}
4925/*
4926 * int strcspn(string $str, string $mask[, int $start[, int $length]])
4927 * Find length of initial segment not matching mask.
4928 * Parameters
4929 * $str
4930 * The input string.
4931 * $mask
4932 * The list of not allowed characters.
4933 * $start
4934 * The position in subject to start searching.
4935 * If start is given and is non-negative, then strspn() will begin examining
4936 * subject at the start'th position. For instance, in the string 'abcdef', the character
4937 * at position 0 is 'a', the character at position 2 is 'c', and so forth.
4938 * If start is given and is negative, then strspn() will begin examining subject at the
4939 * start'th position from the end of subject.
4940 * $length
4941 * The length of the segment from subject to examine.
4942 * If length is given and is non-negative, then subject will be examined for length
4943 * characters after the starting position.
4944 * If lengthis given and is negative, then subject will be examined from the starting
4945 * position up to length characters from the end of subject.
4946 * Return
4947 * Returns the length of the segment as an integer.
4948 */
4949static int jx9Builtin_strcspn(jx9_context *pCtx, int nArg, jx9_value **apArg)
4950{
4951 const char *zString, *zMask, *zEnd;
4952 int iMasklen, iLen;
4953 SyString sToken;
4954 int iCount = 0;
4955 int rc;
4956 if( nArg < 2 ){
4957 /* Missing agruments, return zero */
4958 jx9_result_int(pCtx, 0);
4959 return JX9_OK;
4960 }
4961 /* Extract the target string */
4962 zString = jx9_value_to_string(apArg[0], &iLen);
4963 /* Extract the mask */
4964 zMask = jx9_value_to_string(apArg[1], &iMasklen);
4965 if( iLen < 1 ){
4966 /* Nothing to process, return zero */
4967 jx9_result_int(pCtx, 0);
4968 return JX9_OK;
4969 }
4970 if( iMasklen < 1 ){
4971 /* No given mask, return the string length */
4972 jx9_result_int(pCtx, iLen);
4973 return JX9_OK;
4974 }
4975 if( nArg > 2 ){
4976 int nOfft;
4977 /* Extract the offset */
4978 nOfft = jx9_value_to_int(apArg[2]);
4979 if( nOfft < 0 ){
4980 const char *zBase = &zString[iLen + nOfft];
4981 if( zBase > zString ){
4982 iLen = (int)(&zString[iLen]-zBase);
4983 zString = zBase;
4984 }else{
4985 /* Invalid offset */
4986 jx9_result_int(pCtx, 0);
4987 return JX9_OK;
4988 }
4989 }else{
4990 if( nOfft >= iLen ){
4991 /* Invalid offset */
4992 jx9_result_int(pCtx, 0);
4993 return JX9_OK;
4994 }else{
4995 /* Update offset */
4996 zString += nOfft;
4997 iLen -= nOfft;
4998 }
4999 }
5000 if( nArg > 3 ){
5001 int iUserlen;
5002 /* Extract the desired length */
5003 iUserlen = jx9_value_to_int(apArg[3]);
5004 if( iUserlen > 0 && iUserlen < iLen ){
5005 iLen = iUserlen;
5006 }
5007 }
5008 }
5009 /* Point to the end of the string */
5010 zEnd = &zString[iLen];
5011 /* Extract the first non-space token */
5012 rc = ExtractNonSpaceToken(&zString, zEnd, &sToken);
5013 if( rc == SXRET_OK && sToken.nByte > 0 ){
5014 /* Compare against the current mask */
5015 iCount = LongestStringMask2(sToken.zString, (int)sToken.nByte, zMask, iMasklen);
5016 }
5017 /* Longest match */
5018 jx9_result_int(pCtx, iCount);
5019 return JX9_OK;
5020}
5021/*
5022 * string strpbrk(string $haystack, string $char_list)
5023 * Search a string for any of a set of characters.
5024 * Parameters
5025 * $haystack
5026 * The string where char_list is looked for.
5027 * $char_list
5028 * This parameter is case sensitive.
5029 * Return
5030 * Returns a string starting from the character found, or FALSE if it is not found.
5031 */
5032static int jx9Builtin_strpbrk(jx9_context *pCtx, int nArg, jx9_value **apArg)
5033{
5034 const char *zString, *zList, *zEnd;
5035 int iLen, iListLen, i, c;
5036 sxu32 nOfft, nMax;
5037 sxi32 rc;
5038 if( nArg < 2 ){
5039 /* Missing arguments, return FALSE */
5040 jx9_result_bool(pCtx, 0);
5041 return JX9_OK;
5042 }
5043 /* Extract the haystack and the char list */
5044 zString = jx9_value_to_string(apArg[0], &iLen);
5045 zList = jx9_value_to_string(apArg[1], &iListLen);
5046 if( iLen < 1 ){
5047 /* Nothing to process, return FALSE */
5048 jx9_result_bool(pCtx, 0);
5049 return JX9_OK;
5050 }
5051 /* Point to the end of the string */
5052 zEnd = &zString[iLen];
5053 nOfft = nMax = SXU32_HIGH;
5054 /* perform the requested operation */
5055 for( i = 0 ; i < iListLen ; i++ ){
5056 c = zList[i];
5057 rc = SyByteFind(zString, (sxu32)iLen, c, &nMax);
5058 if( rc == SXRET_OK ){
5059 if( nMax < nOfft ){
5060 nOfft = nMax;
5061 }
5062 }
5063 }
5064 if( nOfft == SXU32_HIGH ){
5065 /* No such substring, return FALSE */
5066 jx9_result_bool(pCtx, 0);
5067 }else{
5068 /* Return the substring */
5069 jx9_result_string(pCtx, &zString[nOfft], (int)(zEnd-&zString[nOfft]));
5070 }
5071 return JX9_OK;
5072}
5073/*
5074 * string soundex(string $str)
5075 * Calculate the soundex key of a string.
5076 * Parameters
5077 * $str
5078 * The input string.
5079 * Return
5080 * Returns the soundex key as a string.
5081 * Note:
5082 * This implementation is based on the one found in the SQLite3
5083 * source tree.
5084 */
5085static int jx9Builtin_soundex(jx9_context *pCtx, int nArg, jx9_value **apArg)
5086{
5087 const unsigned char *zIn;
5088 char zResult[8];
5089 int i, j;
5090 static const unsigned char iCode[] = {
5091 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5092 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5093 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5094 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
5095 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
5096 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
5097 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
5098 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
5099 };
5100 if( nArg < 1 ){
5101 /* Missing arguments, return the empty string */
5102 jx9_result_string(pCtx, "", 0);
5103 return JX9_OK;
5104 }
5105 zIn = (unsigned char *)jx9_value_to_string(apArg[0], 0);
5106 for(i=0; zIn[i] && zIn[i] < 0xc0 && !SyisAlpha(zIn[i]); i++){}
5107 if( zIn[i] ){
5108 unsigned char prevcode = iCode[zIn[i]&0x7f];
5109 zResult[0] = (char)SyToUpper(zIn[i]);
5110 for(j=1; j<4 && zIn[i]; i++){
5111 int code = iCode[zIn[i]&0x7f];
5112 if( code>0 ){
5113 if( code!=prevcode ){
5114 prevcode = (unsigned char)code;
5115 zResult[j++] = (char)code + '0';
5116 }
5117 }else{
5118 prevcode = 0;
5119 }
5120 }
5121 while( j<4 ){
5122 zResult[j++] = '0';
5123 }
5124 jx9_result_string(pCtx, zResult, 4);
5125 }else{
5126 jx9_result_string(pCtx, "?000", 4);
5127 }
5128 return JX9_OK;
5129}
5130/*
5131 * string wordwrap(string $str[, int $width = 75[, string $break = "\n"]])
5132 * Wraps a string to a given number of characters.
5133 * Parameters
5134 * $str
5135 * The input string.
5136 * $width
5137 * The column width.
5138 * $break
5139 * The line is broken using the optional break parameter.
5140 * Return
5141 * Returns the given string wrapped at the specified column.
5142 */
5143static int jx9Builtin_wordwrap(jx9_context *pCtx, int nArg, jx9_value **apArg)
5144{
5145 const char *zIn, *zEnd, *zBreak;
5146 int iLen, iBreaklen, iChunk;
5147 if( nArg < 1 ){
5148 /* Missing arguments, return the empty string */
5149 jx9_result_string(pCtx, "", 0);
5150 return JX9_OK;
5151 }
5152 /* Extract the input string */
5153 zIn = jx9_value_to_string(apArg[0], &iLen);
5154 if( iLen < 1 ){
5155 /* Nothing to process, return the empty string */
5156 jx9_result_string(pCtx, "", 0);
5157 return JX9_OK;
5158 }
5159 /* Chunk length */
5160 iChunk = 75;
5161 iBreaklen = 0;
5162 zBreak = ""; /* cc warning */
5163 if( nArg > 1 ){
5164 iChunk = jx9_value_to_int(apArg[1]);
5165 if( iChunk < 1 ){
5166 iChunk = 75;
5167 }
5168 if( nArg > 2 ){
5169 zBreak = jx9_value_to_string(apArg[2], &iBreaklen);
5170 }
5171 }
5172 if( iBreaklen < 1 ){
5173 /* Set a default column break */
5174#ifdef __WINNT__
5175 zBreak = "\r\n";
5176 iBreaklen = (int)sizeof("\r\n")-1;
5177#else
5178 zBreak = "\n";
5179 iBreaklen = (int)sizeof(char);
5180#endif
5181 }
5182 /* Perform the requested operation */
5183 zEnd = &zIn[iLen];
5184 for(;;){
5185 int nMax;
5186 if( zIn >= zEnd ){
5187 /* No more input to process */
5188 break;
5189 }
5190 nMax = (int)(zEnd-zIn);
5191 if( iChunk > nMax ){
5192 iChunk = nMax;
5193 }
5194 /* Append the column first */
5195 jx9_result_string(pCtx, zIn, iChunk); /* Will make it's own copy */
5196 /* Advance the cursor */
5197 zIn += iChunk;
5198 if( zIn < zEnd ){
5199 /* Append the line break */
5200 jx9_result_string(pCtx, zBreak, iBreaklen);
5201 }
5202 }
5203 return JX9_OK;
5204}
5205/*
5206 * Check if the given character is a member of the given mask.
5207 * Return TRUE on success. FALSE otherwise.
5208 * Refer to [strtok()].
5209 */
5210static int CheckMask(int c, const char *zMask, int nMasklen, int *pOfft)
5211{
5212 int i;
5213 for( i = 0 ; i < nMasklen ; ++i ){
5214 if( c == zMask[i] ){
5215 if( pOfft ){
5216 *pOfft = i;
5217 }
5218 return TRUE;
5219 }
5220 }
5221 return FALSE;
5222}
5223/*
5224 * Extract a single token from the input stream.
5225 * Refer to [strtok()].
5226 */
5227static sxi32 ExtractToken(const char **pzIn, const char *zEnd, const char *zMask, int nMasklen, SyString *pOut)
5228{
5229 const char *zIn = *pzIn;
5230 const char *zPtr;
5231 /* Ignore leading delimiter */
5232 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && CheckMask(zIn[0], zMask, nMasklen, 0) ){
5233 zIn++;
5234 }
5235 if( zIn >= zEnd ){
5236 /* End of input */
5237 return SXERR_EOF;
5238 }
5239 zPtr = zIn;
5240 /* Extract the token */
5241 while( zIn < zEnd ){
5242 if( (unsigned char)zIn[0] >= 0xc0 ){
5243 /* UTF-8 stream */
5244 zIn++;
5245 SX_JMP_UTF8(zIn, zEnd);
5246 }else{
5247 if( CheckMask(zIn[0], zMask, nMasklen, 0) ){
5248 break;
5249 }
5250 zIn++;
5251 }
5252 }
5253 SyStringInitFromBuf(pOut, zPtr, zIn-zPtr);
5254 /* Update the cursor */
5255 *pzIn = zIn;
5256 /* Return to the caller */
5257 return SXRET_OK;
5258}
5259/* strtok auxiliary private data */
5260typedef struct strtok_aux_data strtok_aux_data;
5261struct strtok_aux_data
5262{
5263 const char *zDup; /* Complete duplicate of the input */
5264 const char *zIn; /* Current input stream */
5265 const char *zEnd; /* End of input */
5266};
5267/*
5268 * string strtok(string $str, string $token)
5269 * string strtok(string $token)
5270 * strtok() splits a string (str) into smaller strings (tokens), with each token
5271 * being delimited by any character from token. That is, if you have a string like
5272 * "This is an example string" you could tokenize this string into its individual
5273 * words by using the space character as the token.
5274 * Note that only the first call to strtok uses the string argument. Every subsequent
5275 * call to strtok only needs the token to use, as it keeps track of where it is in
5276 * the current string. To start over, or to tokenize a new string you simply call strtok
5277 * with the string argument again to initialize it. Note that you may put multiple tokens
5278 * in the token parameter. The string will be tokenized when any one of the characters in
5279 * the argument are found.
5280 * Parameters
5281 * $str
5282 * The string being split up into smaller strings (tokens).
5283 * $token
5284 * The delimiter used when splitting up str.
5285 * Return
5286 * Current token or FALSE on EOF.
5287 */
5288static int jx9Builtin_strtok(jx9_context *pCtx, int nArg, jx9_value **apArg)
5289{
5290 strtok_aux_data *pAux;
5291 const char *zMask;
5292 SyString sToken;
5293 int nMasklen;
5294 sxi32 rc;
5295 if( nArg < 2 ){
5296 /* Extract top aux data */
5297 pAux = (strtok_aux_data *)jx9_context_peek_aux_data(pCtx);
5298 if( pAux == 0 ){
5299 /* No aux data, return FALSE */
5300 jx9_result_bool(pCtx, 0);
5301 return JX9_OK;
5302 }
5303 nMasklen = 0;
5304 zMask = ""; /* cc warning */
5305 if( nArg > 0 ){
5306 /* Extract the mask */
5307 zMask = jx9_value_to_string(apArg[0], &nMasklen);
5308 }
5309 if( nMasklen < 1 ){
5310 /* Invalid mask, return FALSE */
5311 jx9_context_free_chunk(pCtx, (void *)pAux->zDup);
5312 jx9_context_free_chunk(pCtx, pAux);
5313 (void)jx9_context_pop_aux_data(pCtx);
5314 jx9_result_bool(pCtx, 0);
5315 return JX9_OK;
5316 }
5317 /* Extract the token */
5318 rc = ExtractToken(&pAux->zIn, pAux->zEnd, zMask, nMasklen, &sToken);
5319 if( rc != SXRET_OK ){
5320 /* EOF , discard the aux data */
5321 jx9_context_free_chunk(pCtx, (void *)pAux->zDup);
5322 jx9_context_free_chunk(pCtx, pAux);
5323 (void)jx9_context_pop_aux_data(pCtx);
5324 jx9_result_bool(pCtx, 0);
5325 }else{
5326 /* Return the extracted token */
5327 jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte);
5328 }
5329 }else{
5330 const char *zInput, *zCur;
5331 char *zDup;
5332 int nLen;
5333 /* Extract the raw input */
5334 zCur = zInput = jx9_value_to_string(apArg[0], &nLen);
5335 if( nLen < 1 ){
5336 /* Empty input, return FALSE */
5337 jx9_result_bool(pCtx, 0);
5338 return JX9_OK;
5339 }
5340 /* Extract the mask */
5341 zMask = jx9_value_to_string(apArg[1], &nMasklen);
5342 if( nMasklen < 1 ){
5343 /* Set a default mask */
5344#define TOK_MASK " \n\t\r\f"
5345 zMask = TOK_MASK;
5346 nMasklen = (int)sizeof(TOK_MASK) - 1;
5347#undef TOK_MASK
5348 }
5349 /* Extract a single token */
5350 rc = ExtractToken(&zInput, &zInput[nLen], zMask, nMasklen, &sToken);
5351 if( rc != SXRET_OK ){
5352 /* Empty input */
5353 jx9_result_bool(pCtx, 0);
5354 return JX9_OK;
5355 }else{
5356 /* Return the extracted token */
5357 jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte);
5358 }
5359 /* Create our auxilliary data and copy the input */
5360 pAux = (strtok_aux_data *)jx9_context_alloc_chunk(pCtx, sizeof(strtok_aux_data), TRUE, FALSE);
5361 if( pAux ){
5362 nLen -= (int)(zInput-zCur);
5363 if( nLen < 1 ){
5364 jx9_context_free_chunk(pCtx, pAux);
5365 return JX9_OK;
5366 }
5367 /* Duplicate input */
5368 zDup = (char *)jx9_context_alloc_chunk(pCtx, (unsigned int)(nLen+1), TRUE, FALSE);
5369 if( zDup ){
5370 SyMemcpy(zInput, zDup, (sxu32)nLen);
5371 /* Register the aux data */
5372 pAux->zDup = pAux->zIn = zDup;
5373 pAux->zEnd = &zDup[nLen];
5374 jx9_context_push_aux_data(pCtx, pAux);
5375 }
5376 }
5377 }
5378 return JX9_OK;
5379}
5380/*
5381 * string str_pad(string $input, int $pad_length[, string $pad_string = " " [, int $pad_type = STR_PAD_RIGHT]])
5382 * Pad a string to a certain length with another string
5383 * Parameters
5384 * $input
5385 * The input string.
5386 * $pad_length
5387 * If the value of pad_length is negative, less than, or equal to the length of the input
5388 * string, no padding takes place.
5389 * $pad_string
5390 * Note:
5391 * The pad_string WIIL NOT BE truncated if the required number of padding characters can't be evenly
5392 * divided by the pad_string's length.
5393 * $pad_type
5394 * Optional argument pad_type can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type
5395 * is not specified it is assumed to be STR_PAD_RIGHT.
5396 * Return
5397 * The padded string.
5398 */
5399static int jx9Builtin_str_pad(jx9_context *pCtx, int nArg, jx9_value **apArg)
5400{
5401 int iLen, iPadlen, iType, i, iDiv, iStrpad, iRealPad, jPad;
5402 const char *zIn, *zPad;
5403 if( nArg < 2 ){
5404 /* Missing arguments, return the empty string */
5405 jx9_result_string(pCtx, "", 0);
5406 return JX9_OK;
5407 }
5408 /* Extract the target string */
5409 zIn = jx9_value_to_string(apArg[0], &iLen);
5410 /* Padding length */
5411 iRealPad = iPadlen = jx9_value_to_int(apArg[1]);
5412 if( iPadlen > 0 ){
5413 iPadlen -= iLen;
5414 }
5415 if( iPadlen < 1 ){
5416 /* Return the string verbatim */
5417 jx9_result_string(pCtx, zIn, iLen);
5418 return JX9_OK;
5419 }
5420 zPad = " "; /* Whitespace padding */
5421 iStrpad = (int)sizeof(char);
5422 iType = 1 ; /* STR_PAD_RIGHT */
5423 if( nArg > 2 ){
5424 /* Padding string */
5425 zPad = jx9_value_to_string(apArg[2], &iStrpad);
5426 if( iStrpad < 1 ){
5427 /* Empty string */
5428 zPad = " "; /* Whitespace padding */
5429 iStrpad = (int)sizeof(char);
5430 }
5431 if( nArg > 3 ){
5432 /* Padd type */
5433 iType = jx9_value_to_int(apArg[3]);
5434 if( iType != 0 /* STR_PAD_LEFT */ && iType != 2 /* STR_PAD_BOTH */ ){
5435 iType = 1 ; /* STR_PAD_RIGHT */
5436 }
5437 }
5438 }
5439 iDiv = 1;
5440 if( iType == 2 ){
5441 iDiv = 2; /* STR_PAD_BOTH */
5442 }
5443 /* Perform the requested operation */
5444 if( iType == 0 /* STR_PAD_LEFT */ || iType == 2 /* STR_PAD_BOTH */ ){
5445 jPad = iStrpad;
5446 for( i = 0 ; i < iPadlen/iDiv ; i += jPad ){
5447 /* Padding */
5448 if( (int)jx9_context_result_buf_length(pCtx) + iLen + jPad >= iRealPad ){
5449 break;
5450 }
5451 jx9_result_string(pCtx, zPad, jPad);
5452 }
5453 if( iType == 0 /* STR_PAD_LEFT */ ){
5454 while( (int)jx9_context_result_buf_length(pCtx) + iLen < iRealPad ){
5455 jPad = iRealPad - (iLen + (int)jx9_context_result_buf_length(pCtx) );
5456 if( jPad > iStrpad ){
5457 jPad = iStrpad;
5458 }
5459 if( jPad < 1){
5460 break;
5461 }
5462 jx9_result_string(pCtx, zPad, jPad);
5463 }
5464 }
5465 }
5466 if( iLen > 0 ){
5467 /* Append the input string */
5468 jx9_result_string(pCtx, zIn, iLen);
5469 }
5470 if( iType == 1 /* STR_PAD_RIGHT */ || iType == 2 /* STR_PAD_BOTH */ ){
5471 for( i = 0 ; i < iPadlen/iDiv ; i += iStrpad ){
5472 /* Padding */
5473 if( (int)jx9_context_result_buf_length(pCtx) + iStrpad >= iRealPad ){
5474 break;
5475 }
5476 jx9_result_string(pCtx, zPad, iStrpad);
5477 }
5478 while( (int)jx9_context_result_buf_length(pCtx) < iRealPad ){
5479 jPad = iRealPad - (int)jx9_context_result_buf_length(pCtx);
5480 if( jPad > iStrpad ){
5481 jPad = iStrpad;
5482 }
5483 if( jPad < 1){
5484 break;
5485 }
5486 jx9_result_string(pCtx, zPad, jPad);
5487 }
5488 }
5489 return JX9_OK;
5490}
5491/*
5492 * String replacement private data.
5493 */
5494typedef struct str_replace_data str_replace_data;
5495struct str_replace_data
5496{
5497 /* The following two fields are only used by the strtr function */
5498 SyBlob *pWorker; /* Working buffer */
5499 ProcStringMatch xMatch; /* Pattern match routine */
5500 /* The following two fields are only used by the str_replace function */
5501 SySet *pCollector; /* Argument collector*/
5502 jx9_context *pCtx; /* Call context */
5503};
5504/*
5505 * Remove a substring.
5506 */
5507#define STRDEL(SRC, SLEN, OFFT, ILEN){\
5508 for(;;){\
5509 if( OFFT + ILEN >= SLEN ) break; SRC[OFFT] = SRC[OFFT+ILEN]; ++OFFT;\
5510 }\
5511}
5512/*
5513 * Shift right and insert algorithm.
5514 */
5515#define SHIFTRANDINSERT(SRC, LEN, OFFT, ENTRY, ELEN){\
5516 sxu32 INLEN = LEN - OFFT;\
5517 for(;;){\
5518 if( LEN > 0 ){ LEN--; } if(INLEN < 1 ) break; SRC[LEN + ELEN] = SRC[LEN] ; --INLEN; \
5519 }\
5520 for(;;){\
5521 if(ELEN < 1)break; SRC[OFFT] = ENTRY[0]; OFFT++; ENTRY++; --ELEN;\
5522 }\
5523}
5524/*
5525 * Replace all occurrences of the search string at offset (nOfft) with the given
5526 * replacement string [i.e: zReplace].
5527 */
5528static int StringReplace(SyBlob *pWorker, sxu32 nOfft, int nLen, const char *zReplace, int nReplen)
5529{
5530 char *zInput = (char *)SyBlobData(pWorker);
5531 sxu32 n, m;
5532 n = SyBlobLength(pWorker);
5533 m = nOfft;
5534 /* Delete the old entry */
5535 STRDEL(zInput, n, m, nLen);
5536 SyBlobLength(pWorker) -= nLen;
5537 if( nReplen > 0 ){
5538 sxi32 iRep = nReplen;
5539 sxi32 rc;
5540 /*
5541 * Make sure the working buffer is big enough to hold the replacement
5542 * string.
5543 */
5544 rc = SyBlobAppend(pWorker, 0/* Grow without an append operation*/, (sxu32)nReplen);
5545 if( rc != SXRET_OK ){
5546 /* Simply ignore any memory failure problem */
5547 return SXRET_OK;
5548 }
5549 /* Perform the insertion now */
5550 zInput = (char *)SyBlobData(pWorker);
5551 n = SyBlobLength(pWorker);
5552 SHIFTRANDINSERT(zInput, n, nOfft, zReplace, iRep);
5553 SyBlobLength(pWorker) += nReplen;
5554 }
5555 return SXRET_OK;
5556}
5557/*
5558 * String replacement walker callback.
5559 * The following callback is invoked for each array entry that hold
5560 * the replace string.
5561 * Refer to the strtr() implementation for more information.
5562 */
5563static int StringReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData)
5564{
5565 str_replace_data *pRepData = (str_replace_data *)pUserData;
5566 const char *zTarget, *zReplace;
5567 SyBlob *pWorker;
5568 int tLen, nLen;
5569 sxu32 nOfft;
5570 sxi32 rc;
5571 /* Point to the working buffer */
5572 pWorker = pRepData->pWorker;
5573 if( !jx9_value_is_string(pKey) ){
5574 /* Target and replace must be a string */
5575 return JX9_OK;
5576 }
5577 /* Extract the target and the replace */
5578 zTarget = jx9_value_to_string(pKey, &tLen);
5579 if( tLen < 1 ){
5580 /* Empty target, return immediately */
5581 return JX9_OK;
5582 }
5583 /* Perform a pattern search */
5584 rc = pRepData->xMatch(SyBlobData(pWorker), SyBlobLength(pWorker), (const void *)zTarget, (sxu32)tLen, &nOfft);
5585 if( rc != SXRET_OK ){
5586 /* Pattern not found */
5587 return JX9_OK;
5588 }
5589 /* Extract the replace string */
5590 zReplace = jx9_value_to_string(pData, &nLen);
5591 /* Perform the replace process */
5592 StringReplace(pWorker, nOfft, tLen, zReplace, nLen);
5593 /* All done */
5594 return JX9_OK;
5595}
5596/*
5597 * The following walker callback is invoked by the str_rplace() function inorder
5598 * to collect search/replace string.
5599 * This callback is invoked only if the given argument is of type array.
5600 */
5601static int StrReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData)
5602{
5603 str_replace_data *pRep = (str_replace_data *)pUserData;
5604 SyString sWorker;
5605 const char *zIn;
5606 int nByte;
5607 /* Extract a string representation of the given argument */
5608 zIn = jx9_value_to_string(pData, &nByte);
5609 SyStringInitFromBuf(&sWorker, 0, 0);
5610 if( nByte > 0 ){
5611 char *zDup;
5612 /* Duplicate the chunk */
5613 zDup = (char *)jx9_context_alloc_chunk(pRep->pCtx, (unsigned int)nByte, FALSE,
5614 TRUE /* Release the chunk automatically, upon this context is destroyd */
5615 );
5616 if( zDup == 0 ){
5617 /* Ignore any memory failure problem */
5618 jx9_context_throw_error(pRep->pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
5619 return JX9_OK;
5620 }
5621 SyMemcpy(zIn, zDup, (sxu32)nByte);
5622 /* Save the chunk */
5623 SyStringInitFromBuf(&sWorker, zDup, nByte);
5624 }
5625 /* Save for later processing */
5626 SySetPut(pRep->pCollector, (const void *)&sWorker);
5627 /* All done */
5628 SXUNUSED(pKey); /* cc warning */
5629 return JX9_OK;
5630}
5631/*
5632 * mixed str_replace(mixed $search, mixed $replace, mixed $subject[, int &$count ])
5633 * mixed str_ireplace(mixed $search, mixed $replace, mixed $subject[, int &$count ])
5634 * Replace all occurrences of the search string with the replacement string.
5635 * Parameters
5636 * If search and replace are arrays, then str_replace() takes a value from each
5637 * array and uses them to search and replace on subject. If replace has fewer values
5638 * than search, then an empty string is used for the rest of replacement values.
5639 * If search is an array and replace is a string, then this replacement string is used
5640 * for every value of search. The converse would not make sense, though.
5641 * If search or replace are arrays, their elements are processed first to last.
5642 * $search
5643 * The value being searched for, otherwise known as the needle. An array may be used
5644 * to designate multiple needles.
5645 * $replace
5646 * The replacement value that replaces found search values. An array may be used
5647 * to designate multiple replacements.
5648 * $subject
5649 * The string or array being searched and replaced on, otherwise known as the haystack.
5650 * If subject is an array, then the search and replace is performed with every entry
5651 * of subject, and the return value is an array as well.
5652 * $count (Not used)
5653 * If passed, this will be set to the number of replacements performed.
5654 * Return
5655 * This function returns a string or an array with the replaced values.
5656 */
5657static int jx9Builtin_str_replace(jx9_context *pCtx, int nArg, jx9_value **apArg)
5658{
5659 SyString sTemp, *pSearch, *pReplace;
5660 ProcStringMatch xMatch;
5661 const char *zIn, *zFunc;
5662 str_replace_data sRep;
5663 SyBlob sWorker;
5664 SySet sReplace;
5665 SySet sSearch;
5666 int rep_str;
5667 int nByte;
5668 sxi32 rc;
5669 if( nArg < 3 ){
5670 /* Missing/Invalid arguments, return null */
5671 jx9_result_null(pCtx);
5672 return JX9_OK;
5673 }
5674 /* Initialize fields */
5675 SySetInit(&sSearch, &pCtx->pVm->sAllocator, sizeof(SyString));
5676 SySetInit(&sReplace, &pCtx->pVm->sAllocator, sizeof(SyString));
5677 SyBlobInit(&sWorker, &pCtx->pVm->sAllocator);
5678 SyZero(&sRep, sizeof(str_replace_data));
5679 sRep.pCtx = pCtx;
5680 sRep.pCollector = &sSearch;
5681 rep_str = 0;
5682 /* Extract the subject */
5683 zIn = jx9_value_to_string(apArg[2], &nByte);
5684 if( nByte < 1 ){
5685 /* Nothing to replace, return the empty string */
5686 jx9_result_string(pCtx, "", 0);
5687 return JX9_OK;
5688 }
5689 /* Copy the subject */
5690 SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nByte);
5691 /* Search string */
5692 if( jx9_value_is_json_array(apArg[0]) ){
5693 /* Collect search string */
5694 jx9_array_walk(apArg[0], StrReplaceWalker, &sRep);
5695 }else{
5696 /* Single pattern */
5697 zIn = jx9_value_to_string(apArg[0], &nByte);
5698 if( nByte < 1 ){
5699 /* Return the subject untouched since no search string is available */
5700 jx9_result_value(pCtx, apArg[2]/* Subject as thrird argument*/);
5701 return JX9_OK;
5702 }
5703 SyStringInitFromBuf(&sTemp, zIn, nByte);
5704 /* Save for later processing */
5705 SySetPut(&sSearch, (const void *)&sTemp);
5706 }
5707 /* Replace string */
5708 if( jx9_value_is_json_array(apArg[1]) ){
5709 /* Collect replace string */
5710 sRep.pCollector = &sReplace;
5711 jx9_array_walk(apArg[1], StrReplaceWalker, &sRep);
5712 }else{
5713 /* Single needle */
5714 zIn = jx9_value_to_string(apArg[1], &nByte);
5715 rep_str = 1;
5716 SyStringInitFromBuf(&sTemp, zIn, nByte);
5717 /* Save for later processing */
5718 SySetPut(&sReplace, (const void *)&sTemp);
5719 }
5720 /* Reset loop cursors */
5721 SySetResetCursor(&sSearch);
5722 SySetResetCursor(&sReplace);
5723 pReplace = pSearch = 0; /* cc warning */
5724 SyStringInitFromBuf(&sTemp, "", 0);
5725 /* Extract function name */
5726 zFunc = jx9_function_name(pCtx);
5727 /* Set the default pattern match routine */
5728 xMatch = SyBlobSearch;
5729 if( SyStrncmp(zFunc, "str_ireplace", sizeof("str_ireplace") - 1) == 0 ){
5730 /* Case insensitive pattern match */
5731 xMatch = iPatternMatch;
5732 }
5733 /* Start the replace process */
5734 while( SXRET_OK == SySetGetNextEntry(&sSearch, (void **)&pSearch) ){
5735 sxu32 nCount, nOfft;
5736 if( pSearch->nByte < 1 ){
5737 /* Empty string, ignore */
5738 continue;
5739 }
5740 /* Extract the replace string */
5741 if( rep_str ){
5742 pReplace = (SyString *)SySetPeek(&sReplace);
5743 }else{
5744 if( SXRET_OK != SySetGetNextEntry(&sReplace, (void **)&pReplace) ){
5745 /* Sepecial case when 'replace set' has fewer values than the search set.
5746 * An empty string is used for the rest of replacement values
5747 */
5748 pReplace = 0;
5749 }
5750 }
5751 if( pReplace == 0 ){
5752 /* Use an empty string instead */
5753 pReplace = &sTemp;
5754 }
5755 nOfft = nCount = 0;
5756 for(;;){
5757 if( nCount >= SyBlobLength(&sWorker) ){
5758 break;
5759 }
5760 /* Perform a pattern lookup */
5761 rc = xMatch(SyBlobDataAt(&sWorker, nCount), SyBlobLength(&sWorker) - nCount, (const void *)pSearch->zString,
5762 pSearch->nByte, &nOfft);
5763 if( rc != SXRET_OK ){
5764 /* Pattern not found */
5765 break;
5766 }
5767 /* Perform the replace operation */
5768 StringReplace(&sWorker, nCount+nOfft, (int)pSearch->nByte, pReplace->zString, (int)pReplace->nByte);
5769 /* Increment offset counter */
5770 nCount += nOfft + pReplace->nByte;
5771 }
5772 }
5773 /* All done, clean-up the mess left behind */
5774 jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker), (int)SyBlobLength(&sWorker));
5775 SySetRelease(&sSearch);
5776 SySetRelease(&sReplace);
5777 SyBlobRelease(&sWorker);
5778 return JX9_OK;
5779}
5780/*
5781 * string strtr(string $str, string $from, string $to)
5782 * string strtr(string $str, array $replace_pairs)
5783 * Translate characters or replace substrings.
5784 * Parameters
5785 * $str
5786 * The string being translated.
5787 * $from
5788 * The string being translated to to.
5789 * $to
5790 * The string replacing from.
5791 * $replace_pairs
5792 * The replace_pairs parameter may be used instead of to and
5793 * from, in which case it's an array in the form array('from' => 'to', ...).
5794 * Return
5795 * The translated string.
5796 * If replace_pairs contains a key which is an empty string (""), FALSE will be returned.
5797 */
5798static int jx9Builtin_strtr(jx9_context *pCtx, int nArg, jx9_value **apArg)
5799{
5800 const char *zIn;
5801 int nLen;
5802 if( nArg < 1 ){
5803 /* Nothing to replace, return FALSE */
5804 jx9_result_bool(pCtx, 0);
5805 return JX9_OK;
5806 }
5807 zIn = jx9_value_to_string(apArg[0], &nLen);
5808 if( nLen < 1 || nArg < 2 ){
5809 /* Invalid arguments */
5810 jx9_result_string(pCtx, zIn, nLen);
5811 return JX9_OK;
5812 }
5813 if( nArg == 2 && jx9_value_is_json_array(apArg[1]) ){
5814 str_replace_data sRepData;
5815 SyBlob sWorker;
5816 /* Initilaize the working buffer */
5817 SyBlobInit(&sWorker, &pCtx->pVm->sAllocator);
5818 /* Copy raw string */
5819 SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nLen);
5820 /* Init our replace data instance */
5821 sRepData.pWorker = &sWorker;
5822 sRepData.xMatch = SyBlobSearch;
5823 /* Iterate throw array entries and perform the replace operation.*/
5824 jx9_array_walk(apArg[1], StringReplaceWalker, &sRepData);
5825 /* All done, return the result string */
5826 jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker),
5827 (int)SyBlobLength(&sWorker)); /* Will make it's own copy */
5828 /* Clean-up */
5829 SyBlobRelease(&sWorker);
5830 }else{
5831 int i, flen, tlen, c, iOfft;
5832 const char *zFrom, *zTo;
5833 if( nArg < 3 ){
5834 /* Nothing to replace */
5835 jx9_result_string(pCtx, zIn, nLen);
5836 return JX9_OK;
5837 }
5838 /* Extract given arguments */
5839 zFrom = jx9_value_to_string(apArg[1], &flen);
5840 zTo = jx9_value_to_string(apArg[2], &tlen);
5841 if( flen < 1 || tlen < 1 ){
5842 /* Nothing to replace */
5843 jx9_result_string(pCtx, zIn, nLen);
5844 return JX9_OK;
5845 }
5846 /* Start the replace process */
5847 for( i = 0 ; i < nLen ; ++i ){
5848 c = zIn[i];
5849 if( CheckMask(c, zFrom, flen, &iOfft) ){
5850 if ( iOfft < tlen ){
5851 c = zTo[iOfft];
5852 }
5853 }
5854 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
5855
5856 }
5857 }
5858 return JX9_OK;
5859}
5860/*
5861 * Parse an INI string.
5862 * According to wikipedia
5863 * The INI file format is an informal standard for configuration files for some platforms or software.
5864 * INI files are simple text files with a basic structure composed of "sections" and "properties".
5865 * Format
5866* Properties
5867* The basic element contained in an INI file is the property. Every property has a name and a value
5868* delimited by an equals sign (=). The name appears to the left of the equals sign.
5869* Example:
5870* name=value
5871* Sections
5872* Properties may be grouped into arbitrarily named sections. The section name appears on a line by itself
5873* in square brackets ([ and ]). All properties after the section declaration are associated with that section.
5874* There is no explicit "end of section" delimiter; sections end at the next section declaration
5875* or the end of the file. Sections may not be nested.
5876* Example:
5877* [section]
5878* Comments
5879* Semicolons (;) at the beginning of the line indicate a comment. Comment lines are ignored.
5880* This function return an array holding parsed values on success.FALSE otherwise.
5881*/
5882JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection)
5883{
5884 jx9_value *pCur, *pArray, *pSection, *pWorker, *pValue;
5885 const char *zCur, *zEnd = &zIn[nByte];
5886 SyHashEntry *pEntry;
5887 SyString sEntry;
5888 SyHash sHash;
5889 int c;
5890 /* Create an empty array and worker variables */
5891 pArray = jx9_context_new_array(pCtx);
5892 pWorker = jx9_context_new_scalar(pCtx);
5893 pValue = jx9_context_new_scalar(pCtx);
5894 if( pArray == 0 || pWorker == 0 || pValue == 0){
5895 /* Out of memory */
5896 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
5897 /* Return FALSE */
5898 jx9_result_bool(pCtx, 0);
5899 return JX9_OK;
5900 }
5901 SyHashInit(&sHash, &pCtx->pVm->sAllocator, 0, 0);
5902 pCur = pArray;
5903 /* Start the parse process */
5904 for(;;){
5905 /* Ignore leading white spaces */
5906 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0])){
5907 zIn++;
5908 }
5909 if( zIn >= zEnd ){
5910 /* No more input to process */
5911 break;
5912 }
5913 if( zIn[0] == ';' || zIn[0] == '#' ){
5914 /* Comment til the end of line */
5915 zIn++;
5916 while(zIn < zEnd && zIn[0] != '\n' ){
5917 zIn++;
5918 }
5919 continue;
5920 }
5921 /* Reset the string cursor of the working variable */
5922 jx9_value_reset_string_cursor(pWorker);
5923 if( zIn[0] == '[' ){
5924 /* Section: Extract the section name */
5925 zIn++;
5926 zCur = zIn;
5927 while( zIn < zEnd && zIn[0] != ']' ){
5928 zIn++;
5929 }
5930 if( zIn > zCur && bProcessSection ){
5931 /* Save the section name */
5932 SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur));
5933 SyStringFullTrim(&sEntry);
5934 jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte);
5935 if( sEntry.nByte > 0 ){
5936 /* Associate an array with the section */
5937 pSection = jx9_context_new_array(pCtx);
5938 if( pSection ){
5939 jx9_array_add_elem(pArray, pWorker/*Section name*/, pSection);
5940 pCur = pSection;
5941 }
5942 }
5943 }
5944 zIn++; /* Trailing square brackets ']' */
5945 }else{
5946 jx9_value *pOldCur;
5947 int is_array;
5948 int iLen;
5949 /* Properties */
5950 is_array = 0;
5951 zCur = zIn;
5952 iLen = 0; /* cc warning */
5953 pOldCur = pCur;
5954 while( zIn < zEnd && zIn[0] != '=' ){
5955 if( zIn[0] == '[' && !is_array ){
5956 /* Array */
5957 iLen = (int)(zIn-zCur);
5958 is_array = 1;
5959 if( iLen > 0 ){
5960 jx9_value *pvArr = 0; /* cc warning */
5961 /* Query the hashtable */
5962 SyStringInitFromBuf(&sEntry, zCur, iLen);
5963 SyStringFullTrim(&sEntry);
5964 pEntry = SyHashGet(&sHash, (const void *)sEntry.zString, sEntry.nByte);
5965 if( pEntry ){
5966 pvArr = (jx9_value *)SyHashEntryGetUserData(pEntry);
5967 }else{
5968 /* Create an empty array */
5969 pvArr = jx9_context_new_array(pCtx);
5970 if( pvArr ){
5971 /* Save the entry */
5972 SyHashInsert(&sHash, (const void *)sEntry.zString, sEntry.nByte, pvArr);
5973 /* Insert the entry */
5974 jx9_value_reset_string_cursor(pWorker);
5975 jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte);
5976 jx9_array_add_elem(pCur, pWorker, pvArr);
5977 jx9_value_reset_string_cursor(pWorker);
5978 }
5979 }
5980 if( pvArr ){
5981 pCur = pvArr;
5982 }
5983 }
5984 while ( zIn < zEnd && zIn[0] != ']' ){
5985 zIn++;
5986 }
5987 }
5988 zIn++;
5989 }
5990 if( !is_array ){
5991 iLen = (int)(zIn-zCur);
5992 }
5993 /* Trim the key */
5994 SyStringInitFromBuf(&sEntry, zCur, iLen);
5995 SyStringFullTrim(&sEntry);
5996 if( sEntry.nByte > 0 ){
5997 if( !is_array ){
5998 /* Save the key name */
5999 jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte);
6000 }
6001 /* extract key value */
6002 jx9_value_reset_string_cursor(pValue);
6003 zIn++; /* '=' */
6004 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
6005 zIn++;
6006 }
6007 if( zIn < zEnd ){
6008 zCur = zIn;
6009 c = zIn[0];
6010 if( c == '"' || c == '\'' ){
6011 zIn++;
6012 /* Delimit the value */
6013 while( zIn < zEnd ){
6014 if ( zIn[0] == c && zIn[-1] != '\\' ){
6015 break;
6016 }
6017 zIn++;
6018 }
6019 if( zIn < zEnd ){
6020 zIn++;
6021 }
6022 }else{
6023 while( zIn < zEnd ){
6024 if( zIn[0] == '\n' ){
6025 if( zIn[-1] != '\\' ){
6026 break;
6027 }
6028 }else if( zIn[0] == ';' || zIn[0] == '#' ){
6029 /* Inline comments */
6030 break;
6031 }
6032 zIn++;
6033 }
6034 }
6035 /* Trim the value */
6036 SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur));
6037 SyStringFullTrim(&sEntry);
6038 if( c == '"' || c == '\'' ){
6039 SyStringTrimLeadingChar(&sEntry, c);
6040 SyStringTrimTrailingChar(&sEntry, c);
6041 }
6042 if( sEntry.nByte > 0 ){
6043 jx9_value_string(pValue, sEntry.zString, (int)sEntry.nByte);
6044 }
6045 /* Insert the key and it's value */
6046 jx9_array_add_elem(pCur, is_array ? 0 /*Automatic index assign */: pWorker, pValue);
6047 }
6048 }else{
6049 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && ( SyisSpace(zIn[0]) || zIn[0] == '=' ) ){
6050 zIn++;
6051 }
6052 }
6053 pCur = pOldCur;
6054 }
6055 }
6056 SyHashRelease(&sHash);
6057 /* Return the parse of the INI string */
6058 jx9_result_value(pCtx, pArray);
6059 return SXRET_OK;
6060}
6061/*
6062 * array parse_ini_string(string $ini[, bool $process_sections = false[, int $scanner_mode = INI_SCANNER_NORMAL ]])
6063 * Parse a configuration string.
6064 * Parameters
6065 * $ini
6066 * The contents of the ini file being parsed.
6067 * $process_sections
6068 * By setting the process_sections parameter to TRUE, you get a multidimensional array, with the section names
6069 * and settings included. The default for process_sections is FALSE.
6070 * $scanner_mode (Not used)
6071 * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. If INI_SCANNER_RAW is supplied
6072 * then option values will not be parsed.
6073 * Return
6074 * The settings are returned as an associative array on success, and FALSE on failure.
6075 */
6076static int jx9Builtin_parse_ini_string(jx9_context *pCtx, int nArg, jx9_value **apArg)
6077{
6078 const char *zIni;
6079 int nByte;
6080 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
6081 /* Missing/Invalid arguments, return FALSE*/
6082 jx9_result_bool(pCtx, 0);
6083 return JX9_OK;
6084 }
6085 /* Extract the raw INI buffer */
6086 zIni = jx9_value_to_string(apArg[0], &nByte);
6087 /* Process the INI buffer*/
6088 jx9ParseIniString(pCtx, zIni, (sxu32)nByte, (nArg > 1) ? jx9_value_to_bool(apArg[1]) : 0);
6089 return JX9_OK;
6090}
6091/*
6092 * Ctype Functions.
6093 * Authors:
6094 * Symisc Systems, devel@symisc.net.
6095 * Copyright (C) Symisc Systems, http://jx9.symisc.net
6096 * Status:
6097 * Stable.
6098 */
6099/*
6100 * bool ctype_alnum(string $text)
6101 * Checks if all of the characters in the provided string, text, are alphanumeric.
6102 * Parameters
6103 * $text
6104 * The tested string.
6105 * Return
6106 * TRUE if every character in text is either a letter or a digit, FALSE otherwise.
6107 */
6108static int jx9Builtin_ctype_alnum(jx9_context *pCtx, int nArg, jx9_value **apArg)
6109{
6110 const unsigned char *zIn, *zEnd;
6111 int nLen;
6112 if( nArg < 1 ){
6113 /* Missing arguments, return FALSE */
6114 jx9_result_bool(pCtx, 0);
6115 return JX9_OK;
6116 }
6117 /* Extract the target string */
6118 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6119 zEnd = &zIn[nLen];
6120 if( nLen < 1 ){
6121 /* Empty string, return FALSE */
6122 jx9_result_bool(pCtx, 0);
6123 return JX9_OK;
6124 }
6125 /* Perform the requested operation */
6126 for(;;){
6127 if( zIn >= zEnd ){
6128 /* If we reach the end of the string, then the test succeeded. */
6129 jx9_result_bool(pCtx, 1);
6130 return JX9_OK;
6131 }
6132 if( !SyisAlphaNum(zIn[0]) ){
6133 break;
6134 }
6135 /* Point to the next character */
6136 zIn++;
6137 }
6138 /* The test failed, return FALSE */
6139 jx9_result_bool(pCtx, 0);
6140 return JX9_OK;
6141}
6142/*
6143 * bool ctype_alpha(string $text)
6144 * Checks if all of the characters in the provided string, text, are alphabetic.
6145 * Parameters
6146 * $text
6147 * The tested string.
6148 * Return
6149 * TRUE if every character in text is a letter from the current locale, FALSE otherwise.
6150 */
6151static int jx9Builtin_ctype_alpha(jx9_context *pCtx, int nArg, jx9_value **apArg)
6152{
6153 const unsigned char *zIn, *zEnd;
6154 int nLen;
6155 if( nArg < 1 ){
6156 /* Missing arguments, return FALSE */
6157 jx9_result_bool(pCtx, 0);
6158 return JX9_OK;
6159 }
6160 /* Extract the target string */
6161 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6162 zEnd = &zIn[nLen];
6163 if( nLen < 1 ){
6164 /* Empty string, return FALSE */
6165 jx9_result_bool(pCtx, 0);
6166 return JX9_OK;
6167 }
6168 /* Perform the requested operation */
6169 for(;;){
6170 if( zIn >= zEnd ){
6171 /* If we reach the end of the string, then the test succeeded. */
6172 jx9_result_bool(pCtx, 1);
6173 return JX9_OK;
6174 }
6175 if( !SyisAlpha(zIn[0]) ){
6176 break;
6177 }
6178 /* Point to the next character */
6179 zIn++;
6180 }
6181 /* The test failed, return FALSE */
6182 jx9_result_bool(pCtx, 0);
6183 return JX9_OK;
6184}
6185/*
6186 * bool ctype_cntrl(string $text)
6187 * Checks if all of the characters in the provided string, text, are control characters.
6188 * Parameters
6189 * $text
6190 * The tested string.
6191 * Return
6192 * TRUE if every character in text is a control characters, FALSE otherwise.
6193 */
6194static int jx9Builtin_ctype_cntrl(jx9_context *pCtx, int nArg, jx9_value **apArg)
6195{
6196 const unsigned char *zIn, *zEnd;
6197 int nLen;
6198 if( nArg < 1 ){
6199 /* Missing arguments, return FALSE */
6200 jx9_result_bool(pCtx, 0);
6201 return JX9_OK;
6202 }
6203 /* Extract the target string */
6204 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6205 zEnd = &zIn[nLen];
6206 if( nLen < 1 ){
6207 /* Empty string, return FALSE */
6208 jx9_result_bool(pCtx, 0);
6209 return JX9_OK;
6210 }
6211 /* Perform the requested operation */
6212 for(;;){
6213 if( zIn >= zEnd ){
6214 /* If we reach the end of the string, then the test succeeded. */
6215 jx9_result_bool(pCtx, 1);
6216 return JX9_OK;
6217 }
6218 if( zIn[0] >= 0xc0 ){
6219 /* UTF-8 stream */
6220 break;
6221 }
6222 if( !SyisCtrl(zIn[0]) ){
6223 break;
6224 }
6225 /* Point to the next character */
6226 zIn++;
6227 }
6228 /* The test failed, return FALSE */
6229 jx9_result_bool(pCtx, 0);
6230 return JX9_OK;
6231}
6232/*
6233 * bool ctype_digit(string $text)
6234 * Checks if all of the characters in the provided string, text, are numerical.
6235 * Parameters
6236 * $text
6237 * The tested string.
6238 * Return
6239 * TRUE if every character in the string text is a decimal digit, FALSE otherwise.
6240 */
6241static int jx9Builtin_ctype_digit(jx9_context *pCtx, int nArg, jx9_value **apArg)
6242{
6243 const unsigned char *zIn, *zEnd;
6244 int nLen;
6245 if( nArg < 1 ){
6246 /* Missing arguments, return FALSE */
6247 jx9_result_bool(pCtx, 0);
6248 return JX9_OK;
6249 }
6250 /* Extract the target string */
6251 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6252 zEnd = &zIn[nLen];
6253 if( nLen < 1 ){
6254 /* Empty string, return FALSE */
6255 jx9_result_bool(pCtx, 0);
6256 return JX9_OK;
6257 }
6258 /* Perform the requested operation */
6259 for(;;){
6260 if( zIn >= zEnd ){
6261 /* If we reach the end of the string, then the test succeeded. */
6262 jx9_result_bool(pCtx, 1);
6263 return JX9_OK;
6264 }
6265 if( zIn[0] >= 0xc0 ){
6266 /* UTF-8 stream */
6267 break;
6268 }
6269 if( !SyisDigit(zIn[0]) ){
6270 break;
6271 }
6272 /* Point to the next character */
6273 zIn++;
6274 }
6275 /* The test failed, return FALSE */
6276 jx9_result_bool(pCtx, 0);
6277 return JX9_OK;
6278}
6279/*
6280 * bool ctype_xdigit(string $text)
6281 * Check for character(s) representing a hexadecimal digit.
6282 * Parameters
6283 * $text
6284 * The tested string.
6285 * Return
6286 * Returns TRUE if every character in text is a hexadecimal 'digit', that is
6287 * a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
6288 */
6289static int jx9Builtin_ctype_xdigit(jx9_context *pCtx, int nArg, jx9_value **apArg)
6290{
6291 const unsigned char *zIn, *zEnd;
6292 int nLen;
6293 if( nArg < 1 ){
6294 /* Missing arguments, return FALSE */
6295 jx9_result_bool(pCtx, 0);
6296 return JX9_OK;
6297 }
6298 /* Extract the target string */
6299 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6300 zEnd = &zIn[nLen];
6301 if( nLen < 1 ){
6302 /* Empty string, return FALSE */
6303 jx9_result_bool(pCtx, 0);
6304 return JX9_OK;
6305 }
6306 /* Perform the requested operation */
6307 for(;;){
6308 if( zIn >= zEnd ){
6309 /* If we reach the end of the string, then the test succeeded. */
6310 jx9_result_bool(pCtx, 1);
6311 return JX9_OK;
6312 }
6313 if( zIn[0] >= 0xc0 ){
6314 /* UTF-8 stream */
6315 break;
6316 }
6317 if( !SyisHex(zIn[0]) ){
6318 break;
6319 }
6320 /* Point to the next character */
6321 zIn++;
6322 }
6323 /* The test failed, return FALSE */
6324 jx9_result_bool(pCtx, 0);
6325 return JX9_OK;
6326}
6327/*
6328 * bool ctype_graph(string $text)
6329 * Checks if all of the characters in the provided string, text, creates visible output.
6330 * Parameters
6331 * $text
6332 * The tested string.
6333 * Return
6334 * Returns TRUE if every character in text is printable and actually creates visible output
6335 * (no white space), FALSE otherwise.
6336 */
6337static int jx9Builtin_ctype_graph(jx9_context *pCtx, int nArg, jx9_value **apArg)
6338{
6339 const unsigned char *zIn, *zEnd;
6340 int nLen;
6341 if( nArg < 1 ){
6342 /* Missing arguments, return FALSE */
6343 jx9_result_bool(pCtx, 0);
6344 return JX9_OK;
6345 }
6346 /* Extract the target string */
6347 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6348 zEnd = &zIn[nLen];
6349 if( nLen < 1 ){
6350 /* Empty string, return FALSE */
6351 jx9_result_bool(pCtx, 0);
6352 return JX9_OK;
6353 }
6354 /* Perform the requested operation */
6355 for(;;){
6356 if( zIn >= zEnd ){
6357 /* If we reach the end of the string, then the test succeeded. */
6358 jx9_result_bool(pCtx, 1);
6359 return JX9_OK;
6360 }
6361 if( zIn[0] >= 0xc0 ){
6362 /* UTF-8 stream */
6363 break;
6364 }
6365 if( !SyisGraph(zIn[0]) ){
6366 break;
6367 }
6368 /* Point to the next character */
6369 zIn++;
6370 }
6371 /* The test failed, return FALSE */
6372 jx9_result_bool(pCtx, 0);
6373 return JX9_OK;
6374}
6375/*
6376 * bool ctype_print(string $text)
6377 * Checks if all of the characters in the provided string, text, are printable.
6378 * Parameters
6379 * $text
6380 * The tested string.
6381 * Return
6382 * Returns TRUE if every character in text will actually create output (including blanks).
6383 * Returns FALSE if text contains control characters or characters that do not have any output
6384 * or control function at all.
6385 */
6386static int jx9Builtin_ctype_print(jx9_context *pCtx, int nArg, jx9_value **apArg)
6387{
6388 const unsigned char *zIn, *zEnd;
6389 int nLen;
6390 if( nArg < 1 ){
6391 /* Missing arguments, return FALSE */
6392 jx9_result_bool(pCtx, 0);
6393 return JX9_OK;
6394 }
6395 /* Extract the target string */
6396 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6397 zEnd = &zIn[nLen];
6398 if( nLen < 1 ){
6399 /* Empty string, return FALSE */
6400 jx9_result_bool(pCtx, 0);
6401 return JX9_OK;
6402 }
6403 /* Perform the requested operation */
6404 for(;;){
6405 if( zIn >= zEnd ){
6406 /* If we reach the end of the string, then the test succeeded. */
6407 jx9_result_bool(pCtx, 1);
6408 return JX9_OK;
6409 }
6410 if( zIn[0] >= 0xc0 ){
6411 /* UTF-8 stream */
6412 break;
6413 }
6414 if( !SyisPrint(zIn[0]) ){
6415 break;
6416 }
6417 /* Point to the next character */
6418 zIn++;
6419 }
6420 /* The test failed, return FALSE */
6421 jx9_result_bool(pCtx, 0);
6422 return JX9_OK;
6423}
6424/*
6425 * bool ctype_punct(string $text)
6426 * Checks if all of the characters in the provided string, text, are punctuation character.
6427 * Parameters
6428 * $text
6429 * The tested string.
6430 * Return
6431 * Returns TRUE if every character in text is printable, but neither letter
6432 * digit or blank, FALSE otherwise.
6433 */
6434static int jx9Builtin_ctype_punct(jx9_context *pCtx, int nArg, jx9_value **apArg)
6435{
6436 const unsigned char *zIn, *zEnd;
6437 int nLen;
6438 if( nArg < 1 ){
6439 /* Missing arguments, return FALSE */
6440 jx9_result_bool(pCtx, 0);
6441 return JX9_OK;
6442 }
6443 /* Extract the target string */
6444 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6445 zEnd = &zIn[nLen];
6446 if( nLen < 1 ){
6447 /* Empty string, return FALSE */
6448 jx9_result_bool(pCtx, 0);
6449 return JX9_OK;
6450 }
6451 /* Perform the requested operation */
6452 for(;;){
6453 if( zIn >= zEnd ){
6454 /* If we reach the end of the string, then the test succeeded. */
6455 jx9_result_bool(pCtx, 1);
6456 return JX9_OK;
6457 }
6458 if( zIn[0] >= 0xc0 ){
6459 /* UTF-8 stream */
6460 break;
6461 }
6462 if( !SyisPunct(zIn[0]) ){
6463 break;
6464 }
6465 /* Point to the next character */
6466 zIn++;
6467 }
6468 /* The test failed, return FALSE */
6469 jx9_result_bool(pCtx, 0);
6470 return JX9_OK;
6471}
6472/*
6473 * bool ctype_space(string $text)
6474 * Checks if all of the characters in the provided string, text, creates whitespace.
6475 * Parameters
6476 * $text
6477 * The tested string.
6478 * Return
6479 * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise.
6480 * Besides the blank character this also includes tab, vertical tab, line feed, carriage return
6481 * and form feed characters.
6482 */
6483static int jx9Builtin_ctype_space(jx9_context *pCtx, int nArg, jx9_value **apArg)
6484{
6485 const unsigned char *zIn, *zEnd;
6486 int nLen;
6487 if( nArg < 1 ){
6488 /* Missing arguments, return FALSE */
6489 jx9_result_bool(pCtx, 0);
6490 return JX9_OK;
6491 }
6492 /* Extract the target string */
6493 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6494 zEnd = &zIn[nLen];
6495 if( nLen < 1 ){
6496 /* Empty string, return FALSE */
6497 jx9_result_bool(pCtx, 0);
6498 return JX9_OK;
6499 }
6500 /* Perform the requested operation */
6501 for(;;){
6502 if( zIn >= zEnd ){
6503 /* If we reach the end of the string, then the test succeeded. */
6504 jx9_result_bool(pCtx, 1);
6505 return JX9_OK;
6506 }
6507 if( zIn[0] >= 0xc0 ){
6508 /* UTF-8 stream */
6509 break;
6510 }
6511 if( !SyisSpace(zIn[0]) ){
6512 break;
6513 }
6514 /* Point to the next character */
6515 zIn++;
6516 }
6517 /* The test failed, return FALSE */
6518 jx9_result_bool(pCtx, 0);
6519 return JX9_OK;
6520}
6521/*
6522 * bool ctype_lower(string $text)
6523 * Checks if all of the characters in the provided string, text, are lowercase letters.
6524 * Parameters
6525 * $text
6526 * The tested string.
6527 * Return
6528 * Returns TRUE if every character in text is a lowercase letter in the current locale.
6529 */
6530static int jx9Builtin_ctype_lower(jx9_context *pCtx, int nArg, jx9_value **apArg)
6531{
6532 const unsigned char *zIn, *zEnd;
6533 int nLen;
6534 if( nArg < 1 ){
6535 /* Missing arguments, return FALSE */
6536 jx9_result_bool(pCtx, 0);
6537 return JX9_OK;
6538 }
6539 /* Extract the target string */
6540 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6541 zEnd = &zIn[nLen];
6542 if( nLen < 1 ){
6543 /* Empty string, return FALSE */
6544 jx9_result_bool(pCtx, 0);
6545 return JX9_OK;
6546 }
6547 /* Perform the requested operation */
6548 for(;;){
6549 if( zIn >= zEnd ){
6550 /* If we reach the end of the string, then the test succeeded. */
6551 jx9_result_bool(pCtx, 1);
6552 return JX9_OK;
6553 }
6554 if( !SyisLower(zIn[0]) ){
6555 break;
6556 }
6557 /* Point to the next character */
6558 zIn++;
6559 }
6560 /* The test failed, return FALSE */
6561 jx9_result_bool(pCtx, 0);
6562 return JX9_OK;
6563}
6564/*
6565 * bool ctype_upper(string $text)
6566 * Checks if all of the characters in the provided string, text, are uppercase letters.
6567 * Parameters
6568 * $text
6569 * The tested string.
6570 * Return
6571 * Returns TRUE if every character in text is a uppercase letter in the current locale.
6572 */
6573static int jx9Builtin_ctype_upper(jx9_context *pCtx, int nArg, jx9_value **apArg)
6574{
6575 const unsigned char *zIn, *zEnd;
6576 int nLen;
6577 if( nArg < 1 ){
6578 /* Missing arguments, return FALSE */
6579 jx9_result_bool(pCtx, 0);
6580 return JX9_OK;
6581 }
6582 /* Extract the target string */
6583 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen);
6584 zEnd = &zIn[nLen];
6585 if( nLen < 1 ){
6586 /* Empty string, return FALSE */
6587 jx9_result_bool(pCtx, 0);
6588 return JX9_OK;
6589 }
6590 /* Perform the requested operation */
6591 for(;;){
6592 if( zIn >= zEnd ){
6593 /* If we reach the end of the string, then the test succeeded. */
6594 jx9_result_bool(pCtx, 1);
6595 return JX9_OK;
6596 }
6597 if( !SyisUpper(zIn[0]) ){
6598 break;
6599 }
6600 /* Point to the next character */
6601 zIn++;
6602 }
6603 /* The test failed, return FALSE */
6604 jx9_result_bool(pCtx, 0);
6605 return JX9_OK;
6606}
6607/*
6608 * Date/Time functions
6609 * Authors:
6610 * Symisc Systems, devel@symisc.net.
6611 * Copyright (C) Symisc Systems, http://jx9.symisc.net
6612 * Status:
6613 * Devel.
6614 */
6615#include <time.h>
6616#ifdef __WINNT__
6617/* GetSystemTime() */
6618#include <Windows.h>
6619#ifdef _WIN32_WCE
6620/*
6621** WindowsCE does not have a localtime() function. So create a
6622** substitute.
6623** Taken from the SQLite3 source tree.
6624** Status: Public domain
6625*/
6626struct tm *__cdecl localtime(const time_t *t)
6627{
6628 static struct tm y;
6629 FILETIME uTm, lTm;
6630 SYSTEMTIME pTm;
6631 jx9_int64 t64;
6632 t64 = *t;
6633 t64 = (t64 + 11644473600)*10000000;
6634 uTm.dwLowDateTime = (DWORD)(t64 & 0xFFFFFFFF);
6635 uTm.dwHighDateTime= (DWORD)(t64 >> 32);
6636 FileTimeToLocalFileTime(&uTm, &lTm);
6637 FileTimeToSystemTime(&lTm, &pTm);
6638 y.tm_year = pTm.wYear - 1900;
6639 y.tm_mon = pTm.wMonth - 1;
6640 y.tm_wday = pTm.wDayOfWeek;
6641 y.tm_mday = pTm.wDay;
6642 y.tm_hour = pTm.wHour;
6643 y.tm_min = pTm.wMinute;
6644 y.tm_sec = pTm.wSecond;
6645 return &y;
6646}
6647#endif /*_WIN32_WCE */
6648#elif defined(__UNIXES__)
6649#include <sys/time.h>
6650#endif /* __WINNT__*/
6651 /*
6652 * int64 time(void)
6653 * Current Unix timestamp
6654 * Parameters
6655 * None.
6656 * Return
6657 * Returns the current time measured in the number of seconds
6658 * since the Unix Epoch (January 1 1970 00:00:00 GMT).
6659 */
6660static int jx9Builtin_time(jx9_context *pCtx, int nArg, jx9_value **apArg)
6661{
6662 time_t tt;
6663 SXUNUSED(nArg); /* cc warning */
6664 SXUNUSED(apArg);
6665 /* Extract the current time */
6666 time(&tt);
6667 /* Return as 64-bit integer */
6668 jx9_result_int64(pCtx, (jx9_int64)tt);
6669 return JX9_OK;
6670}
6671/*
6672 * string/float microtime([ bool $get_as_float = false ])
6673 * microtime() returns the current Unix timestamp with microseconds.
6674 * Parameters
6675 * $get_as_float
6676 * If used and set to TRUE, microtime() will return a float instead of a string
6677 * as described in the return values section below.
6678 * Return
6679 * By default, microtime() returns a string in the form "msec sec", where sec
6680 * is the current time measured in the number of seconds since the Unix
6681 * epoch (0:00:00 January 1, 1970 GMT), and msec is the number of microseconds
6682 * that have elapsed since sec expressed in seconds.
6683 * If get_as_float is set to TRUE, then microtime() returns a float, which represents
6684 * the current time in seconds since the Unix epoch accurate to the nearest microsecond.
6685 */
6686static int jx9Builtin_microtime(jx9_context *pCtx, int nArg, jx9_value **apArg)
6687{
6688 int bFloat = 0;
6689 sytime sTime;
6690#if defined(__UNIXES__)
6691 struct timeval tv;
6692 gettimeofday(&tv, 0);
6693 sTime.tm_sec = (long)tv.tv_sec;
6694 sTime.tm_usec = (long)tv.tv_usec;
6695#else
6696 time_t tt;
6697 time(&tt);
6698 sTime.tm_sec = (long)tt;
6699 sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC);
6700#endif /* __UNIXES__ */
6701 if( nArg > 0 ){
6702 bFloat = jx9_value_to_bool(apArg[0]);
6703 }
6704 if( bFloat ){
6705 /* Return as float */
6706 jx9_result_double(pCtx, (double)sTime.tm_sec);
6707 }else{
6708 /* Return as string */
6709 jx9_result_string_format(pCtx, "%ld %ld", sTime.tm_usec, sTime.tm_sec);
6710 }
6711 return JX9_OK;
6712}
6713/*
6714 * array getdate ([ int $timestamp = time() ])
6715 * Get date/time information.
6716 * Parameter
6717 * $timestamp: The optional timestamp parameter is an integer Unix timestamp
6718 * that defaults to the current local time if a timestamp is not given.
6719 * In other words, it defaults to the value of time().
6720 * Returns
6721 * Returns an associative array of information related to the timestamp.
6722 * Elements from the returned associative array are as follows:
6723 * KEY VALUE
6724 * --------- -------
6725 * "seconds" Numeric representation of seconds 0 to 59
6726 * "minutes" Numeric representation of minutes 0 to 59
6727 * "hours" Numeric representation of hours 0 to 23
6728 * "mday" Numeric representation of the day of the month 1 to 31
6729 * "wday" Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday)
6730 * "mon" Numeric representation of a month 1 through 12
6731 * "year" A full numeric representation of a year, 4 digits Examples: 1999 or 2003
6732 * "yday" Numeric representation of the day of the year 0 through 365
6733 * "weekday" A full textual representation of the day of the week Sunday through Saturday
6734 * "month" A full textual representation of a month, such as January or March January through December
6735 * 0 Seconds since the Unix Epoch, similar to the values returned by time() and used by date().
6736 * NOTE:
6737 * NULL is returned on failure.
6738 */
6739static int jx9Builtin_getdate(jx9_context *pCtx, int nArg, jx9_value **apArg)
6740{
6741 jx9_value *pValue, *pArray;
6742 Sytm sTm;
6743 if( nArg < 1 ){
6744#ifdef __WINNT__
6745 SYSTEMTIME sOS;
6746 GetSystemTime(&sOS);
6747 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
6748#else
6749 struct tm *pTm;
6750 time_t t;
6751 time(&t);
6752 pTm = localtime(&t);
6753 STRUCT_TM_TO_SYTM(pTm, &sTm);
6754#endif
6755 }else{
6756 /* Use the given timestamp */
6757 time_t t;
6758 struct tm *pTm;
6759#ifdef __WINNT__
6760#ifdef _MSC_VER
6761#if _MSC_VER >= 1400 /* Visual Studio 2005 and up */
6762#pragma warning(disable:4996) /* _CRT_SECURE...*/
6763#endif
6764#endif
6765#endif
6766 if( jx9_value_is_int(apArg[0]) ){
6767 t = (time_t)jx9_value_to_int64(apArg[0]);
6768 pTm = localtime(&t);
6769 if( pTm == 0 ){
6770 time(&t);
6771 }
6772 }else{
6773 time(&t);
6774 }
6775 pTm = localtime(&t);
6776 STRUCT_TM_TO_SYTM(pTm, &sTm);
6777 }
6778 /* Element value */
6779 pValue = jx9_context_new_scalar(pCtx);
6780 if( pValue == 0 ){
6781 /* Return NULL */
6782 jx9_result_null(pCtx);
6783 return JX9_OK;
6784 }
6785 /* Create a new array */
6786 pArray = jx9_context_new_array(pCtx);
6787 if( pArray == 0 ){
6788 /* Return NULL */
6789 jx9_result_null(pCtx);
6790 return JX9_OK;
6791 }
6792 /* Fill the array */
6793 /* Seconds */
6794 jx9_value_int(pValue, sTm.tm_sec);
6795 jx9_array_add_strkey_elem(pArray, "seconds", pValue);
6796 /* Minutes */
6797 jx9_value_int(pValue, sTm.tm_min);
6798 jx9_array_add_strkey_elem(pArray, "minutes", pValue);
6799 /* Hours */
6800 jx9_value_int(pValue, sTm.tm_hour);
6801 jx9_array_add_strkey_elem(pArray, "hours", pValue);
6802 /* mday */
6803 jx9_value_int(pValue, sTm.tm_mday);
6804 jx9_array_add_strkey_elem(pArray, "mday", pValue);
6805 /* wday */
6806 jx9_value_int(pValue, sTm.tm_wday);
6807 jx9_array_add_strkey_elem(pArray, "wday", pValue);
6808 /* mon */
6809 jx9_value_int(pValue, sTm.tm_mon+1);
6810 jx9_array_add_strkey_elem(pArray, "mon", pValue);
6811 /* year */
6812 jx9_value_int(pValue, sTm.tm_year);
6813 jx9_array_add_strkey_elem(pArray, "year", pValue);
6814 /* yday */
6815 jx9_value_int(pValue, sTm.tm_yday);
6816 jx9_array_add_strkey_elem(pArray, "yday", pValue);
6817 /* Weekday */
6818 jx9_value_string(pValue, SyTimeGetDay(sTm.tm_wday), -1);
6819 jx9_array_add_strkey_elem(pArray, "weekday", pValue);
6820 /* Month */
6821 jx9_value_reset_string_cursor(pValue);
6822 jx9_value_string(pValue, SyTimeGetMonth(sTm.tm_mon), -1);
6823 jx9_array_add_strkey_elem(pArray, "month", pValue);
6824 /* Seconds since the epoch */
6825 jx9_value_int64(pValue, (jx9_int64)time(0));
6826 jx9_array_add_elem(pArray, 0 /* Index zero */, pValue);
6827 /* Return the freshly created array */
6828 jx9_result_value(pCtx, pArray);
6829 return JX9_OK;
6830}
6831/*
6832 * mixed gettimeofday([ bool $return_float = false ] )
6833 * Returns an associative array containing the data returned from the system call.
6834 * Parameters
6835 * $return_float
6836 * When set to TRUE, a float instead of an array is returned.
6837 * Return
6838 * By default an array is returned. If return_float is set, then
6839 * a float is returned.
6840 */
6841static int jx9Builtin_gettimeofday(jx9_context *pCtx, int nArg, jx9_value **apArg)
6842{
6843 int bFloat = 0;
6844 sytime sTime;
6845#if defined(__UNIXES__)
6846 struct timeval tv;
6847 gettimeofday(&tv, 0);
6848 sTime.tm_sec = (long)tv.tv_sec;
6849 sTime.tm_usec = (long)tv.tv_usec;
6850#else
6851 time_t tt;
6852 time(&tt);
6853 sTime.tm_sec = (long)tt;
6854 sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC);
6855#endif /* __UNIXES__ */
6856 if( nArg > 0 ){
6857 bFloat = jx9_value_to_bool(apArg[0]);
6858 }
6859 if( bFloat ){
6860 /* Return as float */
6861 jx9_result_double(pCtx, (double)sTime.tm_sec);
6862 }else{
6863 /* Return an associative array */
6864 jx9_value *pValue, *pArray;
6865 /* Create a new array */
6866 pArray = jx9_context_new_array(pCtx);
6867 /* Element value */
6868 pValue = jx9_context_new_scalar(pCtx);
6869 if( pValue == 0 || pArray == 0 ){
6870 /* Return NULL */
6871 jx9_result_null(pCtx);
6872 return JX9_OK;
6873 }
6874 /* Fill the array */
6875 /* sec */
6876 jx9_value_int64(pValue, sTime.tm_sec);
6877 jx9_array_add_strkey_elem(pArray, "sec", pValue);
6878 /* usec */
6879 jx9_value_int64(pValue, sTime.tm_usec);
6880 jx9_array_add_strkey_elem(pArray, "usec", pValue);
6881 /* Return the array */
6882 jx9_result_value(pCtx, pArray);
6883 }
6884 return JX9_OK;
6885}
6886/* Check if the given year is leap or not */
6887#define IS_LEAP_YEAR(YEAR) (YEAR % 400 ? ( YEAR % 100 ? ( YEAR % 4 ? 0 : 1 ) : 0 ) : 1)
6888/* ISO-8601 numeric representation of the day of the week */
6889static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 };
6890/*
6891 * Format a given date string.
6892 * Supported format: (Taken from JX9 online docs)
6893 * character Description
6894 * d Day of the month
6895 * D A textual representation of a days
6896 * j Day of the month without leading zeros
6897 * l A full textual representation of the day of the week
6898 * N ISO-8601 numeric representation of the day of the week
6899 * w Numeric representation of the day of the week
6900 * z The day of the year (starting from 0)
6901 * F A full textual representation of a month, such as January or March
6902 * m Numeric representation of a month, with leading zeros 01 through 12
6903 * M A short textual representation of a month, three letters Jan through Dec
6904 * n Numeric representation of a month, without leading zeros 1 through 12
6905 * t Number of days in the given month 28 through 31
6906 * L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
6907 * o ISO-8601 year number. This has the same value as Y, except that if the ISO week number
6908 * (W) belongs to the previous or next year, that year is used instead. (added in JX9 5.1.0) Examples: 1999 or 2003
6909 * Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
6910 * y A two digit representation of a year Examples: 99 or 03
6911 * a Lowercase Ante meridiem and Post meridiem am or pm
6912 * A Uppercase Ante meridiem and Post meridiem AM or PM
6913 * g 12-hour format of an hour without leading zeros 1 through 12
6914 * G 24-hour format of an hour without leading zeros 0 through 23
6915 * h 12-hour format of an hour with leading zeros 01 through 12
6916 * H 24-hour format of an hour with leading zeros 00 through 23
6917 * i Minutes with leading zeros 00 to 59
6918 * s Seconds, with leading zeros 00 through 59
6919 * u Microseconds Example: 654321
6920 * e Timezone identifier Examples: UTC, GMT, Atlantic/Azores
6921 * I (capital i) Whether or not the date is in daylight saving time 1 if Daylight Saving Time, 0 otherwise.
6922 * r RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 +0200
6923 * U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
6924 * S English ordinal suffix for the day of the month, 2 characters
6925 * O Difference to Greenwich time (GMT) in hours
6926 * Z Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those
6927 * east of UTC is always positive.
6928 * c ISO 8601 date
6929 */
6930static sxi32 DateFormat(jx9_context *pCtx, const char *zIn, int nLen, Sytm *pTm)
6931{
6932 const char *zEnd = &zIn[nLen];
6933 const char *zCur;
6934 /* Start the format process */
6935 for(;;){
6936 if( zIn >= zEnd ){
6937 /* No more input to process */
6938 break;
6939 }
6940 switch(zIn[0]){
6941 case 'd':
6942 /* Day of the month, 2 digits with leading zeros */
6943 jx9_result_string_format(pCtx, "%02d", pTm->tm_mday);
6944 break;
6945 case 'D':
6946 /*A textual representation of a day, three letters*/
6947 zCur = SyTimeGetDay(pTm->tm_wday);
6948 jx9_result_string(pCtx, zCur, 3);
6949 break;
6950 case 'j':
6951 /* Day of the month without leading zeros */
6952 jx9_result_string_format(pCtx, "%d", pTm->tm_mday);
6953 break;
6954 case 'l':
6955 /* A full textual representation of the day of the week */
6956 zCur = SyTimeGetDay(pTm->tm_wday);
6957 jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/);
6958 break;
6959 case 'N':{
6960 /* ISO-8601 numeric representation of the day of the week */
6961 jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]);
6962 break;
6963 }
6964 case 'w':
6965 /*Numeric representation of the day of the week*/
6966 jx9_result_string_format(pCtx, "%d", pTm->tm_wday);
6967 break;
6968 case 'z':
6969 /*The day of the year*/
6970 jx9_result_string_format(pCtx, "%d", pTm->tm_yday);
6971 break;
6972 case 'F':
6973 /*A full textual representation of a month, such as January or March*/
6974 zCur = SyTimeGetMonth(pTm->tm_mon);
6975 jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/);
6976 break;
6977 case 'm':
6978 /*Numeric representation of a month, with leading zeros*/
6979 jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1);
6980 break;
6981 case 'M':
6982 /*A short textual representation of a month, three letters*/
6983 zCur = SyTimeGetMonth(pTm->tm_mon);
6984 jx9_result_string(pCtx, zCur, 3);
6985 break;
6986 case 'n':
6987 /*Numeric representation of a month, without leading zeros*/
6988 jx9_result_string_format(pCtx, "%d", pTm->tm_mon + 1);
6989 break;
6990 case 't':{
6991 static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
6992 int nDays = aMonDays[pTm->tm_mon % 12 ];
6993 if( pTm->tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(pTm->tm_year) ){
6994 nDays = 28;
6995 }
6996 /*Number of days in the given month*/
6997 jx9_result_string_format(pCtx, "%d", nDays);
6998 break;
6999 }
7000 case 'L':{
7001 int isLeap = IS_LEAP_YEAR(pTm->tm_year);
7002 /* Whether it's a leap year */
7003 jx9_result_string_format(pCtx, "%d", isLeap);
7004 break;
7005 }
7006 case 'o':
7007 /* ISO-8601 year number.*/
7008 jx9_result_string_format(pCtx, "%4d", pTm->tm_year);
7009 break;
7010 case 'Y':
7011 /* A full numeric representation of a year, 4 digits */
7012 jx9_result_string_format(pCtx, "%4d", pTm->tm_year);
7013 break;
7014 case 'y':
7015 /*A two digit representation of a year*/
7016 jx9_result_string_format(pCtx, "%02d", pTm->tm_year%100);
7017 break;
7018 case 'a':
7019 /* Lowercase Ante meridiem and Post meridiem */
7020 jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", 2);
7021 break;
7022 case 'A':
7023 /* Uppercase Ante meridiem and Post meridiem */
7024 jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", 2);
7025 break;
7026 case 'g':
7027 /* 12-hour format of an hour without leading zeros*/
7028 jx9_result_string_format(pCtx, "%d", 1+(pTm->tm_hour%12));
7029 break;
7030 case 'G':
7031 /* 24-hour format of an hour without leading zeros */
7032 jx9_result_string_format(pCtx, "%d", pTm->tm_hour);
7033 break;
7034 case 'h':
7035 /* 12-hour format of an hour with leading zeros */
7036 jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12));
7037 break;
7038 case 'H':
7039 /* 24-hour format of an hour with leading zeros */
7040 jx9_result_string_format(pCtx, "%02d", pTm->tm_hour);
7041 break;
7042 case 'i':
7043 /* Minutes with leading zeros */
7044 jx9_result_string_format(pCtx, "%02d", pTm->tm_min);
7045 break;
7046 case 's':
7047 /* second with leading zeros */
7048 jx9_result_string_format(pCtx, "%02d", pTm->tm_sec);
7049 break;
7050 case 'u':
7051 /* Microseconds */
7052 jx9_result_string_format(pCtx, "%u", pTm->tm_sec * SX_USEC_PER_SEC);
7053 break;
7054 case 'S':{
7055 /* English ordinal suffix for the day of the month, 2 characters */
7056 static const char zSuffix[] = "thstndrdthththththth";
7057 int v = pTm->tm_mday;
7058 jx9_result_string(pCtx, &zSuffix[2 * (int)(v / 10 % 10 != 1 ? v % 10 : 0)], (int)sizeof(char) * 2);
7059 break;
7060 }
7061 case 'e':
7062 /* Timezone identifier */
7063 zCur = pTm->tm_zone;
7064 if( zCur == 0 ){
7065 /* Assume GMT */
7066 zCur = "GMT";
7067 }
7068 jx9_result_string(pCtx, zCur, -1);
7069 break;
7070 case 'I':
7071 /* Whether or not the date is in daylight saving time */
7072#ifdef __WINNT__
7073#ifdef _MSC_VER
7074#ifndef _WIN32_WCE
7075 _get_daylight(&pTm->tm_isdst);
7076#endif
7077#endif
7078#endif
7079 jx9_result_string_format(pCtx, "%d", pTm->tm_isdst == 1);
7080 break;
7081 case 'r':
7082 /* RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 */
7083 jx9_result_string_format(pCtx, "%.3s, %02d %.3s %4d %02d:%02d:%02d",
7084 SyTimeGetDay(pTm->tm_wday),
7085 pTm->tm_mday,
7086 SyTimeGetMonth(pTm->tm_mon),
7087 pTm->tm_year,
7088 pTm->tm_hour,
7089 pTm->tm_min,
7090 pTm->tm_sec
7091 );
7092 break;
7093 case 'U':{
7094 time_t tt;
7095 /* Seconds since the Unix Epoch */
7096 time(&tt);
7097 jx9_result_string_format(pCtx, "%u", (unsigned int)tt);
7098 break;
7099 }
7100 case 'O':
7101 case 'P':
7102 /* Difference to Greenwich time (GMT) in hours */
7103 jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff);
7104 break;
7105 case 'Z':
7106 /* Timezone offset in seconds. The offset for timezones west of UTC
7107 * is always negative, and for those east of UTC is always positive.
7108 */
7109 jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff);
7110 break;
7111 case 'c':
7112 /* ISO 8601 date */
7113 jx9_result_string_format(pCtx, "%4d-%02d-%02dT%02d:%02d:%02d%+05d",
7114 pTm->tm_year,
7115 pTm->tm_mon+1,
7116 pTm->tm_mday,
7117 pTm->tm_hour,
7118 pTm->tm_min,
7119 pTm->tm_sec,
7120 pTm->tm_gmtoff
7121 );
7122 break;
7123 case '\\':
7124 zIn++;
7125 /* Expand verbatim */
7126 if( zIn < zEnd ){
7127 jx9_result_string(pCtx, zIn, (int)sizeof(char));
7128 }
7129 break;
7130 default:
7131 /* Unknown format specifer, expand verbatim */
7132 jx9_result_string(pCtx, zIn, (int)sizeof(char));
7133 break;
7134 }
7135 /* Point to the next character */
7136 zIn++;
7137 }
7138 return SXRET_OK;
7139}
7140/*
7141 * JX9 implementation of the strftime() function.
7142 * The following formats are supported:
7143 * %a An abbreviated textual representation of the day
7144 * %A A full textual representation of the day
7145 * %d Two-digit day of the month (with leading zeros)
7146 * %e Day of the month, with a space preceding single digits.
7147 * %j Day of the year, 3 digits with leading zeros
7148 * %u ISO-8601 numeric representation of the day of the week 1 (for Monday) though 7 (for Sunday)
7149 * %w Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday)
7150 * %U Week number of the given year, starting with the first Sunday as the first week
7151 * %V ISO-8601:1988 week number of the given year, starting with the first week of the year with at least
7152 * 4 weekdays, with Monday being the start of the week.
7153 * %W A numeric representation of the week of the year
7154 * %b Abbreviated month name, based on the locale
7155 * %B Full month name, based on the locale
7156 * %h Abbreviated month name, based on the locale (an alias of %b)
7157 * %m Two digit representation of the month
7158 * %C Two digit representation of the century (year divided by 100, truncated to an integer)
7159 * %g Two digit representation of the year going by ISO-8601:1988 standards (see %V)
7160 * %G The full four-digit version of %g
7161 * %y Two digit representation of the year
7162 * %Y Four digit representation for the year
7163 * %H Two digit representation of the hour in 24-hour format
7164 * %I Two digit representation of the hour in 12-hour format
7165 * %l (lower-case 'L') Hour in 12-hour format, with a space preceeding single digits
7166 * %M Two digit representation of the minute
7167 * %p UPPER-CASE 'AM' or 'PM' based on the given time
7168 * %P lower-case 'am' or 'pm' based on the given time
7169 * %r Same as "%I:%M:%S %p"
7170 * %R Same as "%H:%M"
7171 * %S Two digit representation of the second
7172 * %T Same as "%H:%M:%S"
7173 * %X Preferred time representation based on locale, without the date
7174 * %z Either the time zone offset from UTC or the abbreviation
7175 * %Z The time zone offset/abbreviation option NOT given by %z
7176 * %c Preferred date and time stamp based on local
7177 * %D Same as "%m/%d/%y"
7178 * %F Same as "%Y-%m-%d"
7179 * %s Unix Epoch Time timestamp (same as the time() function)
7180 * %x Preferred date representation based on locale, without the time
7181 * %n A newline character ("\n")
7182 * %t A Tab character ("\t")
7183 * %% A literal percentage character ("%")
7184 */
7185static int jx9Strftime(
7186 jx9_context *pCtx, /* Call context */
7187 const char *zIn, /* Input string */
7188 int nLen, /* Input length */
7189 Sytm *pTm /* Parse of the given time */
7190 )
7191{
7192 const char *zCur, *zEnd = &zIn[nLen];
7193 int c;
7194 /* Start the format process */
7195 for(;;){
7196 zCur = zIn;
7197 while(zIn < zEnd && zIn[0] != '%' ){
7198 zIn++;
7199 }
7200 if( zIn > zCur ){
7201 /* Consume input verbatim */
7202 jx9_result_string(pCtx, zCur, (int)(zIn-zCur));
7203 }
7204 zIn++; /* Jump the percent sign */
7205 if( zIn >= zEnd ){
7206 /* No more input to process */
7207 break;
7208 }
7209 c = zIn[0];
7210 /* Act according to the current specifer */
7211 switch(c){
7212 case '%':
7213 /* A literal percentage character ("%") */
7214 jx9_result_string(pCtx, "%", (int)sizeof(char));
7215 break;
7216 case 't':
7217 /* A Tab character */
7218 jx9_result_string(pCtx, "\t", (int)sizeof(char));
7219 break;
7220 case 'n':
7221 /* A newline character */
7222 jx9_result_string(pCtx, "\n", (int)sizeof(char));
7223 break;
7224 case 'a':
7225 /* An abbreviated textual representation of the day */
7226 jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), (int)sizeof(char)*3);
7227 break;
7228 case 'A':
7229 /* A full textual representation of the day */
7230 jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), -1/*Compute length automatically*/);
7231 break;
7232 case 'e':
7233 /* Day of the month, 2 digits with leading space for single digit*/
7234 jx9_result_string_format(pCtx, "%2d", pTm->tm_mday);
7235 break;
7236 case 'd':
7237 /* Two-digit day of the month (with leading zeros) */
7238 jx9_result_string_format(pCtx, "%02d", pTm->tm_mon+1);
7239 break;
7240 case 'j':
7241 /*The day of the year, 3 digits with leading zeros*/
7242 jx9_result_string_format(pCtx, "%03d", pTm->tm_yday);
7243 break;
7244 case 'u':
7245 /* ISO-8601 numeric representation of the day of the week */
7246 jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]);
7247 break;
7248 case 'w':
7249 /* Numeric representation of the day of the week */
7250 jx9_result_string_format(pCtx, "%d", pTm->tm_wday);
7251 break;
7252 case 'b':
7253 case 'h':
7254 /*A short textual representation of a month, three letters (Not based on locale)*/
7255 jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), (int)sizeof(char)*3);
7256 break;
7257 case 'B':
7258 /* Full month name (Not based on locale) */
7259 jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), -1/*Compute length automatically*/);
7260 break;
7261 case 'm':
7262 /*Numeric representation of a month, with leading zeros*/
7263 jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1);
7264 break;
7265 case 'C':
7266 /* Two digit representation of the century */
7267 jx9_result_string_format(pCtx, "%2d", pTm->tm_year/100);
7268 break;
7269 case 'y':
7270 case 'g':
7271 /* Two digit representation of the year */
7272 jx9_result_string_format(pCtx, "%2d", pTm->tm_year%100);
7273 break;
7274 case 'Y':
7275 case 'G':
7276 /* Four digit representation of the year */
7277 jx9_result_string_format(pCtx, "%4d", pTm->tm_year);
7278 break;
7279 case 'I':
7280 /* 12-hour format of an hour with leading zeros */
7281 jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12));
7282 break;
7283 case 'l':
7284 /* 12-hour format of an hour with leading space */
7285 jx9_result_string_format(pCtx, "%2d", 1+(pTm->tm_hour%12));
7286 break;
7287 case 'H':
7288 /* 24-hour format of an hour with leading zeros */
7289 jx9_result_string_format(pCtx, "%02d", pTm->tm_hour);
7290 break;
7291 case 'M':
7292 /* Minutes with leading zeros */
7293 jx9_result_string_format(pCtx, "%02d", pTm->tm_min);
7294 break;
7295 case 'S':
7296 /* Seconds with leading zeros */
7297 jx9_result_string_format(pCtx, "%02d", pTm->tm_sec);
7298 break;
7299 case 'z':
7300 case 'Z':
7301 /* Timezone identifier */
7302 zCur = pTm->tm_zone;
7303 if( zCur == 0 ){
7304 /* Assume GMT */
7305 zCur = "GMT";
7306 }
7307 jx9_result_string(pCtx, zCur, -1);
7308 break;
7309 case 'T':
7310 case 'X':
7311 /* Same as "%H:%M:%S" */
7312 jx9_result_string_format(pCtx, "%02d:%02d:%02d", pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
7313 break;
7314 case 'R':
7315 /* Same as "%H:%M" */
7316 jx9_result_string_format(pCtx, "%02d:%02d", pTm->tm_hour, pTm->tm_min);
7317 break;
7318 case 'P':
7319 /* Lowercase Ante meridiem and Post meridiem */
7320 jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", (int)sizeof(char)*2);
7321 break;
7322 case 'p':
7323 /* Uppercase Ante meridiem and Post meridiem */
7324 jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", (int)sizeof(char)*2);
7325 break;
7326 case 'r':
7327 /* Same as "%I:%M:%S %p" */
7328 jx9_result_string_format(pCtx, "%02d:%02d:%02d %s",
7329 1+(pTm->tm_hour%12),
7330 pTm->tm_min,
7331 pTm->tm_sec,
7332 pTm->tm_hour > 12 ? "PM" : "AM"
7333 );
7334 break;
7335 case 'D':
7336 case 'x':
7337 /* Same as "%m/%d/%y" */
7338 jx9_result_string_format(pCtx, "%02d/%02d/%02d",
7339 pTm->tm_mon+1,
7340 pTm->tm_mday,
7341 pTm->tm_year%100
7342 );
7343 break;
7344 case 'F':
7345 /* Same as "%Y-%m-%d" */
7346 jx9_result_string_format(pCtx, "%d-%02d-%02d",
7347 pTm->tm_year,
7348 pTm->tm_mon+1,
7349 pTm->tm_mday
7350 );
7351 break;
7352 case 'c':
7353 jx9_result_string_format(pCtx, "%d-%02d-%02d %02d:%02d:%02d",
7354 pTm->tm_year,
7355 pTm->tm_mon+1,
7356 pTm->tm_mday,
7357 pTm->tm_hour,
7358 pTm->tm_min,
7359 pTm->tm_sec
7360 );
7361 break;
7362 case 's':{
7363 time_t tt;
7364 /* Seconds since the Unix Epoch */
7365 time(&tt);
7366 jx9_result_string_format(pCtx, "%u", (unsigned int)tt);
7367 break;
7368 }
7369 default:
7370 /* unknown specifer, simply ignore*/
7371 break;
7372 }
7373 /* Advance the cursor */
7374 zIn++;
7375 }
7376 return SXRET_OK;
7377}
7378/*
7379 * string date(string $format [, int $timestamp = time() ] )
7380 * Returns a string formatted according to the given format string using
7381 * the given integer timestamp or the current time if no timestamp is given.
7382 * In other words, timestamp is optional and defaults to the value of time().
7383 * Parameters
7384 * $format
7385 * The format of the outputted date string (See code above)
7386 * $timestamp
7387 * The optional timestamp parameter is an integer Unix timestamp
7388 * that defaults to the current local time if a timestamp is not given.
7389 * In other words, it defaults to the value of time().
7390 * Return
7391 * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned.
7392 */
7393static int jx9Builtin_date(jx9_context *pCtx, int nArg, jx9_value **apArg)
7394{
7395 const char *zFormat;
7396 int nLen;
7397 Sytm sTm;
7398 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
7399 /* Missing/Invalid argument, return FALSE */
7400 jx9_result_bool(pCtx, 0);
7401 return JX9_OK;
7402 }
7403 zFormat = jx9_value_to_string(apArg[0], &nLen);
7404 if( nLen < 1 ){
7405 /* Don't bother processing return the empty string */
7406 jx9_result_string(pCtx, "", 0);
7407 }
7408 if( nArg < 2 ){
7409#ifdef __WINNT__
7410 SYSTEMTIME sOS;
7411 GetSystemTime(&sOS);
7412 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
7413#else
7414 struct tm *pTm;
7415 time_t t;
7416 time(&t);
7417 pTm = localtime(&t);
7418 STRUCT_TM_TO_SYTM(pTm, &sTm);
7419#endif
7420 }else{
7421 /* Use the given timestamp */
7422 time_t t;
7423 struct tm *pTm;
7424 if( jx9_value_is_int(apArg[1]) ){
7425 t = (time_t)jx9_value_to_int64(apArg[1]);
7426 pTm = localtime(&t);
7427 if( pTm == 0 ){
7428 time(&t);
7429 }
7430 }else{
7431 time(&t);
7432 }
7433 pTm = localtime(&t);
7434 STRUCT_TM_TO_SYTM(pTm, &sTm);
7435 }
7436 /* Format the given string */
7437 DateFormat(pCtx, zFormat, nLen, &sTm);
7438 return JX9_OK;
7439}
7440/*
7441 * string strftime(string $format [, int $timestamp = time() ] )
7442 * Format a local time/date (PLATFORM INDEPENDANT IMPLEENTATION NOT BASED ON LOCALE)
7443 * Parameters
7444 * $format
7445 * The format of the outputted date string (See code above)
7446 * $timestamp
7447 * The optional timestamp parameter is an integer Unix timestamp
7448 * that defaults to the current local time if a timestamp is not given.
7449 * In other words, it defaults to the value of time().
7450 * Return
7451 * Returns a string formatted according format using the given timestamp
7452 * or the current local time if no timestamp is given.
7453 */
7454static int jx9Builtin_strftime(jx9_context *pCtx, int nArg, jx9_value **apArg)
7455{
7456 const char *zFormat;
7457 int nLen;
7458 Sytm sTm;
7459 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
7460 /* Missing/Invalid argument, return FALSE */
7461 jx9_result_bool(pCtx, 0);
7462 return JX9_OK;
7463 }
7464 zFormat = jx9_value_to_string(apArg[0], &nLen);
7465 if( nLen < 1 ){
7466 /* Don't bother processing return FALSE */
7467 jx9_result_bool(pCtx, 0);
7468 }
7469 if( nArg < 2 ){
7470#ifdef __WINNT__
7471 SYSTEMTIME sOS;
7472 GetSystemTime(&sOS);
7473 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
7474#else
7475 struct tm *pTm;
7476 time_t t;
7477 time(&t);
7478 pTm = localtime(&t);
7479 STRUCT_TM_TO_SYTM(pTm, &sTm);
7480#endif
7481 }else{
7482 /* Use the given timestamp */
7483 time_t t;
7484 struct tm *pTm;
7485 if( jx9_value_is_int(apArg[1]) ){
7486 t = (time_t)jx9_value_to_int64(apArg[1]);
7487 pTm = localtime(&t);
7488 if( pTm == 0 ){
7489 time(&t);
7490 }
7491 }else{
7492 time(&t);
7493 }
7494 pTm = localtime(&t);
7495 STRUCT_TM_TO_SYTM(pTm, &sTm);
7496 }
7497 /* Format the given string */
7498 jx9Strftime(pCtx, zFormat, nLen, &sTm);
7499 if( jx9_context_result_buf_length(pCtx) < 1 ){
7500 /* Nothing was formatted, return FALSE */
7501 jx9_result_bool(pCtx, 0);
7502 }
7503 return JX9_OK;
7504}
7505/*
7506 * string gmdate(string $format [, int $timestamp = time() ] )
7507 * Identical to the date() function except that the time returned
7508 * is Greenwich Mean Time (GMT).
7509 * Parameters
7510 * $format
7511 * The format of the outputted date string (See code above)
7512 * $timestamp
7513 * The optional timestamp parameter is an integer Unix timestamp
7514 * that defaults to the current local time if a timestamp is not given.
7515 * In other words, it defaults to the value of time().
7516 * Return
7517 * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned.
7518 */
7519static int jx9Builtin_gmdate(jx9_context *pCtx, int nArg, jx9_value **apArg)
7520{
7521 const char *zFormat;
7522 int nLen;
7523 Sytm sTm;
7524 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
7525 /* Missing/Invalid argument, return FALSE */
7526 jx9_result_bool(pCtx, 0);
7527 return JX9_OK;
7528 }
7529 zFormat = jx9_value_to_string(apArg[0], &nLen);
7530 if( nLen < 1 ){
7531 /* Don't bother processing return the empty string */
7532 jx9_result_string(pCtx, "", 0);
7533 }
7534 if( nArg < 2 ){
7535#ifdef __WINNT__
7536 SYSTEMTIME sOS;
7537 GetSystemTime(&sOS);
7538 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
7539#else
7540 struct tm *pTm;
7541 time_t t;
7542 time(&t);
7543 pTm = gmtime(&t);
7544 STRUCT_TM_TO_SYTM(pTm, &sTm);
7545#endif
7546 }else{
7547 /* Use the given timestamp */
7548 time_t t;
7549 struct tm *pTm;
7550 if( jx9_value_is_int(apArg[1]) ){
7551 t = (time_t)jx9_value_to_int64(apArg[1]);
7552 pTm = gmtime(&t);
7553 if( pTm == 0 ){
7554 time(&t);
7555 }
7556 }else{
7557 time(&t);
7558 }
7559 pTm = gmtime(&t);
7560 STRUCT_TM_TO_SYTM(pTm, &sTm);
7561 }
7562 /* Format the given string */
7563 DateFormat(pCtx, zFormat, nLen, &sTm);
7564 return JX9_OK;
7565}
7566/*
7567 * array localtime([ int $timestamp = time() [, bool $is_associative = false ]])
7568 * Return the local time.
7569 * Parameter
7570 * $timestamp: The optional timestamp parameter is an integer Unix timestamp
7571 * that defaults to the current local time if a timestamp is not given.
7572 * In other words, it defaults to the value of time().
7573 * $is_associative
7574 * If set to FALSE or not supplied then the array is returned as a regular, numerically
7575 * indexed array. If the argument is set to TRUE then localtime() returns an associative
7576 * array containing all the different elements of the structure returned by the C function
7577 * call to localtime. The names of the different keys of the associative array are as follows:
7578 * "tm_sec" - seconds, 0 to 59
7579 * "tm_min" - minutes, 0 to 59
7580 * "tm_hour" - hours, 0 to 23
7581 * "tm_mday" - day of the month, 1 to 31
7582 * "tm_mon" - month of the year, 0 (Jan) to 11 (Dec)
7583 * "tm_year" - years since 1900
7584 * "tm_wday" - day of the week, 0 (Sun) to 6 (Sat)
7585 * "tm_yday" - day of the year, 0 to 365
7586 * "tm_isdst" - is daylight savings time in effect? Positive if yes, 0 if not, negative if unknown.
7587 * Returns
7588 * An associative array of information related to the timestamp.
7589 */
7590static int jx9Builtin_localtime(jx9_context *pCtx, int nArg, jx9_value **apArg)
7591{
7592 jx9_value *pValue, *pArray;
7593 int isAssoc = 0;
7594 Sytm sTm;
7595 if( nArg < 1 ){
7596#ifdef __WINNT__
7597 SYSTEMTIME sOS;
7598 GetSystemTime(&sOS); /* TODO(chems): GMT not local */
7599 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
7600#else
7601 struct tm *pTm;
7602 time_t t;
7603 time(&t);
7604 pTm = localtime(&t);
7605 STRUCT_TM_TO_SYTM(pTm, &sTm);
7606#endif
7607 }else{
7608 /* Use the given timestamp */
7609 time_t t;
7610 struct tm *pTm;
7611 if( jx9_value_is_int(apArg[0]) ){
7612 t = (time_t)jx9_value_to_int64(apArg[0]);
7613 pTm = localtime(&t);
7614 if( pTm == 0 ){
7615 time(&t);
7616 }
7617 }else{
7618 time(&t);
7619 }
7620 pTm = localtime(&t);
7621 STRUCT_TM_TO_SYTM(pTm, &sTm);
7622 }
7623 /* Element value */
7624 pValue = jx9_context_new_scalar(pCtx);
7625 if( pValue == 0 ){
7626 /* Return NULL */
7627 jx9_result_null(pCtx);
7628 return JX9_OK;
7629 }
7630 /* Create a new array */
7631 pArray = jx9_context_new_array(pCtx);
7632 if( pArray == 0 ){
7633 /* Return NULL */
7634 jx9_result_null(pCtx);
7635 return JX9_OK;
7636 }
7637 if( nArg > 1 ){
7638 isAssoc = jx9_value_to_bool(apArg[1]);
7639 }
7640 /* Fill the array */
7641 /* Seconds */
7642 jx9_value_int(pValue, sTm.tm_sec);
7643 if( isAssoc ){
7644 jx9_array_add_strkey_elem(pArray, "tm_sec", pValue);
7645 }else{
7646 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7647 }
7648 /* Minutes */
7649 jx9_value_int(pValue, sTm.tm_min);
7650 if( isAssoc ){
7651 jx9_array_add_strkey_elem(pArray, "tm_min", pValue);
7652 }else{
7653 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7654 }
7655 /* Hours */
7656 jx9_value_int(pValue, sTm.tm_hour);
7657 if( isAssoc ){
7658 jx9_array_add_strkey_elem(pArray, "tm_hour", pValue);
7659 }else{
7660 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7661 }
7662 /* mday */
7663 jx9_value_int(pValue, sTm.tm_mday);
7664 if( isAssoc ){
7665 jx9_array_add_strkey_elem(pArray, "tm_mday", pValue);
7666 }else{
7667 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7668 }
7669 /* mon */
7670 jx9_value_int(pValue, sTm.tm_mon);
7671 if( isAssoc ){
7672 jx9_array_add_strkey_elem(pArray, "tm_mon", pValue);
7673 }else{
7674 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7675 }
7676 /* year since 1900 */
7677 jx9_value_int(pValue, sTm.tm_year-1900);
7678 if( isAssoc ){
7679 jx9_array_add_strkey_elem(pArray, "tm_year", pValue);
7680 }else{
7681 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7682 }
7683 /* wday */
7684 jx9_value_int(pValue, sTm.tm_wday);
7685 if( isAssoc ){
7686 jx9_array_add_strkey_elem(pArray, "tm_wday", pValue);
7687 }else{
7688 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7689 }
7690 /* yday */
7691 jx9_value_int(pValue, sTm.tm_yday);
7692 if( isAssoc ){
7693 jx9_array_add_strkey_elem(pArray, "tm_yday", pValue);
7694 }else{
7695 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7696 }
7697 /* isdst */
7698#ifdef __WINNT__
7699#ifdef _MSC_VER
7700#ifndef _WIN32_WCE
7701 _get_daylight(&sTm.tm_isdst);
7702#endif
7703#endif
7704#endif
7705 jx9_value_int(pValue, sTm.tm_isdst);
7706 if( isAssoc ){
7707 jx9_array_add_strkey_elem(pArray, "tm_isdst", pValue);
7708 }else{
7709 jx9_array_add_elem(pArray, 0/* Automatic index */, pValue);
7710 }
7711 /* Return the array */
7712 jx9_result_value(pCtx, pArray);
7713 return JX9_OK;
7714}
7715/*
7716 * int idate(string $format [, int $timestamp = time() ])
7717 * Returns a number formatted according to the given format string
7718 * using the given integer timestamp or the current local time if
7719 * no timestamp is given. In other words, timestamp is optional and defaults
7720 * to the value of time().
7721 * Unlike the function date(), idate() accepts just one char in the format
7722 * parameter.
7723 * $Parameters
7724 * Supported format
7725 * d Day of the month
7726 * h Hour (12 hour format)
7727 * H Hour (24 hour format)
7728 * i Minutes
7729 * I (uppercase i)1 if DST is activated, 0 otherwise
7730 * L (uppercase l) returns 1 for leap year, 0 otherwise
7731 * m Month number
7732 * s Seconds
7733 * t Days in current month
7734 * U Seconds since the Unix Epoch - January 1 1970 00:00:00 UTC - this is the same as time()
7735 * w Day of the week (0 on Sunday)
7736 * W ISO-8601 week number of year, weeks starting on Monday
7737 * y Year (1 or 2 digits - check note below)
7738 * Y Year (4 digits)
7739 * z Day of the year
7740 * Z Timezone offset in seconds
7741 * $timestamp
7742 * The optional timestamp parameter is an integer Unix timestamp that defaults
7743 * to the current local time if a timestamp is not given. In other words, it defaults
7744 * to the value of time().
7745 * Return
7746 * An integer.
7747 */
7748static int jx9Builtin_idate(jx9_context *pCtx, int nArg, jx9_value **apArg)
7749{
7750 const char *zFormat;
7751 jx9_int64 iVal = 0;
7752 int nLen;
7753 Sytm sTm;
7754 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
7755 /* Missing/Invalid argument, return -1 */
7756 jx9_result_int(pCtx, -1);
7757 return JX9_OK;
7758 }
7759 zFormat = jx9_value_to_string(apArg[0], &nLen);
7760 if( nLen < 1 ){
7761 /* Don't bother processing return -1*/
7762 jx9_result_int(pCtx, -1);
7763 }
7764 if( nArg < 2 ){
7765#ifdef __WINNT__
7766 SYSTEMTIME sOS;
7767 GetSystemTime(&sOS);
7768 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
7769#else
7770 struct tm *pTm;
7771 time_t t;
7772 time(&t);
7773 pTm = localtime(&t);
7774 STRUCT_TM_TO_SYTM(pTm, &sTm);
7775#endif
7776 }else{
7777 /* Use the given timestamp */
7778 time_t t;
7779 struct tm *pTm;
7780 if( jx9_value_is_int(apArg[1]) ){
7781 t = (time_t)jx9_value_to_int64(apArg[1]);
7782 pTm = localtime(&t);
7783 if( pTm == 0 ){
7784 time(&t);
7785 }
7786 }else{
7787 time(&t);
7788 }
7789 pTm = localtime(&t);
7790 STRUCT_TM_TO_SYTM(pTm, &sTm);
7791 }
7792 /* Perform the requested operation */
7793 switch(zFormat[0]){
7794 case 'd':
7795 /* Day of the month */
7796 iVal = sTm.tm_mday;
7797 break;
7798 case 'h':
7799 /* Hour (12 hour format)*/
7800 iVal = 1 + (sTm.tm_hour % 12);
7801 break;
7802 case 'H':
7803 /* Hour (24 hour format)*/
7804 iVal = sTm.tm_hour;
7805 break;
7806 case 'i':
7807 /*Minutes*/
7808 iVal = sTm.tm_min;
7809 break;
7810 case 'I':
7811 /* returns 1 if DST is activated, 0 otherwise */
7812#ifdef __WINNT__
7813#ifdef _MSC_VER
7814#ifndef _WIN32_WCE
7815 _get_daylight(&sTm.tm_isdst);
7816#endif
7817#endif
7818#endif
7819 iVal = sTm.tm_isdst;
7820 break;
7821 case 'L':
7822 /* returns 1 for leap year, 0 otherwise */
7823 iVal = IS_LEAP_YEAR(sTm.tm_year);
7824 break;
7825 case 'm':
7826 /* Month number*/
7827 iVal = sTm.tm_mon;
7828 break;
7829 case 's':
7830 /*Seconds*/
7831 iVal = sTm.tm_sec;
7832 break;
7833 case 't':{
7834 /*Days in current month*/
7835 static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
7836 int nDays = aMonDays[sTm.tm_mon % 12 ];
7837 if( sTm.tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(sTm.tm_year) ){
7838 nDays = 28;
7839 }
7840 iVal = nDays;
7841 break;
7842 }
7843 case 'U':
7844 /*Seconds since the Unix Epoch*/
7845 iVal = (jx9_int64)time(0);
7846 break;
7847 case 'w':
7848 /* Day of the week (0 on Sunday) */
7849 iVal = sTm.tm_wday;
7850 break;
7851 case 'W': {
7852 /* ISO-8601 week number of year, weeks starting on Monday */
7853 static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 };
7854 iVal = aISO8601[sTm.tm_wday % 7 ];
7855 break;
7856 }
7857 case 'y':
7858 /* Year (2 digits) */
7859 iVal = sTm.tm_year % 100;
7860 break;
7861 case 'Y':
7862 /* Year (4 digits) */
7863 iVal = sTm.tm_year;
7864 break;
7865 case 'z':
7866 /* Day of the year */
7867 iVal = sTm.tm_yday;
7868 break;
7869 case 'Z':
7870 /*Timezone offset in seconds*/
7871 iVal = sTm.tm_gmtoff;
7872 break;
7873 default:
7874 /* unknown format, throw a warning */
7875 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Unknown date format token");
7876 break;
7877 }
7878 /* Return the time value */
7879 jx9_result_int64(pCtx, iVal);
7880 return JX9_OK;
7881}
7882/*
7883 * int mktime/gmmktime([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s")
7884 * [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] )
7885 * Returns the Unix timestamp corresponding to the arguments given. This timestamp is a 64bit integer
7886 * containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time
7887 * specified.
7888 * Arguments may be left out in order from right to left; any arguments thus omitted will be set to
7889 * the current value according to the local date and time.
7890 * Parameters
7891 * $hour
7892 * The number of the hour relevant to the start of the day determined by month, day and year.
7893 * Negative values reference the hour before midnight of the day in question. Values greater
7894 * than 23 reference the appropriate hour in the following day(s).
7895 * $minute
7896 * The number of the minute relevant to the start of the hour. Negative values reference
7897 * the minute in the previous hour. Values greater than 59 reference the appropriate minute
7898 * in the following hour(s).
7899 * $second
7900 * The number of seconds relevant to the start of the minute. Negative values reference
7901 * the second in the previous minute. Values greater than 59 reference the appropriate
7902 * second in the following minute(s).
7903 * $month
7904 * The number of the month relevant to the end of the previous year. Values 1 to 12 reference
7905 * the normal calendar months of the year in question. Values less than 1 (including negative values)
7906 * reference the months in the previous year in reverse order, so 0 is December, -1 is November)...
7907 * $day
7908 * The number of the day relevant to the end of the previous month. Values 1 to 28, 29, 30 or 31
7909 * (depending upon the month) reference the normal days in the relevant month. Values less than 1
7910 * (including negative values) reference the days in the previous month, so 0 is the last day
7911 * of the previous month, -1 is the day before that, etc. Values greater than the number of days
7912 * in the relevant month reference the appropriate day in the following month(s).
7913 * $year
7914 * The number of the year, may be a two or four digit value, with values between 0-69 mapping
7915 * to 2000-2069 and 70-100 to 1970-2000. On systems where time_t is a 32bit signed integer, as
7916 * most common today, the valid range for year is somewhere between 1901 and 2038.
7917 * $is_dst
7918 * This parameter can be set to 1 if the time is during daylight savings time (DST), 0 if it is not,
7919 * or -1 (the default) if it is unknown whether the time is within daylight savings time or not.
7920 * Return
7921 * mktime() returns the Unix timestamp of the arguments given.
7922 * If the arguments are invalid, the function returns FALSE
7923 */
7924static int jx9Builtin_mktime(jx9_context *pCtx, int nArg, jx9_value **apArg)
7925{
7926 const char *zFunction;
7927 jx9_int64 iVal = 0;
7928 struct tm *pTm;
7929 time_t t;
7930 /* Extract function name */
7931 zFunction = jx9_function_name(pCtx);
7932 /* Get the current time */
7933 time(&t);
7934 if( zFunction[0] == 'g' /* gmmktime */ ){
7935 pTm = gmtime(&t);
7936 }else{
7937 /* localtime */
7938 pTm = localtime(&t);
7939 }
7940 if( nArg > 0 ){
7941 int iVal;
7942 /* Hour */
7943 iVal = jx9_value_to_int(apArg[0]);
7944 pTm->tm_hour = iVal;
7945 if( nArg > 1 ){
7946 /* Minutes */
7947 iVal = jx9_value_to_int(apArg[1]);
7948 pTm->tm_min = iVal;
7949 if( nArg > 2 ){
7950 /* Seconds */
7951 iVal = jx9_value_to_int(apArg[2]);
7952 pTm->tm_sec = iVal;
7953 if( nArg > 3 ){
7954 /* Month */
7955 iVal = jx9_value_to_int(apArg[3]);
7956 pTm->tm_mon = iVal - 1;
7957 if( nArg > 4 ){
7958 /* mday */
7959 iVal = jx9_value_to_int(apArg[4]);
7960 pTm->tm_mday = iVal;
7961 if( nArg > 5 ){
7962 /* Year */
7963 iVal = jx9_value_to_int(apArg[5]);
7964 if( iVal > 1900 ){
7965 iVal -= 1900;
7966 }
7967 pTm->tm_year = iVal;
7968 if( nArg > 6 ){
7969 /* is_dst */
7970 iVal = jx9_value_to_bool(apArg[6]);
7971 pTm->tm_isdst = iVal;
7972 }
7973 }
7974 }
7975 }
7976 }
7977 }
7978 }
7979 /* Make the time */
7980 iVal = (jx9_int64)mktime(pTm);
7981 /* Return the timesatmp as a 64bit integer */
7982 jx9_result_int64(pCtx, iVal);
7983 return JX9_OK;
7984}
7985/*
7986 * Section:
7987 * URL handling Functions.
7988 * Authors:
7989 * Symisc Systems, devel@symisc.net.
7990 * Copyright (C) Symisc Systems, http://jx9.symisc.net
7991 * Status:
7992 * Stable.
7993 */
7994/*
7995 * Output consumer callback for the standard Symisc routines.
7996 * [i.e: SyBase64Encode(), SyBase64Decode(), SyUriEncode(), ...].
7997 */
7998static int Consumer(const void *pData, unsigned int nLen, void *pUserData)
7999{
8000 /* Store in the call context result buffer */
8001 jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen);
8002 return SXRET_OK;
8003}
8004/*
8005 * string base64_encode(string $data)
8006 * string convert_uuencode(string $data)
8007 * Encodes data with MIME base64
8008 * Parameter
8009 * $data
8010 * Data to encode
8011 * Return
8012 * Encoded data or FALSE on failure.
8013 */
8014static int jx9Builtin_base64_encode(jx9_context *pCtx, int nArg, jx9_value **apArg)
8015{
8016 const char *zIn;
8017 int nLen;
8018 if( nArg < 1 ){
8019 /* Missing arguments, return FALSE */
8020 jx9_result_bool(pCtx, 0);
8021 return JX9_OK;
8022 }
8023 /* Extract the input string */
8024 zIn = jx9_value_to_string(apArg[0], &nLen);
8025 if( nLen < 1 ){
8026 /* Nothing to process, return FALSE */
8027 jx9_result_bool(pCtx, 0);
8028 return JX9_OK;
8029 }
8030 /* Perform the BASE64 encoding */
8031 SyBase64Encode(zIn, (sxu32)nLen, Consumer, pCtx);
8032 return JX9_OK;
8033}
8034/*
8035 * string base64_decode(string $data)
8036 * string convert_uudecode(string $data)
8037 * Decodes data encoded with MIME base64
8038 * Parameter
8039 * $data
8040 * Encoded data.
8041 * Return
8042 * Returns the original data or FALSE on failure.
8043 */
8044static int jx9Builtin_base64_decode(jx9_context *pCtx, int nArg, jx9_value **apArg)
8045{
8046 const char *zIn;
8047 int nLen;
8048 if( nArg < 1 ){
8049 /* Missing arguments, return FALSE */
8050 jx9_result_bool(pCtx, 0);
8051 return JX9_OK;
8052 }
8053 /* Extract the input string */
8054 zIn = jx9_value_to_string(apArg[0], &nLen);
8055 if( nLen < 1 ){
8056 /* Nothing to process, return FALSE */
8057 jx9_result_bool(pCtx, 0);
8058 return JX9_OK;
8059 }
8060 /* Perform the BASE64 decoding */
8061 SyBase64Decode(zIn, (sxu32)nLen, Consumer, pCtx);
8062 return JX9_OK;
8063}
8064/*
8065 * string urlencode(string $str)
8066 * URL encoding
8067 * Parameter
8068 * $data
8069 * Input string.
8070 * Return
8071 * Returns a string in which all non-alphanumeric characters except -_. have
8072 * been replaced with a percent (%) sign followed by two hex digits and spaces
8073 * encoded as plus (+) signs.
8074 */
8075static int jx9Builtin_urlencode(jx9_context *pCtx, int nArg, jx9_value **apArg)
8076{
8077 const char *zIn;
8078 int nLen;
8079 if( nArg < 1 ){
8080 /* Missing arguments, return FALSE */
8081 jx9_result_bool(pCtx, 0);
8082 return JX9_OK;
8083 }
8084 /* Extract the input string */
8085 zIn = jx9_value_to_string(apArg[0], &nLen);
8086 if( nLen < 1 ){
8087 /* Nothing to process, return FALSE */
8088 jx9_result_bool(pCtx, 0);
8089 return JX9_OK;
8090 }
8091 /* Perform the URL encoding */
8092 SyUriEncode(zIn, (sxu32)nLen, Consumer, pCtx);
8093 return JX9_OK;
8094}
8095/*
8096 * string urldecode(string $str)
8097 * Decodes any %## encoding in the given string.
8098 * Plus symbols ('+') are decoded to a space character.
8099 * Parameter
8100 * $data
8101 * Input string.
8102 * Return
8103 * Decoded URL or FALSE on failure.
8104 */
8105static int jx9Builtin_urldecode(jx9_context *pCtx, int nArg, jx9_value **apArg)
8106{
8107 const char *zIn;
8108 int nLen;
8109 if( nArg < 1 ){
8110 /* Missing arguments, return FALSE */
8111 jx9_result_bool(pCtx, 0);
8112 return JX9_OK;
8113 }
8114 /* Extract the input string */
8115 zIn = jx9_value_to_string(apArg[0], &nLen);
8116 if( nLen < 1 ){
8117 /* Nothing to process, return FALSE */
8118 jx9_result_bool(pCtx, 0);
8119 return JX9_OK;
8120 }
8121 /* Perform the URL decoding */
8122 SyUriDecode(zIn, (sxu32)nLen, Consumer, pCtx, TRUE);
8123 return JX9_OK;
8124}
8125#endif /* JX9_DISABLE_BUILTIN_FUNC */
8126/* Table of the built-in functions */
8127static const jx9_builtin_func aBuiltInFunc[] = {
8128 /* Variable handling functions */
8129 { "is_bool" , jx9Builtin_is_bool },
8130 { "is_float" , jx9Builtin_is_float },
8131 { "is_real" , jx9Builtin_is_float },
8132 { "is_double" , jx9Builtin_is_float },
8133 { "is_int" , jx9Builtin_is_int },
8134 { "is_integer" , jx9Builtin_is_int },
8135 { "is_long" , jx9Builtin_is_int },
8136 { "is_string" , jx9Builtin_is_string },
8137 { "is_null" , jx9Builtin_is_null },
8138 { "is_numeric" , jx9Builtin_is_numeric },
8139 { "is_scalar" , jx9Builtin_is_scalar },
8140 { "is_array" , jx9Builtin_is_array },
8141 { "is_object" , jx9Builtin_is_object },
8142 { "is_resource", jx9Builtin_is_resource },
8143 { "douleval" , jx9Builtin_floatval },
8144 { "floatval" , jx9Builtin_floatval },
8145 { "intval" , jx9Builtin_intval },
8146 { "strval" , jx9Builtin_strval },
8147 { "empty" , jx9Builtin_empty },
8148#ifndef JX9_DISABLE_BUILTIN_FUNC
8149#ifdef JX9_ENABLE_MATH_FUNC
8150 /* Math functions */
8151 { "abs" , jx9Builtin_abs },
8152 { "sqrt" , jx9Builtin_sqrt },
8153 { "exp" , jx9Builtin_exp },
8154 { "floor", jx9Builtin_floor },
8155 { "cos" , jx9Builtin_cos },
8156 { "sin" , jx9Builtin_sin },
8157 { "acos" , jx9Builtin_acos },
8158 { "asin" , jx9Builtin_asin },
8159 { "cosh" , jx9Builtin_cosh },
8160 { "sinh" , jx9Builtin_sinh },
8161 { "ceil" , jx9Builtin_ceil },
8162 { "tan" , jx9Builtin_tan },
8163 { "tanh" , jx9Builtin_tanh },
8164 { "atan" , jx9Builtin_atan },
8165 { "atan2", jx9Builtin_atan2 },
8166 { "log" , jx9Builtin_log },
8167 { "log10" , jx9Builtin_log10 },
8168 { "pow" , jx9Builtin_pow },
8169 { "pi", jx9Builtin_pi },
8170 { "fmod", jx9Builtin_fmod },
8171 { "hypot", jx9Builtin_hypot },
8172#endif /* JX9_ENABLE_MATH_FUNC */
8173 { "round", jx9Builtin_round },
8174 { "dechex", jx9Builtin_dechex },
8175 { "decoct", jx9Builtin_decoct },
8176 { "decbin", jx9Builtin_decbin },
8177 { "hexdec", jx9Builtin_hexdec },
8178 { "bindec", jx9Builtin_bindec },
8179 { "octdec", jx9Builtin_octdec },
8180 { "base_convert", jx9Builtin_base_convert },
8181 /* String handling functions */
8182 { "substr", jx9Builtin_substr },
8183 { "substr_compare", jx9Builtin_substr_compare },
8184 { "substr_count", jx9Builtin_substr_count },
8185 { "chunk_split", jx9Builtin_chunk_split},
8186 { "htmlspecialchars", jx9Builtin_htmlspecialchars },
8187 { "htmlspecialchars_decode", jx9Builtin_htmlspecialchars_decode },
8188 { "get_html_translation_table", jx9Builtin_get_html_translation_table },
8189 { "htmlentities", jx9Builtin_htmlentities},
8190 { "html_entity_decode", jx9Builtin_html_entity_decode},
8191 { "strlen" , jx9Builtin_strlen },
8192 { "strcmp" , jx9Builtin_strcmp },
8193 { "strcoll" , jx9Builtin_strcmp },
8194 { "strncmp" , jx9Builtin_strncmp },
8195 { "strcasecmp" , jx9Builtin_strcasecmp },
8196 { "strncasecmp", jx9Builtin_strncasecmp},
8197 { "implode" , jx9Builtin_implode },
8198 { "join" , jx9Builtin_implode },
8199 { "implode_recursive" , jx9Builtin_implode_recursive },
8200 { "join_recursive" , jx9Builtin_implode_recursive },
8201 { "explode" , jx9Builtin_explode },
8202 { "trim" , jx9Builtin_trim },
8203 { "rtrim" , jx9Builtin_rtrim },
8204 { "chop" , jx9Builtin_rtrim },
8205 { "ltrim" , jx9Builtin_ltrim },
8206 { "strtolower", jx9Builtin_strtolower },
8207 { "mb_strtolower", jx9Builtin_strtolower }, /* Only UTF-8 encoding is supported */
8208 { "strtoupper", jx9Builtin_strtoupper },
8209 { "mb_strtoupper", jx9Builtin_strtoupper }, /* Only UTF-8 encoding is supported */
8210 { "ord", jx9Builtin_ord },
8211 { "chr", jx9Builtin_chr },
8212 { "bin2hex", jx9Builtin_bin2hex },
8213 { "strstr", jx9Builtin_strstr },
8214 { "stristr", jx9Builtin_stristr },
8215 { "strchr", jx9Builtin_strstr },
8216 { "strpos", jx9Builtin_strpos },
8217 { "stripos", jx9Builtin_stripos },
8218 { "strrpos", jx9Builtin_strrpos },
8219 { "strripos", jx9Builtin_strripos },
8220 { "strrchr", jx9Builtin_strrchr },
8221 { "strrev", jx9Builtin_strrev },
8222 { "str_repeat", jx9Builtin_str_repeat },
8223 { "nl2br", jx9Builtin_nl2br },
8224 { "sprintf", jx9Builtin_sprintf },
8225 { "printf", jx9Builtin_printf },
8226 { "vprintf", jx9Builtin_vprintf },
8227 { "vsprintf", jx9Builtin_vsprintf },
8228 { "size_format", jx9Builtin_size_format},
8229#if !defined(JX9_DISABLE_HASH_FUNC)
8230 { "md5", jx9Builtin_md5 },
8231 { "sha1", jx9Builtin_sha1 },
8232 { "crc32", jx9Builtin_crc32 },
8233#endif /* JX9_DISABLE_HASH_FUNC */
8234 { "str_getcsv", jx9Builtin_str_getcsv },
8235 { "strip_tags", jx9Builtin_strip_tags },
8236 { "str_split", jx9Builtin_str_split },
8237 { "strspn", jx9Builtin_strspn },
8238 { "strcspn", jx9Builtin_strcspn },
8239 { "strpbrk", jx9Builtin_strpbrk },
8240 { "soundex", jx9Builtin_soundex },
8241 { "wordwrap", jx9Builtin_wordwrap },
8242 { "strtok", jx9Builtin_strtok },
8243 { "str_pad", jx9Builtin_str_pad },
8244 { "str_replace", jx9Builtin_str_replace},
8245 { "str_ireplace", jx9Builtin_str_replace},
8246 { "strtr", jx9Builtin_strtr },
8247 { "parse_ini_string", jx9Builtin_parse_ini_string},
8248 /* Ctype functions */
8249 { "ctype_alnum", jx9Builtin_ctype_alnum },
8250 { "ctype_alpha", jx9Builtin_ctype_alpha },
8251 { "ctype_cntrl", jx9Builtin_ctype_cntrl },
8252 { "ctype_digit", jx9Builtin_ctype_digit },
8253 { "ctype_xdigit", jx9Builtin_ctype_xdigit},
8254 { "ctype_graph", jx9Builtin_ctype_graph },
8255 { "ctype_print", jx9Builtin_ctype_print },
8256 { "ctype_punct", jx9Builtin_ctype_punct },
8257 { "ctype_space", jx9Builtin_ctype_space },
8258 { "ctype_lower", jx9Builtin_ctype_lower },
8259 { "ctype_upper", jx9Builtin_ctype_upper },
8260 /* Time functions */
8261 { "time" , jx9Builtin_time },
8262 { "microtime", jx9Builtin_microtime },
8263 { "getdate" , jx9Builtin_getdate },
8264 { "gettimeofday", jx9Builtin_gettimeofday },
8265 { "date", jx9Builtin_date },
8266 { "strftime", jx9Builtin_strftime },
8267 { "idate", jx9Builtin_idate },
8268 { "gmdate", jx9Builtin_gmdate },
8269 { "localtime", jx9Builtin_localtime },
8270 { "mktime", jx9Builtin_mktime },
8271 { "gmmktime", jx9Builtin_mktime },
8272 /* URL functions */
8273 { "base64_encode", jx9Builtin_base64_encode },
8274 { "base64_decode", jx9Builtin_base64_decode },
8275 { "convert_uuencode", jx9Builtin_base64_encode },
8276 { "convert_uudecode", jx9Builtin_base64_decode },
8277 { "urlencode", jx9Builtin_urlencode },
8278 { "urldecode", jx9Builtin_urldecode },
8279 { "rawurlencode", jx9Builtin_urlencode },
8280 { "rawurldecode", jx9Builtin_urldecode },
8281#endif /* JX9_DISABLE_BUILTIN_FUNC */
8282};
8283/*
8284 * Register the built-in functions defined above, the array functions
8285 * defined in hashmap.c and the IO functions defined in vfs.c.
8286 */
8287JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm)
8288{
8289 sxu32 n;
8290 for( n = 0 ; n < SX_ARRAYSIZE(aBuiltInFunc) ; ++n ){
8291 jx9_create_function(&(*pVm), aBuiltInFunc[n].zName, aBuiltInFunc[n].xFunc, 0);
8292 }
8293 /* Register hashmap functions [i.e: sort(), count(), array_diff(), ...] */
8294 jx9RegisterHashmapFunctions(&(*pVm));
8295 /* Register IO functions [i.e: fread(), fwrite(), chdir(), mkdir(), file(), ...] */
8296 jx9RegisterIORoutine(&(*pVm));
8297}
diff --git a/common/unqlite/jx9_compile.c b/common/unqlite/jx9_compile.c
new file mode 100644
index 0000000..a7c9916
--- /dev/null
+++ b/common/unqlite/jx9_compile.c
@@ -0,0 +1,3671 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: compile.c v1.7 FreeBSD 2012-12-11 21:46 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/*
18 * This file implement a thread-safe and full-reentrant compiler for the JX9 engine.
19 * That is, routines defined in this file takes a stream of tokens and output
20 * JX9 bytecode instructions.
21 */
22/* Forward declaration */
23typedef struct LangConstruct LangConstruct;
24typedef struct JumpFixup JumpFixup;
25/* Block [i.e: set of statements] control flags */
26#define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for, while, ...] */
27#define GEN_BLOCK_PROTECTED 0x002 /* Protected block */
28#define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/
29#define GEN_BLOCK_FUNC 0x008 /* Function body */
30#define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/
31#define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */
32#define GEN_BLOCK_EXPR 0x040 /* Expression */
33#define GEN_BLOCK_STD 0x080 /* Standard block */
34#define GEN_BLOCK_SWITCH 0x100 /* Switch statement */
35/*
36 * Compilation of some JX9 constructs such as if, for, while, the logical or
37 * (||) and logical and (&&) operators in expressions requires the
38 * generation of forward jumps.
39 * Since the destination PC target of these jumps isn't known when the jumps
40 * are emitted, we record each forward jump in an instance of the following
41 * structure. Those jumps are fixed later when the jump destination is resolved.
42 */
43struct JumpFixup
44{
45 sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */
46 sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */
47};
48/*
49 * Each language construct is represented by an instance
50 * of the following structure.
51 */
52struct LangConstruct
53{
54 sxu32 nID; /* Language construct ID [i.e: JX9_TKWRD_WHILE, JX9_TKWRD_FOR, JX9_TKWRD_IF...] */
55 ProcLangConstruct xConstruct; /* C function implementing the language construct */
56};
57/* Compilation flags */
58#define JX9_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */
59/* Token stream synchronization macros */
60#define SWAP_TOKEN_STREAM(GEN, START, END)\
61 pTmp = GEN->pEnd;\
62 pGen->pIn = START;\
63 pGen->pEnd = END
64#define UPDATE_TOKEN_STREAM(GEN)\
65 if( GEN->pIn < pTmp ){\
66 GEN->pIn++;\
67 }\
68 GEN->pEnd = pTmp
69#define SWAP_DELIMITER(GEN, START, END)\
70 pTmpIn = GEN->pIn;\
71 pTmpEnd = GEN->pEnd;\
72 GEN->pIn = START;\
73 GEN->pEnd = END
74#define RE_SWAP_DELIMITER(GEN)\
75 GEN->pIn = pTmpIn;\
76 GEN->pEnd = pTmpEnd
77/* Flags related to expression compilation */
78#define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */
79#define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'JX9_OP_LOAD' VM instruction for more information */
80#define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by object attributes) */
81/* Forward declaration */
82static sxi32 jx9CompileExpr(
83 jx9_gen_state *pGen, /* Code generator state */
84 sxi32 iFlags, /* Control flags */
85 sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */
86 );
87
88/*
89 * Recover from a compile-time error. In other words synchronize
90 * the token stream cursor with the first semi-colon seen.
91 */
92static sxi32 jx9ErrorRecover(jx9_gen_state *pGen)
93{
94 /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */
95 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI /*';'*/) == 0){
96 pGen->pIn++;
97 }
98 return SXRET_OK;
99}
100/*
101 * Check if the given identifier name is reserved or not.
102 * Return TRUE if reserved.FALSE otherwise.
103 */
104static int GenStateIsReservedID(SyString *pName)
105{
106 if( pName->nByte == sizeof("null") - 1 ){
107 if( SyStrnicmp(pName->zString, "null", sizeof("null")-1) == 0 ){
108 return TRUE;
109 }else if( SyStrnicmp(pName->zString, "true", sizeof("true")-1) == 0 ){
110 return TRUE;
111 }
112 }else if( pName->nByte == sizeof("false") - 1 ){
113 if( SyStrnicmp(pName->zString, "false", sizeof("false")-1) == 0 ){
114 return TRUE;
115 }
116 }
117 /* Not a reserved constant */
118 return FALSE;
119}
120/*
121 * Check if a given token value is installed in the literal table.
122 */
123static sxi32 GenStateFindLiteral(jx9_gen_state *pGen, const SyString *pValue, sxu32 *pIdx)
124{
125 SyHashEntry *pEntry;
126 pEntry = SyHashGet(&pGen->hLiteral, (const void *)pValue->zString, pValue->nByte);
127 if( pEntry == 0 ){
128 return SXERR_NOTFOUND;
129 }
130 *pIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData);
131 return SXRET_OK;
132}
133/*
134 * Install a given constant index in the literal table.
135 * In order to be installed, the jx9_value must be of type string.
136 */
137static sxi32 GenStateInstallLiteral(jx9_gen_state *pGen,jx9_value *pObj, sxu32 nIdx)
138{
139 if( SyBlobLength(&pObj->sBlob) > 0 ){
140 SyHashInsert(&pGen->hLiteral, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), SX_INT_TO_PTR(nIdx));
141 }
142 return SXRET_OK;
143}
144/*
145 * Generate a fatal error.
146 */
147static sxi32 GenStateOutOfMem(jx9_gen_state *pGen)
148{
149 jx9GenCompileError(pGen,E_ERROR,1,"Fatal, Jx9 compiler is running out of memory");
150 /* Abort compilation immediately */
151 return SXERR_ABORT;
152}
153/*
154 * Fetch a block that correspond to the given criteria from the stack of
155 * compiled blocks.
156 * Return a pointer to that block on success. NULL otherwise.
157 */
158static GenBlock * GenStateFetchBlock(GenBlock *pCurrent, sxi32 iBlockType, sxi32 iCount)
159{
160 GenBlock *pBlock = pCurrent;
161 for(;;){
162 if( pBlock->iFlags & iBlockType ){
163 iCount--; /* Decrement nesting level */
164 if( iCount < 1 ){
165 /* Block meet with the desired criteria */
166 return pBlock;
167 }
168 }
169 /* Point to the upper block */
170 pBlock = pBlock->pParent;
171 if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){
172 /* Forbidden */
173 break;
174 }
175 }
176 /* No such block */
177 return 0;
178}
179/*
180 * Initialize a freshly allocated block instance.
181 */
182static void GenStateInitBlock(
183 jx9_gen_state *pGen, /* Code generator state */
184 GenBlock *pBlock, /* Target block */
185 sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/
186 sxu32 nFirstInstr, /* First instruction to compile */
187 void *pUserData /* Upper layer private data */
188 )
189{
190 /* Initialize block fields */
191 pBlock->nFirstInstr = nFirstInstr;
192 pBlock->pUserData = pUserData;
193 pBlock->pGen = pGen;
194 pBlock->iFlags = iType;
195 pBlock->pParent = 0;
196 pBlock->bPostContinue = 0;
197 SySetInit(&pBlock->aJumpFix, &pGen->pVm->sAllocator, sizeof(JumpFixup));
198 SySetInit(&pBlock->aPostContFix, &pGen->pVm->sAllocator, sizeof(JumpFixup));
199}
200/*
201 * Allocate a new block instance.
202 * Return SXRET_OK and write a pointer to the new instantiated block
203 * on success.Otherwise generate a compile-time error and abort
204 * processing on failure.
205 */
206static sxi32 GenStateEnterBlock(
207 jx9_gen_state *pGen, /* Code generator state */
208 sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/
209 sxu32 nFirstInstr, /* First instruction to compile */
210 void *pUserData, /* Upper layer private data */
211 GenBlock **ppBlock /* OUT: instantiated block */
212 )
213{
214 GenBlock *pBlock;
215 /* Allocate a new block instance */
216 pBlock = (GenBlock *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(GenBlock));
217 if( pBlock == 0 ){
218 /* If the supplied memory subsystem is so sick that we are unable to allocate
219 * a tiny chunk of memory, there is no much we can do here.
220 */
221 return GenStateOutOfMem(pGen);
222 }
223 /* Zero the structure */
224 SyZero(pBlock, sizeof(GenBlock));
225 GenStateInitBlock(&(*pGen), pBlock, iType, nFirstInstr, pUserData);
226 /* Link to the parent block */
227 pBlock->pParent = pGen->pCurrent;
228 /* Mark as the current block */
229 pGen->pCurrent = pBlock;
230 if( ppBlock ){
231 /* Write a pointer to the new instance */
232 *ppBlock = pBlock;
233 }
234 return SXRET_OK;
235}
236/*
237 * Release block fields without freeing the whole instance.
238 */
239static void GenStateReleaseBlock(GenBlock *pBlock)
240{
241 SySetRelease(&pBlock->aPostContFix);
242 SySetRelease(&pBlock->aJumpFix);
243}
244/*
245 * Release a block.
246 */
247static void GenStateFreeBlock(GenBlock *pBlock)
248{
249 jx9_gen_state *pGen = pBlock->pGen;
250 GenStateReleaseBlock(&(*pBlock));
251 /* Free the instance */
252 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pBlock);
253}
254/*
255 * POP and release a block from the stack of compiled blocks.
256 */
257static sxi32 GenStateLeaveBlock(jx9_gen_state *pGen, GenBlock **ppBlock)
258{
259 GenBlock *pBlock = pGen->pCurrent;
260 if( pBlock == 0 ){
261 /* No more block to pop */
262 return SXERR_EMPTY;
263 }
264 /* Point to the upper block */
265 pGen->pCurrent = pBlock->pParent;
266 if( ppBlock ){
267 /* Write a pointer to the popped block */
268 *ppBlock = pBlock;
269 }else{
270 /* Safely release the block */
271 GenStateFreeBlock(&(*pBlock));
272 }
273 return SXRET_OK;
274}
275/*
276 * Emit a forward jump.
277 * Notes on forward jumps
278 * Compilation of some JX9 constructs such as if, for, while and the logical or
279 * (||) and logical and (&&) operators in expressions requires the
280 * generation of forward jumps.
281 * Since the destination PC target of these jumps isn't known when the jumps
282 * are emitted, we record each forward jump in an instance of the following
283 * structure. Those jumps are fixed later when the jump destination is resolved.
284 */
285static sxi32 GenStateNewJumpFixup(GenBlock *pBlock, sxi32 nJumpType, sxu32 nInstrIdx)
286{
287 JumpFixup sJumpFix;
288 sxi32 rc;
289 /* Init the JumpFixup structure */
290 sJumpFix.nJumpType = nJumpType;
291 sJumpFix.nInstrIdx = nInstrIdx;
292 /* Insert in the jump fixup table */
293 rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix);
294 return rc;
295}
296/*
297 * Fix a forward jump now the jump destination is resolved.
298 * Return the total number of fixed jumps.
299 * Notes on forward jumps:
300 * Compilation of some JX9 constructs such as if, for, while and the logical or
301 * (||) and logical and (&&) operators in expressions requires the
302 * generation of forward jumps.
303 * Since the destination PC target of these jumps isn't known when the jumps
304 * are emitted, we record each forward jump in an instance of the following
305 * structure.Those jumps are fixed later when the jump destination is resolved.
306 */
307static sxu32 GenStateFixJumps(GenBlock *pBlock, sxi32 nJumpType, sxu32 nJumpDest)
308{
309 JumpFixup *aFix;
310 VmInstr *pInstr;
311 sxu32 nFixed;
312 sxu32 n;
313 /* Point to the jump fixup table */
314 aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix);
315 /* Fix the desired jumps */
316 for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){
317 if( aFix[n].nJumpType < 0 ){
318 /* Already fixed */
319 continue;
320 }
321 if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){
322 /* Not of our interest */
323 continue;
324 }
325 /* Point to the instruction to fix */
326 pInstr = jx9VmGetInstr(pBlock->pGen->pVm, aFix[n].nInstrIdx);
327 if( pInstr ){
328 pInstr->iP2 = nJumpDest;
329 nFixed++;
330 /* Mark as fixed */
331 aFix[n].nJumpType = -1;
332 }
333 }
334 /* Total number of fixed jumps */
335 return nFixed;
336}
337/*
338 * Reserve a room for a numeric constant [i.e: 64-bit integer or real number]
339 * in the constant table.
340 */
341static jx9_value * GenStateInstallNumLiteral(jx9_gen_state *pGen, sxu32 *pIdx)
342{
343 jx9_value *pObj;
344 sxu32 nIdx = 0; /* cc warning */
345 /* Reserve a new constant */
346 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
347 if( pObj == 0 ){
348 GenStateOutOfMem(pGen);
349 return 0;
350 }
351 *pIdx = nIdx;
352 /* TODO(chems): Create a numeric table (64bit int keys) same as
353 * the constant string iterals table [optimization purposes].
354 */
355 return pObj;
356}
357/*
358 * Compile a numeric [i.e: integer or real] literal.
359 * Notes on the integer type.
360 * According to the JX9 language reference manual
361 * Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8)
362 * or binary (base 2) notation, optionally preceded by a sign (- or +).
363 * To use octal notation, precede the number with a 0 (zero). To use hexadecimal
364 * notation precede the number with 0x. To use binary notation precede the number with 0b.
365 */
366static sxi32 jx9CompileNumLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag)
367{
368 SyToken *pToken = pGen->pIn; /* Raw token */
369 sxu32 nIdx = 0;
370 if( pToken->nType & JX9_TK_INTEGER ){
371 jx9_value *pObj;
372 sxi64 iValue;
373 iValue = jx9TokenValueToInt64(&pToken->sData);
374 pObj = GenStateInstallNumLiteral(&(*pGen), &nIdx);
375 if( pObj == 0 ){
376 SXUNUSED(iCompileFlag); /* cc warning */
377 return SXERR_ABORT;
378 }
379 jx9MemObjInitFromInt(pGen->pVm, pObj, iValue);
380 }else{
381 /* Real number */
382 jx9_value *pObj;
383 /* Reserve a new constant */
384 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
385 if( pObj == 0 ){
386 return GenStateOutOfMem(pGen);
387 }
388 jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData);
389 jx9MemObjToReal(pObj);
390 }
391 /* Emit the load constant instruction */
392 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
393 /* Node successfully compiled */
394 return SXRET_OK;
395}
396/*
397 * Compile a nowdoc string.
398 * According to the JX9 language reference manual:
399 *
400 * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings.
401 * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc.
402 * The construct is ideal for embedding JX9 code or other large blocks of text without the
403 * need for escaping. It shares some features in common with the SGML <![CDATA[ ]]>
404 * construct, in that it declares a block of text which is not for parsing.
405 * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier
406 * which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc
407 * identifiers also apply to nowdoc identifiers, especially those regarding the appearance
408 * of the closing identifier.
409 */
410static sxi32 jx9CompileNowdoc(jx9_gen_state *pGen,sxi32 iCompileFlag)
411{
412 SyString *pStr = &pGen->pIn->sData; /* Constant string literal */
413 jx9_value *pObj;
414 sxu32 nIdx;
415 nIdx = 0; /* Prevent compiler warning */
416 if( pStr->nByte <= 0 ){
417 /* Empty string, load NULL */
418 jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC, 0, 0, 0, 0);
419 return SXRET_OK;
420 }
421 /* Reserve a new constant */
422 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
423 if( pObj == 0 ){
424 jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "JX9 engine is running out of memory");
425 SXUNUSED(iCompileFlag); /* cc warning */
426 return SXERR_ABORT;
427 }
428 /* No processing is done here, simply a memcpy() operation */
429 jx9MemObjInitFromString(pGen->pVm, pObj, pStr);
430 /* Emit the load constant instruction */
431 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
432 /* Node successfully compiled */
433 return SXRET_OK;
434}
435/*
436 * Compile a single quoted string.
437 * According to the JX9 language reference manual:
438 *
439 * The simplest way to specify a string is to enclose it in single quotes (the character ' ).
440 * To specify a literal single quote, escape it with a backslash (\). To specify a literal
441 * backslash, double it (\\). All other instances of backslash will be treated as a literal
442 * backslash: this means that the other escape sequences you might be used to, such as \r
443 * or \n, will be output literally as specified rather than having any special meaning.
444 *
445 */
446JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag)
447{
448 SyString *pStr = &pGen->pIn->sData; /* Constant string literal */
449 const char *zIn, *zCur, *zEnd;
450 jx9_value *pObj;
451 sxu32 nIdx;
452 nIdx = 0; /* Prevent compiler warning */
453 /* Delimit the string */
454 zIn = pStr->zString;
455 zEnd = &zIn[pStr->nByte];
456 if( zIn >= zEnd ){
457 /* Empty string, load NULL */
458 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
459 return SXRET_OK;
460 }
461 if( SXRET_OK == GenStateFindLiteral(&(*pGen), pStr, &nIdx) ){
462 /* Already processed, emit the load constant instruction
463 * and return.
464 */
465 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
466 return SXRET_OK;
467 }
468 /* Reserve a new constant */
469 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
470 if( pObj == 0 ){
471 jx9GenCompileError(&(*pGen), E_ERROR, 1, "JX9 engine is running out of memory");
472 SXUNUSED(iCompileFlag); /* cc warning */
473 return SXERR_ABORT;
474 }
475 jx9MemObjInitFromString(pGen->pVm, pObj, 0);
476 /* Compile the node */
477 for(;;){
478 if( zIn >= zEnd ){
479 /* End of input */
480 break;
481 }
482 zCur = zIn;
483 while( zIn < zEnd && zIn[0] != '\\' ){
484 zIn++;
485 }
486 if( zIn > zCur ){
487 /* Append raw contents*/
488 jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur));
489 }
490 zIn++;
491 if( zIn < zEnd ){
492 if( zIn[0] == '\\' ){
493 /* A literal backslash */
494 jx9MemObjStringAppend(pObj, "\\", sizeof(char));
495 }else if( zIn[0] == '\'' ){
496 /* A single quote */
497 jx9MemObjStringAppend(pObj, "'", sizeof(char));
498 }else{
499 /* verbatim copy */
500 zIn--;
501 jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2);
502 zIn++;
503 }
504 }
505 /* Advance the stream cursor */
506 zIn++;
507 }
508 /* Emit the load constant instruction */
509 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
510 if( pStr->nByte < 1024 ){
511 /* Install in the literal table */
512 GenStateInstallLiteral(pGen, pObj, nIdx);
513 }
514 /* Node successfully compiled */
515 return SXRET_OK;
516}
517/*
518 * Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string.
519 * According to the JX9 language reference manual
520 * When a string is specified in double quotes or with heredoc, variables are parsed within it.
521 * There are two types of syntax: a simple one and a complex one. The simple syntax is the most
522 * common and convenient. It provides a way to embed a variable, an array value, or an object
523 * property in a string with a minimum of effort.
524 * Simple syntax
525 * If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible
526 * to form a valid variable name. Enclose the variable name in curly braces to explicitly specify
527 * the end of the name.
528 * Similarly, an array index or an object property can be parsed. With array indices, the closing
529 * square bracket (]) marks the end of the index. The same rules apply to object properties
530 * as to simple variables.
531 * Complex (curly) syntax
532 * This isn't called complex because the syntax is complex, but because it allows for the use
533 * of complex expressions.
534 * Any scalar variable, array element or object property with a string representation can be
535 * included via this syntax. Simply write the expression the same way as it would appear outside
536 * the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only
537 * be recognised when the $ immediately follows the {. Use {\$ to get a literal {$
538 */
539static sxi32 GenStateProcessStringExpression(
540 jx9_gen_state *pGen, /* Code generator state */
541 const char *zIn, /* Raw expression */
542 const char *zEnd /* End of the expression */
543 )
544{
545 SyToken *pTmpIn, *pTmpEnd;
546 SySet sToken;
547 sxi32 rc;
548 /* Initialize the token set */
549 SySetInit(&sToken, &pGen->pVm->sAllocator, sizeof(SyToken));
550 /* Preallocate some slots */
551 SySetAlloc(&sToken, 0x08);
552 /* Tokenize the text */
553 jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken);
554 /* Swap delimiter */
555 pTmpIn = pGen->pIn;
556 pTmpEnd = pGen->pEnd;
557 pGen->pIn = (SyToken *)SySetBasePtr(&sToken);
558 pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)];
559 /* Compile the expression */
560 rc = jx9CompileExpr(&(*pGen), 0, 0);
561 /* Restore token stream */
562 pGen->pIn = pTmpIn;
563 pGen->pEnd = pTmpEnd;
564 /* Release the token set */
565 SySetRelease(&sToken);
566 /* Compilation result */
567 return rc;
568}
569/*
570 * Reserve a new constant for a double quoted/heredoc string.
571 */
572static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount)
573{
574 jx9_value *pConstObj;
575 sxu32 nIdx = 0;
576 /* Reserve a new constant */
577 pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
578 if( pConstObj == 0 ){
579 GenStateOutOfMem(&(*pGen));
580 return 0;
581 }
582 (*pCount)++;
583 jx9MemObjInitFromString(pGen->pVm, pConstObj, 0);
584 /* Emit the load constant instruction */
585 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
586 return pConstObj;
587}
588/*
589 * Compile a double quoted/heredoc string.
590 * According to the JX9 language reference manual
591 * Heredoc
592 * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier
593 * is provided, then a newline. The string itself follows, and then the same identifier again
594 * to close the quotation.
595 * The closing identifier must begin in the first column of the line. Also, the identifier must
596 * follow the same naming rules as any other label in JX9: it must contain only alphanumeric
597 * characters and underscores, and must start with a non-digit character or underscore.
598 * Warning
599 * It is very important to note that the line with the closing identifier must contain
600 * no other characters, except possibly a semicolon (;). That means especially that the identifier
601 * may not be indented, and there may not be any spaces or tabs before or after the semicolon.
602 * It's also important to realize that the first character before the closing identifier must
603 * be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X.
604 * The closing delimiter (possibly followed by a semicolon) must also be followed by a newline.
605 * If this rule is broken and the closing identifier is not "clean", it will not be considered a closing
606 * identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before
607 * the end of the current file, a parse error will result at the last line.
608 * Heredocs can not be used for initializing object properties.
609 * Double quoted
610 * If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters:
611 * Escaped characters Sequence Meaning
612 * \n linefeed (LF or 0x0A (10) in ASCII)
613 * \r carriage return (CR or 0x0D (13) in ASCII)
614 * \t horizontal tab (HT or 0x09 (9) in ASCII)
615 * \v vertical tab (VT or 0x0B (11) in ASCII)
616 * \f form feed (FF or 0x0C (12) in ASCII)
617 * \\ backslash
618 * \$ dollar sign
619 * \" double-quote
620 * \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation
621 * \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation
622 * As in single quoted strings, escaping any other character will result in the backslash being printed too.
623 * The most important feature of double-quoted strings is the fact that variable names will be expanded.
624 * See string parsing for details.
625 */
626static sxi32 GenStateCompileString(jx9_gen_state *pGen)
627{
628 SyString *pStr = &pGen->pIn->sData; /* Raw token value */
629 const char *zIn, *zCur, *zEnd;
630 jx9_value *pObj = 0;
631 sxi32 iCons;
632 sxi32 rc;
633 /* Delimit the string */
634 zIn = pStr->zString;
635 zEnd = &zIn[pStr->nByte];
636 if( zIn >= zEnd ){
637 /* Empty string, load NULL */
638 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
639 return SXRET_OK;
640 }
641 zCur = 0;
642 /* Compile the node */
643 iCons = 0;
644 for(;;){
645 zCur = zIn;
646 while( zIn < zEnd && zIn[0] != '\\' ){
647 if(zIn[0] == '$' && &zIn[1] < zEnd &&
648 (((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){
649 break;
650 }
651 zIn++;
652 }
653 if( zIn > zCur ){
654 if( pObj == 0 ){
655 pObj = GenStateNewStrObj(&(*pGen), &iCons);
656 if( pObj == 0 ){
657 return SXERR_ABORT;
658 }
659 }
660 jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur));
661 }
662 if( zIn >= zEnd ){
663 break;
664 }
665 if( zIn[0] == '\\' ){
666 const char *zPtr = 0;
667 sxu32 n;
668 zIn++;
669 if( zIn >= zEnd ){
670 break;
671 }
672 if( pObj == 0 ){
673 pObj = GenStateNewStrObj(&(*pGen), &iCons);
674 if( pObj == 0 ){
675 return SXERR_ABORT;
676 }
677 }
678 n = sizeof(char); /* size of conversion */
679 switch( zIn[0] ){
680 case '$':
681 /* Dollar sign */
682 jx9MemObjStringAppend(pObj, "$", sizeof(char));
683 break;
684 case '\\':
685 /* A literal backslash */
686 jx9MemObjStringAppend(pObj, "\\", sizeof(char));
687 break;
688 case 'a':
689 /* The "alert" character (BEL)[ctrl+g] ASCII code 7 */
690 jx9MemObjStringAppend(pObj, "\a", sizeof(char));
691 break;
692 case 'b':
693 /* Backspace (BS)[ctrl+h] ASCII code 8 */
694 jx9MemObjStringAppend(pObj, "\b", sizeof(char));
695 break;
696 case 'f':
697 /* Form-feed (FF)[ctrl+l] ASCII code 12 */
698 jx9MemObjStringAppend(pObj, "\f", sizeof(char));
699 break;
700 case 'n':
701 /* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */
702 jx9MemObjStringAppend(pObj, "\n", sizeof(char));
703 break;
704 case 'r':
705 /* Carriage return (CR)[ctrl+m] ASCII code 13 */
706 jx9MemObjStringAppend(pObj, "\r", sizeof(char));
707 break;
708 case 't':
709 /* Horizontal tab (HT)[ctrl+i] ASCII code 9 */
710 jx9MemObjStringAppend(pObj, "\t", sizeof(char));
711 break;
712 case 'v':
713 /* Vertical tab(VT)[ctrl+k] ASCII code 11 */
714 jx9MemObjStringAppend(pObj, "\v", sizeof(char));
715 break;
716 case '\'':
717 /* Single quote */
718 jx9MemObjStringAppend(pObj, "'", sizeof(char));
719 break;
720 case '"':
721 /* Double quote */
722 jx9MemObjStringAppend(pObj, "\"", sizeof(char));
723 break;
724 case '0':
725 /* NUL byte */
726 jx9MemObjStringAppend(pObj, "\0", sizeof(char));
727 break;
728 case 'x':
729 if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){
730 int c;
731 /* Hex digit */
732 c = SyHexToint(zIn[1]) << 4;
733 if( &zIn[2] < zEnd ){
734 c += SyHexToint(zIn[2]);
735 }
736 /* Output char */
737 jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char));
738 n += sizeof(char) * 2;
739 }else{
740 /* Output literal character */
741 jx9MemObjStringAppend(pObj, "x", sizeof(char));
742 }
743 break;
744 case 'o':
745 if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){
746 /* Octal digit stream */
747 int c;
748 c = 0;
749 zIn++;
750 for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){
751 if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){
752 break;
753 }
754 c = c * 8 + (zPtr[0] - '0');
755 }
756 if ( c > 0 ){
757 jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char));
758 }
759 n = (sxu32)(zPtr-zIn);
760 }else{
761 /* Output literal character */
762 jx9MemObjStringAppend(pObj, "o", sizeof(char));
763 }
764 break;
765 default:
766 /* Output without a slash */
767 jx9MemObjStringAppend(pObj, zIn, sizeof(char));
768 break;
769 }
770 /* Advance the stream cursor */
771 zIn += n;
772 continue;
773 }
774 if( zIn[0] == '{' ){
775 /* Curly syntax */
776 const char *zExpr;
777 sxi32 iNest = 1;
778 zIn++;
779 zExpr = zIn;
780 /* Synchronize with the next closing curly braces */
781 while( zIn < zEnd ){
782 if( zIn[0] == '{' ){
783 /* Increment nesting level */
784 iNest++;
785 }else if(zIn[0] == '}' ){
786 /* Decrement nesting level */
787 iNest--;
788 if( iNest <= 0 ){
789 break;
790 }
791 }
792 zIn++;
793 }
794 /* Process the expression */
795 rc = GenStateProcessStringExpression(&(*pGen),zExpr,zIn);
796 if( rc == SXERR_ABORT ){
797 return SXERR_ABORT;
798 }
799 if( rc != SXERR_EMPTY ){
800 ++iCons;
801 }
802 if( zIn < zEnd ){
803 /* Jump the trailing curly */
804 zIn++;
805 }
806 }else{
807 /* Simple syntax */
808 const char *zExpr = zIn;
809 /* Assemble variable name */
810 for(;;){
811 /* Jump leading dollars */
812 while( zIn < zEnd && zIn[0] == '$' ){
813 zIn++;
814 }
815 for(;;){
816 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){
817 zIn++;
818 }
819 if((unsigned char)zIn[0] >= 0xc0 ){
820 /* UTF-8 stream */
821 zIn++;
822 while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){
823 zIn++;
824 }
825 continue;
826 }
827 break;
828 }
829 if( zIn >= zEnd ){
830 break;
831 }
832 if( zIn[0] == '[' ){
833 sxi32 iSquare = 1;
834 zIn++;
835 while( zIn < zEnd ){
836 if( zIn[0] == '[' ){
837 iSquare++;
838 }else if (zIn[0] == ']' ){
839 iSquare--;
840 if( iSquare <= 0 ){
841 break;
842 }
843 }
844 zIn++;
845 }
846 if( zIn < zEnd ){
847 zIn++;
848 }
849 break;
850 }else if( zIn[0] == '.' ){
851 /* Member access operator '.' */
852 zIn++;
853 }else{
854 break;
855 }
856 }
857 /* Process the expression */
858 rc = GenStateProcessStringExpression(&(*pGen),zExpr, zIn);
859 if( rc == SXERR_ABORT ){
860 return SXERR_ABORT;
861 }
862 if( rc != SXERR_EMPTY ){
863 ++iCons;
864 }
865 }
866 /* Invalidate the previously used constant */
867 pObj = 0;
868 }/*for(;;)*/
869 if( iCons > 1 ){
870 /* Concatenate all compiled constants */
871 jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0);
872 }
873 /* Node successfully compiled */
874 return SXRET_OK;
875}
876/*
877 * Compile a double quoted string.
878 * See the block-comment above for more information.
879 */
880JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag)
881{
882 sxi32 rc;
883 rc = GenStateCompileString(&(*pGen));
884 SXUNUSED(iCompileFlag); /* cc warning */
885 /* Compilation result */
886 return rc;
887}
888/*
889 * Compile a literal which is an identifier(name) for simple values.
890 */
891JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag)
892{
893 SyToken *pToken = pGen->pIn;
894 jx9_value *pObj;
895 SyString *pStr;
896 sxu32 nIdx;
897 /* Extract token value */
898 pStr = &pToken->sData;
899 /* Deal with the reserved literals [i.e: null, false, true, ...] first */
900 if( pStr->nByte == sizeof("NULL") - 1 ){
901 if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){
902 /* NULL constant are always indexed at 0 */
903 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
904 return SXRET_OK;
905 }else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){
906 /* TRUE constant are always indexed at 1 */
907 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1, 0, 0);
908 return SXRET_OK;
909 }
910 }else if (pStr->nByte == sizeof("FALSE") - 1 &&
911 SyStrnicmp(pStr->zString, "false", sizeof("FALSE")-1) == 0 ){
912 /* FALSE constant are always indexed at 2 */
913 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 2, 0, 0);
914 return SXRET_OK;
915 }else if(pStr->nByte == sizeof("__LINE__") - 1 &&
916 SyMemcmp(pStr->zString, "__LINE__", sizeof("__LINE__")-1) == 0 ){
917 /* TICKET 1433-004: __LINE__ constant must be resolved at compile time, not run time */
918 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
919 if( pObj == 0 ){
920 SXUNUSED(iCompileFlag); /* cc warning */
921 return GenStateOutOfMem(pGen);
922 }
923 jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine);
924 /* Emit the load constant instruction */
925 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
926 return SXRET_OK;
927 }else if( pStr->nByte == sizeof("__FUNCTION__") - 1 &&
928 SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-1) == 0 ){
929 GenBlock *pBlock = pGen->pCurrent;
930 /* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time, not run time */
931 while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){
932 /* Point to the upper block */
933 pBlock = pBlock->pParent;
934 }
935 if( pBlock == 0 ){
936 /* Called in the global scope, load NULL */
937 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0);
938 }else{
939 /* Extract the target function/method */
940 jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData;
941 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
942 if( pObj == 0 ){
943 return GenStateOutOfMem(pGen);
944 }
945 jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName);
946 /* Emit the load constant instruction */
947 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
948 }
949 return SXRET_OK;
950 }
951 /* Query literal table */
952 if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){
953 jx9_value *pObj;
954 /* Unknown literal, install it in the literal table */
955 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
956 if( pObj == 0 ){
957 return GenStateOutOfMem(pGen);
958 }
959 jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData);
960 GenStateInstallLiteral(&(*pGen), pObj, nIdx);
961 }
962 /* Emit the load constant instruction */
963 jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0);
964 /* Node successfully compiled */
965 return SXRET_OK;
966}
967/*
968 * Compile an array entry whether it is a key or a value.
969 */
970static sxi32 GenStateCompileJSONEntry(
971 jx9_gen_state *pGen, /* Code generator state */
972 SyToken *pIn, /* Token stream */
973 SyToken *pEnd, /* End of the token stream */
974 sxi32 iFlags, /* Compilation flags */
975 sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */
976 )
977{
978 SyToken *pTmpIn, *pTmpEnd;
979 sxi32 rc;
980 /* Swap token stream */
981 SWAP_DELIMITER(pGen, pIn, pEnd);
982 /* Compile the expression*/
983 rc = jx9CompileExpr(&(*pGen), iFlags, xValidator);
984 /* Restore token stream */
985 RE_SWAP_DELIMITER(pGen);
986 return rc;
987}
988/*
989 * Compile a Jx9 JSON Array.
990 */
991JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag)
992{
993 sxi32 nPair = 0;
994 SyToken *pCur;
995 sxi32 rc;
996
997 pGen->pIn++; /* Jump the open square bracket '['*/
998 pGen->pEnd--;
999 SXUNUSED(iCompileFlag); /* cc warning */
1000 for(;;){
1001 /* Jump leading commas */
1002 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){
1003 pGen->pIn++;
1004 }
1005 pCur = pGen->pIn;
1006 if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){
1007 /* No more entry to process */
1008 break;
1009 }
1010 /* Compile entry */
1011 rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0);
1012 if( rc == SXERR_ABORT ){
1013 return SXERR_ABORT;
1014 }
1015 nPair++;
1016 }
1017 /* Emit the load map instruction */
1018 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0);
1019 /* Node successfully compiled */
1020 return SXRET_OK;
1021}
1022/*
1023 * Node validator for a given JSON key.
1024 */
1025static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot)
1026{
1027 sxi32 rc = SXRET_OK;
1028 if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString
1029 && pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){
1030 /* Unexpected expression */
1031 rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0,
1032 "JSON Object: Unexpected expression, key must be of type string, literal or simple variable");
1033 if( rc != SXERR_ABORT ){
1034 rc = SXERR_INVALID;
1035 }
1036 }
1037 return rc;
1038}
1039/*
1040 * Compile a Jx9 JSON Object
1041 */
1042JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag)
1043{
1044 SyToken *pKey, *pCur;
1045 sxi32 nPair = 0;
1046 sxi32 rc;
1047
1048 pGen->pIn++; /* Jump the open querly braces '{'*/
1049 pGen->pEnd--;
1050 SXUNUSED(iCompileFlag); /* cc warning */
1051 for(;;){
1052 /* Jump leading commas */
1053 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){
1054 pGen->pIn++;
1055 }
1056 pCur = pGen->pIn;
1057 if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){
1058 /* No more entry to process */
1059 break;
1060 }
1061 /* Compile the key */
1062 pKey = pCur;
1063 while( pCur < pGen->pIn ){
1064 if( pCur->nType & JX9_TK_COLON /*':'*/ ){
1065 break;
1066 }
1067 pCur++;
1068 }
1069 rc = SXERR_EMPTY;
1070 if( pCur < pGen->pIn ){
1071 if( &pCur[1] >= pGen->pIn ){
1072 /* Missing value */
1073 rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value");
1074 if( rc == SXERR_ABORT ){
1075 return SXERR_ABORT;
1076 }
1077 return SXRET_OK;
1078 }
1079 /* Compile the expression holding the key */
1080 rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur,
1081 EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */,
1082 GenStateJSONObjectKeyNodeValidator /* Node validator callback */
1083 );
1084 if( rc == SXERR_ABORT ){
1085 return SXERR_ABORT;
1086 }
1087 pCur++; /* Jump the double colon ':' */
1088 }else if( pKey == pCur ){
1089 /* Key is omitted, emit an error */
1090 jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key");
1091 pCur++; /* Jump the double colon ':' */
1092 }else{
1093 /* Reset back the cursor and point to the entry value */
1094 pCur = pKey;
1095 }
1096 /* Compile indice value */
1097 rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0);
1098 if( rc == SXERR_ABORT ){
1099 return SXERR_ABORT;
1100 }
1101 nPair++;
1102 }
1103 /* Emit the load map instruction */
1104 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0);
1105 /* Node successfully compiled */
1106 return SXRET_OK;
1107}
1108/*
1109 * Compile a function [i.e: print, exit(), include(), ...] which is a langauge
1110 * construct.
1111 */
1112JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag)
1113{
1114 SyString *pName;
1115 sxu32 nKeyID;
1116 sxi32 rc;
1117 /* Name of the language construct [i.e: print, die...]*/
1118 pName = &pGen->pIn->sData;
1119 nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
1120 pGen->pIn++; /* Jump the language construct keyword */
1121 if( nKeyID == JX9_TKWRD_PRINT ){
1122 SyToken *pTmp, *pNext = 0;
1123 /* Compile arguments one after one */
1124 pTmp = pGen->pEnd;
1125 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0);
1126 while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){
1127 if( pGen->pIn < pNext ){
1128 pGen->pEnd = pNext;
1129 rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0);
1130 if( rc == SXERR_ABORT ){
1131 return SXERR_ABORT;
1132 }
1133 if( rc != SXERR_EMPTY ){
1134 /* Ticket 1433-008: Optimization #1: Consume input directly
1135 * without the overhead of a function call.
1136 * This is a very powerful optimization that improve
1137 * performance greatly.
1138 */
1139 jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0);
1140 }
1141 }
1142 /* Jump trailing commas */
1143 while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){
1144 pNext++;
1145 }
1146 pGen->pIn = pNext;
1147 }
1148 /* Restore token stream */
1149 pGen->pEnd = pTmp;
1150 }else{
1151 sxi32 nArg = 0;
1152 sxu32 nIdx = 0;
1153 rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0);
1154 if( rc == SXERR_ABORT ){
1155 return SXERR_ABORT;
1156 }else if(rc != SXERR_EMPTY ){
1157 nArg = 1;
1158 }
1159 if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){
1160 jx9_value *pObj;
1161 /* Emit the call instruction */
1162 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
1163 if( pObj == 0 ){
1164 SXUNUSED(iCompileFlag); /* cc warning */
1165 return GenStateOutOfMem(pGen);
1166 }
1167 jx9MemObjInitFromString(pGen->pVm, pObj, pName);
1168 /* Install in the literal table */
1169 GenStateInstallLiteral(&(*pGen), pObj, nIdx);
1170 }
1171 /* Emit the call instruction */
1172 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
1173 jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0);
1174 }
1175 /* Node successfully compiled */
1176 return SXRET_OK;
1177}
1178/*
1179 * Compile a node holding a variable declaration.
1180 * According to the J9X language reference
1181 * Variables in JX9 are represented by a dollar sign followed by the name of the variable.
1182 * The variable name is case-sensitive.
1183 * Variable names follow the same rules as other labels in JX9. A valid variable name
1184 * starts with a letter, underscore or any UTF-8 stream, followed by any number of letters
1185 * numbers, or underscores.
1186 * By default, variables are always assigned by value unless the target value is a JSON
1187 * array or a JSON object which is passed by reference.
1188 */
1189JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag)
1190{
1191 sxu32 nLine = pGen->pIn->nLine;
1192 SyHashEntry *pEntry;
1193 SyString *pName;
1194 char *zName = 0;
1195 sxi32 iP1;
1196 void *p3;
1197 sxi32 rc;
1198
1199 pGen->pIn++; /* Jump the dollar sign '$' */
1200 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
1201 /* Invalid variable name */
1202 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name");
1203 if( rc == SXERR_ABORT ){
1204 /* Error count limit reached, abort immediately */
1205 return SXERR_ABORT;
1206 }
1207 return SXRET_OK;
1208 }
1209 /* Extract variable name */
1210 pName = &pGen->pIn->sData;
1211 /* Advance the stream cursor */
1212 pGen->pIn++;
1213 pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte);
1214 if( pEntry == 0 ){
1215 /* Duplicate name */
1216 zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
1217 if( zName == 0 ){
1218 return GenStateOutOfMem(pGen);
1219 }
1220 /* Install in the hashtable */
1221 SyHashInsert(&pGen->hVar, zName, pName->nByte, zName);
1222 }else{
1223 /* Name already available */
1224 zName = (char *)pEntry->pUserData;
1225 }
1226 p3 = (void *)zName;
1227 iP1 = 0;
1228 if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){
1229 if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){
1230 /* Read-only load.In other words do not create the variable if inexistant */
1231 iP1 = 1;
1232 }
1233 }
1234 /* Emit the load instruction */
1235 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0);
1236 /* Node successfully compiled */
1237 return SXRET_OK;
1238}
1239/* Forward declaration */
1240static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc);
1241/*
1242 * Compile an annoynmous function or a closure.
1243 * According to the JX9 language reference
1244 * Anonymous functions, also known as closures, allow the creation of functions
1245 * which have no specified name. They are most useful as the value of callback
1246 * parameters, but they have many other uses. Closures can also be used as
1247 * the values of variables; Assigning a closure to a variable uses the same
1248 * syntax as any other assignment, including the trailing semicolon:
1249 * Example Anonymous function variable assignment example
1250 * $greet = function($name)
1251 * {
1252 * printf("Hello %s\r\n", $name);
1253 * };
1254 * $greet('World');
1255 * $greet('JX9');
1256 * Note that the implementation of annoynmous function and closure under
1257 * JX9 is completely different from the one used by the engine.
1258 */
1259JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag)
1260{
1261 jx9_vm_func *pAnnonFunc; /* Annonymous function body */
1262 char zName[512]; /* Unique lambda name */
1263 static int iCnt = 1; /* There is no worry about thread-safety here, because only
1264 * one thread is allowed to compile the script.
1265 */
1266 jx9_value *pObj;
1267 SyString sName;
1268 sxu32 nIdx;
1269 sxu32 nLen;
1270 sxi32 rc;
1271
1272 pGen->pIn++; /* Jump the 'function' keyword */
1273 if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){
1274 pGen->pIn++;
1275 }
1276 /* Reserve a constant for the lambda */
1277 pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx);
1278 if( pObj == 0 ){
1279 GenStateOutOfMem(pGen);
1280 SXUNUSED(iCompileFlag); /* cc warning */
1281 return SXERR_ABORT;
1282 }
1283 /* Generate a unique name */
1284 nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++);
1285 /* Make sure the generated name is unique */
1286 while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){
1287 nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++);
1288 }
1289 SyStringInitFromBuf(&sName, zName, nLen);
1290 jx9MemObjInitFromString(pGen->pVm, pObj, &sName);
1291 /* Compile the lambda body */
1292 rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc);
1293 if( rc == SXERR_ABORT ){
1294 return SXERR_ABORT;
1295 }
1296 /* Emit the load constant instruction */
1297 jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0);
1298 /* Node successfully compiled */
1299 return SXRET_OK;
1300}
1301/*
1302 * Compile the 'continue' statement.
1303 * According to the JX9 language reference
1304 * continue is used within looping structures to skip the rest of the current loop iteration
1305 * and continue execution at the condition evaluation and then the beginning of the next
1306 * iteration.
1307 * Note: Note that in JX9 the switch statement is considered a looping structure for
1308 * the purposes of continue.
1309 * continue accepts an optional numeric argument which tells it how many levels
1310 * of enclosing loops it should skip to the end of.
1311 * Note:
1312 * continue 0; and continue 1; is the same as running continue;.
1313 */
1314static sxi32 jx9CompileContinue(jx9_gen_state *pGen)
1315{
1316 GenBlock *pLoop; /* Target loop */
1317 sxi32 iLevel; /* How many nesting loop to skip */
1318 sxu32 nLine;
1319 sxi32 rc;
1320 nLine = pGen->pIn->nLine;
1321 iLevel = 0;
1322 /* Jump the 'continue' keyword */
1323 pGen->pIn++;
1324 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){
1325 /* optional numeric argument which tells us how many levels
1326 * of enclosing loops we should skip to the end of.
1327 */
1328 iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData);
1329 if( iLevel < 2 ){
1330 iLevel = 0;
1331 }
1332 pGen->pIn++; /* Jump the optional numeric argument */
1333 }
1334 /* Point to the target loop */
1335 pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel);
1336 if( pLoop == 0 ){
1337 /* Illegal continue */
1338 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch");
1339 if( rc == SXERR_ABORT ){
1340 /* Error count limit reached, abort immediately */
1341 return SXERR_ABORT;
1342 }
1343 }else{
1344 sxu32 nInstrIdx = 0;
1345 /* Emit the unconditional jump to the beginning of the target loop */
1346 jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx);
1347 if( pLoop->bPostContinue == TRUE ){
1348 JumpFixup sJumpFix;
1349 /* Post-continue */
1350 sJumpFix.nJumpType = JX9_OP_JMP;
1351 sJumpFix.nInstrIdx = nInstrIdx;
1352 SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix);
1353 }
1354 }
1355 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
1356 /* Not so fatal, emit a warning only */
1357 jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement");
1358 }
1359 /* Statement successfully compiled */
1360 return SXRET_OK;
1361}
1362/*
1363 * Compile the 'break' statement.
1364 * According to the JX9 language reference
1365 * break ends execution of the current for, foreach, while, do-while or switch
1366 * structure.
1367 * break accepts an optional numeric argument which tells it how many nested
1368 * enclosing structures are to be broken out of.
1369 */
1370static sxi32 jx9CompileBreak(jx9_gen_state *pGen)
1371{
1372 GenBlock *pLoop; /* Target loop */
1373 sxi32 iLevel; /* How many nesting loop to skip */
1374 sxu32 nLine;
1375 sxi32 rc;
1376 nLine = pGen->pIn->nLine;
1377 iLevel = 0;
1378 /* Jump the 'break' keyword */
1379 pGen->pIn++;
1380 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){
1381 /* optional numeric argument which tells us how many levels
1382 * of enclosing loops we should skip to the end of.
1383 */
1384 iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData);
1385 if( iLevel < 2 ){
1386 iLevel = 0;
1387 }
1388 pGen->pIn++; /* Jump the optional numeric argument */
1389 }
1390 /* Extract the target loop */
1391 pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel);
1392 if( pLoop == 0 ){
1393 /* Illegal break */
1394 rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch");
1395 if( rc == SXERR_ABORT ){
1396 /* Error count limit reached, abort immediately */
1397 return SXERR_ABORT;
1398 }
1399 }else{
1400 sxu32 nInstrIdx;
1401 rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx);
1402 if( rc == SXRET_OK ){
1403 /* Fix the jump later when the jump destination is resolved */
1404 GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx);
1405 }
1406 }
1407 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
1408 /* Not so fatal, emit a warning only */
1409 jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement");
1410 }
1411 /* Statement successfully compiled */
1412 return SXRET_OK;
1413}
1414/* Forward declaration */
1415static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags);
1416/*
1417 * Compile a JX9 block.
1418 * A block is simply one or more JX9 statements and expressions to compile
1419 * optionally delimited by braces {}.
1420 * Return SXRET_OK on success. Any other return value indicates failure
1421 * and this function takes care of generating the appropriate error
1422 * message.
1423 */
1424static sxi32 jx9CompileBlock(
1425 jx9_gen_state *pGen /* Code generator state */
1426 )
1427{
1428 sxi32 rc;
1429 if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){
1430 sxu32 nLine = pGen->pIn->nLine;
1431 rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(pGen->pVm), 0, 0);
1432 if( rc != SXRET_OK ){
1433 return SXERR_ABORT;
1434 }
1435 pGen->pIn++;
1436 /* Compile until we hit the closing braces '}' */
1437 for(;;){
1438 if( pGen->pIn >= pGen->pEnd ){
1439 /* No more token to process. Missing closing braces */
1440 jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'");
1441 break;
1442 }
1443 if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){
1444 /* Closing braces found, break immediately*/
1445 pGen->pIn++;
1446 break;
1447 }
1448 /* Compile a single statement */
1449 rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT);
1450 if( rc == SXERR_ABORT ){
1451 return SXERR_ABORT;
1452 }
1453 }
1454 GenStateLeaveBlock(&(*pGen), 0);
1455 }else{
1456 /* Compile a single statement */
1457 rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT);
1458 if( rc == SXERR_ABORT ){
1459 return SXERR_ABORT;
1460 }
1461 }
1462 /* Jump trailing semi-colons ';' */
1463 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){
1464 pGen->pIn++;
1465 }
1466 return SXRET_OK;
1467}
1468/*
1469 * Compile the gentle 'while' statement.
1470 * According to the JX9 language reference
1471 * while loops are the simplest type of loop in JX9.They behave just like their C counterparts.
1472 * The basic form of a while statement is:
1473 * while (expr)
1474 * statement
1475 * The meaning of a while statement is simple. It tells JX9 to execute the nested statement(s)
1476 * repeatedly, as long as the while expression evaluates to TRUE. The value of the expression
1477 * is checked each time at the beginning of the loop, so even if this value changes during
1478 * the execution of the nested statement(s), execution will not stop until the end of the iteration
1479 * (each time JX9 runs the statements in the loop is one iteration). Sometimes, if the while
1480 * expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once.
1481 * Like with the if statement, you can group multiple statements within the same while loop by surrounding
1482 * a group of statements with curly braces, or by using the alternate syntax:
1483 * while (expr):
1484 * statement
1485 * endwhile;
1486 */
1487static sxi32 jx9CompileWhile(jx9_gen_state *pGen)
1488{
1489 GenBlock *pWhileBlock = 0;
1490 SyToken *pTmp, *pEnd = 0;
1491 sxu32 nFalseJump;
1492 sxu32 nLine;
1493 sxi32 rc;
1494 nLine = pGen->pIn->nLine;
1495 /* Jump the 'while' keyword */
1496 pGen->pIn++;
1497 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
1498 /* Syntax error */
1499 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword");
1500 if( rc == SXERR_ABORT ){
1501 /* Error count limit reached, abort immediately */
1502 return SXERR_ABORT;
1503 }
1504 goto Synchronize;
1505 }
1506 /* Jump the left parenthesis '(' */
1507 pGen->pIn++;
1508 /* Create the loop block */
1509 rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock);
1510 if( rc != SXRET_OK ){
1511 return SXERR_ABORT;
1512 }
1513 /* Delimit the condition */
1514 jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
1515 if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
1516 /* Empty expression */
1517 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword");
1518 if( rc == SXERR_ABORT ){
1519 /* Error count limit reached, abort immediately */
1520 return SXERR_ABORT;
1521 }
1522 }
1523 /* Swap token streams */
1524 pTmp = pGen->pEnd;
1525 pGen->pEnd = pEnd;
1526 /* Compile the expression */
1527 rc = jx9CompileExpr(&(*pGen), 0, 0);
1528 if( rc == SXERR_ABORT ){
1529 /* Expression handler request an operation abort [i.e: Out-of-memory] */
1530 return SXERR_ABORT;
1531 }
1532 /* Update token stream */
1533 while(pGen->pIn < pEnd ){
1534 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
1535 if( rc == SXERR_ABORT ){
1536 return SXERR_ABORT;
1537 }
1538 pGen->pIn++;
1539 }
1540 /* Synchronize pointers */
1541 pGen->pIn = &pEnd[1];
1542 pGen->pEnd = pTmp;
1543 /* Emit the false jump */
1544 jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump);
1545 /* Save the instruction index so we can fix it later when the jump destination is resolved */
1546 GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump);
1547 /* Compile the loop body */
1548 rc = jx9CompileBlock(&(*pGen));
1549 if( rc == SXERR_ABORT ){
1550 return SXERR_ABORT;
1551 }
1552 /* Emit the unconditional jump to the start of the loop */
1553 jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0);
1554 /* Fix all jumps now the destination is resolved */
1555 GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm));
1556 /* Release the loop block */
1557 GenStateLeaveBlock(pGen, 0);
1558 /* Statement successfully compiled */
1559 return SXRET_OK;
1560Synchronize:
1561 /* Synchronize with the first semi-colon ';' so we can avoid
1562 * compiling this erroneous block.
1563 */
1564 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
1565 pGen->pIn++;
1566 }
1567 return SXRET_OK;
1568}
1569/*
1570 * Compile the complex and powerful 'for' statement.
1571 * According to the JX9 language reference
1572 * for loops are the most complex loops in JX9. They behave like their C counterparts.
1573 * The syntax of a for loop is:
1574 * for (expr1; expr2; expr3)
1575 * statement
1576 * The first expression (expr1) is evaluated (executed) once unconditionally at
1577 * the beginning of the loop.
1578 * In the beginning of each iteration, expr2 is evaluated. If it evaluates to
1579 * TRUE, the loop continues and the nested statement(s) are executed. If it evaluates
1580 * to FALSE, the execution of the loop ends.
1581 * At the end of each iteration, expr3 is evaluated (executed).
1582 * Each of the expressions can be empty or contain multiple expressions separated by commas.
1583 * In expr2, all expressions separated by a comma are evaluated but the result is taken
1584 * from the last part. expr2 being empty means the loop should be run indefinitely
1585 * (JX9 implicitly considers it as TRUE, like C). This may not be as useless as you might
1586 * think, since often you'd want to end the loop using a conditional break statement instead
1587 * of using the for truth expression.
1588 */
1589static sxi32 jx9CompileFor(jx9_gen_state *pGen)
1590{
1591 SyToken *pTmp, *pPostStart, *pEnd = 0;
1592 GenBlock *pForBlock = 0;
1593 sxu32 nFalseJump;
1594 sxu32 nLine;
1595 sxi32 rc;
1596 nLine = pGen->pIn->nLine;
1597 /* Jump the 'for' keyword */
1598 pGen->pIn++;
1599 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
1600 /* Syntax error */
1601 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword");
1602 if( rc == SXERR_ABORT ){
1603 /* Error count limit reached, abort immediately */
1604 return SXERR_ABORT;
1605 }
1606 return SXRET_OK;
1607 }
1608 /* Jump the left parenthesis '(' */
1609 pGen->pIn++;
1610 /* Delimit the init-expr;condition;post-expr */
1611 jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
1612 if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
1613 /* Empty expression */
1614 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression");
1615 if( rc == SXERR_ABORT ){
1616 /* Error count limit reached, abort immediately */
1617 return SXERR_ABORT;
1618 }
1619 /* Synchronize */
1620 pGen->pIn = pEnd;
1621 if( pGen->pIn < pGen->pEnd ){
1622 pGen->pIn++;
1623 }
1624 return SXRET_OK;
1625 }
1626 /* Swap token streams */
1627 pTmp = pGen->pEnd;
1628 pGen->pEnd = pEnd;
1629 /* Compile initialization expressions if available */
1630 rc = jx9CompileExpr(&(*pGen), 0, 0);
1631 /* Pop operand lvalues */
1632 if( rc == SXERR_ABORT ){
1633 /* Expression handler request an operation abort [i.e: Out-of-memory] */
1634 return SXERR_ABORT;
1635 }else if( rc != SXERR_EMPTY ){
1636 jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
1637 }
1638 if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
1639 /* Syntax error */
1640 rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
1641 "for: Expected ';' after initialization expressions");
1642 if( rc == SXERR_ABORT ){
1643 /* Error count limit reached, abort immediately */
1644 return SXERR_ABORT;
1645 }
1646 return SXRET_OK;
1647 }
1648 /* Jump the trailing ';' */
1649 pGen->pIn++;
1650 /* Create the loop block */
1651 rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock);
1652 if( rc != SXRET_OK ){
1653 return SXERR_ABORT;
1654 }
1655 /* Deffer continue jumps */
1656 pForBlock->bPostContinue = TRUE;
1657 /* Compile the condition */
1658 rc = jx9CompileExpr(&(*pGen), 0, 0);
1659 if( rc == SXERR_ABORT ){
1660 /* Expression handler request an operation abort [i.e: Out-of-memory] */
1661 return SXERR_ABORT;
1662 }else if( rc != SXERR_EMPTY ){
1663 /* Emit the false jump */
1664 jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump);
1665 /* Save the instruction index so we can fix it later when the jump destination is resolved */
1666 GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump);
1667 }
1668 if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
1669 /* Syntax error */
1670 rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
1671 "for: Expected ';' after conditionals expressions");
1672 if( rc == SXERR_ABORT ){
1673 /* Error count limit reached, abort immediately */
1674 return SXERR_ABORT;
1675 }
1676 return SXRET_OK;
1677 }
1678 /* Jump the trailing ';' */
1679 pGen->pIn++;
1680 /* Save the post condition stream */
1681 pPostStart = pGen->pIn;
1682 /* Compile the loop body */
1683 pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */
1684 pGen->pEnd = pTmp;
1685 rc = jx9CompileBlock(&(*pGen));
1686 if( rc == SXERR_ABORT ){
1687 return SXERR_ABORT;
1688 }
1689 /* Fix post-continue jumps */
1690 if( SySetUsed(&pForBlock->aPostContFix) > 0 ){
1691 JumpFixup *aPost;
1692 VmInstr *pInstr;
1693 sxu32 nJumpDest;
1694 sxu32 n;
1695 aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix);
1696 nJumpDest = jx9VmInstrLength(pGen->pVm);
1697 for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){
1698 pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx);
1699 if( pInstr ){
1700 /* Fix jump */
1701 pInstr->iP2 = nJumpDest;
1702 }
1703 }
1704 }
1705 /* compile the post-expressions if available */
1706 while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){
1707 pPostStart++;
1708 }
1709 if( pPostStart < pEnd ){
1710 SyToken *pTmpIn, *pTmpEnd;
1711 SWAP_DELIMITER(pGen, pPostStart, pEnd);
1712 rc = jx9CompileExpr(&(*pGen), 0, 0);
1713 if( pGen->pIn < pGen->pEnd ){
1714 /* Syntax error */
1715 rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions");
1716 if( rc == SXERR_ABORT ){
1717 /* Error count limit reached, abort immediately */
1718 return SXERR_ABORT;
1719 }
1720 return SXRET_OK;
1721 }
1722 RE_SWAP_DELIMITER(pGen);
1723 if( rc == SXERR_ABORT ){
1724 /* Expression handler request an operation abort [i.e: Out-of-memory] */
1725 return SXERR_ABORT;
1726 }else if( rc != SXERR_EMPTY){
1727 /* Pop operand lvalue */
1728 jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
1729 }
1730 }
1731 /* Emit the unconditional jump to the start of the loop */
1732 jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0);
1733 /* Fix all jumps now the destination is resolved */
1734 GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm));
1735 /* Release the loop block */
1736 GenStateLeaveBlock(pGen, 0);
1737 /* Statement successfully compiled */
1738 return SXRET_OK;
1739}
1740/* Expression tree validator callback used by the 'foreach' statement.
1741 * Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...]
1742 * are allowed.
1743 */
1744static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot)
1745{
1746 sxi32 rc = SXRET_OK; /* Assume a valid expression tree */
1747 if( pRoot->xCode != jx9CompileVariable ){
1748 /* Unexpected expression */
1749 rc = jx9GenCompileError(&(*pGen),
1750 E_ERROR,
1751 pRoot->pStart? pRoot->pStart->nLine : 0,
1752 "foreach: Expecting a variable name"
1753 );
1754 if( rc != SXERR_ABORT ){
1755 rc = SXERR_INVALID;
1756 }
1757 }
1758 return rc;
1759}
1760/*
1761 * Compile the 'foreach' statement.
1762 * According to the JX9 language reference
1763 * The foreach construct simply gives an easy way to iterate over arrays. foreach works
1764 * only on arrays (and objects), and will issue an error when you try to use it on a variable
1765 * with a different data type or an uninitialized variable. There are two syntaxes; the second
1766 * is a minor but useful extension of the first:
1767 * foreach (json_array_json_object as $value)
1768 * statement
1769 * foreach (json_array_json_objec as $key,$value)
1770 * statement
1771 * The first form loops over the array given by array_expression. On each loop, the value
1772 * of the current element is assigned to $value and the internal array pointer is advanced
1773 * by one (so on the next loop, you'll be looking at the next element).
1774 * The second form does the same thing, except that the current element's key will be assigned
1775 * to the variable $key on each loop.
1776 * Note:
1777 * When foreach first starts executing, the internal array pointer is automatically reset to the
1778 * first element of the array. This means that you do not need to call reset() before a foreach loop.
1779 */
1780static sxi32 jx9CompileForeach(jx9_gen_state *pGen)
1781{
1782 SyToken *pCur, *pTmp, *pEnd = 0;
1783 GenBlock *pForeachBlock = 0;
1784 jx9_foreach_info *pInfo;
1785 sxu32 nFalseJump;
1786 VmInstr *pInstr;
1787 sxu32 nLine;
1788 sxi32 rc;
1789 nLine = pGen->pIn->nLine;
1790 /* Jump the 'foreach' keyword */
1791 pGen->pIn++;
1792 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
1793 /* Syntax error */
1794 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('");
1795 if( rc == SXERR_ABORT ){
1796 /* Error count limit reached, abort immediately */
1797 return SXERR_ABORT;
1798 }
1799 goto Synchronize;
1800 }
1801 /* Jump the left parenthesis '(' */
1802 pGen->pIn++;
1803 /* Create the loop block */
1804 rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock);
1805 if( rc != SXRET_OK ){
1806 return SXERR_ABORT;
1807 }
1808 /* Delimit the expression */
1809 jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
1810 if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
1811 /* Empty expression */
1812 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression");
1813 if( rc == SXERR_ABORT ){
1814 /* Error count limit reached, abort immediately */
1815 return SXERR_ABORT;
1816 }
1817 /* Synchronize */
1818 pGen->pIn = pEnd;
1819 if( pGen->pIn < pGen->pEnd ){
1820 pGen->pIn++;
1821 }
1822 return SXRET_OK;
1823 }
1824 /* Compile the array expression */
1825 pCur = pGen->pIn;
1826 while( pCur < pEnd ){
1827 if( pCur->nType & JX9_TK_KEYWORD ){
1828 sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData);
1829 if( nKeywrd == JX9_TKWRD_AS ){
1830 /* Break with the first 'as' found */
1831 break;
1832 }
1833 }
1834 /* Advance the stream cursor */
1835 pCur++;
1836 }
1837 if( pCur <= pGen->pIn ){
1838 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
1839 "foreach: Missing array/object expression");
1840 if( rc == SXERR_ABORT ){
1841 /* Don't worry about freeing memory, everything will be released shortly */
1842 return SXERR_ABORT;
1843 }
1844 goto Synchronize;
1845 }
1846 /* Swap token streams */
1847 pTmp = pGen->pEnd;
1848 pGen->pEnd = pCur;
1849 rc = jx9CompileExpr(&(*pGen), 0, 0);
1850 if( rc == SXERR_ABORT ){
1851 /* Expression handler request an operation abort [i.e: Out-of-memory] */
1852 return SXERR_ABORT;
1853 }
1854 /* Update token stream */
1855 while(pGen->pIn < pCur ){
1856 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData);
1857 if( rc == SXERR_ABORT ){
1858 /* Don't worry about freeing memory, everything will be released shortly */
1859 return SXERR_ABORT;
1860 }
1861 pGen->pIn++;
1862 }
1863 pCur++; /* Jump the 'as' keyword */
1864 pGen->pIn = pCur;
1865 if( pGen->pIn >= pEnd ){
1866 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair");
1867 if( rc == SXERR_ABORT ){
1868 return SXERR_ABORT;
1869 }
1870 }
1871 /* Create the foreach context */
1872 pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info));
1873 if( pInfo == 0 ){
1874 jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory");
1875 return SXERR_ABORT;
1876 }
1877 /* Zero the structure */
1878 SyZero(pInfo, sizeof(jx9_foreach_info));
1879 /* Initialize structure fields */
1880 SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *));
1881 /* Check if we have a key field */
1882 while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){
1883 pCur++;
1884 }
1885 if( pCur < pEnd ){
1886 /* Compile the expression holding the key name */
1887 if( pGen->pIn >= pCur ){
1888 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key");
1889 if( rc == SXERR_ABORT ){
1890 /* Don't worry about freeing memory, everything will be released shortly */
1891 return SXERR_ABORT;
1892 }
1893 }else{
1894 pGen->pEnd = pCur;
1895 rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
1896 if( rc == SXERR_ABORT ){
1897 /* Don't worry about freeing memory, everything will be released shortly */
1898 return SXERR_ABORT;
1899 }
1900 pInstr = jx9VmPopInstr(pGen->pVm);
1901 if( pInstr->p3 ){
1902 /* Record key name */
1903 SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3));
1904 }
1905 pInfo->iFlags |= JX9_4EACH_STEP_KEY;
1906 }
1907 pGen->pIn = &pCur[1]; /* Jump the arrow */
1908 }
1909 pGen->pEnd = pEnd;
1910 if( pGen->pIn >= pEnd ){
1911 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value");
1912 if( rc == SXERR_ABORT ){
1913 /* Don't worry about freeing memory, everything will be released shortly */
1914 return SXERR_ABORT;
1915 }
1916 goto Synchronize;
1917 }
1918 /* Compile the expression holding the value name */
1919 rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator);
1920 if( rc == SXERR_ABORT ){
1921 /* Don't worry about freeing memory, everything will be released shortly */
1922 return SXERR_ABORT;
1923 }
1924 pInstr = jx9VmPopInstr(pGen->pVm);
1925 if( pInstr->p3 ){
1926 /* Record value name */
1927 SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3));
1928 }
1929 /* Emit the 'FOREACH_INIT' instruction */
1930 jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump);
1931 /* Save the instruction index so we can fix it later when the jump destination is resolved */
1932 GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump);
1933 /* Record the first instruction to execute */
1934 pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm);
1935 /* Emit the FOREACH_STEP instruction */
1936 jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump);
1937 /* Save the instruction index so we can fix it later when the jump destination is resolved */
1938 GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump);
1939 /* Compile the loop body */
1940 pGen->pIn = &pEnd[1];
1941 pGen->pEnd = pTmp;
1942 rc = jx9CompileBlock(&(*pGen));
1943 if( rc == SXERR_ABORT ){
1944 /* Don't worry about freeing memory, everything will be released shortly */
1945 return SXERR_ABORT;
1946 }
1947 /* Emit the unconditional jump to the start of the loop */
1948 jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0);
1949 /* Fix all jumps now the destination is resolved */
1950 GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm));
1951 /* Release the loop block */
1952 GenStateLeaveBlock(pGen, 0);
1953 /* Statement successfully compiled */
1954 return SXRET_OK;
1955Synchronize:
1956 /* Synchronize with the first semi-colon ';' so we can avoid
1957 * compiling this erroneous block.
1958 */
1959 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
1960 pGen->pIn++;
1961 }
1962 return SXRET_OK;
1963}
1964/*
1965 * Compile the infamous if/elseif/else if/else statements.
1966 * According to the JX9 language reference
1967 * The if construct is one of the most important features of many languages JX9 included.
1968 * It allows for conditional execution of code fragments. JX9 features an if structure
1969 * that is similar to that of C:
1970 * if (expr)
1971 * statement
1972 * else construct:
1973 * Often you'd want to execute a statement if a certain condition is met, and a different
1974 * statement if the condition is not met. This is what else is for. else extends an if statement
1975 * to execute a statement in case the expression in the if statement evaluates to FALSE.
1976 * For example, the following code would display a is greater than b if $a is greater than
1977 * $b, and a is NOT greater than b otherwise.
1978 * The else statement is only executed if the if expression evaluated to FALSE, and if there
1979 * were any elseif expressions - only if they evaluated to FALSE as well
1980 * elseif
1981 * elseif, as its name suggests, is a combination of if and else. Like else, it extends
1982 * an if statement to execute a different statement in case the original if expression evaluates
1983 * to FALSE. However, unlike else, it will execute that alternative expression only if the elseif
1984 * conditional expression evaluates to TRUE. For example, the following code would display a is bigger
1985 * than b, a equal to b or a is smaller than b:
1986 * if ($a > $b) {
1987 * print "a is bigger than b";
1988 * } elseif ($a == $b) {
1989 * print "a is equal to b";
1990 * } else {
1991 * print "a is smaller than b";
1992 * }
1993 */
1994static sxi32 jx9CompileIf(jx9_gen_state *pGen)
1995{
1996 SyToken *pToken, *pTmp, *pEnd = 0;
1997 GenBlock *pCondBlock = 0;
1998 sxu32 nJumpIdx;
1999 sxu32 nKeyID;
2000 sxi32 rc;
2001 /* Jump the 'if' keyword */
2002 pGen->pIn++;
2003 pToken = pGen->pIn;
2004 /* Create the conditional block */
2005 rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(pGen->pVm), 0, &pCondBlock);
2006 if( rc != SXRET_OK ){
2007 return SXERR_ABORT;
2008 }
2009 /* Process as many [if/else if/elseif/else] blocks as we can */
2010 for(;;){
2011 if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_LPAREN) == 0 ){
2012 /* Syntax error */
2013 if( pToken >= pGen->pEnd ){
2014 pToken--;
2015 }
2016 rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('");
2017 if( rc == SXERR_ABORT ){
2018 /* Error count limit reached, abort immediately */
2019 return SXERR_ABORT;
2020 }
2021 goto Synchronize;
2022 }
2023 /* Jump the left parenthesis '(' */
2024 pToken++;
2025 /* Delimit the condition */
2026 jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
2027 if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){
2028 /* Syntax error */
2029 if( pToken >= pGen->pEnd ){
2030 pToken--;
2031 }
2032 rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'");
2033 if( rc == SXERR_ABORT ){
2034 /* Error count limit reached, abort immediately */
2035 return SXERR_ABORT;
2036 }
2037 goto Synchronize;
2038 }
2039 /* Swap token streams */
2040 SWAP_TOKEN_STREAM(pGen, pToken, pEnd);
2041 /* Compile the condition */
2042 rc = jx9CompileExpr(&(*pGen), 0, 0);
2043 /* Update token stream */
2044 while(pGen->pIn < pEnd ){
2045 jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
2046 pGen->pIn++;
2047 }
2048 pGen->pIn = &pEnd[1];
2049 pGen->pEnd = pTmp;
2050 if( rc == SXERR_ABORT ){
2051 /* Expression handler request an operation abort [i.e: Out-of-memory] */
2052 return SXERR_ABORT;
2053 }
2054 /* Emit the false jump */
2055 jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx);
2056 /* Save the instruction index so we can fix it later when the jump destination is resolved */
2057 GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx);
2058 /* Compile the body */
2059 rc = jx9CompileBlock(&(*pGen));
2060 if( rc == SXERR_ABORT ){
2061 return SXERR_ABORT;
2062 }
2063 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
2064 break;
2065 }
2066 /* Ensure that the keyword ID is 'else if' or 'else' */
2067 nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
2068 if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){
2069 break;
2070 }
2071 /* Emit the unconditional jump */
2072 jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx);
2073 /* Save the instruction index so we can fix it later when the jump destination is resolved */
2074 GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx);
2075 if( nKeyID & JX9_TKWRD_ELSE ){
2076 pToken = &pGen->pIn[1];
2077 if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 ||
2078 SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){
2079 break;
2080 }
2081 pGen->pIn++; /* Jump the 'else' keyword */
2082 }
2083 pGen->pIn++; /* Jump the 'elseif/if' keyword */
2084 /* Synchronize cursors */
2085 pToken = pGen->pIn;
2086 /* Fix the false jump */
2087 GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm));
2088 } /* For(;;) */
2089 /* Fix the false jump */
2090 GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm));
2091 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) &&
2092 (SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){
2093 /* Compile the else block */
2094 pGen->pIn++;
2095 rc = jx9CompileBlock(&(*pGen));
2096 if( rc == SXERR_ABORT ){
2097
2098 return SXERR_ABORT;
2099 }
2100 }
2101 nJumpIdx = jx9VmInstrLength(pGen->pVm);
2102 /* Fix all unconditional jumps now the destination is resolved */
2103 GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx);
2104 /* Release the conditional block */
2105 GenStateLeaveBlock(pGen, 0);
2106 /* Statement successfully compiled */
2107 return SXRET_OK;
2108Synchronize:
2109 /* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block.
2110 */
2111 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
2112 pGen->pIn++;
2113 }
2114 return SXRET_OK;
2115}
2116/*
2117 * Compile the return statement.
2118 * According to the JX9 language reference
2119 * If called from within a function, the return() statement immediately ends execution
2120 * of the current function, and returns its argument as the value of the function call.
2121 * return() will also end the execution of an eval() statement or script file.
2122 * If called from the global scope, then execution of the current script file is ended.
2123 * If the current script file was include()ed or require()ed, then control is passed back
2124 * to the calling file. Furthermore, if the current script file was include()ed, then the value
2125 * given to return() will be returned as the value of the include() call. If return() is called
2126 * from within the main script file, then script execution end.
2127 * Note that since return() is a language construct and not a function, the parentheses
2128 * surrounding its arguments are not required. It is common to leave them out, and you actually
2129 * should do so as JX9 has less work to do in this case.
2130 * Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead..
2131 */
2132static sxi32 jx9CompileReturn(jx9_gen_state *pGen)
2133{
2134 sxi32 nRet = 0; /* TRUE if there is a return value */
2135 sxi32 rc;
2136 /* Jump the 'return' keyword */
2137 pGen->pIn++;
2138 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
2139 /* Compile the expression */
2140 rc = jx9CompileExpr(&(*pGen), 0, 0);
2141 if( rc == SXERR_ABORT ){
2142 return SXERR_ABORT;
2143 }else if(rc != SXERR_EMPTY ){
2144 nRet = 1;
2145 }
2146 }
2147 /* Emit the done instruction */
2148 jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, nRet, 0, 0, 0);
2149 return SXRET_OK;
2150}
2151/*
2152 * Compile the die/exit language construct.
2153 * The role of these constructs is to terminate execution of the script.
2154 * Shutdown functions will always be executed even if exit() is called.
2155 */
2156static sxi32 jx9CompileHalt(jx9_gen_state *pGen)
2157{
2158 sxi32 nExpr = 0;
2159 sxi32 rc;
2160 /* Jump the die/exit keyword */
2161 pGen->pIn++;
2162 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
2163 /* Compile the expression */
2164 rc = jx9CompileExpr(&(*pGen), 0, 0);
2165 if( rc == SXERR_ABORT ){
2166 return SXERR_ABORT;
2167 }else if(rc != SXERR_EMPTY ){
2168 nExpr = 1;
2169 }
2170 }
2171 /* Emit the HALT instruction */
2172 jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0);
2173 return SXRET_OK;
2174}
2175/*
2176 * Compile the static statement.
2177 * According to the JX9 language reference
2178 * Another important feature of variable scoping is the static variable.
2179 * A static variable exists only in a local function scope, but it does not lose its value
2180 * when program execution leaves this scope.
2181 * Static variables also provide one way to deal with recursive functions.
2182 */
2183static sxi32 jx9CompileStatic(jx9_gen_state *pGen)
2184{
2185 jx9_vm_func_static_var sStatic; /* Structure describing the static variable */
2186 jx9_vm_func *pFunc; /* Enclosing function */
2187 GenBlock *pBlock;
2188 SyString *pName;
2189 char *zDup;
2190 sxu32 nLine;
2191 sxi32 rc;
2192 /* Jump the static keyword */
2193 nLine = pGen->pIn->nLine;
2194 pGen->pIn++;
2195 /* Extract the enclosing function if any */
2196 pBlock = pGen->pCurrent;
2197 while( pBlock ){
2198 if( pBlock->iFlags & GEN_BLOCK_FUNC){
2199 break;
2200 }
2201 /* Point to the upper block */
2202 pBlock = pBlock->pParent;
2203 }
2204 if( pBlock == 0 ){
2205 /* Static statement, called outside of a function body, treat it as a simple variable. */
2206 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){
2207 rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword");
2208 if( rc == SXERR_ABORT ){
2209 return SXERR_ABORT;
2210 }
2211 goto Synchronize;
2212 }
2213 /* Compile the expression holding the variable */
2214 rc = jx9CompileExpr(&(*pGen), 0, 0);
2215 if( rc == SXERR_ABORT ){
2216 return SXERR_ABORT;
2217 }else if( rc != SXERR_EMPTY ){
2218 /* Emit the POP instruction */
2219 jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
2220 }
2221 return SXRET_OK;
2222 }
2223 pFunc = (jx9_vm_func *)pBlock->pUserData;
2224 /* Make sure we are dealing with a valid statement */
2225 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd ||
2226 (pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
2227 rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword");
2228 if( rc == SXERR_ABORT ){
2229 return SXERR_ABORT;
2230 }
2231 goto Synchronize;
2232 }
2233 pGen->pIn++;
2234 /* Extract variable name */
2235 pName = &pGen->pIn->sData;
2236 pGen->pIn++; /* Jump the var name */
2237 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){
2238 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData);
2239 goto Synchronize;
2240 }
2241 /* Initialize the structure describing the static variable */
2242 SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
2243 sStatic.nIdx = SXU32_HIGH; /* Not yet created */
2244 /* Duplicate variable name */
2245 zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
2246 if( zDup == 0 ){
2247 jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory");
2248 return SXERR_ABORT;
2249 }
2250 SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte);
2251 /* Check if we have an expression to compile */
2252 if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){
2253 SySet *pInstrContainer;
2254 pGen->pIn++; /* Jump the equal '=' sign */
2255 /* Swap bytecode container */
2256 pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
2257 jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode);
2258 /* Compile the expression */
2259 rc = jx9CompileExpr(&(*pGen), 0, 0);
2260 /* Emit the done instruction */
2261 jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
2262 /* Restore default bytecode container */
2263 jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
2264 }
2265 /* Finally save the compiled static variable in the appropriate container */
2266 SySetPut(&pFunc->aStatic, (const void *)&sStatic);
2267 return SXRET_OK;
2268Synchronize:
2269 /* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous
2270 * statement.
2271 */
2272 while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
2273 pGen->pIn++;
2274 }
2275 return SXRET_OK;
2276}
2277/*
2278 * Compile the 'const' statement.
2279 * According to the JX9 language reference
2280 * A constant is an identifier (name) for a simple value. As the name suggests, that value
2281 * cannot change during the execution of the script (except for magic constants, which aren't actually constants).
2282 * A constant is case-sensitive by default. By convention, constant identifiers are always uppercase.
2283 * The name of a constant follows the same rules as any label in JX9. A valid constant name starts
2284 * with a letter or underscore, followed by any number of letters, numbers, or underscores.
2285 * As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*
2286 * Syntax
2287 * You can define a constant by using the define()-function or by using the const keyword outside
2288 * a object definition. Once a constant is defined, it can never be changed or undefined.
2289 * You can get the value of a constant by simply specifying its name. Unlike with variables
2290 * you should not prepend a constant with a $. You can also use the function constant() to read
2291 * a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants()
2292 * to get a list of all defined constants.
2293 */
2294static sxi32 jx9CompileConstant(jx9_gen_state *pGen)
2295{
2296 SySet *pConsCode, *pInstrContainer;
2297 sxu32 nLine = pGen->pIn->nLine;
2298 SyString *pName;
2299 sxi32 rc;
2300 pGen->pIn++; /* Jump the 'const' keyword */
2301 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
2302 /* Invalid constant name */
2303 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name");
2304 if( rc == SXERR_ABORT ){
2305 /* Error count limit reached, abort immediately */
2306 return SXERR_ABORT;
2307 }
2308 goto Synchronize;
2309 }
2310 /* Peek constant name */
2311 pName = &pGen->pIn->sData;
2312 /* Make sure the constant name isn't reserved */
2313 if( GenStateIsReservedID(pName) ){
2314 /* Reserved constant */
2315 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName);
2316 if( rc == SXERR_ABORT ){
2317 /* Error count limit reached, abort immediately */
2318 return SXERR_ABORT;
2319 }
2320 goto Synchronize;
2321 }
2322 pGen->pIn++;
2323 if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){
2324 /* Invalid statement*/
2325 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name");
2326 if( rc == SXERR_ABORT ){
2327 /* Error count limit reached, abort immediately */
2328 return SXERR_ABORT;
2329 }
2330 goto Synchronize;
2331 }
2332 pGen->pIn++; /*Jump the equal sign */
2333 /* Allocate a new constant value container */
2334 pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet));
2335 if( pConsCode == 0 ){
2336 return GenStateOutOfMem(pGen);
2337 }
2338 SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
2339 /* Swap bytecode container */
2340 pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
2341 jx9VmSetByteCodeContainer(pGen->pVm, pConsCode);
2342 /* Compile constant value */
2343 rc = jx9CompileExpr(&(*pGen), 0, 0);
2344 /* Emit the done instruction */
2345 jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
2346 jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
2347 if( rc == SXERR_ABORT ){
2348 /* Don't worry about freeing memory, everything will be released shortly */
2349 return SXERR_ABORT;
2350 }
2351 SySetSetUserData(pConsCode, pGen->pVm);
2352 /* Register the constant */
2353 rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode);
2354 if( rc != SXRET_OK ){
2355 SySetRelease(pConsCode);
2356 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode);
2357 }
2358 return SXRET_OK;
2359Synchronize:
2360 /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */
2361 while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
2362 pGen->pIn++;
2363 }
2364 return SXRET_OK;
2365}
2366/*
2367 * Compile the uplink construct.
2368 * According to the JX9 language reference
2369 * In JX9 global variables must be declared uplink inside a function if they are going
2370 * to be used in that function.
2371 * Example #1 Using global
2372 * $a = 1;
2373 * $b = 2;
2374 * function Sum()
2375 * {
2376 * uplink $a, $b;
2377 * $b = $a + $b;
2378 * }
2379 * Sum();
2380 * print $b;
2381 * ?>
2382 * The above script will output 3. By declaring $a and $b global within the function
2383 * all references to either variable will refer to the global version. There is no limit
2384 * to the number of global variables that can be manipulated by a function.
2385 */
2386static sxi32 jx9CompileUplink(jx9_gen_state *pGen)
2387{
2388 SyToken *pTmp, *pNext = 0;
2389 sxi32 nExpr;
2390 sxi32 rc;
2391 /* Jump the 'uplink' keyword */
2392 pGen->pIn++;
2393 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){
2394 /* Nothing to process */
2395 return SXRET_OK;
2396 }
2397 pTmp = pGen->pEnd;
2398 nExpr = 0;
2399 while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){
2400 if( pGen->pIn < pNext ){
2401 pGen->pEnd = pNext;
2402 if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){
2403 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name");
2404 if( rc == SXERR_ABORT ){
2405 return SXERR_ABORT;
2406 }
2407 }else{
2408 pGen->pIn++;
2409 if( pGen->pIn >= pGen->pEnd ){
2410 /* Emit a warning */
2411 jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name");
2412 }else{
2413 rc = jx9CompileExpr(&(*pGen), 0, 0);
2414 if( rc == SXERR_ABORT ){
2415 return SXERR_ABORT;
2416 }else if(rc != SXERR_EMPTY ){
2417 nExpr++;
2418 }
2419 }
2420 }
2421 }
2422 /* Next expression in the stream */
2423 pGen->pIn = pNext;
2424 /* Jump trailing commas */
2425 while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){
2426 pGen->pIn++;
2427 }
2428 }
2429 /* Restore token stream */
2430 pGen->pEnd = pTmp;
2431 if( nExpr > 0 ){
2432 /* Emit the uplink instruction */
2433 jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0);
2434 }
2435 return SXRET_OK;
2436}
2437/*
2438 * Compile a switch block.
2439 * (See block-comment below for more information)
2440 */
2441static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart)
2442{
2443 sxi32 rc = SXRET_OK;
2444 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){
2445 /* Unexpected token */
2446 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData);
2447 if( rc == SXERR_ABORT ){
2448 return SXERR_ABORT;
2449 }
2450 pGen->pIn++;
2451 }
2452 pGen->pIn++;
2453 /* First instruction to execute in this block. */
2454 *pBlockStart = jx9VmInstrLength(pGen->pVm);
2455 /* Compile the block until we hit a case/default/endswitch keyword
2456 * or the '}' token */
2457 for(;;){
2458 if( pGen->pIn >= pGen->pEnd ){
2459 /* No more input to process */
2460 break;
2461 }
2462 rc = SXRET_OK;
2463 if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
2464 if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){
2465 rc = SXERR_EOF;
2466 break;
2467 }
2468 }else{
2469 sxi32 nKwrd;
2470 /* Extract the keyword */
2471 nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
2472 if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){
2473 break;
2474 }
2475 }
2476 /* Compile block */
2477 rc = jx9CompileBlock(&(*pGen));
2478 if( rc == SXERR_ABORT ){
2479 return SXERR_ABORT;
2480 }
2481 }
2482 return rc;
2483}
2484/*
2485 * Compile a case eXpression.
2486 * (See block-comment below for more information)
2487 */
2488static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_case_expr *pExpr)
2489{
2490 SySet *pInstrContainer;
2491 SyToken *pEnd, *pTmp;
2492 sxi32 iNest = 0;
2493 sxi32 rc;
2494 /* Delimit the expression */
2495 pEnd = pGen->pIn;
2496 while( pEnd < pGen->pEnd ){
2497 if( pEnd->nType & JX9_TK_LPAREN /*(*/ ){
2498 /* Increment nesting level */
2499 iNest++;
2500 }else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){
2501 /* Decrement nesting level */
2502 iNest--;
2503 }else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){
2504 break;
2505 }
2506 pEnd++;
2507 }
2508 if( pGen->pIn >= pEnd ){
2509 rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression");
2510 if( rc == SXERR_ABORT ){
2511 /* Error count limit reached, abort immediately */
2512 return SXERR_ABORT;
2513 }
2514 }
2515 /* Swap token stream */
2516 pTmp = pGen->pEnd;
2517 pGen->pEnd = pEnd;
2518 pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
2519 jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode);
2520 rc = jx9CompileExpr(&(*pGen), 0, 0);
2521 /* Emit the done instruction */
2522 jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
2523 jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
2524 /* Update token stream */
2525 pGen->pIn = pEnd;
2526 pGen->pEnd = pTmp;
2527 if( rc == SXERR_ABORT ){
2528 return SXERR_ABORT;
2529 }
2530 return SXRET_OK;
2531}
2532/*
2533 * Compile the smart switch statement.
2534 * According to the JX9 language reference manual
2535 * The switch statement is similar to a series of IF statements on the same expression.
2536 * In many occasions, you may want to compare the same variable (or expression) with many
2537 * different values, and execute a different piece of code depending on which value it equals to.
2538 * This is exactly what the switch statement is for.
2539 * Note: Note that unlike some other languages, the continue statement applies to switch and acts
2540 * similar to break. If you have a switch inside a loop and wish to continue to the next iteration
2541 * of the outer loop, use continue 2.
2542 * Note that switch/case does loose comparision.
2543 * It is important to understand how the switch statement is executed in order to avoid mistakes.
2544 * The switch statement executes line by line (actually, statement by statement).
2545 * In the beginning, no code is executed. Only when a case statement is found with a value that
2546 * matches the value of the switch expression does JX9 begin to execute the statements.
2547 * JX9 continues to execute the statements until the end of the switch block, or the first time
2548 * it sees a break statement. If you don't write a break statement at the end of a case's statement list.
2549 * In a switch statement, the condition is evaluated only once and the result is compared to each
2550 * case statement. In an elseif statement, the condition is evaluated again. If your condition
2551 * is more complicated than a simple compare and/or is in a tight loop, a switch may be faster.
2552 * The statement list for a case can also be empty, which simply passes control into the statement
2553 * list for the next case.
2554 * The case expression may be any expression that evaluates to a simple type, that is, integer
2555 * or floating-point numbers and strings.
2556 */
2557static sxi32 jx9CompileSwitch(jx9_gen_state *pGen)
2558{
2559 GenBlock *pSwitchBlock;
2560 SyToken *pTmp, *pEnd;
2561 jx9_switch *pSwitch;
2562 sxu32 nLine;
2563 sxi32 rc;
2564 nLine = pGen->pIn->nLine;
2565 /* Jump the 'switch' keyword */
2566 pGen->pIn++;
2567 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
2568 /* Syntax error */
2569 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword");
2570 if( rc == SXERR_ABORT ){
2571 /* Error count limit reached, abort immediately */
2572 return SXERR_ABORT;
2573 }
2574 goto Synchronize;
2575 }
2576 /* Jump the left parenthesis '(' */
2577 pGen->pIn++;
2578 pEnd = 0; /* cc warning */
2579 /* Create the loop block */
2580 rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH,
2581 jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock);
2582 if( rc != SXRET_OK ){
2583 return SXERR_ABORT;
2584 }
2585 /* Delimit the condition */
2586 jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
2587 if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){
2588 /* Empty expression */
2589 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword");
2590 if( rc == SXERR_ABORT ){
2591 /* Error count limit reached, abort immediately */
2592 return SXERR_ABORT;
2593 }
2594 }
2595 /* Swap token streams */
2596 pTmp = pGen->pEnd;
2597 pGen->pEnd = pEnd;
2598 /* Compile the expression */
2599 rc = jx9CompileExpr(&(*pGen), 0, 0);
2600 if( rc == SXERR_ABORT ){
2601 /* Expression handler request an operation abort [i.e: Out-of-memory] */
2602 return SXERR_ABORT;
2603 }
2604 /* Update token stream */
2605 while(pGen->pIn < pEnd ){
2606 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine,
2607 "Switch: Unexpected token '%z'", &pGen->pIn->sData);
2608 if( rc == SXERR_ABORT ){
2609 return SXERR_ABORT;
2610 }
2611 pGen->pIn++;
2612 }
2613 pGen->pIn = &pEnd[1];
2614 pGen->pEnd = pTmp;
2615 if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd ||
2616 (pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){
2617 pTmp = pGen->pIn;
2618 if( pTmp >= pGen->pEnd ){
2619 pTmp--;
2620 }
2621 /* Unexpected token */
2622 rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData);
2623 if( rc == SXERR_ABORT ){
2624 return SXERR_ABORT;
2625 }
2626 goto Synchronize;
2627 }
2628 pGen->pIn++; /* Jump the leading curly braces/colons */
2629 /* Create the switch blocks container */
2630 pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch));
2631 if( pSwitch == 0 ){
2632 /* Abort compilation */
2633 return GenStateOutOfMem(pGen);
2634 }
2635 /* Zero the structure */
2636 SyZero(pSwitch, sizeof(jx9_switch));
2637 /* Initialize fields */
2638 SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr));
2639 /* Emit the switch instruction */
2640 jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0);
2641 /* Compile case blocks */
2642 for(;;){
2643 sxu32 nKwrd;
2644 if( pGen->pIn >= pGen->pEnd ){
2645 /* No more input to process */
2646 break;
2647 }
2648 if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){
2649 if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){
2650 /* Unexpected token */
2651 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'",
2652 &pGen->pIn->sData);
2653 if( rc == SXERR_ABORT ){
2654 return SXERR_ABORT;
2655 }
2656 /* FALL THROUGH */
2657 }
2658 /* Block compiled */
2659 break;
2660 }
2661 /* Extract the keyword */
2662 nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData);
2663 if( nKwrd == JX9_TKWRD_DEFAULT ){
2664 /*
2665 * Accroding to the JX9 language reference manual
2666 * A special case is the default case. This case matches anything
2667 * that wasn't matched by the other cases.
2668 */
2669 if( pSwitch->nDefault > 0 ){
2670 /* Default case already compiled */
2671 rc = jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled");
2672 if( rc == SXERR_ABORT ){
2673 return SXERR_ABORT;
2674 }
2675 }
2676 pGen->pIn++; /* Jump the 'default' keyword */
2677 /* Compile the default block */
2678 rc = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault);
2679 if( rc == SXERR_ABORT){
2680 return SXERR_ABORT;
2681 }else if( rc == SXERR_EOF ){
2682 break;
2683 }
2684 }else if( nKwrd == JX9_TKWRD_CASE ){
2685 jx9_case_expr sCase;
2686 /* Standard case block */
2687 pGen->pIn++; /* Jump the 'case' keyword */
2688 /* initialize the structure */
2689 SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
2690 /* Compile the case expression */
2691 rc = GenStateCompileCaseExpr(pGen, &sCase);
2692 if( rc == SXERR_ABORT ){
2693 return SXERR_ABORT;
2694 }
2695 /* Compile the case block */
2696 rc = GenStateCompileSwitchBlock(pGen,&sCase.nStart);
2697 /* Insert in the switch container */
2698 SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase);
2699 if( rc == SXERR_ABORT){
2700 return SXERR_ABORT;
2701 }else if( rc == SXERR_EOF ){
2702 break;
2703 }
2704 }else{
2705 /* Unexpected token */
2706 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'",
2707 &pGen->pIn->sData);
2708 if( rc == SXERR_ABORT ){
2709 return SXERR_ABORT;
2710 }
2711 break;
2712 }
2713 }
2714 /* Fix all jumps now the destination is resolved */
2715 pSwitch->nOut = jx9VmInstrLength(pGen->pVm);
2716 GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm));
2717 /* Release the loop block */
2718 GenStateLeaveBlock(pGen, 0);
2719 if( pGen->pIn < pGen->pEnd ){
2720 /* Jump the trailing curly braces */
2721 pGen->pIn++;
2722 }
2723 /* Statement successfully compiled */
2724 return SXRET_OK;
2725Synchronize:
2726 /* Synchronize with the first semi-colon */
2727 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){
2728 pGen->pIn++;
2729 }
2730 return SXRET_OK;
2731}
2732/*
2733 * Process default argument values. That is, a function may define C++-style default value
2734 * as follows:
2735 * function makecoffee($type = "cappuccino")
2736 * {
2737 * return "Making a cup of $type.\n";
2738 * }
2739 * Some features:
2740 * 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous
2741 * functions, array member, ..]
2742 * 2 -) Full type hinting: (Arguments are automatically casted to the desired type)
2743 * Example:
2744 * function a(string $a){} function b(int $a, string $c, float $d){}
2745 * 3 -) Function overloading!!
2746 * Example:
2747 * function foo($a) {
2748 * return $a.JX9_EOL;
2749 * }
2750 * function foo($a, $b) {
2751 * return $a + $b;
2752 * }
2753 * print foo(5); // Prints "5"
2754 * print foo(5, 2); // Prints "7"
2755 * // Same arg
2756 * function foo(string $a)
2757 * {
2758 * print "a is a string\n";
2759 * dump($a);
2760 * }
2761 * function foo(int $a)
2762 * {
2763 * print "a is integer\n";
2764 * dump($a);
2765 * }
2766 * function foo(array $a)
2767 * {
2768 * print "a is an array\n";
2769 * dump($a);
2770 * }
2771 * foo('This is a great feature'); // a is a string [first foo]
2772 * foo(52); // a is integer [second foo]
2773 * foo(array(14, __TIME__, __DATE__)); // a is an array [third foo]
2774 * Please refer to the official documentation for more information on the powerful extension
2775 * introduced by the JX9 engine.
2776 */
2777static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd)
2778{
2779 SyToken *pTmpIn, *pTmpEnd;
2780 SySet *pInstrContainer;
2781 sxi32 rc;
2782 /* Swap token stream */
2783 SWAP_DELIMITER(pGen, pIn, pEnd);
2784 pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
2785 jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode);
2786 /* Compile the expression holding the argument value */
2787 rc = jx9CompileExpr(&(*pGen), 0, 0);
2788 /* Emit the done instruction */
2789 jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0);
2790 jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
2791 RE_SWAP_DELIMITER(pGen);
2792 if( rc == SXERR_ABORT ){
2793 return SXERR_ABORT;
2794 }
2795 return SXRET_OK;
2796}
2797/*
2798 * Collect function arguments one after one.
2799 * According to the JX9 language reference manual.
2800 * Information may be passed to functions via the argument list, which is a comma-delimited
2801 * list of expressions.
2802 * JX9 supports passing arguments by value (the default), passing by reference
2803 * and default argument values. Variable-length argument lists are also supported,
2804 * see also the function references for func_num_args(), func_get_arg(), and func_get_args()
2805 * for more information.
2806 * Example #1 Passing arrays to functions
2807 * <?jx9
2808 * function takes_array($input)
2809 * {
2810 * print "$input[0] + $input[1] = ", $input[0]+$input[1];
2811 * }
2812 * ?>
2813 * Making arguments be passed by reference
2814 * By default, function arguments are passed by value (so that if the value of the argument
2815 * within the function is changed, it does not get changed outside of the function).
2816 * To allow a function to modify its arguments, they must be passed by reference.
2817 * To have an argument to a function always passed by reference, prepend an ampersand (&)
2818 * to the argument name in the function definition:
2819 * Example #2 Passing function parameters by reference
2820 * <?jx9
2821 * function add_some_extra(&$string)
2822 * {
2823 * $string .= 'and something extra.';
2824 * }
2825 * $str = 'This is a string, ';
2826 * add_some_extra($str);
2827 * print $str; // outputs 'This is a string, and something extra.'
2828 * ?>
2829 *
2830 * JX9 have introduced powerful extension including full type hinting, function overloading
2831 * complex agrument values.Please refer to the official documentation for more information
2832 * on these extension.
2833 */
2834static sxi32 GenStateCollectFuncArgs(jx9_vm_func *pFunc, jx9_gen_state *pGen, SyToken *pEnd)
2835{
2836 jx9_vm_func_arg sArg; /* Current processed argument */
2837 SyToken *pCur, *pIn; /* Token stream */
2838 SyBlob sSig; /* Function signature */
2839 char *zDup; /* Copy of argument name */
2840 sxi32 rc;
2841
2842 pIn = pGen->pIn;
2843 pCur = 0;
2844 SyBlobInit(&sSig, &pGen->pVm->sAllocator);
2845 /* Process arguments one after one */
2846 for(;;){
2847 if( pIn >= pEnd ){
2848 /* No more arguments to process */
2849 break;
2850 }
2851 SyZero(&sArg, sizeof(jx9_vm_func_arg));
2852 SySetInit(&sArg.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr));
2853 if( pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){
2854 if( pIn->nType & JX9_TK_KEYWORD ){
2855 sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData));
2856 if( nKey & JX9_TKWRD_BOOL ){
2857 sArg.nType = MEMOBJ_BOOL;
2858 }else if( nKey & JX9_TKWRD_INT ){
2859 sArg.nType = MEMOBJ_INT;
2860 }else if( nKey & JX9_TKWRD_STRING ){
2861 sArg.nType = MEMOBJ_STRING;
2862 }else if( nKey & JX9_TKWRD_FLOAT ){
2863 sArg.nType = MEMOBJ_REAL;
2864 }else{
2865 jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine,
2866 "Invalid argument type '%z', Automatic cast will not be performed",
2867 &pIn->sData);
2868 }
2869 }
2870 pIn++;
2871 }
2872 if( pIn >= pEnd ){
2873 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Missing argument name");
2874 return rc;
2875 }
2876 if( pIn >= pEnd || (pIn->nType & JX9_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
2877 /* Invalid argument */
2878 rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid argument name");
2879 return rc;
2880 }
2881 pIn++; /* Jump the dollar sign */
2882 /* Copy argument name */
2883 zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, SyStringData(&pIn->sData), SyStringLength(&pIn->sData));
2884 if( zDup == 0 ){
2885 return GenStateOutOfMem(pGen);
2886 }
2887 SyStringInitFromBuf(&sArg.sName, zDup, SyStringLength(&pIn->sData));
2888 pIn++;
2889 if( pIn < pEnd ){
2890 if( pIn->nType & JX9_TK_EQUAL ){
2891 SyToken *pDefend;
2892 sxi32 iNest = 0;
2893 pIn++; /* Jump the equal sign */
2894 pDefend = pIn;
2895 /* Process the default value associated with this argument */
2896 while( pDefend < pEnd ){
2897 if( (pDefend->nType & JX9_TK_COMMA) && iNest <= 0 ){
2898 break;
2899 }
2900 if( pDefend->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*[*/) ){
2901 /* Increment nesting level */
2902 iNest++;
2903 }else if( pDefend->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*]*/) ){
2904 /* Decrement nesting level */
2905 iNest--;
2906 }
2907 pDefend++;
2908 }
2909 if( pIn >= pDefend ){
2910 rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Missing argument default value");
2911 return rc;
2912 }
2913 /* Process default value */
2914 rc = GenStateProcessArgValue(&(*pGen), &sArg, pIn, pDefend);
2915 if( rc != SXRET_OK ){
2916 return rc;
2917 }
2918 /* Point beyond the default value */
2919 pIn = pDefend;
2920 }
2921 if( pIn < pEnd && (pIn->nType & JX9_TK_COMMA) == 0 ){
2922 rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Unexpected token '%z'", &pIn->sData);
2923 return rc;
2924 }
2925 pIn++; /* Jump the trailing comma */
2926 }
2927 /* Append argument signature */
2928 if( sArg.nType > 0 ){
2929 int c;
2930 c = 'n'; /* cc warning */
2931 /* Type leading character */
2932 switch(sArg.nType){
2933 case MEMOBJ_HASHMAP:
2934 /* Hashmap aka 'array' */
2935 c = 'h';
2936 break;
2937 case MEMOBJ_INT:
2938 /* Integer */
2939 c = 'i';
2940 break;
2941 case MEMOBJ_BOOL:
2942 /* Bool */
2943 c = 'b';
2944 break;
2945 case MEMOBJ_REAL:
2946 /* Float */
2947 c = 'f';
2948 break;
2949 case MEMOBJ_STRING:
2950 /* String */
2951 c = 's';
2952 break;
2953 default:
2954 break;
2955 }
2956 SyBlobAppend(&sSig, (const void *)&c, sizeof(char));
2957 }
2958 /* Save in the argument set */
2959 SySetPut(&pFunc->aArgs, (const void *)&sArg);
2960 }
2961 if( SyBlobLength(&sSig) > 0 ){
2962 /* Save function signature */
2963 SyStringInitFromBuf(&pFunc->sSignature, SyBlobData(&sSig), SyBlobLength(&sSig));
2964 }
2965 return SXRET_OK;
2966}
2967/*
2968 * Compile function [i.e: standard function, annonymous function or closure ] body.
2969 * Return SXRET_OK on success. Any other return value indicates failure
2970 * and this routine takes care of generating the appropriate error message.
2971 */
2972static sxi32 GenStateCompileFuncBody(
2973 jx9_gen_state *pGen, /* Code generator state */
2974 jx9_vm_func *pFunc /* Function state */
2975 )
2976{
2977 SySet *pInstrContainer; /* Instruction container */
2978 GenBlock *pBlock;
2979 sxi32 rc;
2980 /* Attach the new function */
2981 rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC,jx9VmInstrLength(pGen->pVm), pFunc, &pBlock);
2982 if( rc != SXRET_OK ){
2983 return GenStateOutOfMem(pGen);
2984 }
2985 /* Swap bytecode containers */
2986 pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm);
2987 jx9VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode);
2988 /* Compile the body */
2989 jx9CompileBlock(&(*pGen));
2990 /* Emit the final return if not yet done */
2991 jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, 0, 0, 0, 0);
2992 /* Restore the default container */
2993 jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer);
2994 /* Leave function block */
2995 GenStateLeaveBlock(&(*pGen), 0);
2996 if( rc == SXERR_ABORT ){
2997 /* Don't worry about freeing memory, everything will be released shortly */
2998 return SXERR_ABORT;
2999 }
3000 /* All done, function body compiled */
3001 return SXRET_OK;
3002}
3003/*
3004 * Compile a JX9 function whether is a Standard or Annonymous function.
3005 * According to the JX9 language reference manual.
3006 * Function names follow the same rules as other labels in JX9. A valid function name
3007 * starts with a letter or underscore, followed by any number of letters, numbers, or
3008 * underscores. As a regular expression, it would be expressed thus:
3009 * [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*.
3010 * Functions need not be defined before they are referenced.
3011 * All functions and objectes in JX9 have the global scope - they can be called outside
3012 * a function even if they were defined inside and vice versa.
3013 * It is possible to call recursive functions in JX9. However avoid recursive function/method
3014 * calls with over 32-64 recursion levels.
3015 *
3016 * JX9 have introduced powerful extension including full type hinting, function overloading,
3017 * complex agrument values and more. Please refer to the official documentation for more information
3018 * on these extension.
3019 */
3020static sxi32 GenStateCompileFunc(
3021 jx9_gen_state *pGen, /* Code generator state */
3022 SyString *pName, /* Function name. NULL otherwise */
3023 sxi32 iFlags, /* Control flags */
3024 jx9_vm_func **ppFunc /* OUT: function state */
3025 )
3026{
3027 jx9_vm_func *pFunc;
3028 SyToken *pEnd;
3029 sxu32 nLine;
3030 char *zName;
3031 sxi32 rc;
3032 /* Extract line number */
3033 nLine = pGen->pIn->nLine;
3034 /* Jump the left parenthesis '(' */
3035 pGen->pIn++;
3036 /* Delimit the function signature */
3037 jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd);
3038 if( pEnd >= pGen->pEnd ){
3039 /* Syntax error */
3040 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after function '%z' signature", pName);
3041 if( rc == SXERR_ABORT ){
3042 /* Error count limit reached, abort immediately */
3043 return SXERR_ABORT;
3044 }
3045 pGen->pIn = pGen->pEnd;
3046 return SXRET_OK;
3047 }
3048 /* Create the function state */
3049 pFunc = (jx9_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_vm_func));
3050 if( pFunc == 0 ){
3051 goto OutOfMem;
3052 }
3053 /* function ID */
3054 zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte);
3055 if( zName == 0 ){
3056 /* Don't worry about freeing memory, everything will be released shortly */
3057 goto OutOfMem;
3058 }
3059 /* Initialize the function state */
3060 jx9VmInitFuncState(pGen->pVm, pFunc, zName, pName->nByte, iFlags, 0);
3061 if( pGen->pIn < pEnd ){
3062 /* Collect function arguments */
3063 rc = GenStateCollectFuncArgs(pFunc, &(*pGen), pEnd);
3064 if( rc == SXERR_ABORT ){
3065 /* Don't worry about freeing memory, everything will be released shortly */
3066 return SXERR_ABORT;
3067 }
3068 }
3069 /* Compile function body */
3070 pGen->pIn = &pEnd[1];
3071 /* Compile the body */
3072 rc = GenStateCompileFuncBody(&(*pGen), pFunc);
3073 if( rc == SXERR_ABORT ){
3074 return SXERR_ABORT;
3075 }
3076 if( ppFunc ){
3077 *ppFunc = pFunc;
3078 }
3079 /* Finally register the function */
3080 rc = jx9VmInstallUserFunction(pGen->pVm, pFunc, 0);
3081 return rc;
3082 /* Fall through if something goes wrong */
3083OutOfMem:
3084 /* If the supplied memory subsystem is so sick that we are unable to allocate
3085 * a tiny chunk of memory, there is no much we can do here.
3086 */
3087 return GenStateOutOfMem(pGen);
3088}
3089/*
3090 * Compile a standard JX9 function.
3091 * Refer to the block-comment above for more information.
3092 */
3093static sxi32 jx9CompileFunction(jx9_gen_state *pGen)
3094{
3095 SyString *pName;
3096 sxi32 iFlags;
3097 sxu32 nLine;
3098 sxi32 rc;
3099
3100 nLine = pGen->pIn->nLine;
3101 pGen->pIn++; /* Jump the 'function' keyword */
3102 iFlags = 0;
3103 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){
3104 /* Invalid function name */
3105 rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Invalid function name");
3106 if( rc == SXERR_ABORT ){
3107 return SXERR_ABORT;
3108 }
3109 /* Sychronize with the next semi-colon or braces*/
3110 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
3111 pGen->pIn++;
3112 }
3113 return SXRET_OK;
3114 }
3115 pName = &pGen->pIn->sData;
3116 nLine = pGen->pIn->nLine;
3117 /* Jump the function name */
3118 pGen->pIn++;
3119 if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){
3120 /* Syntax error */
3121 rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after function name '%z'", pName);
3122 if( rc == SXERR_ABORT ){
3123 /* Error count limit reached, abort immediately */
3124 return SXERR_ABORT;
3125 }
3126 /* Sychronize with the next semi-colon or '{' */
3127 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){
3128 pGen->pIn++;
3129 }
3130 return SXRET_OK;
3131 }
3132 /* Compile function body */
3133 rc = GenStateCompileFunc(&(*pGen),pName,iFlags,0);
3134 return rc;
3135}
3136/*
3137 * Generate bytecode for a given expression tree.
3138 * If something goes wrong while generating bytecode
3139 * for the expression tree (A very unlikely scenario)
3140 * this function takes care of generating the appropriate
3141 * error message.
3142 */
3143static sxi32 GenStateEmitExprCode(
3144 jx9_gen_state *pGen, /* Code generator state */
3145 jx9_expr_node *pNode, /* Root of the expression tree */
3146 sxi32 iFlags /* Control flags */
3147 )
3148{
3149 VmInstr *pInstr;
3150 sxu32 nJmpIdx;
3151 sxi32 iP1 = 0;
3152 sxu32 iP2 = 0;
3153 void *p3 = 0;
3154 sxi32 iVmOp;
3155 sxi32 rc;
3156 if( pNode->xCode ){
3157 SyToken *pTmpIn, *pTmpEnd;
3158 /* Compile node */
3159 SWAP_DELIMITER(pGen, pNode->pStart, pNode->pEnd);
3160 rc = pNode->xCode(&(*pGen), iFlags);
3161 RE_SWAP_DELIMITER(pGen);
3162 return rc;
3163 }
3164 if( pNode->pOp == 0 ){
3165 jx9GenCompileError(&(*pGen), E_ERROR, pNode->pStart->nLine,
3166 "Invalid expression node, JX9 is aborting compilation");
3167 return SXERR_ABORT;
3168 }
3169 iVmOp = pNode->pOp->iVmOp;
3170 if( pNode->pOp->iOp == EXPR_OP_QUESTY ){
3171 sxu32 nJz, nJmp;
3172 /* Ternary operator require special handling */
3173 /* Phase#1: Compile the condition */
3174 rc = GenStateEmitExprCode(&(*pGen), pNode->pCond, iFlags);
3175 if( rc != SXRET_OK ){
3176 return rc;
3177 }
3178 nJz = nJmp = 0; /* cc -O6 warning */
3179 /* Phase#2: Emit the false jump */
3180 jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJz);
3181 if( pNode->pLeft ){
3182 /* Phase#3: Compile the 'then' expression */
3183 rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags);
3184 if( rc != SXRET_OK ){
3185 return rc;
3186 }
3187 }
3188 /* Phase#4: Emit the unconditional jump */
3189 jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJmp);
3190 /* Phase#5: Fix the false jump now the jump destination is resolved. */
3191 pInstr = jx9VmGetInstr(pGen->pVm, nJz);
3192 if( pInstr ){
3193 pInstr->iP2 = jx9VmInstrLength(pGen->pVm);
3194 }
3195 /* Phase#6: Compile the 'else' expression */
3196 if( pNode->pRight ){
3197 rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags);
3198 if( rc != SXRET_OK ){
3199 return rc;
3200 }
3201 }
3202 if( nJmp > 0 ){
3203 /* Phase#7: Fix the unconditional jump */
3204 pInstr = jx9VmGetInstr(pGen->pVm, nJmp);
3205 if( pInstr ){
3206 pInstr->iP2 = jx9VmInstrLength(pGen->pVm);
3207 }
3208 }
3209 /* All done */
3210 return SXRET_OK;
3211 }
3212 /* Generate code for the left tree */
3213 if( pNode->pLeft ){
3214 if( iVmOp == JX9_OP_CALL ){
3215 jx9_expr_node **apNode;
3216 sxi32 n;
3217 /* Recurse and generate bytecodes for function arguments */
3218 apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
3219 /* Read-only load */
3220 iFlags |= EXPR_FLAG_RDONLY_LOAD;
3221 for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){
3222 rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE);
3223 if( rc != SXRET_OK ){
3224 return rc;
3225 }
3226 }
3227 /* Total number of given arguments */
3228 iP1 = (sxi32)SySetUsed(&pNode->aNodeArgs);
3229 /* Remove stale flags now */
3230 iFlags &= ~EXPR_FLAG_RDONLY_LOAD;
3231 }
3232 rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags);
3233 if( rc != SXRET_OK ){
3234 return rc;
3235 }
3236 if( iVmOp == JX9_OP_CALL ){
3237 pInstr = jx9VmPeekInstr(pGen->pVm);
3238 if( pInstr ){
3239 if ( pInstr->iOp == JX9_OP_LOADC ){
3240 /* Prevent constant expansion */
3241 pInstr->iP1 = 0;
3242 }else if( pInstr->iOp == JX9_OP_MEMBER /* $a.b(1, 2, 3) */ ){
3243 /* Annonymous function call, flag that */
3244 pInstr->iP2 = 1;
3245 }
3246 }
3247 }else if( iVmOp == JX9_OP_LOAD_IDX ){
3248 jx9_expr_node **apNode;
3249 sxi32 n;
3250 /* Recurse and generate bytecodes for array index */
3251 apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
3252 for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){
3253 rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE);
3254 if( rc != SXRET_OK ){
3255 return rc;
3256 }
3257 }
3258 if( SySetUsed(&pNode->aNodeArgs) > 0 ){
3259 iP1 = 1; /* Node have an index associated with it */
3260 }
3261 if( iFlags & EXPR_FLAG_LOAD_IDX_STORE ){
3262 /* Create an empty entry when the desired index is not found */
3263 iP2 = 1;
3264 }
3265 }else if( pNode->pOp->iOp == EXPR_OP_COMMA ){
3266 /* POP the left node */
3267 jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
3268 }
3269 }
3270 rc = SXRET_OK;
3271 nJmpIdx = 0;
3272 /* Generate code for the right tree */
3273 if( pNode->pRight ){
3274 if( iVmOp == JX9_OP_LAND ){
3275 /* Emit the false jump so we can short-circuit the logical and */
3276 jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx);
3277 }else if (iVmOp == JX9_OP_LOR ){
3278 /* Emit the true jump so we can short-circuit the logical or*/
3279 jx9VmEmitInstr(pGen->pVm, JX9_OP_JNZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx);
3280 }else if( pNode->pOp->iPrec == 18 /* Combined binary operators [i.e: =, '.=', '+=', *=' ...] precedence */ ){
3281 iFlags |= EXPR_FLAG_LOAD_IDX_STORE;
3282 }
3283 rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags);
3284 if( iVmOp == JX9_OP_STORE ){
3285 pInstr = jx9VmPeekInstr(pGen->pVm);
3286 if( pInstr ){
3287 if(pInstr->iOp == JX9_OP_MEMBER ){
3288 /* Perform a member store operation [i.e: $this.x = 50] */
3289 iP2 = 1;
3290 }else{
3291 if( pInstr->iOp == JX9_OP_LOAD_IDX ){
3292 /* Transform the STORE instruction to STORE_IDX instruction */
3293 iVmOp = JX9_OP_STORE_IDX;
3294 iP1 = pInstr->iP1;
3295 }else{
3296 p3 = pInstr->p3;
3297 }
3298 /* POP the last dynamic load instruction */
3299 (void)jx9VmPopInstr(pGen->pVm);
3300 }
3301 }
3302 }
3303 }
3304 if( iVmOp > 0 ){
3305 if( iVmOp == JX9_OP_INCR || iVmOp == JX9_OP_DECR ){
3306 if( pNode->iFlags & EXPR_NODE_PRE_INCR ){
3307 /* Pre-increment/decrement operator [i.e: ++$i, --$j ] */
3308 iP1 = 1;
3309 }
3310 }
3311 /* Finally, emit the VM instruction associated with this operator */
3312 jx9VmEmitInstr(pGen->pVm, iVmOp, iP1, iP2, p3, 0);
3313 if( nJmpIdx > 0 ){
3314 /* Fix short-circuited jumps now the destination is resolved */
3315 pInstr = jx9VmGetInstr(pGen->pVm, nJmpIdx);
3316 if( pInstr ){
3317 pInstr->iP2 = jx9VmInstrLength(pGen->pVm);
3318 }
3319 }
3320 }
3321 return rc;
3322}
3323/*
3324 * Compile a JX9 expression.
3325 * According to the JX9 language reference manual:
3326 * Expressions are the most important building stones of JX9.
3327 * In JX9, almost anything you write is an expression.
3328 * The simplest yet most accurate way to define an expression
3329 * is "anything that has a value".
3330 * If something goes wrong while compiling the expression, this
3331 * function takes care of generating the appropriate error
3332 * message.
3333 */
3334static sxi32 jx9CompileExpr(
3335 jx9_gen_state *pGen, /* Code generator state */
3336 sxi32 iFlags, /* Control flags */
3337 sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */
3338 )
3339{
3340 jx9_expr_node *pRoot;
3341 SySet sExprNode;
3342 SyToken *pEnd;
3343 sxi32 nExpr;
3344 sxi32 iNest;
3345 sxi32 rc;
3346 /* Initialize worker variables */
3347 nExpr = 0;
3348 pRoot = 0;
3349 SySetInit(&sExprNode, &pGen->pVm->sAllocator, sizeof(jx9_expr_node *));
3350 SySetAlloc(&sExprNode, 0x10);
3351 rc = SXRET_OK;
3352 /* Delimit the expression */
3353 pEnd = pGen->pIn;
3354 iNest = 0;
3355 while( pEnd < pGen->pEnd ){
3356 if( pEnd->nType & JX9_TK_OCB /* '{' */ ){
3357 /* Ticket 1433-30: Annonymous/Closure functions body */
3358 iNest++;
3359 }else if(pEnd->nType & JX9_TK_CCB /* '}' */ ){
3360 iNest--;
3361 }else if( pEnd->nType & JX9_TK_SEMI /* ';' */ ){
3362 if( iNest <= 0 ){
3363 break;
3364 }
3365 }
3366 pEnd++;
3367 }
3368 if( iFlags & EXPR_FLAG_COMMA_STATEMENT ){
3369 SyToken *pEnd2 = pGen->pIn;
3370 iNest = 0;
3371 /* Stop at the first comma */
3372 while( pEnd2 < pEnd ){
3373 if( pEnd2->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_LPAREN/*'('*/) ){
3374 iNest++;
3375 }else if(pEnd2->nType & (JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*']'*/|JX9_TK_RPAREN/*')'*/)){
3376 iNest--;
3377 }else if( pEnd2->nType & JX9_TK_COMMA /*','*/ ){
3378 if( iNest <= 0 ){
3379 break;
3380 }
3381 }
3382 pEnd2++;
3383 }
3384 if( pEnd2 <pEnd ){
3385 pEnd = pEnd2;
3386 }
3387 }
3388 if( pEnd > pGen->pIn ){
3389 SyToken *pTmp = pGen->pEnd;
3390 /* Swap delimiter */
3391 pGen->pEnd = pEnd;
3392 /* Try to get an expression tree */
3393 rc = jx9ExprMakeTree(&(*pGen), &sExprNode, &pRoot);
3394 if( rc == SXRET_OK && pRoot ){
3395 rc = SXRET_OK;
3396 if( xTreeValidator ){
3397 /* Call the upper layer validator callback */
3398 rc = xTreeValidator(&(*pGen), pRoot);
3399 }
3400 if( rc != SXERR_ABORT ){
3401 /* Generate code for the given tree */
3402 rc = GenStateEmitExprCode(&(*pGen), pRoot, iFlags);
3403 }
3404 nExpr = 1;
3405 }
3406 /* Release the whole tree */
3407 jx9ExprFreeTree(&(*pGen), &sExprNode);
3408 /* Synchronize token stream */
3409 pGen->pEnd = pTmp;
3410 pGen->pIn = pEnd;
3411 if( rc == SXERR_ABORT ){
3412 SySetRelease(&sExprNode);
3413 return SXERR_ABORT;
3414 }
3415 }
3416 SySetRelease(&sExprNode);
3417 return nExpr > 0 ? SXRET_OK : SXERR_EMPTY;
3418}
3419/*
3420 * Return a pointer to the node construct handler associated
3421 * with a given node type [i.e: string, integer, float, ...].
3422 */
3423JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType)
3424{
3425 if( nNodeType & JX9_TK_NUM ){
3426 /* Numeric literal: Either real or integer */
3427 return jx9CompileNumLiteral;
3428 }else if( nNodeType & JX9_TK_DSTR ){
3429 /* Double quoted string */
3430 return jx9CompileString;
3431 }else if( nNodeType & JX9_TK_SSTR ){
3432 /* Single quoted string */
3433 return jx9CompileSimpleString;
3434 }else if( nNodeType & JX9_TK_NOWDOC ){
3435 /* Nowdoc */
3436 return jx9CompileNowdoc;
3437 }
3438 return 0;
3439}
3440/*
3441 * Jx9 Language construct table.
3442 */
3443static const LangConstruct aLangConstruct[] = {
3444 { JX9_TKWRD_IF, jx9CompileIf },
3445 { JX9_TKWRD_FUNCTION, jx9CompileFunction },
3446 { JX9_TKWRD_FOREACH, jx9CompileForeach },
3447 { JX9_TKWRD_WHILE, jx9CompileWhile },
3448 { JX9_TKWRD_FOR, jx9CompileFor },
3449 { JX9_TKWRD_SWITCH, jx9CompileSwitch },
3450 { JX9_TKWRD_DIE, jx9CompileHalt },
3451 { JX9_TKWRD_EXIT, jx9CompileHalt },
3452 { JX9_TKWRD_RETURN, jx9CompileReturn },
3453 { JX9_TKWRD_BREAK, jx9CompileBreak },
3454 { JX9_TKWRD_CONTINUE, jx9CompileContinue },
3455 { JX9_TKWRD_STATIC, jx9CompileStatic },
3456 { JX9_TKWRD_UPLINK, jx9CompileUplink },
3457 { JX9_TKWRD_CONST, jx9CompileConstant },
3458};
3459/*
3460 * Return a pointer to the statement handler routine associated
3461 * with a given JX9 keyword [i.e: if, for, while, ...].
3462 */
3463static ProcLangConstruct GenStateGetStatementHandler(
3464 sxu32 nKeywordID /* Keyword ID*/
3465 )
3466{
3467 sxu32 n = 0;
3468 for(;;){
3469 if( n >= SX_ARRAYSIZE(aLangConstruct) ){
3470 break;
3471 }
3472 if( aLangConstruct[n].nID == nKeywordID ){
3473 /* Return a pointer to the handler.
3474 */
3475 return aLangConstruct[n].xConstruct;
3476 }
3477 n++;
3478 }
3479 /* Not a language construct */
3480 return 0;
3481}
3482/*
3483 * Compile a jx9 program.
3484 * If something goes wrong while compiling the Jx9 chunk, this function
3485 * takes care of generating the appropriate error message.
3486 */
3487static sxi32 GenStateCompileChunk(
3488 jx9_gen_state *pGen, /* Code generator state */
3489 sxi32 iFlags /* Compile flags */
3490 )
3491{
3492 ProcLangConstruct xCons;
3493 sxi32 rc;
3494 rc = SXRET_OK; /* Prevent compiler warning */
3495 for(;;){
3496 if( pGen->pIn >= pGen->pEnd ){
3497 /* No more input to process */
3498 break;
3499 }
3500 xCons = 0;
3501 if( pGen->pIn->nType & JX9_TK_KEYWORD ){
3502 sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData);
3503 /* Try to extract a language construct handler */
3504 xCons = GenStateGetStatementHandler(nKeyword);
3505 if( xCons == 0 && !jx9IsLangConstruct(nKeyword) ){
3506 rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine,
3507 "Syntax error: Unexpected keyword '%z'",
3508 &pGen->pIn->sData);
3509 if( rc == SXERR_ABORT ){
3510 break;
3511 }
3512 /* Synchronize with the first semi-colon and avoid compiling
3513 * this erroneous statement.
3514 */
3515 xCons = jx9ErrorRecover;
3516 }
3517 }
3518 if( xCons == 0 ){
3519 /* Assume an expression an try to compile it */
3520 rc = jx9CompileExpr(&(*pGen), 0, 0);
3521 if( rc != SXERR_EMPTY ){
3522 /* Pop l-value */
3523 jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0);
3524 }
3525 }else{
3526 /* Go compile the sucker */
3527 rc = xCons(&(*pGen));
3528 }
3529 if( rc == SXERR_ABORT ){
3530 /* Request to abort compilation */
3531 break;
3532 }
3533 /* Ignore trailing semi-colons ';' */
3534 while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){
3535 pGen->pIn++;
3536 }
3537 if( iFlags & JX9_COMPILE_SINGLE_STMT ){
3538 /* Compile a single statement and return */
3539 break;
3540 }
3541 /* LOOP ONE */
3542 /* LOOP TWO */
3543 /* LOOP THREE */
3544 /* LOOP FOUR */
3545 }
3546 /* Return compilation status */
3547 return rc;
3548}
3549/*
3550 * Compile a raw chunk. The raw chunk can contain JX9 code embedded
3551 * in HTML, XML and so on. This function handle all the stuff.
3552 * This is the only compile interface exported from this file.
3553 */
3554JX9_PRIVATE sxi32 jx9CompileScript(
3555 jx9_vm *pVm, /* Generate JX9 bytecodes for this Virtual Machine */
3556 SyString *pScript, /* Script to compile */
3557 sxi32 iFlags /* Compile flags */
3558 )
3559{
3560 jx9_gen_state *pGen;
3561 SySet aToken;
3562 sxi32 rc;
3563 if( pScript->nByte < 1 ){
3564 /* Nothing to compile */
3565 return JX9_OK;
3566 }
3567 /* Initialize the tokens containers */
3568 SySetInit(&aToken, &pVm->sAllocator, sizeof(SyToken));
3569 SySetAlloc(&aToken, 0xc0);
3570 pGen = &pVm->sCodeGen;
3571 rc = JX9_OK;
3572 /* Tokenize the JX9 chunk first */
3573 jx9Tokenize(pScript->zString,pScript->nByte,&aToken);
3574 if( SySetUsed(&aToken) < 1 ){
3575 return SXERR_EMPTY;
3576 }
3577 /* Point to the head and tail of the token stream. */
3578 pGen->pIn = (SyToken *)SySetBasePtr(&aToken);
3579 pGen->pEnd = &pGen->pIn[SySetUsed(&aToken)];
3580 /* Compile the chunk */
3581 rc = GenStateCompileChunk(pGen,iFlags);
3582 /* Cleanup */
3583 SySetRelease(&aToken);
3584 return rc;
3585}
3586/*
3587 * Utility routines.Initialize the code generator.
3588 */
3589JX9_PRIVATE sxi32 jx9InitCodeGenerator(
3590 jx9_vm *pVm, /* Target VM */
3591 ProcConsumer xErr, /* Error log consumer callabck */
3592 void *pErrData /* Last argument to xErr() */
3593 )
3594{
3595 jx9_gen_state *pGen = &pVm->sCodeGen;
3596 /* Zero the structure */
3597 SyZero(pGen, sizeof(jx9_gen_state));
3598 /* Initial state */
3599 pGen->pVm = &(*pVm);
3600 pGen->xErr = xErr;
3601 pGen->pErrData = pErrData;
3602 SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0);
3603 SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0);
3604 /* Create the global scope */
3605 GenStateInitBlock(pGen, &pGen->sGlobal,GEN_BLOCK_GLOBAL,jx9VmInstrLength(&(*pVm)), 0);
3606 /* Point to the global scope */
3607 pGen->pCurrent = &pGen->sGlobal;
3608 return SXRET_OK;
3609}
3610/*
3611 * Utility routines. Reset the code generator to it's initial state.
3612 */
3613JX9_PRIVATE sxi32 jx9ResetCodeGenerator(
3614 jx9_vm *pVm, /* Target VM */
3615 ProcConsumer xErr, /* Error log consumer callabck */
3616 void *pErrData /* Last argument to xErr() */
3617 )
3618{
3619 jx9_gen_state *pGen = &pVm->sCodeGen;
3620 GenBlock *pBlock, *pParent;
3621 /* Point to the global scope */
3622 pBlock = pGen->pCurrent;
3623 while( pBlock->pParent != 0 ){
3624 pParent = pBlock->pParent;
3625 GenStateFreeBlock(pBlock);
3626 pBlock = pParent;
3627 }
3628 pGen->xErr = xErr;
3629 pGen->pErrData = pErrData;
3630 pGen->pCurrent = &pGen->sGlobal;
3631 pGen->pIn = pGen->pEnd = 0;
3632 pGen->nErr = 0;
3633 return SXRET_OK;
3634}
3635/*
3636 * Generate a compile-time error message.
3637 * If the error count limit is reached (usually 15 error message)
3638 * this function return SXERR_ABORT.In that case upper-layers must
3639 * abort compilation immediately.
3640 */
3641JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...)
3642{
3643 SyBlob *pWorker = &pGen->pVm->pEngine->xConf.sErrConsumer;
3644 const char *zErr = "Error";
3645 va_list ap;
3646 if( nErrType == E_ERROR ){
3647 /* Increment the error counter */
3648 pGen->nErr++;
3649 if( pGen->nErr > 15 ){
3650 /* Error count limit reached */
3651 SyBlobFormat(pWorker, "%u Error count limit reached, JX9 is aborting compilation\n", nLine);
3652 /* Abort immediately */
3653 return SXERR_ABORT;
3654 }
3655 }
3656 switch(nErrType){
3657 case E_WARNING: zErr = "Warning"; break;
3658 case E_PARSE: zErr = "Parse error"; break;
3659 case E_NOTICE: zErr = "Notice"; break;
3660 default:
3661 break;
3662 }
3663 /* Format the error message */
3664 SyBlobFormat(pWorker, "%u %s: ", nLine, zErr);
3665 va_start(ap, zFormat);
3666 SyBlobFormatAp(pWorker, zFormat, ap);
3667 va_end(ap);
3668 /* Append a new line */
3669 SyBlobAppend(pWorker, (const void *)"\n", sizeof(char));
3670 return JX9_OK;
3671}
diff --git a/common/unqlite/jx9_const.c b/common/unqlite/jx9_const.c
new file mode 100644
index 0000000..37328a0
--- /dev/null
+++ b/common/unqlite/jx9_const.c
@@ -0,0 +1,1423 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: const.c v1.7 Win7 2012-12-13 00:01 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* This file implement built-in constants for the JX9 engine. */
18/*
19 * JX9_VERSION
20 * __JX9__
21 * Expand the current version of the JX9 engine.
22 */
23static void JX9_VER_Const(jx9_value *pVal, void *pUnused)
24{
25 SXUNUSED(pUnused);
26 jx9_value_string(pVal, jx9_lib_signature(), -1/*Compute length automatically*/);
27}
28#ifdef __WINNT__
29#include <Windows.h>
30#elif defined(__UNIXES__)
31#include <sys/utsname.h>
32#endif
33/*
34 * JX9_OS
35 * __OS__
36 * Expand the name of the host Operating System.
37 */
38static void JX9_OS_Const(jx9_value *pVal, void *pUnused)
39{
40#if defined(__WINNT__)
41 jx9_value_string(pVal, "WinNT", (int)sizeof("WinNT")-1);
42#elif defined(__UNIXES__)
43 struct utsname sInfo;
44 if( uname(&sInfo) != 0 ){
45 jx9_value_string(pVal, "Unix", (int)sizeof("Unix")-1);
46 }else{
47 jx9_value_string(pVal, sInfo.sysname, -1);
48 }
49#else
50 jx9_value_string(pVal,"Host OS", (int)sizeof("Host OS")-1);
51#endif
52 SXUNUSED(pUnused);
53}
54/*
55 * JX9_EOL
56 * Expand the correct 'End Of Line' symbol for this platform.
57 */
58static void JX9_EOL_Const(jx9_value *pVal, void *pUnused)
59{
60 SXUNUSED(pUnused);
61#ifdef __WINNT__
62 jx9_value_string(pVal, "\r\n", (int)sizeof("\r\n")-1);
63#else
64 jx9_value_string(pVal, "\n", (int)sizeof(char));
65#endif
66}
67/*
68 * JX9_INT_MAX
69 * Expand the largest integer supported.
70 * Note that JX9 deals with 64-bit integer for all platforms.
71 */
72static void JX9_INTMAX_Const(jx9_value *pVal, void *pUnused)
73{
74 SXUNUSED(pUnused);
75 jx9_value_int64(pVal, SXI64_HIGH);
76}
77/*
78 * JX9_INT_SIZE
79 * Expand the size in bytes of a 64-bit integer.
80 */
81static void JX9_INTSIZE_Const(jx9_value *pVal, void *pUnused)
82{
83 SXUNUSED(pUnused);
84 jx9_value_int64(pVal, sizeof(sxi64));
85}
86/*
87 * DIRECTORY_SEPARATOR.
88 * Expand the directory separator character.
89 */
90static void JX9_DIRSEP_Const(jx9_value *pVal, void *pUnused)
91{
92 SXUNUSED(pUnused);
93#ifdef __WINNT__
94 jx9_value_string(pVal, "\\", (int)sizeof(char));
95#else
96 jx9_value_string(pVal, "/", (int)sizeof(char));
97#endif
98}
99/*
100 * PATH_SEPARATOR.
101 * Expand the path separator character.
102 */
103static void JX9_PATHSEP_Const(jx9_value *pVal, void *pUnused)
104{
105 SXUNUSED(pUnused);
106#ifdef __WINNT__
107 jx9_value_string(pVal, ";", (int)sizeof(char));
108#else
109 jx9_value_string(pVal, ":", (int)sizeof(char));
110#endif
111}
112#ifndef __WINNT__
113#include <time.h>
114#endif
115/*
116 * __TIME__
117 * Expand the current time (GMT).
118 */
119static void JX9_TIME_Const(jx9_value *pVal, void *pUnused)
120{
121 Sytm sTm;
122#ifdef __WINNT__
123 SYSTEMTIME sOS;
124 GetSystemTime(&sOS);
125 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
126#else
127 struct tm *pTm;
128 time_t t;
129 time(&t);
130 pTm = gmtime(&t);
131 STRUCT_TM_TO_SYTM(pTm, &sTm);
132#endif
133 SXUNUSED(pUnused); /* cc warning */
134 /* Expand */
135 jx9_value_string_format(pVal, "%02d:%02d:%02d", sTm.tm_hour, sTm.tm_min, sTm.tm_sec);
136}
137/*
138 * __DATE__
139 * Expand the current date in the ISO-8601 format.
140 */
141static void JX9_DATE_Const(jx9_value *pVal, void *pUnused)
142{
143 Sytm sTm;
144#ifdef __WINNT__
145 SYSTEMTIME sOS;
146 GetSystemTime(&sOS);
147 SYSTEMTIME_TO_SYTM(&sOS, &sTm);
148#else
149 struct tm *pTm;
150 time_t t;
151 time(&t);
152 pTm = gmtime(&t);
153 STRUCT_TM_TO_SYTM(pTm, &sTm);
154#endif
155 SXUNUSED(pUnused); /* cc warning */
156 /* Expand */
157 jx9_value_string_format(pVal, "%04d-%02d-%02d", sTm.tm_year, sTm.tm_mon+1, sTm.tm_mday);
158}
159/*
160 * __FILE__
161 * Path of the processed script.
162 */
163static void JX9_FILE_Const(jx9_value *pVal, void *pUserData)
164{
165 jx9_vm *pVm = (jx9_vm *)pUserData;
166 SyString *pFile;
167 /* Peek the top entry */
168 pFile = (SyString *)SySetPeek(&pVm->aFiles);
169 if( pFile == 0 ){
170 /* Expand the magic word: ":MEMORY:" */
171 jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1);
172 }else{
173 jx9_value_string(pVal, pFile->zString, pFile->nByte);
174 }
175}
176/*
177 * __DIR__
178 * Directory holding the processed script.
179 */
180static void JX9_DIR_Const(jx9_value *pVal, void *pUserData)
181{
182 jx9_vm *pVm = (jx9_vm *)pUserData;
183 SyString *pFile;
184 /* Peek the top entry */
185 pFile = (SyString *)SySetPeek(&pVm->aFiles);
186 if( pFile == 0 ){
187 /* Expand the magic word: ":MEMORY:" */
188 jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1);
189 }else{
190 if( pFile->nByte > 0 ){
191 const char *zDir;
192 int nLen;
193 zDir = jx9ExtractDirName(pFile->zString, (int)pFile->nByte, &nLen);
194 jx9_value_string(pVal, zDir, nLen);
195 }else{
196 /* Expand '.' as the current directory*/
197 jx9_value_string(pVal, ".", (int)sizeof(char));
198 }
199 }
200}
201/*
202 * E_ERROR
203 * Expands 1
204 */
205static void JX9_E_ERROR_Const(jx9_value *pVal, void *pUserData)
206{
207 jx9_value_int(pVal, 1);
208 SXUNUSED(pUserData);
209}
210/*
211 * E_WARNING
212 * Expands 2
213 */
214static void JX9_E_WARNING_Const(jx9_value *pVal, void *pUserData)
215{
216 jx9_value_int(pVal, 2);
217 SXUNUSED(pUserData);
218}
219/*
220 * E_PARSE
221 * Expands 4
222 */
223static void JX9_E_PARSE_Const(jx9_value *pVal, void *pUserData)
224{
225 jx9_value_int(pVal, 4);
226 SXUNUSED(pUserData);
227}
228/*
229 * E_NOTICE
230 * Expands 8
231 */
232static void JX9_E_NOTICE_Const(jx9_value *pVal, void *pUserData)
233{
234 jx9_value_int(pVal, 8);
235 SXUNUSED(pUserData);
236}
237/*
238 * CASE_LOWER
239 * Expands 0.
240 */
241static void JX9_CASE_LOWER_Const(jx9_value *pVal, void *pUserData)
242{
243 jx9_value_int(pVal, 0);
244 SXUNUSED(pUserData);
245}
246/*
247 * CASE_UPPER
248 * Expands 1.
249 */
250static void JX9_CASE_UPPER_Const(jx9_value *pVal, void *pUserData)
251{
252 jx9_value_int(pVal, 1);
253 SXUNUSED(pUserData);
254}
255/*
256 * STR_PAD_LEFT
257 * Expands 0.
258 */
259static void JX9_STR_PAD_LEFT_Const(jx9_value *pVal, void *pUserData)
260{
261 jx9_value_int(pVal, 0);
262 SXUNUSED(pUserData);
263}
264/*
265 * STR_PAD_RIGHT
266 * Expands 1.
267 */
268static void JX9_STR_PAD_RIGHT_Const(jx9_value *pVal, void *pUserData)
269{
270 jx9_value_int(pVal, 1);
271 SXUNUSED(pUserData);
272}
273/*
274 * STR_PAD_BOTH
275 * Expands 2.
276 */
277static void JX9_STR_PAD_BOTH_Const(jx9_value *pVal, void *pUserData)
278{
279 jx9_value_int(pVal, 2);
280 SXUNUSED(pUserData);
281}
282/*
283 * COUNT_NORMAL
284 * Expands 0
285 */
286static void JX9_COUNT_NORMAL_Const(jx9_value *pVal, void *pUserData)
287{
288 jx9_value_int(pVal, 0);
289 SXUNUSED(pUserData);
290}
291/*
292 * COUNT_RECURSIVE
293 * Expands 1.
294 */
295static void JX9_COUNT_RECURSIVE_Const(jx9_value *pVal, void *pUserData)
296{
297 jx9_value_int(pVal, 1);
298 SXUNUSED(pUserData);
299}
300/*
301 * SORT_ASC
302 * Expands 1.
303 */
304static void JX9_SORT_ASC_Const(jx9_value *pVal, void *pUserData)
305{
306 jx9_value_int(pVal, 1);
307 SXUNUSED(pUserData);
308}
309/*
310 * SORT_DESC
311 * Expands 2.
312 */
313static void JX9_SORT_DESC_Const(jx9_value *pVal, void *pUserData)
314{
315 jx9_value_int(pVal, 2);
316 SXUNUSED(pUserData);
317}
318/*
319 * SORT_REGULAR
320 * Expands 3.
321 */
322static void JX9_SORT_REG_Const(jx9_value *pVal, void *pUserData)
323{
324 jx9_value_int(pVal, 3);
325 SXUNUSED(pUserData);
326}
327/*
328 * SORT_NUMERIC
329 * Expands 4.
330 */
331static void JX9_SORT_NUMERIC_Const(jx9_value *pVal, void *pUserData)
332{
333 jx9_value_int(pVal, 4);
334 SXUNUSED(pUserData);
335}
336/*
337 * SORT_STRING
338 * Expands 5.
339 */
340static void JX9_SORT_STRING_Const(jx9_value *pVal, void *pUserData)
341{
342 jx9_value_int(pVal, 5);
343 SXUNUSED(pUserData);
344}
345/*
346 * JX9_ROUND_HALF_UP
347 * Expands 1.
348 */
349static void JX9_JX9_ROUND_HALF_UP_Const(jx9_value *pVal, void *pUserData)
350{
351 jx9_value_int(pVal, 1);
352 SXUNUSED(pUserData);
353}
354/*
355 * SJX9_ROUND_HALF_DOWN
356 * Expands 2.
357 */
358static void JX9_JX9_ROUND_HALF_DOWN_Const(jx9_value *pVal, void *pUserData)
359{
360 jx9_value_int(pVal, 2);
361 SXUNUSED(pUserData);
362}
363/*
364 * JX9_ROUND_HALF_EVEN
365 * Expands 3.
366 */
367static void JX9_JX9_ROUND_HALF_EVEN_Const(jx9_value *pVal, void *pUserData)
368{
369 jx9_value_int(pVal, 3);
370 SXUNUSED(pUserData);
371}
372/*
373 * JX9_ROUND_HALF_ODD
374 * Expands 4.
375 */
376static void JX9_JX9_ROUND_HALF_ODD_Const(jx9_value *pVal, void *pUserData)
377{
378 jx9_value_int(pVal, 4);
379 SXUNUSED(pUserData);
380}
381#ifdef JX9_ENABLE_MATH_FUNC
382/*
383 * PI
384 * Expand the value of pi.
385 */
386static void JX9_M_PI_Const(jx9_value *pVal, void *pUserData)
387{
388 SXUNUSED(pUserData); /* cc warning */
389 jx9_value_double(pVal, JX9_PI);
390}
391/*
392 * M_E
393 * Expand 2.7182818284590452354
394 */
395static void JX9_M_E_Const(jx9_value *pVal, void *pUserData)
396{
397 SXUNUSED(pUserData); /* cc warning */
398 jx9_value_double(pVal, 2.7182818284590452354);
399}
400/*
401 * M_LOG2E
402 * Expand 2.7182818284590452354
403 */
404static void JX9_M_LOG2E_Const(jx9_value *pVal, void *pUserData)
405{
406 SXUNUSED(pUserData); /* cc warning */
407 jx9_value_double(pVal, 1.4426950408889634074);
408}
409/*
410 * M_LOG10E
411 * Expand 0.4342944819032518276
412 */
413static void JX9_M_LOG10E_Const(jx9_value *pVal, void *pUserData)
414{
415 SXUNUSED(pUserData); /* cc warning */
416 jx9_value_double(pVal, 0.4342944819032518276);
417}
418/*
419 * M_LN2
420 * Expand 0.69314718055994530942
421 */
422static void JX9_M_LN2_Const(jx9_value *pVal, void *pUserData)
423{
424 SXUNUSED(pUserData); /* cc warning */
425 jx9_value_double(pVal, 0.69314718055994530942);
426}
427/*
428 * M_LN10
429 * Expand 2.30258509299404568402
430 */
431static void JX9_M_LN10_Const(jx9_value *pVal, void *pUserData)
432{
433 SXUNUSED(pUserData); /* cc warning */
434 jx9_value_double(pVal, 2.30258509299404568402);
435}
436/*
437 * M_PI_2
438 * Expand 1.57079632679489661923
439 */
440static void JX9_M_PI_2_Const(jx9_value *pVal, void *pUserData)
441{
442 SXUNUSED(pUserData); /* cc warning */
443 jx9_value_double(pVal, 1.57079632679489661923);
444}
445/*
446 * M_PI_4
447 * Expand 0.78539816339744830962
448 */
449static void JX9_M_PI_4_Const(jx9_value *pVal, void *pUserData)
450{
451 SXUNUSED(pUserData); /* cc warning */
452 jx9_value_double(pVal, 0.78539816339744830962);
453}
454/*
455 * M_1_PI
456 * Expand 0.31830988618379067154
457 */
458static void JX9_M_1_PI_Const(jx9_value *pVal, void *pUserData)
459{
460 SXUNUSED(pUserData); /* cc warning */
461 jx9_value_double(pVal, 0.31830988618379067154);
462}
463/*
464 * M_2_PI
465 * Expand 0.63661977236758134308
466 */
467static void JX9_M_2_PI_Const(jx9_value *pVal, void *pUserData)
468{
469 SXUNUSED(pUserData); /* cc warning */
470 jx9_value_double(pVal, 0.63661977236758134308);
471}
472/*
473 * M_SQRTPI
474 * Expand 1.77245385090551602729
475 */
476static void JX9_M_SQRTPI_Const(jx9_value *pVal, void *pUserData)
477{
478 SXUNUSED(pUserData); /* cc warning */
479 jx9_value_double(pVal, 1.77245385090551602729);
480}
481/*
482 * M_2_SQRTPI
483 * Expand 1.12837916709551257390
484 */
485static void JX9_M_2_SQRTPI_Const(jx9_value *pVal, void *pUserData)
486{
487 SXUNUSED(pUserData); /* cc warning */
488 jx9_value_double(pVal, 1.12837916709551257390);
489}
490/*
491 * M_SQRT2
492 * Expand 1.41421356237309504880
493 */
494static void JX9_M_SQRT2_Const(jx9_value *pVal, void *pUserData)
495{
496 SXUNUSED(pUserData); /* cc warning */
497 jx9_value_double(pVal, 1.41421356237309504880);
498}
499/*
500 * M_SQRT3
501 * Expand 1.73205080756887729352
502 */
503static void JX9_M_SQRT3_Const(jx9_value *pVal, void *pUserData)
504{
505 SXUNUSED(pUserData); /* cc warning */
506 jx9_value_double(pVal, 1.73205080756887729352);
507}
508/*
509 * M_SQRT1_2
510 * Expand 0.70710678118654752440
511 */
512static void JX9_M_SQRT1_2_Const(jx9_value *pVal, void *pUserData)
513{
514 SXUNUSED(pUserData); /* cc warning */
515 jx9_value_double(pVal, 0.70710678118654752440);
516}
517/*
518 * M_LNPI
519 * Expand 1.14472988584940017414
520 */
521static void JX9_M_LNPI_Const(jx9_value *pVal, void *pUserData)
522{
523 SXUNUSED(pUserData); /* cc warning */
524 jx9_value_double(pVal, 1.14472988584940017414);
525}
526/*
527 * M_EULER
528 * Expand 0.57721566490153286061
529 */
530static void JX9_M_EULER_Const(jx9_value *pVal, void *pUserData)
531{
532 SXUNUSED(pUserData); /* cc warning */
533 jx9_value_double(pVal, 0.57721566490153286061);
534}
535#endif /* JX9_DISABLE_BUILTIN_MATH */
536/*
537 * DATE_ATOM
538 * Expand Atom (example: 2005-08-15T15:52:01+00:00)
539 */
540static void JX9_DATE_ATOM_Const(jx9_value *pVal, void *pUserData)
541{
542 SXUNUSED(pUserData); /* cc warning */
543 jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/);
544}
545/*
546 * DATE_COOKIE
547 * HTTP Cookies (example: Monday, 15-Aug-05 15:52:01 UTC)
548 */
549static void JX9_DATE_COOKIE_Const(jx9_value *pVal, void *pUserData)
550{
551 SXUNUSED(pUserData); /* cc warning */
552 jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/);
553}
554/*
555 * DATE_ISO8601
556 * ISO-8601 (example: 2005-08-15T15:52:01+0000)
557 */
558static void JX9_DATE_ISO8601_Const(jx9_value *pVal, void *pUserData)
559{
560 SXUNUSED(pUserData); /* cc warning */
561 jx9_value_string(pVal, "Y-m-d\\TH:i:sO", -1/*Compute length automatically*/);
562}
563/*
564 * DATE_RFC822
565 * RFC 822 (example: Mon, 15 Aug 05 15:52:01 +0000)
566 */
567static void JX9_DATE_RFC822_Const(jx9_value *pVal, void *pUserData)
568{
569 SXUNUSED(pUserData); /* cc warning */
570 jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/);
571}
572/*
573 * DATE_RFC850
574 * RFC 850 (example: Monday, 15-Aug-05 15:52:01 UTC)
575 */
576static void JX9_DATE_RFC850_Const(jx9_value *pVal, void *pUserData)
577{
578 SXUNUSED(pUserData); /* cc warning */
579 jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/);
580}
581/*
582 * DATE_RFC1036
583 * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000)
584 */
585static void JX9_DATE_RFC1036_Const(jx9_value *pVal, void *pUserData)
586{
587 SXUNUSED(pUserData); /* cc warning */
588 jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/);
589}
590/*
591 * DATE_RFC1123
592 * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000)
593 */
594static void JX9_DATE_RFC1123_Const(jx9_value *pVal, void *pUserData)
595{
596 SXUNUSED(pUserData); /* cc warning */
597 jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/);
598}
599/*
600 * DATE_RFC2822
601 * RFC 2822 (Mon, 15 Aug 2005 15:52:01 +0000)
602 */
603static void JX9_DATE_RFC2822_Const(jx9_value *pVal, void *pUserData)
604{
605 SXUNUSED(pUserData); /* cc warning */
606 jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/);
607}
608/*
609 * DATE_RSS
610 * RSS (Mon, 15 Aug 2005 15:52:01 +0000)
611 */
612static void JX9_DATE_RSS_Const(jx9_value *pVal, void *pUserData)
613{
614 SXUNUSED(pUserData); /* cc warning */
615 jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/);
616}
617/*
618 * DATE_W3C
619 * World Wide Web Consortium (example: 2005-08-15T15:52:01+00:00)
620 */
621static void JX9_DATE_W3C_Const(jx9_value *pVal, void *pUserData)
622{
623 SXUNUSED(pUserData); /* cc warning */
624 jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/);
625}
626/*
627 * ENT_COMPAT
628 * Expand 0x01 (Must be a power of two)
629 */
630static void JX9_ENT_COMPAT_Const(jx9_value *pVal, void *pUserData)
631{
632 SXUNUSED(pUserData); /* cc warning */
633 jx9_value_int(pVal, 0x01);
634}
635/*
636 * ENT_QUOTES
637 * Expand 0x02 (Must be a power of two)
638 */
639static void JX9_ENT_QUOTES_Const(jx9_value *pVal, void *pUserData)
640{
641 SXUNUSED(pUserData); /* cc warning */
642 jx9_value_int(pVal, 0x02);
643}
644/*
645 * ENT_NOQUOTES
646 * Expand 0x04 (Must be a power of two)
647 */
648static void JX9_ENT_NOQUOTES_Const(jx9_value *pVal, void *pUserData)
649{
650 SXUNUSED(pUserData); /* cc warning */
651 jx9_value_int(pVal, 0x04);
652}
653/*
654 * ENT_IGNORE
655 * Expand 0x08 (Must be a power of two)
656 */
657static void JX9_ENT_IGNORE_Const(jx9_value *pVal, void *pUserData)
658{
659 SXUNUSED(pUserData); /* cc warning */
660 jx9_value_int(pVal, 0x08);
661}
662/*
663 * ENT_SUBSTITUTE
664 * Expand 0x10 (Must be a power of two)
665 */
666static void JX9_ENT_SUBSTITUTE_Const(jx9_value *pVal, void *pUserData)
667{
668 SXUNUSED(pUserData); /* cc warning */
669 jx9_value_int(pVal, 0x10);
670}
671/*
672 * ENT_DISALLOWED
673 * Expand 0x20 (Must be a power of two)
674 */
675static void JX9_ENT_DISALLOWED_Const(jx9_value *pVal, void *pUserData)
676{
677 SXUNUSED(pUserData); /* cc warning */
678 jx9_value_int(pVal, 0x20);
679}
680/*
681 * ENT_HTML401
682 * Expand 0x40 (Must be a power of two)
683 */
684static void JX9_ENT_HTML401_Const(jx9_value *pVal, void *pUserData)
685{
686 SXUNUSED(pUserData); /* cc warning */
687 jx9_value_int(pVal, 0x40);
688}
689/*
690 * ENT_XML1
691 * Expand 0x80 (Must be a power of two)
692 */
693static void JX9_ENT_XML1_Const(jx9_value *pVal, void *pUserData)
694{
695 SXUNUSED(pUserData); /* cc warning */
696 jx9_value_int(pVal, 0x80);
697}
698/*
699 * ENT_XHTML
700 * Expand 0x100 (Must be a power of two)
701 */
702static void JX9_ENT_XHTML_Const(jx9_value *pVal, void *pUserData)
703{
704 SXUNUSED(pUserData); /* cc warning */
705 jx9_value_int(pVal, 0x100);
706}
707/*
708 * ENT_HTML5
709 * Expand 0x200 (Must be a power of two)
710 */
711static void JX9_ENT_HTML5_Const(jx9_value *pVal, void *pUserData)
712{
713 SXUNUSED(pUserData); /* cc warning */
714 jx9_value_int(pVal, 0x200);
715}
716/*
717 * ISO-8859-1
718 * ISO_8859_1
719 * Expand 1
720 */
721static void JX9_ISO88591_Const(jx9_value *pVal, void *pUserData)
722{
723 SXUNUSED(pUserData); /* cc warning */
724 jx9_value_int(pVal, 1);
725}
726/*
727 * UTF-8
728 * UTF8
729 * Expand 2
730 */
731static void JX9_UTF8_Const(jx9_value *pVal, void *pUserData)
732{
733 SXUNUSED(pUserData); /* cc warning */
734 jx9_value_int(pVal, 1);
735}
736/*
737 * HTML_ENTITIES
738 * Expand 1
739 */
740static void JX9_HTML_ENTITIES_Const(jx9_value *pVal, void *pUserData)
741{
742 SXUNUSED(pUserData); /* cc warning */
743 jx9_value_int(pVal, 1);
744}
745/*
746 * HTML_SPECIALCHARS
747 * Expand 2
748 */
749static void JX9_HTML_SPECIALCHARS_Const(jx9_value *pVal, void *pUserData)
750{
751 SXUNUSED(pUserData); /* cc warning */
752 jx9_value_int(pVal, 2);
753}
754/*
755 * JX9_URL_SCHEME.
756 * Expand 1
757 */
758static void JX9_JX9_URL_SCHEME_Const(jx9_value *pVal, void *pUserData)
759{
760 SXUNUSED(pUserData); /* cc warning */
761 jx9_value_int(pVal, 1);
762}
763/*
764 * JX9_URL_HOST.
765 * Expand 2
766 */
767static void JX9_JX9_URL_HOST_Const(jx9_value *pVal, void *pUserData)
768{
769 SXUNUSED(pUserData); /* cc warning */
770 jx9_value_int(pVal, 2);
771}
772/*
773 * JX9_URL_PORT.
774 * Expand 3
775 */
776static void JX9_JX9_URL_PORT_Const(jx9_value *pVal, void *pUserData)
777{
778 SXUNUSED(pUserData); /* cc warning */
779 jx9_value_int(pVal, 3);
780}
781/*
782 * JX9_URL_USER.
783 * Expand 4
784 */
785static void JX9_JX9_URL_USER_Const(jx9_value *pVal, void *pUserData)
786{
787 SXUNUSED(pUserData); /* cc warning */
788 jx9_value_int(pVal, 4);
789}
790/*
791 * JX9_URL_PASS.
792 * Expand 5
793 */
794static void JX9_JX9_URL_PASS_Const(jx9_value *pVal, void *pUserData)
795{
796 SXUNUSED(pUserData); /* cc warning */
797 jx9_value_int(pVal, 5);
798}
799/*
800 * JX9_URL_PATH.
801 * Expand 6
802 */
803static void JX9_JX9_URL_PATH_Const(jx9_value *pVal, void *pUserData)
804{
805 SXUNUSED(pUserData); /* cc warning */
806 jx9_value_int(pVal, 6);
807}
808/*
809 * JX9_URL_QUERY.
810 * Expand 7
811 */
812static void JX9_JX9_URL_QUERY_Const(jx9_value *pVal, void *pUserData)
813{
814 SXUNUSED(pUserData); /* cc warning */
815 jx9_value_int(pVal, 7);
816}
817/*
818 * JX9_URL_FRAGMENT.
819 * Expand 8
820 */
821static void JX9_JX9_URL_FRAGMENT_Const(jx9_value *pVal, void *pUserData)
822{
823 SXUNUSED(pUserData); /* cc warning */
824 jx9_value_int(pVal, 8);
825}
826/*
827 * JX9_QUERY_RFC1738
828 * Expand 1
829 */
830static void JX9_JX9_QUERY_RFC1738_Const(jx9_value *pVal, void *pUserData)
831{
832 SXUNUSED(pUserData); /* cc warning */
833 jx9_value_int(pVal, 1);
834}
835/*
836 * JX9_QUERY_RFC3986
837 * Expand 1
838 */
839static void JX9_JX9_QUERY_RFC3986_Const(jx9_value *pVal, void *pUserData)
840{
841 SXUNUSED(pUserData); /* cc warning */
842 jx9_value_int(pVal, 2);
843}
844/*
845 * FNM_NOESCAPE
846 * Expand 0x01 (Must be a power of two)
847 */
848static void JX9_FNM_NOESCAPE_Const(jx9_value *pVal, void *pUserData)
849{
850 SXUNUSED(pUserData); /* cc warning */
851 jx9_value_int(pVal, 0x01);
852}
853/*
854 * FNM_PATHNAME
855 * Expand 0x02 (Must be a power of two)
856 */
857static void JX9_FNM_PATHNAME_Const(jx9_value *pVal, void *pUserData)
858{
859 SXUNUSED(pUserData); /* cc warning */
860 jx9_value_int(pVal, 0x02);
861}
862/*
863 * FNM_PERIOD
864 * Expand 0x04 (Must be a power of two)
865 */
866static void JX9_FNM_PERIOD_Const(jx9_value *pVal, void *pUserData)
867{
868 SXUNUSED(pUserData); /* cc warning */
869 jx9_value_int(pVal, 0x04);
870}
871/*
872 * FNM_CASEFOLD
873 * Expand 0x08 (Must be a power of two)
874 */
875static void JX9_FNM_CASEFOLD_Const(jx9_value *pVal, void *pUserData)
876{
877 SXUNUSED(pUserData); /* cc warning */
878 jx9_value_int(pVal, 0x08);
879}
880/*
881 * PATHINFO_DIRNAME
882 * Expand 1.
883 */
884static void JX9_PATHINFO_DIRNAME_Const(jx9_value *pVal, void *pUserData)
885{
886 SXUNUSED(pUserData); /* cc warning */
887 jx9_value_int(pVal, 1);
888}
889/*
890 * PATHINFO_BASENAME
891 * Expand 2.
892 */
893static void JX9_PATHINFO_BASENAME_Const(jx9_value *pVal, void *pUserData)
894{
895 SXUNUSED(pUserData); /* cc warning */
896 jx9_value_int(pVal, 2);
897}
898/*
899 * PATHINFO_EXTENSION
900 * Expand 3.
901 */
902static void JX9_PATHINFO_EXTENSION_Const(jx9_value *pVal, void *pUserData)
903{
904 SXUNUSED(pUserData); /* cc warning */
905 jx9_value_int(pVal, 3);
906}
907/*
908 * PATHINFO_FILENAME
909 * Expand 4.
910 */
911static void JX9_PATHINFO_FILENAME_Const(jx9_value *pVal, void *pUserData)
912{
913 SXUNUSED(pUserData); /* cc warning */
914 jx9_value_int(pVal, 4);
915}
916/*
917 * ASSERT_ACTIVE.
918 * Expand the value of JX9_ASSERT_ACTIVE defined in jx9Int.h
919 */
920static void JX9_ASSERT_ACTIVE_Const(jx9_value *pVal, void *pUserData)
921{
922 SXUNUSED(pUserData); /* cc warning */
923 jx9_value_int(pVal, JX9_ASSERT_DISABLE);
924}
925/*
926 * ASSERT_WARNING.
927 * Expand the value of JX9_ASSERT_WARNING defined in jx9Int.h
928 */
929static void JX9_ASSERT_WARNING_Const(jx9_value *pVal, void *pUserData)
930{
931 SXUNUSED(pUserData); /* cc warning */
932 jx9_value_int(pVal, JX9_ASSERT_WARNING);
933}
934/*
935 * ASSERT_BAIL.
936 * Expand the value of JX9_ASSERT_BAIL defined in jx9Int.h
937 */
938static void JX9_ASSERT_BAIL_Const(jx9_value *pVal, void *pUserData)
939{
940 SXUNUSED(pUserData); /* cc warning */
941 jx9_value_int(pVal, JX9_ASSERT_BAIL);
942}
943/*
944 * ASSERT_QUIET_EVAL.
945 * Expand the value of JX9_ASSERT_QUIET_EVAL defined in jx9Int.h
946 */
947static void JX9_ASSERT_QUIET_EVAL_Const(jx9_value *pVal, void *pUserData)
948{
949 SXUNUSED(pUserData); /* cc warning */
950 jx9_value_int(pVal, JX9_ASSERT_QUIET_EVAL);
951}
952/*
953 * ASSERT_CALLBACK.
954 * Expand the value of JX9_ASSERT_CALLBACK defined in jx9Int.h
955 */
956static void JX9_ASSERT_CALLBACK_Const(jx9_value *pVal, void *pUserData)
957{
958 SXUNUSED(pUserData); /* cc warning */
959 jx9_value_int(pVal, JX9_ASSERT_CALLBACK);
960}
961/*
962 * SEEK_SET.
963 * Expand 0
964 */
965static void JX9_SEEK_SET_Const(jx9_value *pVal, void *pUserData)
966{
967 SXUNUSED(pUserData); /* cc warning */
968 jx9_value_int(pVal, 0);
969}
970/*
971 * SEEK_CUR.
972 * Expand 1
973 */
974static void JX9_SEEK_CUR_Const(jx9_value *pVal, void *pUserData)
975{
976 SXUNUSED(pUserData); /* cc warning */
977 jx9_value_int(pVal, 1);
978}
979/*
980 * SEEK_END.
981 * Expand 2
982 */
983static void JX9_SEEK_END_Const(jx9_value *pVal, void *pUserData)
984{
985 SXUNUSED(pUserData); /* cc warning */
986 jx9_value_int(pVal, 2);
987}
988/*
989 * LOCK_SH.
990 * Expand 2
991 */
992static void JX9_LOCK_SH_Const(jx9_value *pVal, void *pUserData)
993{
994 SXUNUSED(pUserData); /* cc warning */
995 jx9_value_int(pVal, 1);
996}
997/*
998 * LOCK_NB.
999 * Expand 5
1000 */
1001static void JX9_LOCK_NB_Const(jx9_value *pVal, void *pUserData)
1002{
1003 SXUNUSED(pUserData); /* cc warning */
1004 jx9_value_int(pVal, 5);
1005}
1006/*
1007 * LOCK_EX.
1008 * Expand 0x01 (MUST BE A POWER OF TWO)
1009 */
1010static void JX9_LOCK_EX_Const(jx9_value *pVal, void *pUserData)
1011{
1012 SXUNUSED(pUserData); /* cc warning */
1013 jx9_value_int(pVal, 0x01);
1014}
1015/*
1016 * LOCK_UN.
1017 * Expand 0
1018 */
1019static void JX9_LOCK_UN_Const(jx9_value *pVal, void *pUserData)
1020{
1021 SXUNUSED(pUserData); /* cc warning */
1022 jx9_value_int(pVal, 0);
1023}
1024/*
1025 * FILE_USE_INC_PATH
1026 * Expand 0x01 (Must be a power of two)
1027 */
1028static void JX9_FILE_USE_INCLUDE_PATH_Const(jx9_value *pVal, void *pUserData)
1029{
1030 SXUNUSED(pUserData); /* cc warning */
1031 jx9_value_int(pVal, 0x1);
1032}
1033/*
1034 * FILE_IGN_NL
1035 * Expand 0x02 (Must be a power of two)
1036 */
1037static void JX9_FILE_IGNORE_NEW_LINES_Const(jx9_value *pVal, void *pUserData)
1038{
1039 SXUNUSED(pUserData); /* cc warning */
1040 jx9_value_int(pVal, 0x2);
1041}
1042/*
1043 * FILE_SKIP_EL
1044 * Expand 0x04 (Must be a power of two)
1045 */
1046static void JX9_FILE_SKIP_EMPTY_LINES_Const(jx9_value *pVal, void *pUserData)
1047{
1048 SXUNUSED(pUserData); /* cc warning */
1049 jx9_value_int(pVal, 0x4);
1050}
1051/*
1052 * FILE_APPEND
1053 * Expand 0x08 (Must be a power of two)
1054 */
1055static void JX9_FILE_APPEND_Const(jx9_value *pVal, void *pUserData)
1056{
1057 SXUNUSED(pUserData); /* cc warning */
1058 jx9_value_int(pVal, 0x08);
1059}
1060/*
1061 * SCANDIR_SORT_ASCENDING
1062 * Expand 0
1063 */
1064static void JX9_SCANDIR_SORT_ASCENDING_Const(jx9_value *pVal, void *pUserData)
1065{
1066 SXUNUSED(pUserData); /* cc warning */
1067 jx9_value_int(pVal, 0);
1068}
1069/*
1070 * SCANDIR_SORT_DESCENDING
1071 * Expand 1
1072 */
1073static void JX9_SCANDIR_SORT_DESCENDING_Const(jx9_value *pVal, void *pUserData)
1074{
1075 SXUNUSED(pUserData); /* cc warning */
1076 jx9_value_int(pVal, 1);
1077}
1078/*
1079 * SCANDIR_SORT_NONE
1080 * Expand 2
1081 */
1082static void JX9_SCANDIR_SORT_NONE_Const(jx9_value *pVal, void *pUserData)
1083{
1084 SXUNUSED(pUserData); /* cc warning */
1085 jx9_value_int(pVal, 2);
1086}
1087/*
1088 * GLOB_MARK
1089 * Expand 0x01 (must be a power of two)
1090 */
1091static void JX9_GLOB_MARK_Const(jx9_value *pVal, void *pUserData)
1092{
1093 SXUNUSED(pUserData); /* cc warning */
1094 jx9_value_int(pVal, 0x01);
1095}
1096/*
1097 * GLOB_NOSORT
1098 * Expand 0x02 (must be a power of two)
1099 */
1100static void JX9_GLOB_NOSORT_Const(jx9_value *pVal, void *pUserData)
1101{
1102 SXUNUSED(pUserData); /* cc warning */
1103 jx9_value_int(pVal, 0x02);
1104}
1105/*
1106 * GLOB_NOCHECK
1107 * Expand 0x04 (must be a power of two)
1108 */
1109static void JX9_GLOB_NOCHECK_Const(jx9_value *pVal, void *pUserData)
1110{
1111 SXUNUSED(pUserData); /* cc warning */
1112 jx9_value_int(pVal, 0x04);
1113}
1114/*
1115 * GLOB_NOESCAPE
1116 * Expand 0x08 (must be a power of two)
1117 */
1118static void JX9_GLOB_NOESCAPE_Const(jx9_value *pVal, void *pUserData)
1119{
1120 SXUNUSED(pUserData); /* cc warning */
1121 jx9_value_int(pVal, 0x08);
1122}
1123/*
1124 * GLOB_BRACE
1125 * Expand 0x10 (must be a power of two)
1126 */
1127static void JX9_GLOB_BRACE_Const(jx9_value *pVal, void *pUserData)
1128{
1129 SXUNUSED(pUserData); /* cc warning */
1130 jx9_value_int(pVal, 0x10);
1131}
1132/*
1133 * GLOB_ONLYDIR
1134 * Expand 0x20 (must be a power of two)
1135 */
1136static void JX9_GLOB_ONLYDIR_Const(jx9_value *pVal, void *pUserData)
1137{
1138 SXUNUSED(pUserData); /* cc warning */
1139 jx9_value_int(pVal, 0x20);
1140}
1141/*
1142 * GLOB_ERR
1143 * Expand 0x40 (must be a power of two)
1144 */
1145static void JX9_GLOB_ERR_Const(jx9_value *pVal, void *pUserData)
1146{
1147 SXUNUSED(pUserData); /* cc warning */
1148 jx9_value_int(pVal, 0x40);
1149}
1150/*
1151 * STDIN
1152 * Expand the STDIN handle as a resource.
1153 */
1154static void JX9_STDIN_Const(jx9_value *pVal, void *pUserData)
1155{
1156 jx9_vm *pVm = (jx9_vm *)pUserData;
1157 void *pResource;
1158 pResource = jx9ExportStdin(pVm);
1159 jx9_value_resource(pVal, pResource);
1160}
1161/*
1162 * STDOUT
1163 * Expand the STDOUT handle as a resource.
1164 */
1165static void JX9_STDOUT_Const(jx9_value *pVal, void *pUserData)
1166{
1167 jx9_vm *pVm = (jx9_vm *)pUserData;
1168 void *pResource;
1169 pResource = jx9ExportStdout(pVm);
1170 jx9_value_resource(pVal, pResource);
1171}
1172/*
1173 * STDERR
1174 * Expand the STDERR handle as a resource.
1175 */
1176static void JX9_STDERR_Const(jx9_value *pVal, void *pUserData)
1177{
1178 jx9_vm *pVm = (jx9_vm *)pUserData;
1179 void *pResource;
1180 pResource = jx9ExportStderr(pVm);
1181 jx9_value_resource(pVal, pResource);
1182}
1183/*
1184 * INI_SCANNER_NORMAL
1185 * Expand 1
1186 */
1187static void JX9_INI_SCANNER_NORMAL_Const(jx9_value *pVal, void *pUserData)
1188{
1189 SXUNUSED(pUserData); /* cc warning */
1190 jx9_value_int(pVal, 1);
1191}
1192/*
1193 * INI_SCANNER_RAW
1194 * Expand 2
1195 */
1196static void JX9_INI_SCANNER_RAW_Const(jx9_value *pVal, void *pUserData)
1197{
1198 SXUNUSED(pUserData); /* cc warning */
1199 jx9_value_int(pVal, 2);
1200}
1201/*
1202 * EXTR_OVERWRITE
1203 * Expand 0x01 (Must be a power of two)
1204 */
1205static void JX9_EXTR_OVERWRITE_Const(jx9_value *pVal, void *pUserData)
1206{
1207 SXUNUSED(pUserData); /* cc warning */
1208 jx9_value_int(pVal, 0x1);
1209}
1210/*
1211 * EXTR_SKIP
1212 * Expand 0x02 (Must be a power of two)
1213 */
1214static void JX9_EXTR_SKIP_Const(jx9_value *pVal, void *pUserData)
1215{
1216 SXUNUSED(pUserData); /* cc warning */
1217 jx9_value_int(pVal, 0x2);
1218}
1219/*
1220 * EXTR_PREFIX_SAME
1221 * Expand 0x04 (Must be a power of two)
1222 */
1223static void JX9_EXTR_PREFIX_SAME_Const(jx9_value *pVal, void *pUserData)
1224{
1225 SXUNUSED(pUserData); /* cc warning */
1226 jx9_value_int(pVal, 0x4);
1227}
1228/*
1229 * EXTR_PREFIX_ALL
1230 * Expand 0x08 (Must be a power of two)
1231 */
1232static void JX9_EXTR_PREFIX_ALL_Const(jx9_value *pVal, void *pUserData)
1233{
1234 SXUNUSED(pUserData); /* cc warning */
1235 jx9_value_int(pVal, 0x8);
1236}
1237/*
1238 * EXTR_PREFIX_INVALID
1239 * Expand 0x10 (Must be a power of two)
1240 */
1241static void JX9_EXTR_PREFIX_INVALID_Const(jx9_value *pVal, void *pUserData)
1242{
1243 SXUNUSED(pUserData); /* cc warning */
1244 jx9_value_int(pVal, 0x10);
1245}
1246/*
1247 * EXTR_IF_EXISTS
1248 * Expand 0x20 (Must be a power of two)
1249 */
1250static void JX9_EXTR_IF_EXISTS_Const(jx9_value *pVal, void *pUserData)
1251{
1252 SXUNUSED(pUserData); /* cc warning */
1253 jx9_value_int(pVal, 0x20);
1254}
1255/*
1256 * EXTR_PREFIX_IF_EXISTS
1257 * Expand 0x40 (Must be a power of two)
1258 */
1259static void JX9_EXTR_PREFIX_IF_EXISTS_Const(jx9_value *pVal, void *pUserData)
1260{
1261 SXUNUSED(pUserData); /* cc warning */
1262 jx9_value_int(pVal, 0x40);
1263}
1264/*
1265 * Table of built-in constants.
1266 */
1267static const jx9_builtin_constant aBuiltIn[] = {
1268 {"JX9_VERSION", JX9_VER_Const },
1269 {"JX9_ENGINE", JX9_VER_Const },
1270 {"__JX9__", JX9_VER_Const },
1271 {"JX9_OS", JX9_OS_Const },
1272 {"__OS__", JX9_OS_Const },
1273 {"JX9_EOL", JX9_EOL_Const },
1274 {"JX9_INT_MAX", JX9_INTMAX_Const },
1275 {"MAXINT", JX9_INTMAX_Const },
1276 {"JX9_INT_SIZE", JX9_INTSIZE_Const },
1277 {"PATH_SEPARATOR", JX9_PATHSEP_Const },
1278 {"DIRECTORY_SEPARATOR", JX9_DIRSEP_Const },
1279 {"DIR_SEP", JX9_DIRSEP_Const },
1280 {"__TIME__", JX9_TIME_Const },
1281 {"__DATE__", JX9_DATE_Const },
1282 {"__FILE__", JX9_FILE_Const },
1283 {"__DIR__", JX9_DIR_Const },
1284 {"E_ERROR", JX9_E_ERROR_Const },
1285 {"E_WARNING", JX9_E_WARNING_Const},
1286 {"E_PARSE", JX9_E_PARSE_Const },
1287 {"E_NOTICE", JX9_E_NOTICE_Const },
1288 {"CASE_LOWER", JX9_CASE_LOWER_Const },
1289 {"CASE_UPPER", JX9_CASE_UPPER_Const },
1290 {"STR_PAD_LEFT", JX9_STR_PAD_LEFT_Const },
1291 {"STR_PAD_RIGHT", JX9_STR_PAD_RIGHT_Const},
1292 {"STR_PAD_BOTH", JX9_STR_PAD_BOTH_Const },
1293 {"COUNT_NORMAL", JX9_COUNT_NORMAL_Const },
1294 {"COUNT_RECURSIVE", JX9_COUNT_RECURSIVE_Const },
1295 {"SORT_ASC", JX9_SORT_ASC_Const },
1296 {"SORT_DESC", JX9_SORT_DESC_Const },
1297 {"SORT_REGULAR", JX9_SORT_REG_Const },
1298 {"SORT_NUMERIC", JX9_SORT_NUMERIC_Const },
1299 {"SORT_STRING", JX9_SORT_STRING_Const },
1300 {"JX9_ROUND_HALF_DOWN", JX9_JX9_ROUND_HALF_DOWN_Const },
1301 {"JX9_ROUND_HALF_EVEN", JX9_JX9_ROUND_HALF_EVEN_Const },
1302 {"JX9_ROUND_HALF_UP", JX9_JX9_ROUND_HALF_UP_Const },
1303 {"JX9_ROUND_HALF_ODD", JX9_JX9_ROUND_HALF_ODD_Const },
1304#ifdef JX9_ENABLE_MATH_FUNC
1305 {"PI", JX9_M_PI_Const },
1306 {"M_E", JX9_M_E_Const },
1307 {"M_LOG2E", JX9_M_LOG2E_Const },
1308 {"M_LOG10E", JX9_M_LOG10E_Const },
1309 {"M_LN2", JX9_M_LN2_Const },
1310 {"M_LN10", JX9_M_LN10_Const },
1311 {"M_PI_2", JX9_M_PI_2_Const },
1312 {"M_PI_4", JX9_M_PI_4_Const },
1313 {"M_1_PI", JX9_M_1_PI_Const },
1314 {"M_2_PI", JX9_M_2_PI_Const },
1315 {"M_SQRTPI", JX9_M_SQRTPI_Const },
1316 {"M_2_SQRTPI", JX9_M_2_SQRTPI_Const },
1317 {"M_SQRT2", JX9_M_SQRT2_Const },
1318 {"M_SQRT3", JX9_M_SQRT3_Const },
1319 {"M_SQRT1_2", JX9_M_SQRT1_2_Const },
1320 {"M_LNPI", JX9_M_LNPI_Const },
1321 {"M_EULER", JX9_M_EULER_Const },
1322#endif /* JX9_ENABLE_MATH_FUNC */
1323 {"DATE_ATOM", JX9_DATE_ATOM_Const },
1324 {"DATE_COOKIE", JX9_DATE_COOKIE_Const },
1325 {"DATE_ISO8601", JX9_DATE_ISO8601_Const },
1326 {"DATE_RFC822", JX9_DATE_RFC822_Const },
1327 {"DATE_RFC850", JX9_DATE_RFC850_Const },
1328 {"DATE_RFC1036", JX9_DATE_RFC1036_Const },
1329 {"DATE_RFC1123", JX9_DATE_RFC1123_Const },
1330 {"DATE_RFC2822", JX9_DATE_RFC2822_Const },
1331 {"DATE_RFC3339", JX9_DATE_ATOM_Const },
1332 {"DATE_RSS", JX9_DATE_RSS_Const },
1333 {"DATE_W3C", JX9_DATE_W3C_Const },
1334 {"ENT_COMPAT", JX9_ENT_COMPAT_Const },
1335 {"ENT_QUOTES", JX9_ENT_QUOTES_Const },
1336 {"ENT_NOQUOTES", JX9_ENT_NOQUOTES_Const },
1337 {"ENT_IGNORE", JX9_ENT_IGNORE_Const },
1338 {"ENT_SUBSTITUTE", JX9_ENT_SUBSTITUTE_Const},
1339 {"ENT_DISALLOWED", JX9_ENT_DISALLOWED_Const},
1340 {"ENT_HTML401", JX9_ENT_HTML401_Const },
1341 {"ENT_XML1", JX9_ENT_XML1_Const },
1342 {"ENT_XHTML", JX9_ENT_XHTML_Const },
1343 {"ENT_HTML5", JX9_ENT_HTML5_Const },
1344 {"ISO-8859-1", JX9_ISO88591_Const },
1345 {"ISO_8859_1", JX9_ISO88591_Const },
1346 {"UTF-8", JX9_UTF8_Const },
1347 {"UTF8", JX9_UTF8_Const },
1348 {"HTML_ENTITIES", JX9_HTML_ENTITIES_Const},
1349 {"HTML_SPECIALCHARS", JX9_HTML_SPECIALCHARS_Const },
1350 {"JX9_URL_SCHEME", JX9_JX9_URL_SCHEME_Const},
1351 {"JX9_URL_HOST", JX9_JX9_URL_HOST_Const},
1352 {"JX9_URL_PORT", JX9_JX9_URL_PORT_Const},
1353 {"JX9_URL_USER", JX9_JX9_URL_USER_Const},
1354 {"JX9_URL_PASS", JX9_JX9_URL_PASS_Const},
1355 {"JX9_URL_PATH", JX9_JX9_URL_PATH_Const},
1356 {"JX9_URL_QUERY", JX9_JX9_URL_QUERY_Const},
1357 {"JX9_URL_FRAGMENT", JX9_JX9_URL_FRAGMENT_Const},
1358 {"JX9_QUERY_RFC1738", JX9_JX9_QUERY_RFC1738_Const},
1359 {"JX9_QUERY_RFC3986", JX9_JX9_QUERY_RFC3986_Const},
1360 {"FNM_NOESCAPE", JX9_FNM_NOESCAPE_Const },
1361 {"FNM_PATHNAME", JX9_FNM_PATHNAME_Const },
1362 {"FNM_PERIOD", JX9_FNM_PERIOD_Const },
1363 {"FNM_CASEFOLD", JX9_FNM_CASEFOLD_Const },
1364 {"PATHINFO_DIRNAME", JX9_PATHINFO_DIRNAME_Const },
1365 {"PATHINFO_BASENAME", JX9_PATHINFO_BASENAME_Const },
1366 {"PATHINFO_EXTENSION", JX9_PATHINFO_EXTENSION_Const},
1367 {"PATHINFO_FILENAME", JX9_PATHINFO_FILENAME_Const },
1368 {"ASSERT_ACTIVE", JX9_ASSERT_ACTIVE_Const },
1369 {"ASSERT_WARNING", JX9_ASSERT_WARNING_Const },
1370 {"ASSERT_BAIL", JX9_ASSERT_BAIL_Const },
1371 {"ASSERT_QUIET_EVAL", JX9_ASSERT_QUIET_EVAL_Const },
1372 {"ASSERT_CALLBACK", JX9_ASSERT_CALLBACK_Const },
1373 {"SEEK_SET", JX9_SEEK_SET_Const },
1374 {"SEEK_CUR", JX9_SEEK_CUR_Const },
1375 {"SEEK_END", JX9_SEEK_END_Const },
1376 {"LOCK_EX", JX9_LOCK_EX_Const },
1377 {"LOCK_SH", JX9_LOCK_SH_Const },
1378 {"LOCK_NB", JX9_LOCK_NB_Const },
1379 {"LOCK_UN", JX9_LOCK_UN_Const },
1380 {"FILE_USE_INC_PATH", JX9_FILE_USE_INCLUDE_PATH_Const},
1381 {"FILE_IGN_NL", JX9_FILE_IGNORE_NEW_LINES_Const},
1382 {"FILE_SKIP_EL", JX9_FILE_SKIP_EMPTY_LINES_Const},
1383 {"FILE_APPEND", JX9_FILE_APPEND_Const },
1384 {"SCANDIR_SORT_ASC", JX9_SCANDIR_SORT_ASCENDING_Const },
1385 {"SCANDIR_SORT_DESC", JX9_SCANDIR_SORT_DESCENDING_Const },
1386 {"SCANDIR_SORT_NONE", JX9_SCANDIR_SORT_NONE_Const },
1387 {"GLOB_MARK", JX9_GLOB_MARK_Const },
1388 {"GLOB_NOSORT", JX9_GLOB_NOSORT_Const },
1389 {"GLOB_NOCHECK", JX9_GLOB_NOCHECK_Const },
1390 {"GLOB_NOESCAPE", JX9_GLOB_NOESCAPE_Const},
1391 {"GLOB_BRACE", JX9_GLOB_BRACE_Const },
1392 {"GLOB_ONLYDIR", JX9_GLOB_ONLYDIR_Const },
1393 {"GLOB_ERR", JX9_GLOB_ERR_Const },
1394 {"STDIN", JX9_STDIN_Const },
1395 {"stdin", JX9_STDIN_Const },
1396 {"STDOUT", JX9_STDOUT_Const },
1397 {"stdout", JX9_STDOUT_Const },
1398 {"STDERR", JX9_STDERR_Const },
1399 {"stderr", JX9_STDERR_Const },
1400 {"INI_SCANNER_NORMAL", JX9_INI_SCANNER_NORMAL_Const },
1401 {"INI_SCANNER_RAW", JX9_INI_SCANNER_RAW_Const },
1402 {"EXTR_OVERWRITE", JX9_EXTR_OVERWRITE_Const },
1403 {"EXTR_SKIP", JX9_EXTR_SKIP_Const },
1404 {"EXTR_PREFIX_SAME", JX9_EXTR_PREFIX_SAME_Const },
1405 {"EXTR_PREFIX_ALL", JX9_EXTR_PREFIX_ALL_Const },
1406 {"EXTR_PREFIX_INVALID", JX9_EXTR_PREFIX_INVALID_Const },
1407 {"EXTR_IF_EXISTS", JX9_EXTR_IF_EXISTS_Const },
1408 {"EXTR_PREFIX_IF_EXISTS", JX9_EXTR_PREFIX_IF_EXISTS_Const}
1409};
1410/*
1411 * Register the built-in constants defined above.
1412 */
1413JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm)
1414{
1415 sxu32 n;
1416 /*
1417 * Note that all built-in constants have access to the jx9 virtual machine
1418 * that trigger the constant invocation as their private data.
1419 */
1420 for( n = 0 ; n < SX_ARRAYSIZE(aBuiltIn) ; ++n ){
1421 jx9_create_constant(&(*pVm), aBuiltIn[n].zName, aBuiltIn[n].xExpand, &(*pVm));
1422 }
1423}
diff --git a/common/unqlite/jx9_hashmap.c b/common/unqlite/jx9_hashmap.c
new file mode 100644
index 0000000..e97d41d
--- /dev/null
+++ b/common/unqlite/jx9_hashmap.c
@@ -0,0 +1,2989 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: hashmap.c v2.6 Win7 2012-12-11 00:50 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* This file implement generic hashmaps used to represent JSON arrays and objects */
18/* Allowed node types */
19#define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */
20#define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */
21/*
22 * Default hash function for int [i.e; 64-bit integer] keys.
23 */
24static sxu32 IntHash(sxi64 iKey)
25{
26 return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8));
27}
28/*
29 * Default hash function for string/BLOB keys.
30 */
31static sxu32 BinHash(const void *pSrc, sxu32 nLen)
32{
33 register unsigned char *zIn = (unsigned char *)pSrc;
34 unsigned char *zEnd;
35 sxu32 nH = 5381;
36 zEnd = &zIn[nLen];
37 for(;;){
38 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
39 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
40 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
41 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
42 }
43 return nH;
44}
45/*
46 * Return the total number of entries in a given hashmap.
47 * If bRecurisve is set to TRUE then recurse on hashmap entries.
48 * If the nesting limit is reached, this function abort immediately.
49 */
50static sxi64 HashmapCount(jx9_hashmap *pMap, int bRecursive, int iRecCount)
51{
52 sxi64 iCount = 0;
53 if( !bRecursive ){
54 iCount = pMap->nEntry;
55 }else{
56 /* Recursive hashmap walk */
57 jx9_hashmap_node *pEntry = pMap->pLast;
58 jx9_value *pElem;
59 sxu32 n = 0;
60 for(;;){
61 if( n >= pMap->nEntry ){
62 break;
63 }
64 /* Point to the element value */
65 pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pEntry->nValIdx);
66 if( pElem ){
67 if( pElem->iFlags & MEMOBJ_HASHMAP ){
68 if( iRecCount > 31 ){
69 /* Nesting limit reached */
70 return iCount;
71 }
72 /* Recurse */
73 iRecCount++;
74 iCount += HashmapCount((jx9_hashmap *)pElem->x.pOther, TRUE, iRecCount);
75 iRecCount--;
76 }
77 }
78 /* Point to the next entry */
79 pEntry = pEntry->pNext;
80 ++n;
81 }
82 /* Update count */
83 iCount += pMap->nEntry;
84 }
85 return iCount;
86}
87/*
88 * Allocate a new hashmap node with a 64-bit integer key.
89 * If something goes wrong [i.e: out of memory], this function return NULL.
90 * Otherwise a fresh [jx9_hashmap_node] instance is returned.
91 */
92static jx9_hashmap_node * HashmapNewIntNode(jx9_hashmap *pMap, sxi64 iKey, sxu32 nHash, sxu32 nValIdx)
93{
94 jx9_hashmap_node *pNode;
95 /* Allocate a new node */
96 pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node));
97 if( pNode == 0 ){
98 return 0;
99 }
100 /* Zero the stucture */
101 SyZero(pNode, sizeof(jx9_hashmap_node));
102 /* Fill in the structure */
103 pNode->pMap = &(*pMap);
104 pNode->iType = HASHMAP_INT_NODE;
105 pNode->nHash = nHash;
106 pNode->xKey.iKey = iKey;
107 pNode->nValIdx = nValIdx;
108 return pNode;
109}
110/*
111 * Allocate a new hashmap node with a BLOB key.
112 * If something goes wrong [i.e: out of memory], this function return NULL.
113 * Otherwise a fresh [jx9_hashmap_node] instance is returned.
114 */
115static jx9_hashmap_node * HashmapNewBlobNode(jx9_hashmap *pMap, const void *pKey, sxu32 nKeyLen, sxu32 nHash, sxu32 nValIdx)
116{
117 jx9_hashmap_node *pNode;
118 /* Allocate a new node */
119 pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node));
120 if( pNode == 0 ){
121 return 0;
122 }
123 /* Zero the stucture */
124 SyZero(pNode, sizeof(jx9_hashmap_node));
125 /* Fill in the structure */
126 pNode->pMap = &(*pMap);
127 pNode->iType = HASHMAP_BLOB_NODE;
128 pNode->nHash = nHash;
129 SyBlobInit(&pNode->xKey.sKey, &pMap->pVm->sAllocator);
130 SyBlobAppend(&pNode->xKey.sKey, pKey, nKeyLen);
131 pNode->nValIdx = nValIdx;
132 return pNode;
133}
134/*
135 * link a hashmap node to the given bucket index (last argument to this function).
136 */
137static void HashmapNodeLink(jx9_hashmap *pMap, jx9_hashmap_node *pNode, sxu32 nBucketIdx)
138{
139 /* Link */
140 if( pMap->apBucket[nBucketIdx] != 0 ){
141 pNode->pNextCollide = pMap->apBucket[nBucketIdx];
142 pMap->apBucket[nBucketIdx]->pPrevCollide = pNode;
143 }
144 pMap->apBucket[nBucketIdx] = pNode;
145 /* Link to the map list */
146 if( pMap->pFirst == 0 ){
147 pMap->pFirst = pMap->pLast = pNode;
148 /* Point to the first inserted node */
149 pMap->pCur = pNode;
150 }else{
151 MACRO_LD_PUSH(pMap->pLast, pNode);
152 }
153 ++pMap->nEntry;
154}
155/*
156 * Unlink a node from the hashmap.
157 * If the node count reaches zero then release the whole hash-bucket.
158 */
159static void jx9HashmapUnlinkNode(jx9_hashmap_node *pNode)
160{
161 jx9_hashmap *pMap = pNode->pMap;
162 jx9_vm *pVm = pMap->pVm;
163 /* Unlink from the corresponding bucket */
164 if( pNode->pPrevCollide == 0 ){
165 pMap->apBucket[pNode->nHash & (pMap->nSize - 1)] = pNode->pNextCollide;
166 }else{
167 pNode->pPrevCollide->pNextCollide = pNode->pNextCollide;
168 }
169 if( pNode->pNextCollide ){
170 pNode->pNextCollide->pPrevCollide = pNode->pPrevCollide;
171 }
172 if( pMap->pFirst == pNode ){
173 pMap->pFirst = pNode->pPrev;
174 }
175 if( pMap->pCur == pNode ){
176 /* Advance the node cursor */
177 pMap->pCur = pMap->pCur->pPrev; /* Reverse link */
178 }
179 /* Unlink from the map list */
180 MACRO_LD_REMOVE(pMap->pLast, pNode);
181 /* Restore to the free list */
182 jx9VmUnsetMemObj(pVm, pNode->nValIdx);
183 if( pNode->iType == HASHMAP_BLOB_NODE ){
184 SyBlobRelease(&pNode->xKey.sKey);
185 }
186 SyMemBackendPoolFree(&pVm->sAllocator, pNode);
187 pMap->nEntry--;
188 if( pMap->nEntry < 1 ){
189 /* Free the hash-bucket */
190 SyMemBackendFree(&pVm->sAllocator, pMap->apBucket);
191 pMap->apBucket = 0;
192 pMap->nSize = 0;
193 pMap->pFirst = pMap->pLast = pMap->pCur = 0;
194 }
195}
196#define HASHMAP_FILL_FACTOR 3
197/*
198 * Grow the hash-table and rehash all entries.
199 */
200static sxi32 HashmapGrowBucket(jx9_hashmap *pMap)
201{
202 if( pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR ){
203 jx9_hashmap_node **apOld = pMap->apBucket;
204 jx9_hashmap_node *pEntry, **apNew;
205 sxu32 nNew = pMap->nSize << 1;
206 sxu32 nBucket;
207 sxu32 n;
208 if( nNew < 1 ){
209 nNew = 16;
210 }
211 /* Allocate a new bucket */
212 apNew = (jx9_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator, nNew * sizeof(jx9_hashmap_node *));
213 if( apNew == 0 ){
214 if( pMap->nSize < 1 ){
215 return SXERR_MEM; /* Fatal */
216 }
217 /* Not so fatal here, simply a performance hit */
218 return SXRET_OK;
219 }
220 /* Zero the table */
221 SyZero((void *)apNew, nNew * sizeof(jx9_hashmap_node *));
222 /* Reflect the change */
223 pMap->apBucket = apNew;
224 pMap->nSize = nNew;
225 if( apOld == 0 ){
226 /* First allocated table [i.e: no entry], return immediately */
227 return SXRET_OK;
228 }
229 /* Rehash old entries */
230 pEntry = pMap->pFirst;
231 n = 0;
232 for( ;; ){
233 if( n >= pMap->nEntry ){
234 break;
235 }
236 /* Clear the old collision link */
237 pEntry->pNextCollide = pEntry->pPrevCollide = 0;
238 /* Link to the new bucket */
239 nBucket = pEntry->nHash & (nNew - 1);
240 if( pMap->apBucket[nBucket] != 0 ){
241 pEntry->pNextCollide = pMap->apBucket[nBucket];
242 pMap->apBucket[nBucket]->pPrevCollide = pEntry;
243 }
244 pMap->apBucket[nBucket] = pEntry;
245 /* Point to the next entry */
246 pEntry = pEntry->pPrev; /* Reverse link */
247 n++;
248 }
249 /* Free the old table */
250 SyMemBackendFree(&pMap->pVm->sAllocator, (void *)apOld);
251 }
252 return SXRET_OK;
253}
254/*
255 * Insert a 64-bit integer key and it's associated value (if any) in the given
256 * hashmap.
257 */
258static sxi32 HashmapInsertIntKey(jx9_hashmap *pMap,sxi64 iKey,jx9_value *pValue)
259{
260 jx9_hashmap_node *pNode;
261 jx9_value *pObj;
262 sxu32 nIdx;
263 sxu32 nHash;
264 sxi32 rc;
265 /* Reserve a jx9_value for the value */
266 pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx);
267 if( pObj == 0 ){
268 return SXERR_MEM;
269 }
270 if( pValue ){
271 /* Duplicate the value */
272 jx9MemObjStore(pValue, pObj);
273 }
274 /* Hash the key */
275 nHash = pMap->xIntHash(iKey);
276 /* Allocate a new int node */
277 pNode = HashmapNewIntNode(&(*pMap), iKey, nHash, nIdx);
278 if( pNode == 0 ){
279 return SXERR_MEM;
280 }
281 /* Make sure the bucket is big enough to hold the new entry */
282 rc = HashmapGrowBucket(&(*pMap));
283 if( rc != SXRET_OK ){
284 SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode);
285 return rc;
286 }
287 /* Perform the insertion */
288 HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1));
289 /* All done */
290 return SXRET_OK;
291}
292/*
293 * Insert a BLOB key and it's associated value (if any) in the given
294 * hashmap.
295 */
296static sxi32 HashmapInsertBlobKey(jx9_hashmap *pMap,const void *pKey,sxu32 nKeyLen,jx9_value *pValue)
297{
298 jx9_hashmap_node *pNode;
299 jx9_value *pObj;
300 sxu32 nHash;
301 sxu32 nIdx;
302 sxi32 rc;
303 /* Reserve a jx9_value for the value */
304 pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx);
305 if( pObj == 0 ){
306 return SXERR_MEM;
307 }
308 if( pValue ){
309 /* Duplicate the value */
310 jx9MemObjStore(pValue, pObj);
311 }
312 /* Hash the key */
313 nHash = pMap->xBlobHash(pKey, nKeyLen);
314 /* Allocate a new blob node */
315 pNode = HashmapNewBlobNode(&(*pMap), pKey, nKeyLen, nHash, nIdx);
316 if( pNode == 0 ){
317 return SXERR_MEM;
318 }
319 /* Make sure the bucket is big enough to hold the new entry */
320 rc = HashmapGrowBucket(&(*pMap));
321 if( rc != SXRET_OK ){
322 SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode);
323 return rc;
324 }
325 /* Perform the insertion */
326 HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1));
327 /* All done */
328 return SXRET_OK;
329}
330/*
331 * Check if a given 64-bit integer key exists in the given hashmap.
332 * Write a pointer to the target node on success. Otherwise
333 * SXERR_NOTFOUND is returned on failure.
334 */
335static sxi32 HashmapLookupIntKey(
336 jx9_hashmap *pMap, /* Target hashmap */
337 sxi64 iKey, /* lookup key */
338 jx9_hashmap_node **ppNode /* OUT: target node on success */
339 )
340{
341 jx9_hashmap_node *pNode;
342 sxu32 nHash;
343 if( pMap->nEntry < 1 ){
344 /* Don't bother hashing, there is no entry anyway */
345 return SXERR_NOTFOUND;
346 }
347 /* Hash the key first */
348 nHash = pMap->xIntHash(iKey);
349 /* Point to the appropriate bucket */
350 pNode = pMap->apBucket[nHash & (pMap->nSize - 1)];
351 /* Perform the lookup */
352 for(;;){
353 if( pNode == 0 ){
354 break;
355 }
356 if( pNode->iType == HASHMAP_INT_NODE
357 && pNode->nHash == nHash
358 && pNode->xKey.iKey == iKey ){
359 /* Node found */
360 if( ppNode ){
361 *ppNode = pNode;
362 }
363 return SXRET_OK;
364 }
365 /* Follow the collision link */
366 pNode = pNode->pNextCollide;
367 }
368 /* No such entry */
369 return SXERR_NOTFOUND;
370}
371/*
372 * Check if a given BLOB key exists in the given hashmap.
373 * Write a pointer to the target node on success. Otherwise
374 * SXERR_NOTFOUND is returned on failure.
375 */
376static sxi32 HashmapLookupBlobKey(
377 jx9_hashmap *pMap, /* Target hashmap */
378 const void *pKey, /* Lookup key */
379 sxu32 nKeyLen, /* Key length in bytes */
380 jx9_hashmap_node **ppNode /* OUT: target node on success */
381 )
382{
383 jx9_hashmap_node *pNode;
384 sxu32 nHash;
385 if( pMap->nEntry < 1 ){
386 /* Don't bother hashing, there is no entry anyway */
387 return SXERR_NOTFOUND;
388 }
389 /* Hash the key first */
390 nHash = pMap->xBlobHash(pKey, nKeyLen);
391 /* Point to the appropriate bucket */
392 pNode = pMap->apBucket[nHash & (pMap->nSize - 1)];
393 /* Perform the lookup */
394 for(;;){
395 if( pNode == 0 ){
396 break;
397 }
398 if( pNode->iType == HASHMAP_BLOB_NODE
399 && pNode->nHash == nHash
400 && SyBlobLength(&pNode->xKey.sKey) == nKeyLen
401 && SyMemcmp(SyBlobData(&pNode->xKey.sKey), pKey, nKeyLen) == 0 ){
402 /* Node found */
403 if( ppNode ){
404 *ppNode = pNode;
405 }
406 return SXRET_OK;
407 }
408 /* Follow the collision link */
409 pNode = pNode->pNextCollide;
410 }
411 /* No such entry */
412 return SXERR_NOTFOUND;
413}
414/*
415 * Check if the given BLOB key looks like a decimal number.
416 * Retrurn TRUE on success.FALSE otherwise.
417 */
418static int HashmapIsIntKey(SyBlob *pKey)
419{
420 const char *zIn = (const char *)SyBlobData(pKey);
421 const char *zEnd = &zIn[SyBlobLength(pKey)];
422 if( (int)(zEnd-zIn) > 1 && zIn[0] == '0' ){
423 /* Octal not decimal number */
424 return FALSE;
425 }
426 if( (zIn[0] == '-' || zIn[0] == '+') && &zIn[1] < zEnd ){
427 zIn++;
428 }
429 for(;;){
430 if( zIn >= zEnd ){
431 return TRUE;
432 }
433 if( (unsigned char)zIn[0] >= 0xc0 /* UTF-8 stream */ || !SyisDigit(zIn[0]) ){
434 break;
435 }
436 zIn++;
437 }
438 /* Key does not look like a decimal number */
439 return FALSE;
440}
441/*
442 * Check if a given key exists in the given hashmap.
443 * Write a pointer to the target node on success.
444 * Otherwise SXERR_NOTFOUND is returned on failure.
445 */
446static sxi32 HashmapLookup(
447 jx9_hashmap *pMap, /* Target hashmap */
448 jx9_value *pKey, /* Lookup key */
449 jx9_hashmap_node **ppNode /* OUT: target node on success */
450 )
451{
452 jx9_hashmap_node *pNode = 0; /* cc -O6 warning */
453 sxi32 rc;
454 if( pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES) ){
455 if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){
456 /* Force a string cast */
457 jx9MemObjToString(&(*pKey));
458 }
459 if( SyBlobLength(&pKey->sBlob) > 0 ){
460 /* Perform a blob lookup */
461 rc = HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode);
462 goto result;
463 }
464 }
465 /* Perform an int lookup */
466 if((pKey->iFlags & MEMOBJ_INT) == 0 ){
467 /* Force an integer cast */
468 jx9MemObjToInteger(pKey);
469 }
470 /* Perform an int lookup */
471 rc = HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode);
472result:
473 if( rc == SXRET_OK ){
474 /* Node found */
475 if( ppNode ){
476 *ppNode = pNode;
477 }
478 return SXRET_OK;
479 }
480 /* No such entry */
481 return SXERR_NOTFOUND;
482}
483/*
484 * Insert a given key and it's associated value (if any) in the given
485 * hashmap.
486 * If a node with the given key already exists in the database
487 * then this function overwrite the old value.
488 */
489static sxi32 HashmapInsert(
490 jx9_hashmap *pMap, /* Target hashmap */
491 jx9_value *pKey, /* Lookup key */
492 jx9_value *pVal /* Node value */
493 )
494{
495 jx9_hashmap_node *pNode = 0;
496 sxi32 rc = SXRET_OK;
497 if( pMap->nEntry < 1 && pKey && (pKey->iFlags & MEMOBJ_STRING) ){
498 pMap->iFlags |= HASHMAP_JSON_OBJECT;
499 }
500 if( pKey && (pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES)) ){
501 if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){
502 /* Force a string cast */
503 jx9MemObjToString(&(*pKey));
504 }
505 if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){
506 if(SyBlobLength(&pKey->sBlob) < 1){
507 /* Automatic index assign */
508 pKey = 0;
509 }
510 goto IntKey;
511 }
512 if( SXRET_OK == HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob),
513 SyBlobLength(&pKey->sBlob), &pNode) ){
514 /* Overwrite the old value */
515 jx9_value *pElem;
516 pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx);
517 if( pElem ){
518 if( pVal ){
519 jx9MemObjStore(pVal, pElem);
520 }else{
521 /* Nullify the entry */
522 jx9MemObjToNull(pElem);
523 }
524 }
525 return SXRET_OK;
526 }
527 /* Perform a blob-key insertion */
528 rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&(*pVal));
529 return rc;
530 }
531IntKey:
532 if( pKey ){
533 if((pKey->iFlags & MEMOBJ_INT) == 0 ){
534 /* Force an integer cast */
535 jx9MemObjToInteger(pKey);
536 }
537 if( SXRET_OK == HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode) ){
538 /* Overwrite the old value */
539 jx9_value *pElem;
540 pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx);
541 if( pElem ){
542 if( pVal ){
543 jx9MemObjStore(pVal, pElem);
544 }else{
545 /* Nullify the entry */
546 jx9MemObjToNull(pElem);
547 }
548 }
549 return SXRET_OK;
550 }
551 /* Perform a 64-bit-int-key insertion */
552 rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, &(*pVal));
553 if( rc == SXRET_OK ){
554 if( pKey->x.iVal >= pMap->iNextIdx ){
555 /* Increment the automatic index */
556 pMap->iNextIdx = pKey->x.iVal + 1;
557 /* Make sure the automatic index is not reserved */
558 while( SXRET_OK == HashmapLookupIntKey(&(*pMap), pMap->iNextIdx, 0) ){
559 pMap->iNextIdx++;
560 }
561 }
562 }
563 }else{
564 /* Assign an automatic index */
565 rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,&(*pVal));
566 if( rc == SXRET_OK ){
567 ++pMap->iNextIdx;
568 }
569 }
570 /* Insertion result */
571 return rc;
572}
573/*
574 * Extract node value.
575 */
576static jx9_value * HashmapExtractNodeValue(jx9_hashmap_node *pNode)
577{
578 /* Point to the desired object */
579 jx9_value *pObj;
580 pObj = (jx9_value *)SySetAt(&pNode->pMap->pVm->aMemObj, pNode->nValIdx);
581 return pObj;
582}
583/*
584 * Insert a node in the given hashmap.
585 * If a node with the given key already exists in the database
586 * then this function overwrite the old value.
587 */
588static sxi32 HashmapInsertNode(jx9_hashmap *pMap, jx9_hashmap_node *pNode, int bPreserve)
589{
590 jx9_value *pObj;
591 sxi32 rc;
592 /* Extract the node value */
593 pObj = HashmapExtractNodeValue(&(*pNode));
594 if( pObj == 0 ){
595 return SXERR_EMPTY;
596 }
597 /* Preserve key */
598 if( pNode->iType == HASHMAP_INT_NODE){
599 /* Int64 key */
600 if( !bPreserve ){
601 /* Assign an automatic index */
602 rc = HashmapInsert(&(*pMap), 0, pObj);
603 }else{
604 rc = HashmapInsertIntKey(&(*pMap), pNode->xKey.iKey, pObj);
605 }
606 }else{
607 /* Blob key */
608 rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pNode->xKey.sKey),
609 SyBlobLength(&pNode->xKey.sKey), pObj);
610 }
611 return rc;
612}
613/*
614 * Compare two node values.
615 * Return 0 if the node values are equals, > 0 if pLeft is greater than pRight
616 * or < 0 if pRight is greater than pLeft.
617 * For a full description on jx9_values comparison, refer to the implementation
618 * of the [jx9MemObjCmp()] function defined in memobj.c or the official
619 * documenation.
620 */
621static sxi32 HashmapNodeCmp(jx9_hashmap_node *pLeft, jx9_hashmap_node *pRight, int bStrict)
622{
623 jx9_value sObj1, sObj2;
624 sxi32 rc;
625 if( pLeft == pRight ){
626 /*
627 * Same node.Refer to the sort() implementation defined
628 * below for more information on this sceanario.
629 */
630 return 0;
631 }
632 /* Do the comparison */
633 jx9MemObjInit(pLeft->pMap->pVm, &sObj1);
634 jx9MemObjInit(pLeft->pMap->pVm, &sObj2);
635 jx9HashmapExtractNodeValue(pLeft, &sObj1, FALSE);
636 jx9HashmapExtractNodeValue(pRight, &sObj2, FALSE);
637 rc = jx9MemObjCmp(&sObj1, &sObj2, bStrict, 0);
638 jx9MemObjRelease(&sObj1);
639 jx9MemObjRelease(&sObj2);
640 return rc;
641}
642/*
643 * Rehash a node with a 64-bit integer key.
644 * Refer to [merge_sort(), array_shift()] implementations for more information.
645 */
646static void HashmapRehashIntNode(jx9_hashmap_node *pEntry)
647{
648 jx9_hashmap *pMap = pEntry->pMap;
649 sxu32 nBucket;
650 /* Remove old collision links */
651 if( pEntry->pPrevCollide ){
652 pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide;
653 }else{
654 pMap->apBucket[pEntry->nHash & (pMap->nSize - 1)] = pEntry->pNextCollide;
655 }
656 if( pEntry->pNextCollide ){
657 pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide;
658 }
659 pEntry->pNextCollide = pEntry->pPrevCollide = 0;
660 /* Compute the new hash */
661 pEntry->nHash = pMap->xIntHash(pMap->iNextIdx);
662 pEntry->xKey.iKey = pMap->iNextIdx;
663 nBucket = pEntry->nHash & (pMap->nSize - 1);
664 /* Link to the new bucket */
665 pEntry->pNextCollide = pMap->apBucket[nBucket];
666 if( pMap->apBucket[nBucket] ){
667 pMap->apBucket[nBucket]->pPrevCollide = pEntry;
668 }
669 pEntry->pNextCollide = pMap->apBucket[nBucket];
670 pMap->apBucket[nBucket] = pEntry;
671 /* Increment the automatic index */
672 pMap->iNextIdx++;
673}
674/*
675 * Perform a linear search on a given hashmap.
676 * Write a pointer to the target node on success.
677 * Otherwise SXERR_NOTFOUND is returned on failure.
678 * Refer to [array_intersect(), array_diff(), in_array(), ...] implementations
679 * for more information.
680 */
681static int HashmapFindValue(
682 jx9_hashmap *pMap, /* Target hashmap */
683 jx9_value *pNeedle, /* Lookup key */
684 jx9_hashmap_node **ppNode, /* OUT: target node on success */
685 int bStrict /* TRUE for strict comparison */
686 )
687{
688 jx9_hashmap_node *pEntry;
689 jx9_value sVal, *pVal;
690 jx9_value sNeedle;
691 sxi32 rc;
692 sxu32 n;
693 /* Perform a linear search since we cannot sort the hashmap based on values */
694 pEntry = pMap->pFirst;
695 n = pMap->nEntry;
696 jx9MemObjInit(pMap->pVm, &sVal);
697 jx9MemObjInit(pMap->pVm, &sNeedle);
698 for(;;){
699 if( n < 1 ){
700 break;
701 }
702 /* Extract node value */
703 pVal = HashmapExtractNodeValue(pEntry);
704 if( pVal ){
705 if( (pVal->iFlags|pNeedle->iFlags) & MEMOBJ_NULL ){
706 sxi32 iF1 = pVal->iFlags;
707 sxi32 iF2 = pNeedle->iFlags;
708 if( iF1 == iF2 ){
709 /* NULL values are equals */
710 if( ppNode ){
711 *ppNode = pEntry;
712 }
713 return SXRET_OK;
714 }
715 }else{
716 /* Duplicate value */
717 jx9MemObjLoad(pVal, &sVal);
718 jx9MemObjLoad(pNeedle, &sNeedle);
719 rc = jx9MemObjCmp(&sNeedle, &sVal, bStrict, 0);
720 jx9MemObjRelease(&sVal);
721 jx9MemObjRelease(&sNeedle);
722 if( rc == 0 ){
723 if( ppNode ){
724 *ppNode = pEntry;
725 }
726 /* Match found*/
727 return SXRET_OK;
728 }
729 }
730 }
731 /* Point to the next entry */
732 pEntry = pEntry->pPrev; /* Reverse link */
733 n--;
734 }
735 /* No such entry */
736 return SXERR_NOTFOUND;
737}
738/*
739 * Compare two hashmaps.
740 * Return 0 if the hashmaps are equals.Any other value indicates inequality.
741 * Note on array comparison operators.
742 * According to the JX9 language reference manual.
743 * Array Operators Example Name Result
744 * $a + $b Union Union of $a and $b.
745 * $a == $b Equality TRUE if $a and $b have the same key/value pairs.
746 * $a === $b Identity TRUE if $a and $b have the same key/value pairs in the same
747 * order and of the same types.
748 * $a != $b Inequality TRUE if $a is not equal to $b.
749 * $a <> $b Inequality TRUE if $a is not equal to $b.
750 * $a !== $b Non-identity TRUE if $a is not identical to $b.
751 * The + operator returns the right-hand array appended to the left-hand array;
752 * For keys that exist in both arrays, the elements from the left-hand array will be used
753 * and the matching elements from the right-hand array will be ignored.
754 * <?jx9
755 * $a = array("a" => "apple", "b" => "banana");
756 * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry");
757 * $c = $a + $b; // Union of $a and $b
758 * print "Union of \$a and \$b: \n";
759 * dump($c);
760 * $c = $b + $a; // Union of $b and $a
761 * print "Union of \$b and \$a: \n";
762 * dump($c);
763 * ?>
764 * When executed, this script will print the following:
765 * Union of $a and $b:
766 * array(3) {
767 * ["a"]=>
768 * string(5) "apple"
769 * ["b"]=>
770 * string(6) "banana"
771 * ["c"]=>
772 * string(6) "cherry"
773 * }
774 * Union of $b and $a:
775 * array(3) {
776 * ["a"]=>
777 * string(4) "pear"
778 * ["b"]=>
779 * string(10) "strawberry"
780 * ["c"]=>
781 * string(6) "cherry"
782 * }
783 * Elements of arrays are equal for the comparison if they have the same key and value.
784 */
785JX9_PRIVATE sxi32 jx9HashmapCmp(
786 jx9_hashmap *pLeft, /* Left hashmap */
787 jx9_hashmap *pRight, /* Right hashmap */
788 int bStrict /* TRUE for strict comparison */
789 )
790{
791 jx9_hashmap_node *pLe, *pRe;
792 sxi32 rc;
793 sxu32 n;
794 if( pLeft == pRight ){
795 /* Same hashmap instance. This can easily happen since hashmaps are passed by reference.
796 * Unlike the engine.
797 */
798 return 0;
799 }
800 if( pLeft->nEntry != pRight->nEntry ){
801 /* Must have the same number of entries */
802 return pLeft->nEntry > pRight->nEntry ? 1 : -1;
803 }
804 /* Point to the first inserted entry of the left hashmap */
805 pLe = pLeft->pFirst;
806 pRe = 0; /* cc warning */
807 /* Perform the comparison */
808 n = pLeft->nEntry;
809 for(;;){
810 if( n < 1 ){
811 break;
812 }
813 if( pLe->iType == HASHMAP_INT_NODE){
814 /* Int key */
815 rc = HashmapLookupIntKey(&(*pRight), pLe->xKey.iKey, &pRe);
816 }else{
817 SyBlob *pKey = &pLe->xKey.sKey;
818 /* Blob key */
819 rc = HashmapLookupBlobKey(&(*pRight), SyBlobData(pKey), SyBlobLength(pKey), &pRe);
820 }
821 if( rc != SXRET_OK ){
822 /* No such entry in the right side */
823 return 1;
824 }
825 rc = 0;
826 if( bStrict ){
827 /* Make sure, the keys are of the same type */
828 if( pLe->iType != pRe->iType ){
829 rc = 1;
830 }
831 }
832 if( !rc ){
833 /* Compare nodes */
834 rc = HashmapNodeCmp(pLe, pRe, bStrict);
835 }
836 if( rc != 0 ){
837 /* Nodes key/value differ */
838 return rc;
839 }
840 /* Point to the next entry */
841 pLe = pLe->pPrev; /* Reverse link */
842 n--;
843 }
844 return 0; /* Hashmaps are equals */
845}
846/*
847 * Merge two hashmaps.
848 * Note on the merge process
849 * According to the JX9 language reference manual.
850 * Merges the elements of two arrays together so that the values of one are appended
851 * to the end of the previous one. It returns the resulting array (pDest).
852 * If the input arrays have the same string keys, then the later value for that key
853 * will overwrite the previous one. If, however, the arrays contain numeric keys
854 * the later value will not overwrite the original value, but will be appended.
855 * Values in the input array with numeric keys will be renumbered with incrementing
856 * keys starting from zero in the result array.
857 */
858static sxi32 HashmapMerge(jx9_hashmap *pSrc, jx9_hashmap *pDest)
859{
860 jx9_hashmap_node *pEntry;
861 jx9_value sKey, *pVal;
862 sxi32 rc;
863 sxu32 n;
864 if( pSrc == pDest ){
865 /* Same map. This can easily happen since hashmaps are passed by reference.
866 * Unlike the engine.
867 */
868 return SXRET_OK;
869 }
870 /* Point to the first inserted entry in the source */
871 pEntry = pSrc->pFirst;
872 /* Perform the merge */
873 for( n = 0 ; n < pSrc->nEntry ; ++n ){
874 /* Extract the node value */
875 pVal = HashmapExtractNodeValue(pEntry);
876 if( pEntry->iType == HASHMAP_BLOB_NODE ){
877 /* Blob key insertion */
878 jx9MemObjInitFromString(pDest->pVm, &sKey, 0);
879 jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey));
880 rc = jx9HashmapInsert(&(*pDest), &sKey, pVal);
881 jx9MemObjRelease(&sKey);
882 }else{
883 rc = HashmapInsert(&(*pDest), 0/* Automatic index assign */, pVal);
884 }
885 if( rc != SXRET_OK ){
886 return rc;
887 }
888 /* Point to the next entry */
889 pEntry = pEntry->pPrev; /* Reverse link */
890 }
891 return SXRET_OK;
892}
893/*
894 * Duplicate the contents of a hashmap. Store the copy in pDest.
895 * Refer to the [array_pad(), array_copy(), ...] implementation for more information.
896 */
897JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest)
898{
899 jx9_hashmap_node *pEntry;
900 jx9_value sKey, *pVal;
901 sxi32 rc;
902 sxu32 n;
903 if( pSrc == pDest ){
904 /* Same map. This can easily happen since hashmaps are passed by reference.
905 * Unlike the engine.
906 */
907 return SXRET_OK;
908 }
909 /* Point to the first inserted entry in the source */
910 pEntry = pSrc->pFirst;
911 /* Perform the duplication */
912 for( n = 0 ; n < pSrc->nEntry ; ++n ){
913 /* Extract the node value */
914 pVal = HashmapExtractNodeValue(pEntry);
915 if( pEntry->iType == HASHMAP_BLOB_NODE ){
916 /* Blob key insertion */
917 jx9MemObjInitFromString(pDest->pVm, &sKey, 0);
918 jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey));
919 rc = jx9HashmapInsert(&(*pDest), &sKey, pVal);
920 jx9MemObjRelease(&sKey);
921 }else{
922 /* Int key insertion */
923 rc = HashmapInsertIntKey(&(*pDest), pEntry->xKey.iKey, pVal);
924 }
925 if( rc != SXRET_OK ){
926 return rc;
927 }
928 /* Point to the next entry */
929 pEntry = pEntry->pPrev; /* Reverse link */
930 }
931 return SXRET_OK;
932}
933/*
934 * Perform the union of two hashmaps.
935 * This operation is performed only if the user uses the '+' operator
936 * with a variable holding an array as follows:
937 * <?jx9
938 * $a = array("a" => "apple", "b" => "banana");
939 * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry");
940 * $c = $a + $b; // Union of $a and $b
941 * print "Union of \$a and \$b: \n";
942 * dump($c);
943 * $c = $b + $a; // Union of $b and $a
944 * print "Union of \$b and \$a: \n";
945 * dump($c);
946 * ?>
947 * When executed, this script will print the following:
948 * Union of $a and $b:
949 * array(3) {
950 * ["a"]=>
951 * string(5) "apple"
952 * ["b"]=>
953 * string(6) "banana"
954 * ["c"]=>
955 * string(6) "cherry"
956 * }
957 * Union of $b and $a:
958 * array(3) {
959 * ["a"]=>
960 * string(4) "pear"
961 * ["b"]=>
962 * string(10) "strawberry"
963 * ["c"]=>
964 * string(6) "cherry"
965 * }
966 * The + operator returns the right-hand array appended to the left-hand array;
967 * For keys that exist in both arrays, the elements from the left-hand array will be used
968 * and the matching elements from the right-hand array will be ignored.
969 */
970JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight)
971{
972 jx9_hashmap_node *pEntry;
973 sxi32 rc = SXRET_OK;
974 jx9_value *pObj;
975 sxu32 n;
976 if( pLeft == pRight ){
977 /* Same map. This can easily happen since hashmaps are passed by reference.
978 * Unlike the engine.
979 */
980 return SXRET_OK;
981 }
982 /* Perform the union */
983 pEntry = pRight->pFirst;
984 for(n = 0 ; n < pRight->nEntry ; ++n ){
985 /* Make sure the given key does not exists in the left array */
986 if( pEntry->iType == HASHMAP_BLOB_NODE ){
987 /* BLOB key */
988 if( SXRET_OK !=
989 HashmapLookupBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), 0) ){
990 pObj = HashmapExtractNodeValue(pEntry);
991 if( pObj ){
992 /* Perform the insertion */
993 rc = HashmapInsertBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey),
994 SyBlobLength(&pEntry->xKey.sKey),pObj);
995 if( rc != SXRET_OK ){
996 return rc;
997 }
998 }
999 }
1000 }else{
1001 /* INT key */
1002 if( SXRET_OK != HashmapLookupIntKey(&(*pLeft), pEntry->xKey.iKey, 0) ){
1003 pObj = HashmapExtractNodeValue(pEntry);
1004 if( pObj ){
1005 /* Perform the insertion */
1006 rc = HashmapInsertIntKey(&(*pLeft), pEntry->xKey.iKey, pObj);
1007 if( rc != SXRET_OK ){
1008 return rc;
1009 }
1010 }
1011 }
1012 }
1013 /* Point to the next entry */
1014 pEntry = pEntry->pPrev; /* Reverse link */
1015 }
1016 return SXRET_OK;
1017}
1018/*
1019 * Allocate a new hashmap.
1020 * Return a pointer to the freshly allocated hashmap on success.NULL otherwise.
1021 */
1022JX9_PRIVATE jx9_hashmap * jx9NewHashmap(
1023 jx9_vm *pVm, /* VM that trigger the hashmap creation */
1024 sxu32 (*xIntHash)(sxi64), /* Hash function for int keys.NULL otherwise*/
1025 sxu32 (*xBlobHash)(const void *, sxu32) /* Hash function for BLOB keys.NULL otherwise */
1026 )
1027{
1028 jx9_hashmap *pMap;
1029 /* Allocate a new instance */
1030 pMap = (jx9_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_hashmap));
1031 if( pMap == 0 ){
1032 return 0;
1033 }
1034 /* Zero the structure */
1035 SyZero(pMap, sizeof(jx9_hashmap));
1036 /* Fill in the structure */
1037 pMap->pVm = &(*pVm);
1038 pMap->iRef = 1;
1039 /* pMap->iFlags = 0; */
1040 /* Default hash functions */
1041 pMap->xIntHash = xIntHash ? xIntHash : IntHash;
1042 pMap->xBlobHash = xBlobHash ? xBlobHash : BinHash;
1043 return pMap;
1044}
1045/*
1046 * Install superglobals in the given virtual machine.
1047 * Note on superglobals.
1048 * According to the JX9 language reference manual.
1049 * Superglobals are built-in variables that are always available in all scopes.
1050* Description
1051* All predefined variables in JX9 are "superglobals", which means they
1052* are available in all scopes throughout a script.
1053* These variables are:
1054* $_SERVER
1055* $_GET
1056* $_POST
1057* $_FILES
1058* $_REQUEST
1059* $_ENV
1060*/
1061JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm)
1062{
1063 static const char * azSuper[] = {
1064 "_SERVER", /* $_SERVER */
1065 "_GET", /* $_GET */
1066 "_POST", /* $_POST */
1067 "_FILES", /* $_FILES */
1068 "_REQUEST", /* $_REQUEST */
1069 "_COOKIE", /* $_COOKIE */
1070 "_ENV", /* $_ENV */
1071 "_HEADER", /* $_HEADER */
1072 "argv" /* $argv */
1073 };
1074 SyString *pFile;
1075 sxi32 rc;
1076 sxu32 n;
1077 /* Install globals variable now */
1078 for( n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++ ){
1079 jx9_value *pSuper;
1080 /* Request an empty array */
1081 pSuper = jx9_new_array(&(*pVm));
1082 if( pSuper == 0 ){
1083 return SXERR_MEM;
1084 }
1085 /* Install */
1086 rc = jx9_vm_config(&(*pVm),JX9_VM_CONFIG_CREATE_VAR, azSuper[n]/* Super-global name*/, pSuper/* Super-global value */);
1087 if( rc != SXRET_OK ){
1088 return rc;
1089 }
1090 /* Release the value now it have been installed */
1091 jx9_release_value(&(*pVm), pSuper);
1092 }
1093 /* Set some $_SERVER entries */
1094 pFile = (SyString *)SySetPeek(&pVm->aFiles);
1095 /*
1096 * 'SCRIPT_FILENAME'
1097 * The absolute pathname of the currently executing script.
1098 */
1099 jx9_vm_config(pVm, JX9_VM_CONFIG_SERVER_ATTR,
1100 "SCRIPT_FILENAME",
1101 pFile ? pFile->zString : ":Memory:",
1102 pFile ? pFile->nByte : sizeof(":Memory:") - 1
1103 );
1104 /* All done, all global variables are installed now */
1105 return SXRET_OK;
1106}
1107/*
1108 * Release a hashmap.
1109 */
1110JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS)
1111{
1112 jx9_hashmap_node *pEntry, *pNext;
1113 jx9_vm *pVm = pMap->pVm;
1114 sxu32 n;
1115 /* Start the release process */
1116 n = 0;
1117 pEntry = pMap->pFirst;
1118 for(;;){
1119 if( n >= pMap->nEntry ){
1120 break;
1121 }
1122 pNext = pEntry->pPrev; /* Reverse link */
1123 /* Restore the jx9_value to the free list */
1124 jx9VmUnsetMemObj(pVm, pEntry->nValIdx);
1125 /* Release the node */
1126 if( pEntry->iType == HASHMAP_BLOB_NODE ){
1127 SyBlobRelease(&pEntry->xKey.sKey);
1128 }
1129 SyMemBackendPoolFree(&pVm->sAllocator, pEntry);
1130 /* Point to the next entry */
1131 pEntry = pNext;
1132 n++;
1133 }
1134 if( pMap->nEntry > 0 ){
1135 /* Release the hash bucket */
1136 SyMemBackendFree(&pVm->sAllocator, pMap->apBucket);
1137 }
1138 if( FreeDS ){
1139 /* Free the whole instance */
1140 SyMemBackendPoolFree(&pVm->sAllocator, pMap);
1141 }else{
1142 /* Keep the instance but reset it's fields */
1143 pMap->apBucket = 0;
1144 pMap->iNextIdx = 0;
1145 pMap->nEntry = pMap->nSize = 0;
1146 pMap->pFirst = pMap->pLast = pMap->pCur = 0;
1147 }
1148 return SXRET_OK;
1149}
1150/*
1151 * Decrement the reference count of a given hashmap.
1152 * If the count reaches zero which mean no more variables
1153 * are pointing to this hashmap, then release the whole instance.
1154 */
1155JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap)
1156{
1157 pMap->iRef--;
1158 if( pMap->iRef < 1 ){
1159 jx9HashmapRelease(pMap, TRUE);
1160 }
1161}
1162/*
1163 * Check if a given key exists in the given hashmap.
1164 * Write a pointer to the target node on success.
1165 * Otherwise SXERR_NOTFOUND is returned on failure.
1166 */
1167JX9_PRIVATE sxi32 jx9HashmapLookup(
1168 jx9_hashmap *pMap, /* Target hashmap */
1169 jx9_value *pKey, /* Lookup key */
1170 jx9_hashmap_node **ppNode /* OUT: Target node on success */
1171 )
1172{
1173 sxi32 rc;
1174 if( pMap->nEntry < 1 ){
1175 /* TICKET 1433-25: Don't bother hashing, the hashmap is empty anyway.
1176 */
1177 return SXERR_NOTFOUND;
1178 }
1179 rc = HashmapLookup(&(*pMap), &(*pKey), ppNode);
1180 return rc;
1181}
1182/*
1183 * Insert a given key and it's associated value (if any) in the given
1184 * hashmap.
1185 * If a node with the given key already exists in the database
1186 * then this function overwrite the old value.
1187 */
1188JX9_PRIVATE sxi32 jx9HashmapInsert(
1189 jx9_hashmap *pMap, /* Target hashmap */
1190 jx9_value *pKey, /* Lookup key */
1191 jx9_value *pVal /* Node value.NULL otherwise */
1192 )
1193{
1194 sxi32 rc;
1195 rc = HashmapInsert(&(*pMap), &(*pKey), &(*pVal));
1196 return rc;
1197}
1198/*
1199 * Reset the node cursor of a given hashmap.
1200 */
1201JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap)
1202{
1203 /* Reset the loop cursor */
1204 pMap->pCur = pMap->pFirst;
1205}
1206/*
1207 * Return a pointer to the node currently pointed by the node cursor.
1208 * If the cursor reaches the end of the list, then this function
1209 * return NULL.
1210 * Note that the node cursor is automatically advanced by this function.
1211 */
1212JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap)
1213{
1214 jx9_hashmap_node *pCur = pMap->pCur;
1215 if( pCur == 0 ){
1216 /* End of the list, return null */
1217 return 0;
1218 }
1219 /* Advance the node cursor */
1220 pMap->pCur = pCur->pPrev; /* Reverse link */
1221 return pCur;
1222}
1223/*
1224 * Extract a node value.
1225 */
1226JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode)
1227{
1228 jx9_value *pValue;
1229 pValue = HashmapExtractNodeValue(pNode);
1230 return pValue;
1231}
1232/*
1233 * Extract a node value (Second).
1234 */
1235JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore)
1236{
1237 jx9_value *pEntry = HashmapExtractNodeValue(pNode);
1238 if( pEntry ){
1239 if( bStore ){
1240 jx9MemObjStore(pEntry, pValue);
1241 }else{
1242 jx9MemObjLoad(pEntry, pValue);
1243 }
1244 }else{
1245 jx9MemObjRelease(pValue);
1246 }
1247}
1248/*
1249 * Extract a node key.
1250 */
1251JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode,jx9_value *pKey)
1252{
1253 /* Fill with the current key */
1254 if( pNode->iType == HASHMAP_INT_NODE ){
1255 if( SyBlobLength(&pKey->sBlob) > 0 ){
1256 SyBlobRelease(&pKey->sBlob);
1257 }
1258 pKey->x.iVal = pNode->xKey.iKey;
1259 MemObjSetType(pKey, MEMOBJ_INT);
1260 }else{
1261 SyBlobReset(&pKey->sBlob);
1262 SyBlobAppend(&pKey->sBlob, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey));
1263 MemObjSetType(pKey, MEMOBJ_STRING);
1264 }
1265}
1266#ifndef JX9_DISABLE_BUILTIN_FUNC
1267/*
1268 * Store the address of nodes value in the given container.
1269 * Refer to the [vfprintf(), vprintf(), vsprintf()] implementations
1270 * defined in 'builtin.c' for more information.
1271 */
1272JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut)
1273{
1274 jx9_hashmap_node *pEntry = pMap->pFirst;
1275 jx9_value *pValue;
1276 sxu32 n;
1277 /* Initialize the container */
1278 SySetInit(pOut, &pMap->pVm->sAllocator, sizeof(jx9_value *));
1279 for(n = 0 ; n < pMap->nEntry ; n++ ){
1280 /* Extract node value */
1281 pValue = HashmapExtractNodeValue(pEntry);
1282 if( pValue ){
1283 SySetPut(pOut, (const void *)&pValue);
1284 }
1285 /* Point to the next entry */
1286 pEntry = pEntry->pPrev; /* Reverse link */
1287 }
1288 /* Total inserted entries */
1289 return (int)SySetUsed(pOut);
1290}
1291#endif /* JX9_DISABLE_BUILTIN_FUNC */
1292/*
1293 * Merge sort.
1294 * The merge sort implementation is based on the one found in the SQLite3 source tree.
1295 * Status: Public domain
1296 */
1297/* Node comparison callback signature */
1298typedef sxi32 (*ProcNodeCmp)(jx9_hashmap_node *, jx9_hashmap_node *, void *);
1299/*
1300** Inputs:
1301** a: A sorted, null-terminated linked list. (May be null).
1302** b: A sorted, null-terminated linked list. (May be null).
1303** cmp: A pointer to the comparison function.
1304**
1305** Return Value:
1306** A pointer to the head of a sorted list containing the elements
1307** of both a and b.
1308**
1309** Side effects:
1310** The "next", "prev" pointers for elements in the lists a and b are
1311** changed.
1312*/
1313static jx9_hashmap_node * HashmapNodeMerge(jx9_hashmap_node *pA, jx9_hashmap_node *pB, ProcNodeCmp xCmp, void *pCmpData)
1314{
1315 jx9_hashmap_node result, *pTail;
1316 /* Prevent compiler warning */
1317 result.pNext = result.pPrev = 0;
1318 pTail = &result;
1319 while( pA && pB ){
1320 if( xCmp(pA, pB, pCmpData) < 0 ){
1321 pTail->pPrev = pA;
1322 pA->pNext = pTail;
1323 pTail = pA;
1324 pA = pA->pPrev;
1325 }else{
1326 pTail->pPrev = pB;
1327 pB->pNext = pTail;
1328 pTail = pB;
1329 pB = pB->pPrev;
1330 }
1331 }
1332 if( pA ){
1333 pTail->pPrev = pA;
1334 pA->pNext = pTail;
1335 }else if( pB ){
1336 pTail->pPrev = pB;
1337 pB->pNext = pTail;
1338 }else{
1339 pTail->pPrev = pTail->pNext = 0;
1340 }
1341 return result.pPrev;
1342}
1343/*
1344** Inputs:
1345** Map: Input hashmap
1346** cmp: A comparison function.
1347**
1348** Return Value:
1349** Sorted hashmap.
1350**
1351** Side effects:
1352** The "next" pointers for elements in list are changed.
1353*/
1354#define N_SORT_BUCKET 32
1355static sxi32 HashmapMergeSort(jx9_hashmap *pMap, ProcNodeCmp xCmp, void *pCmpData)
1356{
1357 jx9_hashmap_node *a[N_SORT_BUCKET], *p, *pIn;
1358 sxu32 i;
1359 SyZero(a, sizeof(a));
1360 /* Point to the first inserted entry */
1361 pIn = pMap->pFirst;
1362 while( pIn ){
1363 p = pIn;
1364 pIn = p->pPrev;
1365 p->pPrev = 0;
1366 for(i=0; i<N_SORT_BUCKET-1; i++){
1367 if( a[i]==0 ){
1368 a[i] = p;
1369 break;
1370 }else{
1371 p = HashmapNodeMerge(a[i], p, xCmp, pCmpData);
1372 a[i] = 0;
1373 }
1374 }
1375 if( i==N_SORT_BUCKET-1 ){
1376 /* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list.
1377 * But that is impossible.
1378 */
1379 a[i] = HashmapNodeMerge(a[i], p, xCmp, pCmpData);
1380 }
1381 }
1382 p = a[0];
1383 for(i=1; i<N_SORT_BUCKET; i++){
1384 p = HashmapNodeMerge(p, a[i], xCmp, pCmpData);
1385 }
1386 p->pNext = 0;
1387 /* Reflect the change */
1388 pMap->pFirst = p;
1389 /* Reset the loop cursor */
1390 pMap->pCur = pMap->pFirst;
1391 return SXRET_OK;
1392}
1393/*
1394 * Node comparison callback.
1395 * used-by: [sort(), asort(), ...]
1396 */
1397static sxi32 HashmapCmpCallback1(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData)
1398{
1399 jx9_value sA, sB;
1400 sxi32 iFlags;
1401 int rc;
1402 if( pCmpData == 0 ){
1403 /* Perform a standard comparison */
1404 rc = HashmapNodeCmp(pA, pB, FALSE);
1405 return rc;
1406 }
1407 iFlags = SX_PTR_TO_INT(pCmpData);
1408 /* Duplicate node values */
1409 jx9MemObjInit(pA->pMap->pVm, &sA);
1410 jx9MemObjInit(pA->pMap->pVm, &sB);
1411 jx9HashmapExtractNodeValue(pA, &sA, FALSE);
1412 jx9HashmapExtractNodeValue(pB, &sB, FALSE);
1413 if( iFlags == 5 ){
1414 /* String cast */
1415 if( (sA.iFlags & MEMOBJ_STRING) == 0 ){
1416 jx9MemObjToString(&sA);
1417 }
1418 if( (sB.iFlags & MEMOBJ_STRING) == 0 ){
1419 jx9MemObjToString(&sB);
1420 }
1421 }else{
1422 /* Numeric cast */
1423 jx9MemObjToNumeric(&sA);
1424 jx9MemObjToNumeric(&sB);
1425 }
1426 /* Perform the comparison */
1427 rc = jx9MemObjCmp(&sA, &sB, FALSE, 0);
1428 jx9MemObjRelease(&sA);
1429 jx9MemObjRelease(&sB);
1430 return rc;
1431}
1432/*
1433 * Node comparison callback.
1434 * Used by: [rsort(), arsort()];
1435 */
1436static sxi32 HashmapCmpCallback3(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData)
1437{
1438 jx9_value sA, sB;
1439 sxi32 iFlags;
1440 int rc;
1441 if( pCmpData == 0 ){
1442 /* Perform a standard comparison */
1443 rc = HashmapNodeCmp(pA, pB, FALSE);
1444 return -rc;
1445 }
1446 iFlags = SX_PTR_TO_INT(pCmpData);
1447 /* Duplicate node values */
1448 jx9MemObjInit(pA->pMap->pVm, &sA);
1449 jx9MemObjInit(pA->pMap->pVm, &sB);
1450 jx9HashmapExtractNodeValue(pA, &sA, FALSE);
1451 jx9HashmapExtractNodeValue(pB, &sB, FALSE);
1452 if( iFlags == 5 ){
1453 /* String cast */
1454 if( (sA.iFlags & MEMOBJ_STRING) == 0 ){
1455 jx9MemObjToString(&sA);
1456 }
1457 if( (sB.iFlags & MEMOBJ_STRING) == 0 ){
1458 jx9MemObjToString(&sB);
1459 }
1460 }else{
1461 /* Numeric cast */
1462 jx9MemObjToNumeric(&sA);
1463 jx9MemObjToNumeric(&sB);
1464 }
1465 /* Perform the comparison */
1466 rc = jx9MemObjCmp(&sA, &sB, FALSE, 0);
1467 jx9MemObjRelease(&sA);
1468 jx9MemObjRelease(&sB);
1469 return -rc;
1470}
1471/*
1472 * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison.
1473 * used-by: [usort(), uasort()]
1474 */
1475static sxi32 HashmapCmpCallback4(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData)
1476{
1477 jx9_value sResult, *pCallback;
1478 jx9_value *pV1, *pV2;
1479 jx9_value *apArg[2]; /* Callback arguments */
1480 sxi32 rc;
1481 /* Point to the desired callback */
1482 pCallback = (jx9_value *)pCmpData;
1483 /* initialize the result value */
1484 jx9MemObjInit(pA->pMap->pVm, &sResult);
1485 /* Extract nodes values */
1486 pV1 = HashmapExtractNodeValue(pA);
1487 pV2 = HashmapExtractNodeValue(pB);
1488 apArg[0] = pV1;
1489 apArg[1] = pV2;
1490 /* Invoke the callback */
1491 rc = jx9VmCallUserFunction(pA->pMap->pVm, pCallback, 2, apArg, &sResult);
1492 if( rc != SXRET_OK ){
1493 /* An error occured while calling user defined function [i.e: not defined] */
1494 rc = -1; /* Set a dummy result */
1495 }else{
1496 /* Extract callback result */
1497 if((sResult.iFlags & MEMOBJ_INT) == 0 ){
1498 /* Perform an int cast */
1499 jx9MemObjToInteger(&sResult);
1500 }
1501 rc = (sxi32)sResult.x.iVal;
1502 }
1503 jx9MemObjRelease(&sResult);
1504 /* Callback result */
1505 return rc;
1506}
1507/*
1508 * Rehash all nodes keys after a merge-sort have been applied.
1509 * Used by [sort(), usort() and rsort()].
1510 */
1511static void HashmapSortRehash(jx9_hashmap *pMap)
1512{
1513 jx9_hashmap_node *p, *pLast;
1514 sxu32 i;
1515 /* Rehash all entries */
1516 pLast = p = pMap->pFirst;
1517 pMap->iNextIdx = 0; /* Reset the automatic index */
1518 i = 0;
1519 for( ;; ){
1520 if( i >= pMap->nEntry ){
1521 pMap->pLast = pLast; /* Fix the last link broken by the merge-sort */
1522 break;
1523 }
1524 if( p->iType == HASHMAP_BLOB_NODE ){
1525 /* Do not maintain index association as requested by the JX9 specification */
1526 SyBlobRelease(&p->xKey.sKey);
1527 /* Change key type */
1528 p->iType = HASHMAP_INT_NODE;
1529 }
1530 HashmapRehashIntNode(p);
1531 /* Point to the next entry */
1532 i++;
1533 pLast = p;
1534 p = p->pPrev; /* Reverse link */
1535 }
1536}
1537/*
1538 * Array functions implementation.
1539 * Authors:
1540 * Symisc Systems, devel@symisc.net.
1541 * Copyright (C) Symisc Systems, http://jx9.symisc.net
1542 * Status:
1543 * Stable.
1544 */
1545/*
1546 * bool sort(array &$array[, int $sort_flags = SORT_REGULAR ] )
1547 * Sort an array.
1548 * Parameters
1549 * $array
1550 * The input array.
1551 * $sort_flags
1552 * The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
1553 * Sorting type flags:
1554 * SORT_REGULAR - compare items normally (don't change types)
1555 * SORT_NUMERIC - compare items numerically
1556 * SORT_STRING - compare items as strings
1557 * Return
1558 * TRUE on success or FALSE on failure.
1559 *
1560 */
1561static int jx9_hashmap_sort(jx9_context *pCtx, int nArg, jx9_value **apArg)
1562{
1563 jx9_hashmap *pMap;
1564 /* Make sure we are dealing with a valid hashmap */
1565 if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){
1566 /* Missing/Invalid arguments, return FALSE */
1567 jx9_result_bool(pCtx, 0);
1568 return JX9_OK;
1569 }
1570 /* Point to the internal representation of the input hashmap */
1571 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
1572 if( pMap->nEntry > 1 ){
1573 sxi32 iCmpFlags = 0;
1574 if( nArg > 1 ){
1575 /* Extract comparison flags */
1576 iCmpFlags = jx9_value_to_int(apArg[1]);
1577 if( iCmpFlags == 3 /* SORT_REGULAR */ ){
1578 iCmpFlags = 0; /* Standard comparison */
1579 }
1580 }
1581 /* Do the merge sort */
1582 HashmapMergeSort(pMap, HashmapCmpCallback1, SX_INT_TO_PTR(iCmpFlags));
1583 /* Rehash [Do not maintain index association as requested by the JX9 specification] */
1584 HashmapSortRehash(pMap);
1585 }
1586 /* All done, return TRUE */
1587 jx9_result_bool(pCtx, 1);
1588 return JX9_OK;
1589}
1590/*
1591 * bool rsort(array &$array[, int $sort_flags = SORT_REGULAR ] )
1592 * Sort an array in reverse order.
1593 * Parameters
1594 * $array
1595 * The input array.
1596 * $sort_flags
1597 * The optional second parameter sort_flags may be used to modify the sorting behavior using these values:
1598 * Sorting type flags:
1599 * SORT_REGULAR - compare items normally (don't change types)
1600 * SORT_NUMERIC - compare items numerically
1601 * SORT_STRING - compare items as strings
1602 * Return
1603 * TRUE on success or FALSE on failure.
1604 */
1605static int jx9_hashmap_rsort(jx9_context *pCtx, int nArg, jx9_value **apArg)
1606{
1607 jx9_hashmap *pMap;
1608 /* Make sure we are dealing with a valid hashmap */
1609 if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){
1610 /* Missing/Invalid arguments, return FALSE */
1611 jx9_result_bool(pCtx, 0);
1612 return JX9_OK;
1613 }
1614 /* Point to the internal representation of the input hashmap */
1615 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
1616 if( pMap->nEntry > 1 ){
1617 sxi32 iCmpFlags = 0;
1618 if( nArg > 1 ){
1619 /* Extract comparison flags */
1620 iCmpFlags = jx9_value_to_int(apArg[1]);
1621 if( iCmpFlags == 3 /* SORT_REGULAR */ ){
1622 iCmpFlags = 0; /* Standard comparison */
1623 }
1624 }
1625 /* Do the merge sort */
1626 HashmapMergeSort(pMap, HashmapCmpCallback3, SX_INT_TO_PTR(iCmpFlags));
1627 /* Rehash [Do not maintain index association as requested by the JX9 specification] */
1628 HashmapSortRehash(pMap);
1629 }
1630 /* All done, return TRUE */
1631 jx9_result_bool(pCtx, 1);
1632 return JX9_OK;
1633}
1634/*
1635 * bool usort(array &$array, callable $cmp_function)
1636 * Sort an array by values using a user-defined comparison function.
1637 * Parameters
1638 * $array
1639 * The input array.
1640 * $cmp_function
1641 * The comparison function must return an integer less than, equal to, or greater
1642 * than zero if the first argument is considered to be respectively less than, equal
1643 * to, or greater than the second.
1644 * int callback ( mixed $a, mixed $b )
1645 * Return
1646 * TRUE on success or FALSE on failure.
1647 */
1648static int jx9_hashmap_usort(jx9_context *pCtx, int nArg, jx9_value **apArg)
1649{
1650 jx9_hashmap *pMap;
1651 /* Make sure we are dealing with a valid hashmap */
1652 if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){
1653 /* Missing/Invalid arguments, return FALSE */
1654 jx9_result_bool(pCtx, 0);
1655 return JX9_OK;
1656 }
1657 /* Point to the internal representation of the input hashmap */
1658 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
1659 if( pMap->nEntry > 1 ){
1660 jx9_value *pCallback = 0;
1661 ProcNodeCmp xCmp;
1662 xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */
1663 if( nArg > 1 && jx9_value_is_callable(apArg[1]) ){
1664 /* Point to the desired callback */
1665 pCallback = apArg[1];
1666 }else{
1667 /* Use the default comparison function */
1668 xCmp = HashmapCmpCallback1;
1669 }
1670 /* Do the merge sort */
1671 HashmapMergeSort(pMap, xCmp, pCallback);
1672 /* Rehash [Do not maintain index association as requested by the JX9 specification] */
1673 HashmapSortRehash(pMap);
1674 }
1675 /* All done, return TRUE */
1676 jx9_result_bool(pCtx, 1);
1677 return JX9_OK;
1678}
1679/*
1680 * int count(array $var [, int $mode = COUNT_NORMAL ])
1681 * Count all elements in an array, or something in an object.
1682 * Parameters
1683 * $var
1684 * The array or the object.
1685 * $mode
1686 * If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count()
1687 * will recursively count the array. This is particularly useful for counting
1688 * all the elements of a multidimensional array. count() does not detect infinite
1689 * recursion.
1690 * Return
1691 * Returns the number of elements in the array.
1692 */
1693static int jx9_hashmap_count(jx9_context *pCtx, int nArg, jx9_value **apArg)
1694{
1695 int bRecursive = FALSE;
1696 sxi64 iCount;
1697 if( nArg < 1 ){
1698 /* Missing arguments, return 0 */
1699 jx9_result_int(pCtx, 0);
1700 return JX9_OK;
1701 }
1702 if( !jx9_value_is_json_array(apArg[0]) ){
1703 /* TICKET 1433-19: Handle objects */
1704 int res = !jx9_value_is_null(apArg[0]);
1705 jx9_result_int(pCtx, res);
1706 return JX9_OK;
1707 }
1708 if( nArg > 1 ){
1709 /* Recursive count? */
1710 bRecursive = jx9_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */;
1711 }
1712 /* Count */
1713 iCount = HashmapCount((jx9_hashmap *)apArg[0]->x.pOther, bRecursive, 0);
1714 jx9_result_int64(pCtx, iCount);
1715 return JX9_OK;
1716}
1717/*
1718 * bool array_key_exists(value $key, array $search)
1719 * Checks if the given key or index exists in the array.
1720 * Parameters
1721 * $key
1722 * Value to check.
1723 * $search
1724 * An array with keys to check.
1725 * Return
1726 * TRUE on success or FALSE on failure.
1727 */
1728static int jx9_hashmap_key_exists(jx9_context *pCtx, int nArg, jx9_value **apArg)
1729{
1730 sxi32 rc;
1731 if( nArg < 2 ){
1732 /* Missing arguments, return FALSE */
1733 jx9_result_bool(pCtx, 0);
1734 return JX9_OK;
1735 }
1736 /* Make sure we are dealing with a valid hashmap */
1737 if( !jx9_value_is_json_array(apArg[1]) ){
1738 /* Invalid argument, return FALSE */
1739 jx9_result_bool(pCtx, 0);
1740 return JX9_OK;
1741 }
1742 /* Perform the lookup */
1743 rc = jx9HashmapLookup((jx9_hashmap *)apArg[1]->x.pOther, apArg[0], 0);
1744 /* lookup result */
1745 jx9_result_bool(pCtx, rc == SXRET_OK ? 1 : 0);
1746 return JX9_OK;
1747}
1748/*
1749 * value array_pop(array $array)
1750 * POP the last inserted element from the array.
1751 * Parameter
1752 * The array to get the value from.
1753 * Return
1754 * Poped value or NULL on failure.
1755 */
1756static int jx9_hashmap_pop(jx9_context *pCtx, int nArg, jx9_value **apArg)
1757{
1758 jx9_hashmap *pMap;
1759 if( nArg < 1 ){
1760 /* Missing arguments, return null */
1761 jx9_result_null(pCtx);
1762 return JX9_OK;
1763 }
1764 /* Make sure we are dealing with a valid hashmap */
1765 if( !jx9_value_is_json_array(apArg[0]) ){
1766 /* Invalid argument, return null */
1767 jx9_result_null(pCtx);
1768 return JX9_OK;
1769 }
1770 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
1771 if( pMap->nEntry < 1 ){
1772 /* Noting to pop, return NULL */
1773 jx9_result_null(pCtx);
1774 }else{
1775 jx9_hashmap_node *pLast = pMap->pLast;
1776 jx9_value *pObj;
1777 pObj = HashmapExtractNodeValue(pLast);
1778 if( pObj ){
1779 /* Node value */
1780 jx9_result_value(pCtx, pObj);
1781 /* Unlink the node */
1782 jx9HashmapUnlinkNode(pLast);
1783 }else{
1784 jx9_result_null(pCtx);
1785 }
1786 /* Reset the cursor */
1787 pMap->pCur = pMap->pFirst;
1788 }
1789 return JX9_OK;
1790}
1791/*
1792 * int array_push($array, $var, ...)
1793 * Push one or more elements onto the end of array. (Stack insertion)
1794 * Parameters
1795 * array
1796 * The input array.
1797 * var
1798 * On or more value to push.
1799 * Return
1800 * New array count (including old items).
1801 */
1802static int jx9_hashmap_push(jx9_context *pCtx, int nArg, jx9_value **apArg)
1803{
1804 jx9_hashmap *pMap;
1805 sxi32 rc;
1806 int i;
1807 if( nArg < 1 ){
1808 /* Missing arguments, return 0 */
1809 jx9_result_int(pCtx, 0);
1810 return JX9_OK;
1811 }
1812 /* Make sure we are dealing with a valid hashmap */
1813 if( !jx9_value_is_json_array(apArg[0]) ){
1814 /* Invalid argument, return 0 */
1815 jx9_result_int(pCtx, 0);
1816 return JX9_OK;
1817 }
1818 /* Point to the internal representation of the input hashmap */
1819 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
1820 /* Start pushing given values */
1821 for( i = 1 ; i < nArg ; ++i ){
1822 rc = jx9HashmapInsert(pMap, 0, apArg[i]);
1823 if( rc != SXRET_OK ){
1824 break;
1825 }
1826 }
1827 /* Return the new count */
1828 jx9_result_int64(pCtx, (sxi64)pMap->nEntry);
1829 return JX9_OK;
1830}
1831/*
1832 * value array_shift(array $array)
1833 * Shift an element off the beginning of array.
1834 * Parameter
1835 * The array to get the value from.
1836 * Return
1837 * Shifted value or NULL on failure.
1838 */
1839static int jx9_hashmap_shift(jx9_context *pCtx, int nArg, jx9_value **apArg)
1840{
1841 jx9_hashmap *pMap;
1842 if( nArg < 1 ){
1843 /* Missing arguments, return null */
1844 jx9_result_null(pCtx);
1845 return JX9_OK;
1846 }
1847 /* Make sure we are dealing with a valid hashmap */
1848 if( !jx9_value_is_json_array(apArg[0]) ){
1849 /* Invalid argument, return null */
1850 jx9_result_null(pCtx);
1851 return JX9_OK;
1852 }
1853 /* Point to the internal representation of the hashmap */
1854 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
1855 if( pMap->nEntry < 1 ){
1856 /* Empty hashmap, return NULL */
1857 jx9_result_null(pCtx);
1858 }else{
1859 jx9_hashmap_node *pEntry = pMap->pFirst;
1860 jx9_value *pObj;
1861 sxu32 n;
1862 pObj = HashmapExtractNodeValue(pEntry);
1863 if( pObj ){
1864 /* Node value */
1865 jx9_result_value(pCtx, pObj);
1866 /* Unlink the first node */
1867 jx9HashmapUnlinkNode(pEntry);
1868 }else{
1869 jx9_result_null(pCtx);
1870 }
1871 /* Rehash all int keys */
1872 n = pMap->nEntry;
1873 pEntry = pMap->pFirst;
1874 pMap->iNextIdx = 0; /* Reset the automatic index */
1875 for(;;){
1876 if( n < 1 ){
1877 break;
1878 }
1879 if( pEntry->iType == HASHMAP_INT_NODE ){
1880 HashmapRehashIntNode(pEntry);
1881 }
1882 /* Point to the next entry */
1883 pEntry = pEntry->pPrev; /* Reverse link */
1884 n--;
1885 }
1886 /* Reset the cursor */
1887 pMap->pCur = pMap->pFirst;
1888 }
1889 return JX9_OK;
1890}
1891/*
1892 * Extract the node cursor value.
1893 */
1894static sxi32 HashmapCurrentValue(jx9_context *pCtx, jx9_hashmap *pMap, int iDirection)
1895{
1896 jx9_hashmap_node *pCur = pMap->pCur;
1897 jx9_value *pVal;
1898 if( pCur == 0 ){
1899 /* Cursor does not point to anything, return FALSE */
1900 jx9_result_bool(pCtx, 0);
1901 return JX9_OK;
1902 }
1903 if( iDirection != 0 ){
1904 if( iDirection > 0 ){
1905 /* Point to the next entry */
1906 pMap->pCur = pCur->pPrev; /* Reverse link */
1907 pCur = pMap->pCur;
1908 }else{
1909 /* Point to the previous entry */
1910 pMap->pCur = pCur->pNext; /* Reverse link */
1911 pCur = pMap->pCur;
1912 }
1913 if( pCur == 0 ){
1914 /* End of input reached, return FALSE */
1915 jx9_result_bool(pCtx, 0);
1916 return JX9_OK;
1917 }
1918 }
1919 /* Point to the desired element */
1920 pVal = HashmapExtractNodeValue(pCur);
1921 if( pVal ){
1922 jx9_result_value(pCtx, pVal);
1923 }else{
1924 jx9_result_bool(pCtx, 0);
1925 }
1926 return JX9_OK;
1927}
1928/*
1929 * value current(array $array)
1930 * Return the current element in an array.
1931 * Parameter
1932 * $input: The input array.
1933 * Return
1934 * The current() function simply returns the value of the array element that's currently
1935 * being pointed to by the internal pointer. It does not move the pointer in any way.
1936 * If the internal pointer points beyond the end of the elements list or the array
1937 * is empty, current() returns FALSE.
1938 */
1939static int jx9_hashmap_current(jx9_context *pCtx, int nArg, jx9_value **apArg)
1940{
1941 if( nArg < 1 ){
1942 /* Missing arguments, return FALSE */
1943 jx9_result_bool(pCtx, 0);
1944 return JX9_OK;
1945 }
1946 /* Make sure we are dealing with a valid hashmap */
1947 if( !jx9_value_is_json_array(apArg[0]) ){
1948 /* Invalid argument, return FALSE */
1949 jx9_result_bool(pCtx, 0);
1950 return JX9_OK;
1951 }
1952 HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 0);
1953 return JX9_OK;
1954}
1955/*
1956 * value next(array $input)
1957 * Advance the internal array pointer of an array.
1958 * Parameter
1959 * $input: The input array.
1960 * Return
1961 * next() behaves like current(), with one difference. It advances the internal array
1962 * pointer one place forward before returning the element value. That means it returns
1963 * the next array value and advances the internal array pointer by one.
1964 */
1965static int jx9_hashmap_next(jx9_context *pCtx, int nArg, jx9_value **apArg)
1966{
1967 if( nArg < 1 ){
1968 /* Missing arguments, return FALSE */
1969 jx9_result_bool(pCtx, 0);
1970 return JX9_OK;
1971 }
1972 /* Make sure we are dealing with a valid hashmap */
1973 if( !jx9_value_is_json_array(apArg[0]) ){
1974 /* Invalid argument, return FALSE */
1975 jx9_result_bool(pCtx, 0);
1976 return JX9_OK;
1977 }
1978 HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 1);
1979 return JX9_OK;
1980}
1981/*
1982 * value prev(array $input)
1983 * Rewind the internal array pointer.
1984 * Parameter
1985 * $input: The input array.
1986 * Return
1987 * Returns the array value in the previous place that's pointed
1988 * to by the internal array pointer, or FALSE if there are no more
1989 * elements.
1990 */
1991static int jx9_hashmap_prev(jx9_context *pCtx, int nArg, jx9_value **apArg)
1992{
1993 if( nArg < 1 ){
1994 /* Missing arguments, return FALSE */
1995 jx9_result_bool(pCtx, 0);
1996 return JX9_OK;
1997 }
1998 /* Make sure we are dealing with a valid hashmap */
1999 if( !jx9_value_is_json_array(apArg[0]) ){
2000 /* Invalid argument, return FALSE */
2001 jx9_result_bool(pCtx, 0);
2002 return JX9_OK;
2003 }
2004 HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, -1);
2005 return JX9_OK;
2006}
2007/*
2008 * value end(array $input)
2009 * Set the internal pointer of an array to its last element.
2010 * Parameter
2011 * $input: The input array.
2012 * Return
2013 * Returns the value of the last element or FALSE for empty array.
2014 */
2015static int jx9_hashmap_end(jx9_context *pCtx, int nArg, jx9_value **apArg)
2016{
2017 jx9_hashmap *pMap;
2018 if( nArg < 1 ){
2019 /* Missing arguments, return FALSE */
2020 jx9_result_bool(pCtx, 0);
2021 return JX9_OK;
2022 }
2023 /* Make sure we are dealing with a valid hashmap */
2024 if( !jx9_value_is_json_array(apArg[0]) ){
2025 /* Invalid argument, return FALSE */
2026 jx9_result_bool(pCtx, 0);
2027 return JX9_OK;
2028 }
2029 /* Point to the internal representation of the input hashmap */
2030 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2031 /* Point to the last node */
2032 pMap->pCur = pMap->pLast;
2033 /* Return the last node value */
2034 HashmapCurrentValue(&(*pCtx), pMap, 0);
2035 return JX9_OK;
2036}
2037/*
2038 * value reset(array $array )
2039 * Set the internal pointer of an array to its first element.
2040 * Parameter
2041 * $input: The input array.
2042 * Return
2043 * Returns the value of the first array element, or FALSE if the array is empty.
2044 */
2045static int jx9_hashmap_reset(jx9_context *pCtx, int nArg, jx9_value **apArg)
2046{
2047 jx9_hashmap *pMap;
2048 if( nArg < 1 ){
2049 /* Missing arguments, return FALSE */
2050 jx9_result_bool(pCtx, 0);
2051 return JX9_OK;
2052 }
2053 /* Make sure we are dealing with a valid hashmap */
2054 if( !jx9_value_is_json_array(apArg[0]) ){
2055 /* Invalid argument, return FALSE */
2056 jx9_result_bool(pCtx, 0);
2057 return JX9_OK;
2058 }
2059 /* Point to the internal representation of the input hashmap */
2060 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2061 /* Point to the first node */
2062 pMap->pCur = pMap->pFirst;
2063 /* Return the last node value if available */
2064 HashmapCurrentValue(&(*pCtx), pMap, 0);
2065 return JX9_OK;
2066}
2067/*
2068 * value key(array $array)
2069 * Fetch a key from an array
2070 * Parameter
2071 * $input
2072 * The input array.
2073 * Return
2074 * The key() function simply returns the key of the array element that's currently
2075 * being pointed to by the internal pointer. It does not move the pointer in any way.
2076 * If the internal pointer points beyond the end of the elements list or the array
2077 * is empty, key() returns NULL.
2078 */
2079static int jx9_hashmap_simple_key(jx9_context *pCtx, int nArg, jx9_value **apArg)
2080{
2081 jx9_hashmap_node *pCur;
2082 jx9_hashmap *pMap;
2083 if( nArg < 1 ){
2084 /* Missing arguments, return NULL */
2085 jx9_result_null(pCtx);
2086 return JX9_OK;
2087 }
2088 /* Make sure we are dealing with a valid hashmap */
2089 if( !jx9_value_is_json_array(apArg[0]) ){
2090 /* Invalid argument, return NULL */
2091 jx9_result_null(pCtx);
2092 return JX9_OK;
2093 }
2094 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2095 pCur = pMap->pCur;
2096 if( pCur == 0 ){
2097 /* Cursor does not point to anything, return NULL */
2098 jx9_result_null(pCtx);
2099 return JX9_OK;
2100 }
2101 if( pCur->iType == HASHMAP_INT_NODE){
2102 /* Key is integer */
2103 jx9_result_int64(pCtx, pCur->xKey.iKey);
2104 }else{
2105 /* Key is blob */
2106 jx9_result_string(pCtx,
2107 (const char *)SyBlobData(&pCur->xKey.sKey), (int)SyBlobLength(&pCur->xKey.sKey));
2108 }
2109 return JX9_OK;
2110}
2111/*
2112 * array each(array $input)
2113 * Return the current key and value pair from an array and advance the array cursor.
2114 * Parameter
2115 * $input
2116 * The input array.
2117 * Return
2118 * Returns the current key and value pair from the array array. This pair is returned
2119 * in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key
2120 * contain the key name of the array element, and 1 and value contain the data.
2121 * If the internal pointer for the array points past the end of the array contents
2122 * each() returns FALSE.
2123 */
2124static int jx9_hashmap_each(jx9_context *pCtx, int nArg, jx9_value **apArg)
2125{
2126 jx9_hashmap_node *pCur;
2127 jx9_hashmap *pMap;
2128 jx9_value *pArray;
2129 jx9_value *pVal;
2130 jx9_value sKey;
2131 if( nArg < 1 ){
2132 /* Missing arguments, return FALSE */
2133 jx9_result_bool(pCtx, 0);
2134 return JX9_OK;
2135 }
2136 /* Make sure we are dealing with a valid hashmap */
2137 if( !jx9_value_is_json_array(apArg[0]) ){
2138 /* Invalid argument, return FALSE */
2139 jx9_result_bool(pCtx, 0);
2140 return JX9_OK;
2141 }
2142 /* Point to the internal representation that describe the input hashmap */
2143 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2144 if( pMap->pCur == 0 ){
2145 /* Cursor does not point to anything, return FALSE */
2146 jx9_result_bool(pCtx, 0);
2147 return JX9_OK;
2148 }
2149 pCur = pMap->pCur;
2150 /* Create a new array */
2151 pArray = jx9_context_new_array(pCtx);
2152 if( pArray == 0 ){
2153 jx9_result_bool(pCtx, 0);
2154 return JX9_OK;
2155 }
2156 pVal = HashmapExtractNodeValue(pCur);
2157 /* Insert the current value */
2158 jx9_array_add_strkey_elem(pArray, "1", pVal);
2159 jx9_array_add_strkey_elem(pArray, "value", pVal);
2160 /* Make the key */
2161 if( pCur->iType == HASHMAP_INT_NODE ){
2162 jx9MemObjInitFromInt(pMap->pVm, &sKey, pCur->xKey.iKey);
2163 }else{
2164 jx9MemObjInitFromString(pMap->pVm, &sKey, 0);
2165 jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pCur->xKey.sKey), SyBlobLength(&pCur->xKey.sKey));
2166 }
2167 /* Insert the current key */
2168 jx9_array_add_elem(pArray, 0, &sKey);
2169 jx9_array_add_strkey_elem(pArray, "key", &sKey);
2170 jx9MemObjRelease(&sKey);
2171 /* Advance the cursor */
2172 pMap->pCur = pCur->pPrev; /* Reverse link */
2173 /* Return the current entry */
2174 jx9_result_value(pCtx, pArray);
2175 return JX9_OK;
2176}
2177/*
2178 * array array_values(array $input)
2179 * Returns all the values from the input array and indexes numerically the array.
2180 * Parameters
2181 * input: The input array.
2182 * Return
2183 * An indexed array of values or NULL on failure.
2184 */
2185static int jx9_hashmap_values(jx9_context *pCtx, int nArg, jx9_value **apArg)
2186{
2187 jx9_hashmap_node *pNode;
2188 jx9_hashmap *pMap;
2189 jx9_value *pArray;
2190 jx9_value *pObj;
2191 sxu32 n;
2192 if( nArg < 1 ){
2193 /* Missing arguments, return NULL */
2194 jx9_result_null(pCtx);
2195 return JX9_OK;
2196 }
2197 /* Make sure we are dealing with a valid hashmap */
2198 if( !jx9_value_is_json_array(apArg[0]) ){
2199 /* Invalid argument, return NULL */
2200 jx9_result_null(pCtx);
2201 return JX9_OK;
2202 }
2203 /* Point to the internal representation that describe the input hashmap */
2204 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2205 /* Create a new array */
2206 pArray = jx9_context_new_array(pCtx);
2207 if( pArray == 0 ){
2208 jx9_result_null(pCtx);
2209 return JX9_OK;
2210 }
2211 /* Perform the requested operation */
2212 pNode = pMap->pFirst;
2213 for( n = 0 ; n < pMap->nEntry ; ++n ){
2214 pObj = HashmapExtractNodeValue(pNode);
2215 if( pObj ){
2216 /* perform the insertion */
2217 jx9_array_add_elem(pArray, 0/* Automatic index assign */, pObj);
2218 }
2219 /* Point to the next entry */
2220 pNode = pNode->pPrev; /* Reverse link */
2221 }
2222 /* return the new array */
2223 jx9_result_value(pCtx, pArray);
2224 return JX9_OK;
2225}
2226/*
2227 * bool array_same(array $arr1, array $arr2)
2228 * Return TRUE if the given arrays are the same instance.
2229 * This function is useful under JX9 since arrays and objects
2230 * are passed by reference.
2231 * Parameters
2232 * $arr1
2233 * First array
2234 * $arr2
2235 * Second array
2236 * Return
2237 * TRUE if the arrays are the same instance. FALSE otherwise.
2238 * Note
2239 * This function is a symisc eXtension.
2240 */
2241static int jx9_hashmap_same(jx9_context *pCtx, int nArg, jx9_value **apArg)
2242{
2243 jx9_hashmap *p1, *p2;
2244 int rc;
2245 if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){
2246 /* Missing or invalid arguments, return FALSE*/
2247 jx9_result_bool(pCtx, 0);
2248 return JX9_OK;
2249 }
2250 /* Point to the hashmaps */
2251 p1 = (jx9_hashmap *)apArg[0]->x.pOther;
2252 p2 = (jx9_hashmap *)apArg[1]->x.pOther;
2253 rc = (p1 == p2);
2254 /* Same instance? */
2255 jx9_result_bool(pCtx, rc);
2256 return JX9_OK;
2257}
2258/*
2259 * array array_merge(array $array1, ...)
2260 * Merge one or more arrays.
2261 * Parameters
2262 * $array1
2263 * Initial array to merge.
2264 * ...
2265 * More array to merge.
2266 * Return
2267 * The resulting array.
2268 */
2269static int jx9_hashmap_merge(jx9_context *pCtx, int nArg, jx9_value **apArg)
2270{
2271 jx9_hashmap *pMap, *pSrc;
2272 jx9_value *pArray;
2273 int i;
2274 if( nArg < 1 ){
2275 /* Missing arguments, return NULL */
2276 jx9_result_null(pCtx);
2277 return JX9_OK;
2278 }
2279 /* Create a new array */
2280 pArray = jx9_context_new_array(pCtx);
2281 if( pArray == 0 ){
2282 jx9_result_null(pCtx);
2283 return JX9_OK;
2284 }
2285 /* Point to the internal representation of the hashmap */
2286 pMap = (jx9_hashmap *)pArray->x.pOther;
2287 /* Start merging */
2288 for( i = 0 ; i < nArg ; i++ ){
2289 /* Make sure we are dealing with a valid hashmap */
2290 if( !jx9_value_is_json_array(apArg[i]) ){
2291 /* Insert scalar value */
2292 jx9_array_add_elem(pArray, 0, apArg[i]);
2293 }else{
2294 pSrc = (jx9_hashmap *)apArg[i]->x.pOther;
2295 /* Merge the two hashmaps */
2296 HashmapMerge(pSrc, pMap);
2297 }
2298 }
2299 /* Return the freshly created array */
2300 jx9_result_value(pCtx, pArray);
2301 return JX9_OK;
2302}
2303/*
2304 * bool in_array(value $needle, array $haystack[, bool $strict = FALSE ])
2305 * Checks if a value exists in an array.
2306 * Parameters
2307 * $needle
2308 * The searched value.
2309 * Note:
2310 * If needle is a string, the comparison is done in a case-sensitive manner.
2311 * $haystack
2312 * The target array.
2313 * $strict
2314 * If the third parameter strict is set to TRUE then the in_array() function
2315 * will also check the types of the needle in the haystack.
2316 */
2317static int jx9_hashmap_in_array(jx9_context *pCtx, int nArg, jx9_value **apArg)
2318{
2319 jx9_value *pNeedle;
2320 int bStrict;
2321 int rc;
2322 if( nArg < 2 ){
2323 /* Missing argument, return FALSE */
2324 jx9_result_bool(pCtx, 0);
2325 return JX9_OK;
2326 }
2327 pNeedle = apArg[0];
2328 bStrict = 0;
2329 if( nArg > 2 ){
2330 bStrict = jx9_value_to_bool(apArg[2]);
2331 }
2332 if( !jx9_value_is_json_array(apArg[1]) ){
2333 /* haystack must be an array, perform a standard comparison */
2334 rc = jx9_value_compare(pNeedle, apArg[1], bStrict);
2335 /* Set the comparison result */
2336 jx9_result_bool(pCtx, rc == 0);
2337 return JX9_OK;
2338 }
2339 /* Perform the lookup */
2340 rc = HashmapFindValue((jx9_hashmap *)apArg[1]->x.pOther, pNeedle, 0, bStrict);
2341 /* Lookup result */
2342 jx9_result_bool(pCtx, rc == SXRET_OK);
2343 return JX9_OK;
2344}
2345/*
2346 * array array_copy(array $source)
2347 * Make a blind copy of the target array.
2348 * Parameters
2349 * $source
2350 * Target array
2351 * Return
2352 * Copy of the target array on success. NULL otherwise.
2353 * Note
2354 * This function is a symisc eXtension.
2355 */
2356static int jx9_hashmap_copy(jx9_context *pCtx, int nArg, jx9_value **apArg)
2357{
2358 jx9_hashmap *pMap;
2359 jx9_value *pArray;
2360 if( nArg < 1 ){
2361 /* Missing arguments, return NULL */
2362 jx9_result_null(pCtx);
2363 return JX9_OK;
2364 }
2365 /* Create a new array */
2366 pArray = jx9_context_new_array(pCtx);
2367 if( pArray == 0 ){
2368 jx9_result_null(pCtx);
2369 return JX9_OK;
2370 }
2371 /* Point to the internal representation of the hashmap */
2372 pMap = (jx9_hashmap *)pArray->x.pOther;
2373 if( jx9_value_is_json_array(apArg[0])){
2374 /* Point to the internal representation of the source */
2375 jx9_hashmap *pSrc = (jx9_hashmap *)apArg[0]->x.pOther;
2376 /* Perform the copy */
2377 jx9HashmapDup(pSrc, pMap);
2378 }else{
2379 /* Simple insertion */
2380 jx9HashmapInsert(pMap, 0/* Automatic index assign*/, apArg[0]);
2381 }
2382 /* Return the duplicated array */
2383 jx9_result_value(pCtx, pArray);
2384 return JX9_OK;
2385}
2386/*
2387 * bool array_erase(array $source)
2388 * Remove all elements from a given array.
2389 * Parameters
2390 * $source
2391 * Target array
2392 * Return
2393 * TRUE on success. FALSE otherwise.
2394 * Note
2395 * This function is a symisc eXtension.
2396 */
2397static int jx9_hashmap_erase(jx9_context *pCtx, int nArg, jx9_value **apArg)
2398{
2399 jx9_hashmap *pMap;
2400 if( nArg < 1 ){
2401 /* Missing arguments */
2402 jx9_result_bool(pCtx, 0);
2403 return JX9_OK;
2404 }
2405 /* Point to the target hashmap */
2406 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2407 /* Erase */
2408 jx9HashmapRelease(pMap, FALSE);
2409 return JX9_OK;
2410}
2411/*
2412 * array array_diff(array $array1, array $array2, ...)
2413 * Computes the difference of arrays.
2414 * Parameters
2415 * $array1
2416 * The array to compare from
2417 * $array2
2418 * An array to compare against
2419 * $...
2420 * More arrays to compare against
2421 * Return
2422 * Returns an array containing all the entries from array1 that
2423 * are not present in any of the other arrays.
2424 */
2425static int jx9_hashmap_diff(jx9_context *pCtx, int nArg, jx9_value **apArg)
2426{
2427 jx9_hashmap_node *pEntry;
2428 jx9_hashmap *pSrc, *pMap;
2429 jx9_value *pArray;
2430 jx9_value *pVal;
2431 sxi32 rc;
2432 sxu32 n;
2433 int i;
2434 if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){
2435 /* Missing arguments, return NULL */
2436 jx9_result_null(pCtx);
2437 return JX9_OK;
2438 }
2439 if( nArg == 1 ){
2440 /* Return the first array since we cannot perform a diff */
2441 jx9_result_value(pCtx, apArg[0]);
2442 return JX9_OK;
2443 }
2444 /* Create a new array */
2445 pArray = jx9_context_new_array(pCtx);
2446 if( pArray == 0 ){
2447 jx9_result_null(pCtx);
2448 return JX9_OK;
2449 }
2450 /* Point to the internal representation of the source hashmap */
2451 pSrc = (jx9_hashmap *)apArg[0]->x.pOther;
2452 /* Perform the diff */
2453 pEntry = pSrc->pFirst;
2454 n = pSrc->nEntry;
2455 for(;;){
2456 if( n < 1 ){
2457 break;
2458 }
2459 /* Extract the node value */
2460 pVal = HashmapExtractNodeValue(pEntry);
2461 if( pVal ){
2462 for( i = 1 ; i < nArg ; i++ ){
2463 if( !jx9_value_is_json_array(apArg[i])) {
2464 /* ignore */
2465 continue;
2466 }
2467 /* Point to the internal representation of the hashmap */
2468 pMap = (jx9_hashmap *)apArg[i]->x.pOther;
2469 /* Perform the lookup */
2470 rc = HashmapFindValue(pMap, pVal, 0, TRUE);
2471 if( rc == SXRET_OK ){
2472 /* Value exist */
2473 break;
2474 }
2475 }
2476 if( i >= nArg ){
2477 /* Perform the insertion */
2478 HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE);
2479 }
2480 }
2481 /* Point to the next entry */
2482 pEntry = pEntry->pPrev; /* Reverse link */
2483 n--;
2484 }
2485 /* Return the freshly created array */
2486 jx9_result_value(pCtx, pArray);
2487 return JX9_OK;
2488}
2489/*
2490 * array array_intersect(array $array1 , array $array2, ...)
2491 * Computes the intersection of arrays.
2492 * Parameters
2493 * $array1
2494 * The array to compare from
2495 * $array2
2496 * An array to compare against
2497 * $...
2498 * More arrays to compare against
2499 * Return
2500 * Returns an array containing all of the values in array1 whose values exist
2501 * in all of the parameters. .
2502 * Note that NULL is returned on failure.
2503 */
2504static int jx9_hashmap_intersect(jx9_context *pCtx, int nArg, jx9_value **apArg)
2505{
2506 jx9_hashmap_node *pEntry;
2507 jx9_hashmap *pSrc, *pMap;
2508 jx9_value *pArray;
2509 jx9_value *pVal;
2510 sxi32 rc;
2511 sxu32 n;
2512 int i;
2513 if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){
2514 /* Missing arguments, return NULL */
2515 jx9_result_null(pCtx);
2516 return JX9_OK;
2517 }
2518 if( nArg == 1 ){
2519 /* Return the first array since we cannot perform a diff */
2520 jx9_result_value(pCtx, apArg[0]);
2521 return JX9_OK;
2522 }
2523 /* Create a new array */
2524 pArray = jx9_context_new_array(pCtx);
2525 if( pArray == 0 ){
2526 jx9_result_null(pCtx);
2527 return JX9_OK;
2528 }
2529 /* Point to the internal representation of the source hashmap */
2530 pSrc = (jx9_hashmap *)apArg[0]->x.pOther;
2531 /* Perform the intersection */
2532 pEntry = pSrc->pFirst;
2533 n = pSrc->nEntry;
2534 for(;;){
2535 if( n < 1 ){
2536 break;
2537 }
2538 /* Extract the node value */
2539 pVal = HashmapExtractNodeValue(pEntry);
2540 if( pVal ){
2541 for( i = 1 ; i < nArg ; i++ ){
2542 if( !jx9_value_is_json_array(apArg[i])) {
2543 /* ignore */
2544 continue;
2545 }
2546 /* Point to the internal representation of the hashmap */
2547 pMap = (jx9_hashmap *)apArg[i]->x.pOther;
2548 /* Perform the lookup */
2549 rc = HashmapFindValue(pMap, pVal, 0, TRUE);
2550 if( rc != SXRET_OK ){
2551 /* Value does not exist */
2552 break;
2553 }
2554 }
2555 if( i >= nArg ){
2556 /* Perform the insertion */
2557 HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE);
2558 }
2559 }
2560 /* Point to the next entry */
2561 pEntry = pEntry->pPrev; /* Reverse link */
2562 n--;
2563 }
2564 /* Return the freshly created array */
2565 jx9_result_value(pCtx, pArray);
2566 return JX9_OK;
2567}
2568/*
2569 * number array_sum(array $array )
2570 * Calculate the sum of values in an array.
2571 * Parameters
2572 * $array: The input array.
2573 * Return
2574 * Returns the sum of values as an integer or float.
2575 */
2576static void DoubleSum(jx9_context *pCtx, jx9_hashmap *pMap)
2577{
2578 jx9_hashmap_node *pEntry;
2579 jx9_value *pObj;
2580 double dSum = 0;
2581 sxu32 n;
2582 pEntry = pMap->pFirst;
2583 for( n = 0 ; n < pMap->nEntry ; n++ ){
2584 pObj = HashmapExtractNodeValue(pEntry);
2585 if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){
2586 if( pObj->iFlags & MEMOBJ_REAL ){
2587 dSum += pObj->x.rVal;
2588 }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){
2589 dSum += (double)pObj->x.iVal;
2590 }else if( pObj->iFlags & MEMOBJ_STRING ){
2591 if( SyBlobLength(&pObj->sBlob) > 0 ){
2592 double dv = 0;
2593 SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0);
2594 dSum += dv;
2595 }
2596 }
2597 }
2598 /* Point to the next entry */
2599 pEntry = pEntry->pPrev; /* Reverse link */
2600 }
2601 /* Return sum */
2602 jx9_result_double(pCtx, dSum);
2603}
2604static void Int64Sum(jx9_context *pCtx, jx9_hashmap *pMap)
2605{
2606 jx9_hashmap_node *pEntry;
2607 jx9_value *pObj;
2608 sxi64 nSum = 0;
2609 sxu32 n;
2610 pEntry = pMap->pFirst;
2611 for( n = 0 ; n < pMap->nEntry ; n++ ){
2612 pObj = HashmapExtractNodeValue(pEntry);
2613 if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){
2614 if( pObj->iFlags & MEMOBJ_REAL ){
2615 nSum += (sxi64)pObj->x.rVal;
2616 }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){
2617 nSum += pObj->x.iVal;
2618 }else if( pObj->iFlags & MEMOBJ_STRING ){
2619 if( SyBlobLength(&pObj->sBlob) > 0 ){
2620 sxi64 nv = 0;
2621 SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0);
2622 nSum += nv;
2623 }
2624 }
2625 }
2626 /* Point to the next entry */
2627 pEntry = pEntry->pPrev; /* Reverse link */
2628 }
2629 /* Return sum */
2630 jx9_result_int64(pCtx, nSum);
2631}
2632/* number array_sum(array $array )
2633 * (See block-coment above)
2634 */
2635static int jx9_hashmap_sum(jx9_context *pCtx, int nArg, jx9_value **apArg)
2636{
2637 jx9_hashmap *pMap;
2638 jx9_value *pObj;
2639 if( nArg < 1 ){
2640 /* Missing arguments, return 0 */
2641 jx9_result_int(pCtx, 0);
2642 return JX9_OK;
2643 }
2644 /* Make sure we are dealing with a valid hashmap */
2645 if( !jx9_value_is_json_array(apArg[0]) ){
2646 /* Invalid argument, return 0 */
2647 jx9_result_int(pCtx, 0);
2648 return JX9_OK;
2649 }
2650 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2651 if( pMap->nEntry < 1 ){
2652 /* Nothing to compute, return 0 */
2653 jx9_result_int(pCtx, 0);
2654 return JX9_OK;
2655 }
2656 /* If the first element is of type float, then perform floating
2657 * point computaion.Otherwise switch to int64 computaion.
2658 */
2659 pObj = HashmapExtractNodeValue(pMap->pFirst);
2660 if( pObj == 0 ){
2661 jx9_result_int(pCtx, 0);
2662 return JX9_OK;
2663 }
2664 if( pObj->iFlags & MEMOBJ_REAL ){
2665 DoubleSum(pCtx, pMap);
2666 }else{
2667 Int64Sum(pCtx, pMap);
2668 }
2669 return JX9_OK;
2670}
2671/*
2672 * number array_product(array $array )
2673 * Calculate the product of values in an array.
2674 * Parameters
2675 * $array: The input array.
2676 * Return
2677 * Returns the product of values as an integer or float.
2678 */
2679static void DoubleProd(jx9_context *pCtx, jx9_hashmap *pMap)
2680{
2681 jx9_hashmap_node *pEntry;
2682 jx9_value *pObj;
2683 double dProd;
2684 sxu32 n;
2685 pEntry = pMap->pFirst;
2686 dProd = 1;
2687 for( n = 0 ; n < pMap->nEntry ; n++ ){
2688 pObj = HashmapExtractNodeValue(pEntry);
2689 if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){
2690 if( pObj->iFlags & MEMOBJ_REAL ){
2691 dProd *= pObj->x.rVal;
2692 }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){
2693 dProd *= (double)pObj->x.iVal;
2694 }else if( pObj->iFlags & MEMOBJ_STRING ){
2695 if( SyBlobLength(&pObj->sBlob) > 0 ){
2696 double dv = 0;
2697 SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0);
2698 dProd *= dv;
2699 }
2700 }
2701 }
2702 /* Point to the next entry */
2703 pEntry = pEntry->pPrev; /* Reverse link */
2704 }
2705 /* Return product */
2706 jx9_result_double(pCtx, dProd);
2707}
2708static void Int64Prod(jx9_context *pCtx, jx9_hashmap *pMap)
2709{
2710 jx9_hashmap_node *pEntry;
2711 jx9_value *pObj;
2712 sxi64 nProd;
2713 sxu32 n;
2714 pEntry = pMap->pFirst;
2715 nProd = 1;
2716 for( n = 0 ; n < pMap->nEntry ; n++ ){
2717 pObj = HashmapExtractNodeValue(pEntry);
2718 if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){
2719 if( pObj->iFlags & MEMOBJ_REAL ){
2720 nProd *= (sxi64)pObj->x.rVal;
2721 }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){
2722 nProd *= pObj->x.iVal;
2723 }else if( pObj->iFlags & MEMOBJ_STRING ){
2724 if( SyBlobLength(&pObj->sBlob) > 0 ){
2725 sxi64 nv = 0;
2726 SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0);
2727 nProd *= nv;
2728 }
2729 }
2730 }
2731 /* Point to the next entry */
2732 pEntry = pEntry->pPrev; /* Reverse link */
2733 }
2734 /* Return product */
2735 jx9_result_int64(pCtx, nProd);
2736}
2737/* number array_product(array $array )
2738 * (See block-block comment above)
2739 */
2740static int jx9_hashmap_product(jx9_context *pCtx, int nArg, jx9_value **apArg)
2741{
2742 jx9_hashmap *pMap;
2743 jx9_value *pObj;
2744 if( nArg < 1 ){
2745 /* Missing arguments, return 0 */
2746 jx9_result_int(pCtx, 0);
2747 return JX9_OK;
2748 }
2749 /* Make sure we are dealing with a valid hashmap */
2750 if( !jx9_value_is_json_array(apArg[0]) ){
2751 /* Invalid argument, return 0 */
2752 jx9_result_int(pCtx, 0);
2753 return JX9_OK;
2754 }
2755 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2756 if( pMap->nEntry < 1 ){
2757 /* Nothing to compute, return 0 */
2758 jx9_result_int(pCtx, 0);
2759 return JX9_OK;
2760 }
2761 /* If the first element is of type float, then perform floating
2762 * point computaion.Otherwise switch to int64 computaion.
2763 */
2764 pObj = HashmapExtractNodeValue(pMap->pFirst);
2765 if( pObj == 0 ){
2766 jx9_result_int(pCtx, 0);
2767 return JX9_OK;
2768 }
2769 if( pObj->iFlags & MEMOBJ_REAL ){
2770 DoubleProd(pCtx, pMap);
2771 }else{
2772 Int64Prod(pCtx, pMap);
2773 }
2774 return JX9_OK;
2775}
2776/*
2777 * array array_map(callback $callback, array $arr1)
2778 * Applies the callback to the elements of the given arrays.
2779 * Parameters
2780 * $callback
2781 * Callback function to run for each element in each array.
2782 * $arr1
2783 * An array to run through the callback function.
2784 * Return
2785 * Returns an array containing all the elements of arr1 after applying
2786 * the callback function to each one.
2787 * NOTE:
2788 * array_map() passes only a single value to the callback.
2789 */
2790static int jx9_hashmap_map(jx9_context *pCtx, int nArg, jx9_value **apArg)
2791{
2792 jx9_value *pArray, *pValue, sKey, sResult;
2793 jx9_hashmap_node *pEntry;
2794 jx9_hashmap *pMap;
2795 sxu32 n;
2796 if( nArg < 2 || !jx9_value_is_json_array(apArg[1]) ){
2797 /* Invalid arguments, return NULL */
2798 jx9_result_null(pCtx);
2799 return JX9_OK;
2800 }
2801 /* Create a new array */
2802 pArray = jx9_context_new_array(pCtx);
2803 if( pArray == 0 ){
2804 jx9_result_null(pCtx);
2805 return JX9_OK;
2806 }
2807 /* Point to the internal representation of the input hashmap */
2808 pMap = (jx9_hashmap *)apArg[1]->x.pOther;
2809 jx9MemObjInit(pMap->pVm, &sResult);
2810 jx9MemObjInit(pMap->pVm, &sKey);
2811 /* Perform the requested operation */
2812 pEntry = pMap->pFirst;
2813 for( n = 0 ; n < pMap->nEntry ; n++ ){
2814 /* Extrcat the node value */
2815 pValue = HashmapExtractNodeValue(pEntry);
2816 if( pValue ){
2817 sxi32 rc;
2818 /* Invoke the supplied callback */
2819 rc = jx9VmCallUserFunction(pMap->pVm, apArg[0], 1, &pValue, &sResult);
2820 /* Extract the node key */
2821 jx9HashmapExtractNodeKey(pEntry, &sKey);
2822 if( rc != SXRET_OK ){
2823 /* An error occured while invoking the supplied callback [i.e: not defined] */
2824 jx9_array_add_elem(pArray, &sKey, pValue); /* Keep the same value */
2825 }else{
2826 /* Insert the callback return value */
2827 jx9_array_add_elem(pArray, &sKey, &sResult);
2828 }
2829 jx9MemObjRelease(&sKey);
2830 jx9MemObjRelease(&sResult);
2831 }
2832 /* Point to the next entry */
2833 pEntry = pEntry->pPrev; /* Reverse link */
2834 }
2835 jx9_result_value(pCtx, pArray);
2836 return JX9_OK;
2837}
2838/*
2839 * bool array_walk(array &$array, callback $funcname [, value $userdata ] )
2840 * Apply a user function to every member of an array.
2841 * Parameters
2842 * $array
2843 * The input array.
2844 * $funcname
2845 * Typically, funcname takes on two parameters.The array parameter's value being
2846 * the first, and the key/index second.
2847 * Note:
2848 * If funcname needs to be working with the actual values of the array, specify the first
2849 * parameter of funcname as a reference. Then, any changes made to those elements will
2850 * be made in the original array itself.
2851 * $userdata
2852 * If the optional userdata parameter is supplied, it will be passed as the third parameter
2853 * to the callback funcname.
2854 * Return
2855 * Returns TRUE on success or FALSE on failure.
2856 */
2857static int jx9_hashmap_walk(jx9_context *pCtx, int nArg, jx9_value **apArg)
2858{
2859 jx9_value *pValue, *pUserData, sKey;
2860 jx9_hashmap_node *pEntry;
2861 jx9_hashmap *pMap;
2862 sxi32 rc;
2863 sxu32 n;
2864 if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) ){
2865 /* Invalid/Missing arguments, return FALSE */
2866 jx9_result_bool(pCtx, 0);
2867 return JX9_OK;
2868 }
2869 pUserData = nArg > 2 ? apArg[2] : 0;
2870 /* Point to the internal representation of the input hashmap */
2871 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
2872 jx9MemObjInit(pMap->pVm, &sKey);
2873 /* Perform the desired operation */
2874 pEntry = pMap->pFirst;
2875 for( n = 0 ; n < pMap->nEntry ; n++ ){
2876 /* Extract the node value */
2877 pValue = HashmapExtractNodeValue(pEntry);
2878 if( pValue ){
2879 /* Extract the entry key */
2880 jx9HashmapExtractNodeKey(pEntry, &sKey);
2881 /* Invoke the supplied callback */
2882 rc = jx9VmCallUserFunctionAp(pMap->pVm, apArg[1], 0, pValue, &sKey, pUserData, 0);
2883 jx9MemObjRelease(&sKey);
2884 if( rc != SXRET_OK ){
2885 /* An error occured while invoking the supplied callback [i.e: not defined] */
2886 jx9_result_bool(pCtx, 0); /* return FALSE */
2887 return JX9_OK;
2888 }
2889 }
2890 /* Point to the next entry */
2891 pEntry = pEntry->pPrev; /* Reverse link */
2892 }
2893 /* All done, return TRUE */
2894 jx9_result_bool(pCtx, 1);
2895 return JX9_OK;
2896}
2897/*
2898 * Table of built-in hashmap functions.
2899 */
2900static const jx9_builtin_func aHashmapFunc[] = {
2901 {"count", jx9_hashmap_count },
2902 {"sizeof", jx9_hashmap_count },
2903 {"array_key_exists", jx9_hashmap_key_exists },
2904 {"array_pop", jx9_hashmap_pop },
2905 {"array_push", jx9_hashmap_push },
2906 {"array_shift", jx9_hashmap_shift },
2907 {"array_product", jx9_hashmap_product },
2908 {"array_sum", jx9_hashmap_sum },
2909 {"array_values", jx9_hashmap_values },
2910 {"array_same", jx9_hashmap_same },
2911 {"array_merge", jx9_hashmap_merge },
2912 {"array_diff", jx9_hashmap_diff },
2913 {"array_intersect", jx9_hashmap_intersect},
2914 {"in_array", jx9_hashmap_in_array },
2915 {"array_copy", jx9_hashmap_copy },
2916 {"array_erase", jx9_hashmap_erase },
2917 {"array_map", jx9_hashmap_map },
2918 {"array_walk", jx9_hashmap_walk },
2919 {"sort", jx9_hashmap_sort },
2920 {"rsort", jx9_hashmap_rsort },
2921 {"usort", jx9_hashmap_usort },
2922 {"current", jx9_hashmap_current },
2923 {"each", jx9_hashmap_each },
2924 {"pos", jx9_hashmap_current },
2925 {"next", jx9_hashmap_next },
2926 {"prev", jx9_hashmap_prev },
2927 {"end", jx9_hashmap_end },
2928 {"reset", jx9_hashmap_reset },
2929 {"key", jx9_hashmap_simple_key }
2930};
2931/*
2932 * Register the built-in hashmap functions defined above.
2933 */
2934JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm)
2935{
2936 sxu32 n;
2937 for( n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++ ){
2938 jx9_create_function(&(*pVm), aHashmapFunc[n].zName, aHashmapFunc[n].xFunc, 0);
2939 }
2940}
2941/*
2942 * Iterate throw hashmap entries and invoke the given callback [i.e: xWalk()] for each
2943 * retrieved entry.
2944 * Note that argument are passed to the callback by copy. That is, any modification to
2945 * the entry value in the callback body will not alter the real value.
2946 * If the callback wishes to abort processing [i.e: it's invocation] it must return
2947 * a value different from JX9_OK.
2948 * Refer to [jx9_array_walk()] for more information.
2949 */
2950JX9_PRIVATE sxi32 jx9HashmapWalk(
2951 jx9_hashmap *pMap, /* Target hashmap */
2952 int (*xWalk)(jx9_value *, jx9_value *, void *), /* Walker callback */
2953 void *pUserData /* Last argument to xWalk() */
2954 )
2955{
2956 jx9_hashmap_node *pEntry;
2957 jx9_value sKey, sValue;
2958 sxi32 rc;
2959 sxu32 n;
2960 /* Initialize walker parameter */
2961 rc = SXRET_OK;
2962 jx9MemObjInit(pMap->pVm, &sKey);
2963 jx9MemObjInit(pMap->pVm, &sValue);
2964 n = pMap->nEntry;
2965 pEntry = pMap->pFirst;
2966 /* Start the iteration process */
2967 for(;;){
2968 if( n < 1 ){
2969 break;
2970 }
2971 /* Extract a copy of the key and a copy the current value */
2972 jx9HashmapExtractNodeKey(pEntry, &sKey);
2973 jx9HashmapExtractNodeValue(pEntry, &sValue, FALSE);
2974 /* Invoke the user callback */
2975 rc = xWalk(&sKey, &sValue, pUserData);
2976 /* Release the copy of the key and the value */
2977 jx9MemObjRelease(&sKey);
2978 jx9MemObjRelease(&sValue);
2979 if( rc != JX9_OK ){
2980 /* Callback request an operation abort */
2981 return SXERR_ABORT;
2982 }
2983 /* Point to the next entry */
2984 pEntry = pEntry->pPrev; /* Reverse link */
2985 n--;
2986 }
2987 /* All done */
2988 return SXRET_OK;
2989}
diff --git a/common/unqlite/jx9_json.c b/common/unqlite/jx9_json.c
new file mode 100644
index 0000000..d92f786
--- /dev/null
+++ b/common/unqlite/jx9_json.c
@@ -0,0 +1,730 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: json.c v1.0 FreeBSD 2012-12-16 00:28 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* This file deals with JSON serialization, decoding and stuff like that. */
18/*
19 * Section:
20 * JSON encoding/decoding routines.
21 * Authors:
22 * Symisc Systems, devel@symisc.net.
23 * Copyright (C) Symisc Systems, http://jx9.symisc.net
24 * Status:
25 * Devel.
26 */
27/* Forward reference */
28static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData);
29static int VmJsonObjectEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData);
30/*
31 * JSON encoder state is stored in an instance
32 * of the following structure.
33 */
34typedef struct json_private_data json_private_data;
35struct json_private_data
36{
37 SyBlob *pOut; /* Output consumer buffer */
38 int isFirst; /* True if first encoded entry */
39 int iFlags; /* JSON encoding flags */
40 int nRecCount; /* Recursion count */
41};
42/*
43 * Returns the JSON representation of a value.In other word perform a JSON encoding operation.
44 * According to wikipedia
45 * JSON's basic types are:
46 * Number (double precision floating-point format in JavaScript, generally depends on implementation)
47 * String (double-quoted Unicode, with backslash escaping)
48 * Boolean (true or false)
49 * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values
50 * do not need to be of the same type)
51 * Object (an unordered collection of key:value pairs with the ':' character separating the key
52 * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should
53 * be distinct from each other)
54 * null (empty)
55 * Non-significant white space may be added freely around the "structural characters"
56 * (i.e. the brackets "[{]}", colon ":" and comma ",").
57 */
58static sxi32 VmJsonEncode(
59 jx9_value *pIn, /* Encode this value */
60 json_private_data *pData /* Context data */
61 ){
62 SyBlob *pOut = pData->pOut;
63 int nByte;
64 if( jx9_value_is_null(pIn) || jx9_value_is_resource(pIn)){
65 /* null */
66 SyBlobAppend(pOut, "null", sizeof("null")-1);
67 }else if( jx9_value_is_bool(pIn) ){
68 int iBool = jx9_value_to_bool(pIn);
69 sxu32 iLen;
70 /* true/false */
71 iLen = iBool ? sizeof("true") : sizeof("false");
72 SyBlobAppend(pOut, iBool ? "true" : "false", iLen-1);
73 }else if( jx9_value_is_numeric(pIn) && !jx9_value_is_string(pIn) ){
74 const char *zNum;
75 /* Get a string representation of the number */
76 zNum = jx9_value_to_string(pIn, &nByte);
77 SyBlobAppend(pOut,zNum,nByte);
78 }else if( jx9_value_is_string(pIn) ){
79 const char *zIn, *zEnd;
80 int c;
81 /* Encode the string */
82 zIn = jx9_value_to_string(pIn, &nByte);
83 zEnd = &zIn[nByte];
84 /* Append the double quote */
85 SyBlobAppend(pOut,"\"", sizeof(char));
86 for(;;){
87 if( zIn >= zEnd ){
88 /* No more input to process */
89 break;
90 }
91 c = zIn[0];
92 /* Advance the stream cursor */
93 zIn++;
94 if( c == '"' || c == '\\' ){
95 /* Unescape the character */
96 SyBlobAppend(pOut,"\\", sizeof(char));
97 }
98 /* Append character verbatim */
99 SyBlobAppend(pOut,(const char *)&c,sizeof(char));
100 }
101 /* Append the double quote */
102 SyBlobAppend(pOut,"\"",sizeof(char));
103 }else if( jx9_value_is_json_array(pIn) ){
104 /* Encode the array/object */
105 pData->isFirst = 1;
106 if( jx9_value_is_json_object(pIn) ){
107 /* Encode the object instance */
108 pData->isFirst = 1;
109 /* Append the curly braces */
110 SyBlobAppend(pOut, "{",sizeof(char));
111 /* Iterate throw object attribute */
112 jx9_array_walk(pIn,VmJsonObjectEncode, pData);
113 /* Append the closing curly braces */
114 SyBlobAppend(pOut, "}",sizeof(char));
115 }else{
116 /* Append the square bracket or curly braces */
117 SyBlobAppend(pOut,"[",sizeof(char));
118 /* Iterate throw array entries */
119 jx9_array_walk(pIn, VmJsonArrayEncode, pData);
120 /* Append the closing square bracket or curly braces */
121 SyBlobAppend(pOut,"]",sizeof(char));
122 }
123 }else{
124 /* Can't happen */
125 SyBlobAppend(pOut,"null",sizeof("null")-1);
126 }
127 /* All done */
128 return JX9_OK;
129}
130/*
131 * The following walker callback is invoked each time we need
132 * to encode an array to JSON.
133 */
134static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData)
135{
136 json_private_data *pJson = (json_private_data *)pUserData;
137 if( pJson->nRecCount > 31 ){
138 /* Recursion limit reached, return immediately */
139 SXUNUSED(pKey); /* cc warning */
140 return JX9_OK;
141 }
142 if( !pJson->isFirst ){
143 /* Append the colon first */
144 SyBlobAppend(pJson->pOut,",",(int)sizeof(char));
145 }
146 /* Encode the value */
147 pJson->nRecCount++;
148 VmJsonEncode(pValue, pJson);
149 pJson->nRecCount--;
150 pJson->isFirst = 0;
151 return JX9_OK;
152}
153/*
154 * The following walker callback is invoked each time we need to encode
155 * a object instance [i.e: Object in the JX9 jargon] to JSON.
156 */
157static int VmJsonObjectEncode(jx9_value *pKey,jx9_value *pValue,void *pUserData)
158{
159 json_private_data *pJson = (json_private_data *)pUserData;
160 const char *zKey;
161 int nByte;
162 if( pJson->nRecCount > 31 ){
163 /* Recursion limit reached, return immediately */
164 return JX9_OK;
165 }
166 if( !pJson->isFirst ){
167 /* Append the colon first */
168 SyBlobAppend(pJson->pOut,",",sizeof(char));
169 }
170 /* Extract a string representation of the key */
171 zKey = jx9_value_to_string(pKey, &nByte);
172 /* Append the key and the double colon */
173 if( nByte > 0 ){
174 SyBlobAppend(pJson->pOut,"\"",sizeof(char));
175 SyBlobAppend(pJson->pOut,zKey,(sxu32)nByte);
176 SyBlobAppend(pJson->pOut,"\"",sizeof(char));
177 }else{
178 /* Can't happen */
179 SyBlobAppend(pJson->pOut,"null",sizeof("null")-1);
180 }
181 SyBlobAppend(pJson->pOut,":",sizeof(char));
182 /* Encode the value */
183 pJson->nRecCount++;
184 VmJsonEncode(pValue, pJson);
185 pJson->nRecCount--;
186 pJson->isFirst = 0;
187 return JX9_OK;
188}
189/*
190 * Returns a string containing the JSON representation of value.
191 * In other words, perform the serialization of the given JSON object.
192 */
193JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut)
194{
195 json_private_data sJson;
196 /* Prepare the JSON data */
197 sJson.nRecCount = 0;
198 sJson.pOut = pOut;
199 sJson.isFirst = 1;
200 sJson.iFlags = 0;
201 /* Perform the encoding operation */
202 VmJsonEncode(pValue, &sJson);
203 /* All done */
204 return JX9_OK;
205}
206/* Possible tokens from the JSON tokenization process */
207#define JSON_TK_TRUE 0x001 /* Boolean true */
208#define JSON_TK_FALSE 0x002 /* Boolean false */
209#define JSON_TK_STR 0x004 /* String enclosed in double quotes */
210#define JSON_TK_NULL 0x008 /* null */
211#define JSON_TK_NUM 0x010 /* Numeric */
212#define JSON_TK_OCB 0x020 /* Open curly braces '{' */
213#define JSON_TK_CCB 0x040 /* Closing curly braces '}' */
214#define JSON_TK_OSB 0x080 /* Open square bracke '[' */
215#define JSON_TK_CSB 0x100 /* Closing square bracket ']' */
216#define JSON_TK_COLON 0x200 /* Single colon ':' */
217#define JSON_TK_COMMA 0x400 /* Single comma ',' */
218#define JSON_TK_ID 0x800 /* ID */
219#define JSON_TK_INVALID 0x1000 /* Unexpected token */
220/*
221 * Tokenize an entire JSON input.
222 * Get a single low-level token from the input file.
223 * Update the stream pointer so that it points to the first
224 * character beyond the extracted token.
225 */
226static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, void *pCtxData)
227{
228 int *pJsonErr = (int *)pUserData;
229 SyString *pStr;
230 int c;
231 /* Ignore leading white spaces */
232 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){
233 /* Advance the stream cursor */
234 if( pStream->zText[0] == '\n' ){
235 /* Update line counter */
236 pStream->nLine++;
237 }
238 pStream->zText++;
239 }
240 if( pStream->zText >= pStream->zEnd ){
241 /* End of input reached */
242 SXUNUSED(pCtxData); /* cc warning */
243 return SXERR_EOF;
244 }
245 /* Record token starting position and line */
246 pToken->nLine = pStream->nLine;
247 pToken->pUserData = 0;
248 pStr = &pToken->sData;
249 SyStringInitFromBuf(pStr, pStream->zText, 0);
250 if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){
251 /* The following code fragment is taken verbatim from the xPP source tree.
252 * xPP is a modern embeddable macro processor with advanced features useful for
253 * application seeking for a production quality, ready to use macro processor.
254 * xPP is a widely used library developed and maintened by Symisc Systems.
255 * You can reach the xPP home page by following this link:
256 * http://xpp.symisc.net/
257 */
258 const unsigned char *zIn;
259 /* Isolate UTF-8 or alphanumeric stream */
260 if( pStream->zText[0] < 0xc0 ){
261 pStream->zText++;
262 }
263 for(;;){
264 zIn = pStream->zText;
265 if( zIn[0] >= 0xc0 ){
266 zIn++;
267 /* UTF-8 stream */
268 while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){
269 zIn++;
270 }
271 }
272 /* Skip alphanumeric stream */
273 while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){
274 zIn++;
275 }
276 if( zIn == pStream->zText ){
277 /* Not an UTF-8 or alphanumeric stream */
278 break;
279 }
280 /* Synchronize pointers */
281 pStream->zText = zIn;
282 }
283 /* Record token length */
284 pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString);
285 /* A simple identifier */
286 pToken->nType = JSON_TK_ID;
287 if( pStr->nByte == sizeof("true") -1 && SyStrnicmp(pStr->zString, "true", sizeof("true")-1) == 0 ){
288 /* boolean true */
289 pToken->nType = JSON_TK_TRUE;
290 }else if( pStr->nByte == sizeof("false") -1 && SyStrnicmp(pStr->zString,"false", sizeof("false")-1) == 0 ){
291 /* boolean false */
292 pToken->nType = JSON_TK_FALSE;
293 }else if( pStr->nByte == sizeof("null") -1 && SyStrnicmp(pStr->zString,"null", sizeof("null")-1) == 0 ){
294 /* NULL */
295 pToken->nType = JSON_TK_NULL;
296 }
297 return SXRET_OK;
298 }
299 if( pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']'
300 || pStream->zText[0] == ':' || pStream->zText[0] == ',' ){
301 /* Single character */
302 c = pStream->zText[0];
303 /* Set token type */
304 switch(c){
305 case '[': pToken->nType = JSON_TK_OSB; break;
306 case '{': pToken->nType = JSON_TK_OCB; break;
307 case '}': pToken->nType = JSON_TK_CCB; break;
308 case ']': pToken->nType = JSON_TK_CSB; break;
309 case ':': pToken->nType = JSON_TK_COLON; break;
310 case ',': pToken->nType = JSON_TK_COMMA; break;
311 default:
312 break;
313 }
314 /* Advance the stream cursor */
315 pStream->zText++;
316 }else if( pStream->zText[0] == '"') {
317 /* JSON string */
318 pStream->zText++;
319 pStr->zString++;
320 /* Delimit the string */
321 while( pStream->zText < pStream->zEnd ){
322 if( pStream->zText[0] == '"' && pStream->zText[-1] != '\\' ){
323 break;
324 }
325 if( pStream->zText[0] == '\n' ){
326 /* Update line counter */
327 pStream->nLine++;
328 }
329 pStream->zText++;
330 }
331 if( pStream->zText >= pStream->zEnd ){
332 /* Missing closing '"' */
333 pToken->nType = JSON_TK_INVALID;
334 *pJsonErr = SXERR_SYNTAX;
335 }else{
336 pToken->nType = JSON_TK_STR;
337 pStream->zText++; /* Jump the closing double quotes */
338 }
339 }else if( pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
340 /* Number */
341 pStream->zText++;
342 pToken->nType = JSON_TK_NUM;
343 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
344 pStream->zText++;
345 }
346 if( pStream->zText < pStream->zEnd ){
347 c = pStream->zText[0];
348 if( c == '.' ){
349 /* Real number */
350 pStream->zText++;
351 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
352 pStream->zText++;
353 }
354 if( pStream->zText < pStream->zEnd ){
355 c = pStream->zText[0];
356 if( c=='e' || c=='E' ){
357 pStream->zText++;
358 if( pStream->zText < pStream->zEnd ){
359 c = pStream->zText[0];
360 if( c =='+' || c=='-' ){
361 pStream->zText++;
362 }
363 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
364 pStream->zText++;
365 }
366 }
367 }
368 }
369 }else if( c=='e' || c=='E' ){
370 /* Real number */
371 pStream->zText++;
372 if( pStream->zText < pStream->zEnd ){
373 c = pStream->zText[0];
374 if( c =='+' || c=='-' ){
375 pStream->zText++;
376 }
377 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
378 pStream->zText++;
379 }
380 }
381 }
382 }
383 }else{
384 /* Unexpected token */
385 pToken->nType = JSON_TK_INVALID;
386 /* Advance the stream cursor */
387 pStream->zText++;
388 *pJsonErr = SXERR_SYNTAX;
389 /* Abort processing immediatley */
390 return SXERR_ABORT;
391 }
392 /* record token length */
393 pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString);
394 if( pToken->nType == JSON_TK_STR ){
395 pStr->nByte--;
396 }
397 /* Return to the lexer */
398 return SXRET_OK;
399}
400/*
401 * JSON decoded input consumer callback signature.
402 */
403typedef int (*ProcJSONConsumer)(jx9_context *, jx9_value *, jx9_value *, void *);
404/*
405 * JSON decoder state is kept in the following structure.
406 */
407typedef struct json_decoder json_decoder;
408struct json_decoder
409{
410 jx9_context *pCtx; /* Call context */
411 ProcJSONConsumer xConsumer; /* Consumer callback */
412 void *pUserData; /* Last argument to xConsumer() */
413 int iFlags; /* Configuration flags */
414 SyToken *pIn; /* Token stream */
415 SyToken *pEnd; /* End of the token stream */
416 int rec_count; /* Current nesting level */
417 int *pErr; /* JSON decoding error if any */
418};
419/* Forward declaration */
420static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData);
421/*
422 * Dequote [i.e: Resolve all backslash escapes ] a JSON string and store
423 * the result in the given jx9_value.
424 */
425static void VmJsonDequoteString(const SyString *pStr, jx9_value *pWorker)
426{
427 const char *zIn = pStr->zString;
428 const char *zEnd = &pStr->zString[pStr->nByte];
429 const char *zCur;
430 int c;
431 /* Mark the value as a string */
432 jx9_value_string(pWorker, "", 0); /* Empty string */
433 for(;;){
434 zCur = zIn;
435 while( zIn < zEnd && zIn[0] != '\\' ){
436 zIn++;
437 }
438 if( zIn > zCur ){
439 /* Append chunk verbatim */
440 jx9_value_string(pWorker, zCur, (int)(zIn-zCur));
441 }
442 zIn++;
443 if( zIn >= zEnd ){
444 /* End of the input reached */
445 break;
446 }
447 c = zIn[0];
448 /* Unescape the character */
449 switch(c){
450 case '"': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break;
451 case '\\': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break;
452 case 'n': jx9_value_string(pWorker, "\n", (int)sizeof(char)); break;
453 case 'r': jx9_value_string(pWorker, "\r", (int)sizeof(char)); break;
454 case 't': jx9_value_string(pWorker, "\t", (int)sizeof(char)); break;
455 case 'f': jx9_value_string(pWorker, "\f", (int)sizeof(char)); break;
456 default:
457 jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char));
458 break;
459 }
460 /* Advance the stream cursor */
461 zIn++;
462 }
463}
464/*
465 * Returns a jx9_value holding the image of a JSON string. In other word perform a JSON decoding operation.
466 * According to wikipedia
467 * JSON's basic types are:
468 * Number (double precision floating-point format in JavaScript, generally depends on implementation)
469 * String (double-quoted Unicode, with backslash escaping)
470 * Boolean (true or false)
471 * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values
472 * do not need to be of the same type)
473 * Object (an unordered collection of key:value pairs with the ':' character separating the key
474 * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should
475 * be distinct from each other)
476 * null (empty)
477 * Non-significant white space may be added freely around the "structural characters" (i.e. the brackets "[{]}", colon ":" and comma ", ").
478 */
479static sxi32 VmJsonDecode(
480 json_decoder *pDecoder, /* JSON decoder */
481 jx9_value *pArrayKey /* Key for the decoded array */
482 ){
483 jx9_value *pWorker; /* Worker variable */
484 sxi32 rc;
485 /* Check if we do not nest to much */
486 if( pDecoder->rec_count > 31 ){
487 /* Nesting limit reached, abort decoding immediately */
488 return SXERR_ABORT;
489 }
490 if( pDecoder->pIn->nType & (JSON_TK_STR|JSON_TK_ID|JSON_TK_TRUE|JSON_TK_FALSE|JSON_TK_NULL|JSON_TK_NUM) ){
491 /* Scalar value */
492 pWorker = jx9_context_new_scalar(pDecoder->pCtx);
493 if( pWorker == 0 ){
494 jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
495 /* Abort the decoding operation immediately */
496 return SXERR_ABORT;
497 }
498 /* Reflect the JSON image */
499 if( pDecoder->pIn->nType & JSON_TK_NULL ){
500 /* Nullify the value.*/
501 jx9_value_null(pWorker);
502 }else if( pDecoder->pIn->nType & (JSON_TK_TRUE|JSON_TK_FALSE) ){
503 /* Boolean value */
504 jx9_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0 );
505 }else if( pDecoder->pIn->nType & JSON_TK_NUM ){
506 SyString *pStr = &pDecoder->pIn->sData;
507 /*
508 * Numeric value.
509 * Get a string representation first then try to get a numeric
510 * value.
511 */
512 jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte);
513 /* Obtain a numeric representation */
514 jx9MemObjToNumeric(pWorker);
515 }else if( pDecoder->pIn->nType & JSON_TK_ID ){
516 SyString *pStr = &pDecoder->pIn->sData;
517 jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte);
518 }else{
519 /* Dequote the string */
520 VmJsonDequoteString(&pDecoder->pIn->sData, pWorker);
521 }
522 /* Invoke the consumer callback */
523 rc = pDecoder->xConsumer(pDecoder->pCtx, pArrayKey, pWorker, pDecoder->pUserData);
524 if( rc == SXERR_ABORT ){
525 return SXERR_ABORT;
526 }
527 /* All done, advance the stream cursor */
528 pDecoder->pIn++;
529 }else if( pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) {
530 ProcJSONConsumer xOld;
531 void *pOld;
532 /* Array representation*/
533 pDecoder->pIn++;
534 /* Create a working array */
535 pWorker = jx9_context_new_array(pDecoder->pCtx);
536 if( pWorker == 0 ){
537 jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
538 /* Abort the decoding operation immediately */
539 return SXERR_ABORT;
540 }
541 /* Save the old consumer */
542 xOld = pDecoder->xConsumer;
543 pOld = pDecoder->pUserData;
544 /* Set the new consumer */
545 pDecoder->xConsumer = VmJsonArrayDecoder;
546 pDecoder->pUserData = pWorker;
547 /* Decode the array */
548 for(;;){
549 /* Jump trailing comma. Note that the standard JX9 engine will not let you
550 * do this.
551 */
552 while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){
553 pDecoder->pIn++;
554 }
555 if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/ ){
556 if( pDecoder->pIn < pDecoder->pEnd ){
557 pDecoder->pIn++; /* Jump the trailing ']' */
558 }
559 break;
560 }
561 /* Recurse and decode the entry */
562 pDecoder->rec_count++;
563 rc = VmJsonDecode(pDecoder, 0);
564 pDecoder->rec_count--;
565 if( rc == SXERR_ABORT ){
566 /* Abort processing immediately */
567 return SXERR_ABORT;
568 }
569 /*The cursor is automatically advanced by the VmJsonDecode() function */
570 if( (pDecoder->pIn < pDecoder->pEnd) &&
571 ((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/|JSON_TK_COMMA/*','*/))==0) ){
572 /* Unexpected token, abort immediatley */
573 *pDecoder->pErr = SXERR_SYNTAX;
574 return SXERR_ABORT;
575 }
576 }
577 /* Restore the old consumer */
578 pDecoder->xConsumer = xOld;
579 pDecoder->pUserData = pOld;
580 /* Invoke the old consumer on the decoded array */
581 xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld);
582 }else if( pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) {
583 ProcJSONConsumer xOld;
584 jx9_value *pKey;
585 void *pOld;
586 /* Object representation*/
587 pDecoder->pIn++;
588 /* Create a working array */
589 pWorker = jx9_context_new_array(pDecoder->pCtx);
590 pKey = jx9_context_new_scalar(pDecoder->pCtx);
591 if( pWorker == 0 || pKey == 0){
592 jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
593 /* Abort the decoding operation immediately */
594 return SXERR_ABORT;
595 }
596 /* Save the old consumer */
597 xOld = pDecoder->xConsumer;
598 pOld = pDecoder->pUserData;
599 /* Set the new consumer */
600 pDecoder->xConsumer = VmJsonArrayDecoder;
601 pDecoder->pUserData = pWorker;
602 /* Decode the object */
603 for(;;){
604 /* Jump trailing comma. Note that the standard JX9 engine will not let you
605 * do this.
606 */
607 while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){
608 pDecoder->pIn++;
609 }
610 if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/ ){
611 if( pDecoder->pIn < pDecoder->pEnd ){
612 pDecoder->pIn++; /* Jump the trailing ']' */
613 }
614 break;
615 }
616 if( (pDecoder->pIn->nType & (JSON_TK_ID|JSON_TK_STR)) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd
617 || (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0){
618 /* Syntax error, return immediately */
619 *pDecoder->pErr = SXERR_SYNTAX;
620 return SXERR_ABORT;
621 }
622 if( pDecoder->pIn->nType & JSON_TK_ID ){
623 SyString *pStr = &pDecoder->pIn->sData;
624 jx9_value_string(pKey, pStr->zString, (int)pStr->nByte);
625 }else{
626 /* Dequote the key */
627 VmJsonDequoteString(&pDecoder->pIn->sData, pKey);
628 }
629 /* Jump the key and the colon */
630 pDecoder->pIn += 2;
631 /* Recurse and decode the value */
632 pDecoder->rec_count++;
633 rc = VmJsonDecode(pDecoder, pKey);
634 pDecoder->rec_count--;
635 if( rc == SXERR_ABORT ){
636 /* Abort processing immediately */
637 return SXERR_ABORT;
638 }
639 /* Reset the internal buffer of the key */
640 jx9_value_reset_string_cursor(pKey);
641 /*The cursor is automatically advanced by the VmJsonDecode() function */
642 }
643 /* Restore the old consumer */
644 pDecoder->xConsumer = xOld;
645 pDecoder->pUserData = pOld;
646 /* Invoke the old consumer on the decoded object*/
647 xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld);
648 /* Release the key */
649 jx9_context_release_value(pDecoder->pCtx, pKey);
650 }else{
651 /* Unexpected token */
652 return SXERR_ABORT; /* Abort immediately */
653 }
654 /* Release the worker variable */
655 jx9_context_release_value(pDecoder->pCtx, pWorker);
656 return SXRET_OK;
657}
658/*
659 * The following JSON decoder callback is invoked each time
660 * a JSON array representation [i.e: [15, "hello", FALSE] ]
661 * is being decoded.
662 */
663static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData)
664{
665 jx9_value *pArray = (jx9_value *)pUserData;
666 /* Insert the entry */
667 jx9_array_add_elem(pArray, pKey, pWorker); /* Will make it's own copy */
668 SXUNUSED(pCtx); /* cc warning */
669 /* All done */
670 return SXRET_OK;
671}
672/*
673 * Standard JSON decoder callback.
674 */
675static int VmJsonDefaultDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData)
676{
677 /* Return the value directly */
678 jx9_result_value(pCtx, pWorker); /* Will make it's own copy */
679 SXUNUSED(pKey); /* cc warning */
680 SXUNUSED(pUserData);
681 /* All done */
682 return SXRET_OK;
683}
684/*
685 * Exported JSON decoding interface
686 */
687JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte)
688{
689 jx9_vm *pVm = pCtx->pVm;
690 json_decoder sDecoder;
691 SySet sToken;
692 SyLex sLex;
693 sxi32 rc;
694 /* Tokenize the input */
695 SySetInit(&sToken, &pVm->sAllocator, sizeof(SyToken));
696 rc = SXRET_OK;
697 SyLexInit(&sLex, &sToken, VmJsonTokenize, &rc);
698 SyLexTokenizeInput(&sLex,zJSON,(sxu32)nByte, 0, 0, 0);
699 if( rc != SXRET_OK ){
700 /* Something goes wrong while tokenizing input. [i.e: Unexpected token] */
701 SyLexRelease(&sLex);
702 SySetRelease(&sToken);
703 /* return NULL */
704 jx9_result_null(pCtx);
705 return JX9_OK;
706 }
707 /* Fill the decoder */
708 sDecoder.pCtx = pCtx;
709 sDecoder.pErr = &rc;
710 sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken);
711 sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)];
712 sDecoder.iFlags = 0;
713 sDecoder.rec_count = 0;
714 /* Set a default consumer */
715 sDecoder.xConsumer = VmJsonDefaultDecoder;
716 sDecoder.pUserData = 0;
717 /* Decode the raw JSON input */
718 rc = VmJsonDecode(&sDecoder, 0);
719 if( rc == SXERR_ABORT ){
720 /*
721 * Something goes wrong while decoding JSON input.Return NULL.
722 */
723 jx9_result_null(pCtx);
724 }
725 /* Clean-up the mess left behind */
726 SyLexRelease(&sLex);
727 SySetRelease(&sToken);
728 /* All done */
729 return JX9_OK;
730}
diff --git a/common/unqlite/jx9_lex.c b/common/unqlite/jx9_lex.c
new file mode 100644
index 0000000..7799950
--- /dev/null
+++ b/common/unqlite/jx9_lex.c
@@ -0,0 +1,758 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: lex.c v1.0 FreeBSD 2012-12-09 00:19 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* This file implements a thread-safe and full reentrant lexical analyzer for the Jx9 programming language */
18/* Forward declarations */
19static sxu32 keywordCode(const char *z,int n);
20static sxi32 LexExtractNowdoc(SyStream *pStream,SyToken *pToken);
21/*
22 * Tokenize a raw jx9 input.
23 * Get a single low-level token from the input file. Update the stream pointer so that
24 * it points to the first character beyond the extracted token.
25 */
26static sxi32 jx9TokenizeInput(SyStream *pStream,SyToken *pToken,void *pUserData,void *pCtxData)
27{
28 SyString *pStr;
29 sxi32 rc;
30 /* Ignore leading white spaces */
31 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){
32 /* Advance the stream cursor */
33 if( pStream->zText[0] == '\n' ){
34 /* Update line counter */
35 pStream->nLine++;
36 }
37 pStream->zText++;
38 }
39 if( pStream->zText >= pStream->zEnd ){
40 /* End of input reached */
41 return SXERR_EOF;
42 }
43 /* Record token starting position and line */
44 pToken->nLine = pStream->nLine;
45 pToken->pUserData = 0;
46 pStr = &pToken->sData;
47 SyStringInitFromBuf(pStr, pStream->zText, 0);
48 if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){
49 /* The following code fragment is taken verbatim from the xPP source tree.
50 * xPP is a modern embeddable macro processor with advanced features useful for
51 * application seeking for a production quality, ready to use macro processor.
52 * xPP is a widely used library developed and maintened by Symisc Systems.
53 * You can reach the xPP home page by following this link:
54 * http://xpp.symisc.net/
55 */
56 const unsigned char *zIn;
57 sxu32 nKeyword;
58 /* Isolate UTF-8 or alphanumeric stream */
59 if( pStream->zText[0] < 0xc0 ){
60 pStream->zText++;
61 }
62 for(;;){
63 zIn = pStream->zText;
64 if( zIn[0] >= 0xc0 ){
65 zIn++;
66 /* UTF-8 stream */
67 while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){
68 zIn++;
69 }
70 }
71 /* Skip alphanumeric stream */
72 while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){
73 zIn++;
74 }
75 if( zIn == pStream->zText ){
76 /* Not an UTF-8 or alphanumeric stream */
77 break;
78 }
79 /* Synchronize pointers */
80 pStream->zText = zIn;
81 }
82 /* Record token length */
83 pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString);
84 nKeyword = keywordCode(pStr->zString, (int)pStr->nByte);
85 if( nKeyword != JX9_TK_ID ){
86 /* We are dealing with a keyword [i.e: if, function, CREATE, ...], save the keyword ID */
87 pToken->nType = JX9_TK_KEYWORD;
88 pToken->pUserData = SX_INT_TO_PTR(nKeyword);
89 }else{
90 /* A simple identifier */
91 pToken->nType = JX9_TK_ID;
92 }
93 }else{
94 sxi32 c;
95 /* Non-alpha stream */
96 if( pStream->zText[0] == '#' ||
97 ( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '/') ){
98 pStream->zText++;
99 /* Inline comments */
100 while( pStream->zText < pStream->zEnd && pStream->zText[0] != '\n' ){
101 pStream->zText++;
102 }
103 /* Tell the upper-layer to ignore this token */
104 return SXERR_CONTINUE;
105 }else if( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '*' ){
106 pStream->zText += 2;
107 /* Block comment */
108 while( pStream->zText < pStream->zEnd ){
109 if( pStream->zText[0] == '*' ){
110 if( &pStream->zText[1] >= pStream->zEnd || pStream->zText[1] == '/' ){
111 break;
112 }
113 }
114 if( pStream->zText[0] == '\n' ){
115 pStream->nLine++;
116 }
117 pStream->zText++;
118 }
119 pStream->zText += 2;
120 /* Tell the upper-layer to ignore this token */
121 return SXERR_CONTINUE;
122 }else if( SyisDigit(pStream->zText[0]) ){
123 pStream->zText++;
124 /* Decimal digit stream */
125 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
126 pStream->zText++;
127 }
128 /* Mark the token as integer until we encounter a real number */
129 pToken->nType = JX9_TK_INTEGER;
130 if( pStream->zText < pStream->zEnd ){
131 c = pStream->zText[0];
132 if( c == '.' ){
133 /* Real number */
134 pStream->zText++;
135 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
136 pStream->zText++;
137 }
138 if( pStream->zText < pStream->zEnd ){
139 c = pStream->zText[0];
140 if( c=='e' || c=='E' ){
141 pStream->zText++;
142 if( pStream->zText < pStream->zEnd ){
143 c = pStream->zText[0];
144 if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd &&
145 pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){
146 pStream->zText++;
147 }
148 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
149 pStream->zText++;
150 }
151 }
152 }
153 }
154 pToken->nType = JX9_TK_REAL;
155 }else if( c=='e' || c=='E' ){
156 SXUNUSED(pUserData); /* Prevent compiler warning */
157 SXUNUSED(pCtxData);
158 pStream->zText++;
159 if( pStream->zText < pStream->zEnd ){
160 c = pStream->zText[0];
161 if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd &&
162 pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){
163 pStream->zText++;
164 }
165 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){
166 pStream->zText++;
167 }
168 }
169 pToken->nType = JX9_TK_REAL;
170 }else if( c == 'x' || c == 'X' ){
171 /* Hex digit stream */
172 pStream->zText++;
173 while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisHex(pStream->zText[0]) ){
174 pStream->zText++;
175 }
176 }else if(c == 'b' || c == 'B' ){
177 /* Binary digit stream */
178 pStream->zText++;
179 while( pStream->zText < pStream->zEnd && (pStream->zText[0] == '0' || pStream->zText[0] == '1') ){
180 pStream->zText++;
181 }
182 }
183 }
184 /* Record token length */
185 pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString);
186 return SXRET_OK;
187 }
188 c = pStream->zText[0];
189 pStream->zText++; /* Advance the stream cursor */
190 /* Assume we are dealing with an operator*/
191 pToken->nType = JX9_TK_OP;
192 switch(c){
193 case '$': pToken->nType = JX9_TK_DOLLAR; break;
194 case '{': pToken->nType = JX9_TK_OCB; break;
195 case '}': pToken->nType = JX9_TK_CCB; break;
196 case '(': pToken->nType = JX9_TK_LPAREN; break;
197 case '[': pToken->nType |= JX9_TK_OSB; break; /* Bitwise operation here, since the square bracket token '['
198 * is a potential operator [i.e: subscripting] */
199 case ']': pToken->nType = JX9_TK_CSB; break;
200 case ')': {
201 SySet *pTokSet = pStream->pSet;
202 /* Assemble type cast operators [i.e: (int), (float), (bool)...] */
203 if( pTokSet->nUsed >= 2 ){
204 SyToken *pTmp;
205 /* Peek the last recongnized token */
206 pTmp = (SyToken *)SySetPeek(pTokSet);
207 if( pTmp->nType & JX9_TK_KEYWORD ){
208 sxi32 nID = SX_PTR_TO_INT(pTmp->pUserData);
209 if( (sxu32)nID & (JX9_TKWRD_INT|JX9_TKWRD_FLOAT|JX9_TKWRD_STRING|JX9_TKWRD_BOOL) ){
210 pTmp = (SyToken *)SySetAt(pTokSet, pTokSet->nUsed - 2);
211 if( pTmp->nType & JX9_TK_LPAREN ){
212 /* Merge the three tokens '(' 'TYPE' ')' into a single one */
213 const char * zTypeCast = "(int)";
214 if( nID & JX9_TKWRD_FLOAT ){
215 zTypeCast = "(float)";
216 }else if( nID & JX9_TKWRD_BOOL ){
217 zTypeCast = "(bool)";
218 }else if( nID & JX9_TKWRD_STRING ){
219 zTypeCast = "(string)";
220 }
221 /* Reflect the change */
222 pToken->nType = JX9_TK_OP;
223 SyStringInitFromBuf(&pToken->sData, zTypeCast, SyStrlen(zTypeCast));
224 /* Save the instance associated with the type cast operator */
225 pToken->pUserData = (void *)jx9ExprExtractOperator(&pToken->sData, 0);
226 /* Remove the two previous tokens */
227 pTokSet->nUsed -= 2;
228 return SXRET_OK;
229 }
230 }
231 }
232 }
233 pToken->nType = JX9_TK_RPAREN;
234 break;
235 }
236 case '\'':{
237 /* Single quoted string */
238 pStr->zString++;
239 while( pStream->zText < pStream->zEnd ){
240 if( pStream->zText[0] == '\'' ){
241 if( pStream->zText[-1] != '\\' ){
242 break;
243 }else{
244 const unsigned char *zPtr = &pStream->zText[-2];
245 sxi32 i = 1;
246 while( zPtr > pStream->zInput && zPtr[0] == '\\' ){
247 zPtr--;
248 i++;
249 }
250 if((i&1)==0){
251 break;
252 }
253 }
254 }
255 if( pStream->zText[0] == '\n' ){
256 pStream->nLine++;
257 }
258 pStream->zText++;
259 }
260 /* Record token length and type */
261 pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString);
262 pToken->nType = JX9_TK_SSTR;
263 /* Jump the trailing single quote */
264 pStream->zText++;
265 return SXRET_OK;
266 }
267 case '"':{
268 sxi32 iNest;
269 /* Double quoted string */
270 pStr->zString++;
271 while( pStream->zText < pStream->zEnd ){
272 if( pStream->zText[0] == '{' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '$'){
273 iNest = 1;
274 pStream->zText++;
275 /* TICKET 1433-40: Hnadle braces'{}' in double quoted string where everything is allowed */
276 while(pStream->zText < pStream->zEnd ){
277 if( pStream->zText[0] == '{' ){
278 iNest++;
279 }else if (pStream->zText[0] == '}' ){
280 iNest--;
281 if( iNest <= 0 ){
282 pStream->zText++;
283 break;
284 }
285 }else if( pStream->zText[0] == '\n' ){
286 pStream->nLine++;
287 }
288 pStream->zText++;
289 }
290 if( pStream->zText >= pStream->zEnd ){
291 break;
292 }
293 }
294 if( pStream->zText[0] == '"' ){
295 if( pStream->zText[-1] != '\\' ){
296 break;
297 }else{
298 const unsigned char *zPtr = &pStream->zText[-2];
299 sxi32 i = 1;
300 while( zPtr > pStream->zInput && zPtr[0] == '\\' ){
301 zPtr--;
302 i++;
303 }
304 if((i&1)==0){
305 break;
306 }
307 }
308 }
309 if( pStream->zText[0] == '\n' ){
310 pStream->nLine++;
311 }
312 pStream->zText++;
313 }
314 /* Record token length and type */
315 pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString);
316 pToken->nType = JX9_TK_DSTR;
317 /* Jump the trailing quote */
318 pStream->zText++;
319 return SXRET_OK;
320 }
321 case ':':
322 pToken->nType = JX9_TK_COLON; /* Single colon */
323 break;
324 case ',': pToken->nType |= JX9_TK_COMMA; break; /* Comma is also an operator */
325 case ';': pToken->nType = JX9_TK_SEMI; break;
326 /* Handle combined operators [i.e: +=, ===, !=== ...] */
327 case '=':
328 pToken->nType |= JX9_TK_EQUAL;
329 if( pStream->zText < pStream->zEnd ){
330 if( pStream->zText[0] == '=' ){
331 pToken->nType &= ~JX9_TK_EQUAL;
332 /* Current operator: == */
333 pStream->zText++;
334 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
335 /* Current operator: === */
336 pStream->zText++;
337 }
338 }
339 }
340 break;
341 case '!':
342 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
343 /* Current operator: != */
344 pStream->zText++;
345 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
346 /* Current operator: !== */
347 pStream->zText++;
348 }
349 }
350 break;
351 case '&':
352 pToken->nType |= JX9_TK_AMPER;
353 if( pStream->zText < pStream->zEnd ){
354 if( pStream->zText[0] == '&' ){
355 pToken->nType &= ~JX9_TK_AMPER;
356 /* Current operator: && */
357 pStream->zText++;
358 }else if( pStream->zText[0] == '=' ){
359 pToken->nType &= ~JX9_TK_AMPER;
360 /* Current operator: &= */
361 pStream->zText++;
362 }
363 }
364 case '.':
365 if( pStream->zText < pStream->zEnd && (pStream->zText[0] == '.' || pStream->zText[0] == '=') ){
366 /* Concatenation operator: '..' or '.=' */
367 pStream->zText++;
368 }
369 break;
370 case '|':
371 if( pStream->zText < pStream->zEnd ){
372 if( pStream->zText[0] == '|' ){
373 /* Current operator: || */
374 pStream->zText++;
375 }else if( pStream->zText[0] == '=' ){
376 /* Current operator: |= */
377 pStream->zText++;
378 }
379 }
380 break;
381 case '+':
382 if( pStream->zText < pStream->zEnd ){
383 if( pStream->zText[0] == '+' ){
384 /* Current operator: ++ */
385 pStream->zText++;
386 }else if( pStream->zText[0] == '=' ){
387 /* Current operator: += */
388 pStream->zText++;
389 }
390 }
391 break;
392 case '-':
393 if( pStream->zText < pStream->zEnd ){
394 if( pStream->zText[0] == '-' ){
395 /* Current operator: -- */
396 pStream->zText++;
397 }else if( pStream->zText[0] == '=' ){
398 /* Current operator: -= */
399 pStream->zText++;
400 }else if( pStream->zText[0] == '>' ){
401 /* Current operator: -> */
402 pStream->zText++;
403 }
404 }
405 break;
406 case '*':
407 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
408 /* Current operator: *= */
409 pStream->zText++;
410 }
411 break;
412 case '/':
413 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
414 /* Current operator: /= */
415 pStream->zText++;
416 }
417 break;
418 case '%':
419 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
420 /* Current operator: %= */
421 pStream->zText++;
422 }
423 break;
424 case '^':
425 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
426 /* Current operator: ^= */
427 pStream->zText++;
428 }
429 break;
430 case '<':
431 if( pStream->zText < pStream->zEnd ){
432 if( pStream->zText[0] == '<' ){
433 /* Current operator: << */
434 pStream->zText++;
435 if( pStream->zText < pStream->zEnd ){
436 if( pStream->zText[0] == '=' ){
437 /* Current operator: <<= */
438 pStream->zText++;
439 }else if( pStream->zText[0] == '<' ){
440 /* Current Token: <<< */
441 pStream->zText++;
442 /* This may be the beginning of a Heredoc/Nowdoc string, try to delimit it */
443 rc = LexExtractNowdoc(&(*pStream), &(*pToken));
444 if( rc == SXRET_OK ){
445 /* Here/Now doc successfuly extracted */
446 return SXRET_OK;
447 }
448 }
449 }
450 }else if( pStream->zText[0] == '>' ){
451 /* Current operator: <> */
452 pStream->zText++;
453 }else if( pStream->zText[0] == '=' ){
454 /* Current operator: <= */
455 pStream->zText++;
456 }
457 }
458 break;
459 case '>':
460 if( pStream->zText < pStream->zEnd ){
461 if( pStream->zText[0] == '>' ){
462 /* Current operator: >> */
463 pStream->zText++;
464 if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){
465 /* Current operator: >>= */
466 pStream->zText++;
467 }
468 }else if( pStream->zText[0] == '=' ){
469 /* Current operator: >= */
470 pStream->zText++;
471 }
472 }
473 break;
474 default:
475 break;
476 }
477 if( pStr->nByte <= 0 ){
478 /* Record token length */
479 pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString);
480 }
481 if( pToken->nType & JX9_TK_OP ){
482 const jx9_expr_op *pOp;
483 /* Check if the extracted token is an operator */
484 pOp = jx9ExprExtractOperator(pStr, (SyToken *)SySetPeek(pStream->pSet));
485 if( pOp == 0 ){
486 /* Not an operator */
487 pToken->nType &= ~JX9_TK_OP;
488 if( pToken->nType <= 0 ){
489 pToken->nType = JX9_TK_OTHER;
490 }
491 }else{
492 /* Save the instance associated with this operator for later processing */
493 pToken->pUserData = (void *)pOp;
494 }
495 }
496 }
497 /* Tell the upper-layer to save the extracted token for later processing */
498 return SXRET_OK;
499}
500/***** This file contains automatically generated code ******
501**
502** The code in this file has been automatically generated by
503**
504** $Header: /sqlite/sqlite/tool/mkkeywordhash.c,v 1.38 2011/12/21 01:00:46 <chm@symisc.net> $
505**
506** The code in this file implements a function that determines whether
507** or not a given identifier is really a JX9 keyword. The same thing
508** might be implemented more directly using a hand-written hash table.
509** But by using this automatically generated code, the size of the code
510** is substantially reduced. This is important for embedded applications
511** on platforms with limited memory.
512*/
513/* Hash score: 35 */
514static sxu32 keywordCode(const char *z, int n)
515{
516 /* zText[] encodes 188 bytes of keywords in 128 bytes */
517 /* printegereturnconstaticaselseifloatincludefaultDIEXITcontinue */
518 /* diewhileASPRINTbooleanbreakforeachfunctionimportstringswitch */
519 /* uplink */
520 static const char zText[127] = {
521 'p','r','i','n','t','e','g','e','r','e','t','u','r','n','c','o','n','s',
522 't','a','t','i','c','a','s','e','l','s','e','i','f','l','o','a','t','i',
523 'n','c','l','u','d','e','f','a','u','l','t','D','I','E','X','I','T','c',
524 'o','n','t','i','n','u','e','d','i','e','w','h','i','l','e','A','S','P',
525 'R','I','N','T','b','o','o','l','e','a','n','b','r','e','a','k','f','o',
526 'r','e','a','c','h','f','u','n','c','t','i','o','n','i','m','p','o','r',
527 't','s','t','r','i','n','g','s','w','i','t','c','h','u','p','l','i','n',
528 'k',
529 };
530 static const unsigned char aHash[59] = {
531 0, 0, 0, 0, 15, 0, 30, 0, 0, 2, 19, 18, 0,
532 0, 10, 3, 12, 0, 28, 29, 23, 0, 13, 22, 0, 0,
533 14, 24, 25, 31, 11, 0, 0, 0, 0, 1, 5, 0, 0,
534 20, 0, 27, 9, 0, 0, 0, 8, 0, 0, 26, 6, 0,
535 0, 17, 0, 0, 0, 0, 0,
536 };
537 static const unsigned char aNext[31] = {
538 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0,
539 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 21, 7,
540 0, 0, 0, 0, 0,
541 };
542 static const unsigned char aLen[31] = {
543 5, 7, 3, 6, 5, 6, 4, 2, 6, 4, 2, 5, 7,
544 7, 3, 4, 8, 3, 5, 2, 5, 4, 7, 5, 3, 7,
545 8, 6, 6, 6, 6,
546 };
547 static const sxu16 aOffset[31] = {
548 0, 2, 2, 8, 14, 17, 22, 23, 25, 25, 29, 30, 35,
549 40, 47, 49, 53, 61, 64, 69, 71, 76, 76, 83, 88, 88,
550 95, 103, 109, 115, 121,
551 };
552 static const sxu32 aCode[31] = {
553 JX9_TKWRD_PRINT, JX9_TKWRD_INT, JX9_TKWRD_INT, JX9_TKWRD_RETURN, JX9_TKWRD_CONST,
554 JX9_TKWRD_STATIC, JX9_TKWRD_CASE, JX9_TKWRD_AS, JX9_TKWRD_ELIF, JX9_TKWRD_ELSE,
555 JX9_TKWRD_IF, JX9_TKWRD_FLOAT, JX9_TKWRD_INCLUDE, JX9_TKWRD_DEFAULT, JX9_TKWRD_DIE,
556 JX9_TKWRD_EXIT, JX9_TKWRD_CONTINUE, JX9_TKWRD_DIE, JX9_TKWRD_WHILE, JX9_TKWRD_AS,
557 JX9_TKWRD_PRINT, JX9_TKWRD_BOOL, JX9_TKWRD_BOOL, JX9_TKWRD_BREAK, JX9_TKWRD_FOR,
558 JX9_TKWRD_FOREACH, JX9_TKWRD_FUNCTION, JX9_TKWRD_IMPORT, JX9_TKWRD_STRING, JX9_TKWRD_SWITCH,
559 JX9_TKWRD_UPLINK,
560 };
561 int h, i;
562 if( n<2 ) return JX9_TK_ID;
563 h = (((int)z[0]*4) ^ ((int)z[n-1]*3) ^ n) % 59;
564 for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){
565 if( (int)aLen[i]==n && SyMemcmp(&zText[aOffset[i]],z,n)==0 ){
566 /* JX9_TKWRD_PRINT */
567 /* JX9_TKWRD_INT */
568 /* JX9_TKWRD_INT */
569 /* JX9_TKWRD_RETURN */
570 /* JX9_TKWRD_CONST */
571 /* JX9_TKWRD_STATIC */
572 /* JX9_TKWRD_CASE */
573 /* JX9_TKWRD_AS */
574 /* JX9_TKWRD_ELIF */
575 /* JX9_TKWRD_ELSE */
576 /* JX9_TKWRD_IF */
577 /* JX9_TKWRD_FLOAT */
578 /* JX9_TKWRD_INCLUDE */
579 /* JX9_TKWRD_DEFAULT */
580 /* JX9_TKWRD_DIE */
581 /* JX9_TKWRD_EXIT */
582 /* JX9_TKWRD_CONTINUE */
583 /* JX9_TKWRD_DIE */
584 /* JX9_TKWRD_WHILE */
585 /* JX9_TKWRD_AS */
586 /* JX9_TKWRD_PRINT */
587 /* JX9_TKWRD_BOOL */
588 /* JX9_TKWRD_BOOL */
589 /* JX9_TKWRD_BREAK */
590 /* JX9_TKWRD_FOR */
591 /* JX9_TKWRD_FOREACH */
592 /* JX9_TKWRD_FUNCTION */
593 /* JX9_TKWRD_IMPORT */
594 /* JX9_TKWRD_STRING */
595 /* JX9_TKWRD_SWITCH */
596 /* JX9_TKWRD_UPLINK */
597 return aCode[i];
598 }
599 }
600 return JX9_TK_ID;
601}
602/*
603 * Extract a heredoc/nowdoc text from a raw JX9 input.
604 * According to the JX9 language reference manual:
605 * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier
606 * is provided, then a newline. The string itself follows, and then the same identifier again
607 * to close the quotation.
608 * The closing identifier must begin in the first column of the line. Also, the identifier must
609 * follow the same naming rules as any other label in JX9: it must contain only alphanumeric
610 * characters and underscores, and must start with a non-digit character or underscore.
611 * Heredoc text behaves just like a double-quoted string, without the double quotes.
612 * This means that quotes in a heredoc do not need to be escaped, but the escape codes listed
613 * above can still be used. Variables are expanded, but the same care must be taken when expressing
614 * complex variables inside a heredoc as with strings.
615 * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings.
616 * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc.
617 * The construct is ideal for embedding JX9 code or other large blocks of text without the need
618 * for escaping. It shares some features in common with the SGML <![CDATA[ ]]> construct, in that
619 * it declares a block of text which is not for parsing.
620 * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier which follows
621 * is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc identifiers also apply to nowdoc
622 * identifiers, especially those regarding the appearance of the closing identifier.
623 */
624static sxi32 LexExtractNowdoc(SyStream *pStream, SyToken *pToken)
625{
626 const unsigned char *zIn = pStream->zText;
627 const unsigned char *zEnd = pStream->zEnd;
628 const unsigned char *zPtr;
629 SyString sDelim;
630 SyString sStr;
631 /* Jump leading white spaces */
632 while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){
633 zIn++;
634 }
635 if( zIn >= zEnd ){
636 /* A simple symbol, return immediately */
637 return SXERR_CONTINUE;
638 }
639 if( zIn[0] == '\'' || zIn[0] == '"' ){
640 zIn++;
641 }
642 if( zIn[0] < 0xc0 && !SyisAlphaNum(zIn[0]) && zIn[0] != '_' ){
643 /* Invalid delimiter, return immediately */
644 return SXERR_CONTINUE;
645 }
646 /* Isolate the identifier */
647 sDelim.zString = (const char *)zIn;
648 for(;;){
649 zPtr = zIn;
650 /* Skip alphanumeric stream */
651 while( zPtr < zEnd && zPtr[0] < 0xc0 && (SyisAlphaNum(zPtr[0]) || zPtr[0] == '_') ){
652 zPtr++;
653 }
654 if( zPtr < zEnd && zPtr[0] >= 0xc0 ){
655 zPtr++;
656 /* UTF-8 stream */
657 while( zPtr < zEnd && ((zPtr[0] & 0xc0) == 0x80) ){
658 zPtr++;
659 }
660 }
661 if( zPtr == zIn ){
662 /* Not an UTF-8 or alphanumeric stream */
663 break;
664 }
665 /* Synchronize pointers */
666 zIn = zPtr;
667 }
668 /* Get the identifier length */
669 sDelim.nByte = (sxu32)((const char *)zIn-sDelim.zString);
670 if( zIn[0] == '"' || zIn[0] == '\'' ){
671 /* Jump the trailing single quote */
672 zIn++;
673 }
674 /* Jump trailing white spaces */
675 while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){
676 zIn++;
677 }
678 if( sDelim.nByte <= 0 || zIn >= zEnd || zIn[0] != '\n' ){
679 /* Invalid syntax */
680 return SXERR_CONTINUE;
681 }
682 pStream->nLine++; /* Increment line counter */
683 zIn++;
684 /* Isolate the delimited string */
685 sStr.zString = (const char *)zIn;
686 /* Go and found the closing delimiter */
687 for(;;){
688 /* Synchronize with the next line */
689 while( zIn < zEnd && zIn[0] != '\n' ){
690 zIn++;
691 }
692 if( zIn >= zEnd ){
693 /* End of the input reached, break immediately */
694 pStream->zText = pStream->zEnd;
695 break;
696 }
697 pStream->nLine++; /* Increment line counter */
698 zIn++;
699 if( (sxu32)(zEnd - zIn) >= sDelim.nByte && SyMemcmp((const void *)sDelim.zString, (const void *)zIn, sDelim.nByte) == 0 ){
700 zPtr = &zIn[sDelim.nByte];
701 while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){
702 zPtr++;
703 }
704 if( zPtr >= zEnd ){
705 /* End of input */
706 pStream->zText = zPtr;
707 break;
708 }
709 if( zPtr[0] == ';' ){
710 const unsigned char *zCur = zPtr;
711 zPtr++;
712 while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){
713 zPtr++;
714 }
715 if( zPtr >= zEnd || zPtr[0] == '\n' ){
716 /* Closing delimiter found, break immediately */
717 pStream->zText = zCur; /* Keep the semi-colon */
718 break;
719 }
720 }else if( zPtr[0] == '\n' ){
721 /* Closing delimiter found, break immediately */
722 pStream->zText = zPtr; /* Synchronize with the stream cursor */
723 break;
724 }
725 /* Synchronize pointers and continue searching */
726 zIn = zPtr;
727 }
728 } /* For(;;) */
729 /* Get the delimited string length */
730 sStr.nByte = (sxu32)((const char *)zIn-sStr.zString);
731 /* Record token type and length */
732 pToken->nType = JX9_TK_NOWDOC;
733 SyStringDupPtr(&pToken->sData, &sStr);
734 /* Remove trailing white spaces */
735 SyStringRightTrim(&pToken->sData);
736 /* All done */
737 return SXRET_OK;
738}
739/*
740 * Tokenize a raw jx9 input.
741 * This is the public tokenizer called by most code generator routines.
742 */
743JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput,sxu32 nLen,SySet *pOut)
744{
745 SyLex sLexer;
746 sxi32 rc;
747 /* Initialize the lexer */
748 rc = SyLexInit(&sLexer, &(*pOut),jx9TokenizeInput,0);
749 if( rc != SXRET_OK ){
750 return rc;
751 }
752 /* Tokenize input */
753 rc = SyLexTokenizeInput(&sLexer, zInput, nLen, 0, 0, 0);
754 /* Release the lexer */
755 SyLexRelease(&sLexer);
756 /* Tokenization result */
757 return rc;
758}
diff --git a/common/unqlite/jx9_lib.c b/common/unqlite/jx9_lib.c
new file mode 100644
index 0000000..9f3bcb2
--- /dev/null
+++ b/common/unqlite/jx9_lib.c
@@ -0,0 +1,4387 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: lib.c v5.1 Win7 2012-08-08 04:19 stable <chm@symisc.net> $ */
14/*
15 * Symisc Run-Time API: A modern thread safe replacement of the standard libc
16 * Copyright (C) Symisc Systems 2007-2012, http://www.symisc.net/
17 *
18 * The Symisc Run-Time API is an independent project developed by symisc systems
19 * internally as a secure replacement of the standard libc.
20 * The library is re-entrant, thread-safe and platform independent.
21 */
22#ifndef JX9_AMALGAMATION
23#include "jx9Int.h"
24#endif
25#if defined(__WINNT__)
26#include <Windows.h>
27#else
28#include <stdlib.h>
29#endif
30#if defined(JX9_ENABLE_THREADS)
31/* SyRunTimeApi: sxmutex.c */
32#if defined(__WINNT__)
33struct SyMutex
34{
35 CRITICAL_SECTION sMutex;
36 sxu32 nType; /* Mutex type, one of SXMUTEX_TYPE_* */
37};
38/* Preallocated static mutex */
39static SyMutex aStaticMutexes[] = {
40 {{0}, SXMUTEX_TYPE_STATIC_1},
41 {{0}, SXMUTEX_TYPE_STATIC_2},
42 {{0}, SXMUTEX_TYPE_STATIC_3},
43 {{0}, SXMUTEX_TYPE_STATIC_4},
44 {{0}, SXMUTEX_TYPE_STATIC_5},
45 {{0}, SXMUTEX_TYPE_STATIC_6}
46};
47static BOOL winMutexInit = FALSE;
48static LONG winMutexLock = 0;
49
50static sxi32 WinMutexGlobaInit(void)
51{
52 LONG rc;
53 rc = InterlockedCompareExchange(&winMutexLock, 1, 0);
54 if ( rc == 0 ){
55 sxu32 n;
56 for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){
57 InitializeCriticalSection(&aStaticMutexes[n].sMutex);
58 }
59 winMutexInit = TRUE;
60 }else{
61 /* Someone else is doing this for us */
62 while( winMutexInit == FALSE ){
63 Sleep(1);
64 }
65 }
66 return SXRET_OK;
67}
68static void WinMutexGlobalRelease(void)
69{
70 LONG rc;
71 rc = InterlockedCompareExchange(&winMutexLock, 0, 1);
72 if( rc == 1 ){
73 /* The first to decrement to zero does the actual global release */
74 if( winMutexInit == TRUE ){
75 sxu32 n;
76 for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){
77 DeleteCriticalSection(&aStaticMutexes[n].sMutex);
78 }
79 winMutexInit = FALSE;
80 }
81 }
82}
83static SyMutex * WinMutexNew(int nType)
84{
85 SyMutex *pMutex = 0;
86 if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){
87 /* Allocate a new mutex */
88 pMutex = (SyMutex *)HeapAlloc(GetProcessHeap(), 0, sizeof(SyMutex));
89 if( pMutex == 0 ){
90 return 0;
91 }
92 InitializeCriticalSection(&pMutex->sMutex);
93 }else{
94 /* Use a pre-allocated static mutex */
95 if( nType > SXMUTEX_TYPE_STATIC_6 ){
96 nType = SXMUTEX_TYPE_STATIC_6;
97 }
98 pMutex = &aStaticMutexes[nType - 3];
99 }
100 pMutex->nType = nType;
101 return pMutex;
102}
103static void WinMutexRelease(SyMutex *pMutex)
104{
105 if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){
106 DeleteCriticalSection(&pMutex->sMutex);
107 HeapFree(GetProcessHeap(), 0, pMutex);
108 }
109}
110static void WinMutexEnter(SyMutex *pMutex)
111{
112 EnterCriticalSection(&pMutex->sMutex);
113}
114static sxi32 WinMutexTryEnter(SyMutex *pMutex)
115{
116#ifdef _WIN32_WINNT
117 BOOL rc;
118 /* Only WindowsNT platforms */
119 rc = TryEnterCriticalSection(&pMutex->sMutex);
120 if( rc ){
121 return SXRET_OK;
122 }else{
123 return SXERR_BUSY;
124 }
125#else
126 return SXERR_NOTIMPLEMENTED;
127#endif
128}
129static void WinMutexLeave(SyMutex *pMutex)
130{
131 LeaveCriticalSection(&pMutex->sMutex);
132}
133/* Export Windows mutex interfaces */
134static const SyMutexMethods sWinMutexMethods = {
135 WinMutexGlobaInit, /* xGlobalInit() */
136 WinMutexGlobalRelease, /* xGlobalRelease() */
137 WinMutexNew, /* xNew() */
138 WinMutexRelease, /* xRelease() */
139 WinMutexEnter, /* xEnter() */
140 WinMutexTryEnter, /* xTryEnter() */
141 WinMutexLeave /* xLeave() */
142};
143JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void)
144{
145 return &sWinMutexMethods;
146}
147#elif defined(__UNIXES__)
148#include <pthread.h>
149struct SyMutex
150{
151 pthread_mutex_t sMutex;
152 sxu32 nType;
153};
154static SyMutex * UnixMutexNew(int nType)
155{
156 static SyMutex aStaticMutexes[] = {
157 {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_1},
158 {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_2},
159 {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_3},
160 {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_4},
161 {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_5},
162 {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_6}
163 };
164 SyMutex *pMutex;
165
166 if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){
167 pthread_mutexattr_t sRecursiveAttr;
168 /* Allocate a new mutex */
169 pMutex = (SyMutex *)malloc(sizeof(SyMutex));
170 if( pMutex == 0 ){
171 return 0;
172 }
173 if( nType == SXMUTEX_TYPE_RECURSIVE ){
174 pthread_mutexattr_init(&sRecursiveAttr);
175 pthread_mutexattr_settype(&sRecursiveAttr, PTHREAD_MUTEX_RECURSIVE);
176 }
177 pthread_mutex_init(&pMutex->sMutex, nType == SXMUTEX_TYPE_RECURSIVE ? &sRecursiveAttr : 0 );
178 if( nType == SXMUTEX_TYPE_RECURSIVE ){
179 pthread_mutexattr_destroy(&sRecursiveAttr);
180 }
181 }else{
182 /* Use a pre-allocated static mutex */
183 if( nType > SXMUTEX_TYPE_STATIC_6 ){
184 nType = SXMUTEX_TYPE_STATIC_6;
185 }
186 pMutex = &aStaticMutexes[nType - 3];
187 }
188 pMutex->nType = nType;
189
190 return pMutex;
191}
192static void UnixMutexRelease(SyMutex *pMutex)
193{
194 if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){
195 pthread_mutex_destroy(&pMutex->sMutex);
196 free(pMutex);
197 }
198}
199static void UnixMutexEnter(SyMutex *pMutex)
200{
201 pthread_mutex_lock(&pMutex->sMutex);
202}
203static void UnixMutexLeave(SyMutex *pMutex)
204{
205 pthread_mutex_unlock(&pMutex->sMutex);
206}
207/* Export pthread mutex interfaces */
208static const SyMutexMethods sPthreadMutexMethods = {
209 0, /* xGlobalInit() */
210 0, /* xGlobalRelease() */
211 UnixMutexNew, /* xNew() */
212 UnixMutexRelease, /* xRelease() */
213 UnixMutexEnter, /* xEnter() */
214 0, /* xTryEnter() */
215 UnixMutexLeave /* xLeave() */
216};
217JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void)
218{
219 return &sPthreadMutexMethods;
220}
221#else
222/* Host application must register their own mutex subsystem if the target
223 * platform is not an UNIX-like or windows systems.
224 */
225struct SyMutex
226{
227 sxu32 nType;
228};
229static SyMutex * DummyMutexNew(int nType)
230{
231 static SyMutex sMutex;
232 SXUNUSED(nType);
233 return &sMutex;
234}
235static void DummyMutexRelease(SyMutex *pMutex)
236{
237 SXUNUSED(pMutex);
238}
239static void DummyMutexEnter(SyMutex *pMutex)
240{
241 SXUNUSED(pMutex);
242}
243static void DummyMutexLeave(SyMutex *pMutex)
244{
245 SXUNUSED(pMutex);
246}
247/* Export the dummy mutex interfaces */
248static const SyMutexMethods sDummyMutexMethods = {
249 0, /* xGlobalInit() */
250 0, /* xGlobalRelease() */
251 DummyMutexNew, /* xNew() */
252 DummyMutexRelease, /* xRelease() */
253 DummyMutexEnter, /* xEnter() */
254 0, /* xTryEnter() */
255 DummyMutexLeave /* xLeave() */
256};
257JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void)
258{
259 return &sDummyMutexMethods;
260}
261#endif /* __WINNT__ */
262#endif /* JX9_ENABLE_THREADS */
263static void * SyOSHeapAlloc(sxu32 nByte)
264{
265 void *pNew;
266#if defined(__WINNT__)
267 pNew = HeapAlloc(GetProcessHeap(), 0, nByte);
268#else
269 pNew = malloc((size_t)nByte);
270#endif
271 return pNew;
272}
273static void * SyOSHeapRealloc(void *pOld, sxu32 nByte)
274{
275 void *pNew;
276#if defined(__WINNT__)
277 pNew = HeapReAlloc(GetProcessHeap(), 0, pOld, nByte);
278#else
279 pNew = realloc(pOld, (size_t)nByte);
280#endif
281 return pNew;
282}
283static void SyOSHeapFree(void *pPtr)
284{
285#if defined(__WINNT__)
286 HeapFree(GetProcessHeap(), 0, pPtr);
287#else
288 free(pPtr);
289#endif
290}
291/* SyRunTimeApi:sxstr.c */
292JX9_PRIVATE sxu32 SyStrlen(const char *zSrc)
293{
294 register const char *zIn = zSrc;
295#if defined(UNTRUST)
296 if( zIn == 0 ){
297 return 0;
298 }
299#endif
300 for(;;){
301 if( !zIn[0] ){ break; } zIn++;
302 if( !zIn[0] ){ break; } zIn++;
303 if( !zIn[0] ){ break; } zIn++;
304 if( !zIn[0] ){ break; } zIn++;
305 }
306 return (sxu32)(zIn - zSrc);
307}
308JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos)
309{
310 const char *zIn = zStr;
311 const char *zEnd;
312
313 zEnd = &zIn[nLen];
314 for(;;){
315 if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++;
316 if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++;
317 if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++;
318 if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++;
319 }
320 return SXERR_NOTFOUND;
321}
322#ifndef JX9_DISABLE_BUILTIN_FUNC
323JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos)
324{
325 const char *zIn = zStr;
326 const char *zEnd;
327
328 zEnd = &zIn[nLen - 1];
329 for( ;; ){
330 if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--;
331 if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--;
332 if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--;
333 if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--;
334 }
335 return SXERR_NOTFOUND;
336}
337#endif /* JX9_DISABLE_BUILTIN_FUNC */
338JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos)
339{
340 const char *zIn = zSrc;
341 const char *zPtr;
342 const char *zEnd;
343 sxi32 c;
344 zEnd = &zSrc[nLen];
345 for(;;){
346 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++;
347 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++;
348 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++;
349 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++;
350 }
351 return SXERR_NOTFOUND;
352}
353#ifndef JX9_DISABLE_BUILTIN_FUNC
354JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen)
355{
356 const unsigned char *zP = (const unsigned char *)zLeft;
357 const unsigned char *zQ = (const unsigned char *)zRight;
358
359 if( SX_EMPTY_STR(zP) || SX_EMPTY_STR(zQ) ){
360 return SX_EMPTY_STR(zP) ? (SX_EMPTY_STR(zQ) ? 0 : -1) :1;
361 }
362 if( nLen <= 0 ){
363 return 0;
364 }
365 for(;;){
366 if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--;
367 if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--;
368 if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--;
369 if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--;
370 }
371 return (sxi32)(zP[0] - zQ[0]);
372}
373#endif
374JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen)
375{
376 register unsigned char *p = (unsigned char *)zLeft;
377 register unsigned char *q = (unsigned char *)zRight;
378
379 if( SX_EMPTY_STR(p) || SX_EMPTY_STR(q) ){
380 return SX_EMPTY_STR(p)? SX_EMPTY_STR(q) ? 0 : -1 :1;
381 }
382 for(;;){
383 if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen;
384 if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen;
385 if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen;
386 if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen;
387
388 }
389 return (sxi32)(SyCharToLower(p[0]) - SyCharToLower(q[0]));
390}
391JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen)
392{
393 unsigned char *zBuf = (unsigned char *)zDest;
394 unsigned char *zIn = (unsigned char *)zSrc;
395 unsigned char *zEnd;
396#if defined(UNTRUST)
397 if( zSrc == (const char *)zDest ){
398 return 0;
399 }
400#endif
401 if( nLen <= 0 ){
402 nLen = SyStrlen(zSrc);
403 }
404 zEnd = &zBuf[nDestLen - 1]; /* reserve a room for the null terminator */
405 for(;;){
406 if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--;
407 if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--;
408 if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--;
409 if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--;
410 }
411 zBuf[0] = 0;
412 return (sxu32)(zBuf-(unsigned char *)zDest);
413}
414/* SyRunTimeApi:sxmem.c */
415JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize)
416{
417 register unsigned char *zSrc = (unsigned char *)pSrc;
418 unsigned char *zEnd;
419#if defined(UNTRUST)
420 if( zSrc == 0 || nSize <= 0 ){
421 return ;
422 }
423#endif
424 zEnd = &zSrc[nSize];
425 for(;;){
426 if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++;
427 if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++;
428 if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++;
429 if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++;
430 }
431}
432JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize)
433{
434 sxi32 rc;
435 if( nSize <= 0 ){
436 return 0;
437 }
438 if( pB1 == 0 || pB2 == 0 ){
439 return pB1 != 0 ? 1 : (pB2 == 0 ? 0 : -1);
440 }
441 SX_MACRO_FAST_CMP(pB1, pB2, nSize, rc);
442 return rc;
443}
444JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen)
445{
446 if( pSrc == 0 || pDest == 0 ){
447 return 0;
448 }
449 if( pSrc == (const void *)pDest ){
450 return nLen;
451 }
452 SX_MACRO_FAST_MEMCPY(pSrc, pDest, nLen);
453 return nLen;
454}
455static void * MemOSAlloc(sxu32 nBytes)
456{
457 sxu32 *pChunk;
458 pChunk = (sxu32 *)SyOSHeapAlloc(nBytes + sizeof(sxu32));
459 if( pChunk == 0 ){
460 return 0;
461 }
462 pChunk[0] = nBytes;
463 return (void *)&pChunk[1];
464}
465static void * MemOSRealloc(void *pOld, sxu32 nBytes)
466{
467 sxu32 *pOldChunk;
468 sxu32 *pChunk;
469 pOldChunk = (sxu32 *)(((char *)pOld)-sizeof(sxu32));
470 if( pOldChunk[0] >= nBytes ){
471 return pOld;
472 }
473 pChunk = (sxu32 *)SyOSHeapRealloc(pOldChunk, nBytes + sizeof(sxu32));
474 if( pChunk == 0 ){
475 return 0;
476 }
477 pChunk[0] = nBytes;
478 return (void *)&pChunk[1];
479}
480static void MemOSFree(void *pBlock)
481{
482 void *pChunk;
483 pChunk = (void *)(((char *)pBlock)-sizeof(sxu32));
484 SyOSHeapFree(pChunk);
485}
486static sxu32 MemOSChunkSize(void *pBlock)
487{
488 sxu32 *pChunk;
489 pChunk = (sxu32 *)(((char *)pBlock)-sizeof(sxu32));
490 return pChunk[0];
491}
492/* Export OS allocation methods */
493static const SyMemMethods sOSAllocMethods = {
494 MemOSAlloc,
495 MemOSRealloc,
496 MemOSFree,
497 MemOSChunkSize,
498 0,
499 0,
500 0
501};
502static void * MemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte)
503{
504 SyMemBlock *pBlock;
505 sxi32 nRetry = 0;
506
507 /* Append an extra block so we can tracks allocated chunks and avoid memory
508 * leaks.
509 */
510 nByte += sizeof(SyMemBlock);
511 for(;;){
512 pBlock = (SyMemBlock *)pBackend->pMethods->xAlloc(nByte);
513 if( pBlock != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY
514 || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){
515 break;
516 }
517 nRetry++;
518 }
519 if( pBlock == 0 ){
520 return 0;
521 }
522 pBlock->pNext = pBlock->pPrev = 0;
523 /* Link to the list of already tracked blocks */
524 MACRO_LD_PUSH(pBackend->pBlocks, pBlock);
525#if defined(UNTRUST)
526 pBlock->nGuard = SXMEM_BACKEND_MAGIC;
527#endif
528 pBackend->nBlock++;
529 return (void *)&pBlock[1];
530}
531JX9_PRIVATE void * SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte)
532{
533 void *pChunk;
534#if defined(UNTRUST)
535 if( SXMEM_BACKEND_CORRUPT(pBackend) ){
536 return 0;
537 }
538#endif
539 if( pBackend->pMutexMethods ){
540 SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex);
541 }
542 pChunk = MemBackendAlloc(&(*pBackend), nByte);
543 if( pBackend->pMutexMethods ){
544 SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex);
545 }
546 return pChunk;
547}
548static void * MemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte)
549{
550 SyMemBlock *pBlock, *pNew, *pPrev, *pNext;
551 sxu32 nRetry = 0;
552
553 if( pOld == 0 ){
554 return MemBackendAlloc(&(*pBackend), nByte);
555 }
556 pBlock = (SyMemBlock *)(((char *)pOld) - sizeof(SyMemBlock));
557#if defined(UNTRUST)
558 if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){
559 return 0;
560 }
561#endif
562 nByte += sizeof(SyMemBlock);
563 pPrev = pBlock->pPrev;
564 pNext = pBlock->pNext;
565 for(;;){
566 pNew = (SyMemBlock *)pBackend->pMethods->xRealloc(pBlock, nByte);
567 if( pNew != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY ||
568 SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){
569 break;
570 }
571 nRetry++;
572 }
573 if( pNew == 0 ){
574 return 0;
575 }
576 if( pNew != pBlock ){
577 if( pPrev == 0 ){
578 pBackend->pBlocks = pNew;
579 }else{
580 pPrev->pNext = pNew;
581 }
582 if( pNext ){
583 pNext->pPrev = pNew;
584 }
585#if defined(UNTRUST)
586 pNew->nGuard = SXMEM_BACKEND_MAGIC;
587#endif
588 }
589 return (void *)&pNew[1];
590}
591JX9_PRIVATE void * SyMemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte)
592{
593 void *pChunk;
594#if defined(UNTRUST)
595 if( SXMEM_BACKEND_CORRUPT(pBackend) ){
596 return 0;
597 }
598#endif
599 if( pBackend->pMutexMethods ){
600 SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex);
601 }
602 pChunk = MemBackendRealloc(&(*pBackend), pOld, nByte);
603 if( pBackend->pMutexMethods ){
604 SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex);
605 }
606 return pChunk;
607}
608static sxi32 MemBackendFree(SyMemBackend *pBackend, void * pChunk)
609{
610 SyMemBlock *pBlock;
611 pBlock = (SyMemBlock *)(((char *)pChunk) - sizeof(SyMemBlock));
612#if defined(UNTRUST)
613 if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){
614 return SXERR_CORRUPT;
615 }
616#endif
617 /* Unlink from the list of active blocks */
618 if( pBackend->nBlock > 0 ){
619 /* Release the block */
620#if defined(UNTRUST)
621 /* Mark as stale block */
622 pBlock->nGuard = 0x635B;
623#endif
624 MACRO_LD_REMOVE(pBackend->pBlocks, pBlock);
625 pBackend->nBlock--;
626 pBackend->pMethods->xFree(pBlock);
627 }
628 return SXRET_OK;
629}
630JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void * pChunk)
631{
632 sxi32 rc;
633#if defined(UNTRUST)
634 if( SXMEM_BACKEND_CORRUPT(pBackend) ){
635 return SXERR_CORRUPT;
636 }
637#endif
638 if( pChunk == 0 ){
639 return SXRET_OK;
640 }
641 if( pBackend->pMutexMethods ){
642 SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex);
643 }
644 rc = MemBackendFree(&(*pBackend), pChunk);
645 if( pBackend->pMutexMethods ){
646 SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex);
647 }
648 return rc;
649}
650#if defined(JX9_ENABLE_THREADS)
651JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods)
652{
653 SyMutex *pMutex;
654#if defined(UNTRUST)
655 if( SXMEM_BACKEND_CORRUPT(pBackend) || pMethods == 0 || pMethods->xNew == 0){
656 return SXERR_CORRUPT;
657 }
658#endif
659 pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST);
660 if( pMutex == 0 ){
661 return SXERR_OS;
662 }
663 /* Attach the mutex to the memory backend */
664 pBackend->pMutex = pMutex;
665 pBackend->pMutexMethods = pMethods;
666 return SXRET_OK;
667}
668JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend)
669{
670#if defined(UNTRUST)
671 if( SXMEM_BACKEND_CORRUPT(pBackend) ){
672 return SXERR_CORRUPT;
673 }
674#endif
675 if( pBackend->pMutex == 0 ){
676 /* There is no mutex subsystem at all */
677 return SXRET_OK;
678 }
679 SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex);
680 pBackend->pMutexMethods = 0;
681 pBackend->pMutex = 0;
682 return SXRET_OK;
683}
684#endif
685/*
686 * Memory pool allocator
687 */
688#define SXMEM_POOL_MAGIC 0xDEAD
689#define SXMEM_POOL_MAXALLOC (1<<(SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR))
690#define SXMEM_POOL_MINALLOC (1<<(SXMEM_POOL_INCR))
691static sxi32 MemPoolBucketAlloc(SyMemBackend *pBackend, sxu32 nBucket)
692{
693 char *zBucket, *zBucketEnd;
694 SyMemHeader *pHeader;
695 sxu32 nBucketSize;
696
697 /* Allocate one big block first */
698 zBucket = (char *)MemBackendAlloc(&(*pBackend), SXMEM_POOL_MAXALLOC);
699 if( zBucket == 0 ){
700 return SXERR_MEM;
701 }
702 zBucketEnd = &zBucket[SXMEM_POOL_MAXALLOC];
703 /* Divide the big block into mini bucket pool */
704 nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR);
705 pBackend->apPool[nBucket] = pHeader = (SyMemHeader *)zBucket;
706 for(;;){
707 if( &zBucket[nBucketSize] >= zBucketEnd ){
708 break;
709 }
710 pHeader->pNext = (SyMemHeader *)&zBucket[nBucketSize];
711 /* Advance the cursor to the next available chunk */
712 pHeader = pHeader->pNext;
713 zBucket += nBucketSize;
714 }
715 pHeader->pNext = 0;
716
717 return SXRET_OK;
718}
719static void * MemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte)
720{
721 SyMemHeader *pBucket, *pNext;
722 sxu32 nBucketSize;
723 sxu32 nBucket;
724
725 if( nByte + sizeof(SyMemHeader) >= SXMEM_POOL_MAXALLOC ){
726 /* Allocate a big chunk directly */
727 pBucket = (SyMemHeader *)MemBackendAlloc(&(*pBackend), nByte+sizeof(SyMemHeader));
728 if( pBucket == 0 ){
729 return 0;
730 }
731 /* Record as big block */
732 pBucket->nBucket = (sxu32)(SXMEM_POOL_MAGIC << 16) | SXU16_HIGH;
733 return (void *)(pBucket+1);
734 }
735 /* Locate the appropriate bucket */
736 nBucket = 0;
737 nBucketSize = SXMEM_POOL_MINALLOC;
738 while( nByte + sizeof(SyMemHeader) > nBucketSize ){
739 nBucketSize <<= 1;
740 nBucket++;
741 }
742 pBucket = pBackend->apPool[nBucket];
743 if( pBucket == 0 ){
744 sxi32 rc;
745 rc = MemPoolBucketAlloc(&(*pBackend), nBucket);
746 if( rc != SXRET_OK ){
747 return 0;
748 }
749 pBucket = pBackend->apPool[nBucket];
750 }
751 /* Remove from the free list */
752 pNext = pBucket->pNext;
753 pBackend->apPool[nBucket] = pNext;
754 /* Record bucket&magic number */
755 pBucket->nBucket = (SXMEM_POOL_MAGIC << 16) | nBucket;
756 return (void *)&pBucket[1];
757}
758JX9_PRIVATE void * SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte)
759{
760 void *pChunk;
761#if defined(UNTRUST)
762 if( SXMEM_BACKEND_CORRUPT(pBackend) ){
763 return 0;
764 }
765#endif
766 if( pBackend->pMutexMethods ){
767 SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex);
768 }
769 pChunk = MemBackendPoolAlloc(&(*pBackend), nByte);
770 if( pBackend->pMutexMethods ){
771 SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex);
772 }
773 return pChunk;
774}
775static sxi32 MemBackendPoolFree(SyMemBackend *pBackend, void * pChunk)
776{
777 SyMemHeader *pHeader;
778 sxu32 nBucket;
779 /* Get the corresponding bucket */
780 pHeader = (SyMemHeader *)(((char *)pChunk) - sizeof(SyMemHeader));
781 /* Sanity check to avoid misuse */
782 if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){
783 return SXERR_CORRUPT;
784 }
785 nBucket = pHeader->nBucket & 0xFFFF;
786 if( nBucket == SXU16_HIGH ){
787 /* Free the big block */
788 MemBackendFree(&(*pBackend), pHeader);
789 }else{
790 /* Return to the free list */
791 pHeader->pNext = pBackend->apPool[nBucket & 0x0f];
792 pBackend->apPool[nBucket & 0x0f] = pHeader;
793 }
794 return SXRET_OK;
795}
796JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void * pChunk)
797{
798 sxi32 rc;
799#if defined(UNTRUST)
800 if( SXMEM_BACKEND_CORRUPT(pBackend) || pChunk == 0 ){
801 return SXERR_CORRUPT;
802 }
803#endif
804 if( pBackend->pMutexMethods ){
805 SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex);
806 }
807 rc = MemBackendPoolFree(&(*pBackend), pChunk);
808 if( pBackend->pMutexMethods ){
809 SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex);
810 }
811 return rc;
812}
813#if 0
814static void * MemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte)
815{
816 sxu32 nBucket, nBucketSize;
817 SyMemHeader *pHeader;
818 void * pNew;
819
820 if( pOld == 0 ){
821 /* Allocate a new pool */
822 pNew = MemBackendPoolAlloc(&(*pBackend), nByte);
823 return pNew;
824 }
825 /* Get the corresponding bucket */
826 pHeader = (SyMemHeader *)(((char *)pOld) - sizeof(SyMemHeader));
827 /* Sanity check to avoid misuse */
828 if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){
829 return 0;
830 }
831 nBucket = pHeader->nBucket & 0xFFFF;
832 if( nBucket == SXU16_HIGH ){
833 /* Big block */
834 return MemBackendRealloc(&(*pBackend), pHeader, nByte);
835 }
836 nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR);
837 if( nBucketSize >= nByte + sizeof(SyMemHeader) ){
838 /* The old bucket can honor the requested size */
839 return pOld;
840 }
841 /* Allocate a new pool */
842 pNew = MemBackendPoolAlloc(&(*pBackend), nByte);
843 if( pNew == 0 ){
844 return 0;
845 }
846 /* Copy the old data into the new block */
847 SyMemcpy(pOld, pNew, nBucketSize);
848 /* Free the stale block */
849 MemBackendPoolFree(&(*pBackend), pOld);
850 return pNew;
851}
852JX9_PRIVATE void * SyMemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte)
853{
854 void *pChunk;
855#if defined(UNTRUST)
856 if( SXMEM_BACKEND_CORRUPT(pBackend) ){
857 return 0;
858 }
859#endif
860 if( pBackend->pMutexMethods ){
861 SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex);
862 }
863 pChunk = MemBackendPoolRealloc(&(*pBackend), pOld, nByte);
864 if( pBackend->pMutexMethods ){
865 SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex);
866 }
867 return pChunk;
868}
869#endif
870JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void * pUserData)
871{
872#if defined(UNTRUST)
873 if( pBackend == 0 ){
874 return SXERR_EMPTY;
875 }
876#endif
877 /* Zero the allocator first */
878 SyZero(&(*pBackend), sizeof(SyMemBackend));
879 pBackend->xMemError = xMemErr;
880 pBackend->pUserData = pUserData;
881 /* Switch to the OS memory allocator */
882 pBackend->pMethods = &sOSAllocMethods;
883 if( pBackend->pMethods->xInit ){
884 /* Initialize the backend */
885 if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){
886 return SXERR_ABORT;
887 }
888 }
889#if defined(UNTRUST)
890 pBackend->nMagic = SXMEM_BACKEND_MAGIC;
891#endif
892 return SXRET_OK;
893}
894JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void * pUserData)
895{
896#if defined(UNTRUST)
897 if( pBackend == 0 || pMethods == 0){
898 return SXERR_EMPTY;
899 }
900#endif
901 if( pMethods->xAlloc == 0 || pMethods->xRealloc == 0 || pMethods->xFree == 0 || pMethods->xChunkSize == 0 ){
902 /* mandatory methods are missing */
903 return SXERR_INVALID;
904 }
905 /* Zero the allocator first */
906 SyZero(&(*pBackend), sizeof(SyMemBackend));
907 pBackend->xMemError = xMemErr;
908 pBackend->pUserData = pUserData;
909 /* Switch to the host application memory allocator */
910 pBackend->pMethods = pMethods;
911 if( pBackend->pMethods->xInit ){
912 /* Initialize the backend */
913 if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){
914 return SXERR_ABORT;
915 }
916 }
917#if defined(UNTRUST)
918 pBackend->nMagic = SXMEM_BACKEND_MAGIC;
919#endif
920 return SXRET_OK;
921}
922JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent)
923{
924 sxu8 bInheritMutex;
925#if defined(UNTRUST)
926 if( pBackend == 0 || SXMEM_BACKEND_CORRUPT(pParent) ){
927 return SXERR_CORRUPT;
928 }
929#endif
930 /* Zero the allocator first */
931 SyZero(&(*pBackend), sizeof(SyMemBackend));
932 pBackend->pMethods = pParent->pMethods;
933 pBackend->xMemError = pParent->xMemError;
934 pBackend->pUserData = pParent->pUserData;
935 bInheritMutex = pParent->pMutexMethods ? TRUE : FALSE;
936 if( bInheritMutex ){
937 pBackend->pMutexMethods = pParent->pMutexMethods;
938 /* Create a private mutex */
939 pBackend->pMutex = pBackend->pMutexMethods->xNew(SXMUTEX_TYPE_FAST);
940 if( pBackend->pMutex == 0){
941 return SXERR_OS;
942 }
943 }
944#if defined(UNTRUST)
945 pBackend->nMagic = SXMEM_BACKEND_MAGIC;
946#endif
947 return SXRET_OK;
948}
949static sxi32 MemBackendRelease(SyMemBackend *pBackend)
950{
951 SyMemBlock *pBlock, *pNext;
952
953 pBlock = pBackend->pBlocks;
954 for(;;){
955 if( pBackend->nBlock == 0 ){
956 break;
957 }
958 pNext = pBlock->pNext;
959 pBackend->pMethods->xFree(pBlock);
960 pBlock = pNext;
961 pBackend->nBlock--;
962 /* LOOP ONE */
963 if( pBackend->nBlock == 0 ){
964 break;
965 }
966 pNext = pBlock->pNext;
967 pBackend->pMethods->xFree(pBlock);
968 pBlock = pNext;
969 pBackend->nBlock--;
970 /* LOOP TWO */
971 if( pBackend->nBlock == 0 ){
972 break;
973 }
974 pNext = pBlock->pNext;
975 pBackend->pMethods->xFree(pBlock);
976 pBlock = pNext;
977 pBackend->nBlock--;
978 /* LOOP THREE */
979 if( pBackend->nBlock == 0 ){
980 break;
981 }
982 pNext = pBlock->pNext;
983 pBackend->pMethods->xFree(pBlock);
984 pBlock = pNext;
985 pBackend->nBlock--;
986 /* LOOP FOUR */
987 }
988 if( pBackend->pMethods->xRelease ){
989 pBackend->pMethods->xRelease(pBackend->pMethods->pUserData);
990 }
991 pBackend->pMethods = 0;
992 pBackend->pBlocks = 0;
993#if defined(UNTRUST)
994 pBackend->nMagic = 0x2626;
995#endif
996 return SXRET_OK;
997}
998JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend)
999{
1000 sxi32 rc;
1001#if defined(UNTRUST)
1002 if( SXMEM_BACKEND_CORRUPT(pBackend) ){
1003 return SXERR_INVALID;
1004 }
1005#endif
1006 if( pBackend->pMutexMethods ){
1007 SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex);
1008 }
1009 rc = MemBackendRelease(&(*pBackend));
1010 if( pBackend->pMutexMethods ){
1011 SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex);
1012 SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex);
1013 }
1014 return rc;
1015}
1016JX9_PRIVATE void * SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize)
1017{
1018 void *pNew;
1019#if defined(UNTRUST)
1020 if( pSrc == 0 || nSize <= 0 ){
1021 return 0;
1022 }
1023#endif
1024 pNew = SyMemBackendAlloc(&(*pBackend), nSize);
1025 if( pNew ){
1026 SyMemcpy(pSrc, pNew, nSize);
1027 }
1028 return pNew;
1029}
1030JX9_PRIVATE char * SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize)
1031{
1032 char *zDest;
1033 zDest = (char *)SyMemBackendAlloc(&(*pBackend), nSize + 1);
1034 if( zDest ){
1035 Systrcpy(zDest, nSize+1, zSrc, nSize);
1036 }
1037 return zDest;
1038}
1039JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize)
1040{
1041#if defined(UNTRUST)
1042 if( pBlob == 0 || pBuffer == 0 || nSize < 1 ){
1043 return SXERR_EMPTY;
1044 }
1045#endif
1046 pBlob->pBlob = pBuffer;
1047 pBlob->mByte = nSize;
1048 pBlob->nByte = 0;
1049 pBlob->pAllocator = 0;
1050 pBlob->nFlags = SXBLOB_LOCKED|SXBLOB_STATIC;
1051 return SXRET_OK;
1052}
1053JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator)
1054{
1055#if defined(UNTRUST)
1056 if( pBlob == 0 ){
1057 return SXERR_EMPTY;
1058 }
1059#endif
1060 pBlob->pBlob = 0;
1061 pBlob->mByte = pBlob->nByte = 0;
1062 pBlob->pAllocator = &(*pAllocator);
1063 pBlob->nFlags = 0;
1064 return SXRET_OK;
1065}
1066JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte)
1067{
1068#if defined(UNTRUST)
1069 if( pBlob == 0 ){
1070 return SXERR_EMPTY;
1071 }
1072#endif
1073 pBlob->pBlob = (void *)pData;
1074 pBlob->nByte = nByte;
1075 pBlob->mByte = 0;
1076 pBlob->nFlags |= SXBLOB_RDONLY;
1077 return SXRET_OK;
1078}
1079#ifndef SXBLOB_MIN_GROWTH
1080#define SXBLOB_MIN_GROWTH 16
1081#endif
1082static sxi32 BlobPrepareGrow(SyBlob *pBlob, sxu32 *pByte)
1083{
1084 sxu32 nByte;
1085 void *pNew;
1086 nByte = *pByte;
1087 if( pBlob->nFlags & (SXBLOB_LOCKED|SXBLOB_STATIC) ){
1088 if ( SyBlobFreeSpace(pBlob) < nByte ){
1089 *pByte = SyBlobFreeSpace(pBlob);
1090 if( (*pByte) == 0 ){
1091 return SXERR_SHORT;
1092 }
1093 }
1094 return SXRET_OK;
1095 }
1096 if( pBlob->nFlags & SXBLOB_RDONLY ){
1097 /* Make a copy of the read-only item */
1098 if( pBlob->nByte > 0 ){
1099 pNew = SyMemBackendDup(pBlob->pAllocator, pBlob->pBlob, pBlob->nByte);
1100 if( pNew == 0 ){
1101 return SXERR_MEM;
1102 }
1103 pBlob->pBlob = pNew;
1104 pBlob->mByte = pBlob->nByte;
1105 }else{
1106 pBlob->pBlob = 0;
1107 pBlob->mByte = 0;
1108 }
1109 /* Remove the read-only flag */
1110 pBlob->nFlags &= ~SXBLOB_RDONLY;
1111 }
1112 if( SyBlobFreeSpace(pBlob) >= nByte ){
1113 return SXRET_OK;
1114 }
1115 if( pBlob->mByte > 0 ){
1116 nByte = nByte + pBlob->mByte * 2 + SXBLOB_MIN_GROWTH;
1117 }else if ( nByte < SXBLOB_MIN_GROWTH ){
1118 nByte = SXBLOB_MIN_GROWTH;
1119 }
1120 pNew = SyMemBackendRealloc(pBlob->pAllocator, pBlob->pBlob, nByte);
1121 if( pNew == 0 ){
1122 return SXERR_MEM;
1123 }
1124 pBlob->pBlob = pNew;
1125 pBlob->mByte = nByte;
1126 return SXRET_OK;
1127}
1128JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize)
1129{
1130 sxu8 *zBlob;
1131 sxi32 rc;
1132 if( nSize < 1 ){
1133 return SXRET_OK;
1134 }
1135 rc = BlobPrepareGrow(&(*pBlob), &nSize);
1136 if( SXRET_OK != rc ){
1137 return rc;
1138 }
1139 if( pData ){
1140 zBlob = (sxu8 *)pBlob->pBlob ;
1141 zBlob = &zBlob[pBlob->nByte];
1142 pBlob->nByte += nSize;
1143 SX_MACRO_FAST_MEMCPY(pData, zBlob, nSize);
1144 }
1145 return SXRET_OK;
1146}
1147JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob)
1148{
1149 sxi32 rc;
1150 sxu32 n;
1151 n = pBlob->nByte;
1152 rc = SyBlobAppend(&(*pBlob), (const void *)"\0", sizeof(char));
1153 if (rc == SXRET_OK ){
1154 pBlob->nByte = n;
1155 }
1156 return rc;
1157}
1158JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest)
1159{
1160 sxi32 rc = SXRET_OK;
1161 if( pSrc->nByte > 0 ){
1162 rc = SyBlobAppend(&(*pDest), pSrc->pBlob, pSrc->nByte);
1163 }
1164 return rc;
1165}
1166JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob)
1167{
1168 pBlob->nByte = 0;
1169 if( pBlob->nFlags & SXBLOB_RDONLY ){
1170 /* Read-only (Not malloced chunk) */
1171 pBlob->pBlob = 0;
1172 pBlob->mByte = 0;
1173 pBlob->nFlags &= ~SXBLOB_RDONLY;
1174 }
1175 return SXRET_OK;
1176}
1177JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen)
1178{
1179 if( nNewLen < pBlob->nByte ){
1180 pBlob->nByte = nNewLen;
1181 }
1182 return SXRET_OK;
1183}
1184JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob)
1185{
1186 if( (pBlob->nFlags & (SXBLOB_STATIC|SXBLOB_RDONLY)) == 0 && pBlob->mByte > 0 ){
1187 SyMemBackendFree(pBlob->pAllocator, pBlob->pBlob);
1188 }
1189 pBlob->pBlob = 0;
1190 pBlob->nByte = pBlob->mByte = 0;
1191 pBlob->nFlags = 0;
1192 return SXRET_OK;
1193}
1194#ifndef JX9_DISABLE_BUILTIN_FUNC
1195JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft)
1196{
1197 const char *zIn = (const char *)pBlob;
1198 const char *zEnd;
1199 sxi32 rc;
1200 if( pLen > nLen ){
1201 return SXERR_NOTFOUND;
1202 }
1203 zEnd = &zIn[nLen-pLen];
1204 for(;;){
1205 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++;
1206 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++;
1207 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++;
1208 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++;
1209 }
1210 return SXERR_NOTFOUND;
1211}
1212#endif /* JX9_DISABLE_BUILTIN_FUNC */
1213/* SyRunTimeApi:sxds.c */
1214JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize)
1215{
1216 pSet->nSize = 0 ;
1217 pSet->nUsed = 0;
1218 pSet->nCursor = 0;
1219 pSet->eSize = ElemSize;
1220 pSet->pAllocator = pAllocator;
1221 pSet->pBase = 0;
1222 pSet->pUserData = 0;
1223 return SXRET_OK;
1224}
1225JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem)
1226{
1227 unsigned char *zbase;
1228 if( pSet->nUsed >= pSet->nSize ){
1229 void *pNew;
1230 if( pSet->pAllocator == 0 ){
1231 return SXERR_LOCKED;
1232 }
1233 if( pSet->nSize <= 0 ){
1234 pSet->nSize = 4;
1235 }
1236 pNew = SyMemBackendRealloc(pSet->pAllocator, pSet->pBase, pSet->eSize * pSet->nSize * 2);
1237 if( pNew == 0 ){
1238 return SXERR_MEM;
1239 }
1240 pSet->pBase = pNew;
1241 pSet->nSize <<= 1;
1242 }
1243 zbase = (unsigned char *)pSet->pBase;
1244 SX_MACRO_FAST_MEMCPY(pItem, &zbase[pSet->nUsed * pSet->eSize], pSet->eSize);
1245 pSet->nUsed++;
1246 return SXRET_OK;
1247}
1248JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem)
1249{
1250 if( pSet->nSize > 0 ){
1251 return SXERR_LOCKED;
1252 }
1253 if( nItem < 8 ){
1254 nItem = 8;
1255 }
1256 pSet->pBase = SyMemBackendAlloc(pSet->pAllocator, pSet->eSize * nItem);
1257 if( pSet->pBase == 0 ){
1258 return SXERR_MEM;
1259 }
1260 pSet->nSize = nItem;
1261 return SXRET_OK;
1262}
1263JX9_PRIVATE sxi32 SySetReset(SySet *pSet)
1264{
1265 pSet->nUsed = 0;
1266 pSet->nCursor = 0;
1267 return SXRET_OK;
1268}
1269JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet)
1270{
1271 pSet->nCursor = 0;
1272 return SXRET_OK;
1273}
1274JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry)
1275{
1276 register unsigned char *zSrc;
1277 if( pSet->nCursor >= pSet->nUsed ){
1278 /* Reset cursor */
1279 pSet->nCursor = 0;
1280 return SXERR_EOF;
1281 }
1282 zSrc = (unsigned char *)SySetBasePtr(pSet);
1283 if( ppEntry ){
1284 *ppEntry = (void *)&zSrc[pSet->nCursor * pSet->eSize];
1285 }
1286 pSet->nCursor++;
1287 return SXRET_OK;
1288}
1289JX9_PRIVATE sxi32 SySetRelease(SySet *pSet)
1290{
1291 sxi32 rc = SXRET_OK;
1292 if( pSet->pAllocator && pSet->pBase ){
1293 rc = SyMemBackendFree(pSet->pAllocator, pSet->pBase);
1294 }
1295 pSet->pBase = 0;
1296 pSet->nUsed = 0;
1297 pSet->nCursor = 0;
1298 return rc;
1299}
1300JX9_PRIVATE void * SySetPeek(SySet *pSet)
1301{
1302 const char *zBase;
1303 if( pSet->nUsed <= 0 ){
1304 return 0;
1305 }
1306 zBase = (const char *)pSet->pBase;
1307 return (void *)&zBase[(pSet->nUsed - 1) * pSet->eSize];
1308}
1309JX9_PRIVATE void * SySetPop(SySet *pSet)
1310{
1311 const char *zBase;
1312 void *pData;
1313 if( pSet->nUsed <= 0 ){
1314 return 0;
1315 }
1316 zBase = (const char *)pSet->pBase;
1317 pSet->nUsed--;
1318 pData = (void *)&zBase[pSet->nUsed * pSet->eSize];
1319 return pData;
1320}
1321JX9_PRIVATE void * SySetAt(SySet *pSet, sxu32 nIdx)
1322{
1323 const char *zBase;
1324 if( nIdx >= pSet->nUsed ){
1325 /* Out of range */
1326 return 0;
1327 }
1328 zBase = (const char *)pSet->pBase;
1329 return (void *)&zBase[nIdx * pSet->eSize];
1330}
1331/* Private hash entry */
1332struct SyHashEntry_Pr
1333{
1334 const void *pKey; /* Hash key */
1335 sxu32 nKeyLen; /* Key length */
1336 void *pUserData; /* User private data */
1337 /* Private fields */
1338 sxu32 nHash;
1339 SyHash *pHash;
1340 SyHashEntry_Pr *pNext, *pPrev; /* Next and previous entry in the list */
1341 SyHashEntry_Pr *pNextCollide, *pPrevCollide; /* Collision list */
1342};
1343#define INVALID_HASH(H) ((H)->apBucket == 0)
1344JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp)
1345{
1346 SyHashEntry_Pr **apNew;
1347#if defined(UNTRUST)
1348 if( pHash == 0 ){
1349 return SXERR_EMPTY;
1350 }
1351#endif
1352 /* Allocate a new table */
1353 apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(&(*pAllocator), sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE);
1354 if( apNew == 0 ){
1355 return SXERR_MEM;
1356 }
1357 SyZero((void *)apNew, sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE);
1358 pHash->pAllocator = &(*pAllocator);
1359 pHash->xHash = xHash ? xHash : SyBinHash;
1360 pHash->xCmp = xCmp ? xCmp : SyMemcmp;
1361 pHash->pCurrent = pHash->pList = 0;
1362 pHash->nEntry = 0;
1363 pHash->apBucket = apNew;
1364 pHash->nBucketSize = SXHASH_BUCKET_SIZE;
1365 return SXRET_OK;
1366}
1367JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash)
1368{
1369 SyHashEntry_Pr *pEntry, *pNext;
1370#if defined(UNTRUST)
1371 if( INVALID_HASH(pHash) ){
1372 return SXERR_EMPTY;
1373 }
1374#endif
1375 pEntry = pHash->pList;
1376 for(;;){
1377 if( pHash->nEntry == 0 ){
1378 break;
1379 }
1380 pNext = pEntry->pNext;
1381 SyMemBackendPoolFree(pHash->pAllocator, pEntry);
1382 pEntry = pNext;
1383 pHash->nEntry--;
1384 }
1385 if( pHash->apBucket ){
1386 SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket);
1387 }
1388 pHash->apBucket = 0;
1389 pHash->nBucketSize = 0;
1390 pHash->pAllocator = 0;
1391 return SXRET_OK;
1392}
1393static SyHashEntry_Pr * HashGetEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen)
1394{
1395 SyHashEntry_Pr *pEntry;
1396 sxu32 nHash;
1397
1398 nHash = pHash->xHash(pKey, nKeyLen);
1399 pEntry = pHash->apBucket[nHash & (pHash->nBucketSize - 1)];
1400 for(;;){
1401 if( pEntry == 0 ){
1402 break;
1403 }
1404 if( pEntry->nHash == nHash && pEntry->nKeyLen == nKeyLen &&
1405 pHash->xCmp(pEntry->pKey, pKey, nKeyLen) == 0 ){
1406 return pEntry;
1407 }
1408 pEntry = pEntry->pNextCollide;
1409 }
1410 /* Entry not found */
1411 return 0;
1412}
1413JX9_PRIVATE SyHashEntry * SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen)
1414{
1415 SyHashEntry_Pr *pEntry;
1416#if defined(UNTRUST)
1417 if( INVALID_HASH(pHash) ){
1418 return 0;
1419 }
1420#endif
1421 if( pHash->nEntry < 1 || nKeyLen < 1 ){
1422 /* Don't bother hashing, return immediately */
1423 return 0;
1424 }
1425 pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen);
1426 if( pEntry == 0 ){
1427 return 0;
1428 }
1429 return (SyHashEntry *)pEntry;
1430}
1431static sxi32 HashDeleteEntry(SyHash *pHash, SyHashEntry_Pr *pEntry, void **ppUserData)
1432{
1433 sxi32 rc;
1434 if( pEntry->pPrevCollide == 0 ){
1435 pHash->apBucket[pEntry->nHash & (pHash->nBucketSize - 1)] = pEntry->pNextCollide;
1436 }else{
1437 pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide;
1438 }
1439 if( pEntry->pNextCollide ){
1440 pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide;
1441 }
1442 MACRO_LD_REMOVE(pHash->pList, pEntry);
1443 pHash->nEntry--;
1444 if( ppUserData ){
1445 /* Write a pointer to the user data */
1446 *ppUserData = pEntry->pUserData;
1447 }
1448 /* Release the entry */
1449 rc = SyMemBackendPoolFree(pHash->pAllocator, pEntry);
1450 return rc;
1451}
1452JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData)
1453{
1454 SyHashEntry_Pr *pEntry;
1455 sxi32 rc;
1456#if defined(UNTRUST)
1457 if( INVALID_HASH(pHash) ){
1458 return SXERR_CORRUPT;
1459 }
1460#endif
1461 pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen);
1462 if( pEntry == 0 ){
1463 return SXERR_NOTFOUND;
1464 }
1465 rc = HashDeleteEntry(&(*pHash), pEntry, ppUserData);
1466 return rc;
1467}
1468JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32 (*xStep)(SyHashEntry *, void *), void *pUserData)
1469{
1470 SyHashEntry_Pr *pEntry;
1471 sxi32 rc;
1472 sxu32 n;
1473#if defined(UNTRUST)
1474 if( INVALID_HASH(pHash) || xStep == 0){
1475 return 0;
1476 }
1477#endif
1478 pEntry = pHash->pList;
1479 for( n = 0 ; n < pHash->nEntry ; n++ ){
1480 /* Invoke the callback */
1481 rc = xStep((SyHashEntry *)pEntry, pUserData);
1482 if( rc != SXRET_OK ){
1483 return rc;
1484 }
1485 /* Point to the next entry */
1486 pEntry = pEntry->pNext;
1487 }
1488 return SXRET_OK;
1489}
1490static sxi32 HashGrowTable(SyHash *pHash)
1491{
1492 sxu32 nNewSize = pHash->nBucketSize * 2;
1493 SyHashEntry_Pr *pEntry;
1494 SyHashEntry_Pr **apNew;
1495 sxu32 n, iBucket;
1496
1497 /* Allocate a new larger table */
1498 apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(pHash->pAllocator, nNewSize * sizeof(SyHashEntry_Pr *));
1499 if( apNew == 0 ){
1500 /* Not so fatal, simply a performance hit */
1501 return SXRET_OK;
1502 }
1503 /* Zero the new table */
1504 SyZero((void *)apNew, nNewSize * sizeof(SyHashEntry_Pr *));
1505 /* Rehash all entries */
1506 for( n = 0, pEntry = pHash->pList; n < pHash->nEntry ; n++ ){
1507 pEntry->pNextCollide = pEntry->pPrevCollide = 0;
1508 /* Install in the new bucket */
1509 iBucket = pEntry->nHash & (nNewSize - 1);
1510 pEntry->pNextCollide = apNew[iBucket];
1511 if( apNew[iBucket] != 0 ){
1512 apNew[iBucket]->pPrevCollide = pEntry;
1513 }
1514 apNew[iBucket] = pEntry;
1515 /* Point to the next entry */
1516 pEntry = pEntry->pNext;
1517 }
1518 /* Release the old table and reflect the change */
1519 SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket);
1520 pHash->apBucket = apNew;
1521 pHash->nBucketSize = nNewSize;
1522 return SXRET_OK;
1523}
1524static sxi32 HashInsert(SyHash *pHash, SyHashEntry_Pr *pEntry)
1525{
1526 sxu32 iBucket = pEntry->nHash & (pHash->nBucketSize - 1);
1527 /* Insert the entry in its corresponding bcuket */
1528 pEntry->pNextCollide = pHash->apBucket[iBucket];
1529 if( pHash->apBucket[iBucket] != 0 ){
1530 pHash->apBucket[iBucket]->pPrevCollide = pEntry;
1531 }
1532 pHash->apBucket[iBucket] = pEntry;
1533 /* Link to the entry list */
1534 MACRO_LD_PUSH(pHash->pList, pEntry);
1535 if( pHash->nEntry == 0 ){
1536 pHash->pCurrent = pHash->pList;
1537 }
1538 pHash->nEntry++;
1539 return SXRET_OK;
1540}
1541JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData)
1542{
1543 SyHashEntry_Pr *pEntry;
1544 sxi32 rc;
1545#if defined(UNTRUST)
1546 if( INVALID_HASH(pHash) || pKey == 0 ){
1547 return SXERR_CORRUPT;
1548 }
1549#endif
1550 if( pHash->nEntry >= pHash->nBucketSize * SXHASH_FILL_FACTOR ){
1551 rc = HashGrowTable(&(*pHash));
1552 if( rc != SXRET_OK ){
1553 return rc;
1554 }
1555 }
1556 /* Allocate a new hash entry */
1557 pEntry = (SyHashEntry_Pr *)SyMemBackendPoolAlloc(pHash->pAllocator, sizeof(SyHashEntry_Pr));
1558 if( pEntry == 0 ){
1559 return SXERR_MEM;
1560 }
1561 /* Zero the entry */
1562 SyZero(pEntry, sizeof(SyHashEntry_Pr));
1563 pEntry->pHash = pHash;
1564 pEntry->pKey = pKey;
1565 pEntry->nKeyLen = nKeyLen;
1566 pEntry->pUserData = pUserData;
1567 pEntry->nHash = pHash->xHash(pEntry->pKey, pEntry->nKeyLen);
1568 /* Finally insert the entry in its corresponding bucket */
1569 rc = HashInsert(&(*pHash), pEntry);
1570 return rc;
1571}
1572/* SyRunTimeApi:sxutils.c */
1573JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail)
1574{
1575 const char *zCur, *zEnd;
1576#ifdef UNTRUST
1577 if( SX_EMPTY_STR(zSrc) ){
1578 return SXERR_EMPTY;
1579 }
1580#endif
1581 zEnd = &zSrc[nLen];
1582 /* Jump leading white spaces */
1583 while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisSpace(zSrc[0]) ){
1584 zSrc++;
1585 }
1586 if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){
1587 zSrc++;
1588 }
1589 zCur = zSrc;
1590 if( pReal ){
1591 *pReal = FALSE;
1592 }
1593 for(;;){
1594 if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++;
1595 if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++;
1596 if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++;
1597 if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++;
1598 };
1599 if( zSrc < zEnd && zSrc > zCur ){
1600 int c = zSrc[0];
1601 if( c == '.' ){
1602 zSrc++;
1603 if( pReal ){
1604 *pReal = TRUE;
1605 }
1606 if( pzTail ){
1607 while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){
1608 zSrc++;
1609 }
1610 if( zSrc < zEnd && (zSrc[0] == 'e' || zSrc[0] == 'E') ){
1611 zSrc++;
1612 if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){
1613 zSrc++;
1614 }
1615 while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){
1616 zSrc++;
1617 }
1618 }
1619 }
1620 }else if( c == 'e' || c == 'E' ){
1621 zSrc++;
1622 if( pReal ){
1623 *pReal = TRUE;
1624 }
1625 if( pzTail ){
1626 if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){
1627 zSrc++;
1628 }
1629 while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){
1630 zSrc++;
1631 }
1632 }
1633 }
1634 }
1635 if( pzTail ){
1636 /* Point to the non numeric part */
1637 *pzTail = zSrc;
1638 }
1639 return zSrc > zCur ? SXRET_OK /* String prefix is numeric */ : SXERR_INVALID /* Not a digit stream */;
1640}
1641#define SXINT32_MIN_STR "2147483648"
1642#define SXINT32_MAX_STR "2147483647"
1643#define SXINT64_MIN_STR "9223372036854775808"
1644#define SXINT64_MAX_STR "9223372036854775807"
1645JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest)
1646{
1647 int isNeg = FALSE;
1648 const char *zEnd;
1649 sxi32 nVal = 0;
1650 sxi16 i;
1651#if defined(UNTRUST)
1652 if( SX_EMPTY_STR(zSrc) ){
1653 if( pOutVal ){
1654 *(sxi32 *)pOutVal = 0;
1655 }
1656 return SXERR_EMPTY;
1657 }
1658#endif
1659 zEnd = &zSrc[nLen];
1660 while(zSrc < zEnd && SyisSpace(zSrc[0]) ){
1661 zSrc++;
1662 }
1663 if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){
1664 isNeg = (zSrc[0] == '-') ? TRUE :FALSE;
1665 zSrc++;
1666 }
1667 /* Skip leading zero */
1668 while(zSrc < zEnd && zSrc[0] == '0' ){
1669 zSrc++;
1670 }
1671 i = 10;
1672 if( (sxu32)(zEnd-zSrc) >= 10 ){
1673 /* Handle overflow */
1674 i = SyMemcmp(zSrc, (isNeg == TRUE) ? SXINT32_MIN_STR : SXINT32_MAX_STR, nLen) <= 0 ? 10 : 9;
1675 }
1676 for(;;){
1677 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1678 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1679 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1680 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1681 }
1682 /* Skip trailing spaces */
1683 while(zSrc < zEnd && SyisSpace(zSrc[0])){
1684 zSrc++;
1685 }
1686 if( zRest ){
1687 *zRest = (char *)zSrc;
1688 }
1689 if( pOutVal ){
1690 if( isNeg == TRUE && nVal != 0 ){
1691 nVal = -nVal;
1692 }
1693 *(sxi32 *)pOutVal = nVal;
1694 }
1695 return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX;
1696}
1697JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest)
1698{
1699 int isNeg = FALSE;
1700 const char *zEnd;
1701 sxi64 nVal;
1702 sxi16 i;
1703#if defined(UNTRUST)
1704 if( SX_EMPTY_STR(zSrc) ){
1705 if( pOutVal ){
1706 *(sxi32 *)pOutVal = 0;
1707 }
1708 return SXERR_EMPTY;
1709 }
1710#endif
1711 zEnd = &zSrc[nLen];
1712 while(zSrc < zEnd && SyisSpace(zSrc[0]) ){
1713 zSrc++;
1714 }
1715 if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){
1716 isNeg = (zSrc[0] == '-') ? TRUE :FALSE;
1717 zSrc++;
1718 }
1719 /* Skip leading zero */
1720 while(zSrc < zEnd && zSrc[0] == '0' ){
1721 zSrc++;
1722 }
1723 i = 19;
1724 if( (sxu32)(zEnd-zSrc) >= 19 ){
1725 i = SyMemcmp(zSrc, isNeg ? SXINT64_MIN_STR : SXINT64_MAX_STR, 19) <= 0 ? 19 : 18 ;
1726 }
1727 nVal = 0;
1728 for(;;){
1729 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1730 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1731 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1732 if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++;
1733 }
1734 /* Skip trailing spaces */
1735 while(zSrc < zEnd && SyisSpace(zSrc[0])){
1736 zSrc++;
1737 }
1738 if( zRest ){
1739 *zRest = (char *)zSrc;
1740 }
1741 if( pOutVal ){
1742 if( isNeg == TRUE && nVal != 0 ){
1743 nVal = -nVal;
1744 }
1745 *(sxi64 *)pOutVal = nVal;
1746 }
1747 return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX;
1748}
1749JX9_PRIVATE sxi32 SyHexToint(sxi32 c)
1750{
1751 switch(c){
1752 case '0': return 0;
1753 case '1': return 1;
1754 case '2': return 2;
1755 case '3': return 3;
1756 case '4': return 4;
1757 case '5': return 5;
1758 case '6': return 6;
1759 case '7': return 7;
1760 case '8': return 8;
1761 case '9': return 9;
1762 case 'A': case 'a': return 10;
1763 case 'B': case 'b': return 11;
1764 case 'C': case 'c': return 12;
1765 case 'D': case 'd': return 13;
1766 case 'E': case 'e': return 14;
1767 case 'F': case 'f': return 15;
1768 }
1769 return -1;
1770}
1771JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest)
1772{
1773 const char *zIn, *zEnd;
1774 int isNeg = FALSE;
1775 sxi64 nVal = 0;
1776#if defined(UNTRUST)
1777 if( SX_EMPTY_STR(zSrc) ){
1778 if( pOutVal ){
1779 *(sxi32 *)pOutVal = 0;
1780 }
1781 return SXERR_EMPTY;
1782 }
1783#endif
1784 zEnd = &zSrc[nLen];
1785 while( zSrc < zEnd && SyisSpace(zSrc[0]) ){
1786 zSrc++;
1787 }
1788 if( zSrc < zEnd && ( *zSrc == '-' || *zSrc == '+' ) ){
1789 isNeg = (zSrc[0] == '-') ? TRUE :FALSE;
1790 zSrc++;
1791 }
1792 if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'x' || zSrc[1] == 'X') ){
1793 /* Bypass hex prefix */
1794 zSrc += sizeof(char) * 2;
1795 }
1796 /* Skip leading zero */
1797 while(zSrc < zEnd && zSrc[0] == '0' ){
1798 zSrc++;
1799 }
1800 zIn = zSrc;
1801 for(;;){
1802 if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ;
1803 if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ;
1804 if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ;
1805 if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ;
1806 }
1807 while( zSrc < zEnd && SyisSpace(zSrc[0]) ){
1808 zSrc++;
1809 }
1810 if( zRest ){
1811 *zRest = zSrc;
1812 }
1813 if( pOutVal ){
1814 if( isNeg == TRUE && nVal != 0 ){
1815 nVal = -nVal;
1816 }
1817 *(sxi64 *)pOutVal = nVal;
1818 }
1819 return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX;
1820}
1821JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest)
1822{
1823 const char *zIn, *zEnd;
1824 int isNeg = FALSE;
1825 sxi64 nVal = 0;
1826 int c;
1827#if defined(UNTRUST)
1828 if( SX_EMPTY_STR(zSrc) ){
1829 if( pOutVal ){
1830 *(sxi32 *)pOutVal = 0;
1831 }
1832 return SXERR_EMPTY;
1833 }
1834#endif
1835 zEnd = &zSrc[nLen];
1836 while(zSrc < zEnd && SyisSpace(zSrc[0]) ){
1837 zSrc++;
1838 }
1839 if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){
1840 isNeg = (zSrc[0] == '-') ? TRUE :FALSE;
1841 zSrc++;
1842 }
1843 /* Skip leading zero */
1844 while(zSrc < zEnd && zSrc[0] == '0' ){
1845 zSrc++;
1846 }
1847 zIn = zSrc;
1848 for(;;){
1849 if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++;
1850 if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++;
1851 if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++;
1852 if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++;
1853 }
1854 /* Skip trailing spaces */
1855 while(zSrc < zEnd && SyisSpace(zSrc[0])){
1856 zSrc++;
1857 }
1858 if( zRest ){
1859 *zRest = zSrc;
1860 }
1861 if( pOutVal ){
1862 if( isNeg == TRUE && nVal != 0 ){
1863 nVal = -nVal;
1864 }
1865 *(sxi64 *)pOutVal = nVal;
1866 }
1867 return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX;
1868}
1869JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest)
1870{
1871 const char *zIn, *zEnd;
1872 int isNeg = FALSE;
1873 sxi64 nVal = 0;
1874 int c;
1875#if defined(UNTRUST)
1876 if( SX_EMPTY_STR(zSrc) ){
1877 if( pOutVal ){
1878 *(sxi32 *)pOutVal = 0;
1879 }
1880 return SXERR_EMPTY;
1881 }
1882#endif
1883 zEnd = &zSrc[nLen];
1884 while(zSrc < zEnd && SyisSpace(zSrc[0]) ){
1885 zSrc++;
1886 }
1887 if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){
1888 isNeg = (zSrc[0] == '-') ? TRUE :FALSE;
1889 zSrc++;
1890 }
1891 if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'b' || zSrc[1] == 'B') ){
1892 /* Bypass binary prefix */
1893 zSrc += sizeof(char) * 2;
1894 }
1895 /* Skip leading zero */
1896 while(zSrc < zEnd && zSrc[0] == '0' ){
1897 zSrc++;
1898 }
1899 zIn = zSrc;
1900 for(;;){
1901 if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++;
1902 if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++;
1903 if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++;
1904 if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++;
1905 }
1906 /* Skip trailing spaces */
1907 while(zSrc < zEnd && SyisSpace(zSrc[0])){
1908 zSrc++;
1909 }
1910 if( zRest ){
1911 *zRest = zSrc;
1912 }
1913 if( pOutVal ){
1914 if( isNeg == TRUE && nVal != 0 ){
1915 nVal = -nVal;
1916 }
1917 *(sxi64 *)pOutVal = nVal;
1918 }
1919 return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX;
1920}
1921JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest)
1922{
1923#define SXDBL_DIG 15
1924#define SXDBL_MAX_EXP 308
1925#define SXDBL_MIN_EXP_PLUS 307
1926 static const sxreal aTab[] = {
1927 10,
1928 1.0e2,
1929 1.0e4,
1930 1.0e8,
1931 1.0e16,
1932 1.0e32,
1933 1.0e64,
1934 1.0e128,
1935 1.0e256
1936 };
1937 sxu8 neg = FALSE;
1938 sxreal Val = 0.0;
1939 const char *zEnd;
1940 sxi32 Lim, exp;
1941 sxreal *p = 0;
1942#ifdef UNTRUST
1943 if( SX_EMPTY_STR(zSrc) ){
1944 if( pOutVal ){
1945 *(sxreal *)pOutVal = 0.0;
1946 }
1947 return SXERR_EMPTY;
1948 }
1949#endif
1950 zEnd = &zSrc[nLen];
1951 while( zSrc < zEnd && SyisSpace(zSrc[0]) ){
1952 zSrc++;
1953 }
1954 if( zSrc < zEnd && (zSrc[0] == '-' || zSrc[0] == '+' ) ){
1955 neg = zSrc[0] == '-' ? TRUE : FALSE ;
1956 zSrc++;
1957 }
1958 Lim = SXDBL_DIG ;
1959 for(;;){
1960 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim;
1961 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim;
1962 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim;
1963 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim;
1964 }
1965 if( zSrc < zEnd && ( zSrc[0] == '.' || zSrc[0] == ',' ) ){
1966 sxreal dec = 1.0;
1967 zSrc++;
1968 for(;;){
1969 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim;
1970 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim;
1971 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim;
1972 if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim;
1973 }
1974 Val /= dec;
1975 }
1976 if( neg == TRUE && Val != 0.0 ) {
1977 Val = -Val ;
1978 }
1979 if( Lim <= 0 ){
1980 /* jump overflow digit */
1981 while( zSrc < zEnd ){
1982 if( zSrc[0] == 'e' || zSrc[0] == 'E' ){
1983 break;
1984 }
1985 zSrc++;
1986 }
1987 }
1988 neg = FALSE;
1989 if( zSrc < zEnd && ( zSrc[0] == 'e' || zSrc[0] == 'E' ) ){
1990 zSrc++;
1991 if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+') ){
1992 neg = zSrc[0] == '-' ? TRUE : FALSE ;
1993 zSrc++;
1994 }
1995 exp = 0;
1996 while( zSrc < zEnd && SyisDigit(zSrc[0]) && exp < SXDBL_MAX_EXP ){
1997 exp = exp * 10 + (zSrc[0] - '0');
1998 zSrc++;
1999 }
2000 if( neg ){
2001 if( exp > SXDBL_MIN_EXP_PLUS ) exp = SXDBL_MIN_EXP_PLUS ;
2002 }else if ( exp > SXDBL_MAX_EXP ){
2003 exp = SXDBL_MAX_EXP;
2004 }
2005 for( p = (sxreal *)aTab ; exp ; exp >>= 1 , p++ ){
2006 if( exp & 01 ){
2007 if( neg ){
2008 Val /= *p ;
2009 }else{
2010 Val *= *p;
2011 }
2012 }
2013 }
2014 }
2015 while( zSrc < zEnd && SyisSpace(zSrc[0]) ){
2016 zSrc++;
2017 }
2018 if( zRest ){
2019 *zRest = zSrc;
2020 }
2021 if( pOutVal ){
2022 *(sxreal *)pOutVal = Val;
2023 }
2024 return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX;
2025}
2026/* SyRunTimeApi:sxlib.c */
2027JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen)
2028{
2029 register unsigned char *zIn = (unsigned char *)pSrc;
2030 unsigned char *zEnd;
2031 sxu32 nH = 5381;
2032 zEnd = &zIn[nLen];
2033 for(;;){
2034 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2035 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2036 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2037 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2038 }
2039 return nH;
2040}
2041#ifndef JX9_DISABLE_BUILTIN_FUNC
2042JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData)
2043{
2044 static const unsigned char zBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2045 unsigned char *zIn = (unsigned char *)zSrc;
2046 unsigned char z64[4];
2047 sxu32 i;
2048 sxi32 rc;
2049#if defined(UNTRUST)
2050 if( SX_EMPTY_STR(zSrc) || xConsumer == 0){
2051 return SXERR_EMPTY;
2052 }
2053#endif
2054 for(i = 0; i + 2 < nLen; i += 3){
2055 z64[0] = zBase64[(zIn[i] >> 2) & 0x3F];
2056 z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F];
2057 z64[2] = zBase64[( ((zIn[i+1] & 0x0F) << 2) | (zIn[i + 2] >> 6) ) & 0x3F];
2058 z64[3] = zBase64[ zIn[i + 2] & 0x3F];
2059
2060 rc = xConsumer((const void *)z64, sizeof(z64), pUserData);
2061 if( rc != SXRET_OK ){return SXERR_ABORT;}
2062
2063 }
2064 if ( i+1 < nLen ){
2065 z64[0] = zBase64[(zIn[i] >> 2) & 0x3F];
2066 z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F];
2067 z64[2] = zBase64[(zIn[i+1] & 0x0F) << 2 ];
2068 z64[3] = '=';
2069
2070 rc = xConsumer((const void *)z64, sizeof(z64), pUserData);
2071 if( rc != SXRET_OK ){return SXERR_ABORT;}
2072
2073 }else if( i < nLen ){
2074 z64[0] = zBase64[(zIn[i] >> 2) & 0x3F];
2075 z64[1] = zBase64[(zIn[i] & 0x03) << 4];
2076 z64[2] = '=';
2077 z64[3] = '=';
2078
2079 rc = xConsumer((const void *)z64, sizeof(z64), pUserData);
2080 if( rc != SXRET_OK ){return SXERR_ABORT;}
2081 }
2082
2083 return SXRET_OK;
2084}
2085JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData)
2086{
2087 static const sxu32 aBase64Trans[] = {
2088 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,
2089 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,
2090 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,
2091 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,
2092 0, 0, 0
2093 };
2094 sxu32 n, w, x, y, z;
2095 sxi32 rc;
2096 unsigned char zOut[10];
2097#if defined(UNTRUST)
2098 if( SX_EMPTY_STR(zB64) || xConsumer == 0 ){
2099 return SXERR_EMPTY;
2100 }
2101#endif
2102 while(nLen > 0 && zB64[nLen - 1] == '=' ){
2103 nLen--;
2104 }
2105 for( n = 0 ; n+3<nLen ; n += 4){
2106 w = aBase64Trans[zB64[n] & 0x7F];
2107 x = aBase64Trans[zB64[n+1] & 0x7F];
2108 y = aBase64Trans[zB64[n+2] & 0x7F];
2109 z = aBase64Trans[zB64[n+3] & 0x7F];
2110 zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03);
2111 zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F);
2112 zOut[2] = ((y<<6) & 0xC0) | (z & 0x3F);
2113
2114 rc = xConsumer((const void *)zOut, sizeof(unsigned char)*3, pUserData);
2115 if( rc != SXRET_OK ){ return SXERR_ABORT;}
2116 }
2117 if( n+2 < nLen ){
2118 w = aBase64Trans[zB64[n] & 0x7F];
2119 x = aBase64Trans[zB64[n+1] & 0x7F];
2120 y = aBase64Trans[zB64[n+2] & 0x7F];
2121
2122 zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03);
2123 zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F);
2124
2125 rc = xConsumer((const void *)zOut, sizeof(unsigned char)*2, pUserData);
2126 if( rc != SXRET_OK ){ return SXERR_ABORT;}
2127 }else if( n+1 < nLen ){
2128 w = aBase64Trans[zB64[n] & 0x7F];
2129 x = aBase64Trans[zB64[n+1] & 0x7F];
2130
2131 zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03);
2132
2133 rc = xConsumer((const void *)zOut, sizeof(unsigned char)*1, pUserData);
2134 if( rc != SXRET_OK ){ return SXERR_ABORT;}
2135 }
2136 return SXRET_OK;
2137}
2138#endif /* JX9_DISABLE_BUILTIN_FUNC */
2139#define INVALID_LEXER(LEX) ( LEX == 0 || LEX->xTokenizer == 0 )
2140JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData)
2141{
2142 SyStream *pStream;
2143#if defined (UNTRUST)
2144 if ( pLex == 0 || xTokenizer == 0 ){
2145 return SXERR_CORRUPT;
2146 }
2147#endif
2148 pLex->pTokenSet = 0;
2149 /* Initialize lexer fields */
2150 if( pSet ){
2151 if ( SySetElemSize(pSet) != sizeof(SyToken) ){
2152 return SXERR_INVALID;
2153 }
2154 pLex->pTokenSet = pSet;
2155 }
2156 pStream = &pLex->sStream;
2157 pLex->xTokenizer = xTokenizer;
2158 pLex->pUserData = pUserData;
2159
2160 pStream->nLine = 1;
2161 pStream->nIgn = 0;
2162 pStream->zText = pStream->zEnd = 0;
2163 pStream->pSet = pSet;
2164 return SXRET_OK;
2165}
2166JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp)
2167{
2168 const unsigned char *zCur;
2169 SyStream *pStream;
2170 SyToken sToken;
2171 sxi32 rc;
2172#if defined (UNTRUST)
2173 if ( INVALID_LEXER(pLex) || zInput == 0 ){
2174 return SXERR_CORRUPT;
2175 }
2176#endif
2177 pStream = &pLex->sStream;
2178 /* Point to the head of the input */
2179 pStream->zText = pStream->zInput = (const unsigned char *)zInput;
2180 /* Point to the end of the input */
2181 pStream->zEnd = &pStream->zInput[nLen];
2182 for(;;){
2183 if( pStream->zText >= pStream->zEnd ){
2184 /* End of the input reached */
2185 break;
2186 }
2187 zCur = pStream->zText;
2188 /* Call the tokenizer callback */
2189 rc = pLex->xTokenizer(pStream, &sToken, pLex->pUserData, pCtxData);
2190 if( rc != SXRET_OK && rc != SXERR_CONTINUE ){
2191 /* Tokenizer callback request an operation abort */
2192 if( rc == SXERR_ABORT ){
2193 return SXERR_ABORT;
2194 }
2195 break;
2196 }
2197 if( rc == SXERR_CONTINUE ){
2198 /* Request to ignore this token */
2199 pStream->nIgn++;
2200 }else if( pLex->pTokenSet ){
2201 /* Put the token in the set */
2202 rc = SySetPut(pLex->pTokenSet, (const void *)&sToken);
2203 if( rc != SXRET_OK ){
2204 break;
2205 }
2206 }
2207 if( zCur >= pStream->zText ){
2208 /* Automatic advance of the stream cursor */
2209 pStream->zText = &zCur[1];
2210 }
2211 }
2212 if( xSort && pLex->pTokenSet ){
2213 SyToken *aToken = (SyToken *)SySetBasePtr(pLex->pTokenSet);
2214 /* Sort the extrated tokens */
2215 if( xCmp == 0 ){
2216 /* Use a default comparison function */
2217 xCmp = SyMemcmp;
2218 }
2219 xSort(aToken, SySetUsed(pLex->pTokenSet), sizeof(SyToken), xCmp);
2220 }
2221 return SXRET_OK;
2222}
2223JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex)
2224{
2225 sxi32 rc = SXRET_OK;
2226#if defined (UNTRUST)
2227 if ( INVALID_LEXER(pLex) ){
2228 return SXERR_CORRUPT;
2229 }
2230#else
2231 SXUNUSED(pLex); /* Prevent compiler warning */
2232#endif
2233 return rc;
2234}
2235#ifndef JX9_DISABLE_BUILTIN_FUNC
2236#define SAFE_HTTP(C) (SyisAlphaNum(c) || c == '_' || c == '-' || c == '$' || c == '.' )
2237JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData)
2238{
2239 unsigned char *zIn = (unsigned char *)zSrc;
2240 unsigned char zHex[3] = { '%', 0, 0 };
2241 unsigned char zOut[2];
2242 unsigned char *zCur, *zEnd;
2243 sxi32 c;
2244 sxi32 rc;
2245#ifdef UNTRUST
2246 if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){
2247 return SXERR_EMPTY;
2248 }
2249#endif
2250 rc = SXRET_OK;
2251 zEnd = &zIn[nLen]; zCur = zIn;
2252 for(;;){
2253 if( zCur >= zEnd ){
2254 if( zCur != zIn ){
2255 rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData);
2256 }
2257 break;
2258 }
2259 c = zCur[0];
2260 if( SAFE_HTTP(c) ){
2261 zCur++; continue;
2262 }
2263 if( zCur != zIn && SXRET_OK != (rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData))){
2264 break;
2265 }
2266 if( c == ' ' ){
2267 zOut[0] = '+';
2268 rc = xConsumer((const void *)zOut, sizeof(unsigned char), pUserData);
2269 }else{
2270 zHex[1] = "0123456789ABCDEF"[(c >> 4) & 0x0F];
2271 zHex[2] = "0123456789ABCDEF"[c & 0x0F];
2272 rc = xConsumer(zHex, sizeof(zHex), pUserData);
2273 }
2274 if( SXRET_OK != rc ){
2275 break;
2276 }
2277 zIn = &zCur[1]; zCur = zIn ;
2278 }
2279 return rc == SXRET_OK ? SXRET_OK : SXERR_ABORT;
2280}
2281#endif /* JX9_DISABLE_BUILTIN_FUNC */
2282static sxi32 SyAsciiToHex(sxi32 c)
2283{
2284 if( c >= 'a' && c <= 'f' ){
2285 c += 10 - 'a';
2286 return c;
2287 }
2288 if( c >= '0' && c <= '9' ){
2289 c -= '0';
2290 return c;
2291 }
2292 if( c >= 'A' && c <= 'F') {
2293 c += 10 - 'A';
2294 return c;
2295 }
2296 return 0;
2297}
2298JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8)
2299{
2300 static const sxu8 Utf8Trans[] = {
2301 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
2302 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
2303 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
2304 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
2305 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
2306 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
2307 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
2308 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00
2309 };
2310 const char *zIn = zSrc;
2311 const char *zEnd;
2312 const char *zCur;
2313 sxu8 *zOutPtr;
2314 sxu8 zOut[10];
2315 sxi32 c, d;
2316 sxi32 rc;
2317#if defined(UNTRUST)
2318 if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){
2319 return SXERR_EMPTY;
2320 }
2321#endif
2322 rc = SXRET_OK;
2323 zEnd = &zSrc[nLen];
2324 zCur = zIn;
2325 for(;;){
2326 while(zCur < zEnd && zCur[0] != '%' && zCur[0] != '+' ){
2327 zCur++;
2328 }
2329 if( zCur != zIn ){
2330 /* Consume input */
2331 rc = xConsumer(zIn, (unsigned int)(zCur-zIn), pUserData);
2332 if( rc != SXRET_OK ){
2333 /* User consumer routine request an operation abort */
2334 break;
2335 }
2336 }
2337 if( zCur >= zEnd ){
2338 rc = SXRET_OK;
2339 break;
2340 }
2341 /* Decode unsafe HTTP characters */
2342 zOutPtr = zOut;
2343 if( zCur[0] == '+' ){
2344 *zOutPtr++ = ' ';
2345 zCur++;
2346 }else{
2347 if( &zCur[2] >= zEnd ){
2348 rc = SXERR_OVERFLOW;
2349 break;
2350 }
2351 c = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]);
2352 zCur += 3;
2353 if( c < 0x000C0 ){
2354 *zOutPtr++ = (sxu8)c;
2355 }else{
2356 c = Utf8Trans[c-0xC0];
2357 while( zCur[0] == '%' ){
2358 d = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]);
2359 if( (d&0xC0) != 0x80 ){
2360 break;
2361 }
2362 c = (c<<6) + (0x3f & d);
2363 zCur += 3;
2364 }
2365 if( bUTF8 == FALSE ){
2366 *zOutPtr++ = (sxu8)c;
2367 }else{
2368 SX_WRITE_UTF8(zOutPtr, c);
2369 }
2370 }
2371
2372 }
2373 /* Consume the decoded characters */
2374 rc = xConsumer((const void *)zOut, (unsigned int)(zOutPtr-zOut), pUserData);
2375 if( rc != SXRET_OK ){
2376 break;
2377 }
2378 /* Synchronize pointers */
2379 zIn = zCur;
2380 }
2381 return rc;
2382}
2383#ifndef JX9_DISABLE_BUILTIN_FUNC
2384static const char *zEngDay[] = {
2385 "Sunday", "Monday", "Tuesday", "Wednesday",
2386 "Thursday", "Friday", "Saturday"
2387};
2388static const char *zEngMonth[] = {
2389 "January", "February", "March", "April",
2390 "May", "June", "July", "August",
2391 "September", "October", "November", "December"
2392};
2393static const char * GetDay(sxi32 i)
2394{
2395 return zEngDay[ i % 7 ];
2396}
2397static const char * GetMonth(sxi32 i)
2398{
2399 return zEngMonth[ i % 12 ];
2400}
2401JX9_PRIVATE const char * SyTimeGetDay(sxi32 iDay)
2402{
2403 return GetDay(iDay);
2404}
2405JX9_PRIVATE const char * SyTimeGetMonth(sxi32 iMonth)
2406{
2407 return GetMonth(iMonth);
2408}
2409#endif /* JX9_DISABLE_BUILTIN_FUNC */
2410/* SyRunTimeApi: sxfmt.c */
2411#define SXFMT_BUFSIZ 1024 /* Conversion buffer size */
2412/*
2413** Conversion types fall into various categories as defined by the
2414** following enumeration.
2415*/
2416#define SXFMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */
2417#define SXFMT_FLOAT 2 /* Floating point.%f */
2418#define SXFMT_EXP 3 /* Exponentional notation.%e and %E */
2419#define SXFMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */
2420#define SXFMT_SIZE 5 /* Total number of characters processed so far.%n */
2421#define SXFMT_STRING 6 /* Strings.%s */
2422#define SXFMT_PERCENT 7 /* Percent symbol.%% */
2423#define SXFMT_CHARX 8 /* Characters.%c */
2424#define SXFMT_ERROR 9 /* Used to indicate no such conversion type */
2425/* Extension by Symisc Systems */
2426#define SXFMT_RAWSTR 13 /* %z Pointer to raw string (SyString *) */
2427#define SXFMT_UNUSED 15
2428/*
2429** Allowed values for SyFmtInfo.flags
2430*/
2431#define SXFLAG_SIGNED 0x01
2432#define SXFLAG_UNSIGNED 0x02
2433/* Allowed values for SyFmtConsumer.nType */
2434#define SXFMT_CONS_PROC 1 /* Consumer is a procedure */
2435#define SXFMT_CONS_STR 2 /* Consumer is a managed string */
2436#define SXFMT_CONS_FILE 5 /* Consumer is an open File */
2437#define SXFMT_CONS_BLOB 6 /* Consumer is a BLOB */
2438/*
2439** Each builtin conversion character (ex: the 'd' in "%d") is described
2440** by an instance of the following structure
2441*/
2442typedef struct SyFmtInfo SyFmtInfo;
2443struct SyFmtInfo
2444{
2445 char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */
2446 sxu8 base; /* The base for radix conversion */
2447 int flags; /* One or more of SXFLAG_ constants below */
2448 sxu8 type; /* Conversion paradigm */
2449 char *charset; /* The character set for conversion */
2450 char *prefix; /* Prefix on non-zero values in alt format */
2451};
2452typedef struct SyFmtConsumer SyFmtConsumer;
2453struct SyFmtConsumer
2454{
2455 sxu32 nLen; /* Total output length */
2456 sxi32 nType; /* Type of the consumer see below */
2457 sxi32 rc; /* Consumer return value;Abort processing if rc != SXRET_OK */
2458 union{
2459 struct{
2460 ProcConsumer xUserConsumer;
2461 void *pUserData;
2462 }sFunc;
2463 SyBlob *pBlob;
2464 }uConsumer;
2465};
2466#ifndef SX_OMIT_FLOATINGPOINT
2467static int getdigit(sxlongreal *val, int *cnt)
2468{
2469 sxlongreal d;
2470 int digit;
2471
2472 if( (*cnt)++ >= 16 ){
2473 return '0';
2474 }
2475 digit = (int)*val;
2476 d = digit;
2477 *val = (*val - d)*10.0;
2478 return digit + '0' ;
2479}
2480#endif /* SX_OMIT_FLOATINGPOINT */
2481/*
2482 * The following routine was taken from the SQLITE2 source tree and was
2483 * extended by Symisc Systems to fit its need.
2484 * Status: Public Domain
2485 */
2486static sxi32 InternFormat(ProcConsumer xConsumer, void *pUserData, const char *zFormat, va_list ap)
2487{
2488 /*
2489 * The following table is searched linearly, so it is good to put the most frequently
2490 * used conversion types first.
2491 */
2492static const SyFmtInfo aFmt[] = {
2493 { 'd', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 },
2494 { 's', 0, 0, SXFMT_STRING, 0, 0 },
2495 { 'c', 0, 0, SXFMT_CHARX, 0, 0 },
2496 { 'x', 16, 0, SXFMT_RADIX, "0123456789abcdef", "x0" },
2497 { 'X', 16, 0, SXFMT_RADIX, "0123456789ABCDEF", "X0" },
2498 /* -- Extensions by Symisc Systems -- */
2499 { 'z', 0, 0, SXFMT_RAWSTR, 0, 0 }, /* Pointer to a raw string (SyString *) */
2500 { 'B', 2, 0, SXFMT_RADIX, "01", "b0"},
2501 /* -- End of Extensions -- */
2502 { 'o', 8, 0, SXFMT_RADIX, "01234567", "0" },
2503 { 'u', 10, 0, SXFMT_RADIX, "0123456789", 0 },
2504#ifndef SX_OMIT_FLOATINGPOINT
2505 { 'f', 0, SXFLAG_SIGNED, SXFMT_FLOAT, 0, 0 },
2506 { 'e', 0, SXFLAG_SIGNED, SXFMT_EXP, "e", 0 },
2507 { 'E', 0, SXFLAG_SIGNED, SXFMT_EXP, "E", 0 },
2508 { 'g', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "e", 0 },
2509 { 'G', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "E", 0 },
2510#endif
2511 { 'i', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 },
2512 { 'n', 0, 0, SXFMT_SIZE, 0, 0 },
2513 { '%', 0, 0, SXFMT_PERCENT, 0, 0 },
2514 { 'p', 10, 0, SXFMT_RADIX, "0123456789", 0 }
2515};
2516 int c; /* Next character in the format string */
2517 char *bufpt; /* Pointer to the conversion buffer */
2518 int precision; /* Precision of the current field */
2519 int length; /* Length of the field */
2520 int idx; /* A general purpose loop counter */
2521 int width; /* Width of the current field */
2522 sxu8 flag_leftjustify; /* True if "-" flag is present */
2523 sxu8 flag_plussign; /* True if "+" flag is present */
2524 sxu8 flag_blanksign; /* True if " " flag is present */
2525 sxu8 flag_alternateform; /* True if "#" flag is present */
2526 sxu8 flag_zeropad; /* True if field width constant starts with zero */
2527 sxu8 flag_long; /* True if "l" flag is present */
2528 sxi64 longvalue; /* Value for integer types */
2529 const SyFmtInfo *infop; /* Pointer to the appropriate info structure */
2530 char buf[SXFMT_BUFSIZ]; /* Conversion buffer */
2531 char prefix; /* Prefix character."+" or "-" or " " or '\0'.*/
2532 sxu8 errorflag = 0; /* True if an error is encountered */
2533 sxu8 xtype; /* Conversion paradigm */
2534 char *zExtra;
2535 static char spaces[] = " ";
2536#define etSPACESIZE ((int)sizeof(spaces)-1)
2537#ifndef SX_OMIT_FLOATINGPOINT
2538 sxlongreal realvalue; /* Value for real types */
2539 int exp; /* exponent of real numbers */
2540 double rounder; /* Used for rounding floating point values */
2541 sxu8 flag_dp; /* True if decimal point should be shown */
2542 sxu8 flag_rtz; /* True if trailing zeros should be removed */
2543 sxu8 flag_exp; /* True to force display of the exponent */
2544 int nsd; /* Number of significant digits returned */
2545#endif
2546 int rc;
2547
2548 length = 0;
2549 bufpt = 0;
2550 for(; (c=(*zFormat))!=0; ++zFormat){
2551 if( c!='%' ){
2552 unsigned int amt;
2553 bufpt = (char *)zFormat;
2554 amt = 1;
2555 while( (c=(*++zFormat))!='%' && c!=0 ) amt++;
2556 rc = xConsumer((const void *)bufpt, amt, pUserData);
2557 if( rc != SXRET_OK ){
2558 return SXERR_ABORT; /* Consumer routine request an operation abort */
2559 }
2560 if( c==0 ){
2561 return errorflag > 0 ? SXERR_FORMAT : SXRET_OK;
2562 }
2563 }
2564 if( (c=(*++zFormat))==0 ){
2565 errorflag = 1;
2566 rc = xConsumer("%", sizeof("%")-1, pUserData);
2567 if( rc != SXRET_OK ){
2568 return SXERR_ABORT; /* Consumer routine request an operation abort */
2569 }
2570 return errorflag > 0 ? SXERR_FORMAT : SXRET_OK;
2571 }
2572 /* Find out what flags are present */
2573 flag_leftjustify = flag_plussign = flag_blanksign =
2574 flag_alternateform = flag_zeropad = 0;
2575 do{
2576 switch( c ){
2577 case '-': flag_leftjustify = 1; c = 0; break;
2578 case '+': flag_plussign = 1; c = 0; break;
2579 case ' ': flag_blanksign = 1; c = 0; break;
2580 case '#': flag_alternateform = 1; c = 0; break;
2581 case '0': flag_zeropad = 1; c = 0; break;
2582 default: break;
2583 }
2584 }while( c==0 && (c=(*++zFormat))!=0 );
2585 /* Get the field width */
2586 width = 0;
2587 if( c=='*' ){
2588 width = va_arg(ap, int);
2589 if( width<0 ){
2590 flag_leftjustify = 1;
2591 width = -width;
2592 }
2593 c = *++zFormat;
2594 }else{
2595 while( c>='0' && c<='9' ){
2596 width = width*10 + c - '0';
2597 c = *++zFormat;
2598 }
2599 }
2600 if( width > SXFMT_BUFSIZ-10 ){
2601 width = SXFMT_BUFSIZ-10;
2602 }
2603 /* Get the precision */
2604 precision = -1;
2605 if( c=='.' ){
2606 precision = 0;
2607 c = *++zFormat;
2608 if( c=='*' ){
2609 precision = va_arg(ap, int);
2610 if( precision<0 ) precision = -precision;
2611 c = *++zFormat;
2612 }else{
2613 while( c>='0' && c<='9' ){
2614 precision = precision*10 + c - '0';
2615 c = *++zFormat;
2616 }
2617 }
2618 }
2619 /* Get the conversion type modifier */
2620 flag_long = 0;
2621 if( c=='l' || c == 'q' /* BSD quad (expect a 64-bit integer) */ ){
2622 flag_long = (c == 'q') ? 2 : 1;
2623 c = *++zFormat;
2624 if( c == 'l' ){
2625 /* Standard printf emulation 'lld' (expect a 64bit integer) */
2626 flag_long = 2;
2627 }
2628 }
2629 /* Fetch the info entry for the field */
2630 infop = 0;
2631 xtype = SXFMT_ERROR;
2632 for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){
2633 if( c==aFmt[idx].fmttype ){
2634 infop = &aFmt[idx];
2635 xtype = infop->type;
2636 break;
2637 }
2638 }
2639 zExtra = 0;
2640
2641 /*
2642 ** At this point, variables are initialized as follows:
2643 **
2644 ** flag_alternateform TRUE if a '#' is present.
2645 ** flag_plussign TRUE if a '+' is present.
2646 ** flag_leftjustify TRUE if a '-' is present or if the
2647 ** field width was negative.
2648 ** flag_zeropad TRUE if the width began with 0.
2649 ** flag_long TRUE if the letter 'l' (ell) or 'q'(BSD quad) prefixed
2650 ** the conversion character.
2651 ** flag_blanksign TRUE if a ' ' is present.
2652 ** width The specified field width.This is
2653 ** always non-negative.Zero is the default.
2654 ** precision The specified precision.The default
2655 ** is -1.
2656 ** xtype The object of the conversion.
2657 ** infop Pointer to the appropriate info struct.
2658 */
2659 switch( xtype ){
2660 case SXFMT_RADIX:
2661 if( flag_long > 0 ){
2662 if( flag_long > 1 ){
2663 /* BSD quad: expect a 64-bit integer */
2664 longvalue = va_arg(ap, sxi64);
2665 }else{
2666 longvalue = va_arg(ap, sxlong);
2667 }
2668 }else{
2669 if( infop->flags & SXFLAG_SIGNED ){
2670 longvalue = va_arg(ap, sxi32);
2671 }else{
2672 longvalue = va_arg(ap, sxu32);
2673 }
2674 }
2675 /* Limit the precision to prevent overflowing buf[] during conversion */
2676 if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40;
2677#if 1
2678 /* For the format %#x, the value zero is printed "0" not "0x0".
2679 ** I think this is stupid.*/
2680 if( longvalue==0 ) flag_alternateform = 0;
2681#else
2682 /* More sensible: turn off the prefix for octal (to prevent "00"),
2683 ** but leave the prefix for hex.*/
2684 if( longvalue==0 && infop->base==8 ) flag_alternateform = 0;
2685#endif
2686 if( infop->flags & SXFLAG_SIGNED ){
2687 if( longvalue<0 ){
2688 longvalue = -longvalue;
2689 /* Ticket 1433-003 */
2690 if( longvalue < 0 ){
2691 /* Overflow */
2692 longvalue= 0x7FFFFFFFFFFFFFFF;
2693 }
2694 prefix = '-';
2695 }else if( flag_plussign ) prefix = '+';
2696 else if( flag_blanksign ) prefix = ' ';
2697 else prefix = 0;
2698 }else{
2699 if( longvalue<0 ){
2700 longvalue = -longvalue;
2701 /* Ticket 1433-003 */
2702 if( longvalue < 0 ){
2703 /* Overflow */
2704 longvalue= 0x7FFFFFFFFFFFFFFF;
2705 }
2706 }
2707 prefix = 0;
2708 }
2709 if( flag_zeropad && precision<width-(prefix!=0) ){
2710 precision = width-(prefix!=0);
2711 }
2712 bufpt = &buf[SXFMT_BUFSIZ-1];
2713 {
2714 register char *cset; /* Use registers for speed */
2715 register int base;
2716 cset = infop->charset;
2717 base = infop->base;
2718 do{ /* Convert to ascii */
2719 *(--bufpt) = cset[longvalue%base];
2720 longvalue = longvalue/base;
2721 }while( longvalue>0 );
2722 }
2723 length = &buf[SXFMT_BUFSIZ-1]-bufpt;
2724 for(idx=precision-length; idx>0; idx--){
2725 *(--bufpt) = '0'; /* Zero pad */
2726 }
2727 if( prefix ) *(--bufpt) = prefix; /* Add sign */
2728 if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */
2729 char *pre, x;
2730 pre = infop->prefix;
2731 if( *bufpt!=pre[0] ){
2732 for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x;
2733 }
2734 }
2735 length = &buf[SXFMT_BUFSIZ-1]-bufpt;
2736 break;
2737 case SXFMT_FLOAT:
2738 case SXFMT_EXP:
2739 case SXFMT_GENERIC:
2740#ifndef SX_OMIT_FLOATINGPOINT
2741 realvalue = va_arg(ap, double);
2742 if( precision<0 ) precision = 6; /* Set default precision */
2743 if( precision>SXFMT_BUFSIZ-40) precision = SXFMT_BUFSIZ-40;
2744 if( realvalue<0.0 ){
2745 realvalue = -realvalue;
2746 prefix = '-';
2747 }else{
2748 if( flag_plussign ) prefix = '+';
2749 else if( flag_blanksign ) prefix = ' ';
2750 else prefix = 0;
2751 }
2752 if( infop->type==SXFMT_GENERIC && precision>0 ) precision--;
2753 rounder = 0.0;
2754#if 0
2755 /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */
2756 for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1);
2757#else
2758 /* It makes more sense to use 0.5 */
2759 for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1);
2760#endif
2761 if( infop->type==SXFMT_FLOAT ) realvalue += rounder;
2762 /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
2763 exp = 0;
2764 if( realvalue>0.0 ){
2765 while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
2766 while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
2767 while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; }
2768 while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; }
2769 if( exp>350 || exp<-350 ){
2770 bufpt = "NaN";
2771 length = 3;
2772 break;
2773 }
2774 }
2775 bufpt = buf;
2776 /*
2777 ** If the field type is etGENERIC, then convert to either etEXP
2778 ** or etFLOAT, as appropriate.
2779 */
2780 flag_exp = xtype==SXFMT_EXP;
2781 if( xtype!=SXFMT_FLOAT ){
2782 realvalue += rounder;
2783 if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
2784 }
2785 if( xtype==SXFMT_GENERIC ){
2786 flag_rtz = !flag_alternateform;
2787 if( exp<-4 || exp>precision ){
2788 xtype = SXFMT_EXP;
2789 }else{
2790 precision = precision - exp;
2791 xtype = SXFMT_FLOAT;
2792 }
2793 }else{
2794 flag_rtz = 0;
2795 }
2796 /*
2797 ** The "exp+precision" test causes output to be of type etEXP if
2798 ** the precision is too large to fit in buf[].
2799 */
2800 nsd = 0;
2801 if( xtype==SXFMT_FLOAT && exp+precision<SXFMT_BUFSIZ-30 ){
2802 flag_dp = (precision>0 || flag_alternateform);
2803 if( prefix ) *(bufpt++) = prefix; /* Sign */
2804 if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */
2805 else for(; exp>=0; exp--) *(bufpt++) = (char)getdigit(&realvalue, &nsd);
2806 if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */
2807 for(exp++; exp<0 && precision>0; precision--, exp++){
2808 *(bufpt++) = '0';
2809 }
2810 while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd);
2811 *(bufpt--) = 0; /* Null terminate */
2812 if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */
2813 while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
2814 if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
2815 }
2816 bufpt++; /* point to next free slot */
2817 }else{ /* etEXP or etGENERIC */
2818 flag_dp = (precision>0 || flag_alternateform);
2819 if( prefix ) *(bufpt++) = prefix; /* Sign */
2820 *(bufpt++) = (char)getdigit(&realvalue, &nsd); /* First digit */
2821 if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */
2822 while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd);
2823 bufpt--; /* point to last digit */
2824 if( flag_rtz && flag_dp ){ /* Remove tail zeros */
2825 while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0;
2826 if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0;
2827 }
2828 bufpt++; /* point to next free slot */
2829 if( exp || flag_exp ){
2830 *(bufpt++) = infop->charset[0];
2831 if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */
2832 else { *(bufpt++) = '+'; }
2833 if( exp>=100 ){
2834 *(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */
2835 exp %= 100;
2836 }
2837 *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */
2838 *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */
2839 }
2840 }
2841 /* The converted number is in buf[] and zero terminated.Output it.
2842 ** Note that the number is in the usual order, not reversed as with
2843 ** integer conversions.*/
2844 length = bufpt-buf;
2845 bufpt = buf;
2846
2847 /* Special case: Add leading zeros if the flag_zeropad flag is
2848 ** set and we are not left justified */
2849 if( flag_zeropad && !flag_leftjustify && length < width){
2850 int i;
2851 int nPad = width - length;
2852 for(i=width; i>=nPad; i--){
2853 bufpt[i] = bufpt[i-nPad];
2854 }
2855 i = prefix!=0;
2856 while( nPad-- ) bufpt[i++] = '0';
2857 length = width;
2858 }
2859#else
2860 bufpt = " ";
2861 length = (int)sizeof(" ") - 1;
2862#endif /* SX_OMIT_FLOATINGPOINT */
2863 break;
2864 case SXFMT_SIZE:{
2865 int *pSize = va_arg(ap, int *);
2866 *pSize = ((SyFmtConsumer *)pUserData)->nLen;
2867 length = width = 0;
2868 }
2869 break;
2870 case SXFMT_PERCENT:
2871 buf[0] = '%';
2872 bufpt = buf;
2873 length = 1;
2874 break;
2875 case SXFMT_CHARX:
2876 c = va_arg(ap, int);
2877 buf[0] = (char)c;
2878 /* Limit the precision to prevent overflowing buf[] during conversion */
2879 if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40;
2880 if( precision>=0 ){
2881 for(idx=1; idx<precision; idx++) buf[idx] = (char)c;
2882 length = precision;
2883 }else{
2884 length =1;
2885 }
2886 bufpt = buf;
2887 break;
2888 case SXFMT_STRING:
2889 bufpt = va_arg(ap, char*);
2890 if( bufpt==0 ){
2891 bufpt = " ";
2892 length = (int)sizeof(" ")-1;
2893 break;
2894 }
2895 length = precision;
2896 if( precision < 0 ){
2897 /* Symisc extension */
2898 length = (int)SyStrlen(bufpt);
2899 }
2900 if( precision>=0 && precision<length ) length = precision;
2901 break;
2902 case SXFMT_RAWSTR:{
2903 /* Symisc extension */
2904 SyString *pStr = va_arg(ap, SyString *);
2905 if( pStr == 0 || pStr->zString == 0 ){
2906 bufpt = " ";
2907 length = (int)sizeof(char);
2908 break;
2909 }
2910 bufpt = (char *)pStr->zString;
2911 length = (int)pStr->nByte;
2912 break;
2913 }
2914 case SXFMT_ERROR:
2915 buf[0] = '?';
2916 bufpt = buf;
2917 length = (int)sizeof(char);
2918 if( c==0 ) zFormat--;
2919 break;
2920 }/* End switch over the format type */
2921 /*
2922 ** The text of the conversion is pointed to by "bufpt" and is
2923 ** "length" characters long.The field width is "width".Do
2924 ** the output.
2925 */
2926 if( !flag_leftjustify ){
2927 register int nspace;
2928 nspace = width-length;
2929 if( nspace>0 ){
2930 while( nspace>=etSPACESIZE ){
2931 rc = xConsumer(spaces, etSPACESIZE, pUserData);
2932 if( rc != SXRET_OK ){
2933 return SXERR_ABORT; /* Consumer routine request an operation abort */
2934 }
2935 nspace -= etSPACESIZE;
2936 }
2937 if( nspace>0 ){
2938 rc = xConsumer(spaces, (unsigned int)nspace, pUserData);
2939 if( rc != SXRET_OK ){
2940 return SXERR_ABORT; /* Consumer routine request an operation abort */
2941 }
2942 }
2943 }
2944 }
2945 if( length>0 ){
2946 rc = xConsumer(bufpt, (unsigned int)length, pUserData);
2947 if( rc != SXRET_OK ){
2948 return SXERR_ABORT; /* Consumer routine request an operation abort */
2949 }
2950 }
2951 if( flag_leftjustify ){
2952 register int nspace;
2953 nspace = width-length;
2954 if( nspace>0 ){
2955 while( nspace>=etSPACESIZE ){
2956 rc = xConsumer(spaces, etSPACESIZE, pUserData);
2957 if( rc != SXRET_OK ){
2958 return SXERR_ABORT; /* Consumer routine request an operation abort */
2959 }
2960 nspace -= etSPACESIZE;
2961 }
2962 if( nspace>0 ){
2963 rc = xConsumer(spaces, (unsigned int)nspace, pUserData);
2964 if( rc != SXRET_OK ){
2965 return SXERR_ABORT; /* Consumer routine request an operation abort */
2966 }
2967 }
2968 }
2969 }
2970 }/* End for loop over the format string */
2971 return errorflag ? SXERR_FORMAT : SXRET_OK;
2972}
2973static sxi32 FormatConsumer(const void *pSrc, unsigned int nLen, void *pData)
2974{
2975 SyFmtConsumer *pConsumer = (SyFmtConsumer *)pData;
2976 sxi32 rc = SXERR_ABORT;
2977 switch(pConsumer->nType){
2978 case SXFMT_CONS_PROC:
2979 /* User callback */
2980 rc = pConsumer->uConsumer.sFunc.xUserConsumer(pSrc, nLen, pConsumer->uConsumer.sFunc.pUserData);
2981 break;
2982 case SXFMT_CONS_BLOB:
2983 /* Blob consumer */
2984 rc = SyBlobAppend(pConsumer->uConsumer.pBlob, pSrc, (sxu32)nLen);
2985 break;
2986 default:
2987 /* Unknown consumer */
2988 break;
2989 }
2990 /* Update total number of bytes consumed so far */
2991 pConsumer->nLen += nLen;
2992 pConsumer->rc = rc;
2993 return rc;
2994}
2995static sxi32 FormatMount(sxi32 nType, void *pConsumer, ProcConsumer xUserCons, void *pUserData, sxu32 *pOutLen, const char *zFormat, va_list ap)
2996{
2997 SyFmtConsumer sCons;
2998 sCons.nType = nType;
2999 sCons.rc = SXRET_OK;
3000 sCons.nLen = 0;
3001 if( pOutLen ){
3002 *pOutLen = 0;
3003 }
3004 switch(nType){
3005 case SXFMT_CONS_PROC:
3006#if defined(UNTRUST)
3007 if( xUserCons == 0 ){
3008 return SXERR_EMPTY;
3009 }
3010#endif
3011 sCons.uConsumer.sFunc.xUserConsumer = xUserCons;
3012 sCons.uConsumer.sFunc.pUserData = pUserData;
3013 break;
3014 case SXFMT_CONS_BLOB:
3015 sCons.uConsumer.pBlob = (SyBlob *)pConsumer;
3016 break;
3017 default:
3018 return SXERR_UNKNOWN;
3019 }
3020 InternFormat(FormatConsumer, &sCons, zFormat, ap);
3021 if( pOutLen ){
3022 *pOutLen = sCons.nLen;
3023 }
3024 return sCons.rc;
3025}
3026JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...)
3027{
3028 va_list ap;
3029 sxi32 rc;
3030#if defined(UNTRUST)
3031 if( SX_EMPTY_STR(zFormat) ){
3032 return SXERR_EMPTY;
3033 }
3034#endif
3035 va_start(ap, zFormat);
3036 rc = FormatMount(SXFMT_CONS_PROC, 0, xConsumer, pData, 0, zFormat, ap);
3037 va_end(ap);
3038 return rc;
3039}
3040JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...)
3041{
3042 va_list ap;
3043 sxu32 n;
3044#if defined(UNTRUST)
3045 if( SX_EMPTY_STR(zFormat) ){
3046 return 0;
3047 }
3048#endif
3049 va_start(ap, zFormat);
3050 FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap);
3051 va_end(ap);
3052 return n;
3053}
3054JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap)
3055{
3056 sxu32 n = 0; /* cc warning */
3057#if defined(UNTRUST)
3058 if( SX_EMPTY_STR(zFormat) ){
3059 return 0;
3060 }
3061#endif
3062 FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap);
3063 return n;
3064}
3065JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...)
3066{
3067 SyBlob sBlob;
3068 va_list ap;
3069 sxu32 n;
3070#if defined(UNTRUST)
3071 if( SX_EMPTY_STR(zFormat) ){
3072 return 0;
3073 }
3074#endif
3075 if( SXRET_OK != SyBlobInitFromBuf(&sBlob, zBuf, nLen - 1) ){
3076 return 0;
3077 }
3078 va_start(ap, zFormat);
3079 FormatMount(SXFMT_CONS_BLOB, &sBlob, 0, 0, 0, zFormat, ap);
3080 va_end(ap);
3081 n = SyBlobLength(&sBlob);
3082 /* Append the null terminator */
3083 sBlob.mByte++;
3084 SyBlobAppend(&sBlob, "\0", sizeof(char));
3085 return n;
3086}
3087#ifndef JX9_DISABLE_BUILTIN_FUNC
3088/*
3089 * Zip File Format:
3090 *
3091 * Byte order: Little-endian
3092 *
3093 * [Local file header + Compressed data [+ Extended local header]?]*
3094 * [Central directory]*
3095 * [End of central directory record]
3096 *
3097 * Local file header:*
3098 * Offset Length Contents
3099 * 0 4 bytes Local file header signature (0x04034b50)
3100 * 4 2 bytes Version needed to extract
3101 * 6 2 bytes General purpose bit flag
3102 * 8 2 bytes Compression method
3103 * 10 2 bytes Last mod file time
3104 * 12 2 bytes Last mod file date
3105 * 14 4 bytes CRC-32
3106 * 18 4 bytes Compressed size (n)
3107 * 22 4 bytes Uncompressed size
3108 * 26 2 bytes Filename length (f)
3109 * 28 2 bytes Extra field length (e)
3110 * 30 (f)bytes Filename
3111 * (e)bytes Extra field
3112 * (n)bytes Compressed data
3113 *
3114 * Extended local header:*
3115 * Offset Length Contents
3116 * 0 4 bytes Extended Local file header signature (0x08074b50)
3117 * 4 4 bytes CRC-32
3118 * 8 4 bytes Compressed size
3119 * 12 4 bytes Uncompressed size
3120 *
3121 * Extra field:?(if any)
3122 * Offset Length Contents
3123 * 0 2 bytes Header ID (0x001 until 0xfb4a) see extended appnote from Info-zip
3124 * 2 2 bytes Data size (g)
3125 * (g) bytes (g) bytes of extra field
3126 *
3127 * Central directory:*
3128 * Offset Length Contents
3129 * 0 4 bytes Central file header signature (0x02014b50)
3130 * 4 2 bytes Version made by
3131 * 6 2 bytes Version needed to extract
3132 * 8 2 bytes General purpose bit flag
3133 * 10 2 bytes Compression method
3134 * 12 2 bytes Last mod file time
3135 * 14 2 bytes Last mod file date
3136 * 16 4 bytes CRC-32
3137 * 20 4 bytes Compressed size
3138 * 24 4 bytes Uncompressed size
3139 * 28 2 bytes Filename length (f)
3140 * 30 2 bytes Extra field length (e)
3141 * 32 2 bytes File comment length (c)
3142 * 34 2 bytes Disk number start
3143 * 36 2 bytes Internal file attributes
3144 * 38 4 bytes External file attributes
3145 * 42 4 bytes Relative offset of local header
3146 * 46 (f)bytes Filename
3147 * (e)bytes Extra field
3148 * (c)bytes File comment
3149 *
3150 * End of central directory record:
3151 * Offset Length Contents
3152 * 0 4 bytes End of central dir signature (0x06054b50)
3153 * 4 2 bytes Number of this disk
3154 * 6 2 bytes Number of the disk with the start of the central directory
3155 * 8 2 bytes Total number of entries in the central dir on this disk
3156 * 10 2 bytes Total number of entries in the central dir
3157 * 12 4 bytes Size of the central directory
3158 * 16 4 bytes Offset of start of central directory with respect to the starting disk number
3159 * 20 2 bytes zipfile comment length (c)
3160 * 22 (c)bytes zipfile comment
3161 *
3162 * compression method: (2 bytes)
3163 * 0 - The file is stored (no compression)
3164 * 1 - The file is Shrunk
3165 * 2 - The file is Reduced with compression factor 1
3166 * 3 - The file is Reduced with compression factor 2
3167 * 4 - The file is Reduced with compression factor 3
3168 * 5 - The file is Reduced with compression factor 4
3169 * 6 - The file is Imploded
3170 * 7 - Reserved for Tokenizing compression algorithm
3171 * 8 - The file is Deflated
3172 */
3173
3174#define SXMAKE_ZIP_WORKBUF (SXU16_HIGH/2) /* 32KB Initial working buffer size */
3175#define SXMAKE_ZIP_EXTRACT_VER 0x000a /* Version needed to extract */
3176#define SXMAKE_ZIP_VER 0x003 /* Version made by */
3177
3178#define SXZIP_CENTRAL_MAGIC 0x02014b50
3179#define SXZIP_END_CENTRAL_MAGIC 0x06054b50
3180#define SXZIP_LOCAL_MAGIC 0x04034b50
3181/*#define SXZIP_CRC32_START 0xdebb20e3*/
3182
3183#define SXZIP_LOCAL_HDRSZ 30 /* Local header size */
3184#define SXZIP_LOCAL_EXT_HDRZ 16 /* Extended local header(footer) size */
3185#define SXZIP_CENTRAL_HDRSZ 46 /* Central directory header size */
3186#define SXZIP_END_CENTRAL_HDRSZ 22 /* End of central directory header size */
3187
3188#define SXARCHIVE_HASH_SIZE 64 /* Starting hash table size(MUST BE POWER OF 2)*/
3189static sxi32 SyLittleEndianUnpack32(sxu32 *uNB, const unsigned char *buf, sxu32 Len)
3190{
3191 if( Len < sizeof(sxu32) ){
3192 return SXERR_SHORT;
3193 }
3194 *uNB = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24);
3195 return SXRET_OK;
3196}
3197static sxi32 SyLittleEndianUnpack16(sxu16 *pOut, const unsigned char *zBuf, sxu32 nLen)
3198{
3199 if( nLen < sizeof(sxu16) ){
3200 return SXERR_SHORT;
3201 }
3202 *pOut = zBuf[0] + (zBuf[1] <<8);
3203
3204 return SXRET_OK;
3205}
3206/*
3207 * Archive hashtable manager
3208 */
3209static sxi32 ArchiveHashGetEntry(SyArchive *pArch, const char *zName, sxu32 nLen, SyArchiveEntry **ppEntry)
3210{
3211 SyArchiveEntry *pBucketEntry;
3212 SyString sEntry;
3213 sxu32 nHash;
3214
3215 nHash = pArch->xHash(zName, nLen);
3216 pBucketEntry = pArch->apHash[nHash & (pArch->nSize - 1)];
3217
3218 SyStringInitFromBuf(&sEntry, zName, nLen);
3219
3220 for(;;){
3221 if( pBucketEntry == 0 ){
3222 break;
3223 }
3224 if( nHash == pBucketEntry->nHash && pArch->xCmp(&sEntry, &pBucketEntry->sFileName) == 0 ){
3225 if( ppEntry ){
3226 *ppEntry = pBucketEntry;
3227 }
3228 return SXRET_OK;
3229 }
3230 pBucketEntry = pBucketEntry->pNextHash;
3231 }
3232 return SXERR_NOTFOUND;
3233}
3234static void ArchiveHashBucketInstall(SyArchiveEntry **apTable, sxu32 nBucket, SyArchiveEntry *pEntry)
3235{
3236 pEntry->pNextHash = apTable[nBucket];
3237 if( apTable[nBucket] != 0 ){
3238 apTable[nBucket]->pPrevHash = pEntry;
3239 }
3240 apTable[nBucket] = pEntry;
3241}
3242static sxi32 ArchiveHashGrowTable(SyArchive *pArch)
3243{
3244 sxu32 nNewSize = pArch->nSize * 2;
3245 SyArchiveEntry **apNew;
3246 SyArchiveEntry *pEntry;
3247 sxu32 n;
3248
3249 /* Allocate a new table */
3250 apNew = (SyArchiveEntry **)SyMemBackendAlloc(pArch->pAllocator, nNewSize * sizeof(SyArchiveEntry *));
3251 if( apNew == 0 ){
3252 return SXRET_OK; /* Not so fatal, simply a performance hit */
3253 }
3254 SyZero(apNew, nNewSize * sizeof(SyArchiveEntry *));
3255 /* Rehash old entries */
3256 for( n = 0 , pEntry = pArch->pList ; n < pArch->nLoaded ; n++ , pEntry = pEntry->pNext ){
3257 pEntry->pNextHash = pEntry->pPrevHash = 0;
3258 ArchiveHashBucketInstall(apNew, pEntry->nHash & (nNewSize - 1), pEntry);
3259 }
3260 /* Release the old table */
3261 SyMemBackendFree(pArch->pAllocator, pArch->apHash);
3262 pArch->apHash = apNew;
3263 pArch->nSize = nNewSize;
3264
3265 return SXRET_OK;
3266}
3267static sxi32 ArchiveHashInstallEntry(SyArchive *pArch, SyArchiveEntry *pEntry)
3268{
3269 if( pArch->nLoaded > pArch->nSize * 3 ){
3270 ArchiveHashGrowTable(&(*pArch));
3271 }
3272 pEntry->nHash = pArch->xHash(SyStringData(&pEntry->sFileName), SyStringLength(&pEntry->sFileName));
3273 /* Install the entry in its bucket */
3274 ArchiveHashBucketInstall(pArch->apHash, pEntry->nHash & (pArch->nSize - 1), pEntry);
3275 MACRO_LD_PUSH(pArch->pList, pEntry);
3276 pArch->nLoaded++;
3277
3278 return SXRET_OK;
3279}
3280 /*
3281 * Parse the End of central directory and report status
3282 */
3283 static sxi32 ParseEndOfCentralDirectory(SyArchive *pArch, const unsigned char *zBuf)
3284 {
3285 sxu32 nMagic = 0; /* cc -O6 warning */
3286 sxi32 rc;
3287
3288 /* Sanity check */
3289 rc = SyLittleEndianUnpack32(&nMagic, zBuf, sizeof(sxu32));
3290 if( /* rc != SXRET_OK || */nMagic != SXZIP_END_CENTRAL_MAGIC ){
3291 return SXERR_CORRUPT;
3292 }
3293 /* # of entries */
3294 rc = SyLittleEndianUnpack16((sxu16 *)&pArch->nEntry, &zBuf[8], sizeof(sxu16));
3295 if( /* rc != SXRET_OK || */ pArch->nEntry > SXI16_HIGH /* SXU16_HIGH */ ){
3296 return SXERR_CORRUPT;
3297 }
3298 /* Size of central directory */
3299 rc = SyLittleEndianUnpack32(&pArch->nCentralSize, &zBuf[12], sizeof(sxu32));
3300 if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){
3301 return SXERR_CORRUPT;
3302 }
3303 /* Starting offset of central directory */
3304 rc = SyLittleEndianUnpack32(&pArch->nCentralOfft, &zBuf[16], sizeof(sxu32));
3305 if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){
3306 return SXERR_CORRUPT;
3307 }
3308
3309 return SXRET_OK;
3310 }
3311 /*
3312 * Fill the zip entry with the appropriate information from the central directory
3313 */
3314static sxi32 GetCentralDirectoryEntry(SyArchive *pArch, SyArchiveEntry *pEntry, const unsigned char *zCentral, sxu32 *pNextOffset)
3315 {
3316 SyString *pName = &pEntry->sFileName; /* File name */
3317 sxu16 nDosDate, nDosTime;
3318 sxu16 nComment = 0 ;
3319 sxu32 nMagic = 0; /* cc -O6 warning */
3320 sxi32 rc;
3321 nDosDate = nDosTime = 0; /* cc -O6 warning */
3322 SXUNUSED(pArch);
3323 /* Sanity check */
3324 rc = SyLittleEndianUnpack32(&nMagic, zCentral, sizeof(sxu32));
3325 if( /* rc != SXRET_OK || */ nMagic != SXZIP_CENTRAL_MAGIC ){
3326 rc = SXERR_CORRUPT;
3327 /*
3328 * Try to recover by examing the next central directory record.
3329 * Dont worry here, there is no risk of an infinite loop since
3330 * the buffer size is delimited.
3331 */
3332
3333 /* pName->nByte = 0; nComment = 0; pName->nExtra = 0 */
3334 goto update;
3335 }
3336 /*
3337 * entry name length
3338 */
3339 SyLittleEndianUnpack16((sxu16 *)&pName->nByte, &zCentral[28], sizeof(sxu16));
3340 if( pName->nByte > SXI16_HIGH /* SXU16_HIGH */){
3341 rc = SXERR_BIG;
3342 goto update;
3343 }
3344 /* Extra information */
3345 SyLittleEndianUnpack16(&pEntry->nExtra, &zCentral[30], sizeof(sxu16));
3346 /* Comment length */
3347 SyLittleEndianUnpack16(&nComment, &zCentral[32], sizeof(sxu16));
3348 /* Compression method 0 == stored / 8 == deflated */
3349 rc = SyLittleEndianUnpack16(&pEntry->nComprMeth, &zCentral[10], sizeof(sxu16));
3350 /* DOS Timestamp */
3351 SyLittleEndianUnpack16(&nDosTime, &zCentral[12], sizeof(sxu16));
3352 SyLittleEndianUnpack16(&nDosDate, &zCentral[14], sizeof(sxu16));
3353 SyDosTimeFormat((nDosDate << 16 | nDosTime), &pEntry->sFmt);
3354 /* Little hack to fix month index */
3355 pEntry->sFmt.tm_mon--;
3356 /* CRC32 */
3357 rc = SyLittleEndianUnpack32(&pEntry->nCrc, &zCentral[16], sizeof(sxu32));
3358 /* Content size before compression */
3359 rc = SyLittleEndianUnpack32(&pEntry->nByte, &zCentral[24], sizeof(sxu32));
3360 if( pEntry->nByte > SXI32_HIGH ){
3361 rc = SXERR_BIG;
3362 goto update;
3363 }
3364 /*
3365 * Content size after compression.
3366 * Note that if the file is stored pEntry->nByte should be equal to pEntry->nByteCompr
3367 */
3368 rc = SyLittleEndianUnpack32(&pEntry->nByteCompr, &zCentral[20], sizeof(sxu32));
3369 if( pEntry->nByteCompr > SXI32_HIGH ){
3370 rc = SXERR_BIG;
3371 goto update;
3372 }
3373 /* Finally grab the contents offset */
3374 SyLittleEndianUnpack32(&pEntry->nOfft, &zCentral[42], sizeof(sxu32));
3375 if( pEntry->nOfft > SXI32_HIGH ){
3376 rc = SXERR_BIG;
3377 goto update;
3378 }
3379 rc = SXRET_OK;
3380update:
3381 /* Update the offset to point to the next central directory record */
3382 *pNextOffset = SXZIP_CENTRAL_HDRSZ + pName->nByte + pEntry->nExtra + nComment;
3383 return rc; /* Report failure or success */
3384}
3385static sxi32 ZipFixOffset(SyArchiveEntry *pEntry, void *pSrc)
3386{
3387 sxu16 nExtra, nNameLen;
3388 unsigned char *zHdr;
3389 nExtra = nNameLen = 0;
3390 zHdr = (unsigned char *)pSrc;
3391 zHdr = &zHdr[pEntry->nOfft];
3392 if( SyMemcmp(zHdr, "PK\003\004", sizeof(sxu32)) != 0 ){
3393 return SXERR_CORRUPT;
3394 }
3395 SyLittleEndianUnpack16(&nNameLen, &zHdr[26], sizeof(sxu16));
3396 SyLittleEndianUnpack16(&nExtra, &zHdr[28], sizeof(sxu16));
3397 /* Fix contents offset */
3398 pEntry->nOfft += SXZIP_LOCAL_HDRSZ + nExtra + nNameLen;
3399 return SXRET_OK;
3400}
3401/*
3402 * Extract all valid entries from the central directory
3403 */
3404static sxi32 ZipExtract(SyArchive *pArch, const unsigned char *zCentral, sxu32 nLen, void *pSrc)
3405{
3406 SyArchiveEntry *pEntry, *pDup;
3407 const unsigned char *zEnd ; /* End of central directory */
3408 sxu32 nIncr, nOfft; /* Central Offset */
3409 SyString *pName; /* Entry name */
3410 char *zName;
3411 sxi32 rc;
3412
3413 nOfft = nIncr = 0;
3414 zEnd = &zCentral[nLen];
3415
3416 for(;;){
3417 if( &zCentral[nOfft] >= zEnd ){
3418 break;
3419 }
3420 /* Add a new entry */
3421 pEntry = (SyArchiveEntry *)SyMemBackendPoolAlloc(pArch->pAllocator, sizeof(SyArchiveEntry));
3422 if( pEntry == 0 ){
3423 break;
3424 }
3425 SyZero(pEntry, sizeof(SyArchiveEntry));
3426 pEntry->nMagic = SXARCH_MAGIC;
3427 nIncr = 0;
3428 rc = GetCentralDirectoryEntry(&(*pArch), pEntry, &zCentral[nOfft], &nIncr);
3429 if( rc == SXRET_OK ){
3430 /* Fix the starting record offset so we can access entry contents correctly */
3431 rc = ZipFixOffset(pEntry, pSrc);
3432 }
3433 if(rc != SXRET_OK ){
3434 sxu32 nJmp = 0;
3435 SyMemBackendPoolFree(pArch->pAllocator, pEntry);
3436 /* Try to recover by brute-forcing for a valid central directory record */
3437 if( SXRET_OK == SyBlobSearch((const void *)&zCentral[nOfft + nIncr], (sxu32)(zEnd - &zCentral[nOfft + nIncr]),
3438 (const void *)"PK\001\002", sizeof(sxu32), &nJmp)){
3439 nOfft += nIncr + nJmp; /* Check next entry */
3440 continue;
3441 }
3442 break; /* Giving up, archive is hopelessly corrupted */
3443 }
3444 pName = &pEntry->sFileName;
3445 pName->zString = (const char *)&zCentral[nOfft + SXZIP_CENTRAL_HDRSZ];
3446 if( pName->nByte <= 0 || ( pEntry->nByte <= 0 && pName->zString[pName->nByte - 1] != '/') ){
3447 /* Ignore zero length records (except folders) and records without names */
3448 SyMemBackendPoolFree(pArch->pAllocator, pEntry);
3449 nOfft += nIncr; /* Check next entry */
3450 continue;
3451 }
3452 zName = SyMemBackendStrDup(pArch->pAllocator, pName->zString, pName->nByte);
3453 if( zName == 0 ){
3454 SyMemBackendPoolFree(pArch->pAllocator, pEntry);
3455 nOfft += nIncr; /* Check next entry */
3456 continue;
3457 }
3458 pName->zString = (const char *)zName;
3459 /* Check for duplicates */
3460 rc = ArchiveHashGetEntry(&(*pArch), pName->zString, pName->nByte, &pDup);
3461 if( rc == SXRET_OK ){
3462 /* Another entry with the same name exists ; link them together */
3463 pEntry->pNextName = pDup->pNextName;
3464 pDup->pNextName = pEntry;
3465 pDup->nDup++;
3466 }else{
3467 /* Insert in hashtable */
3468 ArchiveHashInstallEntry(pArch, pEntry);
3469 }
3470 nOfft += nIncr; /* Check next record */
3471 }
3472 pArch->pCursor = pArch->pList;
3473
3474 return pArch->nLoaded > 0 ? SXRET_OK : SXERR_EMPTY;
3475}
3476JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen)
3477 {
3478 const unsigned char *zCentral, *zEnd;
3479 sxi32 rc;
3480#if defined(UNTRUST)
3481 if( SXARCH_INVALID(pArch) || zBuf == 0 ){
3482 return SXERR_INVALID;
3483 }
3484#endif
3485 /* The miminal size of a zip archive:
3486 * LOCAL_HDR_SZ + CENTRAL_HDR_SZ + END_OF_CENTRAL_HDR_SZ
3487 * 30 46 22
3488 */
3489 if( nLen < SXZIP_LOCAL_HDRSZ + SXZIP_CENTRAL_HDRSZ + SXZIP_END_CENTRAL_HDRSZ ){
3490 return SXERR_CORRUPT; /* Don't bother processing return immediately */
3491 }
3492
3493 zEnd = (unsigned char *)&zBuf[nLen - SXZIP_END_CENTRAL_HDRSZ];
3494 /* Find the end of central directory */
3495 while( ((sxu32)((unsigned char *)&zBuf[nLen] - zEnd) < (SXZIP_END_CENTRAL_HDRSZ + SXI16_HIGH)) &&
3496 zEnd > (unsigned char *)zBuf && SyMemcmp(zEnd, "PK\005\006", sizeof(sxu32)) != 0 ){
3497 zEnd--;
3498 }
3499 /* Parse the end of central directory */
3500 rc = ParseEndOfCentralDirectory(&(*pArch), zEnd);
3501 if( rc != SXRET_OK ){
3502 return rc;
3503 }
3504
3505 /* Find the starting offset of the central directory */
3506 zCentral = &zEnd[-(sxi32)pArch->nCentralSize];
3507 if( zCentral <= (unsigned char *)zBuf || SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){
3508 if( pArch->nCentralOfft >= nLen ){
3509 /* Corrupted central directory offset */
3510 return SXERR_CORRUPT;
3511 }
3512 zCentral = (unsigned char *)&zBuf[pArch->nCentralOfft];
3513 if( SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){
3514 /* Corrupted zip archive */
3515 return SXERR_CORRUPT;
3516 }
3517 /* Fall thru and extract all valid entries from the central directory */
3518 }
3519 rc = ZipExtract(&(*pArch), zCentral, (sxu32)(zEnd - zCentral), (void *)zBuf);
3520 return rc;
3521 }
3522/*
3523 * Default comparison function.
3524 */
3525 static sxi32 ArchiveHashCmp(const SyString *pStr1, const SyString *pStr2)
3526 {
3527 sxi32 rc;
3528 rc = SyStringCmp(pStr1, pStr2, SyMemcmp);
3529 return rc;
3530 }
3531JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp)
3532 {
3533 SyArchiveEntry **apHash;
3534#if defined(UNTRUST)
3535 if( pArch == 0 ){
3536 return SXERR_EMPTY;
3537 }
3538#endif
3539 SyZero(pArch, sizeof(SyArchive));
3540 /* Allocate a new hashtable */
3541 apHash = (SyArchiveEntry **)SyMemBackendAlloc(&(*pAllocator), SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *));
3542 if( apHash == 0){
3543 return SXERR_MEM;
3544 }
3545 SyZero(apHash, SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *));
3546 pArch->apHash = apHash;
3547 pArch->xHash = xHash ? xHash : SyBinHash;
3548 pArch->xCmp = xCmp ? xCmp : ArchiveHashCmp;
3549 pArch->nSize = SXARCHIVE_HASH_SIZE;
3550 pArch->pAllocator = &(*pAllocator);
3551 pArch->nMagic = SXARCH_MAGIC;
3552 return SXRET_OK;
3553 }
3554 static sxi32 ArchiveReleaseEntry(SyMemBackend *pAllocator, SyArchiveEntry *pEntry)
3555 {
3556 SyArchiveEntry *pDup = pEntry->pNextName;
3557 SyArchiveEntry *pNextDup;
3558
3559 /* Release duplicates first since there are not stored in the hashtable */
3560 for(;;){
3561 if( pEntry->nDup == 0 ){
3562 break;
3563 }
3564 pNextDup = pDup->pNextName;
3565 pDup->nMagic = 0x2661;
3566 SyMemBackendFree(pAllocator, (void *)SyStringData(&pDup->sFileName));
3567 SyMemBackendPoolFree(pAllocator, pDup);
3568 pDup = pNextDup;
3569 pEntry->nDup--;
3570 }
3571 pEntry->nMagic = 0x2661;
3572 SyMemBackendFree(pAllocator, (void *)SyStringData(&pEntry->sFileName));
3573 SyMemBackendPoolFree(pAllocator, pEntry);
3574 return SXRET_OK;
3575 }
3576JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch)
3577 {
3578 SyArchiveEntry *pEntry, *pNext;
3579 pEntry = pArch->pList;
3580 for(;;){
3581 if( pArch->nLoaded < 1 ){
3582 break;
3583 }
3584 pNext = pEntry->pNext;
3585 MACRO_LD_REMOVE(pArch->pList, pEntry);
3586 ArchiveReleaseEntry(pArch->pAllocator, pEntry);
3587 pEntry = pNext;
3588 pArch->nLoaded--;
3589 }
3590 SyMemBackendFree(pArch->pAllocator, pArch->apHash);
3591 pArch->pCursor = 0;
3592 pArch->nMagic = 0x2626;
3593 return SXRET_OK;
3594 }
3595 JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch)
3596 {
3597 pArch->pCursor = pArch->pList;
3598 return SXRET_OK;
3599 }
3600 JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry)
3601 {
3602 SyArchiveEntry *pNext;
3603 if( pArch->pCursor == 0 ){
3604 /* Rewind the cursor */
3605 pArch->pCursor = pArch->pList;
3606 return SXERR_EOF;
3607 }
3608 *ppEntry = pArch->pCursor;
3609 pNext = pArch->pCursor->pNext;
3610 /* Advance the cursor to the next entry */
3611 pArch->pCursor = pNext;
3612 return SXRET_OK;
3613 }
3614#endif /* JX9_DISABLE_BUILTIN_FUNC */
3615/*
3616 * Psuedo Random Number Generator (PRNG)
3617 * @authors: SQLite authors <http://www.sqlite.org/>
3618 * @status: Public Domain
3619 * NOTE:
3620 * Nothing in this file or anywhere else in the library does any kind of
3621 * encryption.The RC4 algorithm is being used as a PRNG (pseudo-random
3622 * number generator) not as an encryption device.
3623 */
3624#define SXPRNG_MAGIC 0x13C4
3625#ifdef __UNIXES__
3626#include <sys/types.h>
3627#include <sys/stat.h>
3628#include <fcntl.h>
3629#include <unistd.h>
3630#include <errno.h>
3631#include <time.h>
3632#include <sys/time.h>
3633#endif
3634static sxi32 SyOSUtilRandomSeed(void *pBuf, sxu32 nLen, void *pUnused)
3635{
3636 char *zBuf = (char *)pBuf;
3637#ifdef __WINNT__
3638 DWORD nProcessID; /* Yes, keep it uninitialized when compiling using the MinGW32 builds tools */
3639#elif defined(__UNIXES__)
3640 pid_t pid;
3641 int fd;
3642#else
3643 char zGarbage[128]; /* Yes, keep this buffer uninitialized */
3644#endif
3645 SXUNUSED(pUnused);
3646#ifdef __WINNT__
3647#ifndef __MINGW32__
3648 nProcessID = GetProcessId(GetCurrentProcess());
3649#endif
3650 SyMemcpy((const void *)&nProcessID, zBuf, SXMIN(nLen, sizeof(DWORD)));
3651 if( (sxu32)(&zBuf[nLen] - &zBuf[sizeof(DWORD)]) >= sizeof(SYSTEMTIME) ){
3652 GetSystemTime((LPSYSTEMTIME)&zBuf[sizeof(DWORD)]);
3653 }
3654#elif defined(__UNIXES__)
3655 fd = open("/dev/urandom", O_RDONLY);
3656 if (fd >= 0 ){
3657 if( read(fd, zBuf, nLen) > 0 ){
3658 return SXRET_OK;
3659 }
3660 /* FALL THRU */
3661 }
3662 pid = getpid();
3663 SyMemcpy((const void *)&pid, zBuf, SXMIN(nLen, sizeof(pid_t)));
3664 if( &zBuf[nLen] - &zBuf[sizeof(pid_t)] >= (int)sizeof(struct timeval) ){
3665 gettimeofday((struct timeval *)&zBuf[sizeof(pid_t)], 0);
3666 }
3667#else
3668 /* Fill with uninitialized data */
3669 SyMemcpy(zGarbage, zBuf, SXMIN(nLen, sizeof(zGarbage)));
3670#endif
3671 return SXRET_OK;
3672}
3673JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void * pUserData)
3674{
3675 char zSeed[256];
3676 sxu8 t;
3677 sxi32 rc;
3678 sxu32 i;
3679 if( pCtx->nMagic == SXPRNG_MAGIC ){
3680 return SXRET_OK; /* Already initialized */
3681 }
3682 /* Initialize the state of the random number generator once,
3683 ** the first time this routine is called.The seed value does
3684 ** not need to contain a lot of randomness since we are not
3685 ** trying to do secure encryption or anything like that...
3686 */
3687 if( xSeed == 0 ){
3688 xSeed = SyOSUtilRandomSeed;
3689 }
3690 rc = xSeed(zSeed, sizeof(zSeed), pUserData);
3691 if( rc != SXRET_OK ){
3692 return rc;
3693 }
3694 pCtx->i = pCtx->j = 0;
3695 for(i=0; i < SX_ARRAYSIZE(pCtx->s) ; i++){
3696 pCtx->s[i] = (unsigned char)i;
3697 }
3698 for(i=0; i < sizeof(zSeed) ; i++){
3699 pCtx->j += pCtx->s[i] + zSeed[i];
3700 t = pCtx->s[pCtx->j];
3701 pCtx->s[pCtx->j] = pCtx->s[i];
3702 pCtx->s[i] = t;
3703 }
3704 pCtx->nMagic = SXPRNG_MAGIC;
3705
3706 return SXRET_OK;
3707}
3708/*
3709 * Get a single 8-bit random value using the RC4 PRNG.
3710 */
3711static sxu8 randomByte(SyPRNGCtx *pCtx)
3712{
3713 sxu8 t;
3714
3715 /* Generate and return single random byte */
3716 pCtx->i++;
3717 t = pCtx->s[pCtx->i];
3718 pCtx->j += t;
3719 pCtx->s[pCtx->i] = pCtx->s[pCtx->j];
3720 pCtx->s[pCtx->j] = t;
3721 t += pCtx->s[pCtx->i];
3722 return pCtx->s[t];
3723}
3724JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen)
3725{
3726 unsigned char *zBuf = (unsigned char *)pBuf;
3727 unsigned char *zEnd = &zBuf[nLen];
3728#if defined(UNTRUST)
3729 if( pCtx == 0 || pBuf == 0 || nLen <= 0 ){
3730 return SXERR_EMPTY;
3731 }
3732#endif
3733 if(pCtx->nMagic != SXPRNG_MAGIC ){
3734 return SXERR_CORRUPT;
3735 }
3736 for(;;){
3737 if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
3738 if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
3739 if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
3740 if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++;
3741 }
3742 return SXRET_OK;
3743}
3744#ifndef JX9_DISABLE_BUILTIN_FUNC
3745#ifndef JX9_DISABLE_HASH_FUNC
3746/* SyRunTimeApi: sxhash.c */
3747/*
3748 * This code implements the MD5 message-digest algorithm.
3749 * The algorithm is due to Ron Rivest.This code was
3750 * written by Colin Plumb in 1993, no copyright is claimed.
3751 * This code is in the public domain; do with it what you wish.
3752 *
3753 * Equivalent code is available from RSA Data Security, Inc.
3754 * This code has been tested against that, and is equivalent,
3755 * except that you don't need to include two pages of legalese
3756 * with every copy.
3757 *
3758 * To compute the message digest of a chunk of bytes, declare an
3759 * MD5Context structure, pass it to MD5Init, call MD5Update as
3760 * needed on buffers full of bytes, and then call MD5Final, which
3761 * will fill a supplied 16-byte array with the digest.
3762 */
3763#define SX_MD5_BINSZ 16
3764#define SX_MD5_HEXSZ 32
3765/*
3766 * Note: this code is harmless on little-endian machines.
3767 */
3768static void byteReverse (unsigned char *buf, unsigned longs)
3769{
3770 sxu32 t;
3771 do {
3772 t = (sxu32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
3773 ((unsigned)buf[1]<<8 | buf[0]);
3774 *(sxu32*)buf = t;
3775 buf += 4;
3776 } while (--longs);
3777}
3778/* The four core functions - F1 is optimized somewhat */
3779
3780/* #define F1(x, y, z) (x & y | ~x & z) */
3781#ifdef F1
3782#undef F1
3783#endif
3784#ifdef F2
3785#undef F2
3786#endif
3787#ifdef F3
3788#undef F3
3789#endif
3790#ifdef F4
3791#undef F4
3792#endif
3793
3794#define F1(x, y, z) (z ^ (x & (y ^ z)))
3795#define F2(x, y, z) F1(z, x, y)
3796#define F3(x, y, z) (x ^ y ^ z)
3797#define F4(x, y, z) (y ^ (x | ~z))
3798
3799/* This is the central step in the MD5 algorithm.*/
3800#define SX_MD5STEP(f, w, x, y, z, data, s) \
3801 ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
3802
3803/*
3804 * The core of the MD5 algorithm, this alters an existing MD5 hash to
3805 * reflect the addition of 16 longwords of new data.MD5Update blocks
3806 * the data and converts bytes into longwords for this routine.
3807 */
3808static void MD5Transform(sxu32 buf[4], const sxu32 in[16])
3809{
3810 register sxu32 a, b, c, d;
3811
3812 a = buf[0];
3813 b = buf[1];
3814 c = buf[2];
3815 d = buf[3];
3816
3817 SX_MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
3818 SX_MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
3819 SX_MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
3820 SX_MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
3821 SX_MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
3822 SX_MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
3823 SX_MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
3824 SX_MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
3825 SX_MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
3826 SX_MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
3827 SX_MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
3828 SX_MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
3829 SX_MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
3830 SX_MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
3831 SX_MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
3832 SX_MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
3833
3834 SX_MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
3835 SX_MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
3836 SX_MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
3837 SX_MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
3838 SX_MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
3839 SX_MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
3840 SX_MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
3841 SX_MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
3842 SX_MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
3843 SX_MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
3844 SX_MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
3845 SX_MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
3846 SX_MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
3847 SX_MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
3848 SX_MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
3849 SX_MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
3850
3851 SX_MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
3852 SX_MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
3853 SX_MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
3854 SX_MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
3855 SX_MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
3856 SX_MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
3857 SX_MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
3858 SX_MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
3859 SX_MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
3860 SX_MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
3861 SX_MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
3862 SX_MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
3863 SX_MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
3864 SX_MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
3865 SX_MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
3866 SX_MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
3867
3868 SX_MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
3869 SX_MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
3870 SX_MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
3871 SX_MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
3872 SX_MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
3873 SX_MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
3874 SX_MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
3875 SX_MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
3876 SX_MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
3877 SX_MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
3878 SX_MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
3879 SX_MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
3880 SX_MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
3881 SX_MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
3882 SX_MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
3883 SX_MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
3884
3885 buf[0] += a;
3886 buf[1] += b;
3887 buf[2] += c;
3888 buf[3] += d;
3889}
3890/*
3891 * Update context to reflect the concatenation of another buffer full
3892 * of bytes.
3893 */
3894JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len)
3895{
3896 sxu32 t;
3897
3898 /* Update bitcount */
3899 t = ctx->bits[0];
3900 if ((ctx->bits[0] = t + ((sxu32)len << 3)) < t)
3901 ctx->bits[1]++; /* Carry from low to high */
3902 ctx->bits[1] += len >> 29;
3903 t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
3904 /* Handle any leading odd-sized chunks */
3905 if ( t ) {
3906 unsigned char *p = (unsigned char *)ctx->in + t;
3907
3908 t = 64-t;
3909 if (len < t) {
3910 SyMemcpy(buf, p, len);
3911 return;
3912 }
3913 SyMemcpy(buf, p, t);
3914 byteReverse(ctx->in, 16);
3915 MD5Transform(ctx->buf, (sxu32*)ctx->in);
3916 buf += t;
3917 len -= t;
3918 }
3919 /* Process data in 64-byte chunks */
3920 while (len >= 64) {
3921 SyMemcpy(buf, ctx->in, 64);
3922 byteReverse(ctx->in, 16);
3923 MD5Transform(ctx->buf, (sxu32*)ctx->in);
3924 buf += 64;
3925 len -= 64;
3926 }
3927 /* Handle any remaining bytes of data.*/
3928 SyMemcpy(buf, ctx->in, len);
3929}
3930/*
3931 * Final wrapup - pad to 64-byte boundary with the bit pattern
3932 * 1 0* (64-bit count of bits processed, MSB-first)
3933 */
3934JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx){
3935 unsigned count;
3936 unsigned char *p;
3937
3938 /* Compute number of bytes mod 64 */
3939 count = (ctx->bits[0] >> 3) & 0x3F;
3940
3941 /* Set the first char of padding to 0x80.This is safe since there is
3942 always at least one byte free */
3943 p = ctx->in + count;
3944 *p++ = 0x80;
3945
3946 /* Bytes of padding needed to make 64 bytes */
3947 count = 64 - 1 - count;
3948
3949 /* Pad out to 56 mod 64 */
3950 if (count < 8) {
3951 /* Two lots of padding: Pad the first block to 64 bytes */
3952 SyZero(p, count);
3953 byteReverse(ctx->in, 16);
3954 MD5Transform(ctx->buf, (sxu32*)ctx->in);
3955
3956 /* Now fill the next block with 56 bytes */
3957 SyZero(ctx->in, 56);
3958 } else {
3959 /* Pad block to 56 bytes */
3960 SyZero(p, count-8);
3961 }
3962 byteReverse(ctx->in, 14);
3963
3964 /* Append length in bits and transform */
3965 ((sxu32*)ctx->in)[ 14 ] = ctx->bits[0];
3966 ((sxu32*)ctx->in)[ 15 ] = ctx->bits[1];
3967
3968 MD5Transform(ctx->buf, (sxu32*)ctx->in);
3969 byteReverse((unsigned char *)ctx->buf, 4);
3970 SyMemcpy(ctx->buf, digest, 0x10);
3971 SyZero(ctx, sizeof(ctx)); /* In case it's sensitive */
3972}
3973#undef F1
3974#undef F2
3975#undef F3
3976#undef F4
3977JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx)
3978{
3979 pCtx->buf[0] = 0x67452301;
3980 pCtx->buf[1] = 0xefcdab89;
3981 pCtx->buf[2] = 0x98badcfe;
3982 pCtx->buf[3] = 0x10325476;
3983 pCtx->bits[0] = 0;
3984 pCtx->bits[1] = 0;
3985
3986 return SXRET_OK;
3987}
3988JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16])
3989{
3990 MD5Context sCtx;
3991 MD5Init(&sCtx);
3992 MD5Update(&sCtx, (const unsigned char *)pIn, nLen);
3993 MD5Final(zDigest, &sCtx);
3994 return SXRET_OK;
3995}
3996/*
3997 * SHA-1 in C
3998 * By Steve Reid <steve@edmweb.com>
3999 * Status: Public Domain
4000 */
4001/*
4002 * blk0() and blk() perform the initial expand.
4003 * I got the idea of expanding during the round function from SSLeay
4004 *
4005 * blk0le() for little-endian and blk0be() for big-endian.
4006 */
4007#if __GNUC__ && (defined(__i386__) || defined(__x86_64__))
4008/*
4009 * GCC by itself only generates left rotates. Use right rotates if
4010 * possible to be kinder to dinky implementations with iterative rotate
4011 * instructions.
4012 */
4013#define SHA_ROT(op, x, k) \
4014 ({ unsigned int y; asm(op " %1, %0" : "=r" (y) : "I" (k), "0" (x)); y; })
4015#define rol(x, k) SHA_ROT("roll", x, k)
4016#define ror(x, k) SHA_ROT("rorl", x, k)
4017
4018#else
4019/* Generic C equivalent */
4020#define SHA_ROT(x, l, r) ((x) << (l) | (x) >> (r))
4021#define rol(x, k) SHA_ROT(x, k, 32-(k))
4022#define ror(x, k) SHA_ROT(x, 32-(k), k)
4023#endif
4024
4025#define blk0le(i) (block[i] = (ror(block[i], 8)&0xFF00FF00) \
4026 |(rol(block[i], 8)&0x00FF00FF))
4027#define blk0be(i) block[i]
4028#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \
4029 ^block[(i+2)&15]^block[i&15], 1))
4030
4031/*
4032 * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
4033 *
4034 * Rl0() for little-endian and Rb0() for big-endian. Endianness is
4035 * determined at run-time.
4036 */
4037#define Rl0(v, w, x, y, z, i) \
4038 z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v, 5);w=ror(w, 2);
4039#define Rb0(v, w, x, y, z, i) \
4040 z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v, 5);w=ror(w, 2);
4041#define R1(v, w, x, y, z, i) \
4042 z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v, 5);w=ror(w, 2);
4043#define R2(v, w, x, y, z, i) \
4044 z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v, 5);w=ror(w, 2);
4045#define R3(v, w, x, y, z, i) \
4046 z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v, 5);w=ror(w, 2);
4047#define R4(v, w, x, y, z, i) \
4048 z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v, 5);w=ror(w, 2);
4049
4050/*
4051 * Hash a single 512-bit block. This is the core of the algorithm.
4052 */
4053#define a qq[0]
4054#define b qq[1]
4055#define c qq[2]
4056#define d qq[3]
4057#define e qq[4]
4058
4059static void SHA1Transform(unsigned int state[5], const unsigned char buffer[64])
4060{
4061 unsigned int qq[5]; /* a, b, c, d, e; */
4062 static int one = 1;
4063 unsigned int block[16];
4064 SyMemcpy(buffer, (void *)block, 64);
4065 SyMemcpy(state, qq, 5*sizeof(unsigned int));
4066
4067 /* Copy context->state[] to working vars */
4068 /*
4069 a = state[0];
4070 b = state[1];
4071 c = state[2];
4072 d = state[3];
4073 e = state[4];
4074 */
4075
4076 /* 4 rounds of 20 operations each. Loop unrolled. */
4077 if( 1 == *(unsigned char*)&one ){
4078 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);
4079 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);
4080 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);
4081 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);
4082 }else{
4083 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);
4084 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);
4085 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);
4086 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);
4087 }
4088 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);
4089 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);
4090 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);
4091 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);
4092 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);
4093 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);
4094 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);
4095 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);
4096 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);
4097 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);
4098 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);
4099 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);
4100 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);
4101 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);
4102 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);
4103 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);
4104
4105 /* Add the working vars back into context.state[] */
4106 state[0] += a;
4107 state[1] += b;
4108 state[2] += c;
4109 state[3] += d;
4110 state[4] += e;
4111}
4112#undef a
4113#undef b
4114#undef c
4115#undef d
4116#undef e
4117/*
4118 * SHA1Init - Initialize new context
4119 */
4120JX9_PRIVATE void SHA1Init(SHA1Context *context){
4121 /* SHA1 initialization constants */
4122 context->state[0] = 0x67452301;
4123 context->state[1] = 0xEFCDAB89;
4124 context->state[2] = 0x98BADCFE;
4125 context->state[3] = 0x10325476;
4126 context->state[4] = 0xC3D2E1F0;
4127 context->count[0] = context->count[1] = 0;
4128}
4129/*
4130 * Run your data through this.
4131 */
4132JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len){
4133 unsigned int i, j;
4134
4135 j = context->count[0];
4136 if ((context->count[0] += len << 3) < j)
4137 context->count[1] += (len>>29)+1;
4138 j = (j >> 3) & 63;
4139 if ((j + len) > 63) {
4140 (void)SyMemcpy(data, &context->buffer[j], (i = 64-j));
4141 SHA1Transform(context->state, context->buffer);
4142 for ( ; i + 63 < len; i += 64)
4143 SHA1Transform(context->state, &data[i]);
4144 j = 0;
4145 } else {
4146 i = 0;
4147 }
4148 (void)SyMemcpy(&data[i], &context->buffer[j], len - i);
4149}
4150/*
4151 * Add padding and return the message digest.
4152 */
4153JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]){
4154 unsigned int i;
4155 unsigned char finalcount[8];
4156
4157 for (i = 0; i < 8; i++) {
4158 finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
4159 >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
4160 }
4161 SHA1Update(context, (const unsigned char *)"\200", 1);
4162 while ((context->count[0] & 504) != 448)
4163 SHA1Update(context, (const unsigned char *)"\0", 1);
4164 SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
4165
4166 if (digest) {
4167 for (i = 0; i < 20; i++)
4168 digest[i] = (unsigned char)
4169 ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
4170 }
4171}
4172#undef Rl0
4173#undef Rb0
4174#undef R1
4175#undef R2
4176#undef R3
4177#undef R4
4178
4179JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20])
4180{
4181 SHA1Context sCtx;
4182 SHA1Init(&sCtx);
4183 SHA1Update(&sCtx, (const unsigned char *)pIn, nLen);
4184 SHA1Final(&sCtx, zDigest);
4185 return SXRET_OK;
4186}
4187static const sxu32 crc32_table[] = {
4188 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
4189 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
4190 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
4191 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
4192 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
4193 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
4194 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
4195 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
4196 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
4197 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
4198 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
4199 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
4200 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
4201 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
4202 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
4203 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
4204 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
4205 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
4206 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
4207 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
4208 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
4209 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
4210 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
4211 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
4212 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
4213 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
4214 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
4215 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
4216 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
4217 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
4218 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
4219 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
4220 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
4221 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
4222 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
4223 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
4224 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
4225 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
4226 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
4227 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
4228 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
4229 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
4230 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
4231 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
4232 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
4233 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
4234 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
4235 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
4236 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
4237 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
4238 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
4239 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
4240 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
4241 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
4242 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
4243 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
4244 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
4245 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
4246 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
4247 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
4248 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
4249 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
4250 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
4251 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
4252};
4253#define CRC32C(c, d) (c = ( crc32_table[(c ^ (d)) & 0xFF] ^ (c>>8) ) )
4254static sxu32 SyCrc32Update(sxu32 crc32, const void *pSrc, sxu32 nLen)
4255{
4256 register unsigned char *zIn = (unsigned char *)pSrc;
4257 unsigned char *zEnd;
4258 if( zIn == 0 ){
4259 return crc32;
4260 }
4261 zEnd = &zIn[nLen];
4262 for(;;){
4263 if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++;
4264 if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++;
4265 if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++;
4266 if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++;
4267 }
4268
4269 return crc32;
4270}
4271JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen)
4272{
4273 return SyCrc32Update(SXU32_HIGH, pSrc, nLen);
4274}
4275#endif /* JX9_DISABLE_HASH_FUNC */
4276#endif /* JX9_DISABLE_BUILTIN_FUNC */
4277#ifndef JX9_DISABLE_BUILTIN_FUNC
4278JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData)
4279{
4280 static const unsigned char zHexTab[] = "0123456789abcdef";
4281 const unsigned char *zIn, *zEnd;
4282 unsigned char zOut[3];
4283 sxi32 rc;
4284#if defined(UNTRUST)
4285 if( pIn == 0 || xConsumer == 0 ){
4286 return SXERR_EMPTY;
4287 }
4288#endif
4289 zIn = (const unsigned char *)pIn;
4290 zEnd = &zIn[nLen];
4291 for(;;){
4292 if( zIn >= zEnd ){
4293 break;
4294 }
4295 zOut[0] = zHexTab[zIn[0] >> 4]; zOut[1] = zHexTab[zIn[0] & 0x0F];
4296 rc = xConsumer((const void *)zOut, sizeof(char)*2, pConsumerData);
4297 if( rc != SXRET_OK ){
4298 return rc;
4299 }
4300 zIn++;
4301 }
4302 return SXRET_OK;
4303}
4304#endif /* JX9_DISABLE_BUILTIN_FUNC */
4305JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb)
4306{
4307 buf[3] = nb & 0xFF ; nb >>=8;
4308 buf[2] = nb & 0xFF ; nb >>=8;
4309 buf[1] = nb & 0xFF ; nb >>=8;
4310 buf[0] = (unsigned char)nb ;
4311}
4312JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB)
4313{
4314 *uNB = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24);
4315}
4316JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb)
4317{
4318 buf[1] = nb & 0xFF ; nb >>=8;
4319 buf[0] = (unsigned char)nb ;
4320}
4321JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB)
4322{
4323 *uNB = buf[1] + (buf[0] << 8);
4324}
4325JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64)
4326{
4327 buf[7] = n64 & 0xFF; n64 >>=8;
4328 buf[6] = n64 & 0xFF; n64 >>=8;
4329 buf[5] = n64 & 0xFF; n64 >>=8;
4330 buf[4] = n64 & 0xFF; n64 >>=8;
4331 buf[3] = n64 & 0xFF; n64 >>=8;
4332 buf[2] = n64 & 0xFF; n64 >>=8;
4333 buf[1] = n64 & 0xFF; n64 >>=8;
4334 buf[0] = (sxu8)n64 ;
4335}
4336JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64)
4337{
4338 sxu32 u1,u2;
4339 u1 = buf[7] + (buf[6] << 8) + (buf[5] << 16) + (buf[4] << 24);
4340 u2 = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24);
4341 *n64 = (((sxu64)u2) << 32) | u1;
4342}
4343JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64)
4344{
4345 unsigned char zBuf[8];
4346 sxi32 rc;
4347 SyBigEndianPack64(zBuf,n64);
4348 rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf));
4349 return rc;
4350}
4351JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32)
4352{
4353 unsigned char zBuf[4];
4354 sxi32 rc;
4355 SyBigEndianPack32(zBuf,n32);
4356 rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf));
4357 return rc;
4358}
4359JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16)
4360{
4361 unsigned char zBuf[2];
4362 sxi32 rc;
4363 SyBigEndianPack16(zBuf,n16);
4364 rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf));
4365 return rc;
4366}
4367JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut)
4368{
4369 sxi32 nDate,nTime;
4370 nDate = ((pFmt->tm_year - 1980) << 9) + (pFmt->tm_mon << 5) + pFmt->tm_mday;
4371 nTime = (pFmt->tm_hour << 11) + (pFmt->tm_min << 5)+ (pFmt->tm_sec >> 1);
4372 *pOut = (nDate << 16) | nTime;
4373}
4374JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut)
4375{
4376 sxu16 nDate;
4377 sxu16 nTime;
4378 nDate = nDosDate >> 16;
4379 nTime = nDosDate & 0xFFFF;
4380 pOut->tm_isdst = 0;
4381 pOut->tm_year = 1980 + (nDate >> 9);
4382 pOut->tm_mon = (nDate % (1<<9))>>5;
4383 pOut->tm_mday = (nDate % (1<<9))&0x1F;
4384 pOut->tm_hour = nTime >> 11;
4385 pOut->tm_min = (nTime % (1<<11)) >> 5;
4386 pOut->tm_sec = ((nTime % (1<<11))& 0x1F )<<1;
4387}
diff --git a/common/unqlite/jx9_license.txt b/common/unqlite/jx9_license.txt
new file mode 100644
index 0000000..d3bd921
--- /dev/null
+++ b/common/unqlite/jx9_license.txt
@@ -0,0 +1,44 @@
1--BEGIN --SYMISC PUBLIC LICENSE---
2/*
3 * Copyright (C) 2012, 2013 Symisc Systems. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Redistributions in any form must be accompanied by information on
14 * how to obtain complete source code for the JX9 engine and any
15 * accompanying software that uses the JX9 engine software.
16 * The source code must either be included in the distribution
17 * or be available for no more than the cost of distribution plus
18 * a nominal fee, and must be freely redistributable under reasonable
19 * conditions. For an executable file, complete source code means
20 * the source code for all modules it contains.It does not include
21 * source code for modules or files that typically accompany the major
22 * components of the operating system on which the executable file runs.
23 *
24 * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS
25 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
27 * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS
28 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
31 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
32 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
33 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
34 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36 --END -- SYMISC PUBLIC LICENSE---
37
38It is possible to circumvent this licensing policy through the purchase of a commercial software
39license from Symisc Systems consisting of terms and conditions which are negotiated at the time
40of sale.
41For more information, please contact Symisc Systems at:
42 licensing@symisc.net
43 or visit:
44 http://jx9.symisc.net/licensing.html
diff --git a/common/unqlite/jx9_memobj.c b/common/unqlite/jx9_memobj.c
new file mode 100644
index 0000000..7bc7f2f
--- /dev/null
+++ b/common/unqlite/jx9_memobj.c
@@ -0,0 +1,1078 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: memobj.c v2.7 FreeBSD 2012-08-09 03:40 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* This file manage low-level stuff related to indexed memory objects [i.e: jx9_value] */
18/*
19 * Notes on memory objects [i.e: jx9_value].
20 * Internally, the JX9 virtual machine manipulates nearly all JX9 values
21 * [i.e: string, int, float, resource, object, bool, null..] as jx9_values structures.
22 * Each jx9_values struct may cache multiple representations (string,
23 * integer etc.) of the same value.
24 */
25/*
26 * Convert a 64-bit IEEE double into a 64-bit signed integer.
27 * If the double is too large, return 0x8000000000000000.
28 *
29 * Most systems appear to do this simply by assigning ariables and without
30 * the extra range tests.
31 * But there are reports that windows throws an expection if the floating
32 * point value is out of range.
33 */
34static sxi64 MemObjRealToInt(jx9_value *pObj)
35{
36#ifdef JX9_OMIT_FLOATING_POINT
37 /* Real and 64bit integer are the same when floating point arithmetic
38 * is omitted from the build.
39 */
40 return pObj->x.rVal;
41#else
42 /*
43 ** Many compilers we encounter do not define constants for the
44 ** minimum and maximum 64-bit integers, or they define them
45 ** inconsistently. And many do not understand the "LL" notation.
46 ** So we define our own static constants here using nothing
47 ** larger than a 32-bit integer constant.
48 */
49 static const sxi64 maxInt = LARGEST_INT64;
50 static const sxi64 minInt = SMALLEST_INT64;
51 jx9_real r = pObj->x.rVal;
52 if( r<(jx9_real)minInt ){
53 return minInt;
54 }else if( r>(jx9_real)maxInt ){
55 /* minInt is correct here - not maxInt. It turns out that assigning
56 ** a very large positive number to an integer results in a very large
57 ** negative integer. This makes no sense, but it is what x86 hardware
58 ** does so for compatibility we will do the same in software. */
59 return minInt;
60 }else{
61 return (sxi64)r;
62 }
63#endif
64}
65/*
66 * Convert a raw token value typically a stream of digit [i.e: hex, octal, binary or decimal]
67 * to a 64-bit integer.
68 */
69JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pVal)
70{
71 sxi64 iVal = 0;
72 if( pVal->nByte <= 0 ){
73 return 0;
74 }
75 if( pVal->zString[0] == '0' ){
76 sxi32 c;
77 if( pVal->nByte == sizeof(char) ){
78 return 0;
79 }
80 c = pVal->zString[1];
81 if( c == 'x' || c == 'X' ){
82 /* Hex digit stream */
83 SyHexStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0);
84 }else if( c == 'b' || c == 'B' ){
85 /* Binary digit stream */
86 SyBinaryStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0);
87 }else{
88 /* Octal digit stream */
89 SyOctalStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0);
90 }
91 }else{
92 /* Decimal digit stream */
93 SyStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0);
94 }
95 return iVal;
96}
97/*
98 * Return some kind of 64-bit integer value which is the best we can
99 * do at representing the value that pObj describes as a string
100 * representation.
101 */
102static sxi64 MemObjStringToInt(jx9_value *pObj)
103{
104 SyString sVal;
105 SyStringInitFromBuf(&sVal, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
106 return jx9TokenValueToInt64(&sVal);
107}
108/*
109 * Return some kind of integer value which is the best we can
110 * do at representing the value that pObj describes as an integer.
111 * If pObj is an integer, then the value is exact. If pObj is
112 * a floating-point then the value returned is the integer part.
113 * If pObj is a string, then we make an attempt to convert it into
114 * a integer and return that.
115 * If pObj represents a NULL value, return 0.
116 */
117static sxi64 MemObjIntValue(jx9_value *pObj)
118{
119 sxi32 iFlags;
120 iFlags = pObj->iFlags;
121 if (iFlags & MEMOBJ_REAL ){
122 return MemObjRealToInt(&(*pObj));
123 }else if( iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){
124 return pObj->x.iVal;
125 }else if (iFlags & MEMOBJ_STRING) {
126 return MemObjStringToInt(&(*pObj));
127 }else if( iFlags & MEMOBJ_NULL ){
128 return 0;
129 }else if( iFlags & MEMOBJ_HASHMAP ){
130 jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther;
131 sxu32 n = pMap->nEntry;
132 jx9HashmapUnref(pMap);
133 /* Return total number of entries in the hashmap */
134 return n;
135 }else if(iFlags & MEMOBJ_RES ){
136 return pObj->x.pOther != 0;
137 }
138 /* CANT HAPPEN */
139 return 0;
140}
141/*
142 * Return some kind of real value which is the best we can
143 * do at representing the value that pObj describes as a real.
144 * If pObj is a real, then the value is exact.If pObj is an
145 * integer then the integer is promoted to real and that value
146 * is returned.
147 * If pObj is a string, then we make an attempt to convert it
148 * into a real and return that.
149 * If pObj represents a NULL value, return 0.0
150 */
151static jx9_real MemObjRealValue(jx9_value *pObj)
152{
153 sxi32 iFlags;
154 iFlags = pObj->iFlags;
155 if( iFlags & MEMOBJ_REAL ){
156 return pObj->x.rVal;
157 }else if (iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){
158 return (jx9_real)pObj->x.iVal;
159 }else if (iFlags & MEMOBJ_STRING){
160 SyString sString;
161#ifdef JX9_OMIT_FLOATING_POINT
162 jx9_real rVal = 0;
163#else
164 jx9_real rVal = 0.0;
165#endif
166 SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
167 if( SyBlobLength(&pObj->sBlob) > 0 ){
168 /* Convert as much as we can */
169#ifdef JX9_OMIT_FLOATING_POINT
170 rVal = MemObjStringToInt(&(*pObj));
171#else
172 SyStrToReal(sString.zString, sString.nByte, (void *)&rVal, 0);
173#endif
174 }
175 return rVal;
176 }else if( iFlags & MEMOBJ_NULL ){
177#ifdef JX9_OMIT_FLOATING_POINT
178 return 0;
179#else
180 return 0.0;
181#endif
182 }else if( iFlags & MEMOBJ_HASHMAP ){
183 /* Return the total number of entries in the hashmap */
184 jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther;
185 jx9_real n = (jx9_real)pMap->nEntry;
186 jx9HashmapUnref(pMap);
187 return n;
188 }else if(iFlags & MEMOBJ_RES ){
189 return (jx9_real)(pObj->x.pOther != 0);
190 }
191 /* NOT REACHED */
192 return 0;
193}
194/*
195 * Return the string representation of a given jx9_value.
196 * This function never fail and always return SXRET_OK.
197 */
198static sxi32 MemObjStringValue(SyBlob *pOut,jx9_value *pObj)
199{
200 if( pObj->iFlags & MEMOBJ_REAL ){
201 SyBlobFormat(&(*pOut), "%.15g", pObj->x.rVal);
202 }else if( pObj->iFlags & MEMOBJ_INT ){
203 SyBlobFormat(&(*pOut), "%qd", pObj->x.iVal);
204 /* %qd (BSD quad) is equivalent to %lld in the libc printf */
205 }else if( pObj->iFlags & MEMOBJ_BOOL ){
206 if( pObj->x.iVal ){
207 SyBlobAppend(&(*pOut),"true", sizeof("true")-1);
208 }else{
209 SyBlobAppend(&(*pOut),"false", sizeof("false")-1);
210 }
211 }else if( pObj->iFlags & MEMOBJ_HASHMAP ){
212 /* Serialize JSON object or array */
213 jx9JsonSerialize(pObj,pOut);
214 jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther);
215 }else if(pObj->iFlags & MEMOBJ_RES ){
216 SyBlobFormat(&(*pOut), "ResourceID_%#x", pObj->x.pOther);
217 }
218 return SXRET_OK;
219}
220/*
221 * Return some kind of boolean value which is the best we can do
222 * at representing the value that pObj describes as a boolean.
223 * When converting to boolean, the following values are considered FALSE:
224 * NULL
225 * the boolean FALSE itself.
226 * the integer 0 (zero).
227 * the real 0.0 (zero).
228 * the empty string, a stream of zero [i.e: "0", "00", "000", ...] and the string
229 * "false".
230 * an array with zero elements.
231 */
232static sxi32 MemObjBooleanValue(jx9_value *pObj)
233{
234 sxi32 iFlags;
235 iFlags = pObj->iFlags;
236 if (iFlags & MEMOBJ_REAL ){
237#ifdef JX9_OMIT_FLOATING_POINT
238 return pObj->x.rVal ? 1 : 0;
239#else
240 return pObj->x.rVal != 0.0 ? 1 : 0;
241#endif
242 }else if( iFlags & MEMOBJ_INT ){
243 return pObj->x.iVal ? 1 : 0;
244 }else if (iFlags & MEMOBJ_STRING) {
245 SyString sString;
246 SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
247 if( sString.nByte == 0 ){
248 /* Empty string */
249 return 0;
250 }else if( (sString.nByte == sizeof("true") - 1 && SyStrnicmp(sString.zString, "true", sizeof("true")-1) == 0) ||
251 (sString.nByte == sizeof("on") - 1 && SyStrnicmp(sString.zString, "on", sizeof("on")-1) == 0) ||
252 (sString.nByte == sizeof("yes") - 1 && SyStrnicmp(sString.zString, "yes", sizeof("yes")-1) == 0) ){
253 return 1;
254 }else if( sString.nByte == sizeof("false") - 1 && SyStrnicmp(sString.zString, "false", sizeof("false")-1) == 0 ){
255 return 0;
256 }else{
257 const char *zIn, *zEnd;
258 zIn = sString.zString;
259 zEnd = &zIn[sString.nByte];
260 while( zIn < zEnd && zIn[0] == '0' ){
261 zIn++;
262 }
263 return zIn >= zEnd ? 0 : 1;
264 }
265 }else if( iFlags & MEMOBJ_NULL ){
266 return 0;
267 }else if( iFlags & MEMOBJ_HASHMAP ){
268 jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther;
269 sxu32 n = pMap->nEntry;
270 jx9HashmapUnref(pMap);
271 return n > 0 ? TRUE : FALSE;
272 }else if(iFlags & MEMOBJ_RES ){
273 return pObj->x.pOther != 0;
274 }
275 /* NOT REACHED */
276 return 0;
277}
278/*
279 * If the jx9_value is of type real, try to make it an integer also.
280 */
281static sxi32 MemObjTryIntger(jx9_value *pObj)
282{
283 sxi64 iVal = MemObjRealToInt(&(*pObj));
284 /* Only mark the value as an integer if
285 **
286 ** (1) the round-trip conversion real->int->real is a no-op, and
287 ** (2) The integer is neither the largest nor the smallest
288 ** possible integer
289 **
290 ** The second and third terms in the following conditional enforces
291 ** the second condition under the assumption that addition overflow causes
292 ** values to wrap around. On x86 hardware, the third term is always
293 ** true and could be omitted. But we leave it in because other
294 ** architectures might behave differently.
295 */
296 if( pObj->x.rVal ==(jx9_real)iVal && iVal>SMALLEST_INT64 && iVal<LARGEST_INT64 ){
297 pObj->x.iVal = iVal;
298 pObj->iFlags = MEMOBJ_INT;
299 }
300 return SXRET_OK;
301}
302/*
303 * Convert a jx9_value to type integer.Invalidate any prior representations.
304 */
305JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj)
306{
307 if( (pObj->iFlags & MEMOBJ_INT) == 0 ){
308 /* Preform the conversion */
309 pObj->x.iVal = MemObjIntValue(&(*pObj));
310 /* Invalidate any prior representations */
311 SyBlobRelease(&pObj->sBlob);
312 MemObjSetType(pObj, MEMOBJ_INT);
313 }
314 return SXRET_OK;
315}
316/*
317 * Convert a jx9_value to type real (Try to get an integer representation also).
318 * Invalidate any prior representations
319 */
320JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj)
321{
322 if((pObj->iFlags & MEMOBJ_REAL) == 0 ){
323 /* Preform the conversion */
324 pObj->x.rVal = MemObjRealValue(&(*pObj));
325 /* Invalidate any prior representations */
326 SyBlobRelease(&pObj->sBlob);
327 MemObjSetType(pObj, MEMOBJ_REAL);
328 }
329 return SXRET_OK;
330}
331/*
332 * Convert a jx9_value to type boolean.Invalidate any prior representations.
333 */
334JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj)
335{
336 if( (pObj->iFlags & MEMOBJ_BOOL) == 0 ){
337 /* Preform the conversion */
338 pObj->x.iVal = MemObjBooleanValue(&(*pObj));
339 /* Invalidate any prior representations */
340 SyBlobRelease(&pObj->sBlob);
341 MemObjSetType(pObj, MEMOBJ_BOOL);
342 }
343 return SXRET_OK;
344}
345/*
346 * Convert a jx9_value to type string.Prior representations are NOT invalidated.
347 */
348JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj)
349{
350 sxi32 rc = SXRET_OK;
351 if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){
352 /* Perform the conversion */
353 SyBlobReset(&pObj->sBlob); /* Reset the internal buffer */
354 rc = MemObjStringValue(&pObj->sBlob, &(*pObj));
355 MemObjSetType(pObj, MEMOBJ_STRING);
356 }
357 return rc;
358}
359/*
360 * Nullify a jx9_value.In other words invalidate any prior
361 * representation.
362 */
363JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj)
364{
365 return jx9MemObjRelease(pObj);
366}
367/*
368 * Convert a jx9_value to type array.Invalidate any prior representations.
369 * According to the JX9 language reference manual.
370 * For any of the types: integer, float, string, boolean converting a value
371 * to an array results in an array with a single element with index zero
372 * and the value of the scalar which was converted.
373 */
374JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj)
375{
376 if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){
377 jx9_hashmap *pMap;
378 /* Allocate a new hashmap instance */
379 pMap = jx9NewHashmap(pObj->pVm, 0, 0);
380 if( pMap == 0 ){
381 return SXERR_MEM;
382 }
383 if( (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_RES)) == 0 ){
384 /*
385 * According to the JX9 language reference manual.
386 * For any of the types: integer, float, string, boolean converting a value
387 * to an array results in an array with a single element with index zero
388 * and the value of the scalar which was converted.
389 */
390 /* Insert a single element */
391 jx9HashmapInsert(pMap, 0/* Automatic index assign */, &(*pObj));
392 SyBlobRelease(&pObj->sBlob);
393 }
394 /* Invalidate any prior representation */
395 MemObjSetType(pObj, MEMOBJ_HASHMAP);
396 pObj->x.pOther = pMap;
397 }
398 return SXRET_OK;
399}
400/*
401 * Return a pointer to the appropriate convertion method associated
402 * with the given type.
403 * Note on type juggling.
404 * Accoding to the JX9 language reference manual
405 * JX9 does not require (or support) explicit type definition in variable
406 * declaration; a variable's type is determined by the context in which
407 * the variable is used. That is to say, if a string value is assigned
408 * to variable $var, $var becomes a string. If an integer value is then
409 * assigned to $var, it becomes an integer.
410 */
411JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags)
412{
413 if( iFlags & MEMOBJ_STRING ){
414 return jx9MemObjToString;
415 }else if( iFlags & MEMOBJ_INT ){
416 return jx9MemObjToInteger;
417 }else if( iFlags & MEMOBJ_REAL ){
418 return jx9MemObjToReal;
419 }else if( iFlags & MEMOBJ_BOOL ){
420 return jx9MemObjToBool;
421 }else if( iFlags & MEMOBJ_HASHMAP ){
422 return jx9MemObjToHashmap;
423 }
424 /* NULL cast */
425 return jx9MemObjToNull;
426}
427/*
428 * Check whether the jx9_value is numeric [i.e: int/float/bool] or looks
429 * like a numeric number [i.e: if the jx9_value is of type string.].
430 * Return TRUE if numeric.FALSE otherwise.
431 */
432JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj)
433{
434 if( pObj->iFlags & ( MEMOBJ_BOOL|MEMOBJ_INT|MEMOBJ_REAL) ){
435 return TRUE;
436 }else if( pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES) ){
437 return FALSE;
438 }else if( pObj->iFlags & MEMOBJ_STRING ){
439 SyString sStr;
440 sxi32 rc;
441 SyStringInitFromBuf(&sStr, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
442 if( sStr.nByte <= 0 ){
443 /* Empty string */
444 return FALSE;
445 }
446 /* Check if the string representation looks like a numeric number */
447 rc = SyStrIsNumeric(sStr.zString, sStr.nByte, 0, 0);
448 return rc == SXRET_OK ? TRUE : FALSE;
449 }
450 /* NOT REACHED */
451 return FALSE;
452}
453/*
454 * Check whether the jx9_value is empty.Return TRUE if empty.
455 * FALSE otherwise.
456 * An jx9_value is considered empty if the following are true:
457 * NULL value.
458 * Boolean FALSE.
459 * Integer/Float with a 0 (zero) value.
460 * An empty string or a stream of 0 (zero) [i.e: "0", "00", "000", ...].
461 * An empty array.
462 * NOTE
463 * OBJECT VALUE MUST NOT BE MODIFIED.
464 */
465JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj)
466{
467 if( pObj->iFlags & MEMOBJ_NULL ){
468 return TRUE;
469 }else if( pObj->iFlags & MEMOBJ_INT ){
470 return pObj->x.iVal == 0 ? TRUE : FALSE;
471 }else if( pObj->iFlags & MEMOBJ_REAL ){
472 return pObj->x.rVal == (jx9_real)0 ? TRUE : FALSE;
473 }else if( pObj->iFlags & MEMOBJ_BOOL ){
474 return !pObj->x.iVal;
475 }else if( pObj->iFlags & MEMOBJ_STRING ){
476 if( SyBlobLength(&pObj->sBlob) <= 0 ){
477 return TRUE;
478 }else{
479 const char *zIn, *zEnd;
480 zIn = (const char *)SyBlobData(&pObj->sBlob);
481 zEnd = &zIn[SyBlobLength(&pObj->sBlob)];
482 while( zIn < zEnd ){
483 if( zIn[0] != '0' ){
484 break;
485 }
486 zIn++;
487 }
488 return zIn >= zEnd ? TRUE : FALSE;
489 }
490 }else if( pObj->iFlags & MEMOBJ_HASHMAP ){
491 jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther;
492 return pMap->nEntry == 0 ? TRUE : FALSE;
493 }else if ( pObj->iFlags & (MEMOBJ_RES) ){
494 return FALSE;
495 }
496 /* Assume empty by default */
497 return TRUE;
498}
499/*
500 * Convert a jx9_value so that it has types MEMOBJ_REAL or MEMOBJ_INT
501 * or both.
502 * Invalidate any prior representations. Every effort is made to force
503 * the conversion, even if the input is a string that does not look
504 * completely like a number.Convert as much of the string as we can
505 * and ignore the rest.
506 */
507JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj)
508{
509 if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) ){
510 if( pObj->iFlags & (MEMOBJ_BOOL|MEMOBJ_NULL) ){
511 if( pObj->iFlags & MEMOBJ_NULL ){
512 pObj->x.iVal = 0;
513 }
514 MemObjSetType(pObj, MEMOBJ_INT);
515 }
516 /* Already numeric */
517 return SXRET_OK;
518 }
519 if( pObj->iFlags & MEMOBJ_STRING ){
520 sxi32 rc = SXERR_INVALID;
521 sxu8 bReal = FALSE;
522 SyString sString;
523 SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
524 /* Check if the given string looks like a numeric number */
525 if( sString.nByte > 0 ){
526 rc = SyStrIsNumeric(sString.zString, sString.nByte, &bReal, 0);
527 }
528 if( bReal ){
529 jx9MemObjToReal(&(*pObj));
530 }else{
531 if( rc != SXRET_OK ){
532 /* The input does not look at all like a number, set the value to 0 */
533 pObj->x.iVal = 0;
534 }else{
535 /* Convert as much as we can */
536 pObj->x.iVal = MemObjStringToInt(&(*pObj));
537 }
538 MemObjSetType(pObj, MEMOBJ_INT);
539 SyBlobRelease(&pObj->sBlob);
540 }
541 }else if(pObj->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)){
542 jx9MemObjToInteger(pObj);
543 }else{
544 /* Perform a blind cast */
545 jx9MemObjToReal(&(*pObj));
546 }
547 return SXRET_OK;
548}
549/*
550 * Try a get an integer representation of the given jx9_value.
551 * If the jx9_value is not of type real, this function is a no-op.
552 */
553JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj)
554{
555 if( pObj->iFlags & MEMOBJ_REAL ){
556 /* Work only with reals */
557 MemObjTryIntger(&(*pObj));
558 }
559 return SXRET_OK;
560}
561/*
562 * Initialize a jx9_value to the null type.
563 */
564JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj)
565{
566 /* Zero the structure */
567 SyZero(pObj, sizeof(jx9_value));
568 /* Initialize fields */
569 pObj->pVm = pVm;
570 SyBlobInit(&pObj->sBlob, &pVm->sAllocator);
571 /* Set the NULL type */
572 pObj->iFlags = MEMOBJ_NULL;
573 return SXRET_OK;
574}
575/*
576 * Initialize a jx9_value to the integer type.
577 */
578JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal)
579{
580 /* Zero the structure */
581 SyZero(pObj, sizeof(jx9_value));
582 /* Initialize fields */
583 pObj->pVm = pVm;
584 SyBlobInit(&pObj->sBlob, &pVm->sAllocator);
585 /* Set the desired type */
586 pObj->x.iVal = iVal;
587 pObj->iFlags = MEMOBJ_INT;
588 return SXRET_OK;
589}
590/*
591 * Initialize a jx9_value to the boolean type.
592 */
593JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal)
594{
595 /* Zero the structure */
596 SyZero(pObj, sizeof(jx9_value));
597 /* Initialize fields */
598 pObj->pVm = pVm;
599 SyBlobInit(&pObj->sBlob, &pVm->sAllocator);
600 /* Set the desired type */
601 pObj->x.iVal = iVal ? 1 : 0;
602 pObj->iFlags = MEMOBJ_BOOL;
603 return SXRET_OK;
604}
605#if 0
606/*
607 * Initialize a jx9_value to the real type.
608 */
609JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal)
610{
611 /* Zero the structure */
612 SyZero(pObj, sizeof(jx9_value));
613 /* Initialize fields */
614 pObj->pVm = pVm;
615 SyBlobInit(&pObj->sBlob, &pVm->sAllocator);
616 /* Set the desired type */
617 pObj->x.rVal = rVal;
618 pObj->iFlags = MEMOBJ_REAL;
619 return SXRET_OK;
620}
621#endif
622/*
623 * Initialize a jx9_value to the array type.
624 */
625JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray)
626{
627 /* Zero the structure */
628 SyZero(pObj, sizeof(jx9_value));
629 /* Initialize fields */
630 pObj->pVm = pVm;
631 SyBlobInit(&pObj->sBlob, &pVm->sAllocator);
632 /* Set the desired type */
633 pObj->iFlags = MEMOBJ_HASHMAP;
634 pObj->x.pOther = pArray;
635 return SXRET_OK;
636}
637/*
638 * Initialize a jx9_value to the string type.
639 */
640JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal)
641{
642 /* Zero the structure */
643 SyZero(pObj, sizeof(jx9_value));
644 /* Initialize fields */
645 pObj->pVm = pVm;
646 SyBlobInit(&pObj->sBlob, &pVm->sAllocator);
647 if( pVal ){
648 /* Append contents */
649 SyBlobAppend(&pObj->sBlob, (const void *)pVal->zString, pVal->nByte);
650 }
651 /* Set the desired type */
652 pObj->iFlags = MEMOBJ_STRING;
653 return SXRET_OK;
654}
655/*
656 * Append some contents to the internal buffer of a given jx9_value.
657 * If the given jx9_value is not of type string, this function
658 * invalidate any prior representation and set the string type.
659 * Then a simple append operation is performed.
660 */
661JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen)
662{
663 sxi32 rc;
664 if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){
665 /* Invalidate any prior representation */
666 jx9MemObjRelease(pObj);
667 MemObjSetType(pObj, MEMOBJ_STRING);
668 }
669 /* Append contents */
670 rc = SyBlobAppend(&pObj->sBlob, zData, nLen);
671 return rc;
672}
673#if 0
674/*
675 * Format and append some contents to the internal buffer of a given jx9_value.
676 * If the given jx9_value is not of type string, this function invalidate
677 * any prior representation and set the string type.
678 * Then a simple format and append operation is performed.
679 */
680JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap)
681{
682 sxi32 rc;
683 if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){
684 /* Invalidate any prior representation */
685 jx9MemObjRelease(pObj);
686 MemObjSetType(pObj, MEMOBJ_STRING);
687 }
688 /* Format and append contents */
689 rc = SyBlobFormatAp(&pObj->sBlob, zFormat, ap);
690 return rc;
691}
692#endif
693/*
694 * Duplicate the contents of a jx9_value.
695 */
696JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest)
697{
698 jx9_hashmap *pMap = 0;
699 sxi32 rc;
700 if( pSrc->iFlags & MEMOBJ_HASHMAP ){
701 /* Increment reference count */
702 ((jx9_hashmap *)pSrc->x.pOther)->iRef++;
703 }
704 if( pDest->iFlags & MEMOBJ_HASHMAP ){
705 pMap = (jx9_hashmap *)pDest->x.pOther;
706 }
707 SyMemcpy((const void *)&(*pSrc), &(*pDest), sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32)));
708 rc = SXRET_OK;
709 if( SyBlobLength(&pSrc->sBlob) > 0 ){
710 SyBlobReset(&pDest->sBlob);
711 rc = SyBlobDup(&pSrc->sBlob, &pDest->sBlob);
712 }else{
713 if( SyBlobLength(&pDest->sBlob) > 0 ){
714 SyBlobRelease(&pDest->sBlob);
715 }
716 }
717 if( pMap ){
718 jx9HashmapUnref(pMap);
719 }
720 return rc;
721}
722/*
723 * Duplicate the contents of a jx9_value but do not copy internal
724 * buffer contents, simply point to it.
725 */
726JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest)
727{
728 SyMemcpy((const void *)&(*pSrc), &(*pDest),
729 sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32)));
730 if( pSrc->iFlags & MEMOBJ_HASHMAP ){
731 /* Increment reference count */
732 ((jx9_hashmap *)pSrc->x.pOther)->iRef++;
733 }
734 if( SyBlobLength(&pDest->sBlob) > 0 ){
735 SyBlobRelease(&pDest->sBlob);
736 }
737 if( SyBlobLength(&pSrc->sBlob) > 0 ){
738 SyBlobReadOnly(&pDest->sBlob, SyBlobData(&pSrc->sBlob), SyBlobLength(&pSrc->sBlob));
739 }
740 return SXRET_OK;
741}
742/*
743 * Invalidate any prior representation of a given jx9_value.
744 */
745JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj)
746{
747 if( (pObj->iFlags & MEMOBJ_NULL) == 0 ){
748 if( pObj->iFlags & MEMOBJ_HASHMAP ){
749 jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther);
750 }
751 /* Release the internal buffer */
752 SyBlobRelease(&pObj->sBlob);
753 /* Invalidate any prior representation */
754 pObj->iFlags = MEMOBJ_NULL;
755 }
756 return SXRET_OK;
757}
758/*
759 * Compare two jx9_values.
760 * Return 0 if the values are equals, > 0 if pObj1 is greater than pObj2
761 * or < 0 if pObj2 is greater than pObj1.
762 * Type comparison table taken from the JX9 language reference manual.
763 * Comparisons of $x with JX9 functions Expression
764 * gettype() empty() is_null() isset() boolean : if($x)
765 * $x = ""; string TRUE FALSE TRUE FALSE
766 * $x = null NULL TRUE TRUE FALSE FALSE
767 * var $x; NULL TRUE TRUE FALSE FALSE
768 * $x is undefined NULL TRUE TRUE FALSE FALSE
769 * $x = array(); array TRUE FALSE TRUE FALSE
770 * $x = false; boolean TRUE FALSE TRUE FALSE
771 * $x = true; boolean FALSE FALSE TRUE TRUE
772 * $x = 1; integer FALSE FALSE TRUE TRUE
773 * $x = 42; integer FALSE FALSE TRUE TRUE
774 * $x = 0; integer TRUE FALSE TRUE FALSE
775 * $x = -1; integer FALSE FALSE TRUE TRUE
776 * $x = "1"; string FALSE FALSE TRUE TRUE
777 * $x = "0"; string TRUE FALSE TRUE FALSE
778 * $x = "-1"; string FALSE FALSE TRUE TRUE
779 * $x = "jx9"; string FALSE FALSE TRUE TRUE
780 * $x = "true"; string FALSE FALSE TRUE TRUE
781 * $x = "false"; string FALSE FALSE TRUE TRUE
782 * Loose comparisons with ==
783 * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" ""
784 * TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE
785 * FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE TRUE FALSE TRUE
786 * 1 TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
787 * 0 FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE TRUE
788 * -1 TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
789 * "1" TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
790 * "0" FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
791 * "-1" TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
792 * NULL FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE
793 * array() FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE
794 * "jx9" TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
795 * "" FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE
796 * Strict comparisons with ===
797 * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" ""
798 * TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
799 * FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
800 * 1 FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
801 * 0 FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
802 * -1 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
803 * "1" FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
804 * "0" FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE
805 * "-1" FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
806 * NULL FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
807 * array() FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
808 * "jx9" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE
809 * "" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE
810 */
811JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest)
812{
813 sxi32 iComb;
814 sxi32 rc;
815 if( bStrict ){
816 sxi32 iF1, iF2;
817 /* Strict comparisons with === */
818 iF1 = pObj1->iFlags;
819 iF2 = pObj2->iFlags;
820 if( iF1 != iF2 ){
821 /* Not of the same type */
822 return 1;
823 }
824 }
825 /* Combine flag together */
826 iComb = pObj1->iFlags|pObj2->iFlags;
827 if( iComb & (MEMOBJ_NULL|MEMOBJ_RES|MEMOBJ_BOOL) ){
828 /* Convert to boolean: Keep in mind FALSE < TRUE */
829 if( (pObj1->iFlags & MEMOBJ_BOOL) == 0 ){
830 jx9MemObjToBool(pObj1);
831 }
832 if( (pObj2->iFlags & MEMOBJ_BOOL) == 0 ){
833 jx9MemObjToBool(pObj2);
834 }
835 return (sxi32)((pObj1->x.iVal != 0) - (pObj2->x.iVal != 0));
836 }else if ( iComb & MEMOBJ_HASHMAP ){
837 /* Hashmap aka 'array' comparison */
838 if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){
839 /* Array is always greater */
840 return -1;
841 }
842 if( (pObj2->iFlags & MEMOBJ_HASHMAP) == 0 ){
843 /* Array is always greater */
844 return 1;
845 }
846 /* Perform the comparison */
847 rc = jx9HashmapCmp((jx9_hashmap *)pObj1->x.pOther, (jx9_hashmap *)pObj2->x.pOther, bStrict);
848 return rc;
849 }else if ( iComb & MEMOBJ_STRING ){
850 SyString s1, s2;
851 /* Perform a strict string comparison.*/
852 if( (pObj1->iFlags&MEMOBJ_STRING) == 0 ){
853 jx9MemObjToString(pObj1);
854 }
855 if( (pObj2->iFlags&MEMOBJ_STRING) == 0 ){
856 jx9MemObjToString(pObj2);
857 }
858 SyStringInitFromBuf(&s1, SyBlobData(&pObj1->sBlob), SyBlobLength(&pObj1->sBlob));
859 SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob));
860 /*
861 * Strings are compared using memcmp(). If one value is an exact prefix of the
862 * other, then the shorter value is less than the longer value.
863 */
864 rc = SyMemcmp((const void *)s1.zString, (const void *)s2.zString, SXMIN(s1.nByte, s2.nByte));
865 if( rc == 0 ){
866 if( s1.nByte != s2.nByte ){
867 rc = s1.nByte < s2.nByte ? -1 : 1;
868 }
869 }
870 return rc;
871 }else if( iComb & (MEMOBJ_INT|MEMOBJ_REAL) ){
872 /* Perform a numeric comparison if one of the operand is numeric(integer or real) */
873 if( (pObj1->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){
874 jx9MemObjToNumeric(pObj1);
875 }
876 if( (pObj2->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){
877 jx9MemObjToNumeric(pObj2);
878 }
879 if( (pObj1->iFlags & pObj2->iFlags & MEMOBJ_INT) == 0) {
880 jx9_real r1, r2;
881 /* Compare as reals */
882 if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){
883 jx9MemObjToReal(pObj1);
884 }
885 r1 = pObj1->x.rVal;
886 if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){
887 jx9MemObjToReal(pObj2);
888 }
889 r2 = pObj2->x.rVal;
890 if( r1 > r2 ){
891 return 1;
892 }else if( r1 < r2 ){
893 return -1;
894 }
895 return 0;
896 }else{
897 /* Integer comparison */
898 if( pObj1->x.iVal > pObj2->x.iVal ){
899 return 1;
900 }else if( pObj1->x.iVal < pObj2->x.iVal ){
901 return -1;
902 }
903 return 0;
904 }
905 }
906 /* NOT REACHED */
907 SXUNUSED(iNest);
908 return 0;
909}
910/*
911 * Perform an addition operation of two jx9_values.
912 * The reason this function is implemented here rather than 'vm.c'
913 * is that the '+' operator is overloaded.
914 * That is, the '+' operator is used for arithmetic operation and also
915 * used for operation on arrays [i.e: union]. When used with an array
916 * The + operator returns the right-hand array appended to the left-hand array.
917 * For keys that exist in both arrays, the elements from the left-hand array
918 * will be used, and the matching elements from the right-hand array will
919 * be ignored.
920 * This function take care of handling all the scenarios.
921 */
922JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore)
923{
924 if( ((pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP) == 0 ){
925 /* Arithemtic operation */
926 jx9MemObjToNumeric(pObj1);
927 jx9MemObjToNumeric(pObj2);
928 if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_REAL ){
929 /* Floating point arithmetic */
930 jx9_real a, b;
931 if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){
932 jx9MemObjToReal(pObj1);
933 }
934 if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){
935 jx9MemObjToReal(pObj2);
936 }
937 a = pObj1->x.rVal;
938 b = pObj2->x.rVal;
939 pObj1->x.rVal = a+b;
940 MemObjSetType(pObj1, MEMOBJ_REAL);
941 /* Try to get an integer representation also */
942 MemObjTryIntger(&(*pObj1));
943 }else{
944 /* Integer arithmetic */
945 sxi64 a, b;
946 a = pObj1->x.iVal;
947 b = pObj2->x.iVal;
948 pObj1->x.iVal = a+b;
949 MemObjSetType(pObj1, MEMOBJ_INT);
950 }
951 }else{
952 if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP ){
953 jx9_hashmap *pMap;
954 sxi32 rc;
955 if( bAddStore ){
956 /* Do not duplicate the hashmap, use the left one since its an add&store operation.
957 */
958 if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){
959 /* Force a hashmap cast */
960 rc = jx9MemObjToHashmap(pObj1);
961 if( rc != SXRET_OK ){
962 jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array");
963 return rc;
964 }
965 }
966 /* Point to the structure that describe the hashmap */
967 pMap = (jx9_hashmap *)pObj1->x.pOther;
968 }else{
969 /* Create a new hashmap */
970 pMap = jx9NewHashmap(pObj1->pVm, 0, 0);
971 if( pMap == 0){
972 jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array");
973 return SXERR_MEM;
974 }
975 }
976 if( !bAddStore ){
977 if(pObj1->iFlags & MEMOBJ_HASHMAP ){
978 /* Perform a hashmap duplication */
979 jx9HashmapDup((jx9_hashmap *)pObj1->x.pOther, pMap);
980 }else{
981 if((pObj1->iFlags & MEMOBJ_NULL) == 0 ){
982 /* Simple insertion */
983 jx9HashmapInsert(pMap, 0, pObj1);
984 }
985 }
986 }
987 /* Perform the union */
988 if(pObj2->iFlags & MEMOBJ_HASHMAP ){
989 jx9HashmapUnion(pMap, (jx9_hashmap *)pObj2->x.pOther);
990 }else{
991 if((pObj2->iFlags & MEMOBJ_NULL) == 0 ){
992 /* Simple insertion */
993 jx9HashmapInsert(pMap, 0, pObj2);
994 }
995 }
996 /* Reflect the change */
997 if( pObj1->iFlags & MEMOBJ_STRING ){
998 SyBlobRelease(&pObj1->sBlob);
999 }
1000 pObj1->x.pOther = pMap;
1001 MemObjSetType(pObj1, MEMOBJ_HASHMAP);
1002 }
1003 }
1004 return SXRET_OK;
1005}
1006/*
1007 * Return a printable representation of the type of a given
1008 * jx9_value.
1009 */
1010JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal)
1011{
1012 const char *zType = "";
1013 if( pVal->iFlags & MEMOBJ_NULL ){
1014 zType = "null";
1015 }else if( pVal->iFlags & MEMOBJ_INT ){
1016 zType = "int";
1017 }else if( pVal->iFlags & MEMOBJ_REAL ){
1018 zType = "float";
1019 }else if( pVal->iFlags & MEMOBJ_STRING ){
1020 zType = "string";
1021 }else if( pVal->iFlags & MEMOBJ_BOOL ){
1022 zType = "bool";
1023 }else if( pVal->iFlags & MEMOBJ_HASHMAP ){
1024 jx9_hashmap *pMap = (jx9_hashmap *)pVal->x.pOther;
1025 if( pMap->iFlags & HASHMAP_JSON_OBJECT ){
1026 zType = "JSON Object";
1027 }else{
1028 zType = "JSON Array";
1029 }
1030 }else if( pVal->iFlags & MEMOBJ_RES ){
1031 zType = "resource";
1032 }
1033 return zType;
1034}
1035/*
1036 * Dump a jx9_value [i.e: get a printable representation of it's type and contents.].
1037 * Store the dump in the given blob.
1038 */
1039JX9_PRIVATE sxi32 jx9MemObjDump(
1040 SyBlob *pOut, /* Store the dump here */
1041 jx9_value *pObj /* Dump this */
1042 )
1043{
1044 sxi32 rc = SXRET_OK;
1045 const char *zType;
1046 /* Get value type first */
1047 zType = jx9MemObjTypeDump(pObj);
1048 SyBlobAppend(&(*pOut), zType, SyStrlen(zType));
1049 if((pObj->iFlags & MEMOBJ_NULL) == 0 ){
1050 SyBlobAppend(&(*pOut), "(", sizeof(char));
1051 if( pObj->iFlags & MEMOBJ_HASHMAP ){
1052 jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther;
1053 SyBlobFormat(pOut,"%u ",pMap->nEntry);
1054 /* Dump hashmap entries */
1055 rc = jx9JsonSerialize(pObj,pOut);
1056 }else{
1057 SyBlob *pContents = &pObj->sBlob;
1058 /* Get a printable representation of the contents */
1059 if((pObj->iFlags & MEMOBJ_STRING) == 0 ){
1060 MemObjStringValue(&(*pOut), &(*pObj));
1061 }else{
1062 /* Append length first */
1063 SyBlobFormat(&(*pOut), "%u '", SyBlobLength(&pObj->sBlob));
1064 if( SyBlobLength(pContents) > 0 ){
1065 SyBlobAppend(&(*pOut), SyBlobData(pContents), SyBlobLength(pContents));
1066 }
1067 SyBlobAppend(&(*pOut), "'", sizeof(char));
1068 }
1069 }
1070 SyBlobAppend(&(*pOut), ")", sizeof(char));
1071 }
1072#ifdef __WINNT__
1073 SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n")-1);
1074#else
1075 SyBlobAppend(&(*pOut), "\n", sizeof(char));
1076#endif
1077 return rc;
1078}
diff --git a/common/unqlite/jx9_parse.c b/common/unqlite/jx9_parse.c
new file mode 100644
index 0000000..fff934e
--- /dev/null
+++ b/common/unqlite/jx9_parse.c
@@ -0,0 +1,1177 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: parse.c v1.2 FreeBSD 2012-12-11 00:46 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/* Expression parser for the Jx9 programming language */
18/* Operators associativity */
19#define EXPR_OP_ASSOC_LEFT 0x01 /* Left associative operator */
20#define EXPR_OP_ASSOC_RIGHT 0x02 /* Right associative operator */
21#define EXPR_OP_NON_ASSOC 0x04 /* Non-associative operator */
22/*
23 * Operators table
24 * This table is sorted by operators priority (highest to lowest) according
25 * the JX9 language reference manual.
26 * JX9 implements all the 60 JX9 operators and have introduced the eq and ne operators.
27 * The operators precedence table have been improved dramatically so that you can do same
28 * amazing things now such as array dereferencing, on the fly function call, anonymous function
29 * as array values, object member access on instantiation and so on.
30 * Refer to the following page for a full discussion on these improvements:
31 * http://jx9.symisc.net/features.html
32 */
33static const jx9_expr_op aOpTable[] = {
34 /* Postfix operators */
35 /* Precedence 2(Highest), left-associative */
36 { {".", sizeof(char)}, EXPR_OP_DOT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_MEMBER },
37 { {"[", sizeof(char)}, EXPR_OP_SUBSCRIPT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_LOAD_IDX},
38 /* Precedence 3, non-associative */
39 { {"++", sizeof(char)*2}, EXPR_OP_INCR, 3, EXPR_OP_NON_ASSOC , JX9_OP_INCR},
40 { {"--", sizeof(char)*2}, EXPR_OP_DECR, 3, EXPR_OP_NON_ASSOC , JX9_OP_DECR},
41 /* Unary operators */
42 /* Precedence 4, right-associative */
43 { {"-", sizeof(char)}, EXPR_OP_UMINUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UMINUS },
44 { {"+", sizeof(char)}, EXPR_OP_UPLUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UPLUS },
45 { {"~", sizeof(char)}, EXPR_OP_BITNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_BITNOT },
46 { {"!", sizeof(char)}, EXPR_OP_LOGNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_LNOT },
47 /* Cast operators */
48 { {"(int)", sizeof("(int)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_INT },
49 { {"(bool)", sizeof("(bool)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_BOOL },
50 { {"(string)", sizeof("(string)")-1}, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_STR },
51 { {"(float)", sizeof("(float)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_REAL },
52 { {"(array)", sizeof("(array)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */
53 { {"(object)", sizeof("(object)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */
54 /* Binary operators */
55 /* Precedence 7, left-associative */
56 { {"*", sizeof(char)}, EXPR_OP_MUL, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MUL},
57 { {"/", sizeof(char)}, EXPR_OP_DIV, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_DIV},
58 { {"%", sizeof(char)}, EXPR_OP_MOD, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MOD},
59 /* Precedence 8, left-associative */
60 { {"+", sizeof(char)}, EXPR_OP_ADD, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_ADD},
61 { {"-", sizeof(char)}, EXPR_OP_SUB, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_SUB},
62 { {"..", sizeof(char)*2},EXPR_OP_DDOT, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_CAT},
63 /* Precedence 9, left-associative */
64 { {"<<", sizeof(char)*2}, EXPR_OP_SHL, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHL},
65 { {">>", sizeof(char)*2}, EXPR_OP_SHR, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHR},
66 /* Precedence 10, non-associative */
67 { {"<", sizeof(char)}, EXPR_OP_LT, 10, EXPR_OP_NON_ASSOC, JX9_OP_LT},
68 { {">", sizeof(char)}, EXPR_OP_GT, 10, EXPR_OP_NON_ASSOC, JX9_OP_GT},
69 { {"<=", sizeof(char)*2}, EXPR_OP_LE, 10, EXPR_OP_NON_ASSOC, JX9_OP_LE},
70 { {">=", sizeof(char)*2}, EXPR_OP_GE, 10, EXPR_OP_NON_ASSOC, JX9_OP_GE},
71 { {"<>", sizeof(char)*2}, EXPR_OP_NE, 10, EXPR_OP_NON_ASSOC, JX9_OP_NEQ},
72 /* Precedence 11, non-associative */
73 { {"==", sizeof(char)*2}, EXPR_OP_EQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_EQ},
74 { {"!=", sizeof(char)*2}, EXPR_OP_NE, 11, EXPR_OP_NON_ASSOC, JX9_OP_NEQ},
75 { {"===", sizeof(char)*3}, EXPR_OP_TEQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_TEQ},
76 { {"!==", sizeof(char)*3}, EXPR_OP_TNE, 11, EXPR_OP_NON_ASSOC, JX9_OP_TNE},
77 /* Precedence 12, left-associative */
78 { {"&", sizeof(char)}, EXPR_OP_BAND, 12, EXPR_OP_ASSOC_LEFT, JX9_OP_BAND},
79 /* Binary operators */
80 /* Precedence 13, left-associative */
81 { {"^", sizeof(char)}, EXPR_OP_XOR, 13, EXPR_OP_ASSOC_LEFT, JX9_OP_BXOR},
82 /* Precedence 14, left-associative */
83 { {"|", sizeof(char)}, EXPR_OP_BOR, 14, EXPR_OP_ASSOC_LEFT, JX9_OP_BOR},
84 /* Precedence 15, left-associative */
85 { {"&&", sizeof(char)*2}, EXPR_OP_LAND, 15, EXPR_OP_ASSOC_LEFT, JX9_OP_LAND},
86 /* Precedence 16, left-associative */
87 { {"||", sizeof(char)*2}, EXPR_OP_LOR, 16, EXPR_OP_ASSOC_LEFT, JX9_OP_LOR},
88 /* Ternary operator */
89 /* Precedence 17, left-associative */
90 { {"?", sizeof(char)}, EXPR_OP_QUESTY, 17, EXPR_OP_ASSOC_LEFT, 0},
91 /* Combined binary operators */
92 /* Precedence 18, right-associative */
93 { {"=", sizeof(char)}, EXPR_OP_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_STORE},
94 { {"+=", sizeof(char)*2}, EXPR_OP_ADD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_ADD_STORE },
95 { {"-=", sizeof(char)*2}, EXPR_OP_SUB_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SUB_STORE },
96 { {".=", sizeof(char)*2}, EXPR_OP_DOT_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_CAT_STORE },
97 { {"*=", sizeof(char)*2}, EXPR_OP_MUL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MUL_STORE },
98 { {"/=", sizeof(char)*2}, EXPR_OP_DIV_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_DIV_STORE },
99 { {"%=", sizeof(char)*2}, EXPR_OP_MOD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MOD_STORE },
100 { {"&=", sizeof(char)*2}, EXPR_OP_AND_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BAND_STORE },
101 { {"|=", sizeof(char)*2}, EXPR_OP_OR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BOR_STORE },
102 { {"^=", sizeof(char)*2}, EXPR_OP_XOR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BXOR_STORE },
103 { {"<<=", sizeof(char)*3}, EXPR_OP_SHL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHL_STORE },
104 { {">>=", sizeof(char)*3}, EXPR_OP_SHR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHR_STORE },
105 /* Precedence 22, left-associative [Lowest operator] */
106 { {",", sizeof(char)}, EXPR_OP_COMMA, 22, EXPR_OP_ASSOC_LEFT, 0}, /* IMP-0139-COMMA: Symisc eXtension */
107};
108/* Function call operator need special handling */
109static const jx9_expr_op sFCallOp = {{"(", sizeof(char)}, EXPR_OP_FUNC_CALL, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_CALL};
110/*
111 * Check if the given token is a potential operator or not.
112 * This function is called by the lexer each time it extract a token that may
113 * look like an operator.
114 * Return a structure [i.e: jx9_expr_op instnace ] that describe the operator on success.
115 * Otherwise NULL.
116 * Note that the function take care of handling ambiguity [i.e: whether we are dealing with
117 * a binary minus or unary minus.]
118 */
119JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast)
120{
121 sxu32 n = 0;
122 sxi32 rc;
123 /* Do a linear lookup on the operators table */
124 for(;;){
125 if( n >= SX_ARRAYSIZE(aOpTable) ){
126 break;
127 }
128 rc = SyStringCmp(pStr, &aOpTable[n].sOp, SyMemcmp);
129 if( rc == 0 ){
130 if( aOpTable[n].sOp.nByte != sizeof(char) || (aOpTable[n].iOp != EXPR_OP_UMINUS && aOpTable[n].iOp != EXPR_OP_UPLUS) || pLast == 0 ){
131 if( aOpTable[n].iOp == EXPR_OP_SUBSCRIPT && (pLast == 0 || (pLast->nType & (JX9_TK_ID|JX9_TK_CSB/*]*/|JX9_TK_RPAREN/*)*/)) == 0) ){
132 /* JSON Array not subscripting, return NULL */
133 return 0;
134 }
135 /* There is no ambiguity here, simply return the first operator seen */
136 return &aOpTable[n];
137 }
138 /* Handle ambiguity */
139 if( pLast->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_COLON/*:*/|JX9_TK_COMMA/*, '*/) ){
140 /* Unary opertors have prcedence here over binary operators */
141 return &aOpTable[n];
142 }
143 if( pLast->nType & JX9_TK_OP ){
144 const jx9_expr_op *pOp = (const jx9_expr_op *)pLast->pUserData;
145 /* Ticket 1433-31: Handle the '++', '--' operators case */
146 if( pOp->iOp != EXPR_OP_INCR && pOp->iOp != EXPR_OP_DECR ){
147 /* Unary opertors have prcedence here over binary operators */
148 return &aOpTable[n];
149 }
150
151 }
152 }
153 ++n; /* Next operator in the table */
154 }
155 /* No such operator */
156 return 0;
157}
158/*
159 * Delimit a set of token stream.
160 * This function take care of handling the nesting level and stops when it hit
161 * the end of the input or the ending token is found and the nesting level is zero.
162 */
163JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn,SyToken *pEnd,sxu32 nTokStart,sxu32 nTokEnd,SyToken **ppEnd)
164{
165 SyToken *pCur = pIn;
166 sxi32 iNest = 1;
167 for(;;){
168 if( pCur >= pEnd ){
169 break;
170 }
171 if( pCur->nType & nTokStart ){
172 /* Increment nesting level */
173 iNest++;
174 }else if( pCur->nType & nTokEnd ){
175 /* Decrement nesting level */
176 iNest--;
177 if( iNest <= 0 ){
178 break;
179 }
180 }
181 /* Advance cursor */
182 pCur++;
183 }
184 /* Point to the end of the chunk */
185 *ppEnd = pCur;
186}
187/*
188 * Retrun TRUE if the given ID represent a language construct [i.e: print, print..]. FALSE otherwise.
189 * Note on reserved keywords.
190 * According to the JX9 language reference manual:
191 * These words have special meaning in JX9. Some of them represent things which look like
192 * functions, some look like constants, and so on--but they're not, really: they are language
193 * constructs. You cannot use any of the following words as constants, object names, function
194 * or method names. Using them as variable names is generally OK, but could lead to confusion.
195 */
196JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID)
197{
198 if( nKeyID == JX9_TKWRD_PRINT || nKeyID == JX9_TKWRD_EXIT || nKeyID == JX9_TKWRD_DIE
199 || nKeyID == JX9_TKWRD_INCLUDE|| nKeyID == JX9_TKWRD_IMPORT ){
200 return TRUE;
201 }
202 /* Not a language construct */
203 return FALSE;
204}
205/*
206 * Point to the next expression that should be evaluated shortly.
207 * The cursor stops when it hit a comma ', ' or a semi-colon and the nesting
208 * level is zero.
209 */
210JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart,SyToken *pEnd,SyToken **ppNext)
211{
212 SyToken *pCur = pStart;
213 sxi32 iNest = 0;
214 if( pCur >= pEnd || (pCur->nType & JX9_TK_SEMI/*';'*/) ){
215 /* Last expression */
216 return SXERR_EOF;
217 }
218 while( pCur < pEnd ){
219 if( (pCur->nType & (JX9_TK_COMMA/*','*/|JX9_TK_SEMI/*';'*/)) && iNest <= 0){
220 break;
221 }
222 if( pCur->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OSB/*'['*/|JX9_TK_OCB/*'{'*/) ){
223 iNest++;
224 }else if( pCur->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*']'*/|JX9_TK_CCB/*'}*/) ){
225 iNest--;
226 }
227 pCur++;
228 }
229 *ppNext = pCur;
230 return SXRET_OK;
231}
232/*
233 * Collect and assemble tokens holding annonymous functions/closure body.
234 * When errors, JX9 take care of generating the appropriate error message.
235 * Note on annonymous functions.
236 * According to the JX9 language reference manual:
237 * Anonymous functions, also known as closures, allow the creation of functions
238 * which have no specified name. They are most useful as the value of callback
239 * parameters, but they have many other uses.
240 * Closures may also inherit variables from the parent scope. Any such variables
241 * must be declared in the function header. Inheriting variables from the parent
242 * scope is not the same as using global variables. Global variables exist in the global scope
243 * which is the same no matter what function is executing. The parent scope of a closure is the
244 * function in which the closure was declared (not necessarily the function it was called from).
245 *
246 * Some example:
247 * $greet = function($name)
248 * {
249 * printf("Hello %s\r\n", $name);
250 * };
251 * $greet('World');
252 * $greet('JX9');
253 *
254 * $double = function($a) {
255 * return $a * 2;
256 * };
257 * // This is our range of numbers
258 * $numbers = range(1, 5);
259 * // Use the Annonymous function as a callback here to
260 * // double the size of each element in our
261 * // range
262 * $new_numbers = array_map($double, $numbers);
263 * print implode(' ', $new_numbers);
264 */
265static sxi32 ExprAssembleAnnon(jx9_gen_state *pGen,SyToken **ppCur, SyToken *pEnd)
266{
267 SyToken *pIn = *ppCur;
268 sxu32 nLine;
269 sxi32 rc;
270 /* Jump the 'function' keyword */
271 nLine = pIn->nLine;
272 pIn++;
273 if( pIn < pEnd && (pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) ){
274 pIn++;
275 }
276 if( pIn >= pEnd || (pIn->nType & JX9_TK_LPAREN) == 0 ){
277 /* Syntax error */
278 rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing opening parenthesis '(' while declaring annonymous function");
279 if( rc != SXERR_ABORT ){
280 rc = SXERR_SYNTAX;
281 }
282 goto Synchronize;
283 }
284 pIn++; /* Jump the leading parenthesis '(' */
285 jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_LPAREN/*'('*/, JX9_TK_RPAREN/*')'*/, &pIn);
286 if( pIn >= pEnd || &pIn[1] >= pEnd ){
287 /* Syntax error */
288 rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function");
289 if( rc != SXERR_ABORT ){
290 rc = SXERR_SYNTAX;
291 }
292 goto Synchronize;
293 }
294 pIn++; /* Jump the trailing parenthesis */
295 if( pIn->nType & JX9_TK_OCB /*'{'*/ ){
296 pIn++; /* Jump the leading curly '{' */
297 jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_OCB/*'{'*/, JX9_TK_CCB/*'}'*/, &pIn);
298 if( pIn < pEnd ){
299 pIn++;
300 }
301 }else{
302 /* Syntax error */
303 rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function, missing '{'");
304 if( rc == SXERR_ABORT ){
305 return SXERR_ABORT;
306 }
307 }
308 rc = SXRET_OK;
309Synchronize:
310 /* Synchronize pointers */
311 *ppCur = pIn;
312 return rc;
313}
314/*
315 * Make sure we are dealing with a valid expression tree.
316 * This function check for balanced parenthesis, braces, brackets and so on.
317 * When errors, JX9 take care of generating the appropriate error message.
318 * Return SXRET_OK on success. Any other return value indicates syntax error.
319 */
320static sxi32 ExprVerifyNodes(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nNode)
321{
322 sxi32 iParen, iSquare, iBraces;
323 sxi32 i, rc;
324
325 if( nNode > 0 && apNode[0]->pOp && (apNode[0]->pOp->iOp == EXPR_OP_ADD || apNode[0]->pOp->iOp == EXPR_OP_SUB) ){
326 /* Fix and mark as an unary not binary plus/minus operator */
327 apNode[0]->pOp = jx9ExprExtractOperator(&apNode[0]->pStart->sData, 0);
328 apNode[0]->pStart->pUserData = (void *)apNode[0]->pOp;
329 }
330 iParen = iSquare = iBraces = 0;
331 for( i = 0 ; i < nNode ; ++i ){
332 if( apNode[i]->pStart->nType & JX9_TK_LPAREN /*'('*/){
333 if( i > 0 && ( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral ||
334 (apNode[i - 1]->pStart->nType & (JX9_TK_ID|JX9_TK_KEYWORD|JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*]*/))) ){
335 /* Ticket 1433-033: Take care to ignore alpha-stream [i.e: or, xor] operators followed by an opening parenthesis */
336 if( (apNode[i - 1]->pStart->nType & JX9_TK_OP) == 0 ){
337 /* We are dealing with a postfix [i.e: function call] operator
338 * not a simple left parenthesis. Mark the node.
339 */
340 apNode[i]->pStart->nType |= JX9_TK_OP;
341 apNode[i]->pStart->pUserData = (void *)&sFCallOp; /* Function call operator */
342 apNode[i]->pOp = &sFCallOp;
343 }
344 }
345 iParen++;
346 }else if( apNode[i]->pStart->nType & JX9_TK_RPAREN/*')*/){
347 if( iParen <= 0 ){
348 rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ')'");
349 if( rc != SXERR_ABORT ){
350 rc = SXERR_SYNTAX;
351 }
352 return rc;
353 }
354 iParen--;
355 }else if( apNode[i]->pStart->nType & JX9_TK_OSB /*'['*/ && apNode[i]->xCode == 0 ){
356 iSquare++;
357 }else if (apNode[i]->pStart->nType & JX9_TK_CSB /*']'*/){
358 if( iSquare <= 0 ){
359 rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ']'");
360 if( rc != SXERR_ABORT ){
361 rc = SXERR_SYNTAX;
362 }
363 return rc;
364 }
365 iSquare--;
366 }else if( apNode[i]->pStart->nType & JX9_TK_OCB /*'{'*/ && apNode[i]->xCode == 0 ){
367 iBraces++;
368 }else if (apNode[i]->pStart->nType & JX9_TK_CCB /*'}'*/){
369 if( iBraces <= 0 ){
370 rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token '}'");
371 if( rc != SXERR_ABORT ){
372 rc = SXERR_SYNTAX;
373 }
374 return rc;
375 }
376 iBraces--;
377 }else if( apNode[i]->pStart->nType & JX9_TK_OP ){
378 const jx9_expr_op *pOp = (const jx9_expr_op *)apNode[i]->pOp;
379 if( i > 0 && (pOp->iOp == EXPR_OP_UMINUS || pOp->iOp == EXPR_OP_UPLUS)){
380 if( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral ){
381 sxi32 iExprOp = EXPR_OP_SUB; /* Binary minus */
382 sxu32 n = 0;
383 if( pOp->iOp == EXPR_OP_UPLUS ){
384 iExprOp = EXPR_OP_ADD; /* Binary plus */
385 }
386 /*
387 * TICKET 1433-013: This is a fix around an obscure bug when the user uses
388 * a variable name which is an alpha-stream operator [i.e: $and, $xor, $eq..].
389 */
390 while( n < SX_ARRAYSIZE(aOpTable) && aOpTable[n].iOp != iExprOp ){
391 ++n;
392 }
393 pOp = &aOpTable[n];
394 /* Mark as binary '+' or '-', not an unary */
395 apNode[i]->pOp = pOp;
396 apNode[i]->pStart->pUserData = (void *)pOp;
397 }
398 }
399 }
400 }
401 if( iParen != 0 || iSquare != 0 || iBraces != 0){
402 rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[0]->pStart->nLine, "Syntax error, mismatched '(', '[' or '{'");
403 if( rc != SXERR_ABORT ){
404 rc = SXERR_SYNTAX;
405 }
406 return rc;
407 }
408 return SXRET_OK;
409}
410/*
411 * Extract a single expression node from the input.
412 * On success store the freshly extractd node in ppNode.
413 * When errors, JX9 take care of generating the appropriate error message.
414 * An expression node can be a variable [i.e: $var], an operator [i.e: ++]
415 * an annonymous function [i.e: function(){ return "Hello"; }, a double/single
416 * quoted string, a heredoc/nowdoc, a literal [i.e: JX9_EOL], a namespace path
417 * [i.e: namespaces\path\to..], a array/list [i.e: array(4, 5, 6)] and so on.
418 */
419static sxi32 ExprExtractNode(jx9_gen_state *pGen, jx9_expr_node **ppNode)
420{
421 jx9_expr_node *pNode;
422 SyToken *pCur;
423 sxi32 rc;
424 /* Allocate a new node */
425 pNode = (jx9_expr_node *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_expr_node));
426 if( pNode == 0 ){
427 /* If the supplied memory subsystem is so sick that we are unable to allocate
428 * a tiny chunk of memory, there is no much we can do here.
429 */
430 return SXERR_MEM;
431 }
432 /* Zero the structure */
433 SyZero(pNode, sizeof(jx9_expr_node));
434 SySetInit(&pNode->aNodeArgs, &pGen->pVm->sAllocator, sizeof(jx9_expr_node **));
435 /* Point to the head of the token stream */
436 pCur = pNode->pStart = pGen->pIn;
437 /* Start collecting tokens */
438 if( pCur->nType & JX9_TK_OP ){
439 /* Point to the instance that describe this operator */
440 pNode->pOp = (const jx9_expr_op *)pCur->pUserData;
441 /* Advance the stream cursor */
442 pCur++;
443 }else if( pCur->nType & JX9_TK_DOLLAR ){
444 /* Isolate variable */
445 pCur++; /* Jump the dollar sign */
446 if( pCur >= pGen->pEnd ){
447 /* Syntax error */
448 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"Invalid variable name");
449 if( rc != SXERR_ABORT ){
450 rc = SXERR_SYNTAX;
451 }
452 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode);
453 return rc;
454 }
455 pCur++; /* Jump the variable name */
456 pNode->xCode = jx9CompileVariable;
457 }else if( pCur->nType & JX9_TK_OCB /* '{' */ ){
458 /* JSON Object, assemble tokens */
459 pCur++;
460 jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OCB /* '[' */, JX9_TK_CCB /* ']' */, &pCur);
461 if( pCur < pGen->pEnd ){
462 pCur++;
463 }else{
464 /* Syntax error */
465 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Object: Missing closing braces '}'");
466 if( rc != SXERR_ABORT ){
467 rc = SXERR_SYNTAX;
468 }
469 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode);
470 return rc;
471 }
472 pNode->xCode = jx9CompileJsonObject;
473 }else if( pCur->nType & JX9_TK_OSB /* '[' */ && !(pCur->nType & JX9_TK_OP) ){
474 /* JSON Array, assemble tokens */
475 pCur++;
476 jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OSB /* '[' */, JX9_TK_CSB /* ']' */, &pCur);
477 if( pCur < pGen->pEnd ){
478 pCur++;
479 }else{
480 /* Syntax error */
481 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Array: Missing closing square bracket ']'");
482 if( rc != SXERR_ABORT ){
483 rc = SXERR_SYNTAX;
484 }
485 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode);
486 return rc;
487 }
488 pNode->xCode = jx9CompileJsonArray;
489 }else if( pCur->nType & JX9_TK_KEYWORD ){
490 int nKeyword = SX_PTR_TO_INT(pCur->pUserData);
491 if( nKeyword == JX9_TKWRD_FUNCTION ){
492 /* Annonymous function */
493 if( &pCur[1] >= pGen->pEnd ){
494 /* Assume a literal */
495 pCur++;
496 pNode->xCode = jx9CompileLiteral;
497 }else{
498 /* Assemble annonymous functions body */
499 rc = ExprAssembleAnnon(&(*pGen), &pCur, pGen->pEnd);
500 if( rc != SXRET_OK ){
501 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode);
502 return rc;
503 }
504 pNode->xCode = jx9CompileAnnonFunc;
505 }
506 }else if( jx9IsLangConstruct(nKeyword) && &pCur[1] < pGen->pEnd ){
507 /* Language constructs [i.e: print,die...] require special handling */
508 jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_LPAREN|JX9_TK_OCB|JX9_TK_OSB, JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB, &pCur);
509 pNode->xCode = jx9CompileLangConstruct;
510 }else{
511 /* Assume a literal */
512 pCur++;
513 pNode->xCode = jx9CompileLiteral;
514 }
515 }else if( pCur->nType & (JX9_TK_ID) ){
516 /* Constants, function name, namespace path, object name... */
517 pCur++;
518 pNode->xCode = jx9CompileLiteral;
519 }else{
520 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 ){
521 /* Point to the code generator routine */
522 pNode->xCode = jx9GetNodeHandler(pCur->nType);
523 if( pNode->xCode == 0 ){
524 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Syntax error: Unexpected token '%z'", &pNode->pStart->sData);
525 if( rc != SXERR_ABORT ){
526 rc = SXERR_SYNTAX;
527 }
528 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode);
529 return rc;
530 }
531 }
532 /* Advance the stream cursor */
533 pCur++;
534 }
535 /* Point to the end of the token stream */
536 pNode->pEnd = pCur;
537 /* Save the node for later processing */
538 *ppNode = pNode;
539 /* Synchronize cursors */
540 pGen->pIn = pCur;
541 return SXRET_OK;
542}
543/*
544 * Free an expression tree.
545 */
546static void ExprFreeTree(jx9_gen_state *pGen, jx9_expr_node *pNode)
547{
548 if( pNode->pLeft ){
549 /* Release the left tree */
550 ExprFreeTree(&(*pGen), pNode->pLeft);
551 }
552 if( pNode->pRight ){
553 /* Release the right tree */
554 ExprFreeTree(&(*pGen), pNode->pRight);
555 }
556 if( pNode->pCond ){
557 /* Release the conditional tree used by the ternary operator */
558 ExprFreeTree(&(*pGen), pNode->pCond);
559 }
560 if( SySetUsed(&pNode->aNodeArgs) > 0 ){
561 jx9_expr_node **apArg;
562 sxu32 n;
563 /* Release node arguments */
564 apArg = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs);
565 for( n = 0 ; n < SySetUsed(&pNode->aNodeArgs) ; ++n ){
566 ExprFreeTree(&(*pGen), apArg[n]);
567 }
568 SySetRelease(&pNode->aNodeArgs);
569 }
570 /* Finally, release this node */
571 SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode);
572}
573/*
574 * Free an expression tree.
575 * This function is a wrapper around ExprFreeTree() defined above.
576 */
577JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet)
578{
579 jx9_expr_node **apNode;
580 sxu32 n;
581 apNode = (jx9_expr_node **)SySetBasePtr(pNodeSet);
582 for( n = 0 ; n < SySetUsed(pNodeSet) ; ++n ){
583 if( apNode[n] ){
584 ExprFreeTree(&(*pGen), apNode[n]);
585 }
586 }
587 return SXRET_OK;
588}
589/*
590 * Check if the given node is a modifialbe l/r-value.
591 * Return TRUE if modifiable.FALSE otherwise.
592 */
593static int ExprIsModifiableValue(jx9_expr_node *pNode)
594{
595 sxi32 iExprOp;
596 if( pNode->pOp == 0 ){
597 return pNode->xCode == jx9CompileVariable ? TRUE : FALSE;
598 }
599 iExprOp = pNode->pOp->iOp;
600 if( iExprOp == EXPR_OP_DOT /*'.' */ ){
601 return TRUE;
602 }
603 if( iExprOp == EXPR_OP_SUBSCRIPT/*'[]'*/ ){
604 if( pNode->pLeft->pOp ) {
605 if( pNode->pLeft->pOp->iOp != EXPR_OP_SUBSCRIPT /*'['*/ && pNode->pLeft->pOp->iOp != EXPR_OP_DOT /*'.'*/){
606 return FALSE;
607 }
608 }else if( pNode->pLeft->xCode != jx9CompileVariable ){
609 return FALSE;
610 }
611 return TRUE;
612 }
613 /* Not a modifiable l or r-value */
614 return FALSE;
615}
616/* Forward declaration */
617static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken);
618/* Macro to check if the given node is a terminal */
619#define NODE_ISTERM(NODE) (apNode[NODE] && (!apNode[NODE]->pOp || apNode[NODE]->pLeft ))
620/*
621 * Buid an expression tree for each given function argument.
622 * When errors, JX9 take care of generating the appropriate error message.
623 */
624static sxi32 ExprProcessFuncArguments(jx9_gen_state *pGen, jx9_expr_node *pOp, jx9_expr_node **apNode, sxi32 nToken)
625{
626 sxi32 iNest, iCur, iNode;
627 sxi32 rc;
628 /* Process function arguments from left to right */
629 iCur = 0;
630 for(;;){
631 if( iCur >= nToken ){
632 /* No more arguments to process */
633 break;
634 }
635 iNode = iCur;
636 iNest = 0;
637 while( iCur < nToken ){
638 if( apNode[iCur] ){
639 if( (apNode[iCur]->pStart->nType & JX9_TK_COMMA) && apNode[iCur]->pLeft == 0 && iNest <= 0 ){
640 break;
641 }else if( apNode[iCur]->pStart->nType & (JX9_TK_LPAREN|JX9_TK_OSB|JX9_TK_OCB) ){
642 iNest++;
643 }else if( apNode[iCur]->pStart->nType & (JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB) ){
644 iNest--;
645 }
646 }
647 iCur++;
648 }
649 if( iCur > iNode ){
650 ExprMakeTree(&(*pGen), &apNode[iNode], iCur-iNode);
651 if( apNode[iNode] ){
652 /* Put a pointer to the root of the tree in the arguments set */
653 SySetPut(&pOp->aNodeArgs, (const void *)&apNode[iNode]);
654 }else{
655 /* Empty function argument */
656 rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Empty function argument");
657 if( rc != SXERR_ABORT ){
658 rc = SXERR_SYNTAX;
659 }
660 return rc;
661 }
662 }else{
663 rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument");
664 if( rc != SXERR_ABORT ){
665 rc = SXERR_SYNTAX;
666 }
667 return rc;
668 }
669 /* Jump trailing comma */
670 if( iCur < nToken && apNode[iCur] && (apNode[iCur]->pStart->nType & JX9_TK_COMMA) ){
671 iCur++;
672 if( iCur >= nToken ){
673 /* missing function argument */
674 rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument");
675 if( rc != SXERR_ABORT ){
676 rc = SXERR_SYNTAX;
677 }
678 return rc;
679 }
680 }
681 }
682 return SXRET_OK;
683}
684/*
685 * Create an expression tree from an array of tokens.
686 * If successful, the root of the tree is stored in apNode[0].
687 * When errors, JX9 take care of generating the appropriate error message.
688 */
689 static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken)
690 {
691 sxi32 i, iLeft, iRight;
692 jx9_expr_node *pNode;
693 sxi32 iCur;
694 sxi32 rc;
695 if( nToken <= 0 || (nToken == 1 && apNode[0]->xCode) ){
696 /* TICKET 1433-17: self evaluating node */
697 return SXRET_OK;
698 }
699 /* Process expressions enclosed in parenthesis first */
700 for( iCur = 0 ; iCur < nToken ; ++iCur ){
701 sxi32 iNest;
702 /* Note that, we use strict comparison here '!=' instead of the bitwise and '&' operator
703 * since the LPAREN token can also be an operator [i.e: Function call].
704 */
705 if( apNode[iCur] == 0 || apNode[iCur]->pStart->nType != JX9_TK_LPAREN ){
706 continue;
707 }
708 iNest = 1;
709 iLeft = iCur;
710 /* Find the closing parenthesis */
711 iCur++;
712 while( iCur < nToken ){
713 if( apNode[iCur] ){
714 if( apNode[iCur]->pStart->nType & JX9_TK_RPAREN /* ')' */){
715 /* Decrement nesting level */
716 iNest--;
717 if( iNest <= 0 ){
718 break;
719 }
720 }else if( apNode[iCur]->pStart->nType & JX9_TK_LPAREN /* '(' */ ){
721 /* Increment nesting level */
722 iNest++;
723 }
724 }
725 iCur++;
726 }
727 if( iCur - iLeft > 1 ){
728 /* Recurse and process this expression */
729 rc = ExprMakeTree(&(*pGen), &apNode[iLeft + 1], iCur - iLeft - 1);
730 if( rc != SXRET_OK ){
731 return rc;
732 }
733 }
734 /* Free the left and right nodes */
735 ExprFreeTree(&(*pGen), apNode[iLeft]);
736 ExprFreeTree(&(*pGen), apNode[iCur]);
737 apNode[iLeft] = 0;
738 apNode[iCur] = 0;
739 }
740 /* Handle postfix [i.e: function call, member access] operators with precedence 2 */
741 iLeft = -1;
742 for( iCur = 0 ; iCur < nToken ; ++iCur ){
743 if( apNode[iCur] == 0 ){
744 continue;
745 }
746 pNode = apNode[iCur];
747 if( pNode->pOp && pNode->pOp->iPrec == 2 && pNode->pLeft == 0 ){
748 if( pNode->pOp->iOp == EXPR_OP_FUNC_CALL ){
749 /* Collect function arguments */
750 sxi32 iPtr = 0;
751 sxi32 nFuncTok = 0;
752 while( nFuncTok + iCur < nToken ){
753 if( apNode[nFuncTok+iCur] ){
754 if( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_LPAREN /*'('*/ ){
755 iPtr++;
756 }else if ( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_RPAREN /*')'*/){
757 iPtr--;
758 if( iPtr <= 0 ){
759 break;
760 }
761 }
762 }
763 nFuncTok++;
764 }
765 if( nFuncTok + iCur >= nToken ){
766 /* Syntax error */
767 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Missing right parenthesis ')'");
768 if( rc != SXERR_ABORT ){
769 rc = SXERR_SYNTAX;
770 }
771 return rc;
772 }
773 if( iLeft < 0 || !NODE_ISTERM(iLeft) /*|| ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2)*/ ){
774 /* Syntax error */
775 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Invalid function name");
776 if( rc != SXERR_ABORT ){
777 rc = SXERR_SYNTAX;
778 }
779 return rc;
780 }
781 if( nFuncTok > 1 ){
782 /* Process function arguments */
783 rc = ExprProcessFuncArguments(&(*pGen), pNode, &apNode[iCur+1], nFuncTok-1);
784 if( rc != SXRET_OK ){
785 return rc;
786 }
787 }
788 /* Link the node to the tree */
789 pNode->pLeft = apNode[iLeft];
790 apNode[iLeft] = 0;
791 for( iPtr = 1; iPtr <= nFuncTok ; iPtr++ ){
792 apNode[iCur+iPtr] = 0;
793 }
794 }else if (pNode->pOp->iOp == EXPR_OP_SUBSCRIPT ){
795 /* Subscripting */
796 sxi32 iArrTok = iCur + 1;
797 sxi32 iNest = 1;
798 if( iLeft >= 0 && (apNode[iLeft]->xCode == jx9CompileVariable || (apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* postfix */) ) ){
799 /* Collect index tokens */
800 while( iArrTok < nToken ){
801 if( apNode[iArrTok] ){
802 if( apNode[iArrTok]->pStart->nType & JX9_TK_OSB /*'['*/){
803 /* Increment nesting level */
804 iNest++;
805 }else if( apNode[iArrTok]->pStart->nType & JX9_TK_CSB /*']'*/){
806 /* Decrement nesting level */
807 iNest--;
808 if( iNest <= 0 ){
809 break;
810 }
811 }
812 }
813 ++iArrTok;
814 }
815 if( iArrTok > iCur + 1 ){
816 /* Recurse and process this expression */
817 rc = ExprMakeTree(&(*pGen), &apNode[iCur+1], iArrTok - iCur - 1);
818 if( rc != SXRET_OK ){
819 return rc;
820 }
821 /* Link the node to it's index */
822 SySetPut(&pNode->aNodeArgs, (const void *)&apNode[iCur+1]);
823 }
824 /* Link the node to the tree */
825 pNode->pLeft = apNode[iLeft];
826 pNode->pRight = 0;
827 apNode[iLeft] = 0;
828 for( iNest = iCur + 1 ; iNest <= iArrTok ; ++iNest ){
829 apNode[iNest] = 0;
830 }
831 }
832 }else{
833 /* Member access operators [i.e: '.' ] */
834 iRight = iCur + 1;
835 while( iRight < nToken && apNode[iRight] == 0 ){
836 iRight++;
837 }
838 if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){
839 /* Syntax error */
840 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid member name", &pNode->pOp->sOp);
841 if( rc != SXERR_ABORT ){
842 rc = SXERR_SYNTAX;
843 }
844 return rc;
845 }
846 /* Link the node to the tree */
847 pNode->pLeft = apNode[iLeft];
848 if( pNode->pLeft->pOp == 0 && pNode->pLeft->xCode != jx9CompileVariable ){
849 /* Syntax error */
850 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,
851 "'%z': Expecting a variable as left operand", &pNode->pOp->sOp);
852 if( rc != SXERR_ABORT ){
853 rc = SXERR_SYNTAX;
854 }
855 return rc;
856 }
857 pNode->pRight = apNode[iRight];
858 apNode[iLeft] = apNode[iRight] = 0;
859 }
860 }
861 iLeft = iCur;
862 }
863 /* Handle post/pre icrement/decrement [i.e: ++/--] operators with precedence 3 */
864 iLeft = -1;
865 for( iCur = 0 ; iCur < nToken ; ++iCur ){
866 if( apNode[iCur] == 0 ){
867 continue;
868 }
869 pNode = apNode[iCur];
870 if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){
871 if( iLeft >= 0 && ((apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* Postfix */)
872 || apNode[iLeft]->xCode == jx9CompileVariable) ){
873 /* Link the node to the tree */
874 pNode->pLeft = apNode[iLeft];
875 apNode[iLeft] = 0;
876 }
877 }
878 iLeft = iCur;
879 }
880 iLeft = -1;
881 for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){
882 if( apNode[iCur] == 0 ){
883 continue;
884 }
885 pNode = apNode[iCur];
886 if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){
887 if( iLeft < 0 || (apNode[iLeft]->pOp == 0 && apNode[iLeft]->xCode != jx9CompileVariable)
888 || ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2 /* Postfix */) ){
889 /* Syntax error */
890 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z' operator needs l-value", &pNode->pOp->sOp);
891 if( rc != SXERR_ABORT ){
892 rc = SXERR_SYNTAX;
893 }
894 return rc;
895 }
896 /* Link the node to the tree */
897 pNode->pLeft = apNode[iLeft];
898 apNode[iLeft] = 0;
899 /* Mark as pre-increment/decrement node */
900 pNode->iFlags |= EXPR_NODE_PRE_INCR;
901 }
902 iLeft = iCur;
903 }
904 /* Handle right associative unary and cast operators [i.e: !, (string), ~...] with precedence 4 */
905 iLeft = 0;
906 for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){
907 if( apNode[iCur] ){
908 pNode = apNode[iCur];
909 if( pNode->pOp && pNode->pOp->iPrec == 4 && pNode->pLeft == 0){
910 if( iLeft > 0 ){
911 /* Link the node to the tree */
912 pNode->pLeft = apNode[iLeft];
913 apNode[iLeft] = 0;
914 if( pNode->pLeft && pNode->pLeft->pOp && pNode->pLeft->pOp->iPrec > 4 ){
915 if( pNode->pLeft->pLeft == 0 || pNode->pLeft->pRight == 0 ){
916 /* Syntax error */
917 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pLeft->pStart->nLine, "'%z': Missing operand", &pNode->pLeft->pOp->sOp);
918 if( rc != SXERR_ABORT ){
919 rc = SXERR_SYNTAX;
920 }
921 return rc;
922 }
923 }
924 }else{
925 /* Syntax error */
926 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing operand", &pNode->pOp->sOp);
927 if( rc != SXERR_ABORT ){
928 rc = SXERR_SYNTAX;
929 }
930 return rc;
931 }
932 }
933 /* Save terminal position */
934 iLeft = iCur;
935 }
936 }
937 /* Process left and non-associative binary operators [i.e: *, /, &&, ||...]*/
938 for( i = 7 ; i < 17 ; i++ ){
939 iLeft = -1;
940 for( iCur = 0 ; iCur < nToken ; ++iCur ){
941 if( apNode[iCur] == 0 ){
942 continue;
943 }
944 pNode = apNode[iCur];
945 if( pNode->pOp && pNode->pOp->iPrec == i && pNode->pLeft == 0 ){
946 /* Get the right node */
947 iRight = iCur + 1;
948 while( iRight < nToken && apNode[iRight] == 0 ){
949 iRight++;
950 }
951 if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){
952 /* Syntax error */
953 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp);
954 if( rc != SXERR_ABORT ){
955 rc = SXERR_SYNTAX;
956 }
957 return rc;
958 }
959 /* Link the node to the tree */
960 pNode->pLeft = apNode[iLeft];
961 pNode->pRight = apNode[iRight];
962 apNode[iLeft] = apNode[iRight] = 0;
963 }
964 iLeft = iCur;
965 }
966 }
967 /* Handle the ternary operator. (expr1) ? (expr2) : (expr3)
968 * Note that we do not need a precedence loop here since
969 * we are dealing with a single operator.
970 */
971 iLeft = -1;
972 for( iCur = 0 ; iCur < nToken ; ++iCur ){
973 if( apNode[iCur] == 0 ){
974 continue;
975 }
976 pNode = apNode[iCur];
977 if( pNode->pOp && pNode->pOp->iOp == EXPR_OP_QUESTY && pNode->pLeft == 0 ){
978 sxi32 iNest = 1;
979 if( iLeft < 0 || !NODE_ISTERM(iLeft) ){
980 /* Missing condition */
981 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Syntax error", &pNode->pOp->sOp);
982 if( rc != SXERR_ABORT ){
983 rc = SXERR_SYNTAX;
984 }
985 return rc;
986 }
987 /* Get the right node */
988 iRight = iCur + 1;
989 while( iRight < nToken ){
990 if( apNode[iRight] ){
991 if( apNode[iRight]->pOp && apNode[iRight]->pOp->iOp == EXPR_OP_QUESTY && apNode[iRight]->pCond == 0){
992 /* Increment nesting level */
993 ++iNest;
994 }else if( apNode[iRight]->pStart->nType & JX9_TK_COLON /*:*/ ){
995 /* Decrement nesting level */
996 --iNest;
997 if( iNest <= 0 ){
998 break;
999 }
1000 }
1001 }
1002 iRight++;
1003 }
1004 if( iRight > iCur + 1 ){
1005 /* Recurse and process the then expression */
1006 rc = ExprMakeTree(&(*pGen), &apNode[iCur + 1], iRight - iCur - 1);
1007 if( rc != SXRET_OK ){
1008 return rc;
1009 }
1010 /* Link the node to the tree */
1011 pNode->pLeft = apNode[iCur + 1];
1012 }else{
1013 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'then' expression", &pNode->pOp->sOp);
1014 if( rc != SXERR_ABORT ){
1015 rc = SXERR_SYNTAX;
1016 }
1017 return rc;
1018 }
1019 apNode[iCur + 1] = 0;
1020 if( iRight + 1 < nToken ){
1021 /* Recurse and process the else expression */
1022 rc = ExprMakeTree(&(*pGen), &apNode[iRight + 1], nToken - iRight - 1);
1023 if( rc != SXRET_OK ){
1024 return rc;
1025 }
1026 /* Link the node to the tree */
1027 pNode->pRight = apNode[iRight + 1];
1028 apNode[iRight + 1] = apNode[iRight] = 0;
1029 }else{
1030 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'else' expression", &pNode->pOp->sOp);
1031 if( rc != SXERR_ABORT ){
1032 rc = SXERR_SYNTAX;
1033 }
1034 return rc;
1035 }
1036 /* Point to the condition */
1037 pNode->pCond = apNode[iLeft];
1038 apNode[iLeft] = 0;
1039 break;
1040 }
1041 iLeft = iCur;
1042 }
1043 /* Process right associative binary operators [i.e: '=', '+=', '/=']
1044 * Note: All right associative binary operators have precedence 18
1045 * so there is no need for a precedence loop here.
1046 */
1047 iRight = -1;
1048 for( iCur = nToken - 1 ; iCur >= 0 ; iCur--){
1049 if( apNode[iCur] == 0 ){
1050 continue;
1051 }
1052 pNode = apNode[iCur];
1053 if( pNode->pOp && pNode->pOp->iPrec == 18 && pNode->pLeft == 0 ){
1054 /* Get the left node */
1055 iLeft = iCur - 1;
1056 while( iLeft >= 0 && apNode[iLeft] == 0 ){
1057 iLeft--;
1058 }
1059 if( iLeft < 0 || iRight < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){
1060 /* Syntax error */
1061 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp);
1062 if( rc != SXERR_ABORT ){
1063 rc = SXERR_SYNTAX;
1064 }
1065 return rc;
1066 }
1067 if( ExprIsModifiableValue(apNode[iLeft]) == FALSE ){
1068 if( pNode->pOp->iVmOp != JX9_OP_STORE ){
1069 /* Left operand must be a modifiable l-value */
1070 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,
1071 "'%z': Left operand must be a modifiable l-value", &pNode->pOp->sOp);
1072 if( rc != SXERR_ABORT ){
1073 rc = SXERR_SYNTAX;
1074 }
1075 return rc;
1076 }
1077 }
1078 /* Link the node to the tree (Reverse) */
1079 pNode->pLeft = apNode[iRight];
1080 pNode->pRight = apNode[iLeft];
1081 apNode[iLeft] = apNode[iRight] = 0;
1082 }
1083 iRight = iCur;
1084 }
1085 /* Process the lowest precedence operator (22, comma) */
1086 iLeft = -1;
1087 for( iCur = 0 ; iCur < nToken ; ++iCur ){
1088 if( apNode[iCur] == 0 ){
1089 continue;
1090 }
1091 pNode = apNode[iCur];
1092 if( pNode->pOp && pNode->pOp->iPrec == 22 /* ',' */ && pNode->pLeft == 0 ){
1093 /* Get the right node */
1094 iRight = iCur + 1;
1095 while( iRight < nToken && apNode[iRight] == 0 ){
1096 iRight++;
1097 }
1098 if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){
1099 /* Syntax error */
1100 rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp);
1101 if( rc != SXERR_ABORT ){
1102 rc = SXERR_SYNTAX;
1103 }
1104 return rc;
1105 }
1106 /* Link the node to the tree */
1107 pNode->pLeft = apNode[iLeft];
1108 pNode->pRight = apNode[iRight];
1109 apNode[iLeft] = apNode[iRight] = 0;
1110 }
1111 iLeft = iCur;
1112 }
1113 /* Point to the root of the expression tree */
1114 for( iCur = 1 ; iCur < nToken ; ++iCur ){
1115 if( apNode[iCur] ){
1116 if( (apNode[iCur]->pOp || apNode[iCur]->xCode ) && apNode[0] != 0){
1117 rc = jx9GenCompileError(pGen, E_ERROR, apNode[iCur]->pStart->nLine, "Unexpected token '%z'", &apNode[iCur]->pStart->sData);
1118 if( rc != SXERR_ABORT ){
1119 rc = SXERR_SYNTAX;
1120 }
1121 return rc;
1122 }
1123 apNode[0] = apNode[iCur];
1124 apNode[iCur] = 0;
1125 }
1126 }
1127 return SXRET_OK;
1128 }
1129 /*
1130 * Build an expression tree from the freshly extracted raw tokens.
1131 * If successful, the root of the tree is stored in ppRoot.
1132 * When errors, JX9 take care of generating the appropriate error message.
1133 * This is the public interface used by the most code generator routines.
1134 */
1135JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot)
1136{
1137 jx9_expr_node **apNode;
1138 jx9_expr_node *pNode;
1139 sxi32 rc;
1140 /* Reset node container */
1141 SySetReset(pExprNode);
1142 pNode = 0; /* Prevent compiler warning */
1143 /* Extract nodes one after one until we hit the end of the input */
1144 while( pGen->pIn < pGen->pEnd ){
1145 rc = ExprExtractNode(&(*pGen), &pNode);
1146 if( rc != SXRET_OK ){
1147 return rc;
1148 }
1149 /* Save the extracted node */
1150 SySetPut(pExprNode, (const void *)&pNode);
1151 }
1152 if( SySetUsed(pExprNode) < 1 ){
1153 /* Empty expression [i.e: A semi-colon;] */
1154 *ppRoot = 0;
1155 return SXRET_OK;
1156 }
1157 apNode = (jx9_expr_node **)SySetBasePtr(pExprNode);
1158 /* Make sure we are dealing with valid nodes */
1159 rc = ExprVerifyNodes(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode));
1160 if( rc != SXRET_OK ){
1161 /* Don't worry about freeing memory, upper layer will
1162 * cleanup the mess left behind.
1163 */
1164 *ppRoot = 0;
1165 return rc;
1166 }
1167 /* Build the tree */
1168 rc = ExprMakeTree(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode));
1169 if( rc != SXRET_OK ){
1170 /* Something goes wrong [i.e: Syntax error] */
1171 *ppRoot = 0;
1172 return rc;
1173 }
1174 /* Point to the root of the tree */
1175 *ppRoot = apNode[0];
1176 return SXRET_OK;
1177}
diff --git a/common/unqlite/jx9_vfs.c b/common/unqlite/jx9_vfs.c
new file mode 100644
index 0000000..d6aee2e
--- /dev/null
+++ b/common/unqlite/jx9_vfs.c
@@ -0,0 +1,8222 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: vfs.c v2.1 Ubuntu 2012-12-13 00:013 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/*
18 * This file implement a virtual file systems (VFS) for the JX9 engine.
19 */
20/*
21 * Given a string containing the path of a file or directory, this function
22 * return the parent directory's path.
23 */
24JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen)
25{
26 const char *zEnd = &zPath[nByte - 1];
27 int c, d;
28 c = d = '/';
29#ifdef __WINNT__
30 d = '\\';
31#endif
32 while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){
33 zEnd--;
34 }
35 *pLen = (int)(zEnd-zPath);
36#ifdef __WINNT__
37 if( (*pLen) == (int)sizeof(char) && zPath[0] == '/' ){
38 /* Normalize path on windows */
39 return "\\";
40 }
41#endif
42 if( zEnd == zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d) ){
43 /* No separator, return "." as the current directory */
44 *pLen = sizeof(char);
45 return ".";
46 }
47 if( (*pLen) == 0 ){
48 *pLen = sizeof(char);
49#ifdef __WINNT__
50 return "\\";
51#else
52 return "/";
53#endif
54 }
55 return zPath;
56}
57/*
58 * Omit the vfs layer implementation from the built if the JX9_DISABLE_BUILTIN_FUNC directive is defined.
59 */
60#ifndef JX9_DISABLE_BUILTIN_FUNC
61/*
62 * bool chdir(string $directory)
63 * Change the current directory.
64 * Parameters
65 * $directory
66 * The new current directory
67 * Return
68 * TRUE on success or FALSE on failure.
69 */
70static int jx9Vfs_chdir(jx9_context *pCtx, int nArg, jx9_value **apArg)
71{
72 const char *zPath;
73 jx9_vfs *pVfs;
74 int rc;
75 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
76 /* Missing/Invalid argument, return FALSE */
77 jx9_result_bool(pCtx, 0);
78 return JX9_OK;
79 }
80 /* Point to the underlying vfs */
81 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
82 if( pVfs == 0 || pVfs->xChdir == 0 ){
83 /* IO routine not implemented, return NULL */
84 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
85 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
86 jx9_function_name(pCtx)
87 );
88 jx9_result_bool(pCtx, 0);
89 return JX9_OK;
90 }
91 /* Point to the desired directory */
92 zPath = jx9_value_to_string(apArg[0], 0);
93 /* Perform the requested operation */
94 rc = pVfs->xChdir(zPath);
95 /* IO return value */
96 jx9_result_bool(pCtx, rc == JX9_OK);
97 return JX9_OK;
98}
99/*
100 * bool chroot(string $directory)
101 * Change the root directory.
102 * Parameters
103 * $directory
104 * The path to change the root directory to
105 * Return
106 * TRUE on success or FALSE on failure.
107 */
108static int jx9Vfs_chroot(jx9_context *pCtx, int nArg, jx9_value **apArg)
109{
110 const char *zPath;
111 jx9_vfs *pVfs;
112 int rc;
113 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
114 /* Missing/Invalid argument, return FALSE */
115 jx9_result_bool(pCtx, 0);
116 return JX9_OK;
117 }
118 /* Point to the underlying vfs */
119 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
120 if( pVfs == 0 || pVfs->xChroot == 0 ){
121 /* IO routine not implemented, return NULL */
122 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
123 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
124 jx9_function_name(pCtx)
125 );
126 jx9_result_bool(pCtx, 0);
127 return JX9_OK;
128 }
129 /* Point to the desired directory */
130 zPath = jx9_value_to_string(apArg[0], 0);
131 /* Perform the requested operation */
132 rc = pVfs->xChroot(zPath);
133 /* IO return value */
134 jx9_result_bool(pCtx, rc == JX9_OK);
135 return JX9_OK;
136}
137/*
138 * string getcwd(void)
139 * Gets the current working directory.
140 * Parameters
141 * None
142 * Return
143 * Returns the current working directory on success, or FALSE on failure.
144 */
145static int jx9Vfs_getcwd(jx9_context *pCtx, int nArg, jx9_value **apArg)
146{
147 jx9_vfs *pVfs;
148 int rc;
149 /* Point to the underlying vfs */
150 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
151 if( pVfs == 0 || pVfs->xGetcwd == 0 ){
152 SXUNUSED(nArg); /* cc warning */
153 SXUNUSED(apArg);
154 /* IO routine not implemented, return NULL */
155 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
156 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
157 jx9_function_name(pCtx)
158 );
159 jx9_result_bool(pCtx, 0);
160 return JX9_OK;
161 }
162 jx9_result_string(pCtx, "", 0);
163 /* Perform the requested operation */
164 rc = pVfs->xGetcwd(pCtx);
165 if( rc != JX9_OK ){
166 /* Error, return FALSE */
167 jx9_result_bool(pCtx, 0);
168 }
169 return JX9_OK;
170}
171/*
172 * bool rmdir(string $directory)
173 * Removes directory.
174 * Parameters
175 * $directory
176 * The path to the directory
177 * Return
178 * TRUE on success or FALSE on failure.
179 */
180static int jx9Vfs_rmdir(jx9_context *pCtx, int nArg, jx9_value **apArg)
181{
182 const char *zPath;
183 jx9_vfs *pVfs;
184 int rc;
185 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
186 /* Missing/Invalid argument, return FALSE */
187 jx9_result_bool(pCtx, 0);
188 return JX9_OK;
189 }
190 /* Point to the underlying vfs */
191 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
192 if( pVfs == 0 || pVfs->xRmdir == 0 ){
193 /* IO routine not implemented, return NULL */
194 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
195 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
196 jx9_function_name(pCtx)
197 );
198 jx9_result_bool(pCtx, 0);
199 return JX9_OK;
200 }
201 /* Point to the desired directory */
202 zPath = jx9_value_to_string(apArg[0], 0);
203 /* Perform the requested operation */
204 rc = pVfs->xRmdir(zPath);
205 /* IO return value */
206 jx9_result_bool(pCtx, rc == JX9_OK);
207 return JX9_OK;
208}
209/*
210 * bool is_dir(string $filename)
211 * Tells whether the given filename is a directory.
212 * Parameters
213 * $filename
214 * Path to the file.
215 * Return
216 * TRUE on success or FALSE on failure.
217 */
218static int jx9Vfs_is_dir(jx9_context *pCtx, int nArg, jx9_value **apArg)
219{
220 const char *zPath;
221 jx9_vfs *pVfs;
222 int rc;
223 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
224 /* Missing/Invalid argument, return FALSE */
225 jx9_result_bool(pCtx, 0);
226 return JX9_OK;
227 }
228 /* Point to the underlying vfs */
229 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
230 if( pVfs == 0 || pVfs->xIsdir == 0 ){
231 /* IO routine not implemented, return NULL */
232 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
233 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
234 jx9_function_name(pCtx)
235 );
236 jx9_result_bool(pCtx, 0);
237 return JX9_OK;
238 }
239 /* Point to the desired directory */
240 zPath = jx9_value_to_string(apArg[0], 0);
241 /* Perform the requested operation */
242 rc = pVfs->xIsdir(zPath);
243 /* IO return value */
244 jx9_result_bool(pCtx, rc == JX9_OK);
245 return JX9_OK;
246}
247/*
248 * bool mkdir(string $pathname[, int $mode = 0777])
249 * Make a directory.
250 * Parameters
251 * $pathname
252 * The directory path.
253 * $mode
254 * The mode is 0777 by default, which means the widest possible access.
255 * Note:
256 * mode is ignored on Windows.
257 * Note that you probably want to specify the mode as an octal number, which means
258 * it should have a leading zero. The mode is also modified by the current umask
259 * which you can change using umask().
260 * Return
261 * TRUE on success or FALSE on failure.
262 */
263static int jx9Vfs_mkdir(jx9_context *pCtx, int nArg, jx9_value **apArg)
264{
265 int iRecursive = 0;
266 const char *zPath;
267 jx9_vfs *pVfs;
268 int iMode, rc;
269 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
270 /* Missing/Invalid argument, return FALSE */
271 jx9_result_bool(pCtx, 0);
272 return JX9_OK;
273 }
274 /* Point to the underlying vfs */
275 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
276 if( pVfs == 0 || pVfs->xMkdir == 0 ){
277 /* IO routine not implemented, return NULL */
278 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
279 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
280 jx9_function_name(pCtx)
281 );
282 jx9_result_bool(pCtx, 0);
283 return JX9_OK;
284 }
285 /* Point to the desired directory */
286 zPath = jx9_value_to_string(apArg[0], 0);
287#ifdef __WINNT__
288 iMode = 0;
289#else
290 /* Assume UNIX */
291 iMode = 0777;
292#endif
293 if( nArg > 1 ){
294 iMode = jx9_value_to_int(apArg[1]);
295 if( nArg > 2 ){
296 iRecursive = jx9_value_to_bool(apArg[2]);
297 }
298 }
299 /* Perform the requested operation */
300 rc = pVfs->xMkdir(zPath, iMode, iRecursive);
301 /* IO return value */
302 jx9_result_bool(pCtx, rc == JX9_OK);
303 return JX9_OK;
304}
305/*
306 * bool rename(string $oldname, string $newname)
307 * Attempts to rename oldname to newname.
308 * Parameters
309 * $oldname
310 * Old name.
311 * $newname
312 * New name.
313 * Return
314 * TRUE on success or FALSE on failure.
315 */
316static int jx9Vfs_rename(jx9_context *pCtx, int nArg, jx9_value **apArg)
317{
318 const char *zOld, *zNew;
319 jx9_vfs *pVfs;
320 int rc;
321 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){
322 /* Missing/Invalid arguments, return FALSE */
323 jx9_result_bool(pCtx, 0);
324 return JX9_OK;
325 }
326 /* Point to the underlying vfs */
327 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
328 if( pVfs == 0 || pVfs->xRename == 0 ){
329 /* IO routine not implemented, return NULL */
330 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
331 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
332 jx9_function_name(pCtx)
333 );
334 jx9_result_bool(pCtx, 0);
335 return JX9_OK;
336 }
337 /* Perform the requested operation */
338 zOld = jx9_value_to_string(apArg[0], 0);
339 zNew = jx9_value_to_string(apArg[1], 0);
340 rc = pVfs->xRename(zOld, zNew);
341 /* IO result */
342 jx9_result_bool(pCtx, rc == JX9_OK );
343 return JX9_OK;
344}
345/*
346 * string realpath(string $path)
347 * Returns canonicalized absolute pathname.
348 * Parameters
349 * $path
350 * Target path.
351 * Return
352 * Canonicalized absolute pathname on success. or FALSE on failure.
353 */
354static int jx9Vfs_realpath(jx9_context *pCtx, int nArg, jx9_value **apArg)
355{
356 const char *zPath;
357 jx9_vfs *pVfs;
358 int rc;
359 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
360 /* Missing/Invalid argument, return FALSE */
361 jx9_result_bool(pCtx, 0);
362 return JX9_OK;
363 }
364 /* Point to the underlying vfs */
365 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
366 if( pVfs == 0 || pVfs->xRealpath == 0 ){
367 /* IO routine not implemented, return NULL */
368 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
369 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
370 jx9_function_name(pCtx)
371 );
372 jx9_result_bool(pCtx, 0);
373 return JX9_OK;
374 }
375 /* Set an empty string untnil the underlying OS interface change that */
376 jx9_result_string(pCtx, "", 0);
377 /* Perform the requested operation */
378 zPath = jx9_value_to_string(apArg[0], 0);
379 rc = pVfs->xRealpath(zPath, pCtx);
380 if( rc != JX9_OK ){
381 jx9_result_bool(pCtx, 0);
382 }
383 return JX9_OK;
384}
385/*
386 * int sleep(int $seconds)
387 * Delays the program execution for the given number of seconds.
388 * Parameters
389 * $seconds
390 * Halt time in seconds.
391 * Return
392 * Zero on success or FALSE on failure.
393 */
394static int jx9Vfs_sleep(jx9_context *pCtx, int nArg, jx9_value **apArg)
395{
396 jx9_vfs *pVfs;
397 int rc, nSleep;
398 if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){
399 /* Missing/Invalid argument, return FALSE */
400 jx9_result_bool(pCtx, 0);
401 return JX9_OK;
402 }
403 /* Point to the underlying vfs */
404 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
405 if( pVfs == 0 || pVfs->xSleep == 0 ){
406 /* IO routine not implemented, return NULL */
407 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
408 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
409 jx9_function_name(pCtx)
410 );
411 jx9_result_bool(pCtx, 0);
412 return JX9_OK;
413 }
414 /* Amount to sleep */
415 nSleep = jx9_value_to_int(apArg[0]);
416 if( nSleep < 0 ){
417 /* Invalid value, return FALSE */
418 jx9_result_bool(pCtx, 0);
419 return JX9_OK;
420 }
421 /* Perform the requested operation (Microseconds) */
422 rc = pVfs->xSleep((unsigned int)(nSleep * SX_USEC_PER_SEC));
423 if( rc != JX9_OK ){
424 /* Return FALSE */
425 jx9_result_bool(pCtx, 0);
426 }else{
427 /* Return zero */
428 jx9_result_int(pCtx, 0);
429 }
430 return JX9_OK;
431}
432/*
433 * void usleep(int $micro_seconds)
434 * Delays program execution for the given number of micro seconds.
435 * Parameters
436 * $micro_seconds
437 * Halt time in micro seconds. A micro second is one millionth of a second.
438 * Return
439 * None.
440 */
441static int jx9Vfs_usleep(jx9_context *pCtx, int nArg, jx9_value **apArg)
442{
443 jx9_vfs *pVfs;
444 int nSleep;
445 if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){
446 /* Missing/Invalid argument, return immediately */
447 return JX9_OK;
448 }
449 /* Point to the underlying vfs */
450 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
451 if( pVfs == 0 || pVfs->xSleep == 0 ){
452 /* IO routine not implemented, return NULL */
453 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
454 "IO routine(%s) not implemented in the underlying VFS",
455 jx9_function_name(pCtx)
456 );
457 return JX9_OK;
458 }
459 /* Amount to sleep */
460 nSleep = jx9_value_to_int(apArg[0]);
461 if( nSleep < 0 ){
462 /* Invalid value, return immediately */
463 return JX9_OK;
464 }
465 /* Perform the requested operation (Microseconds) */
466 pVfs->xSleep((unsigned int)nSleep);
467 return JX9_OK;
468}
469/*
470 * bool unlink (string $filename)
471 * Delete a file.
472 * Parameters
473 * $filename
474 * Path to the file.
475 * Return
476 * TRUE on success or FALSE on failure.
477 */
478static int jx9Vfs_unlink(jx9_context *pCtx, int nArg, jx9_value **apArg)
479{
480 const char *zPath;
481 jx9_vfs *pVfs;
482 int rc;
483 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
484 /* Missing/Invalid argument, return FALSE */
485 jx9_result_bool(pCtx, 0);
486 return JX9_OK;
487 }
488 /* Point to the underlying vfs */
489 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
490 if( pVfs == 0 || pVfs->xUnlink == 0 ){
491 /* IO routine not implemented, return NULL */
492 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
493 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
494 jx9_function_name(pCtx)
495 );
496 jx9_result_bool(pCtx, 0);
497 return JX9_OK;
498 }
499 /* Point to the desired directory */
500 zPath = jx9_value_to_string(apArg[0], 0);
501 /* Perform the requested operation */
502 rc = pVfs->xUnlink(zPath);
503 /* IO return value */
504 jx9_result_bool(pCtx, rc == JX9_OK);
505 return JX9_OK;
506}
507/*
508 * bool chmod(string $filename, int $mode)
509 * Attempts to change the mode of the specified file to that given in mode.
510 * Parameters
511 * $filename
512 * Path to the file.
513 * $mode
514 * Mode (Must be an integer)
515 * Return
516 * TRUE on success or FALSE on failure.
517 */
518static int jx9Vfs_chmod(jx9_context *pCtx, int nArg, jx9_value **apArg)
519{
520 const char *zPath;
521 jx9_vfs *pVfs;
522 int iMode;
523 int rc;
524 if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){
525 /* Missing/Invalid argument, return FALSE */
526 jx9_result_bool(pCtx, 0);
527 return JX9_OK;
528 }
529 /* Point to the underlying vfs */
530 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
531 if( pVfs == 0 || pVfs->xChmod == 0 ){
532 /* IO routine not implemented, return NULL */
533 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
534 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
535 jx9_function_name(pCtx)
536 );
537 jx9_result_bool(pCtx, 0);
538 return JX9_OK;
539 }
540 /* Point to the desired directory */
541 zPath = jx9_value_to_string(apArg[0], 0);
542 /* Extract the mode */
543 iMode = jx9_value_to_int(apArg[1]);
544 /* Perform the requested operation */
545 rc = pVfs->xChmod(zPath, iMode);
546 /* IO return value */
547 jx9_result_bool(pCtx, rc == JX9_OK);
548 return JX9_OK;
549}
550/*
551 * bool chown(string $filename, string $user)
552 * Attempts to change the owner of the file filename to user user.
553 * Parameters
554 * $filename
555 * Path to the file.
556 * $user
557 * Username.
558 * Return
559 * TRUE on success or FALSE on failure.
560 */
561static int jx9Vfs_chown(jx9_context *pCtx, int nArg, jx9_value **apArg)
562{
563 const char *zPath, *zUser;
564 jx9_vfs *pVfs;
565 int rc;
566 if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){
567 /* Missing/Invalid arguments, return FALSE */
568 jx9_result_bool(pCtx, 0);
569 return JX9_OK;
570 }
571 /* Point to the underlying vfs */
572 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
573 if( pVfs == 0 || pVfs->xChown == 0 ){
574 /* IO routine not implemented, return NULL */
575 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
576 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
577 jx9_function_name(pCtx)
578 );
579 jx9_result_bool(pCtx, 0);
580 return JX9_OK;
581 }
582 /* Point to the desired directory */
583 zPath = jx9_value_to_string(apArg[0], 0);
584 /* Extract the user */
585 zUser = jx9_value_to_string(apArg[1], 0);
586 /* Perform the requested operation */
587 rc = pVfs->xChown(zPath, zUser);
588 /* IO return value */
589 jx9_result_bool(pCtx, rc == JX9_OK);
590 return JX9_OK;
591}
592/*
593 * bool chgrp(string $filename, string $group)
594 * Attempts to change the group of the file filename to group.
595 * Parameters
596 * $filename
597 * Path to the file.
598 * $group
599 * groupname.
600 * Return
601 * TRUE on success or FALSE on failure.
602 */
603static int jx9Vfs_chgrp(jx9_context *pCtx, int nArg, jx9_value **apArg)
604{
605 const char *zPath, *zGroup;
606 jx9_vfs *pVfs;
607 int rc;
608 if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){
609 /* Missing/Invalid arguments, return FALSE */
610 jx9_result_bool(pCtx, 0);
611 return JX9_OK;
612 }
613 /* Point to the underlying vfs */
614 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
615 if( pVfs == 0 || pVfs->xChgrp == 0 ){
616 /* IO routine not implemented, return NULL */
617 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
618 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
619 jx9_function_name(pCtx)
620 );
621 jx9_result_bool(pCtx, 0);
622 return JX9_OK;
623 }
624 /* Point to the desired directory */
625 zPath = jx9_value_to_string(apArg[0], 0);
626 /* Extract the user */
627 zGroup = jx9_value_to_string(apArg[1], 0);
628 /* Perform the requested operation */
629 rc = pVfs->xChgrp(zPath, zGroup);
630 /* IO return value */
631 jx9_result_bool(pCtx, rc == JX9_OK);
632 return JX9_OK;
633}
634/*
635 * int64 disk_free_space(string $directory)
636 * Returns available space on filesystem or disk partition.
637 * Parameters
638 * $directory
639 * A directory of the filesystem or disk partition.
640 * Return
641 * Returns the number of available bytes as a 64-bit integer or FALSE on failure.
642 */
643static int jx9Vfs_disk_free_space(jx9_context *pCtx, int nArg, jx9_value **apArg)
644{
645 const char *zPath;
646 jx9_int64 iSize;
647 jx9_vfs *pVfs;
648 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
649 /* Missing/Invalid argument, return FALSE */
650 jx9_result_bool(pCtx, 0);
651 return JX9_OK;
652 }
653 /* Point to the underlying vfs */
654 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
655 if( pVfs == 0 || pVfs->xFreeSpace == 0 ){
656 /* IO routine not implemented, return NULL */
657 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
658 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
659 jx9_function_name(pCtx)
660 );
661 jx9_result_bool(pCtx, 0);
662 return JX9_OK;
663 }
664 /* Point to the desired directory */
665 zPath = jx9_value_to_string(apArg[0], 0);
666 /* Perform the requested operation */
667 iSize = pVfs->xFreeSpace(zPath);
668 /* IO return value */
669 jx9_result_int64(pCtx, iSize);
670 return JX9_OK;
671}
672/*
673 * int64 disk_total_space(string $directory)
674 * Returns the total size of a filesystem or disk partition.
675 * Parameters
676 * $directory
677 * A directory of the filesystem or disk partition.
678 * Return
679 * Returns the number of available bytes as a 64-bit integer or FALSE on failure.
680 */
681static int jx9Vfs_disk_total_space(jx9_context *pCtx, int nArg, jx9_value **apArg)
682{
683 const char *zPath;
684 jx9_int64 iSize;
685 jx9_vfs *pVfs;
686 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
687 /* Missing/Invalid argument, return FALSE */
688 jx9_result_bool(pCtx, 0);
689 return JX9_OK;
690 }
691 /* Point to the underlying vfs */
692 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
693 if( pVfs == 0 || pVfs->xTotalSpace == 0 ){
694 /* IO routine not implemented, return NULL */
695 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
696 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
697 jx9_function_name(pCtx)
698 );
699 jx9_result_bool(pCtx, 0);
700 return JX9_OK;
701 }
702 /* Point to the desired directory */
703 zPath = jx9_value_to_string(apArg[0], 0);
704 /* Perform the requested operation */
705 iSize = pVfs->xTotalSpace(zPath);
706 /* IO return value */
707 jx9_result_int64(pCtx, iSize);
708 return JX9_OK;
709}
710/*
711 * bool file_exists(string $filename)
712 * Checks whether a file or directory exists.
713 * Parameters
714 * $filename
715 * Path to the file.
716 * Return
717 * TRUE on success or FALSE on failure.
718 */
719static int jx9Vfs_file_exists(jx9_context *pCtx, int nArg, jx9_value **apArg)
720{
721 const char *zPath;
722 jx9_vfs *pVfs;
723 int rc;
724 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
725 /* Missing/Invalid argument, return FALSE */
726 jx9_result_bool(pCtx, 0);
727 return JX9_OK;
728 }
729 /* Point to the underlying vfs */
730 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
731 if( pVfs == 0 || pVfs->xFileExists == 0 ){
732 /* IO routine not implemented, return NULL */
733 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
734 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
735 jx9_function_name(pCtx)
736 );
737 jx9_result_bool(pCtx, 0);
738 return JX9_OK;
739 }
740 /* Point to the desired directory */
741 zPath = jx9_value_to_string(apArg[0], 0);
742 /* Perform the requested operation */
743 rc = pVfs->xFileExists(zPath);
744 /* IO return value */
745 jx9_result_bool(pCtx, rc == JX9_OK);
746 return JX9_OK;
747}
748/*
749 * int64 file_size(string $filename)
750 * Gets the size for the given file.
751 * Parameters
752 * $filename
753 * Path to the file.
754 * Return
755 * File size on success or FALSE on failure.
756 */
757static int jx9Vfs_file_size(jx9_context *pCtx, int nArg, jx9_value **apArg)
758{
759 const char *zPath;
760 jx9_int64 iSize;
761 jx9_vfs *pVfs;
762 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
763 /* Missing/Invalid argument, return FALSE */
764 jx9_result_bool(pCtx, 0);
765 return JX9_OK;
766 }
767 /* Point to the underlying vfs */
768 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
769 if( pVfs == 0 || pVfs->xFileSize == 0 ){
770 /* IO routine not implemented, return NULL */
771 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
772 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
773 jx9_function_name(pCtx)
774 );
775 jx9_result_bool(pCtx, 0);
776 return JX9_OK;
777 }
778 /* Point to the desired directory */
779 zPath = jx9_value_to_string(apArg[0], 0);
780 /* Perform the requested operation */
781 iSize = pVfs->xFileSize(zPath);
782 /* IO return value */
783 jx9_result_int64(pCtx, iSize);
784 return JX9_OK;
785}
786/*
787 * int64 fileatime(string $filename)
788 * Gets the last access time of the given file.
789 * Parameters
790 * $filename
791 * Path to the file.
792 * Return
793 * File atime on success or FALSE on failure.
794 */
795static int jx9Vfs_file_atime(jx9_context *pCtx, int nArg, jx9_value **apArg)
796{
797 const char *zPath;
798 jx9_int64 iTime;
799 jx9_vfs *pVfs;
800 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
801 /* Missing/Invalid argument, return FALSE */
802 jx9_result_bool(pCtx, 0);
803 return JX9_OK;
804 }
805 /* Point to the underlying vfs */
806 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
807 if( pVfs == 0 || pVfs->xFileAtime == 0 ){
808 /* IO routine not implemented, return NULL */
809 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
810 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
811 jx9_function_name(pCtx)
812 );
813 jx9_result_bool(pCtx, 0);
814 return JX9_OK;
815 }
816 /* Point to the desired directory */
817 zPath = jx9_value_to_string(apArg[0], 0);
818 /* Perform the requested operation */
819 iTime = pVfs->xFileAtime(zPath);
820 /* IO return value */
821 jx9_result_int64(pCtx, iTime);
822 return JX9_OK;
823}
824/*
825 * int64 filemtime(string $filename)
826 * Gets file modification time.
827 * Parameters
828 * $filename
829 * Path to the file.
830 * Return
831 * File mtime on success or FALSE on failure.
832 */
833static int jx9Vfs_file_mtime(jx9_context *pCtx, int nArg, jx9_value **apArg)
834{
835 const char *zPath;
836 jx9_int64 iTime;
837 jx9_vfs *pVfs;
838 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
839 /* Missing/Invalid argument, return FALSE */
840 jx9_result_bool(pCtx, 0);
841 return JX9_OK;
842 }
843 /* Point to the underlying vfs */
844 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
845 if( pVfs == 0 || pVfs->xFileMtime == 0 ){
846 /* IO routine not implemented, return NULL */
847 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
848 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
849 jx9_function_name(pCtx)
850 );
851 jx9_result_bool(pCtx, 0);
852 return JX9_OK;
853 }
854 /* Point to the desired directory */
855 zPath = jx9_value_to_string(apArg[0], 0);
856 /* Perform the requested operation */
857 iTime = pVfs->xFileMtime(zPath);
858 /* IO return value */
859 jx9_result_int64(pCtx, iTime);
860 return JX9_OK;
861}
862/*
863 * int64 filectime(string $filename)
864 * Gets inode change time of file.
865 * Parameters
866 * $filename
867 * Path to the file.
868 * Return
869 * File ctime on success or FALSE on failure.
870 */
871static int jx9Vfs_file_ctime(jx9_context *pCtx, int nArg, jx9_value **apArg)
872{
873 const char *zPath;
874 jx9_int64 iTime;
875 jx9_vfs *pVfs;
876 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
877 /* Missing/Invalid argument, return FALSE */
878 jx9_result_bool(pCtx, 0);
879 return JX9_OK;
880 }
881 /* Point to the underlying vfs */
882 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
883 if( pVfs == 0 || pVfs->xFileCtime == 0 ){
884 /* IO routine not implemented, return NULL */
885 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
886 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
887 jx9_function_name(pCtx)
888 );
889 jx9_result_bool(pCtx, 0);
890 return JX9_OK;
891 }
892 /* Point to the desired directory */
893 zPath = jx9_value_to_string(apArg[0], 0);
894 /* Perform the requested operation */
895 iTime = pVfs->xFileCtime(zPath);
896 /* IO return value */
897 jx9_result_int64(pCtx, iTime);
898 return JX9_OK;
899}
900/*
901 * bool is_file(string $filename)
902 * Tells whether the filename is a regular file.
903 * Parameters
904 * $filename
905 * Path to the file.
906 * Return
907 * TRUE on success or FALSE on failure.
908 */
909static int jx9Vfs_is_file(jx9_context *pCtx, int nArg, jx9_value **apArg)
910{
911 const char *zPath;
912 jx9_vfs *pVfs;
913 int rc;
914 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
915 /* Missing/Invalid argument, return FALSE */
916 jx9_result_bool(pCtx, 0);
917 return JX9_OK;
918 }
919 /* Point to the underlying vfs */
920 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
921 if( pVfs == 0 || pVfs->xIsfile == 0 ){
922 /* IO routine not implemented, return NULL */
923 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
924 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
925 jx9_function_name(pCtx)
926 );
927 jx9_result_bool(pCtx, 0);
928 return JX9_OK;
929 }
930 /* Point to the desired directory */
931 zPath = jx9_value_to_string(apArg[0], 0);
932 /* Perform the requested operation */
933 rc = pVfs->xIsfile(zPath);
934 /* IO return value */
935 jx9_result_bool(pCtx, rc == JX9_OK);
936 return JX9_OK;
937}
938/*
939 * bool is_link(string $filename)
940 * Tells whether the filename is a symbolic link.
941 * Parameters
942 * $filename
943 * Path to the file.
944 * Return
945 * TRUE on success or FALSE on failure.
946 */
947static int jx9Vfs_is_link(jx9_context *pCtx, int nArg, jx9_value **apArg)
948{
949 const char *zPath;
950 jx9_vfs *pVfs;
951 int rc;
952 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
953 /* Missing/Invalid argument, return FALSE */
954 jx9_result_bool(pCtx, 0);
955 return JX9_OK;
956 }
957 /* Point to the underlying vfs */
958 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
959 if( pVfs == 0 || pVfs->xIslink == 0 ){
960 /* IO routine not implemented, return NULL */
961 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
962 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
963 jx9_function_name(pCtx)
964 );
965 jx9_result_bool(pCtx, 0);
966 return JX9_OK;
967 }
968 /* Point to the desired directory */
969 zPath = jx9_value_to_string(apArg[0], 0);
970 /* Perform the requested operation */
971 rc = pVfs->xIslink(zPath);
972 /* IO return value */
973 jx9_result_bool(pCtx, rc == JX9_OK);
974 return JX9_OK;
975}
976/*
977 * bool is_readable(string $filename)
978 * Tells whether a file exists and is readable.
979 * Parameters
980 * $filename
981 * Path to the file.
982 * Return
983 * TRUE on success or FALSE on failure.
984 */
985static int jx9Vfs_is_readable(jx9_context *pCtx, int nArg, jx9_value **apArg)
986{
987 const char *zPath;
988 jx9_vfs *pVfs;
989 int rc;
990 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
991 /* Missing/Invalid argument, return FALSE */
992 jx9_result_bool(pCtx, 0);
993 return JX9_OK;
994 }
995 /* Point to the underlying vfs */
996 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
997 if( pVfs == 0 || pVfs->xReadable == 0 ){
998 /* IO routine not implemented, return NULL */
999 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1000 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1001 jx9_function_name(pCtx)
1002 );
1003 jx9_result_bool(pCtx, 0);
1004 return JX9_OK;
1005 }
1006 /* Point to the desired directory */
1007 zPath = jx9_value_to_string(apArg[0], 0);
1008 /* Perform the requested operation */
1009 rc = pVfs->xReadable(zPath);
1010 /* IO return value */
1011 jx9_result_bool(pCtx, rc == JX9_OK);
1012 return JX9_OK;
1013}
1014/*
1015 * bool is_writable(string $filename)
1016 * Tells whether the filename is writable.
1017 * Parameters
1018 * $filename
1019 * Path to the file.
1020 * Return
1021 * TRUE on success or FALSE on failure.
1022 */
1023static int jx9Vfs_is_writable(jx9_context *pCtx, int nArg, jx9_value **apArg)
1024{
1025 const char *zPath;
1026 jx9_vfs *pVfs;
1027 int rc;
1028 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1029 /* Missing/Invalid argument, return FALSE */
1030 jx9_result_bool(pCtx, 0);
1031 return JX9_OK;
1032 }
1033 /* Point to the underlying vfs */
1034 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1035 if( pVfs == 0 || pVfs->xWritable == 0 ){
1036 /* IO routine not implemented, return NULL */
1037 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1038 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1039 jx9_function_name(pCtx)
1040 );
1041 jx9_result_bool(pCtx, 0);
1042 return JX9_OK;
1043 }
1044 /* Point to the desired directory */
1045 zPath = jx9_value_to_string(apArg[0], 0);
1046 /* Perform the requested operation */
1047 rc = pVfs->xWritable(zPath);
1048 /* IO return value */
1049 jx9_result_bool(pCtx, rc == JX9_OK);
1050 return JX9_OK;
1051}
1052/*
1053 * bool is_executable(string $filename)
1054 * Tells whether the filename is executable.
1055 * Parameters
1056 * $filename
1057 * Path to the file.
1058 * Return
1059 * TRUE on success or FALSE on failure.
1060 */
1061static int jx9Vfs_is_executable(jx9_context *pCtx, int nArg, jx9_value **apArg)
1062{
1063 const char *zPath;
1064 jx9_vfs *pVfs;
1065 int rc;
1066 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1067 /* Missing/Invalid argument, return FALSE */
1068 jx9_result_bool(pCtx, 0);
1069 return JX9_OK;
1070 }
1071 /* Point to the underlying vfs */
1072 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1073 if( pVfs == 0 || pVfs->xExecutable == 0 ){
1074 /* IO routine not implemented, return NULL */
1075 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1076 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1077 jx9_function_name(pCtx)
1078 );
1079 jx9_result_bool(pCtx, 0);
1080 return JX9_OK;
1081 }
1082 /* Point to the desired directory */
1083 zPath = jx9_value_to_string(apArg[0], 0);
1084 /* Perform the requested operation */
1085 rc = pVfs->xExecutable(zPath);
1086 /* IO return value */
1087 jx9_result_bool(pCtx, rc == JX9_OK);
1088 return JX9_OK;
1089}
1090/*
1091 * string filetype(string $filename)
1092 * Gets file type.
1093 * Parameters
1094 * $filename
1095 * Path to the file.
1096 * Return
1097 * The type of the file. Possible values are fifo, char, dir, block, link
1098 * file, socket and unknown.
1099 */
1100static int jx9Vfs_filetype(jx9_context *pCtx, int nArg, jx9_value **apArg)
1101{
1102 const char *zPath;
1103 jx9_vfs *pVfs;
1104 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1105 /* Missing/Invalid argument, return 'unknown' */
1106 jx9_result_string(pCtx, "unknown", sizeof("unknown")-1);
1107 return JX9_OK;
1108 }
1109 /* Point to the underlying vfs */
1110 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1111 if( pVfs == 0 || pVfs->xFiletype == 0 ){
1112 /* IO routine not implemented, return NULL */
1113 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1114 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1115 jx9_function_name(pCtx)
1116 );
1117 jx9_result_bool(pCtx, 0);
1118 return JX9_OK;
1119 }
1120 /* Point to the desired directory */
1121 zPath = jx9_value_to_string(apArg[0], 0);
1122 /* Set the empty string as the default return value */
1123 jx9_result_string(pCtx, "", 0);
1124 /* Perform the requested operation */
1125 pVfs->xFiletype(zPath, pCtx);
1126 return JX9_OK;
1127}
1128/*
1129 * array stat(string $filename)
1130 * Gives information about a file.
1131 * Parameters
1132 * $filename
1133 * Path to the file.
1134 * Return
1135 * An associative array on success holding the following entries on success
1136 * 0 dev device number
1137 * 1 ino inode number (zero on windows)
1138 * 2 mode inode protection mode
1139 * 3 nlink number of links
1140 * 4 uid userid of owner (zero on windows)
1141 * 5 gid groupid of owner (zero on windows)
1142 * 6 rdev device type, if inode device
1143 * 7 size size in bytes
1144 * 8 atime time of last access (Unix timestamp)
1145 * 9 mtime time of last modification (Unix timestamp)
1146 * 10 ctime time of last inode change (Unix timestamp)
1147 * 11 blksize blocksize of filesystem IO (zero on windows)
1148 * 12 blocks number of 512-byte blocks allocated.
1149 * Note:
1150 * FALSE is returned on failure.
1151 */
1152static int jx9Vfs_stat(jx9_context *pCtx, int nArg, jx9_value **apArg)
1153{
1154 jx9_value *pArray, *pValue;
1155 const char *zPath;
1156 jx9_vfs *pVfs;
1157 int rc;
1158 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1159 /* Missing/Invalid argument, return FALSE */
1160 jx9_result_bool(pCtx, 0);
1161 return JX9_OK;
1162 }
1163 /* Point to the underlying vfs */
1164 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1165 if( pVfs == 0 || pVfs->xStat == 0 ){
1166 /* IO routine not implemented, return NULL */
1167 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1168 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1169 jx9_function_name(pCtx)
1170 );
1171 jx9_result_bool(pCtx, 0);
1172 return JX9_OK;
1173 }
1174 /* Create the array and the working value */
1175 pArray = jx9_context_new_array(pCtx);
1176 pValue = jx9_context_new_scalar(pCtx);
1177 if( pArray == 0 || pValue == 0 ){
1178 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
1179 jx9_result_bool(pCtx, 0);
1180 return JX9_OK;
1181 }
1182 /* Extract the file path */
1183 zPath = jx9_value_to_string(apArg[0], 0);
1184 /* Perform the requested operation */
1185 rc = pVfs->xStat(zPath, pArray, pValue);
1186 if( rc != JX9_OK ){
1187 /* IO error, return FALSE */
1188 jx9_result_bool(pCtx, 0);
1189 }else{
1190 /* Return the associative array */
1191 jx9_result_value(pCtx, pArray);
1192 }
1193 /* Don't worry about freeing memory here, everything will be released
1194 * automatically as soon we return from this function. */
1195 return JX9_OK;
1196}
1197/*
1198 * array lstat(string $filename)
1199 * Gives information about a file or symbolic link.
1200 * Parameters
1201 * $filename
1202 * Path to the file.
1203 * Return
1204 * An associative array on success holding the following entries on success
1205 * 0 dev device number
1206 * 1 ino inode number (zero on windows)
1207 * 2 mode inode protection mode
1208 * 3 nlink number of links
1209 * 4 uid userid of owner (zero on windows)
1210 * 5 gid groupid of owner (zero on windows)
1211 * 6 rdev device type, if inode device
1212 * 7 size size in bytes
1213 * 8 atime time of last access (Unix timestamp)
1214 * 9 mtime time of last modification (Unix timestamp)
1215 * 10 ctime time of last inode change (Unix timestamp)
1216 * 11 blksize blocksize of filesystem IO (zero on windows)
1217 * 12 blocks number of 512-byte blocks allocated.
1218 * Note:
1219 * FALSE is returned on failure.
1220 */
1221static int jx9Vfs_lstat(jx9_context *pCtx, int nArg, jx9_value **apArg)
1222{
1223 jx9_value *pArray, *pValue;
1224 const char *zPath;
1225 jx9_vfs *pVfs;
1226 int rc;
1227 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1228 /* Missing/Invalid argument, return FALSE */
1229 jx9_result_bool(pCtx, 0);
1230 return JX9_OK;
1231 }
1232 /* Point to the underlying vfs */
1233 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1234 if( pVfs == 0 || pVfs->xlStat == 0 ){
1235 /* IO routine not implemented, return NULL */
1236 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1237 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1238 jx9_function_name(pCtx)
1239 );
1240 jx9_result_bool(pCtx, 0);
1241 return JX9_OK;
1242 }
1243 /* Create the array and the working value */
1244 pArray = jx9_context_new_array(pCtx);
1245 pValue = jx9_context_new_scalar(pCtx);
1246 if( pArray == 0 || pValue == 0 ){
1247 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
1248 jx9_result_bool(pCtx, 0);
1249 return JX9_OK;
1250 }
1251 /* Extract the file path */
1252 zPath = jx9_value_to_string(apArg[0], 0);
1253 /* Perform the requested operation */
1254 rc = pVfs->xlStat(zPath, pArray, pValue);
1255 if( rc != JX9_OK ){
1256 /* IO error, return FALSE */
1257 jx9_result_bool(pCtx, 0);
1258 }else{
1259 /* Return the associative array */
1260 jx9_result_value(pCtx, pArray);
1261 }
1262 /* Don't worry about freeing memory here, everything will be released
1263 * automatically as soon we return from this function. */
1264 return JX9_OK;
1265}
1266/*
1267 * string getenv(string $varname)
1268 * Gets the value of an environment variable.
1269 * Parameters
1270 * $varname
1271 * The variable name.
1272 * Return
1273 * Returns the value of the environment variable varname, or FALSE if the environment
1274 * variable varname does not exist.
1275 */
1276static int jx9Vfs_getenv(jx9_context *pCtx, int nArg, jx9_value **apArg)
1277{
1278 const char *zEnv;
1279 jx9_vfs *pVfs;
1280 int iLen;
1281 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1282 /* Missing/Invalid argument, return FALSE */
1283 jx9_result_bool(pCtx, 0);
1284 return JX9_OK;
1285 }
1286 /* Point to the underlying vfs */
1287 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1288 if( pVfs == 0 || pVfs->xGetenv == 0 ){
1289 /* IO routine not implemented, return NULL */
1290 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1291 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1292 jx9_function_name(pCtx)
1293 );
1294 jx9_result_bool(pCtx, 0);
1295 return JX9_OK;
1296 }
1297 /* Extract the environment variable */
1298 zEnv = jx9_value_to_string(apArg[0], &iLen);
1299 /* Set a boolean FALSE as the default return value */
1300 jx9_result_bool(pCtx, 0);
1301 if( iLen < 1 ){
1302 /* Empty string */
1303 return JX9_OK;
1304 }
1305 /* Perform the requested operation */
1306 pVfs->xGetenv(zEnv, pCtx);
1307 return JX9_OK;
1308}
1309/*
1310 * bool putenv(string $settings)
1311 * Set the value of an environment variable.
1312 * Parameters
1313 * $setting
1314 * The setting, like "FOO=BAR"
1315 * Return
1316 * TRUE on success or FALSE on failure.
1317 */
1318static int jx9Vfs_putenv(jx9_context *pCtx, int nArg, jx9_value **apArg)
1319{
1320 const char *zName, *zValue;
1321 char *zSettings, *zEnd;
1322 jx9_vfs *pVfs;
1323 int iLen, rc;
1324 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1325 /* Missing/Invalid argument, return FALSE */
1326 jx9_result_bool(pCtx, 0);
1327 return JX9_OK;
1328 }
1329 /* Extract the setting variable */
1330 zSettings = (char *)jx9_value_to_string(apArg[0], &iLen);
1331 if( iLen < 1 ){
1332 /* Empty string, return FALSE */
1333 jx9_result_bool(pCtx, 0);
1334 return JX9_OK;
1335 }
1336 /* Parse the setting */
1337 zEnd = &zSettings[iLen];
1338 zValue = 0;
1339 zName = zSettings;
1340 while( zSettings < zEnd ){
1341 if( zSettings[0] == '=' ){
1342 /* Null terminate the name */
1343 zSettings[0] = 0;
1344 zValue = &zSettings[1];
1345 break;
1346 }
1347 zSettings++;
1348 }
1349 /* Install the environment variable in the $_Env array */
1350 if( zValue == 0 || zName[0] == 0 || zValue >= zEnd || zName >= zValue ){
1351 /* Invalid settings, retun FALSE */
1352 jx9_result_bool(pCtx, 0);
1353 if( zSettings < zEnd ){
1354 zSettings[0] = '=';
1355 }
1356 return JX9_OK;
1357 }
1358 jx9_vm_config(pCtx->pVm, JX9_VM_CONFIG_ENV_ATTR, zName, zValue, (int)(zEnd-zValue));
1359 /* Point to the underlying vfs */
1360 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1361 if( pVfs == 0 || pVfs->xSetenv == 0 ){
1362 /* IO routine not implemented, return NULL */
1363 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1364 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1365 jx9_function_name(pCtx)
1366 );
1367 jx9_result_bool(pCtx, 0);
1368 zSettings[0] = '=';
1369 return JX9_OK;
1370 }
1371 /* Perform the requested operation */
1372 rc = pVfs->xSetenv(zName, zValue);
1373 jx9_result_bool(pCtx, rc == JX9_OK );
1374 zSettings[0] = '=';
1375 return JX9_OK;
1376}
1377/*
1378 * bool touch(string $filename[, int64 $time = time()[, int64 $atime]])
1379 * Sets access and modification time of file.
1380 * Note: On windows
1381 * If the file does not exists, it will not be created.
1382 * Parameters
1383 * $filename
1384 * The name of the file being touched.
1385 * $time
1386 * The touch time. If time is not supplied, the current system time is used.
1387 * $atime
1388 * If present, the access time of the given filename is set to the value of atime.
1389 * Otherwise, it is set to the value passed to the time parameter. If neither are
1390 * present, the current system time is used.
1391 * Return
1392 * TRUE on success or FALSE on failure.
1393*/
1394static int jx9Vfs_touch(jx9_context *pCtx, int nArg, jx9_value **apArg)
1395{
1396 jx9_int64 nTime, nAccess;
1397 const char *zFile;
1398 jx9_vfs *pVfs;
1399 int rc;
1400 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1401 /* Missing/Invalid argument, return FALSE */
1402 jx9_result_bool(pCtx, 0);
1403 return JX9_OK;
1404 }
1405 /* Point to the underlying vfs */
1406 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
1407 if( pVfs == 0 || pVfs->xTouch == 0 ){
1408 /* IO routine not implemented, return NULL */
1409 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
1410 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
1411 jx9_function_name(pCtx)
1412 );
1413 jx9_result_bool(pCtx, 0);
1414 return JX9_OK;
1415 }
1416 /* Perform the requested operation */
1417 nTime = nAccess = -1;
1418 zFile = jx9_value_to_string(apArg[0], 0);
1419 if( nArg > 1 ){
1420 nTime = jx9_value_to_int64(apArg[1]);
1421 if( nArg > 2 ){
1422 nAccess = jx9_value_to_int64(apArg[1]);
1423 }else{
1424 nAccess = nTime;
1425 }
1426 }
1427 rc = pVfs->xTouch(zFile, nTime, nAccess);
1428 /* IO result */
1429 jx9_result_bool(pCtx, rc == JX9_OK);
1430 return JX9_OK;
1431}
1432/*
1433 * Path processing functions that do not need access to the VFS layer
1434 * Authors:
1435 * Symisc Systems, devel@symisc.net.
1436 * Copyright (C) Symisc Systems, http://jx9.symisc.net
1437 * Status:
1438 * Stable.
1439 */
1440/*
1441 * string dirname(string $path)
1442 * Returns parent directory's path.
1443 * Parameters
1444 * $path
1445 * Target path.
1446 * On Windows, both slash (/) and backslash (\) are used as directory separator character.
1447 * In other environments, it is the forward slash (/).
1448 * Return
1449 * The path of the parent directory. If there are no slashes in path, a dot ('.')
1450 * is returned, indicating the current directory.
1451 */
1452static int jx9Builtin_dirname(jx9_context *pCtx, int nArg, jx9_value **apArg)
1453{
1454 const char *zPath, *zDir;
1455 int iLen, iDirlen;
1456 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1457 /* Missing/Invalid arguments, return the empty string */
1458 jx9_result_string(pCtx, "", 0);
1459 return JX9_OK;
1460 }
1461 /* Point to the target path */
1462 zPath = jx9_value_to_string(apArg[0], &iLen);
1463 if( iLen < 1 ){
1464 /* Reuturn "." */
1465 jx9_result_string(pCtx, ".", sizeof(char));
1466 return JX9_OK;
1467 }
1468 /* Perform the requested operation */
1469 zDir = jx9ExtractDirName(zPath, iLen, &iDirlen);
1470 /* Return directory name */
1471 jx9_result_string(pCtx, zDir, iDirlen);
1472 return JX9_OK;
1473}
1474/*
1475 * string basename(string $path[, string $suffix ])
1476 * Returns trailing name component of path.
1477 * Parameters
1478 * $path
1479 * Target path.
1480 * On Windows, both slash (/) and backslash (\) are used as directory separator character.
1481 * In other environments, it is the forward slash (/).
1482 * $suffix
1483 * If the name component ends in suffix this will also be cut off.
1484 * Return
1485 * The base name of the given path.
1486 */
1487static int jx9Builtin_basename(jx9_context *pCtx, int nArg, jx9_value **apArg)
1488{
1489 const char *zPath, *zBase, *zEnd;
1490 int c, d, iLen;
1491 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1492 /* Missing/Invalid argument, return the empty string */
1493 jx9_result_string(pCtx, "", 0);
1494 return JX9_OK;
1495 }
1496 c = d = '/';
1497#ifdef __WINNT__
1498 d = '\\';
1499#endif
1500 /* Point to the target path */
1501 zPath = jx9_value_to_string(apArg[0], &iLen);
1502 if( iLen < 1 ){
1503 /* Empty string */
1504 jx9_result_string(pCtx, "", 0);
1505 return JX9_OK;
1506 }
1507 /* Perform the requested operation */
1508 zEnd = &zPath[iLen - 1];
1509 /* Ignore trailing '/' */
1510 while( zEnd > zPath && ( (int)zEnd[0] == c || (int)zEnd[0] == d ) ){
1511 zEnd--;
1512 }
1513 iLen = (int)(&zEnd[1]-zPath);
1514 while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){
1515 zEnd--;
1516 }
1517 zBase = (zEnd > zPath) ? &zEnd[1] : zPath;
1518 zEnd = &zPath[iLen];
1519 if( nArg > 1 && jx9_value_is_string(apArg[1]) ){
1520 const char *zSuffix;
1521 int nSuffix;
1522 /* Strip suffix */
1523 zSuffix = jx9_value_to_string(apArg[1], &nSuffix);
1524 if( nSuffix > 0 && nSuffix < iLen && SyMemcmp(&zEnd[-nSuffix], zSuffix, nSuffix) == 0 ){
1525 zEnd -= nSuffix;
1526 }
1527 }
1528 /* Store the basename */
1529 jx9_result_string(pCtx, zBase, (int)(zEnd-zBase));
1530 return JX9_OK;
1531}
1532/*
1533 * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ])
1534 * Returns information about a file path.
1535 * Parameter
1536 * $path
1537 * The path to be parsed.
1538 * $options
1539 * If present, specifies a specific element to be returned; one of
1540 * PATHINFO_DIRNAME, PATHINFO_BASENAME, PATHINFO_EXTENSION or PATHINFO_FILENAME.
1541 * Return
1542 * If the options parameter is not passed, an associative array containing the following
1543 * elements is returned: dirname, basename, extension (if any), and filename.
1544 * If options is present, returns a string containing the requested element.
1545 */
1546typedef struct path_info path_info;
1547struct path_info
1548{
1549 SyString sDir; /* Directory [i.e: /var/www] */
1550 SyString sBasename; /* Basename [i.e httpd.conf] */
1551 SyString sExtension; /* File extension [i.e xml, pdf..] */
1552 SyString sFilename; /* Filename */
1553};
1554/*
1555 * Extract path fields.
1556 */
1557static sxi32 ExtractPathInfo(const char *zPath, int nByte, path_info *pOut)
1558{
1559 const char *zPtr, *zEnd = &zPath[nByte - 1];
1560 SyString *pCur;
1561 int c, d;
1562 c = d = '/';
1563#ifdef __WINNT__
1564 d = '\\';
1565#endif
1566 /* Zero the structure */
1567 SyZero(pOut, sizeof(path_info));
1568 /* Handle special case */
1569 if( nByte == sizeof(char) && ( (int)zPath[0] == c || (int)zPath[0] == d ) ){
1570#ifdef __WINNT__
1571 SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char));
1572#else
1573 SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char));
1574#endif
1575 return SXRET_OK;
1576 }
1577 /* Extract the basename */
1578 while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){
1579 zEnd--;
1580 }
1581 zPtr = (zEnd > zPath) ? &zEnd[1] : zPath;
1582 zEnd = &zPath[nByte];
1583 /* dirname */
1584 pCur = &pOut->sDir;
1585 SyStringInitFromBuf(pCur, zPath, zPtr-zPath);
1586 if( pCur->nByte > 1 ){
1587 SyStringTrimTrailingChar(pCur, '/');
1588#ifdef __WINNT__
1589 SyStringTrimTrailingChar(pCur, '\\');
1590#endif
1591 }else if( (int)zPath[0] == c || (int)zPath[0] == d ){
1592#ifdef __WINNT__
1593 SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char));
1594#else
1595 SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char));
1596#endif
1597 }
1598 /* basename/filename */
1599 pCur = &pOut->sBasename;
1600 SyStringInitFromBuf(pCur, zPtr, zEnd-zPtr);
1601 SyStringTrimLeadingChar(pCur, '/');
1602#ifdef __WINNT__
1603 SyStringTrimLeadingChar(pCur, '\\');
1604#endif
1605 SyStringDupPtr(&pOut->sFilename, pCur);
1606 if( pCur->nByte > 0 ){
1607 /* extension */
1608 zEnd--;
1609 while( zEnd > pCur->zString /*basename*/ && zEnd[0] != '.' ){
1610 zEnd--;
1611 }
1612 if( zEnd > pCur->zString ){
1613 zEnd++; /* Jump leading dot */
1614 SyStringInitFromBuf(&pOut->sExtension, zEnd, &zPath[nByte]-zEnd);
1615 /* Fix filename */
1616 pCur = &pOut->sFilename;
1617 if( pCur->nByte > SyStringLength(&pOut->sExtension) ){
1618 pCur->nByte -= 1 + SyStringLength(&pOut->sExtension);
1619 }
1620 }
1621 }
1622 return SXRET_OK;
1623}
1624/*
1625 * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ])
1626 * See block comment above.
1627 */
1628static int jx9Builtin_pathinfo(jx9_context *pCtx, int nArg, jx9_value **apArg)
1629{
1630 const char *zPath;
1631 path_info sInfo;
1632 SyString *pComp;
1633 int iLen;
1634 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
1635 /* Missing/Invalid argument, return the empty string */
1636 jx9_result_string(pCtx, "", 0);
1637 return JX9_OK;
1638 }
1639 /* Point to the target path */
1640 zPath = jx9_value_to_string(apArg[0], &iLen);
1641 if( iLen < 1 ){
1642 /* Empty string */
1643 jx9_result_string(pCtx, "", 0);
1644 return JX9_OK;
1645 }
1646 /* Extract path info */
1647 ExtractPathInfo(zPath, iLen, &sInfo);
1648 if( nArg > 1 && jx9_value_is_int(apArg[1]) ){
1649 /* Return path component */
1650 int nComp = jx9_value_to_int(apArg[1]);
1651 switch(nComp){
1652 case 1: /* PATHINFO_DIRNAME */
1653 pComp = &sInfo.sDir;
1654 if( pComp->nByte > 0 ){
1655 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
1656 }else{
1657 /* Expand the empty string */
1658 jx9_result_string(pCtx, "", 0);
1659 }
1660 break;
1661 case 2: /*PATHINFO_BASENAME*/
1662 pComp = &sInfo.sBasename;
1663 if( pComp->nByte > 0 ){
1664 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
1665 }else{
1666 /* Expand the empty string */
1667 jx9_result_string(pCtx, "", 0);
1668 }
1669 break;
1670 case 3: /*PATHINFO_EXTENSION*/
1671 pComp = &sInfo.sExtension;
1672 if( pComp->nByte > 0 ){
1673 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
1674 }else{
1675 /* Expand the empty string */
1676 jx9_result_string(pCtx, "", 0);
1677 }
1678 break;
1679 case 4: /*PATHINFO_FILENAME*/
1680 pComp = &sInfo.sFilename;
1681 if( pComp->nByte > 0 ){
1682 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
1683 }else{
1684 /* Expand the empty string */
1685 jx9_result_string(pCtx, "", 0);
1686 }
1687 break;
1688 default:
1689 /* Expand the empty string */
1690 jx9_result_string(pCtx, "", 0);
1691 break;
1692 }
1693 }else{
1694 /* Return an associative array */
1695 jx9_value *pArray, *pValue;
1696 pArray = jx9_context_new_array(pCtx);
1697 pValue = jx9_context_new_scalar(pCtx);
1698 if( pArray == 0 || pValue == 0 ){
1699 /* Out of mem, return NULL */
1700 jx9_result_bool(pCtx, 0);
1701 return JX9_OK;
1702 }
1703 /* dirname */
1704 pComp = &sInfo.sDir;
1705 if( pComp->nByte > 0 ){
1706 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
1707 /* Perform the insertion */
1708 jx9_array_add_strkey_elem(pArray, "dirname", pValue); /* Will make it's own copy */
1709 }
1710 /* Reset the string cursor */
1711 jx9_value_reset_string_cursor(pValue);
1712 /* basername */
1713 pComp = &sInfo.sBasename;
1714 if( pComp->nByte > 0 ){
1715 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
1716 /* Perform the insertion */
1717 jx9_array_add_strkey_elem(pArray, "basename", pValue); /* Will make it's own copy */
1718 }
1719 /* Reset the string cursor */
1720 jx9_value_reset_string_cursor(pValue);
1721 /* extension */
1722 pComp = &sInfo.sExtension;
1723 if( pComp->nByte > 0 ){
1724 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
1725 /* Perform the insertion */
1726 jx9_array_add_strkey_elem(pArray, "extension", pValue); /* Will make it's own copy */
1727 }
1728 /* Reset the string cursor */
1729 jx9_value_reset_string_cursor(pValue);
1730 /* filename */
1731 pComp = &sInfo.sFilename;
1732 if( pComp->nByte > 0 ){
1733 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
1734 /* Perform the insertion */
1735 jx9_array_add_strkey_elem(pArray, "filename", pValue); /* Will make it's own copy */
1736 }
1737 /* Return the created array */
1738 jx9_result_value(pCtx, pArray);
1739 /* Don't worry about freeing memory, everything will be released
1740 * automatically as soon we return from this foreign function.
1741 */
1742 }
1743 return JX9_OK;
1744}
1745/*
1746 * Globbing implementation extracted from the sqlite3 source tree.
1747 * Original author: D. Richard Hipp (http://www.sqlite.org)
1748 * Status: Public Domain
1749 */
1750typedef unsigned char u8;
1751/* An array to map all upper-case characters into their corresponding
1752** lower-case character.
1753**
1754** SQLite only considers US-ASCII (or EBCDIC) characters. We do not
1755** handle case conversions for the UTF character set since the tables
1756** involved are nearly as big or bigger than SQLite itself.
1757*/
1758static const unsigned char sqlite3UpperToLower[] = {
1759 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
1760 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
1761 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
1762 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103,
1763 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
1764 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107,
1765 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125,
1766 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
1767 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161,
1768 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
1769 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197,
1770 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215,
1771 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233,
1772 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251,
1773 252, 253, 254, 255
1774};
1775#define GlogUpperToLower(A) if( A<0x80 ){ A = sqlite3UpperToLower[A]; }
1776/*
1777** Assuming zIn points to the first byte of a UTF-8 character,
1778** advance zIn to point to the first byte of the next UTF-8 character.
1779*/
1780#define SQLITE_SKIP_UTF8(zIn) { \
1781 if( (*(zIn++))>=0xc0 ){ \
1782 while( (*zIn & 0xc0)==0x80 ){ zIn++; } \
1783 } \
1784}
1785/*
1786** Compare two UTF-8 strings for equality where the first string can
1787** potentially be a "glob" expression. Return true (1) if they
1788** are the same and false (0) if they are different.
1789**
1790** Globbing rules:
1791**
1792** '*' Matches any sequence of zero or more characters.
1793**
1794** '?' Matches exactly one character.
1795**
1796** [...] Matches one character from the enclosed list of
1797** characters.
1798**
1799** [^...] Matches one character not in the enclosed list.
1800**
1801** With the [...] and [^...] matching, a ']' character can be included
1802** in the list by making it the first character after '[' or '^'. A
1803** range of characters can be specified using '-'. Example:
1804** "[a-z]" matches any single lower-case letter. To match a '-', make
1805** it the last character in the list.
1806**
1807** This routine is usually quick, but can be N**2 in the worst case.
1808**
1809** Hints: to match '*' or '?', put them in "[]". Like this:
1810**
1811** abc[*]xyz Matches "abc*xyz" only
1812*/
1813static int patternCompare(
1814 const u8 *zPattern, /* The glob pattern */
1815 const u8 *zString, /* The string to compare against the glob */
1816 const int esc, /* The escape character */
1817 int noCase
1818){
1819 int c, c2;
1820 int invert;
1821 int seen;
1822 u8 matchOne = '?';
1823 u8 matchAll = '*';
1824 u8 matchSet = '[';
1825 int prevEscape = 0; /* True if the previous character was 'escape' */
1826
1827 if( !zPattern || !zString ) return 0;
1828 while( (c = jx9Utf8Read(zPattern, 0, &zPattern))!=0 ){
1829 if( !prevEscape && c==matchAll ){
1830 while( (c= jx9Utf8Read(zPattern, 0, &zPattern)) == matchAll
1831 || c == matchOne ){
1832 if( c==matchOne && jx9Utf8Read(zString, 0, &zString)==0 ){
1833 return 0;
1834 }
1835 }
1836 if( c==0 ){
1837 return 1;
1838 }else if( c==esc ){
1839 c = jx9Utf8Read(zPattern, 0, &zPattern);
1840 if( c==0 ){
1841 return 0;
1842 }
1843 }else if( c==matchSet ){
1844 if( (esc==0) || (matchSet<0x80) ) return 0;
1845 while( *zString && patternCompare(&zPattern[-1], zString, esc, noCase)==0 ){
1846 SQLITE_SKIP_UTF8(zString);
1847 }
1848 return *zString!=0;
1849 }
1850 while( (c2 = jx9Utf8Read(zString, 0, &zString))!=0 ){
1851 if( noCase ){
1852 GlogUpperToLower(c2);
1853 GlogUpperToLower(c);
1854 while( c2 != 0 && c2 != c ){
1855 c2 = jx9Utf8Read(zString, 0, &zString);
1856 GlogUpperToLower(c2);
1857 }
1858 }else{
1859 while( c2 != 0 && c2 != c ){
1860 c2 = jx9Utf8Read(zString, 0, &zString);
1861 }
1862 }
1863 if( c2==0 ) return 0;
1864 if( patternCompare(zPattern, zString, esc, noCase) ) return 1;
1865 }
1866 return 0;
1867 }else if( !prevEscape && c==matchOne ){
1868 if( jx9Utf8Read(zString, 0, &zString)==0 ){
1869 return 0;
1870 }
1871 }else if( c==matchSet ){
1872 int prior_c = 0;
1873 if( esc == 0 ) return 0;
1874 seen = 0;
1875 invert = 0;
1876 c = jx9Utf8Read(zString, 0, &zString);
1877 if( c==0 ) return 0;
1878 c2 = jx9Utf8Read(zPattern, 0, &zPattern);
1879 if( c2=='^' ){
1880 invert = 1;
1881 c2 = jx9Utf8Read(zPattern, 0, &zPattern);
1882 }
1883 if( c2==']' ){
1884 if( c==']' ) seen = 1;
1885 c2 = jx9Utf8Read(zPattern, 0, &zPattern);
1886 }
1887 while( c2 && c2!=']' ){
1888 if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){
1889 c2 = jx9Utf8Read(zPattern, 0, &zPattern);
1890 if( c>=prior_c && c<=c2 ) seen = 1;
1891 prior_c = 0;
1892 }else{
1893 if( c==c2 ){
1894 seen = 1;
1895 }
1896 prior_c = c2;
1897 }
1898 c2 = jx9Utf8Read(zPattern, 0, &zPattern);
1899 }
1900 if( c2==0 || (seen ^ invert)==0 ){
1901 return 0;
1902 }
1903 }else if( esc==c && !prevEscape ){
1904 prevEscape = 1;
1905 }else{
1906 c2 = jx9Utf8Read(zString, 0, &zString);
1907 if( noCase ){
1908 GlogUpperToLower(c);
1909 GlogUpperToLower(c2);
1910 }
1911 if( c!=c2 ){
1912 return 0;
1913 }
1914 prevEscape = 0;
1915 }
1916 }
1917 return *zString==0;
1918}
1919/*
1920 * Wrapper around patternCompare() defined above.
1921 * See block comment above for more information.
1922 */
1923static int Glob(const unsigned char *zPattern, const unsigned char *zString, int iEsc, int CaseCompare)
1924{
1925 int rc;
1926 if( iEsc < 0 ){
1927 iEsc = '\\';
1928 }
1929 rc = patternCompare(zPattern, zString, iEsc, CaseCompare);
1930 return rc;
1931}
1932/*
1933 * bool fnmatch(string $pattern, string $string[, int $flags = 0 ])
1934 * Match filename against a pattern.
1935 * Parameters
1936 * $pattern
1937 * The shell wildcard pattern.
1938 * $string
1939 * The tested string.
1940 * $flags
1941 * A list of possible flags:
1942 * FNM_NOESCAPE Disable backslash escaping.
1943 * FNM_PATHNAME Slash in string only matches slash in the given pattern.
1944 * FNM_PERIOD Leading period in string must be exactly matched by period in the given pattern.
1945 * FNM_CASEFOLD Caseless match.
1946 * Return
1947 * TRUE if there is a match, FALSE otherwise.
1948 */
1949static int jx9Builtin_fnmatch(jx9_context *pCtx, int nArg, jx9_value **apArg)
1950{
1951 const char *zString, *zPattern;
1952 int iEsc = '\\';
1953 int noCase = 0;
1954 int rc;
1955 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){
1956 /* Missing/Invalid arguments, return FALSE */
1957 jx9_result_bool(pCtx, 0);
1958 return JX9_OK;
1959 }
1960 /* Extract the pattern and the string */
1961 zPattern = jx9_value_to_string(apArg[0], 0);
1962 zString = jx9_value_to_string(apArg[1], 0);
1963 /* Extract the flags if avaialble */
1964 if( nArg > 2 && jx9_value_is_int(apArg[2]) ){
1965 rc = jx9_value_to_int(apArg[2]);
1966 if( rc & 0x01 /*FNM_NOESCAPE*/){
1967 iEsc = 0;
1968 }
1969 if( rc & 0x08 /*FNM_CASEFOLD*/){
1970 noCase = 1;
1971 }
1972 }
1973 /* Go globbing */
1974 rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, noCase);
1975 /* Globbing result */
1976 jx9_result_bool(pCtx, rc);
1977 return JX9_OK;
1978}
1979/*
1980 * bool strglob(string $pattern, string $string)
1981 * Match string against a pattern.
1982 * Parameters
1983 * $pattern
1984 * The shell wildcard pattern.
1985 * $string
1986 * The tested string.
1987 * Return
1988 * TRUE if there is a match, FALSE otherwise.
1989 */
1990static int jx9Builtin_strglob(jx9_context *pCtx, int nArg, jx9_value **apArg)
1991{
1992 const char *zString, *zPattern;
1993 int iEsc = '\\';
1994 int rc;
1995 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){
1996 /* Missing/Invalid arguments, return FALSE */
1997 jx9_result_bool(pCtx, 0);
1998 return JX9_OK;
1999 }
2000 /* Extract the pattern and the string */
2001 zPattern = jx9_value_to_string(apArg[0], 0);
2002 zString = jx9_value_to_string(apArg[1], 0);
2003 /* Go globbing */
2004 rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, 0);
2005 /* Globbing result */
2006 jx9_result_bool(pCtx, rc);
2007 return JX9_OK;
2008}
2009/*
2010 * bool link(string $target, string $link)
2011 * Create a hard link.
2012 * Parameters
2013 * $target
2014 * Target of the link.
2015 * $link
2016 * The link name.
2017 * Return
2018 * TRUE on success or FALSE on failure.
2019 */
2020static int jx9Vfs_link(jx9_context *pCtx, int nArg, jx9_value **apArg)
2021{
2022 const char *zTarget, *zLink;
2023 jx9_vfs *pVfs;
2024 int rc;
2025 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){
2026 /* Missing/Invalid arguments, return FALSE */
2027 jx9_result_bool(pCtx, 0);
2028 return JX9_OK;
2029 }
2030 /* Point to the underlying vfs */
2031 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2032 if( pVfs == 0 || pVfs->xLink == 0 ){
2033 /* IO routine not implemented, return NULL */
2034 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2035 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
2036 jx9_function_name(pCtx)
2037 );
2038 jx9_result_bool(pCtx, 0);
2039 return JX9_OK;
2040 }
2041 /* Extract the given arguments */
2042 zTarget = jx9_value_to_string(apArg[0], 0);
2043 zLink = jx9_value_to_string(apArg[1], 0);
2044 /* Perform the requested operation */
2045 rc = pVfs->xLink(zTarget, zLink, 0/*Not a symbolic link */);
2046 /* IO result */
2047 jx9_result_bool(pCtx, rc == JX9_OK );
2048 return JX9_OK;
2049}
2050/*
2051 * bool symlink(string $target, string $link)
2052 * Creates a symbolic link.
2053 * Parameters
2054 * $target
2055 * Target of the link.
2056 * $link
2057 * The link name.
2058 * Return
2059 * TRUE on success or FALSE on failure.
2060 */
2061static int jx9Vfs_symlink(jx9_context *pCtx, int nArg, jx9_value **apArg)
2062{
2063 const char *zTarget, *zLink;
2064 jx9_vfs *pVfs;
2065 int rc;
2066 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){
2067 /* Missing/Invalid arguments, return FALSE */
2068 jx9_result_bool(pCtx, 0);
2069 return JX9_OK;
2070 }
2071 /* Point to the underlying vfs */
2072 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2073 if( pVfs == 0 || pVfs->xLink == 0 ){
2074 /* IO routine not implemented, return NULL */
2075 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2076 "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE",
2077 jx9_function_name(pCtx)
2078 );
2079 jx9_result_bool(pCtx, 0);
2080 return JX9_OK;
2081 }
2082 /* Extract the given arguments */
2083 zTarget = jx9_value_to_string(apArg[0], 0);
2084 zLink = jx9_value_to_string(apArg[1], 0);
2085 /* Perform the requested operation */
2086 rc = pVfs->xLink(zTarget, zLink, 1/*A symbolic link */);
2087 /* IO result */
2088 jx9_result_bool(pCtx, rc == JX9_OK );
2089 return JX9_OK;
2090}
2091/*
2092 * int umask([ int $mask ])
2093 * Changes the current umask.
2094 * Parameters
2095 * $mask
2096 * The new umask.
2097 * Return
2098 * umask() without arguments simply returns the current umask.
2099 * Otherwise the old umask is returned.
2100 */
2101static int jx9Vfs_umask(jx9_context *pCtx, int nArg, jx9_value **apArg)
2102{
2103 int iOld, iNew;
2104 jx9_vfs *pVfs;
2105 /* Point to the underlying vfs */
2106 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2107 if( pVfs == 0 || pVfs->xUmask == 0 ){
2108 /* IO routine not implemented, return -1 */
2109 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2110 "IO routine(%s) not implemented in the underlying VFS",
2111 jx9_function_name(pCtx)
2112 );
2113 jx9_result_int(pCtx, 0);
2114 return JX9_OK;
2115 }
2116 iNew = 0;
2117 if( nArg > 0 ){
2118 iNew = jx9_value_to_int(apArg[0]);
2119 }
2120 /* Perform the requested operation */
2121 iOld = pVfs->xUmask(iNew);
2122 /* Old mask */
2123 jx9_result_int(pCtx, iOld);
2124 return JX9_OK;
2125}
2126/*
2127 * string sys_get_temp_dir()
2128 * Returns directory path used for temporary files.
2129 * Parameters
2130 * None
2131 * Return
2132 * Returns the path of the temporary directory.
2133 */
2134static int jx9Vfs_sys_get_temp_dir(jx9_context *pCtx, int nArg, jx9_value **apArg)
2135{
2136 jx9_vfs *pVfs;
2137 /* Set the empty string as the default return value */
2138 jx9_result_string(pCtx, "", 0);
2139 /* Point to the underlying vfs */
2140 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2141 if( pVfs == 0 || pVfs->xTempDir == 0 ){
2142 SXUNUSED(nArg); /* cc warning */
2143 SXUNUSED(apArg);
2144 /* IO routine not implemented, return "" */
2145 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2146 "IO routine(%s) not implemented in the underlying VFS",
2147 jx9_function_name(pCtx)
2148 );
2149 return JX9_OK;
2150 }
2151 /* Perform the requested operation */
2152 pVfs->xTempDir(pCtx);
2153 return JX9_OK;
2154}
2155/*
2156 * string get_current_user()
2157 * Returns the name of the current working user.
2158 * Parameters
2159 * None
2160 * Return
2161 * Returns the name of the current working user.
2162 */
2163static int jx9Vfs_get_current_user(jx9_context *pCtx, int nArg, jx9_value **apArg)
2164{
2165 jx9_vfs *pVfs;
2166 /* Point to the underlying vfs */
2167 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2168 if( pVfs == 0 || pVfs->xUsername == 0 ){
2169 SXUNUSED(nArg); /* cc warning */
2170 SXUNUSED(apArg);
2171 /* IO routine not implemented */
2172 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2173 "IO routine(%s) not implemented in the underlying VFS",
2174 jx9_function_name(pCtx)
2175 );
2176 /* Set a dummy username */
2177 jx9_result_string(pCtx, "unknown", sizeof("unknown")-1);
2178 return JX9_OK;
2179 }
2180 /* Perform the requested operation */
2181 pVfs->xUsername(pCtx);
2182 return JX9_OK;
2183}
2184/*
2185 * int64 getmypid()
2186 * Gets process ID.
2187 * Parameters
2188 * None
2189 * Return
2190 * Returns the process ID.
2191 */
2192static int jx9Vfs_getmypid(jx9_context *pCtx, int nArg, jx9_value **apArg)
2193{
2194 jx9_int64 nProcessId;
2195 jx9_vfs *pVfs;
2196 /* Point to the underlying vfs */
2197 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2198 if( pVfs == 0 || pVfs->xProcessId == 0 ){
2199 SXUNUSED(nArg); /* cc warning */
2200 SXUNUSED(apArg);
2201 /* IO routine not implemented, return -1 */
2202 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2203 "IO routine(%s) not implemented in the underlying VFS",
2204 jx9_function_name(pCtx)
2205 );
2206 jx9_result_int(pCtx, -1);
2207 return JX9_OK;
2208 }
2209 /* Perform the requested operation */
2210 nProcessId = (jx9_int64)pVfs->xProcessId();
2211 /* Set the result */
2212 jx9_result_int64(pCtx, nProcessId);
2213 return JX9_OK;
2214}
2215/*
2216 * int getmyuid()
2217 * Get user ID.
2218 * Parameters
2219 * None
2220 * Return
2221 * Returns the user ID.
2222 */
2223static int jx9Vfs_getmyuid(jx9_context *pCtx, int nArg, jx9_value **apArg)
2224{
2225 jx9_vfs *pVfs;
2226 int nUid;
2227 /* Point to the underlying vfs */
2228 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2229 if( pVfs == 0 || pVfs->xUid == 0 ){
2230 SXUNUSED(nArg); /* cc warning */
2231 SXUNUSED(apArg);
2232 /* IO routine not implemented, return -1 */
2233 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2234 "IO routine(%s) not implemented in the underlying VFS",
2235 jx9_function_name(pCtx)
2236 );
2237 jx9_result_int(pCtx, -1);
2238 return JX9_OK;
2239 }
2240 /* Perform the requested operation */
2241 nUid = pVfs->xUid();
2242 /* Set the result */
2243 jx9_result_int(pCtx, nUid);
2244 return JX9_OK;
2245}
2246/*
2247 * int getmygid()
2248 * Get group ID.
2249 * Parameters
2250 * None
2251 * Return
2252 * Returns the group ID.
2253 */
2254static int jx9Vfs_getmygid(jx9_context *pCtx, int nArg, jx9_value **apArg)
2255{
2256 jx9_vfs *pVfs;
2257 int nGid;
2258 /* Point to the underlying vfs */
2259 pVfs = (jx9_vfs *)jx9_context_user_data(pCtx);
2260 if( pVfs == 0 || pVfs->xGid == 0 ){
2261 SXUNUSED(nArg); /* cc warning */
2262 SXUNUSED(apArg);
2263 /* IO routine not implemented, return -1 */
2264 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2265 "IO routine(%s) not implemented in the underlying VFS",
2266 jx9_function_name(pCtx)
2267 );
2268 jx9_result_int(pCtx, -1);
2269 return JX9_OK;
2270 }
2271 /* Perform the requested operation */
2272 nGid = pVfs->xGid();
2273 /* Set the result */
2274 jx9_result_int(pCtx, nGid);
2275 return JX9_OK;
2276}
2277#ifdef __WINNT__
2278#include <Windows.h>
2279#elif defined(__UNIXES__)
2280#include <sys/utsname.h>
2281#endif
2282/*
2283 * string uname([ string $mode = "a" ])
2284 * Returns information about the host operating system.
2285 * Parameters
2286 * $mode
2287 * mode is a single character that defines what information is returned:
2288 * 'a': This is the default. Contains all modes in the sequence "s n r v m".
2289 * 's': Operating system name. eg. FreeBSD.
2290 * 'n': Host name. eg. localhost.example.com.
2291 * 'r': Release name. eg. 5.1.2-RELEASE.
2292 * 'v': Version information. Varies a lot between operating systems.
2293 * 'm': Machine type. eg. i386.
2294 * Return
2295 * OS description as a string.
2296 */
2297static int jx9Vfs_uname(jx9_context *pCtx, int nArg, jx9_value **apArg)
2298{
2299#if defined(__WINNT__)
2300 const char *zName = "Microsoft Windows";
2301 OSVERSIONINFOW sVer;
2302#elif defined(__UNIXES__)
2303 struct utsname sName;
2304#endif
2305 const char *zMode = "a";
2306 if( nArg > 0 && jx9_value_is_string(apArg[0]) ){
2307 /* Extract the desired mode */
2308 zMode = jx9_value_to_string(apArg[0], 0);
2309 }
2310#if defined(__WINNT__)
2311 sVer.dwOSVersionInfoSize = sizeof(sVer);
2312 if( TRUE != GetVersionExW(&sVer)){
2313 jx9_result_string(pCtx, zName, -1);
2314 return JX9_OK;
2315 }
2316 if( sVer.dwPlatformId == VER_PLATFORM_WIN32_NT ){
2317 if( sVer.dwMajorVersion <= 4 ){
2318 zName = "Microsoft Windows NT";
2319 }else if( sVer.dwMajorVersion == 5 ){
2320 switch(sVer.dwMinorVersion){
2321 case 0: zName = "Microsoft Windows 2000"; break;
2322 case 1: zName = "Microsoft Windows XP"; break;
2323 case 2: zName = "Microsoft Windows Server 2003"; break;
2324 }
2325 }else if( sVer.dwMajorVersion == 6){
2326 switch(sVer.dwMinorVersion){
2327 case 0: zName = "Microsoft Windows Vista"; break;
2328 case 1: zName = "Microsoft Windows 7"; break;
2329 case 2: zName = "Microsoft Windows 8"; break;
2330 default: break;
2331 }
2332 }
2333 }
2334 switch(zMode[0]){
2335 case 's':
2336 /* Operating system name */
2337 jx9_result_string(pCtx, zName, -1/* Compute length automatically*/);
2338 break;
2339 case 'n':
2340 /* Host name */
2341 jx9_result_string(pCtx, "localhost", (int)sizeof("localhost")-1);
2342 break;
2343 case 'r':
2344 case 'v':
2345 /* Version information. */
2346 jx9_result_string_format(pCtx, "%u.%u build %u",
2347 sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber
2348 );
2349 break;
2350 case 'm':
2351 /* Machine name */
2352 jx9_result_string(pCtx, "x86", (int)sizeof("x86")-1);
2353 break;
2354 default:
2355 jx9_result_string_format(pCtx, "%s localhost %u.%u build %u x86",
2356 zName,
2357 sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber
2358 );
2359 break;
2360 }
2361#elif defined(__UNIXES__)
2362 if( uname(&sName) != 0 ){
2363 jx9_result_string(pCtx, "Unix", (int)sizeof("Unix")-1);
2364 return JX9_OK;
2365 }
2366 switch(zMode[0]){
2367 case 's':
2368 /* Operating system name */
2369 jx9_result_string(pCtx, sName.sysname, -1/* Compute length automatically*/);
2370 break;
2371 case 'n':
2372 /* Host name */
2373 jx9_result_string(pCtx, sName.nodename, -1/* Compute length automatically*/);
2374 break;
2375 case 'r':
2376 /* Release information */
2377 jx9_result_string(pCtx, sName.release, -1/* Compute length automatically*/);
2378 break;
2379 case 'v':
2380 /* Version information. */
2381 jx9_result_string(pCtx, sName.version, -1/* Compute length automatically*/);
2382 break;
2383 case 'm':
2384 /* Machine name */
2385 jx9_result_string(pCtx, sName.machine, -1/* Compute length automatically*/);
2386 break;
2387 default:
2388 jx9_result_string_format(pCtx,
2389 "%s %s %s %s %s",
2390 sName.sysname,
2391 sName.release,
2392 sName.version,
2393 sName.nodename,
2394 sName.machine
2395 );
2396 break;
2397 }
2398#else
2399 jx9_result_string(pCtx, "Host Operating System/localhost", (int)sizeof("Host Operating System/localhost")-1);
2400#endif
2401 return JX9_OK;
2402}
2403/*
2404 * Section:
2405 * IO stream implementation.
2406 * Authors:
2407 * Symisc Systems, devel@symisc.net.
2408 * Copyright (C) Symisc Systems, http://jx9.symisc.net
2409 * Status:
2410 * Stable.
2411 */
2412typedef struct io_private io_private;
2413struct io_private
2414{
2415 const jx9_io_stream *pStream; /* Underlying IO device */
2416 void *pHandle; /* IO handle */
2417 /* Unbuffered IO */
2418 SyBlob sBuffer; /* Working buffer */
2419 sxu32 nOfft; /* Current read offset */
2420 sxu32 iMagic; /* Sanity check to avoid misuse */
2421};
2422#define IO_PRIVATE_MAGIC 0xFEAC14
2423/* Make sure we are dealing with a valid io_private instance */
2424#define IO_PRIVATE_INVALID(IO) ( IO == 0 || IO->iMagic != IO_PRIVATE_MAGIC )
2425/* Forward declaration */
2426static void ResetIOPrivate(io_private *pDev);
2427/*
2428 * bool ftruncate(resource $handle, int64 $size)
2429 * Truncates a file to a given length.
2430 * Parameters
2431 * $handle
2432 * The file pointer.
2433 * Note:
2434 * The handle must be open for writing.
2435 * $size
2436 * The size to truncate to.
2437 * Return
2438 * TRUE on success or FALSE on failure.
2439 */
2440static int jx9Builtin_ftruncate(jx9_context *pCtx, int nArg, jx9_value **apArg)
2441{
2442 const jx9_io_stream *pStream;
2443 io_private *pDev;
2444 int rc;
2445 if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){
2446 /* Missing/Invalid arguments, return FALSE */
2447 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2448 jx9_result_bool(pCtx, 0);
2449 return JX9_OK;
2450 }
2451 /* Extract our private data */
2452 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
2453 /* Make sure we are dealing with a valid io_private instance */
2454 if( IO_PRIVATE_INVALID(pDev) ){
2455 /*Expecting an IO handle */
2456 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2457 jx9_result_bool(pCtx, 0);
2458 return JX9_OK;
2459 }
2460 /* Point to the target IO stream device */
2461 pStream = pDev->pStream;
2462 if( pStream == 0 || pStream->xTrunc == 0){
2463 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2464 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
2465 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
2466 );
2467 jx9_result_bool(pCtx, 0);
2468 return JX9_OK;
2469 }
2470 /* Perform the requested operation */
2471 rc = pStream->xTrunc(pDev->pHandle, jx9_value_to_int64(apArg[1]));
2472 if( rc == JX9_OK ){
2473 /* Discard buffered data */
2474 ResetIOPrivate(pDev);
2475 }
2476 /* IO result */
2477 jx9_result_bool(pCtx, rc == JX9_OK);
2478 return JX9_OK;
2479}
2480/*
2481 * int fseek(resource $handle, int $offset[, int $whence = SEEK_SET ])
2482 * Seeks on a file pointer.
2483 * Parameters
2484 * $handle
2485 * A file system pointer resource that is typically created using fopen().
2486 * $offset
2487 * The offset.
2488 * To move to a position before the end-of-file, you need to pass a negative
2489 * value in offset and set whence to SEEK_END.
2490 * whence
2491 * whence values are:
2492 * SEEK_SET - Set position equal to offset bytes.
2493 * SEEK_CUR - Set position to current location plus offset.
2494 * SEEK_END - Set position to end-of-file plus offset.
2495 * Return
2496 * 0 on success, -1 on failure
2497 */
2498static int jx9Builtin_fseek(jx9_context *pCtx, int nArg, jx9_value **apArg)
2499{
2500 const jx9_io_stream *pStream;
2501 io_private *pDev;
2502 jx9_int64 iOfft;
2503 int whence;
2504 int rc;
2505 if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){
2506 /* Missing/Invalid arguments, return FALSE */
2507 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2508 jx9_result_int(pCtx, -1);
2509 return JX9_OK;
2510 }
2511 /* Extract our private data */
2512 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
2513 /* Make sure we are dealing with a valid io_private instance */
2514 if( IO_PRIVATE_INVALID(pDev) ){
2515 /*Expecting an IO handle */
2516 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2517 jx9_result_int(pCtx, -1);
2518 return JX9_OK;
2519 }
2520 /* Point to the target IO stream device */
2521 pStream = pDev->pStream;
2522 if( pStream == 0 || pStream->xSeek == 0){
2523 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2524 "IO routine(%s) not implemented in the underlying stream(%s) device",
2525 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
2526 );
2527 jx9_result_int(pCtx, -1);
2528 return JX9_OK;
2529 }
2530 /* Extract the offset */
2531 iOfft = jx9_value_to_int64(apArg[1]);
2532 whence = 0;/* SEEK_SET */
2533 if( nArg > 2 && jx9_value_is_int(apArg[2]) ){
2534 whence = jx9_value_to_int(apArg[2]);
2535 }
2536 /* Perform the requested operation */
2537 rc = pStream->xSeek(pDev->pHandle, iOfft, whence);
2538 if( rc == JX9_OK ){
2539 /* Ignore buffered data */
2540 ResetIOPrivate(pDev);
2541 }
2542 /* IO result */
2543 jx9_result_int(pCtx, rc == JX9_OK ? 0 : - 1);
2544 return JX9_OK;
2545}
2546/*
2547 * int64 ftell(resource $handle)
2548 * Returns the current position of the file read/write pointer.
2549 * Parameters
2550 * $handle
2551 * The file pointer.
2552 * Return
2553 * Returns the position of the file pointer referenced by handle
2554 * as an integer; i.e., its offset into the file stream.
2555 * FALSE is returned on failure.
2556 */
2557static int jx9Builtin_ftell(jx9_context *pCtx, int nArg, jx9_value **apArg)
2558{
2559 const jx9_io_stream *pStream;
2560 io_private *pDev;
2561 jx9_int64 iOfft;
2562 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
2563 /* Missing/Invalid arguments, return FALSE */
2564 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2565 jx9_result_bool(pCtx, 0);
2566 return JX9_OK;
2567 }
2568 /* Extract our private data */
2569 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
2570 /* Make sure we are dealing with a valid io_private instance */
2571 if( IO_PRIVATE_INVALID(pDev) ){
2572 /*Expecting an IO handle */
2573 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2574 jx9_result_bool(pCtx, 0);
2575 return JX9_OK;
2576 }
2577 /* Point to the target IO stream device */
2578 pStream = pDev->pStream;
2579 if( pStream == 0 || pStream->xTell == 0){
2580 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2581 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
2582 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
2583 );
2584 jx9_result_bool(pCtx, 0);
2585 return JX9_OK;
2586 }
2587 /* Perform the requested operation */
2588 iOfft = pStream->xTell(pDev->pHandle);
2589 /* IO result */
2590 jx9_result_int64(pCtx, iOfft);
2591 return JX9_OK;
2592}
2593/*
2594 * bool rewind(resource $handle)
2595 * Rewind the position of a file pointer.
2596 * Parameters
2597 * $handle
2598 * The file pointer.
2599 * Return
2600 * TRUE on success or FALSE on failure.
2601 */
2602static int jx9Builtin_rewind(jx9_context *pCtx, int nArg, jx9_value **apArg)
2603{
2604 const jx9_io_stream *pStream;
2605 io_private *pDev;
2606 int rc;
2607 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
2608 /* Missing/Invalid arguments, return FALSE */
2609 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2610 jx9_result_bool(pCtx, 0);
2611 return JX9_OK;
2612 }
2613 /* Extract our private data */
2614 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
2615 /* Make sure we are dealing with a valid io_private instance */
2616 if( IO_PRIVATE_INVALID(pDev) ){
2617 /*Expecting an IO handle */
2618 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2619 jx9_result_bool(pCtx, 0);
2620 return JX9_OK;
2621 }
2622 /* Point to the target IO stream device */
2623 pStream = pDev->pStream;
2624 if( pStream == 0 || pStream->xSeek == 0){
2625 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2626 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
2627 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
2628 );
2629 jx9_result_bool(pCtx, 0);
2630 return JX9_OK;
2631 }
2632 /* Perform the requested operation */
2633 rc = pStream->xSeek(pDev->pHandle, 0, 0/*SEEK_SET*/);
2634 if( rc == JX9_OK ){
2635 /* Ignore buffered data */
2636 ResetIOPrivate(pDev);
2637 }
2638 /* IO result */
2639 jx9_result_bool(pCtx, rc == JX9_OK);
2640 return JX9_OK;
2641}
2642/*
2643 * bool fflush(resource $handle)
2644 * Flushes the output to a file.
2645 * Parameters
2646 * $handle
2647 * The file pointer.
2648 * Return
2649 * TRUE on success or FALSE on failure.
2650 */
2651static int jx9Builtin_fflush(jx9_context *pCtx, int nArg, jx9_value **apArg)
2652{
2653 const jx9_io_stream *pStream;
2654 io_private *pDev;
2655 int rc;
2656 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
2657 /* Missing/Invalid arguments, return FALSE */
2658 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2659 jx9_result_bool(pCtx, 0);
2660 return JX9_OK;
2661 }
2662 /* Extract our private data */
2663 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
2664 /* Make sure we are dealing with a valid io_private instance */
2665 if( IO_PRIVATE_INVALID(pDev) ){
2666 /*Expecting an IO handle */
2667 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2668 jx9_result_bool(pCtx, 0);
2669 return JX9_OK;
2670 }
2671 /* Point to the target IO stream device */
2672 pStream = pDev->pStream;
2673 if( pStream == 0 || pStream->xSync == 0){
2674 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2675 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
2676 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
2677 );
2678 jx9_result_bool(pCtx, 0);
2679 return JX9_OK;
2680 }
2681 /* Perform the requested operation */
2682 rc = pStream->xSync(pDev->pHandle);
2683 /* IO result */
2684 jx9_result_bool(pCtx, rc == JX9_OK);
2685 return JX9_OK;
2686}
2687/*
2688 * bool feof(resource $handle)
2689 * Tests for end-of-file on a file pointer.
2690 * Parameters
2691 * $handle
2692 * The file pointer.
2693 * Return
2694 * Returns TRUE if the file pointer is at EOF.FALSE otherwise
2695 */
2696static int jx9Builtin_feof(jx9_context *pCtx, int nArg, jx9_value **apArg)
2697{
2698 const jx9_io_stream *pStream;
2699 io_private *pDev;
2700 int rc;
2701 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
2702 /* Missing/Invalid arguments */
2703 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2704 jx9_result_bool(pCtx, 1);
2705 return JX9_OK;
2706 }
2707 /* Extract our private data */
2708 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
2709 /* Make sure we are dealing with a valid io_private instance */
2710 if( IO_PRIVATE_INVALID(pDev) ){
2711 /*Expecting an IO handle */
2712 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
2713 jx9_result_bool(pCtx, 1);
2714 return JX9_OK;
2715 }
2716 /* Point to the target IO stream device */
2717 pStream = pDev->pStream;
2718 if( pStream == 0 ){
2719 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
2720 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
2721 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
2722 );
2723 jx9_result_bool(pCtx, 1);
2724 return JX9_OK;
2725 }
2726 rc = SXERR_EOF;
2727 /* Perform the requested operation */
2728 if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){
2729 /* Data is available */
2730 rc = JX9_OK;
2731 }else{
2732 char zBuf[4096];
2733 jx9_int64 n;
2734 /* Perform a buffered read */
2735 n = pStream->xRead(pDev->pHandle, zBuf, sizeof(zBuf));
2736 if( n > 0 ){
2737 /* Copy buffered data */
2738 SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n);
2739 rc = JX9_OK;
2740 }
2741 }
2742 /* EOF or not */
2743 jx9_result_bool(pCtx, rc == SXERR_EOF);
2744 return JX9_OK;
2745}
2746/*
2747 * Read n bytes from the underlying IO stream device.
2748 * Return total numbers of bytes readen on success. A number < 1 on failure
2749 * [i.e: IO error ] or EOF.
2750 */
2751static jx9_int64 StreamRead(io_private *pDev, void *pBuf, jx9_int64 nLen)
2752{
2753 const jx9_io_stream *pStream = pDev->pStream;
2754 char *zBuf = (char *)pBuf;
2755 jx9_int64 n, nRead;
2756 n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft;
2757 if( n > 0 ){
2758 if( n > nLen ){
2759 n = nLen;
2760 }
2761 /* Copy the buffered data */
2762 SyMemcpy(SyBlobDataAt(&pDev->sBuffer, pDev->nOfft), pBuf, (sxu32)n);
2763 /* Update the read offset */
2764 pDev->nOfft += (sxu32)n;
2765 if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){
2766 /* Reset the working buffer so that we avoid excessive memory allocation */
2767 SyBlobReset(&pDev->sBuffer);
2768 pDev->nOfft = 0;
2769 }
2770 nLen -= n;
2771 if( nLen < 1 ){
2772 /* All done */
2773 return n;
2774 }
2775 /* Advance the cursor */
2776 zBuf += n;
2777 }
2778 /* Read without buffering */
2779 nRead = pStream->xRead(pDev->pHandle, zBuf, nLen);
2780 if( nRead > 0 ){
2781 n += nRead;
2782 }else if( n < 1 ){
2783 /* EOF or IO error */
2784 return nRead;
2785 }
2786 return n;
2787}
2788/*
2789 * Extract a single line from the buffered input.
2790 */
2791static sxi32 GetLine(io_private *pDev, jx9_int64 *pLen, const char **pzLine)
2792{
2793 const char *zIn, *zEnd, *zPtr;
2794 zIn = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft);
2795 zEnd = &zIn[SyBlobLength(&pDev->sBuffer)-pDev->nOfft];
2796 zPtr = zIn;
2797 while( zIn < zEnd ){
2798 if( zIn[0] == '\n' ){
2799 /* Line found */
2800 zIn++; /* Include the line ending as requested by the JX9 specification */
2801 *pLen = (jx9_int64)(zIn-zPtr);
2802 *pzLine = zPtr;
2803 return SXRET_OK;
2804 }
2805 zIn++;
2806 }
2807 /* No line were found */
2808 return SXERR_NOTFOUND;
2809}
2810/*
2811 * Read a single line from the underlying IO stream device.
2812 */
2813static jx9_int64 StreamReadLine(io_private *pDev, const char **pzData, jx9_int64 nMaxLen)
2814{
2815 const jx9_io_stream *pStream = pDev->pStream;
2816 char zBuf[8192];
2817 jx9_int64 n;
2818 sxi32 rc;
2819 n = 0;
2820 if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){
2821 /* Reset the working buffer so that we avoid excessive memory allocation */
2822 SyBlobReset(&pDev->sBuffer);
2823 pDev->nOfft = 0;
2824 }
2825 if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){
2826 /* Check if there is a line */
2827 rc = GetLine(pDev, &n, pzData);
2828 if( rc == SXRET_OK ){
2829 /* Got line, update the cursor */
2830 pDev->nOfft += (sxu32)n;
2831 return n;
2832 }
2833 }
2834 /* Perform the read operation until a new line is extracted or length
2835 * limit is reached.
2836 */
2837 for(;;){
2838 n = pStream->xRead(pDev->pHandle, zBuf, (nMaxLen > 0 && nMaxLen < sizeof(zBuf)) ? nMaxLen : sizeof(zBuf));
2839 if( n < 1 ){
2840 /* EOF or IO error */
2841 break;
2842 }
2843 /* Append the data just read */
2844 SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n);
2845 /* Try to extract a line */
2846 rc = GetLine(pDev, &n, pzData);
2847 if( rc == SXRET_OK ){
2848 /* Got one, return immediately */
2849 pDev->nOfft += (sxu32)n;
2850 return n;
2851 }
2852 if( nMaxLen > 0 && (SyBlobLength(&pDev->sBuffer) - pDev->nOfft >= nMaxLen) ){
2853 /* Read limit reached, return the available data */
2854 *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft);
2855 n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft;
2856 /* Reset the working buffer */
2857 SyBlobReset(&pDev->sBuffer);
2858 pDev->nOfft = 0;
2859 return n;
2860 }
2861 }
2862 if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){
2863 /* Read limit reached, return the available data */
2864 *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft);
2865 n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft;
2866 /* Reset the working buffer */
2867 SyBlobReset(&pDev->sBuffer);
2868 pDev->nOfft = 0;
2869 }
2870 return n;
2871}
2872/*
2873 * Open an IO stream handle.
2874 * Notes on stream:
2875 * According to the JX9 reference manual.
2876 * In its simplest definition, a stream is a resource object which exhibits streamable behavior.
2877 * That is, it can be read from or written to in a linear fashion, and may be able to fseek()
2878 * to an arbitrary locations within the stream.
2879 * A wrapper is additional code which tells the stream how to handle specific protocols/encodings.
2880 * For example, the http wrapper knows how to translate a URL into an HTTP/1.0 request for a file
2881 * on a remote server.
2882 * A stream is referenced as: scheme://target
2883 * scheme(string) - The name of the wrapper to be used. Examples include: file, http...
2884 * If no wrapper is specified, the function default is used (typically file://).
2885 * target - Depends on the wrapper used. For filesystem related streams this is typically a path
2886 * and filename of the desired file. For network related streams this is typically a hostname, often
2887 * with a path appended.
2888 *
2889 * Note that JX9 IO streams looks like JX9 streams but their implementation differ greately.
2890 * Please refer to the official documentation for a full discussion.
2891 * This function return a handle on success. Otherwise null.
2892 */
2893JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile,
2894 int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew)
2895{
2896 void *pHandle = 0; /* cc warning */
2897 SyString sFile;
2898 int rc;
2899 if( pStream == 0 ){
2900 /* No such stream device */
2901 return 0;
2902 }
2903 SyStringInitFromBuf(&sFile, zFile, SyStrlen(zFile));
2904 if( use_include ){
2905 if( sFile.zString[0] == '/' ||
2906#ifdef __WINNT__
2907 (sFile.nByte > 2 && sFile.zString[1] == ':' && (sFile.zString[2] == '\\' || sFile.zString[2] == '/') ) ||
2908#endif
2909 (sFile.nByte > 1 && sFile.zString[0] == '.' && sFile.zString[1] == '/') ||
2910 (sFile.nByte > 2 && sFile.zString[0] == '.' && sFile.zString[1] == '.' && sFile.zString[2] == '/') ){
2911 /* Open the file directly */
2912 rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle);
2913 }else{
2914 SyString *pPath;
2915 SyBlob sWorker;
2916#ifdef __WINNT__
2917 static const int c = '\\';
2918#else
2919 static const int c = '/';
2920#endif
2921 /* Init the path builder working buffer */
2922 SyBlobInit(&sWorker, &pVm->sAllocator);
2923 /* Build a path from the set of include path */
2924 SySetResetCursor(&pVm->aPaths);
2925 rc = SXERR_IO;
2926 while( SXRET_OK == SySetGetNextEntry(&pVm->aPaths, (void **)&pPath) ){
2927 /* Build full path */
2928 SyBlobFormat(&sWorker, "%z%c%z", pPath, c, &sFile);
2929 /* Append null terminator */
2930 if( SXRET_OK != SyBlobNullAppend(&sWorker) ){
2931 continue;
2932 }
2933 /* Try to open the file */
2934 rc = pStream->xOpen((const char *)SyBlobData(&sWorker), iFlags, pResource, &pHandle);
2935 if( rc == JX9_OK ){
2936 if( bPushInclude ){
2937 /* Mark as included */
2938 jx9VmPushFilePath(pVm, (const char *)SyBlobData(&sWorker), SyBlobLength(&sWorker), FALSE, pNew);
2939 }
2940 break;
2941 }
2942 /* Reset the working buffer */
2943 SyBlobReset(&sWorker);
2944 /* Check the next path */
2945 }
2946 SyBlobRelease(&sWorker);
2947 }
2948 if( rc == JX9_OK ){
2949 if( bPushInclude ){
2950 /* Mark as included */
2951 jx9VmPushFilePath(pVm, sFile.zString, sFile.nByte, FALSE, pNew);
2952 }
2953 }
2954 }else{
2955 /* Open the URI direcly */
2956 rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle);
2957 }
2958 if( rc != JX9_OK ){
2959 /* IO error */
2960 return 0;
2961 }
2962 /* Return the file handle */
2963 return pHandle;
2964}
2965/*
2966 * Read the whole contents of an open IO stream handle [i.e local file/URL..]
2967 * Store the read data in the given BLOB (last argument).
2968 * The read operation is stopped when he hit the EOF or an IO error occurs.
2969 */
2970JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut)
2971{
2972 jx9_int64 nRead;
2973 char zBuf[8192]; /* 8K */
2974 int rc;
2975 /* Perform the requested operation */
2976 for(;;){
2977 nRead = pStream->xRead(pHandle, zBuf, sizeof(zBuf));
2978 if( nRead < 1 ){
2979 /* EOF or IO error */
2980 break;
2981 }
2982 /* Append contents */
2983 rc = SyBlobAppend(pOut, zBuf, (sxu32)nRead);
2984 if( rc != SXRET_OK ){
2985 break;
2986 }
2987 }
2988 return SyBlobLength(pOut) > 0 ? SXRET_OK : -1;
2989}
2990/*
2991 * Close an open IO stream handle [i.e local file/URI..].
2992 */
2993JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle)
2994{
2995 if( pStream->xClose ){
2996 pStream->xClose(pHandle);
2997 }
2998}
2999/*
3000 * string fgetc(resource $handle)
3001 * Gets a character from the given file pointer.
3002 * Parameters
3003 * $handle
3004 * The file pointer.
3005 * Return
3006 * Returns a string containing a single character read from the file
3007 * pointed to by handle. Returns FALSE on EOF.
3008 * WARNING
3009 * This operation is extremely slow.Avoid using it.
3010 */
3011static int jx9Builtin_fgetc(jx9_context *pCtx, int nArg, jx9_value **apArg)
3012{
3013 const jx9_io_stream *pStream;
3014 io_private *pDev;
3015 int c, n;
3016 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3017 /* Missing/Invalid arguments, return FALSE */
3018 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3019 jx9_result_bool(pCtx, 0);
3020 return JX9_OK;
3021 }
3022 /* Extract our private data */
3023 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3024 /* Make sure we are dealing with a valid io_private instance */
3025 if( IO_PRIVATE_INVALID(pDev) ){
3026 /*Expecting an IO handle */
3027 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3028 jx9_result_bool(pCtx, 0);
3029 return JX9_OK;
3030 }
3031 /* Point to the target IO stream device */
3032 pStream = pDev->pStream;
3033 if( pStream == 0 ){
3034 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3035 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3036 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3037 );
3038 jx9_result_bool(pCtx, 0);
3039 return JX9_OK;
3040 }
3041 /* Perform the requested operation */
3042 n = (int)StreamRead(pDev, (void *)&c, sizeof(char));
3043 /* IO result */
3044 if( n < 1 ){
3045 /* EOF or error, return FALSE */
3046 jx9_result_bool(pCtx, 0);
3047 }else{
3048 /* Return the string holding the character */
3049 jx9_result_string(pCtx, (const char *)&c, sizeof(char));
3050 }
3051 return JX9_OK;
3052}
3053/*
3054 * string fgets(resource $handle[, int64 $length ])
3055 * Gets line from file pointer.
3056 * Parameters
3057 * $handle
3058 * The file pointer.
3059 * $length
3060 * Reading ends when length - 1 bytes have been read, on a newline
3061 * (which is included in the return value), or on EOF (whichever comes first).
3062 * If no length is specified, it will keep reading from the stream until it reaches
3063 * the end of the line.
3064 * Return
3065 * Returns a string of up to length - 1 bytes read from the file pointed to by handle.
3066 * If there is no more data to read in the file pointer, then FALSE is returned.
3067 * If an error occurs, FALSE is returned.
3068 */
3069static int jx9Builtin_fgets(jx9_context *pCtx, int nArg, jx9_value **apArg)
3070{
3071 const jx9_io_stream *pStream;
3072 const char *zLine;
3073 io_private *pDev;
3074 jx9_int64 n, nLen;
3075 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3076 /* Missing/Invalid arguments, return FALSE */
3077 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3078 jx9_result_bool(pCtx, 0);
3079 return JX9_OK;
3080 }
3081 /* Extract our private data */
3082 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3083 /* Make sure we are dealing with a valid io_private instance */
3084 if( IO_PRIVATE_INVALID(pDev) ){
3085 /*Expecting an IO handle */
3086 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3087 jx9_result_bool(pCtx, 0);
3088 return JX9_OK;
3089 }
3090 /* Point to the target IO stream device */
3091 pStream = pDev->pStream;
3092 if( pStream == 0 ){
3093 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3094 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3095 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3096 );
3097 jx9_result_bool(pCtx, 0);
3098 return JX9_OK;
3099 }
3100 nLen = -1;
3101 if( nArg > 1 ){
3102 /* Maximum data to read */
3103 nLen = jx9_value_to_int64(apArg[1]);
3104 }
3105 /* Perform the requested operation */
3106 n = StreamReadLine(pDev, &zLine, nLen);
3107 if( n < 1 ){
3108 /* EOF or IO error, return FALSE */
3109 jx9_result_bool(pCtx, 0);
3110 }else{
3111 /* Return the freshly extracted line */
3112 jx9_result_string(pCtx, zLine, (int)n);
3113 }
3114 return JX9_OK;
3115}
3116/*
3117 * string fread(resource $handle, int64 $length)
3118 * Binary-safe file read.
3119 * Parameters
3120 * $handle
3121 * The file pointer.
3122 * $length
3123 * Up to length number of bytes read.
3124 * Return
3125 * The data readen on success or FALSE on failure.
3126 */
3127static int jx9Builtin_fread(jx9_context *pCtx, int nArg, jx9_value **apArg)
3128{
3129 const jx9_io_stream *pStream;
3130 io_private *pDev;
3131 jx9_int64 nRead;
3132 void *pBuf;
3133 int nLen;
3134 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3135 /* Missing/Invalid arguments, return FALSE */
3136 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3137 jx9_result_bool(pCtx, 0);
3138 return JX9_OK;
3139 }
3140 /* Extract our private data */
3141 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3142 /* Make sure we are dealing with a valid io_private instance */
3143 if( IO_PRIVATE_INVALID(pDev) ){
3144 /*Expecting an IO handle */
3145 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3146 jx9_result_bool(pCtx, 0);
3147 return JX9_OK;
3148 }
3149 /* Point to the target IO stream device */
3150 pStream = pDev->pStream;
3151 if( pStream == 0 ){
3152 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3153 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3154 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3155 );
3156 jx9_result_bool(pCtx, 0);
3157 return JX9_OK;
3158 }
3159 nLen = 4096;
3160 if( nArg > 1 ){
3161 nLen = jx9_value_to_int(apArg[1]);
3162 if( nLen < 1 ){
3163 /* Invalid length, set a default length */
3164 nLen = 4096;
3165 }
3166 }
3167 /* Allocate enough buffer */
3168 pBuf = jx9_context_alloc_chunk(pCtx, (unsigned int)nLen, FALSE, FALSE);
3169 if( pBuf == 0 ){
3170 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
3171 jx9_result_bool(pCtx, 0);
3172 return JX9_OK;
3173 }
3174 /* Perform the requested operation */
3175 nRead = StreamRead(pDev, pBuf, (jx9_int64)nLen);
3176 if( nRead < 1 ){
3177 /* Nothing read, return FALSE */
3178 jx9_result_bool(pCtx, 0);
3179 }else{
3180 /* Make a copy of the data just read */
3181 jx9_result_string(pCtx, (const char *)pBuf, (int)nRead);
3182 }
3183 /* Release the buffer */
3184 jx9_context_free_chunk(pCtx, pBuf);
3185 return JX9_OK;
3186}
3187/*
3188 * array fgetcsv(resource $handle [, int $length = 0
3189 * [, string $delimiter = ', '[, string $enclosure = '"'[, string $escape='\\']]]])
3190 * Gets line from file pointer and parse for CSV fields.
3191 * Parameters
3192 * $handle
3193 * The file pointer.
3194 * $length
3195 * Reading ends when length - 1 bytes have been read, on a newline
3196 * (which is included in the return value), or on EOF (whichever comes first).
3197 * If no length is specified, it will keep reading from the stream until it reaches
3198 * the end of the line.
3199 * $delimiter
3200 * Set the field delimiter (one character only).
3201 * $enclosure
3202 * Set the field enclosure character (one character only).
3203 * $escape
3204 * Set the escape character (one character only). Defaults as a backslash (\)
3205 * Return
3206 * Returns a string of up to length - 1 bytes read from the file pointed to by handle.
3207 * If there is no more data to read in the file pointer, then FALSE is returned.
3208 * If an error occurs, FALSE is returned.
3209 */
3210static int jx9Builtin_fgetcsv(jx9_context *pCtx, int nArg, jx9_value **apArg)
3211{
3212 const jx9_io_stream *pStream;
3213 const char *zLine;
3214 io_private *pDev;
3215 jx9_int64 n, nLen;
3216 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3217 /* Missing/Invalid arguments, return FALSE */
3218 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3219 jx9_result_bool(pCtx, 0);
3220 return JX9_OK;
3221 }
3222 /* Extract our private data */
3223 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3224 /* Make sure we are dealing with a valid io_private instance */
3225 if( IO_PRIVATE_INVALID(pDev) ){
3226 /*Expecting an IO handle */
3227 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3228 jx9_result_bool(pCtx, 0);
3229 return JX9_OK;
3230 }
3231 /* Point to the target IO stream device */
3232 pStream = pDev->pStream;
3233 if( pStream == 0 ){
3234 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3235 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3236 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3237 );
3238 jx9_result_bool(pCtx, 0);
3239 return JX9_OK;
3240 }
3241 nLen = -1;
3242 if( nArg > 1 ){
3243 /* Maximum data to read */
3244 nLen = jx9_value_to_int64(apArg[1]);
3245 }
3246 /* Perform the requested operation */
3247 n = StreamReadLine(pDev, &zLine, nLen);
3248 if( n < 1 ){
3249 /* EOF or IO error, return FALSE */
3250 jx9_result_bool(pCtx, 0);
3251 }else{
3252 jx9_value *pArray;
3253 int delim = ','; /* Delimiter */
3254 int encl = '"' ; /* Enclosure */
3255 int escape = '\\'; /* Escape character */
3256 if( nArg > 2 ){
3257 const char *zPtr;
3258 int i;
3259 if( jx9_value_is_string(apArg[2]) ){
3260 /* Extract the delimiter */
3261 zPtr = jx9_value_to_string(apArg[2], &i);
3262 if( i > 0 ){
3263 delim = zPtr[0];
3264 }
3265 }
3266 if( nArg > 3 ){
3267 if( jx9_value_is_string(apArg[3]) ){
3268 /* Extract the enclosure */
3269 zPtr = jx9_value_to_string(apArg[3], &i);
3270 if( i > 0 ){
3271 encl = zPtr[0];
3272 }
3273 }
3274 if( nArg > 4 ){
3275 if( jx9_value_is_string(apArg[4]) ){
3276 /* Extract the escape character */
3277 zPtr = jx9_value_to_string(apArg[4], &i);
3278 if( i > 0 ){
3279 escape = zPtr[0];
3280 }
3281 }
3282 }
3283 }
3284 }
3285 /* Create our array */
3286 pArray = jx9_context_new_array(pCtx);
3287 if( pArray == 0 ){
3288 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
3289 jx9_result_null(pCtx);
3290 return JX9_OK;
3291 }
3292 /* Parse the raw input */
3293 jx9ProcessCsv(zLine, (int)n, delim, encl, escape, jx9CsvConsumer, pArray);
3294 /* Return the freshly created array */
3295 jx9_result_value(pCtx, pArray);
3296 }
3297 return JX9_OK;
3298}
3299/*
3300 * string fgetss(resource $handle [, int $length [, string $allowable_tags ]])
3301 * Gets line from file pointer and strip HTML tags.
3302 * Parameters
3303 * $handle
3304 * The file pointer.
3305 * $length
3306 * Reading ends when length - 1 bytes have been read, on a newline
3307 * (which is included in the return value), or on EOF (whichever comes first).
3308 * If no length is specified, it will keep reading from the stream until it reaches
3309 * the end of the line.
3310 * $allowable_tags
3311 * You can use the optional second parameter to specify tags which should not be stripped.
3312 * Return
3313 * Returns a string of up to length - 1 bytes read from the file pointed to by
3314 * handle, with all HTML and JX9 code stripped. If an error occurs, returns FALSE.
3315 */
3316static int jx9Builtin_fgetss(jx9_context *pCtx, int nArg, jx9_value **apArg)
3317{
3318 const jx9_io_stream *pStream;
3319 const char *zLine;
3320 io_private *pDev;
3321 jx9_int64 n, nLen;
3322 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3323 /* Missing/Invalid arguments, return FALSE */
3324 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3325 jx9_result_bool(pCtx, 0);
3326 return JX9_OK;
3327 }
3328 /* Extract our private data */
3329 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3330 /* Make sure we are dealing with a valid io_private instance */
3331 if( IO_PRIVATE_INVALID(pDev) ){
3332 /*Expecting an IO handle */
3333 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3334 jx9_result_bool(pCtx, 0);
3335 return JX9_OK;
3336 }
3337 /* Point to the target IO stream device */
3338 pStream = pDev->pStream;
3339 if( pStream == 0 ){
3340 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3341 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3342 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3343 );
3344 jx9_result_bool(pCtx, 0);
3345 return JX9_OK;
3346 }
3347 nLen = -1;
3348 if( nArg > 1 ){
3349 /* Maximum data to read */
3350 nLen = jx9_value_to_int64(apArg[1]);
3351 }
3352 /* Perform the requested operation */
3353 n = StreamReadLine(pDev, &zLine, nLen);
3354 if( n < 1 ){
3355 /* EOF or IO error, return FALSE */
3356 jx9_result_bool(pCtx, 0);
3357 }else{
3358 const char *zTaglist = 0;
3359 int nTaglen = 0;
3360 if( nArg > 2 && jx9_value_is_string(apArg[2]) ){
3361 /* Allowed tag */
3362 zTaglist = jx9_value_to_string(apArg[2], &nTaglen);
3363 }
3364 /* Process data just read */
3365 jx9StripTagsFromString(pCtx, zLine, (int)n, zTaglist, nTaglen);
3366 }
3367 return JX9_OK;
3368}
3369/*
3370 * string readdir(resource $dir_handle)
3371 * Read entry from directory handle.
3372 * Parameter
3373 * $dir_handle
3374 * The directory handle resource previously opened with opendir().
3375 * Return
3376 * Returns the filename on success or FALSE on failure.
3377 */
3378static int jx9Builtin_readdir(jx9_context *pCtx, int nArg, jx9_value **apArg)
3379{
3380 const jx9_io_stream *pStream;
3381 io_private *pDev;
3382 int rc;
3383 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3384 /* Missing/Invalid arguments, return FALSE */
3385 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3386 jx9_result_bool(pCtx, 0);
3387 return JX9_OK;
3388 }
3389 /* Extract our private data */
3390 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3391 /* Make sure we are dealing with a valid io_private instance */
3392 if( IO_PRIVATE_INVALID(pDev) ){
3393 /*Expecting an IO handle */
3394 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3395 jx9_result_bool(pCtx, 0);
3396 return JX9_OK;
3397 }
3398 /* Point to the target IO stream device */
3399 pStream = pDev->pStream;
3400 if( pStream == 0 || pStream->xReadDir == 0 ){
3401 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3402 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3403 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3404 );
3405 jx9_result_bool(pCtx, 0);
3406 return JX9_OK;
3407 }
3408 jx9_result_bool(pCtx, 0);
3409 /* Perform the requested operation */
3410 rc = pStream->xReadDir(pDev->pHandle, pCtx);
3411 if( rc != JX9_OK ){
3412 /* Return FALSE */
3413 jx9_result_bool(pCtx, 0);
3414 }
3415 return JX9_OK;
3416}
3417/*
3418 * void rewinddir(resource $dir_handle)
3419 * Rewind directory handle.
3420 * Parameter
3421 * $dir_handle
3422 * The directory handle resource previously opened with opendir().
3423 * Return
3424 * FALSE on failure.
3425 */
3426static int jx9Builtin_rewinddir(jx9_context *pCtx, int nArg, jx9_value **apArg)
3427{
3428 const jx9_io_stream *pStream;
3429 io_private *pDev;
3430 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3431 /* Missing/Invalid arguments, return FALSE */
3432 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3433 jx9_result_bool(pCtx, 0);
3434 return JX9_OK;
3435 }
3436 /* Extract our private data */
3437 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3438 /* Make sure we are dealing with a valid io_private instance */
3439 if( IO_PRIVATE_INVALID(pDev) ){
3440 /*Expecting an IO handle */
3441 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3442 jx9_result_bool(pCtx, 0);
3443 return JX9_OK;
3444 }
3445 /* Point to the target IO stream device */
3446 pStream = pDev->pStream;
3447 if( pStream == 0 || pStream->xRewindDir == 0 ){
3448 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3449 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3450 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3451 );
3452 jx9_result_bool(pCtx, 0);
3453 return JX9_OK;
3454 }
3455 /* Perform the requested operation */
3456 pStream->xRewindDir(pDev->pHandle);
3457 return JX9_OK;
3458 }
3459/* Forward declaration */
3460static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut);
3461static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev);
3462/*
3463 * void closedir(resource $dir_handle)
3464 * Close directory handle.
3465 * Parameter
3466 * $dir_handle
3467 * The directory handle resource previously opened with opendir().
3468 * Return
3469 * FALSE on failure.
3470 */
3471static int jx9Builtin_closedir(jx9_context *pCtx, int nArg, jx9_value **apArg)
3472{
3473 const jx9_io_stream *pStream;
3474 io_private *pDev;
3475 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
3476 /* Missing/Invalid arguments, return FALSE */
3477 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3478 jx9_result_bool(pCtx, 0);
3479 return JX9_OK;
3480 }
3481 /* Extract our private data */
3482 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
3483 /* Make sure we are dealing with a valid io_private instance */
3484 if( IO_PRIVATE_INVALID(pDev) ){
3485 /*Expecting an IO handle */
3486 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
3487 jx9_result_bool(pCtx, 0);
3488 return JX9_OK;
3489 }
3490 /* Point to the target IO stream device */
3491 pStream = pDev->pStream;
3492 if( pStream == 0 || pStream->xCloseDir == 0 ){
3493 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3494 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
3495 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
3496 );
3497 jx9_result_bool(pCtx, 0);
3498 return JX9_OK;
3499 }
3500 /* Perform the requested operation */
3501 pStream->xCloseDir(pDev->pHandle);
3502 /* Release the private stucture */
3503 ReleaseIOPrivate(pCtx, pDev);
3504 jx9MemObjRelease(apArg[0]);
3505 return JX9_OK;
3506 }
3507/*
3508 * resource opendir(string $path[, resource $context])
3509 * Open directory handle.
3510 * Parameters
3511 * $path
3512 * The directory path that is to be opened.
3513 * $context
3514 * A context stream resource.
3515 * Return
3516 * A directory handle resource on success, or FALSE on failure.
3517 */
3518static int jx9Builtin_opendir(jx9_context *pCtx, int nArg, jx9_value **apArg)
3519{
3520 const jx9_io_stream *pStream;
3521 const char *zPath;
3522 io_private *pDev;
3523 int iLen, rc;
3524 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
3525 /* Missing/Invalid arguments, return FALSE */
3526 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a directory path");
3527 jx9_result_bool(pCtx, 0);
3528 return JX9_OK;
3529 }
3530 /* Extract the target path */
3531 zPath = jx9_value_to_string(apArg[0], &iLen);
3532 /* Try to extract a stream */
3533 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zPath, iLen);
3534 if( pStream == 0 ){
3535 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3536 "No stream device is associated with the given path(%s)", zPath);
3537 jx9_result_bool(pCtx, 0);
3538 return JX9_OK;
3539 }
3540 if( pStream->xOpenDir == 0 ){
3541 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
3542 "IO routine(%s) not implemented in the underlying stream(%s) device",
3543 jx9_function_name(pCtx), pStream->zName
3544 );
3545 jx9_result_bool(pCtx, 0);
3546 return JX9_OK;
3547 }
3548 /* Allocate a new IO private instance */
3549 pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE);
3550 if( pDev == 0 ){
3551 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
3552 jx9_result_bool(pCtx, 0);
3553 return JX9_OK;
3554 }
3555 /* Initialize the structure */
3556 InitIOPrivate(pCtx->pVm, pStream, pDev);
3557 /* Open the target directory */
3558 rc = pStream->xOpenDir(zPath, nArg > 1 ? apArg[1] : 0, &pDev->pHandle);
3559 if( rc != JX9_OK ){
3560 /* IO error, return FALSE */
3561 ReleaseIOPrivate(pCtx, pDev);
3562 jx9_result_bool(pCtx, 0);
3563 }else{
3564 /* Return the handle as a resource */
3565 jx9_result_resource(pCtx, pDev);
3566 }
3567 return JX9_OK;
3568}
3569/*
3570 * int readfile(string $filename[, bool $use_include_path = false [, resource $context ]])
3571 * Reads a file and writes it to the output buffer.
3572 * Parameters
3573 * $filename
3574 * The filename being read.
3575 * $use_include_path
3576 * You can use the optional second parameter and set it to
3577 * TRUE, if you want to search for the file in the include_path, too.
3578 * $context
3579 * A context stream resource.
3580 * Return
3581 * The number of bytes read from the file on success or FALSE on failure.
3582 */
3583static int jx9Builtin_readfile(jx9_context *pCtx, int nArg, jx9_value **apArg)
3584{
3585 int use_include = FALSE;
3586 const jx9_io_stream *pStream;
3587 jx9_int64 n, nRead;
3588 const char *zFile;
3589 char zBuf[8192];
3590 void *pHandle;
3591 int rc, nLen;
3592 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
3593 /* Missing/Invalid arguments, return FALSE */
3594 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
3595 jx9_result_bool(pCtx, 0);
3596 return JX9_OK;
3597 }
3598 /* Extract the file path */
3599 zFile = jx9_value_to_string(apArg[0], &nLen);
3600 /* Point to the target IO stream device */
3601 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
3602 if( pStream == 0 ){
3603 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
3604 jx9_result_bool(pCtx, 0);
3605 return JX9_OK;
3606 }
3607 if( nArg > 1 ){
3608 use_include = jx9_value_to_bool(apArg[1]);
3609 }
3610 /* Try to open the file in read-only mode */
3611 pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY,
3612 use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0);
3613 if( pHandle == 0 ){
3614 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
3615 jx9_result_bool(pCtx, 0);
3616 return JX9_OK;
3617 }
3618 /* Perform the requested operation */
3619 nRead = 0;
3620 for(;;){
3621 n = pStream->xRead(pHandle, zBuf, sizeof(zBuf));
3622 if( n < 1 ){
3623 /* EOF or IO error, break immediately */
3624 break;
3625 }
3626 /* Output data */
3627 rc = jx9_context_output(pCtx, zBuf, (int)n);
3628 if( rc == JX9_ABORT ){
3629 break;
3630 }
3631 /* Increment counter */
3632 nRead += n;
3633 }
3634 /* Close the stream */
3635 jx9StreamCloseHandle(pStream, pHandle);
3636 /* Total number of bytes readen */
3637 jx9_result_int64(pCtx, nRead);
3638 return JX9_OK;
3639}
3640/*
3641 * string file_get_contents(string $filename[, bool $use_include_path = false
3642 * [, resource $context [, int $offset = -1 [, int $maxlen ]]]])
3643 * Reads entire file into a string.
3644 * Parameters
3645 * $filename
3646 * The filename being read.
3647 * $use_include_path
3648 * You can use the optional second parameter and set it to
3649 * TRUE, if you want to search for the file in the include_path, too.
3650 * $context
3651 * A context stream resource.
3652 * $offset
3653 * The offset where the reading starts on the original stream.
3654 * $maxlen
3655 * Maximum length of data read. The default is to read until end of file
3656 * is reached. Note that this parameter is applied to the stream processed by the filters.
3657 * Return
3658 * The function returns the read data or FALSE on failure.
3659 */
3660static int jx9Builtin_file_get_contents(jx9_context *pCtx, int nArg, jx9_value **apArg)
3661{
3662 const jx9_io_stream *pStream;
3663 jx9_int64 n, nRead, nMaxlen;
3664 int use_include = FALSE;
3665 const char *zFile;
3666 char zBuf[8192];
3667 void *pHandle;
3668 int nLen;
3669
3670 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
3671 /* Missing/Invalid arguments, return FALSE */
3672 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
3673 jx9_result_bool(pCtx, 0);
3674 return JX9_OK;
3675 }
3676 /* Extract the file path */
3677 zFile = jx9_value_to_string(apArg[0], &nLen);
3678 /* Point to the target IO stream device */
3679 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
3680 if( pStream == 0 ){
3681 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
3682 jx9_result_bool(pCtx, 0);
3683 return JX9_OK;
3684 }
3685 nMaxlen = -1;
3686 if( nArg > 1 ){
3687 use_include = jx9_value_to_bool(apArg[1]);
3688 }
3689 /* Try to open the file in read-only mode */
3690 pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0);
3691 if( pHandle == 0 ){
3692 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
3693 jx9_result_bool(pCtx, 0);
3694 return JX9_OK;
3695 }
3696 if( nArg > 3 ){
3697 /* Extract the offset */
3698 n = jx9_value_to_int64(apArg[3]);
3699 if( n > 0 ){
3700 if( pStream->xSeek ){
3701 /* Seek to the desired offset */
3702 pStream->xSeek(pHandle, n, 0/*SEEK_SET*/);
3703 }
3704 }
3705 if( nArg > 4 ){
3706 /* Maximum data to read */
3707 nMaxlen = jx9_value_to_int64(apArg[4]);
3708 }
3709 }
3710 /* Perform the requested operation */
3711 nRead = 0;
3712 for(;;){
3713 n = pStream->xRead(pHandle, zBuf,
3714 (nMaxlen > 0 && (nMaxlen < sizeof(zBuf))) ? nMaxlen : sizeof(zBuf));
3715 if( n < 1 ){
3716 /* EOF or IO error, break immediately */
3717 break;
3718 }
3719 /* Append data */
3720 jx9_result_string(pCtx, zBuf, (int)n);
3721 /* Increment read counter */
3722 nRead += n;
3723 if( nMaxlen > 0 && nRead >= nMaxlen ){
3724 /* Read limit reached */
3725 break;
3726 }
3727 }
3728 /* Close the stream */
3729 jx9StreamCloseHandle(pStream, pHandle);
3730 /* Check if we have read something */
3731 if( jx9_context_result_buf_length(pCtx) < 1 ){
3732 /* Nothing read, return FALSE */
3733 jx9_result_bool(pCtx, 0);
3734 }
3735 return JX9_OK;
3736}
3737/*
3738 * int file_put_contents(string $filename, mixed $data[, int $flags = 0[, resource $context]])
3739 * Write a string to a file.
3740 * Parameters
3741 * $filename
3742 * Path to the file where to write the data.
3743 * $data
3744 * The data to write(Must be a string).
3745 * $flags
3746 * The value of flags can be any combination of the following
3747 * flags, joined with the binary OR (|) operator.
3748 * FILE_USE_INCLUDE_PATH Search for filename in the include directory. See include_path for more information.
3749 * FILE_APPEND If file filename already exists, append the data to the file instead of overwriting it.
3750 * LOCK_EX Acquire an exclusive lock on the file while proceeding to the writing.
3751 * context
3752 * A context stream resource.
3753 * Return
3754 * The function returns the number of bytes that were written to the file, or FALSE on failure.
3755 */
3756static int jx9Builtin_file_put_contents(jx9_context *pCtx, int nArg, jx9_value **apArg)
3757{
3758 int use_include = FALSE;
3759 const jx9_io_stream *pStream;
3760 const char *zFile;
3761 const char *zData;
3762 int iOpenFlags;
3763 void *pHandle;
3764 int iFlags;
3765 int nLen;
3766
3767 if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){
3768 /* Missing/Invalid arguments, return FALSE */
3769 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
3770 jx9_result_bool(pCtx, 0);
3771 return JX9_OK;
3772 }
3773 /* Extract the file path */
3774 zFile = jx9_value_to_string(apArg[0], &nLen);
3775 /* Point to the target IO stream device */
3776 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
3777 if( pStream == 0 ){
3778 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
3779 jx9_result_bool(pCtx, 0);
3780 return JX9_OK;
3781 }
3782 /* Data to write */
3783 zData = jx9_value_to_string(apArg[1], &nLen);
3784 if( nLen < 1 ){
3785 /* Nothing to write, return immediately */
3786 jx9_result_bool(pCtx, 0);
3787 return JX9_OK;
3788 }
3789 /* Try to open the file in read-write mode */
3790 iOpenFlags = JX9_IO_OPEN_CREATE|JX9_IO_OPEN_RDWR|JX9_IO_OPEN_TRUNC;
3791 /* Extract the flags */
3792 iFlags = 0;
3793 if( nArg > 2 ){
3794 iFlags = jx9_value_to_int(apArg[2]);
3795 if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/){
3796 use_include = TRUE;
3797 }
3798 if( iFlags & 0x08 /* FILE_APPEND */){
3799 /* If the file already exists, append the data to the file
3800 * instead of overwriting it.
3801 */
3802 iOpenFlags &= ~JX9_IO_OPEN_TRUNC;
3803 /* Append mode */
3804 iOpenFlags |= JX9_IO_OPEN_APPEND;
3805 }
3806 }
3807 pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, iOpenFlags, use_include,
3808 nArg > 3 ? apArg[3] : 0, FALSE, FALSE);
3809 if( pHandle == 0 ){
3810 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
3811 jx9_result_bool(pCtx, 0);
3812 return JX9_OK;
3813 }
3814 if( pStream->xWrite ){
3815 jx9_int64 n;
3816 if( (iFlags & 0x01/* LOCK_EX */) && pStream->xLock ){
3817 /* Try to acquire an exclusive lock */
3818 pStream->xLock(pHandle, 1/* LOCK_EX */);
3819 }
3820 /* Perform the write operation */
3821 n = pStream->xWrite(pHandle, (const void *)zData, nLen);
3822 if( n < 1 ){
3823 /* IO error, return FALSE */
3824 jx9_result_bool(pCtx, 0);
3825 }else{
3826 /* Total number of bytes written */
3827 jx9_result_int64(pCtx, n);
3828 }
3829 }else{
3830 /* Read-only stream */
3831 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR,
3832 "Read-only stream(%s): Cannot perform write operation",
3833 pStream ? pStream->zName : "null_stream"
3834 );
3835 jx9_result_bool(pCtx, 0);
3836 }
3837 /* Close the handle */
3838 jx9StreamCloseHandle(pStream, pHandle);
3839 return JX9_OK;
3840}
3841/*
3842 * array file(string $filename[, int $flags = 0[, resource $context]])
3843 * Reads entire file into an array.
3844 * Parameters
3845 * $filename
3846 * The filename being read.
3847 * $flags
3848 * The optional parameter flags can be one, or more, of the following constants:
3849 * FILE_USE_INCLUDE_PATH
3850 * Search for the file in the include_path.
3851 * FILE_IGNORE_NEW_LINES
3852 * Do not add newline at the end of each array element
3853 * FILE_SKIP_EMPTY_LINES
3854 * Skip empty lines
3855 * $context
3856 * A context stream resource.
3857 * Return
3858 * The function returns the read data or FALSE on failure.
3859 */
3860static int jx9Builtin_file(jx9_context *pCtx, int nArg, jx9_value **apArg)
3861{
3862 const char *zFile, *zPtr, *zEnd, *zBuf;
3863 jx9_value *pArray, *pLine;
3864 const jx9_io_stream *pStream;
3865 int use_include = 0;
3866 io_private *pDev;
3867 jx9_int64 n;
3868 int iFlags;
3869 int nLen;
3870
3871 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
3872 /* Missing/Invalid arguments, return FALSE */
3873 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
3874 jx9_result_bool(pCtx, 0);
3875 return JX9_OK;
3876 }
3877 /* Extract the file path */
3878 zFile = jx9_value_to_string(apArg[0], &nLen);
3879 /* Point to the target IO stream device */
3880 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
3881 if( pStream == 0 ){
3882 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
3883 jx9_result_bool(pCtx, 0);
3884 return JX9_OK;
3885 }
3886 /* Allocate a new IO private instance */
3887 pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE);
3888 if( pDev == 0 ){
3889 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
3890 jx9_result_bool(pCtx, 0);
3891 return JX9_OK;
3892 }
3893 /* Initialize the structure */
3894 InitIOPrivate(pCtx->pVm, pStream, pDev);
3895 iFlags = 0;
3896 if( nArg > 1 ){
3897 iFlags = jx9_value_to_int(apArg[1]);
3898 }
3899 if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/ ){
3900 use_include = TRUE;
3901 }
3902 /* Create the array and the working value */
3903 pArray = jx9_context_new_array(pCtx);
3904 pLine = jx9_context_new_scalar(pCtx);
3905 if( pArray == 0 || pLine == 0 ){
3906 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
3907 jx9_result_bool(pCtx, 0);
3908 return JX9_OK;
3909 }
3910 /* Try to open the file in read-only mode */
3911 pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0);
3912 if( pDev->pHandle == 0 ){
3913 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
3914 jx9_result_bool(pCtx, 0);
3915 /* Don't worry about freeing memory, everything will be released automatically
3916 * as soon we return from this function.
3917 */
3918 return JX9_OK;
3919 }
3920 /* Perform the requested operation */
3921 for(;;){
3922 /* Try to extract a line */
3923 n = StreamReadLine(pDev, &zBuf, -1);
3924 if( n < 1 ){
3925 /* EOF or IO error */
3926 break;
3927 }
3928 /* Reset the cursor */
3929 jx9_value_reset_string_cursor(pLine);
3930 /* Remove line ending if requested by the caller */
3931 zPtr = zBuf;
3932 zEnd = &zBuf[n];
3933 if( iFlags & 0x02 /* FILE_IGNORE_NEW_LINES */ ){
3934 /* Ignore trailig lines */
3935 while( zPtr < zEnd && (zEnd[-1] == '\n'
3936#ifdef __WINNT__
3937 || zEnd[-1] == '\r'
3938#endif
3939 )){
3940 n--;
3941 zEnd--;
3942 }
3943 }
3944 if( iFlags & 0x04 /* FILE_SKIP_EMPTY_LINES */ ){
3945 /* Ignore empty lines */
3946 while( zPtr < zEnd && (unsigned char)zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) ){
3947 zPtr++;
3948 }
3949 if( zPtr >= zEnd ){
3950 /* Empty line */
3951 continue;
3952 }
3953 }
3954 jx9_value_string(pLine, zBuf, (int)(zEnd-zBuf));
3955 /* Insert line */
3956 jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pLine);
3957 }
3958 /* Close the stream */
3959 jx9StreamCloseHandle(pStream, pDev->pHandle);
3960 /* Release the io_private instance */
3961 ReleaseIOPrivate(pCtx, pDev);
3962 /* Return the created array */
3963 jx9_result_value(pCtx, pArray);
3964 return JX9_OK;
3965}
3966/*
3967 * bool copy(string $source, string $dest[, resource $context ] )
3968 * Makes a copy of the file source to dest.
3969 * Parameters
3970 * $source
3971 * Path to the source file.
3972 * $dest
3973 * The destination path. If dest is a URL, the copy operation
3974 * may fail if the wrapper does not support overwriting of existing files.
3975 * $context
3976 * A context stream resource.
3977 * Return
3978 * TRUE on success or FALSE on failure.
3979 */
3980static int jx9Builtin_copy(jx9_context *pCtx, int nArg, jx9_value **apArg)
3981{
3982 const jx9_io_stream *pSin, *pSout;
3983 const char *zFile;
3984 char zBuf[8192];
3985 void *pIn, *pOut;
3986 jx9_int64 n;
3987 int nLen;
3988 if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1])){
3989 /* Missing/Invalid arguments, return FALSE */
3990 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a source and a destination path");
3991 jx9_result_bool(pCtx, 0);
3992 return JX9_OK;
3993 }
3994 /* Extract the source name */
3995 zFile = jx9_value_to_string(apArg[0], &nLen);
3996 /* Point to the target IO stream device */
3997 pSin = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
3998 if( pSin == 0 ){
3999 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
4000 jx9_result_bool(pCtx, 0);
4001 return JX9_OK;
4002 }
4003 /* Try to open the source file in a read-only mode */
4004 pIn = jx9StreamOpenHandle(pCtx->pVm, pSin, zFile, JX9_IO_OPEN_RDONLY, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0);
4005 if( pIn == 0 ){
4006 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening source: '%s'", zFile);
4007 jx9_result_bool(pCtx, 0);
4008 return JX9_OK;
4009 }
4010 /* Extract the destination name */
4011 zFile = jx9_value_to_string(apArg[1], &nLen);
4012 /* Point to the target IO stream device */
4013 pSout = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
4014 if( pSout == 0 ){
4015 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
4016 jx9_result_bool(pCtx, 0);
4017 jx9StreamCloseHandle(pSin, pIn);
4018 return JX9_OK;
4019 }
4020 if( pSout->xWrite == 0 ){
4021 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4022 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
4023 jx9_function_name(pCtx), pSin->zName
4024 );
4025 jx9_result_bool(pCtx, 0);
4026 jx9StreamCloseHandle(pSin, pIn);
4027 return JX9_OK;
4028 }
4029 /* Try to open the destination file in a read-write mode */
4030 pOut = jx9StreamOpenHandle(pCtx->pVm, pSout, zFile,
4031 JX9_IO_OPEN_CREATE|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_RDWR, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0);
4032 if( pOut == 0 ){
4033 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening destination: '%s'", zFile);
4034 jx9_result_bool(pCtx, 0);
4035 jx9StreamCloseHandle(pSin, pIn);
4036 return JX9_OK;
4037 }
4038 /* Perform the requested operation */
4039 for(;;){
4040 /* Read from source */
4041 n = pSin->xRead(pIn, zBuf, sizeof(zBuf));
4042 if( n < 1 ){
4043 /* EOF or IO error, break immediately */
4044 break;
4045 }
4046 /* Write to dest */
4047 n = pSout->xWrite(pOut, zBuf, n);
4048 if( n < 1 ){
4049 /* IO error, break immediately */
4050 break;
4051 }
4052 }
4053 /* Close the streams */
4054 jx9StreamCloseHandle(pSin, pIn);
4055 jx9StreamCloseHandle(pSout, pOut);
4056 /* Return TRUE */
4057 jx9_result_bool(pCtx, 1);
4058 return JX9_OK;
4059}
4060/*
4061 * array fstat(resource $handle)
4062 * Gets information about a file using an open file pointer.
4063 * Parameters
4064 * $handle
4065 * The file pointer.
4066 * Return
4067 * Returns an array with the statistics of the file or FALSE on failure.
4068 */
4069static int jx9Builtin_fstat(jx9_context *pCtx, int nArg, jx9_value **apArg)
4070{
4071 jx9_value *pArray, *pValue;
4072 const jx9_io_stream *pStream;
4073 io_private *pDev;
4074 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
4075 /* Missing/Invalid arguments, return FALSE */
4076 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4077 jx9_result_bool(pCtx, 0);
4078 return JX9_OK;
4079 }
4080 /* Extract our private data */
4081 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4082 /* Make sure we are dealing with a valid io_private instance */
4083 if( IO_PRIVATE_INVALID(pDev) ){
4084 /* Expecting an IO handle */
4085 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4086 jx9_result_bool(pCtx, 0);
4087 return JX9_OK;
4088 }
4089 /* Point to the target IO stream device */
4090 pStream = pDev->pStream;
4091 if( pStream == 0 || pStream->xStat == 0){
4092 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4093 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
4094 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
4095 );
4096 jx9_result_bool(pCtx, 0);
4097 return JX9_OK;
4098 }
4099 /* Create the array and the working value */
4100 pArray = jx9_context_new_array(pCtx);
4101 pValue = jx9_context_new_scalar(pCtx);
4102 if( pArray == 0 || pValue == 0 ){
4103 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
4104 jx9_result_bool(pCtx, 0);
4105 return JX9_OK;
4106 }
4107 /* Perform the requested operation */
4108 pStream->xStat(pDev->pHandle, pArray, pValue);
4109 /* Return the freshly created array */
4110 jx9_result_value(pCtx, pArray);
4111 /* Don't worry about freeing memory here, everything will be
4112 * released automatically as soon we return from this function.
4113 */
4114 return JX9_OK;
4115}
4116/*
4117 * int fwrite(resource $handle, string $string[, int $length])
4118 * Writes the contents of string to the file stream pointed to by handle.
4119 * Parameters
4120 * $handle
4121 * The file pointer.
4122 * $string
4123 * The string that is to be written.
4124 * $length
4125 * If the length argument is given, writing will stop after length bytes have been written
4126 * or the end of string is reached, whichever comes first.
4127 * Return
4128 * Returns the number of bytes written, or FALSE on error.
4129 */
4130static int jx9Builtin_fwrite(jx9_context *pCtx, int nArg, jx9_value **apArg)
4131{
4132 const jx9_io_stream *pStream;
4133 const char *zString;
4134 io_private *pDev;
4135 int nLen, n;
4136 if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){
4137 /* Missing/Invalid arguments, return FALSE */
4138 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4139 jx9_result_bool(pCtx, 0);
4140 return JX9_OK;
4141 }
4142 /* Extract our private data */
4143 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4144 /* Make sure we are dealing with a valid io_private instance */
4145 if( IO_PRIVATE_INVALID(pDev) ){
4146 /* Expecting an IO handle */
4147 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4148 jx9_result_bool(pCtx, 0);
4149 return JX9_OK;
4150 }
4151 /* Point to the target IO stream device */
4152 pStream = pDev->pStream;
4153 if( pStream == 0 || pStream->xWrite == 0){
4154 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4155 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
4156 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
4157 );
4158 jx9_result_bool(pCtx, 0);
4159 return JX9_OK;
4160 }
4161 /* Extract the data to write */
4162 zString = jx9_value_to_string(apArg[1], &nLen);
4163 if( nArg > 2 ){
4164 /* Maximum data length to write */
4165 n = jx9_value_to_int(apArg[2]);
4166 if( n >= 0 && n < nLen ){
4167 nLen = n;
4168 }
4169 }
4170 if( nLen < 1 ){
4171 /* Nothing to write */
4172 jx9_result_int(pCtx, 0);
4173 return JX9_OK;
4174 }
4175 /* Perform the requested operation */
4176 n = (int)pStream->xWrite(pDev->pHandle, (const void *)zString, nLen);
4177 if( n < 0 ){
4178 /* IO error, return FALSE */
4179 jx9_result_bool(pCtx, 0);
4180 }else{
4181 /* #Bytes written */
4182 jx9_result_int(pCtx, n);
4183 }
4184 return JX9_OK;
4185}
4186/*
4187 * bool flock(resource $handle, int $operation)
4188 * Portable advisory file locking.
4189 * Parameters
4190 * $handle
4191 * The file pointer.
4192 * $operation
4193 * operation is one of the following:
4194 * LOCK_SH to acquire a shared lock (reader).
4195 * LOCK_EX to acquire an exclusive lock (writer).
4196 * LOCK_UN to release a lock (shared or exclusive).
4197 * Return
4198 * Returns TRUE on success or FALSE on failure.
4199 */
4200static int jx9Builtin_flock(jx9_context *pCtx, int nArg, jx9_value **apArg)
4201{
4202 const jx9_io_stream *pStream;
4203 io_private *pDev;
4204 int nLock;
4205 int rc;
4206 if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){
4207 /* Missing/Invalid arguments, return FALSE */
4208 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4209 jx9_result_bool(pCtx, 0);
4210 return JX9_OK;
4211 }
4212 /* Extract our private data */
4213 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4214 /* Make sure we are dealing with a valid io_private instance */
4215 if( IO_PRIVATE_INVALID(pDev) ){
4216 /*Expecting an IO handle */
4217 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4218 jx9_result_bool(pCtx, 0);
4219 return JX9_OK;
4220 }
4221 /* Point to the target IO stream device */
4222 pStream = pDev->pStream;
4223 if( pStream == 0 || pStream->xLock == 0){
4224 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4225 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
4226 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
4227 );
4228 jx9_result_bool(pCtx, 0);
4229 return JX9_OK;
4230 }
4231 /* Requested lock operation */
4232 nLock = jx9_value_to_int(apArg[1]);
4233 /* Lock operation */
4234 rc = pStream->xLock(pDev->pHandle, nLock);
4235 /* IO result */
4236 jx9_result_bool(pCtx, rc == JX9_OK);
4237 return JX9_OK;
4238}
4239/*
4240 * int fpassthru(resource $handle)
4241 * Output all remaining data on a file pointer.
4242 * Parameters
4243 * $handle
4244 * The file pointer.
4245 * Return
4246 * Total number of characters read from handle and passed through
4247 * to the output on success or FALSE on failure.
4248 */
4249static int jx9Builtin_fpassthru(jx9_context *pCtx, int nArg, jx9_value **apArg)
4250{
4251 const jx9_io_stream *pStream;
4252 io_private *pDev;
4253 jx9_int64 n, nRead;
4254 char zBuf[8192];
4255 int rc;
4256 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
4257 /* Missing/Invalid arguments, return FALSE */
4258 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4259 jx9_result_bool(pCtx, 0);
4260 return JX9_OK;
4261 }
4262 /* Extract our private data */
4263 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4264 /* Make sure we are dealing with a valid io_private instance */
4265 if( IO_PRIVATE_INVALID(pDev) ){
4266 /*Expecting an IO handle */
4267 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4268 jx9_result_bool(pCtx, 0);
4269 return JX9_OK;
4270 }
4271 /* Point to the target IO stream device */
4272 pStream = pDev->pStream;
4273 if( pStream == 0 ){
4274 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4275 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
4276 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
4277 );
4278 jx9_result_bool(pCtx, 0);
4279 return JX9_OK;
4280 }
4281 /* Perform the requested operation */
4282 nRead = 0;
4283 for(;;){
4284 n = StreamRead(pDev, zBuf, sizeof(zBuf));
4285 if( n < 1 ){
4286 /* Error or EOF */
4287 break;
4288 }
4289 /* Increment the read counter */
4290 nRead += n;
4291 /* Output data */
4292 rc = jx9_context_output(pCtx, zBuf, (int)nRead /* FIXME: 64-bit issues */);
4293 if( rc == JX9_ABORT ){
4294 /* Consumer callback request an operation abort */
4295 break;
4296 }
4297 }
4298 /* Total number of bytes readen */
4299 jx9_result_int64(pCtx, nRead);
4300 return JX9_OK;
4301}
4302/* CSV reader/writer private data */
4303struct csv_data
4304{
4305 int delimiter; /* Delimiter. Default ', ' */
4306 int enclosure; /* Enclosure. Default '"'*/
4307 io_private *pDev; /* Open stream handle */
4308 int iCount; /* Counter */
4309};
4310/*
4311 * The following callback is used by the fputcsv() function inorder to iterate
4312 * throw array entries and output CSV data based on the current key and it's
4313 * associated data.
4314 */
4315static int csv_write_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData)
4316{
4317 struct csv_data *pData = (struct csv_data *)pUserData;
4318 const char *zData;
4319 int nLen, c2;
4320 sxu32 n;
4321 /* Point to the raw data */
4322 zData = jx9_value_to_string(pValue, &nLen);
4323 if( nLen < 1 ){
4324 /* Nothing to write */
4325 return JX9_OK;
4326 }
4327 if( pData->iCount > 0 ){
4328 /* Write the delimiter */
4329 pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->delimiter, sizeof(char));
4330 }
4331 n = 1;
4332 c2 = 0;
4333 if( SyByteFind(zData, (sxu32)nLen, pData->delimiter, 0) == SXRET_OK ||
4334 SyByteFind(zData, (sxu32)nLen, pData->enclosure, &n) == SXRET_OK ){
4335 c2 = 1;
4336 if( n == 0 ){
4337 c2 = 2;
4338 }
4339 /* Write the enclosure */
4340 pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char));
4341 if( c2 > 1 ){
4342 pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char));
4343 }
4344 }
4345 /* Write the data */
4346 if( pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)zData, (jx9_int64)nLen) < 1 ){
4347 SXUNUSED(pKey); /* cc warning */
4348 return JX9_ABORT;
4349 }
4350 if( c2 > 0 ){
4351 /* Write the enclosure */
4352 pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char));
4353 if( c2 > 1 ){
4354 pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char));
4355 }
4356 }
4357 pData->iCount++;
4358 return JX9_OK;
4359}
4360/*
4361 * int fputcsv(resource $handle, array $fields[, string $delimiter = ', '[, string $enclosure = '"' ]])
4362 * Format line as CSV and write to file pointer.
4363 * Parameters
4364 * $handle
4365 * Open file handle.
4366 * $fields
4367 * An array of values.
4368 * $delimiter
4369 * The optional delimiter parameter sets the field delimiter (one character only).
4370 * $enclosure
4371 * The optional enclosure parameter sets the field enclosure (one character only).
4372 */
4373static int jx9Builtin_fputcsv(jx9_context *pCtx, int nArg, jx9_value **apArg)
4374{
4375 const jx9_io_stream *pStream;
4376 struct csv_data sCsv;
4377 io_private *pDev;
4378 char *zEol;
4379 int eolen;
4380 if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){
4381 /* Missing/Invalid arguments, return FALSE */
4382 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Missing/Invalid arguments");
4383 jx9_result_bool(pCtx, 0);
4384 return JX9_OK;
4385 }
4386 /* Extract our private data */
4387 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4388 /* Make sure we are dealing with a valid io_private instance */
4389 if( IO_PRIVATE_INVALID(pDev) ){
4390 /*Expecting an IO handle */
4391 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4392 jx9_result_bool(pCtx, 0);
4393 return JX9_OK;
4394 }
4395 /* Point to the target IO stream device */
4396 pStream = pDev->pStream;
4397 if( pStream == 0 || pStream->xWrite == 0){
4398 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4399 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
4400 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
4401 );
4402 jx9_result_bool(pCtx, 0);
4403 return JX9_OK;
4404 }
4405 /* Set default csv separator */
4406 sCsv.delimiter = ',';
4407 sCsv.enclosure = '"';
4408 sCsv.pDev = pDev;
4409 sCsv.iCount = 0;
4410 if( nArg > 2 ){
4411 /* User delimiter */
4412 const char *z;
4413 int n;
4414 z = jx9_value_to_string(apArg[2], &n);
4415 if( n > 0 ){
4416 sCsv.delimiter = z[0];
4417 }
4418 if( nArg > 3 ){
4419 z = jx9_value_to_string(apArg[3], &n);
4420 if( n > 0 ){
4421 sCsv.enclosure = z[0];
4422 }
4423 }
4424 }
4425 /* Iterate throw array entries and write csv data */
4426 jx9_array_walk(apArg[1], csv_write_callback, &sCsv);
4427 /* Write a line ending */
4428#ifdef __WINNT__
4429 zEol = "\r\n";
4430 eolen = (int)sizeof("\r\n")-1;
4431#else
4432 /* Assume UNIX LF */
4433 zEol = "\n";
4434 eolen = (int)sizeof(char);
4435#endif
4436 pDev->pStream->xWrite(pDev->pHandle, (const void *)zEol, eolen);
4437 return JX9_OK;
4438}
4439/*
4440 * fprintf, vfprintf private data.
4441 * An instance of the following structure is passed to the formatted
4442 * input consumer callback defined below.
4443 */
4444typedef struct fprintf_data fprintf_data;
4445struct fprintf_data
4446{
4447 io_private *pIO; /* IO stream */
4448 jx9_int64 nCount; /* Total number of bytes written */
4449};
4450/*
4451 * Callback [i.e: Formatted input consumer] for the fprintf function.
4452 */
4453static int fprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData)
4454{
4455 fprintf_data *pFdata = (fprintf_data *)pUserData;
4456 jx9_int64 n;
4457 /* Write the formatted data */
4458 n = pFdata->pIO->pStream->xWrite(pFdata->pIO->pHandle, (const void *)zInput, nLen);
4459 if( n < 1 ){
4460 SXUNUSED(pCtx); /* cc warning */
4461 /* IO error, abort immediately */
4462 return SXERR_ABORT;
4463 }
4464 /* Increment counter */
4465 pFdata->nCount += n;
4466 return JX9_OK;
4467}
4468/*
4469 * int fprintf(resource $handle, string $format[, mixed $args [, mixed $... ]])
4470 * Write a formatted string to a stream.
4471 * Parameters
4472 * $handle
4473 * The file pointer.
4474 * $format
4475 * String format (see sprintf()).
4476 * Return
4477 * The length of the written string.
4478 */
4479static int jx9Builtin_fprintf(jx9_context *pCtx, int nArg, jx9_value **apArg)
4480{
4481 fprintf_data sFdata;
4482 const char *zFormat;
4483 io_private *pDev;
4484 int nLen;
4485 if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) ){
4486 /* Missing/Invalid arguments, return zero */
4487 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments");
4488 jx9_result_int(pCtx, 0);
4489 return JX9_OK;
4490 }
4491 /* Extract our private data */
4492 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4493 /* Make sure we are dealing with a valid io_private instance */
4494 if( IO_PRIVATE_INVALID(pDev) ){
4495 /*Expecting an IO handle */
4496 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4497 jx9_result_int(pCtx, 0);
4498 return JX9_OK;
4499 }
4500 /* Point to the target IO stream device */
4501 if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){
4502 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4503 "IO routine(%s) not implemented in the underlying stream(%s) device",
4504 jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream"
4505 );
4506 jx9_result_int(pCtx, 0);
4507 return JX9_OK;
4508 }
4509 /* Extract the string format */
4510 zFormat = jx9_value_to_string(apArg[1], &nLen);
4511 if( nLen < 1 ){
4512 /* Empty string, return zero */
4513 jx9_result_int(pCtx, 0);
4514 return JX9_OK;
4515 }
4516 /* Prepare our private data */
4517 sFdata.nCount = 0;
4518 sFdata.pIO = pDev;
4519 /* Format the string */
4520 jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, nArg - 1, &apArg[1], (void *)&sFdata, FALSE);
4521 /* Return total number of bytes written */
4522 jx9_result_int64(pCtx, sFdata.nCount);
4523 return JX9_OK;
4524}
4525/*
4526 * int vfprintf(resource $handle, string $format, array $args)
4527 * Write a formatted string to a stream.
4528 * Parameters
4529 * $handle
4530 * The file pointer.
4531 * $format
4532 * String format (see sprintf()).
4533 * $args
4534 * User arguments.
4535 * Return
4536 * The length of the written string.
4537 */
4538static int jx9Builtin_vfprintf(jx9_context *pCtx, int nArg, jx9_value **apArg)
4539{
4540 fprintf_data sFdata;
4541 const char *zFormat;
4542 jx9_hashmap *pMap;
4543 io_private *pDev;
4544 SySet sArg;
4545 int n, nLen;
4546 if( nArg < 3 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) || !jx9_value_is_json_array(apArg[2]) ){
4547 /* Missing/Invalid arguments, return zero */
4548 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments");
4549 jx9_result_int(pCtx, 0);
4550 return JX9_OK;
4551 }
4552 /* Extract our private data */
4553 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4554 /* Make sure we are dealing with a valid io_private instance */
4555 if( IO_PRIVATE_INVALID(pDev) ){
4556 /*Expecting an IO handle */
4557 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4558 jx9_result_int(pCtx, 0);
4559 return JX9_OK;
4560 }
4561 /* Point to the target IO stream device */
4562 if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){
4563 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4564 "IO routine(%s) not implemented in the underlying stream(%s) device",
4565 jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream"
4566 );
4567 jx9_result_int(pCtx, 0);
4568 return JX9_OK;
4569 }
4570 /* Extract the string format */
4571 zFormat = jx9_value_to_string(apArg[1], &nLen);
4572 if( nLen < 1 ){
4573 /* Empty string, return zero */
4574 jx9_result_int(pCtx, 0);
4575 return JX9_OK;
4576 }
4577 /* Point to hashmap */
4578 pMap = (jx9_hashmap *)apArg[2]->x.pOther;
4579 /* Extract arguments from the hashmap */
4580 n = jx9HashmapValuesToSet(pMap, &sArg);
4581 /* Prepare our private data */
4582 sFdata.nCount = 0;
4583 sFdata.pIO = pDev;
4584 /* Format the string */
4585 jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&sFdata, TRUE);
4586 /* Return total number of bytes written*/
4587 jx9_result_int64(pCtx, sFdata.nCount);
4588 SySetRelease(&sArg);
4589 return JX9_OK;
4590}
4591/*
4592 * Convert open modes (string passed to the fopen() function) [i.e: 'r', 'w+', 'a', ...] into JX9 flags.
4593 * According to the JX9 reference manual:
4594 * The mode parameter specifies the type of access you require to the stream. It may be any of the following
4595 * 'r' Open for reading only; place the file pointer at the beginning of the file.
4596 * 'r+' Open for reading and writing; place the file pointer at the beginning of the file.
4597 * 'w' Open for writing only; place the file pointer at the beginning of the file and truncate the file
4598 * to zero length. If the file does not exist, attempt to create it.
4599 * 'w+' Open for reading and writing; place the file pointer at the beginning of the file and truncate
4600 * the file to zero length. If the file does not exist, attempt to create it.
4601 * 'a' Open for writing only; place the file pointer at the end of the file. If the file does not
4602 * exist, attempt to create it.
4603 * 'a+' Open for reading and writing; place the file pointer at the end of the file. If the file does
4604 * not exist, attempt to create it.
4605 * 'x' Create and open for writing only; place the file pointer at the beginning of the file. If the file
4606 * already exists,
4607 * the fopen() call will fail by returning FALSE and generating an error of level E_WARNING. If the file
4608 * does not exist attempt to create it. This is equivalent to specifying O_EXCL|O_CREAT flags for
4609 * the underlying open(2) system call.
4610 * 'x+' Create and open for reading and writing; otherwise it has the same behavior as 'x'.
4611 * 'c' Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated
4612 * (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer
4613 * is positioned on the beginning of the file.
4614 * This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file
4615 * as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can
4616 * be used after the lock is requested).
4617 * 'c+' Open the file for reading and writing; otherwise it has the same behavior as 'c'.
4618 */
4619static int StrModeToFlags(jx9_context *pCtx, const char *zMode, int nLen)
4620{
4621 const char *zEnd = &zMode[nLen];
4622 int iFlag = 0;
4623 int c;
4624 if( nLen < 1 ){
4625 /* Open in a read-only mode */
4626 return JX9_IO_OPEN_RDONLY;
4627 }
4628 c = zMode[0];
4629 if( c == 'r' || c == 'R' ){
4630 /* Read-only access */
4631 iFlag = JX9_IO_OPEN_RDONLY;
4632 zMode++; /* Advance */
4633 if( zMode < zEnd ){
4634 c = zMode[0];
4635 if( c == '+' || c == 'w' || c == 'W' ){
4636 /* Read+Write access */
4637 iFlag = JX9_IO_OPEN_RDWR;
4638 }
4639 }
4640 }else if( c == 'w' || c == 'W' ){
4641 /* Overwrite mode.
4642 * If the file does not exists, try to create it
4643 */
4644 iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_CREATE;
4645 zMode++; /* Advance */
4646 if( zMode < zEnd ){
4647 c = zMode[0];
4648 if( c == '+' || c == 'r' || c == 'R' ){
4649 /* Read+Write access */
4650 iFlag &= ~JX9_IO_OPEN_WRONLY;
4651 iFlag |= JX9_IO_OPEN_RDWR;
4652 }
4653 }
4654 }else if( c == 'a' || c == 'A' ){
4655 /* Append mode (place the file pointer at the end of the file).
4656 * Create the file if it does not exists.
4657 */
4658 iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_APPEND|JX9_IO_OPEN_CREATE;
4659 zMode++; /* Advance */
4660 if( zMode < zEnd ){
4661 c = zMode[0];
4662 if( c == '+' ){
4663 /* Read-Write access */
4664 iFlag &= ~JX9_IO_OPEN_WRONLY;
4665 iFlag |= JX9_IO_OPEN_RDWR;
4666 }
4667 }
4668 }else if( c == 'x' || c == 'X' ){
4669 /* Exclusive access.
4670 * If the file already exists, return immediately with a failure code.
4671 * Otherwise create a new file.
4672 */
4673 iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_EXCL;
4674 zMode++; /* Advance */
4675 if( zMode < zEnd ){
4676 c = zMode[0];
4677 if( c == '+' || c == 'r' || c == 'R' ){
4678 /* Read-Write access */
4679 iFlag &= ~JX9_IO_OPEN_WRONLY;
4680 iFlag |= JX9_IO_OPEN_RDWR;
4681 }
4682 }
4683 }else if( c == 'c' || c == 'C' ){
4684 /* Overwrite mode.Create the file if it does not exists.*/
4685 iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_CREATE;
4686 zMode++; /* Advance */
4687 if( zMode < zEnd ){
4688 c = zMode[0];
4689 if( c == '+' ){
4690 /* Read-Write access */
4691 iFlag &= ~JX9_IO_OPEN_WRONLY;
4692 iFlag |= JX9_IO_OPEN_RDWR;
4693 }
4694 }
4695 }else{
4696 /* Invalid mode. Assume a read only open */
4697 jx9_context_throw_error(pCtx, JX9_CTX_NOTICE, "Invalid open mode, JX9 is assuming a Read-Only open");
4698 iFlag = JX9_IO_OPEN_RDONLY;
4699 }
4700 while( zMode < zEnd ){
4701 c = zMode[0];
4702 if( c == 'b' || c == 'B' ){
4703 iFlag &= ~JX9_IO_OPEN_TEXT;
4704 iFlag |= JX9_IO_OPEN_BINARY;
4705 }else if( c == 't' || c == 'T' ){
4706 iFlag &= ~JX9_IO_OPEN_BINARY;
4707 iFlag |= JX9_IO_OPEN_TEXT;
4708 }
4709 zMode++;
4710 }
4711 return iFlag;
4712}
4713/*
4714 * Initialize the IO private structure.
4715 */
4716static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut)
4717{
4718 pOut->pStream = pStream;
4719 SyBlobInit(&pOut->sBuffer, &pVm->sAllocator);
4720 pOut->nOfft = 0;
4721 /* Set the magic number */
4722 pOut->iMagic = IO_PRIVATE_MAGIC;
4723}
4724/*
4725 * Release the IO private structure.
4726 */
4727static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev)
4728{
4729 SyBlobRelease(&pDev->sBuffer);
4730 pDev->iMagic = 0x2126; /* Invalid magic number so we can detetct misuse */
4731 /* Release the whole structure */
4732 jx9_context_free_chunk(pCtx, pDev);
4733}
4734/*
4735 * Reset the IO private structure.
4736 */
4737static void ResetIOPrivate(io_private *pDev)
4738{
4739 SyBlobReset(&pDev->sBuffer);
4740 pDev->nOfft = 0;
4741}
4742/* Forward declaration */
4743static int is_jx9_stream(const jx9_io_stream *pStream);
4744/*
4745 * resource fopen(string $filename, string $mode [, bool $use_include_path = false[, resource $context ]])
4746 * Open a file, a URL or any other IO stream.
4747 * Parameters
4748 * $filename
4749 * If filename is of the form "scheme://...", it is assumed to be a URL and JX9 will search
4750 * for a protocol handler (also known as a wrapper) for that scheme. If no scheme is given
4751 * then a regular file is assumed.
4752 * $mode
4753 * The mode parameter specifies the type of access you require to the stream
4754 * See the block comment associated with the StrModeToFlags() for the supported
4755 * modes.
4756 * $use_include_path
4757 * You can use the optional second parameter and set it to
4758 * TRUE, if you want to search for the file in the include_path, too.
4759 * $context
4760 * A context stream resource.
4761 * Return
4762 * File handle on success or FALSE on failure.
4763 */
4764static int jx9Builtin_fopen(jx9_context *pCtx, int nArg, jx9_value **apArg)
4765{
4766 const jx9_io_stream *pStream;
4767 const char *zUri, *zMode;
4768 jx9_value *pResource;
4769 io_private *pDev;
4770 int iLen, imLen;
4771 int iOpenFlags;
4772 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
4773 /* Missing/Invalid arguments, return FALSE */
4774 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path or URL");
4775 jx9_result_bool(pCtx, 0);
4776 return JX9_OK;
4777 }
4778 /* Extract the URI and the desired access mode */
4779 zUri = jx9_value_to_string(apArg[0], &iLen);
4780 if( nArg > 1 ){
4781 zMode = jx9_value_to_string(apArg[1], &imLen);
4782 }else{
4783 /* Set a default read-only mode */
4784 zMode = "r";
4785 imLen = (int)sizeof(char);
4786 }
4787 /* Try to extract a stream */
4788 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zUri, iLen);
4789 if( pStream == 0 ){
4790 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4791 "No stream device is associated with the given URI(%s)", zUri);
4792 jx9_result_bool(pCtx, 0);
4793 return JX9_OK;
4794 }
4795 /* Allocate a new IO private instance */
4796 pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE);
4797 if( pDev == 0 ){
4798 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
4799 jx9_result_bool(pCtx, 0);
4800 return JX9_OK;
4801 }
4802 pResource = 0;
4803 if( nArg > 3 ){
4804 pResource = apArg[3];
4805 }else if( is_jx9_stream(pStream) ){
4806 /* TICKET 1433-80: The jx9:// stream need a jx9_value to access the underlying
4807 * virtual machine.
4808 */
4809 pResource = apArg[0];
4810 }
4811 /* Initialize the structure */
4812 InitIOPrivate(pCtx->pVm, pStream, pDev);
4813 /* Convert open mode to JX9 flags */
4814 iOpenFlags = StrModeToFlags(pCtx, zMode, imLen);
4815 /* Try to get a handle */
4816 pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zUri, iOpenFlags,
4817 nArg > 2 ? jx9_value_to_bool(apArg[2]) : FALSE, pResource, FALSE, 0);
4818 if( pDev->pHandle == 0 ){
4819 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zUri);
4820 jx9_result_bool(pCtx, 0);
4821 jx9_context_free_chunk(pCtx, pDev);
4822 return JX9_OK;
4823 }
4824 /* All done, return the io_private instance as a resource */
4825 jx9_result_resource(pCtx, pDev);
4826 return JX9_OK;
4827}
4828/*
4829 * bool fclose(resource $handle)
4830 * Closes an open file pointer
4831 * Parameters
4832 * $handle
4833 * The file pointer.
4834 * Return
4835 * TRUE on success or FALSE on failure.
4836 */
4837static int jx9Builtin_fclose(jx9_context *pCtx, int nArg, jx9_value **apArg)
4838{
4839 const jx9_io_stream *pStream;
4840 io_private *pDev;
4841 jx9_vm *pVm;
4842 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
4843 /* Missing/Invalid arguments, return FALSE */
4844 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4845 jx9_result_bool(pCtx, 0);
4846 return JX9_OK;
4847 }
4848 /* Extract our private data */
4849 pDev = (io_private *)jx9_value_to_resource(apArg[0]);
4850 /* Make sure we are dealing with a valid io_private instance */
4851 if( IO_PRIVATE_INVALID(pDev) ){
4852 /*Expecting an IO handle */
4853 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle");
4854 jx9_result_bool(pCtx, 0);
4855 return JX9_OK;
4856 }
4857 /* Point to the target IO stream device */
4858 pStream = pDev->pStream;
4859 if( pStream == 0 ){
4860 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING,
4861 "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE",
4862 jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream"
4863 );
4864 jx9_result_bool(pCtx, 0);
4865 return JX9_OK;
4866 }
4867 /* Point to the VM that own this context */
4868 pVm = pCtx->pVm;
4869 /* TICKET 1433-62: Keep the STDIN/STDOUT/STDERR handles open */
4870 if( pDev != pVm->pStdin && pDev != pVm->pStdout && pDev != pVm->pStderr ){
4871 /* Perform the requested operation */
4872 jx9StreamCloseHandle(pStream, pDev->pHandle);
4873 /* Release the IO private structure */
4874 ReleaseIOPrivate(pCtx, pDev);
4875 /* Invalidate the resource handle */
4876 jx9_value_release(apArg[0]);
4877 }
4878 /* Return TRUE */
4879 jx9_result_bool(pCtx, 1);
4880 return JX9_OK;
4881}
4882#if !defined(JX9_DISABLE_HASH_FUNC)
4883/*
4884 * MD5/SHA1 digest consumer.
4885 */
4886static int vfsHashConsumer(const void *pData, unsigned int nLen, void *pUserData)
4887{
4888 /* Append hex chunk verbatim */
4889 jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen);
4890 return SXRET_OK;
4891}
4892/*
4893 * string md5_file(string $uri[, bool $raw_output = false ])
4894 * Calculates the md5 hash of a given file.
4895 * Parameters
4896 * $uri
4897 * Target URI (file(/path/to/something) or URL(http://www.symisc.net/))
4898 * $raw_output
4899 * When TRUE, returns the digest in raw binary format with a length of 16.
4900 * Return
4901 * Return the MD5 digest on success or FALSE on failure.
4902 */
4903static int jx9Builtin_md5_file(jx9_context *pCtx, int nArg, jx9_value **apArg)
4904{
4905 const jx9_io_stream *pStream;
4906 unsigned char zDigest[16];
4907 int raw_output = FALSE;
4908 const char *zFile;
4909 MD5Context sCtx;
4910 char zBuf[8192];
4911 void *pHandle;
4912 jx9_int64 n;
4913 int nLen;
4914 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
4915 /* Missing/Invalid arguments, return FALSE */
4916 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
4917 jx9_result_bool(pCtx, 0);
4918 return JX9_OK;
4919 }
4920 /* Extract the file path */
4921 zFile = jx9_value_to_string(apArg[0], &nLen);
4922 /* Point to the target IO stream device */
4923 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
4924 if( pStream == 0 ){
4925 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
4926 jx9_result_bool(pCtx, 0);
4927 return JX9_OK;
4928 }
4929 if( nArg > 1 ){
4930 raw_output = jx9_value_to_bool(apArg[1]);
4931 }
4932 /* Try to open the file in read-only mode */
4933 pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0);
4934 if( pHandle == 0 ){
4935 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
4936 jx9_result_bool(pCtx, 0);
4937 return JX9_OK;
4938 }
4939 /* Init the MD5 context */
4940 MD5Init(&sCtx);
4941 /* Perform the requested operation */
4942 for(;;){
4943 n = pStream->xRead(pHandle, zBuf, sizeof(zBuf));
4944 if( n < 1 ){
4945 /* EOF or IO error, break immediately */
4946 break;
4947 }
4948 MD5Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n);
4949 }
4950 /* Close the stream */
4951 jx9StreamCloseHandle(pStream, pHandle);
4952 /* Extract the digest */
4953 MD5Final(zDigest, &sCtx);
4954 if( raw_output ){
4955 /* Output raw digest */
4956 jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest));
4957 }else{
4958 /* Perform a binary to hex conversion */
4959 SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx);
4960 }
4961 return JX9_OK;
4962}
4963/*
4964 * string sha1_file(string $uri[, bool $raw_output = false ])
4965 * Calculates the SHA1 hash of a given file.
4966 * Parameters
4967 * $uri
4968 * Target URI (file(/path/to/something) or URL(http://www.symisc.net/))
4969 * $raw_output
4970 * When TRUE, returns the digest in raw binary format with a length of 20.
4971 * Return
4972 * Return the SHA1 digest on success or FALSE on failure.
4973 */
4974static int jx9Builtin_sha1_file(jx9_context *pCtx, int nArg, jx9_value **apArg)
4975{
4976 const jx9_io_stream *pStream;
4977 unsigned char zDigest[20];
4978 int raw_output = FALSE;
4979 const char *zFile;
4980 SHA1Context sCtx;
4981 char zBuf[8192];
4982 void *pHandle;
4983 jx9_int64 n;
4984 int nLen;
4985 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
4986 /* Missing/Invalid arguments, return FALSE */
4987 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
4988 jx9_result_bool(pCtx, 0);
4989 return JX9_OK;
4990 }
4991 /* Extract the file path */
4992 zFile = jx9_value_to_string(apArg[0], &nLen);
4993 /* Point to the target IO stream device */
4994 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
4995 if( pStream == 0 ){
4996 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
4997 jx9_result_bool(pCtx, 0);
4998 return JX9_OK;
4999 }
5000 if( nArg > 1 ){
5001 raw_output = jx9_value_to_bool(apArg[1]);
5002 }
5003 /* Try to open the file in read-only mode */
5004 pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0);
5005 if( pHandle == 0 ){
5006 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
5007 jx9_result_bool(pCtx, 0);
5008 return JX9_OK;
5009 }
5010 /* Init the SHA1 context */
5011 SHA1Init(&sCtx);
5012 /* Perform the requested operation */
5013 for(;;){
5014 n = pStream->xRead(pHandle, zBuf, sizeof(zBuf));
5015 if( n < 1 ){
5016 /* EOF or IO error, break immediately */
5017 break;
5018 }
5019 SHA1Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n);
5020 }
5021 /* Close the stream */
5022 jx9StreamCloseHandle(pStream, pHandle);
5023 /* Extract the digest */
5024 SHA1Final(&sCtx, zDigest);
5025 if( raw_output ){
5026 /* Output raw digest */
5027 jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest));
5028 }else{
5029 /* Perform a binary to hex conversion */
5030 SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx);
5031 }
5032 return JX9_OK;
5033}
5034#endif /* JX9_DISABLE_HASH_FUNC */
5035/*
5036 * array parse_ini_file(string $filename[, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]] )
5037 * Parse a configuration file.
5038 * Parameters
5039 * $filename
5040 * The filename of the ini file being parsed.
5041 * $process_sections
5042 * By setting the process_sections parameter to TRUE, you get a multidimensional array
5043 * with the section names and settings included.
5044 * The default for process_sections is FALSE.
5045 * $scanner_mode
5046 * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW.
5047 * If INI_SCANNER_RAW is supplied, then option values will not be parsed.
5048 * Return
5049 * The settings are returned as an associative array on success.
5050 * Otherwise is returned.
5051 */
5052static int jx9Builtin_parse_ini_file(jx9_context *pCtx, int nArg, jx9_value **apArg)
5053{
5054 const jx9_io_stream *pStream;
5055 const char *zFile;
5056 SyBlob sContents;
5057 void *pHandle;
5058 int nLen;
5059 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
5060 /* Missing/Invalid arguments, return FALSE */
5061 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
5062 jx9_result_bool(pCtx, 0);
5063 return JX9_OK;
5064 }
5065 /* Extract the file path */
5066 zFile = jx9_value_to_string(apArg[0], &nLen);
5067 /* Point to the target IO stream device */
5068 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
5069 if( pStream == 0 ){
5070 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
5071 jx9_result_bool(pCtx, 0);
5072 return JX9_OK;
5073 }
5074 /* Try to open the file in read-only mode */
5075 pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0);
5076 if( pHandle == 0 ){
5077 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
5078 jx9_result_bool(pCtx, 0);
5079 return JX9_OK;
5080 }
5081 SyBlobInit(&sContents, &pCtx->pVm->sAllocator);
5082 /* Read the whole file */
5083 jx9StreamReadWholeFile(pHandle, pStream, &sContents);
5084 if( SyBlobLength(&sContents) < 1 ){
5085 /* Empty buffer, return FALSE */
5086 jx9_result_bool(pCtx, 0);
5087 }else{
5088 /* Process the raw INI buffer */
5089 jx9ParseIniString(pCtx, (const char *)SyBlobData(&sContents), SyBlobLength(&sContents),
5090 nArg > 1 ? jx9_value_to_bool(apArg[1]) : 0);
5091 }
5092 /* Close the stream */
5093 jx9StreamCloseHandle(pStream, pHandle);
5094 /* Release the working buffer */
5095 SyBlobRelease(&sContents);
5096 return JX9_OK;
5097}
5098/*
5099 * Section:
5100 * ZIP archive processing.
5101 * Authors:
5102 * Symisc Systems, devel@symisc.net.
5103 * Copyright (C) Symisc Systems, http://jx9.symisc.net
5104 * Status:
5105 * Stable.
5106 */
5107typedef struct zip_raw_data zip_raw_data;
5108struct zip_raw_data
5109{
5110 int iType; /* Where the raw data is stored */
5111 union raw_data{
5112 struct mmap_data{
5113 void *pMap; /* Memory mapped data */
5114 jx9_int64 nSize; /* Map size */
5115 const jx9_vfs *pVfs; /* Underlying vfs */
5116 }mmap;
5117 SyBlob sBlob; /* Memory buffer */
5118 }raw;
5119};
5120#define ZIP_RAW_DATA_MMAPED 1 /* Memory mapped ZIP raw data */
5121#define ZIP_RAW_DATA_MEMBUF 2 /* ZIP raw data stored in a dynamically
5122 * allocated memory chunk.
5123 */
5124 /*
5125 * mixed zip_open(string $filename)
5126 * Opens a new zip archive for reading.
5127 * Parameters
5128 * $filename
5129 * The file name of the ZIP archive to open.
5130 * Return
5131 * A resource handle for later use with zip_read() and zip_close() or FALSE on failure.
5132 */
5133static int jx9Builtin_zip_open(jx9_context *pCtx, int nArg, jx9_value **apArg)
5134{
5135 const jx9_io_stream *pStream;
5136 SyArchive *pArchive;
5137 zip_raw_data *pRaw;
5138 const char *zFile;
5139 SyBlob *pContents;
5140 void *pHandle;
5141 int nLen;
5142 sxi32 rc;
5143 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
5144 /* Missing/Invalid arguments, return FALSE */
5145 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path");
5146 jx9_result_bool(pCtx, 0);
5147 return JX9_OK;
5148 }
5149 /* Extract the file path */
5150 zFile = jx9_value_to_string(apArg[0], &nLen);
5151 /* Point to the target IO stream device */
5152 pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen);
5153 if( pStream == 0 ){
5154 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE");
5155 jx9_result_bool(pCtx, 0);
5156 return JX9_OK;
5157 }
5158 /* Create an in-memory archive */
5159 pArchive = (SyArchive *)jx9_context_alloc_chunk(pCtx, sizeof(SyArchive)+sizeof(zip_raw_data), TRUE, FALSE);
5160 if( pArchive == 0 ){
5161 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "JX9 is running out of memory");
5162 jx9_result_bool(pCtx, 0);
5163 return JX9_OK;
5164 }
5165 pRaw = (zip_raw_data *)&pArchive[1];
5166 /* Initialize the archive */
5167 SyArchiveInit(pArchive, &pCtx->pVm->sAllocator, 0, 0);
5168 /* Extract the default stream */
5169 if( pStream == pCtx->pVm->pDefStream /* file:// stream*/){
5170 const jx9_vfs *pVfs;
5171 /* Try to get a memory view of the whole file since ZIP files
5172 * tends to be very big this days, this is a huge performance win.
5173 */
5174 pVfs = jx9ExportBuiltinVfs();
5175 if( pVfs && pVfs->xMmap ){
5176 rc = pVfs->xMmap(zFile, &pRaw->raw.mmap.pMap, &pRaw->raw.mmap.nSize);
5177 if( rc == JX9_OK ){
5178 /* Nice, Extract the whole archive */
5179 rc = SyZipExtractFromBuf(pArchive, (const char *)pRaw->raw.mmap.pMap, (sxu32)pRaw->raw.mmap.nSize);
5180 if( rc != SXRET_OK ){
5181 if( pVfs->xUnmap ){
5182 pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize);
5183 }
5184 /* Release the allocated chunk */
5185 jx9_context_free_chunk(pCtx, pArchive);
5186 /* Something goes wrong with this ZIP archive, return FALSE */
5187 jx9_result_bool(pCtx, 0);
5188 return JX9_OK;
5189 }
5190 /* Archive successfully opened */
5191 pRaw->iType = ZIP_RAW_DATA_MMAPED;
5192 pRaw->raw.mmap.pVfs = pVfs;
5193 goto success;
5194 }
5195 }
5196 /* FALL THROUGH */
5197 }
5198 /* Try to open the file in read-only mode */
5199 pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0);
5200 if( pHandle == 0 ){
5201 jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile);
5202 jx9_result_bool(pCtx, 0);
5203 return JX9_OK;
5204 }
5205 pContents = &pRaw->raw.sBlob;
5206 SyBlobInit(pContents, &pCtx->pVm->sAllocator);
5207 /* Read the whole file */
5208 jx9StreamReadWholeFile(pHandle, pStream, pContents);
5209 /* Assume an invalid ZIP file */
5210 rc = SXERR_INVALID;
5211 if( SyBlobLength(pContents) > 0 ){
5212 /* Extract archive entries */
5213 rc = SyZipExtractFromBuf(pArchive, (const char *)SyBlobData(pContents), SyBlobLength(pContents));
5214 }
5215 pRaw->iType = ZIP_RAW_DATA_MEMBUF;
5216 /* Close the stream */
5217 jx9StreamCloseHandle(pStream, pHandle);
5218 if( rc != SXRET_OK ){
5219 /* Release the working buffer */
5220 SyBlobRelease(pContents);
5221 /* Release the allocated chunk */
5222 jx9_context_free_chunk(pCtx, pArchive);
5223 /* Something goes wrong with this ZIP archive, return FALSE */
5224 jx9_result_bool(pCtx, 0);
5225 return JX9_OK;
5226 }
5227success:
5228 /* Reset the loop cursor */
5229 SyArchiveResetLoopCursor(pArchive);
5230 /* Return the in-memory archive as a resource handle */
5231 jx9_result_resource(pCtx, pArchive);
5232 return JX9_OK;
5233}
5234/*
5235 * void zip_close(resource $zip)
5236 * Close an in-memory ZIP archive.
5237 * Parameters
5238 * $zip
5239 * A ZIP file previously opened with zip_open().
5240 * Return
5241 * null.
5242 */
5243static int jx9Builtin_zip_close(jx9_context *pCtx, int nArg, jx9_value **apArg)
5244{
5245 SyArchive *pArchive;
5246 zip_raw_data *pRaw;
5247 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5248 /* Missing/Invalid arguments */
5249 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive");
5250 return JX9_OK;
5251 }
5252 /* Point to the in-memory archive */
5253 pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]);
5254 /* Make sure we are dealing with a valid ZIP archive */
5255 if( SXARCH_INVALID(pArchive) ){
5256 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive");
5257 return JX9_OK;
5258 }
5259 /* Release the archive */
5260 SyArchiveRelease(pArchive);
5261 pRaw = (zip_raw_data *)&pArchive[1];
5262 if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){
5263 SyBlobRelease(&pRaw->raw.sBlob);
5264 }else{
5265 const jx9_vfs *pVfs = pRaw->raw.mmap.pVfs;
5266 if( pVfs->xUnmap ){
5267 /* Unmap the memory view */
5268 pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize);
5269 }
5270 }
5271 /* Release the memory chunk */
5272 jx9_context_free_chunk(pCtx, pArchive);
5273 return JX9_OK;
5274}
5275/*
5276 * mixed zip_read(resource $zip)
5277 * Reads the next entry from an in-memory ZIP archive.
5278 * Parameters
5279 * $zip
5280 * A ZIP file previously opened with zip_open().
5281 * Return
5282 * A directory entry resource for later use with the zip_entry_... functions
5283 * or FALSE if there are no more entries to read, or an error code if an error occurred.
5284 */
5285static int jx9Builtin_zip_read(jx9_context *pCtx, int nArg, jx9_value **apArg)
5286{
5287 SyArchiveEntry *pNext = 0; /* cc warning */
5288 SyArchive *pArchive;
5289 sxi32 rc;
5290 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5291 /* Missing/Invalid arguments */
5292 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive");
5293 /* return FALSE */
5294 jx9_result_bool(pCtx, 0);
5295 return JX9_OK;
5296 }
5297 /* Point to the in-memory archive */
5298 pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]);
5299 /* Make sure we are dealing with a valid ZIP archive */
5300 if( SXARCH_INVALID(pArchive) ){
5301 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive");
5302 /* return FALSE */
5303 jx9_result_bool(pCtx, 0);
5304 return JX9_OK;
5305 }
5306 /* Extract the next entry */
5307 rc = SyArchiveGetNextEntry(pArchive, &pNext);
5308 if( rc != SXRET_OK ){
5309 /* No more entries in the central directory, return FALSE */
5310 jx9_result_bool(pCtx, 0);
5311 }else{
5312 /* Return as a resource handle */
5313 jx9_result_resource(pCtx, pNext);
5314 /* Point to the ZIP raw data */
5315 pNext->pUserData = (void *)&pArchive[1];
5316 }
5317 return JX9_OK;
5318}
5319/*
5320 * bool zip_entry_open(resource $zip, resource $zip_entry[, string $mode ])
5321 * Open a directory entry for reading
5322 * Parameters
5323 * $zip
5324 * A ZIP file previously opened with zip_open().
5325 * $zip_entry
5326 * A directory entry returned by zip_read().
5327 * $mode
5328 * Not used
5329 * Return
5330 * A directory entry resource for later use with the zip_entry_... functions
5331 * or FALSE if there are no more entries to read, or an error code if an error occurred.
5332 */
5333static int jx9Builtin_zip_entry_open(jx9_context *pCtx, int nArg, jx9_value **apArg)
5334{
5335 SyArchiveEntry *pEntry;
5336 SyArchive *pArchive;
5337 if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_resource(apArg[1]) ){
5338 /* Missing/Invalid arguments */
5339 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive");
5340 /* return FALSE */
5341 jx9_result_bool(pCtx, 0);
5342 return JX9_OK;
5343 }
5344 /* Point to the in-memory archive */
5345 pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]);
5346 /* Make sure we are dealing with a valid ZIP archive */
5347 if( SXARCH_INVALID(pArchive) ){
5348 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive");
5349 /* return FALSE */
5350 jx9_result_bool(pCtx, 0);
5351 return JX9_OK;
5352 }
5353 /* Make sure we are dealing with a valid ZIP archive entry */
5354 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[1]);
5355 if( SXARCH_ENTRY_INVALID(pEntry) ){
5356 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5357 /* return FALSE */
5358 jx9_result_bool(pCtx, 0);
5359 return JX9_OK;
5360 }
5361 /* All done. Actually this function is a no-op, return TRUE */
5362 jx9_result_bool(pCtx, 1);
5363 return JX9_OK;
5364}
5365/*
5366 * bool zip_entry_close(resource $zip_entry)
5367 * Close a directory entry.
5368 * Parameters
5369 * $zip_entry
5370 * A directory entry returned by zip_read().
5371 * Return
5372 * Returns TRUE on success or FALSE on failure.
5373 */
5374static int jx9Builtin_zip_entry_close(jx9_context *pCtx, int nArg, jx9_value **apArg)
5375{
5376 SyArchiveEntry *pEntry;
5377 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5378 /* Missing/Invalid arguments */
5379 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5380 /* return FALSE */
5381 jx9_result_bool(pCtx, 0);
5382 return JX9_OK;
5383 }
5384 /* Make sure we are dealing with a valid ZIP archive entry */
5385 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]);
5386 if( SXARCH_ENTRY_INVALID(pEntry) ){
5387 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5388 /* return FALSE */
5389 jx9_result_bool(pCtx, 0);
5390 return JX9_OK;
5391 }
5392 /* Reset the read cursor */
5393 pEntry->nReadCount = 0;
5394 /*All done. Actually this function is a no-op, return TRUE */
5395 jx9_result_bool(pCtx, 1);
5396 return JX9_OK;
5397}
5398/*
5399 * string zip_entry_name(resource $zip_entry)
5400 * Retrieve the name of a directory entry.
5401 * Parameters
5402 * $zip_entry
5403 * A directory entry returned by zip_read().
5404 * Return
5405 * The name of the directory entry.
5406 */
5407static int jx9Builtin_zip_entry_name(jx9_context *pCtx, int nArg, jx9_value **apArg)
5408{
5409 SyArchiveEntry *pEntry;
5410 SyString *pName;
5411 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5412 /* Missing/Invalid arguments */
5413 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5414 /* return FALSE */
5415 jx9_result_bool(pCtx, 0);
5416 return JX9_OK;
5417 }
5418 /* Make sure we are dealing with a valid ZIP archive entry */
5419 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]);
5420 if( SXARCH_ENTRY_INVALID(pEntry) ){
5421 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5422 /* return FALSE */
5423 jx9_result_bool(pCtx, 0);
5424 return JX9_OK;
5425 }
5426 /* Return entry name */
5427 pName = &pEntry->sFileName;
5428 jx9_result_string(pCtx, pName->zString, (int)pName->nByte);
5429 return JX9_OK;
5430}
5431/*
5432 * int64 zip_entry_filesize(resource $zip_entry)
5433 * Retrieve the actual file size of a directory entry.
5434 * Parameters
5435 * $zip_entry
5436 * A directory entry returned by zip_read().
5437 * Return
5438 * The size of the directory entry.
5439 */
5440static int jx9Builtin_zip_entry_filesize(jx9_context *pCtx, int nArg, jx9_value **apArg)
5441{
5442 SyArchiveEntry *pEntry;
5443 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5444 /* Missing/Invalid arguments */
5445 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5446 /* return FALSE */
5447 jx9_result_bool(pCtx, 0);
5448 return JX9_OK;
5449 }
5450 /* Make sure we are dealing with a valid ZIP archive entry */
5451 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]);
5452 if( SXARCH_ENTRY_INVALID(pEntry) ){
5453 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5454 /* return FALSE */
5455 jx9_result_bool(pCtx, 0);
5456 return JX9_OK;
5457 }
5458 /* Return entry size */
5459 jx9_result_int64(pCtx, (jx9_int64)pEntry->nByte);
5460 return JX9_OK;
5461}
5462/*
5463 * int64 zip_entry_compressedsize(resource $zip_entry)
5464 * Retrieve the compressed size of a directory entry.
5465 * Parameters
5466 * $zip_entry
5467 * A directory entry returned by zip_read().
5468 * Return
5469 * The compressed size.
5470 */
5471static int jx9Builtin_zip_entry_compressedsize(jx9_context *pCtx, int nArg, jx9_value **apArg)
5472{
5473 SyArchiveEntry *pEntry;
5474 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5475 /* Missing/Invalid arguments */
5476 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5477 /* return FALSE */
5478 jx9_result_bool(pCtx, 0);
5479 return JX9_OK;
5480 }
5481 /* Make sure we are dealing with a valid ZIP archive entry */
5482 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]);
5483 if( SXARCH_ENTRY_INVALID(pEntry) ){
5484 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5485 /* return FALSE */
5486 jx9_result_bool(pCtx, 0);
5487 return JX9_OK;
5488 }
5489 /* Return entry compressed size */
5490 jx9_result_int64(pCtx, (jx9_int64)pEntry->nByteCompr);
5491 return JX9_OK;
5492}
5493/*
5494 * string zip_entry_read(resource $zip_entry[, int $length])
5495 * Reads from an open directory entry.
5496 * Parameters
5497 * $zip_entry
5498 * A directory entry returned by zip_read().
5499 * $length
5500 * The number of bytes to return. If not specified, this function
5501 * will attempt to read 1024 bytes.
5502 * Return
5503 * Returns the data read, or FALSE if the end of the file is reached.
5504 */
5505static int jx9Builtin_zip_entry_read(jx9_context *pCtx, int nArg, jx9_value **apArg)
5506{
5507 SyArchiveEntry *pEntry;
5508 zip_raw_data *pRaw;
5509 const char *zData;
5510 int iLength;
5511 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5512 /* Missing/Invalid arguments */
5513 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5514 /* return FALSE */
5515 jx9_result_bool(pCtx, 0);
5516 return JX9_OK;
5517 }
5518 /* Make sure we are dealing with a valid ZIP archive entry */
5519 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]);
5520 if( SXARCH_ENTRY_INVALID(pEntry) ){
5521 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5522 /* return FALSE */
5523 jx9_result_bool(pCtx, 0);
5524 return JX9_OK;
5525 }
5526 zData = 0;
5527 if( pEntry->nReadCount >= pEntry->nByteCompr ){
5528 /* No more data to read, return FALSE */
5529 jx9_result_bool(pCtx, 0);
5530 return JX9_OK;
5531 }
5532 /* Set a default read length */
5533 iLength = 1024;
5534 if( nArg > 1 ){
5535 iLength = jx9_value_to_int(apArg[1]);
5536 if( iLength < 1 ){
5537 iLength = 1024;
5538 }
5539 }
5540 if( (sxu32)iLength > pEntry->nByteCompr - pEntry->nReadCount ){
5541 iLength = (int)(pEntry->nByteCompr - pEntry->nReadCount);
5542 }
5543 /* Return the entry contents */
5544 pRaw = (zip_raw_data *)pEntry->pUserData;
5545 if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){
5546 zData = (const char *)SyBlobDataAt(&pRaw->raw.sBlob, (pEntry->nOfft+pEntry->nReadCount));
5547 }else{
5548 const char *zMap = (const char *)pRaw->raw.mmap.pMap;
5549 /* Memory mmaped chunk */
5550 zData = &zMap[pEntry->nOfft+pEntry->nReadCount];
5551 }
5552 /* Increment the read counter */
5553 pEntry->nReadCount += iLength;
5554 /* Return the raw data */
5555 jx9_result_string(pCtx, zData, iLength);
5556 return JX9_OK;
5557}
5558/*
5559 * bool zip_entry_reset_cursor(resource $zip_entry)
5560 * Reset the read cursor of an open directory entry.
5561 * Parameters
5562 * $zip_entry
5563 * A directory entry returned by zip_read().
5564 * Return
5565 * TRUE on success, FALSE on failure.
5566 */
5567static int jx9Builtin_zip_entry_reset_cursor(jx9_context *pCtx, int nArg, jx9_value **apArg)
5568{
5569 SyArchiveEntry *pEntry;
5570 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5571 /* Missing/Invalid arguments */
5572 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5573 /* return FALSE */
5574 jx9_result_bool(pCtx, 0);
5575 return JX9_OK;
5576 }
5577 /* Make sure we are dealing with a valid ZIP archive entry */
5578 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]);
5579 if( SXARCH_ENTRY_INVALID(pEntry) ){
5580 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5581 /* return FALSE */
5582 jx9_result_bool(pCtx, 0);
5583 return JX9_OK;
5584 }
5585 /* Reset the cursor */
5586 pEntry->nReadCount = 0;
5587 /* Return TRUE */
5588 jx9_result_bool(pCtx, 1);
5589 return JX9_OK;
5590}
5591/*
5592 * string zip_entry_compressionmethod(resource $zip_entry)
5593 * Retrieve the compression method of a directory entry.
5594 * Parameters
5595 * $zip_entry
5596 * A directory entry returned by zip_read().
5597 * Return
5598 * The compression method on success or FALSE on failure.
5599 */
5600static int jx9Builtin_zip_entry_compressionmethod(jx9_context *pCtx, int nArg, jx9_value **apArg)
5601{
5602 SyArchiveEntry *pEntry;
5603 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
5604 /* Missing/Invalid arguments */
5605 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5606 /* return FALSE */
5607 jx9_result_bool(pCtx, 0);
5608 return JX9_OK;
5609 }
5610 /* Make sure we are dealing with a valid ZIP archive entry */
5611 pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]);
5612 if( SXARCH_ENTRY_INVALID(pEntry) ){
5613 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry");
5614 /* return FALSE */
5615 jx9_result_bool(pCtx, 0);
5616 return JX9_OK;
5617 }
5618 switch(pEntry->nComprMeth){
5619 case 0:
5620 /* No compression;entry is stored */
5621 jx9_result_string(pCtx, "stored", (int)sizeof("stored")-1);
5622 break;
5623 case 8:
5624 /* Entry is deflated (Default compression algorithm) */
5625 jx9_result_string(pCtx, "deflate", (int)sizeof("deflate")-1);
5626 break;
5627 /* Exotic compression algorithms */
5628 case 1:
5629 jx9_result_string(pCtx, "shrunk", (int)sizeof("shrunk")-1);
5630 break;
5631 case 2:
5632 case 3:
5633 case 4:
5634 case 5:
5635 /* Entry is reduced */
5636 jx9_result_string(pCtx, "reduced", (int)sizeof("reduced")-1);
5637 break;
5638 case 6:
5639 /* Entry is imploded */
5640 jx9_result_string(pCtx, "implode", (int)sizeof("implode")-1);
5641 break;
5642 default:
5643 jx9_result_string(pCtx, "unknown", (int)sizeof("unknown")-1);
5644 break;
5645 }
5646 return JX9_OK;
5647}
5648#endif /* #ifndef JX9_DISABLE_BUILTIN_FUNC*/
5649/* NULL VFS [i.e: a no-op VFS]*/
5650static const jx9_vfs null_vfs = {
5651 "null_vfs",
5652 JX9_VFS_VERSION,
5653 0, /* int (*xChdir)(const char *) */
5654 0, /* int (*xChroot)(const char *); */
5655 0, /* int (*xGetcwd)(jx9_context *) */
5656 0, /* int (*xMkdir)(const char *, int, int) */
5657 0, /* int (*xRmdir)(const char *) */
5658 0, /* int (*xIsdir)(const char *) */
5659 0, /* int (*xRename)(const char *, const char *) */
5660 0, /*int (*xRealpath)(const char *, jx9_context *)*/
5661 0, /* int (*xSleep)(unsigned int) */
5662 0, /* int (*xUnlink)(const char *) */
5663 0, /* int (*xFileExists)(const char *) */
5664 0, /*int (*xChmod)(const char *, int)*/
5665 0, /*int (*xChown)(const char *, const char *)*/
5666 0, /*int (*xChgrp)(const char *, const char *)*/
5667 0, /* jx9_int64 (*xFreeSpace)(const char *) */
5668 0, /* jx9_int64 (*xTotalSpace)(const char *) */
5669 0, /* jx9_int64 (*xFileSize)(const char *) */
5670 0, /* jx9_int64 (*xFileAtime)(const char *) */
5671 0, /* jx9_int64 (*xFileMtime)(const char *) */
5672 0, /* jx9_int64 (*xFileCtime)(const char *) */
5673 0, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */
5674 0, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */
5675 0, /* int (*xIsfile)(const char *) */
5676 0, /* int (*xIslink)(const char *) */
5677 0, /* int (*xReadable)(const char *) */
5678 0, /* int (*xWritable)(const char *) */
5679 0, /* int (*xExecutable)(const char *) */
5680 0, /* int (*xFiletype)(const char *, jx9_context *) */
5681 0, /* int (*xGetenv)(const char *, jx9_context *) */
5682 0, /* int (*xSetenv)(const char *, const char *) */
5683 0, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */
5684 0, /* int (*xMmap)(const char *, void **, jx9_int64 *) */
5685 0, /* void (*xUnmap)(void *, jx9_int64); */
5686 0, /* int (*xLink)(const char *, const char *, int) */
5687 0, /* int (*xUmask)(int) */
5688 0, /* void (*xTempDir)(jx9_context *) */
5689 0, /* unsigned int (*xProcessId)(void) */
5690 0, /* int (*xUid)(void) */
5691 0, /* int (*xGid)(void) */
5692 0, /* void (*xUsername)(jx9_context *) */
5693 0 /* int (*xExec)(const char *, jx9_context *) */
5694};
5695#ifndef JX9_DISABLE_BUILTIN_FUNC
5696#ifndef JX9_DISABLE_DISK_IO
5697#ifdef __WINNT__
5698/*
5699 * Windows VFS implementation for the JX9 engine.
5700 * Authors:
5701 * Symisc Systems, devel@symisc.net.
5702 * Copyright (C) Symisc Systems, http://jx9.symisc.net
5703 * Status:
5704 * Stable.
5705 */
5706/* What follows here is code that is specific to windows systems. */
5707#include <Windows.h>
5708/*
5709** Convert a UTF-8 string to microsoft unicode (UTF-16?).
5710**
5711** Space to hold the returned string is obtained from HeapAlloc().
5712** Taken from the sqlite3 source tree
5713** status: Public Domain
5714*/
5715static WCHAR *jx9utf8ToUnicode(const char *zFilename){
5716 int nChar;
5717 WCHAR *zWideFilename;
5718
5719 nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0);
5720 zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, nChar*sizeof(zWideFilename[0]));
5721 if( zWideFilename == 0 ){
5722 return 0;
5723 }
5724 nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar);
5725 if( nChar==0 ){
5726 HeapFree(GetProcessHeap(), 0, zWideFilename);
5727 return 0;
5728 }
5729 return zWideFilename;
5730}
5731/*
5732** Convert a UTF-8 filename into whatever form the underlying
5733** operating system wants filenames in.Space to hold the result
5734** is obtained from HeapAlloc() and must be freed by the calling
5735** function.
5736** Taken from the sqlite3 source tree
5737** status: Public Domain
5738*/
5739static void *jx9convertUtf8Filename(const char *zFilename){
5740 void *zConverted;
5741 zConverted = jx9utf8ToUnicode(zFilename);
5742 return zConverted;
5743}
5744/*
5745** Convert microsoft unicode to UTF-8. Space to hold the returned string is
5746** obtained from HeapAlloc().
5747** Taken from the sqlite3 source tree
5748** status: Public Domain
5749*/
5750static char *jx9unicodeToUtf8(const WCHAR *zWideFilename){
5751 char *zFilename;
5752 int nByte;
5753
5754 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
5755 zFilename = (char *)HeapAlloc(GetProcessHeap(), 0, nByte);
5756 if( zFilename == 0 ){
5757 return 0;
5758 }
5759 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, 0, 0);
5760 if( nByte == 0 ){
5761 HeapFree(GetProcessHeap(), 0, zFilename);
5762 return 0;
5763 }
5764 return zFilename;
5765}
5766/* int (*xchdir)(const char *) */
5767static int WinVfs_chdir(const char *zPath)
5768{
5769 void * pConverted;
5770 BOOL rc;
5771 pConverted = jx9convertUtf8Filename(zPath);
5772 if( pConverted == 0 ){
5773 return -1;
5774 }
5775 rc = SetCurrentDirectoryW((LPCWSTR)pConverted);
5776 HeapFree(GetProcessHeap(), 0, pConverted);
5777 return rc ? JX9_OK : -1;
5778}
5779/* int (*xGetcwd)(jx9_context *) */
5780static int WinVfs_getcwd(jx9_context *pCtx)
5781{
5782 WCHAR zDir[2048];
5783 char *zConverted;
5784 DWORD rc;
5785 /* Get the current directory */
5786 rc = GetCurrentDirectoryW(sizeof(zDir), zDir);
5787 if( rc < 1 ){
5788 return -1;
5789 }
5790 zConverted = jx9unicodeToUtf8(zDir);
5791 if( zConverted == 0 ){
5792 return -1;
5793 }
5794 jx9_result_string(pCtx, zConverted, -1/*Compute length automatically*/); /* Will make it's own copy */
5795 HeapFree(GetProcessHeap(), 0, zConverted);
5796 return JX9_OK;
5797}
5798/* int (*xMkdir)(const char *, int, int) */
5799static int WinVfs_mkdir(const char *zPath, int mode, int recursive)
5800{
5801 void * pConverted;
5802 BOOL rc;
5803 pConverted = jx9convertUtf8Filename(zPath);
5804 if( pConverted == 0 ){
5805 return -1;
5806 }
5807 mode= 0; /* MSVC warning */
5808 recursive = 0;
5809 rc = CreateDirectoryW((LPCWSTR)pConverted, 0);
5810 HeapFree(GetProcessHeap(), 0, pConverted);
5811 return rc ? JX9_OK : -1;
5812}
5813/* int (*xRmdir)(const char *) */
5814static int WinVfs_rmdir(const char *zPath)
5815{
5816 void * pConverted;
5817 BOOL rc;
5818 pConverted = jx9convertUtf8Filename(zPath);
5819 if( pConverted == 0 ){
5820 return -1;
5821 }
5822 rc = RemoveDirectoryW((LPCWSTR)pConverted);
5823 HeapFree(GetProcessHeap(), 0, pConverted);
5824 return rc ? JX9_OK : -1;
5825}
5826/* int (*xIsdir)(const char *) */
5827static int WinVfs_isdir(const char *zPath)
5828{
5829 void * pConverted;
5830 DWORD dwAttr;
5831 pConverted = jx9convertUtf8Filename(zPath);
5832 if( pConverted == 0 ){
5833 return -1;
5834 }
5835 dwAttr = GetFileAttributesW((LPCWSTR)pConverted);
5836 HeapFree(GetProcessHeap(), 0, pConverted);
5837 if( dwAttr == INVALID_FILE_ATTRIBUTES ){
5838 return -1;
5839 }
5840 return (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? JX9_OK : -1;
5841}
5842/* int (*xRename)(const char *, const char *) */
5843static int WinVfs_Rename(const char *zOld, const char *zNew)
5844{
5845 void *pOld, *pNew;
5846 BOOL rc = 0;
5847 pOld = jx9convertUtf8Filename(zOld);
5848 if( pOld == 0 ){
5849 return -1;
5850 }
5851 pNew = jx9convertUtf8Filename(zNew);
5852 if( pNew ){
5853 rc = MoveFileW((LPCWSTR)pOld, (LPCWSTR)pNew);
5854 }
5855 HeapFree(GetProcessHeap(), 0, pOld);
5856 if( pNew ){
5857 HeapFree(GetProcessHeap(), 0, pNew);
5858 }
5859 return rc ? JX9_OK : - 1;
5860}
5861/* int (*xRealpath)(const char *, jx9_context *) */
5862static int WinVfs_Realpath(const char *zPath, jx9_context *pCtx)
5863{
5864 WCHAR zTemp[2048];
5865 void *pPath;
5866 char *zReal;
5867 DWORD n;
5868 pPath = jx9convertUtf8Filename(zPath);
5869 if( pPath == 0 ){
5870 return -1;
5871 }
5872 n = GetFullPathNameW((LPCWSTR)pPath, 0, 0, 0);
5873 if( n > 0 ){
5874 if( n >= sizeof(zTemp) ){
5875 n = sizeof(zTemp) - 1;
5876 }
5877 GetFullPathNameW((LPCWSTR)pPath, n, zTemp, 0);
5878 }
5879 HeapFree(GetProcessHeap(), 0, pPath);
5880 if( !n ){
5881 return -1;
5882 }
5883 zReal = jx9unicodeToUtf8(zTemp);
5884 if( zReal == 0 ){
5885 return -1;
5886 }
5887 jx9_result_string(pCtx, zReal, -1); /* Will make it's own copy */
5888 HeapFree(GetProcessHeap(), 0, zReal);
5889 return JX9_OK;
5890}
5891/* int (*xSleep)(unsigned int) */
5892static int WinVfs_Sleep(unsigned int uSec)
5893{
5894 Sleep(uSec/1000/*uSec per Millisec */);
5895 return JX9_OK;
5896}
5897/* int (*xUnlink)(const char *) */
5898static int WinVfs_unlink(const char *zPath)
5899{
5900 void * pConverted;
5901 BOOL rc;
5902 pConverted = jx9convertUtf8Filename(zPath);
5903 if( pConverted == 0 ){
5904 return -1;
5905 }
5906 rc = DeleteFileW((LPCWSTR)pConverted);
5907 HeapFree(GetProcessHeap(), 0, pConverted);
5908 return rc ? JX9_OK : - 1;
5909}
5910/* jx9_int64 (*xFreeSpace)(const char *) */
5911static jx9_int64 WinVfs_DiskFreeSpace(const char *zPath)
5912{
5913#ifdef _WIN32_WCE
5914 /* GetDiskFreeSpace is not supported under WINCE */
5915 SXUNUSED(zPath);
5916 return 0;
5917#else
5918 DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters;
5919 void * pConverted;
5920 WCHAR *p;
5921 BOOL rc;
5922 pConverted = jx9convertUtf8Filename(zPath);
5923 if( pConverted == 0 ){
5924 return 0;
5925 }
5926 p = (WCHAR *)pConverted;
5927 for(;*p;p++){
5928 if( *p == '\\' || *p == '/'){
5929 *p = '\0';
5930 break;
5931 }
5932 }
5933 rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters);
5934 if( !rc ){
5935 return 0;
5936 }
5937 return (jx9_int64)dwFreeClusters * dwSectPerClust * dwBytesPerSect;
5938#endif
5939}
5940/* jx9_int64 (*xTotalSpace)(const char *) */
5941static jx9_int64 WinVfs_DiskTotalSpace(const char *zPath)
5942{
5943#ifdef _WIN32_WCE
5944 /* GetDiskFreeSpace is not supported under WINCE */
5945 SXUNUSED(zPath);
5946 return 0;
5947#else
5948 DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters;
5949 void * pConverted;
5950 WCHAR *p;
5951 BOOL rc;
5952 pConverted = jx9convertUtf8Filename(zPath);
5953 if( pConverted == 0 ){
5954 return 0;
5955 }
5956 p = (WCHAR *)pConverted;
5957 for(;*p;p++){
5958 if( *p == '\\' || *p == '/'){
5959 *p = '\0';
5960 break;
5961 }
5962 }
5963 rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters);
5964 if( !rc ){
5965 return 0;
5966 }
5967 return (jx9_int64)dwTotalClusters * dwSectPerClust * dwBytesPerSect;
5968#endif
5969}
5970/* int (*xFileExists)(const char *) */
5971static int WinVfs_FileExists(const char *zPath)
5972{
5973 void * pConverted;
5974 DWORD dwAttr;
5975 pConverted = jx9convertUtf8Filename(zPath);
5976 if( pConverted == 0 ){
5977 return -1;
5978 }
5979 dwAttr = GetFileAttributesW((LPCWSTR)pConverted);
5980 HeapFree(GetProcessHeap(), 0, pConverted);
5981 if( dwAttr == INVALID_FILE_ATTRIBUTES ){
5982 return -1;
5983 }
5984 return JX9_OK;
5985}
5986/* Open a file in a read-only mode */
5987static HANDLE OpenReadOnly(LPCWSTR pPath)
5988{
5989 DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS;
5990 DWORD dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
5991 DWORD dwAccess = GENERIC_READ;
5992 DWORD dwCreate = OPEN_EXISTING;
5993 HANDLE pHandle;
5994 pHandle = CreateFileW(pPath, dwAccess, dwShare, 0, dwCreate, dwType, 0);
5995 if( pHandle == INVALID_HANDLE_VALUE){
5996 return 0;
5997 }
5998 return pHandle;
5999}
6000/* jx9_int64 (*xFileSize)(const char *) */
6001static jx9_int64 WinVfs_FileSize(const char *zPath)
6002{
6003 DWORD dwLow, dwHigh;
6004 void * pConverted;
6005 jx9_int64 nSize;
6006 HANDLE pHandle;
6007
6008 pConverted = jx9convertUtf8Filename(zPath);
6009 if( pConverted == 0 ){
6010 return -1;
6011 }
6012 /* Open the file in read-only mode */
6013 pHandle = OpenReadOnly((LPCWSTR)pConverted);
6014 HeapFree(GetProcessHeap(), 0, pConverted);
6015 if( pHandle ){
6016 dwLow = GetFileSize(pHandle, &dwHigh);
6017 nSize = dwHigh;
6018 nSize <<= 32;
6019 nSize += dwLow;
6020 CloseHandle(pHandle);
6021 }else{
6022 nSize = -1;
6023 }
6024 return nSize;
6025}
6026#define TICKS_PER_SECOND 10000000
6027#define EPOCH_DIFFERENCE 11644473600LL
6028/* Convert Windows timestamp to UNIX timestamp */
6029static jx9_int64 convertWindowsTimeToUnixTime(LPFILETIME pTime)
6030{
6031 jx9_int64 input, temp;
6032 input = pTime->dwHighDateTime;
6033 input <<= 32;
6034 input += pTime->dwLowDateTime;
6035 temp = input / TICKS_PER_SECOND; /*convert from 100ns intervals to seconds*/
6036 temp = temp - EPOCH_DIFFERENCE; /*subtract number of seconds between epochs*/
6037 return temp;
6038}
6039/* Convert UNIX timestamp to Windows timestamp */
6040static void convertUnixTimeToWindowsTime(jx9_int64 nUnixtime, LPFILETIME pOut)
6041{
6042 jx9_int64 result = EPOCH_DIFFERENCE;
6043 result += nUnixtime;
6044 result *= 10000000LL;
6045 pOut->dwHighDateTime = (DWORD)(nUnixtime>>32);
6046 pOut->dwLowDateTime = (DWORD)nUnixtime;
6047}
6048/* int (*xTouch)(const char *, jx9_int64, jx9_int64) */
6049static int WinVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time)
6050{
6051 FILETIME sTouch, sAccess;
6052 void *pConverted;
6053 void *pHandle;
6054 BOOL rc = 0;
6055 pConverted = jx9convertUtf8Filename(zPath);
6056 if( pConverted == 0 ){
6057 return -1;
6058 }
6059 pHandle = OpenReadOnly((LPCWSTR)pConverted);
6060 if( pHandle ){
6061 if( touch_time < 0 ){
6062 GetSystemTimeAsFileTime(&sTouch);
6063 }else{
6064 convertUnixTimeToWindowsTime(touch_time, &sTouch);
6065 }
6066 if( access_time < 0 ){
6067 /* Use the touch time */
6068 sAccess = sTouch; /* Structure assignment */
6069 }else{
6070 convertUnixTimeToWindowsTime(access_time, &sAccess);
6071 }
6072 rc = SetFileTime(pHandle, &sTouch, &sAccess, 0);
6073 /* Close the handle */
6074 CloseHandle(pHandle);
6075 }
6076 HeapFree(GetProcessHeap(), 0, pConverted);
6077 return rc ? JX9_OK : -1;
6078}
6079/* jx9_int64 (*xFileAtime)(const char *) */
6080static jx9_int64 WinVfs_FileAtime(const char *zPath)
6081{
6082 BY_HANDLE_FILE_INFORMATION sInfo;
6083 void * pConverted;
6084 jx9_int64 atime;
6085 HANDLE pHandle;
6086 pConverted = jx9convertUtf8Filename(zPath);
6087 if( pConverted == 0 ){
6088 return -1;
6089 }
6090 /* Open the file in read-only mode */
6091 pHandle = OpenReadOnly((LPCWSTR)pConverted);
6092 if( pHandle ){
6093 BOOL rc;
6094 rc = GetFileInformationByHandle(pHandle, &sInfo);
6095 if( rc ){
6096 atime = convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime);
6097 }else{
6098 atime = -1;
6099 }
6100 CloseHandle(pHandle);
6101 }else{
6102 atime = -1;
6103 }
6104 HeapFree(GetProcessHeap(), 0, pConverted);
6105 return atime;
6106}
6107/* jx9_int64 (*xFileMtime)(const char *) */
6108static jx9_int64 WinVfs_FileMtime(const char *zPath)
6109{
6110 BY_HANDLE_FILE_INFORMATION sInfo;
6111 void * pConverted;
6112 jx9_int64 mtime;
6113 HANDLE pHandle;
6114 pConverted = jx9convertUtf8Filename(zPath);
6115 if( pConverted == 0 ){
6116 return -1;
6117 }
6118 /* Open the file in read-only mode */
6119 pHandle = OpenReadOnly((LPCWSTR)pConverted);
6120 if( pHandle ){
6121 BOOL rc;
6122 rc = GetFileInformationByHandle(pHandle, &sInfo);
6123 if( rc ){
6124 mtime = convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime);
6125 }else{
6126 mtime = -1;
6127 }
6128 CloseHandle(pHandle);
6129 }else{
6130 mtime = -1;
6131 }
6132 HeapFree(GetProcessHeap(), 0, pConverted);
6133 return mtime;
6134}
6135/* jx9_int64 (*xFileCtime)(const char *) */
6136static jx9_int64 WinVfs_FileCtime(const char *zPath)
6137{
6138 BY_HANDLE_FILE_INFORMATION sInfo;
6139 void * pConverted;
6140 jx9_int64 ctime;
6141 HANDLE pHandle;
6142 pConverted = jx9convertUtf8Filename(zPath);
6143 if( pConverted == 0 ){
6144 return -1;
6145 }
6146 /* Open the file in read-only mode */
6147 pHandle = OpenReadOnly((LPCWSTR)pConverted);
6148 if( pHandle ){
6149 BOOL rc;
6150 rc = GetFileInformationByHandle(pHandle, &sInfo);
6151 if( rc ){
6152 ctime = convertWindowsTimeToUnixTime(&sInfo.ftCreationTime);
6153 }else{
6154 ctime = -1;
6155 }
6156 CloseHandle(pHandle);
6157 }else{
6158 ctime = -1;
6159 }
6160 HeapFree(GetProcessHeap(), 0, pConverted);
6161 return ctime;
6162}
6163/* int (*xStat)(const char *, jx9_value *, jx9_value *) */
6164/* int (*xlStat)(const char *, jx9_value *, jx9_value *) */
6165static int WinVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker)
6166{
6167 BY_HANDLE_FILE_INFORMATION sInfo;
6168 void *pConverted;
6169 HANDLE pHandle;
6170 BOOL rc;
6171 pConverted = jx9convertUtf8Filename(zPath);
6172 if( pConverted == 0 ){
6173 return -1;
6174 }
6175 /* Open the file in read-only mode */
6176 pHandle = OpenReadOnly((LPCWSTR)pConverted);
6177 HeapFree(GetProcessHeap(), 0, pConverted);
6178 if( pHandle == 0 ){
6179 return -1;
6180 }
6181 rc = GetFileInformationByHandle(pHandle, &sInfo);
6182 CloseHandle(pHandle);
6183 if( !rc ){
6184 return -1;
6185 }
6186 /* dev */
6187 jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber);
6188 jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */
6189 /* ino */
6190 jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow));
6191 jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */
6192 /* mode */
6193 jx9_value_int(pWorker, 0);
6194 jx9_array_add_strkey_elem(pArray, "mode", pWorker);
6195 /* nlink */
6196 jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks);
6197 jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */
6198 /* uid, gid, rdev */
6199 jx9_value_int(pWorker, 0);
6200 jx9_array_add_strkey_elem(pArray, "uid", pWorker);
6201 jx9_array_add_strkey_elem(pArray, "gid", pWorker);
6202 jx9_array_add_strkey_elem(pArray, "rdev", pWorker);
6203 /* size */
6204 jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow));
6205 jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */
6206 /* atime */
6207 jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime));
6208 jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */
6209 /* mtime */
6210 jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime));
6211 jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */
6212 /* ctime */
6213 jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime));
6214 jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */
6215 /* blksize, blocks */
6216 jx9_value_int(pWorker, 0);
6217 jx9_array_add_strkey_elem(pArray, "blksize", pWorker);
6218 jx9_array_add_strkey_elem(pArray, "blocks", pWorker);
6219 return JX9_OK;
6220}
6221/* int (*xIsfile)(const char *) */
6222static int WinVfs_isfile(const char *zPath)
6223{
6224 void * pConverted;
6225 DWORD dwAttr;
6226 pConverted = jx9convertUtf8Filename(zPath);
6227 if( pConverted == 0 ){
6228 return -1;
6229 }
6230 dwAttr = GetFileAttributesW((LPCWSTR)pConverted);
6231 HeapFree(GetProcessHeap(), 0, pConverted);
6232 if( dwAttr == INVALID_FILE_ATTRIBUTES ){
6233 return -1;
6234 }
6235 return (dwAttr & (FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE)) ? JX9_OK : -1;
6236}
6237/* int (*xIslink)(const char *) */
6238static int WinVfs_islink(const char *zPath)
6239{
6240 void * pConverted;
6241 DWORD dwAttr;
6242 pConverted = jx9convertUtf8Filename(zPath);
6243 if( pConverted == 0 ){
6244 return -1;
6245 }
6246 dwAttr = GetFileAttributesW((LPCWSTR)pConverted);
6247 HeapFree(GetProcessHeap(), 0, pConverted);
6248 if( dwAttr == INVALID_FILE_ATTRIBUTES ){
6249 return -1;
6250 }
6251 return (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) ? JX9_OK : -1;
6252}
6253/* int (*xWritable)(const char *) */
6254static int WinVfs_iswritable(const char *zPath)
6255{
6256 void * pConverted;
6257 DWORD dwAttr;
6258 pConverted = jx9convertUtf8Filename(zPath);
6259 if( pConverted == 0 ){
6260 return -1;
6261 }
6262 dwAttr = GetFileAttributesW((LPCWSTR)pConverted);
6263 HeapFree(GetProcessHeap(), 0, pConverted);
6264 if( dwAttr == INVALID_FILE_ATTRIBUTES ){
6265 return -1;
6266 }
6267 if( (dwAttr & (FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL)) == 0 ){
6268 /* Not a regular file */
6269 return -1;
6270 }
6271 if( dwAttr & FILE_ATTRIBUTE_READONLY ){
6272 /* Read-only file */
6273 return -1;
6274 }
6275 /* File is writable */
6276 return JX9_OK;
6277}
6278/* int (*xExecutable)(const char *) */
6279static int WinVfs_isexecutable(const char *zPath)
6280{
6281 void * pConverted;
6282 DWORD dwAttr;
6283 pConverted = jx9convertUtf8Filename(zPath);
6284 if( pConverted == 0 ){
6285 return -1;
6286 }
6287 dwAttr = GetFileAttributesW((LPCWSTR)pConverted);
6288 HeapFree(GetProcessHeap(), 0, pConverted);
6289 if( dwAttr == INVALID_FILE_ATTRIBUTES ){
6290 return -1;
6291 }
6292 if( (dwAttr & FILE_ATTRIBUTE_NORMAL) == 0 ){
6293 /* Not a regular file */
6294 return -1;
6295 }
6296 /* File is executable */
6297 return JX9_OK;
6298}
6299/* int (*xFiletype)(const char *, jx9_context *) */
6300static int WinVfs_Filetype(const char *zPath, jx9_context *pCtx)
6301{
6302 void * pConverted;
6303 DWORD dwAttr;
6304 pConverted = jx9convertUtf8Filename(zPath);
6305 if( pConverted == 0 ){
6306 /* Expand 'unknown' */
6307 jx9_result_string(pCtx, "unknown", sizeof("unknown")-1);
6308 return -1;
6309 }
6310 dwAttr = GetFileAttributesW((LPCWSTR)pConverted);
6311 HeapFree(GetProcessHeap(), 0, pConverted);
6312 if( dwAttr == INVALID_FILE_ATTRIBUTES ){
6313 /* Expand 'unknown' */
6314 jx9_result_string(pCtx, "unknown", sizeof("unknown")-1);
6315 return -1;
6316 }
6317 if(dwAttr & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE) ){
6318 jx9_result_string(pCtx, "file", sizeof("file")-1);
6319 }else if(dwAttr & FILE_ATTRIBUTE_DIRECTORY){
6320 jx9_result_string(pCtx, "dir", sizeof("dir")-1);
6321 }else if(dwAttr & FILE_ATTRIBUTE_REPARSE_POINT){
6322 jx9_result_string(pCtx, "link", sizeof("link")-1);
6323 }else if(dwAttr & (FILE_ATTRIBUTE_DEVICE)){
6324 jx9_result_string(pCtx, "block", sizeof("block")-1);
6325 }else{
6326 jx9_result_string(pCtx, "unknown", sizeof("unknown")-1);
6327 }
6328 return JX9_OK;
6329}
6330/* int (*xGetenv)(const char *, jx9_context *) */
6331static int WinVfs_Getenv(const char *zVar, jx9_context *pCtx)
6332{
6333 char zValue[1024];
6334 DWORD n;
6335 /*
6336 * According to MSDN
6337 * If lpBuffer is not large enough to hold the data, the return
6338 * value is the buffer size, in characters, required to hold the
6339 * string and its terminating null character and the contents
6340 * of lpBuffer are undefined.
6341 */
6342 n = sizeof(zValue);
6343 SyMemcpy("Undefined", zValue, sizeof("Undefined")-1);
6344 /* Extract the environment value */
6345 n = GetEnvironmentVariableA(zVar, zValue, sizeof(zValue));
6346 if( !n ){
6347 /* No such variable*/
6348 return -1;
6349 }
6350 jx9_result_string(pCtx, zValue, (int)n);
6351 return JX9_OK;
6352}
6353/* int (*xSetenv)(const char *, const char *) */
6354static int WinVfs_Setenv(const char *zName, const char *zValue)
6355{
6356 BOOL rc;
6357 rc = SetEnvironmentVariableA(zName, zValue);
6358 return rc ? JX9_OK : -1;
6359}
6360/* int (*xMmap)(const char *, void **, jx9_int64 *) */
6361static int WinVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize)
6362{
6363 DWORD dwSizeLow, dwSizeHigh;
6364 HANDLE pHandle, pMapHandle;
6365 void *pConverted, *pView;
6366
6367 pConverted = jx9convertUtf8Filename(zPath);
6368 if( pConverted == 0 ){
6369 return -1;
6370 }
6371 pHandle = OpenReadOnly((LPCWSTR)pConverted);
6372 HeapFree(GetProcessHeap(), 0, pConverted);
6373 if( pHandle == 0 ){
6374 return -1;
6375 }
6376 /* Get the file size */
6377 dwSizeLow = GetFileSize(pHandle, &dwSizeHigh);
6378 /* Create the mapping */
6379 pMapHandle = CreateFileMappingW(pHandle, 0, PAGE_READONLY, dwSizeHigh, dwSizeLow, 0);
6380 if( pMapHandle == 0 ){
6381 CloseHandle(pHandle);
6382 return -1;
6383 }
6384 *pSize = ((jx9_int64)dwSizeHigh << 32) | dwSizeLow;
6385 /* Obtain the view */
6386 pView = MapViewOfFile(pMapHandle, FILE_MAP_READ, 0, 0, (SIZE_T)(*pSize));
6387 if( pView ){
6388 /* Let the upper layer point to the view */
6389 *ppMap = pView;
6390 }
6391 /* Close the handle
6392 * According to MSDN it's OK the close the HANDLES.
6393 */
6394 CloseHandle(pMapHandle);
6395 CloseHandle(pHandle);
6396 return pView ? JX9_OK : -1;
6397}
6398/* void (*xUnmap)(void *, jx9_int64) */
6399static void WinVfs_Unmap(void *pView, jx9_int64 nSize)
6400{
6401 nSize = 0; /* Compiler warning */
6402 UnmapViewOfFile(pView);
6403}
6404/* void (*xTempDir)(jx9_context *) */
6405static void WinVfs_TempDir(jx9_context *pCtx)
6406{
6407 CHAR zTemp[1024];
6408 DWORD n;
6409 n = GetTempPathA(sizeof(zTemp), zTemp);
6410 if( n < 1 ){
6411 /* Assume the default windows temp directory */
6412 jx9_result_string(pCtx, "C:\\Windows\\Temp", -1/*Compute length automatically*/);
6413 }else{
6414 jx9_result_string(pCtx, zTemp, (int)n);
6415 }
6416}
6417/* unsigned int (*xProcessId)(void) */
6418static unsigned int WinVfs_ProcessId(void)
6419{
6420 DWORD nID = 0;
6421#ifndef __MINGW32__
6422 nID = GetProcessId(GetCurrentProcess());
6423#endif /* __MINGW32__ */
6424 return (unsigned int)nID;
6425}
6426
6427/* Export the windows vfs */
6428static const jx9_vfs sWinVfs = {
6429 "Windows_vfs",
6430 JX9_VFS_VERSION,
6431 WinVfs_chdir, /* int (*xChdir)(const char *) */
6432 0, /* int (*xChroot)(const char *); */
6433 WinVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */
6434 WinVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */
6435 WinVfs_rmdir, /* int (*xRmdir)(const char *) */
6436 WinVfs_isdir, /* int (*xIsdir)(const char *) */
6437 WinVfs_Rename, /* int (*xRename)(const char *, const char *) */
6438 WinVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/
6439 WinVfs_Sleep, /* int (*xSleep)(unsigned int) */
6440 WinVfs_unlink, /* int (*xUnlink)(const char *) */
6441 WinVfs_FileExists, /* int (*xFileExists)(const char *) */
6442 0, /*int (*xChmod)(const char *, int)*/
6443 0, /*int (*xChown)(const char *, const char *)*/
6444 0, /*int (*xChgrp)(const char *, const char *)*/
6445 WinVfs_DiskFreeSpace, /* jx9_int64 (*xFreeSpace)(const char *) */
6446 WinVfs_DiskTotalSpace, /* jx9_int64 (*xTotalSpace)(const char *) */
6447 WinVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */
6448 WinVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */
6449 WinVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */
6450 WinVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */
6451 WinVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */
6452 WinVfs_Stat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */
6453 WinVfs_isfile, /* int (*xIsfile)(const char *) */
6454 WinVfs_islink, /* int (*xIslink)(const char *) */
6455 WinVfs_isfile, /* int (*xReadable)(const char *) */
6456 WinVfs_iswritable, /* int (*xWritable)(const char *) */
6457 WinVfs_isexecutable, /* int (*xExecutable)(const char *) */
6458 WinVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */
6459 WinVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */
6460 WinVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */
6461 WinVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */
6462 WinVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */
6463 WinVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */
6464 0, /* int (*xLink)(const char *, const char *, int) */
6465 0, /* int (*xUmask)(int) */
6466 WinVfs_TempDir, /* void (*xTempDir)(jx9_context *) */
6467 WinVfs_ProcessId, /* unsigned int (*xProcessId)(void) */
6468 0, /* int (*xUid)(void) */
6469 0, /* int (*xGid)(void) */
6470 0, /* void (*xUsername)(jx9_context *) */
6471 0 /* int (*xExec)(const char *, jx9_context *) */
6472};
6473/* Windows file IO */
6474#ifndef INVALID_SET_FILE_POINTER
6475# define INVALID_SET_FILE_POINTER ((DWORD)-1)
6476#endif
6477/* int (*xOpen)(const char *, int, jx9_value *, void **) */
6478static int WinFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle)
6479{
6480 DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS;
6481 DWORD dwAccess = GENERIC_READ;
6482 DWORD dwShare, dwCreate;
6483 void *pConverted;
6484 HANDLE pHandle;
6485
6486 pConverted = jx9convertUtf8Filename(zPath);
6487 if( pConverted == 0 ){
6488 return -1;
6489 }
6490 /* Set the desired flags according to the open mode */
6491 if( iOpenMode & JX9_IO_OPEN_CREATE ){
6492 /* Open existing file, or create if it doesn't exist */
6493 dwCreate = OPEN_ALWAYS;
6494 if( iOpenMode & JX9_IO_OPEN_TRUNC ){
6495 /* If the specified file exists and is writable, the function overwrites the file */
6496 dwCreate = CREATE_ALWAYS;
6497 }
6498 }else if( iOpenMode & JX9_IO_OPEN_EXCL ){
6499 /* Creates a new file, only if it does not already exist.
6500 * If the file exists, it fails.
6501 */
6502 dwCreate = CREATE_NEW;
6503 }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){
6504 /* Opens a file and truncates it so that its size is zero bytes
6505 * The file must exist.
6506 */
6507 dwCreate = TRUNCATE_EXISTING;
6508 }else{
6509 /* Opens a file, only if it exists. */
6510 dwCreate = OPEN_EXISTING;
6511 }
6512 if( iOpenMode & JX9_IO_OPEN_RDWR ){
6513 /* Read+Write access */
6514 dwAccess |= GENERIC_WRITE;
6515 }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){
6516 /* Write only access */
6517 dwAccess = GENERIC_WRITE;
6518 }
6519 if( iOpenMode & JX9_IO_OPEN_APPEND ){
6520 /* Append mode */
6521 dwAccess = FILE_APPEND_DATA;
6522 }
6523 if( iOpenMode & JX9_IO_OPEN_TEMP ){
6524 /* File is temporary */
6525 dwType = FILE_ATTRIBUTE_TEMPORARY;
6526 }
6527 dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
6528 pHandle = CreateFileW((LPCWSTR)pConverted, dwAccess, dwShare, 0, dwCreate, dwType, 0);
6529 HeapFree(GetProcessHeap(), 0, pConverted);
6530 if( pHandle == INVALID_HANDLE_VALUE){
6531 SXUNUSED(pResource); /* MSVC warning */
6532 return -1;
6533 }
6534 /* Make the handle accessible to the upper layer */
6535 *ppHandle = (void *)pHandle;
6536 return JX9_OK;
6537}
6538/* An instance of the following structure is used to record state information
6539 * while iterating throw directory entries.
6540 */
6541typedef struct WinDir_Info WinDir_Info;
6542struct WinDir_Info
6543{
6544 HANDLE pDirHandle;
6545 void *pPath;
6546 WIN32_FIND_DATAW sInfo;
6547 int rc;
6548};
6549/* int (*xOpenDir)(const char *, jx9_value *, void **) */
6550static int WinDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle)
6551{
6552 WinDir_Info *pDirInfo;
6553 void *pConverted;
6554 char *zPrep;
6555 sxu32 n;
6556 /* Prepare the path */
6557 n = SyStrlen(zPath);
6558 zPrep = (char *)HeapAlloc(GetProcessHeap(), 0, n+sizeof("\\*")+4);
6559 if( zPrep == 0 ){
6560 return -1;
6561 }
6562 SyMemcpy((const void *)zPath, zPrep, n);
6563 zPrep[n] = '\\';
6564 zPrep[n+1] = '*';
6565 zPrep[n+2] = 0;
6566 pConverted = jx9convertUtf8Filename(zPrep);
6567 HeapFree(GetProcessHeap(), 0, zPrep);
6568 if( pConverted == 0 ){
6569 return -1;
6570 }
6571 /* Allocate a new instance */
6572 pDirInfo = (WinDir_Info *)HeapAlloc(GetProcessHeap(), 0, sizeof(WinDir_Info));
6573 if( pDirInfo == 0 ){
6574 pResource = 0; /* Compiler warning */
6575 return -1;
6576 }
6577 pDirInfo->rc = SXRET_OK;
6578 pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pConverted, &pDirInfo->sInfo);
6579 if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){
6580 /* Cannot open directory */
6581 HeapFree(GetProcessHeap(), 0, pConverted);
6582 HeapFree(GetProcessHeap(), 0, pDirInfo);
6583 return -1;
6584 }
6585 /* Save the path */
6586 pDirInfo->pPath = pConverted;
6587 /* Save our structure */
6588 *ppHandle = pDirInfo;
6589 return JX9_OK;
6590}
6591/* void (*xCloseDir)(void *) */
6592static void WinDir_Close(void *pUserData)
6593{
6594 WinDir_Info *pDirInfo = (WinDir_Info *)pUserData;
6595 if( pDirInfo->pDirHandle != INVALID_HANDLE_VALUE ){
6596 FindClose(pDirInfo->pDirHandle);
6597 }
6598 HeapFree(GetProcessHeap(), 0, pDirInfo->pPath);
6599 HeapFree(GetProcessHeap(), 0, pDirInfo);
6600}
6601/* void (*xClose)(void *); */
6602static void WinFile_Close(void *pUserData)
6603{
6604 HANDLE pHandle = (HANDLE)pUserData;
6605 CloseHandle(pHandle);
6606}
6607/* int (*xReadDir)(void *, jx9_context *) */
6608static int WinDir_Read(void *pUserData, jx9_context *pCtx)
6609{
6610 WinDir_Info *pDirInfo = (WinDir_Info *)pUserData;
6611 LPWIN32_FIND_DATAW pData;
6612 char *zName;
6613 BOOL rc;
6614 sxu32 n;
6615 if( pDirInfo->rc != SXRET_OK ){
6616 /* No more entry to process */
6617 return -1;
6618 }
6619 pData = &pDirInfo->sInfo;
6620 for(;;){
6621 zName = jx9unicodeToUtf8(pData->cFileName);
6622 if( zName == 0 ){
6623 /* Out of memory */
6624 return -1;
6625 }
6626 n = SyStrlen(zName);
6627 /* Ignore '.' && '..' */
6628 if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){
6629 break;
6630 }
6631 HeapFree(GetProcessHeap(), 0, zName);
6632 rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo);
6633 if( !rc ){
6634 return -1;
6635 }
6636 }
6637 /* Return the current file name */
6638 jx9_result_string(pCtx, zName, -1);
6639 HeapFree(GetProcessHeap(), 0, zName);
6640 /* Point to the next entry */
6641 rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo);
6642 if( !rc ){
6643 pDirInfo->rc = SXERR_EOF;
6644 }
6645 return JX9_OK;
6646}
6647/* void (*xRewindDir)(void *) */
6648static void WinDir_RewindDir(void *pUserData)
6649{
6650 WinDir_Info *pDirInfo = (WinDir_Info *)pUserData;
6651 FindClose(pDirInfo->pDirHandle);
6652 pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pDirInfo->pPath, &pDirInfo->sInfo);
6653 if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){
6654 pDirInfo->rc = SXERR_EOF;
6655 }else{
6656 pDirInfo->rc = SXRET_OK;
6657 }
6658}
6659/* jx9_int64 (*xRead)(void *, void *, jx9_int64); */
6660static jx9_int64 WinFile_Read(void *pOS, void *pBuffer, jx9_int64 nDatatoRead)
6661{
6662 HANDLE pHandle = (HANDLE)pOS;
6663 DWORD nRd;
6664 BOOL rc;
6665 rc = ReadFile(pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0);
6666 if( !rc ){
6667 /* EOF or IO error */
6668 return -1;
6669 }
6670 return (jx9_int64)nRd;
6671}
6672/* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */
6673static jx9_int64 WinFile_Write(void *pOS, const void *pBuffer, jx9_int64 nWrite)
6674{
6675 const char *zData = (const char *)pBuffer;
6676 HANDLE pHandle = (HANDLE)pOS;
6677 jx9_int64 nCount;
6678 DWORD nWr;
6679 BOOL rc;
6680 nWr = 0;
6681 nCount = 0;
6682 for(;;){
6683 if( nWrite < 1 ){
6684 break;
6685 }
6686 rc = WriteFile(pHandle, zData, (DWORD)nWrite, &nWr, 0);
6687 if( !rc ){
6688 /* IO error */
6689 break;
6690 }
6691 nWrite -= nWr;
6692 nCount += nWr;
6693 zData += nWr;
6694 }
6695 if( nWrite > 0 ){
6696 return -1;
6697 }
6698 return nCount;
6699}
6700/* int (*xSeek)(void *, jx9_int64, int) */
6701static int WinFile_Seek(void *pUserData, jx9_int64 iOfft, int whence)
6702{
6703 HANDLE pHandle = (HANDLE)pUserData;
6704 DWORD dwMove, dwNew;
6705 LONG nHighOfft;
6706 switch(whence){
6707 case 1:/*SEEK_CUR*/
6708 dwMove = FILE_CURRENT;
6709 break;
6710 case 2: /* SEEK_END */
6711 dwMove = FILE_END;
6712 break;
6713 case 0: /* SEEK_SET */
6714 default:
6715 dwMove = FILE_BEGIN;
6716 break;
6717 }
6718 nHighOfft = (LONG)(iOfft >> 32);
6719 dwNew = SetFilePointer(pHandle, (LONG)iOfft, &nHighOfft, dwMove);
6720 if( dwNew == INVALID_SET_FILE_POINTER ){
6721 return -1;
6722 }
6723 return JX9_OK;
6724}
6725/* int (*xLock)(void *, int) */
6726static int WinFile_Lock(void *pUserData, int lock_type)
6727{
6728 HANDLE pHandle = (HANDLE)pUserData;
6729 static DWORD dwLo = 0, dwHi = 0; /* xx: MT-SAFE */
6730 OVERLAPPED sDummy;
6731 BOOL rc;
6732 SyZero(&sDummy, sizeof(sDummy));
6733 /* Get the file size */
6734 if( lock_type < 1 ){
6735 /* Unlock the file */
6736 rc = UnlockFileEx(pHandle, 0, dwLo, dwHi, &sDummy);
6737 }else{
6738 DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; /* Shared non-blocking lock by default*/
6739 /* Lock the file */
6740 if( lock_type == 1 /* LOCK_EXCL */ ){
6741 dwFlags |= LOCKFILE_EXCLUSIVE_LOCK;
6742 }
6743 dwLo = GetFileSize(pHandle, &dwHi);
6744 rc = LockFileEx(pHandle, dwFlags, 0, dwLo, dwHi, &sDummy);
6745 }
6746 return rc ? JX9_OK : -1 /* Lock error */;
6747}
6748/* jx9_int64 (*xTell)(void *) */
6749static jx9_int64 WinFile_Tell(void *pUserData)
6750{
6751 HANDLE pHandle = (HANDLE)pUserData;
6752 DWORD dwNew;
6753 dwNew = SetFilePointer(pHandle, 0, 0, FILE_CURRENT/* SEEK_CUR */);
6754 if( dwNew == INVALID_SET_FILE_POINTER ){
6755 return -1;
6756 }
6757 return (jx9_int64)dwNew;
6758}
6759/* int (*xTrunc)(void *, jx9_int64) */
6760static int WinFile_Trunc(void *pUserData, jx9_int64 nOfft)
6761{
6762 HANDLE pHandle = (HANDLE)pUserData;
6763 LONG HighOfft;
6764 DWORD dwNew;
6765 BOOL rc;
6766 HighOfft = (LONG)(nOfft >> 32);
6767 dwNew = SetFilePointer(pHandle, (LONG)nOfft, &HighOfft, FILE_BEGIN);
6768 if( dwNew == INVALID_SET_FILE_POINTER ){
6769 return -1;
6770 }
6771 rc = SetEndOfFile(pHandle);
6772 return rc ? JX9_OK : -1;
6773}
6774/* int (*xSync)(void *); */
6775static int WinFile_Sync(void *pUserData)
6776{
6777 HANDLE pHandle = (HANDLE)pUserData;
6778 BOOL rc;
6779 rc = FlushFileBuffers(pHandle);
6780 return rc ? JX9_OK : - 1;
6781}
6782/* int (*xStat)(void *, jx9_value *, jx9_value *) */
6783static int WinFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker)
6784{
6785 BY_HANDLE_FILE_INFORMATION sInfo;
6786 HANDLE pHandle = (HANDLE)pUserData;
6787 BOOL rc;
6788 rc = GetFileInformationByHandle(pHandle, &sInfo);
6789 if( !rc ){
6790 return -1;
6791 }
6792 /* dev */
6793 jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber);
6794 jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */
6795 /* ino */
6796 jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow));
6797 jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */
6798 /* mode */
6799 jx9_value_int(pWorker, 0);
6800 jx9_array_add_strkey_elem(pArray, "mode", pWorker);
6801 /* nlink */
6802 jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks);
6803 jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */
6804 /* uid, gid, rdev */
6805 jx9_value_int(pWorker, 0);
6806 jx9_array_add_strkey_elem(pArray, "uid", pWorker);
6807 jx9_array_add_strkey_elem(pArray, "gid", pWorker);
6808 jx9_array_add_strkey_elem(pArray, "rdev", pWorker);
6809 /* size */
6810 jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow));
6811 jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */
6812 /* atime */
6813 jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime));
6814 jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */
6815 /* mtime */
6816 jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime));
6817 jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */
6818 /* ctime */
6819 jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime));
6820 jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */
6821 /* blksize, blocks */
6822 jx9_value_int(pWorker, 0);
6823 jx9_array_add_strkey_elem(pArray, "blksize", pWorker);
6824 jx9_array_add_strkey_elem(pArray, "blocks", pWorker);
6825 return JX9_OK;
6826}
6827/* Export the file:// stream */
6828static const jx9_io_stream sWinFileStream = {
6829 "file", /* Stream name */
6830 JX9_IO_STREAM_VERSION,
6831 WinFile_Open, /* xOpen */
6832 WinDir_Open, /* xOpenDir */
6833 WinFile_Close, /* xClose */
6834 WinDir_Close, /* xCloseDir */
6835 WinFile_Read, /* xRead */
6836 WinDir_Read, /* xReadDir */
6837 WinFile_Write, /* xWrite */
6838 WinFile_Seek, /* xSeek */
6839 WinFile_Lock, /* xLock */
6840 WinDir_RewindDir, /* xRewindDir */
6841 WinFile_Tell, /* xTell */
6842 WinFile_Trunc, /* xTrunc */
6843 WinFile_Sync, /* xSeek */
6844 WinFile_Stat /* xStat */
6845};
6846#elif defined(__UNIXES__)
6847/*
6848 * UNIX VFS implementation for the JX9 engine.
6849 * Authors:
6850 * Symisc Systems, devel@symisc.net.
6851 * Copyright (C) Symisc Systems, http://jx9.symisc.net
6852 * Status:
6853 * Stable.
6854 */
6855#include <sys/types.h>
6856#include <limits.h>
6857#include <fcntl.h>
6858#include <unistd.h>
6859#include <sys/uio.h>
6860#include <sys/stat.h>
6861#include <sys/mman.h>
6862#include <sys/file.h>
6863#include <pwd.h>
6864#include <grp.h>
6865#include <dirent.h>
6866#include <utime.h>
6867#include <stdio.h>
6868#include <stdlib.h>
6869/* int (*xchdir)(const char *) */
6870static int UnixVfs_chdir(const char *zPath)
6871{
6872 int rc;
6873 rc = chdir(zPath);
6874 return rc == 0 ? JX9_OK : -1;
6875}
6876/* int (*xGetcwd)(jx9_context *) */
6877static int UnixVfs_getcwd(jx9_context *pCtx)
6878{
6879 char zBuf[4096];
6880 char *zDir;
6881 /* Get the current directory */
6882 zDir = getcwd(zBuf, sizeof(zBuf));
6883 if( zDir == 0 ){
6884 return -1;
6885 }
6886 jx9_result_string(pCtx, zDir, -1/*Compute length automatically*/);
6887 return JX9_OK;
6888}
6889/* int (*xMkdir)(const char *, int, int) */
6890static int UnixVfs_mkdir(const char *zPath, int mode, int recursive)
6891{
6892 int rc;
6893 rc = mkdir(zPath, mode);
6894 recursive = 0; /* cc warning */
6895 return rc == 0 ? JX9_OK : -1;
6896}
6897/* int (*xRmdir)(const char *) */
6898static int UnixVfs_rmdir(const char *zPath)
6899{
6900 int rc;
6901 rc = rmdir(zPath);
6902 return rc == 0 ? JX9_OK : -1;
6903}
6904/* int (*xIsdir)(const char *) */
6905static int UnixVfs_isdir(const char *zPath)
6906{
6907 struct stat st;
6908 int rc;
6909 rc = stat(zPath, &st);
6910 if( rc != 0 ){
6911 return -1;
6912 }
6913 rc = S_ISDIR(st.st_mode);
6914 return rc ? JX9_OK : -1 ;
6915}
6916/* int (*xRename)(const char *, const char *) */
6917static int UnixVfs_Rename(const char *zOld, const char *zNew)
6918{
6919 int rc;
6920 rc = rename(zOld, zNew);
6921 return rc == 0 ? JX9_OK : -1;
6922}
6923/* int (*xRealpath)(const char *, jx9_context *) */
6924static int UnixVfs_Realpath(const char *zPath, jx9_context *pCtx)
6925{
6926#ifndef JX9_UNIX_OLD_LIBC
6927 char *zReal;
6928 zReal = realpath(zPath, 0);
6929 if( zReal == 0 ){
6930 return -1;
6931 }
6932 jx9_result_string(pCtx, zReal, -1/*Compute length automatically*/);
6933 /* Release the allocated buffer */
6934 free(zReal);
6935 return JX9_OK;
6936#else
6937 zPath = 0; /* cc warning */
6938 pCtx = 0;
6939 return -1;
6940#endif
6941}
6942/* int (*xSleep)(unsigned int) */
6943static int UnixVfs_Sleep(unsigned int uSec)
6944{
6945 usleep(uSec);
6946 return JX9_OK;
6947}
6948/* int (*xUnlink)(const char *) */
6949static int UnixVfs_unlink(const char *zPath)
6950{
6951 int rc;
6952 rc = unlink(zPath);
6953 return rc == 0 ? JX9_OK : -1 ;
6954}
6955/* int (*xFileExists)(const char *) */
6956static int UnixVfs_FileExists(const char *zPath)
6957{
6958 int rc;
6959 rc = access(zPath, F_OK);
6960 return rc == 0 ? JX9_OK : -1;
6961}
6962/* jx9_int64 (*xFileSize)(const char *) */
6963static jx9_int64 UnixVfs_FileSize(const char *zPath)
6964{
6965 struct stat st;
6966 int rc;
6967 rc = stat(zPath, &st);
6968 if( rc != 0 ){
6969 return -1;
6970 }
6971 return (jx9_int64)st.st_size;
6972}
6973/* int (*xTouch)(const char *, jx9_int64, jx9_int64) */
6974static int UnixVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time)
6975{
6976 struct utimbuf ut;
6977 int rc;
6978 ut.actime = (time_t)access_time;
6979 ut.modtime = (time_t)touch_time;
6980 rc = utime(zPath, &ut);
6981 if( rc != 0 ){
6982 return -1;
6983 }
6984 return JX9_OK;
6985}
6986/* jx9_int64 (*xFileAtime)(const char *) */
6987static jx9_int64 UnixVfs_FileAtime(const char *zPath)
6988{
6989 struct stat st;
6990 int rc;
6991 rc = stat(zPath, &st);
6992 if( rc != 0 ){
6993 return -1;
6994 }
6995 return (jx9_int64)st.st_atime;
6996}
6997/* jx9_int64 (*xFileMtime)(const char *) */
6998static jx9_int64 UnixVfs_FileMtime(const char *zPath)
6999{
7000 struct stat st;
7001 int rc;
7002 rc = stat(zPath, &st);
7003 if( rc != 0 ){
7004 return -1;
7005 }
7006 return (jx9_int64)st.st_mtime;
7007}
7008/* jx9_int64 (*xFileCtime)(const char *) */
7009static jx9_int64 UnixVfs_FileCtime(const char *zPath)
7010{
7011 struct stat st;
7012 int rc;
7013 rc = stat(zPath, &st);
7014 if( rc != 0 ){
7015 return -1;
7016 }
7017 return (jx9_int64)st.st_ctime;
7018}
7019/* int (*xStat)(const char *, jx9_value *, jx9_value *) */
7020static int UnixVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker)
7021{
7022 struct stat st;
7023 int rc;
7024 rc = stat(zPath, &st);
7025 if( rc != 0 ){
7026 return -1;
7027 }
7028 /* dev */
7029 jx9_value_int64(pWorker, (jx9_int64)st.st_dev);
7030 jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */
7031 /* ino */
7032 jx9_value_int64(pWorker, (jx9_int64)st.st_ino);
7033 jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */
7034 /* mode */
7035 jx9_value_int(pWorker, (int)st.st_mode);
7036 jx9_array_add_strkey_elem(pArray, "mode", pWorker);
7037 /* nlink */
7038 jx9_value_int(pWorker, (int)st.st_nlink);
7039 jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */
7040 /* uid, gid, rdev */
7041 jx9_value_int(pWorker, (int)st.st_uid);
7042 jx9_array_add_strkey_elem(pArray, "uid", pWorker);
7043 jx9_value_int(pWorker, (int)st.st_gid);
7044 jx9_array_add_strkey_elem(pArray, "gid", pWorker);
7045 jx9_value_int(pWorker, (int)st.st_rdev);
7046 jx9_array_add_strkey_elem(pArray, "rdev", pWorker);
7047 /* size */
7048 jx9_value_int64(pWorker, (jx9_int64)st.st_size);
7049 jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */
7050 /* atime */
7051 jx9_value_int64(pWorker, (jx9_int64)st.st_atime);
7052 jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */
7053 /* mtime */
7054 jx9_value_int64(pWorker, (jx9_int64)st.st_mtime);
7055 jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */
7056 /* ctime */
7057 jx9_value_int64(pWorker, (jx9_int64)st.st_ctime);
7058 jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */
7059 /* blksize, blocks */
7060 jx9_value_int(pWorker, (int)st.st_blksize);
7061 jx9_array_add_strkey_elem(pArray, "blksize", pWorker);
7062 jx9_value_int(pWorker, (int)st.st_blocks);
7063 jx9_array_add_strkey_elem(pArray, "blocks", pWorker);
7064 return JX9_OK;
7065}
7066/* int (*xlStat)(const char *, jx9_value *, jx9_value *) */
7067static int UnixVfs_lStat(const char *zPath, jx9_value *pArray, jx9_value *pWorker)
7068{
7069 struct stat st;
7070 int rc;
7071 rc = lstat(zPath, &st);
7072 if( rc != 0 ){
7073 return -1;
7074 }
7075 /* dev */
7076 jx9_value_int64(pWorker, (jx9_int64)st.st_dev);
7077 jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */
7078 /* ino */
7079 jx9_value_int64(pWorker, (jx9_int64)st.st_ino);
7080 jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */
7081 /* mode */
7082 jx9_value_int(pWorker, (int)st.st_mode);
7083 jx9_array_add_strkey_elem(pArray, "mode", pWorker);
7084 /* nlink */
7085 jx9_value_int(pWorker, (int)st.st_nlink);
7086 jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */
7087 /* uid, gid, rdev */
7088 jx9_value_int(pWorker, (int)st.st_uid);
7089 jx9_array_add_strkey_elem(pArray, "uid", pWorker);
7090 jx9_value_int(pWorker, (int)st.st_gid);
7091 jx9_array_add_strkey_elem(pArray, "gid", pWorker);
7092 jx9_value_int(pWorker, (int)st.st_rdev);
7093 jx9_array_add_strkey_elem(pArray, "rdev", pWorker);
7094 /* size */
7095 jx9_value_int64(pWorker, (jx9_int64)st.st_size);
7096 jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */
7097 /* atime */
7098 jx9_value_int64(pWorker, (jx9_int64)st.st_atime);
7099 jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */
7100 /* mtime */
7101 jx9_value_int64(pWorker, (jx9_int64)st.st_mtime);
7102 jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */
7103 /* ctime */
7104 jx9_value_int64(pWorker, (jx9_int64)st.st_ctime);
7105 jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */
7106 /* blksize, blocks */
7107 jx9_value_int(pWorker, (int)st.st_blksize);
7108 jx9_array_add_strkey_elem(pArray, "blksize", pWorker);
7109 jx9_value_int(pWorker, (int)st.st_blocks);
7110 jx9_array_add_strkey_elem(pArray, "blocks", pWorker);
7111 return JX9_OK;
7112}
7113/* int (*xChmod)(const char *, int) */
7114static int UnixVfs_Chmod(const char *zPath, int mode)
7115{
7116 int rc;
7117 rc = chmod(zPath, (mode_t)mode);
7118 return rc == 0 ? JX9_OK : - 1;
7119}
7120/* int (*xChown)(const char *, const char *) */
7121static int UnixVfs_Chown(const char *zPath, const char *zUser)
7122{
7123#ifndef JX9_UNIX_STATIC_BUILD
7124 struct passwd *pwd;
7125 uid_t uid;
7126 int rc;
7127 pwd = getpwnam(zUser); /* Try getting UID for username */
7128 if (pwd == 0) {
7129 return -1;
7130 }
7131 uid = pwd->pw_uid;
7132 rc = chown(zPath, uid, -1);
7133 return rc == 0 ? JX9_OK : -1;
7134#else
7135 SXUNUSED(zPath);
7136 SXUNUSED(zUser);
7137 return -1;
7138#endif /* JX9_UNIX_STATIC_BUILD */
7139}
7140/* int (*xChgrp)(const char *, const char *) */
7141static int UnixVfs_Chgrp(const char *zPath, const char *zGroup)
7142{
7143#ifndef JX9_UNIX_STATIC_BUILD
7144 struct group *group;
7145 gid_t gid;
7146 int rc;
7147 group = getgrnam(zGroup);
7148 if (group == 0) {
7149 return -1;
7150 }
7151 gid = group->gr_gid;
7152 rc = chown(zPath, -1, gid);
7153 return rc == 0 ? JX9_OK : -1;
7154#else
7155 SXUNUSED(zPath);
7156 SXUNUSED(zGroup);
7157 return -1;
7158#endif /* JX9_UNIX_STATIC_BUILD */
7159}
7160/* int (*xIsfile)(const char *) */
7161static int UnixVfs_isfile(const char *zPath)
7162{
7163 struct stat st;
7164 int rc;
7165 rc = stat(zPath, &st);
7166 if( rc != 0 ){
7167 return -1;
7168 }
7169 rc = S_ISREG(st.st_mode);
7170 return rc ? JX9_OK : -1 ;
7171}
7172/* int (*xIslink)(const char *) */
7173static int UnixVfs_islink(const char *zPath)
7174{
7175 struct stat st;
7176 int rc;
7177 rc = stat(zPath, &st);
7178 if( rc != 0 ){
7179 return -1;
7180 }
7181 rc = S_ISLNK(st.st_mode);
7182 return rc ? JX9_OK : -1 ;
7183}
7184/* int (*xReadable)(const char *) */
7185static int UnixVfs_isreadable(const char *zPath)
7186{
7187 int rc;
7188 rc = access(zPath, R_OK);
7189 return rc == 0 ? JX9_OK : -1;
7190}
7191/* int (*xWritable)(const char *) */
7192static int UnixVfs_iswritable(const char *zPath)
7193{
7194 int rc;
7195 rc = access(zPath, W_OK);
7196 return rc == 0 ? JX9_OK : -1;
7197}
7198/* int (*xExecutable)(const char *) */
7199static int UnixVfs_isexecutable(const char *zPath)
7200{
7201 int rc;
7202 rc = access(zPath, X_OK);
7203 return rc == 0 ? JX9_OK : -1;
7204}
7205/* int (*xFiletype)(const char *, jx9_context *) */
7206static int UnixVfs_Filetype(const char *zPath, jx9_context *pCtx)
7207{
7208 struct stat st;
7209 int rc;
7210 rc = stat(zPath, &st);
7211 if( rc != 0 ){
7212 /* Expand 'unknown' */
7213 jx9_result_string(pCtx, "unknown", sizeof("unknown")-1);
7214 return -1;
7215 }
7216 if(S_ISREG(st.st_mode) ){
7217 jx9_result_string(pCtx, "file", sizeof("file")-1);
7218 }else if(S_ISDIR(st.st_mode)){
7219 jx9_result_string(pCtx, "dir", sizeof("dir")-1);
7220 }else if(S_ISLNK(st.st_mode)){
7221 jx9_result_string(pCtx, "link", sizeof("link")-1);
7222 }else if(S_ISBLK(st.st_mode)){
7223 jx9_result_string(pCtx, "block", sizeof("block")-1);
7224 }else if(S_ISSOCK(st.st_mode)){
7225 jx9_result_string(pCtx, "socket", sizeof("socket")-1);
7226 }else if(S_ISFIFO(st.st_mode)){
7227 jx9_result_string(pCtx, "fifo", sizeof("fifo")-1);
7228 }else{
7229 jx9_result_string(pCtx, "unknown", sizeof("unknown")-1);
7230 }
7231 return JX9_OK;
7232}
7233/* int (*xGetenv)(const char *, jx9_context *) */
7234static int UnixVfs_Getenv(const char *zVar, jx9_context *pCtx)
7235{
7236 char *zEnv;
7237 zEnv = getenv(zVar);
7238 if( zEnv == 0 ){
7239 return -1;
7240 }
7241 jx9_result_string(pCtx, zEnv, -1/*Compute length automatically*/);
7242 return JX9_OK;
7243}
7244/* int (*xSetenv)(const char *, const char *) */
7245static int UnixVfs_Setenv(const char *zName, const char *zValue)
7246{
7247 int rc;
7248 rc = setenv(zName, zValue, 1);
7249 return rc == 0 ? JX9_OK : -1;
7250}
7251/* int (*xMmap)(const char *, void **, jx9_int64 *) */
7252static int UnixVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize)
7253{
7254 struct stat st;
7255 void *pMap;
7256 int fd;
7257 int rc;
7258 /* Open the file in a read-only mode */
7259 fd = open(zPath, O_RDONLY);
7260 if( fd < 0 ){
7261 return -1;
7262 }
7263 /* stat the handle */
7264 fstat(fd, &st);
7265 /* Obtain a memory view of the whole file */
7266 pMap = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE|MAP_FILE, fd, 0);
7267 rc = JX9_OK;
7268 if( pMap == MAP_FAILED ){
7269 rc = -1;
7270 }else{
7271 /* Point to the memory view */
7272 *ppMap = pMap;
7273 *pSize = (jx9_int64)st.st_size;
7274 }
7275 close(fd);
7276 return rc;
7277}
7278/* void (*xUnmap)(void *, jx9_int64) */
7279static void UnixVfs_Unmap(void *pView, jx9_int64 nSize)
7280{
7281 munmap(pView, (size_t)nSize);
7282}
7283/* void (*xTempDir)(jx9_context *) */
7284static void UnixVfs_TempDir(jx9_context *pCtx)
7285{
7286 static const char *azDirs[] = {
7287 "/var/tmp",
7288 "/usr/tmp",
7289 "/usr/local/tmp"
7290 };
7291 unsigned int i;
7292 struct stat buf;
7293 const char *zDir;
7294 zDir = getenv("TMPDIR");
7295 if( zDir && zDir[0] != 0 && !access(zDir, 07) ){
7296 jx9_result_string(pCtx, zDir, -1);
7297 return;
7298 }
7299 for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); i++){
7300 zDir=azDirs[i];
7301 if( zDir==0 ) continue;
7302 if( stat(zDir, &buf) ) continue;
7303 if( !S_ISDIR(buf.st_mode) ) continue;
7304 if( access(zDir, 07) ) continue;
7305 /* Got one */
7306 jx9_result_string(pCtx, zDir, -1);
7307 return;
7308 }
7309 /* Default temp dir */
7310 jx9_result_string(pCtx, "/tmp", (int)sizeof("/tmp")-1);
7311}
7312/* unsigned int (*xProcessId)(void) */
7313static unsigned int UnixVfs_ProcessId(void)
7314{
7315 return (unsigned int)getpid();
7316}
7317/* int (*xUid)(void) */
7318static int UnixVfs_uid(void)
7319{
7320 return (int)getuid();
7321}
7322/* int (*xGid)(void) */
7323static int UnixVfs_gid(void)
7324{
7325 return (int)getgid();
7326}
7327/* int (*xUmask)(int) */
7328static int UnixVfs_Umask(int new_mask)
7329{
7330 int old_mask;
7331 old_mask = umask(new_mask);
7332 return old_mask;
7333}
7334/* void (*xUsername)(jx9_context *) */
7335static void UnixVfs_Username(jx9_context *pCtx)
7336{
7337#ifndef JX9_UNIX_STATIC_BUILD
7338 struct passwd *pwd;
7339 uid_t uid;
7340 uid = getuid();
7341 pwd = getpwuid(uid); /* Try getting UID for username */
7342 if (pwd == 0) {
7343 return;
7344 }
7345 /* Return the username */
7346 jx9_result_string(pCtx, pwd->pw_name, -1);
7347#else
7348 jx9_result_string(pCtx, "Unknown", -1);
7349#endif /* JX9_UNIX_STATIC_BUILD */
7350 return;
7351}
7352/* int (*xLink)(const char *, const char *, int) */
7353static int UnixVfs_link(const char *zSrc, const char *zTarget, int is_sym)
7354{
7355 int rc;
7356 if( is_sym ){
7357 /* Symbolic link */
7358 rc = symlink(zSrc, zTarget);
7359 }else{
7360 /* Hard link */
7361 rc = link(zSrc, zTarget);
7362 }
7363 return rc == 0 ? JX9_OK : -1;
7364}
7365/* int (*xChroot)(const char *) */
7366static int UnixVfs_chroot(const char *zRootDir)
7367{
7368 int rc;
7369 rc = chroot(zRootDir);
7370 return rc == 0 ? JX9_OK : -1;
7371}
7372/* Export the UNIX vfs */
7373static const jx9_vfs sUnixVfs = {
7374 "Unix_vfs",
7375 JX9_VFS_VERSION,
7376 UnixVfs_chdir, /* int (*xChdir)(const char *) */
7377 UnixVfs_chroot, /* int (*xChroot)(const char *); */
7378 UnixVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */
7379 UnixVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */
7380 UnixVfs_rmdir, /* int (*xRmdir)(const char *) */
7381 UnixVfs_isdir, /* int (*xIsdir)(const char *) */
7382 UnixVfs_Rename, /* int (*xRename)(const char *, const char *) */
7383 UnixVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/
7384 UnixVfs_Sleep, /* int (*xSleep)(unsigned int) */
7385 UnixVfs_unlink, /* int (*xUnlink)(const char *) */
7386 UnixVfs_FileExists, /* int (*xFileExists)(const char *) */
7387 UnixVfs_Chmod, /*int (*xChmod)(const char *, int)*/
7388 UnixVfs_Chown, /*int (*xChown)(const char *, const char *)*/
7389 UnixVfs_Chgrp, /*int (*xChgrp)(const char *, const char *)*/
7390 0, /* jx9_int64 (*xFreeSpace)(const char *) */
7391 0, /* jx9_int64 (*xTotalSpace)(const char *) */
7392 UnixVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */
7393 UnixVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */
7394 UnixVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */
7395 UnixVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */
7396 UnixVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */
7397 UnixVfs_lStat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */
7398 UnixVfs_isfile, /* int (*xIsfile)(const char *) */
7399 UnixVfs_islink, /* int (*xIslink)(const char *) */
7400 UnixVfs_isreadable, /* int (*xReadable)(const char *) */
7401 UnixVfs_iswritable, /* int (*xWritable)(const char *) */
7402 UnixVfs_isexecutable, /* int (*xExecutable)(const char *) */
7403 UnixVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */
7404 UnixVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */
7405 UnixVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */
7406 UnixVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */
7407 UnixVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */
7408 UnixVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */
7409 UnixVfs_link, /* int (*xLink)(const char *, const char *, int) */
7410 UnixVfs_Umask, /* int (*xUmask)(int) */
7411 UnixVfs_TempDir, /* void (*xTempDir)(jx9_context *) */
7412 UnixVfs_ProcessId, /* unsigned int (*xProcessId)(void) */
7413 UnixVfs_uid, /* int (*xUid)(void) */
7414 UnixVfs_gid, /* int (*xGid)(void) */
7415 UnixVfs_Username, /* void (*xUsername)(jx9_context *) */
7416 0 /* int (*xExec)(const char *, jx9_context *) */
7417};
7418/* UNIX File IO */
7419#define JX9_UNIX_OPEN_MODE 0640 /* Default open mode */
7420/* int (*xOpen)(const char *, int, jx9_value *, void **) */
7421static int UnixFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle)
7422{
7423 int iOpen = O_RDONLY;
7424 int fd;
7425 /* Set the desired flags according to the open mode */
7426 if( iOpenMode & JX9_IO_OPEN_CREATE ){
7427 /* Open existing file, or create if it doesn't exist */
7428 iOpen = O_CREAT;
7429 if( iOpenMode & JX9_IO_OPEN_TRUNC ){
7430 /* If the specified file exists and is writable, the function overwrites the file */
7431 iOpen |= O_TRUNC;
7432 SXUNUSED(pResource); /* cc warning */
7433 }
7434 }else if( iOpenMode & JX9_IO_OPEN_EXCL ){
7435 /* Creates a new file, only if it does not already exist.
7436 * If the file exists, it fails.
7437 */
7438 iOpen = O_CREAT|O_EXCL;
7439 }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){
7440 /* Opens a file and truncates it so that its size is zero bytes
7441 * The file must exist.
7442 */
7443 iOpen = O_RDWR|O_TRUNC;
7444 }
7445 if( iOpenMode & JX9_IO_OPEN_RDWR ){
7446 /* Read+Write access */
7447 iOpen &= ~O_RDONLY;
7448 iOpen |= O_RDWR;
7449 }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){
7450 /* Write only access */
7451 iOpen &= ~O_RDONLY;
7452 iOpen |= O_WRONLY;
7453 }
7454 if( iOpenMode & JX9_IO_OPEN_APPEND ){
7455 /* Append mode */
7456 iOpen |= O_APPEND;
7457 }
7458#ifdef O_TEMP
7459 if( iOpenMode & JX9_IO_OPEN_TEMP ){
7460 /* File is temporary */
7461 iOpen |= O_TEMP;
7462 }
7463#endif
7464 /* Open the file now */
7465 fd = open(zPath, iOpen, JX9_UNIX_OPEN_MODE);
7466 if( fd < 0 ){
7467 /* IO error */
7468 return -1;
7469 }
7470 /* Save the handle */
7471 *ppHandle = SX_INT_TO_PTR(fd);
7472 return JX9_OK;
7473}
7474/* int (*xOpenDir)(const char *, jx9_value *, void **) */
7475static int UnixDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle)
7476{
7477 DIR *pDir;
7478 /* Open the target directory */
7479 pDir = opendir(zPath);
7480 if( pDir == 0 ){
7481 pResource = 0; /* Compiler warning */
7482 return -1;
7483 }
7484 /* Save our structure */
7485 *ppHandle = pDir;
7486 return JX9_OK;
7487}
7488/* void (*xCloseDir)(void *) */
7489static void UnixDir_Close(void *pUserData)
7490{
7491 closedir((DIR *)pUserData);
7492}
7493/* void (*xClose)(void *); */
7494static void UnixFile_Close(void *pUserData)
7495{
7496 close(SX_PTR_TO_INT(pUserData));
7497}
7498/* int (*xReadDir)(void *, jx9_context *) */
7499static int UnixDir_Read(void *pUserData, jx9_context *pCtx)
7500{
7501 DIR *pDir = (DIR *)pUserData;
7502 struct dirent *pEntry;
7503 char *zName = 0; /* cc warning */
7504 sxu32 n = 0;
7505 for(;;){
7506 pEntry = readdir(pDir);
7507 if( pEntry == 0 ){
7508 /* No more entries to process */
7509 return -1;
7510 }
7511 zName = pEntry->d_name;
7512 n = SyStrlen(zName);
7513 /* Ignore '.' && '..' */
7514 if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){
7515 break;
7516 }
7517 /* Next entry */
7518 }
7519 /* Return the current file name */
7520 jx9_result_string(pCtx, zName, (int)n);
7521 return JX9_OK;
7522}
7523/* void (*xRewindDir)(void *) */
7524static void UnixDir_Rewind(void *pUserData)
7525{
7526 rewinddir((DIR *)pUserData);
7527}
7528/* jx9_int64 (*xRead)(void *, void *, jx9_int64); */
7529static jx9_int64 UnixFile_Read(void *pUserData, void *pBuffer, jx9_int64 nDatatoRead)
7530{
7531 ssize_t nRd;
7532 nRd = read(SX_PTR_TO_INT(pUserData), pBuffer, (size_t)nDatatoRead);
7533 if( nRd < 1 ){
7534 /* EOF or IO error */
7535 return -1;
7536 }
7537 return (jx9_int64)nRd;
7538}
7539/* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */
7540static jx9_int64 UnixFile_Write(void *pUserData, const void *pBuffer, jx9_int64 nWrite)
7541{
7542 const char *zData = (const char *)pBuffer;
7543 int fd = SX_PTR_TO_INT(pUserData);
7544 jx9_int64 nCount;
7545 ssize_t nWr;
7546 nCount = 0;
7547 for(;;){
7548 if( nWrite < 1 ){
7549 break;
7550 }
7551 nWr = write(fd, zData, (size_t)nWrite);
7552 if( nWr < 1 ){
7553 /* IO error */
7554 break;
7555 }
7556 nWrite -= nWr;
7557 nCount += nWr;
7558 zData += nWr;
7559 }
7560 if( nWrite > 0 ){
7561 return -1;
7562 }
7563 return nCount;
7564}
7565/* int (*xSeek)(void *, jx9_int64, int) */
7566static int UnixFile_Seek(void *pUserData, jx9_int64 iOfft, int whence)
7567{
7568 off_t iNew;
7569 switch(whence){
7570 case 1:/*SEEK_CUR*/
7571 whence = SEEK_CUR;
7572 break;
7573 case 2: /* SEEK_END */
7574 whence = SEEK_END;
7575 break;
7576 case 0: /* SEEK_SET */
7577 default:
7578 whence = SEEK_SET;
7579 break;
7580 }
7581 iNew = lseek(SX_PTR_TO_INT(pUserData), (off_t)iOfft, whence);
7582 if( iNew < 0 ){
7583 return -1;
7584 }
7585 return JX9_OK;
7586}
7587/* int (*xLock)(void *, int) */
7588static int UnixFile_Lock(void *pUserData, int lock_type)
7589{
7590 int fd = SX_PTR_TO_INT(pUserData);
7591 int rc = JX9_OK; /* cc warning */
7592 if( lock_type < 0 ){
7593 /* Unlock the file */
7594 rc = flock(fd, LOCK_UN);
7595 }else{
7596 if( lock_type == 1 ){
7597 /* Exculsive lock */
7598 rc = flock(fd, LOCK_EX);
7599 }else{
7600 /* Shared lock */
7601 rc = flock(fd, LOCK_SH);
7602 }
7603 }
7604 return !rc ? JX9_OK : -1;
7605}
7606/* jx9_int64 (*xTell)(void *) */
7607static jx9_int64 UnixFile_Tell(void *pUserData)
7608{
7609 off_t iNew;
7610 iNew = lseek(SX_PTR_TO_INT(pUserData), 0, SEEK_CUR);
7611 return (jx9_int64)iNew;
7612}
7613/* int (*xTrunc)(void *, jx9_int64) */
7614static int UnixFile_Trunc(void *pUserData, jx9_int64 nOfft)
7615{
7616 int rc;
7617 rc = ftruncate(SX_PTR_TO_INT(pUserData), (off_t)nOfft);
7618 if( rc != 0 ){
7619 return -1;
7620 }
7621 return JX9_OK;
7622}
7623/* int (*xSync)(void *); */
7624static int UnixFile_Sync(void *pUserData)
7625{
7626 int rc;
7627 rc = fsync(SX_PTR_TO_INT(pUserData));
7628 return rc == 0 ? JX9_OK : - 1;
7629}
7630/* int (*xStat)(void *, jx9_value *, jx9_value *) */
7631static int UnixFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker)
7632{
7633 struct stat st;
7634 int rc;
7635 rc = fstat(SX_PTR_TO_INT(pUserData), &st);
7636 if( rc != 0 ){
7637 return -1;
7638 }
7639 /* dev */
7640 jx9_value_int64(pWorker, (jx9_int64)st.st_dev);
7641 jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */
7642 /* ino */
7643 jx9_value_int64(pWorker, (jx9_int64)st.st_ino);
7644 jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */
7645 /* mode */
7646 jx9_value_int(pWorker, (int)st.st_mode);
7647 jx9_array_add_strkey_elem(pArray, "mode", pWorker);
7648 /* nlink */
7649 jx9_value_int(pWorker, (int)st.st_nlink);
7650 jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */
7651 /* uid, gid, rdev */
7652 jx9_value_int(pWorker, (int)st.st_uid);
7653 jx9_array_add_strkey_elem(pArray, "uid", pWorker);
7654 jx9_value_int(pWorker, (int)st.st_gid);
7655 jx9_array_add_strkey_elem(pArray, "gid", pWorker);
7656 jx9_value_int(pWorker, (int)st.st_rdev);
7657 jx9_array_add_strkey_elem(pArray, "rdev", pWorker);
7658 /* size */
7659 jx9_value_int64(pWorker, (jx9_int64)st.st_size);
7660 jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */
7661 /* atime */
7662 jx9_value_int64(pWorker, (jx9_int64)st.st_atime);
7663 jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */
7664 /* mtime */
7665 jx9_value_int64(pWorker, (jx9_int64)st.st_mtime);
7666 jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */
7667 /* ctime */
7668 jx9_value_int64(pWorker, (jx9_int64)st.st_ctime);
7669 jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */
7670 /* blksize, blocks */
7671 jx9_value_int(pWorker, (int)st.st_blksize);
7672 jx9_array_add_strkey_elem(pArray, "blksize", pWorker);
7673 jx9_value_int(pWorker, (int)st.st_blocks);
7674 jx9_array_add_strkey_elem(pArray, "blocks", pWorker);
7675 return JX9_OK;
7676}
7677/* Export the file:// stream */
7678static const jx9_io_stream sUnixFileStream = {
7679 "file", /* Stream name */
7680 JX9_IO_STREAM_VERSION,
7681 UnixFile_Open, /* xOpen */
7682 UnixDir_Open, /* xOpenDir */
7683 UnixFile_Close, /* xClose */
7684 UnixDir_Close, /* xCloseDir */
7685 UnixFile_Read, /* xRead */
7686 UnixDir_Read, /* xReadDir */
7687 UnixFile_Write, /* xWrite */
7688 UnixFile_Seek, /* xSeek */
7689 UnixFile_Lock, /* xLock */
7690 UnixDir_Rewind, /* xRewindDir */
7691 UnixFile_Tell, /* xTell */
7692 UnixFile_Trunc, /* xTrunc */
7693 UnixFile_Sync, /* xSeek */
7694 UnixFile_Stat /* xStat */
7695};
7696#endif /* __WINNT__/__UNIXES__ */
7697#endif /* JX9_DISABLE_DISK_IO */
7698#endif /* JX9_DISABLE_BUILTIN_FUNC */
7699/*
7700 * Export the builtin vfs.
7701 * Return a pointer to the builtin vfs if available.
7702 * Otherwise return the null_vfs [i.e: a no-op vfs] instead.
7703 * Note:
7704 * The built-in vfs is always available for Windows/UNIX systems.
7705 * Note:
7706 * If the engine is compiled with the JX9_DISABLE_DISK_IO/JX9_DISABLE_BUILTIN_FUNC
7707 * directives defined then this function return the null_vfs instead.
7708 */
7709JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void)
7710{
7711#ifndef JX9_DISABLE_BUILTIN_FUNC
7712#ifdef JX9_DISABLE_DISK_IO
7713 return &null_vfs;
7714#else
7715#ifdef __WINNT__
7716 return &sWinVfs;
7717#elif defined(__UNIXES__)
7718 return &sUnixVfs;
7719#else
7720 return &null_vfs;
7721#endif /* __WINNT__/__UNIXES__ */
7722#endif /*JX9_DISABLE_DISK_IO*/
7723#else
7724 return &null_vfs;
7725#endif /* JX9_DISABLE_BUILTIN_FUNC */
7726}
7727#ifndef JX9_DISABLE_BUILTIN_FUNC
7728#ifndef JX9_DISABLE_DISK_IO
7729/*
7730 * The following defines are mostly used by the UNIX built and have
7731 * no particular meaning on windows.
7732 */
7733#ifndef STDIN_FILENO
7734#define STDIN_FILENO 0
7735#endif
7736#ifndef STDOUT_FILENO
7737#define STDOUT_FILENO 1
7738#endif
7739#ifndef STDERR_FILENO
7740#define STDERR_FILENO 2
7741#endif
7742/*
7743 * jx9:// Accessing various I/O streams
7744 * According to the JX9 langage reference manual
7745 * JX9 provides a number of miscellaneous I/O streams that allow access to JX9's own input
7746 * and output streams, the standard input, output and error file descriptors.
7747 * jx9://stdin, jx9://stdout and jx9://stderr:
7748 * Allow direct access to the corresponding input or output stream of the JX9 process.
7749 * The stream references a duplicate file descriptor, so if you open jx9://stdin and later
7750 * close it, you close only your copy of the descriptor-the actual stream referenced by STDIN is unaffected.
7751 * jx9://stdin is read-only, whereas jx9://stdout and jx9://stderr are write-only.
7752 * jx9://output
7753 * jx9://output is a write-only stream that allows you to write to the output buffer
7754 * mechanism in the same way as print and print.
7755 */
7756typedef struct jx9_stream_data jx9_stream_data;
7757/* Supported IO streams */
7758#define JX9_IO_STREAM_STDIN 1 /* jx9://stdin */
7759#define JX9_IO_STREAM_STDOUT 2 /* jx9://stdout */
7760#define JX9_IO_STREAM_STDERR 3 /* jx9://stderr */
7761#define JX9_IO_STREAM_OUTPUT 4 /* jx9://output */
7762 /* The following structure is the private data associated with the jx9:// stream */
7763struct jx9_stream_data
7764{
7765 jx9_vm *pVm; /* VM that own this instance */
7766 int iType; /* Stream type */
7767 union{
7768 void *pHandle; /* Stream handle */
7769 jx9_output_consumer sConsumer; /* VM output consumer */
7770 }x;
7771};
7772/*
7773 * Allocate a new instance of the jx9_stream_data structure.
7774 */
7775static jx9_stream_data * JX9StreamDataInit(jx9_vm *pVm, int iType)
7776{
7777 jx9_stream_data *pData;
7778 if( pVm == 0 ){
7779 return 0;
7780 }
7781 /* Allocate a new instance */
7782 pData = (jx9_stream_data *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(jx9_stream_data));
7783 if( pData == 0 ){
7784 return 0;
7785 }
7786 /* Zero the structure */
7787 SyZero(pData, sizeof(jx9_stream_data));
7788 /* Initialize fields */
7789 pData->iType = iType;
7790 if( iType == JX9_IO_STREAM_OUTPUT ){
7791 /* Point to the default VM consumer routine. */
7792 pData->x.sConsumer = pVm->sVmConsumer;
7793 }else{
7794#ifdef __WINNT__
7795 DWORD nChannel;
7796 switch(iType){
7797 case JX9_IO_STREAM_STDOUT: nChannel = STD_OUTPUT_HANDLE; break;
7798 case JX9_IO_STREAM_STDERR: nChannel = STD_ERROR_HANDLE; break;
7799 default:
7800 nChannel = STD_INPUT_HANDLE;
7801 break;
7802 }
7803 pData->x.pHandle = GetStdHandle(nChannel);
7804#else
7805 /* Assume an UNIX system */
7806 int ifd = STDIN_FILENO;
7807 switch(iType){
7808 case JX9_IO_STREAM_STDOUT: ifd = STDOUT_FILENO; break;
7809 case JX9_IO_STREAM_STDERR: ifd = STDERR_FILENO; break;
7810 default:
7811 break;
7812 }
7813 pData->x.pHandle = SX_INT_TO_PTR(ifd);
7814#endif
7815 }
7816 pData->pVm = pVm;
7817 return pData;
7818}
7819/*
7820 * Implementation of the jx9:// IO streams routines
7821 * Authors:
7822 * Symisc Systems, devel@symisc.net.
7823 * Copyright (C) Symisc Systems, http://jx9.symisc.net
7824 * Status:
7825 * Stable.
7826 */
7827/* int (*xOpen)(const char *, int, jx9_value *, void **) */
7828static int JX9StreamData_Open(const char *zName, int iMode, jx9_value *pResource, void ** ppHandle)
7829{
7830 jx9_stream_data *pData;
7831 SyString sStream;
7832 SyStringInitFromBuf(&sStream, zName, SyStrlen(zName));
7833 /* Trim leading and trailing white spaces */
7834 SyStringFullTrim(&sStream);
7835 /* Stream to open */
7836 if( SyStrnicmp(sStream.zString, "stdin", sizeof("stdin")-1) == 0 ){
7837 iMode = JX9_IO_STREAM_STDIN;
7838 }else if( SyStrnicmp(sStream.zString, "output", sizeof("output")-1) == 0 ){
7839 iMode = JX9_IO_STREAM_OUTPUT;
7840 }else if( SyStrnicmp(sStream.zString, "stdout", sizeof("stdout")-1) == 0 ){
7841 iMode = JX9_IO_STREAM_STDOUT;
7842 }else if( SyStrnicmp(sStream.zString, "stderr", sizeof("stderr")-1) == 0 ){
7843 iMode = JX9_IO_STREAM_STDERR;
7844 }else{
7845 /* unknown stream name */
7846 return -1;
7847 }
7848 /* Create our handle */
7849 pData = JX9StreamDataInit(pResource?pResource->pVm:0, iMode);
7850 if( pData == 0 ){
7851 return -1;
7852 }
7853 /* Make the handle public */
7854 *ppHandle = (void *)pData;
7855 return JX9_OK;
7856}
7857/* jx9_int64 (*xRead)(void *, void *, jx9_int64) */
7858static jx9_int64 JX9StreamData_Read(void *pHandle, void *pBuffer, jx9_int64 nDatatoRead)
7859{
7860 jx9_stream_data *pData = (jx9_stream_data *)pHandle;
7861 if( pData == 0 ){
7862 return -1;
7863 }
7864 if( pData->iType != JX9_IO_STREAM_STDIN ){
7865 /* Forbidden */
7866 return -1;
7867 }
7868#ifdef __WINNT__
7869 {
7870 DWORD nRd;
7871 BOOL rc;
7872 rc = ReadFile(pData->x.pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0);
7873 if( !rc ){
7874 /* IO error */
7875 return -1;
7876 }
7877 return (jx9_int64)nRd;
7878 }
7879#elif defined(__UNIXES__)
7880 {
7881 ssize_t nRd;
7882 int fd;
7883 fd = SX_PTR_TO_INT(pData->x.pHandle);
7884 nRd = read(fd, pBuffer, (size_t)nDatatoRead);
7885 if( nRd < 1 ){
7886 return -1;
7887 }
7888 return (jx9_int64)nRd;
7889 }
7890#else
7891 return -1;
7892#endif
7893}
7894/* jx9_int64 (*xWrite)(void *, const void *, jx9_int64) */
7895static jx9_int64 JX9StreamData_Write(void *pHandle, const void *pBuf, jx9_int64 nWrite)
7896{
7897 jx9_stream_data *pData = (jx9_stream_data *)pHandle;
7898 if( pData == 0 ){
7899 return -1;
7900 }
7901 if( pData->iType == JX9_IO_STREAM_STDIN ){
7902 /* Forbidden */
7903 return -1;
7904 }else if( pData->iType == JX9_IO_STREAM_OUTPUT ){
7905 jx9_output_consumer *pCons = &pData->x.sConsumer;
7906 int rc;
7907 /* Call the vm output consumer */
7908 rc = pCons->xConsumer(pBuf, (unsigned int)nWrite, pCons->pUserData);
7909 if( rc == JX9_ABORT ){
7910 return -1;
7911 }
7912 return nWrite;
7913 }
7914#ifdef __WINNT__
7915 {
7916 DWORD nWr;
7917 BOOL rc;
7918 rc = WriteFile(pData->x.pHandle, pBuf, (DWORD)nWrite, &nWr, 0);
7919 if( !rc ){
7920 /* IO error */
7921 return -1;
7922 }
7923 return (jx9_int64)nWr;
7924 }
7925#elif defined(__UNIXES__)
7926 {
7927 ssize_t nWr;
7928 int fd;
7929 fd = SX_PTR_TO_INT(pData->x.pHandle);
7930 nWr = write(fd, pBuf, (size_t)nWrite);
7931 if( nWr < 1 ){
7932 return -1;
7933 }
7934 return (jx9_int64)nWr;
7935 }
7936#else
7937 return -1;
7938#endif
7939}
7940/* void (*xClose)(void *) */
7941static void JX9StreamData_Close(void *pHandle)
7942{
7943 jx9_stream_data *pData = (jx9_stream_data *)pHandle;
7944 jx9_vm *pVm;
7945 if( pData == 0 ){
7946 return;
7947 }
7948 pVm = pData->pVm;
7949 /* Free the instance */
7950 SyMemBackendFree(&pVm->sAllocator, pData);
7951}
7952/* Export the jx9:// stream */
7953static const jx9_io_stream sjx9Stream = {
7954 "jx9",
7955 JX9_IO_STREAM_VERSION,
7956 JX9StreamData_Open, /* xOpen */
7957 0, /* xOpenDir */
7958 JX9StreamData_Close, /* xClose */
7959 0, /* xCloseDir */
7960 JX9StreamData_Read, /* xRead */
7961 0, /* xReadDir */
7962 JX9StreamData_Write, /* xWrite */
7963 0, /* xSeek */
7964 0, /* xLock */
7965 0, /* xRewindDir */
7966 0, /* xTell */
7967 0, /* xTrunc */
7968 0, /* xSeek */
7969 0 /* xStat */
7970};
7971#endif /* JX9_DISABLE_DISK_IO */
7972/*
7973 * Return TRUE if we are dealing with the jx9:// stream.
7974 * FALSE otherwise.
7975 */
7976static int is_jx9_stream(const jx9_io_stream *pStream)
7977{
7978#ifndef JX9_DISABLE_DISK_IO
7979 return pStream == &sjx9Stream;
7980#else
7981 SXUNUSED(pStream); /* cc warning */
7982 return 0;
7983#endif /* JX9_DISABLE_DISK_IO */
7984}
7985
7986#endif /* JX9_DISABLE_BUILTIN_FUNC */
7987/*
7988 * Export the IO routines defined above and the built-in IO streams
7989 * [i.e: file://, jx9://].
7990 * Note:
7991 * If the engine is compiled with the JX9_DISABLE_BUILTIN_FUNC directive
7992 * defined then this function is a no-op.
7993 */
7994JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm)
7995{
7996#ifndef JX9_DISABLE_BUILTIN_FUNC
7997 /* VFS functions */
7998 static const jx9_builtin_func aVfsFunc[] = {
7999 {"chdir", jx9Vfs_chdir },
8000 {"chroot", jx9Vfs_chroot },
8001 {"getcwd", jx9Vfs_getcwd },
8002 {"rmdir", jx9Vfs_rmdir },
8003 {"is_dir", jx9Vfs_is_dir },
8004 {"mkdir", jx9Vfs_mkdir },
8005 {"rename", jx9Vfs_rename },
8006 {"realpath", jx9Vfs_realpath},
8007 {"sleep", jx9Vfs_sleep },
8008 {"usleep", jx9Vfs_usleep },
8009 {"unlink", jx9Vfs_unlink },
8010 {"delete", jx9Vfs_unlink },
8011 {"chmod", jx9Vfs_chmod },
8012 {"chown", jx9Vfs_chown },
8013 {"chgrp", jx9Vfs_chgrp },
8014 {"disk_free_space", jx9Vfs_disk_free_space },
8015 {"disk_total_space", jx9Vfs_disk_total_space},
8016 {"file_exists", jx9Vfs_file_exists },
8017 {"filesize", jx9Vfs_file_size },
8018 {"fileatime", jx9Vfs_file_atime },
8019 {"filemtime", jx9Vfs_file_mtime },
8020 {"filectime", jx9Vfs_file_ctime },
8021 {"is_file", jx9Vfs_is_file },
8022 {"is_link", jx9Vfs_is_link },
8023 {"is_readable", jx9Vfs_is_readable },
8024 {"is_writable", jx9Vfs_is_writable },
8025 {"is_executable", jx9Vfs_is_executable},
8026 {"filetype", jx9Vfs_filetype },
8027 {"stat", jx9Vfs_stat },
8028 {"lstat", jx9Vfs_lstat },
8029 {"getenv", jx9Vfs_getenv },
8030 {"setenv", jx9Vfs_putenv },
8031 {"putenv", jx9Vfs_putenv },
8032 {"touch", jx9Vfs_touch },
8033 {"link", jx9Vfs_link },
8034 {"symlink", jx9Vfs_symlink },
8035 {"umask", jx9Vfs_umask },
8036 {"sys_get_temp_dir", jx9Vfs_sys_get_temp_dir },
8037 {"get_current_user", jx9Vfs_get_current_user },
8038 {"getpid", jx9Vfs_getmypid },
8039 {"getuid", jx9Vfs_getmyuid },
8040 {"getgid", jx9Vfs_getmygid },
8041 {"uname", jx9Vfs_uname},
8042 /* Path processing */
8043 {"dirname", jx9Builtin_dirname },
8044 {"basename", jx9Builtin_basename },
8045 {"pathinfo", jx9Builtin_pathinfo },
8046 {"strglob", jx9Builtin_strglob },
8047 {"fnmatch", jx9Builtin_fnmatch },
8048 /* ZIP processing */
8049 {"zip_open", jx9Builtin_zip_open },
8050 {"zip_close", jx9Builtin_zip_close},
8051 {"zip_read", jx9Builtin_zip_read },
8052 {"zip_entry_open", jx9Builtin_zip_entry_open },
8053 {"zip_entry_close", jx9Builtin_zip_entry_close},
8054 {"zip_entry_name", jx9Builtin_zip_entry_name },
8055 {"zip_entry_filesize", jx9Builtin_zip_entry_filesize },
8056 {"zip_entry_compressedsize", jx9Builtin_zip_entry_compressedsize },
8057 {"zip_entry_read", jx9Builtin_zip_entry_read },
8058 {"zip_entry_reset_cursor", jx9Builtin_zip_entry_reset_cursor},
8059 {"zip_entry_compressionmethod", jx9Builtin_zip_entry_compressionmethod}
8060 };
8061 /* IO stream functions */
8062 static const jx9_builtin_func aIOFunc[] = {
8063 {"ftruncate", jx9Builtin_ftruncate },
8064 {"fseek", jx9Builtin_fseek },
8065 {"ftell", jx9Builtin_ftell },
8066 {"rewind", jx9Builtin_rewind },
8067 {"fflush", jx9Builtin_fflush },
8068 {"feof", jx9Builtin_feof },
8069 {"fgetc", jx9Builtin_fgetc },
8070 {"fgets", jx9Builtin_fgets },
8071 {"fread", jx9Builtin_fread },
8072 {"fgetcsv", jx9Builtin_fgetcsv},
8073 {"fgetss", jx9Builtin_fgetss },
8074 {"readdir", jx9Builtin_readdir},
8075 {"rewinddir", jx9Builtin_rewinddir },
8076 {"closedir", jx9Builtin_closedir},
8077 {"opendir", jx9Builtin_opendir },
8078 {"readfile", jx9Builtin_readfile},
8079 {"file_get_contents", jx9Builtin_file_get_contents},
8080 {"file_put_contents", jx9Builtin_file_put_contents},
8081 {"file", jx9Builtin_file },
8082 {"copy", jx9Builtin_copy },
8083 {"fstat", jx9Builtin_fstat },
8084 {"fwrite", jx9Builtin_fwrite },
8085 {"fputs", jx9Builtin_fwrite },
8086 {"flock", jx9Builtin_flock },
8087 {"fclose", jx9Builtin_fclose },
8088 {"fopen", jx9Builtin_fopen },
8089 {"fpassthru", jx9Builtin_fpassthru },
8090 {"fputcsv", jx9Builtin_fputcsv },
8091 {"fprintf", jx9Builtin_fprintf },
8092#if !defined(JX9_DISABLE_HASH_FUNC)
8093 {"md5_file", jx9Builtin_md5_file},
8094 {"sha1_file", jx9Builtin_sha1_file},
8095#endif /* JX9_DISABLE_HASH_FUNC */
8096 {"parse_ini_file", jx9Builtin_parse_ini_file},
8097 {"vfprintf", jx9Builtin_vfprintf}
8098 };
8099 const jx9_io_stream *pFileStream = 0;
8100 sxu32 n = 0;
8101 /* Register the functions defined above */
8102 for( n = 0 ; n < SX_ARRAYSIZE(aVfsFunc) ; ++n ){
8103 jx9_create_function(&(*pVm), aVfsFunc[n].zName, aVfsFunc[n].xFunc, (void *)pVm->pEngine->pVfs);
8104 }
8105 for( n = 0 ; n < SX_ARRAYSIZE(aIOFunc) ; ++n ){
8106 jx9_create_function(&(*pVm), aIOFunc[n].zName, aIOFunc[n].xFunc, pVm);
8107 }
8108#ifndef JX9_DISABLE_DISK_IO
8109 /* Register the file stream if available */
8110#ifdef __WINNT__
8111 pFileStream = &sWinFileStream;
8112#elif defined(__UNIXES__)
8113 pFileStream = &sUnixFileStream;
8114#endif
8115 /* Install the jx9:// stream */
8116 jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, &sjx9Stream);
8117#endif /* JX9_DISABLE_DISK_IO */
8118 if( pFileStream ){
8119 /* Install the file:// stream */
8120 jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, pFileStream);
8121 }
8122#else
8123 SXUNUSED(pVm); /* cc warning */
8124#endif /* JX9_DISABLE_BUILTIN_FUNC */
8125 return SXRET_OK;
8126}
8127/*
8128 * Export the STDIN handle.
8129 */
8130JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm)
8131{
8132#ifndef JX9_DISABLE_BUILTIN_FUNC
8133#ifndef JX9_DISABLE_DISK_IO
8134 if( pVm->pStdin == 0 ){
8135 io_private *pIn;
8136 /* Allocate an IO private instance */
8137 pIn = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private));
8138 if( pIn == 0 ){
8139 return 0;
8140 }
8141 InitIOPrivate(pVm, &sjx9Stream, pIn);
8142 /* Initialize the handle */
8143 pIn->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDIN);
8144 /* Install the STDIN stream */
8145 pVm->pStdin = pIn;
8146 return pIn;
8147 }else{
8148 /* NULL or STDIN */
8149 return pVm->pStdin;
8150 }
8151#else
8152 return 0;
8153#endif
8154#else
8155 SXUNUSED(pVm); /* cc warning */
8156 return 0;
8157#endif
8158}
8159/*
8160 * Export the STDOUT handle.
8161 */
8162JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm)
8163{
8164#ifndef JX9_DISABLE_BUILTIN_FUNC
8165#ifndef JX9_DISABLE_DISK_IO
8166 if( pVm->pStdout == 0 ){
8167 io_private *pOut;
8168 /* Allocate an IO private instance */
8169 pOut = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private));
8170 if( pOut == 0 ){
8171 return 0;
8172 }
8173 InitIOPrivate(pVm, &sjx9Stream, pOut);
8174 /* Initialize the handle */
8175 pOut->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDOUT);
8176 /* Install the STDOUT stream */
8177 pVm->pStdout = pOut;
8178 return pOut;
8179 }else{
8180 /* NULL or STDOUT */
8181 return pVm->pStdout;
8182 }
8183#else
8184 return 0;
8185#endif
8186#else
8187 SXUNUSED(pVm); /* cc warning */
8188 return 0;
8189#endif
8190}
8191/*
8192 * Export the STDERR handle.
8193 */
8194JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm)
8195{
8196#ifndef JX9_DISABLE_BUILTIN_FUNC
8197#ifndef JX9_DISABLE_DISK_IO
8198 if( pVm->pStderr == 0 ){
8199 io_private *pErr;
8200 /* Allocate an IO private instance */
8201 pErr = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private));
8202 if( pErr == 0 ){
8203 return 0;
8204 }
8205 InitIOPrivate(pVm, &sjx9Stream, pErr);
8206 /* Initialize the handle */
8207 pErr->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDERR);
8208 /* Install the STDERR stream */
8209 pVm->pStderr = pErr;
8210 return pErr;
8211 }else{
8212 /* NULL or STDERR */
8213 return pVm->pStderr;
8214 }
8215#else
8216 return 0;
8217#endif
8218#else
8219 SXUNUSED(pVm); /* cc warning */
8220 return 0;
8221#endif
8222}
diff --git a/common/unqlite/jx9_vm.c b/common/unqlite/jx9_vm.c
new file mode 100644
index 0000000..1585b13
--- /dev/null
+++ b/common/unqlite/jx9_vm.c
@@ -0,0 +1,7141 @@
1/*
2 * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON.
3 * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/
4 * Version 1.7.2
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://jx9.symisc.net/
12 */
13 /* $SymiscID: jx9_vm.c v1.0 FreeBSD 2012-12-09 00:19 stable <chm@symisc.net> $ */
14#ifndef JX9_AMALGAMATION
15#include "jx9Int.h"
16#endif
17/*
18 * The code in this file implements execution method of the JX9 Virtual Machine.
19 * The JX9 compiler (implemented in 'compiler.c' and 'parse.c') generates a bytecode program
20 * which is then executed by the virtual machine implemented here to do the work of the JX9
21 * statements.
22 * JX9 bytecode programs are similar in form to assembly language. The program consists
23 * of a linear sequence of operations .Each operation has an opcode and 3 operands.
24 * Operands P1 and P2 are integers where the first is signed while the second is unsigned.
25 * Operand P3 is an arbitrary pointer specific to each instruction. The P2 operand is usually
26 * the jump destination used by the OP_JMP, OP_JZ, OP_JNZ, ... instructions.
27 * Opcodes will typically ignore one or more operands. Many opcodes ignore all three operands.
28 * Computation results are stored on a stack. Each entry on the stack is of type jx9_value.
29 * JX9 uses the jx9_value object to represent all values that can be stored in a JX9 variable.
30 * Since JX9 uses dynamic typing for the values it stores. Values stored in jx9_value objects
31 * can be integers, floating point values, strings, arrays, object instances (object in the JX9 jargon)
32 * and so on.
33 * Internally, the JX9 virtual machine manipulates nearly all values as jx9_values structures.
34 * Each jx9_value may cache multiple representations(string, integer etc.) of the same value.
35 * An implicit conversion from one type to the other occurs as necessary.
36 * Most of the code in this file is taken up by the [VmByteCodeExec()] function which does
37 * the work of interpreting a JX9 bytecode program. But other routines are also provided
38 * to help in building up a program instruction by instruction.
39 */
40/*
41 * Each active virtual machine frame is represented by an instance
42 * of the following structure.
43 * VM Frame hold local variables and other stuff related to function call.
44 */
45struct VmFrame
46{
47 VmFrame *pParent; /* Parent frame or NULL if global scope */
48 void *pUserData; /* Upper layer private data associated with this frame */
49 SySet sLocal; /* Local variables container (VmSlot instance) */
50 jx9_vm *pVm; /* VM that own this frame */
51 SyHash hVar; /* Variable hashtable for fast lookup */
52 SySet sArg; /* Function arguments container */
53 sxi32 iFlags; /* Frame configuration flags (See below)*/
54 sxu32 iExceptionJump; /* Exception jump destination */
55};
56/*
57 * When a user defined variable is garbage collected, memory object index
58 * is stored in an instance of the following structure and put in the free object
59 * table so that it can be reused again without allocating a new memory object.
60 */
61typedef struct VmSlot VmSlot;
62struct VmSlot
63{
64 sxu32 nIdx; /* Index in pVm->aMemObj[] */
65 void *pUserData; /* Upper-layer private data */
66};
67/*
68 * Each parsed URI is recorded and stored in an instance of the following structure.
69 * This structure and it's related routines are taken verbatim from the xHT project
70 * [A modern embeddable HTTP engine implementing all the RFC2616 methods]
71 * the xHT project is developed internally by Symisc Systems.
72 */
73typedef struct SyhttpUri SyhttpUri;
74struct SyhttpUri
75{
76 SyString sHost; /* Hostname or IP address */
77 SyString sPort; /* Port number */
78 SyString sPath; /* Mandatory resource path passed verbatim (Not decoded) */
79 SyString sQuery; /* Query part */
80 SyString sFragment; /* Fragment part */
81 SyString sScheme; /* Scheme */
82 SyString sUser; /* Username */
83 SyString sPass; /* Password */
84 SyString sRaw; /* Raw URI */
85};
86/*
87 * An instance of the following structure is used to record all MIME headers seen
88 * during a HTTP interaction.
89 * This structure and it's related routines are taken verbatim from the xHT project
90 * [A modern embeddable HTTP engine implementing all the RFC2616 methods]
91 * the xHT project is developed internally by Symisc Systems.
92 */
93typedef struct SyhttpHeader SyhttpHeader;
94struct SyhttpHeader
95{
96 SyString sName; /* Header name [i.e:"Content-Type", "Host", "User-Agent"]. NOT NUL TERMINATED */
97 SyString sValue; /* Header values [i.e: "text/html"]. NOT NUL TERMINATED */
98};
99/*
100 * Supported HTTP methods.
101 */
102#define HTTP_METHOD_GET 1 /* GET */
103#define HTTP_METHOD_HEAD 2 /* HEAD */
104#define HTTP_METHOD_POST 3 /* POST */
105#define HTTP_METHOD_PUT 4 /* PUT */
106#define HTTP_METHOD_OTHR 5 /* Other HTTP methods [i.e: DELETE, TRACE, OPTIONS...]*/
107/*
108 * Supported HTTP protocol version.
109 */
110#define HTTP_PROTO_10 1 /* HTTP/1.0 */
111#define HTTP_PROTO_11 2 /* HTTP/1.1 */
112/*
113 * Register a constant and it's associated expansion callback so that
114 * it can be expanded from the target JX9 program.
115 * The constant expansion mechanism under JX9 is extremely powerful yet
116 * simple and work as follows:
117 * Each registered constant have a C procedure associated with it.
118 * This procedure known as the constant expansion callback is responsible
119 * of expanding the invoked constant to the desired value, for example:
120 * The C procedure associated with the "__PI__" constant expands to 3.14 (the value of PI).
121 * The "__OS__" constant procedure expands to the name of the host Operating Systems
122 * (Windows, Linux, ...) and so on.
123 * Please refer to the official documentation for additional information.
124 */
125JX9_PRIVATE sxi32 jx9VmRegisterConstant(
126 jx9_vm *pVm, /* Target VM */
127 const SyString *pName, /* Constant name */
128 ProcConstant xExpand, /* Constant expansion callback */
129 void *pUserData /* Last argument to xExpand() */
130 )
131{
132 jx9_constant *pCons;
133 SyHashEntry *pEntry;
134 char *zDupName;
135 sxi32 rc;
136 pEntry = SyHashGet(&pVm->hConstant, (const void *)pName->zString, pName->nByte);
137 if( pEntry ){
138 /* Overwrite the old definition and return immediately */
139 pCons = (jx9_constant *)pEntry->pUserData;
140 pCons->xExpand = xExpand;
141 pCons->pUserData = pUserData;
142 return SXRET_OK;
143 }
144 /* Allocate a new constant instance */
145 pCons = (jx9_constant *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_constant));
146 if( pCons == 0 ){
147 return 0;
148 }
149 /* Duplicate constant name */
150 zDupName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte);
151 if( zDupName == 0 ){
152 SyMemBackendPoolFree(&pVm->sAllocator, pCons);
153 return 0;
154 }
155 /* Install the constant */
156 SyStringInitFromBuf(&pCons->sName, zDupName, pName->nByte);
157 pCons->xExpand = xExpand;
158 pCons->pUserData = pUserData;
159 rc = SyHashInsert(&pVm->hConstant, (const void *)zDupName, SyStringLength(&pCons->sName), pCons);
160 if( rc != SXRET_OK ){
161 SyMemBackendFree(&pVm->sAllocator, zDupName);
162 SyMemBackendPoolFree(&pVm->sAllocator, pCons);
163 return rc;
164 }
165 /* All done, constant can be invoked from JX9 code */
166 return SXRET_OK;
167}
168/*
169 * Allocate a new foreign function instance.
170 * This function return SXRET_OK on success. Any other
171 * return value indicates failure.
172 * Please refer to the official documentation for an introduction to
173 * the foreign function mechanism.
174 */
175static sxi32 jx9NewForeignFunction(
176 jx9_vm *pVm, /* Target VM */
177 const SyString *pName, /* Foreign function name */
178 ProcHostFunction xFunc, /* Foreign function implementation */
179 void *pUserData, /* Foreign function private data */
180 jx9_user_func **ppOut /* OUT: VM image of the foreign function */
181 )
182{
183 jx9_user_func *pFunc;
184 char *zDup;
185 /* Allocate a new user function */
186 pFunc = (jx9_user_func *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_user_func));
187 if( pFunc == 0 ){
188 return SXERR_MEM;
189 }
190 /* Duplicate function name */
191 zDup = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte);
192 if( zDup == 0 ){
193 SyMemBackendPoolFree(&pVm->sAllocator, pFunc);
194 return SXERR_MEM;
195 }
196 /* Zero the structure */
197 SyZero(pFunc, sizeof(jx9_user_func));
198 /* Initialize structure fields */
199 SyStringInitFromBuf(&pFunc->sName, zDup, pName->nByte);
200 pFunc->pVm = pVm;
201 pFunc->xFunc = xFunc;
202 pFunc->pUserData = pUserData;
203 SySetInit(&pFunc->aAux, &pVm->sAllocator, sizeof(jx9_aux_data));
204 /* Write a pointer to the new function */
205 *ppOut = pFunc;
206 return SXRET_OK;
207}
208/*
209 * Install a foreign function and it's associated callback so that
210 * it can be invoked from the target JX9 code.
211 * This function return SXRET_OK on successful registration. Any other
212 * return value indicates failure.
213 * Please refer to the official documentation for an introduction to
214 * the foreign function mechanism.
215 */
216JX9_PRIVATE sxi32 jx9VmInstallForeignFunction(
217 jx9_vm *pVm, /* Target VM */
218 const SyString *pName, /* Foreign function name */
219 ProcHostFunction xFunc, /* Foreign function implementation */
220 void *pUserData /* Foreign function private data */
221 )
222{
223 jx9_user_func *pFunc;
224 SyHashEntry *pEntry;
225 sxi32 rc;
226 /* Overwrite any previously registered function with the same name */
227 pEntry = SyHashGet(&pVm->hHostFunction, pName->zString, pName->nByte);
228 if( pEntry ){
229 pFunc = (jx9_user_func *)pEntry->pUserData;
230 pFunc->pUserData = pUserData;
231 pFunc->xFunc = xFunc;
232 SySetReset(&pFunc->aAux);
233 return SXRET_OK;
234 }
235 /* Create a new user function */
236 rc = jx9NewForeignFunction(&(*pVm), &(*pName), xFunc, pUserData, &pFunc);
237 if( rc != SXRET_OK ){
238 return rc;
239 }
240 /* Install the function in the corresponding hashtable */
241 rc = SyHashInsert(&pVm->hHostFunction, SyStringData(&pFunc->sName), pName->nByte, pFunc);
242 if( rc != SXRET_OK ){
243 SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName));
244 SyMemBackendPoolFree(&pVm->sAllocator, pFunc);
245 return rc;
246 }
247 /* User function successfully installed */
248 return SXRET_OK;
249}
250/*
251 * Initialize a VM function.
252 */
253JX9_PRIVATE sxi32 jx9VmInitFuncState(
254 jx9_vm *pVm, /* Target VM */
255 jx9_vm_func *pFunc, /* Target Fucntion */
256 const char *zName, /* Function name */
257 sxu32 nByte, /* zName length */
258 sxi32 iFlags, /* Configuration flags */
259 void *pUserData /* Function private data */
260 )
261{
262 /* Zero the structure */
263 SyZero(pFunc, sizeof(jx9_vm_func));
264 /* Initialize structure fields */
265 /* Arguments container */
266 SySetInit(&pFunc->aArgs, &pVm->sAllocator, sizeof(jx9_vm_func_arg));
267 /* Static variable container */
268 SySetInit(&pFunc->aStatic, &pVm->sAllocator, sizeof(jx9_vm_func_static_var));
269 /* Bytecode container */
270 SySetInit(&pFunc->aByteCode, &pVm->sAllocator, sizeof(VmInstr));
271 /* Preallocate some instruction slots */
272 SySetAlloc(&pFunc->aByteCode, 0x10);
273 pFunc->iFlags = iFlags;
274 pFunc->pUserData = pUserData;
275 SyStringInitFromBuf(&pFunc->sName, zName, nByte);
276 return SXRET_OK;
277}
278/*
279 * Install a user defined function in the corresponding VM container.
280 */
281JX9_PRIVATE sxi32 jx9VmInstallUserFunction(
282 jx9_vm *pVm, /* Target VM */
283 jx9_vm_func *pFunc, /* Target function */
284 SyString *pName /* Function name */
285 )
286{
287 SyHashEntry *pEntry;
288 sxi32 rc;
289 if( pName == 0 ){
290 /* Use the built-in name */
291 pName = &pFunc->sName;
292 }
293 /* Check for duplicates (functions with the same name) first */
294 pEntry = SyHashGet(&pVm->hFunction, pName->zString, pName->nByte);
295 if( pEntry ){
296 jx9_vm_func *pLink = (jx9_vm_func *)pEntry->pUserData;
297 if( pLink != pFunc ){
298 /* Link */
299 pFunc->pNextName = pLink;
300 pEntry->pUserData = pFunc;
301 }
302 return SXRET_OK;
303 }
304 /* First time seen */
305 pFunc->pNextName = 0;
306 rc = SyHashInsert(&pVm->hFunction, pName->zString, pName->nByte, pFunc);
307 return rc;
308}
309/*
310 * Instruction builder interface.
311 */
312JX9_PRIVATE sxi32 jx9VmEmitInstr(
313 jx9_vm *pVm, /* Target VM */
314 sxi32 iOp, /* Operation to perform */
315 sxi32 iP1, /* First operand */
316 sxu32 iP2, /* Second operand */
317 void *p3, /* Third operand */
318 sxu32 *pIndex /* Instruction index. NULL otherwise */
319 )
320{
321 VmInstr sInstr;
322 sxi32 rc;
323 /* Fill the VM instruction */
324 sInstr.iOp = (sxu8)iOp;
325 sInstr.iP1 = iP1;
326 sInstr.iP2 = iP2;
327 sInstr.p3 = p3;
328 if( pIndex ){
329 /* Instruction index in the bytecode array */
330 *pIndex = SySetUsed(pVm->pByteContainer);
331 }
332 /* Finally, record the instruction */
333 rc = SySetPut(pVm->pByteContainer, (const void *)&sInstr);
334 if( rc != SXRET_OK ){
335 jx9GenCompileError(&pVm->sCodeGen, E_ERROR, 1, "Fatal, Cannot emit instruction due to a memory failure");
336 /* Fall throw */
337 }
338 return rc;
339}
340/*
341 * Swap the current bytecode container with the given one.
342 */
343JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer)
344{
345 if( pContainer == 0 ){
346 /* Point to the default container */
347 pVm->pByteContainer = &pVm->aByteCode;
348 }else{
349 /* Change container */
350 pVm->pByteContainer = &(*pContainer);
351 }
352 return SXRET_OK;
353}
354/*
355 * Return the current bytecode container.
356 */
357JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm)
358{
359 return pVm->pByteContainer;
360}
361/*
362 * Extract the VM instruction rooted at nIndex.
363 */
364JX9_PRIVATE VmInstr * jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex)
365{
366 VmInstr *pInstr;
367 pInstr = (VmInstr *)SySetAt(pVm->pByteContainer, nIndex);
368 return pInstr;
369}
370/*
371 * Return the total number of VM instructions recorded so far.
372 */
373JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm)
374{
375 return SySetUsed(pVm->pByteContainer);
376}
377/*
378 * Pop the last VM instruction.
379 */
380JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm)
381{
382 return (VmInstr *)SySetPop(pVm->pByteContainer);
383}
384/*
385 * Peek the last VM instruction.
386 */
387JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm)
388{
389 return (VmInstr *)SySetPeek(pVm->pByteContainer);
390}
391/*
392 * Allocate a new virtual machine frame.
393 */
394static VmFrame * VmNewFrame(
395 jx9_vm *pVm, /* Target VM */
396 void *pUserData /* Upper-layer private data */
397 )
398{
399 VmFrame *pFrame;
400 /* Allocate a new vm frame */
401 pFrame = (VmFrame *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmFrame));
402 if( pFrame == 0 ){
403 return 0;
404 }
405 /* Zero the structure */
406 SyZero(pFrame, sizeof(VmFrame));
407 /* Initialize frame fields */
408 pFrame->pUserData = pUserData;
409 pFrame->pVm = pVm;
410 SyHashInit(&pFrame->hVar, &pVm->sAllocator, 0, 0);
411 SySetInit(&pFrame->sArg, &pVm->sAllocator, sizeof(VmSlot));
412 SySetInit(&pFrame->sLocal, &pVm->sAllocator, sizeof(VmSlot));
413 return pFrame;
414}
415/*
416 * Enter a VM frame.
417 */
418static sxi32 VmEnterFrame(
419 jx9_vm *pVm, /* Target VM */
420 void *pUserData, /* Upper-layer private data */
421 VmFrame **ppFrame /* OUT: Top most active frame */
422 )
423{
424 VmFrame *pFrame;
425 /* Allocate a new frame */
426 pFrame = VmNewFrame(&(*pVm), pUserData);
427 if( pFrame == 0 ){
428 return SXERR_MEM;
429 }
430 /* Link to the list of active VM frame */
431 pFrame->pParent = pVm->pFrame;
432 pVm->pFrame = pFrame;
433 if( ppFrame ){
434 /* Write a pointer to the new VM frame */
435 *ppFrame = pFrame;
436 }
437 return SXRET_OK;
438}
439/*
440 * Link a foreign variable with the TOP most active frame.
441 * Refer to the JX9_OP_UPLINK instruction implementation for more
442 * information.
443 */
444static sxi32 VmFrameLink(jx9_vm *pVm,SyString *pName)
445{
446 VmFrame *pTarget, *pFrame;
447 SyHashEntry *pEntry = 0;
448 sxi32 rc;
449 /* Point to the upper frame */
450 pFrame = pVm->pFrame;
451 pTarget = pFrame;
452 pFrame = pTarget->pParent;
453 while( pFrame ){
454 /* Query the current frame */
455 pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte);
456 if( pEntry ){
457 /* Variable found */
458 break;
459 }
460 /* Point to the upper frame */
461 pFrame = pFrame->pParent;
462 }
463 if( pEntry == 0 ){
464 /* Inexistant variable */
465 return SXERR_NOTFOUND;
466 }
467 /* Link to the current frame */
468 rc = SyHashInsert(&pTarget->hVar, pEntry->pKey, pEntry->nKeyLen, pEntry->pUserData);
469 return rc;
470}
471/*
472 * Leave the top-most active frame.
473 */
474static void VmLeaveFrame(jx9_vm *pVm)
475{
476 VmFrame *pFrame = pVm->pFrame;
477 if( pFrame ){
478 /* Unlink from the list of active VM frame */
479 pVm->pFrame = pFrame->pParent;
480 if( pFrame->pParent ){
481 VmSlot *aSlot;
482 sxu32 n;
483 /* Restore local variable to the free pool so that they can be reused again */
484 aSlot = (VmSlot *)SySetBasePtr(&pFrame->sLocal);
485 for(n = 0 ; n < SySetUsed(&pFrame->sLocal) ; ++n ){
486 /* Unset the local variable */
487 jx9VmUnsetMemObj(&(*pVm), aSlot[n].nIdx);
488 }
489 }
490 /* Release internal containers */
491 SyHashRelease(&pFrame->hVar);
492 SySetRelease(&pFrame->sArg);
493 SySetRelease(&pFrame->sLocal);
494 /* Release the whole structure */
495 SyMemBackendPoolFree(&pVm->sAllocator, pFrame);
496 }
497}
498/*
499 * Compare two functions signature and return the comparison result.
500 */
501static int VmOverloadCompare(SyString *pFirst, SyString *pSecond)
502{
503 const char *zSend = &pSecond->zString[pSecond->nByte];
504 const char *zFend = &pFirst->zString[pFirst->nByte];
505 const char *zSin = pSecond->zString;
506 const char *zFin = pFirst->zString;
507 const char *zPtr = zFin;
508 for(;;){
509 if( zFin >= zFend || zSin >= zSend ){
510 break;
511 }
512 if( zFin[0] != zSin[0] ){
513 /* mismatch */
514 break;
515 }
516 zFin++;
517 zSin++;
518 }
519 return (int)(zFin-zPtr);
520}
521/*
522 * Select the appropriate VM function for the current call context.
523 * This is the implementation of the powerful 'function overloading' feature
524 * introduced by the version 2 of the JX9 engine.
525 * Refer to the official documentation for more information.
526 */
527static jx9_vm_func * VmOverload(
528 jx9_vm *pVm, /* Target VM */
529 jx9_vm_func *pList, /* Linked list of candidates for overloading */
530 jx9_value *aArg, /* Array of passed arguments */
531 int nArg /* Total number of passed arguments */
532 )
533{
534 int iTarget, i, j, iCur, iMax;
535 jx9_vm_func *apSet[10]; /* Maximum number of candidates */
536 jx9_vm_func *pLink;
537 SyString sArgSig;
538 SyBlob sSig;
539
540 pLink = pList;
541 i = 0;
542 /* Put functions expecting the same number of passed arguments */
543 while( i < (int)SX_ARRAYSIZE(apSet) ){
544 if( pLink == 0 ){
545 break;
546 }
547 if( (int)SySetUsed(&pLink->aArgs) == nArg ){
548 /* Candidate for overloading */
549 apSet[i++] = pLink;
550 }
551 /* Point to the next entry */
552 pLink = pLink->pNextName;
553 }
554 if( i < 1 ){
555 /* No candidates, return the head of the list */
556 return pList;
557 }
558 if( nArg < 1 || i < 2 ){
559 /* Return the only candidate */
560 return apSet[0];
561 }
562 /* Calculate function signature */
563 SyBlobInit(&sSig, &pVm->sAllocator);
564 for( j = 0 ; j < nArg ; j++ ){
565 int c = 'n'; /* null */
566 if( aArg[j].iFlags & MEMOBJ_HASHMAP ){
567 /* Hashmap */
568 c = 'h';
569 }else if( aArg[j].iFlags & MEMOBJ_BOOL ){
570 /* bool */
571 c = 'b';
572 }else if( aArg[j].iFlags & MEMOBJ_INT ){
573 /* int */
574 c = 'i';
575 }else if( aArg[j].iFlags & MEMOBJ_STRING ){
576 /* String */
577 c = 's';
578 }else if( aArg[j].iFlags & MEMOBJ_REAL ){
579 /* Float */
580 c = 'f';
581 }
582 if( c > 0 ){
583 SyBlobAppend(&sSig, (const void *)&c, sizeof(char));
584 }
585 }
586 SyStringInitFromBuf(&sArgSig, SyBlobData(&sSig), SyBlobLength(&sSig));
587 iTarget = 0;
588 iMax = -1;
589 /* Select the appropriate function */
590 for( j = 0 ; j < i ; j++ ){
591 /* Compare the two signatures */
592 iCur = VmOverloadCompare(&sArgSig, &apSet[j]->sSignature);
593 if( iCur > iMax ){
594 iMax = iCur;
595 iTarget = j;
596 }
597 }
598 SyBlobRelease(&sSig);
599 /* Appropriate function for the current call context */
600 return apSet[iTarget];
601}
602/*
603 * Dummy read-only buffer used for slot reservation.
604 */
605static const char zDummy[sizeof(jx9_value)] = { 0 }; /* Must be >= sizeof(jx9_value) */
606/*
607 * Reserve a constant memory object.
608 * Return a pointer to the raw jx9_value on success. NULL on failure.
609 */
610JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex)
611{
612 jx9_value *pObj;
613 sxi32 rc;
614 if( pIndex ){
615 /* Object index in the object table */
616 *pIndex = SySetUsed(&pVm->aLitObj);
617 }
618 /* Reserve a slot for the new object */
619 rc = SySetPut(&pVm->aLitObj, (const void *)zDummy);
620 if( rc != SXRET_OK ){
621 /* If the supplied memory subsystem is so sick that we are unable to allocate
622 * a tiny chunk of memory, there is no much we can do here.
623 */
624 return 0;
625 }
626 pObj = (jx9_value *)SySetPeek(&pVm->aLitObj);
627 return pObj;
628}
629/*
630 * Reserve a memory object.
631 * Return a pointer to the raw jx9_value on success. NULL on failure.
632 */
633static jx9_value * VmReserveMemObj(jx9_vm *pVm, sxu32 *pIndex)
634{
635 jx9_value *pObj;
636 sxi32 rc;
637 if( pIndex ){
638 /* Object index in the object table */
639 *pIndex = SySetUsed(&pVm->aMemObj);
640 }
641 /* Reserve a slot for the new object */
642 rc = SySetPut(&pVm->aMemObj, (const void *)zDummy);
643 if( rc != SXRET_OK ){
644 /* If the supplied memory subsystem is so sick that we are unable to allocate
645 * a tiny chunk of memory, there is no much we can do here.
646 */
647 return 0;
648 }
649 pObj = (jx9_value *)SySetPeek(&pVm->aMemObj);
650 return pObj;
651}
652/* Forward declaration */
653static sxi32 VmEvalChunk(jx9_vm *pVm, jx9_context *pCtx, SyString *pChunk, int iFlags, int bTrueReturn);
654/*
655 * Built-in functions that cannot be implemented directly as foreign functions.
656 */
657#define JX9_BUILTIN_LIB \
658 "function scandir(string $directory, int $sort_order = SCANDIR_SORT_ASCENDING)"\
659 "{"\
660 " if( func_num_args() < 1 ){ return FALSE; }"\
661 " $aDir = [];"\
662 " $pHandle = opendir($directory);"\
663 " if( $pHandle == FALSE ){ return FALSE; }"\
664 " while(FALSE !== ($pEntry = readdir($pHandle)) ){"\
665 " $aDir[] = $pEntry;"\
666 " }"\
667 " closedir($pHandle);"\
668 " if( $sort_order == SCANDIR_SORT_DESCENDING ){"\
669 " rsort($aDir);"\
670 " }else if( $sort_order == SCANDIR_SORT_ASCENDING ){"\
671 " sort($aDir);"\
672 " }"\
673 " return $aDir;"\
674 "}"\
675 "function glob(string $pattern, int $iFlags = 0){"\
676 "/* Open the target directory */"\
677 "$zDir = dirname($pattern);"\
678 "if(!is_string($zDir) ){ $zDir = './'; }"\
679 "$pHandle = opendir($zDir);"\
680 "if( $pHandle == FALSE ){"\
681 " /* IO error while opening the current directory, return FALSE */"\
682 " return FALSE;"\
683 "}"\
684 "$pattern = basename($pattern);"\
685 "$pArray = []; /* Empty array */"\
686 "/* Loop throw available entries */"\
687 "while( FALSE !== ($pEntry = readdir($pHandle)) ){"\
688 " /* Use the built-in strglob function which is a Symisc eXtension for wildcard comparison*/"\
689 " $rc = strglob($pattern, $pEntry);"\
690 " if( $rc ){"\
691 " if( is_dir($pEntry) ){"\
692 " if( $iFlags & GLOB_MARK ){"\
693 " /* Adds a slash to each directory returned */"\
694 " $pEntry .= DIRECTORY_SEPARATOR;"\
695 " }"\
696 " }else if( $iFlags & GLOB_ONLYDIR ){"\
697 " /* Not a directory, ignore */"\
698 " continue;"\
699 " }"\
700 " /* Add the entry */"\
701 " $pArray[] = $pEntry;"\
702 " }"\
703 " }"\
704 "/* Close the handle */"\
705 "closedir($pHandle);"\
706 "if( ($iFlags & GLOB_NOSORT) == 0 ){"\
707 " /* Sort the array */"\
708 " sort($pArray);"\
709 "}"\
710 "if( ($iFlags & GLOB_NOCHECK) && sizeof($pArray) < 1 ){"\
711 " /* Return the search pattern if no files matching were found */"\
712 " $pArray[] = $pattern;"\
713 "}"\
714 "/* Return the created array */"\
715 "return $pArray;"\
716 "}"\
717 "/* Creates a temporary file */"\
718 "function tmpfile(){"\
719 " /* Extract the temp directory */"\
720 " $zTempDir = sys_get_temp_dir();"\
721 " if( strlen($zTempDir) < 1 ){"\
722 " /* Use the current dir */"\
723 " $zTempDir = '.';"\
724 " }"\
725 " /* Create the file */"\
726 " $pHandle = fopen($zTempDir.DIRECTORY_SEPARATOR.'JX9'.rand_str(12), 'w+');"\
727 " return $pHandle;"\
728 "}"\
729 "/* Creates a temporary filename */"\
730 "function tempnam(string $zDir = sys_get_temp_dir() /* Symisc eXtension */, string $zPrefix = 'JX9')"\
731 "{"\
732 " return $zDir.DIRECTORY_SEPARATOR.$zPrefix.rand_str(12);"\
733 "}"\
734 "function max(){"\
735 " $pArgs = func_get_args();"\
736 " if( sizeof($pArgs) < 1 ){"\
737 " return null;"\
738 " }"\
739 " if( sizeof($pArgs) < 2 ){"\
740 " $pArg = $pArgs[0];"\
741 " if( !is_array($pArg) ){"\
742 " return $pArg; "\
743 " }"\
744 " if( sizeof($pArg) < 1 ){"\
745 " return null;"\
746 " }"\
747 " $pArg = array_copy($pArgs[0]);"\
748 " reset($pArg);"\
749 " $max = current($pArg);"\
750 " while( FALSE !== ($val = next($pArg)) ){"\
751 " if( $val > $max ){"\
752 " $max = $val;"\
753 " }"\
754 " }"\
755 " return $max;"\
756 " }"\
757 " $max = $pArgs[0];"\
758 " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\
759 " $val = $pArgs[$i];"\
760 "if( $val > $max ){"\
761 " $max = $val;"\
762 "}"\
763 " }"\
764 " return $max;"\
765 "}"\
766 "function min(){"\
767 " $pArgs = func_get_args();"\
768 " if( sizeof($pArgs) < 1 ){"\
769 " return null;"\
770 " }"\
771 " if( sizeof($pArgs) < 2 ){"\
772 " $pArg = $pArgs[0];"\
773 " if( !is_array($pArg) ){"\
774 " return $pArg; "\
775 " }"\
776 " if( sizeof($pArg) < 1 ){"\
777 " return null;"\
778 " }"\
779 " $pArg = array_copy($pArgs[0]);"\
780 " reset($pArg);"\
781 " $min = current($pArg);"\
782 " while( FALSE !== ($val = next($pArg)) ){"\
783 " if( $val < $min ){"\
784 " $min = $val;"\
785 " }"\
786 " }"\
787 " return $min;"\
788 " }"\
789 " $min = $pArgs[0];"\
790 " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\
791 " $val = $pArgs[$i];"\
792 "if( $val < $min ){"\
793 " $min = $val;"\
794 " }"\
795 " }"\
796 " return $min;"\
797 "}"
798/*
799 * Initialize a freshly allocated JX9 Virtual Machine so that we can
800 * start compiling the target JX9 program.
801 */
802JX9_PRIVATE sxi32 jx9VmInit(
803 jx9_vm *pVm, /* Initialize this */
804 jx9 *pEngine /* Master engine */
805 )
806{
807 SyString sBuiltin;
808 jx9_value *pObj;
809 sxi32 rc;
810 /* Zero the structure */
811 SyZero(pVm, sizeof(jx9_vm));
812 /* Initialize VM fields */
813 pVm->pEngine = &(*pEngine);
814 SyMemBackendInitFromParent(&pVm->sAllocator, &pEngine->sAllocator);
815 /* Instructions containers */
816 SySetInit(&pVm->aByteCode, &pVm->sAllocator, sizeof(VmInstr));
817 SySetAlloc(&pVm->aByteCode, 0xFF);
818 pVm->pByteContainer = &pVm->aByteCode;
819 /* Object containers */
820 SySetInit(&pVm->aMemObj, &pVm->sAllocator, sizeof(jx9_value));
821 SySetAlloc(&pVm->aMemObj, 0xFF);
822 /* Virtual machine internal containers */
823 SyBlobInit(&pVm->sConsumer, &pVm->sAllocator);
824 SyBlobInit(&pVm->sWorker, &pVm->sAllocator);
825 SyBlobInit(&pVm->sArgv, &pVm->sAllocator);
826 SySetInit(&pVm->aLitObj, &pVm->sAllocator, sizeof(jx9_value));
827 SySetAlloc(&pVm->aLitObj, 0xFF);
828 SyHashInit(&pVm->hHostFunction, &pVm->sAllocator, 0, 0);
829 SyHashInit(&pVm->hFunction, &pVm->sAllocator, 0, 0);
830 SyHashInit(&pVm->hConstant, &pVm->sAllocator, 0, 0);
831 SyHashInit(&pVm->hSuper, &pVm->sAllocator, 0, 0);
832 SySetInit(&pVm->aFreeObj, &pVm->sAllocator, sizeof(VmSlot));
833 /* Configuration containers */
834 SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString));
835 SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString));
836 SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString));
837 SySetInit(&pVm->aIOstream, &pVm->sAllocator, sizeof(jx9_io_stream *));
838 /* Error callbacks containers */
839 jx9MemObjInit(&(*pVm), &pVm->sAssertCallback);
840 /* Set a default recursion limit */
841#if defined(__WINNT__) || defined(__UNIXES__)
842 pVm->nMaxDepth = 32;
843#else
844 pVm->nMaxDepth = 16;
845#endif
846 /* Default assertion flags */
847 pVm->iAssertFlags = JX9_ASSERT_WARNING; /* Issue a warning for each failed assertion */
848 /* PRNG context */
849 SyRandomnessInit(&pVm->sPrng, 0, 0);
850 /* Install the null constant */
851 pObj = jx9VmReserveConstObj(&(*pVm), 0);
852 if( pObj == 0 ){
853 rc = SXERR_MEM;
854 goto Err;
855 }
856 jx9MemObjInit(pVm, pObj);
857 /* Install the boolean TRUE constant */
858 pObj = jx9VmReserveConstObj(&(*pVm), 0);
859 if( pObj == 0 ){
860 rc = SXERR_MEM;
861 goto Err;
862 }
863 jx9MemObjInitFromBool(pVm, pObj, 1);
864 /* Install the boolean FALSE constant */
865 pObj = jx9VmReserveConstObj(&(*pVm), 0);
866 if( pObj == 0 ){
867 rc = SXERR_MEM;
868 goto Err;
869 }
870 jx9MemObjInitFromBool(pVm, pObj, 0);
871 /* Create the global frame */
872 rc = VmEnterFrame(&(*pVm), 0, 0);
873 if( rc != SXRET_OK ){
874 goto Err;
875 }
876 /* Initialize the code generator */
877 rc = jx9InitCodeGenerator(pVm, pEngine->xConf.xErr, pEngine->xConf.pErrData);
878 if( rc != SXRET_OK ){
879 goto Err;
880 }
881 /* VM correctly initialized, set the magic number */
882 pVm->nMagic = JX9_VM_INIT;
883 SyStringInitFromBuf(&sBuiltin,JX9_BUILTIN_LIB, sizeof(JX9_BUILTIN_LIB)-1);
884 /* Compile the built-in library */
885 VmEvalChunk(&(*pVm), 0, &sBuiltin, 0, FALSE);
886 /* Reset the code generator */
887 jx9ResetCodeGenerator(&(*pVm), pEngine->xConf.xErr, pEngine->xConf.pErrData);
888 return SXRET_OK;
889Err:
890 SyMemBackendRelease(&pVm->sAllocator);
891 return rc;
892}
893/*
894 * Default VM output consumer callback.That is, all VM output is redirected to this
895 * routine which store the output in an internal blob.
896 * The output can be extracted later after program execution [jx9_vm_exec()] via
897 * the [jx9_vm_config()] interface with a configuration verb set to
898 * jx9VM_CONFIG_EXTRACT_OUTPUT.
899 * Refer to the official docurmentation for additional information.
900 * Note that for performance reason it's preferable to install a VM output
901 * consumer callback via (jx9VM_CONFIG_OUTPUT) rather than waiting for the VM
902 * to finish executing and extracting the output.
903 */
904JX9_PRIVATE sxi32 jx9VmBlobConsumer(
905 const void *pOut, /* VM Generated output*/
906 unsigned int nLen, /* Generated output length */
907 void *pUserData /* User private data */
908 )
909{
910 sxi32 rc;
911 /* Store the output in an internal BLOB */
912 rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen);
913 return rc;
914}
915#define VM_STACK_GUARD 16
916/*
917 * Allocate a new operand stack so that we can start executing
918 * our compiled JX9 program.
919 * Return a pointer to the operand stack (array of jx9_values)
920 * on success. NULL (Fatal error) on failure.
921 */
922static jx9_value * VmNewOperandStack(
923 jx9_vm *pVm, /* Target VM */
924 sxu32 nInstr /* Total numer of generated bytecode instructions */
925 )
926{
927 jx9_value *pStack;
928 /* No instruction ever pushes more than a single element onto the
929 ** stack and the stack never grows on successive executions of the
930 ** same loop. So the total number of instructions is an upper bound
931 ** on the maximum stack depth required.
932 **
933 ** Allocation all the stack space we will ever need.
934 */
935 nInstr += VM_STACK_GUARD;
936 pStack = (jx9_value *)SyMemBackendAlloc(&pVm->sAllocator, nInstr * sizeof(jx9_value));
937 if( pStack == 0 ){
938 return 0;
939 }
940 /* Initialize the operand stack */
941 while( nInstr > 0 ){
942 jx9MemObjInit(&(*pVm), &pStack[nInstr - 1]);
943 --nInstr;
944 }
945 /* Ready for bytecode execution */
946 return pStack;
947}
948/* Forward declaration */
949static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm);
950/*
951 * Prepare the Virtual Machine for bytecode execution.
952 * This routine gets called by the JX9 engine after
953 * successful compilation of the target JX9 program.
954 */
955JX9_PRIVATE sxi32 jx9VmMakeReady(
956 jx9_vm *pVm /* Target VM */
957 )
958{
959 sxi32 rc;
960 if( pVm->nMagic != JX9_VM_INIT ){
961 /* Initialize your VM first */
962 return SXERR_CORRUPT;
963 }
964 /* Mark the VM ready for bytecode execution */
965 pVm->nMagic = JX9_VM_RUN;
966 /* Release the code generator now we have compiled our program */
967 jx9ResetCodeGenerator(pVm, 0, 0);
968 /* Emit the DONE instruction */
969 rc = jx9VmEmitInstr(&(*pVm), JX9_OP_DONE, 0, 0, 0, 0);
970 if( rc != SXRET_OK ){
971 return SXERR_MEM;
972 }
973 /* Script return value */
974 jx9MemObjInit(&(*pVm), &pVm->sExec); /* Assume a NULL return value */
975 /* Allocate a new operand stack */
976 pVm->aOps = VmNewOperandStack(&(*pVm), SySetUsed(pVm->pByteContainer));
977 if( pVm->aOps == 0 ){
978 return SXERR_MEM;
979 }
980 /* Set the default VM output consumer callback and it's
981 * private data. */
982 pVm->sVmConsumer.xConsumer = jx9VmBlobConsumer;
983 pVm->sVmConsumer.pUserData = &pVm->sConsumer;
984 /* Register special functions first [i.e: print, func_get_args(), die, etc.] */
985 rc = VmRegisterSpecialFunction(&(*pVm));
986 if( rc != SXRET_OK ){
987 /* Don't worry about freeing memory, everything will be released shortly */
988 return rc;
989 }
990 /* Create superglobals [i.e: $GLOBALS, $_GET, $_POST...] */
991 rc = jx9HashmapLoadBuiltin(&(*pVm));
992 if( rc != SXRET_OK ){
993 /* Don't worry about freeing memory, everything will be released shortly */
994 return rc;
995 }
996 /* Register built-in constants [i.e: JX9_EOL, JX9_OS...] */
997 jx9RegisterBuiltInConstant(&(*pVm));
998 /* Register built-in functions [i.e: is_null(), array_diff(), strlen(), etc.] */
999 jx9RegisterBuiltInFunction(&(*pVm));
1000 /* VM is ready for bytecode execution */
1001 return SXRET_OK;
1002}
1003/*
1004 * Reset a Virtual Machine to it's initial state.
1005 */
1006JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm)
1007{
1008 if( pVm->nMagic != JX9_VM_RUN && pVm->nMagic != JX9_VM_EXEC ){
1009 return SXERR_CORRUPT;
1010 }
1011 /* TICKET 1433-003: As of this version, the VM is automatically reset */
1012 SyBlobReset(&pVm->sConsumer);
1013 jx9MemObjRelease(&pVm->sExec);
1014 /* Set the ready flag */
1015 pVm->nMagic = JX9_VM_RUN;
1016 return SXRET_OK;
1017}
1018/*
1019 * Release a Virtual Machine.
1020 * Every virtual machine must be destroyed in order to avoid memory leaks.
1021 */
1022JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm)
1023{
1024 /* Set the stale magic number */
1025 pVm->nMagic = JX9_VM_STALE;
1026 /* Release the private memory subsystem */
1027 SyMemBackendRelease(&pVm->sAllocator);
1028 return SXRET_OK;
1029}
1030/*
1031 * Initialize a foreign function call context.
1032 * The context in which a foreign function executes is stored in a jx9_context object.
1033 * A pointer to a jx9_context object is always first parameter to application-defined foreign
1034 * functions.
1035 * The application-defined foreign function implementation will pass this pointer through into
1036 * calls to dozens of interfaces, these includes jx9_result_int(), jx9_result_string(), jx9_result_value(),
1037 * jx9_context_new_scalar(), jx9_context_alloc_chunk(), jx9_context_output(), jx9_context_throw_error()
1038 * and many more. Refer to the C/C++ Interfaces documentation for additional information.
1039 */
1040static sxi32 VmInitCallContext(
1041 jx9_context *pOut, /* Call Context */
1042 jx9_vm *pVm, /* Target VM */
1043 jx9_user_func *pFunc, /* Foreign function to execute shortly */
1044 jx9_value *pRet, /* Store return value here*/
1045 sxi32 iFlags /* Control flags */
1046 )
1047{
1048 pOut->pFunc = pFunc;
1049 pOut->pVm = pVm;
1050 SySetInit(&pOut->sVar, &pVm->sAllocator, sizeof(jx9_value *));
1051 SySetInit(&pOut->sChunk, &pVm->sAllocator, sizeof(jx9_aux_data));
1052 /* Assume a null return value */
1053 MemObjSetType(pRet, MEMOBJ_NULL);
1054 pOut->pRet = pRet;
1055 pOut->iFlags = iFlags;
1056 return SXRET_OK;
1057}
1058/*
1059 * Release a foreign function call context and cleanup the mess
1060 * left behind.
1061 */
1062static void VmReleaseCallContext(jx9_context *pCtx)
1063{
1064 sxu32 n;
1065 if( SySetUsed(&pCtx->sVar) > 0 ){
1066 jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar);
1067 for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){
1068 if( apObj[n] == 0 ){
1069 /* Already released */
1070 continue;
1071 }
1072 jx9MemObjRelease(apObj[n]);
1073 SyMemBackendPoolFree(&pCtx->pVm->sAllocator, apObj[n]);
1074 }
1075 SySetRelease(&pCtx->sVar);
1076 }
1077 if( SySetUsed(&pCtx->sChunk) > 0 ){
1078 jx9_aux_data *aAux;
1079 void *pChunk;
1080 /* Automatic release of dynamically allocated chunk
1081 * using [jx9_context_alloc_chunk()].
1082 */
1083 aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk);
1084 for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){
1085 pChunk = aAux[n].pAuxData;
1086 /* Release the chunk */
1087 if( pChunk ){
1088 SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk);
1089 }
1090 }
1091 SySetRelease(&pCtx->sChunk);
1092 }
1093}
1094/*
1095 * Release a jx9_value allocated from the body of a foreign function.
1096 * Refer to [jx9_context_release_value()] for additional information.
1097 */
1098JX9_PRIVATE void jx9VmReleaseContextValue(
1099 jx9_context *pCtx, /* Call context */
1100 jx9_value *pValue /* Release this value */
1101 )
1102{
1103 if( pValue == 0 ){
1104 /* NULL value is a harmless operation */
1105 return;
1106 }
1107 if( SySetUsed(&pCtx->sVar) > 0 ){
1108 jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar);
1109 sxu32 n;
1110 for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){
1111 if( apObj[n] == pValue ){
1112 jx9MemObjRelease(pValue);
1113 SyMemBackendPoolFree(&pCtx->pVm->sAllocator, pValue);
1114 /* Mark as released */
1115 apObj[n] = 0;
1116 break;
1117 }
1118 }
1119 }
1120}
1121/*
1122 * Pop and release as many memory object from the operand stack.
1123 */
1124static void VmPopOperand(
1125 jx9_value **ppTos, /* Operand stack */
1126 sxi32 nPop /* Total number of memory objects to pop */
1127 )
1128{
1129 jx9_value *pTos = *ppTos;
1130 while( nPop > 0 ){
1131 jx9MemObjRelease(pTos);
1132 pTos--;
1133 nPop--;
1134 }
1135 /* Top of the stack */
1136 *ppTos = pTos;
1137}
1138/*
1139 * Reserve a memory object.
1140 * Return a pointer to the raw jx9_value on success. NULL on failure.
1141 */
1142JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIdx)
1143{
1144 jx9_value *pObj = 0;
1145 VmSlot *pSlot;
1146 sxu32 nIdx;
1147 /* Check for a free slot */
1148 nIdx = SXU32_HIGH; /* cc warning */
1149 pSlot = (VmSlot *)SySetPop(&pVm->aFreeObj);
1150 if( pSlot ){
1151 pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx);
1152 nIdx = pSlot->nIdx;
1153 }
1154 if( pObj == 0 ){
1155 /* Reserve a new memory object */
1156 pObj = VmReserveMemObj(&(*pVm), &nIdx);
1157 if( pObj == 0 ){
1158 return 0;
1159 }
1160 }
1161 /* Set a null default value */
1162 jx9MemObjInit(&(*pVm), pObj);
1163 if( pIdx ){
1164 *pIdx = nIdx;
1165 }
1166 pObj->nIdx = nIdx;
1167 return pObj;
1168}
1169/*
1170 * Extract a variable value from the top active VM frame.
1171 * Return a pointer to the variable value on success.
1172 * NULL otherwise (non-existent variable/Out-of-memory, ...).
1173 */
1174static jx9_value * VmExtractMemObj(
1175 jx9_vm *pVm, /* Target VM */
1176 const SyString *pName, /* Variable name */
1177 int bDup, /* True to duplicate variable name */
1178 int bCreate /* True to create the variable if non-existent */
1179 )
1180{
1181 int bNullify = FALSE;
1182 SyHashEntry *pEntry;
1183 VmFrame *pFrame;
1184 jx9_value *pObj;
1185 sxu32 nIdx;
1186 sxi32 rc;
1187 /* Point to the top active frame */
1188 pFrame = pVm->pFrame;
1189 /* Perform the lookup */
1190 if( pName == 0 || pName->nByte < 1 ){
1191 static const SyString sAnnon = { " " , sizeof(char) };
1192 pName = &sAnnon;
1193 /* Always nullify the object */
1194 bNullify = TRUE;
1195 bDup = FALSE;
1196 }
1197 /* Check the superglobals table first */
1198 pEntry = SyHashGet(&pVm->hSuper, (const void *)pName->zString, pName->nByte);
1199 if( pEntry == 0 ){
1200 /* Query the top active frame */
1201 pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte);
1202 if( pEntry == 0 ){
1203 char *zName = (char *)pName->zString;
1204 VmSlot sLocal;
1205 if( !bCreate ){
1206 /* Do not create the variable, return NULL */
1207 return 0;
1208 }
1209 /* No such variable, automatically create a new one and install
1210 * it in the current frame.
1211 */
1212 pObj = jx9VmReserveMemObj(&(*pVm),&nIdx);
1213 if( pObj == 0 ){
1214 return 0;
1215 }
1216 if( bDup ){
1217 /* Duplicate name */
1218 zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte);
1219 if( zName == 0 ){
1220 return 0;
1221 }
1222 }
1223 /* Link to the top active VM frame */
1224 rc = SyHashInsert(&pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx));
1225 if( rc != SXRET_OK ){
1226 /* Return the slot to the free pool */
1227 sLocal.nIdx = nIdx;
1228 sLocal.pUserData = 0;
1229 SySetPut(&pVm->aFreeObj, (const void *)&sLocal);
1230 return 0;
1231 }
1232 if( pFrame->pParent != 0 ){
1233 /* Local variable */
1234 sLocal.nIdx = nIdx;
1235 SySetPut(&pFrame->sLocal, (const void *)&sLocal);
1236 }
1237 }else{
1238 /* Extract variable contents */
1239 nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData);
1240 pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx);
1241 if( bNullify && pObj ){
1242 jx9MemObjRelease(pObj);
1243 }
1244 }
1245 }else{
1246 /* Superglobal */
1247 nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData);
1248 pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx);
1249 }
1250 return pObj;
1251}
1252/*
1253 * Extract a superglobal variable such as $_GET, $_POST, $_HEADERS, ....
1254 * Return a pointer to the variable value on success.NULL otherwise.
1255 */
1256static jx9_value * VmExtractSuper(
1257 jx9_vm *pVm, /* Target VM */
1258 const char *zName, /* Superglobal name: NOT NULL TERMINATED */
1259 sxu32 nByte /* zName length */
1260 )
1261{
1262 SyHashEntry *pEntry;
1263 jx9_value *pValue;
1264 sxu32 nIdx;
1265 /* Query the superglobal table */
1266 pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte);
1267 if( pEntry == 0 ){
1268 /* No such entry */
1269 return 0;
1270 }
1271 /* Extract the superglobal index in the global object pool */
1272 nIdx = SX_PTR_TO_INT(pEntry->pUserData);
1273 /* Extract the variable value */
1274 pValue = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx);
1275 return pValue;
1276}
1277/*
1278 * Perform a raw hashmap insertion.
1279 * Refer to the [jx9VmConfigure()] implementation for additional information.
1280 */
1281static sxi32 VmHashmapInsert(
1282 jx9_hashmap *pMap, /* Target hashmap */
1283 const char *zKey, /* Entry key */
1284 int nKeylen, /* zKey length*/
1285 const char *zData, /* Entry data */
1286 int nLen /* zData length */
1287 )
1288{
1289 jx9_value sKey,sValue;
1290 jx9_value *pKey;
1291 sxi32 rc;
1292 pKey = 0;
1293 jx9MemObjInit(pMap->pVm, &sKey);
1294 jx9MemObjInitFromString(pMap->pVm, &sValue, 0);
1295 if( zKey ){
1296 if( nKeylen < 0 ){
1297 nKeylen = (int)SyStrlen(zKey);
1298 }
1299 jx9MemObjStringAppend(&sKey, zKey, (sxu32)nKeylen);
1300 pKey = &sKey;
1301 }
1302 if( zData ){
1303 if( nLen < 0 ){
1304 /* Compute length automatically */
1305 nLen = (int)SyStrlen(zData);
1306 }
1307 jx9MemObjStringAppend(&sValue, zData, (sxu32)nLen);
1308 }
1309 /* Perform the insertion */
1310 rc = jx9HashmapInsert(&(*pMap),pKey,&sValue);
1311 jx9MemObjRelease(&sKey);
1312 jx9MemObjRelease(&sValue);
1313 return rc;
1314}
1315/* Forward declaration */
1316static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte);
1317/*
1318 * Configure a working virtual machine instance.
1319 *
1320 * This routine is used to configure a JX9 virtual machine obtained by a prior
1321 * successful call to one of the compile interface such as jx9_compile()
1322 * jx9_compile_v2() or jx9_compile_file().
1323 * The second argument to this function is an integer configuration option
1324 * that determines what property of the JX9 virtual machine is to be configured.
1325 * Subsequent arguments vary depending on the configuration option in the second
1326 * argument. There are many verbs but the most important are JX9_VM_CONFIG_OUTPUT,
1327 * JX9_VM_CONFIG_HTTP_REQUEST and JX9_VM_CONFIG_ARGV_ENTRY.
1328 * Refer to the official documentation for the list of allowed verbs.
1329 */
1330JX9_PRIVATE sxi32 jx9VmConfigure(
1331 jx9_vm *pVm, /* Target VM */
1332 sxi32 nOp, /* Configuration verb */
1333 va_list ap /* Subsequent option arguments */
1334 )
1335{
1336 sxi32 rc = SXRET_OK;
1337 switch(nOp){
1338 case JX9_VM_CONFIG_OUTPUT: {
1339 ProcConsumer xConsumer = va_arg(ap, ProcConsumer);
1340 void *pUserData = va_arg(ap, void *);
1341 /* VM output consumer callback */
1342#ifdef UNTRUST
1343 if( xConsumer == 0 ){
1344 rc = SXERR_CORRUPT;
1345 break;
1346 }
1347#endif
1348 /* Install the output consumer */
1349 pVm->sVmConsumer.xConsumer = xConsumer;
1350 pVm->sVmConsumer.pUserData = pUserData;
1351 break;
1352 }
1353 case JX9_VM_CONFIG_IMPORT_PATH: {
1354 /* Import path */
1355 const char *zPath;
1356 SyString sPath;
1357 zPath = va_arg(ap, const char *);
1358#if defined(UNTRUST)
1359 if( zPath == 0 ){
1360 rc = SXERR_EMPTY;
1361 break;
1362 }
1363#endif
1364 SyStringInitFromBuf(&sPath, zPath, SyStrlen(zPath));
1365 /* Remove trailing slashes and backslashes */
1366#ifdef __WINNT__
1367 SyStringTrimTrailingChar(&sPath, '\\');
1368#endif
1369 SyStringTrimTrailingChar(&sPath, '/');
1370 /* Remove leading and trailing white spaces */
1371 SyStringFullTrim(&sPath);
1372 if( sPath.nByte > 0 ){
1373 /* Store the path in the corresponding conatiner */
1374 rc = SySetPut(&pVm->aPaths, (const void *)&sPath);
1375 }
1376 break;
1377 }
1378 case JX9_VM_CONFIG_ERR_REPORT:
1379 /* Run-Time Error report */
1380 pVm->bErrReport = 1;
1381 break;
1382 case JX9_VM_CONFIG_RECURSION_DEPTH:{
1383 /* Recursion depth */
1384 int nDepth = va_arg(ap, int);
1385 if( nDepth > 2 && nDepth < 1024 ){
1386 pVm->nMaxDepth = nDepth;
1387 }
1388 break;
1389 }
1390 case JX9_VM_OUTPUT_LENGTH: {
1391 /* VM output length in bytes */
1392 sxu32 *pOut = va_arg(ap, sxu32 *);
1393#ifdef UNTRUST
1394 if( pOut == 0 ){
1395 rc = SXERR_CORRUPT;
1396 break;
1397 }
1398#endif
1399 *pOut = pVm->nOutputLen;
1400 break;
1401 }
1402 case JX9_VM_CONFIG_CREATE_VAR: {
1403 /* Create a new superglobal/global variable */
1404 const char *zName = va_arg(ap, const char *);
1405 jx9_value *pValue = va_arg(ap, jx9_value *);
1406 SyHashEntry *pEntry;
1407 jx9_value *pObj;
1408 sxu32 nByte;
1409 sxu32 nIdx;
1410#ifdef UNTRUST
1411 if( SX_EMPTY_STR(zName) || pValue == 0 ){
1412 rc = SXERR_CORRUPT;
1413 break;
1414 }
1415#endif
1416 nByte = SyStrlen(zName);
1417 /* Check if the superglobal is already installed */
1418 pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte);
1419 if( pEntry ){
1420 /* Variable already installed */
1421 nIdx = SX_PTR_TO_INT(pEntry->pUserData);
1422 /* Extract contents */
1423 pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx);
1424 if( pObj ){
1425 /* Overwrite old contents */
1426 jx9MemObjStore(pValue, pObj);
1427 }
1428 }else{
1429 /* Install a new variable */
1430 pObj = jx9VmReserveMemObj(&(*pVm),&nIdx);
1431 if( pObj == 0 ){
1432 rc = SXERR_MEM;
1433 break;
1434 }
1435 /* Copy value */
1436 jx9MemObjStore(pValue, pObj);
1437 /* Install the superglobal */
1438 rc = SyHashInsert(&pVm->hSuper, (const void *)zName, nByte, SX_INT_TO_PTR(nIdx));
1439 }
1440 break;
1441 }
1442 case JX9_VM_CONFIG_SERVER_ATTR:
1443 case JX9_VM_CONFIG_ENV_ATTR: {
1444 const char *zKey = va_arg(ap, const char *);
1445 const char *zValue = va_arg(ap, const char *);
1446 int nLen = va_arg(ap, int);
1447 jx9_hashmap *pMap;
1448 jx9_value *pValue;
1449 if( nOp == JX9_VM_CONFIG_ENV_ATTR ){
1450 /* Extract the $_ENV superglobal */
1451 pValue = VmExtractSuper(&(*pVm), "_ENV", sizeof("_ENV")-1);
1452 }else{
1453 /* Extract the $_SERVER superglobal */
1454 pValue = VmExtractSuper(&(*pVm), "_SERVER", sizeof("_SERVER")-1);
1455 }
1456 if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){
1457 /* No such entry */
1458 rc = SXERR_NOTFOUND;
1459 break;
1460 }
1461 /* Point to the hashmap */
1462 pMap = (jx9_hashmap *)pValue->x.pOther;
1463 /* Perform the insertion */
1464 rc = VmHashmapInsert(pMap, zKey, -1, zValue, nLen);
1465 break;
1466 }
1467 case JX9_VM_CONFIG_ARGV_ENTRY:{
1468 /* Script arguments */
1469 const char *zValue = va_arg(ap, const char *);
1470 jx9_hashmap *pMap;
1471 jx9_value *pValue;
1472 /* Extract the $argv array */
1473 pValue = VmExtractSuper(&(*pVm), "argv", sizeof("argv")-1);
1474 if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){
1475 /* No such entry */
1476 rc = SXERR_NOTFOUND;
1477 break;
1478 }
1479 /* Point to the hashmap */
1480 pMap = (jx9_hashmap *)pValue->x.pOther;
1481 /* Perform the insertion */
1482 rc = VmHashmapInsert(pMap, 0, 0, zValue,-1);
1483 if( rc == SXRET_OK && zValue && zValue[0] != 0 ){
1484 if( pMap->nEntry > 1 ){
1485 /* Append space separator first */
1486 SyBlobAppend(&pVm->sArgv, (const void *)" ", sizeof(char));
1487 }
1488 SyBlobAppend(&pVm->sArgv, (const void *)zValue,SyStrlen(zValue));
1489 }
1490 break;
1491 }
1492 case JX9_VM_CONFIG_EXEC_VALUE: {
1493 /* Script return value */
1494 jx9_value **ppValue = va_arg(ap, jx9_value **);
1495#ifdef UNTRUST
1496 if( ppValue == 0 ){
1497 rc = SXERR_CORRUPT;
1498 break;
1499 }
1500#endif
1501 *ppValue = &pVm->sExec;
1502 break;
1503 }
1504 case JX9_VM_CONFIG_IO_STREAM: {
1505 /* Register an IO stream device */
1506 const jx9_io_stream *pStream = va_arg(ap, const jx9_io_stream *);
1507 /* Make sure we are dealing with a valid IO stream */
1508 if( pStream == 0 || pStream->zName == 0 || pStream->zName[0] == 0 ||
1509 pStream->xOpen == 0 || pStream->xRead == 0 ){
1510 /* Invalid stream */
1511 rc = SXERR_INVALID;
1512 break;
1513 }
1514 if( pVm->pDefStream == 0 && SyStrnicmp(pStream->zName, "file", sizeof("file")-1) == 0 ){
1515 /* Make the 'file://' stream the defaut stream device */
1516 pVm->pDefStream = pStream;
1517 }
1518 /* Insert in the appropriate container */
1519 rc = SySetPut(&pVm->aIOstream, (const void *)&pStream);
1520 break;
1521 }
1522 case JX9_VM_CONFIG_EXTRACT_OUTPUT: {
1523 /* Point to the VM internal output consumer buffer */
1524 const void **ppOut = va_arg(ap, const void **);
1525 unsigned int *pLen = va_arg(ap, unsigned int *);
1526#ifdef UNTRUST
1527 if( ppOut == 0 || pLen == 0 ){
1528 rc = SXERR_CORRUPT;
1529 break;
1530 }
1531#endif
1532 *ppOut = SyBlobData(&pVm->sConsumer);
1533 *pLen = SyBlobLength(&pVm->sConsumer);
1534 break;
1535 }
1536 case JX9_VM_CONFIG_HTTP_REQUEST:{
1537 /* Raw HTTP request*/
1538 const char *zRequest = va_arg(ap, const char *);
1539 int nByte = va_arg(ap, int);
1540 if( SX_EMPTY_STR(zRequest) ){
1541 rc = SXERR_EMPTY;
1542 break;
1543 }
1544 if( nByte < 0 ){
1545 /* Compute length automatically */
1546 nByte = (int)SyStrlen(zRequest);
1547 }
1548 /* Process the request */
1549 rc = VmHttpProcessRequest(&(*pVm), zRequest, nByte);
1550 break;
1551 }
1552 default:
1553 /* Unknown configuration option */
1554 rc = SXERR_UNKNOWN;
1555 break;
1556 }
1557 return rc;
1558}
1559/* Forward declaration */
1560static const char * VmInstrToString(sxi32 nOp);
1561/*
1562 * This routine is used to dump JX9 bytecode instructions to a human readable
1563 * format.
1564 * The dump is redirected to the given consumer callback which is responsible
1565 * of consuming the generated dump perhaps redirecting it to its standard output
1566 * (STDOUT).
1567 */
1568static sxi32 VmByteCodeDump(
1569 SySet *pByteCode, /* Bytecode container */
1570 ProcConsumer xConsumer, /* Dump consumer callback */
1571 void *pUserData /* Last argument to xConsumer() */
1572 )
1573{
1574 static const char zDump[] = {
1575 "====================================================\n"
1576 "JX9 VM Dump Copyright (C) 2012-2013 Symisc Systems\n"
1577 " http://jx9.symisc.net/\n"
1578 "====================================================\n"
1579 };
1580 VmInstr *pInstr, *pEnd;
1581 sxi32 rc = SXRET_OK;
1582 sxu32 n;
1583 /* Point to the JX9 instructions */
1584 pInstr = (VmInstr *)SySetBasePtr(pByteCode);
1585 pEnd = &pInstr[SySetUsed(pByteCode)];
1586 n = 0;
1587 xConsumer((const void *)zDump, sizeof(zDump)-1, pUserData);
1588 /* Dump instructions */
1589 for(;;){
1590 if( pInstr >= pEnd ){
1591 /* No more instructions */
1592 break;
1593 }
1594 /* Format and call the consumer callback */
1595 rc = SyProcFormat(xConsumer, pUserData, "%s %8d %8u %#8x [%u]\n",
1596 VmInstrToString(pInstr->iOp), pInstr->iP1, pInstr->iP2,
1597 SX_PTR_TO_INT(pInstr->p3), n);
1598 if( rc != SXRET_OK ){
1599 /* Consumer routine request an operation abort */
1600 return rc;
1601 }
1602 ++n;
1603 pInstr++; /* Next instruction in the stream */
1604 }
1605 return rc;
1606}
1607/*
1608 * Consume a generated run-time error message by invoking the VM output
1609 * consumer callback.
1610 */
1611static sxi32 VmCallErrorHandler(jx9_vm *pVm, SyBlob *pMsg)
1612{
1613 jx9_output_consumer *pCons = &pVm->sVmConsumer;
1614 sxi32 rc = SXRET_OK;
1615 /* Append a new line */
1616#ifdef __WINNT__
1617 SyBlobAppend(pMsg, "\r\n", sizeof("\r\n")-1);
1618#else
1619 SyBlobAppend(pMsg, "\n", sizeof(char));
1620#endif
1621 /* Invoke the output consumer callback */
1622 rc = pCons->xConsumer(SyBlobData(pMsg), SyBlobLength(pMsg), pCons->pUserData);
1623 /* Increment output length */
1624 pVm->nOutputLen += SyBlobLength(pMsg);
1625
1626 return rc;
1627}
1628/*
1629 * Throw a run-time error and invoke the supplied VM output consumer callback.
1630 * Refer to the implementation of [jx9_context_throw_error()] for additional
1631 * information.
1632 */
1633JX9_PRIVATE sxi32 jx9VmThrowError(
1634 jx9_vm *pVm, /* Target VM */
1635 SyString *pFuncName, /* Function name. NULL otherwise */
1636 sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice]*/
1637 const char *zMessage /* Null terminated error message */
1638 )
1639{
1640 SyBlob *pWorker = &pVm->sWorker;
1641 SyString *pFile;
1642 char *zErr;
1643 sxi32 rc;
1644 if( !pVm->bErrReport ){
1645 /* Don't bother reporting errors */
1646 return SXRET_OK;
1647 }
1648 /* Reset the working buffer */
1649 SyBlobReset(pWorker);
1650 /* Peek the processed file if available */
1651 pFile = (SyString *)SySetPeek(&pVm->aFiles);
1652 if( pFile ){
1653 /* Append file name */
1654 SyBlobAppend(pWorker, pFile->zString, pFile->nByte);
1655 SyBlobAppend(pWorker, (const void *)" ", sizeof(char));
1656 }
1657 zErr = "Error: ";
1658 switch(iErr){
1659 case JX9_CTX_WARNING: zErr = "Warning: "; break;
1660 case JX9_CTX_NOTICE: zErr = "Notice: "; break;
1661 default:
1662 iErr = JX9_CTX_ERR;
1663 break;
1664 }
1665 SyBlobAppend(pWorker, zErr, SyStrlen(zErr));
1666 if( pFuncName ){
1667 /* Append function name first */
1668 SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte);
1669 SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1);
1670 }
1671 SyBlobAppend(pWorker, zMessage, SyStrlen(zMessage));
1672 /* Consume the error message */
1673 rc = VmCallErrorHandler(&(*pVm), pWorker);
1674 return rc;
1675}
1676/*
1677 * Format and throw a run-time error and invoke the supplied VM output consumer callback.
1678 * Refer to the implementation of [jx9_context_throw_error_format()] for additional
1679 * information.
1680 */
1681static sxi32 VmThrowErrorAp(
1682 jx9_vm *pVm, /* Target VM */
1683 SyString *pFuncName, /* Function name. NULL otherwise */
1684 sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice] */
1685 const char *zFormat, /* Format message */
1686 va_list ap /* Variable list of arguments */
1687 )
1688{
1689 SyBlob *pWorker = &pVm->sWorker;
1690 SyString *pFile;
1691 char *zErr;
1692 sxi32 rc;
1693 if( !pVm->bErrReport ){
1694 /* Don't bother reporting errors */
1695 return SXRET_OK;
1696 }
1697 /* Reset the working buffer */
1698 SyBlobReset(pWorker);
1699 /* Peek the processed file if available */
1700 pFile = (SyString *)SySetPeek(&pVm->aFiles);
1701 if( pFile ){
1702 /* Append file name */
1703 SyBlobAppend(pWorker, pFile->zString, pFile->nByte);
1704 SyBlobAppend(pWorker, (const void *)" ", sizeof(char));
1705 }
1706 zErr = "Error: ";
1707 switch(iErr){
1708 case JX9_CTX_WARNING: zErr = "Warning: "; break;
1709 case JX9_CTX_NOTICE: zErr = "Notice: "; break;
1710 default:
1711 iErr = JX9_CTX_ERR;
1712 break;
1713 }
1714 SyBlobAppend(pWorker, zErr, SyStrlen(zErr));
1715 if( pFuncName ){
1716 /* Append function name first */
1717 SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte);
1718 SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1);
1719 }
1720 SyBlobFormatAp(pWorker, zFormat, ap);
1721 /* Consume the error message */
1722 rc = VmCallErrorHandler(&(*pVm), pWorker);
1723 return rc;
1724}
1725/*
1726 * Format and throw a run-time error and invoke the supplied VM output consumer callback.
1727 * Refer to the implementation of [jx9_context_throw_error_format()] for additional
1728 * information.
1729 * ------------------------------------
1730 * Simple boring wrapper function.
1731 * ------------------------------------
1732 */
1733static sxi32 VmErrorFormat(jx9_vm *pVm, sxi32 iErr, const char *zFormat, ...)
1734{
1735 va_list ap;
1736 sxi32 rc;
1737 va_start(ap, zFormat);
1738 rc = VmThrowErrorAp(&(*pVm), 0, iErr, zFormat, ap);
1739 va_end(ap);
1740 return rc;
1741}
1742/*
1743 * Format and throw a run-time error and invoke the supplied VM output consumer callback.
1744 * Refer to the implementation of [jx9_context_throw_error_format()] for additional
1745 * information.
1746 * ------------------------------------
1747 * Simple boring wrapper function.
1748 * ------------------------------------
1749 */
1750JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap)
1751{
1752 sxi32 rc;
1753 rc = VmThrowErrorAp(&(*pVm), &(*pFuncName), iErr, zFormat, ap);
1754 return rc;
1755}
1756/* Forward declaration */
1757static sxi32 VmLocalExec(jx9_vm *pVm,SySet *pByteCode,jx9_value *pResult);
1758/*
1759 * Execute as much of a JX9 bytecode program as we can then return.
1760 *
1761 * [jx9VmMakeReady()] must be called before this routine in order to
1762 * close the program with a final OP_DONE and to set up the default
1763 * consumer routines and other stuff. Refer to the implementation
1764 * of [jx9VmMakeReady()] for additional information.
1765 * If the installed VM output consumer callback ever returns JX9_ABORT
1766 * then the program execution is halted.
1767 * After this routine has finished, [jx9VmRelease()] or [jx9VmReset()]
1768 * should be used respectively to clean up the mess that was left behind
1769 * or to reset the VM to it's initial state.
1770 */
1771static sxi32 VmByteCodeExec(
1772 jx9_vm *pVm, /* Target VM */
1773 VmInstr *aInstr, /* JX9 bytecode program */
1774 jx9_value *pStack, /* Operand stack */
1775 int nTos, /* Top entry in the operand stack (usually -1) */
1776 jx9_value *pResult /* Store program return value here. NULL otherwise */
1777 )
1778{
1779 VmInstr *pInstr;
1780 jx9_value *pTos;
1781 SySet aArg;
1782 sxi32 pc;
1783 sxi32 rc;
1784 /* Argument container */
1785 SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *));
1786 if( nTos < 0 ){
1787 pTos = &pStack[-1];
1788 }else{
1789 pTos = &pStack[nTos];
1790 }
1791 pc = 0;
1792 /* Execute as much as we can */
1793 for(;;){
1794 /* Fetch the instruction to execute */
1795 pInstr = &aInstr[pc];
1796 rc = SXRET_OK;
1797/*
1798 * What follows here is a massive switch statement where each case implements a
1799 * separate instruction in the virtual machine. If we follow the usual
1800 * indentation convention each case should be indented by 6 spaces. But
1801 * that is a lot of wasted space on the left margin. So the code within
1802 * the switch statement will break with convention and be flush-left.
1803 */
1804 switch(pInstr->iOp){
1805/*
1806 * DONE: P1 * *
1807 *
1808 * Program execution completed: Clean up the mess left behind
1809 * and return immediately.
1810 */
1811case JX9_OP_DONE:
1812 if( pInstr->iP1 ){
1813#ifdef UNTRUST
1814 if( pTos < pStack ){
1815 goto Abort;
1816 }
1817#endif
1818 if( pResult ){
1819 /* Execution result */
1820 jx9MemObjStore(pTos, pResult);
1821 }
1822 VmPopOperand(&pTos, 1);
1823 }
1824 goto Done;
1825/*
1826 * HALT: P1 * *
1827 *
1828 * Program execution aborted: Clean up the mess left behind
1829 * and abort immediately.
1830 */
1831case JX9_OP_HALT:
1832 if( pInstr->iP1 ){
1833#ifdef UNTRUST
1834 if( pTos < pStack ){
1835 goto Abort;
1836 }
1837#endif
1838 if( pTos->iFlags & MEMOBJ_STRING ){
1839 if( SyBlobLength(&pTos->sBlob) > 0 ){
1840 /* Output the exit message */
1841 pVm->sVmConsumer.xConsumer(SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob),
1842 pVm->sVmConsumer.pUserData);
1843 /* Increment output length */
1844 pVm->nOutputLen += SyBlobLength(&pTos->sBlob);
1845 }
1846 }else if(pTos->iFlags & MEMOBJ_INT ){
1847 /* Record exit status */
1848 pVm->iExitStatus = (sxi32)pTos->x.iVal;
1849 }
1850 VmPopOperand(&pTos, 1);
1851 }
1852 goto Abort;
1853/*
1854 * JMP: * P2 *
1855 *
1856 * Unconditional jump: The next instruction executed will be
1857 * the one at index P2 from the beginning of the program.
1858 */
1859case JX9_OP_JMP:
1860 pc = pInstr->iP2 - 1;
1861 break;
1862/*
1863 * JZ: P1 P2 *
1864 *
1865 * Take the jump if the top value is zero (FALSE jump).Pop the top most
1866 * entry in the stack if P1 is zero.
1867 */
1868case JX9_OP_JZ:
1869#ifdef UNTRUST
1870 if( pTos < pStack ){
1871 goto Abort;
1872 }
1873#endif
1874 /* Get a boolean value */
1875 if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){
1876 jx9MemObjToBool(pTos);
1877 }
1878 if( !pTos->x.iVal ){
1879 /* Take the jump */
1880 pc = pInstr->iP2 - 1;
1881 }
1882 if( !pInstr->iP1 ){
1883 VmPopOperand(&pTos, 1);
1884 }
1885 break;
1886/*
1887 * JNZ: P1 P2 *
1888 *
1889 * Take the jump if the top value is not zero (TRUE jump).Pop the top most
1890 * entry in the stack if P1 is zero.
1891 */
1892case JX9_OP_JNZ:
1893#ifdef UNTRUST
1894 if( pTos < pStack ){
1895 goto Abort;
1896 }
1897#endif
1898 /* Get a boolean value */
1899 if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){
1900 jx9MemObjToBool(pTos);
1901 }
1902 if( pTos->x.iVal ){
1903 /* Take the jump */
1904 pc = pInstr->iP2 - 1;
1905 }
1906 if( !pInstr->iP1 ){
1907 VmPopOperand(&pTos, 1);
1908 }
1909 break;
1910/*
1911 * NOOP: * * *
1912 *
1913 * Do nothing. This instruction is often useful as a jump
1914 * destination.
1915 */
1916case JX9_OP_NOOP:
1917 break;
1918/*
1919 * POP: P1 * *
1920 *
1921 * Pop P1 elements from the operand stack.
1922 */
1923case JX9_OP_POP: {
1924 sxi32 n = pInstr->iP1;
1925 if( &pTos[-n+1] < pStack ){
1926 /* TICKET 1433-51 Stack underflow must be handled at run-time */
1927 n = (sxi32)(pTos - pStack);
1928 }
1929 VmPopOperand(&pTos, n);
1930 break;
1931 }
1932/*
1933 * CVT_INT: * * *
1934 *
1935 * Force the top of the stack to be an integer.
1936 */
1937case JX9_OP_CVT_INT:
1938#ifdef UNTRUST
1939 if( pTos < pStack ){
1940 goto Abort;
1941 }
1942#endif
1943 if((pTos->iFlags & MEMOBJ_INT) == 0 ){
1944 jx9MemObjToInteger(pTos);
1945 }
1946 /* Invalidate any prior representation */
1947 MemObjSetType(pTos, MEMOBJ_INT);
1948 break;
1949/*
1950 * CVT_REAL: * * *
1951 *
1952 * Force the top of the stack to be a real.
1953 */
1954case JX9_OP_CVT_REAL:
1955#ifdef UNTRUST
1956 if( pTos < pStack ){
1957 goto Abort;
1958 }
1959#endif
1960 if((pTos->iFlags & MEMOBJ_REAL) == 0 ){
1961 jx9MemObjToReal(pTos);
1962 }
1963 /* Invalidate any prior representation */
1964 MemObjSetType(pTos, MEMOBJ_REAL);
1965 break;
1966/*
1967 * CVT_STR: * * *
1968 *
1969 * Force the top of the stack to be a string.
1970 */
1971case JX9_OP_CVT_STR:
1972#ifdef UNTRUST
1973 if( pTos < pStack ){
1974 goto Abort;
1975 }
1976#endif
1977 if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){
1978 jx9MemObjToString(pTos);
1979 }
1980 break;
1981/*
1982 * CVT_BOOL: * * *
1983 *
1984 * Force the top of the stack to be a boolean.
1985 */
1986case JX9_OP_CVT_BOOL:
1987#ifdef UNTRUST
1988 if( pTos < pStack ){
1989 goto Abort;
1990 }
1991#endif
1992 if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){
1993 jx9MemObjToBool(pTos);
1994 }
1995 break;
1996/*
1997 * CVT_NULL: * * *
1998 *
1999 * Nullify the top of the stack.
2000 */
2001case JX9_OP_CVT_NULL:
2002#ifdef UNTRUST
2003 if( pTos < pStack ){
2004 goto Abort;
2005 }
2006#endif
2007 jx9MemObjRelease(pTos);
2008 break;
2009/*
2010 * CVT_NUMC: * * *
2011 *
2012 * Force the top of the stack to be a numeric type (integer, real or both).
2013 */
2014case JX9_OP_CVT_NUMC:
2015#ifdef UNTRUST
2016 if( pTos < pStack ){
2017 goto Abort;
2018 }
2019#endif
2020 /* Force a numeric cast */
2021 jx9MemObjToNumeric(pTos);
2022 break;
2023/*
2024 * CVT_ARRAY: * * *
2025 *
2026 * Force the top of the stack to be a hashmap aka 'array'.
2027 */
2028case JX9_OP_CVT_ARRAY:
2029#ifdef UNTRUST
2030 if( pTos < pStack ){
2031 goto Abort;
2032 }
2033#endif
2034 /* Force a hashmap cast */
2035 rc = jx9MemObjToHashmap(pTos);
2036 if( rc != SXRET_OK ){
2037 /* Not so fatal, emit a simple warning */
2038 jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING,
2039 "JX9 engine is running out of memory while performing an array cast");
2040 }
2041 break;
2042/*
2043 * LOADC P1 P2 *
2044 *
2045 * Load a constant [i.e: JX9_EOL, JX9_OS, __TIME__, ...] indexed at P2 in the constant pool.
2046 * If P1 is set, then this constant is candidate for expansion via user installable callbacks.
2047 */
2048case JX9_OP_LOADC: {
2049 jx9_value *pObj;
2050 /* Reserve a room */
2051 pTos++;
2052 if( (pObj = (jx9_value *)SySetAt(&pVm->aLitObj, pInstr->iP2)) != 0 ){
2053 if( pInstr->iP1 == 1 && SyBlobLength(&pObj->sBlob) <= 64 ){
2054 SyHashEntry *pEntry;
2055 /* Candidate for expansion via user defined callbacks */
2056 pEntry = SyHashGet(&pVm->hConstant, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
2057 if( pEntry ){
2058 jx9_constant *pCons = (jx9_constant *)pEntry->pUserData;
2059 /* Set a NULL default value */
2060 MemObjSetType(pTos, MEMOBJ_NULL);
2061 SyBlobReset(&pTos->sBlob);
2062 /* Invoke the callback and deal with the expanded value */
2063 pCons->xExpand(pTos, pCons->pUserData);
2064 /* Mark as constant */
2065 pTos->nIdx = SXU32_HIGH;
2066 break;
2067 }
2068 }
2069 jx9MemObjLoad(pObj, pTos);
2070 }else{
2071 /* Set a NULL value */
2072 MemObjSetType(pTos, MEMOBJ_NULL);
2073 }
2074 /* Mark as constant */
2075 pTos->nIdx = SXU32_HIGH;
2076 break;
2077 }
2078/*
2079 * LOAD: P1 * P3
2080 *
2081 * Load a variable where it's name is taken from the top of the stack or
2082 * from the P3 operand.
2083 * If P1 is set, then perform a lookup only.In other words do not create
2084 * the variable if non existent and push the NULL constant instead.
2085 */
2086case JX9_OP_LOAD:{
2087 jx9_value *pObj;
2088 SyString sName;
2089 if( pInstr->p3 == 0 ){
2090 /* Take the variable name from the top of the stack */
2091#ifdef UNTRUST
2092 if( pTos < pStack ){
2093 goto Abort;
2094 }
2095#endif
2096 /* Force a string cast */
2097 if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){
2098 jx9MemObjToString(pTos);
2099 }
2100 SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob));
2101 }else{
2102 SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3));
2103 /* Reserve a room for the target object */
2104 pTos++;
2105 }
2106 /* Extract the requested memory object */
2107 pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, pInstr->iP1 != 1);
2108 if( pObj == 0 ){
2109 if( pInstr->iP1 ){
2110 /* Variable not found, load NULL */
2111 if( !pInstr->p3 ){
2112 jx9MemObjRelease(pTos);
2113 }else{
2114 MemObjSetType(pTos, MEMOBJ_NULL);
2115 }
2116 pTos->nIdx = SXU32_HIGH; /* Mark as constant */
2117 break;
2118 }else{
2119 /* Fatal error */
2120 VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName);
2121 goto Abort;
2122 }
2123 }
2124 /* Load variable contents */
2125 jx9MemObjLoad(pObj, pTos);
2126 pTos->nIdx = pObj->nIdx;
2127 break;
2128 }
2129/*
2130 * LOAD_MAP P1 * *
2131 *
2132 * Allocate a new empty hashmap (array in the JX9 jargon) and push it on the stack.
2133 * If the P1 operand is greater than zero then pop P1 elements from the
2134 * stack and insert them (key => value pair) in the new hashmap.
2135 */
2136case JX9_OP_LOAD_MAP: {
2137 jx9_hashmap *pMap;
2138 int is_json_object; /* TRUE if we are dealing with a JSON object */
2139 int iIncr = 1;
2140 /* Allocate a new hashmap instance */
2141 pMap = jx9NewHashmap(&(*pVm), 0, 0);
2142 if( pMap == 0 ){
2143 VmErrorFormat(&(*pVm), JX9_CTX_ERR,
2144 "Fatal, JX9 engine is running out of memory while loading JSON array/object at instruction #:%d", pc);
2145 goto Abort;
2146 }
2147 is_json_object = 0;
2148 if( pInstr->iP2 ){
2149 /* JSON object, record that */
2150 pMap->iFlags |= HASHMAP_JSON_OBJECT;
2151 is_json_object = 1;
2152 iIncr = 2;
2153 }
2154 if( pInstr->iP1 > 0 ){
2155 jx9_value *pEntry = &pTos[-pInstr->iP1+1]; /* Point to the first entry */
2156 /* Perform the insertion */
2157 while( pEntry <= pTos ){
2158 /* Standard insertion */
2159 jx9HashmapInsert(pMap,
2160 is_json_object ? pEntry : 0 /* Automatic index assign */,
2161 is_json_object ? &pEntry[1] : pEntry
2162 );
2163 /* Next pair on the stack */
2164 pEntry += iIncr;
2165 }
2166 /* Pop P1 elements */
2167 VmPopOperand(&pTos, pInstr->iP1);
2168 }
2169 /* Push the hashmap */
2170 pTos++;
2171 pTos->x.pOther = pMap;
2172 MemObjSetType(pTos, MEMOBJ_HASHMAP);
2173 break;
2174 }
2175/*
2176 * LOAD_IDX: P1 P2 *
2177 *
2178 * Load a hasmap entry where it's index (either numeric or string) is taken
2179 * from the stack.
2180 * If the index does not refer to a valid element, then push the NULL constant
2181 * instead.
2182 */
2183case JX9_OP_LOAD_IDX: {
2184 jx9_hashmap_node *pNode = 0; /* cc warning */
2185 jx9_hashmap *pMap = 0;
2186 jx9_value *pIdx;
2187 pIdx = 0;
2188 if( pInstr->iP1 == 0 ){
2189 if( !pInstr->iP2){
2190 /* No available index, load NULL */
2191 if( pTos >= pStack ){
2192 jx9MemObjRelease(pTos);
2193 }else{
2194 /* TICKET 1433-020: Empty stack */
2195 pTos++;
2196 MemObjSetType(pTos, MEMOBJ_NULL);
2197 pTos->nIdx = SXU32_HIGH;
2198 }
2199 /* Emit a notice */
2200 jx9VmThrowError(&(*pVm), 0, JX9_CTX_NOTICE,
2201 "JSON Array/Object: Attempt to access an undefined member, JX9 is loading NULL");
2202 break;
2203 }
2204 }else{
2205 pIdx = pTos;
2206 pTos--;
2207 }
2208 if( pTos->iFlags & MEMOBJ_STRING ){
2209 /* String access */
2210 if( pIdx ){
2211 sxu32 nOfft;
2212 if( (pIdx->iFlags & MEMOBJ_INT) == 0 ){
2213 /* Force an int cast */
2214 jx9MemObjToInteger(pIdx);
2215 }
2216 nOfft = (sxu32)pIdx->x.iVal;
2217 if( nOfft >= SyBlobLength(&pTos->sBlob) ){
2218 /* Invalid offset, load null */
2219 jx9MemObjRelease(pTos);
2220 }else{
2221 const char *zData = (const char *)SyBlobData(&pTos->sBlob);
2222 int c = zData[nOfft];
2223 jx9MemObjRelease(pTos);
2224 MemObjSetType(pTos, MEMOBJ_STRING);
2225 SyBlobAppend(&pTos->sBlob, (const void *)&c, sizeof(char));
2226 }
2227 }else{
2228 /* No available index, load NULL */
2229 MemObjSetType(pTos, MEMOBJ_NULL);
2230 }
2231 break;
2232 }
2233 if( pInstr->iP2 && (pTos->iFlags & MEMOBJ_HASHMAP) == 0 ){
2234 if( pTos->nIdx != SXU32_HIGH ){
2235 jx9_value *pObj;
2236 if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
2237 jx9MemObjToHashmap(pObj);
2238 jx9MemObjLoad(pObj, pTos);
2239 }
2240 }
2241 }
2242 rc = SXERR_NOTFOUND; /* Assume the index is invalid */
2243 if( pTos->iFlags & MEMOBJ_HASHMAP ){
2244 /* Point to the hashmap */
2245 pMap = (jx9_hashmap *)pTos->x.pOther;
2246 if( pIdx ){
2247 /* Load the desired entry */
2248 rc = jx9HashmapLookup(pMap, pIdx, &pNode);
2249 }
2250 if( rc != SXRET_OK && pInstr->iP2 ){
2251 /* Create a new empty entry */
2252 rc = jx9HashmapInsert(pMap, pIdx, 0);
2253 if( rc == SXRET_OK ){
2254 /* Point to the last inserted entry */
2255 pNode = pMap->pLast;
2256 }
2257 }
2258 }
2259 if( pIdx ){
2260 jx9MemObjRelease(pIdx);
2261 }
2262 if( rc == SXRET_OK ){
2263 /* Load entry contents */
2264 if( pMap->iRef < 2 ){
2265 /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy
2266 * of the entry value, rather than pointing to it.
2267 */
2268 pTos->nIdx = SXU32_HIGH;
2269 jx9HashmapExtractNodeValue(pNode, pTos, TRUE);
2270 }else{
2271 pTos->nIdx = pNode->nValIdx;
2272 jx9HashmapExtractNodeValue(pNode, pTos, FALSE);
2273 jx9HashmapUnref(pMap);
2274 }
2275 }else{
2276 /* No such entry, load NULL */
2277 jx9MemObjRelease(pTos);
2278 pTos->nIdx = SXU32_HIGH;
2279 }
2280 break;
2281 }
2282/*
2283 * STORE * P2 P3
2284 *
2285 * Perform a store (Assignment) operation.
2286 */
2287case JX9_OP_STORE: {
2288 jx9_value *pObj;
2289 SyString sName;
2290#ifdef UNTRUST
2291 if( pTos < pStack ){
2292 goto Abort;
2293 }
2294#endif
2295 if( pInstr->iP2 ){
2296 sxu32 nIdx;
2297 /* Member store operation */
2298 nIdx = pTos->nIdx;
2299 VmPopOperand(&pTos, 1);
2300 if( nIdx == SXU32_HIGH ){
2301 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR,
2302 "Cannot perform assignment on a constant object attribute, JX9 is loading NULL");
2303 pTos->nIdx = SXU32_HIGH;
2304 }else{
2305 /* Point to the desired memory object */
2306 pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx);
2307 if( pObj ){
2308 /* Perform the store operation */
2309 jx9MemObjStore(pTos, pObj);
2310 }
2311 }
2312 break;
2313 }else if( pInstr->p3 == 0 ){
2314 /* Take the variable name from the next on the stack */
2315 if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){
2316 /* Force a string cast */
2317 jx9MemObjToString(pTos);
2318 }
2319 SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob));
2320 pTos--;
2321#ifdef UNTRUST
2322 if( pTos < pStack ){
2323 goto Abort;
2324 }
2325#endif
2326 }else{
2327 SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3));
2328 }
2329 /* Extract the desired variable and if not available dynamically create it */
2330 pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, TRUE);
2331 if( pObj == 0 ){
2332 VmErrorFormat(&(*pVm), JX9_CTX_ERR,
2333 "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName);
2334 goto Abort;
2335 }
2336 if( !pInstr->p3 ){
2337 jx9MemObjRelease(&pTos[1]);
2338 }
2339 /* Perform the store operation */
2340 jx9MemObjStore(pTos, pObj);
2341 break;
2342 }
2343/*
2344 * STORE_IDX: P1 * P3
2345 *
2346 * Perfrom a store operation an a hashmap entry.
2347 */
2348case JX9_OP_STORE_IDX: {
2349 jx9_hashmap *pMap = 0; /* cc warning */
2350 jx9_value *pKey;
2351 sxu32 nIdx;
2352 if( pInstr->iP1 ){
2353 /* Key is next on stack */
2354 pKey = pTos;
2355 pTos--;
2356 }else{
2357 pKey = 0;
2358 }
2359 nIdx = pTos->nIdx;
2360 if( pTos->iFlags & MEMOBJ_HASHMAP ){
2361 /* Hashmap already loaded */
2362 pMap = (jx9_hashmap *)pTos->x.pOther;
2363 if( pMap->iRef < 2 ){
2364 /* TICKET 1433-48: Prevent garbage collection */
2365 pMap->iRef = 2;
2366 }
2367 }else{
2368 jx9_value *pObj;
2369 pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx);
2370 if( pObj == 0 ){
2371 if( pKey ){
2372 jx9MemObjRelease(pKey);
2373 }
2374 VmPopOperand(&pTos, 1);
2375 break;
2376 }
2377 /* Phase#1: Load the array */
2378 if( (pObj->iFlags & MEMOBJ_STRING) ){
2379 VmPopOperand(&pTos, 1);
2380 if( (pTos->iFlags&MEMOBJ_STRING) == 0 ){
2381 /* Force a string cast */
2382 jx9MemObjToString(pTos);
2383 }
2384 if( pKey == 0 ){
2385 /* Append string */
2386 if( SyBlobLength(&pTos->sBlob) > 0 ){
2387 SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob));
2388 }
2389 }else{
2390 sxu32 nOfft;
2391 if((pKey->iFlags & MEMOBJ_INT)){
2392 /* Force an int cast */
2393 jx9MemObjToInteger(pKey);
2394 }
2395 nOfft = (sxu32)pKey->x.iVal;
2396 if( nOfft < SyBlobLength(&pObj->sBlob) && SyBlobLength(&pTos->sBlob) > 0 ){
2397 const char *zBlob = (const char *)SyBlobData(&pTos->sBlob);
2398 char *zData = (char *)SyBlobData(&pObj->sBlob);
2399 zData[nOfft] = zBlob[0];
2400 }else{
2401 if( SyBlobLength(&pTos->sBlob) >= sizeof(char) ){
2402 /* Perform an append operation */
2403 SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), sizeof(char));
2404 }
2405 }
2406 }
2407 if( pKey ){
2408 jx9MemObjRelease(pKey);
2409 }
2410 break;
2411 }else if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){
2412 /* Force a hashmap cast */
2413 rc = jx9MemObjToHashmap(pObj);
2414 if( rc != SXRET_OK ){
2415 VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while creating a new array");
2416 goto Abort;
2417 }
2418 }
2419 pMap = (jx9_hashmap *)pObj->x.pOther;
2420 }
2421 VmPopOperand(&pTos, 1);
2422 /* Phase#2: Perform the insertion */
2423 jx9HashmapInsert(pMap, pKey, pTos);
2424 if( pKey ){
2425 jx9MemObjRelease(pKey);
2426 }
2427 break;
2428 }
2429/*
2430 * INCR: P1 * *
2431 *
2432 * Force a numeric cast and increment the top of the stack by 1.
2433 * If the P1 operand is set then perform a duplication of the top of
2434 * the stack and increment after that.
2435 */
2436case JX9_OP_INCR:
2437#ifdef UNTRUST
2438 if( pTos < pStack ){
2439 goto Abort;
2440 }
2441#endif
2442 if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0 ){
2443 if( pTos->nIdx != SXU32_HIGH ){
2444 jx9_value *pObj;
2445 if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
2446 /* Force a numeric cast */
2447 jx9MemObjToNumeric(pObj);
2448 if( pObj->iFlags & MEMOBJ_REAL ){
2449 pObj->x.rVal++;
2450 /* Try to get an integer representation */
2451 jx9MemObjTryInteger(pTos);
2452 }else{
2453 pObj->x.iVal++;
2454 MemObjSetType(pTos, MEMOBJ_INT);
2455 }
2456 if( pInstr->iP1 ){
2457 /* Pre-icrement */
2458 jx9MemObjStore(pObj, pTos);
2459 }
2460 }
2461 }else{
2462 if( pInstr->iP1 ){
2463 /* Force a numeric cast */
2464 jx9MemObjToNumeric(pTos);
2465 /* Pre-increment */
2466 if( pTos->iFlags & MEMOBJ_REAL ){
2467 pTos->x.rVal++;
2468 /* Try to get an integer representation */
2469 jx9MemObjTryInteger(pTos);
2470 }else{
2471 pTos->x.iVal++;
2472 MemObjSetType(pTos, MEMOBJ_INT);
2473 }
2474 }
2475 }
2476 }
2477 break;
2478/*
2479 * DECR: P1 * *
2480 *
2481 * Force a numeric cast and decrement the top of the stack by 1.
2482 * If the P1 operand is set then perform a duplication of the top of the stack
2483 * and decrement after that.
2484 */
2485case JX9_OP_DECR:
2486#ifdef UNTRUST
2487 if( pTos < pStack ){
2488 goto Abort;
2489 }
2490#endif
2491 if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES|MEMOBJ_NULL)) == 0 ){
2492 /* Force a numeric cast */
2493 jx9MemObjToNumeric(pTos);
2494 if( pTos->nIdx != SXU32_HIGH ){
2495 jx9_value *pObj;
2496 if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
2497 /* Force a numeric cast */
2498 jx9MemObjToNumeric(pObj);
2499 if( pObj->iFlags & MEMOBJ_REAL ){
2500 pObj->x.rVal--;
2501 /* Try to get an integer representation */
2502 jx9MemObjTryInteger(pTos);
2503 }else{
2504 pObj->x.iVal--;
2505 MemObjSetType(pTos, MEMOBJ_INT);
2506 }
2507 if( pInstr->iP1 ){
2508 /* Pre-icrement */
2509 jx9MemObjStore(pObj, pTos);
2510 }
2511 }
2512 }else{
2513 if( pInstr->iP1 ){
2514 /* Pre-increment */
2515 if( pTos->iFlags & MEMOBJ_REAL ){
2516 pTos->x.rVal--;
2517 /* Try to get an integer representation */
2518 jx9MemObjTryInteger(pTos);
2519 }else{
2520 pTos->x.iVal--;
2521 MemObjSetType(pTos, MEMOBJ_INT);
2522 }
2523 }
2524 }
2525 }
2526 break;
2527/*
2528 * UMINUS: * * *
2529 *
2530 * Perform a unary minus operation.
2531 */
2532case JX9_OP_UMINUS:
2533#ifdef UNTRUST
2534 if( pTos < pStack ){
2535 goto Abort;
2536 }
2537#endif
2538 /* Force a numeric (integer, real or both) cast */
2539 jx9MemObjToNumeric(pTos);
2540 if( pTos->iFlags & MEMOBJ_REAL ){
2541 pTos->x.rVal = -pTos->x.rVal;
2542 }
2543 if( pTos->iFlags & MEMOBJ_INT ){
2544 pTos->x.iVal = -pTos->x.iVal;
2545 }
2546 break;
2547/*
2548 * UPLUS: * * *
2549 *
2550 * Perform a unary plus operation.
2551 */
2552case JX9_OP_UPLUS:
2553#ifdef UNTRUST
2554 if( pTos < pStack ){
2555 goto Abort;
2556 }
2557#endif
2558 /* Force a numeric (integer, real or both) cast */
2559 jx9MemObjToNumeric(pTos);
2560 if( pTos->iFlags & MEMOBJ_REAL ){
2561 pTos->x.rVal = +pTos->x.rVal;
2562 }
2563 if( pTos->iFlags & MEMOBJ_INT ){
2564 pTos->x.iVal = +pTos->x.iVal;
2565 }
2566 break;
2567/*
2568 * OP_LNOT: * * *
2569 *
2570 * Interpret the top of the stack as a boolean value. Replace it
2571 * with its complement.
2572 */
2573case JX9_OP_LNOT:
2574#ifdef UNTRUST
2575 if( pTos < pStack ){
2576 goto Abort;
2577 }
2578#endif
2579 /* Force a boolean cast */
2580 if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){
2581 jx9MemObjToBool(pTos);
2582 }
2583 pTos->x.iVal = !pTos->x.iVal;
2584 break;
2585/*
2586 * OP_BITNOT: * * *
2587 *
2588 * Interpret the top of the stack as an value.Replace it
2589 * with its ones-complement.
2590 */
2591case JX9_OP_BITNOT:
2592#ifdef UNTRUST
2593 if( pTos < pStack ){
2594 goto Abort;
2595 }
2596#endif
2597 /* Force an integer cast */
2598 if( (pTos->iFlags & MEMOBJ_INT) == 0 ){
2599 jx9MemObjToInteger(pTos);
2600 }
2601 pTos->x.iVal = ~pTos->x.iVal;
2602 break;
2603/* OP_MUL * * *
2604 * OP_MUL_STORE * * *
2605 *
2606 * Pop the top two elements from the stack, multiply them together,
2607 * and push the result back onto the stack.
2608 */
2609case JX9_OP_MUL:
2610case JX9_OP_MUL_STORE: {
2611 jx9_value *pNos = &pTos[-1];
2612 /* Force the operand to be numeric */
2613#ifdef UNTRUST
2614 if( pNos < pStack ){
2615 goto Abort;
2616 }
2617#endif
2618 jx9MemObjToNumeric(pTos);
2619 jx9MemObjToNumeric(pNos);
2620 /* Perform the requested operation */
2621 if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){
2622 /* Floating point arithemic */
2623 jx9_real a, b, r;
2624 if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){
2625 jx9MemObjToReal(pTos);
2626 }
2627 if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){
2628 jx9MemObjToReal(pNos);
2629 }
2630 a = pNos->x.rVal;
2631 b = pTos->x.rVal;
2632 r = a * b;
2633 /* Push the result */
2634 pNos->x.rVal = r;
2635 MemObjSetType(pNos, MEMOBJ_REAL);
2636 /* Try to get an integer representation */
2637 jx9MemObjTryInteger(pNos);
2638 }else{
2639 /* Integer arithmetic */
2640 sxi64 a, b, r;
2641 a = pNos->x.iVal;
2642 b = pTos->x.iVal;
2643 r = a * b;
2644 /* Push the result */
2645 pNos->x.iVal = r;
2646 MemObjSetType(pNos, MEMOBJ_INT);
2647 }
2648 if( pInstr->iOp == JX9_OP_MUL_STORE ){
2649 jx9_value *pObj;
2650 if( pTos->nIdx == SXU32_HIGH ){
2651 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
2652 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
2653 jx9MemObjStore(pNos, pObj);
2654 }
2655 }
2656 VmPopOperand(&pTos, 1);
2657 break;
2658 }
2659/* OP_ADD * * *
2660 *
2661 * Pop the top two elements from the stack, add them together,
2662 * and push the result back onto the stack.
2663 */
2664case JX9_OP_ADD:{
2665 jx9_value *pNos = &pTos[-1];
2666#ifdef UNTRUST
2667 if( pNos < pStack ){
2668 goto Abort;
2669 }
2670#endif
2671 /* Perform the addition */
2672 jx9MemObjAdd(pNos, pTos, FALSE);
2673 VmPopOperand(&pTos, 1);
2674 break;
2675 }
2676/*
2677 * OP_ADD_STORE * * *
2678 *
2679 * Pop the top two elements from the stack, add them together,
2680 * and push the result back onto the stack.
2681 */
2682case JX9_OP_ADD_STORE:{
2683 jx9_value *pNos = &pTos[-1];
2684 jx9_value *pObj;
2685 sxu32 nIdx;
2686#ifdef UNTRUST
2687 if( pNos < pStack ){
2688 goto Abort;
2689 }
2690#endif
2691 /* Perform the addition */
2692 nIdx = pTos->nIdx;
2693 jx9MemObjAdd(pTos, pNos, TRUE);
2694 /* Peform the store operation */
2695 if( nIdx == SXU32_HIGH ){
2696 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
2697 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx)) != 0 ){
2698 jx9MemObjStore(pTos, pObj);
2699 }
2700 /* Ticket 1433-35: Perform a stack dup */
2701 jx9MemObjStore(pTos, pNos);
2702 VmPopOperand(&pTos, 1);
2703 break;
2704 }
2705/* OP_SUB * * *
2706 *
2707 * Pop the top two elements from the stack, subtract the
2708 * first (what was next on the stack) from the second (the
2709 * top of the stack) and push the result back onto the stack.
2710 */
2711case JX9_OP_SUB: {
2712 jx9_value *pNos = &pTos[-1];
2713#ifdef UNTRUST
2714 if( pNos < pStack ){
2715 goto Abort;
2716 }
2717#endif
2718 if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){
2719 /* Floating point arithemic */
2720 jx9_real a, b, r;
2721 if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){
2722 jx9MemObjToReal(pTos);
2723 }
2724 if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){
2725 jx9MemObjToReal(pNos);
2726 }
2727 a = pNos->x.rVal;
2728 b = pTos->x.rVal;
2729 r = a - b;
2730 /* Push the result */
2731 pNos->x.rVal = r;
2732 MemObjSetType(pNos, MEMOBJ_REAL);
2733 /* Try to get an integer representation */
2734 jx9MemObjTryInteger(pNos);
2735 }else{
2736 /* Integer arithmetic */
2737 sxi64 a, b, r;
2738 a = pNos->x.iVal;
2739 b = pTos->x.iVal;
2740 r = a - b;
2741 /* Push the result */
2742 pNos->x.iVal = r;
2743 MemObjSetType(pNos, MEMOBJ_INT);
2744 }
2745 VmPopOperand(&pTos, 1);
2746 break;
2747 }
2748/* OP_SUB_STORE * * *
2749 *
2750 * Pop the top two elements from the stack, subtract the
2751 * first (what was next on the stack) from the second (the
2752 * top of the stack) and push the result back onto the stack.
2753 */
2754case JX9_OP_SUB_STORE: {
2755 jx9_value *pNos = &pTos[-1];
2756 jx9_value *pObj;
2757#ifdef UNTRUST
2758 if( pNos < pStack ){
2759 goto Abort;
2760 }
2761#endif
2762 if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){
2763 /* Floating point arithemic */
2764 jx9_real a, b, r;
2765 if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){
2766 jx9MemObjToReal(pTos);
2767 }
2768 if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){
2769 jx9MemObjToReal(pNos);
2770 }
2771 a = pTos->x.rVal;
2772 b = pNos->x.rVal;
2773 r = a - b;
2774 /* Push the result */
2775 pNos->x.rVal = r;
2776 MemObjSetType(pNos, MEMOBJ_REAL);
2777 /* Try to get an integer representation */
2778 jx9MemObjTryInteger(pNos);
2779 }else{
2780 /* Integer arithmetic */
2781 sxi64 a, b, r;
2782 a = pTos->x.iVal;
2783 b = pNos->x.iVal;
2784 r = a - b;
2785 /* Push the result */
2786 pNos->x.iVal = r;
2787 MemObjSetType(pNos, MEMOBJ_INT);
2788 }
2789 if( pTos->nIdx == SXU32_HIGH ){
2790 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
2791 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
2792 jx9MemObjStore(pNos, pObj);
2793 }
2794 VmPopOperand(&pTos, 1);
2795 break;
2796 }
2797
2798/*
2799 * OP_MOD * * *
2800 *
2801 * Pop the top two elements from the stack, divide the
2802 * first (what was next on the stack) from the second (the
2803 * top of the stack) and push the remainder after division
2804 * onto the stack.
2805 * Note: Only integer arithemtic is allowed.
2806 */
2807case JX9_OP_MOD:{
2808 jx9_value *pNos = &pTos[-1];
2809 sxi64 a, b, r;
2810#ifdef UNTRUST
2811 if( pNos < pStack ){
2812 goto Abort;
2813 }
2814#endif
2815 /* Force the operands to be integer */
2816 if( (pTos->iFlags & MEMOBJ_INT) == 0 ){
2817 jx9MemObjToInteger(pTos);
2818 }
2819 if( (pNos->iFlags & MEMOBJ_INT) == 0 ){
2820 jx9MemObjToInteger(pNos);
2821 }
2822 /* Perform the requested operation */
2823 a = pNos->x.iVal;
2824 b = pTos->x.iVal;
2825 if( b == 0 ){
2826 r = 0;
2827 VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a);
2828 /* goto Abort; */
2829 }else{
2830 r = a%b;
2831 }
2832 /* Push the result */
2833 pNos->x.iVal = r;
2834 MemObjSetType(pNos, MEMOBJ_INT);
2835 VmPopOperand(&pTos, 1);
2836 break;
2837 }
2838/*
2839 * OP_MOD_STORE * * *
2840 *
2841 * Pop the top two elements from the stack, divide the
2842 * first (what was next on the stack) from the second (the
2843 * top of the stack) and push the remainder after division
2844 * onto the stack.
2845 * Note: Only integer arithemtic is allowed.
2846 */
2847case JX9_OP_MOD_STORE: {
2848 jx9_value *pNos = &pTos[-1];
2849 jx9_value *pObj;
2850 sxi64 a, b, r;
2851#ifdef UNTRUST
2852 if( pNos < pStack ){
2853 goto Abort;
2854 }
2855#endif
2856 /* Force the operands to be integer */
2857 if( (pTos->iFlags & MEMOBJ_INT) == 0 ){
2858 jx9MemObjToInteger(pTos);
2859 }
2860 if( (pNos->iFlags & MEMOBJ_INT) == 0 ){
2861 jx9MemObjToInteger(pNos);
2862 }
2863 /* Perform the requested operation */
2864 a = pTos->x.iVal;
2865 b = pNos->x.iVal;
2866 if( b == 0 ){
2867 r = 0;
2868 VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a);
2869 /* goto Abort; */
2870 }else{
2871 r = a%b;
2872 }
2873 /* Push the result */
2874 pNos->x.iVal = r;
2875 MemObjSetType(pNos, MEMOBJ_INT);
2876 if( pTos->nIdx == SXU32_HIGH ){
2877 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
2878 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
2879 jx9MemObjStore(pNos, pObj);
2880 }
2881 VmPopOperand(&pTos, 1);
2882 break;
2883 }
2884/*
2885 * OP_DIV * * *
2886 *
2887 * Pop the top two elements from the stack, divide the
2888 * first (what was next on the stack) from the second (the
2889 * top of the stack) and push the result onto the stack.
2890 * Note: Only floating point arithemtic is allowed.
2891 */
2892case JX9_OP_DIV:{
2893 jx9_value *pNos = &pTos[-1];
2894 jx9_real a, b, r;
2895#ifdef UNTRUST
2896 if( pNos < pStack ){
2897 goto Abort;
2898 }
2899#endif
2900 /* Force the operands to be real */
2901 if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){
2902 jx9MemObjToReal(pTos);
2903 }
2904 if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){
2905 jx9MemObjToReal(pNos);
2906 }
2907 /* Perform the requested operation */
2908 a = pNos->x.rVal;
2909 b = pTos->x.rVal;
2910 if( b == 0 ){
2911 /* Division by zero */
2912 r = 0;
2913 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Division by zero");
2914 /* goto Abort; */
2915 }else{
2916 r = a/b;
2917 /* Push the result */
2918 pNos->x.rVal = r;
2919 MemObjSetType(pNos, MEMOBJ_REAL);
2920 /* Try to get an integer representation */
2921 jx9MemObjTryInteger(pNos);
2922 }
2923 VmPopOperand(&pTos, 1);
2924 break;
2925 }
2926/*
2927 * OP_DIV_STORE * * *
2928 *
2929 * Pop the top two elements from the stack, divide the
2930 * first (what was next on the stack) from the second (the
2931 * top of the stack) and push the result onto the stack.
2932 * Note: Only floating point arithemtic is allowed.
2933 */
2934case JX9_OP_DIV_STORE:{
2935 jx9_value *pNos = &pTos[-1];
2936 jx9_value *pObj;
2937 jx9_real a, b, r;
2938#ifdef UNTRUST
2939 if( pNos < pStack ){
2940 goto Abort;
2941 }
2942#endif
2943 /* Force the operands to be real */
2944 if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){
2945 jx9MemObjToReal(pTos);
2946 }
2947 if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){
2948 jx9MemObjToReal(pNos);
2949 }
2950 /* Perform the requested operation */
2951 a = pTos->x.rVal;
2952 b = pNos->x.rVal;
2953 if( b == 0 ){
2954 /* Division by zero */
2955 r = 0;
2956 VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd/0", a);
2957 /* goto Abort; */
2958 }else{
2959 r = a/b;
2960 /* Push the result */
2961 pNos->x.rVal = r;
2962 MemObjSetType(pNos, MEMOBJ_REAL);
2963 /* Try to get an integer representation */
2964 jx9MemObjTryInteger(pNos);
2965 }
2966 if( pTos->nIdx == SXU32_HIGH ){
2967 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
2968 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
2969 jx9MemObjStore(pNos, pObj);
2970 }
2971 VmPopOperand(&pTos, 1);
2972 break;
2973 }
2974/* OP_BAND * * *
2975 *
2976 * Pop the top two elements from the stack. Convert both elements
2977 * to integers. Push back onto the stack the bit-wise AND of the
2978 * two elements.
2979*/
2980/* OP_BOR * * *
2981 *
2982 * Pop the top two elements from the stack. Convert both elements
2983 * to integers. Push back onto the stack the bit-wise OR of the
2984 * two elements.
2985 */
2986/* OP_BXOR * * *
2987 *
2988 * Pop the top two elements from the stack. Convert both elements
2989 * to integers. Push back onto the stack the bit-wise XOR of the
2990 * two elements.
2991 */
2992case JX9_OP_BAND:
2993case JX9_OP_BOR:
2994case JX9_OP_BXOR:{
2995 jx9_value *pNos = &pTos[-1];
2996 sxi64 a, b, r;
2997#ifdef UNTRUST
2998 if( pNos < pStack ){
2999 goto Abort;
3000 }
3001#endif
3002 /* Force the operands to be integer */
3003 if( (pTos->iFlags & MEMOBJ_INT) == 0 ){
3004 jx9MemObjToInteger(pTos);
3005 }
3006 if( (pNos->iFlags & MEMOBJ_INT) == 0 ){
3007 jx9MemObjToInteger(pNos);
3008 }
3009 /* Perform the requested operation */
3010 a = pNos->x.iVal;
3011 b = pTos->x.iVal;
3012 switch(pInstr->iOp){
3013 case JX9_OP_BOR_STORE:
3014 case JX9_OP_BOR: r = a|b; break;
3015 case JX9_OP_BXOR_STORE:
3016 case JX9_OP_BXOR: r = a^b; break;
3017 case JX9_OP_BAND_STORE:
3018 case JX9_OP_BAND:
3019 default: r = a&b; break;
3020 }
3021 /* Push the result */
3022 pNos->x.iVal = r;
3023 MemObjSetType(pNos, MEMOBJ_INT);
3024 VmPopOperand(&pTos, 1);
3025 break;
3026 }
3027/* OP_BAND_STORE * * *
3028 *
3029 * Pop the top two elements from the stack. Convert both elements
3030 * to integers. Push back onto the stack the bit-wise AND of the
3031 * two elements.
3032*/
3033/* OP_BOR_STORE * * *
3034 *
3035 * Pop the top two elements from the stack. Convert both elements
3036 * to integers. Push back onto the stack the bit-wise OR of the
3037 * two elements.
3038 */
3039/* OP_BXOR_STORE * * *
3040 *
3041 * Pop the top two elements from the stack. Convert both elements
3042 * to integers. Push back onto the stack the bit-wise XOR of the
3043 * two elements.
3044 */
3045case JX9_OP_BAND_STORE:
3046case JX9_OP_BOR_STORE:
3047case JX9_OP_BXOR_STORE:{
3048 jx9_value *pNos = &pTos[-1];
3049 jx9_value *pObj;
3050 sxi64 a, b, r;
3051#ifdef UNTRUST
3052 if( pNos < pStack ){
3053 goto Abort;
3054 }
3055#endif
3056 /* Force the operands to be integer */
3057 if( (pTos->iFlags & MEMOBJ_INT) == 0 ){
3058 jx9MemObjToInteger(pTos);
3059 }
3060 if( (pNos->iFlags & MEMOBJ_INT) == 0 ){
3061 jx9MemObjToInteger(pNos);
3062 }
3063 /* Perform the requested operation */
3064 a = pTos->x.iVal;
3065 b = pNos->x.iVal;
3066 switch(pInstr->iOp){
3067 case JX9_OP_BOR_STORE:
3068 case JX9_OP_BOR: r = a|b; break;
3069 case JX9_OP_BXOR_STORE:
3070 case JX9_OP_BXOR: r = a^b; break;
3071 case JX9_OP_BAND_STORE:
3072 case JX9_OP_BAND:
3073 default: r = a&b; break;
3074 }
3075 /* Push the result */
3076 pNos->x.iVal = r;
3077 MemObjSetType(pNos, MEMOBJ_INT);
3078 if( pTos->nIdx == SXU32_HIGH ){
3079 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
3080 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
3081 jx9MemObjStore(pNos, pObj);
3082 }
3083 VmPopOperand(&pTos, 1);
3084 break;
3085 }
3086/* OP_SHL * * *
3087 *
3088 * Pop the top two elements from the stack. Convert both elements
3089 * to integers. Push back onto the stack the second element shifted
3090 * left by N bits where N is the top element on the stack.
3091 * Note: Only integer arithmetic is allowed.
3092 */
3093/* OP_SHR * * *
3094 *
3095 * Pop the top two elements from the stack. Convert both elements
3096 * to integers. Push back onto the stack the second element shifted
3097 * right by N bits where N is the top element on the stack.
3098 * Note: Only integer arithmetic is allowed.
3099 */
3100case JX9_OP_SHL:
3101case JX9_OP_SHR: {
3102 jx9_value *pNos = &pTos[-1];
3103 sxi64 a, r;
3104 sxi32 b;
3105#ifdef UNTRUST
3106 if( pNos < pStack ){
3107 goto Abort;
3108 }
3109#endif
3110 /* Force the operands to be integer */
3111 if( (pTos->iFlags & MEMOBJ_INT) == 0 ){
3112 jx9MemObjToInteger(pTos);
3113 }
3114 if( (pNos->iFlags & MEMOBJ_INT) == 0 ){
3115 jx9MemObjToInteger(pNos);
3116 }
3117 /* Perform the requested operation */
3118 a = pNos->x.iVal;
3119 b = (sxi32)pTos->x.iVal;
3120 if( pInstr->iOp == JX9_OP_SHL ){
3121 r = a << b;
3122 }else{
3123 r = a >> b;
3124 }
3125 /* Push the result */
3126 pNos->x.iVal = r;
3127 MemObjSetType(pNos, MEMOBJ_INT);
3128 VmPopOperand(&pTos, 1);
3129 break;
3130 }
3131/* OP_SHL_STORE * * *
3132 *
3133 * Pop the top two elements from the stack. Convert both elements
3134 * to integers. Push back onto the stack the second element shifted
3135 * left by N bits where N is the top element on the stack.
3136 * Note: Only integer arithmetic is allowed.
3137 */
3138/* OP_SHR_STORE * * *
3139 *
3140 * Pop the top two elements from the stack. Convert both elements
3141 * to integers. Push back onto the stack the second element shifted
3142 * right by N bits where N is the top element on the stack.
3143 * Note: Only integer arithmetic is allowed.
3144 */
3145case JX9_OP_SHL_STORE:
3146case JX9_OP_SHR_STORE: {
3147 jx9_value *pNos = &pTos[-1];
3148 jx9_value *pObj;
3149 sxi64 a, r;
3150 sxi32 b;
3151#ifdef UNTRUST
3152 if( pNos < pStack ){
3153 goto Abort;
3154 }
3155#endif
3156 /* Force the operands to be integer */
3157 if( (pTos->iFlags & MEMOBJ_INT) == 0 ){
3158 jx9MemObjToInteger(pTos);
3159 }
3160 if( (pNos->iFlags & MEMOBJ_INT) == 0 ){
3161 jx9MemObjToInteger(pNos);
3162 }
3163 /* Perform the requested operation */
3164 a = pTos->x.iVal;
3165 b = (sxi32)pNos->x.iVal;
3166 if( pInstr->iOp == JX9_OP_SHL_STORE ){
3167 r = a << b;
3168 }else{
3169 r = a >> b;
3170 }
3171 /* Push the result */
3172 pNos->x.iVal = r;
3173 MemObjSetType(pNos, MEMOBJ_INT);
3174 if( pTos->nIdx == SXU32_HIGH ){
3175 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
3176 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
3177 jx9MemObjStore(pNos, pObj);
3178 }
3179 VmPopOperand(&pTos, 1);
3180 break;
3181 }
3182/* CAT: P1 * *
3183 *
3184 * Pop P1 elements from the stack. Concatenate them togeher and push the result
3185 * back.
3186 */
3187case JX9_OP_CAT:{
3188 jx9_value *pNos, *pCur;
3189 if( pInstr->iP1 < 1 ){
3190 pNos = &pTos[-1];
3191 }else{
3192 pNos = &pTos[-pInstr->iP1+1];
3193 }
3194#ifdef UNTRUST
3195 if( pNos < pStack ){
3196 goto Abort;
3197 }
3198#endif
3199 /* Force a string cast */
3200 if( (pNos->iFlags & MEMOBJ_STRING) == 0 ){
3201 jx9MemObjToString(pNos);
3202 }
3203 pCur = &pNos[1];
3204 while( pCur <= pTos ){
3205 if( (pCur->iFlags & MEMOBJ_STRING) == 0 ){
3206 jx9MemObjToString(pCur);
3207 }
3208 /* Perform the concatenation */
3209 if( SyBlobLength(&pCur->sBlob) > 0 ){
3210 jx9MemObjStringAppend(pNos, (const char *)SyBlobData(&pCur->sBlob), SyBlobLength(&pCur->sBlob));
3211 }
3212 SyBlobRelease(&pCur->sBlob);
3213 pCur++;
3214 }
3215 pTos = pNos;
3216 break;
3217 }
3218/* CAT_STORE: * * *
3219 *
3220 * Pop two elements from the stack. Concatenate them togeher and push the result
3221 * back.
3222 */
3223case JX9_OP_CAT_STORE:{
3224 jx9_value *pNos = &pTos[-1];
3225 jx9_value *pObj;
3226#ifdef UNTRUST
3227 if( pNos < pStack ){
3228 goto Abort;
3229 }
3230#endif
3231 if((pTos->iFlags & MEMOBJ_STRING) == 0 ){
3232 /* Force a string cast */
3233 jx9MemObjToString(pTos);
3234 }
3235 if((pNos->iFlags & MEMOBJ_STRING) == 0 ){
3236 /* Force a string cast */
3237 jx9MemObjToString(pNos);
3238 }
3239 /* Perform the concatenation (Reverse order) */
3240 if( SyBlobLength(&pNos->sBlob) > 0 ){
3241 jx9MemObjStringAppend(pTos, (const char *)SyBlobData(&pNos->sBlob), SyBlobLength(&pNos->sBlob));
3242 }
3243 /* Perform the store operation */
3244 if( pTos->nIdx == SXU32_HIGH ){
3245 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute");
3246 }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){
3247 jx9MemObjStore(pTos, pObj);
3248 }
3249 jx9MemObjStore(pTos, pNos);
3250 VmPopOperand(&pTos, 1);
3251 break;
3252 }
3253/* OP_AND: * * *
3254 *
3255 * Pop two values off the stack. Take the logical AND of the
3256 * two values and push the resulting boolean value back onto the
3257 * stack.
3258 */
3259/* OP_OR: * * *
3260 *
3261 * Pop two values off the stack. Take the logical OR of the
3262 * two values and push the resulting boolean value back onto the
3263 * stack.
3264 */
3265case JX9_OP_LAND:
3266case JX9_OP_LOR: {
3267 jx9_value *pNos = &pTos[-1];
3268 sxi32 v1, v2; /* 0==TRUE, 1==FALSE, 2==UNKNOWN or NULL */
3269#ifdef UNTRUST
3270 if( pNos < pStack ){
3271 goto Abort;
3272 }
3273#endif
3274 /* Force a boolean cast */
3275 if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){
3276 jx9MemObjToBool(pTos);
3277 }
3278 if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){
3279 jx9MemObjToBool(pNos);
3280 }
3281 v1 = pNos->x.iVal == 0 ? 1 : 0;
3282 v2 = pTos->x.iVal == 0 ? 1 : 0;
3283 if( pInstr->iOp == JX9_OP_LAND ){
3284 static const unsigned char and_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 };
3285 v1 = and_logic[v1*3+v2];
3286 }else{
3287 static const unsigned char or_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 };
3288 v1 = or_logic[v1*3+v2];
3289 }
3290 if( v1 == 2 ){
3291 v1 = 1;
3292 }
3293 VmPopOperand(&pTos, 1);
3294 pTos->x.iVal = v1 == 0 ? 1 : 0;
3295 MemObjSetType(pTos, MEMOBJ_BOOL);
3296 break;
3297 }
3298/* OP_LXOR: * * *
3299 *
3300 * Pop two values off the stack. Take the logical XOR of the
3301 * two values and push the resulting boolean value back onto the
3302 * stack.
3303 * According to the JX9 language reference manual:
3304 * $a xor $b is evaluated to TRUE if either $a or $b is
3305 * TRUE, but not both.
3306 */
3307case JX9_OP_LXOR:{
3308 jx9_value *pNos = &pTos[-1];
3309 sxi32 v = 0;
3310#ifdef UNTRUST
3311 if( pNos < pStack ){
3312 goto Abort;
3313 }
3314#endif
3315 /* Force a boolean cast */
3316 if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){
3317 jx9MemObjToBool(pTos);
3318 }
3319 if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){
3320 jx9MemObjToBool(pNos);
3321 }
3322 if( (pNos->x.iVal && !pTos->x.iVal) || (pTos->x.iVal && !pNos->x.iVal) ){
3323 v = 1;
3324 }
3325 VmPopOperand(&pTos, 1);
3326 pTos->x.iVal = v;
3327 MemObjSetType(pTos, MEMOBJ_BOOL);
3328 break;
3329 }
3330/* OP_EQ P1 P2 P3
3331 *
3332 * Pop the top two elements from the stack. If they are equal, then
3333 * jump to instruction P2. Otherwise, continue to the next instruction.
3334 * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the
3335 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3336 */
3337/* OP_NEQ P1 P2 P3
3338 *
3339 * Pop the top two elements from the stack. If they are not equal, then
3340 * jump to instruction P2. Otherwise, continue to the next instruction.
3341 * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the
3342 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3343 */
3344case JX9_OP_EQ:
3345case JX9_OP_NEQ: {
3346 jx9_value *pNos = &pTos[-1];
3347 /* Perform the comparison and act accordingly */
3348#ifdef UNTRUST
3349 if( pNos < pStack ){
3350 goto Abort;
3351 }
3352#endif
3353 rc = jx9MemObjCmp(pNos, pTos, FALSE, 0);
3354 if( pInstr->iOp == JX9_OP_EQ ){
3355 rc = rc == 0;
3356 }else{
3357 rc = rc != 0;
3358 }
3359 VmPopOperand(&pTos, 1);
3360 if( !pInstr->iP2 ){
3361 /* Push comparison result without taking the jump */
3362 jx9MemObjRelease(pTos);
3363 pTos->x.iVal = rc;
3364 /* Invalidate any prior representation */
3365 MemObjSetType(pTos, MEMOBJ_BOOL);
3366 }else{
3367 if( rc ){
3368 /* Jump to the desired location */
3369 pc = pInstr->iP2 - 1;
3370 VmPopOperand(&pTos, 1);
3371 }
3372 }
3373 break;
3374 }
3375/* OP_TEQ P1 P2 *
3376 *
3377 * Pop the top two elements from the stack. If they have the same type and are equal
3378 * then jump to instruction P2. Otherwise, continue to the next instruction.
3379 * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the
3380 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3381 */
3382case JX9_OP_TEQ: {
3383 jx9_value *pNos = &pTos[-1];
3384 /* Perform the comparison and act accordingly */
3385#ifdef UNTRUST
3386 if( pNos < pStack ){
3387 goto Abort;
3388 }
3389#endif
3390 rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) == 0;
3391 VmPopOperand(&pTos, 1);
3392 if( !pInstr->iP2 ){
3393 /* Push comparison result without taking the jump */
3394 jx9MemObjRelease(pTos);
3395 pTos->x.iVal = rc;
3396 /* Invalidate any prior representation */
3397 MemObjSetType(pTos, MEMOBJ_BOOL);
3398 }else{
3399 if( rc ){
3400 /* Jump to the desired location */
3401 pc = pInstr->iP2 - 1;
3402 VmPopOperand(&pTos, 1);
3403 }
3404 }
3405 break;
3406 }
3407/* OP_TNE P1 P2 *
3408 *
3409 * Pop the top two elements from the stack.If they are not equal an they are not
3410 * of the same type, then jump to instruction P2. Otherwise, continue to the next
3411 * instruction.
3412 * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the
3413 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3414 *
3415 */
3416case JX9_OP_TNE: {
3417 jx9_value *pNos = &pTos[-1];
3418 /* Perform the comparison and act accordingly */
3419#ifdef UNTRUST
3420 if( pNos < pStack ){
3421 goto Abort;
3422 }
3423#endif
3424 rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) != 0;
3425 VmPopOperand(&pTos, 1);
3426 if( !pInstr->iP2 ){
3427 /* Push comparison result without taking the jump */
3428 jx9MemObjRelease(pTos);
3429 pTos->x.iVal = rc;
3430 /* Invalidate any prior representation */
3431 MemObjSetType(pTos, MEMOBJ_BOOL);
3432 }else{
3433 if( rc ){
3434 /* Jump to the desired location */
3435 pc = pInstr->iP2 - 1;
3436 VmPopOperand(&pTos, 1);
3437 }
3438 }
3439 break;
3440 }
3441/* OP_LT P1 P2 P3
3442 *
3443 * Pop the top two elements from the stack. If the second element (the top of stack)
3444 * is less than the first (next on stack), then jump to instruction P2.Otherwise
3445 * continue to the next instruction. In other words, jump if pNos<pTos.
3446 * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the
3447 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3448 *
3449 */
3450/* OP_LE P1 P2 P3
3451 *
3452 * Pop the top two elements from the stack. If the second element (the top of stack)
3453 * is less than or equal to the first (next on stack), then jump to instruction P2.
3454 * Otherwise continue to the next instruction. In other words, jump if pNos<pTos.
3455 * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the
3456 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3457 *
3458 */
3459case JX9_OP_LT:
3460case JX9_OP_LE: {
3461 jx9_value *pNos = &pTos[-1];
3462 /* Perform the comparison and act accordingly */
3463#ifdef UNTRUST
3464 if( pNos < pStack ){
3465 goto Abort;
3466 }
3467#endif
3468 rc = jx9MemObjCmp(pNos, pTos, FALSE, 0);
3469 if( pInstr->iOp == JX9_OP_LE ){
3470 rc = rc < 1;
3471 }else{
3472 rc = rc < 0;
3473 }
3474 VmPopOperand(&pTos, 1);
3475 if( !pInstr->iP2 ){
3476 /* Push comparison result without taking the jump */
3477 jx9MemObjRelease(pTos);
3478 pTos->x.iVal = rc;
3479 /* Invalidate any prior representation */
3480 MemObjSetType(pTos, MEMOBJ_BOOL);
3481 }else{
3482 if( rc ){
3483 /* Jump to the desired location */
3484 pc = pInstr->iP2 - 1;
3485 VmPopOperand(&pTos, 1);
3486 }
3487 }
3488 break;
3489 }
3490/* OP_GT P1 P2 P3
3491 *
3492 * Pop the top two elements from the stack. If the second element (the top of stack)
3493 * is greater than the first (next on stack), then jump to instruction P2.Otherwise
3494 * continue to the next instruction. In other words, jump if pNos<pTos.
3495 * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the
3496 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3497 *
3498 */
3499/* OP_GE P1 P2 P3
3500 *
3501 * Pop the top two elements from the stack. If the second element (the top of stack)
3502 * is greater than or equal to the first (next on stack), then jump to instruction P2.
3503 * Otherwise continue to the next instruction. In other words, jump if pNos<pTos.
3504 * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the
3505 * stack if the jump would have been taken, or a 0 (FALSE) if not.
3506 *
3507 */
3508case JX9_OP_GT:
3509case JX9_OP_GE: {
3510 jx9_value *pNos = &pTos[-1];
3511 /* Perform the comparison and act accordingly */
3512#ifdef UNTRUST
3513 if( pNos < pStack ){
3514 goto Abort;
3515 }
3516#endif
3517 rc = jx9MemObjCmp(pNos, pTos, FALSE, 0);
3518 if( pInstr->iOp == JX9_OP_GE ){
3519 rc = rc >= 0;
3520 }else{
3521 rc = rc > 0;
3522 }
3523 VmPopOperand(&pTos, 1);
3524 if( !pInstr->iP2 ){
3525 /* Push comparison result without taking the jump */
3526 jx9MemObjRelease(pTos);
3527 pTos->x.iVal = rc;
3528 /* Invalidate any prior representation */
3529 MemObjSetType(pTos, MEMOBJ_BOOL);
3530 }else{
3531 if( rc ){
3532 /* Jump to the desired location */
3533 pc = pInstr->iP2 - 1;
3534 VmPopOperand(&pTos, 1);
3535 }
3536 }
3537 break;
3538 }
3539/*
3540 * OP_FOREACH_INIT * P2 P3
3541 * Prepare a foreach step.
3542 */
3543case JX9_OP_FOREACH_INIT: {
3544 jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3;
3545 void *pName;
3546#ifdef UNTRUST
3547 if( pTos < pStack ){
3548 goto Abort;
3549 }
3550#endif
3551 if( SyStringLength(&pInfo->sValue) < 1 ){
3552 /* Take the variable name from the top of the stack */
3553 if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){
3554 /* Force a string cast */
3555 jx9MemObjToString(pTos);
3556 }
3557 /* Duplicate name */
3558 if( SyBlobLength(&pTos->sBlob) > 0 ){
3559 pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob));
3560 SyStringInitFromBuf(&pInfo->sValue, pName, SyBlobLength(&pTos->sBlob));
3561 }
3562 VmPopOperand(&pTos, 1);
3563 }
3564 if( (pInfo->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) < 1 ){
3565 if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){
3566 /* Force a string cast */
3567 jx9MemObjToString(pTos);
3568 }
3569 /* Duplicate name */
3570 if( SyBlobLength(&pTos->sBlob) > 0 ){
3571 pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob));
3572 SyStringInitFromBuf(&pInfo->sKey, pName, SyBlobLength(&pTos->sBlob));
3573 }
3574 VmPopOperand(&pTos, 1);
3575 }
3576 /* Make sure we are dealing with a hashmap [i.e. JSON array or object ]*/
3577 if( (pTos->iFlags & (MEMOBJ_HASHMAP)) == 0 || SyStringLength(&pInfo->sValue) < 1 ){
3578 /* Jump out of the loop */
3579 if( (pTos->iFlags & MEMOBJ_NULL) == 0 ){
3580 jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING,
3581 "Invalid argument supplied for the foreach statement, expecting JSON array or object instance");
3582 }
3583 pc = pInstr->iP2 - 1;
3584 }else{
3585 jx9_foreach_step *pStep;
3586 pStep = (jx9_foreach_step *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_foreach_step));
3587 if( pStep == 0 ){
3588 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step");
3589 /* Jump out of the loop */
3590 pc = pInstr->iP2 - 1;
3591 }else{
3592 /* Zero the structure */
3593 SyZero(pStep, sizeof(jx9_foreach_step));
3594 /* Prepare the step */
3595 pStep->iFlags = pInfo->iFlags;
3596 if( pTos->iFlags & MEMOBJ_HASHMAP ){
3597 jx9_hashmap *pMap = (jx9_hashmap *)pTos->x.pOther;
3598 /* Reset the internal loop cursor */
3599 jx9HashmapResetLoopCursor(pMap);
3600 /* Mark the step */
3601 pStep->pMap = pMap;
3602 pMap->iRef++;
3603 }
3604 }
3605 if( SXRET_OK != SySetPut(&pInfo->aStep, (const void *)&pStep) ){
3606 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step");
3607 SyMemBackendPoolFree(&pVm->sAllocator, pStep);
3608 /* Jump out of the loop */
3609 pc = pInstr->iP2 - 1;
3610 }
3611 }
3612 VmPopOperand(&pTos, 1);
3613 break;
3614 }
3615/*
3616 * OP_FOREACH_STEP * P2 P3
3617 * Perform a foreach step. Jump to P2 at the end of the step.
3618 */
3619case JX9_OP_FOREACH_STEP: {
3620 jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3;
3621 jx9_foreach_step **apStep, *pStep;
3622 jx9_hashmap_node *pNode;
3623 jx9_hashmap *pMap;
3624 jx9_value *pValue;
3625 /* Peek the last step */
3626 apStep = (jx9_foreach_step **)SySetBasePtr(&pInfo->aStep);
3627 pStep = apStep[SySetUsed(&pInfo->aStep) - 1];
3628 pMap = pStep->pMap;
3629 /* Extract the current node value */
3630 pNode = jx9HashmapGetNextEntry(pMap);
3631 if( pNode == 0 ){
3632 /* No more entry to process */
3633 pc = pInstr->iP2 - 1; /* Jump to this destination */
3634 /* Automatically reset the loop cursor */
3635 jx9HashmapResetLoopCursor(pMap);
3636 /* Cleanup the mess left behind */
3637 SyMemBackendPoolFree(&pVm->sAllocator, pStep);
3638 SySetPop(&pInfo->aStep);
3639 jx9HashmapUnref(pMap);
3640 }else{
3641 if( (pStep->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0 ){
3642 jx9_value *pKey = VmExtractMemObj(&(*pVm), &pInfo->sKey, FALSE, TRUE);
3643 if( pKey ){
3644 jx9HashmapExtractNodeKey(pNode, pKey);
3645 }
3646 }
3647 /* Make a copy of the entry value */
3648 pValue = VmExtractMemObj(&(*pVm), &pInfo->sValue, FALSE, TRUE);
3649 if( pValue ){
3650 jx9HashmapExtractNodeValue(pNode, pValue, TRUE);
3651 }
3652 }
3653 break;
3654 }
3655/*
3656 * OP_MEMBER P1 P2
3657 * Load JSON object entry on the stack.
3658 */
3659case JX9_OP_MEMBER: {
3660 jx9_hashmap_node *pNode = 0; /* cc warning */
3661 jx9_hashmap *pMap = 0;
3662 jx9_value *pIdx;
3663 pIdx = pTos;
3664 pTos--;
3665 rc = SXERR_NOTFOUND; /* Assume the index is invalid */
3666 if( pTos->iFlags & MEMOBJ_HASHMAP ){
3667 /* Point to the hashmap */
3668 pMap = (jx9_hashmap *)pTos->x.pOther;
3669 /* Load the desired entry */
3670 rc = jx9HashmapLookup(pMap, pIdx, &pNode);
3671 }
3672 jx9MemObjRelease(pIdx);
3673 if( rc == SXRET_OK ){
3674 /* Load entry contents */
3675 if( pMap->iRef < 2 ){
3676 /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy
3677 * of the entry value, rather than pointing to it.
3678 */
3679 pTos->nIdx = SXU32_HIGH;
3680 jx9HashmapExtractNodeValue(pNode, pTos, TRUE);
3681 }else{
3682 pTos->nIdx = pNode->nValIdx;
3683 jx9HashmapExtractNodeValue(pNode, pTos, FALSE);
3684 jx9HashmapUnref(pMap);
3685 }
3686 }else{
3687 /* No such entry, load NULL */
3688 jx9MemObjRelease(pTos);
3689 pTos->nIdx = SXU32_HIGH;
3690 }
3691 break;
3692 }
3693/*
3694 * OP_SWITCH * * P3
3695 * This is the bytecode implementation of the complex switch() JX9 construct.
3696 */
3697case JX9_OP_SWITCH: {
3698 jx9_switch *pSwitch = (jx9_switch *)pInstr->p3;
3699 jx9_case_expr *aCase, *pCase;
3700 jx9_value sValue, sCaseValue;
3701 sxu32 n, nEntry;
3702#ifdef UNTRUST
3703 if( pSwitch == 0 || pTos < pStack ){
3704 goto Abort;
3705 }
3706#endif
3707 /* Point to the case table */
3708 aCase = (jx9_case_expr *)SySetBasePtr(&pSwitch->aCaseExpr);
3709 nEntry = SySetUsed(&pSwitch->aCaseExpr);
3710 /* Select the appropriate case block to execute */
3711 jx9MemObjInit(pVm, &sValue);
3712 jx9MemObjInit(pVm, &sCaseValue);
3713 for( n = 0 ; n < nEntry ; ++n ){
3714 pCase = &aCase[n];
3715 jx9MemObjLoad(pTos, &sValue);
3716 /* Execute the case expression first */
3717 VmLocalExec(pVm,&pCase->aByteCode, &sCaseValue);
3718 /* Compare the two expression */
3719 rc = jx9MemObjCmp(&sValue, &sCaseValue, FALSE, 0);
3720 jx9MemObjRelease(&sValue);
3721 jx9MemObjRelease(&sCaseValue);
3722 if( rc == 0 ){
3723 /* Value match, jump to this block */
3724 pc = pCase->nStart - 1;
3725 break;
3726 }
3727 }
3728 VmPopOperand(&pTos, 1);
3729 if( n >= nEntry ){
3730 /* No approprite case to execute, jump to the default case */
3731 if( pSwitch->nDefault > 0 ){
3732 pc = pSwitch->nDefault - 1;
3733 }else{
3734 /* No default case, jump out of this switch */
3735 pc = pSwitch->nOut - 1;
3736 }
3737 }
3738 break;
3739 }
3740/*
3741 * OP_UPLINK P1 * *
3742 * Link a variable to the top active VM frame.
3743 * This is used to implement the 'uplink' JX9 construct.
3744 */
3745case JX9_OP_UPLINK: {
3746 if( pVm->pFrame->pParent ){
3747 jx9_value *pLink = &pTos[-pInstr->iP1+1];
3748 SyString sName;
3749 /* Perform the link */
3750 while( pLink <= pTos ){
3751 if((pLink->iFlags & MEMOBJ_STRING) == 0 ){
3752 /* Force a string cast */
3753 jx9MemObjToString(pLink);
3754 }
3755 SyStringInitFromBuf(&sName, SyBlobData(&pLink->sBlob), SyBlobLength(&pLink->sBlob));
3756 if( sName.nByte > 0 ){
3757 VmFrameLink(&(*pVm), &sName);
3758 }
3759 pLink++;
3760 }
3761 }
3762 VmPopOperand(&pTos, pInstr->iP1);
3763 break;
3764 }
3765/*
3766 * OP_CALL P1 * *
3767 * Call a JX9 or a foreign function and push the return value of the called
3768 * function on the stack.
3769 */
3770case JX9_OP_CALL: {
3771 jx9_value *pArg = &pTos[-pInstr->iP1];
3772 SyHashEntry *pEntry;
3773 SyString sName;
3774 /* Extract function name */
3775 if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){
3776 /* Raise exception: Invalid function name */
3777 VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Invalid function name, JX9 is returning NULL.");
3778 /* Pop given arguments */
3779 if( pInstr->iP1 > 0 ){
3780 VmPopOperand(&pTos, pInstr->iP1);
3781 }
3782 /* Assume a null return value so that the program continue it's execution normally */
3783 jx9MemObjRelease(pTos);
3784 break;
3785 }
3786 SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob));
3787 /* Check for a compiled function first */
3788 pEntry = SyHashGet(&pVm->hFunction, (const void *)sName.zString, sName.nByte);
3789 if( pEntry ){
3790 jx9_vm_func_arg *aFormalArg;
3791 jx9_value *pFrameStack;
3792 jx9_vm_func *pVmFunc;
3793 VmFrame *pFrame;
3794 jx9_value *pObj;
3795 VmSlot sArg;
3796 sxu32 n;
3797 pVmFunc = (jx9_vm_func *)pEntry->pUserData;
3798 /* Check The recursion limit */
3799 if( pVm->nRecursionDepth > pVm->nMaxDepth ){
3800 VmErrorFormat(&(*pVm), JX9_CTX_ERR,
3801 "Recursion limit reached while invoking user function '%z', JX9 will set a NULL return value",
3802 &pVmFunc->sName);
3803 /* Pop given arguments */
3804 if( pInstr->iP1 > 0 ){
3805 VmPopOperand(&pTos, pInstr->iP1);
3806 }
3807 /* Assume a null return value so that the program continue it's execution normally */
3808 jx9MemObjRelease(pTos);
3809 break;
3810 }
3811 if( pVmFunc->pNextName ){
3812 /* Function is candidate for overloading, select the appropriate function to call */
3813 pVmFunc = VmOverload(&(*pVm), pVmFunc, pArg, (int)(pTos-pArg));
3814 }
3815 /* Extract the formal argument set */
3816 aFormalArg = (jx9_vm_func_arg *)SySetBasePtr(&pVmFunc->aArgs);
3817 /* Create a new VM frame */
3818 rc = VmEnterFrame(&(*pVm),pVmFunc,&pFrame);
3819 if( rc != SXRET_OK ){
3820 /* Raise exception: Out of memory */
3821 VmErrorFormat(&(*pVm), JX9_CTX_ERR,
3822 "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.",
3823 &pVmFunc->sName);
3824 /* Pop given arguments */
3825 if( pInstr->iP1 > 0 ){
3826 VmPopOperand(&pTos, pInstr->iP1);
3827 }
3828 /* Assume a null return value so that the program continue it's execution normally */
3829 jx9MemObjRelease(pTos);
3830 break;
3831 }
3832 if( SySetUsed(&pVmFunc->aStatic) > 0 ){
3833 jx9_vm_func_static_var *pStatic, *aStatic;
3834 /* Install static variables */
3835 aStatic = (jx9_vm_func_static_var *)SySetBasePtr(&pVmFunc->aStatic);
3836 for( n = 0 ; n < SySetUsed(&pVmFunc->aStatic) ; ++n ){
3837 pStatic = &aStatic[n];
3838 if( pStatic->nIdx == SXU32_HIGH ){
3839 /* Initialize the static variables */
3840 pObj = VmReserveMemObj(&(*pVm), &pStatic->nIdx);
3841 if( pObj ){
3842 /* Assume a NULL initialization value */
3843 jx9MemObjInit(&(*pVm), pObj);
3844 if( SySetUsed(&pStatic->aByteCode) > 0 ){
3845 /* Evaluate initialization expression (Any complex expression) */
3846 VmLocalExec(&(*pVm), &pStatic->aByteCode, pObj);
3847 }
3848 pObj->nIdx = pStatic->nIdx;
3849 }else{
3850 continue;
3851 }
3852 }
3853 /* Install in the current frame */
3854 SyHashInsert(&pFrame->hVar, SyStringData(&pStatic->sName), SyStringLength(&pStatic->sName),
3855 SX_INT_TO_PTR(pStatic->nIdx));
3856 }
3857 }
3858 /* Push arguments in the local frame */
3859 n = 0;
3860 while( pArg < pTos ){
3861 if( n < SySetUsed(&pVmFunc->aArgs) ){
3862 if( (pArg->iFlags & MEMOBJ_NULL) && SySetUsed(&aFormalArg[n].aByteCode) > 0 ){
3863 /* NULL values are redirected to default arguments */
3864 rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pArg);
3865 if( rc == JX9_ABORT ){
3866 goto Abort;
3867 }
3868 }
3869 /* Make sure the given arguments are of the correct type */
3870 if( aFormalArg[n].nType > 0 ){
3871 if( ((pArg->iFlags & aFormalArg[n].nType) == 0) ){
3872 ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType);
3873 /* Cast to the desired type */
3874 if( xCast ){
3875 xCast(pArg);
3876 }
3877 }
3878 }
3879 /* Pass by value, make a copy of the given argument */
3880 pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE);
3881 }else{
3882 char zName[32];
3883 SyString sName;
3884 /* Set a dummy name */
3885 sName.nByte = SyBufferFormat(zName, sizeof(zName), "[%u]apArg", n);
3886 sName.zString = zName;
3887 /* Annonymous argument */
3888 pObj = VmExtractMemObj(&(*pVm), &sName, TRUE, TRUE);
3889 }
3890 if( pObj ){
3891 jx9MemObjStore(pArg, pObj);
3892 /* Insert argument index */
3893 sArg.nIdx = pObj->nIdx;
3894 sArg.pUserData = 0;
3895 SySetPut(&pFrame->sArg, (const void *)&sArg);
3896 }
3897 jx9MemObjRelease(pArg);
3898 pArg++;
3899 ++n;
3900 }
3901 /* Process default values */
3902 while( n < SySetUsed(&pVmFunc->aArgs) ){
3903 if( SySetUsed(&aFormalArg[n].aByteCode) > 0 ){
3904 pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE);
3905 if( pObj ){
3906 /* Evaluate the default value and extract it's result */
3907 rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pObj);
3908 if( rc == JX9_ABORT ){
3909 goto Abort;
3910 }
3911 /* Insert argument index */
3912 sArg.nIdx = pObj->nIdx;
3913 sArg.pUserData = 0;
3914 SySetPut(&pFrame->sArg, (const void *)&sArg);
3915 /* Make sure the default argument is of the correct type */
3916 if( aFormalArg[n].nType > 0 && ((pObj->iFlags & aFormalArg[n].nType) == 0) ){
3917 ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType);
3918 /* Cast to the desired type */
3919 xCast(pObj);
3920 }
3921 }
3922 }
3923 ++n;
3924 }
3925 /* Pop arguments, function name from the operand stack and assume the function
3926 * does not return anything.
3927 */
3928 jx9MemObjRelease(pTos);
3929 pTos = &pTos[-pInstr->iP1];
3930 /* Allocate a new operand stack and evaluate the function body */
3931 pFrameStack = VmNewOperandStack(&(*pVm), SySetUsed(&pVmFunc->aByteCode));
3932 if( pFrameStack == 0 ){
3933 /* Raise exception: Out of memory */
3934 VmErrorFormat(&(*pVm), JX9_CTX_ERR, "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.",
3935 &pVmFunc->sName);
3936 if( pInstr->iP1 > 0 ){
3937 VmPopOperand(&pTos, pInstr->iP1);
3938 }
3939 break;
3940 }
3941 /* Increment nesting level */
3942 pVm->nRecursionDepth++;
3943 /* Execute function body */
3944 rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(&pVmFunc->aByteCode), pFrameStack, -1, pTos);
3945 /* Decrement nesting level */
3946 pVm->nRecursionDepth--;
3947 /* Free the operand stack */
3948 SyMemBackendFree(&pVm->sAllocator, pFrameStack);
3949 /* Leave the frame */
3950 VmLeaveFrame(&(*pVm));
3951 if( rc == JX9_ABORT ){
3952 /* Abort processing immeditaley */
3953 goto Abort;
3954 }
3955 }else{
3956 jx9_user_func *pFunc;
3957 jx9_context sCtx;
3958 jx9_value sRet;
3959 /* Look for an installed foreign function */
3960 pEntry = SyHashGet(&pVm->hHostFunction, (const void *)sName.zString, sName.nByte);
3961 if( pEntry == 0 ){
3962 /* Call to undefined function */
3963 VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Call to undefined function '%z', JX9 is returning NULL.", &sName);
3964 /* Pop given arguments */
3965 if( pInstr->iP1 > 0 ){
3966 VmPopOperand(&pTos, pInstr->iP1);
3967 }
3968 /* Assume a null return value so that the program continue it's execution normally */
3969 jx9MemObjRelease(pTos);
3970 break;
3971 }
3972 pFunc = (jx9_user_func *)pEntry->pUserData;
3973 /* Start collecting function arguments */
3974 SySetReset(&aArg);
3975 while( pArg < pTos ){
3976 SySetPut(&aArg, (const void *)&pArg);
3977 pArg++;
3978 }
3979 /* Assume a null return value */
3980 jx9MemObjInit(&(*pVm), &sRet);
3981 /* Init the call context */
3982 VmInitCallContext(&sCtx, &(*pVm), pFunc, &sRet, 0);
3983 /* Call the foreign function */
3984 rc = pFunc->xFunc(&sCtx, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg));
3985 /* Release the call context */
3986 VmReleaseCallContext(&sCtx);
3987 if( rc == JX9_ABORT ){
3988 goto Abort;
3989 }
3990 if( pInstr->iP1 > 0 ){
3991 /* Pop function name and arguments */
3992 VmPopOperand(&pTos, pInstr->iP1);
3993 }
3994 /* Save foreign function return value */
3995 jx9MemObjStore(&sRet, pTos);
3996 jx9MemObjRelease(&sRet);
3997 }
3998 break;
3999 }
4000/*
4001 * OP_CONSUME: P1 * *
4002 * Consume (Invoke the installed VM output consumer callback) and POP P1 elements from the stack.
4003 */
4004case JX9_OP_CONSUME: {
4005 jx9_output_consumer *pCons = &pVm->sVmConsumer;
4006 jx9_value *pCur, *pOut = pTos;
4007
4008 pOut = &pTos[-pInstr->iP1 + 1];
4009 pCur = pOut;
4010 /* Start the consume process */
4011 while( pOut <= pTos ){
4012 /* Force a string cast */
4013 if( (pOut->iFlags & MEMOBJ_STRING) == 0 ){
4014 jx9MemObjToString(pOut);
4015 }
4016 if( SyBlobLength(&pOut->sBlob) > 0 ){
4017 /*SyBlobNullAppend(&pOut->sBlob);*/
4018 /* Invoke the output consumer callback */
4019 rc = pCons->xConsumer(SyBlobData(&pOut->sBlob), SyBlobLength(&pOut->sBlob), pCons->pUserData);
4020 /* Increment output length */
4021 pVm->nOutputLen += SyBlobLength(&pOut->sBlob);
4022 SyBlobRelease(&pOut->sBlob);
4023 if( rc == SXERR_ABORT ){
4024 /* Output consumer callback request an operation abort. */
4025 goto Abort;
4026 }
4027 }
4028 pOut++;
4029 }
4030 pTos = &pCur[-1];
4031 break;
4032 }
4033
4034 } /* Switch() */
4035 pc++; /* Next instruction in the stream */
4036 } /* For(;;) */
4037Done:
4038 SySetRelease(&aArg);
4039 return SXRET_OK;
4040Abort:
4041 SySetRelease(&aArg);
4042 while( pTos >= pStack ){
4043 jx9MemObjRelease(pTos);
4044 pTos--;
4045 }
4046 return JX9_ABORT;
4047}
4048/*
4049 * Execute as much of a local JX9 bytecode program as we can then return.
4050 * This function is a wrapper around [VmByteCodeExec()].
4051 * See block-comment on that function for additional information.
4052 */
4053static sxi32 VmLocalExec(jx9_vm *pVm, SySet *pByteCode,jx9_value *pResult)
4054{
4055 jx9_value *pStack;
4056 sxi32 rc;
4057 /* Allocate a new operand stack */
4058 pStack = VmNewOperandStack(&(*pVm), SySetUsed(pByteCode));
4059 if( pStack == 0 ){
4060 return SXERR_MEM;
4061 }
4062 /* Execute the program */
4063 rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pByteCode), pStack, -1, &(*pResult));
4064 /* Free the operand stack */
4065 SyMemBackendFree(&pVm->sAllocator, pStack);
4066 /* Execution result */
4067 return rc;
4068}
4069/*
4070 * Execute as much of a JX9 bytecode program as we can then return.
4071 * This function is a wrapper around [VmByteCodeExec()].
4072 * See block-comment on that function for additional information.
4073 */
4074JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm)
4075{
4076 /* Make sure we are ready to execute this program */
4077 if( pVm->nMagic != JX9_VM_RUN ){
4078 return pVm->nMagic == JX9_VM_EXEC ? SXERR_LOCKED /* Locked VM */ : SXERR_CORRUPT; /* Stale VM */
4079 }
4080 /* Set the execution magic number */
4081 pVm->nMagic = JX9_VM_EXEC;
4082 /* Execute the program */
4083 VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pVm->pByteContainer), pVm->aOps, -1, &pVm->sExec);
4084 /*
4085 * TICKET 1433-100: Do not remove the JX9_VM_EXEC magic number
4086 * so that any following call to [jx9_vm_exec()] without calling
4087 * [jx9_vm_reset()] first would fail.
4088 */
4089 return SXRET_OK;
4090}
4091/*
4092 * Extract a memory object (i.e. a variable) from the running script.
4093 * This function must be called after calling jx9_vm_exec(). Otherwise
4094 * NULL is returned.
4095 */
4096JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar)
4097{
4098 jx9_value *pValue;
4099 if( pVm->nMagic != JX9_VM_EXEC ){
4100 /* call jx9_vm_exec() first */
4101 return 0;
4102 }
4103 /* Perform the lookup */
4104 pValue = VmExtractMemObj(pVm,pVar,FALSE,FALSE);
4105 /* Lookup result */
4106 return pValue;
4107}
4108/*
4109 * Invoke the installed VM output consumer callback to consume
4110 * the desired message.
4111 * Refer to the implementation of [jx9_context_output()] defined
4112 * in 'api.c' for additional information.
4113 */
4114JX9_PRIVATE sxi32 jx9VmOutputConsume(
4115 jx9_vm *pVm, /* Target VM */
4116 SyString *pString /* Message to output */
4117 )
4118{
4119 jx9_output_consumer *pCons = &pVm->sVmConsumer;
4120 sxi32 rc = SXRET_OK;
4121 /* Call the output consumer */
4122 if( pString->nByte > 0 ){
4123 rc = pCons->xConsumer((const void *)pString->zString, pString->nByte, pCons->pUserData);
4124 /* Increment output length */
4125 pVm->nOutputLen += pString->nByte;
4126 }
4127 return rc;
4128}
4129/*
4130 * Format a message and invoke the installed VM output consumer
4131 * callback to consume the formatted message.
4132 * Refer to the implementation of [jx9_context_output_format()] defined
4133 * in 'api.c' for additional information.
4134 */
4135JX9_PRIVATE sxi32 jx9VmOutputConsumeAp(
4136 jx9_vm *pVm, /* Target VM */
4137 const char *zFormat, /* Formatted message to output */
4138 va_list ap /* Variable list of arguments */
4139 )
4140{
4141 jx9_output_consumer *pCons = &pVm->sVmConsumer;
4142 sxi32 rc = SXRET_OK;
4143 SyBlob sWorker;
4144 /* Format the message and call the output consumer */
4145 SyBlobInit(&sWorker, &pVm->sAllocator);
4146 SyBlobFormatAp(&sWorker, zFormat, ap);
4147 if( SyBlobLength(&sWorker) > 0 ){
4148 /* Consume the formatted message */
4149 rc = pCons->xConsumer(SyBlobData(&sWorker), SyBlobLength(&sWorker), pCons->pUserData);
4150 }
4151 /* Increment output length */
4152 pVm->nOutputLen += SyBlobLength(&sWorker);
4153 /* Release the working buffer */
4154 SyBlobRelease(&sWorker);
4155 return rc;
4156}
4157/*
4158 * Return a string representation of the given JX9 OP code.
4159 * This function never fail and always return a pointer
4160 * to a null terminated string.
4161 */
4162static const char * VmInstrToString(sxi32 nOp)
4163{
4164 const char *zOp = "Unknown ";
4165 switch(nOp){
4166 case JX9_OP_DONE: zOp = "DONE "; break;
4167 case JX9_OP_HALT: zOp = "HALT "; break;
4168 case JX9_OP_LOAD: zOp = "LOAD "; break;
4169 case JX9_OP_LOADC: zOp = "LOADC "; break;
4170 case JX9_OP_LOAD_MAP: zOp = "LOAD_MAP "; break;
4171 case JX9_OP_LOAD_IDX: zOp = "LOAD_IDX "; break;
4172 case JX9_OP_NOOP: zOp = "NOOP "; break;
4173 case JX9_OP_JMP: zOp = "JMP "; break;
4174 case JX9_OP_JZ: zOp = "JZ "; break;
4175 case JX9_OP_JNZ: zOp = "JNZ "; break;
4176 case JX9_OP_POP: zOp = "POP "; break;
4177 case JX9_OP_CAT: zOp = "CAT "; break;
4178 case JX9_OP_CVT_INT: zOp = "CVT_INT "; break;
4179 case JX9_OP_CVT_STR: zOp = "CVT_STR "; break;
4180 case JX9_OP_CVT_REAL: zOp = "CVT_REAL "; break;
4181 case JX9_OP_CALL: zOp = "CALL "; break;
4182 case JX9_OP_UMINUS: zOp = "UMINUS "; break;
4183 case JX9_OP_UPLUS: zOp = "UPLUS "; break;
4184 case JX9_OP_BITNOT: zOp = "BITNOT "; break;
4185 case JX9_OP_LNOT: zOp = "LOGNOT "; break;
4186 case JX9_OP_MUL: zOp = "MUL "; break;
4187 case JX9_OP_DIV: zOp = "DIV "; break;
4188 case JX9_OP_MOD: zOp = "MOD "; break;
4189 case JX9_OP_ADD: zOp = "ADD "; break;
4190 case JX9_OP_SUB: zOp = "SUB "; break;
4191 case JX9_OP_SHL: zOp = "SHL "; break;
4192 case JX9_OP_SHR: zOp = "SHR "; break;
4193 case JX9_OP_LT: zOp = "LT "; break;
4194 case JX9_OP_LE: zOp = "LE "; break;
4195 case JX9_OP_GT: zOp = "GT "; break;
4196 case JX9_OP_GE: zOp = "GE "; break;
4197 case JX9_OP_EQ: zOp = "EQ "; break;
4198 case JX9_OP_NEQ: zOp = "NEQ "; break;
4199 case JX9_OP_TEQ: zOp = "TEQ "; break;
4200 case JX9_OP_TNE: zOp = "TNE "; break;
4201 case JX9_OP_BAND: zOp = "BITAND "; break;
4202 case JX9_OP_BXOR: zOp = "BITXOR "; break;
4203 case JX9_OP_BOR: zOp = "BITOR "; break;
4204 case JX9_OP_LAND: zOp = "LOGAND "; break;
4205 case JX9_OP_LOR: zOp = "LOGOR "; break;
4206 case JX9_OP_LXOR: zOp = "LOGXOR "; break;
4207 case JX9_OP_STORE: zOp = "STORE "; break;
4208 case JX9_OP_STORE_IDX: zOp = "STORE_IDX "; break;
4209 case JX9_OP_PULL: zOp = "PULL "; break;
4210 case JX9_OP_SWAP: zOp = "SWAP "; break;
4211 case JX9_OP_YIELD: zOp = "YIELD "; break;
4212 case JX9_OP_CVT_BOOL: zOp = "CVT_BOOL "; break;
4213 case JX9_OP_CVT_NULL: zOp = "CVT_NULL "; break;
4214 case JX9_OP_CVT_ARRAY: zOp = "CVT_JSON "; break;
4215 case JX9_OP_CVT_NUMC: zOp = "CVT_NUMC "; break;
4216 case JX9_OP_INCR: zOp = "INCR "; break;
4217 case JX9_OP_DECR: zOp = "DECR "; break;
4218 case JX9_OP_ADD_STORE: zOp = "ADD_STORE "; break;
4219 case JX9_OP_SUB_STORE: zOp = "SUB_STORE "; break;
4220 case JX9_OP_MUL_STORE: zOp = "MUL_STORE "; break;
4221 case JX9_OP_DIV_STORE: zOp = "DIV_STORE "; break;
4222 case JX9_OP_MOD_STORE: zOp = "MOD_STORE "; break;
4223 case JX9_OP_CAT_STORE: zOp = "CAT_STORE "; break;
4224 case JX9_OP_SHL_STORE: zOp = "SHL_STORE "; break;
4225 case JX9_OP_SHR_STORE: zOp = "SHR_STORE "; break;
4226 case JX9_OP_BAND_STORE: zOp = "BAND_STORE "; break;
4227 case JX9_OP_BOR_STORE: zOp = "BOR_STORE "; break;
4228 case JX9_OP_BXOR_STORE: zOp = "BXOR_STORE "; break;
4229 case JX9_OP_CONSUME: zOp = "CONSUME "; break;
4230 case JX9_OP_MEMBER: zOp = "MEMBER "; break;
4231 case JX9_OP_UPLINK: zOp = "UPLINK "; break;
4232 case JX9_OP_SWITCH: zOp = "SWITCH "; break;
4233 case JX9_OP_FOREACH_INIT:
4234 zOp = "4EACH_INIT "; break;
4235 case JX9_OP_FOREACH_STEP:
4236 zOp = "4EACH_STEP "; break;
4237 default:
4238 break;
4239 }
4240 return zOp;
4241}
4242/*
4243 * Dump JX9 bytecodes instructions to a human readable format.
4244 * The xConsumer() callback which is an used defined function
4245 * is responsible of consuming the generated dump.
4246 */
4247JX9_PRIVATE sxi32 jx9VmDump(
4248 jx9_vm *pVm, /* Target VM */
4249 ProcConsumer xConsumer, /* Output [i.e: dump] consumer callback */
4250 void *pUserData /* Last argument to xConsumer() */
4251 )
4252{
4253 sxi32 rc;
4254 rc = VmByteCodeDump(pVm->pByteContainer, xConsumer, pUserData);
4255 return rc;
4256}
4257/*
4258 * Default constant expansion callback used by the 'const' statement if used
4259 * outside a object body [i.e: global or function scope].
4260 * Refer to the implementation of [JX9_CompileConstant()] defined
4261 * in 'compile.c' for additional information.
4262 */
4263JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData)
4264{
4265 SySet *pByteCode = (SySet *)pUserData;
4266 /* Evaluate and expand constant value */
4267 VmLocalExec((jx9_vm *)SySetGetUserData(pByteCode), pByteCode, (jx9_value *)pVal);
4268}
4269/*
4270 * Section:
4271 * Function handling functions.
4272 * Authors:
4273 * Symisc Systems, devel@symisc.net.
4274 * Copyright (C) Symisc Systems, http://jx9.symisc.net
4275 * Status:
4276 * Stable.
4277 */
4278/*
4279 * int func_num_args(void)
4280 * Returns the number of arguments passed to the function.
4281 * Parameters
4282 * None.
4283 * Return
4284 * Total number of arguments passed into the current user-defined function
4285 * or -1 if called from the globe scope.
4286 */
4287static int vm_builtin_func_num_args(jx9_context *pCtx, int nArg, jx9_value **apArg)
4288{
4289 VmFrame *pFrame;
4290 jx9_vm *pVm;
4291 /* Point to the target VM */
4292 pVm = pCtx->pVm;
4293 /* Current frame */
4294 pFrame = pVm->pFrame;
4295 if( pFrame->pParent == 0 ){
4296 SXUNUSED(nArg);
4297 SXUNUSED(apArg);
4298 /* Global frame, return -1 */
4299 jx9_result_int(pCtx, -1);
4300 return SXRET_OK;
4301 }
4302 /* Total number of arguments passed to the enclosing function */
4303 nArg = (int)SySetUsed(&pFrame->sArg);
4304 jx9_result_int(pCtx, nArg);
4305 return SXRET_OK;
4306}
4307/*
4308 * value func_get_arg(int $arg_num)
4309 * Return an item from the argument list.
4310 * Parameters
4311 * Argument number(index start from zero).
4312 * Return
4313 * Returns the specified argument or FALSE on error.
4314 */
4315static int vm_builtin_func_get_arg(jx9_context *pCtx, int nArg, jx9_value **apArg)
4316{
4317 jx9_value *pObj = 0;
4318 VmSlot *pSlot = 0;
4319 VmFrame *pFrame;
4320 jx9_vm *pVm;
4321 /* Point to the target VM */
4322 pVm = pCtx->pVm;
4323 /* Current frame */
4324 pFrame = pVm->pFrame;
4325 if( nArg < 1 || pFrame->pParent == 0 ){
4326 /* Global frame or Missing arguments, return FALSE */
4327 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope");
4328 jx9_result_bool(pCtx, 0);
4329 return SXRET_OK;
4330 }
4331 /* Extract the desired index */
4332 nArg = jx9_value_to_int(apArg[0]);
4333 if( nArg < 0 || nArg >= (int)SySetUsed(&pFrame->sArg) ){
4334 /* Invalid index, return FALSE */
4335 jx9_result_bool(pCtx, 0);
4336 return SXRET_OK;
4337 }
4338 /* Extract the desired argument */
4339 if( (pSlot = (VmSlot *)SySetAt(&pFrame->sArg, (sxu32)nArg)) != 0 ){
4340 if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx)) != 0 ){
4341 /* Return the desired argument */
4342 jx9_result_value(pCtx, (jx9_value *)pObj);
4343 }else{
4344 /* No such argument, return false */
4345 jx9_result_bool(pCtx, 0);
4346 }
4347 }else{
4348 /* CAN'T HAPPEN */
4349 jx9_result_bool(pCtx, 0);
4350 }
4351 return SXRET_OK;
4352}
4353/*
4354 * array func_get_args(void)
4355 * Returns an array comprising a copy of function's argument list.
4356 * Parameters
4357 * None.
4358 * Return
4359 * Returns an array in which each element is a copy of the corresponding
4360 * member of the current user-defined function's argument list.
4361 * Otherwise FALSE is returned on failure.
4362 */
4363static int vm_builtin_func_get_args(jx9_context *pCtx, int nArg, jx9_value **apArg)
4364{
4365 jx9_value *pObj = 0;
4366 jx9_value *pArray;
4367 VmFrame *pFrame;
4368 VmSlot *aSlot;
4369 sxu32 n;
4370 /* Point to the current frame */
4371 pFrame = pCtx->pVm->pFrame;
4372 if( pFrame->pParent == 0 ){
4373 /* Global frame, return FALSE */
4374 jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope");
4375 jx9_result_bool(pCtx, 0);
4376 return SXRET_OK;
4377 }
4378 /* Create a new array */
4379 pArray = jx9_context_new_array(pCtx);
4380 if( pArray == 0 ){
4381 SXUNUSED(nArg); /* cc warning */
4382 SXUNUSED(apArg);
4383 jx9_result_bool(pCtx, 0);
4384 return SXRET_OK;
4385 }
4386 /* Start filling the array with the given arguments */
4387 aSlot = (VmSlot *)SySetBasePtr(&pFrame->sArg);
4388 for( n = 0; n < SySetUsed(&pFrame->sArg) ; n++ ){
4389 pObj = (jx9_value *)SySetAt(&pCtx->pVm->aMemObj, aSlot[n].nIdx);
4390 if( pObj ){
4391 jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pObj);
4392 }
4393 }
4394 /* Return the freshly created array */
4395 jx9_result_value(pCtx, pArray);
4396 return SXRET_OK;
4397}
4398/*
4399 * bool function_exists(string $name)
4400 * Return TRUE if the given function has been defined.
4401 * Parameters
4402 * The name of the desired function.
4403 * Return
4404 * Return TRUE if the given function has been defined.False otherwise
4405 */
4406static int vm_builtin_func_exists(jx9_context *pCtx, int nArg, jx9_value **apArg)
4407{
4408 const char *zName;
4409 jx9_vm *pVm;
4410 int nLen;
4411 int res;
4412 if( nArg < 1 ){
4413 /* Missing argument, return FALSE */
4414 jx9_result_bool(pCtx, 0);
4415 return SXRET_OK;
4416 }
4417 /* Point to the target VM */
4418 pVm = pCtx->pVm;
4419 /* Extract the function name */
4420 zName = jx9_value_to_string(apArg[0], &nLen);
4421 /* Assume the function is not defined */
4422 res = 0;
4423 /* Perform the lookup */
4424 if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 ||
4425 SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){
4426 /* Function is defined */
4427 res = 1;
4428 }
4429 jx9_result_bool(pCtx, res);
4430 return SXRET_OK;
4431}
4432/*
4433 * Verify that the contents of a variable can be called as a function.
4434 * [i.e: Whether it is callable or not].
4435 * Return TRUE if callable.FALSE otherwise.
4436 */
4437JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue)
4438{
4439 int res = 0;
4440 if( pValue->iFlags & MEMOBJ_STRING ){
4441 const char *zName;
4442 int nLen;
4443 /* Extract the name */
4444 zName = jx9_value_to_string(pValue, &nLen);
4445 /* Perform the lookup */
4446 if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 ||
4447 SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){
4448 /* Function is callable */
4449 res = 1;
4450 }
4451 }
4452 return res;
4453}
4454/*
4455 * bool is_callable(callable $name[, bool $syntax_only = false])
4456 * Verify that the contents of a variable can be called as a function.
4457 * Parameters
4458 * $name
4459 * The callback function to check
4460 * $syntax_only
4461 * If set to TRUE the function only verifies that name might be a function or method.
4462 * It will only reject simple variables that are not strings, or an array that does
4463 * not have a valid structure to be used as a callback. The valid ones are supposed
4464 * to have only 2 entries, the first of which is an object or a string, and the second
4465 * a string.
4466 * Return
4467 * TRUE if name is callable, FALSE otherwise.
4468 */
4469static int vm_builtin_is_callable(jx9_context *pCtx, int nArg, jx9_value **apArg)
4470{
4471 jx9_vm *pVm;
4472 int res;
4473 if( nArg < 1 ){
4474 /* Missing arguments, return FALSE */
4475 jx9_result_bool(pCtx, 0);
4476 return SXRET_OK;
4477 }
4478 /* Point to the target VM */
4479 pVm = pCtx->pVm;
4480 /* Perform the requested operation */
4481 res = jx9VmIsCallable(pVm, apArg[0]);
4482 jx9_result_bool(pCtx, res);
4483 return SXRET_OK;
4484}
4485/*
4486 * Hash walker callback used by the [get_defined_functions()] function
4487 * defined below.
4488 */
4489static int VmHashFuncStep(SyHashEntry *pEntry, void *pUserData)
4490{
4491 jx9_value *pArray = (jx9_value *)pUserData;
4492 jx9_value sName;
4493 sxi32 rc;
4494 /* Prepare the function name for insertion */
4495 jx9MemObjInitFromString(pArray->pVm, &sName, 0);
4496 jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen);
4497 /* Perform the insertion */
4498 rc = jx9_array_add_elem(pArray, 0/* Automatic index assign */, &sName); /* Will make it's own copy */
4499 jx9MemObjRelease(&sName);
4500 return rc;
4501}
4502/*
4503 * array get_defined_functions(void)
4504 * Returns an array of all defined functions.
4505 * Parameter
4506 * None.
4507 * Return
4508 * Returns an multidimensional array containing a list of all defined functions
4509 * both built-in (internal) and user-defined.
4510 * The internal functions will be accessible via $arr["internal"], and the user
4511 * defined ones using $arr["user"].
4512 * Note:
4513 * NULL is returned on failure.
4514 */
4515static int vm_builtin_get_defined_func(jx9_context *pCtx, int nArg, jx9_value **apArg)
4516{
4517 jx9_value *pArray;
4518 /* NOTE:
4519 * Don't worry about freeing memory here, every allocated resource will be released
4520 * automatically by the engine as soon we return from this foreign function.
4521 */
4522 pArray = jx9_context_new_array(pCtx);
4523 if( pArray == 0 ){
4524 SXUNUSED(nArg); /* cc warning */
4525 SXUNUSED(apArg);
4526 /* Return NULL */
4527 jx9_result_null(pCtx);
4528 return SXRET_OK;
4529 }
4530 /* Fill with the appropriate information */
4531 SyHashForEach(&pCtx->pVm->hHostFunction,VmHashFuncStep,pArray);
4532 /* Fill with the appropriate information */
4533 SyHashForEach(&pCtx->pVm->hFunction, VmHashFuncStep,pArray);
4534 /* Return a copy of the array array */
4535 jx9_result_value(pCtx, pArray);
4536 return SXRET_OK;
4537}
4538/*
4539 * Call a user defined or foreign function where the name of the function
4540 * is stored in the pFunc parameter and the given arguments are stored
4541 * in the apArg[] array.
4542 * Return SXRET_OK if the function was successfuly called.Any other
4543 * return value indicates failure.
4544 */
4545JX9_PRIVATE sxi32 jx9VmCallUserFunction(
4546 jx9_vm *pVm, /* Target VM */
4547 jx9_value *pFunc, /* Callback name */
4548 int nArg, /* Total number of given arguments */
4549 jx9_value **apArg, /* Callback arguments */
4550 jx9_value *pResult /* Store callback return value here. NULL otherwise */
4551 )
4552{
4553 jx9_value *aStack;
4554 VmInstr aInstr[2];
4555 int i;
4556 if((pFunc->iFlags & (MEMOBJ_STRING)) == 0 ){
4557 /* Don't bother processing, it's invalid anyway */
4558 if( pResult ){
4559 /* Assume a null return value */
4560 jx9MemObjRelease(pResult);
4561 }
4562 return SXERR_INVALID;
4563 }
4564 /* Create a new operand stack */
4565 aStack = VmNewOperandStack(&(*pVm), 1+nArg);
4566 if( aStack == 0 ){
4567 jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR,
4568 "JX9 is running out of memory while invoking user callback");
4569 if( pResult ){
4570 /* Assume a null return value */
4571 jx9MemObjRelease(pResult);
4572 }
4573 return SXERR_MEM;
4574 }
4575 /* Fill the operand stack with the given arguments */
4576 for( i = 0 ; i < nArg ; i++ ){
4577 jx9MemObjLoad(apArg[i], &aStack[i]);
4578 aStack[i].nIdx = apArg[i]->nIdx;
4579 }
4580 /* Push the function name */
4581 jx9MemObjLoad(pFunc, &aStack[i]);
4582 aStack[i].nIdx = SXU32_HIGH; /* Mark as constant */
4583 /* Emit the CALL istruction */
4584 aInstr[0].iOp = JX9_OP_CALL;
4585 aInstr[0].iP1 = nArg; /* Total number of given arguments */
4586 aInstr[0].iP2 = 0;
4587 aInstr[0].p3 = 0;
4588 /* Emit the DONE instruction */
4589 aInstr[1].iOp = JX9_OP_DONE;
4590 aInstr[1].iP1 = 1; /* Extract function return value if available */
4591 aInstr[1].iP2 = 0;
4592 aInstr[1].p3 = 0;
4593 /* Execute the function body (if available) */
4594 VmByteCodeExec(&(*pVm), aInstr, aStack, nArg, pResult);
4595 /* Clean up the mess left behind */
4596 SyMemBackendFree(&pVm->sAllocator, aStack);
4597 return JX9_OK;
4598}
4599/*
4600 * Call a user defined or foreign function whith a varibale number
4601 * of arguments where the name of the function is stored in the pFunc
4602 * parameter.
4603 * Return SXRET_OK if the function was successfuly called.Any other
4604 * return value indicates failure.
4605 */
4606JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp(
4607 jx9_vm *pVm, /* Target VM */
4608 jx9_value *pFunc, /* Callback name */
4609 jx9_value *pResult, /* Store callback return value here. NULL otherwise */
4610 ... /* 0 (Zero) or more Callback arguments */
4611 )
4612{
4613 jx9_value *pArg;
4614 SySet aArg;
4615 va_list ap;
4616 sxi32 rc;
4617 SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *));
4618 /* Copy arguments one after one */
4619 va_start(ap, pResult);
4620 for(;;){
4621 pArg = va_arg(ap, jx9_value *);
4622 if( pArg == 0 ){
4623 break;
4624 }
4625 SySetPut(&aArg, (const void *)&pArg);
4626 }
4627 /* Call the core routine */
4628 rc = jx9VmCallUserFunction(&(*pVm), pFunc, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg), pResult);
4629 /* Cleanup */
4630 SySetRelease(&aArg);
4631 return rc;
4632}
4633/*
4634 * bool defined(string $name)
4635 * Checks whether a given named constant exists.
4636 * Parameter:
4637 * Name of the desired constant.
4638 * Return
4639 * TRUE if the given constant exists.FALSE otherwise.
4640 */
4641static int vm_builtin_defined(jx9_context *pCtx, int nArg, jx9_value **apArg)
4642{
4643 const char *zName;
4644 int nLen = 0;
4645 int res = 0;
4646 if( nArg < 1 ){
4647 /* Missing constant name, return FALSE */
4648 jx9_context_throw_error(pCtx,JX9_CTX_NOTICE,"Missing constant name");
4649 jx9_result_bool(pCtx, 0);
4650 return SXRET_OK;
4651 }
4652 /* Extract constant name */
4653 zName = jx9_value_to_string(apArg[0], &nLen);
4654 /* Perform the lookup */
4655 if( nLen > 0 && SyHashGet(&pCtx->pVm->hConstant, (const void *)zName, (sxu32)nLen) != 0 ){
4656 /* Already defined */
4657 res = 1;
4658 }
4659 jx9_result_bool(pCtx, res);
4660 return SXRET_OK;
4661}
4662/*
4663 * Hash walker callback used by the [get_defined_constants()] function
4664 * defined below.
4665 */
4666static int VmHashConstStep(SyHashEntry *pEntry, void *pUserData)
4667{
4668 jx9_value *pArray = (jx9_value *)pUserData;
4669 jx9_value sName;
4670 sxi32 rc;
4671 /* Prepare the constant name for insertion */
4672 jx9MemObjInitFromString(pArray->pVm, &sName, 0);
4673 jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen);
4674 /* Perform the insertion */
4675 rc = jx9_array_add_elem(pArray, 0, &sName); /* Will make it's own copy */
4676 jx9MemObjRelease(&sName);
4677 return rc;
4678}
4679/*
4680 * array get_defined_constants(void)
4681 * Returns an associative array with the names of all defined
4682 * constants.
4683 * Parameters
4684 * NONE.
4685 * Returns
4686 * Returns the names of all the constants currently defined.
4687 */
4688static int vm_builtin_get_defined_constants(jx9_context *pCtx, int nArg, jx9_value **apArg)
4689{
4690 jx9_value *pArray;
4691 /* Create the array first*/
4692 pArray = jx9_context_new_array(pCtx);
4693 if( pArray == 0 ){
4694 SXUNUSED(nArg); /* cc warning */
4695 SXUNUSED(apArg);
4696 /* Return NULL */
4697 jx9_result_null(pCtx);
4698 return SXRET_OK;
4699 }
4700 /* Fill the array with the defined constants */
4701 SyHashForEach(&pCtx->pVm->hConstant, VmHashConstStep, pArray);
4702 /* Return the created array */
4703 jx9_result_value(pCtx, pArray);
4704 return SXRET_OK;
4705}
4706/*
4707 * Section:
4708 * Random numbers/string generators.
4709 * Authors:
4710 * Symisc Systems, devel@symisc.net.
4711 * Copyright (C) Symisc Systems, http://jx9.symisc.net
4712 * Status:
4713 * Stable.
4714 */
4715/*
4716 * Generate a random 32-bit unsigned integer.
4717 * JX9 use it's own private PRNG which is based on the one
4718 * used by te SQLite3 library.
4719 */
4720JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm)
4721{
4722 sxu32 iNum;
4723 SyRandomness(&pVm->sPrng, (void *)&iNum, sizeof(sxu32));
4724 return iNum;
4725}
4726/*
4727 * Generate a random string (English Alphabet) of length nLen.
4728 * Note that the generated string is NOT null terminated.
4729 * JX9 use it's own private PRNG which is based on the one used
4730 * by te SQLite3 library.
4731 */
4732JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen)
4733{
4734 static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */
4735 int i;
4736 /* Generate a binary string first */
4737 SyRandomness(&pVm->sPrng, zBuf, (sxu32)nLen);
4738 /* Turn the binary string into english based alphabet */
4739 for( i = 0 ; i < nLen ; ++i ){
4740 zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)];
4741 }
4742}
4743/*
4744 * int rand()
4745 * Generate a random (unsigned 32-bit) integer.
4746 * Parameter
4747 * $min
4748 * The lowest value to return (default: 0)
4749 * $max
4750 * The highest value to return (default: getrandmax())
4751 * Return
4752 * A pseudo random value between min (or 0) and max (or getrandmax(), inclusive).
4753 * Note:
4754 * JX9 use it's own private PRNG which is based on the one used
4755 * by te SQLite3 library.
4756 */
4757static int vm_builtin_rand(jx9_context *pCtx, int nArg, jx9_value **apArg)
4758{
4759 sxu32 iNum;
4760 /* Generate the random number */
4761 iNum = jx9VmRandomNum(pCtx->pVm);
4762 if( nArg > 1 ){
4763 sxu32 iMin, iMax;
4764 iMin = (sxu32)jx9_value_to_int(apArg[0]);
4765 iMax = (sxu32)jx9_value_to_int(apArg[1]);
4766 if( iMin < iMax ){
4767 sxu32 iDiv = iMax+1-iMin;
4768 if( iDiv > 0 ){
4769 iNum = (iNum % iDiv)+iMin;
4770 }
4771 }else if(iMax > 0 ){
4772 iNum %= iMax;
4773 }
4774 }
4775 /* Return the number */
4776 jx9_result_int64(pCtx, (jx9_int64)iNum);
4777 return SXRET_OK;
4778}
4779/*
4780 * int getrandmax(void)
4781 * Show largest possible random value
4782 * Return
4783 * The largest possible random value returned by rand() which is in
4784 * this implementation 0xFFFFFFFF.
4785 * Note:
4786 * JX9 use it's own private PRNG which is based on the one used
4787 * by te SQLite3 library.
4788 */
4789static int vm_builtin_getrandmax(jx9_context *pCtx, int nArg, jx9_value **apArg)
4790{
4791 SXUNUSED(nArg); /* cc warning */
4792 SXUNUSED(apArg);
4793 jx9_result_int64(pCtx, SXU32_HIGH);
4794 return SXRET_OK;
4795}
4796/*
4797 * string rand_str()
4798 * string rand_str(int $len)
4799 * Generate a random string (English alphabet).
4800 * Parameter
4801 * $len
4802 * Length of the desired string (default: 16, Min: 1, Max: 1024)
4803 * Return
4804 * A pseudo random string.
4805 * Note:
4806 * JX9 use it's own private PRNG which is based on the one used
4807 * by te SQLite3 library.
4808 */
4809static int vm_builtin_rand_str(jx9_context *pCtx, int nArg, jx9_value **apArg)
4810{
4811 char zString[1024];
4812 int iLen = 0x10;
4813 if( nArg > 0 ){
4814 /* Get the desired length */
4815 iLen = jx9_value_to_int(apArg[0]);
4816 if( iLen < 1 || iLen > 1024 ){
4817 /* Default length */
4818 iLen = 0x10;
4819 }
4820 }
4821 /* Generate the random string */
4822 jx9VmRandomString(pCtx->pVm, zString, iLen);
4823 /* Return the generated string */
4824 jx9_result_string(pCtx, zString, iLen); /* Will make it's own copy */
4825 return SXRET_OK;
4826}
4827/*
4828 * Section:
4829 * Language construct implementation as foreign functions.
4830 * Authors:
4831 * Symisc Systems, devel@symisc.net.
4832 * Copyright (C) Symisc Systems, http://jx9.symisc.net
4833 * Status:
4834 * Stable.
4835 */
4836/*
4837 * void print($string...)
4838 * Output one or more messages.
4839 * Parameters
4840 * $string
4841 * Message to output.
4842 * Return
4843 * NULL.
4844 */
4845static int vm_builtin_print(jx9_context *pCtx, int nArg,jx9_value **apArg)
4846{
4847 const char *zData;
4848 int nDataLen = 0;
4849 jx9_vm *pVm;
4850 int i, rc;
4851 /* Point to the target VM */
4852 pVm = pCtx->pVm;
4853 /* Output */
4854 for( i = 0 ; i < nArg ; ++i ){
4855 zData = jx9_value_to_string(apArg[i], &nDataLen);
4856 if( nDataLen > 0 ){
4857 rc = pVm->sVmConsumer.xConsumer((const void *)zData, (unsigned int)nDataLen, pVm->sVmConsumer.pUserData);
4858 /* Increment output length */
4859 pVm->nOutputLen += nDataLen;
4860 if( rc == SXERR_ABORT ){
4861 /* Output consumer callback request an operation abort */
4862 return JX9_ABORT;
4863 }
4864 }
4865 }
4866 return SXRET_OK;
4867}
4868/*
4869 * void exit(string $msg)
4870 * void exit(int $status)
4871 * void die(string $ms)
4872 * void die(int $status)
4873 * Output a message and terminate program execution.
4874 * Parameter
4875 * If status is a string, this function prints the status just before exiting.
4876 * If status is an integer, that value will be used as the exit status
4877 * and not printed
4878 * Return
4879 * NULL
4880 */
4881static int vm_builtin_exit(jx9_context *pCtx, int nArg, jx9_value **apArg)
4882{
4883 if( nArg > 0 ){
4884 if( jx9_value_is_string(apArg[0]) ){
4885 const char *zData;
4886 int iLen = 0;
4887 /* Print exit message */
4888 zData = jx9_value_to_string(apArg[0], &iLen);
4889 jx9_context_output(pCtx, zData, iLen);
4890 }else if(jx9_value_is_int(apArg[0]) ){
4891 sxi32 iExitStatus;
4892 /* Record exit status code */
4893 iExitStatus = jx9_value_to_int(apArg[0]);
4894 pCtx->pVm->iExitStatus = iExitStatus;
4895 }
4896 }
4897 /* Abort processing immediately */
4898 return JX9_ABORT;
4899}
4900/*
4901 * Unset a memory object [i.e: a jx9_value].
4902 */
4903JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm,sxu32 nObjIdx)
4904{
4905 jx9_value *pObj;
4906 pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nObjIdx);
4907 if( pObj ){
4908 VmSlot sFree;
4909 /* Release the object */
4910 jx9MemObjRelease(pObj);
4911 /* Restore to the free list */
4912 sFree.nIdx = nObjIdx;
4913 sFree.pUserData = 0;
4914 SySetPut(&pVm->aFreeObj, (const void *)&sFree);
4915 }
4916 return SXRET_OK;
4917}
4918/*
4919 * string gettype($var)
4920 * Get the type of a variable
4921 * Parameters
4922 * $var
4923 * The variable being type checked.
4924 * Return
4925 * String representation of the given variable type.
4926 */
4927static int vm_builtin_gettype(jx9_context *pCtx, int nArg, jx9_value **apArg)
4928{
4929 const char *zType = "null";
4930 if( nArg > 0 ){
4931 zType = jx9MemObjTypeDump(apArg[0]);
4932 }
4933 /* Return the variable type */
4934 jx9_result_string(pCtx, zType, -1/*Compute length automatically*/);
4935 return SXRET_OK;
4936}
4937/*
4938 * string get_resource_type(resource $handle)
4939 * This function gets the type of the given resource.
4940 * Parameters
4941 * $handle
4942 * The evaluated resource handle.
4943 * Return
4944 * If the given handle is a resource, this function will return a string
4945 * representing its type. If the type is not identified by this function
4946 * the return value will be the string Unknown.
4947 * This function will return FALSE and generate an error if handle
4948 * is not a resource.
4949 */
4950static int vm_builtin_get_resource_type(jx9_context *pCtx, int nArg, jx9_value **apArg)
4951{
4952 if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){
4953 /* Missing/Invalid arguments, return FALSE*/
4954 jx9_result_bool(pCtx, 0);
4955 return SXRET_OK;
4956 }
4957 jx9_result_string_format(pCtx, "resID_%#x", apArg[0]->x.pOther);
4958 return SXRET_OK;
4959}
4960/*
4961 * void dump(expression, ....)
4962 * dump — Dumps information about a variable
4963 * Parameters
4964 * One or more expression to dump.
4965 * Returns
4966 * Nothing.
4967 */
4968static int vm_builtin_dump(jx9_context *pCtx, int nArg, jx9_value **apArg)
4969{
4970 SyBlob sDump; /* Generated dump is stored here */
4971 int i;
4972 SyBlobInit(&sDump,&pCtx->pVm->sAllocator);
4973 /* Dump one or more expressions */
4974 for( i = 0 ; i < nArg ; i++ ){
4975 jx9_value *pObj = apArg[i];
4976 /* Reset the working buffer */
4977 SyBlobReset(&sDump);
4978 /* Dump the given expression */
4979 jx9MemObjDump(&sDump,pObj);
4980 /* Output */
4981 if( SyBlobLength(&sDump) > 0 ){
4982 jx9_context_output(pCtx, (const char *)SyBlobData(&sDump), (int)SyBlobLength(&sDump));
4983 }
4984 }
4985 /* Release the working buffer */
4986 SyBlobRelease(&sDump);
4987 return SXRET_OK;
4988}
4989/*
4990 * Section:
4991 * Version, Credits and Copyright related functions.
4992 * Authors:
4993 * Symisc Systems, devel@symisc.net.
4994 * Copyright (C) Symisc Systems, http://jx9.symisc.net
4995 * Stable.
4996 */
4997/*
4998 * string jx9_version(void)
4999 * string jx9_credits(void)
5000 * Returns the running version of the jx9 version.
5001 * Parameters
5002 * None
5003 * Return
5004 * Current jx9 version.
5005 */
5006static int vm_builtin_jx9_version(jx9_context *pCtx, int nArg, jx9_value **apArg)
5007{
5008 SXUNUSED(nArg);
5009 SXUNUSED(apArg); /* cc warning */
5010 /* Current engine version, signature and cipyright notice */
5011 jx9_result_string_format(pCtx,"%s %s, %s",JX9_VERSION,JX9_SIG,JX9_COPYRIGHT);
5012 return JX9_OK;
5013}
5014/*
5015 * Section:
5016 * URL related routines.
5017 * Authors:
5018 * Symisc Systems, devel@symisc.net.
5019 * Copyright (C) Symisc Systems, http://jx9.symisc.net
5020 * Status:
5021 * Stable.
5022 */
5023/* Forward declaration */
5024static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen);
5025/*
5026 * value parse_url(string $url [, int $component = -1 ])
5027 * Parse a URL and return its fields.
5028 * Parameters
5029 * $url
5030 * The URL to parse.
5031 * $component
5032 * Specify one of JX9_URL_SCHEME, JX9_URL_HOST, JX9_URL_PORT, JX9_URL_USER
5033 * JX9_URL_PASS, JX9_URL_PATH, JX9_URL_QUERY or JX9_URL_FRAGMENT to retrieve
5034 * just a specific URL component as a string (except when JX9_URL_PORT is given
5035 * in which case the return value will be an integer).
5036 * Return
5037 * If the component parameter is omitted, an associative array is returned.
5038 * At least one element will be present within the array. Potential keys within
5039 * this array are:
5040 * scheme - e.g. http
5041 * host
5042 * port
5043 * user
5044 * pass
5045 * path
5046 * query - after the question mark ?
5047 * fragment - after the hashmark #
5048 * Note:
5049 * FALSE is returned on failure.
5050 * This function work with relative URL unlike the one shipped
5051 * with the standard JX9 engine.
5052 */
5053static int vm_builtin_parse_url(jx9_context *pCtx, int nArg, jx9_value **apArg)
5054{
5055 const char *zStr; /* Input string */
5056 SyString *pComp; /* Pointer to the URI component */
5057 SyhttpUri sURI; /* Parse of the given URI */
5058 int nLen;
5059 sxi32 rc;
5060 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
5061 /* Missing/Invalid arguments, return FALSE */
5062 jx9_result_bool(pCtx, 0);
5063 return JX9_OK;
5064 }
5065 /* Extract the given URI */
5066 zStr = jx9_value_to_string(apArg[0], &nLen);
5067 if( nLen < 1 ){
5068 /* Nothing to process, return FALSE */
5069 jx9_result_bool(pCtx, 0);
5070 return JX9_OK;
5071 }
5072 /* Get a parse */
5073 rc = VmHttpSplitURI(&sURI, zStr, (sxu32)nLen);
5074 if( rc != SXRET_OK ){
5075 /* Malformed input, return FALSE */
5076 jx9_result_bool(pCtx, 0);
5077 return JX9_OK;
5078 }
5079 if( nArg > 1 ){
5080 int nComponent = jx9_value_to_int(apArg[1]);
5081 /* Refer to constant.c for constants values */
5082 switch(nComponent){
5083 case 1: /* JX9_URL_SCHEME */
5084 pComp = &sURI.sScheme;
5085 if( pComp->nByte < 1 ){
5086 /* No available value, return NULL */
5087 jx9_result_null(pCtx);
5088 }else{
5089 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
5090 }
5091 break;
5092 case 2: /* JX9_URL_HOST */
5093 pComp = &sURI.sHost;
5094 if( pComp->nByte < 1 ){
5095 /* No available value, return NULL */
5096 jx9_result_null(pCtx);
5097 }else{
5098 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
5099 }
5100 break;
5101 case 3: /* JX9_URL_PORT */
5102 pComp = &sURI.sPort;
5103 if( pComp->nByte < 1 ){
5104 /* No available value, return NULL */
5105 jx9_result_null(pCtx);
5106 }else{
5107 int iPort = 0;
5108 /* Cast the value to integer */
5109 SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0);
5110 jx9_result_int(pCtx, iPort);
5111 }
5112 break;
5113 case 4: /* JX9_URL_USER */
5114 pComp = &sURI.sUser;
5115 if( pComp->nByte < 1 ){
5116 /* No available value, return NULL */
5117 jx9_result_null(pCtx);
5118 }else{
5119 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
5120 }
5121 break;
5122 case 5: /* JX9_URL_PASS */
5123 pComp = &sURI.sPass;
5124 if( pComp->nByte < 1 ){
5125 /* No available value, return NULL */
5126 jx9_result_null(pCtx);
5127 }else{
5128 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
5129 }
5130 break;
5131 case 7: /* JX9_URL_QUERY */
5132 pComp = &sURI.sQuery;
5133 if( pComp->nByte < 1 ){
5134 /* No available value, return NULL */
5135 jx9_result_null(pCtx);
5136 }else{
5137 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
5138 }
5139 break;
5140 case 8: /* JX9_URL_FRAGMENT */
5141 pComp = &sURI.sFragment;
5142 if( pComp->nByte < 1 ){
5143 /* No available value, return NULL */
5144 jx9_result_null(pCtx);
5145 }else{
5146 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
5147 }
5148 break;
5149 case 6: /* JX9_URL_PATH */
5150 pComp = &sURI.sPath;
5151 if( pComp->nByte < 1 ){
5152 /* No available value, return NULL */
5153 jx9_result_null(pCtx);
5154 }else{
5155 jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte);
5156 }
5157 break;
5158 default:
5159 /* No such entry, return NULL */
5160 jx9_result_null(pCtx);
5161 break;
5162 }
5163 }else{
5164 jx9_value *pArray, *pValue;
5165 /* Return an associative array */
5166 pArray = jx9_context_new_array(pCtx); /* Empty array */
5167 pValue = jx9_context_new_scalar(pCtx); /* Array value */
5168 if( pArray == 0 || pValue == 0 ){
5169 /* Out of memory */
5170 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "jx9 engine is running out of memory");
5171 /* Return false */
5172 jx9_result_bool(pCtx, 0);
5173 return JX9_OK;
5174 }
5175 /* Fill the array */
5176 pComp = &sURI.sScheme;
5177 if( pComp->nByte > 0 ){
5178 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
5179 jx9_array_add_strkey_elem(pArray, "scheme", pValue); /* Will make it's own copy */
5180 }
5181 /* Reset the string cursor */
5182 jx9_value_reset_string_cursor(pValue);
5183 pComp = &sURI.sHost;
5184 if( pComp->nByte > 0 ){
5185 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
5186 jx9_array_add_strkey_elem(pArray, "host", pValue); /* Will make it's own copy */
5187 }
5188 /* Reset the string cursor */
5189 jx9_value_reset_string_cursor(pValue);
5190 pComp = &sURI.sPort;
5191 if( pComp->nByte > 0 ){
5192 int iPort = 0;/* cc warning */
5193 /* Convert to integer */
5194 SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0);
5195 jx9_value_int(pValue, iPort);
5196 jx9_array_add_strkey_elem(pArray, "port", pValue); /* Will make it's own copy */
5197 }
5198 /* Reset the string cursor */
5199 jx9_value_reset_string_cursor(pValue);
5200 pComp = &sURI.sUser;
5201 if( pComp->nByte > 0 ){
5202 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
5203 jx9_array_add_strkey_elem(pArray, "user", pValue); /* Will make it's own copy */
5204 }
5205 /* Reset the string cursor */
5206 jx9_value_reset_string_cursor(pValue);
5207 pComp = &sURI.sPass;
5208 if( pComp->nByte > 0 ){
5209 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
5210 jx9_array_add_strkey_elem(pArray, "pass", pValue); /* Will make it's own copy */
5211 }
5212 /* Reset the string cursor */
5213 jx9_value_reset_string_cursor(pValue);
5214 pComp = &sURI.sPath;
5215 if( pComp->nByte > 0 ){
5216 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
5217 jx9_array_add_strkey_elem(pArray, "path", pValue); /* Will make it's own copy */
5218 }
5219 /* Reset the string cursor */
5220 jx9_value_reset_string_cursor(pValue);
5221 pComp = &sURI.sQuery;
5222 if( pComp->nByte > 0 ){
5223 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
5224 jx9_array_add_strkey_elem(pArray, "query", pValue); /* Will make it's own copy */
5225 }
5226 /* Reset the string cursor */
5227 jx9_value_reset_string_cursor(pValue);
5228 pComp = &sURI.sFragment;
5229 if( pComp->nByte > 0 ){
5230 jx9_value_string(pValue, pComp->zString, (int)pComp->nByte);
5231 jx9_array_add_strkey_elem(pArray, "fragment", pValue); /* Will make it's own copy */
5232 }
5233 /* Return the created array */
5234 jx9_result_value(pCtx, pArray);
5235 /* NOTE:
5236 * Don't worry about freeing 'pValue', everything will be released
5237 * automatically as soon we return from this function.
5238 */
5239 }
5240 /* All done */
5241 return JX9_OK;
5242}
5243/*
5244 * Section:
5245 * Array related routines.
5246 * Authors:
5247 * Symisc Systems, devel@symisc.net.
5248 * Copyright (C) Symisc Systems, http://jx9.symisc.net
5249 * Status:
5250 * Stable.
5251 * Note 2012-5-21 01:04:15:
5252 * Array related functions that need access to the underlying
5253 * virtual machine are implemented here rather than 'hashmap.c'
5254 */
5255/*
5256 * The [extract()] function store it's state information in an instance
5257 * of the following structure.
5258 */
5259typedef struct extract_aux_data extract_aux_data;
5260struct extract_aux_data
5261{
5262 jx9_vm *pVm; /* VM that own this instance */
5263 int iCount; /* Number of variables successfully imported */
5264 const char *zPrefix; /* Prefix name */
5265 int Prefixlen; /* Prefix length */
5266 int iFlags; /* Control flags */
5267 char zWorker[1024]; /* Working buffer */
5268};
5269/* Forward declaration */
5270static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData);
5271/*
5272 * int extract(array $var_array[, int $extract_type = EXTR_OVERWRITE[, string $prefix = NULL ]])
5273 * Import variables into the current symbol table from an array.
5274 * Parameters
5275 * $var_array
5276 * An associative array. This function treats keys as variable names and values
5277 * as variable values. For each key/value pair it will create a variable in the current symbol
5278 * table, subject to extract_type and prefix parameters.
5279 * You must use an associative array; a numerically indexed array will not produce results
5280 * unless you use EXTR_PREFIX_ALL or EXTR_PREFIX_INVALID.
5281 * $extract_type
5282 * The way invalid/numeric keys and collisions are treated is determined by the extract_type.
5283 * It can be one of the following values:
5284 * EXTR_OVERWRITE
5285 * If there is a collision, overwrite the existing variable.
5286 * EXTR_SKIP
5287 * If there is a collision, don't overwrite the existing variable.
5288 * EXTR_PREFIX_SAME
5289 * If there is a collision, prefix the variable name with prefix.
5290 * EXTR_PREFIX_ALL
5291 * Prefix all variable names with prefix.
5292 * EXTR_PREFIX_INVALID
5293 * Only prefix invalid/numeric variable names with prefix.
5294 * EXTR_IF_EXISTS
5295 * Only overwrite the variable if it already exists in the current symbol table
5296 * otherwise do nothing.
5297 * This is useful for defining a list of valid variables and then extracting only those
5298 * variables you have defined out of $_REQUEST, for example.
5299 * EXTR_PREFIX_IF_EXISTS
5300 * Only create prefixed variable names if the non-prefixed version of the same variable exists in
5301 * the current symbol table.
5302 * $prefix
5303 * Note that prefix is only required if extract_type is EXTR_PREFIX_SAME, EXTR_PREFIX_ALL
5304 * EXTR_PREFIX_INVALID or EXTR_PREFIX_IF_EXISTS. If the prefixed result is not a valid variable name
5305 * it is not imported into the symbol table. Prefixes are automatically separated from the array key by an
5306 * underscore character.
5307 * Return
5308 * Returns the number of variables successfully imported into the symbol table.
5309 */
5310static int vm_builtin_extract(jx9_context *pCtx, int nArg, jx9_value **apArg)
5311{
5312 extract_aux_data sAux;
5313 jx9_hashmap *pMap;
5314 if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){
5315 /* Missing/Invalid arguments, return 0 */
5316 jx9_result_int(pCtx, 0);
5317 return JX9_OK;
5318 }
5319 /* Point to the target hashmap */
5320 pMap = (jx9_hashmap *)apArg[0]->x.pOther;
5321 if( pMap->nEntry < 1 ){
5322 /* Empty map, return 0 */
5323 jx9_result_int(pCtx, 0);
5324 return JX9_OK;
5325 }
5326 /* Prepare the aux data */
5327 SyZero(&sAux, sizeof(extract_aux_data)-sizeof(sAux.zWorker));
5328 if( nArg > 1 ){
5329 sAux.iFlags = jx9_value_to_int(apArg[1]);
5330 if( nArg > 2 ){
5331 sAux.zPrefix = jx9_value_to_string(apArg[2], &sAux.Prefixlen);
5332 }
5333 }
5334 sAux.pVm = pCtx->pVm;
5335 /* Invoke the worker callback */
5336 jx9HashmapWalk(pMap, VmExtractCallback, &sAux);
5337 /* Number of variables successfully imported */
5338 jx9_result_int(pCtx, sAux.iCount);
5339 return JX9_OK;
5340}
5341/*
5342 * Worker callback for the [extract()] function defined
5343 * below.
5344 */
5345static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData)
5346{
5347 extract_aux_data *pAux = (extract_aux_data *)pUserData;
5348 int iFlags = pAux->iFlags;
5349 jx9_vm *pVm = pAux->pVm;
5350 jx9_value *pObj;
5351 SyString sVar;
5352 if( (iFlags & 0x10/* EXTR_PREFIX_INVALID */) && (pKey->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL|MEMOBJ_REAL))){
5353 iFlags |= 0x08; /*EXTR_PREFIX_ALL*/
5354 }
5355 /* Perform a string cast */
5356 jx9MemObjToString(pKey);
5357 if( SyBlobLength(&pKey->sBlob) < 1 ){
5358 /* Unavailable variable name */
5359 return SXRET_OK;
5360 }
5361 sVar.nByte = 0; /* cc warning */
5362 if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/ ) && pAux->Prefixlen > 0 ){
5363 sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker, sizeof(pAux->zWorker), "%.*s_%.*s",
5364 pAux->Prefixlen, pAux->zPrefix,
5365 SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob)
5366 );
5367 }else{
5368 sVar.nByte = (sxu32) SyMemcpy(SyBlobData(&pKey->sBlob), pAux->zWorker,
5369 SXMIN(SyBlobLength(&pKey->sBlob), sizeof(pAux->zWorker)));
5370 }
5371 sVar.zString = pAux->zWorker;
5372 /* Try to extract the variable */
5373 pObj = VmExtractMemObj(pVm, &sVar, TRUE, FALSE);
5374 if( pObj ){
5375 /* Collision */
5376 if( iFlags & 0x02 /* EXTR_SKIP */ ){
5377 return SXRET_OK;
5378 }
5379 if( iFlags & 0x04 /* EXTR_PREFIX_SAME */ ){
5380 if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/) || pAux->Prefixlen < 1){
5381 /* Already prefixed */
5382 return SXRET_OK;
5383 }
5384 sVar.nByte = SyBufferFormat(
5385 pAux->zWorker, sizeof(pAux->zWorker),
5386 "%.*s_%.*s",
5387 pAux->Prefixlen, pAux->zPrefix,
5388 SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob)
5389 );
5390 pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE);
5391 }
5392 }else{
5393 /* Create the variable */
5394 pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE);
5395 }
5396 if( pObj ){
5397 /* Overwrite the old value */
5398 jx9MemObjStore(pValue, pObj);
5399 /* Increment counter */
5400 pAux->iCount++;
5401 }
5402 return SXRET_OK;
5403}
5404/*
5405 * Compile and evaluate a JX9 chunk at run-time.
5406 * Refer to the include language construct implementation for more
5407 * information.
5408 */
5409static sxi32 VmEvalChunk(
5410 jx9_vm *pVm, /* Underlying Virtual Machine */
5411 jx9_context *pCtx, /* Call Context */
5412 SyString *pChunk, /* JX9 chunk to evaluate */
5413 int iFlags, /* Compile flag */
5414 int bTrueReturn /* TRUE to return execution result */
5415 )
5416{
5417 SySet *pByteCode, aByteCode;
5418 ProcConsumer xErr = 0;
5419 void *pErrData = 0;
5420 /* Initialize bytecode container */
5421 SySetInit(&aByteCode, &pVm->sAllocator, sizeof(VmInstr));
5422 SySetAlloc(&aByteCode, 0x20);
5423 /* Reset the code generator */
5424 if( bTrueReturn ){
5425 /* Included file, log compile-time errors */
5426 xErr = pVm->pEngine->xConf.xErr;
5427 pErrData = pVm->pEngine->xConf.pErrData;
5428 }
5429 jx9ResetCodeGenerator(pVm, xErr, pErrData);
5430 /* Swap bytecode container */
5431 pByteCode = pVm->pByteContainer;
5432 pVm->pByteContainer = &aByteCode;
5433 /* Compile the chunk */
5434 jx9CompileScript(pVm, pChunk, iFlags);
5435 if( pVm->sCodeGen.nErr > 0 ){
5436 /* Compilation error, return false */
5437 if( pCtx ){
5438 jx9_result_bool(pCtx, 0);
5439 }
5440 }else{
5441 jx9_value sResult; /* Return value */
5442 if( SXRET_OK != jx9VmEmitInstr(pVm, JX9_OP_DONE, 0, 0, 0, 0) ){
5443 /* Out of memory */
5444 if( pCtx ){
5445 jx9_result_bool(pCtx, 0);
5446 }
5447 goto Cleanup;
5448 }
5449 if( bTrueReturn ){
5450 /* Assume a boolean true return value */
5451 jx9MemObjInitFromBool(pVm, &sResult, 1);
5452 }else{
5453 /* Assume a null return value */
5454 jx9MemObjInit(pVm, &sResult);
5455 }
5456 /* Execute the compiled chunk */
5457 VmLocalExec(pVm, &aByteCode, &sResult);
5458 if( pCtx ){
5459 /* Set the execution result */
5460 jx9_result_value(pCtx, &sResult);
5461 }
5462 jx9MemObjRelease(&sResult);
5463 }
5464Cleanup:
5465 /* Cleanup the mess left behind */
5466 pVm->pByteContainer = pByteCode;
5467 SySetRelease(&aByteCode);
5468 return SXRET_OK;
5469}
5470/*
5471 * Check if a file path is already included.
5472 */
5473static int VmIsIncludedFile(jx9_vm *pVm, SyString *pFile)
5474{
5475 SyString *aEntries;
5476 sxu32 n;
5477 aEntries = (SyString *)SySetBasePtr(&pVm->aIncluded);
5478 /* Perform a linear search */
5479 for( n = 0 ; n < SySetUsed(&pVm->aIncluded) ; ++n ){
5480 if( SyStringCmp(pFile, &aEntries[n], SyMemcmp) == 0 ){
5481 /* Already included */
5482 return TRUE;
5483 }
5484 }
5485 return FALSE;
5486}
5487/*
5488 * Push a file path in the appropriate VM container.
5489 */
5490JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew)
5491{
5492 SyString sPath;
5493 char *zDup;
5494#ifdef __WINNT__
5495 char *zCur;
5496#endif
5497 sxi32 rc;
5498 if( nLen < 0 ){
5499 nLen = SyStrlen(zPath);
5500 }
5501 /* Duplicate the file path first */
5502 zDup = SyMemBackendStrDup(&pVm->sAllocator, zPath, nLen);
5503 if( zDup == 0 ){
5504 return SXERR_MEM;
5505 }
5506#ifdef __WINNT__
5507 /* Normalize path on windows
5508 * Example:
5509 * Path/To/File.jx9
5510 * becomes
5511 * path\to\file.jx9
5512 */
5513 zCur = zDup;
5514 while( zCur[0] != 0 ){
5515 if( zCur[0] == '/' ){
5516 zCur[0] = '\\';
5517 }else if( (unsigned char)zCur[0] < 0xc0 && SyisUpper(zCur[0]) ){
5518 int c = SyToLower(zCur[0]);
5519 zCur[0] = (char)c; /* MSVC stupidity */
5520 }
5521 zCur++;
5522 }
5523#endif
5524 /* Install the file path */
5525 SyStringInitFromBuf(&sPath, zDup, nLen);
5526 if( !bMain ){
5527 if( VmIsIncludedFile(&(*pVm), &sPath) ){
5528 /* Already included */
5529 *pNew = 0;
5530 }else{
5531 /* Insert in the corresponding container */
5532 rc = SySetPut(&pVm->aIncluded, (const void *)&sPath);
5533 if( rc != SXRET_OK ){
5534 SyMemBackendFree(&pVm->sAllocator, zDup);
5535 return rc;
5536 }
5537 *pNew = 1;
5538 }
5539 }
5540 SySetPut(&pVm->aFiles, (const void *)&sPath);
5541 return SXRET_OK;
5542}
5543/*
5544 * Compile and Execute a JX9 script at run-time.
5545 * SXRET_OK is returned on sucessful evaluation.Any other return values
5546 * indicates failure.
5547 * Note that the JX9 script to evaluate can be a local or remote file.In
5548 * either cases the [jx9StreamReadWholeFile()] function handle all the underlying
5549 * operations.
5550 * If the [jJX9_DISABLE_BUILTIN_FUNC] compile-time directive is defined, then
5551 * this function is a no-op.
5552 * Refer to the implementation of the include(), import() language
5553 * constructs for more information.
5554 */
5555static sxi32 VmExecIncludedFile(
5556 jx9_context *pCtx, /* Call Context */
5557 SyString *pPath, /* Script path or URL*/
5558 int IncludeOnce /* TRUE if called from import() or require_once() */
5559 )
5560{
5561 sxi32 rc;
5562#ifndef JX9_DISABLE_BUILTIN_FUNC
5563 const jx9_io_stream *pStream;
5564 SyBlob sContents;
5565 void *pHandle;
5566 jx9_vm *pVm;
5567 int isNew;
5568 /* Initialize fields */
5569 pVm = pCtx->pVm;
5570 SyBlobInit(&sContents, &pVm->sAllocator);
5571 isNew = 0;
5572 /* Extract the associated stream */
5573 pStream = jx9VmGetStreamDevice(pVm, &pPath->zString, pPath->nByte);
5574 /*
5575 * Open the file or the URL [i.e: http://jx9.symisc.net/example/hello.jx9.txt"]
5576 * in a read-only mode.
5577 */
5578 pHandle = jx9StreamOpenHandle(pVm, pStream,pPath->zString, JX9_IO_OPEN_RDONLY, TRUE, 0, TRUE, &isNew);
5579 if( pHandle == 0 ){
5580 return SXERR_IO;
5581 }
5582 rc = SXRET_OK; /* Stupid cc warning */
5583 if( IncludeOnce && !isNew ){
5584 /* Already included */
5585 rc = SXERR_EXISTS;
5586 }else{
5587 /* Read the whole file contents */
5588 rc = jx9StreamReadWholeFile(pHandle, pStream, &sContents);
5589 if( rc == SXRET_OK ){
5590 SyString sScript;
5591 /* Compile and execute the script */
5592 SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents));
5593 VmEvalChunk(pCtx->pVm, &(*pCtx), &sScript, 0, TRUE);
5594 }
5595 }
5596 /* Pop from the set of included file */
5597 (void)SySetPop(&pVm->aFiles);
5598 /* Close the handle */
5599 jx9StreamCloseHandle(pStream, pHandle);
5600 /* Release the working buffer */
5601 SyBlobRelease(&sContents);
5602#else
5603 pCtx = 0; /* cc warning */
5604 pPath = 0;
5605 IncludeOnce = 0;
5606 rc = SXERR_IO;
5607#endif /* JX9_DISABLE_BUILTIN_FUNC */
5608 return rc;
5609}
5610/* * include:
5611 * According to the JX9 reference manual.
5612 * The include() function includes and evaluates the specified file.
5613 * Files are included based on the file path given or, if none is given
5614 * the include_path specified.If the file isn't found in the include_path
5615 * include() will finally check in the calling script's own directory
5616 * and the current working directory before failing. The include()
5617 * construct will emit a warning if it cannot find a file; this is different
5618 * behavior from require(), which will emit a fatal error.
5619 * If a path is defined — whether absolute (starting with a drive letter
5620 * or \ on Windows, or / on Unix/Linux systems) or relative to the current
5621 * directory (starting with . or ..) — the include_path will be ignored altogether.
5622 * For example, if a filename begins with ../, the parser will look in the parent
5623 * directory to find the requested file.
5624 * When a file is included, the code it contains inherits the variable scope
5625 * of the line on which the include occurs. Any variables available at that line
5626 * in the calling file will be available within the called file, from that point forward.
5627 * However, all functions and objectes defined in the included file have the global scope.
5628 */
5629static int vm_builtin_include(jx9_context *pCtx, int nArg, jx9_value **apArg)
5630{
5631 SyString sFile;
5632 sxi32 rc;
5633 if( nArg < 1 ){
5634 /* Nothing to evaluate, return NULL */
5635 jx9_result_null(pCtx);
5636 return SXRET_OK;
5637 }
5638 /* File to include */
5639 sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte);
5640 if( sFile.nByte < 1 ){
5641 /* Empty string, return NULL */
5642 jx9_result_null(pCtx);
5643 return SXRET_OK;
5644 }
5645 /* Open, compile and execute the desired script */
5646 rc = VmExecIncludedFile(&(*pCtx), &sFile, FALSE);
5647 if( rc != SXRET_OK ){
5648 /* Emit a warning and return false */
5649 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile);
5650 jx9_result_bool(pCtx, 0);
5651 }
5652 return SXRET_OK;
5653}
5654/*
5655 * import:
5656 * According to the JX9 reference manual.
5657 * The import() statement includes and evaluates the specified file during
5658 * the execution of the script. This is a behavior similar to the include()
5659 * statement, with the only difference being that if the code from a file has already
5660 * been included, it will not be included again. As the name suggests, it will be included
5661 * just once.
5662 */
5663static int vm_builtin_import(jx9_context *pCtx, int nArg, jx9_value **apArg)
5664{
5665 SyString sFile;
5666 sxi32 rc;
5667 if( nArg < 1 ){
5668 /* Nothing to evaluate, return NULL */
5669 jx9_result_null(pCtx);
5670 return SXRET_OK;
5671 }
5672 /* File to include */
5673 sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte);
5674 if( sFile.nByte < 1 ){
5675 /* Empty string, return NULL */
5676 jx9_result_null(pCtx);
5677 return SXRET_OK;
5678 }
5679 /* Open, compile and execute the desired script */
5680 rc = VmExecIncludedFile(&(*pCtx), &sFile, TRUE);
5681 if( rc == SXERR_EXISTS ){
5682 /* File already included, return TRUE */
5683 jx9_result_bool(pCtx, 1);
5684 return SXRET_OK;
5685 }
5686 if( rc != SXRET_OK ){
5687 /* Emit a warning and return false */
5688 jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile);
5689 jx9_result_bool(pCtx, 0);
5690 }
5691 return SXRET_OK;
5692}
5693/*
5694 * Section:
5695 * Command line arguments processing.
5696 * Authors:
5697 * Symisc Systems, devel@symisc.net.
5698 * Copyright (C) Symisc Systems, http://jx9.symisc.net
5699 * Status:
5700 * Stable.
5701 */
5702/*
5703 * Check if a short option argument [i.e: -c] is available in the command
5704 * line string. Return a pointer to the start of the stream on success.
5705 * NULL otherwise.
5706 */
5707static const char * VmFindShortOpt(int c, const char *zIn, const char *zEnd)
5708{
5709 while( zIn < zEnd ){
5710 if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == c ){
5711 /* Got one */
5712 return &zIn[1];
5713 }
5714 /* Advance the cursor */
5715 zIn++;
5716 }
5717 /* No such option */
5718 return 0;
5719}
5720/*
5721 * Check if a long option argument [i.e: --opt] is available in the command
5722 * line string. Return a pointer to the start of the stream on success.
5723 * NULL otherwise.
5724 */
5725static const char * VmFindLongOpt(const char *zLong, int nByte, const char *zIn, const char *zEnd)
5726{
5727 const char *zOpt;
5728 while( zIn < zEnd ){
5729 if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == '-' ){
5730 zIn += 2;
5731 zOpt = zIn;
5732 while( zIn < zEnd && !SyisSpace(zIn[0]) ){
5733 if( zIn[0] == '=' /* --opt=val */){
5734 break;
5735 }
5736 zIn++;
5737 }
5738 /* Test */
5739 if( (int)(zIn-zOpt) == nByte && SyMemcmp(zOpt, zLong, nByte) == 0 ){
5740 /* Got one, return it's value */
5741 return zIn;
5742 }
5743
5744 }else{
5745 zIn++;
5746 }
5747 }
5748 /* No such option */
5749 return 0;
5750}
5751/*
5752 * Long option [i.e: --opt] arguments private data structure.
5753 */
5754struct getopt_long_opt
5755{
5756 const char *zArgIn, *zArgEnd; /* Command line arguments */
5757 jx9_value *pWorker; /* Worker variable*/
5758 jx9_value *pArray; /* getopt() return value */
5759 jx9_context *pCtx; /* Call Context */
5760};
5761/* Forward declaration */
5762static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData);
5763/*
5764 * Extract short or long argument option values.
5765 */
5766static void VmExtractOptArgValue(
5767 jx9_value *pArray, /* getopt() return value */
5768 jx9_value *pWorker, /* Worker variable */
5769 const char *zArg, /* Argument stream */
5770 const char *zArgEnd, /* End of the argument stream */
5771 int need_val, /* TRUE to fetch option argument */
5772 jx9_context *pCtx, /* Call Context */
5773 const char *zName /* Option name */)
5774{
5775 jx9_value_bool(pWorker, 0);
5776 if( !need_val ){
5777 /*
5778 * Option does not need arguments.
5779 * Insert the option name and a boolean FALSE.
5780 */
5781 jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */
5782 }else{
5783 const char *zCur;
5784 /* Extract option argument */
5785 zArg++;
5786 if( zArg < zArgEnd && zArg[0] == '=' ){
5787 zArg++;
5788 }
5789 while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){
5790 zArg++;
5791 }
5792 if( zArg >= zArgEnd || zArg[0] == '-' ){
5793 /*
5794 * Argument not found.
5795 * Insert the option name and a boolean FALSE.
5796 */
5797 jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */
5798 return;
5799 }
5800 /* Delimit the value */
5801 zCur = zArg;
5802 if( zArg[0] == '\'' || zArg[0] == '"' ){
5803 int d = zArg[0];
5804 /* Delimt the argument */
5805 zArg++;
5806 zCur = zArg;
5807 while( zArg < zArgEnd ){
5808 if( zArg[0] == d && zArg[-1] != '\\' ){
5809 /* Delimiter found, exit the loop */
5810 break;
5811 }
5812 zArg++;
5813 }
5814 /* Save the value */
5815 jx9_value_string(pWorker, zCur, (int)(zArg-zCur));
5816 if( zArg < zArgEnd ){ zArg++; }
5817 }else{
5818 while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){
5819 zArg++;
5820 }
5821 /* Save the value */
5822 jx9_value_string(pWorker, zCur, (int)(zArg-zCur));
5823 }
5824 /*
5825 * Check if we are dealing with multiple values.
5826 * If so, create an array to hold them, rather than a scalar variable.
5827 */
5828 while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){
5829 zArg++;
5830 }
5831 if( zArg < zArgEnd && zArg[0] != '-' ){
5832 jx9_value *pOptArg; /* Array of option arguments */
5833 pOptArg = jx9_context_new_array(pCtx);
5834 if( pOptArg == 0 ){
5835 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory");
5836 }else{
5837 /* Insert the first value */
5838 jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */
5839 for(;;){
5840 if( zArg >= zArgEnd || zArg[0] == '-' ){
5841 /* No more value */
5842 break;
5843 }
5844 /* Delimit the value */
5845 zCur = zArg;
5846 if( zArg < zArgEnd && zArg[0] == '\\' ){
5847 zArg++;
5848 zCur = zArg;
5849 }
5850 while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){
5851 zArg++;
5852 }
5853 /* Reset the string cursor */
5854 jx9_value_reset_string_cursor(pWorker);
5855 /* Save the value */
5856 jx9_value_string(pWorker, zCur, (int)(zArg-zCur));
5857 /* Insert */
5858 jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */
5859 /* Jump trailing white spaces */
5860 while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){
5861 zArg++;
5862 }
5863 }
5864 /* Insert the option arg array */
5865 jx9_array_add_strkey_elem(pArray, (const char *)zName, pOptArg); /* Will make it's own copy */
5866 /* Safely release */
5867 jx9_context_release_value(pCtx, pOptArg);
5868 }
5869 }else{
5870 /* Single value */
5871 jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */
5872 }
5873 }
5874}
5875/*
5876 * array getopt(string $options[, array $longopts ])
5877 * Gets options from the command line argument list.
5878 * Parameters
5879 * $options
5880 * Each character in this string will be used as option characters
5881 * and matched against options passed to the script starting with
5882 * a single hyphen (-). For example, an option string "x" recognizes
5883 * an option -x. Only a-z, A-Z and 0-9 are allowed.
5884 * $longopts
5885 * An array of options. Each element in this array will be used as option
5886 * strings and matched against options passed to the script starting with
5887 * two hyphens (--). For example, an longopts element "opt" recognizes an
5888 * option --opt.
5889 * Return
5890 * This function will return an array of option / argument pairs or FALSE
5891 * on failure.
5892 */
5893static int vm_builtin_getopt(jx9_context *pCtx, int nArg, jx9_value **apArg)
5894{
5895 const char *zIn, *zEnd, *zArg, *zArgIn, *zArgEnd;
5896 struct getopt_long_opt sLong;
5897 jx9_value *pArray, *pWorker;
5898 SyBlob *pArg;
5899 int nByte;
5900 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
5901 /* Missing/Invalid arguments, return FALSE */
5902 jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Missing/Invalid option arguments");
5903 jx9_result_bool(pCtx, 0);
5904 return JX9_OK;
5905 }
5906 /* Extract option arguments */
5907 zIn = jx9_value_to_string(apArg[0], &nByte);
5908 zEnd = &zIn[nByte];
5909 /* Point to the string representation of the $argv[] array */
5910 pArg = &pCtx->pVm->sArgv;
5911 /* Create a new empty array and a worker variable */
5912 pArray = jx9_context_new_array(pCtx);
5913 pWorker = jx9_context_new_scalar(pCtx);
5914 if( pArray == 0 || pWorker == 0 ){
5915 jx9_context_throw_error(pCtx,JX9_CTX_ERR, "JX9 is running out of memory");
5916 jx9_result_bool(pCtx, 0);
5917 return JX9_OK;
5918 }
5919 if( SyBlobLength(pArg) < 1 ){
5920 /* Empty command line, return the empty array*/
5921 jx9_result_value(pCtx, pArray);
5922 /* Everything will be released automatically when we return
5923 * from this function.
5924 */
5925 return JX9_OK;
5926 }
5927 zArgIn = (const char *)SyBlobData(pArg);
5928 zArgEnd = &zArgIn[SyBlobLength(pArg)];
5929 /* Fill the long option structure */
5930 sLong.pArray = pArray;
5931 sLong.pWorker = pWorker;
5932 sLong.zArgIn = zArgIn;
5933 sLong.zArgEnd = zArgEnd;
5934 sLong.pCtx = pCtx;
5935 /* Start processing */
5936 while( zIn < zEnd ){
5937 int c = zIn[0];
5938 int need_val = 0;
5939 /* Advance the stream cursor */
5940 zIn++;
5941 /* Ignore non-alphanum characters */
5942 if( !SyisAlphaNum(c) ){
5943 continue;
5944 }
5945 if( zIn < zEnd && zIn[0] == ':' ){
5946 zIn++;
5947 need_val = 1;
5948 if( zIn < zEnd && zIn[0] == ':' ){
5949 zIn++;
5950 }
5951 }
5952 /* Find option */
5953 zArg = VmFindShortOpt(c, zArgIn, zArgEnd);
5954 if( zArg == 0 ){
5955 /* No such option */
5956 continue;
5957 }
5958 /* Extract option argument value */
5959 VmExtractOptArgValue(pArray, pWorker, zArg, zArgEnd, need_val, pCtx, (const char *)&c);
5960 }
5961 if( nArg > 1 && jx9_value_is_json_array(apArg[1]) && jx9_array_count(apArg[1]) > 0 ){
5962 /* Process long options */
5963 jx9_array_walk(apArg[1], VmProcessLongOpt, &sLong);
5964 }
5965 /* Return the option array */
5966 jx9_result_value(pCtx, pArray);
5967 /*
5968 * Don't worry about freeing memory, everything will be released
5969 * automatically as soon we return from this foreign function.
5970 */
5971 return JX9_OK;
5972}
5973/*
5974 * Array walker callback used for processing long options values.
5975 */
5976static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData)
5977{
5978 struct getopt_long_opt *pOpt = (struct getopt_long_opt *)pUserData;
5979 const char *zArg, *zOpt, *zEnd;
5980 int need_value = 0;
5981 int nByte;
5982 /* Value must be of type string */
5983 if( !jx9_value_is_string(pValue) ){
5984 /* Simply ignore */
5985 return JX9_OK;
5986 }
5987 zOpt = jx9_value_to_string(pValue, &nByte);
5988 if( nByte < 1 ){
5989 /* Empty string, ignore */
5990 return JX9_OK;
5991 }
5992 zEnd = &zOpt[nByte - 1];
5993 if( zEnd[0] == ':' ){
5994 char *zTerm;
5995 /* Try to extract a value */
5996 need_value = 1;
5997 while( zEnd >= zOpt && zEnd[0] == ':' ){
5998 zEnd--;
5999 }
6000 if( zOpt >= zEnd ){
6001 /* Empty string, ignore */
6002 SXUNUSED(pKey);
6003 return JX9_OK;
6004 }
6005 zEnd++;
6006 zTerm = (char *)zEnd;
6007 zTerm[0] = 0;
6008 }else{
6009 zEnd = &zOpt[nByte];
6010 }
6011 /* Find the option */
6012 zArg = VmFindLongOpt(zOpt, (int)(zEnd-zOpt), pOpt->zArgIn, pOpt->zArgEnd);
6013 if( zArg == 0 ){
6014 /* No such option, return immediately */
6015 return JX9_OK;
6016 }
6017 /* Try to extract a value */
6018 VmExtractOptArgValue(pOpt->pArray, pOpt->pWorker, zArg, pOpt->zArgEnd, need_value, pOpt->pCtx, zOpt);
6019 return JX9_OK;
6020}
6021/*
6022 * int utf8_encode(string $input)
6023 * UTF-8 encoding.
6024 * This function encodes the string data to UTF-8, and returns the encoded version.
6025 * UTF-8 is a standard mechanism used by Unicode for encoding wide character values
6026 * into a byte stream. UTF-8 is transparent to plain ASCII characters, is self-synchronized
6027 * (meaning it is possible for a program to figure out where in the bytestream characters start)
6028 * and can be used with normal string comparison functions for sorting and such.
6029 * Notes on UTF-8 (According to SQLite3 authors):
6030 * Byte-0 Byte-1 Byte-2 Byte-3 Value
6031 * 0xxxxxxx 00000000 00000000 0xxxxxxx
6032 * 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx
6033 * 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx
6034 * 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx
6035 * Parameters
6036 * $input
6037 * String to encode or NULL on failure.
6038 * Return
6039 * An UTF-8 encoded string.
6040 */
6041static int vm_builtin_utf8_encode(jx9_context *pCtx, int nArg, jx9_value **apArg)
6042{
6043 const unsigned char *zIn, *zEnd;
6044 int nByte, c, e;
6045 if( nArg < 1 ){
6046 /* Missing arguments, return null */
6047 jx9_result_null(pCtx);
6048 return JX9_OK;
6049 }
6050 /* Extract the target string */
6051 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte);
6052 if( nByte < 1 ){
6053 /* Empty string, return null */
6054 jx9_result_null(pCtx);
6055 return JX9_OK;
6056 }
6057 zEnd = &zIn[nByte];
6058 /* Start the encoding process */
6059 for(;;){
6060 if( zIn >= zEnd ){
6061 /* End of input */
6062 break;
6063 }
6064 c = zIn[0];
6065 /* Advance the stream cursor */
6066 zIn++;
6067 /* Encode */
6068 if( c<0x00080 ){
6069 e = (c&0xFF);
6070 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6071 }else if( c<0x00800 ){
6072 e = 0xC0 + ((c>>6)&0x1F);
6073 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6074 e = 0x80 + (c & 0x3F);
6075 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6076 }else if( c<0x10000 ){
6077 e = 0xE0 + ((c>>12)&0x0F);
6078 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6079 e = 0x80 + ((c>>6) & 0x3F);
6080 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6081 e = 0x80 + (c & 0x3F);
6082 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6083 }else{
6084 e = 0xF0 + ((c>>18) & 0x07);
6085 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6086 e = 0x80 + ((c>>12) & 0x3F);
6087 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6088 e = 0x80 + ((c>>6) & 0x3F);
6089 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6090 e = 0x80 + (c & 0x3F);
6091 jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char));
6092 }
6093 }
6094 /* All done */
6095 return JX9_OK;
6096}
6097/*
6098 * UTF-8 decoding routine extracted from the sqlite3 source tree.
6099 * Original author: D. Richard Hipp (http://www.sqlite.org)
6100 * Status: Public Domain
6101 */
6102/*
6103** This lookup table is used to help decode the first byte of
6104** a multi-byte UTF8 character.
6105*/
6106static const unsigned char UtfTrans1[] = {
6107 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
6108 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
6109 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
6110 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
6111 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
6112 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
6113 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
6114 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00,
6115};
6116/*
6117** Translate a single UTF-8 character. Return the unicode value.
6118**
6119** During translation, assume that the byte that zTerm points
6120** is a 0x00.
6121**
6122** Write a pointer to the next unread byte back into *pzNext.
6123**
6124** Notes On Invalid UTF-8:
6125**
6126** * This routine never allows a 7-bit character (0x00 through 0x7f) to
6127** be encoded as a multi-byte character. Any multi-byte character that
6128** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd.
6129**
6130** * This routine never allows a UTF16 surrogate value to be encoded.
6131** If a multi-byte character attempts to encode a value between
6132** 0xd800 and 0xe000 then it is rendered as 0xfffd.
6133**
6134** * Bytes in the range of 0x80 through 0xbf which occur as the first
6135** byte of a character are interpreted as single-byte characters
6136** and rendered as themselves even though they are technically
6137** invalid characters.
6138**
6139** * This routine accepts an infinite number of different UTF8 encodings
6140** for unicode values 0x80 and greater. It do not change over-length
6141** encodings to 0xfffd as some systems recommend.
6142*/
6143#define READ_UTF8(zIn, zTerm, c) \
6144 c = *(zIn++); \
6145 if( c>=0xc0 ){ \
6146 c = UtfTrans1[c-0xc0]; \
6147 while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \
6148 c = (c<<6) + (0x3f & *(zIn++)); \
6149 } \
6150 if( c<0x80 \
6151 || (c&0xFFFFF800)==0xD800 \
6152 || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \
6153 }
6154JX9_PRIVATE int jx9Utf8Read(
6155 const unsigned char *z, /* First byte of UTF-8 character */
6156 const unsigned char *zTerm, /* Pretend this byte is 0x00 */
6157 const unsigned char **pzNext /* Write first byte past UTF-8 char here */
6158){
6159 int c;
6160 READ_UTF8(z, zTerm, c);
6161 *pzNext = z;
6162 return c;
6163}
6164/*
6165 * string utf8_decode(string $data)
6166 * This function decodes data, assumed to be UTF-8 encoded, to unicode.
6167 * Parameters
6168 * data
6169 * An UTF-8 encoded string.
6170 * Return
6171 * Unicode decoded string or NULL on failure.
6172 */
6173static int vm_builtin_utf8_decode(jx9_context *pCtx, int nArg, jx9_value **apArg)
6174{
6175 const unsigned char *zIn, *zEnd;
6176 int nByte, c;
6177 if( nArg < 1 ){
6178 /* Missing arguments, return null */
6179 jx9_result_null(pCtx);
6180 return JX9_OK;
6181 }
6182 /* Extract the target string */
6183 zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte);
6184 if( nByte < 1 ){
6185 /* Empty string, return null */
6186 jx9_result_null(pCtx);
6187 return JX9_OK;
6188 }
6189 zEnd = &zIn[nByte];
6190 /* Start the decoding process */
6191 while( zIn < zEnd ){
6192 c = jx9Utf8Read(zIn, zEnd, &zIn);
6193 if( c == 0x0 ){
6194 break;
6195 }
6196 jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char));
6197 }
6198 return JX9_OK;
6199}
6200/*
6201 * string json_encode(mixed $value)
6202 * Returns a string containing the JSON representation of value.
6203 * Parameters
6204 * $value
6205 * The value being encoded. Can be any type except a resource.
6206 * Return
6207 * Returns a JSON encoded string on success. FALSE otherwise
6208 */
6209static int vm_builtin_json_encode(jx9_context *pCtx,int nArg,jx9_value **apArg)
6210{
6211 SyBlob sBlob;
6212 if( nArg < 1 ){
6213 /* Missing arguments, return FALSE */
6214 jx9_result_bool(pCtx, 0);
6215 return JX9_OK;
6216 }
6217 /* Init the working buffer */
6218 SyBlobInit(&sBlob,&pCtx->pVm->sAllocator);
6219 /* Perform the encoding operation */
6220 jx9JsonSerialize(apArg[0],&sBlob);
6221 /* Return the serialized value */
6222 jx9_result_string(pCtx,(const char *)SyBlobData(&sBlob),(int)SyBlobLength(&sBlob));
6223 /* Cleanup */
6224 SyBlobRelease(&sBlob);
6225 /* All done */
6226 return JX9_OK;
6227}
6228/*
6229 * mixed json_decode(string $json)
6230 * Takes a JSON encoded string and converts it into a JX9 variable.
6231 * Parameters
6232 * $json
6233 * The json string being decoded.
6234 * Return
6235 * The value encoded in json in appropriate JX9 type. Values true, false and null (case-insensitive)
6236 * are returned as TRUE, FALSE and NULL respectively. NULL is returned if the json cannot be decoded
6237 * or if the encoded data is deeper than the recursion limit.
6238 */
6239static int vm_builtin_json_decode(jx9_context *pCtx, int nArg, jx9_value **apArg)
6240{
6241 const char *zJSON;
6242 int nByte;
6243 if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){
6244 /* Missing/Invalid arguments, return NULL */
6245 jx9_result_null(pCtx);
6246 return JX9_OK;
6247 }
6248 /* Extract the JSON string */
6249 zJSON = jx9_value_to_string(apArg[0], &nByte);
6250 if( nByte < 1 ){
6251 /* Empty string, return NULL */
6252 jx9_result_null(pCtx);
6253 return JX9_OK;
6254 }
6255 /* Decode the raw JSON */
6256 jx9JsonDecode(pCtx,zJSON,nByte);
6257 return JX9_OK;
6258}
6259/* Table of built-in VM functions. */
6260static const jx9_builtin_func aVmFunc[] = {
6261 /* JSON Encoding/Decoding */
6262 { "json_encode", vm_builtin_json_encode },
6263 { "json_decode", vm_builtin_json_decode },
6264 /* Functions calls */
6265 { "func_num_args" , vm_builtin_func_num_args },
6266 { "func_get_arg" , vm_builtin_func_get_arg },
6267 { "func_get_args" , vm_builtin_func_get_args },
6268 { "function_exists", vm_builtin_func_exists },
6269 { "is_callable" , vm_builtin_is_callable },
6270 { "get_defined_functions", vm_builtin_get_defined_func },
6271 /* Constants management */
6272 { "defined", vm_builtin_defined },
6273 { "get_defined_constants", vm_builtin_get_defined_constants },
6274 /* Random numbers/strings generators */
6275 { "rand", vm_builtin_rand },
6276 { "rand_str", vm_builtin_rand_str },
6277 { "getrandmax", vm_builtin_getrandmax },
6278 /* Language constructs functions */
6279 { "print", vm_builtin_print },
6280 { "exit", vm_builtin_exit },
6281 { "die", vm_builtin_exit },
6282 /* Variable handling functions */
6283 { "gettype", vm_builtin_gettype },
6284 { "get_resource_type", vm_builtin_get_resource_type},
6285 /* Variable dumping */
6286 { "dump", vm_builtin_dump },
6287 /* Release info */
6288 {"jx9_version", vm_builtin_jx9_version },
6289 {"jx9_credits", vm_builtin_jx9_version },
6290 {"jx9_info", vm_builtin_jx9_version },
6291 {"jx9_copyright", vm_builtin_jx9_version },
6292 /* hashmap */
6293 {"extract", vm_builtin_extract },
6294 /* URL related function */
6295 {"parse_url", vm_builtin_parse_url },
6296 /* UTF-8 encoding/decoding */
6297 {"utf8_encode", vm_builtin_utf8_encode},
6298 {"utf8_decode", vm_builtin_utf8_decode},
6299 /* Command line processing */
6300 {"getopt", vm_builtin_getopt },
6301 /* Files/URI inclusion facility */
6302 { "include", vm_builtin_include },
6303 { "import", vm_builtin_import }
6304};
6305/*
6306 * Register the built-in VM functions defined above.
6307 */
6308static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm)
6309{
6310 sxi32 rc;
6311 sxu32 n;
6312 for( n = 0 ; n < SX_ARRAYSIZE(aVmFunc) ; ++n ){
6313 /* Note that these special functions have access
6314 * to the underlying virtual machine as their
6315 * private data.
6316 */
6317 rc = jx9_create_function(&(*pVm), aVmFunc[n].zName, aVmFunc[n].xFunc, &(*pVm));
6318 if( rc != SXRET_OK ){
6319 return rc;
6320 }
6321 }
6322 return SXRET_OK;
6323}
6324#ifndef JX9_DISABLE_BUILTIN_FUNC
6325/*
6326 * Extract the IO stream device associated with a given scheme.
6327 * Return a pointer to an instance of jx9_io_stream when the scheme
6328 * have an associated IO stream registered with it. NULL otherwise.
6329 * If no scheme:// is avalilable then the file:// scheme is assumed.
6330 * For more information on how to register IO stream devices, please
6331 * refer to the official documentation.
6332 */
6333JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(
6334 jx9_vm *pVm, /* Target VM */
6335 const char **pzDevice, /* Full path, URI, ... */
6336 int nByte /* *pzDevice length*/
6337 )
6338{
6339 const char *zIn, *zEnd, *zCur, *zNext;
6340 jx9_io_stream **apStream, *pStream;
6341 SyString sDev, sCur;
6342 sxu32 n, nEntry;
6343 int rc;
6344 /* Check if a scheme [i.e: file://, http://, zip://...] is available */
6345 zNext = zCur = zIn = *pzDevice;
6346 zEnd = &zIn[nByte];
6347 while( zIn < zEnd ){
6348 if( zIn < &zEnd[-3]/*://*/ && zIn[0] == ':' && zIn[1] == '/' && zIn[2] == '/' ){
6349 /* Got one */
6350 zNext = &zIn[sizeof("://")-1];
6351 break;
6352 }
6353 /* Advance the cursor */
6354 zIn++;
6355 }
6356 if( zIn >= zEnd ){
6357 /* No such scheme, return the default stream */
6358 return pVm->pDefStream;
6359 }
6360 SyStringInitFromBuf(&sDev, zCur, zIn-zCur);
6361 /* Remove leading and trailing white spaces */
6362 SyStringFullTrim(&sDev);
6363 /* Perform a linear lookup on the installed stream devices */
6364 apStream = (jx9_io_stream **)SySetBasePtr(&pVm->aIOstream);
6365 nEntry = SySetUsed(&pVm->aIOstream);
6366 for( n = 0 ; n < nEntry ; n++ ){
6367 pStream = apStream[n];
6368 SyStringInitFromBuf(&sCur, pStream->zName, SyStrlen(pStream->zName));
6369 /* Perfrom a case-insensitive comparison */
6370 rc = SyStringCmp(&sDev, &sCur, SyStrnicmp);
6371 if( rc == 0 ){
6372 /* Stream device found */
6373 *pzDevice = zNext;
6374 return pStream;
6375 }
6376 }
6377 /* No such stream, return NULL */
6378 return 0;
6379}
6380#endif /* JX9_DISABLE_BUILTIN_FUNC */
6381/*
6382 * Section:
6383 * HTTP/URI related routines.
6384 * Authors:
6385 * Symisc Systems, devel@symisc.net.
6386 * Copyright (C) Symisc Systems, http://jx9.symisc.net
6387 * Status:
6388 * Stable.
6389 */
6390 /*
6391 * URI Parser: Split an URI into components [i.e: Host, Path, Query, ...].
6392 * URI syntax: [method:/][/[user[:pwd]@]host[:port]/][document]
6393 * This almost, but not quite, RFC1738 URI syntax.
6394 * This routine is not a validator, it does not check for validity
6395 * nor decode URI parts, the only thing this routine does is splitting
6396 * the input to its fields.
6397 * Upper layer are responsible of decoding and validating URI parts.
6398 * On success, this function populate the "SyhttpUri" structure passed
6399 * as the first argument. Otherwise SXERR_* is returned when a malformed
6400 * input is encountered.
6401 */
6402 static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen)
6403 {
6404 const char *zEnd = &zUri[nLen];
6405 sxu8 bHostOnly = FALSE;
6406 sxu8 bIPv6 = FALSE ;
6407 const char *zCur;
6408 SyString *pComp;
6409 sxu32 nPos = 0;
6410 sxi32 rc;
6411 /* Zero the structure first */
6412 SyZero(pOut, sizeof(SyhttpUri));
6413 /* Remove leading and trailing white spaces */
6414 SyStringInitFromBuf(&pOut->sRaw, zUri, nLen);
6415 SyStringFullTrim(&pOut->sRaw);
6416 /* Find the first '/' separator */
6417 rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos);
6418 if( rc != SXRET_OK ){
6419 /* Assume a host name only */
6420 zCur = zEnd;
6421 bHostOnly = TRUE;
6422 goto ProcessHost;
6423 }
6424 zCur = &zUri[nPos];
6425 if( zUri != zCur && zCur[-1] == ':' ){
6426 /* Extract a scheme:
6427 * Not that we can get an invalid scheme here.
6428 * Fortunately the caller can discard any URI by comparing this scheme with its
6429 * registered schemes and will report the error as soon as his comparison function
6430 * fail.
6431 */
6432 pComp = &pOut->sScheme;
6433 SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri - 1));
6434 SyStringLeftTrim(pComp);
6435 }
6436 if( zCur[1] != '/' ){
6437 if( zCur == zUri || zCur[-1] == ':' ){
6438 /* No authority */
6439 goto PathSplit;
6440 }
6441 /* There is something here , we will assume its an authority
6442 * and someone has forgot the two prefix slashes "//",
6443 * sooner or later we will detect if we are dealing with a malicious
6444 * user or not, but now assume we are dealing with an authority
6445 * and let the caller handle all the validation process.
6446 */
6447 goto ProcessHost;
6448 }
6449 zUri = &zCur[2];
6450 zCur = zEnd;
6451 rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos);
6452 if( rc == SXRET_OK ){
6453 zCur = &zUri[nPos];
6454 }
6455 ProcessHost:
6456 /* Extract user information if present */
6457 rc = SyByteFind(zUri, (sxu32)(zCur - zUri), '@', &nPos);
6458 if( rc == SXRET_OK ){
6459 if( nPos > 0 ){
6460 sxu32 nPassOfft; /* Password offset */
6461 pComp = &pOut->sUser;
6462 SyStringInitFromBuf(pComp, zUri, nPos);
6463 /* Extract the password if available */
6464 rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPassOfft);
6465 if( rc == SXRET_OK && nPassOfft < nPos){
6466 pComp->nByte = nPassOfft;
6467 pComp = &pOut->sPass;
6468 pComp->zString = &zUri[nPassOfft+sizeof(char)];
6469 pComp->nByte = nPos - nPassOfft - 1;
6470 }
6471 /* Update the cursor */
6472 zUri = &zUri[nPos+1];
6473 }else{
6474 zUri++;
6475 }
6476 }
6477 pComp = &pOut->sHost;
6478 while( zUri < zCur && SyisSpace(zUri[0])){
6479 zUri++;
6480 }
6481 SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri));
6482 if( pComp->zString[0] == '[' ){
6483 /* An IPv6 Address: Make a simple naive test
6484 */
6485 zUri++; pComp->zString++; pComp->nByte = 0;
6486 while( ((unsigned char)zUri[0] < 0xc0 && SyisHex(zUri[0])) || zUri[0] == ':' ){
6487 zUri++; pComp->nByte++;
6488 }
6489 if( zUri[0] != ']' ){
6490 return SXERR_CORRUPT; /* Malformed IPv6 address */
6491 }
6492 zUri++;
6493 bIPv6 = TRUE;
6494 }
6495 /* Extract a port number if available */
6496 rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPos);
6497 if( rc == SXRET_OK ){
6498 if( bIPv6 == FALSE ){
6499 pComp->nByte = (sxu32)(&zUri[nPos] - zUri);
6500 }
6501 pComp = &pOut->sPort;
6502 SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zCur - &zUri[nPos+1]));
6503 }
6504 if( bHostOnly == TRUE ){
6505 return SXRET_OK;
6506 }
6507PathSplit:
6508 zUri = zCur;
6509 pComp = &pOut->sPath;
6510 SyStringInitFromBuf(pComp, zUri, (sxu32)(zEnd-zUri));
6511 if( pComp->nByte == 0 ){
6512 return SXRET_OK; /* Empty path */
6513 }
6514 if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '?', &nPos) ){
6515 pComp->nByte = nPos; /* Update path length */
6516 pComp = &pOut->sQuery;
6517 SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1]));
6518 }
6519 if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '#', &nPos) ){
6520 /* Update path or query length */
6521 if( pComp == &pOut->sPath ){
6522 pComp->nByte = nPos;
6523 }else{
6524 if( &zUri[nPos] < (char *)SyStringData(pComp) ){
6525 /* Malformed syntax : Query must be present before fragment */
6526 return SXERR_SYNTAX;
6527 }
6528 pComp->nByte -= (sxu32)(zEnd - &zUri[nPos]);
6529 }
6530 pComp = &pOut->sFragment;
6531 SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1]))
6532 }
6533 return SXRET_OK;
6534 }
6535 /*
6536 * Extract a single line from a raw HTTP request.
6537 * Return SXRET_OK on success, SXERR_EOF when end of input
6538 * and SXERR_MORE when more input is needed.
6539 */
6540static sxi32 VmGetNextLine(SyString *pCursor, SyString *pCurrent)
6541{
6542 const char *zIn;
6543 sxu32 nPos;
6544 /* Jump leading white spaces */
6545 SyStringLeftTrim(pCursor);
6546 if( pCursor->nByte < 1 ){
6547 SyStringInitFromBuf(pCurrent, 0, 0);
6548 return SXERR_EOF; /* End of input */
6549 }
6550 zIn = SyStringData(pCursor);
6551 if( SXRET_OK != SyByteListFind(pCursor->zString, pCursor->nByte, "\r\n", &nPos) ){
6552 /* Line not found, tell the caller to read more input from source */
6553 SyStringDupPtr(pCurrent, pCursor);
6554 return SXERR_MORE;
6555 }
6556 pCurrent->zString = zIn;
6557 pCurrent->nByte = nPos;
6558 /* advance the cursor so we can call this routine again */
6559 pCursor->zString = &zIn[nPos];
6560 pCursor->nByte -= nPos;
6561 return SXRET_OK;
6562 }
6563 /*
6564 * Split a single MIME header into a name value pair.
6565 * This function return SXRET_OK, SXERR_CONTINUE on success.
6566 * Otherwise SXERR_NEXT is returned when a malformed header
6567 * is encountered.
6568 * Note: This function handle also mult-line headers.
6569 */
6570 static sxi32 VmHttpProcessOneHeader(SyhttpHeader *pHdr, SyhttpHeader *pLast, const char *zLine, sxu32 nLen)
6571 {
6572 SyString *pName;
6573 sxu32 nPos;
6574 sxi32 rc;
6575 if( nLen < 1 ){
6576 return SXERR_NEXT;
6577 }
6578 /* Check for multi-line header */
6579 if( pLast && (zLine[-1] == ' ' || zLine[-1] == '\t') ){
6580 SyString *pTmp = &pLast->sValue;
6581 SyStringFullTrim(pTmp);
6582 if( pTmp->nByte == 0 ){
6583 SyStringInitFromBuf(pTmp, zLine, nLen);
6584 }else{
6585 /* Update header value length */
6586 pTmp->nByte = (sxu32)(&zLine[nLen] - pTmp->zString);
6587 }
6588 /* Simply tell the caller to reset its states and get another line */
6589 return SXERR_CONTINUE;
6590 }
6591 /* Split the header */
6592 pName = &pHdr->sName;
6593 rc = SyByteFind(zLine, nLen, ':', &nPos);
6594 if(rc != SXRET_OK ){
6595 return SXERR_NEXT; /* Malformed header;Check the next entry */
6596 }
6597 SyStringInitFromBuf(pName, zLine, nPos);
6598 SyStringFullTrim(pName);
6599 /* Extract a header value */
6600 SyStringInitFromBuf(&pHdr->sValue, &zLine[nPos + 1], nLen - nPos - 1);
6601 /* Remove leading and trailing whitespaces */
6602 SyStringFullTrim(&pHdr->sValue);
6603 return SXRET_OK;
6604 }
6605 /*
6606 * Extract all MIME headers associated with a HTTP request.
6607 * After processing the first line of a HTTP request, the following
6608 * routine is called in order to extract MIME headers.
6609 * This function return SXRET_OK on success, SXERR_MORE when it needs
6610 * more inputs.
6611 * Note: Any malformed header is simply discarded.
6612 */
6613 static sxi32 VmHttpExtractHeaders(SyString *pRequest, SySet *pOut)
6614 {
6615 SyhttpHeader *pLast = 0;
6616 SyString sCurrent;
6617 SyhttpHeader sHdr;
6618 sxu8 bEol;
6619 sxi32 rc;
6620 if( SySetUsed(pOut) > 0 ){
6621 pLast = (SyhttpHeader *)SySetAt(pOut, SySetUsed(pOut)-1);
6622 }
6623 bEol = FALSE;
6624 for(;;){
6625 SyZero(&sHdr, sizeof(SyhttpHeader));
6626 /* Extract a single line from the raw HTTP request */
6627 rc = VmGetNextLine(pRequest, &sCurrent);
6628 if(rc != SXRET_OK ){
6629 if( sCurrent.nByte < 1 ){
6630 break;
6631 }
6632 bEol = TRUE;
6633 }
6634 /* Process the header */
6635 if( SXRET_OK == VmHttpProcessOneHeader(&sHdr, pLast, sCurrent.zString, sCurrent.nByte)){
6636 if( SXRET_OK != SySetPut(pOut, (const void *)&sHdr) ){
6637 break;
6638 }
6639 /* Retrieve the last parsed header so we can handle multi-line header
6640 * in case we face one of them.
6641 */
6642 pLast = (SyhttpHeader *)SySetPeek(pOut);
6643 }
6644 if( bEol ){
6645 break;
6646 }
6647 } /* for(;;) */
6648 return SXRET_OK;
6649 }
6650 /*
6651 * Process the first line of a HTTP request.
6652 * This routine perform the following operations
6653 * 1) Extract the HTTP method.
6654 * 2) Split the request URI to it's fields [ie: host, path, query, ...].
6655 * 3) Extract the HTTP protocol version.
6656 */
6657 static sxi32 VmHttpProcessFirstLine(
6658 SyString *pRequest, /* Raw HTTP request */
6659 sxi32 *pMethod, /* OUT: HTTP method */
6660 SyhttpUri *pUri, /* OUT: Parse of the URI */
6661 sxi32 *pProto /* OUT: HTTP protocol */
6662 )
6663 {
6664 static const char *azMethods[] = { "get", "post", "head", "put"};
6665 static const sxi32 aMethods[] = { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PUT};
6666 const char *zIn, *zEnd, *zPtr;
6667 SyString sLine;
6668 sxu32 nLen;
6669 sxi32 rc;
6670 /* Extract the first line and update the pointer */
6671 rc = VmGetNextLine(pRequest, &sLine);
6672 if( rc != SXRET_OK ){
6673 return rc;
6674 }
6675 if ( sLine.nByte < 1 ){
6676 /* Empty HTTP request */
6677 return SXERR_EMPTY;
6678 }
6679 /* Delimit the line and ignore trailing and leading white spaces */
6680 zIn = sLine.zString;
6681 zEnd = &zIn[sLine.nByte];
6682 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
6683 zIn++;
6684 }
6685 /* Extract the HTTP method */
6686 zPtr = zIn;
6687 while( zIn < zEnd && !SyisSpace(zIn[0]) ){
6688 zIn++;
6689 }
6690 *pMethod = HTTP_METHOD_OTHR;
6691 if( zIn > zPtr ){
6692 sxu32 i;
6693 nLen = (sxu32)(zIn-zPtr);
6694 for( i = 0 ; i < SX_ARRAYSIZE(azMethods) ; ++i ){
6695 if( SyStrnicmp(azMethods[i], zPtr, nLen) == 0 ){
6696 *pMethod = aMethods[i];
6697 break;
6698 }
6699 }
6700 }
6701 /* Jump trailing white spaces */
6702 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
6703 zIn++;
6704 }
6705 /* Extract the request URI */
6706 zPtr = zIn;
6707 while( zIn < zEnd && !SyisSpace(zIn[0]) ){
6708 zIn++;
6709 }
6710 if( zIn > zPtr ){
6711 nLen = (sxu32)(zIn-zPtr);
6712 /* Split raw URI to it's fields */
6713 VmHttpSplitURI(pUri, zPtr, nLen);
6714 }
6715 /* Jump trailing white spaces */
6716 while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){
6717 zIn++;
6718 }
6719 /* Extract the HTTP version */
6720 zPtr = zIn;
6721 while( zIn < zEnd && !SyisSpace(zIn[0]) ){
6722 zIn++;
6723 }
6724 *pProto = HTTP_PROTO_11; /* HTTP/1.1 */
6725 rc = 1;
6726 if( zIn > zPtr ){
6727 rc = SyStrnicmp(zPtr, "http/1.0", (sxu32)(zIn-zPtr));
6728 }
6729 if( !rc ){
6730 *pProto = HTTP_PROTO_10; /* HTTP/1.0 */
6731 }
6732 return SXRET_OK;
6733 }
6734 /*
6735 * Tokenize, decode and split a raw query encoded as: "x-www-form-urlencoded"
6736 * into a name value pair.
6737 * Note that this encoding is implicit in GET based requests.
6738 * After the tokenization process, register the decoded queries
6739 * in the $_GET/$_POST/$_REQUEST superglobals arrays.
6740 */
6741 static sxi32 VmHttpSplitEncodedQuery(
6742 jx9_vm *pVm, /* Target VM */
6743 SyString *pQuery, /* Raw query to decode */
6744 SyBlob *pWorker, /* Working buffer */
6745 int is_post /* TRUE if we are dealing with a POST request */
6746 )
6747 {
6748 const char *zEnd = &pQuery->zString[pQuery->nByte];
6749 const char *zIn = pQuery->zString;
6750 jx9_value *pGet, *pRequest;
6751 SyString sName, sValue;
6752 const char *zPtr;
6753 sxu32 nBlobOfft;
6754 /* Extract superglobals */
6755 if( is_post ){
6756 /* $_POST superglobal */
6757 pGet = VmExtractSuper(&(*pVm), "_POST", sizeof("_POST")-1);
6758 }else{
6759 /* $_GET superglobal */
6760 pGet = VmExtractSuper(&(*pVm), "_GET", sizeof("_GET")-1);
6761 }
6762 pRequest = VmExtractSuper(&(*pVm), "_REQUEST", sizeof("_REQUEST")-1);
6763 /* Split up the raw query */
6764 for(;;){
6765 /* Jump leading white spaces */
6766 while(zIn < zEnd && SyisSpace(zIn[0]) ){
6767 zIn++;
6768 }
6769 if( zIn >= zEnd ){
6770 break;
6771 }
6772 zPtr = zIn;
6773 while( zPtr < zEnd && zPtr[0] != '=' && zPtr[0] != '&' && zPtr[0] != ';' ){
6774 zPtr++;
6775 }
6776 /* Reset the working buffer */
6777 SyBlobReset(pWorker);
6778 /* Decode the entry */
6779 SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE);
6780 /* Save the entry */
6781 sName.nByte = SyBlobLength(pWorker);
6782 sValue.zString = 0;
6783 sValue.nByte = 0;
6784 if( zPtr < zEnd && zPtr[0] == '=' ){
6785 zPtr++;
6786 zIn = zPtr;
6787 /* Store field value */
6788 while( zPtr < zEnd && zPtr[0] != '&' && zPtr[0] != ';' ){
6789 zPtr++;
6790 }
6791 if( zPtr > zIn ){
6792 /* Decode the value */
6793 nBlobOfft = SyBlobLength(pWorker);
6794 SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE);
6795 sValue.zString = (const char *)SyBlobDataAt(pWorker, nBlobOfft);
6796 sValue.nByte = SyBlobLength(pWorker) - nBlobOfft;
6797
6798 }
6799 /* Synchronize pointers */
6800 zIn = zPtr;
6801 }
6802 sName.zString = (const char *)SyBlobData(pWorker);
6803 /* Install the decoded query in the $_GET/$_REQUEST array */
6804 if( pGet && (pGet->iFlags & MEMOBJ_HASHMAP) ){
6805 VmHashmapInsert((jx9_hashmap *)pGet->x.pOther,
6806 sName.zString, (int)sName.nByte,
6807 sValue.zString, (int)sValue.nByte
6808 );
6809 }
6810 if( pRequest && (pRequest->iFlags & MEMOBJ_HASHMAP) ){
6811 VmHashmapInsert((jx9_hashmap *)pRequest->x.pOther,
6812 sName.zString, (int)sName.nByte,
6813 sValue.zString, (int)sValue.nByte
6814 );
6815 }
6816 /* Advance the pointer */
6817 zIn = &zPtr[1];
6818 }
6819 /* All done*/
6820 return SXRET_OK;
6821 }
6822 /*
6823 * Extract MIME header value from the given set.
6824 * Return header value on success. NULL otherwise.
6825 */
6826 static SyString * VmHttpExtractHeaderValue(SySet *pSet, const char *zMime, sxu32 nByte)
6827 {
6828 SyhttpHeader *aMime, *pMime;
6829 SyString sMime;
6830 sxu32 n;
6831 SyStringInitFromBuf(&sMime, zMime, nByte);
6832 /* Point to the MIME entries */
6833 aMime = (SyhttpHeader *)SySetBasePtr(pSet);
6834 /* Perform the lookup */
6835 for( n = 0 ; n < SySetUsed(pSet) ; ++n ){
6836 pMime = &aMime[n];
6837 if( SyStringCmp(&sMime, &pMime->sName, SyStrnicmp) == 0 ){
6838 /* Header found, return it's associated value */
6839 return &pMime->sValue;
6840 }
6841 }
6842 /* No such MIME header */
6843 return 0;
6844 }
6845 /*
6846 * Tokenize and decode a raw "Cookie:" MIME header into a name value pair
6847 * and insert it's fields [i.e name, value] in the $_COOKIE superglobal.
6848 */
6849 static sxi32 VmHttpPorcessCookie(jx9_vm *pVm, SyBlob *pWorker, const char *zIn, sxu32 nByte)
6850 {
6851 const char *zPtr, *zDelimiter, *zEnd = &zIn[nByte];
6852 SyString sName, sValue;
6853 jx9_value *pCookie;
6854 sxu32 nOfft;
6855 /* Make sure the $_COOKIE superglobal is available */
6856 pCookie = VmExtractSuper(&(*pVm), "_COOKIE", sizeof("_COOKIE")-1);
6857 if( pCookie == 0 || (pCookie->iFlags & MEMOBJ_HASHMAP) == 0 ){
6858 /* $_COOKIE superglobal not available */
6859 return SXERR_NOTFOUND;
6860 }
6861 for(;;){
6862 /* Jump leading white spaces */
6863 while( zIn < zEnd && SyisSpace(zIn[0]) ){
6864 zIn++;
6865 }
6866 if( zIn >= zEnd ){
6867 break;
6868 }
6869 /* Reset the working buffer */
6870 SyBlobReset(pWorker);
6871 zDelimiter = zIn;
6872 /* Delimit the name[=value]; pair */
6873 while( zDelimiter < zEnd && zDelimiter[0] != ';' ){
6874 zDelimiter++;
6875 }
6876 zPtr = zIn;
6877 while( zPtr < zDelimiter && zPtr[0] != '=' ){
6878 zPtr++;
6879 }
6880 /* Decode the cookie */
6881 SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE);
6882 sName.nByte = SyBlobLength(pWorker);
6883 zPtr++;
6884 sValue.zString = 0;
6885 sValue.nByte = 0;
6886 if( zPtr < zDelimiter ){
6887 /* Got a Cookie value */
6888 nOfft = SyBlobLength(pWorker);
6889 SyUriDecode(zPtr, (sxu32)(zDelimiter-zPtr), jx9VmBlobConsumer, pWorker, TRUE);
6890 SyStringInitFromBuf(&sValue, SyBlobDataAt(pWorker, nOfft), SyBlobLength(pWorker)-nOfft);
6891 }
6892 /* Synchronize pointers */
6893 zIn = &zDelimiter[1];
6894 /* Perform the insertion */
6895 sName.zString = (const char *)SyBlobData(pWorker);
6896 VmHashmapInsert((jx9_hashmap *)pCookie->x.pOther,
6897 sName.zString, (int)sName.nByte,
6898 sValue.zString, (int)sValue.nByte
6899 );
6900 }
6901 return SXRET_OK;
6902 }
6903 /*
6904 * Process a full HTTP request and populate the appropriate arrays
6905 * such as $_SERVER, $_GET, $_POST, $_COOKIE, $_REQUEST, ... with the information
6906 * extracted from the raw HTTP request. As an extension Symisc introduced
6907 * the $_HEADER array which hold a copy of the processed HTTP MIME headers
6908 * and their associated values. [i.e: $_HEADER['Server'], $_HEADER['User-Agent'], ...].
6909 * This function return SXRET_OK on success. Any other return value indicates
6910 * a malformed HTTP request.
6911 */
6912 static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte)
6913 {
6914 SyString *pName, *pValue, sRequest; /* Raw HTTP request */
6915 jx9_value *pHeaderArray; /* $_HEADER superglobal (Symisc eXtension to the JX9 specification)*/
6916 SyhttpHeader *pHeader; /* MIME header */
6917 SyhttpUri sUri; /* Parse of the raw URI*/
6918 SyBlob sWorker; /* General purpose working buffer */
6919 SySet sHeader; /* MIME headers set */
6920 sxi32 iMethod; /* HTTP method [i.e: GET, POST, HEAD...]*/
6921 sxi32 iVer; /* HTTP protocol version */
6922 sxi32 rc;
6923 SyStringInitFromBuf(&sRequest, zRequest, nByte);
6924 SySetInit(&sHeader, &pVm->sAllocator, sizeof(SyhttpHeader));
6925 SyBlobInit(&sWorker, &pVm->sAllocator);
6926 /* Ignore leading and trailing white spaces*/
6927 SyStringFullTrim(&sRequest);
6928 /* Process the first line */
6929 rc = VmHttpProcessFirstLine(&sRequest, &iMethod, &sUri, &iVer);
6930 if( rc != SXRET_OK ){
6931 return rc;
6932 }
6933 /* Process MIME headers */
6934 VmHttpExtractHeaders(&sRequest, &sHeader);
6935 /*
6936 * Setup $_SERVER environments
6937 */
6938 /* 'SERVER_PROTOCOL': Name and revision of the information protocol via which the page was requested */
6939 jx9_vm_config(pVm,
6940 JX9_VM_CONFIG_SERVER_ATTR,
6941 "SERVER_PROTOCOL",
6942 iVer == HTTP_PROTO_10 ? "HTTP/1.0" : "HTTP/1.1",
6943 sizeof("HTTP/1.1")-1
6944 );
6945 /* 'REQUEST_METHOD': Which request method was used to access the page */
6946 jx9_vm_config(pVm,
6947 JX9_VM_CONFIG_SERVER_ATTR,
6948 "REQUEST_METHOD",
6949 iMethod == HTTP_METHOD_GET ? "GET" :
6950 (iMethod == HTTP_METHOD_POST ? "POST":
6951 (iMethod == HTTP_METHOD_PUT ? "PUT" :
6952 (iMethod == HTTP_METHOD_HEAD ? "HEAD" : "OTHER"))),
6953 -1 /* Compute attribute length automatically */
6954 );
6955 if( SyStringLength(&sUri.sQuery) > 0 && iMethod == HTTP_METHOD_GET ){
6956 pValue = &sUri.sQuery;
6957 /* 'QUERY_STRING': The query string, if any, via which the page was accessed */
6958 jx9_vm_config(pVm,
6959 JX9_VM_CONFIG_SERVER_ATTR,
6960 "QUERY_STRING",
6961 pValue->zString,
6962 pValue->nByte
6963 );
6964 /* Decoded the raw query */
6965 VmHttpSplitEncodedQuery(&(*pVm), pValue, &sWorker, FALSE);
6966 }
6967 /* REQUEST_URI: The URI which was given in order to access this page; for instance, '/index.html' */
6968 pValue = &sUri.sRaw;
6969 jx9_vm_config(pVm,
6970 JX9_VM_CONFIG_SERVER_ATTR,
6971 "REQUEST_URI",
6972 pValue->zString,
6973 pValue->nByte
6974 );
6975 /*
6976 * 'PATH_INFO'
6977 * 'ORIG_PATH_INFO'
6978 * Contains any client-provided pathname information trailing the actual script filename but preceding
6979 * the query string, if available. For instance, if the current script was accessed via the URL
6980 * http://www.example.com/jx9/path_info.jx9/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain
6981 * /some/stuff.
6982 */
6983 pValue = &sUri.sPath;
6984 jx9_vm_config(pVm,
6985 JX9_VM_CONFIG_SERVER_ATTR,
6986 "PATH_INFO",
6987 pValue->zString,
6988 pValue->nByte
6989 );
6990 jx9_vm_config(pVm,
6991 JX9_VM_CONFIG_SERVER_ATTR,
6992 "ORIG_PATH_INFO",
6993 pValue->zString,
6994 pValue->nByte
6995 );
6996 /* 'HTTP_ACCEPT': Contents of the Accept: header from the current request, if there is one */
6997 pValue = VmHttpExtractHeaderValue(&sHeader, "Accept", sizeof("Accept")-1);
6998 if( pValue ){
6999 jx9_vm_config(pVm,
7000 JX9_VM_CONFIG_SERVER_ATTR,
7001 "HTTP_ACCEPT",
7002 pValue->zString,
7003 pValue->nByte
7004 );
7005 }
7006 /* 'HTTP_ACCEPT_CHARSET': Contents of the Accept-Charset: header from the current request, if there is one. */
7007 pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Charset", sizeof("Accept-Charset")-1);
7008 if( pValue ){
7009 jx9_vm_config(pVm,
7010 JX9_VM_CONFIG_SERVER_ATTR,
7011 "HTTP_ACCEPT_CHARSET",
7012 pValue->zString,
7013 pValue->nByte
7014 );
7015 }
7016 /* 'HTTP_ACCEPT_ENCODING': Contents of the Accept-Encoding: header from the current request, if there is one. */
7017 pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Encoding", sizeof("Accept-Encoding")-1);
7018 if( pValue ){
7019 jx9_vm_config(pVm,
7020 JX9_VM_CONFIG_SERVER_ATTR,
7021 "HTTP_ACCEPT_ENCODING",
7022 pValue->zString,
7023 pValue->nByte
7024 );
7025 }
7026 /* 'HTTP_ACCEPT_LANGUAGE': Contents of the Accept-Language: header from the current request, if there is one */
7027 pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Language", sizeof("Accept-Language")-1);
7028 if( pValue ){
7029 jx9_vm_config(pVm,
7030 JX9_VM_CONFIG_SERVER_ATTR,
7031 "HTTP_ACCEPT_LANGUAGE",
7032 pValue->zString,
7033 pValue->nByte
7034 );
7035 }
7036 /* 'HTTP_CONNECTION': Contents of the Connection: header from the current request, if there is one. */
7037 pValue = VmHttpExtractHeaderValue(&sHeader, "Connection", sizeof("Connection")-1);
7038 if( pValue ){
7039 jx9_vm_config(pVm,
7040 JX9_VM_CONFIG_SERVER_ATTR,
7041 "HTTP_CONNECTION",
7042 pValue->zString,
7043 pValue->nByte
7044 );
7045 }
7046 /* 'HTTP_HOST': Contents of the Host: header from the current request, if there is one. */
7047 pValue = VmHttpExtractHeaderValue(&sHeader, "Host", sizeof("Host")-1);
7048 if( pValue ){
7049 jx9_vm_config(pVm,
7050 JX9_VM_CONFIG_SERVER_ATTR,
7051 "HTTP_HOST",
7052 pValue->zString,
7053 pValue->nByte
7054 );
7055 }
7056 /* 'HTTP_REFERER': Contents of the Referer: header from the current request, if there is one. */
7057 pValue = VmHttpExtractHeaderValue(&sHeader, "Referer", sizeof("Referer")-1);
7058 if( pValue ){
7059 jx9_vm_config(pVm,
7060 JX9_VM_CONFIG_SERVER_ATTR,
7061 "HTTP_REFERER",
7062 pValue->zString,
7063 pValue->nByte
7064 );
7065 }
7066 /* 'HTTP_USER_AGENT': Contents of the Referer: header from the current request, if there is one. */
7067 pValue = VmHttpExtractHeaderValue(&sHeader, "User-Agent", sizeof("User-Agent")-1);
7068 if( pValue ){
7069 jx9_vm_config(pVm,
7070 JX9_VM_CONFIG_SERVER_ATTR,
7071 "HTTP_USER_AGENT",
7072 pValue->zString,
7073 pValue->nByte
7074 );
7075 }
7076 /* 'JX9_AUTH_DIGEST': When doing Digest HTTP authentication this variable is set to the 'Authorization'
7077 * header sent by the client (which you should then use to make the appropriate validation).
7078 */
7079 pValue = VmHttpExtractHeaderValue(&sHeader, "Authorization", sizeof("Authorization")-1);
7080 if( pValue ){
7081 jx9_vm_config(pVm,
7082 JX9_VM_CONFIG_SERVER_ATTR,
7083 "JX9_AUTH_DIGEST",
7084 pValue->zString,
7085 pValue->nByte
7086 );
7087 jx9_vm_config(pVm,
7088 JX9_VM_CONFIG_SERVER_ATTR,
7089 "JX9_AUTH",
7090 pValue->zString,
7091 pValue->nByte
7092 );
7093 }
7094 /* Install all clients HTTP headers in the $_HEADER superglobal */
7095 pHeaderArray = VmExtractSuper(&(*pVm), "_HEADER", sizeof("_HEADER")-1);
7096 /* Iterate throw the available MIME headers*/
7097 SySetResetCursor(&sHeader);
7098 pHeader = 0; /* stupid cc warning */
7099 while( SXRET_OK == SySetGetNextEntry(&sHeader, (void **)&pHeader) ){
7100 pName = &pHeader->sName;
7101 pValue = &pHeader->sValue;
7102 if( pHeaderArray && (pHeaderArray->iFlags & MEMOBJ_HASHMAP)){
7103 /* Insert the MIME header and it's associated value */
7104 VmHashmapInsert((jx9_hashmap *)pHeaderArray->x.pOther,
7105 pName->zString, (int)pName->nByte,
7106 pValue->zString, (int)pValue->nByte
7107 );
7108 }
7109 if( pName->nByte == sizeof("Cookie")-1 && SyStrnicmp(pName->zString, "Cookie", sizeof("Cookie")-1) == 0
7110 && pValue->nByte > 0){
7111 /* Process the name=value pair and insert them in the $_COOKIE superglobal array */
7112 VmHttpPorcessCookie(&(*pVm), &sWorker, pValue->zString, pValue->nByte);
7113 }
7114 }
7115 if( iMethod == HTTP_METHOD_POST ){
7116 /* Extract raw POST data */
7117 pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Type", sizeof("Content-Type") - 1);
7118 if( pValue && pValue->nByte >= sizeof("application/x-www-form-urlencoded") - 1 &&
7119 SyMemcmp("application/x-www-form-urlencoded", pValue->zString, pValue->nByte) == 0 ){
7120 /* Extract POST data length */
7121 pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Length", sizeof("Content-Length") - 1);
7122 if( pValue ){
7123 sxi32 iLen = 0; /* POST data length */
7124 SyStrToInt32(pValue->zString, pValue->nByte, (void *)&iLen, 0);
7125 if( iLen > 0 ){
7126 /* Remove leading and trailing white spaces */
7127 SyStringFullTrim(&sRequest);
7128 if( (int)sRequest.nByte > iLen ){
7129 sRequest.nByte = (sxu32)iLen;
7130 }
7131 /* Decode POST data now */
7132 VmHttpSplitEncodedQuery(&(*pVm), &sRequest, &sWorker, TRUE);
7133 }
7134 }
7135 }
7136 }
7137 /* All done, clean-up the mess left behind */
7138 SySetRelease(&sHeader);
7139 SyBlobRelease(&sWorker);
7140 return SXRET_OK;
7141 }
diff --git a/common/unqlite/lhash_kv.c b/common/unqlite/lhash_kv.c
new file mode 100644
index 0000000..4af5b3e
--- /dev/null
+++ b/common/unqlite/lhash_kv.c
@@ -0,0 +1,3082 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Copyright (C) 2014, Yuras Shumovich <shumovichy@gmail.com>
5 * Version 1.1.6
6 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
7 * please contact Symisc Systems via:
8 * legal@symisc.net
9 * licensing@symisc.net
10 * contact@symisc.net
11 * or visit:
12 * http://unqlite.org/licensing.html
13 */
14 /* $SymiscID: lhash_kv.c v1.7 Solaris 2013-01-14 12:56 stable <chm@symisc.net> $ */
15#ifndef UNQLITE_AMALGAMATION
16#include "unqliteInt.h"
17#endif
18/*
19 * This file implements disk based hashtable using the linear hashing algorithm.
20 * This implementation is the one decribed in the paper:
21 * LINEAR HASHING : A NEW TOOL FOR FILE AND TABLE ADDRESSING. Witold Litwin. I. N. Ft. I. A.. 78 150 Le Chesnay, France.
22 * Plus a smart extension called Virtual Bucket Table. (contact devel@symisc.net for additional information).
23 */
24/* Magic number identifying a valid storage image */
25#define L_HASH_MAGIC 0xFA782DCB
26/*
27 * Magic word to hash to identify a valid hash function.
28 */
29#define L_HASH_WORD "chm@symisc"
30/*
31 * Cell size on disk.
32 */
33#define L_HASH_CELL_SZ (4/*Hash*/+4/*Key*/+8/*Data*/+2/* Offset of the next cell */+8/*Overflow*/)
34/*
35 * Primary page (not overflow pages) header size on disk.
36 */
37#define L_HASH_PAGE_HDR_SZ (2/* Cell offset*/+2/* Free block offset*/+8/*Slave page number*/)
38/*
39 * The maximum amount of payload (in bytes) that can be stored locally for
40 * a database entry. If the entry contains more data than this, the
41 * extra goes onto overflow pages.
42*/
43#define L_HASH_MX_PAYLOAD(PageSize) (PageSize-(L_HASH_PAGE_HDR_SZ+L_HASH_CELL_SZ))
44/*
45 * Maxium free space on a single page.
46 */
47#define L_HASH_MX_FREE_SPACE(PageSize) (PageSize - (L_HASH_PAGE_HDR_SZ))
48/*
49** The maximum number of bytes of payload allowed on a single overflow page.
50*/
51#define L_HASH_OVERFLOW_SIZE(PageSize) (PageSize-8)
52/* Forward declaration */
53typedef struct lhash_kv_engine lhash_kv_engine;
54typedef struct lhpage lhpage;
55/*
56 * Each record in the database is identified either in-memory or in
57 * disk by an instance of the following structure.
58 */
59typedef struct lhcell lhcell;
60struct lhcell
61{
62 /* Disk-data (Big-Endian) */
63 sxu32 nHash; /* Hash of the key: 4 bytes */
64 sxu32 nKey; /* Key length: 4 bytes */
65 sxu64 nData; /* Data length: 8 bytes */
66 sxu16 iNext; /* Offset of the next cell: 2 bytes */
67 pgno iOvfl; /* Overflow page number if any: 8 bytes */
68 /* In-memory data only */
69 lhpage *pPage; /* Page this cell belongs */
70 sxu16 iStart; /* Offset of this cell */
71 pgno iDataPage; /* Data page number when overflow */
72 sxu16 iDataOfft; /* Offset of the data in iDataPage */
73 SyBlob sKey; /* Record key for fast lookup (Kept in-memory if < 256KB ) */
74 lhcell *pNext,*pPrev; /* Linked list of the loaded memory cells */
75 lhcell *pNextCol,*pPrevCol; /* Collison chain */
76};
77/*
78** Each database page has a header that is an instance of this
79** structure.
80*/
81typedef struct lhphdr lhphdr;
82struct lhphdr
83{
84 sxu16 iOfft; /* Offset of the first cell */
85 sxu16 iFree; /* Offset of the first free block*/
86 pgno iSlave; /* Slave page number */
87};
88/*
89 * Each loaded primary disk page is represented in-memory using
90 * an instance of the following structure.
91 */
92struct lhpage
93{
94 lhash_kv_engine *pHash; /* KV Storage engine that own this page */
95 unqlite_page *pRaw; /* Raw page contents */
96 lhphdr sHdr; /* Processed page header */
97 lhcell **apCell; /* Cell buckets */
98 lhcell *pList,*pFirst; /* Linked list of cells */
99 sxu32 nCell; /* Total number of cells */
100 sxu32 nCellSize; /* apCell[] size */
101 lhpage *pMaster; /* Master page in case we are dealing with a slave page */
102 lhpage *pSlave; /* List of slave pages */
103 lhpage *pNextSlave; /* Next slave page on the list */
104 sxi32 iSlave; /* Total number of slave pages */
105 sxu16 nFree; /* Amount of free space available in the page */
106};
107/*
108 * A Bucket map record which is used to map logical bucket number to real
109 * bucket number is represented by an instance of the following structure.
110 */
111typedef struct lhash_bmap_rec lhash_bmap_rec;
112struct lhash_bmap_rec
113{
114 pgno iLogic; /* Logical bucket number */
115 pgno iReal; /* Real bucket number */
116 lhash_bmap_rec *pNext,*pPrev; /* Link to other bucket map */
117 lhash_bmap_rec *pNextCol,*pPrevCol; /* Collision links */
118};
119typedef struct lhash_bmap_page lhash_bmap_page;
120struct lhash_bmap_page
121{
122 pgno iNum; /* Page number where this entry is stored */
123 sxu16 iPtr; /* Offset to start reading/writing from */
124 sxu32 nRec; /* Total number of records in this page */
125 pgno iNext; /* Next map page */
126};
127/*
128 * An in memory linear hash implemenation is represented by in an isntance
129 * of the following structure.
130 */
131struct lhash_kv_engine
132{
133 const unqlite_kv_io *pIo; /* IO methods: Must be first */
134 /* Private fields */
135 SyMemBackend sAllocator; /* Private memory backend */
136 ProcHash xHash; /* Default hash function */
137 ProcCmp xCmp; /* Default comparison function */
138 unqlite_page *pHeader; /* Page one to identify a valid implementation */
139 lhash_bmap_rec **apMap; /* Buckets map records */
140 sxu32 nBuckRec; /* Total number of bucket map records */
141 sxu32 nBuckSize; /* apMap[] size */
142 lhash_bmap_rec *pList; /* List of bucket map records */
143 lhash_bmap_rec *pFirst; /* First record*/
144 lhash_bmap_page sPageMap; /* Primary bucket map */
145 int iPageSize; /* Page size */
146 pgno nFreeList; /* List of free pages */
147 pgno split_bucket; /* Current split bucket: MUST BE A POWER OF TWO */
148 pgno max_split_bucket; /* Maximum split bucket: MUST BE A POWER OF TWO */
149 pgno nmax_split_nucket; /* Next maximum split bucket (1 << nMsb): In-memory only */
150 sxu32 nMagic; /* Magic number to identify a valid linear hash disk database */
151};
152/*
153 * Given a logical bucket number, return the record associated with it.
154 */
155static lhash_bmap_rec * lhMapFindBucket(lhash_kv_engine *pEngine,pgno iLogic)
156{
157 lhash_bmap_rec *pRec;
158 if( pEngine->nBuckRec < 1 ){
159 /* Don't bother */
160 return 0;
161 }
162 pRec = pEngine->apMap[iLogic & (pEngine->nBuckSize - 1)];
163 for(;;){
164 if( pRec == 0 ){
165 break;
166 }
167 if( pRec->iLogic == iLogic ){
168 return pRec;
169 }
170 /* Point to the next entry */
171 pRec = pRec->pNextCol;
172 }
173 /* No such record */
174 return 0;
175}
176/*
177 * Install a new bucket map record.
178 */
179static int lhMapInstallBucket(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal)
180{
181 lhash_bmap_rec *pRec;
182 sxu32 iBucket;
183 /* Allocate a new instance */
184 pRec = (lhash_bmap_rec *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhash_bmap_rec));
185 if( pRec == 0 ){
186 return UNQLITE_NOMEM;
187 }
188 /* Zero the structure */
189 SyZero(pRec,sizeof(lhash_bmap_rec));
190 /* Fill in the structure */
191 pRec->iLogic = iLogic;
192 pRec->iReal = iReal;
193 iBucket = iLogic & (pEngine->nBuckSize - 1);
194 pRec->pNextCol = pEngine->apMap[iBucket];
195 if( pEngine->apMap[iBucket] ){
196 pEngine->apMap[iBucket]->pPrevCol = pRec;
197 }
198 pEngine->apMap[iBucket] = pRec;
199 /* Link */
200 if( pEngine->pFirst == 0 ){
201 pEngine->pFirst = pEngine->pList = pRec;
202 }else{
203 MACRO_LD_PUSH(pEngine->pList,pRec);
204 }
205 pEngine->nBuckRec++;
206 if( (pEngine->nBuckRec >= pEngine->nBuckSize * 3) && pEngine->nBuckRec < 100000 ){
207 /* Allocate a new larger table */
208 sxu32 nNewSize = pEngine->nBuckSize << 1;
209 lhash_bmap_rec *pEntry;
210 lhash_bmap_rec **apNew;
211 sxu32 n;
212
213 apNew = (lhash_bmap_rec **)SyMemBackendAlloc(&pEngine->sAllocator, nNewSize * sizeof(lhash_bmap_rec *));
214 if( apNew ){
215 /* Zero the new table */
216 SyZero((void *)apNew, nNewSize * sizeof(lhash_bmap_rec *));
217 /* Rehash all entries */
218 n = 0;
219 pEntry = pEngine->pList;
220 for(;;){
221 /* Loop one */
222 if( n >= pEngine->nBuckRec ){
223 break;
224 }
225 pEntry->pNextCol = pEntry->pPrevCol = 0;
226 /* Install in the new bucket */
227 iBucket = pEntry->iLogic & (nNewSize - 1);
228 pEntry->pNextCol = apNew[iBucket];
229 if( apNew[iBucket] ){
230 apNew[iBucket]->pPrevCol = pEntry;
231 }
232 apNew[iBucket] = pEntry;
233 /* Point to the next entry */
234 pEntry = pEntry->pNext;
235 n++;
236 }
237 /* Release the old table and reflect the change */
238 SyMemBackendFree(&pEngine->sAllocator,(void *)pEngine->apMap);
239 pEngine->apMap = apNew;
240 pEngine->nBuckSize = nNewSize;
241 }
242 }
243 return UNQLITE_OK;
244}
245/*
246 * Process a raw bucket map record.
247 */
248static int lhMapLoadPage(lhash_kv_engine *pEngine,lhash_bmap_page *pMap,const unsigned char *zRaw)
249{
250 const unsigned char *zEnd = &zRaw[pEngine->iPageSize];
251 const unsigned char *zPtr = zRaw;
252 pgno iLogic,iReal;
253 sxu32 n;
254 int rc;
255 if( pMap->iPtr == 0 ){
256 /* Read the map header */
257 SyBigEndianUnpack64(zRaw,&pMap->iNext);
258 zRaw += 8;
259 SyBigEndianUnpack32(zRaw,&pMap->nRec);
260 zRaw += 4;
261 }else{
262 /* Mostly page one of the database */
263 zRaw += pMap->iPtr;
264 }
265 /* Start processing */
266 for( n = 0; n < pMap->nRec ; ++n ){
267 if( zRaw >= zEnd ){
268 break;
269 }
270 /* Extract the logical and real bucket number */
271 SyBigEndianUnpack64(zRaw,&iLogic);
272 zRaw += 8;
273 SyBigEndianUnpack64(zRaw,&iReal);
274 zRaw += 8;
275 /* Install the record in the map */
276 rc = lhMapInstallBucket(pEngine,iLogic,iReal);
277 if( rc != UNQLITE_OK ){
278 return rc;
279 }
280 }
281 pMap->iPtr = (sxu16)(zRaw-zPtr);
282 /* All done */
283 return UNQLITE_OK;
284}
285/*
286 * Allocate a new cell instance.
287 */
288static lhcell * lhNewCell(lhash_kv_engine *pEngine,lhpage *pPage)
289{
290 lhcell *pCell;
291 pCell = (lhcell *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhcell));
292 if( pCell == 0 ){
293 return 0;
294 }
295 /* Zero the structure */
296 SyZero(pCell,sizeof(lhcell));
297 /* Fill in the structure */
298 SyBlobInit(&pCell->sKey,&pEngine->sAllocator);
299 pCell->pPage = pPage;
300 return pCell;
301}
302/*
303 * Discard a cell from the page table.
304 */
305static void lhCellDiscard(lhcell *pCell)
306{
307 lhpage *pPage = pCell->pPage->pMaster;
308
309 if( pCell->pPrevCol ){
310 pCell->pPrevCol->pNextCol = pCell->pNextCol;
311 }else{
312 pPage->apCell[pCell->nHash & (pPage->nCellSize - 1)] = pCell->pNextCol;
313 }
314 if( pCell->pNextCol ){
315 pCell->pNextCol->pPrevCol = pCell->pPrevCol;
316 }
317 MACRO_LD_REMOVE(pPage->pList,pCell);
318 if( pCell == pPage->pFirst ){
319 pPage->pFirst = pCell->pPrev;
320 }
321 pPage->nCell--;
322 /* Release the cell */
323 SyBlobRelease(&pCell->sKey);
324 SyMemBackendPoolFree(&pPage->pHash->sAllocator,pCell);
325}
326/*
327 * Install a cell in the page table.
328 */
329static int lhInstallCell(lhcell *pCell)
330{
331 lhpage *pPage = pCell->pPage->pMaster;
332 sxu32 iBucket;
333 if( pPage->nCell < 1 ){
334 sxu32 nTableSize = 32; /* Must be a power of two */
335 lhcell **apTable;
336 /* Allocate a new cell table */
337 apTable = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nTableSize * sizeof(lhcell *));
338 if( apTable == 0 ){
339 return UNQLITE_NOMEM;
340 }
341 /* Zero the new table */
342 SyZero((void *)apTable, nTableSize * sizeof(lhcell *));
343 /* Install it */
344 pPage->apCell = apTable;
345 pPage->nCellSize = nTableSize;
346 }
347 iBucket = pCell->nHash & (pPage->nCellSize - 1);
348 pCell->pNextCol = pPage->apCell[iBucket];
349 if( pPage->apCell[iBucket] ){
350 pPage->apCell[iBucket]->pPrevCol = pCell;
351 }
352 pPage->apCell[iBucket] = pCell;
353 if( pPage->pFirst == 0 ){
354 pPage->pFirst = pPage->pList = pCell;
355 }else{
356 MACRO_LD_PUSH(pPage->pList,pCell);
357 }
358 pPage->nCell++;
359 if( (pPage->nCell >= pPage->nCellSize * 3) && pPage->nCell < 100000 ){
360 /* Allocate a new larger table */
361 sxu32 nNewSize = pPage->nCellSize << 1;
362 lhcell *pEntry;
363 lhcell **apNew;
364 sxu32 n;
365
366 apNew = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nNewSize * sizeof(lhcell *));
367 if( apNew ){
368 /* Zero the new table */
369 SyZero((void *)apNew, nNewSize * sizeof(lhcell *));
370 /* Rehash all entries */
371 n = 0;
372 pEntry = pPage->pList;
373 for(;;){
374 /* Loop one */
375 if( n >= pPage->nCell ){
376 break;
377 }
378 pEntry->pNextCol = pEntry->pPrevCol = 0;
379 /* Install in the new bucket */
380 iBucket = pEntry->nHash & (nNewSize - 1);
381 pEntry->pNextCol = apNew[iBucket];
382 if( apNew[iBucket] ){
383 apNew[iBucket]->pPrevCol = pEntry;
384 }
385 apNew[iBucket] = pEntry;
386 /* Point to the next entry */
387 pEntry = pEntry->pNext;
388 n++;
389 }
390 /* Release the old table and reflect the change */
391 SyMemBackendFree(&pPage->pHash->sAllocator,(void *)pPage->apCell);
392 pPage->apCell = apNew;
393 pPage->nCellSize = nNewSize;
394 }
395 }
396 return UNQLITE_OK;
397}
398/*
399 * Private data of lhKeyCmp().
400 */
401struct lhash_key_cmp
402{
403 const char *zIn; /* Start of the stream */
404 const char *zEnd; /* End of the stream */
405 ProcCmp xCmp; /* Comparison function */
406};
407/*
408 * Comparsion callback for large key > 256 KB
409 */
410static int lhKeyCmp(const void *pData,sxu32 nLen,void *pUserData)
411{
412 struct lhash_key_cmp *pCmp = (struct lhash_key_cmp *)pUserData;
413 int rc;
414 if( pCmp->zIn >= pCmp->zEnd ){
415 if( nLen > 0 ){
416 return UNQLITE_ABORT;
417 }
418 return UNQLITE_OK;
419 }
420 /* Perform the comparison */
421 rc = pCmp->xCmp((const void *)pCmp->zIn,pData,nLen);
422 if( rc != 0 ){
423 /* Abort comparison */
424 return UNQLITE_ABORT;
425 }
426 /* Advance the cursor */
427 pCmp->zIn += nLen;
428 return UNQLITE_OK;
429}
430/* Forward declaration */
431static int lhConsumeCellkey(lhcell *pCell,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData,int offt_only);
432/*
433 * given a key, return the cell associated with it on success. NULL otherwise.
434 */
435static lhcell * lhFindCell(
436 lhpage *pPage, /* Target page */
437 const void *pKey, /* Lookup key */
438 sxu32 nByte, /* Key length */
439 sxu32 nHash /* Hash of the key */
440 )
441{
442 lhcell *pEntry;
443 if( pPage->nCell < 1 ){
444 /* Don't bother hashing */
445 return 0;
446 }
447 /* Point to the corresponding bucket */
448 pEntry = pPage->apCell[nHash & (pPage->nCellSize - 1)];
449 for(;;){
450 if( pEntry == 0 ){
451 break;
452 }
453 if( pEntry->nHash == nHash && pEntry->nKey == nByte ){
454 if( SyBlobLength(&pEntry->sKey) < 1 ){
455 /* Large key (> 256 KB) are not kept in-memory */
456 struct lhash_key_cmp sCmp;
457 int rc;
458 /* Fill-in the structure */
459 sCmp.zIn = (const char *)pKey;
460 sCmp.zEnd = &sCmp.zIn[nByte];
461 sCmp.xCmp = pPage->pHash->xCmp;
462 /* Fetch the key from disk and perform the comparison */
463 rc = lhConsumeCellkey(pEntry,lhKeyCmp,&sCmp,0);
464 if( rc == UNQLITE_OK ){
465 /* Cell found */
466 return pEntry;
467 }
468 }else if ( pPage->pHash->xCmp(pKey,SyBlobData(&pEntry->sKey),nByte) == 0 ){
469 /* Cell found */
470 return pEntry;
471 }
472 }
473 /* Point to the next entry */
474 pEntry = pEntry->pNextCol;
475 }
476 /* No such entry */
477 return 0;
478}
479/*
480 * Parse a raw cell fetched from disk.
481 */
482static int lhParseOneCell(lhpage *pPage,const unsigned char *zRaw,const unsigned char *zEnd,lhcell **ppOut)
483{
484 sxu16 iNext,iOfft;
485 sxu32 iHash,nKey;
486 lhcell *pCell;
487 sxu64 nData;
488 int rc;
489 /* Offset this cell is stored */
490 iOfft = (sxu16)(zRaw - (const unsigned char *)pPage->pRaw->zData);
491 /* 4 byte hash number */
492 SyBigEndianUnpack32(zRaw,&iHash);
493 zRaw += 4;
494 /* 4 byte key length */
495 SyBigEndianUnpack32(zRaw,&nKey);
496 zRaw += 4;
497 /* 8 byte data length */
498 SyBigEndianUnpack64(zRaw,&nData);
499 zRaw += 8;
500 /* 2 byte offset of the next cell */
501 SyBigEndianUnpack16(zRaw,&iNext);
502 /* Perform a sanity check */
503 if( iNext > 0 && &pPage->pRaw->zData[iNext] >= zEnd ){
504 return UNQLITE_CORRUPT;
505 }
506 zRaw += 2;
507 pCell = lhNewCell(pPage->pHash,pPage);
508 if( pCell == 0 ){
509 return UNQLITE_NOMEM;
510 }
511 /* Fill in the structure */
512 pCell->iNext = iNext;
513 pCell->nKey = nKey;
514 pCell->nData = nData;
515 pCell->nHash = iHash;
516 /* Overflow page if any */
517 SyBigEndianUnpack64(zRaw,&pCell->iOvfl);
518 zRaw += 8;
519 /* Cell offset */
520 pCell->iStart = iOfft;
521 /* Consume the key */
522 rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,pCell->nKey > 262144 /* 256 KB */? 1 : 0);
523 if( rc != UNQLITE_OK ){
524 /* TICKET: 14-32-chm@symisc.net: Key too large for memory */
525 SyBlobRelease(&pCell->sKey);
526 }
527 /* Finally install the cell */
528 rc = lhInstallCell(pCell);
529 if( rc != UNQLITE_OK ){
530 return rc;
531 }
532 if( ppOut ){
533 *ppOut = pCell;
534 }
535 return UNQLITE_OK;
536}
537/*
538 * Compute the total number of free space on a given page.
539 */
540static int lhPageFreeSpace(lhpage *pPage)
541{
542 const unsigned char *zEnd,*zRaw = pPage->pRaw->zData;
543 lhphdr *pHdr = &pPage->sHdr;
544 sxu16 iNext,iAmount;
545 sxu16 nFree = 0;
546 if( pHdr->iFree < 1 ){
547 /* Don't bother processing, the page is full */
548 pPage->nFree = 0;
549 return UNQLITE_OK;
550 }
551 /* Point to first free block */
552 zEnd = &zRaw[pPage->pHash->iPageSize];
553 zRaw += pHdr->iFree;
554 for(;;){
555 /* Offset of the next free block */
556 SyBigEndianUnpack16(zRaw,&iNext);
557 zRaw += 2;
558 /* Available space on this block */
559 SyBigEndianUnpack16(zRaw,&iAmount);
560 nFree += iAmount;
561 if( iNext < 1 ){
562 /* No more free blocks */
563 break;
564 }
565 /* Point to the next free block*/
566 zRaw = &pPage->pRaw->zData[iNext];
567 if( zRaw >= zEnd ){
568 /* Corrupt page */
569 return UNQLITE_CORRUPT;
570 }
571 }
572 /* Save the amount of free space */
573 pPage->nFree = nFree;
574 return UNQLITE_OK;
575}
576/*
577 * Given a primary page, load all its cell.
578 */
579static int lhLoadCells(lhpage *pPage)
580{
581 const unsigned char *zEnd,*zRaw = pPage->pRaw->zData;
582 lhphdr *pHdr = &pPage->sHdr;
583 lhcell *pCell = 0; /* cc warning */
584 int rc;
585 /* Calculate the amount of free space available first */
586 rc = lhPageFreeSpace(pPage);
587 if( rc != UNQLITE_OK ){
588 return rc;
589 }
590 if( pHdr->iOfft < 1 ){
591 /* Don't bother processing, the page is empty */
592 return UNQLITE_OK;
593 }
594 /* Point to first cell */
595 zRaw += pHdr->iOfft;
596 zEnd = &zRaw[pPage->pHash->iPageSize];
597 for(;;){
598 /* Parse a single cell */
599 rc = lhParseOneCell(pPage,zRaw,zEnd,&pCell);
600 if( rc != UNQLITE_OK ){
601 return rc;
602 }
603 if( pCell->iNext < 1 ){
604 /* No more cells */
605 break;
606 }
607 /* Point to the next cell */
608 zRaw = &pPage->pRaw->zData[pCell->iNext];
609 if( zRaw >= zEnd ){
610 /* Corrupt page */
611 return UNQLITE_CORRUPT;
612 }
613 }
614 /* All done */
615 return UNQLITE_OK;
616}
617/*
618 * Given a page, parse its raw headers.
619 */
620static int lhParsePageHeader(lhpage *pPage)
621{
622 const unsigned char *zRaw = pPage->pRaw->zData;
623 lhphdr *pHdr = &pPage->sHdr;
624 /* Offset of the first cell */
625 SyBigEndianUnpack16(zRaw,&pHdr->iOfft);
626 zRaw += 2;
627 /* Offset of the first free block */
628 SyBigEndianUnpack16(zRaw,&pHdr->iFree);
629 zRaw += 2;
630 /* Slave page number */
631 SyBigEndianUnpack64(zRaw,&pHdr->iSlave);
632 /* All done */
633 return UNQLITE_OK;
634}
635/*
636 * Allocate a new page instance.
637 */
638static lhpage * lhNewPage(
639 lhash_kv_engine *pEngine, /* KV store which own this instance */
640 unqlite_page *pRaw, /* Raw page contents */
641 lhpage *pMaster /* Master page in case we are dealing with a slave page */
642 )
643{
644 lhpage *pPage;
645 /* Allocate a new instance */
646 pPage = (lhpage *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhpage));
647 if( pPage == 0 ){
648 return 0;
649 }
650 /* Zero the structure */
651 SyZero(pPage,sizeof(lhpage));
652 /* Fill-in the structure */
653 pPage->pHash = pEngine;
654 pPage->pRaw = pRaw;
655 pPage->pMaster = pMaster ? pMaster /* Slave page */ : pPage /* Master page */ ;
656 if( pPage->pMaster != pPage ){
657 /* Slave page, attach it to its master */
658 pPage->pNextSlave = pMaster->pSlave;
659 pMaster->pSlave = pPage;
660 pMaster->iSlave++;
661 }
662 /* Save this instance for future fast lookup */
663 pRaw->pUserData = pPage;
664 /* All done */
665 return pPage;
666}
667/*
668 * Load a primary and its associated slave pages from disk.
669 */
670static int lhLoadPage(lhash_kv_engine *pEngine,pgno pnum,lhpage *pMaster,lhpage **ppOut,int iNest)
671{
672 unqlite_page *pRaw;
673 lhpage *pPage = 0; /* cc warning */
674 int rc;
675 /* Aquire the page from the pager first */
676 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pnum,&pRaw);
677 if( rc != UNQLITE_OK ){
678 return rc;
679 }
680 if( pRaw->pUserData ){
681 /* The page is already parsed and loaded in memory. Point to it */
682 pPage = (lhpage *)pRaw->pUserData;
683 }else{
684 /* Allocate a new page */
685 pPage = lhNewPage(pEngine,pRaw,pMaster);
686 if( pPage == 0 ){
687 return UNQLITE_NOMEM;
688 }
689 /* Process the page */
690 rc = lhParsePageHeader(pPage);
691 if( rc == UNQLITE_OK ){
692 /* Load cells */
693 rc = lhLoadCells(pPage);
694 }
695 if( rc != UNQLITE_OK ){
696 pEngine->pIo->xPageUnref(pPage->pRaw); /* pPage will be released inside this call */
697 return rc;
698 }
699 if( pPage->sHdr.iSlave > 0 && iNest < 128 ){
700 if( pMaster == 0 ){
701 pMaster = pPage;
702 }
703 /* Slave page. Not a fatal error if something goes wrong here */
704 lhLoadPage(pEngine,pPage->sHdr.iSlave,pMaster,0,iNest++);
705 }
706 }
707 if( ppOut ){
708 *ppOut = pPage;
709 }
710 return UNQLITE_OK;
711}
712/*
713 * Given a cell, Consume its key by invoking the given callback for each extracted chunk.
714 */
715static int lhConsumeCellkey(
716 lhcell *pCell, /* Target cell */
717 int (*xConsumer)(const void *,unsigned int,void *), /* Consumer callback */
718 void *pUserData, /* Last argument to xConsumer() */
719 int offt_only
720 )
721{
722 lhpage *pPage = pCell->pPage;
723 const unsigned char *zRaw = pPage->pRaw->zData;
724 const unsigned char *zPayload;
725 int rc;
726 /* Point to the payload area */
727 zPayload = &zRaw[pCell->iStart];
728 if( pCell->iOvfl == 0 ){
729 /* Best scenario, consume the key directly without any overflow page */
730 zPayload += L_HASH_CELL_SZ;
731 rc = xConsumer((const void *)zPayload,pCell->nKey,pUserData);
732 if( rc != UNQLITE_OK ){
733 rc = UNQLITE_ABORT;
734 }
735 }else{
736 lhash_kv_engine *pEngine = pPage->pHash;
737 sxu32 nByte,nData = pCell->nKey;
738 unqlite_page *pOvfl;
739 int data_offset = 0;
740 pgno iOvfl;
741 /* Overflow page */
742 iOvfl = pCell->iOvfl;
743 /* Total usable bytes in an overflow page */
744 nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize);
745 for(;;){
746 if( iOvfl == 0 || nData < 1 ){
747 /* no more overflow page */
748 break;
749 }
750 /* Point to the overflow page */
751 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl);
752 if( rc != UNQLITE_OK ){
753 return rc;
754 }
755 zPayload = &pOvfl->zData[8];
756 /* Point to the raw content */
757 if( !data_offset ){
758 /* Get the data page and offset */
759 SyBigEndianUnpack64(zPayload,&pCell->iDataPage);
760 zPayload += 8;
761 SyBigEndianUnpack16(zPayload,&pCell->iDataOfft);
762 zPayload += 2;
763 if( offt_only ){
764 /* Key too large, grab the data offset and return */
765 pEngine->pIo->xPageUnref(pOvfl);
766 return UNQLITE_OK;
767 }
768 data_offset = 1;
769 }
770 /* Consume the key */
771 if( nData <= nByte ){
772 rc = xConsumer((const void *)zPayload,nData,pUserData);
773 if( rc != UNQLITE_OK ){
774 pEngine->pIo->xPageUnref(pOvfl);
775 return UNQLITE_ABORT;
776 }
777 nData = 0;
778 }else{
779 rc = xConsumer((const void *)zPayload,nByte,pUserData);
780 if( rc != UNQLITE_OK ){
781 pEngine->pIo->xPageUnref(pOvfl);
782 return UNQLITE_ABORT;
783 }
784 nData -= nByte;
785 }
786 /* Next overflow page in the chain */
787 SyBigEndianUnpack64(pOvfl->zData,&iOvfl);
788 /* Unref the page */
789 pEngine->pIo->xPageUnref(pOvfl);
790 }
791 rc = UNQLITE_OK;
792 }
793 return rc;
794}
795/*
796 * Given a cell, Consume its data by invoking the given callback for each extracted chunk.
797 */
798static int lhConsumeCellData(
799 lhcell *pCell, /* Target cell */
800 int (*xConsumer)(const void *,unsigned int,void *), /* Data consumer callback */
801 void *pUserData /* Last argument to xConsumer() */
802 )
803{
804 lhpage *pPage = pCell->pPage;
805 const unsigned char *zRaw = pPage->pRaw->zData;
806 const unsigned char *zPayload;
807 int rc;
808 /* Point to the payload area */
809 zPayload = &zRaw[pCell->iStart];
810 if( pCell->iOvfl == 0 ){
811 /* Best scenario, consume the data directly without any overflow page */
812 zPayload += L_HASH_CELL_SZ + pCell->nKey;
813 rc = xConsumer((const void *)zPayload,(sxu32)pCell->nData,pUserData);
814 if( rc != UNQLITE_OK ){
815 rc = UNQLITE_ABORT;
816 }
817 }else{
818 lhash_kv_engine *pEngine = pPage->pHash;
819 sxu64 nData = pCell->nData;
820 unqlite_page *pOvfl;
821 int fix_offset = 0;
822 sxu32 nByte;
823 pgno iOvfl;
824 /* Overflow page where data is stored */
825 iOvfl = pCell->iDataPage;
826 for(;;){
827 if( iOvfl == 0 || nData < 1 ){
828 /* no more overflow page */
829 break;
830 }
831 /* Point to the overflow page */
832 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl);
833 if( rc != UNQLITE_OK ){
834 return rc;
835 }
836 /* Point to the raw content */
837 zPayload = pOvfl->zData;
838 if( !fix_offset ){
839 /* Point to the data */
840 zPayload += pCell->iDataOfft;
841 nByte = pEngine->iPageSize - pCell->iDataOfft;
842 fix_offset = 1;
843 }else{
844 zPayload += 8;
845 /* Total usable bytes in an overflow page */
846 nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize);
847 }
848 /* Consume the data */
849 if( nData <= (sxu64)nByte ){
850 rc = xConsumer((const void *)zPayload,(unsigned int)nData,pUserData);
851 if( rc != UNQLITE_OK ){
852 pEngine->pIo->xPageUnref(pOvfl);
853 return UNQLITE_ABORT;
854 }
855 nData = 0;
856 }else{
857 if( nByte > 0 ){
858 rc = xConsumer((const void *)zPayload,nByte,pUserData);
859 if( rc != UNQLITE_OK ){
860 pEngine->pIo->xPageUnref(pOvfl);
861 return UNQLITE_ABORT;
862 }
863 nData -= nByte;
864 }
865 }
866 /* Next overflow page in the chain */
867 SyBigEndianUnpack64(pOvfl->zData,&iOvfl);
868 /* Unref the page */
869 pEngine->pIo->xPageUnref(pOvfl);
870 }
871 rc = UNQLITE_OK;
872 }
873 return rc;
874}
875/*
876 * Read the linear hash header (Page one of the database).
877 */
878static int lhash_read_header(lhash_kv_engine *pEngine,unqlite_page *pHeader)
879{
880 const unsigned char *zRaw = pHeader->zData;
881 lhash_bmap_page *pMap;
882 sxu32 nHash;
883 int rc;
884 pEngine->pHeader = pHeader;
885 /* 4 byte magic number */
886 SyBigEndianUnpack32(zRaw,&pEngine->nMagic);
887 zRaw += 4;
888 if( pEngine->nMagic != L_HASH_MAGIC ){
889 /* Corrupt implementation */
890 return UNQLITE_CORRUPT;
891 }
892 /* 4 byte hash value to identify a valid hash function */
893 SyBigEndianUnpack32(zRaw,&nHash);
894 zRaw += 4;
895 /* Sanity check */
896 if( pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1) != nHash ){
897 /* Different hash function */
898 pEngine->pIo->xErr(pEngine->pIo->pHandle,"Invalid hash function");
899 return UNQLITE_INVALID;
900 }
901 /* List of free pages */
902 SyBigEndianUnpack64(zRaw,&pEngine->nFreeList);
903 zRaw += 8;
904 /* Current split bucket */
905 SyBigEndianUnpack64(zRaw,&pEngine->split_bucket);
906 zRaw += 8;
907 /* Maximum split bucket */
908 SyBigEndianUnpack64(zRaw,&pEngine->max_split_bucket);
909 zRaw += 8;
910 /* Next generation */
911 pEngine->nmax_split_nucket = pEngine->max_split_bucket << 1;
912 /* Initialiaze the bucket map */
913 pMap = &pEngine->sPageMap;
914 /* Fill in the structure */
915 pMap->iNum = pHeader->pgno;
916 /* Next page in the bucket map */
917 SyBigEndianUnpack64(zRaw,&pMap->iNext);
918 zRaw += 8;
919 /* Total number of records in the bucket map (This page only) */
920 SyBigEndianUnpack32(zRaw,&pMap->nRec);
921 zRaw += 4;
922 pMap->iPtr = (sxu16)(zRaw - pHeader->zData);
923 /* Load the map in memory */
924 rc = lhMapLoadPage(pEngine,pMap,pHeader->zData);
925 if( rc != UNQLITE_OK ){
926 return rc;
927 }
928 /* Load the bucket map chain if any */
929 for(;;){
930 pgno iNext = pMap->iNext;
931 unqlite_page *pPage;
932 if( iNext == 0 ){
933 /* No more map pages */
934 break;
935 }
936 /* Point to the target page */
937 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pPage);
938 if( rc != UNQLITE_OK ){
939 return rc;
940 }
941 /* Fill in the structure */
942 pMap->iNum = iNext;
943 pMap->iPtr = 0;
944 /* Load the map in memory */
945 rc = lhMapLoadPage(pEngine,pMap,pPage->zData);
946 if( rc != UNQLITE_OK ){
947 return rc;
948 }
949 }
950 /* All done */
951 return UNQLITE_OK;
952}
953/*
954 * Perform a record lookup.
955 */
956static int lhRecordLookup(
957 lhash_kv_engine *pEngine, /* KV storage engine */
958 const void *pKey, /* Lookup key */
959 sxu32 nByte, /* Key length */
960 lhcell **ppCell /* OUT: Target cell on success */
961 )
962{
963 lhash_bmap_rec *pRec;
964 lhpage *pPage;
965 lhcell *pCell;
966 pgno iBucket;
967 sxu32 nHash;
968 int rc;
969 /* Acquire the first page (hash Header) so that everything gets loaded autmatically */
970 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0);
971 if( rc != UNQLITE_OK ){
972 return rc;
973 }
974 /* Compute the hash of the key first */
975 nHash = pEngine->xHash(pKey,nByte);
976 /* Extract the logical (i.e. not real) page number */
977 iBucket = nHash & (pEngine->nmax_split_nucket - 1);
978 if( iBucket >= (pEngine->split_bucket + pEngine->max_split_bucket) ){
979 /* Low mask */
980 iBucket = nHash & (pEngine->max_split_bucket - 1);
981 }
982 /* Map the logical bucket number to real page number */
983 pRec = lhMapFindBucket(pEngine,iBucket);
984 if( pRec == 0 ){
985 /* No such entry */
986 return UNQLITE_NOTFOUND;
987 }
988 /* Load the master page and it's slave page in-memory */
989 rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0);
990 if( rc != UNQLITE_OK ){
991 /* IO error, unlikely scenario */
992 return rc;
993 }
994 /* Lookup for the cell */
995 pCell = lhFindCell(pPage,pKey,nByte,nHash);
996 if( pCell == 0 ){
997 /* No such entry */
998 return UNQLITE_NOTFOUND;
999 }
1000 if( ppCell ){
1001 *ppCell = pCell;
1002 }
1003 return UNQLITE_OK;
1004}
1005/*
1006 * Acquire a new page either from the free list or ask the pager
1007 * for a new one.
1008 */
1009static int lhAcquirePage(lhash_kv_engine *pEngine,unqlite_page **ppOut)
1010{
1011 unqlite_page *pPage;
1012 int rc;
1013 if( pEngine->nFreeList != 0 ){
1014 /* Acquire one from the free list */
1015 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pEngine->nFreeList,&pPage);
1016 if( rc == UNQLITE_OK ){
1017 /* Point to the next free page */
1018 SyBigEndianUnpack64(pPage->zData,&pEngine->nFreeList);
1019 /* Update the database header */
1020 rc = pEngine->pIo->xWrite(pEngine->pHeader);
1021 if( rc != UNQLITE_OK ){
1022 return rc;
1023 }
1024 SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList);
1025 /* Tell the pager do not journal this page */
1026 pEngine->pIo->xDontJournal(pPage);
1027 /* Return to the caller */
1028 *ppOut = pPage;
1029 /* All done */
1030 return UNQLITE_OK;
1031 }
1032 }
1033 /* Acquire a new page */
1034 rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pPage);
1035 if( rc != UNQLITE_OK ){
1036 return rc;
1037 }
1038 /* Point to the target page */
1039 *ppOut = pPage;
1040 return UNQLITE_OK;
1041}
1042/*
1043 * Write a bucket map record to disk.
1044 */
1045static int lhMapWriteRecord(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal)
1046{
1047 lhash_bmap_page *pMap = &pEngine->sPageMap;
1048 unqlite_page *pPage = 0;
1049 int rc;
1050 if( pMap->iPtr > (pEngine->iPageSize - 16) /* 8 byte logical bucket number + 8 byte real bucket number */ ){
1051 unqlite_page *pOld;
1052 /* Point to the old page */
1053 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pOld);
1054 if( rc != UNQLITE_OK ){
1055 return rc;
1056 }
1057 /* Acquire a new page */
1058 rc = lhAcquirePage(pEngine,&pPage);
1059 if( rc != UNQLITE_OK ){
1060 return rc;
1061 }
1062 /* Reflect the change */
1063 pMap->iNext = 0;
1064 pMap->iNum = pPage->pgno;
1065 pMap->nRec = 0;
1066 pMap->iPtr = 8/* Next page number */+4/* Total records in the map*/;
1067 /* Link this page */
1068 rc = pEngine->pIo->xWrite(pOld);
1069 if( rc != UNQLITE_OK ){
1070 return rc;
1071 }
1072 if( pOld->pgno == pEngine->pHeader->pgno ){
1073 /* First page (Hash header) */
1074 SyBigEndianPack64(&pOld->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/],pPage->pgno);
1075 }else{
1076 /* Link the new page */
1077 SyBigEndianPack64(pOld->zData,pPage->pgno);
1078 /* Unref */
1079 pEngine->pIo->xPageUnref(pOld);
1080 }
1081 /* Assume the last bucket map page */
1082 rc = pEngine->pIo->xWrite(pPage);
1083 if( rc != UNQLITE_OK ){
1084 return rc;
1085 }
1086 SyBigEndianPack64(pPage->zData,0); /* Next bucket map page on the list */
1087 }
1088 if( pPage == 0){
1089 /* Point to the current map page */
1090 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pPage);
1091 if( rc != UNQLITE_OK ){
1092 return rc;
1093 }
1094 }
1095 /* Make page writable */
1096 rc = pEngine->pIo->xWrite(pPage);
1097 if( rc != UNQLITE_OK ){
1098 return rc;
1099 }
1100 /* Write the data */
1101 SyBigEndianPack64(&pPage->zData[pMap->iPtr],iLogic);
1102 pMap->iPtr += 8;
1103 SyBigEndianPack64(&pPage->zData[pMap->iPtr],iReal);
1104 pMap->iPtr += 8;
1105 /* Install the bucket map */
1106 rc = lhMapInstallBucket(pEngine,iLogic,iReal);
1107 if( rc == UNQLITE_OK ){
1108 /* Total number of records */
1109 pMap->nRec++;
1110 if( pPage->pgno == pEngine->pHeader->pgno ){
1111 /* Page one: Always writable */
1112 SyBigEndianPack32(
1113 &pPage->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/+8/*Next map page*/],
1114 pMap->nRec);
1115 }else{
1116 /* Make page writable */
1117 rc = pEngine->pIo->xWrite(pPage);
1118 if( rc != UNQLITE_OK ){
1119 return rc;
1120 }
1121 SyBigEndianPack32(&pPage->zData[8],pMap->nRec);
1122 }
1123 }
1124 return rc;
1125}
1126/*
1127 * Defragment a page.
1128 */
1129static int lhPageDefragment(lhpage *pPage)
1130{
1131 lhash_kv_engine *pEngine = pPage->pHash;
1132 unsigned char *zTmp,*zPtr,*zEnd,*zPayload;
1133 lhcell *pCell;
1134 /* Get a temporary page from the pager. This opertaion never fail */
1135 zTmp = pEngine->pIo->xTmpPage(pEngine->pIo->pHandle);
1136 /* Move the target cells to the begining */
1137 pCell = pPage->pList;
1138 /* Write the slave page number */
1139 SyBigEndianPack64(&zTmp[2/*Offset of the first cell */+2/*Offset of the first free block */],pPage->sHdr.iSlave);
1140 zPtr = &zTmp[L_HASH_PAGE_HDR_SZ]; /* Offset to start writing from */
1141 zEnd = &zTmp[pEngine->iPageSize];
1142 pPage->sHdr.iOfft = 0; /* Offset of the first cell */
1143 for(;;){
1144 if( pCell == 0 ){
1145 /* No more cells */
1146 break;
1147 }
1148 if( pCell->pPage->pRaw->pgno == pPage->pRaw->pgno ){
1149 /* Cell payload if locally stored */
1150 zPayload = 0;
1151 if( pCell->iOvfl == 0 ){
1152 zPayload = &pCell->pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ];
1153 }
1154 /* Move the cell */
1155 pCell->iNext = pPage->sHdr.iOfft;
1156 pCell->iStart = (sxu16)(zPtr - zTmp); /* Offset where this cell start */
1157 pPage->sHdr.iOfft = pCell->iStart;
1158 /* Write the cell header */
1159 /* 4 byte hash number */
1160 SyBigEndianPack32(zPtr,pCell->nHash);
1161 zPtr += 4;
1162 /* 4 byte ley length */
1163 SyBigEndianPack32(zPtr,pCell->nKey);
1164 zPtr += 4;
1165 /* 8 byte data length */
1166 SyBigEndianPack64(zPtr,pCell->nData);
1167 zPtr += 8;
1168 /* 2 byte offset of the next cell */
1169 SyBigEndianPack16(zPtr,pCell->iNext);
1170 zPtr += 2;
1171 /* 8 byte overflow page number */
1172 SyBigEndianPack64(zPtr,pCell->iOvfl);
1173 zPtr += 8;
1174 if( zPayload ){
1175 /* Local payload */
1176 SyMemcpy((const void *)zPayload,zPtr,(sxu32)(pCell->nKey + pCell->nData));
1177 zPtr += pCell->nKey + pCell->nData;
1178 }
1179 if( zPtr >= zEnd ){
1180 /* Can't happen */
1181 break;
1182 }
1183 }
1184 /* Point to the next page */
1185 pCell = pCell->pNext;
1186 }
1187 /* Mark the free block */
1188 pPage->nFree = (sxu16)(zEnd - zPtr); /* Block length */
1189 if( pPage->nFree > 3 ){
1190 pPage->sHdr.iFree = (sxu16)(zPtr - zTmp); /* Offset of the free block */
1191 /* Mark the block */
1192 SyBigEndianPack16(zPtr,0); /* Offset of the next free block */
1193 SyBigEndianPack16(&zPtr[2],pPage->nFree); /* Block length */
1194 }else{
1195 /* Block of length less than 4 bytes are simply discarded */
1196 pPage->nFree = 0;
1197 pPage->sHdr.iFree = 0;
1198 }
1199 /* Reflect the change */
1200 SyBigEndianPack16(zTmp,pPage->sHdr.iOfft); /* Offset of the first cell */
1201 SyBigEndianPack16(&zTmp[2],pPage->sHdr.iFree); /* Offset of the first free block */
1202 SyMemcpy((const void *)zTmp,pPage->pRaw->zData,pEngine->iPageSize);
1203 /* All done */
1204 return UNQLITE_OK;
1205}
1206/*
1207** Allocate nByte bytes of space on a page.
1208**
1209** Return the index into pPage->pRaw->zData[] of the first byte of
1210** the new allocation. Or return 0 if there is not enough free
1211** space on the page to satisfy the allocation request.
1212**
1213** If the page contains nBytes of free space but does not contain
1214** nBytes of contiguous free space, then this routine automatically
1215** calls defragementPage() to consolidate all free space before
1216** allocating the new chunk.
1217*/
1218static int lhAllocateSpace(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft)
1219{
1220 const unsigned char *zEnd,*zPtr;
1221 sxu16 iNext,iBlksz,nByte;
1222 unsigned char *zPrev;
1223 int rc;
1224 if( (sxu64)pPage->nFree < nAmount ){
1225 /* Don't bother looking for a free chunk */
1226 return UNQLITE_FULL;
1227 }
1228 if( pPage->nCell < 10 && ((int)nAmount >= (pPage->pHash->iPageSize / 2)) ){
1229 /* Big chunk need an overflow page for its data */
1230 return UNQLITE_FULL;
1231 }
1232 zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree];
1233 zEnd = &pPage->pRaw->zData[pPage->pHash->iPageSize];
1234 nByte = (sxu16)nAmount;
1235 zPrev = 0;
1236 iBlksz = 0; /* cc warning */
1237 /* Perform the lookup */
1238 for(;;){
1239 if( zPtr >= zEnd ){
1240 return UNQLITE_FULL;
1241 }
1242 /* Offset of the next free block */
1243 SyBigEndianUnpack16(zPtr,&iNext);
1244 /* Block size */
1245 SyBigEndianUnpack16(&zPtr[2],&iBlksz);
1246 if( iBlksz >= nByte ){
1247 /* Got one */
1248 break;
1249 }
1250 zPrev = (unsigned char *)zPtr;
1251 if( iNext == 0 ){
1252 /* No more free blocks, defragment the page */
1253 rc = lhPageDefragment(pPage);
1254 if( rc == UNQLITE_OK && pPage->nFree >= nByte) {
1255 /* Free blocks are merged together */
1256 iNext = 0;
1257 zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree];
1258 iBlksz = pPage->nFree;
1259 zPrev = 0;
1260 break;
1261 }else{
1262 return UNQLITE_FULL;
1263 }
1264 }
1265 /* Point to the next free block */
1266 zPtr = &pPage->pRaw->zData[iNext];
1267 }
1268 /* Acquire writer lock on this page */
1269 rc = pPage->pHash->pIo->xWrite(pPage->pRaw);
1270 if( rc != UNQLITE_OK ){
1271 return rc;
1272 }
1273 /* Save block offset */
1274 *pOfft = (sxu16)(zPtr - pPage->pRaw->zData);
1275 /* Fix pointers */
1276 if( iBlksz >= nByte && (iBlksz - nByte) > 3 ){
1277 unsigned char *zBlock = &pPage->pRaw->zData[(*pOfft) + nByte];
1278 /* Create a new block */
1279 zPtr = zBlock;
1280 SyBigEndianPack16(zBlock,iNext); /* Offset of the next block */
1281 SyBigEndianPack16(&zBlock[2],iBlksz-nByte); /* Block size*/
1282 /* Offset of the new block */
1283 iNext = (sxu16)(zPtr - pPage->pRaw->zData);
1284 iBlksz = nByte;
1285 }
1286 /* Fix offsets */
1287 if( zPrev ){
1288 SyBigEndianPack16(zPrev,iNext);
1289 }else{
1290 /* First block */
1291 pPage->sHdr.iFree = iNext;
1292 /* Reflect on the page header */
1293 SyBigEndianPack16(&pPage->pRaw->zData[2/* Offset of the first cell1*/],iNext);
1294 }
1295 /* All done */
1296 pPage->nFree -= iBlksz;
1297 return UNQLITE_OK;
1298}
1299/*
1300 * Write the cell header into the corresponding offset.
1301 */
1302static int lhCellWriteHeader(lhcell *pCell)
1303{
1304 lhpage *pPage = pCell->pPage;
1305 unsigned char *zRaw = pPage->pRaw->zData;
1306 /* Seek to the desired location */
1307 zRaw += pCell->iStart;
1308 /* 4 byte hash number */
1309 SyBigEndianPack32(zRaw,pCell->nHash);
1310 zRaw += 4;
1311 /* 4 byte key length */
1312 SyBigEndianPack32(zRaw,pCell->nKey);
1313 zRaw += 4;
1314 /* 8 byte data length */
1315 SyBigEndianPack64(zRaw,pCell->nData);
1316 zRaw += 8;
1317 /* 2 byte offset of the next cell */
1318 pCell->iNext = pPage->sHdr.iOfft;
1319 SyBigEndianPack16(zRaw,pCell->iNext);
1320 zRaw += 2;
1321 /* 8 byte overflow page number */
1322 SyBigEndianPack64(zRaw,pCell->iOvfl);
1323 /* Update the page header */
1324 pPage->sHdr.iOfft = pCell->iStart;
1325 /* pEngine->pIo->xWrite() has been successfully called on this page */
1326 SyBigEndianPack16(pPage->pRaw->zData,pCell->iStart);
1327 /* All done */
1328 return UNQLITE_OK;
1329}
1330/*
1331 * Write local payload.
1332 */
1333static int lhCellWriteLocalPayload(lhcell *pCell,
1334 const void *pKey,sxu32 nKeylen,
1335 const void *pData,unqlite_int64 nDatalen
1336 )
1337{
1338 /* A writer lock have been acquired on this page */
1339 lhpage *pPage = pCell->pPage;
1340 unsigned char *zRaw = pPage->pRaw->zData;
1341 /* Seek to the desired location */
1342 zRaw += pCell->iStart + L_HASH_CELL_SZ;
1343 /* Write the key */
1344 SyMemcpy(pKey,(void *)zRaw,nKeylen);
1345 zRaw += nKeylen;
1346 if( nDatalen > 0 ){
1347 /* Write the Data */
1348 SyMemcpy(pData,(void *)zRaw,(sxu32)nDatalen);
1349 }
1350 return UNQLITE_OK;
1351}
1352/*
1353 * Allocate as much overflow page we need to store the cell payload.
1354 */
1355static int lhCellWriteOvflPayload(lhcell *pCell,const void *pKey,sxu32 nKeylen,...)
1356{
1357 lhpage *pPage = pCell->pPage;
1358 lhash_kv_engine *pEngine = pPage->pHash;
1359 unqlite_page *pOvfl,*pFirst,*pNew;
1360 const unsigned char *zPtr,*zEnd;
1361 unsigned char *zRaw,*zRawEnd;
1362 sxu32 nAvail;
1363 va_list ap;
1364 int rc;
1365 /* Acquire a new overflow page */
1366 rc = lhAcquirePage(pEngine,&pOvfl);
1367 if( rc != UNQLITE_OK ){
1368 return rc;
1369 }
1370 /* Acquire a writer lock */
1371 rc = pEngine->pIo->xWrite(pOvfl);
1372 if( rc != UNQLITE_OK ){
1373 return rc;
1374 }
1375 pFirst = pOvfl;
1376 /* Link */
1377 pCell->iOvfl = pOvfl->pgno;
1378 /* Update the cell header */
1379 SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4/*Hash*/ + 4/*Key*/ + 8/*Data*/ + 2 /*Next cell*/],pCell->iOvfl);
1380 /* Start the write process */
1381 zPtr = (const unsigned char *)pKey;
1382 zEnd = &zPtr[nKeylen];
1383 SyBigEndianPack64(pOvfl->zData,0); /* Next overflow page on the chain */
1384 zRaw = &pOvfl->zData[8/* Next ovfl page*/ + 8 /* Data page */ + 2 /* Data offset*/];
1385 zRawEnd = &pOvfl->zData[pEngine->iPageSize];
1386 pNew = pOvfl;
1387 /* Write the key */
1388 for(;;){
1389 if( zPtr >= zEnd ){
1390 break;
1391 }
1392 if( zRaw >= zRawEnd ){
1393 /* Acquire a new page */
1394 rc = lhAcquirePage(pEngine,&pNew);
1395 if( rc != UNQLITE_OK ){
1396 return rc;
1397 }
1398 rc = pEngine->pIo->xWrite(pNew);
1399 if( rc != UNQLITE_OK ){
1400 return rc;
1401 }
1402 /* Link */
1403 SyBigEndianPack64(pOvfl->zData,pNew->pgno);
1404 pEngine->pIo->xPageUnref(pOvfl);
1405 SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
1406 pOvfl = pNew;
1407 zRaw = &pNew->zData[8];
1408 zRawEnd = &pNew->zData[pEngine->iPageSize];
1409 }
1410 nAvail = (sxu32)(zRawEnd-zRaw);
1411 nKeylen = (sxu32)(zEnd-zPtr);
1412 if( nKeylen > nAvail ){
1413 nKeylen = nAvail;
1414 }
1415 SyMemcpy((const void *)zPtr,(void *)zRaw,nKeylen);
1416 /* Synchronize pointers */
1417 zPtr += nKeylen;
1418 zRaw += nKeylen;
1419 }
1420 rc = UNQLITE_OK;
1421 va_start(ap,nKeylen);
1422 pCell->iDataPage = pNew->pgno;
1423 pCell->iDataOfft = (sxu16)(zRaw-pNew->zData);
1424 /* Write the data page and its offset */
1425 SyBigEndianPack64(&pFirst->zData[8/*Next ovfl*/],pCell->iDataPage);
1426 SyBigEndianPack16(&pFirst->zData[8/*Next ovfl*/+8/*Data page*/],pCell->iDataOfft);
1427 /* Write data */
1428 for(;;){
1429 const void *pData;
1430 sxu32 nDatalen;
1431 sxu64 nData;
1432 pData = va_arg(ap,const void *);
1433 nData = va_arg(ap,sxu64);
1434 if( pData == 0 ){
1435 /* No more chunks */
1436 break;
1437 }
1438 /* Write this chunk */
1439 zPtr = (const unsigned char *)pData;
1440 zEnd = &zPtr[nData];
1441 for(;;){
1442 if( zPtr >= zEnd ){
1443 break;
1444 }
1445 if( zRaw >= zRawEnd ){
1446 /* Acquire a new page */
1447 rc = lhAcquirePage(pEngine,&pNew);
1448 if( rc != UNQLITE_OK ){
1449 va_end(ap);
1450 return rc;
1451 }
1452 rc = pEngine->pIo->xWrite(pNew);
1453 if( rc != UNQLITE_OK ){
1454 va_end(ap);
1455 return rc;
1456 }
1457 /* Link */
1458 SyBigEndianPack64(pOvfl->zData,pNew->pgno);
1459 pEngine->pIo->xPageUnref(pOvfl);
1460 SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
1461 pOvfl = pNew;
1462 zRaw = &pNew->zData[8];
1463 zRawEnd = &pNew->zData[pEngine->iPageSize];
1464 }
1465 nAvail = (sxu32)(zRawEnd-zRaw);
1466 nDatalen = (sxu32)(zEnd-zPtr);
1467 if( nDatalen > nAvail ){
1468 nDatalen = nAvail;
1469 }
1470 SyMemcpy((const void *)zPtr,(void *)zRaw,nDatalen);
1471 /* Synchronize pointers */
1472 zPtr += nDatalen;
1473 zRaw += nDatalen;
1474 }
1475 }
1476 /* Unref the overflow page */
1477 pEngine->pIo->xPageUnref(pOvfl);
1478 va_end(ap);
1479 return UNQLITE_OK;
1480}
1481/*
1482 * Restore a page to the free list.
1483 */
1484static int lhRestorePage(lhash_kv_engine *pEngine,unqlite_page *pPage)
1485{
1486 int rc;
1487 rc = pEngine->pIo->xWrite(pEngine->pHeader);
1488 if( rc != UNQLITE_OK ){
1489 return rc;
1490 }
1491 rc = pEngine->pIo->xWrite(pPage);
1492 if( rc != UNQLITE_OK ){
1493 return rc;
1494 }
1495 /* Link to the list of free page */
1496 SyBigEndianPack64(pPage->zData,pEngine->nFreeList);
1497 pEngine->nFreeList = pPage->pgno;
1498 SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList);
1499 /* All done */
1500 return UNQLITE_OK;
1501}
1502/*
1503 * Restore cell space and mark it as a free block.
1504 */
1505static int lhRestoreSpace(lhpage *pPage,sxu16 iOfft,sxu16 nByte)
1506{
1507 unsigned char *zRaw;
1508 if( nByte < 4 ){
1509 /* At least 4 bytes of freespace must form a valid block */
1510 return UNQLITE_OK;
1511 }
1512 /* pEngine->pIo->xWrite() has been successfully called on this page */
1513 zRaw = &pPage->pRaw->zData[iOfft];
1514 /* Mark as a free block */
1515 SyBigEndianPack16(zRaw,pPage->sHdr.iFree); /* Offset of the next free block */
1516 zRaw += 2;
1517 SyBigEndianPack16(zRaw,nByte);
1518 /* Link */
1519 SyBigEndianPack16(&pPage->pRaw->zData[2/* offset of the first cell */],iOfft);
1520 pPage->sHdr.iFree = iOfft;
1521 pPage->nFree += nByte;
1522 return UNQLITE_OK;
1523}
1524/* Forward declaration */
1525static lhcell * lhFindSibeling(lhcell *pCell);
1526/*
1527 * Unlink a cell.
1528 */
1529static int lhUnlinkCell(lhcell *pCell)
1530{
1531 lhash_kv_engine *pEngine = pCell->pPage->pHash;
1532 lhpage *pPage = pCell->pPage;
1533 sxu16 nByte = L_HASH_CELL_SZ;
1534 lhcell *pPrev;
1535 int rc;
1536 rc = pEngine->pIo->xWrite(pPage->pRaw);
1537 if( rc != UNQLITE_OK ){
1538 return rc;
1539 }
1540 /* Bring the link */
1541 pPrev = lhFindSibeling(pCell);
1542 if( pPrev ){
1543 pPrev->iNext = pCell->iNext;
1544 /* Fix offsets in the page header */
1545 SyBigEndianPack16(&pPage->pRaw->zData[pPrev->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext);
1546 }else{
1547 /* First entry on this page (either master or slave) */
1548 pPage->sHdr.iOfft = pCell->iNext;
1549 /* Update the page header */
1550 SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext);
1551 }
1552 /* Restore cell space */
1553 if( pCell->iOvfl == 0 ){
1554 nByte += (sxu16)(pCell->nData + pCell->nKey);
1555 }
1556 lhRestoreSpace(pPage,pCell->iStart,nByte);
1557 /* Discard the cell from the in-memory hashtable */
1558 lhCellDiscard(pCell);
1559 return UNQLITE_OK;
1560}
1561/*
1562 * Remove a cell and its paylod (key + data).
1563 */
1564static int lhRecordRemove(lhcell *pCell)
1565{
1566 lhash_kv_engine *pEngine = pCell->pPage->pHash;
1567 int rc;
1568 if( pCell->iOvfl > 0){
1569 /* Discard overflow pages */
1570 unqlite_page *pOvfl;
1571 pgno iNext = pCell->iOvfl;
1572 for(;;){
1573 /* Point to the overflow page */
1574 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pOvfl);
1575 if( rc != UNQLITE_OK ){
1576 return rc;
1577 }
1578 /* Next page on the chain */
1579 SyBigEndianUnpack64(pOvfl->zData,&iNext);
1580 /* Restore the page to the free list */
1581 rc = lhRestorePage(pEngine,pOvfl);
1582 if( rc != UNQLITE_OK ){
1583 return rc;
1584 }
1585 /* Unref */
1586 pEngine->pIo->xPageUnref(pOvfl);
1587 if( iNext == 0 ){
1588 break;
1589 }
1590 }
1591 }
1592 /* Unlink the cell */
1593 rc = lhUnlinkCell(pCell);
1594 return rc;
1595}
1596/*
1597 * Find cell sibeling.
1598 */
1599static lhcell * lhFindSibeling(lhcell *pCell)
1600{
1601 lhpage *pPage = pCell->pPage->pMaster;
1602 lhcell *pEntry;
1603 pEntry = pPage->pFirst;
1604 while( pEntry ){
1605 if( pEntry->pPage == pCell->pPage && pEntry->iNext == pCell->iStart ){
1606 /* Sibeling found */
1607 return pEntry;
1608 }
1609 /* Point to the previous entry */
1610 pEntry = pEntry->pPrev;
1611 }
1612 /* Last inserted cell */
1613 return 0;
1614}
1615/*
1616 * Move a cell to a new location with its new data.
1617 */
1618static int lhMoveLocalCell(
1619 lhcell *pCell,
1620 sxu16 iOfft,
1621 const void *pData,
1622 unqlite_int64 nData
1623 )
1624{
1625 sxu16 iKeyOfft = pCell->iStart + L_HASH_CELL_SZ;
1626 lhpage *pPage = pCell->pPage;
1627 lhcell *pSibeling;
1628 pSibeling = lhFindSibeling(pCell);
1629 if( pSibeling ){
1630 /* Fix link */
1631 SyBigEndianPack16(&pPage->pRaw->zData[pSibeling->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext);
1632 pSibeling->iNext = pCell->iNext;
1633 }else{
1634 /* First cell, update page header only */
1635 SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext);
1636 pPage->sHdr.iOfft = pCell->iNext;
1637 }
1638 /* Set the new offset */
1639 pCell->iStart = iOfft;
1640 pCell->nData = (sxu64)nData;
1641 /* Write the cell payload */
1642 lhCellWriteLocalPayload(pCell,(const void *)&pPage->pRaw->zData[iKeyOfft],pCell->nKey,pData,nData);
1643 /* Finally write the cell header */
1644 lhCellWriteHeader(pCell);
1645 /* All done */
1646 return UNQLITE_OK;
1647}
1648/*
1649 * Overwrite an existing record.
1650 */
1651static int lhRecordOverwrite(
1652 lhcell *pCell,
1653 const void *pData,unqlite_int64 nByte
1654 )
1655{
1656 lhash_kv_engine *pEngine = pCell->pPage->pHash;
1657 unsigned char *zRaw,*zRawEnd,*zPayload;
1658 const unsigned char *zPtr,*zEnd;
1659 unqlite_page *pOvfl,*pOld,*pNew;
1660 lhpage *pPage = pCell->pPage;
1661 sxu32 nAvail;
1662 pgno iOvfl;
1663 int rc;
1664 /* Acquire a writer lock on this page */
1665 rc = pEngine->pIo->xWrite(pPage->pRaw);
1666 if( rc != UNQLITE_OK ){
1667 return rc;
1668 }
1669 if( pCell->iOvfl == 0 ){
1670 /* Local payload, try to deal with the free space issues */
1671 zPayload = &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey];
1672 if( pCell->nData == (sxu64)nByte ){
1673 /* Best scenario, simply a memcpy operation */
1674 SyMemcpy(pData,(void *)zPayload,(sxu32)nByte);
1675 }else if( (sxu64)nByte < pCell->nData ){
1676 /* Shorter data, not so ugly */
1677 SyMemcpy(pData,(void *)zPayload,(sxu32)nByte);
1678 /* Update the cell header */
1679 SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],nByte);
1680 /* Restore freespace */
1681 lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ + pCell->nKey + nByte),(sxu16)(pCell->nData - nByte));
1682 /* New data size */
1683 pCell->nData = (sxu64)nByte;
1684 }else{
1685 sxu16 iOfft = 0; /* cc warning */
1686 /* Check if another chunk is available for this cell */
1687 rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + nByte,&iOfft);
1688 if( rc != UNQLITE_OK ){
1689 /* Transfer the payload to an overflow page */
1690 rc = lhCellWriteOvflPayload(pCell,&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey,pData,nByte,(const void *)0);
1691 if( rc != UNQLITE_OK ){
1692 return rc;
1693 }
1694 /* Update the cell header */
1695 SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],(sxu64)nByte);
1696 /* Restore freespace */
1697 lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData));
1698 /* New data size */
1699 pCell->nData = (sxu64)nByte;
1700 }else{
1701 sxu16 iOldOfft = pCell->iStart;
1702 sxu32 iOld = (sxu32)pCell->nData;
1703 /* Space is available, transfer the cell */
1704 lhMoveLocalCell(pCell,iOfft,pData,nByte);
1705 /* Restore cell space */
1706 lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld));
1707 }
1708 }
1709 return UNQLITE_OK;
1710 }
1711 /* Point to the overflow page */
1712 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl);
1713 if( rc != UNQLITE_OK ){
1714 return rc;
1715 }
1716 /* Relase all old overflow pages first */
1717 SyBigEndianUnpack64(pOvfl->zData,&iOvfl);
1718 pOld = pOvfl;
1719 for(;;){
1720 if( iOvfl == 0 ){
1721 /* No more overflow pages on the chain */
1722 break;
1723 }
1724 /* Point to the target page */
1725 if( UNQLITE_OK != pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOld) ){
1726 /* Not so fatal if something goes wrong here */
1727 break;
1728 }
1729 /* Next overflow page to be released */
1730 SyBigEndianUnpack64(pOld->zData,&iOvfl);
1731 if( pOld != pOvfl ){ /* xx: chm is maniac */
1732 /* Restore the page to the free list */
1733 lhRestorePage(pEngine,pOld);
1734 /* Unref */
1735 pEngine->pIo->xPageUnref(pOld);
1736 }
1737 }
1738 /* Point to the data offset */
1739 zRaw = &pOvfl->zData[pCell->iDataOfft];
1740 zRawEnd = &pOvfl->zData[pEngine->iPageSize];
1741 /* The data to be stored */
1742 zPtr = (const unsigned char *)pData;
1743 zEnd = &zPtr[nByte];
1744 /* Start the overwrite process */
1745 /* Acquire a writer lock */
1746 rc = pEngine->pIo->xWrite(pOvfl);
1747 if( rc != UNQLITE_OK ){
1748 return rc;
1749 }
1750 SyBigEndianPack64(pOvfl->zData,0);
1751 for(;;){
1752 sxu32 nLen;
1753 if( zPtr >= zEnd ){
1754 break;
1755 }
1756 if( zRaw >= zRawEnd ){
1757 /* Acquire a new page */
1758 rc = lhAcquirePage(pEngine,&pNew);
1759 if( rc != UNQLITE_OK ){
1760 return rc;
1761 }
1762 rc = pEngine->pIo->xWrite(pNew);
1763 if( rc != UNQLITE_OK ){
1764 return rc;
1765 }
1766 /* Link */
1767 SyBigEndianPack64(pOvfl->zData,pNew->pgno);
1768 pEngine->pIo->xPageUnref(pOvfl);
1769 SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
1770 pOvfl = pNew;
1771 zRaw = &pNew->zData[8];
1772 zRawEnd = &pNew->zData[pEngine->iPageSize];
1773 }
1774 nAvail = (sxu32)(zRawEnd-zRaw);
1775 nLen = (sxu32)(zEnd-zPtr);
1776 if( nLen > nAvail ){
1777 nLen = nAvail;
1778 }
1779 SyMemcpy((const void *)zPtr,(void *)zRaw,nLen);
1780 /* Synchronize pointers */
1781 zPtr += nLen;
1782 zRaw += nLen;
1783 }
1784 /* Unref the last overflow page */
1785 pEngine->pIo->xPageUnref(pOvfl);
1786 /* Finally, update the cell header */
1787 pCell->nData = (sxu64)nByte;
1788 SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData);
1789 /* All done */
1790 return UNQLITE_OK;
1791}
1792/*
1793 * Append data to an existing record.
1794 */
1795static int lhRecordAppend(
1796 lhcell *pCell,
1797 const void *pData,unqlite_int64 nByte
1798 )
1799{
1800 lhash_kv_engine *pEngine = pCell->pPage->pHash;
1801 const unsigned char *zPtr,*zEnd;
1802 lhpage *pPage = pCell->pPage;
1803 unsigned char *zRaw,*zRawEnd;
1804 unqlite_page *pOvfl,*pNew;
1805 sxu64 nDatalen;
1806 sxu32 nAvail;
1807 pgno iOvfl;
1808 int rc;
1809 if( pCell->nData + nByte < pCell->nData ){
1810 /* Overflow */
1811 pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow");
1812 return UNQLITE_LIMIT;
1813 }
1814 /* Acquire a writer lock on this page */
1815 rc = pEngine->pIo->xWrite(pPage->pRaw);
1816 if( rc != UNQLITE_OK ){
1817 return rc;
1818 }
1819 if( pCell->iOvfl == 0 ){
1820 sxu16 iOfft = 0; /* cc warning */
1821 /* Local payload, check for a bigger place */
1822 rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + pCell->nData + nByte,&iOfft);
1823 if( rc != UNQLITE_OK ){
1824 /* Transfer the payload to an overflow page */
1825 rc = lhCellWriteOvflPayload(pCell,
1826 &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey,
1827 (const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],pCell->nData,
1828 pData,nByte,
1829 (const void *)0);
1830 if( rc != UNQLITE_OK ){
1831 return rc;
1832 }
1833 /* Update the cell header */
1834 SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData + nByte);
1835 /* Restore freespace */
1836 lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData));
1837 /* New data size */
1838 pCell->nData += nByte;
1839 }else{
1840 sxu16 iOldOfft = pCell->iStart;
1841 sxu32 iOld = (sxu32)pCell->nData;
1842 SyBlob sWorker;
1843 SyBlobInit(&sWorker,&pEngine->sAllocator);
1844 /* Copy the old data */
1845 rc = SyBlobAppend(&sWorker,(const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],(sxu32)pCell->nData);
1846 if( rc == SXRET_OK ){
1847 /* Append the new data */
1848 rc = SyBlobAppend(&sWorker,pData,(sxu32)nByte);
1849 }
1850 if( rc != UNQLITE_OK ){
1851 SyBlobRelease(&sWorker);
1852 return rc;
1853 }
1854 /* Space is available, transfer the cell */
1855 lhMoveLocalCell(pCell,iOfft,SyBlobData(&sWorker),(unqlite_int64)SyBlobLength(&sWorker));
1856 /* Restore cell space */
1857 lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld));
1858 /* All done */
1859 SyBlobRelease(&sWorker);
1860 }
1861 return UNQLITE_OK;
1862 }
1863 /* Point to the overflow page which hold the data */
1864 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl);
1865 if( rc != UNQLITE_OK ){
1866 return rc;
1867 }
1868 /* Next overflow page in the chain */
1869 SyBigEndianUnpack64(pOvfl->zData,&iOvfl);
1870 /* Point to the end of the chunk */
1871 zRaw = &pOvfl->zData[pCell->iDataOfft];
1872 zRawEnd = &pOvfl->zData[pEngine->iPageSize];
1873 nDatalen = pCell->nData;
1874 nAvail = (sxu32)(zRawEnd - zRaw);
1875 for(;;){
1876 if( zRaw >= zRawEnd ){
1877 if( iOvfl == 0 ){
1878 /* Cant happen */
1879 pEngine->pIo->xErr(pEngine->pIo->pHandle,"Corrupt overflow page");
1880 return UNQLITE_CORRUPT;
1881 }
1882 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pNew);
1883 if( rc != UNQLITE_OK ){
1884 return rc;
1885 }
1886 /* Next overflow page on the chain */
1887 SyBigEndianUnpack64(pNew->zData,&iOvfl);
1888 /* Unref the previous overflow page */
1889 pEngine->pIo->xPageUnref(pOvfl);
1890 /* Point to the new chunk */
1891 zRaw = &pNew->zData[8];
1892 zRawEnd = &pNew->zData[pCell->pPage->pHash->iPageSize];
1893 nAvail = L_HASH_OVERFLOW_SIZE(pCell->pPage->pHash->iPageSize);
1894 pOvfl = pNew;
1895 }
1896 if( (sxu64)nAvail > nDatalen ){
1897 zRaw += nDatalen;
1898 break;
1899 }else{
1900 nDatalen -= nAvail;
1901 }
1902 zRaw += nAvail;
1903 }
1904 /* Start the append process */
1905 zPtr = (const unsigned char *)pData;
1906 zEnd = &zPtr[nByte];
1907 /* Acquire a writer lock */
1908 rc = pEngine->pIo->xWrite(pOvfl);
1909 if( rc != UNQLITE_OK ){
1910 return rc;
1911 }
1912 for(;;){
1913 sxu32 nLen;
1914 if( zPtr >= zEnd ){
1915 break;
1916 }
1917 if( zRaw >= zRawEnd ){
1918 /* Acquire a new page */
1919 rc = lhAcquirePage(pEngine,&pNew);
1920 if( rc != UNQLITE_OK ){
1921 return rc;
1922 }
1923 rc = pEngine->pIo->xWrite(pNew);
1924 if( rc != UNQLITE_OK ){
1925 return rc;
1926 }
1927 /* Link */
1928 SyBigEndianPack64(pOvfl->zData,pNew->pgno);
1929 pEngine->pIo->xPageUnref(pOvfl);
1930 SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */
1931 pOvfl = pNew;
1932 zRaw = &pNew->zData[8];
1933 zRawEnd = &pNew->zData[pEngine->iPageSize];
1934 }
1935 nAvail = (sxu32)(zRawEnd-zRaw);
1936 nLen = (sxu32)(zEnd-zPtr);
1937 if( nLen > nAvail ){
1938 nLen = nAvail;
1939 }
1940 SyMemcpy((const void *)zPtr,(void *)zRaw,nLen);
1941 /* Synchronize pointers */
1942 zPtr += nLen;
1943 zRaw += nLen;
1944 }
1945 /* Unref the last overflow page */
1946 pEngine->pIo->xPageUnref(pOvfl);
1947 /* Finally, update the cell header */
1948 pCell->nData += nByte;
1949 SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData);
1950 /* All done */
1951 return UNQLITE_OK;
1952}
1953/*
1954 * A write privilege have been acquired on this page.
1955 * Mark it as an empty page (No cells).
1956 */
1957static int lhSetEmptyPage(lhpage *pPage)
1958{
1959 unsigned char *zRaw = pPage->pRaw->zData;
1960 lhphdr *pHeader = &pPage->sHdr;
1961 sxu16 nByte;
1962 int rc;
1963 /* Acquire a writer lock */
1964 rc = pPage->pHash->pIo->xWrite(pPage->pRaw);
1965 if( rc != UNQLITE_OK ){
1966 return rc;
1967 }
1968 /* Offset of the first cell */
1969 SyBigEndianPack16(zRaw,0);
1970 zRaw += 2;
1971 /* Offset of the first free block */
1972 pHeader->iFree = L_HASH_PAGE_HDR_SZ;
1973 SyBigEndianPack16(zRaw,L_HASH_PAGE_HDR_SZ);
1974 zRaw += 2;
1975 /* Slave page number */
1976 SyBigEndianPack64(zRaw,0);
1977 zRaw += 8;
1978 /* Fill the free block */
1979 SyBigEndianPack16(zRaw,0); /* Offset of the next free block */
1980 zRaw += 2;
1981 nByte = (sxu16)L_HASH_MX_FREE_SPACE(pPage->pHash->iPageSize);
1982 SyBigEndianPack16(zRaw,nByte);
1983 pPage->nFree = nByte;
1984 /* Do not add this page to the hot dirty list */
1985 pPage->pHash->pIo->xDontMkHot(pPage->pRaw);
1986 return UNQLITE_OK;
1987}
1988/* Forward declaration */
1989static int lhSlaveStore(
1990 lhpage *pPage,
1991 const void *pKey,sxu32 nKeyLen,
1992 const void *pData,unqlite_int64 nDataLen,
1993 sxu32 nHash
1994 );
1995/*
1996 * Store a cell and its payload in a given page.
1997 */
1998static int lhStoreCell(
1999 lhpage *pPage, /* Target page */
2000 const void *pKey,sxu32 nKeyLen, /* Payload: Key */
2001 const void *pData,unqlite_int64 nDataLen, /* Payload: Data */
2002 sxu32 nHash, /* Hash of the key */
2003 int auto_append /* Auto append a slave page if full */
2004 )
2005{
2006 lhash_kv_engine *pEngine = pPage->pHash;
2007 int iNeedOvfl = 0; /* Need overflow page for this cell and its payload*/
2008 lhcell *pCell;
2009 sxu16 nOfft;
2010 int rc;
2011 /* Acquire a writer lock on this page first */
2012 rc = pEngine->pIo->xWrite(pPage->pRaw);
2013 if( rc != UNQLITE_OK ){
2014 return rc;
2015 }
2016 /* Check for a free block */
2017 rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ+nKeyLen+nDataLen,&nOfft);
2018 if( rc != UNQLITE_OK ){
2019 /* Check for a free block to hold a single cell only (without payload) */
2020 rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft);
2021 if( rc != UNQLITE_OK ){
2022 if( !auto_append ){
2023 /* A split must be done */
2024 return UNQLITE_FULL;
2025 }else{
2026 /* Store this record in a slave page */
2027 rc = lhSlaveStore(pPage,pKey,nKeyLen,pData,nDataLen,nHash);
2028 return rc;
2029 }
2030 }
2031 iNeedOvfl = 1;
2032 }
2033 /* Allocate a new cell instance */
2034 pCell = lhNewCell(pEngine,pPage);
2035 if( pCell == 0 ){
2036 pEngine->pIo->xErr(pEngine->pIo->pHandle,"KV store is running out of memory");
2037 return UNQLITE_NOMEM;
2038 }
2039 /* Fill-in the structure */
2040 pCell->iStart = nOfft;
2041 pCell->nKey = nKeyLen;
2042 pCell->nData = (sxu64)nDataLen;
2043 pCell->nHash = nHash;
2044 if( nKeyLen < 262144 /* 256 KB */ ){
2045 /* Keep the key in-memory for fast lookup */
2046 SyBlobAppend(&pCell->sKey,pKey,nKeyLen);
2047 }
2048 /* Link the cell */
2049 rc = lhInstallCell(pCell);
2050 if( rc != UNQLITE_OK ){
2051 return rc;
2052 }
2053 /* Write the payload */
2054 if( iNeedOvfl ){
2055 rc = lhCellWriteOvflPayload(pCell,pKey,nKeyLen,pData,nDataLen,(const void *)0);
2056 if( rc != UNQLITE_OK ){
2057 lhCellDiscard(pCell);
2058 return rc;
2059 }
2060 }else{
2061 lhCellWriteLocalPayload(pCell,pKey,nKeyLen,pData,nDataLen);
2062 }
2063 /* Finally, Write the cell header */
2064 lhCellWriteHeader(pCell);
2065 /* All done */
2066 return UNQLITE_OK;
2067}
2068/*
2069 * Find a slave page capable of hosting the given amount.
2070 */
2071static int lhFindSlavePage(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft,lhpage **ppSlave)
2072{
2073 lhash_kv_engine *pEngine = pPage->pHash;
2074 lhpage *pMaster = pPage->pMaster;
2075 lhpage *pSlave = pMaster->pSlave;
2076 unqlite_page *pRaw;
2077 lhpage *pNew;
2078 sxu16 iOfft;
2079 sxi32 i;
2080 int rc;
2081 /* Look for an already attached slave page */
2082 for( i = 0 ; i < pMaster->iSlave ; ++i ){
2083 /* Find a free chunk big enough */
2084 sxu16 size = L_HASH_CELL_SZ + nAmount;
2085 rc = lhAllocateSpace(pSlave,size,&iOfft);
2086 if( rc != UNQLITE_OK ){
2087 /* A space for cell header only */
2088 size = L_HASH_CELL_SZ;
2089 rc = lhAllocateSpace(pSlave,size,&iOfft);
2090 }
2091 if( rc == UNQLITE_OK ){
2092 /* All done */
2093 if( pOfft ){
2094 *pOfft = iOfft;
2095 }else{
2096 rc = lhRestoreSpace(pSlave, iOfft, size);
2097 }
2098 *ppSlave = pSlave;
2099 return rc;
2100 }
2101 /* Point to the next slave page */
2102 pSlave = pSlave->pNextSlave;
2103 }
2104 /* Acquire a new slave page */
2105 rc = lhAcquirePage(pEngine,&pRaw);
2106 if( rc != UNQLITE_OK ){
2107 return rc;
2108 }
2109 /* Last slave page */
2110 pSlave = pMaster->pSlave;
2111 if( pSlave == 0 ){
2112 /* First slave page */
2113 pSlave = pMaster;
2114 }
2115 /* Initialize the page */
2116 pNew = lhNewPage(pEngine,pRaw,pMaster);
2117 if( pNew == 0 ){
2118 return UNQLITE_NOMEM;
2119 }
2120 /* Mark as an empty page */
2121 rc = lhSetEmptyPage(pNew);
2122 if( rc != UNQLITE_OK ){
2123 goto fail;
2124 }
2125 if( pOfft ){
2126 /* Look for a free block */
2127 if( UNQLITE_OK != lhAllocateSpace(pNew,L_HASH_CELL_SZ+nAmount,&iOfft) ){
2128 /* Cell header only */
2129 lhAllocateSpace(pNew,L_HASH_CELL_SZ,&iOfft); /* Never fail */
2130 }
2131 *pOfft = iOfft;
2132 }
2133 /* Link this page to the previous slave page */
2134 rc = pEngine->pIo->xWrite(pSlave->pRaw);
2135 if( rc != UNQLITE_OK ){
2136 goto fail;
2137 }
2138 /* Reflect in the page header */
2139 SyBigEndianPack64(&pSlave->pRaw->zData[2/*Cell offset*/+2/*Free block offset*/],pRaw->pgno);
2140 pSlave->sHdr.iSlave = pRaw->pgno;
2141 /* All done */
2142 *ppSlave = pNew;
2143 return UNQLITE_OK;
2144fail:
2145 pEngine->pIo->xPageUnref(pNew->pRaw); /* pNew will be released in this call */
2146 return rc;
2147
2148}
2149/*
2150 * Perform a store operation in a slave page.
2151 */
2152static int lhSlaveStore(
2153 lhpage *pPage, /* Master page */
2154 const void *pKey,sxu32 nKeyLen, /* Payload: key */
2155 const void *pData,unqlite_int64 nDataLen, /* Payload: data */
2156 sxu32 nHash /* Hash of the key */
2157 )
2158{
2159 lhpage *pSlave;
2160 int rc;
2161 /* Find a slave page */
2162 rc = lhFindSlavePage(pPage,nKeyLen + nDataLen,0,&pSlave);
2163 if( rc != UNQLITE_OK ){
2164 return rc;
2165 }
2166 /* Perform the insertion in the slave page */
2167 rc = lhStoreCell(pSlave,pKey,nKeyLen,pData,nDataLen,nHash,1);
2168 return rc;
2169}
2170/*
2171 * Transfer a cell to a new page (either a master or slave).
2172 */
2173static int lhTransferCell(lhcell *pTarget,lhpage *pPage)
2174{
2175 lhcell *pCell;
2176 sxu16 nOfft;
2177 int rc;
2178 /* Check for a free block to hold a single cell only */
2179 rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft);
2180 if( rc != UNQLITE_OK ){
2181 /* Store in a slave page */
2182 rc = lhFindSlavePage(pPage,L_HASH_CELL_SZ,&nOfft,&pPage);
2183 if( rc != UNQLITE_OK ){
2184 return rc;
2185 }
2186 }
2187 /* Allocate a new cell instance */
2188 pCell = lhNewCell(pPage->pHash,pPage);
2189 if( pCell == 0 ){
2190 return UNQLITE_NOMEM;
2191 }
2192 /* Fill-in the structure */
2193 pCell->iStart = nOfft;
2194 pCell->nData = pTarget->nData;
2195 pCell->nKey = pTarget->nKey;
2196 pCell->iOvfl = pTarget->iOvfl;
2197 pCell->iDataOfft = pTarget->iDataOfft;
2198 pCell->iDataPage = pTarget->iDataPage;
2199 pCell->nHash = pTarget->nHash;
2200 SyBlobDup(&pTarget->sKey,&pCell->sKey);
2201 /* Link the cell */
2202 rc = lhInstallCell(pCell);
2203 if( rc != UNQLITE_OK ){
2204 return rc;
2205 }
2206 /* Finally, Write the cell header */
2207 lhCellWriteHeader(pCell);
2208 /* All done */
2209 return UNQLITE_OK;
2210}
2211/*
2212 * Perform a page split.
2213 */
2214static int lhPageSplit(
2215 lhpage *pOld, /* Page to be split */
2216 lhpage *pNew, /* New page */
2217 pgno split_bucket, /* Current split bucket */
2218 pgno high_mask /* High mask (Max split bucket - 1) */
2219 )
2220{
2221 lhcell *pCell,*pNext;
2222 SyBlob sWorker;
2223 pgno iBucket;
2224 int rc;
2225 SyBlobInit(&sWorker,&pOld->pHash->sAllocator);
2226 /* Perform the split */
2227 pCell = pOld->pList;
2228 for( ;; ){
2229 if( pCell == 0 ){
2230 /* No more cells */
2231 break;
2232 }
2233 /* Obtain the new logical bucket */
2234 iBucket = pCell->nHash & high_mask;
2235 pNext = pCell->pNext;
2236 if( iBucket != split_bucket){
2237 rc = UNQLITE_OK;
2238 if( pCell->iOvfl ){
2239 /* Transfer the cell only */
2240 rc = lhTransferCell(pCell,pNew);
2241 }else{
2242 /* Transfer the cell and its payload */
2243 SyBlobReset(&sWorker);
2244 if( SyBlobLength(&pCell->sKey) < 1 ){
2245 /* Consume the key */
2246 rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,0);
2247 if( rc != UNQLITE_OK ){
2248 goto fail;
2249 }
2250 }
2251 /* Consume the data (Very small data < 65k) */
2252 rc = lhConsumeCellData(pCell,unqliteDataConsumer,&sWorker);
2253 if( rc != UNQLITE_OK ){
2254 goto fail;
2255 }
2256 /* Perform the transfer */
2257 rc = lhStoreCell(
2258 pNew,
2259 SyBlobData(&pCell->sKey),(int)SyBlobLength(&pCell->sKey),
2260 SyBlobData(&sWorker),SyBlobLength(&sWorker),
2261 pCell->nHash,
2262 1
2263 );
2264 }
2265 if( rc != UNQLITE_OK ){
2266 goto fail;
2267 }
2268 /* Discard the cell from the old page */
2269 lhUnlinkCell(pCell);
2270 }
2271 /* Point to the next cell */
2272 pCell = pNext;
2273 }
2274 /* All done */
2275 rc = UNQLITE_OK;
2276fail:
2277 SyBlobRelease(&sWorker);
2278 return rc;
2279}
2280/*
2281 * Perform the infamous linear hash split operation.
2282 */
2283static int lhSplit(lhpage *pTarget,int *pRetry)
2284{
2285 lhash_kv_engine *pEngine = pTarget->pHash;
2286 lhash_bmap_rec *pRec;
2287 lhpage *pOld,*pNew;
2288 unqlite_page *pRaw;
2289 int rc;
2290 /* Get the real page number of the bucket to split */
2291 pRec = lhMapFindBucket(pEngine,pEngine->split_bucket);
2292 if( pRec == 0 ){
2293 /* Can't happen */
2294 return UNQLITE_CORRUPT;
2295 }
2296 /* Load the page to be split */
2297 rc = lhLoadPage(pEngine,pRec->iReal,0,&pOld,0);
2298 if( rc != UNQLITE_OK ){
2299 return rc;
2300 }
2301 /* Request a new page */
2302 rc = lhAcquirePage(pEngine,&pRaw);
2303 if( rc != UNQLITE_OK ){
2304 return rc;
2305 }
2306 /* Initialize the page */
2307 pNew = lhNewPage(pEngine,pRaw,0);
2308 if( pNew == 0 ){
2309 return UNQLITE_NOMEM;
2310 }
2311 /* Mark as an empty page */
2312 rc = lhSetEmptyPage(pNew);
2313 if( rc != UNQLITE_OK ){
2314 goto fail;
2315 }
2316 /* Install and write the logical map record */
2317 rc = lhMapWriteRecord(pEngine,
2318 pEngine->split_bucket + pEngine->max_split_bucket,
2319 pRaw->pgno
2320 );
2321 if( rc != UNQLITE_OK ){
2322 goto fail;
2323 }
2324 if( pTarget->pRaw->pgno == pOld->pRaw->pgno ){
2325 *pRetry = 1;
2326 }
2327 /* Perform the split */
2328 rc = lhPageSplit(pOld,pNew,pEngine->split_bucket,pEngine->nmax_split_nucket - 1);
2329 if( rc != UNQLITE_OK ){
2330 goto fail;
2331 }
2332 /* Update the database header */
2333 pEngine->split_bucket++;
2334 /* Acquire a writer lock on the first page */
2335 rc = pEngine->pIo->xWrite(pEngine->pHeader);
2336 if( rc != UNQLITE_OK ){
2337 return rc;
2338 }
2339 if( pEngine->split_bucket >= pEngine->max_split_bucket ){
2340 /* Increment the generation number */
2341 pEngine->split_bucket = 0;
2342 pEngine->max_split_bucket = pEngine->nmax_split_nucket;
2343 pEngine->nmax_split_nucket <<= 1;
2344 if( !pEngine->nmax_split_nucket ){
2345 /* If this happen to your installation, please tell us <chm@symisc.net> */
2346 pEngine->pIo->xErr(pEngine->pIo->pHandle,"Database page (64-bit integer) limit reached");
2347 return UNQLITE_LIMIT;
2348 }
2349 /* Reflect in the page header */
2350 SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket);
2351 SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/+8/*Split bucket*/],pEngine->max_split_bucket);
2352 }else{
2353 /* Modify only the split bucket */
2354 SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket);
2355 }
2356 /* All done */
2357 return UNQLITE_OK;
2358fail:
2359 pEngine->pIo->xPageUnref(pNew->pRaw);
2360 return rc;
2361}
2362/*
2363 * Store a record in the target page.
2364 */
2365static int lhRecordInstall(
2366 lhpage *pPage, /* Target page */
2367 sxu32 nHash, /* Hash of the key */
2368 const void *pKey,sxu32 nKeyLen, /* Payload: Key */
2369 const void *pData,unqlite_int64 nDataLen /* Payload: Data */
2370 )
2371{
2372 int rc;
2373 rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,0);
2374 if( rc == UNQLITE_FULL ){
2375 int do_retry = 0;
2376 /* Split */
2377 rc = lhSplit(pPage,&do_retry);
2378 if( rc == UNQLITE_OK ){
2379 if( do_retry ){
2380 /* Re-calculate logical bucket number */
2381 return SXERR_RETRY;
2382 }
2383 /* Perform the store */
2384 rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1);
2385 }
2386 }
2387 return rc;
2388}
2389/*
2390 * Insert a record (Either overwrite or append operation) in our database.
2391 */
2392static int lh_record_insert(
2393 unqlite_kv_engine *pKv, /* KV store */
2394 const void *pKey,sxu32 nKeyLen, /* Payload: Key */
2395 const void *pData,unqlite_int64 nDataLen, /* Payload: data */
2396 int is_append /* True for an append operation */
2397 )
2398{
2399 lhash_kv_engine *pEngine = (lhash_kv_engine *)pKv;
2400 lhash_bmap_rec *pRec;
2401 unqlite_page *pRaw;
2402 lhpage *pPage;
2403 lhcell *pCell;
2404 pgno iBucket;
2405 sxu32 nHash;
2406 int iCnt;
2407 int rc;
2408
2409 /* Acquire the first page (DB hash Header) so that everything gets loaded autmatically */
2410 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0);
2411 if( rc != UNQLITE_OK ){
2412 return rc;
2413 }
2414 iCnt = 0;
2415 /* Compute the hash of the key first */
2416 nHash = pEngine->xHash(pKey,(sxu32)nKeyLen);
2417retry:
2418 /* Extract the logical bucket number */
2419 iBucket = nHash & (pEngine->nmax_split_nucket - 1);
2420 if( iBucket >= pEngine->split_bucket + pEngine->max_split_bucket ){
2421 /* Low mask */
2422 iBucket = nHash & (pEngine->max_split_bucket - 1);
2423 }
2424 /* Map the logical bucket number to real page number */
2425 pRec = lhMapFindBucket(pEngine,iBucket);
2426 if( pRec == 0 ){
2427 /* Request a new page */
2428 rc = lhAcquirePage(pEngine,&pRaw);
2429 if( rc != UNQLITE_OK ){
2430 return rc;
2431 }
2432 /* Initialize the page */
2433 pPage = lhNewPage(pEngine,pRaw,0);
2434 if( pPage == 0 ){
2435 return UNQLITE_NOMEM;
2436 }
2437 /* Mark as an empty page */
2438 rc = lhSetEmptyPage(pPage);
2439 if( rc != UNQLITE_OK ){
2440 pEngine->pIo->xPageUnref(pRaw); /* pPage will be released during this call */
2441 return rc;
2442 }
2443 /* Store the cell */
2444 rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1);
2445 if( rc == UNQLITE_OK ){
2446 /* Install and write the logical map record */
2447 rc = lhMapWriteRecord(pEngine,iBucket,pRaw->pgno);
2448 }
2449 pEngine->pIo->xPageUnref(pRaw);
2450 return rc;
2451 }else{
2452 /* Load the page */
2453 rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0);
2454 if( rc != UNQLITE_OK ){
2455 /* IO error, unlikely scenario */
2456 return rc;
2457 }
2458 /* Do not add this page to the hot dirty list */
2459 pEngine->pIo->xDontMkHot(pPage->pRaw);
2460 /* Lookup for the cell */
2461 pCell = lhFindCell(pPage,pKey,(sxu32)nKeyLen,nHash);
2462 if( pCell == 0 ){
2463 /* Create the record */
2464 rc = lhRecordInstall(pPage,nHash,pKey,nKeyLen,pData,nDataLen);
2465 if( rc == SXERR_RETRY && iCnt++ < 2 ){
2466 rc = UNQLITE_OK;
2467 goto retry;
2468 }
2469 }else{
2470 if( is_append ){
2471 /* Append operation */
2472 rc = lhRecordAppend(pCell,pData,nDataLen);
2473 }else{
2474 /* Overwrite old value */
2475 rc = lhRecordOverwrite(pCell,pData,nDataLen);
2476 }
2477 }
2478 pEngine->pIo->xPageUnref(pPage->pRaw);
2479 }
2480 return rc;
2481}
2482/*
2483 * Replace method.
2484 */
2485static int lhash_kv_replace(
2486 unqlite_kv_engine *pKv,
2487 const void *pKey,int nKeyLen,
2488 const void *pData,unqlite_int64 nDataLen
2489 )
2490{
2491 int rc;
2492 rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,0);
2493 return rc;
2494}
2495/*
2496 * Append method.
2497 */
2498static int lhash_kv_append(
2499 unqlite_kv_engine *pKv,
2500 const void *pKey,int nKeyLen,
2501 const void *pData,unqlite_int64 nDataLen
2502 )
2503{
2504 int rc;
2505 rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,1);
2506 return rc;
2507}
2508/*
2509 * Write the hash header (Page one).
2510 */
2511static int lhash_write_header(lhash_kv_engine *pEngine,unqlite_page *pHeader)
2512{
2513 unsigned char *zRaw = pHeader->zData;
2514 lhash_bmap_page *pMap;
2515
2516 pEngine->pHeader = pHeader;
2517 /* 4 byte magic number */
2518 SyBigEndianPack32(zRaw,pEngine->nMagic);
2519 zRaw += 4;
2520 /* 4 byte hash value to identify a valid hash function */
2521 SyBigEndianPack32(zRaw,pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1));
2522 zRaw += 4;
2523 /* List of free pages: Empty */
2524 SyBigEndianPack64(zRaw,0);
2525 zRaw += 8;
2526 /* Current split bucket */
2527 SyBigEndianPack64(zRaw,pEngine->split_bucket);
2528 zRaw += 8;
2529 /* Maximum split bucket */
2530 SyBigEndianPack64(zRaw,pEngine->max_split_bucket);
2531 zRaw += 8;
2532 /* Initialiaze the bucket map */
2533 pMap = &pEngine->sPageMap;
2534 /* Fill in the structure */
2535 pMap->iNum = pHeader->pgno;
2536 /* Next page in the bucket map */
2537 SyBigEndianPack64(zRaw,0);
2538 zRaw += 8;
2539 /* Total number of records in the bucket map */
2540 SyBigEndianPack32(zRaw,0);
2541 zRaw += 4;
2542 pMap->iPtr = (sxu16)(zRaw - pHeader->zData);
2543 /* All done */
2544 return UNQLITE_OK;
2545 }
2546/*
2547 * Exported: xOpen() method.
2548 */
2549static int lhash_kv_open(unqlite_kv_engine *pEngine,pgno dbSize)
2550{
2551 lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine;
2552 unqlite_page *pHeader;
2553 int rc;
2554 if( dbSize < 1 ){
2555 /* A new database, create the header */
2556 rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pHeader);
2557 if( rc != UNQLITE_OK ){
2558 return rc;
2559 }
2560 /* Acquire a writer lock */
2561 rc = pEngine->pIo->xWrite(pHeader);
2562 if( rc != UNQLITE_OK ){
2563 return rc;
2564 }
2565 /* Write the hash header */
2566 rc = lhash_write_header(pHash,pHeader);
2567 if( rc != UNQLITE_OK ){
2568 return rc;
2569 }
2570 }else{
2571 /* Acquire the page one of the database */
2572 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,&pHeader);
2573 if( rc != UNQLITE_OK ){
2574 return rc;
2575 }
2576 /* Read the database header */
2577 rc = lhash_read_header(pHash,pHeader);
2578 if( rc != UNQLITE_OK ){
2579 return rc;
2580 }
2581 }
2582 return UNQLITE_OK;
2583}
2584/*
2585 * Release a master or slave page. (xUnpin callback).
2586 */
2587static void lhash_page_release(void *pUserData)
2588{
2589 lhpage *pPage = (lhpage *)pUserData;
2590 lhash_kv_engine *pEngine = pPage->pHash;
2591 lhcell *pNext,*pCell = pPage->pList;
2592 unqlite_page *pRaw = pPage->pRaw;
2593 sxu32 n;
2594 /* Drop in-memory cells */
2595 for( n = 0 ; n < pPage->nCell ; ++n ){
2596 pNext = pCell->pNext;
2597 SyBlobRelease(&pCell->sKey);
2598 /* Release the cell instance */
2599 SyMemBackendPoolFree(&pEngine->sAllocator,(void *)pCell);
2600 /* Point to the next entry */
2601 pCell = pNext;
2602 }
2603 if( pPage->apCell ){
2604 /* Release the cell table */
2605 SyMemBackendFree(&pEngine->sAllocator,(void *)pPage->apCell);
2606 }
2607 /* Finally, release the whole page */
2608 SyMemBackendPoolFree(&pEngine->sAllocator,pPage);
2609 pRaw->pUserData = 0;
2610}
2611/*
2612 * Default hash function (DJB).
2613 */
2614static sxu32 lhash_bin_hash(const void *pSrc,sxu32 nLen)
2615{
2616 register unsigned char *zIn = (unsigned char *)pSrc;
2617 unsigned char *zEnd;
2618 sxu32 nH = 5381;
2619 if( nLen > 2048 /* 2K */ ){
2620 nLen = 2048;
2621 }
2622 zEnd = &zIn[nLen];
2623 for(;;){
2624 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2625 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2626 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2627 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
2628 }
2629 return nH;
2630}
2631/*
2632 * Exported: xInit() method.
2633 * Initialize the Key value storage engine.
2634 */
2635static int lhash_kv_init(unqlite_kv_engine *pEngine,int iPageSize)
2636{
2637 lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine;
2638 int rc;
2639
2640 /* This structure is always zeroed, go to the initialization directly */
2641 SyMemBackendInitFromParent(&pHash->sAllocator,unqliteExportMemBackend());
2642#if defined(UNQLITE_ENABLE_THREADS)
2643 /* Already protected by the upper layers */
2644 SyMemBackendDisbaleMutexing(&pHash->sAllocator);
2645#endif
2646 pHash->iPageSize = iPageSize;
2647 /* Default hash function */
2648 pHash->xHash = lhash_bin_hash;
2649 /* Default comparison function */
2650 pHash->xCmp = SyMemcmp;
2651 /* Allocate a new record map */
2652 pHash->nBuckSize = 32;
2653 pHash->apMap = (lhash_bmap_rec **)SyMemBackendAlloc(&pHash->sAllocator,pHash->nBuckSize *sizeof(lhash_bmap_rec *));
2654 if( pHash->apMap == 0 ){
2655 rc = UNQLITE_NOMEM;
2656 goto err;
2657 }
2658 /* Zero the table */
2659 SyZero(pHash->apMap,pHash->nBuckSize * sizeof(lhash_bmap_rec *));
2660 /* Linear hashing components */
2661 pHash->split_bucket = 0; /* Logical not real bucket number */
2662 pHash->max_split_bucket = 1;
2663 pHash->nmax_split_nucket = 2;
2664 pHash->nMagic = L_HASH_MAGIC;
2665 /* Install the cache unpin and reload callbacks */
2666 pHash->pIo->xSetUnpin(pHash->pIo->pHandle,lhash_page_release);
2667 pHash->pIo->xSetReload(pHash->pIo->pHandle,lhash_page_release);
2668 return UNQLITE_OK;
2669err:
2670 SyMemBackendRelease(&pHash->sAllocator);
2671 return rc;
2672}
2673/*
2674 * Exported: xRelease() method.
2675 * Release the Key value storage engine.
2676 */
2677static void lhash_kv_release(unqlite_kv_engine *pEngine)
2678{
2679 lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine;
2680 /* Release the private memory backend */
2681 SyMemBackendRelease(&pHash->sAllocator);
2682}
2683/*
2684 * Exported: xConfig() method.
2685 * Configure the linear hash KV store.
2686 */
2687static int lhash_kv_config(unqlite_kv_engine *pEngine,int op,va_list ap)
2688{
2689 lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine;
2690 int rc = UNQLITE_OK;
2691 switch(op){
2692 case UNQLITE_KV_CONFIG_HASH_FUNC: {
2693 /* Default hash function */
2694 if( pHash->nBuckRec > 0 ){
2695 /* Locked operation */
2696 rc = UNQLITE_LOCKED;
2697 }else{
2698 ProcHash xHash = va_arg(ap,ProcHash);
2699 if( xHash ){
2700 pHash->xHash = xHash;
2701 }
2702 }
2703 break;
2704 }
2705 case UNQLITE_KV_CONFIG_CMP_FUNC: {
2706 /* Default comparison function */
2707 ProcCmp xCmp = va_arg(ap,ProcCmp);
2708 if( xCmp ){
2709 pHash->xCmp = xCmp;
2710 }
2711 break;
2712 }
2713 default:
2714 /* Unknown OP */
2715 rc = UNQLITE_UNKNOWN;
2716 break;
2717 }
2718 return rc;
2719}
2720/*
2721 * Each public cursor is identified by an instance of this structure.
2722 */
2723typedef struct lhash_kv_cursor lhash_kv_cursor;
2724struct lhash_kv_cursor
2725{
2726 unqlite_kv_engine *pStore; /* Must be first */
2727 /* Private fields */
2728 int iState; /* Current state of the cursor */
2729 int is_first; /* True to read the database header */
2730 lhcell *pCell; /* Current cell we are processing */
2731 unqlite_page *pRaw; /* Raw disk page */
2732 lhash_bmap_rec *pRec; /* Logical to real bucket map */
2733};
2734/*
2735 * Possible state of the cursor
2736 */
2737#define L_HASH_CURSOR_STATE_NEXT_PAGE 1 /* Next page in the list */
2738#define L_HASH_CURSOR_STATE_CELL 2 /* Processing Cell */
2739#define L_HASH_CURSOR_STATE_DONE 3 /* Cursor does not point to anything */
2740/*
2741 * Initialize the cursor.
2742 */
2743static void lhInitCursor(unqlite_kv_cursor *pPtr)
2744{
2745 lhash_kv_engine *pEngine = (lhash_kv_engine *)pPtr->pStore;
2746 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr;
2747 /* Init */
2748 pCur->iState = L_HASH_CURSOR_STATE_NEXT_PAGE;
2749 pCur->pCell = 0;
2750 pCur->pRec = pEngine->pFirst;
2751 pCur->pRaw = 0;
2752 pCur->is_first = 1;
2753}
2754/*
2755 * Point to the next page on the database.
2756 */
2757static int lhCursorNextPage(lhash_kv_cursor *pPtr)
2758{
2759 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr;
2760 lhash_bmap_rec *pRec;
2761 lhpage *pPage;
2762 int rc;
2763 for(;;){
2764 pRec = pCur->pRec;
2765 if( pRec == 0 ){
2766 pCur->iState = L_HASH_CURSOR_STATE_DONE;
2767 return UNQLITE_DONE;
2768 }
2769 if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){
2770 /* Unref this page */
2771 pCur->pStore->pIo->xPageUnref(pPtr->pRaw);
2772 pPtr->pRaw = 0;
2773 }
2774 /* Advance the map cursor */
2775 pCur->pRec = pRec->pPrev; /* Not a bug, reverse link */
2776 /* Load the next page on the list */
2777 rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0);
2778 if( rc != UNQLITE_OK ){
2779 return rc;
2780 }
2781 if( pPage->pList ){
2782 /* Reflect the change */
2783 pCur->pCell = pPage->pList;
2784 pCur->iState = L_HASH_CURSOR_STATE_CELL;
2785 pCur->pRaw = pPage->pRaw;
2786 break;
2787 }
2788 /* Empty page, discard this page and continue */
2789 pPage->pHash->pIo->xPageUnref(pPage->pRaw);
2790 }
2791 return UNQLITE_OK;
2792}
2793/*
2794 * Point to the previous page on the database.
2795 */
2796static int lhCursorPrevPage(lhash_kv_cursor *pPtr)
2797{
2798 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr;
2799 lhash_bmap_rec *pRec;
2800 lhpage *pPage;
2801 int rc;
2802 for(;;){
2803 pRec = pCur->pRec;
2804 if( pRec == 0 ){
2805 pCur->iState = L_HASH_CURSOR_STATE_DONE;
2806 return UNQLITE_DONE;
2807 }
2808 if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){
2809 /* Unref this page */
2810 pCur->pStore->pIo->xPageUnref(pPtr->pRaw);
2811 pPtr->pRaw = 0;
2812 }
2813 /* Advance the map cursor */
2814 pCur->pRec = pRec->pNext; /* Not a bug, reverse link */
2815 /* Load the previous page on the list */
2816 rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0);
2817 if( rc != UNQLITE_OK ){
2818 return rc;
2819 }
2820 if( pPage->pFirst ){
2821 /* Reflect the change */
2822 pCur->pCell = pPage->pFirst;
2823 pCur->iState = L_HASH_CURSOR_STATE_CELL;
2824 pCur->pRaw = pPage->pRaw;
2825 break;
2826 }
2827 /* Discard this page and continue */
2828 pPage->pHash->pIo->xPageUnref(pPage->pRaw);
2829 }
2830 return UNQLITE_OK;
2831}
2832/*
2833 * Is a valid cursor.
2834 */
2835static int lhCursorValid(unqlite_kv_cursor *pPtr)
2836{
2837 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr;
2838 return (pCur->iState == L_HASH_CURSOR_STATE_CELL) && pCur->pCell;
2839}
2840/*
2841 * Point to the first record.
2842 */
2843static int lhCursorFirst(unqlite_kv_cursor *pCursor)
2844{
2845 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
2846 lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore;
2847 int rc;
2848 if( pCur->is_first ){
2849 /* Read the database header first */
2850 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0);
2851 if( rc != UNQLITE_OK ){
2852 return rc;
2853 }
2854 pCur->is_first = 0;
2855 }
2856 /* Point to the first map record */
2857 pCur->pRec = pEngine->pFirst;
2858 /* Load the cells */
2859 rc = lhCursorNextPage(pCur);
2860 return rc;
2861}
2862/*
2863 * Point to the last record.
2864 */
2865static int lhCursorLast(unqlite_kv_cursor *pCursor)
2866{
2867 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
2868 lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore;
2869 int rc;
2870 if( pCur->is_first ){
2871 /* Read the database header first */
2872 rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0);
2873 if( rc != UNQLITE_OK ){
2874 return rc;
2875 }
2876 pCur->is_first = 0;
2877 }
2878 /* Point to the last map record */
2879 pCur->pRec = pEngine->pList;
2880 /* Load the cells */
2881 rc = lhCursorPrevPage(pCur);
2882 return rc;
2883}
2884/*
2885 * Reset the cursor.
2886 */
2887static void lhCursorReset(unqlite_kv_cursor *pCursor)
2888{
2889 lhCursorFirst(pCursor);
2890}
2891/*
2892 * Point to the next record.
2893 */
2894static int lhCursorNext(unqlite_kv_cursor *pCursor)
2895{
2896 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
2897 lhcell *pCell;
2898 int rc;
2899 if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){
2900 /* Load the cells of the next page */
2901 rc = lhCursorNextPage(pCur);
2902 return rc;
2903 }
2904 pCell = pCur->pCell;
2905 pCur->pCell = pCell->pNext;
2906 if( pCur->pCell == 0 ){
2907 /* Load the cells of the next page */
2908 rc = lhCursorNextPage(pCur);
2909 return rc;
2910 }
2911 return UNQLITE_OK;
2912}
2913/*
2914 * Point to the previous record.
2915 */
2916static int lhCursorPrev(unqlite_kv_cursor *pCursor)
2917{
2918 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
2919 lhcell *pCell;
2920 int rc;
2921 if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){
2922 /* Load the cells of the previous page */
2923 rc = lhCursorPrevPage(pCur);
2924 return rc;
2925 }
2926 pCell = pCur->pCell;
2927 pCur->pCell = pCell->pPrev;
2928 if( pCur->pCell == 0 ){
2929 /* Load the cells of the previous page */
2930 rc = lhCursorPrevPage(pCur);
2931 return rc;
2932 }
2933 return UNQLITE_OK;
2934}
2935/*
2936 * Return key length.
2937 */
2938static int lhCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen)
2939{
2940 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
2941 lhcell *pCell;
2942
2943 if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){
2944 /* Invalid state */
2945 return UNQLITE_INVALID;
2946 }
2947 /* Point to the target cell */
2948 pCell = pCur->pCell;
2949 /* Return key length */
2950 *pLen = (int)pCell->nKey;
2951 return UNQLITE_OK;
2952}
2953/*
2954 * Return data length.
2955 */
2956static int lhCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen)
2957{
2958 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
2959 lhcell *pCell;
2960
2961 if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){
2962 /* Invalid state */
2963 return UNQLITE_INVALID;
2964 }
2965 /* Point to the target cell */
2966 pCell = pCur->pCell;
2967 /* Return data length */
2968 *pLen = (unqlite_int64)pCell->nData;
2969 return UNQLITE_OK;
2970}
2971/*
2972 * Consume the key.
2973 */
2974static int lhCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
2975{
2976 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
2977 lhcell *pCell;
2978 int rc;
2979 if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){
2980 /* Invalid state */
2981 return UNQLITE_INVALID;
2982 }
2983 /* Point to the target cell */
2984 pCell = pCur->pCell;
2985 if( SyBlobLength(&pCell->sKey) > 0 ){
2986 /* Consume the key directly */
2987 rc = xConsumer(SyBlobData(&pCell->sKey),SyBlobLength(&pCell->sKey),pUserData);
2988 }else{
2989 /* Very large key */
2990 rc = lhConsumeCellkey(pCell,xConsumer,pUserData,0);
2991 }
2992 return rc;
2993}
2994/*
2995 * Consume the data.
2996 */
2997static int lhCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
2998{
2999 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
3000 lhcell *pCell;
3001 int rc;
3002 if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){
3003 /* Invalid state */
3004 return UNQLITE_INVALID;
3005 }
3006 /* Point to the target cell */
3007 pCell = pCur->pCell;
3008 /* Consume the data */
3009 rc = lhConsumeCellData(pCell,xConsumer,pUserData);
3010 return rc;
3011}
3012/*
3013 * Find a partiuclar record.
3014 */
3015static int lhCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos)
3016{
3017 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
3018 int rc;
3019 /* Perform a lookup */
3020 rc = lhRecordLookup((lhash_kv_engine *)pCur->pStore,pKey,nByte,&pCur->pCell);
3021 if( rc != UNQLITE_OK ){
3022 SXUNUSED(iPos);
3023 pCur->pCell = 0;
3024 pCur->iState = L_HASH_CURSOR_STATE_DONE;
3025 return rc;
3026 }
3027 pCur->iState = L_HASH_CURSOR_STATE_CELL;
3028 return UNQLITE_OK;
3029}
3030/*
3031 * Remove a particular record.
3032 */
3033static int lhCursorDelete(unqlite_kv_cursor *pCursor)
3034{
3035 lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor;
3036 lhcell *pCell;
3037 int rc;
3038 if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){
3039 /* Invalid state */
3040 return UNQLITE_INVALID;
3041 }
3042 /* Point to the target cell */
3043 pCell = pCur->pCell;
3044 /* Point to the next entry */
3045 pCur->pCell = pCell->pNext;
3046 /* Perform the deletion */
3047 rc = lhRecordRemove(pCell);
3048 return rc;
3049}
3050/*
3051 * Export the linear-hash storage engine.
3052 */
3053UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void)
3054{
3055 static const unqlite_kv_methods sDiskStore = {
3056 "hash", /* zName */
3057 sizeof(lhash_kv_engine), /* szKv */
3058 sizeof(lhash_kv_cursor), /* szCursor */
3059 1, /* iVersion */
3060 lhash_kv_init, /* xInit */
3061 lhash_kv_release, /* xRelease */
3062 lhash_kv_config, /* xConfig */
3063 lhash_kv_open, /* xOpen */
3064 lhash_kv_replace, /* xReplace */
3065 lhash_kv_append, /* xAppend */
3066 lhInitCursor, /* xCursorInit */
3067 lhCursorSeek, /* xSeek */
3068 lhCursorFirst, /* xFirst */
3069 lhCursorLast, /* xLast */
3070 lhCursorValid, /* xValid */
3071 lhCursorNext, /* xNext */
3072 lhCursorPrev, /* xPrev */
3073 lhCursorDelete, /* xDelete */
3074 lhCursorKeyLength, /* xKeyLength */
3075 lhCursorKey, /* xKey */
3076 lhCursorDataLength, /* xDataLength */
3077 lhCursorData, /* xData */
3078 lhCursorReset, /* xReset */
3079 0 /* xRelease */
3080 };
3081 return &sDiskStore;
3082}
diff --git a/common/unqlite/license.txt b/common/unqlite/license.txt
new file mode 100644
index 0000000..968624c
--- /dev/null
+++ b/common/unqlite/license.txt
@@ -0,0 +1,25 @@
1/*
2 * Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>].
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS
15 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
17 * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
21 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
diff --git a/common/unqlite/mem_kv.c b/common/unqlite/mem_kv.c
new file mode 100644
index 0000000..bdd3776
--- /dev/null
+++ b/common/unqlite/mem_kv.c
@@ -0,0 +1,678 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: mem_kv.c v1.7 Win7 2012-11-28 01:41 stable <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/*
18 * This file implements an in-memory key value storage engine for unQLite.
19 * Note that this storage engine does not support transactions.
20 *
21 * Normaly, I (chm@symisc.net) planned to implement a red-black tree
22 * which is suitable for this kind of operation, but due to the lack
23 * of time, I decided to implement a tunned hashtable which everybody
24 * know works very well for this kind of operation.
25 * Again, I insist on a red-black tree implementation for future version
26 * of Unqlite.
27 */
28/* Forward declaration */
29typedef struct mem_hash_kv_engine mem_hash_kv_engine;
30/*
31 * Each record is storead in an instance of the following structure.
32 */
33typedef struct mem_hash_record mem_hash_record;
34struct mem_hash_record
35{
36 mem_hash_kv_engine *pEngine; /* Storage engine */
37 sxu32 nHash; /* Hash of the key */
38 const void *pKey; /* Key */
39 sxu32 nKeyLen; /* Key size (Max 1GB) */
40 const void *pData; /* Data */
41 sxu32 nDataLen; /* Data length (Max 4GB) */
42 mem_hash_record *pNext,*pPrev; /* Link to other records */
43 mem_hash_record *pNextHash,*pPrevHash; /* Collision link */
44};
45/*
46 * Each in-memory KV engine is represented by an instance
47 * of the following structure.
48 */
49struct mem_hash_kv_engine
50{
51 const unqlite_kv_io *pIo; /* IO methods: MUST be first */
52 /* Private data */
53 SyMemBackend sAlloc; /* Private memory allocator */
54 ProcHash xHash; /* Default hash function */
55 ProcCmp xCmp; /* Default comparison function */
56 sxu32 nRecord; /* Total number of records */
57 sxu32 nBucket; /* Bucket size: Must be a power of two */
58 mem_hash_record **apBucket; /* Hash bucket */
59 mem_hash_record *pFirst; /* First inserted entry */
60 mem_hash_record *pLast; /* Last inserted entry */
61};
62/*
63 * Allocate a new hash record.
64 */
65static mem_hash_record * MemHashNewRecord(
66 mem_hash_kv_engine *pEngine,
67 const void *pKey,int nKey,
68 const void *pData,unqlite_int64 nData,
69 sxu32 nHash
70 )
71{
72 SyMemBackend *pAlloc = &pEngine->sAlloc;
73 mem_hash_record *pRecord;
74 void *pDupData;
75 sxu32 nByte;
76 char *zPtr;
77
78 /* Total number of bytes to alloc */
79 nByte = sizeof(mem_hash_record) + nKey;
80 /* Allocate a new instance */
81 pRecord = (mem_hash_record *)SyMemBackendAlloc(pAlloc,nByte);
82 if( pRecord == 0 ){
83 return 0;
84 }
85 pDupData = (void *)SyMemBackendAlloc(pAlloc,(sxu32)nData);
86 if( pDupData == 0 ){
87 SyMemBackendFree(pAlloc,pRecord);
88 return 0;
89 }
90 zPtr = (char *)pRecord;
91 zPtr += sizeof(mem_hash_record);
92 /* Zero the structure */
93 SyZero(pRecord,sizeof(mem_hash_record));
94 /* Fill in the structure */
95 pRecord->pEngine = pEngine;
96 pRecord->nDataLen = (sxu32)nData;
97 pRecord->nKeyLen = (sxu32)nKey;
98 pRecord->nHash = nHash;
99 SyMemcpy(pKey,zPtr,pRecord->nKeyLen);
100 pRecord->pKey = (const void *)zPtr;
101 SyMemcpy(pData,pDupData,pRecord->nDataLen);
102 pRecord->pData = pDupData;
103 /* All done */
104 return pRecord;
105}
106/*
107 * Install a given record in the hashtable.
108 */
109static void MemHashLinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pRecord)
110{
111 sxu32 nBucket = pRecord->nHash & (pEngine->nBucket - 1);
112 pRecord->pNextHash = pEngine->apBucket[nBucket];
113 if( pEngine->apBucket[nBucket] ){
114 pEngine->apBucket[nBucket]->pPrevHash = pRecord;
115 }
116 pEngine->apBucket[nBucket] = pRecord;
117 if( pEngine->pFirst == 0 ){
118 pEngine->pFirst = pEngine->pLast = pRecord;
119 }else{
120 MACRO_LD_PUSH(pEngine->pLast,pRecord);
121 }
122 pEngine->nRecord++;
123}
124/*
125 * Unlink a given record from the hashtable.
126 */
127static void MemHashUnlinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pEntry)
128{
129 sxu32 nBucket = pEntry->nHash & (pEngine->nBucket - 1);
130 SyMemBackend *pAlloc = &pEngine->sAlloc;
131 if( pEntry->pPrevHash == 0 ){
132 pEngine->apBucket[nBucket] = pEntry->pNextHash;
133 }else{
134 pEntry->pPrevHash->pNextHash = pEntry->pNextHash;
135 }
136 if( pEntry->pNextHash ){
137 pEntry->pNextHash->pPrevHash = pEntry->pPrevHash;
138 }
139 MACRO_LD_REMOVE(pEngine->pLast,pEntry);
140 if( pEntry == pEngine->pFirst ){
141 pEngine->pFirst = pEntry->pPrev;
142 }
143 pEngine->nRecord--;
144 /* Release the entry */
145 SyMemBackendFree(pAlloc,(void *)pEntry->pData);
146 SyMemBackendFree(pAlloc,pEntry); /* Key is also stored here */
147}
148/*
149 * Perform a lookup for a given entry.
150 */
151static mem_hash_record * MemHashGetEntry(
152 mem_hash_kv_engine *pEngine,
153 const void *pKey,int nKeyLen
154 )
155{
156 mem_hash_record *pEntry;
157 sxu32 nHash,nBucket;
158 /* Hash the entry */
159 nHash = pEngine->xHash(pKey,(sxu32)nKeyLen);
160 nBucket = nHash & (pEngine->nBucket - 1);
161 pEntry = pEngine->apBucket[nBucket];
162 for(;;){
163 if( pEntry == 0 ){
164 break;
165 }
166 if( pEntry->nHash == nHash && pEntry->nKeyLen == (sxu32)nKeyLen &&
167 pEngine->xCmp(pEntry->pKey,pKey,pEntry->nKeyLen) == 0 ){
168 return pEntry;
169 }
170 pEntry = pEntry->pNextHash;
171 }
172 /* No such entry */
173 return 0;
174}
175/*
176 * Rehash all the entries in the given table.
177 */
178static int MemHashGrowTable(mem_hash_kv_engine *pEngine)
179{
180 sxu32 nNewSize = pEngine->nBucket << 1;
181 mem_hash_record *pEntry;
182 mem_hash_record **apNew;
183 sxu32 n,iBucket;
184 /* Allocate a new larger table */
185 apNew = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc, nNewSize * sizeof(mem_hash_record *));
186 if( apNew == 0 ){
187 /* Not so fatal, simply a performance hit */
188 return UNQLITE_OK;
189 }
190 /* Zero the new table */
191 SyZero((void *)apNew, nNewSize * sizeof(mem_hash_record *));
192 /* Rehash all entries */
193 n = 0;
194 pEntry = pEngine->pLast;
195 for(;;){
196
197 /* Loop one */
198 if( n >= pEngine->nRecord ){
199 break;
200 }
201 pEntry->pNextHash = pEntry->pPrevHash = 0;
202 /* Install in the new bucket */
203 iBucket = pEntry->nHash & (nNewSize - 1);
204 pEntry->pNextHash = apNew[iBucket];
205 if( apNew[iBucket] ){
206 apNew[iBucket]->pPrevHash = pEntry;
207 }
208 apNew[iBucket] = pEntry;
209 /* Point to the next entry */
210 pEntry = pEntry->pNext;
211 n++;
212
213 /* Loop two */
214 if( n >= pEngine->nRecord ){
215 break;
216 }
217 pEntry->pNextHash = pEntry->pPrevHash = 0;
218 /* Install in the new bucket */
219 iBucket = pEntry->nHash & (nNewSize - 1);
220 pEntry->pNextHash = apNew[iBucket];
221 if( apNew[iBucket] ){
222 apNew[iBucket]->pPrevHash = pEntry;
223 }
224 apNew[iBucket] = pEntry;
225 /* Point to the next entry */
226 pEntry = pEntry->pNext;
227 n++;
228
229 /* Loop three */
230 if( n >= pEngine->nRecord ){
231 break;
232 }
233 pEntry->pNextHash = pEntry->pPrevHash = 0;
234 /* Install in the new bucket */
235 iBucket = pEntry->nHash & (nNewSize - 1);
236 pEntry->pNextHash = apNew[iBucket];
237 if( apNew[iBucket] ){
238 apNew[iBucket]->pPrevHash = pEntry;
239 }
240 apNew[iBucket] = pEntry;
241 /* Point to the next entry */
242 pEntry = pEntry->pNext;
243 n++;
244
245 /* Loop four */
246 if( n >= pEngine->nRecord ){
247 break;
248 }
249 pEntry->pNextHash = pEntry->pPrevHash = 0;
250 /* Install in the new bucket */
251 iBucket = pEntry->nHash & (nNewSize - 1);
252 pEntry->pNextHash = apNew[iBucket];
253 if( apNew[iBucket] ){
254 apNew[iBucket]->pPrevHash = pEntry;
255 }
256 apNew[iBucket] = pEntry;
257 /* Point to the next entry */
258 pEntry = pEntry->pNext;
259 n++;
260 }
261 /* Release the old table and reflect the change */
262 SyMemBackendFree(&pEngine->sAlloc,(void *)pEngine->apBucket);
263 pEngine->apBucket = apNew;
264 pEngine->nBucket = nNewSize;
265 return UNQLITE_OK;
266}
267/*
268 * Exported Interfaces.
269 */
270/*
271 * Each public cursor is identified by an instance of this structure.
272 */
273typedef struct mem_hash_cursor mem_hash_cursor;
274struct mem_hash_cursor
275{
276 unqlite_kv_engine *pStore; /* Must be first */
277 /* Private fields */
278 mem_hash_record *pCur; /* Current hash record */
279};
280/*
281 * Initialize the cursor.
282 */
283static void MemHashInitCursor(unqlite_kv_cursor *pCursor)
284{
285 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore;
286 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
287 /* Point to the first inserted entry */
288 pMem->pCur = pEngine->pFirst;
289}
290/*
291 * Point to the first entry.
292 */
293static int MemHashCursorFirst(unqlite_kv_cursor *pCursor)
294{
295 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore;
296 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
297 pMem->pCur = pEngine->pFirst;
298 return UNQLITE_OK;
299}
300/*
301 * Point to the last entry.
302 */
303static int MemHashCursorLast(unqlite_kv_cursor *pCursor)
304{
305 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore;
306 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
307 pMem->pCur = pEngine->pLast;
308 return UNQLITE_OK;
309}
310/*
311 * is a Valid Cursor.
312 */
313static int MemHashCursorValid(unqlite_kv_cursor *pCursor)
314{
315 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
316 return pMem->pCur != 0 ? 1 : 0;
317}
318/*
319 * Point to the next entry.
320 */
321static int MemHashCursorNext(unqlite_kv_cursor *pCursor)
322{
323 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
324 if( pMem->pCur == 0){
325 return UNQLITE_EOF;
326 }
327 pMem->pCur = pMem->pCur->pPrev; /* Reverse link: Not a Bug */
328 return UNQLITE_OK;
329}
330/*
331 * Point to the previous entry.
332 */
333static int MemHashCursorPrev(unqlite_kv_cursor *pCursor)
334{
335 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
336 if( pMem->pCur == 0){
337 return UNQLITE_EOF;
338 }
339 pMem->pCur = pMem->pCur->pNext; /* Reverse link: Not a Bug */
340 return UNQLITE_OK;
341}
342/*
343 * Return key length.
344 */
345static int MemHashCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen)
346{
347 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
348 if( pMem->pCur == 0){
349 return UNQLITE_EOF;
350 }
351 *pLen = (int)pMem->pCur->nKeyLen;
352 return UNQLITE_OK;
353}
354/*
355 * Return data length.
356 */
357static int MemHashCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen)
358{
359 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
360 if( pMem->pCur == 0 ){
361 return UNQLITE_EOF;
362 }
363 *pLen = pMem->pCur->nDataLen;
364 return UNQLITE_OK;
365}
366/*
367 * Consume the key.
368 */
369static int MemHashCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
370{
371 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
372 int rc;
373 if( pMem->pCur == 0){
374 return UNQLITE_EOF;
375 }
376 /* Invoke the callback */
377 rc = xConsumer(pMem->pCur->pKey,pMem->pCur->nKeyLen,pUserData);
378 /* Callback result */
379 return rc;
380}
381/*
382 * Consume the data.
383 */
384static int MemHashCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData)
385{
386 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
387 int rc;
388 if( pMem->pCur == 0){
389 return UNQLITE_EOF;
390 }
391 /* Invoke the callback */
392 rc = xConsumer(pMem->pCur->pData,pMem->pCur->nDataLen,pUserData);
393 /* Callback result */
394 return rc;
395}
396/*
397 * Reset the cursor.
398 */
399static void MemHashCursorReset(unqlite_kv_cursor *pCursor)
400{
401 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
402 pMem->pCur = ((mem_hash_kv_engine *)pCursor->pStore)->pFirst;
403}
404/*
405 * Remove a particular record.
406 */
407static int MemHashCursorDelete(unqlite_kv_cursor *pCursor)
408{
409 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
410 mem_hash_record *pNext;
411 if( pMem->pCur == 0 ){
412 /* Cursor does not point to anything */
413 return UNQLITE_NOTFOUND;
414 }
415 pNext = pMem->pCur->pPrev;
416 /* Perform the deletion */
417 MemHashUnlinkRecord(pMem->pCur->pEngine,pMem->pCur);
418 /* Point to the next entry */
419 pMem->pCur = pNext;
420 return UNQLITE_OK;
421}
422/*
423 * Find a particular record.
424 */
425static int MemHashCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos)
426{
427 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore;
428 mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor;
429 /* Perform the lookup */
430 pMem->pCur = MemHashGetEntry(pEngine,pKey,nByte);
431 if( pMem->pCur == 0 ){
432 if( iPos != UNQLITE_CURSOR_MATCH_EXACT ){
433 /* noop; */
434 }
435 /* No such record */
436 return UNQLITE_NOTFOUND;
437 }
438 return UNQLITE_OK;
439}
440/*
441 * Builtin hash function.
442 */
443static sxu32 MemHashFunc(const void *pSrc,sxu32 nLen)
444{
445 register unsigned char *zIn = (unsigned char *)pSrc;
446 unsigned char *zEnd;
447 sxu32 nH = 5381;
448 zEnd = &zIn[nLen];
449 for(;;){
450 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
451 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
452 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
453 if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++;
454 }
455 return nH;
456}
457/* Default bucket size */
458#define MEM_HASH_BUCKET_SIZE 64
459/* Default fill factor */
460#define MEM_HASH_FILL_FACTOR 4 /* or 3 */
461/*
462 * Initialize the in-memory storage engine.
463 */
464static int MemHashInit(unqlite_kv_engine *pKvEngine,int iPageSize)
465{
466 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine;
467 /* Note that this instance is already zeroed */
468 /* Memory backend */
469 SyMemBackendInitFromParent(&pEngine->sAlloc,unqliteExportMemBackend());
470#if defined(UNQLITE_ENABLE_THREADS)
471 /* Already protected by the upper layers */
472 SyMemBackendDisbaleMutexing(&pEngine->sAlloc);
473#endif
474 /* Default hash & comparison function */
475 pEngine->xHash = MemHashFunc;
476 pEngine->xCmp = SyMemcmp;
477 /* Allocate a new bucket */
478 pEngine->apBucket = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *));
479 if( pEngine->apBucket == 0 ){
480 SXUNUSED(iPageSize); /* cc warning */
481 return UNQLITE_NOMEM;
482 }
483 /* Zero the bucket */
484 SyZero(pEngine->apBucket,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *));
485 pEngine->nRecord = 0;
486 pEngine->nBucket = MEM_HASH_BUCKET_SIZE;
487 return UNQLITE_OK;
488}
489/*
490 * Release the in-memory storage engine.
491 */
492static void MemHashRelease(unqlite_kv_engine *pKvEngine)
493{
494 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine;
495 /* Release the private memory backend */
496 SyMemBackendRelease(&pEngine->sAlloc);
497}
498/*
499 * Configure the in-memory storage engine.
500 */
501static int MemHashConfigure(unqlite_kv_engine *pKvEngine,int iOp,va_list ap)
502{
503 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine;
504 int rc = UNQLITE_OK;
505 switch(iOp){
506 case UNQLITE_KV_CONFIG_HASH_FUNC:{
507 /* Use a default hash function */
508 if( pEngine->nRecord > 0 ){
509 rc = UNQLITE_LOCKED;
510 }else{
511 ProcHash xHash = va_arg(ap,ProcHash);
512 if( xHash ){
513 pEngine->xHash = xHash;
514 }
515 }
516 break;
517 }
518 case UNQLITE_KV_CONFIG_CMP_FUNC: {
519 /* Default comparison function */
520 ProcCmp xCmp = va_arg(ap,ProcCmp);
521 if( xCmp ){
522 pEngine->xCmp = xCmp;
523 }
524 break;
525 }
526 default:
527 /* Unknown configuration option */
528 rc = UNQLITE_UNKNOWN;
529 }
530 return rc;
531}
532/*
533 * Replace method.
534 */
535static int MemHashReplace(
536 unqlite_kv_engine *pKv,
537 const void *pKey,int nKeyLen,
538 const void *pData,unqlite_int64 nDataLen
539 )
540{
541 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv;
542 mem_hash_record *pRecord;
543 if( nDataLen > SXU32_HIGH ){
544 /* Database limit */
545 pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached");
546 return UNQLITE_LIMIT;
547 }
548 /* Fetch the record first */
549 pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen);
550 if( pRecord == 0 ){
551 /* Allocate a new record */
552 pRecord = MemHashNewRecord(pEngine,
553 pKey,nKeyLen,
554 pData,nDataLen,
555 pEngine->xHash(pKey,nKeyLen)
556 );
557 if( pRecord == 0 ){
558 return UNQLITE_NOMEM;
559 }
560 /* Link the entry */
561 MemHashLinkRecord(pEngine,pRecord);
562 if( (pEngine->nRecord >= pEngine->nBucket * MEM_HASH_FILL_FACTOR) && pEngine->nRecord < 100000 ){
563 /* Rehash the table */
564 MemHashGrowTable(pEngine);
565 }
566 }else{
567 sxu32 nData = (sxu32)nDataLen;
568 void *pNew;
569 /* Replace an existing record */
570 if( nData == pRecord->nDataLen ){
571 /* No need to free the old chunk */
572 pNew = (void *)pRecord->pData;
573 }else{
574 pNew = SyMemBackendAlloc(&pEngine->sAlloc,nData);
575 if( pNew == 0 ){
576 return UNQLITE_NOMEM;
577 }
578 /* Release the old data */
579 SyMemBackendFree(&pEngine->sAlloc,(void *)pRecord->pData);
580 }
581 /* Reflect the change */
582 pRecord->nDataLen = nData;
583 SyMemcpy(pData,pNew,nData);
584 pRecord->pData = pNew;
585 }
586 return UNQLITE_OK;
587}
588/*
589 * Append method.
590 */
591static int MemHashAppend(
592 unqlite_kv_engine *pKv,
593 const void *pKey,int nKeyLen,
594 const void *pData,unqlite_int64 nDataLen
595 )
596{
597 mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv;
598 mem_hash_record *pRecord;
599 if( nDataLen > SXU32_HIGH ){
600 /* Database limit */
601 pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached");
602 return UNQLITE_LIMIT;
603 }
604 /* Fetch the record first */
605 pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen);
606 if( pRecord == 0 ){
607 /* Allocate a new record */
608 pRecord = MemHashNewRecord(pEngine,
609 pKey,nKeyLen,
610 pData,nDataLen,
611 pEngine->xHash(pKey,nKeyLen)
612 );
613 if( pRecord == 0 ){
614 return UNQLITE_NOMEM;
615 }
616 /* Link the entry */
617 MemHashLinkRecord(pEngine,pRecord);
618 if( pEngine->nRecord * MEM_HASH_FILL_FACTOR >= pEngine->nBucket && pEngine->nRecord < 100000 ){
619 /* Rehash the table */
620 MemHashGrowTable(pEngine);
621 }
622 }else{
623 unqlite_int64 nNew = pRecord->nDataLen + nDataLen;
624 void *pOld = (void *)pRecord->pData;
625 sxu32 nData;
626 char *zNew;
627 /* Append data to the existing record */
628 if( nNew > SXU32_HIGH ){
629 /* Overflow */
630 pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow");
631 return UNQLITE_LIMIT;
632 }
633 nData = (sxu32)nNew;
634 /* Allocate bigger chunk */
635 zNew = (char *)SyMemBackendRealloc(&pEngine->sAlloc,pOld,nData);
636 if( zNew == 0 ){
637 return UNQLITE_NOMEM;
638 }
639 /* Reflect the change */
640 SyMemcpy(pData,&zNew[pRecord->nDataLen],(sxu32)nDataLen);
641 pRecord->pData = (const void *)zNew;
642 pRecord->nDataLen = nData;
643 }
644 return UNQLITE_OK;
645}
646/*
647 * Export the in-memory storage engine.
648 */
649UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void)
650{
651 static const unqlite_kv_methods sMemStore = {
652 "mem", /* zName */
653 sizeof(mem_hash_kv_engine), /* szKv */
654 sizeof(mem_hash_cursor), /* szCursor */
655 1, /* iVersion */
656 MemHashInit, /* xInit */
657 MemHashRelease, /* xRelease */
658 MemHashConfigure, /* xConfig */
659 0, /* xOpen */
660 MemHashReplace, /* xReplace */
661 MemHashAppend, /* xAppend */
662 MemHashInitCursor, /* xCursorInit */
663 MemHashCursorSeek, /* xSeek */
664 MemHashCursorFirst, /* xFirst */
665 MemHashCursorLast, /* xLast */
666 MemHashCursorValid, /* xValid */
667 MemHashCursorNext, /* xNext */
668 MemHashCursorPrev, /* xPrev */
669 MemHashCursorDelete, /* xDelete */
670 MemHashCursorKeyLength, /* xKeyLength */
671 MemHashCursorKey, /* xKey */
672 MemHashCursorDataLength, /* xDataLength */
673 MemHashCursorData, /* xData */
674 MemHashCursorReset, /* xReset */
675 0 /* xRelease */
676 };
677 return &sMemStore;
678}
diff --git a/common/unqlite/os.c b/common/unqlite/os.c
new file mode 100644
index 0000000..180c898
--- /dev/null
+++ b/common/unqlite/os.c
@@ -0,0 +1,117 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: os.c v1.0 FreeBSD 2012-11-12 21:27 devel <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/* OS interfaces abstraction layers: Mostly SQLite3 source tree */
18/*
19** The following routines are convenience wrappers around methods
20** of the unqlite_file object. This is mostly just syntactic sugar. All
21** of this would be completely automatic if UnQLite were coded using
22** C++ instead of plain old C.
23*/
24UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset)
25{
26 return id->pMethods->xRead(id, pBuf, amt, offset);
27}
28UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset)
29{
30 return id->pMethods->xWrite(id, pBuf, amt, offset);
31}
32UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size)
33{
34 return id->pMethods->xTruncate(id, size);
35}
36UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags)
37{
38 return id->pMethods->xSync(id, flags);
39}
40UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize)
41{
42 return id->pMethods->xFileSize(id, pSize);
43}
44UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType)
45{
46 return id->pMethods->xLock(id, lockType);
47}
48UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType)
49{
50 return id->pMethods->xUnlock(id, lockType);
51}
52UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut)
53{
54 return id->pMethods->xCheckReservedLock(id, pResOut);
55}
56UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id)
57{
58 if( id->pMethods->xSectorSize ){
59 return id->pMethods->xSectorSize(id);
60 }
61 return UNQLITE_DEFAULT_SECTOR_SIZE;
62}
63/*
64** The next group of routines are convenience wrappers around the
65** VFS methods.
66*/
67UNQLITE_PRIVATE int unqliteOsOpen(
68 unqlite_vfs *pVfs,
69 SyMemBackend *pAlloc,
70 const char *zPath,
71 unqlite_file **ppOut,
72 unsigned int flags
73)
74{
75 unqlite_file *pFile;
76 int rc;
77 *ppOut = 0;
78 if( zPath == 0 ){
79 /* May happen if dealing with an in-memory database */
80 return SXERR_EMPTY;
81 }
82 /* Allocate a new instance */
83 pFile = (unqlite_file *)SyMemBackendAlloc(pAlloc,sizeof(unqlite_file)+pVfs->szOsFile);
84 if( pFile == 0 ){
85 return UNQLITE_NOMEM;
86 }
87 /* Zero the structure */
88 SyZero(pFile,sizeof(unqlite_file)+pVfs->szOsFile);
89 /* Invoke the xOpen method of the underlying VFS */
90 rc = pVfs->xOpen(pVfs, zPath, pFile, flags);
91 if( rc != UNQLITE_OK ){
92 SyMemBackendFree(pAlloc,pFile);
93 pFile = 0;
94 }
95 *ppOut = pFile;
96 return rc;
97}
98UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId)
99{
100 int rc = UNQLITE_OK;
101 if( pId ){
102 rc = pId->pMethods->xClose(pId);
103 SyMemBackendFree(pAlloc,pId);
104 }
105 return rc;
106}
107UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync){
108 return pVfs->xDelete(pVfs, zPath, dirSync);
109}
110UNQLITE_PRIVATE int unqliteOsAccess(
111 unqlite_vfs *pVfs,
112 const char *zPath,
113 int flags,
114 int *pResOut
115){
116 return pVfs->xAccess(pVfs, zPath, flags, pResOut);
117}
diff --git a/common/unqlite/os_unix.c b/common/unqlite/os_unix.c
new file mode 100644
index 0000000..f578d07
--- /dev/null
+++ b/common/unqlite/os_unix.c
@@ -0,0 +1,1769 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: os_unix.c v1.3 FreeBSD 2013-04-05 01:10 devel <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/*
18 * Omit the whole layer from the build if compiling for platforms other than Unix (Linux, BSD, Solaris, OS X, etc.).
19 * Note: Mostly SQLite3 source tree.
20 */
21#if defined(__UNIXES__)
22/** This file contains the VFS implementation for unix-like operating systems
23** include Linux, MacOSX, *BSD, QNX, VxWorks, AIX, HPUX, and others.
24**
25** There are actually several different VFS implementations in this file.
26** The differences are in the way that file locking is done. The default
27** implementation uses Posix Advisory Locks. Alternative implementations
28** use flock(), dot-files, various proprietary locking schemas, or simply
29** skip locking all together.
30**
31** This source file is organized into divisions where the logic for various
32** subfunctions is contained within the appropriate division. PLEASE
33** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed
34** in the correct division and should be clearly labeled.
35**
36*/
37/*
38** standard include files.
39*/
40#include <sys/types.h>
41#include <sys/stat.h>
42#include <sys/uio.h>
43#include <sys/file.h>
44#include <fcntl.h>
45#include <unistd.h>
46#include <time.h>
47#include <sys/time.h>
48#include <errno.h>
49#if defined(__APPLE__)
50# include <sys/mount.h>
51#endif
52/*
53** Allowed values of unixFile.fsFlags
54*/
55#define UNQLITE_FSFLAGS_IS_MSDOS 0x1
56
57/*
58** Default permissions when creating a new file
59*/
60#ifndef UNQLITE_DEFAULT_FILE_PERMISSIONS
61# define UNQLITE_DEFAULT_FILE_PERMISSIONS 0644
62#endif
63/*
64 ** Default permissions when creating auto proxy dir
65 */
66#ifndef UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS
67# define UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS 0755
68#endif
69/*
70** Maximum supported path-length.
71*/
72#define MAX_PATHNAME 512
73/*
74** Only set the lastErrno if the error code is a real error and not
75** a normal expected return code of UNQLITE_BUSY or UNQLITE_OK
76*/
77#define IS_LOCK_ERROR(x) ((x != UNQLITE_OK) && (x != UNQLITE_BUSY))
78/* Forward references */
79typedef struct unixInodeInfo unixInodeInfo; /* An i-node */
80typedef struct UnixUnusedFd UnixUnusedFd; /* An unused file descriptor */
81/*
82** Sometimes, after a file handle is closed by SQLite, the file descriptor
83** cannot be closed immediately. In these cases, instances of the following
84** structure are used to store the file descriptor while waiting for an
85** opportunity to either close or reuse it.
86*/
87struct UnixUnusedFd {
88 int fd; /* File descriptor to close */
89 int flags; /* Flags this file descriptor was opened with */
90 UnixUnusedFd *pNext; /* Next unused file descriptor on same file */
91};
92/*
93** The unixFile structure is subclass of unqlite3_file specific to the unix
94** VFS implementations.
95*/
96typedef struct unixFile unixFile;
97struct unixFile {
98 const unqlite_io_methods *pMethod; /* Always the first entry */
99 unixInodeInfo *pInode; /* Info about locks on this inode */
100 int h; /* The file descriptor */
101 int dirfd; /* File descriptor for the directory */
102 unsigned char eFileLock; /* The type of lock held on this fd */
103 int lastErrno; /* The unix errno from last I/O error */
104 void *lockingContext; /* Locking style specific state */
105 UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */
106 int fileFlags; /* Miscellanous flags */
107 const char *zPath; /* Name of the file */
108 unsigned fsFlags; /* cached details from statfs() */
109};
110/*
111** The following macros define bits in unixFile.fileFlags
112*/
113#define UNQLITE_WHOLE_FILE_LOCKING 0x0001 /* Use whole-file locking */
114/*
115** Define various macros that are missing from some systems.
116*/
117#ifndef O_LARGEFILE
118# define O_LARGEFILE 0
119#endif
120#ifndef O_NOFOLLOW
121# define O_NOFOLLOW 0
122#endif
123#ifndef O_BINARY
124# define O_BINARY 0
125#endif
126/*
127** Helper functions to obtain and relinquish the global mutex. The
128** global mutex is used to protect the unixInodeInfo and
129** vxworksFileId objects used by this file, all of which may be
130** shared by multiple threads.
131**
132** Function unixMutexHeld() is used to assert() that the global mutex
133** is held when required. This function is only used as part of assert()
134** statements. e.g.
135**
136** unixEnterMutex()
137** assert( unixMutexHeld() );
138** unixEnterLeave()
139*/
140static void unixEnterMutex(void){
141#ifdef UNQLITE_ENABLE_THREADS
142 const SyMutexMethods *pMutexMethods = SyMutexExportMethods();
143 if( pMutexMethods ){
144 SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */
145 SyMutexEnter(pMutexMethods,pMutex);
146 }
147#endif /* UNQLITE_ENABLE_THREADS */
148}
149static void unixLeaveMutex(void){
150#ifdef UNQLITE_ENABLE_THREADS
151 const SyMutexMethods *pMutexMethods = SyMutexExportMethods();
152 if( pMutexMethods ){
153 SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */
154 SyMutexLeave(pMutexMethods,pMutex);
155 }
156#endif /* UNQLITE_ENABLE_THREADS */
157}
158/*
159** This routine translates a standard POSIX errno code into something
160** useful to the clients of the unqlite3 functions. Specifically, it is
161** intended to translate a variety of "try again" errors into UNQLITE_BUSY
162** and a variety of "please close the file descriptor NOW" errors into
163** UNQLITE_IOERR
164**
165** Errors during initialization of locks, or file system support for locks,
166** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately.
167*/
168static int unqliteErrorFromPosixError(int posixError, int unqliteIOErr) {
169 switch (posixError) {
170 case 0:
171 return UNQLITE_OK;
172
173 case EAGAIN:
174 case ETIMEDOUT:
175 case EBUSY:
176 case EINTR:
177 case ENOLCK:
178 /* random NFS retry error, unless during file system support
179 * introspection, in which it actually means what it says */
180 return UNQLITE_BUSY;
181
182 case EACCES:
183 /* EACCES is like EAGAIN during locking operations, but not any other time*/
184 return UNQLITE_BUSY;
185
186 case EPERM:
187 return UNQLITE_PERM;
188
189 case EDEADLK:
190 return UNQLITE_IOERR;
191
192#if EOPNOTSUPP!=ENOTSUP
193 case EOPNOTSUPP:
194 /* something went terribly awry, unless during file system support
195 * introspection, in which it actually means what it says */
196#endif
197#ifdef ENOTSUP
198 case ENOTSUP:
199 /* invalid fd, unless during file system support introspection, in which
200 * it actually means what it says */
201#endif
202 case EIO:
203 case EBADF:
204 case EINVAL:
205 case ENOTCONN:
206 case ENODEV:
207 case ENXIO:
208 case ENOENT:
209 case ESTALE:
210 case ENOSYS:
211 /* these should force the client to close the file and reconnect */
212
213 default:
214 return unqliteIOErr;
215 }
216}
217/******************************************************************************
218*************************** Posix Advisory Locking ****************************
219**
220** POSIX advisory locks are broken by design. ANSI STD 1003.1 (1996)
221** section 6.5.2.2 lines 483 through 490 specify that when a process
222** sets or clears a lock, that operation overrides any prior locks set
223** by the same process. It does not explicitly say so, but this implies
224** that it overrides locks set by the same process using a different
225** file descriptor. Consider this test case:
226**
227** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644);
228** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644);
229**
230** Suppose ./file1 and ./file2 are really the same file (because
231** one is a hard or symbolic link to the other) then if you set
232** an exclusive lock on fd1, then try to get an exclusive lock
233** on fd2, it works. I would have expected the second lock to
234** fail since there was already a lock on the file due to fd1.
235** But not so. Since both locks came from the same process, the
236** second overrides the first, even though they were on different
237** file descriptors opened on different file names.
238**
239** This means that we cannot use POSIX locks to synchronize file access
240** among competing threads of the same process. POSIX locks will work fine
241** to synchronize access for threads in separate processes, but not
242** threads within the same process.
243**
244** To work around the problem, SQLite has to manage file locks internally
245** on its own. Whenever a new database is opened, we have to find the
246** specific inode of the database file (the inode is determined by the
247** st_dev and st_ino fields of the stat structure that fstat() fills in)
248** and check for locks already existing on that inode. When locks are
249** created or removed, we have to look at our own internal record of the
250** locks to see if another thread has previously set a lock on that same
251** inode.
252**
253** (Aside: The use of inode numbers as unique IDs does not work on VxWorks.
254** For VxWorks, we have to use the alternative unique ID system based on
255** canonical filename and implemented in the previous division.)
256**
257** There is one locking structure
258** per inode, so if the same inode is opened twice, both unixFile structures
259** point to the same locking structure. The locking structure keeps
260** a reference count (so we will know when to delete it) and a "cnt"
261** field that tells us its internal lock status. cnt==0 means the
262** file is unlocked. cnt==-1 means the file has an exclusive lock.
263** cnt>0 means there are cnt shared locks on the file.
264**
265** Any attempt to lock or unlock a file first checks the locking
266** structure. The fcntl() system call is only invoked to set a
267** POSIX lock if the internal lock structure transitions between
268** a locked and an unlocked state.
269**
270** But wait: there are yet more problems with POSIX advisory locks.
271**
272** If you close a file descriptor that points to a file that has locks,
273** all locks on that file that are owned by the current process are
274** released. To work around this problem, each unixInodeInfo object
275** maintains a count of the number of pending locks on that inode.
276** When an attempt is made to close an unixFile, if there are
277** other unixFile open on the same inode that are holding locks, the call
278** to close() the file descriptor is deferred until all of the locks clear.
279** The unixInodeInfo structure keeps a list of file descriptors that need to
280** be closed and that list is walked (and cleared) when the last lock
281** clears.
282**
283** Yet another problem: LinuxThreads do not play well with posix locks.
284**
285** Many older versions of linux use the LinuxThreads library which is
286** not posix compliant. Under LinuxThreads, a lock created by thread
287** A cannot be modified or overridden by a different thread B.
288** Only thread A can modify the lock. Locking behavior is correct
289** if the appliation uses the newer Native Posix Thread Library (NPTL)
290** on linux - with NPTL a lock created by thread A can override locks
291** in thread B. But there is no way to know at compile-time which
292** threading library is being used. So there is no way to know at
293** compile-time whether or not thread A can override locks on thread B.
294** One has to do a run-time check to discover the behavior of the
295** current process.
296**
297*/
298
299/*
300** An instance of the following structure serves as the key used
301** to locate a particular unixInodeInfo object.
302*/
303struct unixFileId {
304 dev_t dev; /* Device number */
305 ino_t ino; /* Inode number */
306};
307/*
308** An instance of the following structure is allocated for each open
309** inode. Or, on LinuxThreads, there is one of these structures for
310** each inode opened by each thread.
311**
312** A single inode can have multiple file descriptors, so each unixFile
313** structure contains a pointer to an instance of this object and this
314** object keeps a count of the number of unixFile pointing to it.
315*/
316struct unixInodeInfo {
317 struct unixFileId fileId; /* The lookup key */
318 int nShared; /* Number of SHARED locks held */
319 int eFileLock; /* One of SHARED_LOCK, RESERVED_LOCK etc. */
320 int nRef; /* Number of pointers to this structure */
321 int nLock; /* Number of outstanding file locks */
322 UnixUnusedFd *pUnused; /* Unused file descriptors to close */
323 unixInodeInfo *pNext; /* List of all unixInodeInfo objects */
324 unixInodeInfo *pPrev; /* .... doubly linked */
325};
326
327static unixInodeInfo *inodeList = 0;
328/*
329 * Local memory allocation stuff.
330 */
331static void * unqlite_malloc(sxu32 nByte)
332{
333 SyMemBackend *pAlloc;
334 void *p;
335 pAlloc = (SyMemBackend *)unqliteExportMemBackend();
336 p = SyMemBackendAlloc(pAlloc,nByte);
337 return p;
338}
339static void unqlite_free(void *p)
340{
341 SyMemBackend *pAlloc;
342 pAlloc = (SyMemBackend *)unqliteExportMemBackend();
343 SyMemBackendFree(pAlloc,p);
344}
345/*
346** Close all file descriptors accumuated in the unixInodeInfo->pUnused list.
347** If all such file descriptors are closed without error, the list is
348** cleared and UNQLITE_OK returned.
349**
350** Otherwise, if an error occurs, then successfully closed file descriptor
351** entries are removed from the list, and UNQLITE_IOERR_CLOSE returned.
352** not deleted and UNQLITE_IOERR_CLOSE returned.
353*/
354static int closePendingFds(unixFile *pFile){
355 int rc = UNQLITE_OK;
356 unixInodeInfo *pInode = pFile->pInode;
357 UnixUnusedFd *pError = 0;
358 UnixUnusedFd *p;
359 UnixUnusedFd *pNext;
360 for(p=pInode->pUnused; p; p=pNext){
361 pNext = p->pNext;
362 if( close(p->fd) ){
363 pFile->lastErrno = errno;
364 rc = UNQLITE_IOERR;
365 p->pNext = pError;
366 pError = p;
367 }else{
368 unqlite_free(p);
369 }
370 }
371 pInode->pUnused = pError;
372 return rc;
373}
374/*
375** Release a unixInodeInfo structure previously allocated by findInodeInfo().
376**
377** The mutex entered using the unixEnterMutex() function must be held
378** when this function is called.
379*/
380static void releaseInodeInfo(unixFile *pFile){
381 unixInodeInfo *pInode = pFile->pInode;
382 if( pInode ){
383 pInode->nRef--;
384 if( pInode->nRef==0 ){
385 closePendingFds(pFile);
386 if( pInode->pPrev ){
387 pInode->pPrev->pNext = pInode->pNext;
388 }else{
389 inodeList = pInode->pNext;
390 }
391 if( pInode->pNext ){
392 pInode->pNext->pPrev = pInode->pPrev;
393 }
394 unqlite_free(pInode);
395 }
396 }
397}
398/*
399** Given a file descriptor, locate the unixInodeInfo object that
400** describes that file descriptor. Create a new one if necessary. The
401** return value might be uninitialized if an error occurs.
402**
403** The mutex entered using the unixEnterMutex() function must be held
404** when this function is called.
405**
406** Return an appropriate error code.
407*/
408static int findInodeInfo(
409 unixFile *pFile, /* Unix file with file desc used in the key */
410 unixInodeInfo **ppInode /* Return the unixInodeInfo object here */
411){
412 int rc; /* System call return code */
413 int fd; /* The file descriptor for pFile */
414 struct unixFileId fileId; /* Lookup key for the unixInodeInfo */
415 struct stat statbuf; /* Low-level file information */
416 unixInodeInfo *pInode = 0; /* Candidate unixInodeInfo object */
417
418 /* Get low-level information about the file that we can used to
419 ** create a unique name for the file.
420 */
421 fd = pFile->h;
422 rc = fstat(fd, &statbuf);
423 if( rc!=0 ){
424 pFile->lastErrno = errno;
425#ifdef EOVERFLOW
426 if( pFile->lastErrno==EOVERFLOW ) return UNQLITE_NOTIMPLEMENTED;
427#endif
428 return UNQLITE_IOERR;
429 }
430
431#ifdef __APPLE__
432 /* On OS X on an msdos filesystem, the inode number is reported
433 ** incorrectly for zero-size files. See ticket #3260. To work
434 ** around this problem (we consider it a bug in OS X, not SQLite)
435 ** we always increase the file size to 1 by writing a single byte
436 ** prior to accessing the inode number. The one byte written is
437 ** an ASCII 'S' character which also happens to be the first byte
438 ** in the header of every SQLite database. In this way, if there
439 ** is a race condition such that another thread has already populated
440 ** the first page of the database, no damage is done.
441 */
442 if( statbuf.st_size==0 && (pFile->fsFlags & UNQLITE_FSFLAGS_IS_MSDOS)!=0 ){
443 rc = write(fd, "S", 1);
444 if( rc!=1 ){
445 pFile->lastErrno = errno;
446 return UNQLITE_IOERR;
447 }
448 rc = fstat(fd, &statbuf);
449 if( rc!=0 ){
450 pFile->lastErrno = errno;
451 return UNQLITE_IOERR;
452 }
453 }
454#endif
455 SyZero(&fileId,sizeof(fileId));
456 fileId.dev = statbuf.st_dev;
457 fileId.ino = statbuf.st_ino;
458 pInode = inodeList;
459 while( pInode && SyMemcmp((const void *)&fileId,(const void *)&pInode->fileId, sizeof(fileId)) ){
460 pInode = pInode->pNext;
461 }
462 if( pInode==0 ){
463 pInode = (unixInodeInfo *)unqlite_malloc( sizeof(*pInode) );
464 if( pInode==0 ){
465 return UNQLITE_NOMEM;
466 }
467 SyZero(pInode,sizeof(*pInode));
468 SyMemcpy((const void *)&fileId,(void *)&pInode->fileId,sizeof(fileId));
469 pInode->nRef = 1;
470 pInode->pNext = inodeList;
471 pInode->pPrev = 0;
472 if( inodeList ) inodeList->pPrev = pInode;
473 inodeList = pInode;
474 }else{
475 pInode->nRef++;
476 }
477 *ppInode = pInode;
478 return UNQLITE_OK;
479}
480/*
481** This routine checks if there is a RESERVED lock held on the specified
482** file by this or any other process. If such a lock is held, set *pResOut
483** to a non-zero value otherwise *pResOut is set to zero. The return value
484** is set to UNQLITE_OK unless an I/O error occurs during lock checking.
485*/
486static int unixCheckReservedLock(unqlite_file *id, int *pResOut){
487 int rc = UNQLITE_OK;
488 int reserved = 0;
489 unixFile *pFile = (unixFile*)id;
490
491
492 unixEnterMutex(); /* Because pFile->pInode is shared across threads */
493
494 /* Check if a thread in this process holds such a lock */
495 if( pFile->pInode->eFileLock>SHARED_LOCK ){
496 reserved = 1;
497 }
498
499 /* Otherwise see if some other process holds it.
500 */
501 if( !reserved ){
502 struct flock lock;
503 lock.l_whence = SEEK_SET;
504 lock.l_start = RESERVED_BYTE;
505 lock.l_len = 1;
506 lock.l_type = F_WRLCK;
507 if (-1 == fcntl(pFile->h, F_GETLK, &lock)) {
508 int tErrno = errno;
509 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
510 pFile->lastErrno = tErrno;
511 } else if( lock.l_type!=F_UNLCK ){
512 reserved = 1;
513 }
514 }
515
516 unixLeaveMutex();
517
518 *pResOut = reserved;
519 return rc;
520}
521/*
522** Lock the file with the lock specified by parameter eFileLock - one
523** of the following:
524**
525** (1) SHARED_LOCK
526** (2) RESERVED_LOCK
527** (3) PENDING_LOCK
528** (4) EXCLUSIVE_LOCK
529**
530** Sometimes when requesting one lock state, additional lock states
531** are inserted in between. The locking might fail on one of the later
532** transitions leaving the lock state different from what it started but
533** still short of its goal. The following chart shows the allowed
534** transitions and the inserted intermediate states:
535**
536** UNLOCKED -> SHARED
537** SHARED -> RESERVED
538** SHARED -> (PENDING) -> EXCLUSIVE
539** RESERVED -> (PENDING) -> EXCLUSIVE
540** PENDING -> EXCLUSIVE
541**
542** This routine will only increase a lock. Use the unqliteOsUnlock()
543** routine to lower a locking level.
544*/
545static int unixLock(unqlite_file *id, int eFileLock){
546 /* The following describes the implementation of the various locks and
547 ** lock transitions in terms of the POSIX advisory shared and exclusive
548 ** lock primitives (called read-locks and write-locks below, to avoid
549 ** confusion with SQLite lock names). The algorithms are complicated
550 ** slightly in order to be compatible with unixdows systems simultaneously
551 ** accessing the same database file, in case that is ever required.
552 **
553 ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved
554 ** byte', each single bytes at well known offsets, and the 'shared byte
555 ** range', a range of 510 bytes at a well known offset.
556 **
557 ** To obtain a SHARED lock, a read-lock is obtained on the 'pending
558 ** byte'. If this is successful, a random byte from the 'shared byte
559 ** range' is read-locked and the lock on the 'pending byte' released.
560 **
561 ** A process may only obtain a RESERVED lock after it has a SHARED lock.
562 ** A RESERVED lock is implemented by grabbing a write-lock on the
563 ** 'reserved byte'.
564 **
565 ** A process may only obtain a PENDING lock after it has obtained a
566 ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock
567 ** on the 'pending byte'. This ensures that no new SHARED locks can be
568 ** obtained, but existing SHARED locks are allowed to persist. A process
569 ** does not have to obtain a RESERVED lock on the way to a PENDING lock.
570 ** This property is used by the algorithm for rolling back a journal file
571 ** after a crash.
572 **
573 ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is
574 ** implemented by obtaining a write-lock on the entire 'shared byte
575 ** range'. Since all other locks require a read-lock on one of the bytes
576 ** within this range, this ensures that no other locks are held on the
577 ** database.
578 **
579 ** The reason a single byte cannot be used instead of the 'shared byte
580 ** range' is that some versions of unixdows do not support read-locks. By
581 ** locking a random byte from a range, concurrent SHARED locks may exist
582 ** even if the locking primitive used is always a write-lock.
583 */
584 int rc = UNQLITE_OK;
585 unixFile *pFile = (unixFile*)id;
586 unixInodeInfo *pInode = pFile->pInode;
587 struct flock lock;
588 int s = 0;
589 int tErrno = 0;
590
591 /* If there is already a lock of this type or more restrictive on the
592 ** unixFile, do nothing. Don't use the end_lock: exit path, as
593 ** unixEnterMutex() hasn't been called yet.
594 */
595 if( pFile->eFileLock>=eFileLock ){
596 return UNQLITE_OK;
597 }
598 /* This mutex is needed because pFile->pInode is shared across threads
599 */
600 unixEnterMutex();
601 pInode = pFile->pInode;
602
603 /* If some thread using this PID has a lock via a different unixFile*
604 ** handle that precludes the requested lock, return BUSY.
605 */
606 if( (pFile->eFileLock!=pInode->eFileLock &&
607 (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK))
608 ){
609 rc = UNQLITE_BUSY;
610 goto end_lock;
611 }
612
613 /* If a SHARED lock is requested, and some thread using this PID already
614 ** has a SHARED or RESERVED lock, then increment reference counts and
615 ** return UNQLITE_OK.
616 */
617 if( eFileLock==SHARED_LOCK &&
618 (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){
619 pFile->eFileLock = SHARED_LOCK;
620 pInode->nShared++;
621 pInode->nLock++;
622 goto end_lock;
623 }
624 /* A PENDING lock is needed before acquiring a SHARED lock and before
625 ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will
626 ** be released.
627 */
628 lock.l_len = 1L;
629 lock.l_whence = SEEK_SET;
630 if( eFileLock==SHARED_LOCK
631 || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLock<PENDING_LOCK)
632 ){
633 lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK);
634 lock.l_start = PENDING_BYTE;
635 s = fcntl(pFile->h, F_SETLK, &lock);
636 if( s==(-1) ){
637 tErrno = errno;
638 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
639 if( IS_LOCK_ERROR(rc) ){
640 pFile->lastErrno = tErrno;
641 }
642 goto end_lock;
643 }
644 }
645 /* If control gets to this point, then actually go ahead and make
646 ** operating system calls for the specified lock.
647 */
648 if( eFileLock==SHARED_LOCK ){
649 /* Now get the read-lock */
650 lock.l_start = SHARED_FIRST;
651 lock.l_len = SHARED_SIZE;
652 if( (s = fcntl(pFile->h, F_SETLK, &lock))==(-1) ){
653 tErrno = errno;
654 }
655 /* Drop the temporary PENDING lock */
656 lock.l_start = PENDING_BYTE;
657 lock.l_len = 1L;
658 lock.l_type = F_UNLCK;
659 if( fcntl(pFile->h, F_SETLK, &lock)!=0 ){
660 if( s != -1 ){
661 /* This could happen with a network mount */
662 tErrno = errno;
663 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
664 if( IS_LOCK_ERROR(rc) ){
665 pFile->lastErrno = tErrno;
666 }
667 goto end_lock;
668 }
669 }
670 if( s==(-1) ){
671 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
672 if( IS_LOCK_ERROR(rc) ){
673 pFile->lastErrno = tErrno;
674 }
675 }else{
676 pFile->eFileLock = SHARED_LOCK;
677 pInode->nLock++;
678 pInode->nShared = 1;
679 }
680 }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){
681 /* We are trying for an exclusive lock but another thread in this
682 ** same process is still holding a shared lock. */
683 rc = UNQLITE_BUSY;
684 }else{
685 /* The request was for a RESERVED or EXCLUSIVE lock. It is
686 ** assumed that there is a SHARED or greater lock on the file
687 ** already.
688 */
689 lock.l_type = F_WRLCK;
690 switch( eFileLock ){
691 case RESERVED_LOCK:
692 lock.l_start = RESERVED_BYTE;
693 break;
694 case EXCLUSIVE_LOCK:
695 lock.l_start = SHARED_FIRST;
696 lock.l_len = SHARED_SIZE;
697 break;
698 default:
699 /* Can't happen */
700 break;
701 }
702 s = fcntl(pFile->h, F_SETLK, &lock);
703 if( s==(-1) ){
704 tErrno = errno;
705 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
706 if( IS_LOCK_ERROR(rc) ){
707 pFile->lastErrno = tErrno;
708 }
709 }
710 }
711 if( rc==UNQLITE_OK ){
712 pFile->eFileLock = eFileLock;
713 pInode->eFileLock = eFileLock;
714 }else if( eFileLock==EXCLUSIVE_LOCK ){
715 pFile->eFileLock = PENDING_LOCK;
716 pInode->eFileLock = PENDING_LOCK;
717 }
718end_lock:
719 unixLeaveMutex();
720 return rc;
721}
722/*
723** Add the file descriptor used by file handle pFile to the corresponding
724** pUnused list.
725*/
726static void setPendingFd(unixFile *pFile){
727 unixInodeInfo *pInode = pFile->pInode;
728 UnixUnusedFd *p = pFile->pUnused;
729 p->pNext = pInode->pUnused;
730 pInode->pUnused = p;
731 pFile->h = -1;
732 pFile->pUnused = 0;
733}
734/*
735** Lower the locking level on file descriptor pFile to eFileLock. eFileLock
736** must be either NO_LOCK or SHARED_LOCK.
737**
738** If the locking level of the file descriptor is already at or below
739** the requested locking level, this routine is a no-op.
740**
741** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED
742** the byte range is divided into 2 parts and the first part is unlocked then
743** set to a read lock, then the other part is simply unlocked. This works
744** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to
745** remove the write lock on a region when a read lock is set.
746*/
747static int _posixUnlock(unqlite_file *id, int eFileLock, int handleNFSUnlock){
748 unixFile *pFile = (unixFile*)id;
749 unixInodeInfo *pInode;
750 struct flock lock;
751 int rc = UNQLITE_OK;
752 int h;
753 int tErrno; /* Error code from system call errors */
754
755 if( pFile->eFileLock<=eFileLock ){
756 return UNQLITE_OK;
757 }
758 unixEnterMutex();
759
760 h = pFile->h;
761 pInode = pFile->pInode;
762
763 if( pFile->eFileLock>SHARED_LOCK ){
764 /* downgrading to a shared lock on NFS involves clearing the write lock
765 ** before establishing the readlock - to avoid a race condition we downgrade
766 ** the lock in 2 blocks, so that part of the range will be covered by a
767 ** write lock until the rest is covered by a read lock:
768 ** 1: [WWWWW]
769 ** 2: [....W]
770 ** 3: [RRRRW]
771 ** 4: [RRRR.]
772 */
773 if( eFileLock==SHARED_LOCK ){
774 if( handleNFSUnlock ){
775 off_t divSize = SHARED_SIZE - 1;
776
777 lock.l_type = F_UNLCK;
778 lock.l_whence = SEEK_SET;
779 lock.l_start = SHARED_FIRST;
780 lock.l_len = divSize;
781 if( fcntl(h, F_SETLK, &lock)==(-1) ){
782 tErrno = errno;
783 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
784 if( IS_LOCK_ERROR(rc) ){
785 pFile->lastErrno = tErrno;
786 }
787 goto end_unlock;
788 }
789 lock.l_type = F_RDLCK;
790 lock.l_whence = SEEK_SET;
791 lock.l_start = SHARED_FIRST;
792 lock.l_len = divSize;
793 if( fcntl(h, F_SETLK, &lock)==(-1) ){
794 tErrno = errno;
795 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
796 if( IS_LOCK_ERROR(rc) ){
797 pFile->lastErrno = tErrno;
798 }
799 goto end_unlock;
800 }
801 lock.l_type = F_UNLCK;
802 lock.l_whence = SEEK_SET;
803 lock.l_start = SHARED_FIRST+divSize;
804 lock.l_len = SHARED_SIZE-divSize;
805 if( fcntl(h, F_SETLK, &lock)==(-1) ){
806 tErrno = errno;
807 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
808 if( IS_LOCK_ERROR(rc) ){
809 pFile->lastErrno = tErrno;
810 }
811 goto end_unlock;
812 }
813 }else{
814 lock.l_type = F_RDLCK;
815 lock.l_whence = SEEK_SET;
816 lock.l_start = SHARED_FIRST;
817 lock.l_len = SHARED_SIZE;
818 if( fcntl(h, F_SETLK, &lock)==(-1) ){
819 tErrno = errno;
820 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
821 if( IS_LOCK_ERROR(rc) ){
822 pFile->lastErrno = tErrno;
823 }
824 goto end_unlock;
825 }
826 }
827 }
828 lock.l_type = F_UNLCK;
829 lock.l_whence = SEEK_SET;
830 lock.l_start = PENDING_BYTE;
831 lock.l_len = 2L;
832 if( fcntl(h, F_SETLK, &lock)!=(-1) ){
833 pInode->eFileLock = SHARED_LOCK;
834 }else{
835 tErrno = errno;
836 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
837 if( IS_LOCK_ERROR(rc) ){
838 pFile->lastErrno = tErrno;
839 }
840 goto end_unlock;
841 }
842 }
843 if( eFileLock==NO_LOCK ){
844 /* Decrement the shared lock counter. Release the lock using an
845 ** OS call only when all threads in this same process have released
846 ** the lock.
847 */
848 pInode->nShared--;
849 if( pInode->nShared==0 ){
850 lock.l_type = F_UNLCK;
851 lock.l_whence = SEEK_SET;
852 lock.l_start = lock.l_len = 0L;
853
854 if( fcntl(h, F_SETLK, &lock)!=(-1) ){
855 pInode->eFileLock = NO_LOCK;
856 }else{
857 tErrno = errno;
858 rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR);
859 if( IS_LOCK_ERROR(rc) ){
860 pFile->lastErrno = tErrno;
861 }
862 pInode->eFileLock = NO_LOCK;
863 pFile->eFileLock = NO_LOCK;
864 }
865 }
866
867 /* Decrement the count of locks against this same file. When the
868 ** count reaches zero, close any other file descriptors whose close
869 ** was deferred because of outstanding locks.
870 */
871 pInode->nLock--;
872
873 if( pInode->nLock==0 ){
874 int rc2 = closePendingFds(pFile);
875 if( rc==UNQLITE_OK ){
876 rc = rc2;
877 }
878 }
879 }
880
881end_unlock:
882
883 unixLeaveMutex();
884
885 if( rc==UNQLITE_OK ) pFile->eFileLock = eFileLock;
886 return rc;
887}
888/*
889** Lower the locking level on file descriptor pFile to eFileLock. eFileLock
890** must be either NO_LOCK or SHARED_LOCK.
891**
892** If the locking level of the file descriptor is already at or below
893** the requested locking level, this routine is a no-op.
894*/
895static int unixUnlock(unqlite_file *id, int eFileLock){
896 return _posixUnlock(id, eFileLock, 0);
897}
898/*
899** This function performs the parts of the "close file" operation
900** common to all locking schemes. It closes the directory and file
901** handles, if they are valid, and sets all fields of the unixFile
902** structure to 0.
903**
904*/
905static int closeUnixFile(unqlite_file *id){
906 unixFile *pFile = (unixFile*)id;
907 if( pFile ){
908 if( pFile->dirfd>=0 ){
909 int err = close(pFile->dirfd);
910 if( err ){
911 pFile->lastErrno = errno;
912 return UNQLITE_IOERR;
913 }else{
914 pFile->dirfd=-1;
915 }
916 }
917 if( pFile->h>=0 ){
918 int err = close(pFile->h);
919 if( err ){
920 pFile->lastErrno = errno;
921 return UNQLITE_IOERR;
922 }
923 }
924 unqlite_free(pFile->pUnused);
925 SyZero(pFile,sizeof(unixFile));
926 }
927 return UNQLITE_OK;
928}
929/*
930** Close a file.
931*/
932static int unixClose(unqlite_file *id){
933 int rc = UNQLITE_OK;
934 if( id ){
935 unixFile *pFile = (unixFile *)id;
936 unixUnlock(id, NO_LOCK);
937 unixEnterMutex();
938 if( pFile->pInode && pFile->pInode->nLock ){
939 /* If there are outstanding locks, do not actually close the file just
940 ** yet because that would clear those locks. Instead, add the file
941 ** descriptor to pInode->pUnused list. It will be automatically closed
942 ** when the last lock is cleared.
943 */
944 setPendingFd(pFile);
945 }
946 releaseInodeInfo(pFile);
947 rc = closeUnixFile(id);
948 unixLeaveMutex();
949 }
950 return rc;
951}
952/************** End of the posix advisory lock implementation *****************
953******************************************************************************/
954/*
955**
956** The next division contains implementations for all methods of the
957** unqlite_file object other than the locking methods. The locking
958** methods were defined in divisions above (one locking method per
959** division). Those methods that are common to all locking modes
960** are gather together into this division.
961*/
962/*
963** Seek to the offset passed as the second argument, then read cnt
964** bytes into pBuf. Return the number of bytes actually read.
965**
966** NB: If you define USE_PREAD or USE_PREAD64, then it might also
967** be necessary to define _XOPEN_SOURCE to be 500. This varies from
968** one system to another. Since SQLite does not define USE_PREAD
969** any form by default, we will not attempt to define _XOPEN_SOURCE.
970** See tickets #2741 and #2681.
971**
972** To avoid stomping the errno value on a failed read the lastErrno value
973** is set before returning.
974*/
975static int seekAndRead(unixFile *id, unqlite_int64 offset, void *pBuf, int cnt){
976 int got;
977#if (!defined(USE_PREAD) && !defined(USE_PREAD64))
978 unqlite_int64 newOffset;
979#endif
980
981#if defined(USE_PREAD)
982 got = pread(id->h, pBuf, cnt, offset);
983#elif defined(USE_PREAD64)
984 got = pread64(id->h, pBuf, cnt, offset);
985#else
986 newOffset = lseek(id->h, offset, SEEK_SET);
987
988 if( newOffset!=offset ){
989 if( newOffset == -1 ){
990 ((unixFile*)id)->lastErrno = errno;
991 }else{
992 ((unixFile*)id)->lastErrno = 0;
993 }
994 return -1;
995 }
996 got = read(id->h, pBuf, cnt);
997#endif
998 if( got<0 ){
999 ((unixFile*)id)->lastErrno = errno;
1000 }
1001 return got;
1002}
1003/*
1004** Read data from a file into a buffer. Return UNQLITE_OK if all
1005** bytes were read successfully and UNQLITE_IOERR if anything goes
1006** wrong.
1007*/
1008static int unixRead(
1009 unqlite_file *id,
1010 void *pBuf,
1011 unqlite_int64 amt,
1012 unqlite_int64 offset
1013){
1014 unixFile *pFile = (unixFile *)id;
1015 int got;
1016
1017 got = seekAndRead(pFile, offset, pBuf, (int)amt);
1018 if( got==(int)amt ){
1019 return UNQLITE_OK;
1020 }else if( got<0 ){
1021 /* lastErrno set by seekAndRead */
1022 return UNQLITE_IOERR;
1023 }else{
1024 pFile->lastErrno = 0; /* not a system error */
1025 /* Unread parts of the buffer must be zero-filled */
1026 SyZero(&((char*)pBuf)[got],(sxu32)amt-got);
1027 return UNQLITE_IOERR;
1028 }
1029}
1030/*
1031** Seek to the offset in id->offset then read cnt bytes into pBuf.
1032** Return the number of bytes actually read. Update the offset.
1033**
1034** To avoid stomping the errno value on a failed write the lastErrno value
1035** is set before returning.
1036*/
1037static int seekAndWrite(unixFile *id, unqlite_int64 offset, const void *pBuf, unqlite_int64 cnt){
1038 int got;
1039#if (!defined(USE_PREAD) && !defined(USE_PREAD64))
1040 unqlite_int64 newOffset;
1041#endif
1042
1043#if defined(USE_PREAD)
1044 got = pwrite(id->h, pBuf, cnt, offset);
1045#elif defined(USE_PREAD64)
1046 got = pwrite64(id->h, pBuf, cnt, offset);
1047#else
1048 newOffset = lseek(id->h, offset, SEEK_SET);
1049 if( newOffset!=offset ){
1050 if( newOffset == -1 ){
1051 ((unixFile*)id)->lastErrno = errno;
1052 }else{
1053 ((unixFile*)id)->lastErrno = 0;
1054 }
1055 return -1;
1056 }
1057 got = write(id->h, pBuf, cnt);
1058#endif
1059 if( got<0 ){
1060 ((unixFile*)id)->lastErrno = errno;
1061 }
1062 return got;
1063}
1064/*
1065** Write data from a buffer into a file. Return UNQLITE_OK on success
1066** or some other error code on failure.
1067*/
1068static int unixWrite(
1069 unqlite_file *id,
1070 const void *pBuf,
1071 unqlite_int64 amt,
1072 unqlite_int64 offset
1073){
1074 unixFile *pFile = (unixFile*)id;
1075 int wrote = 0;
1076
1077 while( amt>0 && (wrote = seekAndWrite(pFile, offset, pBuf, amt))>0 ){
1078 amt -= wrote;
1079 offset += wrote;
1080 pBuf = &((char*)pBuf)[wrote];
1081 }
1082
1083 if( amt>0 ){
1084 if( wrote<0 ){
1085 /* lastErrno set by seekAndWrite */
1086 return UNQLITE_IOERR;
1087 }else{
1088 pFile->lastErrno = 0; /* not a system error */
1089 return UNQLITE_FULL;
1090 }
1091 }
1092 return UNQLITE_OK;
1093}
1094/*
1095** We do not trust systems to provide a working fdatasync(). Some do.
1096** Others do no. To be safe, we will stick with the (slower) fsync().
1097** If you know that your system does support fdatasync() correctly,
1098** then simply compile with -Dfdatasync=fdatasync
1099*/
1100#if !defined(fdatasync) && !defined(__linux__)
1101# define fdatasync fsync
1102#endif
1103
1104/*
1105** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not
1106** the F_FULLFSYNC macro is defined. F_FULLFSYNC is currently
1107** only available on Mac OS X. But that could change.
1108*/
1109#ifdef F_FULLFSYNC
1110# define HAVE_FULLFSYNC 1
1111#else
1112# define HAVE_FULLFSYNC 0
1113#endif
1114/*
1115** The fsync() system call does not work as advertised on many
1116** unix systems. The following procedure is an attempt to make
1117** it work better.
1118**
1119**
1120** SQLite sets the dataOnly flag if the size of the file is unchanged.
1121** The idea behind dataOnly is that it should only write the file content
1122** to disk, not the inode. We only set dataOnly if the file size is
1123** unchanged since the file size is part of the inode. However,
1124** Ted Ts'o tells us that fdatasync() will also write the inode if the
1125** file size has changed. The only real difference between fdatasync()
1126** and fsync(), Ted tells us, is that fdatasync() will not flush the
1127** inode if the mtime or owner or other inode attributes have changed.
1128** We only care about the file size, not the other file attributes, so
1129** as far as SQLite is concerned, an fdatasync() is always adequate.
1130** So, we always use fdatasync() if it is available, regardless of
1131** the value of the dataOnly flag.
1132*/
1133static int full_fsync(int fd, int fullSync, int dataOnly){
1134 int rc;
1135#if HAVE_FULLFSYNC
1136 SXUNUSED(dataOnly);
1137#else
1138 SXUNUSED(fullSync);
1139 SXUNUSED(dataOnly);
1140#endif
1141
1142 /* If we compiled with the UNQLITE_NO_SYNC flag, then syncing is a
1143 ** no-op
1144 */
1145#if HAVE_FULLFSYNC
1146 if( fullSync ){
1147 rc = fcntl(fd, F_FULLFSYNC, 0);
1148 }else{
1149 rc = 1;
1150 }
1151 /* If the FULLFSYNC failed, fall back to attempting an fsync().
1152 ** It shouldn't be possible for fullfsync to fail on the local
1153 ** file system (on OSX), so failure indicates that FULLFSYNC
1154 ** isn't supported for this file system. So, attempt an fsync
1155 ** and (for now) ignore the overhead of a superfluous fcntl call.
1156 ** It'd be better to detect fullfsync support once and avoid
1157 ** the fcntl call every time sync is called.
1158 */
1159 if( rc ) rc = fsync(fd);
1160
1161#elif defined(__APPLE__)
1162 /* fdatasync() on HFS+ doesn't yet flush the file size if it changed correctly
1163 ** so currently we default to the macro that redefines fdatasync to fsync
1164 */
1165 rc = fsync(fd);
1166#else
1167 rc = fdatasync(fd);
1168#endif /* ifdef UNQLITE_NO_SYNC elif HAVE_FULLFSYNC */
1169 if( rc!= -1 ){
1170 rc = 0;
1171 }
1172 return rc;
1173}
1174/*
1175** Make sure all writes to a particular file are committed to disk.
1176**
1177** If dataOnly==0 then both the file itself and its metadata (file
1178** size, access time, etc) are synced. If dataOnly!=0 then only the
1179** file data is synced.
1180**
1181** Under Unix, also make sure that the directory entry for the file
1182** has been created by fsync-ing the directory that contains the file.
1183** If we do not do this and we encounter a power failure, the directory
1184** entry for the journal might not exist after we reboot. The next
1185** SQLite to access the file will not know that the journal exists (because
1186** the directory entry for the journal was never created) and the transaction
1187** will not roll back - possibly leading to database corruption.
1188*/
1189static int unixSync(unqlite_file *id, int flags){
1190 int rc;
1191 unixFile *pFile = (unixFile*)id;
1192
1193 int isDataOnly = (flags&UNQLITE_SYNC_DATAONLY);
1194 int isFullsync = (flags&0x0F)==UNQLITE_SYNC_FULL;
1195
1196 rc = full_fsync(pFile->h, isFullsync, isDataOnly);
1197
1198 if( rc ){
1199 pFile->lastErrno = errno;
1200 return UNQLITE_IOERR;
1201 }
1202 if( pFile->dirfd>=0 ){
1203 int err;
1204#ifndef UNQLITE_DISABLE_DIRSYNC
1205 /* The directory sync is only attempted if full_fsync is
1206 ** turned off or unavailable. If a full_fsync occurred above,
1207 ** then the directory sync is superfluous.
1208 */
1209 if( (!HAVE_FULLFSYNC || !isFullsync) && full_fsync(pFile->dirfd,0,0) ){
1210 /*
1211 ** We have received multiple reports of fsync() returning
1212 ** errors when applied to directories on certain file systems.
1213 ** A failed directory sync is not a big deal. So it seems
1214 ** better to ignore the error. Ticket #1657
1215 */
1216 /* pFile->lastErrno = errno; */
1217 /* return UNQLITE_IOERR; */
1218 }
1219#endif
1220 err = close(pFile->dirfd); /* Only need to sync once, so close the */
1221 if( err==0 ){ /* directory when we are done */
1222 pFile->dirfd = -1;
1223 }else{
1224 pFile->lastErrno = errno;
1225 rc = UNQLITE_IOERR;
1226 }
1227 }
1228 return rc;
1229}
1230/*
1231** Truncate an open file to a specified size
1232*/
1233static int unixTruncate(unqlite_file *id, sxi64 nByte){
1234 unixFile *pFile = (unixFile *)id;
1235 int rc;
1236
1237 rc = ftruncate(pFile->h, (off_t)nByte);
1238 if( rc ){
1239 pFile->lastErrno = errno;
1240 return UNQLITE_IOERR;
1241 }else{
1242 return UNQLITE_OK;
1243 }
1244}
1245/*
1246** Determine the current size of a file in bytes
1247*/
1248static int unixFileSize(unqlite_file *id,sxi64 *pSize){
1249 int rc;
1250 struct stat buf;
1251
1252 rc = fstat(((unixFile*)id)->h, &buf);
1253
1254 if( rc!=0 ){
1255 ((unixFile*)id)->lastErrno = errno;
1256 return UNQLITE_IOERR;
1257 }
1258 *pSize = buf.st_size;
1259
1260 /* When opening a zero-size database, the findInodeInfo() procedure
1261 ** writes a single byte into that file in order to work around a bug
1262 ** in the OS-X msdos filesystem. In order to avoid problems with upper
1263 ** layers, we need to report this file size as zero even though it is
1264 ** really 1. Ticket #3260.
1265 */
1266 if( *pSize==1 ) *pSize = 0;
1267
1268 return UNQLITE_OK;
1269}
1270/*
1271** Return the sector size in bytes of the underlying block device for
1272** the specified file. This is almost always 512 bytes, but may be
1273** larger for some devices.
1274**
1275** SQLite code assumes this function cannot fail. It also assumes that
1276** if two files are created in the same file-system directory (i.e.
1277** a database and its journal file) that the sector size will be the
1278** same for both.
1279*/
1280static int unixSectorSize(unqlite_file *NotUsed){
1281 SXUNUSED(NotUsed);
1282 return UNQLITE_DEFAULT_SECTOR_SIZE;
1283}
1284/*
1285** This vector defines all the methods that can operate on an
1286** unqlite_file for Windows systems.
1287*/
1288static const unqlite_io_methods unixIoMethod = {
1289 1, /* iVersion */
1290 unixClose, /* xClose */
1291 unixRead, /* xRead */
1292 unixWrite, /* xWrite */
1293 unixTruncate, /* xTruncate */
1294 unixSync, /* xSync */
1295 unixFileSize, /* xFileSize */
1296 unixLock, /* xLock */
1297 unixUnlock, /* xUnlock */
1298 unixCheckReservedLock, /* xCheckReservedLock */
1299 unixSectorSize, /* xSectorSize */
1300};
1301/****************************************************************************
1302**************************** unqlite_vfs methods ****************************
1303**
1304** This division contains the implementation of methods on the
1305** unqlite_vfs object.
1306*/
1307/*
1308** Initialize the contents of the unixFile structure pointed to by pId.
1309*/
1310static int fillInUnixFile(
1311 unqlite_vfs *pVfs, /* Pointer to vfs object */
1312 int h, /* Open file descriptor of file being opened */
1313 int dirfd, /* Directory file descriptor */
1314 unqlite_file *pId, /* Write to the unixFile structure here */
1315 const char *zFilename, /* Name of the file being opened */
1316 int noLock, /* Omit locking if true */
1317 int isDelete /* Delete on close if true */
1318){
1319 const unqlite_io_methods *pLockingStyle = &unixIoMethod;
1320 unixFile *pNew = (unixFile *)pId;
1321 int rc = UNQLITE_OK;
1322
1323 /* Parameter isDelete is only used on vxworks. Express this explicitly
1324 ** here to prevent compiler warnings about unused parameters.
1325 */
1326 SXUNUSED(isDelete);
1327 SXUNUSED(noLock);
1328 SXUNUSED(pVfs);
1329
1330 pNew->h = h;
1331 pNew->dirfd = dirfd;
1332 pNew->fileFlags = 0;
1333 pNew->zPath = zFilename;
1334
1335 unixEnterMutex();
1336 rc = findInodeInfo(pNew, &pNew->pInode);
1337 if( rc!=UNQLITE_OK ){
1338 /* If an error occured in findInodeInfo(), close the file descriptor
1339 ** immediately, before releasing the mutex. findInodeInfo() may fail
1340 ** in two scenarios:
1341 **
1342 ** (a) A call to fstat() failed.
1343 ** (b) A malloc failed.
1344 **
1345 ** Scenario (b) may only occur if the process is holding no other
1346 ** file descriptors open on the same file. If there were other file
1347 ** descriptors on this file, then no malloc would be required by
1348 ** findInodeInfo(). If this is the case, it is quite safe to close
1349 ** handle h - as it is guaranteed that no posix locks will be released
1350 ** by doing so.
1351 **
1352 ** If scenario (a) caused the error then things are not so safe. The
1353 ** implicit assumption here is that if fstat() fails, things are in
1354 ** such bad shape that dropping a lock or two doesn't matter much.
1355 */
1356 close(h);
1357 h = -1;
1358 }
1359 unixLeaveMutex();
1360
1361 pNew->lastErrno = 0;
1362 if( rc!=UNQLITE_OK ){
1363 if( dirfd>=0 ) close(dirfd); /* silent leak if fail, already in error */
1364 if( h>=0 ) close(h);
1365 }else{
1366 pNew->pMethod = pLockingStyle;
1367 }
1368 return rc;
1369}
1370/*
1371** Open a file descriptor to the directory containing file zFilename.
1372** If successful, *pFd is set to the opened file descriptor and
1373** UNQLITE_OK is returned. If an error occurs, either UNQLITE_NOMEM
1374** or UNQLITE_CANTOPEN is returned and *pFd is set to an undefined
1375** value.
1376**
1377** If UNQLITE_OK is returned, the caller is responsible for closing
1378** the file descriptor *pFd using close().
1379*/
1380static int openDirectory(const char *zFilename, int *pFd){
1381 sxu32 ii;
1382 int fd = -1;
1383 char zDirname[MAX_PATHNAME+1];
1384 sxu32 n;
1385 n = Systrcpy(zDirname,sizeof(zDirname),zFilename,0);
1386 for(ii=n; ii>1 && zDirname[ii]!='/'; ii--);
1387 if( ii>0 ){
1388 zDirname[ii] = '\0';
1389 fd = open(zDirname, O_RDONLY|O_BINARY, 0);
1390 if( fd>=0 ){
1391#ifdef FD_CLOEXEC
1392 fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
1393#endif
1394 }
1395 }
1396 *pFd = fd;
1397 return (fd>=0?UNQLITE_OK: UNQLITE_IOERR );
1398}
1399/*
1400** Search for an unused file descriptor that was opened on the database
1401** file (not a journal or master-journal file) identified by pathname
1402** zPath with UNQLITE_OPEN_XXX flags matching those passed as the second
1403** argument to this function.
1404**
1405** Such a file descriptor may exist if a database connection was closed
1406** but the associated file descriptor could not be closed because some
1407** other file descriptor open on the same file is holding a file-lock.
1408** Refer to comments in the unixClose() function and the lengthy comment
1409** describing "Posix Advisory Locking" at the start of this file for
1410** further details. Also, ticket #4018.
1411**
1412** If a suitable file descriptor is found, then it is returned. If no
1413** such file descriptor is located, -1 is returned.
1414*/
1415static UnixUnusedFd *findReusableFd(const char *zPath, int flags){
1416 UnixUnusedFd *pUnused = 0;
1417 struct stat sStat; /* Results of stat() call */
1418 /* A stat() call may fail for various reasons. If this happens, it is
1419 ** almost certain that an open() call on the same path will also fail.
1420 ** For this reason, if an error occurs in the stat() call here, it is
1421 ** ignored and -1 is returned. The caller will try to open a new file
1422 ** descriptor on the same path, fail, and return an error to SQLite.
1423 **
1424 ** Even if a subsequent open() call does succeed, the consequences of
1425 ** not searching for a resusable file descriptor are not dire. */
1426 if( 0==stat(zPath, &sStat) ){
1427 unixInodeInfo *pInode;
1428
1429 unixEnterMutex();
1430 pInode = inodeList;
1431 while( pInode && (pInode->fileId.dev!=sStat.st_dev
1432 || pInode->fileId.ino!=sStat.st_ino) ){
1433 pInode = pInode->pNext;
1434 }
1435 if( pInode ){
1436 UnixUnusedFd **pp;
1437 for(pp=&pInode->pUnused; *pp && (*pp)->flags!=flags; pp=&((*pp)->pNext));
1438 pUnused = *pp;
1439 if( pUnused ){
1440 *pp = pUnused->pNext;
1441 }
1442 }
1443 unixLeaveMutex();
1444 }
1445 return pUnused;
1446}
1447/*
1448** This function is called by unixOpen() to determine the unix permissions
1449** to create new files with. If no error occurs, then UNQLITE_OK is returned
1450** and a value suitable for passing as the third argument to open(2) is
1451** written to *pMode. If an IO error occurs, an SQLite error code is
1452** returned and the value of *pMode is not modified.
1453**
1454** If the file being opened is a temporary file, it is always created with
1455** the octal permissions 0600 (read/writable by owner only). If the file
1456** is a database or master journal file, it is created with the permissions
1457** mask UNQLITE_DEFAULT_FILE_PERMISSIONS.
1458**
1459** Finally, if the file being opened is a WAL or regular journal file, then
1460** this function queries the file-system for the permissions on the
1461** corresponding database file and sets *pMode to this value. Whenever
1462** possible, WAL and journal files are created using the same permissions
1463** as the associated database file.
1464*/
1465static int findCreateFileMode(
1466 const char *zPath, /* Path of file (possibly) being created */
1467 int flags, /* Flags passed as 4th argument to xOpen() */
1468 mode_t *pMode /* OUT: Permissions to open file with */
1469){
1470 int rc = UNQLITE_OK; /* Return Code */
1471 if( flags & UNQLITE_OPEN_TEMP_DB ){
1472 *pMode = 0600;
1473 SXUNUSED(zPath);
1474 }else{
1475 *pMode = UNQLITE_DEFAULT_FILE_PERMISSIONS;
1476 }
1477 return rc;
1478}
1479/*
1480** Open the file zPath.
1481**
1482** Previously, the SQLite OS layer used three functions in place of this
1483** one:
1484**
1485** unqliteOsOpenReadWrite();
1486** unqliteOsOpenReadOnly();
1487** unqliteOsOpenExclusive();
1488**
1489** These calls correspond to the following combinations of flags:
1490**
1491** ReadWrite() -> (READWRITE | CREATE)
1492** ReadOnly() -> (READONLY)
1493** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE)
1494**
1495** The old OpenExclusive() accepted a boolean argument - "delFlag". If
1496** true, the file was configured to be automatically deleted when the
1497** file handle closed. To achieve the same effect using this new
1498** interface, add the DELETEONCLOSE flag to those specified above for
1499** OpenExclusive().
1500*/
1501static int unixOpen(
1502 unqlite_vfs *pVfs, /* The VFS for which this is the xOpen method */
1503 const char *zPath, /* Pathname of file to be opened */
1504 unqlite_file *pFile, /* The file descriptor to be filled in */
1505 unsigned int flags /* Input flags to control the opening */
1506){
1507 unixFile *p = (unixFile *)pFile;
1508 int fd = -1; /* File descriptor returned by open() */
1509 int dirfd = -1; /* Directory file descriptor */
1510 int openFlags = 0; /* Flags to pass to open() */
1511 int noLock; /* True to omit locking primitives */
1512 int rc = UNQLITE_OK; /* Function Return Code */
1513 UnixUnusedFd *pUnused;
1514 int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE);
1515 int isDelete = (flags & UNQLITE_OPEN_TEMP_DB);
1516 int isCreate = (flags & UNQLITE_OPEN_CREATE);
1517 int isReadonly = (flags & UNQLITE_OPEN_READONLY);
1518 int isReadWrite = (flags & UNQLITE_OPEN_READWRITE);
1519 /* If creating a master or main-file journal, this function will open
1520 ** a file-descriptor on the directory too. The first time unixSync()
1521 ** is called the directory file descriptor will be fsync()ed and close()d.
1522 */
1523 int isOpenDirectory = isCreate ;
1524 const char *zName = zPath;
1525
1526 SyZero(p,sizeof(unixFile));
1527
1528 pUnused = findReusableFd(zName, flags);
1529 if( pUnused ){
1530 fd = pUnused->fd;
1531 }else{
1532 pUnused = unqlite_malloc(sizeof(*pUnused));
1533 if( !pUnused ){
1534 return UNQLITE_NOMEM;
1535 }
1536 }
1537 p->pUnused = pUnused;
1538
1539 /* Determine the value of the flags parameter passed to POSIX function
1540 ** open(). These must be calculated even if open() is not called, as
1541 ** they may be stored as part of the file handle and used by the
1542 ** 'conch file' locking functions later on. */
1543 if( isReadonly ) openFlags |= O_RDONLY;
1544 if( isReadWrite ) openFlags |= O_RDWR;
1545 if( isCreate ) openFlags |= O_CREAT;
1546 if( isExclusive ) openFlags |= (O_EXCL|O_NOFOLLOW);
1547 openFlags |= (O_LARGEFILE|O_BINARY);
1548
1549 if( fd<0 ){
1550 mode_t openMode; /* Permissions to create file with */
1551 rc = findCreateFileMode(zName, flags, &openMode);
1552 if( rc!=UNQLITE_OK ){
1553 return rc;
1554 }
1555 fd = open(zName, openFlags, openMode);
1556 if( fd<0 ){
1557 rc = UNQLITE_IOERR;
1558 goto open_finished;
1559 }
1560 }
1561
1562 if( p->pUnused ){
1563 p->pUnused->fd = fd;
1564 p->pUnused->flags = flags;
1565 }
1566
1567 if( isDelete ){
1568 unlink(zName);
1569 }
1570
1571 if( isOpenDirectory ){
1572 rc = openDirectory(zPath, &dirfd);
1573 if( rc!=UNQLITE_OK ){
1574 /* It is safe to close fd at this point, because it is guaranteed not
1575 ** to be open on a database file. If it were open on a database file,
1576 ** it would not be safe to close as this would release any locks held
1577 ** on the file by this process. */
1578 close(fd); /* silently leak if fail, already in error */
1579 goto open_finished;
1580 }
1581 }
1582
1583#ifdef FD_CLOEXEC
1584 fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC);
1585#endif
1586
1587 noLock = 0;
1588
1589#if defined(__APPLE__)
1590 struct statfs fsInfo;
1591 if( fstatfs(fd, &fsInfo) == -1 ){
1592 ((unixFile*)pFile)->lastErrno = errno;
1593 if( dirfd>=0 ) close(dirfd); /* silently leak if fail, in error */
1594 close(fd); /* silently leak if fail, in error */
1595 return UNQLITE_IOERR;
1596 }
1597 if (0 == SyStrncmp("msdos", fsInfo.f_fstypename, 5)) {
1598 ((unixFile*)pFile)->fsFlags |= UNQLITE_FSFLAGS_IS_MSDOS;
1599 }
1600#endif
1601
1602 rc = fillInUnixFile(pVfs, fd, dirfd, pFile, zPath, noLock, isDelete);
1603open_finished:
1604 if( rc!=UNQLITE_OK ){
1605 unqlite_free(p->pUnused);
1606 }
1607 return rc;
1608}
1609/*
1610** Delete the file at zPath. If the dirSync argument is true, fsync()
1611** the directory after deleting the file.
1612*/
1613static int unixDelete(
1614 unqlite_vfs *NotUsed, /* VFS containing this as the xDelete method */
1615 const char *zPath, /* Name of file to be deleted */
1616 int dirSync /* If true, fsync() directory after deleting file */
1617){
1618 int rc = UNQLITE_OK;
1619 SXUNUSED(NotUsed);
1620
1621 if( unlink(zPath)==(-1) && errno!=ENOENT ){
1622 return UNQLITE_IOERR;
1623 }
1624#ifndef UNQLITE_DISABLE_DIRSYNC
1625 if( dirSync ){
1626 int fd;
1627 rc = openDirectory(zPath, &fd);
1628 if( rc==UNQLITE_OK ){
1629 if( fsync(fd) )
1630 {
1631 rc = UNQLITE_IOERR;
1632 }
1633 if( close(fd) && !rc ){
1634 rc = UNQLITE_IOERR;
1635 }
1636 }
1637 }
1638#endif
1639 return rc;
1640}
1641/*
1642** Sleep for a little while. Return the amount of time slept.
1643** The argument is the number of microseconds we want to sleep.
1644** The return value is the number of microseconds of sleep actually
1645** requested from the underlying operating system, a number which
1646** might be greater than or equal to the argument, but not less
1647** than the argument.
1648*/
1649static int unixSleep(unqlite_vfs *NotUsed, int microseconds)
1650{
1651#if defined(HAVE_USLEEP) && HAVE_USLEEP
1652 usleep(microseconds);
1653 SXUNUSED(NotUsed);
1654 return microseconds;
1655#else
1656 int seconds = (microseconds+999999)/1000000;
1657 SXUNUSED(NotUsed);
1658 sleep(seconds);
1659 return seconds*1000000;
1660#endif
1661}
1662/*
1663 * Export the current system time.
1664 */
1665static int unixCurrentTime(unqlite_vfs *pVfs,Sytm *pOut)
1666{
1667 struct tm *pTm;
1668 time_t tt;
1669 SXUNUSED(pVfs);
1670 time(&tt);
1671 pTm = gmtime(&tt);
1672 if( pTm ){ /* Yes, it can fail */
1673 STRUCT_TM_TO_SYTM(pTm,pOut);
1674 }
1675 return UNQLITE_OK;
1676}
1677/*
1678** Test the existance of or access permissions of file zPath. The
1679** test performed depends on the value of flags:
1680**
1681** UNQLITE_ACCESS_EXISTS: Return 1 if the file exists
1682** UNQLITE_ACCESS_READWRITE: Return 1 if the file is read and writable.
1683** UNQLITE_ACCESS_READONLY: Return 1 if the file is readable.
1684**
1685** Otherwise return 0.
1686*/
1687static int unixAccess(
1688 unqlite_vfs *NotUsed, /* The VFS containing this xAccess method */
1689 const char *zPath, /* Path of the file to examine */
1690 int flags, /* What do we want to learn about the zPath file? */
1691 int *pResOut /* Write result boolean here */
1692){
1693 int amode = 0;
1694 SXUNUSED(NotUsed);
1695 switch( flags ){
1696 case UNQLITE_ACCESS_EXISTS:
1697 amode = F_OK;
1698 break;
1699 case UNQLITE_ACCESS_READWRITE:
1700 amode = W_OK|R_OK;
1701 break;
1702 case UNQLITE_ACCESS_READ:
1703 amode = R_OK;
1704 break;
1705 default:
1706 /* Can't happen */
1707 break;
1708 }
1709 *pResOut = (access(zPath, amode)==0);
1710 if( flags==UNQLITE_ACCESS_EXISTS && *pResOut ){
1711 struct stat buf;
1712 if( 0==stat(zPath, &buf) && buf.st_size==0 ){
1713 *pResOut = 0;
1714 }
1715 }
1716 return UNQLITE_OK;
1717}
1718/*
1719** Turn a relative pathname into a full pathname. The relative path
1720** is stored as a nul-terminated string in the buffer pointed to by
1721** zPath.
1722**
1723** zOut points to a buffer of at least unqlite_vfs.mxPathname bytes
1724** (in this case, MAX_PATHNAME bytes). The full-path is written to
1725** this buffer before returning.
1726*/
1727static int unixFullPathname(
1728 unqlite_vfs *pVfs, /* Pointer to vfs object */
1729 const char *zPath, /* Possibly relative input path */
1730 int nOut, /* Size of output buffer in bytes */
1731 char *zOut /* Output buffer */
1732){
1733 if( zPath[0]=='/' ){
1734 Systrcpy(zOut,(sxu32)nOut,zPath,0);
1735 SXUNUSED(pVfs);
1736 }else{
1737 sxu32 nCwd;
1738 zOut[nOut-1] = '\0';
1739 if( getcwd(zOut, nOut-1)==0 ){
1740 return UNQLITE_IOERR;
1741 }
1742 nCwd = SyStrlen(zOut);
1743 SyBufferFormat(&zOut[nCwd],(sxu32)nOut-nCwd,"/%s",zPath);
1744 }
1745 return UNQLITE_OK;
1746}
1747/*
1748 * Export the Unix Vfs.
1749 */
1750UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void)
1751{
1752 static const unqlite_vfs sUnixvfs = {
1753 "Unix", /* Vfs name */
1754 1, /* Vfs structure version */
1755 sizeof(unixFile), /* szOsFile */
1756 MAX_PATHNAME, /* mxPathName */
1757 unixOpen, /* xOpen */
1758 unixDelete, /* xDelete */
1759 unixAccess, /* xAccess */
1760 unixFullPathname, /* xFullPathname */
1761 0, /* xTmp */
1762 unixSleep, /* xSleep */
1763 unixCurrentTime, /* xCurrentTime */
1764 0, /* xGetLastError */
1765 };
1766 return &sUnixvfs;
1767}
1768
1769#endif /* __UNIXES__ */
diff --git a/common/unqlite/os_win.c b/common/unqlite/os_win.c
new file mode 100644
index 0000000..c1e0821
--- /dev/null
+++ b/common/unqlite/os_win.c
@@ -0,0 +1,940 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: os_win.c v1.2 Win7 2012-11-10 12:10 devel <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/* Omit the whole layer from the build if compiling for platforms other than Windows */
18#ifdef __WINNT__
19/* This file contains code that is specific to windows. (Mostly SQLite3 source tree) */
20#include <Windows.h>
21/*
22** Some microsoft compilers lack this definition.
23*/
24#ifndef INVALID_FILE_ATTRIBUTES
25# define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
26#endif
27/*
28** WinCE lacks native support for file locking so we have to fake it
29** with some code of our own.
30*/
31#ifdef __WIN_CE__
32typedef struct winceLock {
33 int nReaders; /* Number of reader locks obtained */
34 BOOL bPending; /* Indicates a pending lock has been obtained */
35 BOOL bReserved; /* Indicates a reserved lock has been obtained */
36 BOOL bExclusive; /* Indicates an exclusive lock has been obtained */
37} winceLock;
38#define AreFileApisANSI() 1
39#define FormatMessageW(a,b,c,d,e,f,g) 0
40#endif
41
42/*
43** The winFile structure is a subclass of unqlite_file* specific to the win32
44** portability layer.
45*/
46typedef struct winFile winFile;
47struct winFile {
48 const unqlite_io_methods *pMethod; /*** Must be first ***/
49 unqlite_vfs *pVfs; /* The VFS used to open this file */
50 HANDLE h; /* Handle for accessing the file */
51 sxu8 locktype; /* Type of lock currently held on this file */
52 short sharedLockByte; /* Randomly chosen byte used as a shared lock */
53 DWORD lastErrno; /* The Windows errno from the last I/O error */
54 DWORD sectorSize; /* Sector size of the device file is on */
55 int szChunk; /* Chunk size */
56#ifdef __WIN_CE__
57 WCHAR *zDeleteOnClose; /* Name of file to delete when closing */
58 HANDLE hMutex; /* Mutex used to control access to shared lock */
59 HANDLE hShared; /* Shared memory segment used for locking */
60 winceLock local; /* Locks obtained by this instance of winFile */
61 winceLock *shared; /* Global shared lock memory for the file */
62#endif
63};
64/*
65** Convert a UTF-8 string to microsoft unicode (UTF-16?).
66**
67** Space to hold the returned string is obtained from HeapAlloc().
68*/
69static WCHAR *utf8ToUnicode(const char *zFilename){
70 int nChar;
71 WCHAR *zWideFilename;
72
73 nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0);
74 zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nChar*sizeof(zWideFilename[0]) );
75 if( zWideFilename==0 ){
76 return 0;
77 }
78 nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar);
79 if( nChar==0 ){
80 HeapFree(GetProcessHeap(),0,zWideFilename);
81 zWideFilename = 0;
82 }
83 return zWideFilename;
84}
85
86/*
87** Convert microsoft unicode to UTF-8. Space to hold the returned string is
88** obtained from malloc().
89*/
90static char *unicodeToUtf8(const WCHAR *zWideFilename){
91 int nByte;
92 char *zFilename;
93
94 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0);
95 zFilename = (char *)HeapAlloc(GetProcessHeap(),0,nByte );
96 if( zFilename==0 ){
97 return 0;
98 }
99 nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte,
100 0, 0);
101 if( nByte == 0 ){
102 HeapFree(GetProcessHeap(),0,zFilename);
103 zFilename = 0;
104 }
105 return zFilename;
106}
107
108/*
109** Convert an ansi string to microsoft unicode, based on the
110** current codepage settings for file apis.
111**
112** Space to hold the returned string is obtained
113** from malloc.
114*/
115static WCHAR *mbcsToUnicode(const char *zFilename){
116 int nByte;
117 WCHAR *zMbcsFilename;
118 int codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
119
120 nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, 0,0)*sizeof(WCHAR);
121 zMbcsFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zMbcsFilename[0]) );
122 if( zMbcsFilename==0 ){
123 return 0;
124 }
125 nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte);
126 if( nByte==0 ){
127 HeapFree(GetProcessHeap(),0,zMbcsFilename);
128 zMbcsFilename = 0;
129 }
130 return zMbcsFilename;
131}
132/*
133** Convert multibyte character string to UTF-8. Space to hold the
134** returned string is obtained from malloc().
135*/
136char *unqlite_win32_mbcs_to_utf8(const char *zFilename){
137 char *zFilenameUtf8;
138 WCHAR *zTmpWide;
139
140 zTmpWide = mbcsToUnicode(zFilename);
141 if( zTmpWide==0 ){
142 return 0;
143 }
144 zFilenameUtf8 = unicodeToUtf8(zTmpWide);
145 HeapFree(GetProcessHeap(),0,zTmpWide);
146 return zFilenameUtf8;
147}
148/*
149** Some microsoft compilers lack this definition.
150*/
151#ifndef INVALID_SET_FILE_POINTER
152# define INVALID_SET_FILE_POINTER ((DWORD)-1)
153#endif
154
155/*
156** Move the current position of the file handle passed as the first
157** argument to offset iOffset within the file. If successful, return 0.
158** Otherwise, set pFile->lastErrno and return non-zero.
159*/
160static int seekWinFile(winFile *pFile, unqlite_int64 iOffset){
161 LONG upperBits; /* Most sig. 32 bits of new offset */
162 LONG lowerBits; /* Least sig. 32 bits of new offset */
163 DWORD dwRet; /* Value returned by SetFilePointer() */
164
165 upperBits = (LONG)((iOffset>>32) & 0x7fffffff);
166 lowerBits = (LONG)(iOffset & 0xffffffff);
167
168 /* API oddity: If successful, SetFilePointer() returns a dword
169 ** containing the lower 32-bits of the new file-offset. Or, if it fails,
170 ** it returns INVALID_SET_FILE_POINTER. However according to MSDN,
171 ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine
172 ** whether an error has actually occured, it is also necessary to call
173 ** GetLastError().
174 */
175 dwRet = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN);
176 if( (dwRet==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR) ){
177 pFile->lastErrno = GetLastError();
178 return 1;
179 }
180 return 0;
181}
182/*
183** Close a file.
184**
185** It is reported that an attempt to close a handle might sometimes
186** fail. This is a very unreasonable result, but windows is notorious
187** for being unreasonable so I do not doubt that it might happen. If
188** the close fails, we pause for 100 milliseconds and try again. As
189** many as MX_CLOSE_ATTEMPT attempts to close the handle are made before
190** giving up and returning an error.
191*/
192#define MX_CLOSE_ATTEMPT 3
193static int winClose(unqlite_file *id)
194{
195 int rc, cnt = 0;
196 winFile *pFile = (winFile*)id;
197 do{
198 rc = CloseHandle(pFile->h);
199 }while( rc==0 && ++cnt < MX_CLOSE_ATTEMPT && (Sleep(100), 1) );
200
201 return rc ? UNQLITE_OK : UNQLITE_IOERR;
202}
203/*
204** Read data from a file into a buffer. Return UNQLITE_OK if all
205** bytes were read successfully and UNQLITE_IOERR if anything goes
206** wrong.
207*/
208static int winRead(
209 unqlite_file *id, /* File to read from */
210 void *pBuf, /* Write content into this buffer */
211 unqlite_int64 amt, /* Number of bytes to read */
212 unqlite_int64 offset /* Begin reading at this offset */
213){
214 winFile *pFile = (winFile*)id; /* file handle */
215 DWORD nRead; /* Number of bytes actually read from file */
216
217 if( seekWinFile(pFile, offset) ){
218 return UNQLITE_FULL;
219 }
220 if( !ReadFile(pFile->h, pBuf, (DWORD)amt, &nRead, 0) ){
221 pFile->lastErrno = GetLastError();
222 return UNQLITE_IOERR;
223 }
224 if( nRead<(DWORD)amt ){
225 /* Unread parts of the buffer must be zero-filled */
226 SyZero(&((char*)pBuf)[nRead],(sxu32)(amt-nRead));
227 return UNQLITE_IOERR;
228 }
229
230 return UNQLITE_OK;
231}
232
233/*
234** Write data from a buffer into a file. Return UNQLITE_OK on success
235** or some other error code on failure.
236*/
237static int winWrite(
238 unqlite_file *id, /* File to write into */
239 const void *pBuf, /* The bytes to be written */
240 unqlite_int64 amt, /* Number of bytes to write */
241 unqlite_int64 offset /* Offset into the file to begin writing at */
242){
243 int rc; /* True if error has occured, else false */
244 winFile *pFile = (winFile*)id; /* File handle */
245
246 rc = seekWinFile(pFile, offset);
247 if( rc==0 ){
248 sxu8 *aRem = (sxu8 *)pBuf; /* Data yet to be written */
249 unqlite_int64 nRem = amt; /* Number of bytes yet to be written */
250 DWORD nWrite; /* Bytes written by each WriteFile() call */
251
252 while( nRem>0 && WriteFile(pFile->h, aRem, (DWORD)nRem, &nWrite, 0) && nWrite>0 ){
253 aRem += nWrite;
254 nRem -= nWrite;
255 }
256 if( nRem>0 ){
257 pFile->lastErrno = GetLastError();
258 rc = 1;
259 }
260 }
261 if( rc ){
262 if( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ){
263 return UNQLITE_FULL;
264 }
265 return UNQLITE_IOERR;
266 }
267 return UNQLITE_OK;
268}
269
270/*
271** Truncate an open file to a specified size
272*/
273static int winTruncate(unqlite_file *id, unqlite_int64 nByte){
274 winFile *pFile = (winFile*)id; /* File handle object */
275 int rc = UNQLITE_OK; /* Return code for this function */
276
277
278 /* If the user has configured a chunk-size for this file, truncate the
279 ** file so that it consists of an integer number of chunks (i.e. the
280 ** actual file size after the operation may be larger than the requested
281 ** size).
282 */
283 if( pFile->szChunk ){
284 nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk;
285 }
286
287 /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */
288 if( seekWinFile(pFile, nByte) ){
289 rc = UNQLITE_IOERR;
290 }else if( 0==SetEndOfFile(pFile->h) ){
291 pFile->lastErrno = GetLastError();
292 rc = UNQLITE_IOERR;
293 }
294 return rc;
295}
296/*
297** Make sure all writes to a particular file are committed to disk.
298*/
299static int winSync(unqlite_file *id, int flags){
300 winFile *pFile = (winFile*)id;
301 SXUNUSED(flags); /* MSVC warning */
302 if( FlushFileBuffers(pFile->h) ){
303 return UNQLITE_OK;
304 }else{
305 pFile->lastErrno = GetLastError();
306 return UNQLITE_IOERR;
307 }
308}
309/*
310** Determine the current size of a file in bytes
311*/
312static int winFileSize(unqlite_file *id, unqlite_int64 *pSize){
313 DWORD upperBits;
314 DWORD lowerBits;
315 winFile *pFile = (winFile*)id;
316 DWORD error;
317 lowerBits = GetFileSize(pFile->h, &upperBits);
318 if( (lowerBits == INVALID_FILE_SIZE)
319 && ((error = GetLastError()) != NO_ERROR) )
320 {
321 pFile->lastErrno = error;
322 return UNQLITE_IOERR;
323 }
324 *pSize = (((unqlite_int64)upperBits)<<32) + lowerBits;
325 return UNQLITE_OK;
326}
327/*
328** LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems.
329*/
330#ifndef LOCKFILE_FAIL_IMMEDIATELY
331# define LOCKFILE_FAIL_IMMEDIATELY 1
332#endif
333
334/*
335** Acquire a reader lock.
336*/
337static int getReadLock(winFile *pFile){
338 int res;
339 OVERLAPPED ovlp;
340 ovlp.Offset = SHARED_FIRST;
341 ovlp.OffsetHigh = 0;
342 ovlp.hEvent = 0;
343 res = LockFileEx(pFile->h, LOCKFILE_FAIL_IMMEDIATELY,0, SHARED_SIZE, 0, &ovlp);
344 if( res == 0 ){
345 pFile->lastErrno = GetLastError();
346 }
347 return res;
348}
349/*
350** Undo a readlock
351*/
352static int unlockReadLock(winFile *pFile){
353 int res;
354 res = UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
355 if( res == 0 ){
356 pFile->lastErrno = GetLastError();
357 }
358 return res;
359}
360/*
361** Lock the file with the lock specified by parameter locktype - one
362** of the following:
363**
364** (1) SHARED_LOCK
365** (2) RESERVED_LOCK
366** (3) PENDING_LOCK
367** (4) EXCLUSIVE_LOCK
368**
369** Sometimes when requesting one lock state, additional lock states
370** are inserted in between. The locking might fail on one of the later
371** transitions leaving the lock state different from what it started but
372** still short of its goal. The following chart shows the allowed
373** transitions and the inserted intermediate states:
374**
375** UNLOCKED -> SHARED
376** SHARED -> RESERVED
377** SHARED -> (PENDING) -> EXCLUSIVE
378** RESERVED -> (PENDING) -> EXCLUSIVE
379** PENDING -> EXCLUSIVE
380**
381** This routine will only increase a lock. The winUnlock() routine
382** erases all locks at once and returns us immediately to locking level 0.
383** It is not possible to lower the locking level one step at a time. You
384** must go straight to locking level 0.
385*/
386static int winLock(unqlite_file *id, int locktype){
387 int rc = UNQLITE_OK; /* Return code from subroutines */
388 int res = 1; /* Result of a windows lock call */
389 int newLocktype; /* Set pFile->locktype to this value before exiting */
390 int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */
391 winFile *pFile = (winFile*)id;
392 DWORD error = NO_ERROR;
393
394 /* If there is already a lock of this type or more restrictive on the
395 ** OsFile, do nothing.
396 */
397 if( pFile->locktype>=locktype ){
398 return UNQLITE_OK;
399 }
400
401 /* Make sure the locking sequence is correct
402 assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK );
403 assert( locktype!=PENDING_LOCK );
404 assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK );
405 */
406 /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or
407 ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of
408 ** the PENDING_LOCK byte is temporary.
409 */
410 newLocktype = pFile->locktype;
411 if( (pFile->locktype==NO_LOCK)
412 || ( (locktype==EXCLUSIVE_LOCK)
413 && (pFile->locktype==RESERVED_LOCK))
414 ){
415 int cnt = 3;
416 while( cnt-->0 && (res = LockFile(pFile->h, PENDING_BYTE, 0, 1, 0))==0 ){
417 /* Try 3 times to get the pending lock. The pending lock might be
418 ** held by another reader process who will release it momentarily.
419 */
420 Sleep(1);
421 }
422 gotPendingLock = res;
423 if( !res ){
424 error = GetLastError();
425 }
426 }
427
428 /* Acquire a shared lock
429 */
430 if( locktype==SHARED_LOCK && res ){
431 /* assert( pFile->locktype==NO_LOCK ); */
432 res = getReadLock(pFile);
433 if( res ){
434 newLocktype = SHARED_LOCK;
435 }else{
436 error = GetLastError();
437 }
438 }
439
440 /* Acquire a RESERVED lock
441 */
442 if( locktype==RESERVED_LOCK && res ){
443 /* assert( pFile->locktype==SHARED_LOCK ); */
444 res = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
445 if( res ){
446 newLocktype = RESERVED_LOCK;
447 }else{
448 error = GetLastError();
449 }
450 }
451
452 /* Acquire a PENDING lock
453 */
454 if( locktype==EXCLUSIVE_LOCK && res ){
455 newLocktype = PENDING_LOCK;
456 gotPendingLock = 0;
457 }
458
459 /* Acquire an EXCLUSIVE lock
460 */
461 if( locktype==EXCLUSIVE_LOCK && res ){
462 /* assert( pFile->locktype>=SHARED_LOCK ); */
463 res = unlockReadLock(pFile);
464 res = LockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
465 if( res ){
466 newLocktype = EXCLUSIVE_LOCK;
467 }else{
468 error = GetLastError();
469 getReadLock(pFile);
470 }
471 }
472
473 /* If we are holding a PENDING lock that ought to be released, then
474 ** release it now.
475 */
476 if( gotPendingLock && locktype==SHARED_LOCK ){
477 UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0);
478 }
479
480 /* Update the state of the lock has held in the file descriptor then
481 ** return the appropriate result code.
482 */
483 if( res ){
484 rc = UNQLITE_OK;
485 }else{
486 pFile->lastErrno = error;
487 rc = UNQLITE_BUSY;
488 }
489 pFile->locktype = (sxu8)newLocktype;
490 return rc;
491}
492/*
493** This routine checks if there is a RESERVED lock held on the specified
494** file by this or any other process. If such a lock is held, return
495** non-zero, otherwise zero.
496*/
497static int winCheckReservedLock(unqlite_file *id, int *pResOut){
498 int rc;
499 winFile *pFile = (winFile*)id;
500 if( pFile->locktype>=RESERVED_LOCK ){
501 rc = 1;
502 }else{
503 rc = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
504 if( rc ){
505 UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
506 }
507 rc = !rc;
508 }
509 *pResOut = rc;
510 return UNQLITE_OK;
511}
512/*
513** Lower the locking level on file descriptor id to locktype. locktype
514** must be either NO_LOCK or SHARED_LOCK.
515**
516** If the locking level of the file descriptor is already at or below
517** the requested locking level, this routine is a no-op.
518**
519** It is not possible for this routine to fail if the second argument
520** is NO_LOCK. If the second argument is SHARED_LOCK then this routine
521** might return UNQLITE_IOERR;
522*/
523static int winUnlock(unqlite_file *id, int locktype){
524 int type;
525 winFile *pFile = (winFile*)id;
526 int rc = UNQLITE_OK;
527
528 type = pFile->locktype;
529 if( type>=EXCLUSIVE_LOCK ){
530 UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0);
531 if( locktype==SHARED_LOCK && !getReadLock(pFile) ){
532 /* This should never happen. We should always be able to
533 ** reacquire the read lock */
534 rc = UNQLITE_IOERR;
535 }
536 }
537 if( type>=RESERVED_LOCK ){
538 UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0);
539 }
540 if( locktype==NO_LOCK && type>=SHARED_LOCK ){
541 unlockReadLock(pFile);
542 }
543 if( type>=PENDING_LOCK ){
544 UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0);
545 }
546 pFile->locktype = (sxu8)locktype;
547 return rc;
548}
549/*
550** Return the sector size in bytes of the underlying block device for
551** the specified file. This is almost always 512 bytes, but may be
552** larger for some devices.
553**
554*/
555static int winSectorSize(unqlite_file *id){
556 return (int)(((winFile*)id)->sectorSize);
557}
558/*
559** This vector defines all the methods that can operate on an
560** unqlite_file for Windows systems.
561*/
562static const unqlite_io_methods winIoMethod = {
563 1, /* iVersion */
564 winClose, /* xClose */
565 winRead, /* xRead */
566 winWrite, /* xWrite */
567 winTruncate, /* xTruncate */
568 winSync, /* xSync */
569 winFileSize, /* xFileSize */
570 winLock, /* xLock */
571 winUnlock, /* xUnlock */
572 winCheckReservedLock, /* xCheckReservedLock */
573 winSectorSize, /* xSectorSize */
574};
575/*
576 * Windows VFS Methods.
577 */
578/*
579** Convert a UTF-8 filename into whatever form the underlying
580** operating system wants filenames in. Space to hold the result
581** is obtained from malloc and must be freed by the calling
582** function.
583*/
584static void *convertUtf8Filename(const char *zFilename)
585{
586 void *zConverted;
587 zConverted = utf8ToUnicode(zFilename);
588 /* caller will handle out of memory */
589 return zConverted;
590}
591/*
592** Delete the named file.
593**
594** Note that windows does not allow a file to be deleted if some other
595** process has it open. Sometimes a virus scanner or indexing program
596** will open a journal file shortly after it is created in order to do
597** whatever it does. While this other process is holding the
598** file open, we will be unable to delete it. To work around this
599** problem, we delay 100 milliseconds and try to delete again. Up
600** to MX_DELETION_ATTEMPTs deletion attempts are run before giving
601** up and returning an error.
602*/
603#define MX_DELETION_ATTEMPTS 5
604static int winDelete(
605 unqlite_vfs *pVfs, /* Not used on win32 */
606 const char *zFilename, /* Name of file to delete */
607 int syncDir /* Not used on win32 */
608){
609 int cnt = 0;
610 DWORD rc;
611 DWORD error = 0;
612 void *zConverted;
613 zConverted = convertUtf8Filename(zFilename);
614 if( zConverted==0 ){
615 SXUNUSED(pVfs);
616 SXUNUSED(syncDir);
617 return UNQLITE_NOMEM;
618 }
619 do{
620 DeleteFileW((LPCWSTR)zConverted);
621 }while( ( ((rc = GetFileAttributesW((LPCWSTR)zConverted)) != INVALID_FILE_ATTRIBUTES)
622 || ((error = GetLastError()) == ERROR_ACCESS_DENIED))
623 && (++cnt < MX_DELETION_ATTEMPTS)
624 && (Sleep(100), 1)
625 );
626 HeapFree(GetProcessHeap(),0,zConverted);
627
628 return ( (rc == INVALID_FILE_ATTRIBUTES)
629 && (error == ERROR_FILE_NOT_FOUND)) ? UNQLITE_OK : UNQLITE_IOERR;
630}
631/*
632** Check the existance and status of a file.
633*/
634static int winAccess(
635 unqlite_vfs *pVfs, /* Not used */
636 const char *zFilename, /* Name of file to check */
637 int flags, /* Type of test to make on this file */
638 int *pResOut /* OUT: Result */
639){
640 WIN32_FILE_ATTRIBUTE_DATA sAttrData;
641 DWORD attr;
642 int rc = 0;
643 void *zConverted;
644 SXUNUSED(pVfs);
645
646 zConverted = convertUtf8Filename(zFilename);
647 if( zConverted==0 ){
648 return UNQLITE_NOMEM;
649 }
650 SyZero(&sAttrData,sizeof(sAttrData));
651 if( GetFileAttributesExW((WCHAR*)zConverted,
652 GetFileExInfoStandard,
653 &sAttrData) ){
654 /* For an UNQLITE_ACCESS_EXISTS query, treat a zero-length file
655 ** as if it does not exist.
656 */
657 if( flags==UNQLITE_ACCESS_EXISTS
658 && sAttrData.nFileSizeHigh==0
659 && sAttrData.nFileSizeLow==0 ){
660 attr = INVALID_FILE_ATTRIBUTES;
661 }else{
662 attr = sAttrData.dwFileAttributes;
663 }
664 }else{
665 if( GetLastError()!=ERROR_FILE_NOT_FOUND ){
666 HeapFree(GetProcessHeap(),0,zConverted);
667 return UNQLITE_IOERR;
668 }else{
669 attr = INVALID_FILE_ATTRIBUTES;
670 }
671 }
672 HeapFree(GetProcessHeap(),0,zConverted);
673 switch( flags ){
674 case UNQLITE_ACCESS_READWRITE:
675 rc = (attr & FILE_ATTRIBUTE_READONLY)==0;
676 break;
677 case UNQLITE_ACCESS_READ:
678 case UNQLITE_ACCESS_EXISTS:
679 default:
680 rc = attr!=INVALID_FILE_ATTRIBUTES;
681 break;
682 }
683 *pResOut = rc;
684 return UNQLITE_OK;
685}
686/*
687** Turn a relative pathname into a full pathname. Write the full
688** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname
689** bytes in size.
690*/
691static int winFullPathname(
692 unqlite_vfs *pVfs, /* Pointer to vfs object */
693 const char *zRelative, /* Possibly relative input path */
694 int nFull, /* Size of output buffer in bytes */
695 char *zFull /* Output buffer */
696){
697 int nByte;
698 void *zConverted;
699 WCHAR *zTemp;
700 char *zOut;
701 SXUNUSED(nFull);
702 zConverted = convertUtf8Filename(zRelative);
703 if( zConverted == 0 ){
704 return UNQLITE_NOMEM;
705 }
706 nByte = GetFullPathNameW((WCHAR*)zConverted, 0, 0, 0) + 3;
707 zTemp = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zTemp[0]) );
708 if( zTemp==0 ){
709 HeapFree(GetProcessHeap(),0,zConverted);
710 return UNQLITE_NOMEM;
711 }
712 GetFullPathNameW((WCHAR*)zConverted, nByte, zTemp, 0);
713 HeapFree(GetProcessHeap(),0,zConverted);
714 zOut = unicodeToUtf8(zTemp);
715 HeapFree(GetProcessHeap(),0,zTemp);
716 if( zOut == 0 ){
717 return UNQLITE_NOMEM;
718 }
719 Systrcpy(zFull,(sxu32)pVfs->mxPathname,zOut,0);
720 HeapFree(GetProcessHeap(),0,zOut);
721 return UNQLITE_OK;
722}
723/*
724** Get the sector size of the device used to store
725** file.
726*/
727static int getSectorSize(
728 unqlite_vfs *pVfs,
729 const char *zRelative /* UTF-8 file name */
730){
731 DWORD bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE;
732 char zFullpath[MAX_PATH+1];
733 int rc;
734 DWORD dwRet = 0;
735 DWORD dwDummy;
736 /*
737 ** We need to get the full path name of the file
738 ** to get the drive letter to look up the sector
739 ** size.
740 */
741 rc = winFullPathname(pVfs, zRelative, MAX_PATH, zFullpath);
742 if( rc == UNQLITE_OK )
743 {
744 void *zConverted = convertUtf8Filename(zFullpath);
745 if( zConverted ){
746 /* trim path to just drive reference */
747 WCHAR *p = (WCHAR *)zConverted;
748 for(;*p;p++){
749 if( *p == '\\' ){
750 *p = '\0';
751 break;
752 }
753 }
754 dwRet = GetDiskFreeSpaceW((WCHAR*)zConverted,
755 &dwDummy,
756 &bytesPerSector,
757 &dwDummy,
758 &dwDummy);
759 HeapFree(GetProcessHeap(),0,zConverted);
760 }
761 if( !dwRet ){
762 bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE;
763 }
764 }
765 return (int) bytesPerSector;
766}
767/*
768** Sleep for a little while. Return the amount of time slept.
769*/
770static int winSleep(unqlite_vfs *pVfs, int microsec){
771 Sleep((microsec+999)/1000);
772 SXUNUSED(pVfs);
773 return ((microsec+999)/1000)*1000;
774}
775/*
776 * Export the current system time.
777 */
778static int winCurrentTime(unqlite_vfs *pVfs,Sytm *pOut)
779{
780 SYSTEMTIME sSys;
781 SXUNUSED(pVfs);
782 GetSystemTime(&sSys);
783 SYSTEMTIME_TO_SYTM(&sSys,pOut);
784 return UNQLITE_OK;
785}
786/*
787** The idea is that this function works like a combination of
788** GetLastError() and FormatMessage() on windows (or errno and
789** strerror_r() on unix). After an error is returned by an OS
790** function, UnQLite calls this function with zBuf pointing to
791** a buffer of nBuf bytes. The OS layer should populate the
792** buffer with a nul-terminated UTF-8 encoded error message
793** describing the last IO error to have occurred within the calling
794** thread.
795**
796** If the error message is too large for the supplied buffer,
797** it should be truncated. The return value of xGetLastError
798** is zero if the error message fits in the buffer, or non-zero
799** otherwise (if the message was truncated). If non-zero is returned,
800** then it is not necessary to include the nul-terminator character
801** in the output buffer.
802*/
803static int winGetLastError(unqlite_vfs *pVfs, int nBuf, char *zBuf)
804{
805 /* FormatMessage returns 0 on failure. Otherwise it
806 ** returns the number of TCHARs written to the output
807 ** buffer, excluding the terminating null char.
808 */
809 DWORD error = GetLastError();
810 WCHAR *zTempWide = 0;
811 DWORD dwLen;
812 char *zOut = 0;
813
814 SXUNUSED(pVfs);
815 dwLen = FormatMessageW(
816 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
817 0,
818 error,
819 0,
820 (LPWSTR) &zTempWide,
821 0,
822 0
823 );
824 if( dwLen > 0 ){
825 /* allocate a buffer and convert to UTF8 */
826 zOut = unicodeToUtf8(zTempWide);
827 /* free the system buffer allocated by FormatMessage */
828 LocalFree(zTempWide);
829 }
830 if( 0 == dwLen ){
831 Systrcpy(zBuf,(sxu32)nBuf,"OS Error",sizeof("OS Error")-1);
832 }else{
833 /* copy a maximum of nBuf chars to output buffer */
834 Systrcpy(zBuf,(sxu32)nBuf,zOut,0 /* Compute input length automatically */);
835 /* free the UTF8 buffer */
836 HeapFree(GetProcessHeap(),0,zOut);
837 }
838 return 0;
839}
840/*
841** Open a file.
842*/
843static int winOpen(
844 unqlite_vfs *pVfs, /* Not used */
845 const char *zName, /* Name of the file (UTF-8) */
846 unqlite_file *id, /* Write the UnQLite file handle here */
847 unsigned int flags /* Open mode flags */
848){
849 HANDLE h;
850 DWORD dwDesiredAccess;
851 DWORD dwShareMode;
852 DWORD dwCreationDisposition;
853 DWORD dwFlagsAndAttributes = 0;
854 winFile *pFile = (winFile*)id;
855 void *zConverted; /* Filename in OS encoding */
856 const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */
857 int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE);
858 int isDelete = (flags & UNQLITE_OPEN_TEMP_DB);
859 int isCreate = (flags & UNQLITE_OPEN_CREATE);
860 int isReadWrite = (flags & UNQLITE_OPEN_READWRITE);
861
862 pFile->h = INVALID_HANDLE_VALUE;
863 /* Convert the filename to the system encoding. */
864 zConverted = convertUtf8Filename(zUtf8Name);
865 if( zConverted==0 ){
866 return UNQLITE_NOMEM;
867 }
868 if( isReadWrite ){
869 dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
870 }else{
871 dwDesiredAccess = GENERIC_READ;
872 }
873 /* UNQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is
874 ** created.
875 */
876 if( isExclusive ){
877 /* Creates a new file, only if it does not already exist. */
878 /* If the file exists, it fails. */
879 dwCreationDisposition = CREATE_NEW;
880 }else if( isCreate ){
881 /* Open existing file, or create if it doesn't exist */
882 dwCreationDisposition = OPEN_ALWAYS;
883 }else{
884 /* Opens a file, only if it exists. */
885 dwCreationDisposition = OPEN_EXISTING;
886 }
887
888 dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
889
890 if( isDelete ){
891 dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY
892 | FILE_ATTRIBUTE_HIDDEN
893 | FILE_FLAG_DELETE_ON_CLOSE;
894 }else{
895 dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
896 }
897 h = CreateFileW((WCHAR*)zConverted,
898 dwDesiredAccess,
899 dwShareMode,
900 NULL,
901 dwCreationDisposition,
902 dwFlagsAndAttributes,
903 NULL
904 );
905 if( h==INVALID_HANDLE_VALUE ){
906 pFile->lastErrno = GetLastError();
907 HeapFree(GetProcessHeap(),0,zConverted);
908 return UNQLITE_IOERR;
909 }
910 SyZero(pFile,sizeof(*pFile));
911 pFile->pMethod = &winIoMethod;
912 pFile->h = h;
913 pFile->lastErrno = NO_ERROR;
914 pFile->pVfs = pVfs;
915 pFile->sectorSize = getSectorSize(pVfs, zUtf8Name);
916 HeapFree(GetProcessHeap(),0,zConverted);
917 return UNQLITE_OK;
918}
919/*
920 * Export the Windows Vfs.
921 */
922UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void)
923{
924 static const unqlite_vfs sWinvfs = {
925 "Windows", /* Vfs name */
926 1, /* Vfs structure version */
927 sizeof(winFile), /* szOsFile */
928 MAX_PATH, /* mxPathName */
929 winOpen, /* xOpen */
930 winDelete, /* xDelete */
931 winAccess, /* xAccess */
932 winFullPathname, /* xFullPathname */
933 0, /* xTmp */
934 winSleep, /* xSleep */
935 winCurrentTime, /* xCurrentTime */
936 winGetLastError, /* xGetLastError */
937 };
938 return &sWinvfs;
939}
940#endif /* __WINNT__ */
diff --git a/common/unqlite/pager.c b/common/unqlite/pager.c
new file mode 100644
index 0000000..474a1ca
--- /dev/null
+++ b/common/unqlite/pager.c
@@ -0,0 +1,2808 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Copyright (C) 2014, Yuras Shumovich <shumovichy@gmail.com>
5 * Version 1.1.6
6 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
7 * please contact Symisc Systems via:
8 * legal@symisc.net
9 * licensing@symisc.net
10 * contact@symisc.net
11 * or visit:
12 * http://unqlite.org/licensing.html
13 */
14 /* $SymiscID: pager.c v1.1 Win7 2012-11-29 03:46 stable <chm@symisc.net> $ */
15#ifndef UNQLITE_AMALGAMATION
16#include "unqliteInt.h"
17#endif
18/*
19** This file implements the pager and the transaction manager for UnQLite (Mostly inspired from the SQLite3 Source tree).
20**
21** The Pager.eState variable stores the current 'state' of a pager. A
22** pager may be in any one of the seven states shown in the following
23** state diagram.
24**
25** OPEN <------+------+
26** | | |
27** V | |
28** +---------> READER-------+ |
29** | | |
30** | V |
31** |<-------WRITER_LOCKED--------->|
32** | | |
33** | V |
34** |<------WRITER_CACHEMOD-------->|
35** | | |
36** | V |
37** |<-------WRITER_DBMOD---------->|
38** | | |
39** | V |
40** +<------WRITER_FINISHED-------->+
41**
42** OPEN:
43**
44** The pager starts up in this state. Nothing is guaranteed in this
45** state - the file may or may not be locked and the database size is
46** unknown. The database may not be read or written.
47**
48** * No read or write transaction is active.
49** * Any lock, or no lock at all, may be held on the database file.
50** * The dbSize and dbOrigSize variables may not be trusted.
51**
52** READER:
53**
54** In this state all the requirements for reading the database in
55** rollback mode are met. Unless the pager is (or recently
56** was) in exclusive-locking mode, a user-level read transaction is
57** open. The database size is known in this state.
58**
59** * A read transaction may be active (but a write-transaction cannot).
60** * A SHARED or greater lock is held on the database file.
61** * The dbSize variable may be trusted (even if a user-level read
62** transaction is not active). The dbOrigSize variables
63** may not be trusted at this point.
64** * Even if a read-transaction is not open, it is guaranteed that
65** there is no hot-journal in the file-system.
66**
67** WRITER_LOCKED:
68**
69** The pager moves to this state from READER when a write-transaction
70** is first opened on the database. In WRITER_LOCKED state, all locks
71** required to start a write-transaction are held, but no actual
72** modifications to the cache or database have taken place.
73**
74** In rollback mode, a RESERVED or (if the transaction was opened with
75** EXCLUSIVE flag) EXCLUSIVE lock is obtained on the database file when
76** moving to this state, but the journal file is not written to or opened
77** to in this state. If the transaction is committed or rolled back while
78** in WRITER_LOCKED state, all that is required is to unlock the database
79** file.
80**
81** * A write transaction is active.
82** * If the connection is open in rollback-mode, a RESERVED or greater
83** lock is held on the database file.
84** * The dbSize and dbOrigSize variables are all valid.
85** * The contents of the pager cache have not been modified.
86** * The journal file may or may not be open.
87** * Nothing (not even the first header) has been written to the journal.
88**
89** WRITER_CACHEMOD:
90**
91** A pager moves from WRITER_LOCKED state to this state when a page is
92** first modified by the upper layer. In rollback mode the journal file
93** is opened (if it is not already open) and a header written to the
94** start of it. The database file on disk has not been modified.
95**
96** * A write transaction is active.
97** * A RESERVED or greater lock is held on the database file.
98** * The journal file is open and the first header has been written
99** to it, but the header has not been synced to disk.
100** * The contents of the page cache have been modified.
101**
102** WRITER_DBMOD:
103**
104** The pager transitions from WRITER_CACHEMOD into WRITER_DBMOD state
105** when it modifies the contents of the database file.
106**
107** * A write transaction is active.
108** * An EXCLUSIVE or greater lock is held on the database file.
109** * The journal file is open and the first header has been written
110** and synced to disk.
111** * The contents of the page cache have been modified (and possibly
112** written to disk).
113**
114** WRITER_FINISHED:
115**
116** A rollback-mode pager changes to WRITER_FINISHED state from WRITER_DBMOD
117** state after the entire transaction has been successfully written into the
118** database file. In this state the transaction may be committed simply
119** by finalizing the journal file. Once in WRITER_FINISHED state, it is
120** not possible to modify the database further. At this point, the upper
121** layer must either commit or rollback the transaction.
122**
123** * A write transaction is active.
124** * An EXCLUSIVE or greater lock is held on the database file.
125** * All writing and syncing of journal and database data has finished.
126** If no error occured, all that remains is to finalize the journal to
127** commit the transaction. If an error did occur, the caller will need
128** to rollback the transaction.
129**
130**
131*/
132#define PAGER_OPEN 0
133#define PAGER_READER 1
134#define PAGER_WRITER_LOCKED 2
135#define PAGER_WRITER_CACHEMOD 3
136#define PAGER_WRITER_DBMOD 4
137#define PAGER_WRITER_FINISHED 5
138/*
139** Journal files begin with the following magic string. The data
140** was obtained from /dev/random. It is used only as a sanity check.
141**
142** NOTE: These values must be different from the one used by SQLite3
143** to avoid journal file collision.
144**
145*/
146static const unsigned char aJournalMagic[] = {
147 0xa6, 0xe8, 0xcd, 0x2b, 0x1c, 0x92, 0xdb, 0x9f,
148};
149/*
150** The journal header size for this pager. This is usually the same
151** size as a single disk sector. See also setSectorSize().
152*/
153#define JOURNAL_HDR_SZ(pPager) (pPager->iSectorSize)
154/*
155 * Database page handle.
156 * Each raw disk page is represented in memory by an instance
157 * of the following structure.
158 */
159typedef struct Page Page;
160struct Page {
161 /* Must correspond to unqlite_page */
162 unsigned char *zData; /* Content of this page */
163 void *pUserData; /* Extra content */
164 pgno pgno; /* Page number for this page */
165 /**********************************************************************
166 ** Elements above are public. All that follows is private to pcache.c
167 ** and should not be accessed by other modules.
168 */
169 Pager *pPager; /* The pager this page is part of */
170 int flags; /* Page flags defined below */
171 int nRef; /* Number of users of this page */
172 Page *pNext, *pPrev; /* A list of all pages */
173 Page *pDirtyNext; /* Next element in list of dirty pages */
174 Page *pDirtyPrev; /* Previous element in list of dirty pages */
175 Page *pNextCollide,*pPrevCollide; /* Collission chain */
176 Page *pNextHot,*pPrevHot; /* Hot dirty pages chain */
177};
178/* Bit values for Page.flags */
179#define PAGE_DIRTY 0x002 /* Page has changed */
180#define PAGE_NEED_SYNC 0x004 /* fsync the rollback journal before
181 ** writing this page to the database */
182#define PAGE_DONT_WRITE 0x008 /* Dont write page content to disk */
183#define PAGE_NEED_READ 0x010 /* Content is unread */
184#define PAGE_IN_JOURNAL 0x020 /* Page written to the journal */
185#define PAGE_HOT_DIRTY 0x040 /* Hot dirty page */
186#define PAGE_DONT_MAKE_HOT 0x080 /* Dont make this page Hot. In other words,
187 * do not link it to the hot dirty list.
188 */
189/*
190 * Each active database pager is represented by an instance of
191 * the following structure.
192 */
193struct Pager
194{
195 SyMemBackend *pAllocator; /* Memory backend */
196 unqlite *pDb; /* DB handle that own this instance */
197 unqlite_kv_engine *pEngine; /* Underlying KV storage engine */
198 char *zFilename; /* Name of the database file */
199 char *zJournal; /* Name of the journal file */
200 unqlite_vfs *pVfs; /* Underlying virtual file system */
201 unqlite_file *pfd,*pjfd; /* File descriptors for database and journal */
202 pgno dbSize; /* Number of pages in the file */
203 pgno dbOrigSize; /* dbSize before the current change */
204 sxi64 dbByteSize; /* Database size in bytes */
205 void *pMmap; /* Read-only Memory view (mmap) of the whole file if requested (UNQLITE_OPEN_MMAP). */
206 sxu32 nRec; /* Number of pages written to the journal */
207 SyPRNGCtx sPrng; /* PRNG Context */
208 sxu32 cksumInit; /* Quasi-random value added to every checksum */
209 sxu32 iOpenFlags; /* Flag passed to unqlite_open() after processing */
210 sxi64 iJournalOfft; /* Journal offset we are reading from */
211 int (*xBusyHandler)(void *); /* Busy handler */
212 void *pBusyHandlerArg; /* First arg to xBusyHandler() */
213 void (*xPageUnpin)(void *); /* Page Unpin callback */
214 void (*xPageReload)(void *); /* Page Reload callback */
215 Bitvec *pVec; /* Bitmap */
216 Page *pHeader; /* Page one of the database (Unqlite header) */
217 Sytm tmCreate; /* Database creation time */
218 SyString sKv; /* Underlying Key/Value storage engine name */
219 int iState; /* Pager state */
220 int iLock; /* Lock state */
221 sxi32 iFlags; /* Control flags (see below) */
222 int is_mem; /* True for an in-memory database */
223 int is_rdonly; /* True for a read-only database */
224 int no_jrnl; /* TRUE to omit journaling */
225 int iPageSize; /* Page size in bytes (default 4K) */
226 int iSectorSize; /* Size of a single sector on disk */
227 unsigned char *zTmpPage; /* Temporary page */
228 Page *pFirstDirty; /* First dirty pages */
229 Page *pDirty; /* Transient list of dirty pages */
230 Page *pAll; /* List of all pages */
231 Page *pHotDirty; /* List of hot dirty pages */
232 Page *pFirstHot; /* First hot dirty page */
233 sxu32 nHot; /* Total number of hot dirty pages */
234 Page **apHash; /* Page table */
235 sxu32 nSize; /* apHash[] size: Must be a power of two */
236 sxu32 nPage; /* Total number of page loaded in memory */
237 sxu32 nCacheMax; /* Maximum page to cache*/
238};
239/* Control flags */
240#define PAGER_CTRL_COMMIT_ERR 0x001 /* Commit error */
241#define PAGER_CTRL_DIRTY_COMMIT 0x002 /* Dirty commit has been applied */
242/*
243** Read a 32-bit integer from the given file descriptor.
244** All values are stored on disk as big-endian.
245*/
246static int ReadInt32(unqlite_file *pFd,sxu32 *pOut,sxi64 iOfft)
247{
248 unsigned char zBuf[4];
249 int rc;
250 rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft);
251 if( rc != UNQLITE_OK ){
252 return rc;
253 }
254 SyBigEndianUnpack32(zBuf,pOut);
255 return UNQLITE_OK;
256}
257/*
258** Read a 64-bit integer from the given file descriptor.
259** All values are stored on disk as big-endian.
260*/
261static int ReadInt64(unqlite_file *pFd,sxu64 *pOut,sxi64 iOfft)
262{
263 unsigned char zBuf[8];
264 int rc;
265 rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft);
266 if( rc != UNQLITE_OK ){
267 return rc;
268 }
269 SyBigEndianUnpack64(zBuf,pOut);
270 return UNQLITE_OK;
271}
272/*
273** Write a 32-bit integer into the given file descriptor.
274*/
275static int WriteInt32(unqlite_file *pFd,sxu32 iNum,sxi64 iOfft)
276{
277 unsigned char zBuf[4];
278 int rc;
279 SyBigEndianPack32(zBuf,iNum);
280 rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft);
281 return rc;
282}
283/*
284** Write a 64-bit integer into the given file descriptor.
285*/
286static int WriteInt64(unqlite_file *pFd,sxu64 iNum,sxi64 iOfft)
287{
288 unsigned char zBuf[8];
289 int rc;
290 SyBigEndianPack64(zBuf,iNum);
291 rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft);
292 return rc;
293}
294/*
295** The maximum allowed sector size. 64KiB. If the xSectorsize() method
296** returns a value larger than this, then MAX_SECTOR_SIZE is used instead.
297** This could conceivably cause corruption following a power failure on
298** such a system. This is currently an undocumented limit.
299*/
300#define MAX_SECTOR_SIZE 0x10000
301/*
302** Get the size of a single sector on disk.
303** The sector size will be used used to determine the size
304** and alignment of journal header and within created journal files.
305**
306** The default sector size is set to 512.
307*/
308static int GetSectorSize(unqlite_file *pFd)
309{
310 int iSectorSize = UNQLITE_DEFAULT_SECTOR_SIZE;
311 if( pFd ){
312 iSectorSize = unqliteOsSectorSize(pFd);
313 if( iSectorSize < 32 ){
314 iSectorSize = 512;
315 }
316 if( iSectorSize > MAX_SECTOR_SIZE ){
317 iSectorSize = MAX_SECTOR_SIZE;
318 }
319 }
320 return iSectorSize;
321}
322/* Hash function for page number */
323#define PAGE_HASH(PNUM) (PNUM)
324/*
325 * Fetch a page from the cache.
326 */
327static Page * pager_fetch_page(Pager *pPager,pgno page_num)
328{
329 Page *pEntry;
330 if( pPager->nPage < 1 ){
331 /* Don't bother hashing */
332 return 0;
333 }
334 /* Perform the lookup */
335 pEntry = pPager->apHash[PAGE_HASH(page_num) & (pPager->nSize - 1)];
336 for(;;){
337 if( pEntry == 0 ){
338 break;
339 }
340 if( pEntry->pgno == page_num ){
341 return pEntry;
342 }
343 /* Point to the next entry in the colission chain */
344 pEntry = pEntry->pNextCollide;
345 }
346 /* No such page */
347 return 0;
348}
349/*
350 * Allocate and initialize a new page.
351 */
352static Page * pager_alloc_page(Pager *pPager,pgno num_page)
353{
354 Page *pNew;
355
356 pNew = (Page *)SyMemBackendPoolAlloc(pPager->pAllocator,sizeof(Page)+pPager->iPageSize);
357 if( pNew == 0 ){
358 return 0;
359 }
360 /* Zero the structure */
361 SyZero(pNew,sizeof(Page)+pPager->iPageSize);
362 /* Page data */
363 pNew->zData = (unsigned char *)&pNew[1];
364 /* Fill in the structure */
365 pNew->pPager = pPager;
366 pNew->nRef = 1;
367 pNew->pgno = num_page;
368 return pNew;
369}
370/*
371 * Increment the reference count of a given page.
372 */
373static void page_ref(Page *pPage)
374{
375 pPage->nRef++;
376}
377/*
378 * Release an in-memory page after its reference count reach zero.
379 */
380static int pager_release_page(Pager *pPager,Page *pPage)
381{
382 int rc = UNQLITE_OK;
383 if( !(pPage->flags & PAGE_DIRTY)){
384 /* Invoke the unpin callback if available */
385 if( pPager->xPageUnpin && pPage->pUserData ){
386 pPager->xPageUnpin(pPage->pUserData);
387 }
388 pPage->pUserData = 0;
389 SyMemBackendPoolFree(pPager->pAllocator,pPage);
390 }else{
391 /* Dirty page, it will be released later when a dirty commit
392 * or the final commit have been applied.
393 */
394 rc = UNQLITE_LOCKED;
395 }
396 return rc;
397}
398/* Forward declaration */
399static int pager_unlink_page(Pager *pPager,Page *pPage);
400/*
401 * Decrement the reference count of a given page.
402 */
403static void page_unref(Page *pPage)
404{
405 pPage->nRef--;
406 if( pPage->nRef < 1 ){
407 Pager *pPager = pPage->pPager;
408 if( !(pPage->flags & PAGE_DIRTY) ){
409 pager_unlink_page(pPager,pPage);
410 /* Release the page */
411 pager_release_page(pPager,pPage);
412 }else{
413 if( pPage->flags & PAGE_DONT_MAKE_HOT ){
414 /* Do not add this page to the hot dirty list */
415 return;
416 }
417 if( !(pPage->flags & PAGE_HOT_DIRTY) ){
418 /* Add to the hot dirty list */
419 pPage->pPrevHot = 0;
420 if( pPager->pFirstHot == 0 ){
421 pPager->pFirstHot = pPager->pHotDirty = pPage;
422 }else{
423 pPage->pNextHot = pPager->pHotDirty;
424 if( pPager->pHotDirty ){
425 pPager->pHotDirty->pPrevHot = pPage;
426 }
427 pPager->pHotDirty = pPage;
428 }
429 pPager->nHot++;
430 pPage->flags |= PAGE_HOT_DIRTY;
431 }
432 }
433 }
434}
435/*
436 * Link a freshly created page to the list of active page.
437 */
438static int pager_link_page(Pager *pPager,Page *pPage)
439{
440 sxu32 nBucket;
441 /* Install in the corresponding bucket */
442 nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1);
443 pPage->pNextCollide = pPager->apHash[nBucket];
444 if( pPager->apHash[nBucket] ){
445 pPager->apHash[nBucket]->pPrevCollide = pPage;
446 }
447 pPager->apHash[nBucket] = pPage;
448 /* Link to the list of active pages */
449 MACRO_LD_PUSH(pPager->pAll,pPage);
450 pPager->nPage++;
451 if( (pPager->nPage >= pPager->nSize * 4) && pPager->nPage < 100000 ){
452 /* Grow the hashtable */
453 sxu32 nNewSize = pPager->nSize << 1;
454 Page *pEntry,**apNew;
455 sxu32 n;
456 apNew = (Page **)SyMemBackendAlloc(pPager->pAllocator, nNewSize * sizeof(Page *));
457 if( apNew ){
458 sxu32 iBucket;
459 /* Zero the new table */
460 SyZero((void *)apNew, nNewSize * sizeof(Page *));
461 /* Rehash all entries */
462 n = 0;
463 pEntry = pPager->pAll;
464 for(;;){
465 /* Loop one */
466 if( n >= pPager->nPage ){
467 break;
468 }
469 pEntry->pNextCollide = pEntry->pPrevCollide = 0;
470 /* Install in the new bucket */
471 iBucket = PAGE_HASH(pEntry->pgno) & (nNewSize - 1);
472 pEntry->pNextCollide = apNew[iBucket];
473 if( apNew[iBucket] ){
474 apNew[iBucket]->pPrevCollide = pEntry;
475 }
476 apNew[iBucket] = pEntry;
477 /* Point to the next entry */
478 pEntry = pEntry->pNext;
479 n++;
480 }
481 /* Release the old table and reflect the change */
482 SyMemBackendFree(pPager->pAllocator,(void *)pPager->apHash);
483 pPager->apHash = apNew;
484 pPager->nSize = nNewSize;
485 }
486 }
487 return UNQLITE_OK;
488}
489/*
490 * Unlink a page from the list of active pages.
491 */
492static int pager_unlink_page(Pager *pPager,Page *pPage)
493{
494 if( pPage->pNextCollide ){
495 pPage->pNextCollide->pPrevCollide = pPage->pPrevCollide;
496 }
497 if( pPage->pPrevCollide ){
498 pPage->pPrevCollide->pNextCollide = pPage->pNextCollide;
499 }else{
500 sxu32 nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1);
501 pPager->apHash[nBucket] = pPage->pNextCollide;
502 }
503 MACRO_LD_REMOVE(pPager->pAll,pPage);
504 pPager->nPage--;
505 return UNQLITE_OK;
506}
507/*
508 * Update the content of a cached page.
509 */
510static int pager_fill_page(Pager *pPager,pgno iNum,void *pContents)
511{
512 Page *pPage;
513 /* Fetch the page from the catch */
514 pPage = pager_fetch_page(pPager,iNum);
515 if( pPage == 0 ){
516 return SXERR_NOTFOUND;
517 }
518 /* Reflect the change */
519 SyMemcpy(pContents,pPage->zData,pPager->iPageSize);
520
521 return UNQLITE_OK;
522}
523/*
524 * Read the content of a page from disk.
525 */
526static int pager_get_page_contents(Pager *pPager,Page *pPage,int noContent)
527{
528 int rc = UNQLITE_OK;
529 if( pPager->is_mem || noContent || pPage->pgno >= pPager->dbSize ){
530 /* Do not bother reading, zero the page contents only */
531 SyZero(pPage->zData,pPager->iPageSize);
532 return UNQLITE_OK;
533 }
534 if( (pPager->iOpenFlags & UNQLITE_OPEN_MMAP) && (pPager->pMmap /* Paranoid edition */) ){
535 unsigned char *zMap = (unsigned char *)pPager->pMmap;
536 pPage->zData = &zMap[pPage->pgno * pPager->iPageSize];
537 }else{
538 /* Read content */
539 rc = unqliteOsRead(pPager->pfd,pPage->zData,pPager->iPageSize,pPage->pgno * pPager->iPageSize);
540 }
541 return rc;
542}
543/*
544 * Add a page to the dirty list.
545 */
546static void pager_page_to_dirty_list(Pager *pPager,Page *pPage)
547{
548 if( pPage->flags & PAGE_DIRTY ){
549 /* Already set */
550 return;
551 }
552 /* Mark the page as dirty */
553 pPage->flags |= PAGE_DIRTY|PAGE_NEED_SYNC|PAGE_IN_JOURNAL;
554 /* Link to the list */
555 pPage->pDirtyPrev = 0;
556 pPage->pDirtyNext = pPager->pDirty;
557 if( pPager->pDirty ){
558 pPager->pDirty->pDirtyPrev = pPage;
559 }
560 pPager->pDirty = pPage;
561 if( pPager->pFirstDirty == 0 ){
562 pPager->pFirstDirty = pPage;
563 }
564}
565/*
566 * Merge sort.
567 * The merge sort implementation is based on the one used by
568 * the PH7 Embeddable PHP Engine (http://ph7.symisc.net/).
569 */
570/*
571** Inputs:
572** a: A sorted, null-terminated linked list. (May be null).
573** b: A sorted, null-terminated linked list. (May be null).
574** cmp: A pointer to the comparison function.
575**
576** Return Value:
577** A pointer to the head of a sorted list containing the elements
578** of both a and b.
579**
580** Side effects:
581** The "next", "prev" pointers for elements in the lists a and b are
582** changed.
583*/
584static Page * page_merge_dirty(Page *pA, Page *pB)
585{
586 Page result, *pTail;
587 /* Prevent compiler warning */
588 result.pDirtyNext = result.pDirtyPrev = 0;
589 pTail = &result;
590 while( pA && pB ){
591 if( pA->pgno < pB->pgno ){
592 pTail->pDirtyPrev = pA;
593 pA->pDirtyNext = pTail;
594 pTail = pA;
595 pA = pA->pDirtyPrev;
596 }else{
597 pTail->pDirtyPrev = pB;
598 pB->pDirtyNext = pTail;
599 pTail = pB;
600 pB = pB->pDirtyPrev;
601 }
602 }
603 if( pA ){
604 pTail->pDirtyPrev = pA;
605 pA->pDirtyNext = pTail;
606 }else if( pB ){
607 pTail->pDirtyPrev = pB;
608 pB->pDirtyNext = pTail;
609 }else{
610 pTail->pDirtyPrev = pTail->pDirtyNext = 0;
611 }
612 return result.pDirtyPrev;
613}
614/*
615** Inputs:
616** Map: Input hashmap
617** cmp: A comparison function.
618**
619** Return Value:
620** Sorted hashmap.
621**
622** Side effects:
623** The "next" pointers for elements in list are changed.
624*/
625#define N_SORT_BUCKET 32
626static Page * pager_get_dirty_pages(Pager *pPager)
627{
628 Page *a[N_SORT_BUCKET], *p, *pIn;
629 sxu32 i;
630 if( pPager->pFirstDirty == 0 ){
631 /* Don't bother sorting, the list is already empty */
632 return 0;
633 }
634 SyZero(a, sizeof(a));
635 /* Point to the first inserted entry */
636 pIn = pPager->pFirstDirty;
637 while( pIn ){
638 p = pIn;
639 pIn = p->pDirtyPrev;
640 p->pDirtyPrev = 0;
641 for(i=0; i<N_SORT_BUCKET-1; i++){
642 if( a[i]==0 ){
643 a[i] = p;
644 break;
645 }else{
646 p = page_merge_dirty(a[i], p);
647 a[i] = 0;
648 }
649 }
650 if( i==N_SORT_BUCKET-1 ){
651 /* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list.
652 * But that is impossible.
653 */
654 a[i] = page_merge_dirty(a[i], p);
655 }
656 }
657 p = a[0];
658 for(i=1; i<N_SORT_BUCKET; i++){
659 p = page_merge_dirty(p,a[i]);
660 }
661 p->pDirtyNext = 0;
662 return p;
663}
664/*
665 * See block comment above.
666 */
667static Page * page_merge_hot(Page *pA, Page *pB)
668{
669 Page result, *pTail;
670 /* Prevent compiler warning */
671 result.pNextHot = result.pPrevHot = 0;
672 pTail = &result;
673 while( pA && pB ){
674 if( pA->pgno < pB->pgno ){
675 pTail->pPrevHot = pA;
676 pA->pNextHot = pTail;
677 pTail = pA;
678 pA = pA->pPrevHot;
679 }else{
680 pTail->pPrevHot = pB;
681 pB->pNextHot = pTail;
682 pTail = pB;
683 pB = pB->pPrevHot;
684 }
685 }
686 if( pA ){
687 pTail->pPrevHot = pA;
688 pA->pNextHot = pTail;
689 }else if( pB ){
690 pTail->pPrevHot = pB;
691 pB->pNextHot = pTail;
692 }else{
693 pTail->pPrevHot = pTail->pNextHot = 0;
694 }
695 return result.pPrevHot;
696}
697/*
698** Inputs:
699** Map: Input hashmap
700** cmp: A comparison function.
701**
702** Return Value:
703** Sorted hashmap.
704**
705** Side effects:
706** The "next" pointers for elements in list are changed.
707*/
708#define N_SORT_BUCKET 32
709static Page * pager_get_hot_pages(Pager *pPager)
710{
711 Page *a[N_SORT_BUCKET], *p, *pIn;
712 sxu32 i;
713 if( pPager->pFirstHot == 0 ){
714 /* Don't bother sorting, the list is already empty */
715 return 0;
716 }
717 SyZero(a, sizeof(a));
718 /* Point to the first inserted entry */
719 pIn = pPager->pFirstHot;
720 while( pIn ){
721 p = pIn;
722 pIn = p->pPrevHot;
723 p->pPrevHot = 0;
724 for(i=0; i<N_SORT_BUCKET-1; i++){
725 if( a[i]==0 ){
726 a[i] = p;
727 break;
728 }else{
729 p = page_merge_hot(a[i], p);
730 a[i] = 0;
731 }
732 }
733 if( i==N_SORT_BUCKET-1 ){
734 /* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list.
735 * But that is impossible.
736 */
737 a[i] = page_merge_hot(a[i], p);
738 }
739 }
740 p = a[0];
741 for(i=1; i<N_SORT_BUCKET; i++){
742 p = page_merge_hot(p,a[i]);
743 }
744 p->pNextHot = 0;
745 return p;
746}
747/*
748** The format for the journal header is as follows:
749** - 8 bytes: Magic identifying journal format.
750** - 4 bytes: Number of records in journal.
751** - 4 bytes: Random number used for page hash.
752** - 8 bytes: Initial database page count.
753** - 4 bytes: Sector size used by the process that wrote this journal.
754** - 4 bytes: Database page size.
755**
756** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space.
757*/
758/*
759** Open the journal file and extract its header information.
760**
761** If the header is read successfully, *pNRec is set to the number of
762** page records following this header and *pDbSize is set to the size of the
763** database before the transaction began, in pages. Also, pPager->cksumInit
764** is set to the value read from the journal header. UNQLITE_OK is returned
765** in this case.
766**
767** If the journal header file appears to be corrupted, UNQLITE_DONE is
768** returned and *pNRec and *PDbSize are undefined. If JOURNAL_HDR_SZ bytes
769** cannot be read from the journal file an error code is returned.
770*/
771static int pager_read_journal_header(
772 Pager *pPager, /* Pager object */
773 sxu32 *pNRec, /* OUT: Value read from the nRec field */
774 pgno *pDbSize /* OUT: Value of original database size field */
775)
776{
777 sxu32 iPageSize,iSectorSize;
778 unsigned char zMagic[8];
779 sxi64 iHdrOfft;
780 sxi64 iSize;
781 int rc;
782 /* Offset to start reading from */
783 iHdrOfft = 0;
784 /* Get the size of the journal */
785 rc = unqliteOsFileSize(pPager->pjfd,&iSize);
786 if( rc != UNQLITE_OK ){
787 return UNQLITE_DONE;
788 }
789 /* If the journal file is too small, return UNQLITE_DONE. */
790 if( 32 /* Minimum sector size */> iSize ){
791 return UNQLITE_DONE;
792 }
793 /* Make sure we are dealing with a valid journal */
794 rc = unqliteOsRead(pPager->pjfd,zMagic,sizeof(zMagic),iHdrOfft);
795 if( rc != UNQLITE_OK ){
796 return rc;
797 }
798 if( SyMemcmp(zMagic,aJournalMagic,sizeof(zMagic)) != 0 ){
799 return UNQLITE_DONE;
800 }
801 iHdrOfft += sizeof(zMagic);
802 /* Read the first three 32-bit fields of the journal header: The nRec
803 ** field, the checksum-initializer and the database size at the start
804 ** of the transaction. Return an error code if anything goes wrong.
805 */
806 rc = ReadInt32(pPager->pjfd,pNRec,iHdrOfft);
807 if( rc != UNQLITE_OK ){
808 return rc;
809 }
810 iHdrOfft += 4;
811 rc = ReadInt32(pPager->pjfd,&pPager->cksumInit,iHdrOfft);
812 if( rc != UNQLITE_OK ){
813 return rc;
814 }
815 iHdrOfft += 4;
816 rc = ReadInt64(pPager->pjfd,pDbSize,iHdrOfft);
817 if( rc != UNQLITE_OK ){
818 return rc;
819 }
820 iHdrOfft += 8;
821 /* Read the page-size and sector-size journal header fields. */
822 rc = ReadInt32(pPager->pjfd,&iSectorSize,iHdrOfft);
823 if( rc != UNQLITE_OK ){
824 return rc;
825 }
826 iHdrOfft += 4;
827 rc = ReadInt32(pPager->pjfd,&iPageSize,iHdrOfft);
828 if( rc != UNQLITE_OK ){
829 return rc;
830 }
831 /* Check that the values read from the page-size and sector-size fields
832 ** are within range. To be 'in range', both values need to be a power
833 ** of two greater than or equal to 512 or 32, and not greater than their
834 ** respective compile time maximum limits.
835 */
836 if( iPageSize < UNQLITE_MIN_PAGE_SIZE || iSectorSize<32
837 || iPageSize > UNQLITE_MAX_PAGE_SIZE || iSectorSize>MAX_SECTOR_SIZE
838 || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0
839 ){
840 /* If the either the page-size or sector-size in the journal-header is
841 ** invalid, then the process that wrote the journal-header must have
842 ** crashed before the header was synced. In this case stop reading
843 ** the journal file here.
844 */
845 return UNQLITE_DONE;
846 }
847 /* Update the assumed sector-size to match the value used by
848 ** the process that created this journal. If this journal was
849 ** created by a process other than this one, then this routine
850 ** is being called from within pager_playback(). The local value
851 ** of Pager.sectorSize is restored at the end of that routine.
852 */
853 pPager->iSectorSize = iSectorSize;
854 pPager->iPageSize = iPageSize;
855 /* Ready to rollback */
856 pPager->iJournalOfft = JOURNAL_HDR_SZ(pPager);
857 /* All done */
858 return UNQLITE_OK;
859}
860/*
861 * Write the journal header in the given memory buffer.
862 * The given buffer is big enough to hold the whole header.
863 */
864static int pager_write_journal_header(Pager *pPager,unsigned char *zBuf)
865{
866 unsigned char *zPtr = zBuf;
867 /* 8 bytes magic number */
868 SyMemcpy(aJournalMagic,zPtr,sizeof(aJournalMagic));
869 zPtr += sizeof(aJournalMagic);
870 /* 4 bytes: Number of records in journal. */
871 SyBigEndianPack32(zPtr,0);
872 zPtr += 4;
873 /* 4 bytes: Random number used to compute page checksum. */
874 SyBigEndianPack32(zPtr,pPager->cksumInit);
875 zPtr += 4;
876 /* 8 bytes: Initial database page count. */
877 SyBigEndianPack64(zPtr,pPager->dbOrigSize);
878 zPtr += 8;
879 /* 4 bytes: Sector size used by the process that wrote this journal. */
880 SyBigEndianPack32(zPtr,(sxu32)pPager->iSectorSize);
881 zPtr += 4;
882 /* 4 bytes: Database page size. */
883 SyBigEndianPack32(zPtr,(sxu32)pPager->iPageSize);
884 return UNQLITE_OK;
885}
886/*
887** Parameter aData must point to a buffer of pPager->pageSize bytes
888** of data. Compute and return a checksum based ont the contents of the
889** page of data and the current value of pPager->cksumInit.
890**
891** This is not a real checksum. It is really just the sum of the
892** random initial value (pPager->cksumInit) and every 200th byte
893** of the page data, starting with byte offset (pPager->pageSize%200).
894** Each byte is interpreted as an 8-bit unsigned integer.
895**
896** Changing the formula used to compute this checksum results in an
897** incompatible journal file format.
898**
899** If journal corruption occurs due to a power failure, the most likely
900** scenario is that one end or the other of the record will be changed.
901** It is much less likely that the two ends of the journal record will be
902** correct and the middle be corrupt. Thus, this "checksum" scheme,
903** though fast and simple, catches the mostly likely kind of corruption.
904*/
905static sxu32 pager_cksum(Pager *pPager,const unsigned char *zData)
906{
907 sxu32 cksum = pPager->cksumInit; /* Checksum value to return */
908 int i = pPager->iPageSize-200; /* Loop counter */
909 while( i>0 ){
910 cksum += zData[i];
911 i -= 200;
912 }
913 return cksum;
914}
915/*
916** Read a single page from the journal file opened on file descriptor
917** jfd. Playback this one page. Update the offset to read from.
918*/
919static int pager_play_back_one_page(Pager *pPager,sxi64 *pOfft,unsigned char *zTmp)
920{
921 unsigned char *zData = zTmp;
922 sxi64 iOfft; /* Offset to read from */
923 pgno iNum; /* Pager number */
924 sxu32 ckSum; /* Sanity check */
925 int rc;
926 /* Offset to start reading from */
927 iOfft = *pOfft;
928 /* Database page number */
929 rc = ReadInt64(pPager->pjfd,&iNum,iOfft);
930 if( rc != UNQLITE_OK ){ return rc; }
931 iOfft += 8;
932 /* Page data */
933 rc = unqliteOsRead(pPager->pjfd,zData,pPager->iPageSize,iOfft);
934 if( rc != UNQLITE_OK ){ return rc; }
935 iOfft += pPager->iPageSize;
936 /* Page cksum */
937 rc = ReadInt32(pPager->pjfd,&ckSum,iOfft);
938 if( rc != UNQLITE_OK ){ return rc; }
939 iOfft += 4;
940 /* Synchronize pointers */
941 *pOfft = iOfft;
942 /* Make sure we are dealing with a valid page */
943 if( ckSum != pager_cksum(pPager,zData) ){
944 /* Ignore that page */
945 return SXERR_IGNORE;
946 }
947 if( iNum >= pPager->dbSize ){
948 /* Ignore that page */
949 return UNQLITE_OK;
950 }
951 /* playback */
952 rc = unqliteOsWrite(pPager->pfd,zData,pPager->iPageSize,iNum * pPager->iPageSize);
953 if( rc == UNQLITE_OK ){
954 /* Flush the cache */
955 pager_fill_page(pPager,iNum,zData);
956 }
957 return rc;
958}
959/*
960** Playback the journal and thus restore the database file to
961** the state it was in before we started making changes.
962**
963** The journal file format is as follows:
964**
965** (1) 8 byte prefix. A copy of aJournalMagic[].
966** (2) 4 byte big-endian integer which is the number of valid page records
967** in the journal.
968** (3) 4 byte big-endian integer which is the initial value for the
969** sanity checksum.
970** (4) 8 byte integer which is the number of pages to truncate the
971** database to during a rollback.
972** (5) 4 byte big-endian integer which is the sector size. The header
973** is this many bytes in size.
974** (6) 4 byte big-endian integer which is the page size.
975** (7) zero padding out to the next sector size.
976** (8) Zero or more pages instances, each as follows:
977** + 4 byte page number.
978** + pPager->pageSize bytes of data.
979** + 4 byte checksum
980**
981** When we speak of the journal header, we mean the first 7 items above.
982** Each entry in the journal is an instance of the 8th item.
983**
984** Call the value from the second bullet "nRec". nRec is the number of
985** valid page entries in the journal. In most cases, you can compute the
986** value of nRec from the size of the journal file. But if a power
987** failure occurred while the journal was being written, it could be the
988** case that the size of the journal file had already been increased but
989** the extra entries had not yet made it safely to disk. In such a case,
990** the value of nRec computed from the file size would be too large. For
991** that reason, we always use the nRec value in the header.
992**
993** If the file opened as the journal file is not a well-formed
994** journal file then all pages up to the first corrupted page are rolled
995** back (or no pages if the journal header is corrupted). The journal file
996** is then deleted and SQLITE_OK returned, just as if no corruption had
997** been encountered.
998**
999** If an I/O or malloc() error occurs, the journal-file is not deleted
1000** and an error code is returned.
1001**
1002*/
1003static int pager_playback(Pager *pPager)
1004{
1005 unsigned char *zTmp = 0; /* cc warning */
1006 sxu32 n,nRec;
1007 sxi64 iOfft;
1008 int rc;
1009 /* Read the journal header*/
1010 rc = pager_read_journal_header(pPager,&nRec,&pPager->dbSize);
1011 if( rc != UNQLITE_OK ){
1012 if( rc == UNQLITE_DONE ){
1013 goto end_playback;
1014 }
1015 unqliteGenErrorFormat(pPager->pDb,"IO error while reading journal file '%s' header",pPager->zJournal);
1016 return rc;
1017 }
1018 /* Truncate the database back to its original size */
1019 rc = unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize);
1020 if( rc != UNQLITE_OK ){
1021 unqliteGenError(pPager->pDb,"IO error while truncating database file");
1022 return rc;
1023 }
1024 /* Allocate a temporary page */
1025 zTmp = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize);
1026 if( zTmp == 0 ){
1027 unqliteGenOutofMem(pPager->pDb);
1028 return UNQLITE_NOMEM;
1029 }
1030 SyZero((void *)zTmp,(sxu32)pPager->iPageSize);
1031 /* Copy original pages out of the journal and back into the
1032 ** database file and/or page cache.
1033 */
1034 iOfft = pPager->iJournalOfft;
1035 for( n = 0 ; n < nRec ; ++n ){
1036 rc = pager_play_back_one_page(pPager,&iOfft,zTmp);
1037 if( rc != UNQLITE_OK ){
1038 if( rc != SXERR_IGNORE ){
1039 unqliteGenError(pPager->pDb,"Page playback error");
1040 goto end_playback;
1041 }
1042 }
1043 }
1044end_playback:
1045 /* Release the temp page */
1046 SyMemBackendFree(pPager->pAllocator,(void *)zTmp);
1047 if( rc == UNQLITE_OK ){
1048 /* Sync the database file */
1049 unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL);
1050 }
1051 if( rc == UNQLITE_DONE ){
1052 rc = UNQLITE_OK;
1053 }
1054 /* Return to the caller */
1055 return rc;
1056}
1057/*
1058** Unlock the database file to level eLock, which must be either NO_LOCK
1059** or SHARED_LOCK. Regardless of whether or not the call to xUnlock()
1060** succeeds, set the Pager.iLock variable to match the (attempted) new lock.
1061**
1062** Except, if Pager.iLock is set to NO_LOCK when this function is
1063** called, do not modify it. See the comment above the #define of
1064** NO_LOCK for an explanation of this.
1065*/
1066static int pager_unlock_db(Pager *pPager, int eLock)
1067{
1068 int rc = UNQLITE_OK;
1069 if( pPager->iLock != NO_LOCK ){
1070 rc = unqliteOsUnlock(pPager->pfd,eLock);
1071 pPager->iLock = eLock;
1072 }
1073 return rc;
1074}
1075/*
1076** Lock the database file to level eLock, which must be either SHARED_LOCK,
1077** RESERVED_LOCK or EXCLUSIVE_LOCK. If the caller is successful, set the
1078** Pager.eLock variable to the new locking state.
1079**
1080** Except, if Pager.eLock is set to NO_LOCK when this function is
1081** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK.
1082** See the comment above the #define of NO_LOCK for an explanation
1083** of this.
1084*/
1085static int pager_lock_db(Pager *pPager, int eLock){
1086 int rc = UNQLITE_OK;
1087 if( pPager->iLock < eLock || pPager->iLock == NO_LOCK ){
1088 rc = unqliteOsLock(pPager->pfd, eLock);
1089 if( rc==UNQLITE_OK ){
1090 pPager->iLock = eLock;
1091 }else{
1092 unqliteGenError(pPager->pDb,
1093 rc == UNQLITE_BUSY ? "Another process or thread hold the requested lock" : "Error while requesting database lock"
1094 );
1095 }
1096 }
1097 return rc;
1098}
1099/*
1100** Try to obtain a lock of type locktype on the database file. If
1101** a similar or greater lock is already held, this function is a no-op
1102** (returning UNQLITE_OK immediately).
1103**
1104** Otherwise, attempt to obtain the lock using unqliteOsLock(). Invoke
1105** the busy callback if the lock is currently not available. Repeat
1106** until the busy callback returns false or until the attempt to
1107** obtain the lock succeeds.
1108**
1109** Return UNQLITE_OK on success and an error code if we cannot obtain
1110** the lock. If the lock is obtained successfully, set the Pager.state
1111** variable to locktype before returning.
1112*/
1113static int pager_wait_on_lock(Pager *pPager, int locktype){
1114 int rc; /* Return code */
1115 do {
1116 rc = pager_lock_db(pPager,locktype);
1117 }while( rc==UNQLITE_BUSY && pPager->xBusyHandler && pPager->xBusyHandler(pPager->pBusyHandlerArg) );
1118 return rc;
1119}
1120/*
1121** This function is called after transitioning from PAGER_OPEN to
1122** PAGER_SHARED state. It tests if there is a hot journal present in
1123** the file-system for the given pager. A hot journal is one that
1124** needs to be played back. According to this function, a hot-journal
1125** file exists if the following criteria are met:
1126**
1127** * The journal file exists in the file system, and
1128** * No process holds a RESERVED or greater lock on the database file, and
1129** * The database file itself is greater than 0 bytes in size, and
1130** * The first byte of the journal file exists and is not 0x00.
1131**
1132** If the current size of the database file is 0 but a journal file
1133** exists, that is probably an old journal left over from a prior
1134** database with the same name. In this case the journal file is
1135** just deleted using OsDelete, *pExists is set to 0 and UNQLITE_OK
1136** is returned.
1137**
1138** If a hot-journal file is found to exist, *pExists is set to 1 and
1139** UNQLITE_OK returned. If no hot-journal file is present, *pExists is
1140** set to 0 and UNQLITE_OK returned. If an IO error occurs while trying
1141** to determine whether or not a hot-journal file exists, the IO error
1142** code is returned and the value of *pExists is undefined.
1143*/
1144static int pager_has_hot_journal(Pager *pPager, int *pExists)
1145{
1146 unqlite_vfs *pVfs = pPager->pVfs;
1147 int rc = UNQLITE_OK; /* Return code */
1148 int exists = 1; /* True if a journal file is present */
1149
1150 *pExists = 0;
1151 rc = unqliteOsAccess(pVfs, pPager->zJournal, UNQLITE_ACCESS_EXISTS, &exists);
1152 if( rc==UNQLITE_OK && exists ){
1153 int locked = 0; /* True if some process holds a RESERVED lock */
1154
1155 /* Race condition here: Another process might have been holding the
1156 ** the RESERVED lock and have a journal open at the unqliteOsAccess()
1157 ** call above, but then delete the journal and drop the lock before
1158 ** we get to the following unqliteOsCheckReservedLock() call. If that
1159 ** is the case, this routine might think there is a hot journal when
1160 ** in fact there is none. This results in a false-positive which will
1161 ** be dealt with by the playback routine.
1162 */
1163 rc = unqliteOsCheckReservedLock(pPager->pfd, &locked);
1164 if( rc==UNQLITE_OK && !locked ){
1165 sxi64 n = 0; /* Size of db file in bytes */
1166
1167 /* Check the size of the database file. If it consists of 0 pages,
1168 ** then delete the journal file. See the header comment above for
1169 ** the reasoning here. Delete the obsolete journal file under
1170 ** a RESERVED lock to avoid race conditions.
1171 */
1172 rc = unqliteOsFileSize(pPager->pfd,&n);
1173 if( rc==UNQLITE_OK ){
1174 if( n < 1 ){
1175 if( pager_lock_db(pPager, RESERVED_LOCK)==UNQLITE_OK ){
1176 unqliteOsDelete(pVfs, pPager->zJournal, 0);
1177 pager_unlock_db(pPager, SHARED_LOCK);
1178 }
1179 }else{
1180 /* The journal file exists and no other connection has a reserved
1181 ** or greater lock on the database file. */
1182 *pExists = 1;
1183 }
1184 }
1185 }
1186 }
1187 return rc;
1188}
1189/*
1190 * Rollback a journal file. (See block-comment above).
1191 */
1192static int pager_journal_rollback(Pager *pPager,int check_hot)
1193{
1194 int rc;
1195 if( check_hot ){
1196 int iExists = 0; /* cc warning */
1197 /* Check if the journal file exists */
1198 rc = pager_has_hot_journal(pPager,&iExists);
1199 if( rc != UNQLITE_OK ){
1200 /* IO error */
1201 return rc;
1202 }
1203 if( !iExists ){
1204 /* Journal file does not exists */
1205 return UNQLITE_OK;
1206 }
1207 }
1208 if( pPager->is_rdonly ){
1209 unqliteGenErrorFormat(pPager->pDb,
1210 "Cannot rollback journal file '%s' due to a read-only database handle",pPager->zJournal);
1211 return UNQLITE_READ_ONLY;
1212 }
1213 /* Get an EXCLUSIVE lock on the database file. At this point it is
1214 ** important that a RESERVED lock is not obtained on the way to the
1215 ** EXCLUSIVE lock. If it were, another process might open the
1216 ** database file, detect the RESERVED lock, and conclude that the
1217 ** database is safe to read while this process is still rolling the
1218 ** hot-journal back.
1219 **
1220 ** Because the intermediate RESERVED lock is not requested, any
1221 ** other process attempting to access the database file will get to
1222 ** this point in the code and fail to obtain its own EXCLUSIVE lock
1223 ** on the database file.
1224 **
1225 ** Unless the pager is in locking_mode=exclusive mode, the lock is
1226 ** downgraded to SHARED_LOCK before this function returns.
1227 */
1228 /* Open the journal file */
1229 rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal,&pPager->pjfd,UNQLITE_OPEN_READWRITE);
1230 if( rc != UNQLITE_OK ){
1231 unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: '%s'",pPager->zJournal);
1232 goto fail;
1233 }
1234 rc = pager_lock_db(pPager,EXCLUSIVE_LOCK);
1235 if( rc != UNQLITE_OK ){
1236 unqliteGenError(pPager->pDb,"Cannot acquire an exclusive lock on the database while journal rollback");
1237 goto fail;
1238 }
1239 /* Sync the journal file */
1240 unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL);
1241 /* Finally rollback the database */
1242 rc = pager_playback(pPager);
1243 /* Switch back to shared lock */
1244 pager_unlock_db(pPager,SHARED_LOCK);
1245fail:
1246 /* Close the journal handle */
1247 unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd);
1248 pPager->pjfd = 0;
1249 if( rc == UNQLITE_OK ){
1250 /* Delete the journal file */
1251 unqliteOsDelete(pPager->pVfs,pPager->zJournal,TRUE);
1252 }
1253 return rc;
1254}
1255/*
1256 * Write the unqlite header (First page). (Big-Endian)
1257 */
1258static int pager_write_db_header(Pager *pPager)
1259{
1260 unsigned char *zRaw = pPager->pHeader->zData;
1261 unqlite_kv_engine *pEngine = pPager->pEngine;
1262 sxu32 nDos;
1263 sxu16 nLen;
1264 /* Database signature */
1265 SyMemcpy(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1);
1266 zRaw += sizeof(UNQLITE_DB_SIG)-1;
1267 /* Database magic number */
1268 SyBigEndianPack32(zRaw,UNQLITE_DB_MAGIC);
1269 zRaw += 4; /* 4 byte magic number */
1270 /* Database creation time */
1271 SyZero(&pPager->tmCreate,sizeof(Sytm));
1272 if( pPager->pVfs->xCurrentTime ){
1273 pPager->pVfs->xCurrentTime(pPager->pVfs,&pPager->tmCreate);
1274 }
1275 /* DOS time format (4 bytes) */
1276 SyTimeFormatToDos(&pPager->tmCreate,&nDos);
1277 SyBigEndianPack32(zRaw,nDos);
1278 zRaw += 4; /* 4 byte DOS time */
1279 /* Sector size */
1280 SyBigEndianPack32(zRaw,(sxu32)pPager->iSectorSize);
1281 zRaw += 4; /* 4 byte sector size */
1282 /* Page size */
1283 SyBigEndianPack32(zRaw,(sxu32)pPager->iPageSize);
1284 zRaw += 4; /* 4 byte page size */
1285 /* Key value storage engine */
1286 nLen = (sxu16)SyStrlen(pEngine->pIo->pMethods->zName);
1287 SyBigEndianPack16(zRaw,nLen); /* 2 byte storage engine name */
1288 zRaw += 2;
1289 SyMemcpy((const void *)pEngine->pIo->pMethods->zName,(void *)zRaw,nLen);
1290 zRaw += nLen;
1291 /* All rest are meta-data available to the host application */
1292 return UNQLITE_OK;
1293}
1294/*
1295 * Read the unqlite header (first page). (Big-Endian)
1296 */
1297static int pager_extract_header(Pager *pPager,const unsigned char *zRaw,sxu32 nByte)
1298{
1299 const unsigned char *zEnd = &zRaw[nByte];
1300 sxu32 nDos,iMagic;
1301 sxu16 nLen;
1302 char *zKv;
1303 /* Database signature */
1304 if( SyMemcmp(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1) != 0 ){
1305 /* Corrupt database */
1306 return UNQLITE_CORRUPT;
1307 }
1308 zRaw += sizeof(UNQLITE_DB_SIG)-1;
1309 /* Database magic number */
1310 SyBigEndianUnpack32(zRaw,&iMagic);
1311 zRaw += 4; /* 4 byte magic number */
1312 if( iMagic != UNQLITE_DB_MAGIC ){
1313 /* Corrupt database */
1314 return UNQLITE_CORRUPT;
1315 }
1316 /* Database creation time */
1317 SyBigEndianUnpack32(zRaw,&nDos);
1318 zRaw += 4; /* 4 byte DOS time format */
1319 SyDosTimeFormat(nDos,&pPager->tmCreate);
1320 /* Sector size */
1321 SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iSectorSize);
1322 zRaw += 4; /* 4 byte sector size */
1323 /* Page size */
1324 SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iPageSize);
1325 zRaw += 4; /* 4 byte page size */
1326 /* Check that the values read from the page-size and sector-size fields
1327 ** are within range. To be 'in range', both values need to be a power
1328 ** of two greater than or equal to 512 or 32, and not greater than their
1329 ** respective compile time maximum limits.
1330 */
1331 if( pPager->iPageSize<UNQLITE_MIN_PAGE_SIZE || pPager->iSectorSize<32
1332 || pPager->iPageSize>UNQLITE_MAX_PAGE_SIZE || pPager->iSectorSize>MAX_SECTOR_SIZE
1333 || ((pPager->iPageSize<-1)&pPager->iPageSize)!=0 || ((pPager->iSectorSize-1)&pPager->iSectorSize)!=0
1334 ){
1335 return UNQLITE_CORRUPT;
1336 }
1337 /* Key value storage engine */
1338 SyBigEndianUnpack16(zRaw,&nLen); /* 2 byte storage engine length */
1339 zRaw += 2;
1340 if( nLen > (sxu16)(zEnd - zRaw) ){
1341 nLen = (sxu16)(zEnd - zRaw);
1342 }
1343 zKv = (char *)SyMemBackendDup(pPager->pAllocator,(const char *)zRaw,nLen);
1344 if( zKv == 0 ){
1345 return UNQLITE_NOMEM;
1346 }
1347 SyStringInitFromBuf(&pPager->sKv,zKv,nLen);
1348 return UNQLITE_OK;
1349}
1350/*
1351 * Read the database header.
1352 */
1353static int pager_read_db_header(Pager *pPager)
1354{
1355 unsigned char zRaw[UNQLITE_MIN_PAGE_SIZE]; /* Minimum page size */
1356 sxi64 n = 0; /* Size of db file in bytes */
1357 int rc;
1358 /* Get the file size first */
1359 rc = unqliteOsFileSize(pPager->pfd,&n);
1360 if( rc != UNQLITE_OK ){
1361 return rc;
1362 }
1363 pPager->dbByteSize = n;
1364 if( n > 0 ){
1365 unqlite_kv_methods *pMethods;
1366 SyString *pKv;
1367 pgno nPage;
1368 if( n < UNQLITE_MIN_PAGE_SIZE ){
1369 /* A valid unqlite database must be at least 512 bytes long */
1370 unqliteGenError(pPager->pDb,"Malformed database image");
1371 return UNQLITE_CORRUPT;
1372 }
1373 /* Read the database header */
1374 rc = unqliteOsRead(pPager->pfd,zRaw,sizeof(zRaw),0);
1375 if( rc != UNQLITE_OK ){
1376 unqliteGenError(pPager->pDb,"IO error while reading database header");
1377 return rc;
1378 }
1379 /* Extract the header */
1380 rc = pager_extract_header(pPager,zRaw,sizeof(zRaw));
1381 if( rc != UNQLITE_OK ){
1382 unqliteGenError(pPager->pDb,rc == UNQLITE_NOMEM ? "Unqlite is running out of memory" : "Malformed database image");
1383 return rc;
1384 }
1385 /* Update pager state */
1386 nPage = (pgno)(n / pPager->iPageSize);
1387 if( nPage==0 && n>0 ){
1388 nPage = 1;
1389 }
1390 pPager->dbSize = nPage;
1391 /* Laod the target Key/Value storage engine */
1392 pKv = &pPager->sKv;
1393 pMethods = unqliteFindKVStore(pKv->zString,pKv->nByte);
1394 if( pMethods == 0 ){
1395 unqliteGenErrorFormat(pPager->pDb,"No such Key/Value storage engine '%z'",pKv);
1396 return UNQLITE_NOTIMPLEMENTED;
1397 }
1398 /* Install the new KV storage engine */
1399 rc = unqlitePagerRegisterKvEngine(pPager,pMethods);
1400 if( rc != UNQLITE_OK ){
1401 return rc;
1402 }
1403 }else{
1404 /* Set a default page and sector size */
1405 pPager->iSectorSize = GetSectorSize(pPager->pfd);
1406 pPager->iPageSize = unqliteGetPageSize();
1407 SyStringInitFromBuf(&pPager->sKv,pPager->pEngine->pIo->pMethods->zName,SyStrlen(pPager->pEngine->pIo->pMethods->zName));
1408 pPager->dbSize = 0;
1409 }
1410 /* Allocate a temporary page size */
1411 pPager->zTmpPage = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize);
1412 if( pPager->zTmpPage == 0 ){
1413 unqliteGenOutofMem(pPager->pDb);
1414 return UNQLITE_NOMEM;
1415 }
1416 SyZero(pPager->zTmpPage,(sxu32)pPager->iPageSize);
1417 return UNQLITE_OK;
1418}
1419/*
1420 * Write the database header.
1421 */
1422static int pager_create_header(Pager *pPager)
1423{
1424 Page *pHeader;
1425 int rc;
1426 /* Allocate a new page */
1427 pHeader = pager_alloc_page(pPager,0);
1428 if( pHeader == 0 ){
1429 return UNQLITE_NOMEM;
1430 }
1431 pPager->pHeader = pHeader;
1432 /* Link the page */
1433 pager_link_page(pPager,pHeader);
1434 /* Add to the dirty list */
1435 pager_page_to_dirty_list(pPager,pHeader);
1436 /* Write the database header */
1437 rc = pager_write_db_header(pPager);
1438 return rc;
1439}
1440/*
1441** This function is called to obtain a shared lock on the database file.
1442** It is illegal to call unqlitePagerAcquire() until after this function
1443** has been successfully called. If a shared-lock is already held when
1444** this function is called, it is a no-op.
1445**
1446** The following operations are also performed by this function.
1447**
1448** 1) If the pager is currently in PAGER_OPEN state (no lock held
1449** on the database file), then an attempt is made to obtain a
1450** SHARED lock on the database file. Immediately after obtaining
1451** the SHARED lock, the file-system is checked for a hot-journal,
1452** which is played back if present.
1453**
1454** If everything is successful, UNQLITE_OK is returned. If an IO error
1455** occurs while locking the database, checking for a hot-journal file or
1456** rolling back a journal file, the IO error code is returned.
1457*/
1458static int pager_shared_lock(Pager *pPager)
1459{
1460 int rc = UNQLITE_OK;
1461 if( pPager->iState == PAGER_OPEN ){
1462 unqlite_kv_methods *pMethods;
1463 /* Open the target database */
1464 rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zFilename,&pPager->pfd,pPager->iOpenFlags);
1465 if( rc != UNQLITE_OK ){
1466 unqliteGenErrorFormat(pPager->pDb,
1467 "IO error while opening the target database file: %s",pPager->zFilename
1468 );
1469 return rc;
1470 }
1471 /* Try to obtain a shared lock */
1472 rc = pager_wait_on_lock(pPager,SHARED_LOCK);
1473 if( rc == UNQLITE_OK ){
1474 if( pPager->iLock <= SHARED_LOCK ){
1475 /* Rollback any hot journal */
1476 rc = pager_journal_rollback(pPager,1);
1477 if( rc != UNQLITE_OK ){
1478 return rc;
1479 }
1480 }
1481 /* Read the database header */
1482 rc = pager_read_db_header(pPager);
1483 if( rc != UNQLITE_OK ){
1484 return rc;
1485 }
1486 if(pPager->dbSize > 0 ){
1487 if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){
1488 const jx9_vfs *pVfs = jx9ExportBuiltinVfs();
1489 /* Obtain a read-only memory view of the whole file */
1490 if( pVfs && pVfs->xMmap ){
1491 int vr;
1492 vr = pVfs->xMmap(pPager->zFilename,&pPager->pMmap,&pPager->dbByteSize);
1493 if( vr != JX9_OK ){
1494 /* Generate a warning */
1495 unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database");
1496 pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP;
1497 }
1498 }else{
1499 /* Generate a warning */
1500 unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database");
1501 pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP;
1502 }
1503 }
1504 }
1505 /* Update the pager state */
1506 pPager->iState = PAGER_READER;
1507 /* Invoke the xOpen methods if available */
1508 pMethods = pPager->pEngine->pIo->pMethods;
1509 if( pMethods->xOpen ){
1510 rc = pMethods->xOpen(pPager->pEngine,pPager->dbSize);
1511 if( rc != UNQLITE_OK ){
1512 unqliteGenErrorFormat(pPager->pDb,
1513 "xOpen() method of the underlying KV engine '%z' failed",
1514 &pPager->sKv
1515 );
1516 pager_unlock_db(pPager,NO_LOCK);
1517 pPager->iState = PAGER_OPEN;
1518 return rc;
1519 }
1520 }
1521 }else if( rc == UNQLITE_BUSY ){
1522 unqliteGenError(pPager->pDb,"Another process or thread have a reserved or exclusive lock on this database");
1523 }
1524 }
1525 return rc;
1526}
1527/*
1528** Begin a write-transaction on the specified pager object. If a
1529** write-transaction has already been opened, this function is a no-op.
1530*/
1531UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager)
1532{
1533 int rc;
1534 /* Obtain a shared lock on the database first */
1535 rc = pager_shared_lock(pPager);
1536 if( rc != UNQLITE_OK ){
1537 return rc;
1538 }
1539 if( pPager->iState >= PAGER_WRITER_LOCKED ){
1540 return UNQLITE_OK;
1541 }
1542 if( pPager->is_rdonly ){
1543 unqliteGenError(pPager->pDb,"Read-only database");
1544 /* Read only database */
1545 return UNQLITE_READ_ONLY;
1546 }
1547 /* Obtain a reserved lock on the database */
1548 rc = pager_wait_on_lock(pPager,RESERVED_LOCK);
1549 if( rc == UNQLITE_OK ){
1550 /* Create the bitvec */
1551 pPager->pVec = unqliteBitvecCreate(pPager->pAllocator,pPager->dbSize);
1552 if( pPager->pVec == 0 ){
1553 unqliteGenOutofMem(pPager->pDb);
1554 rc = UNQLITE_NOMEM;
1555 goto fail;
1556 }
1557 /* Change to the WRITER_LOCK state */
1558 pPager->iState = PAGER_WRITER_LOCKED;
1559 pPager->dbOrigSize = pPager->dbSize;
1560 pPager->iJournalOfft = 0;
1561 pPager->nRec = 0;
1562 if( pPager->dbSize < 1 ){
1563 /* Write the database header */
1564 rc = pager_create_header(pPager);
1565 if( rc != UNQLITE_OK ){
1566 goto fail;
1567 }
1568 pPager->dbSize = 1;
1569 }
1570 }else if( rc == UNQLITE_BUSY ){
1571 unqliteGenError(pPager->pDb,"Another process or thread have a reserved lock on this database");
1572 }
1573 return rc;
1574fail:
1575 /* Downgrade to shared lock */
1576 pager_unlock_db(pPager,SHARED_LOCK);
1577 return rc;
1578}
1579/*
1580** This function is called at the start of every write transaction.
1581** There must already be a RESERVED or EXCLUSIVE lock on the database
1582** file when this routine is called.
1583**
1584*/
1585static int unqliteOpenJournal(Pager *pPager)
1586{
1587 unsigned char *zHeader;
1588 int rc = UNQLITE_OK;
1589 if( pPager->is_mem || pPager->no_jrnl ){
1590 /* Journaling is omitted for this database */
1591 goto finish;
1592 }
1593 if( pPager->iState >= PAGER_WRITER_CACHEMOD ){
1594 /* Already opened */
1595 return UNQLITE_OK;
1596 }
1597 /* Delete any previously journal with the same name */
1598 unqliteOsDelete(pPager->pVfs,pPager->zJournal,1);
1599 /* Open the journal file */
1600 rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal,
1601 &pPager->pjfd,UNQLITE_OPEN_CREATE|UNQLITE_OPEN_READWRITE);
1602 if( rc != UNQLITE_OK ){
1603 unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: %s",pPager->zJournal);
1604 return rc;
1605 }
1606 /* Write the journal header */
1607 zHeader = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iSectorSize);
1608 if( zHeader == 0 ){
1609 rc = UNQLITE_NOMEM;
1610 goto fail;
1611 }
1612 pager_write_journal_header(pPager,zHeader);
1613 /* Perform the disk write */
1614 rc = unqliteOsWrite(pPager->pjfd,zHeader,pPager->iSectorSize,0);
1615 /* Offset to start writing from */
1616 pPager->iJournalOfft = pPager->iSectorSize;
1617 /* All done, journal will be synced later */
1618 SyMemBackendFree(pPager->pAllocator,zHeader);
1619finish:
1620 if( rc == UNQLITE_OK ){
1621 pPager->iState = PAGER_WRITER_CACHEMOD;
1622 return UNQLITE_OK;
1623 }
1624fail:
1625 /* Unlink the journal file if something goes wrong */
1626 unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd);
1627 unqliteOsDelete(pPager->pVfs,pPager->zJournal,0);
1628 pPager->pjfd = 0;
1629 return rc;
1630}
1631/*
1632** Sync the journal. In other words, make sure all the pages that have
1633** been written to the journal have actually reached the surface of the
1634** disk and can be restored in the event of a hot-journal rollback.
1635*
1636* This routine try also to obtain an exlusive lock on the database.
1637*/
1638static int unqliteFinalizeJournal(Pager *pPager,int *pRetry,int close_jrnl)
1639{
1640 int rc;
1641 *pRetry = 0;
1642 /* Grab the exclusive lock first */
1643 rc = pager_lock_db(pPager,EXCLUSIVE_LOCK);
1644 if( rc != UNQLITE_OK ){
1645 /* Retry the excusive lock process */
1646 *pRetry = 1;
1647 rc = UNQLITE_OK;
1648 }
1649 if( pPager->no_jrnl ){
1650 /* Journaling is omitted, return immediately */
1651 return UNQLITE_OK;
1652 }
1653 /* Write the total number of database records */
1654 rc = WriteInt32(pPager->pjfd,pPager->nRec,8 /* sizeof(aJournalRec) */);
1655 if( rc != UNQLITE_OK ){
1656 if( pPager->nRec > 0 ){
1657 return rc;
1658 }else{
1659 /* Not so fatal */
1660 rc = UNQLITE_OK;
1661 }
1662 }
1663 /* Sync the journal and close it */
1664 rc = unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL);
1665 if( close_jrnl ){
1666 /* close the journal file */
1667 if( UNQLITE_OK != unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd) ){
1668 if( rc != UNQLITE_OK /* unqliteOsSync */ ){
1669 return rc;
1670 }
1671 }
1672 pPager->pjfd = 0;
1673 }
1674 if( (*pRetry) == 1 ){
1675 if( pager_lock_db(pPager,EXCLUSIVE_LOCK) == UNQLITE_OK ){
1676 /* Got exclusive lock */
1677 *pRetry = 0;
1678 }
1679 }
1680 return UNQLITE_OK;
1681}
1682/*
1683 * Mark a single data page as writeable. The page is written into the
1684 * main journal as required.
1685 */
1686static int page_write(Pager *pPager,Page *pPage)
1687{
1688 int rc;
1689 if( !pPager->is_mem && !pPager->no_jrnl ){
1690 /* Write the page to the transaction journal */
1691 if( pPage->pgno < pPager->dbOrigSize && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){
1692 sxu32 cksum;
1693 if( pPager->nRec == SXU32_HIGH ){
1694 /* Journal Limit reached */
1695 unqliteGenError(pPager->pDb,"Journal record limit reached, commit your changes");
1696 return UNQLITE_LIMIT;
1697 }
1698 /* Write the page number */
1699 rc = WriteInt64(pPager->pjfd,pPage->pgno,pPager->iJournalOfft);
1700 if( rc != UNQLITE_OK ){ return rc; }
1701 /* Write the raw page */
1702 /** CODEC */
1703 rc = unqliteOsWrite(pPager->pjfd,pPage->zData,pPager->iPageSize,pPager->iJournalOfft + 8);
1704 if( rc != UNQLITE_OK ){ return rc; }
1705 /* Compute the checksum */
1706 cksum = pager_cksum(pPager,pPage->zData);
1707 rc = WriteInt32(pPager->pjfd,cksum,pPager->iJournalOfft + 8 + pPager->iPageSize);
1708 if( rc != UNQLITE_OK ){ return rc; }
1709 /* Update the journal offset */
1710 pPager->iJournalOfft += 8 /* page num */ + pPager->iPageSize + 4 /* cksum */;
1711 pPager->nRec++;
1712 /* Mark as journalled */
1713 unqliteBitvecSet(pPager->pVec,pPage->pgno);
1714 }
1715 }
1716 /* Add the page to the dirty list */
1717 pager_page_to_dirty_list(pPager,pPage);
1718 /* Update the database size and return. */
1719 if( (1 + pPage->pgno) > pPager->dbSize ){
1720 pPager->dbSize = 1 + pPage->pgno;
1721 if( pPager->dbSize == SXU64_HIGH ){
1722 unqliteGenError(pPager->pDb,"Database maximum page limit (64-bit) reached");
1723 return UNQLITE_LIMIT;
1724 }
1725 }
1726 return UNQLITE_OK;
1727}
1728/*
1729** The argument is the first in a linked list of dirty pages connected
1730** by the PgHdr.pDirty pointer. This function writes each one of the
1731** in-memory pages in the list to the database file. The argument may
1732** be NULL, representing an empty list. In this case this function is
1733** a no-op.
1734**
1735** The pager must hold at least a RESERVED lock when this function
1736** is called. Before writing anything to the database file, this lock
1737** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained,
1738** UNQLITE_BUSY is returned and no data is written to the database file.
1739*/
1740static int pager_write_dirty_pages(Pager *pPager,Page *pDirty)
1741{
1742 int rc = UNQLITE_OK;
1743 Page *pNext;
1744 for(;;){
1745 if( pDirty == 0 ){
1746 break;
1747 }
1748 /* Point to the next dirty page */
1749 pNext = pDirty->pDirtyPrev; /* Not a bug: Reverse link */
1750 if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){
1751 rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize);
1752 if( rc != UNQLITE_OK ){
1753 /* A rollback should be done */
1754 break;
1755 }
1756 }
1757 /* Remove stale flags */
1758 pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY);
1759 if( pDirty->nRef < 1 ){
1760 /* Unlink the page now it is unused */
1761 pager_unlink_page(pPager,pDirty);
1762 /* Release the page */
1763 pager_release_page(pPager,pDirty);
1764 }
1765 /* Point to the next page */
1766 pDirty = pNext;
1767 }
1768 pPager->pDirty = pPager->pFirstDirty = 0;
1769 pPager->pHotDirty = pPager->pFirstHot = 0;
1770 pPager->nHot = 0;
1771 return rc;
1772}
1773/*
1774** The argument is the first in a linked list of hot dirty pages connected
1775** by the PgHdr.pHotDirty pointer. This function writes each one of the
1776** in-memory pages in the list to the database file. The argument may
1777** be NULL, representing an empty list. In this case this function is
1778** a no-op.
1779**
1780** The pager must hold at least a RESERVED lock when this function
1781** is called. Before writing anything to the database file, this lock
1782** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained,
1783** UNQLITE_BUSY is returned and no data is written to the database file.
1784*/
1785static int pager_write_hot_dirty_pages(Pager *pPager,Page *pDirty)
1786{
1787 int rc = UNQLITE_OK;
1788 Page *pNext;
1789 for(;;){
1790 if( pDirty == 0 ){
1791 break;
1792 }
1793 /* Point to the next page */
1794 pNext = pDirty->pPrevHot; /* Not a bug: Reverse link */
1795 if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){
1796 rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize);
1797 if( rc != UNQLITE_OK ){
1798 break;
1799 }
1800 }
1801 /* Remove stale flags */
1802 pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY);
1803 /* Unlink from the list of dirty pages */
1804 if( pDirty->pDirtyPrev ){
1805 pDirty->pDirtyPrev->pDirtyNext = pDirty->pDirtyNext;
1806 }else{
1807 pPager->pDirty = pDirty->pDirtyNext;
1808 }
1809 if( pDirty->pDirtyNext ){
1810 pDirty->pDirtyNext->pDirtyPrev = pDirty->pDirtyPrev;
1811 }else{
1812 pPager->pFirstDirty = pDirty->pDirtyPrev;
1813 }
1814 /* Discard */
1815 pager_unlink_page(pPager,pDirty);
1816 /* Release the page */
1817 pager_release_page(pPager,pDirty);
1818 /* Next hot page */
1819 pDirty = pNext;
1820 }
1821 return rc;
1822}
1823/*
1824 * Commit a transaction: Phase one.
1825 */
1826static int pager_commit_phase1(Pager *pPager)
1827{
1828 int get_excl = 0;
1829 Page *pDirty;
1830 int rc;
1831 /* If no database changes have been made, return early. */
1832 if( pPager->iState < PAGER_WRITER_CACHEMOD ){
1833 return UNQLITE_OK;
1834 }
1835 if( pPager->is_mem ){
1836 /* An in-memory database */
1837 return UNQLITE_OK;
1838 }
1839 if( pPager->is_rdonly ){
1840 /* Read-Only DB */
1841 unqliteGenError(pPager->pDb,"Read-Only database");
1842 return UNQLITE_READ_ONLY;
1843 }
1844 /* Finalize the journal file */
1845 rc = unqliteFinalizeJournal(pPager,&get_excl,1);
1846 if( rc != UNQLITE_OK ){
1847 return rc;
1848 }
1849 /* Get the dirty pages */
1850 pDirty = pager_get_dirty_pages(pPager);
1851 if( get_excl ){
1852 /* Wait one last time for the exclusive lock */
1853 rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK);
1854 if( rc != UNQLITE_OK ){
1855 unqliteGenError(pPager->pDb,"Cannot obtain an Exclusive lock on the target database");
1856 return rc;
1857 }
1858 }
1859 if( pPager->iFlags & PAGER_CTRL_DIRTY_COMMIT ){
1860 /* Synce the database first if a dirty commit have been applied */
1861 unqliteOsSync(pPager->pfd,UNQLITE_SYNC_NORMAL);
1862 }
1863 /* Write the dirty pages */
1864 rc = pager_write_dirty_pages(pPager,pDirty);
1865 if( rc != UNQLITE_OK ){
1866 /* Rollback your DB */
1867 pPager->iFlags |= PAGER_CTRL_COMMIT_ERR;
1868 pPager->pFirstDirty = pDirty;
1869 unqliteGenError(pPager->pDb,"IO error while writing dirty pages, rollback your database");
1870 return rc;
1871 }
1872 /* If the file on disk is not the same size as the database image,
1873 * then use unqliteOsTruncate to grow or shrink the file here.
1874 */
1875 if( pPager->dbSize != pPager->dbOrigSize ){
1876 unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize);
1877 }
1878 /* Sync the database file */
1879 unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL);
1880 /* Remove stale flags */
1881 pPager->iJournalOfft = 0;
1882 pPager->nRec = 0;
1883 return UNQLITE_OK;
1884}
1885/*
1886 * Commit a transaction: Phase two.
1887 */
1888static int pager_commit_phase2(Pager *pPager)
1889{
1890 if( !pPager->is_mem ){
1891 if( pPager->iState == PAGER_OPEN ){
1892 return UNQLITE_OK;
1893 }
1894 if( pPager->iState != PAGER_READER ){
1895 if( !pPager->no_jrnl ){
1896 /* Finally, unlink the journal file */
1897 unqliteOsDelete(pPager->pVfs,pPager->zJournal,1);
1898 }
1899 /* Downgrade to shraed lock */
1900 pager_unlock_db(pPager,SHARED_LOCK);
1901 pPager->iState = PAGER_READER;
1902 if( pPager->pVec ){
1903 unqliteBitvecDestroy(pPager->pVec);
1904 pPager->pVec = 0;
1905 }
1906 }
1907 }
1908 return UNQLITE_OK;
1909}
1910/*
1911 * Perform a dirty commit.
1912 */
1913static int pager_dirty_commit(Pager *pPager)
1914{
1915 int get_excl = 0;
1916 Page *pHot;
1917 int rc;
1918 /* Finalize the journal file without closing it */
1919 rc = unqliteFinalizeJournal(pPager,&get_excl,0);
1920 if( rc != UNQLITE_OK ){
1921 /* It's not a fatal error if something goes wrong here since
1922 * its not the final commit.
1923 */
1924 return UNQLITE_OK;
1925 }
1926 /* Point to the list of hot pages */
1927 pHot = pager_get_hot_pages(pPager);
1928 if( pHot == 0 ){
1929 return UNQLITE_OK;
1930 }
1931 if( get_excl ){
1932 /* Wait one last time for the exclusive lock */
1933 rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK);
1934 if( rc != UNQLITE_OK ){
1935 /* Not so fatal, will try another time */
1936 return UNQLITE_OK;
1937 }
1938 }
1939 /* Tell that a dirty commit happen */
1940 pPager->iFlags |= PAGER_CTRL_DIRTY_COMMIT;
1941 /* Write the hot pages now */
1942 rc = pager_write_hot_dirty_pages(pPager,pHot);
1943 if( rc != UNQLITE_OK ){
1944 pPager->iFlags |= PAGER_CTRL_COMMIT_ERR;
1945 unqliteGenError(pPager->pDb,"IO error while writing hot dirty pages, rollback your database");
1946 return rc;
1947 }
1948 pPager->pFirstHot = pPager->pHotDirty = 0;
1949 pPager->nHot = 0;
1950 /* No need to sync the database file here, since the journal is already
1951 * open here and this is not the final commit.
1952 */
1953 return UNQLITE_OK;
1954}
1955/*
1956** Commit a transaction and sync the database file for the pager pPager.
1957**
1958** This routine ensures that:
1959**
1960** * the journal is synced,
1961** * all dirty pages are written to the database file,
1962** * the database file is truncated (if required), and
1963** * the database file synced.
1964** * the journal file is deleted.
1965*/
1966UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager)
1967{
1968 int rc;
1969 /* Commit: Phase One */
1970 rc = pager_commit_phase1(pPager);
1971 if( rc != UNQLITE_OK ){
1972 goto fail;
1973 }
1974 /* Commit: Phase Two */
1975 rc = pager_commit_phase2(pPager);
1976 if( rc != UNQLITE_OK ){
1977 goto fail;
1978 }
1979 /* Remove stale flags */
1980 pPager->iFlags &= ~PAGER_CTRL_COMMIT_ERR;
1981 /* All done */
1982 return UNQLITE_OK;
1983fail:
1984 /* Disable the auto-commit flag */
1985 pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT;
1986 return rc;
1987}
1988/*
1989 * Reset the pager to its initial state. This is caused by
1990 * a rollback operation.
1991 */
1992static int pager_reset_state(Pager *pPager,int bResetKvEngine)
1993{
1994 unqlite_kv_engine *pEngine = pPager->pEngine;
1995 Page *pNext,*pPtr = pPager->pAll;
1996 const unqlite_kv_io *pIo;
1997 int rc;
1998 /* Remove stale flags */
1999 pPager->iFlags &= ~(PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT);
2000 pPager->iJournalOfft = 0;
2001 pPager->nRec = 0;
2002 /* Database original size */
2003 pPager->dbSize = pPager->dbOrigSize;
2004 /* Discard all in-memory pages */
2005 for(;;){
2006 if( pPtr == 0 ){
2007 break;
2008 }
2009 pNext = pPtr->pNext; /* Reverse link */
2010 /* Remove stale flags */
2011 pPtr->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY);
2012 /* Release the page */
2013 pager_release_page(pPager,pPtr);
2014 /* Point to the next page */
2015 pPtr = pNext;
2016 }
2017 pPager->pAll = 0;
2018 pPager->nPage = 0;
2019 pPager->pDirty = pPager->pFirstDirty = 0;
2020 pPager->pHotDirty = pPager->pFirstHot = 0;
2021 pPager->nHot = 0;
2022 if( pPager->apHash ){
2023 /* Zero the table */
2024 SyZero((void *)pPager->apHash,sizeof(Page *) * pPager->nSize);
2025 }
2026 if( pPager->pVec ){
2027 unqliteBitvecDestroy(pPager->pVec);
2028 pPager->pVec = 0;
2029 }
2030 /* Switch back to shared lock */
2031 pager_unlock_db(pPager,SHARED_LOCK);
2032 pPager->iState = PAGER_READER;
2033 if( bResetKvEngine ){
2034 /* Reset the underlying KV engine */
2035 pIo = pEngine->pIo;
2036 if( pIo->pMethods->xRelease ){
2037 /* Call the release callback */
2038 pIo->pMethods->xRelease(pEngine);
2039 }
2040 /* Zero the structure */
2041 SyZero(pEngine,(sxu32)pIo->pMethods->szKv);
2042 /* Fill in */
2043 pEngine->pIo = pIo;
2044 if( pIo->pMethods->xInit ){
2045 /* Call the init method */
2046 rc = pIo->pMethods->xInit(pEngine,pPager->iPageSize);
2047 if( rc != UNQLITE_OK ){
2048 return rc;
2049 }
2050 }
2051 if( pIo->pMethods->xOpen ){
2052 /* Call the xOpen method */
2053 rc = pIo->pMethods->xOpen(pEngine,pPager->dbSize);
2054 if( rc != UNQLITE_OK ){
2055 return rc;
2056 }
2057 }
2058 }
2059 /* All done */
2060 return UNQLITE_OK;
2061}
2062/*
2063** If a write transaction is open, then all changes made within the
2064** transaction are reverted and the current write-transaction is closed.
2065** The pager falls back to PAGER_READER state if successful.
2066**
2067** Otherwise, in rollback mode, this function performs two functions:
2068**
2069** 1) It rolls back the journal file, restoring all database file and
2070** in-memory cache pages to the state they were in when the transaction
2071** was opened, and
2072**
2073** 2) It finalizes the journal file, so that it is not used for hot
2074** rollback at any point in the future (i.e. deletion).
2075**
2076** Finalization of the journal file (task 2) is only performed if the
2077** rollback is successful.
2078**
2079*/
2080UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine)
2081{
2082 int rc = UNQLITE_OK;
2083 if( pPager->iState < PAGER_WRITER_LOCKED ){
2084 /* A write transaction must be opened */
2085 return UNQLITE_OK;
2086 }
2087 if( pPager->is_mem ){
2088 /* As of this release 1.1.6: Transactions are not supported for in-memory databases */
2089 return UNQLITE_OK;
2090 }
2091 if( pPager->is_rdonly ){
2092 /* Read-Only DB */
2093 unqliteGenError(pPager->pDb,"Read-Only database");
2094 return UNQLITE_READ_ONLY;
2095 }
2096 if( pPager->iState >= PAGER_WRITER_CACHEMOD ){
2097 if( !pPager->no_jrnl ){
2098 /* Close any outstanding joural file */
2099 if( pPager->pjfd ){
2100 /* Sync the journal file */
2101 unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL);
2102 }
2103 unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd);
2104 pPager->pjfd = 0;
2105 if( pPager->iFlags & (PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT) ){
2106 /* Perform the rollback */
2107 rc = pager_journal_rollback(pPager,0);
2108 if( rc != UNQLITE_OK ){
2109 /* Set the auto-commit flag */
2110 pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT;
2111 return rc;
2112 }
2113 }
2114 }
2115 /* Unlink the journal file */
2116 unqliteOsDelete(pPager->pVfs,pPager->zJournal,1);
2117 /* Reset the pager state */
2118 rc = pager_reset_state(pPager,bResetKvEngine);
2119 if( rc != UNQLITE_OK ){
2120 /* Mostly an unlikely scenario */
2121 pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; /* Set the auto-commit flag */
2122 unqliteGenError(pPager->pDb,"Error while reseting pager to its initial state");
2123 return rc;
2124 }
2125 }else{
2126 /* Downgrade to shared lock */
2127 pager_unlock_db(pPager,SHARED_LOCK);
2128 pPager->iState = PAGER_READER;
2129 }
2130 return UNQLITE_OK;
2131}
2132/*
2133 * Mark a data page as non writeable.
2134 */
2135static int unqlitePagerDontWrite(unqlite_page *pMyPage)
2136{
2137 Page *pPage = (Page *)pMyPage;
2138 if( pPage->pgno > 0 /* Page 0 is always writeable */ ){
2139 pPage->flags |= PAGE_DONT_WRITE;
2140 }
2141 return UNQLITE_OK;
2142}
2143/*
2144** Mark a data page as writeable. This routine must be called before
2145** making changes to a page. The caller must check the return value
2146** of this function and be careful not to change any page data unless
2147** this routine returns UNQLITE_OK.
2148*/
2149static int unqlitePageWrite(unqlite_page *pMyPage)
2150{
2151 Page *pPage = (Page *)pMyPage;
2152 Pager *pPager = pPage->pPager;
2153 int rc;
2154 /* Begin the write transaction */
2155 rc = unqlitePagerBegin(pPager);
2156 if( rc != UNQLITE_OK ){
2157 return rc;
2158 }
2159 if( pPager->iState == PAGER_WRITER_LOCKED ){
2160 /* The journal file needs to be opened. Higher level routines have already
2161 ** obtained the necessary locks to begin the write-transaction, but the
2162 ** rollback journal might not yet be open. Open it now if this is the case.
2163 */
2164 rc = unqliteOpenJournal(pPager);
2165 if( rc != UNQLITE_OK ){
2166 return rc;
2167 }
2168 }
2169 if( pPager->nHot > 127 ){
2170 /* Write hot dirty pages */
2171 rc = pager_dirty_commit(pPager);
2172 if( rc != UNQLITE_OK ){
2173 /* A rollback must be done */
2174 unqliteGenError(pPager->pDb,"Please perform a rollback");
2175 return rc;
2176 }
2177 }
2178 /* Write the page to the journal file */
2179 rc = page_write(pPager,pPage);
2180 return rc;
2181}
2182/*
2183** Acquire a reference to page number pgno in pager pPager (a page
2184** reference has type unqlite_page*). If the requested reference is
2185** successfully obtained, it is copied to *ppPage and UNQLITE_OK returned.
2186**
2187** If the requested page is already in the cache, it is returned.
2188** Otherwise, a new page object is allocated and populated with data
2189** read from the database file.
2190*/
2191static int unqlitePagerAcquire(
2192 Pager *pPager, /* The pager open on the database file */
2193 pgno pgno, /* Page number to fetch */
2194 unqlite_page **ppPage, /* OUT: Acquired page */
2195 int fetchOnly, /* Cache lookup only */
2196 int noContent /* Do not bother reading content from disk if true */
2197)
2198{
2199 Page *pPage;
2200 int rc;
2201 /* Acquire a shared lock (if not yet done) on the database and rollback any hot-journal if present */
2202 rc = pager_shared_lock(pPager);
2203 if( rc != UNQLITE_OK ){
2204 return rc;
2205 }
2206 /* Fetch the page from the cache */
2207 pPage = pager_fetch_page(pPager,pgno);
2208 if( fetchOnly ){
2209 if( ppPage ){
2210 *ppPage = (unqlite_page *)pPage;
2211 }
2212 return pPage ? UNQLITE_OK : UNQLITE_NOTFOUND;
2213 }
2214 if( pPage == 0 ){
2215 /* Allocate a new page */
2216 pPage = pager_alloc_page(pPager,pgno);
2217 if( pPage == 0 ){
2218 unqliteGenOutofMem(pPager->pDb);
2219 return UNQLITE_NOMEM;
2220 }
2221 /* Read page contents */
2222 rc = pager_get_page_contents(pPager,pPage,noContent);
2223 if( rc != UNQLITE_OK ){
2224 SyMemBackendPoolFree(pPager->pAllocator,pPage);
2225 return rc;
2226 }
2227 /* Link the page */
2228 pager_link_page(pPager,pPage);
2229 }else{
2230 if( ppPage ){
2231 page_ref(pPage);
2232 }
2233 }
2234 /* All done, page is loaded in memeory */
2235 if( ppPage ){
2236 *ppPage = (unqlite_page *)pPage;
2237 }
2238 return UNQLITE_OK;
2239}
2240/*
2241 * Return true if we are dealing with an in-memory database.
2242 */
2243static int unqliteInMemory(const char *zFilename)
2244{
2245 sxu32 n;
2246 if( SX_EMPTY_STR(zFilename) ){
2247 /* NULL or the empty string means an in-memory database */
2248 return TRUE;
2249 }
2250 n = SyStrlen(zFilename);
2251 if( n == sizeof(":mem:") - 1 &&
2252 SyStrnicmp(zFilename,":mem:",sizeof(":mem:") - 1) == 0 ){
2253 return TRUE;
2254 }
2255 if( n == sizeof(":memory:") - 1 &&
2256 SyStrnicmp(zFilename,":memory:",sizeof(":memory:") - 1) == 0 ){
2257 return TRUE;
2258 }
2259 return FALSE;
2260}
2261/*
2262 * Allocate a new KV cursor.
2263 */
2264UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut)
2265{
2266 unqlite_kv_methods *pMethods;
2267 unqlite_kv_cursor *pCur;
2268 sxu32 nByte;
2269 /* Storage engine methods */
2270 pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods;
2271 if( pMethods->szCursor < 1 ){
2272 /* Implementation does not supprt cursors */
2273 unqliteGenErrorFormat(pDb,"Storage engine '%s' does not support cursors",pMethods->zName);
2274 return UNQLITE_NOTIMPLEMENTED;
2275 }
2276 nByte = pMethods->szCursor;
2277 if( nByte < sizeof(unqlite_kv_cursor) ){
2278 nByte += sizeof(unqlite_kv_cursor);
2279 }
2280 pCur = (unqlite_kv_cursor *)SyMemBackendPoolAlloc(&pDb->sMem,nByte);
2281 if( pCur == 0 ){
2282 unqliteGenOutofMem(pDb);
2283 return UNQLITE_NOMEM;
2284 }
2285 /* Zero the structure */
2286 SyZero(pCur,nByte);
2287 /* Save the cursor */
2288 pCur->pStore = pDb->sDB.pPager->pEngine;
2289 /* Invoke the initialization callback if any */
2290 if( pMethods->xCursorInit ){
2291 pMethods->xCursorInit(pCur);
2292 }
2293 /* All done */
2294 *ppOut = pCur;
2295 return UNQLITE_OK;
2296}
2297/*
2298 * Release a cursor.
2299 */
2300UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur)
2301{
2302 unqlite_kv_methods *pMethods;
2303 /* Storage engine methods */
2304 pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods;
2305 /* Invoke the release callback if available */
2306 if( pMethods->xCursorRelease ){
2307 pMethods->xCursorRelease(pCur);
2308 }
2309 /* Finally, free the whole instance */
2310 SyMemBackendPoolFree(&pDb->sMem,pCur);
2311 return UNQLITE_OK;
2312}
2313/*
2314 * Release the underlying KV storage engine and invoke
2315 * its associated callbacks if available.
2316 */
2317static void pager_release_kv_engine(Pager *pPager)
2318{
2319 unqlite_kv_engine *pEngine = pPager->pEngine;
2320 unqlite_db *pStorage = &pPager->pDb->sDB;
2321 if( pStorage->pCursor ){
2322 /* Release the associated cursor */
2323 unqliteReleaseCursor(pPager->pDb,pStorage->pCursor);
2324 pStorage->pCursor = 0;
2325 }
2326 if( pEngine->pIo->pMethods->xRelease ){
2327 pEngine->pIo->pMethods->xRelease(pEngine);
2328 }
2329 /* Release the whole instance */
2330 SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine->pIo);
2331 SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine);
2332 pPager->pEngine = 0;
2333}
2334/* Forward declaration */
2335static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo);
2336/*
2337 * Allocate, initialize and register a new KV storage engine
2338 * within this database instance.
2339 */
2340UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods)
2341{
2342 unqlite_db *pStorage = &pPager->pDb->sDB;
2343 unqlite *pDb = pPager->pDb;
2344 unqlite_kv_engine *pEngine;
2345 unqlite_kv_io *pIo;
2346 sxu32 nByte;
2347 int rc;
2348 if( pPager->pEngine ){
2349 if( pMethods == pPager->pEngine->pIo->pMethods ){
2350 /* Ticket 1432: Same implementation */
2351 return UNQLITE_OK;
2352 }
2353 /* Release the old KV engine */
2354 pager_release_kv_engine(pPager);
2355 }
2356 /* Allocate a new KV engine instance */
2357 nByte = (sxu32)pMethods->szKv;
2358 pEngine = (unqlite_kv_engine *)SyMemBackendAlloc(&pDb->sMem,nByte);
2359 if( pEngine == 0 ){
2360 unqliteGenOutofMem(pDb);
2361 return UNQLITE_NOMEM;
2362 }
2363 pIo = (unqlite_kv_io *)SyMemBackendAlloc(&pDb->sMem,sizeof(unqlite_kv_io));
2364 if( pIo == 0 ){
2365 SyMemBackendFree(&pDb->sMem,pEngine);
2366 unqliteGenOutofMem(pDb);
2367 return UNQLITE_NOMEM;
2368 }
2369 /* Zero the structure */
2370 SyZero(pIo,sizeof(unqlite_io_methods));
2371 SyZero(pEngine,nByte);
2372 /* Populate the IO structure */
2373 pager_kv_io_init(pPager,pMethods,pIo);
2374 pEngine->pIo = pIo;
2375 /* Invoke the init callback if avaialble */
2376 if( pMethods->xInit ){
2377 rc = pMethods->xInit(pEngine,unqliteGetPageSize());
2378 if( rc != UNQLITE_OK ){
2379 unqliteGenErrorFormat(pDb,
2380 "xInit() method of the underlying KV engine '%z' failed",&pPager->sKv);
2381 goto fail;
2382 }
2383 pEngine->pIo = pIo;
2384 }
2385 pPager->pEngine = pEngine;
2386 /* Allocate a new cursor */
2387 rc = unqliteInitCursor(pDb,&pStorage->pCursor);
2388 if( rc != UNQLITE_OK ){
2389 goto fail;
2390 }
2391 return UNQLITE_OK;
2392fail:
2393 SyMemBackendFree(&pDb->sMem,pEngine);
2394 SyMemBackendFree(&pDb->sMem,pIo);
2395 return rc;
2396}
2397/*
2398 * Return the underlying KV storage engine instance.
2399 */
2400UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb)
2401{
2402 return pDb->sDB.pPager->pEngine;
2403}
2404/*
2405* Allocate and initialize a new Pager object. The pager should
2406* eventually be freed by passing it to unqlitePagerClose().
2407*
2408* The zFilename argument is the path to the database file to open.
2409* If zFilename is NULL or ":memory:" then all information is held
2410* in cache. It is never written to disk. This can be used to implement
2411* an in-memory database.
2412*/
2413UNQLITE_PRIVATE int unqlitePagerOpen(
2414 unqlite_vfs *pVfs, /* The virtual file system to use */
2415 unqlite *pDb, /* Database handle */
2416 const char *zFilename, /* Name of the database file to open */
2417 unsigned int iFlags /* flags controlling this file */
2418 )
2419{
2420 unqlite_kv_methods *pMethods = 0;
2421 int is_mem,rd_only,no_jrnl;
2422 Pager *pPager;
2423 sxu32 nByte;
2424 sxu32 nLen;
2425 int rc;
2426
2427 /* Select the appropriate KV storage subsytem */
2428 if( (iFlags & UNQLITE_OPEN_IN_MEMORY) || unqliteInMemory(zFilename) ){
2429 /* An in-memory database, record that */
2430 pMethods = unqliteFindKVStore("mem",sizeof("mem") - 1); /* Always available */
2431 iFlags |= UNQLITE_OPEN_IN_MEMORY;
2432 }else{
2433 /* Install the default key value storage subsystem [i.e. Linear Hash] */
2434 pMethods = unqliteFindKVStore("hash",sizeof("hash")-1);
2435 if( pMethods == 0 ){
2436 /* Use the b+tree storage backend if the linear hash storage is not available */
2437 pMethods = unqliteFindKVStore("btree",sizeof("btree")-1);
2438 }
2439 }
2440 if( pMethods == 0 ){
2441 /* Can't happen */
2442 unqliteGenError(pDb,"Cannot install a default Key/Value storage engine");
2443 return UNQLITE_NOTIMPLEMENTED;
2444 }
2445 is_mem = (iFlags & UNQLITE_OPEN_IN_MEMORY) != 0;
2446 rd_only = (iFlags & UNQLITE_OPEN_READONLY) != 0;
2447 no_jrnl = (iFlags & UNQLITE_OPEN_OMIT_JOURNALING) != 0;
2448 rc = UNQLITE_OK;
2449 if( is_mem ){
2450 /* Omit journaling for in-memory database */
2451 no_jrnl = 1;
2452 }
2453 /* Total number of bytes to allocate */
2454 nByte = sizeof(Pager);
2455 nLen = 0;
2456 if( !is_mem ){
2457 nLen = SyStrlen(zFilename);
2458 nByte += pVfs->mxPathname + nLen + sizeof(char) /* null termniator */;
2459 }
2460 /* Allocate */
2461 pPager = (Pager *)SyMemBackendAlloc(&pDb->sMem,nByte);
2462 if( pPager == 0 ){
2463 return UNQLITE_NOMEM;
2464 }
2465 /* Zero the structure */
2466 SyZero(pPager,nByte);
2467 /* Fill-in the structure */
2468 pPager->pAllocator = &pDb->sMem;
2469 pPager->pDb = pDb;
2470 pDb->sDB.pPager = pPager;
2471 /* Allocate page table */
2472 pPager->nSize = 128; /* Must be a power of two */
2473 nByte = pPager->nSize * sizeof(Page *);
2474 pPager->apHash = (Page **)SyMemBackendAlloc(pPager->pAllocator,nByte);
2475 if( pPager->apHash == 0 ){
2476 rc = UNQLITE_NOMEM;
2477 goto fail;
2478 }
2479 SyZero(pPager->apHash,nByte);
2480 pPager->is_mem = is_mem;
2481 pPager->no_jrnl = no_jrnl;
2482 pPager->is_rdonly = rd_only;
2483 pPager->iOpenFlags = iFlags;
2484 pPager->pVfs = pVfs;
2485 SyRandomnessInit(&pPager->sPrng,0,0);
2486 SyRandomness(&pPager->sPrng,(void *)&pPager->cksumInit,sizeof(sxu32));
2487 /* Unlimited cache size */
2488 pPager->nCacheMax = SXU32_HIGH;
2489 /* Copy filename and journal name */
2490 if( !is_mem ){
2491 pPager->zFilename = (char *)&pPager[1];
2492 rc = UNQLITE_OK;
2493 if( pVfs->xFullPathname ){
2494 rc = pVfs->xFullPathname(pVfs,zFilename,pVfs->mxPathname + nLen,pPager->zFilename);
2495 }
2496 if( rc != UNQLITE_OK ){
2497 /* Simple filename copy */
2498 SyMemcpy(zFilename,pPager->zFilename,nLen);
2499 pPager->zFilename[nLen] = 0;
2500 rc = UNQLITE_OK;
2501 }else{
2502 nLen = SyStrlen(pPager->zFilename);
2503 }
2504 pPager->zJournal = (char *) SyMemBackendAlloc(pPager->pAllocator,nLen + sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) + sizeof(char));
2505 if( pPager->zJournal == 0 ){
2506 rc = UNQLITE_NOMEM;
2507 goto fail;
2508 }
2509 /* Copy filename */
2510 SyMemcpy(pPager->zFilename,pPager->zJournal,nLen);
2511 /* Copy journal suffix */
2512 SyMemcpy(UNQLITE_JOURNAL_FILE_SUFFIX,&pPager->zJournal[nLen],sizeof(UNQLITE_JOURNAL_FILE_SUFFIX)-1);
2513 /* Append the nul terminator to the journal path */
2514 pPager->zJournal[nLen + ( sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) - 1)] = 0;
2515 }
2516 /* Finally, register the selected KV engine */
2517 rc = unqlitePagerRegisterKvEngine(pPager,pMethods);
2518 if( rc != UNQLITE_OK ){
2519 goto fail;
2520 }
2521 /* Set the pager state */
2522 if( pPager->is_mem ){
2523 pPager->iState = PAGER_WRITER_FINISHED;
2524 pPager->iLock = EXCLUSIVE_LOCK;
2525 }else{
2526 pPager->iState = PAGER_OPEN;
2527 pPager->iLock = NO_LOCK;
2528 }
2529 /* All done, ready for processing */
2530 return UNQLITE_OK;
2531fail:
2532 SyMemBackendFree(&pDb->sMem,pPager);
2533 return rc;
2534}
2535/*
2536 * Set a cache limit. Note that, this is a simple hint, the pager is not
2537 * forced to honor this limit.
2538 */
2539UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage)
2540{
2541 if( mxPage < 256 ){
2542 return UNQLITE_INVALID;
2543 }
2544 pPager->nCacheMax = mxPage;
2545 return UNQLITE_OK;
2546}
2547/*
2548 * Shutdown the page cache. Free all memory and close the database file.
2549 */
2550UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager)
2551{
2552 /* Release the KV engine */
2553 pager_release_kv_engine(pPager);
2554 if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){
2555 const jx9_vfs *pVfs = jx9ExportBuiltinVfs();
2556 if( pVfs && pVfs->xUnmap && pPager->pMmap ){
2557 pVfs->xUnmap(pPager->pMmap,pPager->dbByteSize);
2558 }
2559 }
2560 if( !pPager->is_mem && pPager->iState > PAGER_OPEN ){
2561 /* Release all lock on this database handle */
2562 pager_unlock_db(pPager,NO_LOCK);
2563 /* Close the file */
2564 unqliteOsCloseFree(pPager->pAllocator,pPager->pfd);
2565 }
2566 if( pPager->pVec ){
2567 unqliteBitvecDestroy(pPager->pVec);
2568 pPager->pVec = 0;
2569 }
2570 return UNQLITE_OK;
2571}
2572/*
2573 * Generate a random string.
2574 */
2575UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen)
2576{
2577 static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */
2578 sxu32 i;
2579 /* Generate a binary string first */
2580 SyRandomness(&pPager->sPrng,zBuf,nLen);
2581 /* Turn the binary string into english based alphabet */
2582 for( i = 0 ; i < nLen ; ++i ){
2583 zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)];
2584 }
2585}
2586/*
2587 * Generate a random number.
2588 */
2589UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager)
2590{
2591 sxu32 iNum;
2592 SyRandomness(&pPager->sPrng,(void *)&iNum,sizeof(iNum));
2593 return iNum;
2594}
2595/* Exported KV IO Methods */
2596/*
2597 * Refer to [unqlitePagerAcquire()]
2598 */
2599static int unqliteKvIoPageGet(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage)
2600{
2601 int rc;
2602 rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,0,0);
2603 return rc;
2604}
2605/*
2606 * Refer to [unqlitePagerAcquire()]
2607 */
2608static int unqliteKvIoPageLookup(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage)
2609{
2610 int rc;
2611 rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,1,0);
2612 return rc;
2613}
2614/*
2615 * Refer to [unqlitePagerAcquire()]
2616 */
2617static int unqliteKvIoNewPage(unqlite_kv_handle pHandle,unqlite_page **ppPage)
2618{
2619 Pager *pPager = (Pager *)pHandle;
2620 int rc;
2621 /*
2622 * Acquire a reader-lock first so that pPager->dbSize get initialized.
2623 */
2624 rc = pager_shared_lock(pPager);
2625 if( rc == UNQLITE_OK ){
2626 rc = unqlitePagerAcquire(pPager,pPager->dbSize == 0 ? /* Page 0 is reserved */ 1 : pPager->dbSize ,ppPage,0,0);
2627 }
2628 return rc;
2629}
2630/*
2631 * Refer to [unqlitePageWrite()]
2632 */
2633static int unqliteKvIopageWrite(unqlite_page *pPage)
2634{
2635 int rc;
2636 if( pPage == 0 ){
2637 /* TICKET 1433-0348 */
2638 return UNQLITE_OK;
2639 }
2640 rc = unqlitePageWrite(pPage);
2641 return rc;
2642}
2643/*
2644 * Refer to [unqlitePagerDontWrite()]
2645 */
2646static int unqliteKvIoPageDontWrite(unqlite_page *pPage)
2647{
2648 int rc;
2649 if( pPage == 0 ){
2650 /* TICKET 1433-0348 */
2651 return UNQLITE_OK;
2652 }
2653 rc = unqlitePagerDontWrite(pPage);
2654 return rc;
2655}
2656/*
2657 * Refer to [unqliteBitvecSet()]
2658 */
2659static int unqliteKvIoPageDontJournal(unqlite_page *pRaw)
2660{
2661 Page *pPage = (Page *)pRaw;
2662 Pager *pPager;
2663 if( pPage == 0 ){
2664 /* TICKET 1433-0348 */
2665 return UNQLITE_OK;
2666 }
2667 pPager = pPage->pPager;
2668 if( pPager->iState >= PAGER_WRITER_LOCKED ){
2669 if( !pPager->no_jrnl && pPager->pVec && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){
2670 unqliteBitvecSet(pPager->pVec,pPage->pgno);
2671 }
2672 }
2673 return UNQLITE_OK;
2674}
2675/*
2676 * Do not add a page to the hot dirty list.
2677 */
2678static int unqliteKvIoPageDontMakeHot(unqlite_page *pRaw)
2679{
2680 Page *pPage = (Page *)pRaw;
2681
2682 if( pPage == 0 ){
2683 /* TICKET 1433-0348 */
2684 return UNQLITE_OK;
2685 }
2686 pPage->flags |= PAGE_DONT_MAKE_HOT;
2687
2688 /* Remove from hot dirty list if it is already there */
2689 if( pPage->flags & PAGE_HOT_DIRTY ){
2690 Pager *pPager = pPage->pPager;
2691 if( pPage->pNextHot ){
2692 pPage->pNextHot->pPrevHot = pPage->pPrevHot;
2693 }
2694 if( pPage->pPrevHot ){
2695 pPage->pPrevHot->pNextHot = pPage->pNextHot;
2696 }
2697 if( pPager->pFirstHot == pPage ){
2698 pPager->pFirstHot = pPage->pPrevHot;
2699 }
2700 if( pPager->pHotDirty == pPage ){
2701 pPager->pHotDirty = pPage->pNextHot;
2702 }
2703 pPager->nHot--;
2704 pPage->flags &= ~PAGE_HOT_DIRTY;
2705 }
2706
2707 return UNQLITE_OK;
2708}
2709/*
2710 * Refer to [page_ref()]
2711 */
2712static int unqliteKvIopage_ref(unqlite_page *pPage)
2713{
2714 if( pPage ){
2715 page_ref((Page *)pPage);
2716 }
2717 return UNQLITE_OK;
2718}
2719/*
2720 * Refer to [page_unref()]
2721 */
2722static int unqliteKvIoPageUnRef(unqlite_page *pPage)
2723{
2724 if( pPage ){
2725 page_unref((Page *)pPage);
2726 }
2727 return UNQLITE_OK;
2728}
2729/*
2730 * Refer to the declaration of the [Pager] structure
2731 */
2732static int unqliteKvIoReadOnly(unqlite_kv_handle pHandle)
2733{
2734 return ((Pager *)pHandle)->is_rdonly;
2735}
2736/*
2737 * Refer to the declaration of the [Pager] structure
2738 */
2739static int unqliteKvIoPageSize(unqlite_kv_handle pHandle)
2740{
2741 return ((Pager *)pHandle)->iPageSize;
2742}
2743/*
2744 * Refer to the declaration of the [Pager] structure
2745 */
2746static unsigned char * unqliteKvIoTempPage(unqlite_kv_handle pHandle)
2747{
2748 return ((Pager *)pHandle)->zTmpPage;
2749}
2750/*
2751 * Set a page unpin callback.
2752 * Refer to the declaration of the [Pager] structure
2753 */
2754static void unqliteKvIoPageUnpin(unqlite_kv_handle pHandle,void (*xPageUnpin)(void *))
2755{
2756 Pager *pPager = (Pager *)pHandle;
2757 pPager->xPageUnpin = xPageUnpin;
2758}
2759/*
2760 * Set a page reload callback.
2761 * Refer to the declaration of the [Pager] structure
2762 */
2763static void unqliteKvIoPageReload(unqlite_kv_handle pHandle,void (*xPageReload)(void *))
2764{
2765 Pager *pPager = (Pager *)pHandle;
2766 pPager->xPageReload = xPageReload;
2767}
2768/*
2769 * Log an error.
2770 * Refer to the declaration of the [Pager] structure
2771 */
2772static void unqliteKvIoErr(unqlite_kv_handle pHandle,const char *zErr)
2773{
2774 Pager *pPager = (Pager *)pHandle;
2775 unqliteGenError(pPager->pDb,zErr);
2776}
2777/*
2778 * Init an instance of the [unqlite_kv_io] structure.
2779 */
2780static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo)
2781{
2782 pIo->pHandle = pPager;
2783 pIo->pMethods = pMethods;
2784
2785 pIo->xGet = unqliteKvIoPageGet;
2786 pIo->xLookup = unqliteKvIoPageLookup;
2787 pIo->xNew = unqliteKvIoNewPage;
2788
2789 pIo->xWrite = unqliteKvIopageWrite;
2790 pIo->xDontWrite = unqliteKvIoPageDontWrite;
2791 pIo->xDontJournal = unqliteKvIoPageDontJournal;
2792 pIo->xDontMkHot = unqliteKvIoPageDontMakeHot;
2793
2794 pIo->xPageRef = unqliteKvIopage_ref;
2795 pIo->xPageUnref = unqliteKvIoPageUnRef;
2796
2797 pIo->xPageSize = unqliteKvIoPageSize;
2798 pIo->xReadOnly = unqliteKvIoReadOnly;
2799
2800 pIo->xTmpPage = unqliteKvIoTempPage;
2801
2802 pIo->xSetUnpin = unqliteKvIoPageUnpin;
2803 pIo->xSetReload = unqliteKvIoPageReload;
2804
2805 pIo->xErr = unqliteKvIoErr;
2806
2807 return UNQLITE_OK;
2808}
diff --git a/common/unqlite/unqlite.h b/common/unqlite/unqlite.h
new file mode 100644
index 0000000..f9b6ae6
--- /dev/null
+++ b/common/unqlite/unqlite.h
@@ -0,0 +1,954 @@
1/* This file was automatically generated. Do not edit (Except for compile time directives)! */
2#ifndef _UNQLITE_H_
3#define _UNQLITE_H_
4/*
5 * Symisc UnQLite: An Embeddable NoSQL (Post Modern) Database Engine.
6 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
7 * Version 1.1.6
8 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
9 * please contact Symisc Systems via:
10 * legal@symisc.net
11 * licensing@symisc.net
12 * contact@symisc.net
13 * or visit:
14 * http://unqlite.org/licensing.html
15 */
16/*
17 * Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>].
18 * All rights reserved.
19 *
20 * Redistribution and use in source and binary forms, with or without
21 * modification, are permitted provided that the following conditions
22 * are met:
23 * 1. Redistributions of source code must retain the above copyright
24 * notice, this list of conditions and the following disclaimer.
25 * 2. Redistributions in binary form must reproduce the above copyright
26 * notice, this list of conditions and the following disclaimer in the
27 * documentation and/or other materials provided with the distribution.
28 *
29 * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS
30 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
32 * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS
33 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
34 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
35 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
36 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
37 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
38 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
39 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 */
41 /* $SymiscID: unqlite.h v1.1 UNIX|WIN32/64 2012-11-02 02:10 stable <chm@symisc.net> $ */
42#include <stdarg.h> /* needed for the definition of va_list */
43/*
44 * Compile time engine version, signature, identification in the symisc source tree
45 * and copyright notice.
46 * Each macro have an equivalent C interface associated with it that provide the same
47 * information but are associated with the library instead of the header file.
48 * Refer to [unqlite_lib_version()], [unqlite_lib_signature()], [unqlite_lib_ident()] and
49 * [unqlite_lib_copyright()] for more information.
50 */
51/*
52 * The UNQLITE_VERSION C preprocessor macroevaluates to a string literal
53 * that is the unqlite version in the format "X.Y.Z" where X is the major
54 * version number and Y is the minor version number and Z is the release
55 * number.
56 */
57#define UNQLITE_VERSION "1.1.6"
58/*
59 * The UNQLITE_VERSION_NUMBER C preprocessor macro resolves to an integer
60 * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same
61 * numbers used in [UNQLITE_VERSION].
62 */
63#define UNQLITE_VERSION_NUMBER 1001006
64/*
65 * The UNQLITE_SIG C preprocessor macro evaluates to a string
66 * literal which is the public signature of the unqlite engine.
67 * This signature could be included for example in a host-application
68 * generated Server MIME header as follows:
69 * Server: YourWebServer/x.x unqlite/x.x.x \r\n
70 */
71#define UNQLITE_SIG "unqlite/1.1.6"
72/*
73 * UnQLite identification in the Symisc source tree:
74 * Each particular check-in of a particular software released
75 * by symisc systems have an unique identifier associated with it.
76 * This macro hold the one associated with unqlite.
77 */
78#define UNQLITE_IDENT "unqlite:b172a1e2c3f62fb35c8e1fb2795121f82356cad6"
79/*
80 * Copyright notice.
81 * If you have any questions about the licensing situation, please
82 * visit http://unqlite.org/licensing.html
83 * or contact Symisc Systems via:
84 * legal@symisc.net
85 * licensing@symisc.net
86 * contact@symisc.net
87 */
88#define UNQLITE_COPYRIGHT "Copyright (C) Symisc Systems, S.U.A.R.L [Mrad Chems Eddine <chm@symisc.net>] 2012-2013, http://unqlite.org/"
89/* Make sure we can call this stuff from C++ */
90#ifdef __cplusplus
91extern "C" {
92#endif
93/* Forward declaration to public objects */
94typedef struct unqlite_io_methods unqlite_io_methods;
95typedef struct unqlite_kv_methods unqlite_kv_methods;
96typedef struct unqlite_kv_engine unqlite_kv_engine;
97typedef struct jx9_io_stream unqlite_io_stream;
98typedef struct jx9_context unqlite_context;
99typedef struct jx9_value unqlite_value;
100typedef struct unqlite_vfs unqlite_vfs;
101typedef struct unqlite_vm unqlite_vm;
102typedef struct unqlite unqlite;
103/*
104 * ------------------------------
105 * Compile time directives
106 * ------------------------------
107 * For most purposes, UnQLite can be built just fine using the default compilation options.
108 * However, if required, the compile-time options documented below can be used to omit UnQLite
109 * features (resulting in a smaller compiled library size) or to change the default values
110 * of some parameters.
111 * Every effort has been made to ensure that the various combinations of compilation options
112 * work harmoniously and produce a working library.
113 *
114 * UNQLITE_ENABLE_THREADS
115 * This option controls whether or not code is included in UnQLite to enable it to operate
116 * safely in a multithreaded environment. The default is not. All mutexing code is omitted
117 * and it is unsafe to use UnQLite in a multithreaded program. When compiled with the
118 * UNQLITE_ENABLE_THREADS directive enabled, UnQLite can be used in a multithreaded program
119 * and it is safe to share the same virtual machine and engine handle between two or more threads.
120 * The value of UNQLITE_ENABLE_THREADS can be determined at run-time using the unqlite_lib_is_threadsafe()
121 * interface.
122 * When UnQLite has been compiled with threading support then the threading mode can be altered
123 * at run-time using the unqlite_lib_config() interface together with one of these verbs:
124 * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE
125 * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI
126 * Platforms others than Windows and UNIX systems must install their own mutex subsystem via
127 * unqlite_lib_config() with a configuration verb set to UNQLITE_LIB_CONFIG_USER_MUTEX.
128 * Otherwise the library is not threadsafe.
129 * Note that you must link UnQLite with the POSIX threads library under UNIX systems (i.e: -lpthread).
130 *
131 * Options To Omit/Enable Features
132 *
133 * The following options can be used to reduce the size of the compiled library by omitting optional
134 * features. This is probably only useful in embedded systems where space is especially tight, as even
135 * with all features included the UnQLite library is relatively small. Don't forget to tell your
136 * compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler
137 * to optimize for size usually has a much larger impact on library footprint than employing
138 * any of these compile-time options.
139 *
140 * JX9_DISABLE_BUILTIN_FUNC
141 * Jx9 is shipped with more than 312 built-in functions suitable for most purposes like
142 * string and INI processing, ZIP extracting, Base64 encoding/decoding, JSON encoding/decoding
143 * and so forth.
144 * If this directive is enabled, then all built-in Jx9 functions are omitted from the build.
145 * Note that special functions such as db_create(), db_store(), db_fetch(), etc. are not omitted
146 * from the build and are not affected by this directive.
147 *
148 * JX9_ENABLE_MATH_FUNC
149 * If this directive is enabled, built-in math functions such as sqrt(), abs(), log(), ceil(), etc.
150 * are included in the build. Note that you may need to link UnQLite with the math library in same
151 * Linux/BSD flavor (i.e: -lm).
152 *
153 * JX9_DISABLE_DISK_IO
154 * If this directive is enabled, built-in VFS functions such as chdir(), mkdir(), chroot(), unlink(),
155 * sleep(), etc. are omitted from the build.
156 *
157 * UNQLITE_ENABLE_JX9_HASH_IO
158 * If this directive is enabled, built-in hash functions such as md5(), sha1(), md5_file(), crc32(), etc.
159 * are included in the build.
160 */
161/* Symisc public definitions */
162#if !defined(SYMISC_STANDARD_DEFS)
163#define SYMISC_STANDARD_DEFS
164#if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE)
165/* Windows Systems */
166#if !defined(__WINNT__)
167#define __WINNT__
168#endif
169/*
170 * Determine if we are dealing with WindowsCE - which has a much
171 * reduced API.
172 */
173#if defined(_WIN32_WCE)
174#ifndef __WIN_CE__
175#define __WIN_CE__
176#endif /* __WIN_CE__ */
177#endif /* _WIN32_WCE */
178#else
179/*
180 * By default we will assume that we are compiling on a UNIX systems.
181 * Otherwise the OS_OTHER directive must be defined.
182 */
183#if !defined(OS_OTHER)
184#if !defined(__UNIXES__)
185#define __UNIXES__
186#endif /* __UNIXES__ */
187#else
188#endif /* OS_OTHER */
189#endif /* __WINNT__/__UNIXES__ */
190#if defined(_MSC_VER) || defined(__BORLANDC__)
191typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */
192typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */
193#else
194typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */
195typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */
196#endif /* _MSC_VER */
197/* Signature of the consumer routine */
198typedef int (*ProcConsumer)(const void *, unsigned int, void *);
199/* Forward reference */
200typedef struct SyMutexMethods SyMutexMethods;
201typedef struct SyMemMethods SyMemMethods;
202typedef struct SyString SyString;
203typedef struct syiovec syiovec;
204typedef struct SyMutex SyMutex;
205typedef struct Sytm Sytm;
206/* Scatter and gather array. */
207struct syiovec
208{
209#if defined (__WINNT__)
210 /* Same fields type and offset as WSABUF structure defined one winsock2 header */
211 unsigned long nLen;
212 char *pBase;
213#else
214 void *pBase;
215 unsigned long nLen;
216#endif
217};
218struct SyString
219{
220 const char *zString; /* Raw string (may not be null terminated) */
221 unsigned int nByte; /* Raw string length */
222};
223/* Time structure. */
224struct Sytm
225{
226 int tm_sec; /* seconds (0 - 60) */
227 int tm_min; /* minutes (0 - 59) */
228 int tm_hour; /* hours (0 - 23) */
229 int tm_mday; /* day of month (1 - 31) */
230 int tm_mon; /* month of year (0 - 11) */
231 int tm_year; /* year + 1900 */
232 int tm_wday; /* day of week (Sunday = 0) */
233 int tm_yday; /* day of year (0 - 365) */
234 int tm_isdst; /* is summer time in effect? */
235 char *tm_zone; /* abbreviation of timezone name */
236 long tm_gmtoff; /* offset from UTC in seconds */
237};
238/* Convert a tm structure (struct tm *) found in <time.h> to a Sytm structure */
239#define STRUCT_TM_TO_SYTM(pTM, pSYTM) \
240 (pSYTM)->tm_hour = (pTM)->tm_hour;\
241 (pSYTM)->tm_min = (pTM)->tm_min;\
242 (pSYTM)->tm_sec = (pTM)->tm_sec;\
243 (pSYTM)->tm_mon = (pTM)->tm_mon;\
244 (pSYTM)->tm_mday = (pTM)->tm_mday;\
245 (pSYTM)->tm_year = (pTM)->tm_year + 1900;\
246 (pSYTM)->tm_yday = (pTM)->tm_yday;\
247 (pSYTM)->tm_wday = (pTM)->tm_wday;\
248 (pSYTM)->tm_isdst = (pTM)->tm_isdst;\
249 (pSYTM)->tm_gmtoff = 0;\
250 (pSYTM)->tm_zone = 0;
251
252/* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */
253#define SYSTEMTIME_TO_SYTM(pSYSTIME, pSYTM) \
254 (pSYTM)->tm_hour = (pSYSTIME)->wHour;\
255 (pSYTM)->tm_min = (pSYSTIME)->wMinute;\
256 (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\
257 (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\
258 (pSYTM)->tm_mday = (pSYSTIME)->wDay;\
259 (pSYTM)->tm_year = (pSYSTIME)->wYear;\
260 (pSYTM)->tm_yday = 0;\
261 (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\
262 (pSYTM)->tm_gmtoff = 0;\
263 (pSYTM)->tm_isdst = -1;\
264 (pSYTM)->tm_zone = 0;
265
266/* Dynamic memory allocation methods. */
267struct SyMemMethods
268{
269 void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */
270 void * (*xRealloc)(void *, unsigned int); /* [Required:] Re-allocate a memory chunk */
271 void (*xFree)(void *); /* [Required:] Release a memory chunk */
272 unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */
273 int (*xInit)(void *); /* [Optional:] Initialization callback */
274 void (*xRelease)(void *); /* [Optional:] Release callback */
275 void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */
276};
277/* Out of memory callback signature. */
278typedef int (*ProcMemError)(void *);
279/* Mutex methods. */
280struct SyMutexMethods
281{
282 int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */
283 void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */
284 SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */
285 void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */
286 void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */
287 int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */
288 void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */
289};
290#if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec)
291#define SX_APIIMPORT __declspec(dllimport)
292#define SX_APIEXPORT __declspec(dllexport)
293#else
294#define SX_APIIMPORT
295#define SX_APIEXPORT
296#endif
297/* Standard return values from Symisc public interfaces */
298#define SXRET_OK 0 /* Not an error */
299#define SXERR_MEM (-1) /* Out of memory */
300#define SXERR_IO (-2) /* IO error */
301#define SXERR_EMPTY (-3) /* Empty field */
302#define SXERR_LOCKED (-4) /* Locked operation */
303#define SXERR_ORANGE (-5) /* Out of range value */
304#define SXERR_NOTFOUND (-6) /* Item not found */
305#define SXERR_LIMIT (-7) /* Limit reached */
306#define SXERR_MORE (-8) /* Need more input */
307#define SXERR_INVALID (-9) /* Invalid parameter */
308#define SXERR_ABORT (-10) /* User callback request an operation abort */
309#define SXERR_EXISTS (-11) /* Item exists */
310#define SXERR_SYNTAX (-12) /* Syntax error */
311#define SXERR_UNKNOWN (-13) /* Unknown error */
312#define SXERR_BUSY (-14) /* Busy operation */
313#define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */
314#define SXERR_WILLBLOCK (-16) /* Operation will block */
315#define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */
316#define SXERR_EOF (-18) /* End of input */
317#define SXERR_PERM (-19) /* Permission error */
318#define SXERR_NOOP (-20) /* No-op */
319#define SXERR_FORMAT (-21) /* Invalid format */
320#define SXERR_NEXT (-22) /* Not an error */
321#define SXERR_OS (-23) /* System call return an error */
322#define SXERR_CORRUPT (-24) /* Corrupted pointer */
323#define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */
324#define SXERR_NOMATCH (-26) /* No match */
325#define SXERR_RESET (-27) /* Operation reset */
326#define SXERR_DONE (-28) /* Not an error */
327#define SXERR_SHORT (-29) /* Buffer too short */
328#define SXERR_PATH (-30) /* Path error */
329#define SXERR_TIMEOUT (-31) /* Timeout */
330#define SXERR_BIG (-32) /* Too big for processing */
331#define SXERR_RETRY (-33) /* Retry your call */
332#define SXERR_IGNORE (-63) /* Ignore */
333#endif /* SYMISC_PUBLIC_DEFS */
334/*
335 * Marker for exported interfaces.
336 */
337#define UNQLITE_APIEXPORT SX_APIEXPORT
338/*
339 * If compiling for a processor that lacks floating point
340 * support, substitute integer for floating-point.
341 */
342#ifdef UNQLITE_OMIT_FLOATING_POINT
343typedef sxi64 uqlite_real;
344#else
345typedef double unqlite_real;
346#endif
347typedef sxi64 unqlite_int64;
348/* Standard UnQLite return values */
349#define UNQLITE_OK SXRET_OK /* Successful result */
350/* Beginning of error codes */
351#define UNQLITE_NOMEM SXERR_MEM /* Out of memory */
352#define UNQLITE_ABORT SXERR_ABORT /* Another thread have released this instance */
353#define UNQLITE_IOERR SXERR_IO /* IO error */
354#define UNQLITE_CORRUPT SXERR_CORRUPT /* Corrupt pointer */
355#define UNQLITE_LOCKED SXERR_LOCKED /* Forbidden Operation */
356#define UNQLITE_BUSY SXERR_BUSY /* The database file is locked */
357#define UNQLITE_DONE SXERR_DONE /* Operation done */
358#define UNQLITE_PERM SXERR_PERM /* Permission error */
359#define UNQLITE_NOTIMPLEMENTED SXERR_NOTIMPLEMENTED /* Method not implemented by the underlying Key/Value storage engine */
360#define UNQLITE_NOTFOUND SXERR_NOTFOUND /* No such record */
361#define UNQLITE_NOOP SXERR_NOOP /* No such method */
362#define UNQLITE_INVALID SXERR_INVALID /* Invalid parameter */
363#define UNQLITE_EOF SXERR_EOF /* End Of Input */
364#define UNQLITE_UNKNOWN SXERR_UNKNOWN /* Unknown configuration option */
365#define UNQLITE_LIMIT SXERR_LIMIT /* Database limit reached */
366#define UNQLITE_EXISTS SXERR_EXISTS /* Record exists */
367#define UNQLITE_EMPTY SXERR_EMPTY /* Empty record */
368#define UNQLITE_COMPILE_ERR (-70) /* Compilation error */
369#define UNQLITE_VM_ERR (-71) /* Virtual machine error */
370#define UNQLITE_FULL (-73) /* Full database (unlikely) */
371#define UNQLITE_CANTOPEN (-74) /* Unable to open the database file */
372#define UNQLITE_READ_ONLY (-75) /* Read only Key/Value storage engine */
373#define UNQLITE_LOCKERR (-76) /* Locking protocol error */
374/* end-of-error-codes */
375/*
376 * Database Handle Configuration Commands.
377 *
378 * The following set of constants are the available configuration verbs that can
379 * be used by the host-application to configure an UnQLite database handle.
380 * These constants must be passed as the second argument to [unqlite_config()].
381 *
382 * Each options require a variable number of arguments.
383 * The [unqlite_config()] interface will return UNQLITE_OK on success, any other
384 * return value indicates failure.
385 * For a full discussion on the configuration verbs and their expected
386 * parameters, please refer to this page:
387 * http://unqlite.org/c_api/unqlite_config.html
388 */
389#define UNQLITE_CONFIG_JX9_ERR_LOG 1 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */
390#define UNQLITE_CONFIG_MAX_PAGE_CACHE 2 /* ONE ARGUMENT: int nMaxPage */
391#define UNQLITE_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */
392#define UNQLITE_CONFIG_KV_ENGINE 4 /* ONE ARGUMENT: const char *zKvName */
393#define UNQLITE_CONFIG_DISABLE_AUTO_COMMIT 5 /* NO ARGUMENTS */
394#define UNQLITE_CONFIG_GET_KV_NAME 6 /* ONE ARGUMENT: const char **pzPtr */
395/*
396 * UnQLite/Jx9 Virtual Machine Configuration Commands.
397 *
398 * The following set of constants are the available configuration verbs that can
399 * be used by the host-application to configure the Jx9 (Via UnQLite) Virtual machine.
400 * These constants must be passed as the second argument to the [unqlite_vm_config()]
401 * interface.
402 * Each options require a variable number of arguments.
403 * The [unqlite_vm_config()] interface will return UNQLITE_OK on success, any other return
404 * value indicates failure.
405 * There are many options but the most importants are: UNQLITE_VM_CONFIG_OUTPUT which install
406 * a VM output consumer callback, UNQLITE_VM_CONFIG_HTTP_REQUEST which parse and register
407 * a HTTP request and UNQLITE_VM_CONFIG_ARGV_ENTRY which populate the $argv array.
408 * For a full discussion on the configuration verbs and their expected parameters, please
409 * refer to this page:
410 * http://unqlite.org/c_api/unqlite_vm_config.html
411 */
412#define UNQLITE_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */
413#define UNQLITE_VM_CONFIG_IMPORT_PATH 2 /* ONE ARGUMENT: const char *zIncludePath */
414#define UNQLITE_VM_CONFIG_ERR_REPORT 3 /* NO ARGUMENTS: Report all run-time errors in the VM output */
415#define UNQLITE_VM_CONFIG_RECURSION_DEPTH 4 /* ONE ARGUMENT: int nMaxDepth */
416#define UNQLITE_VM_OUTPUT_LENGTH 5 /* ONE ARGUMENT: unsigned int *pLength */
417#define UNQLITE_VM_CONFIG_CREATE_VAR 6 /* TWO ARGUMENTS: const char *zName, unqlite_value *pValue */
418#define UNQLITE_VM_CONFIG_HTTP_REQUEST 7 /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */
419#define UNQLITE_VM_CONFIG_SERVER_ATTR 8 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */
420#define UNQLITE_VM_CONFIG_ENV_ATTR 9 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */
421#define UNQLITE_VM_CONFIG_EXEC_VALUE 10 /* ONE ARGUMENT: unqlite_value **ppValue */
422#define UNQLITE_VM_CONFIG_IO_STREAM 11 /* ONE ARGUMENT: const unqlite_io_stream *pStream */
423#define UNQLITE_VM_CONFIG_ARGV_ENTRY 12 /* ONE ARGUMENT: const char *zValue */
424#define UNQLITE_VM_CONFIG_EXTRACT_OUTPUT 13 /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */
425/*
426 * Storage engine configuration commands.
427 *
428 * The following set of constants are the available configuration verbs that can
429 * be used by the host-application to configure the underlying storage engine (i.e Hash, B+tree, R+tree).
430 * These constants must be passed as the first argument to [unqlite_kv_config()].
431 * Each options require a variable number of arguments.
432 * The [unqlite_kv_config()] interface will return UNQLITE_OK on success, any other return
433 * value indicates failure.
434 * For a full discussion on the configuration verbs and their expected parameters, please
435 * refer to this page:
436 * http://unqlite.org/c_api/unqlite_kv_config.html
437 */
438#define UNQLITE_KV_CONFIG_HASH_FUNC 1 /* ONE ARGUMENT: unsigned int (*xHash)(const void *,unsigned int) */
439#define UNQLITE_KV_CONFIG_CMP_FUNC 2 /* ONE ARGUMENT: int (*xCmp)(const void *,const void *,unsigned int) */
440/*
441 * Global Library Configuration Commands.
442 *
443 * The following set of constants are the available configuration verbs that can
444 * be used by the host-application to configure the whole library.
445 * These constants must be passed as the first argument to [unqlite_lib_config()].
446 *
447 * Each options require a variable number of arguments.
448 * The [unqlite_lib_config()] interface will return UNQLITE_OK on success, any other return
449 * value indicates failure.
450 * Notes:
451 * The default configuration is recommended for most applications and so the call to
452 * [unqlite_lib_config()] is usually not necessary. It is provided to support rare
453 * applications with unusual needs.
454 * The [unqlite_lib_config()] interface is not threadsafe. The application must insure that
455 * no other [unqlite_*()] interfaces are invoked by other threads while [unqlite_lib_config()]
456 * is running. Furthermore, [unqlite_lib_config()] may only be invoked prior to library
457 * initialization using [unqlite_lib_init()] or [unqlite_init()] or after shutdown
458 * by [unqlite_lib_shutdown()]. If [unqlite_lib_config()] is called after [unqlite_lib_init()]
459 * or [unqlite_init()] and before [unqlite_lib_shutdown()] then it will return UNQLITE_LOCKED.
460 * For a full discussion on the configuration verbs and their expected parameters, please
461 * refer to this page:
462 * http://unqlite.org/c_api/unqlite_lib.html
463 */
464#define UNQLITE_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */
465#define UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */
466#define UNQLITE_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */
467#define UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */
468#define UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */
469#define UNQLITE_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const unqlite_vfs *pVfs */
470#define UNQLITE_LIB_CONFIG_STORAGE_ENGINE 7 /* ONE ARGUMENT: unqlite_kv_methods *pStorage */
471#define UNQLITE_LIB_CONFIG_PAGE_SIZE 8 /* ONE ARGUMENT: int iPageSize */
472/*
473 * These bit values are intended for use in the 3rd parameter to the [unqlite_open()] interface
474 * and in the 4th parameter to the xOpen method of the [unqlite_vfs] object.
475 */
476#define UNQLITE_OPEN_READONLY 0x00000001 /* Read only mode. Ok for [unqlite_open] */
477#define UNQLITE_OPEN_READWRITE 0x00000002 /* Ok for [unqlite_open] */
478#define UNQLITE_OPEN_CREATE 0x00000004 /* Ok for [unqlite_open] */
479#define UNQLITE_OPEN_EXCLUSIVE 0x00000008 /* VFS only */
480#define UNQLITE_OPEN_TEMP_DB 0x00000010 /* VFS only */
481#define UNQLITE_OPEN_NOMUTEX 0x00000020 /* Ok for [unqlite_open] */
482#define UNQLITE_OPEN_OMIT_JOURNALING 0x00000040 /* Omit journaling for this database. Ok for [unqlite_open] */
483#define UNQLITE_OPEN_IN_MEMORY 0x00000080 /* An in memory database. Ok for [unqlite_open]*/
484#define UNQLITE_OPEN_MMAP 0x00000100 /* Obtain a memory view of the whole file. Ok for [unqlite_open] */
485/*
486 * Synchronization Type Flags
487 *
488 * When UnQLite invokes the xSync() method of an [unqlite_io_methods] object it uses
489 * a combination of these integer values as the second argument.
490 *
491 * When the UNQLITE_SYNC_DATAONLY flag is used, it means that the sync operation only
492 * needs to flush data to mass storage. Inode information need not be flushed.
493 * If the lower four bits of the flag equal UNQLITE_SYNC_NORMAL, that means to use normal
494 * fsync() semantics. If the lower four bits equal UNQLITE_SYNC_FULL, that means to use
495 * Mac OS X style fullsync instead of fsync().
496 */
497#define UNQLITE_SYNC_NORMAL 0x00002
498#define UNQLITE_SYNC_FULL 0x00003
499#define UNQLITE_SYNC_DATAONLY 0x00010
500/*
501 * File Locking Levels
502 *
503 * UnQLite uses one of these integer values as the second
504 * argument to calls it makes to the xLock() and xUnlock() methods
505 * of an [unqlite_io_methods] object.
506 */
507#define UNQLITE_LOCK_NONE 0
508#define UNQLITE_LOCK_SHARED 1
509#define UNQLITE_LOCK_RESERVED 2
510#define UNQLITE_LOCK_PENDING 3
511#define UNQLITE_LOCK_EXCLUSIVE 4
512/*
513 * CAPIREF: OS Interface: Open File Handle
514 *
515 * An [unqlite_file] object represents an open file in the [unqlite_vfs] OS interface
516 * layer.
517 * Individual OS interface implementations will want to subclass this object by appending
518 * additional fields for their own use. The pMethods entry is a pointer to an
519 * [unqlite_io_methods] object that defines methods for performing
520 * I/O operations on the open file.
521*/
522typedef struct unqlite_file unqlite_file;
523struct unqlite_file {
524 const unqlite_io_methods *pMethods; /* Methods for an open file. MUST BE FIRST */
525};
526/*
527 * CAPIREF: OS Interface: File Methods Object
528 *
529 * Every file opened by the [unqlite_vfs] xOpen method populates an
530 * [unqlite_file] object (or, more commonly, a subclass of the
531 * [unqlite_file] object) with a pointer to an instance of this object.
532 * This object defines the methods used to perform various operations
533 * against the open file represented by the [unqlite_file] object.
534 *
535 * If the xOpen method sets the unqlite_file.pMethods element
536 * to a non-NULL pointer, then the unqlite_io_methods.xClose method
537 * may be invoked even if the xOpen reported that it failed. The
538 * only way to prevent a call to xClose following a failed xOpen
539 * is for the xOpen to set the unqlite_file.pMethods element to NULL.
540 *
541 * The flags argument to xSync may be one of [UNQLITE_SYNC_NORMAL] or
542 * [UNQLITE_SYNC_FULL]. The first choice is the normal fsync().
543 * The second choice is a Mac OS X style fullsync. The [UNQLITE_SYNC_DATAONLY]
544 * flag may be ORed in to indicate that only the data of the file
545 * and not its inode needs to be synced.
546 *
547 * The integer values to xLock() and xUnlock() are one of
548 *
549 * UNQLITE_LOCK_NONE
550 * UNQLITE_LOCK_SHARED
551 * UNQLITE_LOCK_RESERVED
552 * UNQLITE_LOCK_PENDING
553 * UNQLITE_LOCK_EXCLUSIVE
554 *
555 * xLock() increases the lock. xUnlock() decreases the lock.
556 * The xCheckReservedLock() method checks whether any database connection,
557 * either in this process or in some other process, is holding a RESERVED,
558 * PENDING, or EXCLUSIVE lock on the file. It returns true if such a lock exists
559 * and false otherwise.
560 *
561 * The xSectorSize() method returns the sector size of the device that underlies
562 * the file. The sector size is the minimum write that can be performed without
563 * disturbing other bytes in the file.
564 *
565 */
566struct unqlite_io_methods {
567 int iVersion; /* Structure version number (currently 1) */
568 int (*xClose)(unqlite_file*);
569 int (*xRead)(unqlite_file*, void*, unqlite_int64 iAmt, unqlite_int64 iOfst);
570 int (*xWrite)(unqlite_file*, const void*, unqlite_int64 iAmt, unqlite_int64 iOfst);
571 int (*xTruncate)(unqlite_file*, unqlite_int64 size);
572 int (*xSync)(unqlite_file*, int flags);
573 int (*xFileSize)(unqlite_file*, unqlite_int64 *pSize);
574 int (*xLock)(unqlite_file*, int);
575 int (*xUnlock)(unqlite_file*, int);
576 int (*xCheckReservedLock)(unqlite_file*, int *pResOut);
577 int (*xSectorSize)(unqlite_file*);
578};
579/*
580 * CAPIREF: OS Interface Object
581 *
582 * An instance of the unqlite_vfs object defines the interface between
583 * the UnQLite core and the underlying operating system. The "vfs"
584 * in the name of the object stands for "Virtual File System".
585 *
586 * Only a single vfs can be registered within the UnQLite core.
587 * Vfs registration is done using the [unqlite_lib_config()] interface
588 * with a configuration verb set to UNQLITE_LIB_CONFIG_VFS.
589 * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users
590 * does not have to worry about registering and installing a vfs since UnQLite
591 * come with a built-in vfs for these platforms that implements most the methods
592 * defined below.
593 *
594 * Clients running on exotic systems (ie: Other than Windows and UNIX systems)
595 * must register their own vfs in order to be able to use the UnQLite library.
596 *
597 * The value of the iVersion field is initially 1 but may be larger in
598 * future versions of UnQLite.
599 *
600 * The szOsFile field is the size of the subclassed [unqlite_file] structure
601 * used by this VFS. mxPathname is the maximum length of a pathname in this VFS.
602 *
603 * At least szOsFile bytes of memory are allocated by UnQLite to hold the [unqlite_file]
604 * structure passed as the third argument to xOpen. The xOpen method does not have to
605 * allocate the structure; it should just fill it in. Note that the xOpen method must
606 * set the unqlite_file.pMethods to either a valid [unqlite_io_methods] object or to NULL.
607 * xOpen must do this even if the open fails. UnQLite expects that the unqlite_file.pMethods
608 * element will be valid after xOpen returns regardless of the success or failure of the
609 * xOpen call.
610 *
611 */
612struct unqlite_vfs {
613 const char *zName; /* Name of this virtual file system [i.e: Windows, UNIX, etc.] */
614 int iVersion; /* Structure version number (currently 1) */
615 int szOsFile; /* Size of subclassed unqlite_file */
616 int mxPathname; /* Maximum file pathname length */
617 int (*xOpen)(unqlite_vfs*, const char *zName, unqlite_file*,unsigned int flags);
618 int (*xDelete)(unqlite_vfs*, const char *zName, int syncDir);
619 int (*xAccess)(unqlite_vfs*, const char *zName, int flags, int *pResOut);
620 int (*xFullPathname)(unqlite_vfs*, const char *zName,int buf_len,char *zBuf);
621 int (*xTmpDir)(unqlite_vfs*,char *zBuf,int buf_len);
622 int (*xSleep)(unqlite_vfs*, int microseconds);
623 int (*xCurrentTime)(unqlite_vfs*,Sytm *pOut);
624 int (*xGetLastError)(unqlite_vfs*, int, char *);
625};
626/*
627 * Flags for the xAccess VFS method
628 *
629 * These integer constants can be used as the third parameter to
630 * the xAccess method of an [unqlite_vfs] object. They determine
631 * what kind of permissions the xAccess method is looking for.
632 * With UNQLITE_ACCESS_EXISTS, the xAccess method
633 * simply checks whether the file exists.
634 * With UNQLITE_ACCESS_READWRITE, the xAccess method
635 * checks whether the named directory is both readable and writable
636 * (in other words, if files can be added, removed, and renamed within
637 * the directory).
638 * The UNQLITE_ACCESS_READWRITE constant is currently used only by the
639 * [temp_store_directory pragma], though this could change in a future
640 * release of UnQLite.
641 * With UNQLITE_ACCESS_READ, the xAccess method
642 * checks whether the file is readable. The UNQLITE_ACCESS_READ constant is
643 * currently unused, though it might be used in a future release of
644 * UnQLite.
645 */
646#define UNQLITE_ACCESS_EXISTS 0
647#define UNQLITE_ACCESS_READWRITE 1
648#define UNQLITE_ACCESS_READ 2
649/*
650 * The type used to represent a page number. The first page in a file
651 * is called page 1. 0 is used to represent "not a page".
652 * A page number is an unsigned 64-bit integer.
653 */
654typedef sxu64 pgno;
655/*
656 * A database disk page is represented by an instance
657 * of the follwoing structure.
658 */
659typedef struct unqlite_page unqlite_page;
660struct unqlite_page
661{
662 unsigned char *zData; /* Content of this page */
663 void *pUserData; /* Extra content */
664 pgno pgno; /* Page number for this page */
665};
666/*
667 * UnQLite handle to the underlying Key/Value Storage Engine (See below).
668 */
669typedef void * unqlite_kv_handle;
670/*
671 * UnQLite pager IO methods.
672 *
673 * An instance of the following structure define the exported methods of the UnQLite pager
674 * to the underlying Key/Value storage engine.
675 */
676typedef struct unqlite_kv_io unqlite_kv_io;
677struct unqlite_kv_io
678{
679 unqlite_kv_handle pHandle; /* UnQLite handle passed as the first parameter to the
680 * method defined below.
681 */
682 unqlite_kv_methods *pMethods; /* Underlying storage engine */
683 /* Pager methods */
684 int (*xGet)(unqlite_kv_handle,pgno,unqlite_page **);
685 int (*xLookup)(unqlite_kv_handle,pgno,unqlite_page **);
686 int (*xNew)(unqlite_kv_handle,unqlite_page **);
687 int (*xWrite)(unqlite_page *);
688 int (*xDontWrite)(unqlite_page *);
689 int (*xDontJournal)(unqlite_page *);
690 int (*xDontMkHot)(unqlite_page *);
691 int (*xPageRef)(unqlite_page *);
692 int (*xPageUnref)(unqlite_page *);
693 int (*xPageSize)(unqlite_kv_handle);
694 int (*xReadOnly)(unqlite_kv_handle);
695 unsigned char * (*xTmpPage)(unqlite_kv_handle);
696 void (*xSetUnpin)(unqlite_kv_handle,void (*xPageUnpin)(void *));
697 void (*xSetReload)(unqlite_kv_handle,void (*xPageReload)(void *));
698 void (*xErr)(unqlite_kv_handle,const char *);
699};
700/*
701 * Key/Value Storage Engine Cursor Object
702 *
703 * An instance of a subclass of the following object defines a cursor
704 * used to scan through a key-value storage engine.
705 */
706typedef struct unqlite_kv_cursor unqlite_kv_cursor;
707struct unqlite_kv_cursor
708{
709 unqlite_kv_engine *pStore; /* Must be first */
710 /* Subclasses will typically add additional fields */
711};
712/*
713 * Possible seek positions.
714 */
715#define UNQLITE_CURSOR_MATCH_EXACT 1
716#define UNQLITE_CURSOR_MATCH_LE 2
717#define UNQLITE_CURSOR_MATCH_GE 3
718/*
719 * Key/Value Storage Engine.
720 *
721 * A Key-Value storage engine is defined by an instance of the following
722 * object.
723 * UnQLite works with run-time interchangeable storage engines (i.e. Hash, B+Tree, R+Tree, LSM, etc.).
724 * The storage engine works with key/value pairs where both the key
725 * and the value are byte arrays of arbitrary length and with no restrictions on content.
726 * UnQLite come with two built-in KV storage engine: A Virtual Linear Hash (VLH) storage
727 * engine is used for persistent on-disk databases with O(1) lookup time and an in-memory
728 * hash-table or Red-black tree storage engine is used for in-memory databases.
729 * Future versions of UnQLite might add other built-in storage engines (i.e. LSM).
730 * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()]
731 * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE.
732 */
733struct unqlite_kv_engine
734{
735 const unqlite_kv_io *pIo; /* IO methods: MUST be first */
736 /* Subclasses will typically add additional fields */
737};
738/*
739 * Key/Value Storage Engine Virtual Method Table.
740 *
741 * Key/Value storage engine methods is defined by an instance of the following
742 * object.
743 * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()]
744 * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE.
745 */
746struct unqlite_kv_methods
747{
748 const char *zName; /* Storage engine name [i.e. Hash, B+tree, LSM, R-tree, Mem, etc.]*/
749 int szKv; /* 'unqlite_kv_engine' subclass size */
750 int szCursor; /* 'unqlite_kv_cursor' subclass size */
751 int iVersion; /* Structure version, currently 1 */
752 /* Storage engine methods */
753 int (*xInit)(unqlite_kv_engine *,int iPageSize);
754 void (*xRelease)(unqlite_kv_engine *);
755 int (*xConfig)(unqlite_kv_engine *,int op,va_list ap);
756 int (*xOpen)(unqlite_kv_engine *,pgno);
757 int (*xReplace)(
758 unqlite_kv_engine *,
759 const void *pKey,int nKeyLen,
760 const void *pData,unqlite_int64 nDataLen
761 );
762 int (*xAppend)(
763 unqlite_kv_engine *,
764 const void *pKey,int nKeyLen,
765 const void *pData,unqlite_int64 nDataLen
766 );
767 void (*xCursorInit)(unqlite_kv_cursor *);
768 int (*xSeek)(unqlite_kv_cursor *,const void *pKey,int nByte,int iPos); /* Mandatory */
769 int (*xFirst)(unqlite_kv_cursor *);
770 int (*xLast)(unqlite_kv_cursor *);
771 int (*xValid)(unqlite_kv_cursor *);
772 int (*xNext)(unqlite_kv_cursor *);
773 int (*xPrev)(unqlite_kv_cursor *);
774 int (*xDelete)(unqlite_kv_cursor *);
775 int (*xKeyLength)(unqlite_kv_cursor *,int *);
776 int (*xKey)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData);
777 int (*xDataLength)(unqlite_kv_cursor *,unqlite_int64 *);
778 int (*xData)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData);
779 void (*xReset)(unqlite_kv_cursor *);
780 void (*xCursorRelease)(unqlite_kv_cursor *);
781};
782/*
783 * UnQLite journal file suffix.
784 */
785#ifndef UNQLITE_JOURNAL_FILE_SUFFIX
786#define UNQLITE_JOURNAL_FILE_SUFFIX "_unqlite_journal"
787#endif
788/*
789 * Call Context - Error Message Serverity Level.
790 *
791 * The following constans are the allowed severity level that can
792 * passed as the second argument to the [unqlite_context_throw_error()] or
793 * [unqlite_context_throw_error_format()] interfaces.
794 * Refer to the official documentation for additional information.
795 */
796#define UNQLITE_CTX_ERR 1 /* Call context error such as unexpected number of arguments, invalid types and so on. */
797#define UNQLITE_CTX_WARNING 2 /* Call context Warning */
798#define UNQLITE_CTX_NOTICE 3 /* Call context Notice */
799/*
800 * C-API-REF: Please refer to the official documentation for interfaces
801 * purpose and expected parameters.
802 */
803
804/* Database Engine Handle */
805UNQLITE_APIEXPORT int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode);
806UNQLITE_APIEXPORT int unqlite_config(unqlite *pDb,int nOp,...);
807UNQLITE_APIEXPORT int unqlite_close(unqlite *pDb);
808
809/* Key/Value (KV) Store Interfaces */
810UNQLITE_APIEXPORT int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen);
811UNQLITE_APIEXPORT int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen);
812UNQLITE_APIEXPORT int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...);
813UNQLITE_APIEXPORT int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...);
814UNQLITE_APIEXPORT int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 /* in|out */*pBufLen);
815UNQLITE_APIEXPORT int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey,
816 int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData);
817UNQLITE_APIEXPORT int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen);
818UNQLITE_APIEXPORT int unqlite_kv_config(unqlite *pDb,int iOp,...);
819
820/* Document (JSON) Store Interfaces powered by the Jx9 Scripting Language */
821UNQLITE_APIEXPORT int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut);
822UNQLITE_APIEXPORT int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut);
823UNQLITE_APIEXPORT int unqlite_vm_config(unqlite_vm *pVm,int iOp,...);
824UNQLITE_APIEXPORT int unqlite_vm_exec(unqlite_vm *pVm);
825UNQLITE_APIEXPORT int unqlite_vm_reset(unqlite_vm *pVm);
826UNQLITE_APIEXPORT int unqlite_vm_release(unqlite_vm *pVm);
827UNQLITE_APIEXPORT int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData);
828UNQLITE_APIEXPORT unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname);
829
830/* Cursor Iterator Interfaces */
831UNQLITE_APIEXPORT int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut);
832UNQLITE_APIEXPORT int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur);
833UNQLITE_APIEXPORT int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos);
834UNQLITE_APIEXPORT int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor);
835UNQLITE_APIEXPORT int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor);
836UNQLITE_APIEXPORT int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor);
837UNQLITE_APIEXPORT int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor);
838UNQLITE_APIEXPORT int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor);
839UNQLITE_APIEXPORT int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte);
840UNQLITE_APIEXPORT int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData);
841UNQLITE_APIEXPORT int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnData);
842UNQLITE_APIEXPORT int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData);
843UNQLITE_APIEXPORT int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor);
844UNQLITE_APIEXPORT int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor);
845
846/* Manual Transaction Manager */
847UNQLITE_APIEXPORT int unqlite_begin(unqlite *pDb);
848UNQLITE_APIEXPORT int unqlite_commit(unqlite *pDb);
849UNQLITE_APIEXPORT int unqlite_rollback(unqlite *pDb);
850
851/* Utility interfaces */
852UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize);
853UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize);
854UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size);
855UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb);
856
857/* In-process extending interfaces */
858UNQLITE_APIEXPORT int unqlite_create_function(unqlite_vm *pVm,const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData);
859UNQLITE_APIEXPORT int unqlite_delete_function(unqlite_vm *pVm, const char *zName);
860UNQLITE_APIEXPORT int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData);
861UNQLITE_APIEXPORT int unqlite_delete_constant(unqlite_vm *pVm, const char *zName);
862
863/* On Demand Object allocation interfaces */
864UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm);
865UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm);
866UNQLITE_APIEXPORT int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue);
867UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx);
868UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_array(unqlite_context *pCtx);
869UNQLITE_APIEXPORT void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue);
870
871/* Dynamically Typed Value Object Management Interfaces */
872UNQLITE_APIEXPORT int unqlite_value_int(unqlite_value *pVal, int iValue);
873UNQLITE_APIEXPORT int unqlite_value_int64(unqlite_value *pVal, unqlite_int64 iValue);
874UNQLITE_APIEXPORT int unqlite_value_bool(unqlite_value *pVal, int iBool);
875UNQLITE_APIEXPORT int unqlite_value_null(unqlite_value *pVal);
876UNQLITE_APIEXPORT int unqlite_value_double(unqlite_value *pVal, double Value);
877UNQLITE_APIEXPORT int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen);
878UNQLITE_APIEXPORT int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...);
879UNQLITE_APIEXPORT int unqlite_value_reset_string_cursor(unqlite_value *pVal);
880UNQLITE_APIEXPORT int unqlite_value_resource(unqlite_value *pVal, void *pUserData);
881UNQLITE_APIEXPORT int unqlite_value_release(unqlite_value *pVal);
882
883/* Foreign Function Parameter Values */
884UNQLITE_APIEXPORT int unqlite_value_to_int(unqlite_value *pValue);
885UNQLITE_APIEXPORT int unqlite_value_to_bool(unqlite_value *pValue);
886UNQLITE_APIEXPORT unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue);
887UNQLITE_APIEXPORT double unqlite_value_to_double(unqlite_value *pValue);
888UNQLITE_APIEXPORT const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen);
889UNQLITE_APIEXPORT void * unqlite_value_to_resource(unqlite_value *pValue);
890UNQLITE_APIEXPORT int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict);
891
892/* Setting The Result Of A Foreign Function */
893UNQLITE_APIEXPORT int unqlite_result_int(unqlite_context *pCtx, int iValue);
894UNQLITE_APIEXPORT int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue);
895UNQLITE_APIEXPORT int unqlite_result_bool(unqlite_context *pCtx, int iBool);
896UNQLITE_APIEXPORT int unqlite_result_double(unqlite_context *pCtx, double Value);
897UNQLITE_APIEXPORT int unqlite_result_null(unqlite_context *pCtx);
898UNQLITE_APIEXPORT int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen);
899UNQLITE_APIEXPORT int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...);
900UNQLITE_APIEXPORT int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue);
901UNQLITE_APIEXPORT int unqlite_result_resource(unqlite_context *pCtx, void *pUserData);
902
903/* Dynamically Typed Value Object Query Interfaces */
904UNQLITE_APIEXPORT int unqlite_value_is_int(unqlite_value *pVal);
905UNQLITE_APIEXPORT int unqlite_value_is_float(unqlite_value *pVal);
906UNQLITE_APIEXPORT int unqlite_value_is_bool(unqlite_value *pVal);
907UNQLITE_APIEXPORT int unqlite_value_is_string(unqlite_value *pVal);
908UNQLITE_APIEXPORT int unqlite_value_is_null(unqlite_value *pVal);
909UNQLITE_APIEXPORT int unqlite_value_is_numeric(unqlite_value *pVal);
910UNQLITE_APIEXPORT int unqlite_value_is_callable(unqlite_value *pVal);
911UNQLITE_APIEXPORT int unqlite_value_is_scalar(unqlite_value *pVal);
912UNQLITE_APIEXPORT int unqlite_value_is_json_array(unqlite_value *pVal);
913UNQLITE_APIEXPORT int unqlite_value_is_json_object(unqlite_value *pVal);
914UNQLITE_APIEXPORT int unqlite_value_is_resource(unqlite_value *pVal);
915UNQLITE_APIEXPORT int unqlite_value_is_empty(unqlite_value *pVal);
916
917/* JSON Array/Object Management Interfaces */
918UNQLITE_APIEXPORT unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte);
919UNQLITE_APIEXPORT int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData);
920UNQLITE_APIEXPORT int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue);
921UNQLITE_APIEXPORT int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue);
922UNQLITE_APIEXPORT int unqlite_array_count(unqlite_value *pArray);
923
924/* Call Context Handling Interfaces */
925UNQLITE_APIEXPORT int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen);
926UNQLITE_APIEXPORT int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...);
927UNQLITE_APIEXPORT int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr);
928UNQLITE_APIEXPORT int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...);
929UNQLITE_APIEXPORT unsigned int unqlite_context_random_num(unqlite_context *pCtx);
930UNQLITE_APIEXPORT int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen);
931UNQLITE_APIEXPORT void * unqlite_context_user_data(unqlite_context *pCtx);
932UNQLITE_APIEXPORT int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData);
933UNQLITE_APIEXPORT void * unqlite_context_peek_aux_data(unqlite_context *pCtx);
934UNQLITE_APIEXPORT unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx);
935UNQLITE_APIEXPORT const char * unqlite_function_name(unqlite_context *pCtx);
936
937/* Call Context Memory Management Interfaces */
938UNQLITE_APIEXPORT void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease);
939UNQLITE_APIEXPORT void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte);
940UNQLITE_APIEXPORT void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk);
941
942/* Global Library Management Interfaces */
943UNQLITE_APIEXPORT int unqlite_lib_config(int nConfigOp,...);
944UNQLITE_APIEXPORT int unqlite_lib_init(void);
945UNQLITE_APIEXPORT int unqlite_lib_shutdown(void);
946UNQLITE_APIEXPORT int unqlite_lib_is_threadsafe(void);
947UNQLITE_APIEXPORT const char * unqlite_lib_version(void);
948UNQLITE_APIEXPORT const char * unqlite_lib_signature(void);
949UNQLITE_APIEXPORT const char * unqlite_lib_ident(void);
950UNQLITE_APIEXPORT const char * unqlite_lib_copyright(void);
951#ifdef __cplusplus
952}
953#endif /* __cplusplus */
954#endif /* _UNQLITE_H_ */
diff --git a/common/unqlite/unqliteInt.h b/common/unqlite/unqliteInt.h
new file mode 100644
index 0000000..4c93a68
--- /dev/null
+++ b/common/unqlite/unqliteInt.h
@@ -0,0 +1,319 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: unqliteInt.h v1.7 FreeBSD 2012-11-02 11:25 devel <chm@symisc.net> $ */
14#ifndef __UNQLITEINT_H__
15#define __UNQLITEINT_H__
16/* Internal interface definitions for UnQLite. */
17#ifdef UNQLITE_AMALGAMATION
18/* Marker for routines not intended for external use */
19#define UNQLITE_PRIVATE static
20#define JX9_AMALGAMATION
21#else
22#define UNQLITE_PRIVATE
23#include "unqlite.h"
24#include "jx9Int.h"
25#endif
26/* forward declaration */
27typedef struct unqlite_db unqlite_db;
28/*
29** The following values may be passed as the second argument to
30** UnqliteOsLock(). The various locks exhibit the following semantics:
31**
32** SHARED: Any number of processes may hold a SHARED lock simultaneously.
33** RESERVED: A single process may hold a RESERVED lock on a file at
34** any time. Other processes may hold and obtain new SHARED locks.
35** PENDING: A single process may hold a PENDING lock on a file at
36** any one time. Existing SHARED locks may persist, but no new
37** SHARED locks may be obtained by other processes.
38** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks.
39**
40** PENDING_LOCK may not be passed directly to UnqliteOsLock(). Instead, a
41** process that requests an EXCLUSIVE lock may actually obtain a PENDING
42** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to
43** UnqliteOsLock().
44*/
45#define NO_LOCK 0
46#define SHARED_LOCK 1
47#define RESERVED_LOCK 2
48#define PENDING_LOCK 3
49#define EXCLUSIVE_LOCK 4
50/*
51 * UnQLite Locking Strategy (Same as SQLite3)
52 *
53 * The following #defines specify the range of bytes used for locking.
54 * SHARED_SIZE is the number of bytes available in the pool from which
55 * a random byte is selected for a shared lock. The pool of bytes for
56 * shared locks begins at SHARED_FIRST.
57 *
58 * The same locking strategy and byte ranges are used for Unix and Windows.
59 * This leaves open the possiblity of having clients on winNT, and
60 * unix all talking to the same shared file and all locking correctly.
61 * To do so would require that samba (or whatever
62 * tool is being used for file sharing) implements locks correctly between
63 * windows and unix. I'm guessing that isn't likely to happen, but by
64 * using the same locking range we are at least open to the possibility.
65 *
66 * Locking in windows is mandatory. For this reason, we cannot store
67 * actual data in the bytes used for locking. The pager never allocates
68 * the pages involved in locking therefore. SHARED_SIZE is selected so
69 * that all locks will fit on a single page even at the minimum page size.
70 * PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE
71 * is set high so that we don't have to allocate an unused page except
72 * for very large databases. But one should test the page skipping logic
73 * by setting PENDING_BYTE low and running the entire regression suite.
74 *
75 * Changing the value of PENDING_BYTE results in a subtly incompatible
76 * file format. Depending on how it is changed, you might not notice
77 * the incompatibility right away, even running a full regression test.
78 * The default location of PENDING_BYTE is the first byte past the
79 * 1GB boundary.
80 */
81#define PENDING_BYTE (0x40000000)
82#define RESERVED_BYTE (PENDING_BYTE+1)
83#define SHARED_FIRST (PENDING_BYTE+2)
84#define SHARED_SIZE 510
85/*
86 * The default size of a disk sector in bytes.
87 */
88#ifndef UNQLITE_DEFAULT_SECTOR_SIZE
89#define UNQLITE_DEFAULT_SECTOR_SIZE 512
90#endif
91/*
92 * Each open database file is managed by a separate instance
93 * of the "Pager" structure.
94 */
95typedef struct Pager Pager;
96/*
97 * Each database file to be accessed by the system is an instance
98 * of the following structure.
99 */
100struct unqlite_db
101{
102 Pager *pPager; /* Pager and Transaction manager */
103 jx9 *pJx9; /* Jx9 Engine handle */
104 unqlite_kv_cursor *pCursor; /* Database cursor for common usage */
105};
106/*
107 * Each database connection is an instance of the following structure.
108 */
109struct unqlite
110{
111 SyMemBackend sMem; /* Memory allocator subsystem */
112 SyBlob sErr; /* Error log */
113 unqlite_db sDB; /* Storage backend */
114#if defined(UNQLITE_ENABLE_THREADS)
115 const SyMutexMethods *pMethods; /* Mutex methods */
116 SyMutex *pMutex; /* Per-handle mutex */
117#endif
118 unqlite_vm *pVms; /* List of active VM */
119 sxi32 iVm; /* Total number of active VM */
120 sxi32 iFlags; /* Control flags (See below) */
121 unqlite *pNext,*pPrev; /* List of active DB handles */
122 sxu32 nMagic; /* Sanity check against misuse */
123};
124#define UNQLITE_FL_DISABLE_AUTO_COMMIT 0x001 /* Disable auto-commit on close */
125/*
126 * VM control flags (Mostly related to collection handling).
127 */
128#define UNQLITE_VM_COLLECTION_CREATE 0x001 /* Create a new collection */
129#define UNQLITE_VM_COLLECTION_OVERWRITE 0x002 /* Overwrite old collection */
130#define UNQLITE_VM_AUTO_LOAD 0x004 /* Auto load a collection from the vfs */
131/* Forward declaration */
132typedef struct unqlite_col_record unqlite_col_record;
133typedef struct unqlite_col unqlite_col;
134/*
135 * Each an in-memory collection record is stored in an instance
136 * of the following structure.
137 */
138struct unqlite_col_record
139{
140 unqlite_col *pCol; /* Collecion this record belong */
141 jx9_int64 nId; /* Unique record ID */
142 jx9_value sValue; /* In-memory value of the record */
143 unqlite_col_record *pNextCol,*pPrevCol; /* Collision chain */
144 unqlite_col_record *pNext,*pPrev; /* Linked list of records */
145};
146/*
147 * Magic number to identify a valid collection on disk.
148 */
149#define UNQLITE_COLLECTION_MAGIC 0x611E /* sizeof(unsigned short) 2 bytes */
150/*
151 * A loaded collection is identified by an instance of the following structure.
152 */
153struct unqlite_col
154{
155 unqlite_vm *pVm; /* VM that own this instance */
156 SyString sName; /* ID of the collection */
157 sxu32 nHash; /* sName hash */
158 jx9_value sSchema; /* Collection schema */
159 sxu32 nSchemaOfft; /* Shema offset in sHeader */
160 SyBlob sWorker; /* General purpose working buffer */
161 SyBlob sHeader; /* Collection binary header */
162 jx9_int64 nLastid; /* Last collection record ID */
163 jx9_int64 nCurid; /* Current record ID */
164 jx9_int64 nTotRec; /* Total number of records in the collection */
165 int iFlags; /* Control flags (see below) */
166 unqlite_col_record **apRecord; /* Hashtable of loaded records */
167 unqlite_col_record *pList; /* Linked list of records */
168 sxu32 nRec; /* Total number of records in apRecord[] */
169 sxu32 nRecSize; /* apRecord[] size */
170 Sytm sCreation; /* Colleation creation time */
171 unqlite_kv_cursor *pCursor; /* Cursor pointing to the raw binary data */
172 unqlite_col *pNext,*pPrev; /* Next and previous collection in the chain */
173 unqlite_col *pNextCol,*pPrevCol; /* Collision chain */
174};
175/*
176 * Each unQLite Virtual Machine resulting from successful compilation of
177 * a Jx9 script is represented by an instance of the following structure.
178 */
179struct unqlite_vm
180{
181 unqlite *pDb; /* Database handle that own this instance */
182 SyMemBackend sAlloc; /* Private memory allocator */
183#if defined(UNQLITE_ENABLE_THREADS)
184 SyMutex *pMutex; /* Recursive mutex associated with this VM. */
185#endif
186 unqlite_col **apCol; /* Table of loaded collections */
187 unqlite_col *pCol; /* List of loaded collections */
188 sxu32 iCol; /* Total number of loaded collections */
189 sxu32 iColSize; /* apCol[] size */
190 jx9_vm *pJx9Vm; /* Compiled Jx9 script*/
191 unqlite_vm *pNext,*pPrev; /* Linked list of active unQLite VM */
192 sxu32 nMagic; /* Magic number to avoid misuse */
193};
194/*
195 * Database signature to identify a valid database image.
196 */
197#define UNQLITE_DB_SIG "unqlite"
198/*
199 * Database magic number (4 bytes).
200 */
201#define UNQLITE_DB_MAGIC 0xDB7C2712
202/*
203 * Maximum page size in bytes.
204 */
205#ifdef UNQLITE_MAX_PAGE_SIZE
206# undef UNQLITE_MAX_PAGE_SIZE
207#endif
208#define UNQLITE_MAX_PAGE_SIZE 65536 /* 65K */
209/*
210 * Minimum page size in bytes.
211 */
212#ifdef UNQLITE_MIN_PAGE_SIZE
213# undef UNQLITE_MIN_PAGE_SIZE
214#endif
215#define UNQLITE_MIN_PAGE_SIZE 512
216/*
217 * The default size of a database page.
218 */
219#ifndef UNQLITE_DEFAULT_PAGE_SIZE
220# undef UNQLITE_DEFAULT_PAGE_SIZE
221#endif
222# define UNQLITE_DEFAULT_PAGE_SIZE 4096 /* 4K */
223/* Forward declaration */
224typedef struct Bitvec Bitvec;
225/* Private library functions */
226/* api.c */
227UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void);
228UNQLITE_PRIVATE int unqliteDataConsumer(
229 const void *pOut, /* Data to consume */
230 unsigned int nLen, /* Data length */
231 void *pUserData /* User private data */
232 );
233UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore(
234 const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */
235 sxu32 nByte /* zName length */
236 );
237UNQLITE_PRIVATE int unqliteGetPageSize(void);
238UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr);
239UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...);
240UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb);
241/* unql_vm.c */
242UNQLITE_PRIVATE int unqliteCreateCollection(unqlite_vm *pVm,SyString *pName);
243UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol);
244UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol);
245UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(unqlite_col *pCol,jx9_int64 nId);
246UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol);
247UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol);
248UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue);
249UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(unqlite_col *pCol,jx9_int64 nId,jx9_value *pValue);
250UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(unqlite_vm *pVm,SyString *pCol,int iFlag);
251UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue);
252UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag);
253UNQLITE_PRIVATE int unqliteCollectionDropRecord(unqlite_col *pCol,jx9_int64 nId,int wr_header,int log_err);
254UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol);
255/* unql_jx9.c */
256UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm);
257/* fastjson.c */
258UNQLITE_PRIVATE sxi32 FastJsonEncode(
259 jx9_value *pValue, /* Value to encode */
260 SyBlob *pOut, /* Store encoded value here */
261 int iNest /* Nesting limit */
262 );
263UNQLITE_PRIVATE sxi32 FastJsonDecode(
264 const void *pIn, /* Binary JSON */
265 sxu32 nByte, /* Chunk delimiter */
266 jx9_value *pOut, /* Decoded value */
267 const unsigned char **pzPtr,
268 int iNest /* Nesting limit */
269 );
270/* vfs.c [io_win.c, io_unix.c ] */
271UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void);
272/* mem_kv.c */
273UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void);
274/* lhash_kv.c */
275UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void);
276/* os.c */
277UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset);
278UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset);
279UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size);
280UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags);
281UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize);
282UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType);
283UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType);
284UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut);
285UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id);
286UNQLITE_PRIVATE int unqliteOsOpen(
287 unqlite_vfs *pVfs,
288 SyMemBackend *pAlloc,
289 const char *zPath,
290 unqlite_file **ppOut,
291 unsigned int flags
292);
293UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId);
294UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync);
295UNQLITE_PRIVATE int unqliteOsAccess(unqlite_vfs *pVfs,const char *zPath,int flags,int *pResOut);
296/* bitmap.c */
297UNQLITE_PRIVATE Bitvec *unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize);
298UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i);
299UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i);
300UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p);
301/* pager.c */
302UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut);
303UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur);
304UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage);
305UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager);
306UNQLITE_PRIVATE int unqlitePagerOpen(
307 unqlite_vfs *pVfs, /* The virtual file system to use */
308 unqlite *pDb, /* Database handle */
309 const char *zFilename, /* Name of the database file to open */
310 unsigned int iFlags /* flags controlling this file */
311 );
312UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods);
313UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb);
314UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager);
315UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager);
316UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine);
317UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen);
318UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager);
319#endif /* __UNQLITEINT_H__ */
diff --git a/common/unqlite/unqlite_jx9.c b/common/unqlite/unqlite_jx9.c
new file mode 100644
index 0000000..7362c58
--- /dev/null
+++ b/common/unqlite/unqlite_jx9.c
@@ -0,0 +1,977 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: unql_jx9.c v1.2 FreeBSD 2013-01-24 22:45 stable <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/*
18 * This file implements UnQLite functions (db_exists(), db_create(), db_put(), db_get(), etc.) for the
19 * underlying Jx9 Virtual Machine.
20 */
21/*
22 * string db_version(void)
23 * Return the current version of the unQLite database engine.
24 * Parameter
25 * None
26 * Return
27 * unQLite version number (string).
28 */
29static int unqliteBuiltin_db_version(jx9_context *pCtx,int argc,jx9_value **argv)
30{
31 SXUNUSED(argc); /* cc warning */
32 SXUNUSED(argv);
33 jx9_result_string(pCtx,UNQLITE_VERSION,(int)sizeof(UNQLITE_VERSION)-1);
34 return JX9_OK;
35}
36/*
37 * string db_errlog(void)
38 * Return the database error log.
39 * Parameter
40 * None
41 * Return
42 * Database error log (string).
43 */
44static int unqliteBuiltin_db_errlog(jx9_context *pCtx,int argc,jx9_value **argv)
45{
46 unqlite_vm *pVm;
47 SyBlob *pErr;
48
49 SXUNUSED(argc); /* cc warning */
50 SXUNUSED(argv);
51
52 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
53 /* Point to the error log */
54 pErr = &pVm->pDb->sErr;
55 /* Return the log */
56 jx9_result_string(pCtx,(const char *)SyBlobData(pErr),(int)SyBlobLength(pErr));
57 return JX9_OK;
58}
59/*
60 * string db_copyright(void)
61 * string db_credits(void)
62 * Return the unQLite database engine copyright notice.
63 * Parameter
64 * None
65 * Return
66 * Copyright notice.
67 */
68static int unqliteBuiltin_db_credits(jx9_context *pCtx,int argc,jx9_value **argv)
69{
70 SXUNUSED(argc); /* cc warning */
71 SXUNUSED(argv);
72 jx9_result_string(pCtx,UNQLITE_COPYRIGHT,(int)sizeof(UNQLITE_COPYRIGHT)-1);
73 return JX9_OK;
74}
75/*
76 * string db_sig(void)
77 * Return the unQLite database engine unique signature.
78 * Parameter
79 * None
80 * Return
81 * unQLite signature.
82 */
83static int unqliteBuiltin_db_sig(jx9_context *pCtx,int argc,jx9_value **argv)
84{
85 SXUNUSED(argc); /* cc warning */
86 SXUNUSED(argv);
87 jx9_result_string(pCtx,UNQLITE_IDENT,sizeof(UNQLITE_IDENT)-1);
88 return JX9_OK;
89}
90/*
91 * bool collection_exists(string $name)
92 * bool db_exits(string $name)
93 * Check if a given collection exists in the underlying database.
94 * Parameter
95 * name: Lookup name
96 * Return
97 * TRUE if the collection exits. FALSE otherwise.
98 */
99static int unqliteBuiltin_collection_exists(jx9_context *pCtx,int argc,jx9_value **argv)
100{
101 unqlite_col *pCol;
102 const char *zName;
103 unqlite_vm *pVm;
104 SyString sName;
105 int nByte;
106 /* Extract collection name */
107 if( argc < 1 ){
108 /* Missing arguments */
109 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
110 /* Return false */
111 jx9_result_bool(pCtx,0);
112 return JX9_OK;
113 }
114 zName = jx9_value_to_string(argv[0],&nByte);
115 if( nByte < 1){
116 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
117 /* Return false */
118 jx9_result_bool(pCtx,0);
119 return JX9_OK;
120 }
121 SyStringInitFromBuf(&sName,zName,nByte);
122 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
123 /* Perform the lookup */
124 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
125 /* Lookup result */
126 jx9_result_bool(pCtx,pCol ? 1 : 0);
127 return JX9_OK;
128}
129/*
130 * bool collection_create(string $name)
131 * bool db_create(string $name)
132 * Create a new collection.
133 * Parameter
134 * name: Collection name
135 * Return
136 * TRUE if the collection was successfuly created. FALSE otherwise.
137 */
138static int unqliteBuiltin_collection_create(jx9_context *pCtx,int argc,jx9_value **argv)
139{
140 const char *zName;
141 unqlite_vm *pVm;
142 SyString sName;
143 int nByte;
144 int rc;
145 /* Extract collection name */
146 if( argc < 1 ){
147 /* Missing arguments */
148 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
149 /* Return false */
150 jx9_result_bool(pCtx,0);
151 return JX9_OK;
152 }
153 zName = jx9_value_to_string(argv[0],&nByte);
154 if( nByte < 1){
155 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
156 /* Return false */
157 jx9_result_bool(pCtx,0);
158 return JX9_OK;
159 }
160 SyStringInitFromBuf(&sName,zName,nByte);
161 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
162 /* Try to create the collection */
163 rc = unqliteCreateCollection(pVm,&sName);
164 /* Return the result to the caller */
165 jx9_result_bool(pCtx,rc == UNQLITE_OK ? 1 : 0);
166 return JX9_OK;
167}
168/*
169 * value db_fetch(string $col_name)
170 * value db_get(string $col_name)
171 * Fetch the current record from a given collection and advance
172 * the record cursor.
173 * Parameter
174 * col_name: Collection name
175 * Return
176 * Record content success. NULL on failure (No more records to retrieve).
177 */
178static int unqliteBuiltin_db_fetch_next(jx9_context *pCtx,int argc,jx9_value **argv)
179{
180 unqlite_col *pCol;
181 const char *zName;
182 unqlite_vm *pVm;
183 SyString sName;
184 int nByte;
185 int rc;
186 /* Extract collection name */
187 if( argc < 1 ){
188 /* Missing arguments */
189 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
190 /* Return null */
191 jx9_result_null(pCtx);
192 return JX9_OK;
193 }
194 zName = jx9_value_to_string(argv[0],&nByte);
195 if( nByte < 1){
196 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
197 /* Return null */
198 jx9_result_null(pCtx);
199 return JX9_OK;
200 }
201 SyStringInitFromBuf(&sName,zName,nByte);
202 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
203 /* Fetch the collection */
204 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
205 if( pCol ){
206 /* Fetch the current record */
207 jx9_value *pValue;
208 pValue = jx9_context_new_scalar(pCtx);
209 if( pValue == 0 ){
210 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory");
211 jx9_result_null(pCtx);
212 return JX9_OK;
213 }else{
214 rc = unqliteCollectionFetchNextRecord(pCol,pValue);
215 if( rc == UNQLITE_OK ){
216 jx9_result_value(pCtx,pValue);
217 /* pValue will be automatically released as soon we return from this function */
218 }else{
219 /* Return null */
220 jx9_result_null(pCtx);
221 }
222 }
223 }else{
224 /* No such collection, return null */
225 jx9_result_null(pCtx);
226 }
227 return JX9_OK;
228}
229/*
230 * value db_fetch_by_id(string $col_name,int64 $record_id)
231 * value db_get_by_id(string $col_name,int64 $record_id)
232 * Fetch a record using its unique ID from a given collection.
233 * Parameter
234 * col_name: Collection name
235 * record_id: Record number (__id field of a JSON object)
236 * Return
237 * Record content success. NULL on failure (No such record).
238 */
239static int unqliteBuiltin_db_fetch_by_id(jx9_context *pCtx,int argc,jx9_value **argv)
240{
241 unqlite_col *pCol;
242 const char *zName;
243 unqlite_vm *pVm;
244 SyString sName;
245 jx9_int64 nId;
246 int nByte;
247 int rc;
248 /* Extract collection name */
249 if( argc < 2 ){
250 /* Missing arguments */
251 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or record ID");
252 /* Return NULL */
253 jx9_result_null(pCtx);
254 return JX9_OK;
255 }
256 zName = jx9_value_to_string(argv[0],&nByte);
257 if( nByte < 1){
258 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
259 /* Return NULL */
260 jx9_result_null(pCtx);
261 return JX9_OK;
262 }
263 /* Extract the record ID */
264 nId = jx9_value_to_int(argv[1]);
265 SyStringInitFromBuf(&sName,zName,nByte);
266 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
267 /* Fetch the collection */
268 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
269 if( pCol ){
270 /* Fetch the desired record */
271 jx9_value *pValue;
272 pValue = jx9_context_new_scalar(pCtx);
273 if( pValue == 0 ){
274 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory");
275 jx9_result_null(pCtx);
276 return JX9_OK;
277 }else{
278 rc = unqliteCollectionFetchRecordById(pCol,nId,pValue);
279 if( rc == UNQLITE_OK ){
280 jx9_result_value(pCtx,pValue);
281 /* pValue will be automatically released as soon we return from this function */
282 }else{
283 /* No such record, return null */
284 jx9_result_null(pCtx);
285 }
286 }
287 }else{
288 /* No such collection, return null */
289 jx9_result_null(pCtx);
290 }
291 return JX9_OK;
292}
293/*
294 * array db_fetch_all(string $col_name,[callback filter_callback])
295 * array db_get_all(string $col_name,[callback filter_callback])
296 * Retrieve all records of a given collection and apply the given
297 * callback if available to filter records.
298 * Parameter
299 * col_name: Collection name
300 * Return
301 * Contents of the collection (JSON array) on success. NULL on failure.
302 */
303static int unqliteBuiltin_db_fetch_all(jx9_context *pCtx,int argc,jx9_value **argv)
304{
305 unqlite_col *pCol;
306 const char *zName;
307 unqlite_vm *pVm;
308 SyString sName;
309 int nByte;
310 int rc;
311 /* Extract collection name */
312 if( argc < 1 ){
313 /* Missing arguments */
314 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
315 /* Return NULL */
316 jx9_result_null(pCtx);
317 return JX9_OK;
318 }
319 zName = jx9_value_to_string(argv[0],&nByte);
320 if( nByte < 1){
321 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
322 /* Return NULL */
323 jx9_result_null(pCtx);
324 return JX9_OK;
325 }
326 SyStringInitFromBuf(&sName,zName,nByte);
327 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
328 /* Fetch the collection */
329 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
330 if( pCol ){
331 jx9_value *pValue,*pArray,*pCallback = 0;
332 jx9_value sResult; /* Callback result */
333 /* Allocate an empty scalar value and an empty JSON array */
334 pArray = jx9_context_new_array(pCtx);
335 pValue = jx9_context_new_scalar(pCtx);
336 jx9MemObjInit(pCtx->pVm,&sResult);
337 if( pValue == 0 || pArray == 0 ){
338 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory");
339 jx9_result_null(pCtx);
340 return JX9_OK;
341 }
342 if( argc > 1 && jx9_value_is_callable(argv[1]) ){
343 pCallback = argv[1];
344 }
345 unqliteCollectionResetRecordCursor(pCol);
346 /* Fetch collection records one after one */
347 while( UNQLITE_OK == unqliteCollectionFetchNextRecord(pCol,pValue) ){
348 if( pCallback ){
349 jx9_value *apArg[2];
350 /* Invoke the filter callback */
351 apArg[0] = pValue;
352 rc = jx9VmCallUserFunction(pCtx->pVm,pCallback,1,apArg,&sResult);
353 if( rc == JX9_OK ){
354 int iResult; /* Callback result */
355 /* Extract callback result */
356 iResult = jx9_value_to_bool(&sResult);
357 if( !iResult ){
358 /* Discard the result */
359 unqliteCollectionCacheRemoveRecord(pCol,unqliteCollectionCurrentRecordId(pCol) - 1);
360 continue;
361 }
362 }
363 }
364 /* Put the value in the JSON array */
365 jx9_array_add_elem(pArray,0,pValue);
366 /* Release the value */
367 jx9_value_null(pValue);
368 }
369 jx9MemObjRelease(&sResult);
370 /* Finally, return our array */
371 jx9_result_value(pCtx,pArray);
372 /* pValue will be automatically released as soon we return from
373 * this foreign function.
374 */
375 }else{
376 /* No such collection, return null */
377 jx9_result_null(pCtx);
378 }
379 return JX9_OK;
380}
381/*
382 * int64 db_last_record_id(string $col_name)
383 * Return the ID of the last inserted record.
384 * Parameter
385 * col_name: Collection name
386 * Return
387 * Record ID (64-bit integer) on success. FALSE on failure.
388 */
389static int unqliteBuiltin_db_last_record_id(jx9_context *pCtx,int argc,jx9_value **argv)
390{
391 unqlite_col *pCol;
392 const char *zName;
393 unqlite_vm *pVm;
394 SyString sName;
395 int nByte;
396 /* Extract collection name */
397 if( argc < 1 ){
398 /* Missing arguments */
399 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
400 /* Return false */
401 jx9_result_bool(pCtx,0);
402 return JX9_OK;
403 }
404 zName = jx9_value_to_string(argv[0],&nByte);
405 if( nByte < 1){
406 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
407 /* Return false */
408 jx9_result_bool(pCtx,0);
409 return JX9_OK;
410 }
411 SyStringInitFromBuf(&sName,zName,nByte);
412 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
413 /* Fetch the collection */
414 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
415 if( pCol ){
416 jx9_result_int64(pCtx,unqliteCollectionLastRecordId(pCol));
417 }else{
418 /* No such collection, return FALSE */
419 jx9_result_bool(pCtx,0);
420 }
421 return JX9_OK;
422}
423/*
424 * inr64 db_current_record_id(string $col_name)
425 * Return the current record ID.
426 * Parameter
427 * col_name: Collection name
428 * Return
429 * Current record ID (64-bit integer) on success. FALSE on failure.
430 */
431static int unqliteBuiltin_db_current_record_id(jx9_context *pCtx,int argc,jx9_value **argv)
432{
433 unqlite_col *pCol;
434 const char *zName;
435 unqlite_vm *pVm;
436 SyString sName;
437 int nByte;
438 /* Extract collection name */
439 if( argc < 1 ){
440 /* Missing arguments */
441 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
442 /* Return false */
443 jx9_result_bool(pCtx,0);
444 return JX9_OK;
445 }
446 zName = jx9_value_to_string(argv[0],&nByte);
447 if( nByte < 1){
448 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
449 /* Return false */
450 jx9_result_bool(pCtx,0);
451 return JX9_OK;
452 }
453 SyStringInitFromBuf(&sName,zName,nByte);
454 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
455 /* Fetch the collection */
456 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
457 if( pCol ){
458 jx9_result_int64(pCtx,unqliteCollectionCurrentRecordId(pCol));
459 }else{
460 /* No such collection, return FALSE */
461 jx9_result_bool(pCtx,0);
462 }
463 return JX9_OK;
464}
465/*
466 * bool db_reset_record_cursor(string $col_name)
467 * Reset the record ID cursor.
468 * Parameter
469 * col_name: Collection name
470 * Return
471 * TRUE on success. FALSE on failure.
472 */
473static int unqliteBuiltin_db_reset_record_cursor(jx9_context *pCtx,int argc,jx9_value **argv)
474{
475 unqlite_col *pCol;
476 const char *zName;
477 unqlite_vm *pVm;
478 SyString sName;
479 int nByte;
480 /* Extract collection name */
481 if( argc < 1 ){
482 /* Missing arguments */
483 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
484 /* Return false */
485 jx9_result_bool(pCtx,0);
486 return JX9_OK;
487 }
488 zName = jx9_value_to_string(argv[0],&nByte);
489 if( nByte < 1){
490 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
491 /* Return false */
492 jx9_result_bool(pCtx,0);
493 return JX9_OK;
494 }
495 SyStringInitFromBuf(&sName,zName,nByte);
496 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
497 /* Fetch the collection */
498 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
499 if( pCol ){
500 unqliteCollectionResetRecordCursor(pCol);
501 jx9_result_bool(pCtx,1);
502 }else{
503 /* No such collection */
504 jx9_result_bool(pCtx,0);
505 }
506 return JX9_OK;
507}
508/*
509 * int64 db_total_records(string $col_name)
510 * Return the total number of inserted records in the given collection.
511 * Parameter
512 * col_name: Collection name
513 * Return
514 * Total number of records on success. FALSE on failure.
515 */
516static int unqliteBuiltin_db_total_records(jx9_context *pCtx,int argc,jx9_value **argv)
517{
518 unqlite_col *pCol;
519 const char *zName;
520 unqlite_vm *pVm;
521 SyString sName;
522 int nByte;
523 /* Extract collection name */
524 if( argc < 1 ){
525 /* Missing arguments */
526 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
527 /* Return false */
528 jx9_result_bool(pCtx,0);
529 return JX9_OK;
530 }
531 zName = jx9_value_to_string(argv[0],&nByte);
532 if( nByte < 1){
533 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
534 /* Return false */
535 jx9_result_bool(pCtx,0);
536 return JX9_OK;
537 }
538 SyStringInitFromBuf(&sName,zName,nByte);
539 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
540 /* Fetch the collection */
541 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
542 if( pCol ){
543 unqlite_int64 nRec;
544 nRec = unqliteCollectionTotalRecords(pCol);
545 jx9_result_int64(pCtx,nRec);
546 }else{
547 /* No such collection */
548 jx9_result_bool(pCtx,0);
549 }
550 return JX9_OK;
551}
552/*
553 * string db_creation_date(string $col_name)
554 * Return the creation date of the given collection.
555 * Parameter
556 * col_name: Collection name
557 * Return
558 * Creation date on success. FALSE on failure.
559 */
560static int unqliteBuiltin_db_creation_date(jx9_context *pCtx,int argc,jx9_value **argv)
561{
562 unqlite_col *pCol;
563 const char *zName;
564 unqlite_vm *pVm;
565 SyString sName;
566 int nByte;
567 /* Extract collection name */
568 if( argc < 1 ){
569 /* Missing arguments */
570 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
571 /* Return false */
572 jx9_result_bool(pCtx,0);
573 return JX9_OK;
574 }
575 zName = jx9_value_to_string(argv[0],&nByte);
576 if( nByte < 1){
577 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
578 /* Return false */
579 jx9_result_bool(pCtx,0);
580 return JX9_OK;
581 }
582 SyStringInitFromBuf(&sName,zName,nByte);
583 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
584 /* Fetch the collection */
585 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
586 if( pCol ){
587 Sytm *pTm = &pCol->sCreation;
588 jx9_result_string_format(pCtx,"%d-%d-%d %02d:%02d:%02d",
589 pTm->tm_year,pTm->tm_mon,pTm->tm_mday,
590 pTm->tm_hour,pTm->tm_min,pTm->tm_sec
591 );
592 }else{
593 /* No such collection */
594 jx9_result_bool(pCtx,0);
595 }
596 return JX9_OK;
597}
598/*
599 * bool db_store(string $col_name,...)
600 * bool db_put(string $col_name,...)
601 * Store one or more JSON values in a given collection.
602 * Parameter
603 * col_name: Collection name
604 * Return
605 * TRUE on success. FALSE on failure.
606 */
607static int unqliteBuiltin_db_store(jx9_context *pCtx,int argc,jx9_value **argv)
608{
609 unqlite_col *pCol;
610 const char *zName;
611 unqlite_vm *pVm;
612 SyString sName;
613 int nByte;
614 int rc;
615 int i;
616 /* Extract collection name */
617 if( argc < 2 ){
618 /* Missing arguments */
619 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records");
620 /* Return false */
621 jx9_result_bool(pCtx,0);
622 return JX9_OK;
623 }
624 zName = jx9_value_to_string(argv[0],&nByte);
625 if( nByte < 1){
626 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
627 /* Return false */
628 jx9_result_bool(pCtx,0);
629 return JX9_OK;
630 }
631 SyStringInitFromBuf(&sName,zName,nByte);
632 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
633 /* Fetch the collection */
634 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
635 if( pCol == 0 ){
636 jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName);
637 /* Return false */
638 jx9_result_bool(pCtx,0);
639 return JX9_OK;
640 }
641 /* Store the given values */
642 for( i = 1 ; i < argc ; ++i ){
643 rc = unqliteCollectionPut(pCol,argv[i],0);
644 if( rc != UNQLITE_OK){
645 jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,
646 "Error while storing record %d in collection '%z'",i,&sName
647 );
648 /* Return false */
649 jx9_result_bool(pCtx,0);
650 return JX9_OK;
651 }
652 }
653 /* All done, return TRUE */
654 jx9_result_bool(pCtx,1);
655 return JX9_OK;
656}
657/*
658 * bool db_drop_collection(string $col_name)
659 * bool collection_delete(string $col_name)
660 * Remove a given collection from the database.
661 * Parameter
662 * col_name: Collection name
663 * Return
664 * TRUE on success. FALSE on failure.
665 */
666static int unqliteBuiltin_db_drop_col(jx9_context *pCtx,int argc,jx9_value **argv)
667{
668 unqlite_col *pCol;
669 const char *zName;
670 unqlite_vm *pVm;
671 SyString sName;
672 int nByte;
673 int rc;
674 /* Extract collection name */
675 if( argc < 1 ){
676 /* Missing arguments */
677 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name");
678 /* Return false */
679 jx9_result_bool(pCtx,0);
680 return JX9_OK;
681 }
682 zName = jx9_value_to_string(argv[0],&nByte);
683 if( nByte < 1){
684 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
685 /* Return false */
686 jx9_result_bool(pCtx,0);
687 return JX9_OK;
688 }
689 SyStringInitFromBuf(&sName,zName,nByte);
690 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
691 /* Fetch the collection */
692 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
693 if( pCol == 0 ){
694 jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName);
695 /* Return false */
696 jx9_result_bool(pCtx,0);
697 return JX9_OK;
698 }
699 /* Drop the collection */
700 rc = unqliteDropCollection(pCol);
701 /* Processing result */
702 jx9_result_bool(pCtx,rc == UNQLITE_OK);
703 return JX9_OK;
704}
705/*
706 * bool db_drop_record(string $col_name,int64 record_id)
707 * Remove a given record from a collection.
708 * Parameter
709 * col_name: Collection name.
710 * record_id: ID of the record.
711 * Return
712 * TRUE on success. FALSE on failure.
713 */
714static int unqliteBuiltin_db_drop_record(jx9_context *pCtx,int argc,jx9_value **argv)
715{
716 unqlite_col *pCol;
717 const char *zName;
718 unqlite_vm *pVm;
719 SyString sName;
720 jx9_int64 nId;
721 int nByte;
722 int rc;
723 /* Extract collection name */
724 if( argc < 2 ){
725 /* Missing arguments */
726 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records");
727 /* Return false */
728 jx9_result_bool(pCtx,0);
729 return JX9_OK;
730 }
731 zName = jx9_value_to_string(argv[0],&nByte);
732 if( nByte < 1){
733 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
734 /* Return false */
735 jx9_result_bool(pCtx,0);
736 return JX9_OK;
737 }
738 SyStringInitFromBuf(&sName,zName,nByte);
739 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
740 /* Fetch the collection */
741 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
742 if( pCol == 0 ){
743 jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName);
744 /* Return false */
745 jx9_result_bool(pCtx,0);
746 return JX9_OK;
747 }
748 /* Extract the record ID */
749 nId = jx9_value_to_int64(argv[1]);
750 /* Drop the record */
751 rc = unqliteCollectionDropRecord(pCol,nId,1,1);
752 /* Processing result */
753 jx9_result_bool(pCtx,rc == UNQLITE_OK);
754 return JX9_OK;
755}
756/*
757 * bool db_set_schema(string $col_name, object $json_object)
758 * Set a schema for a given collection.
759 * Parameter
760 * col_name: Collection name.
761 * json_object: Collection schema (Must be a JSON object).
762 * Return
763 * TRUE on success. FALSE on failure.
764 */
765static int unqliteBuiltin_db_set_schema(jx9_context *pCtx,int argc,jx9_value **argv)
766{
767 unqlite_col *pCol;
768 const char *zName;
769 unqlite_vm *pVm;
770 SyString sName;
771 int nByte;
772 int rc;
773 /* Extract collection name */
774 if( argc < 2 ){
775 /* Missing arguments */
776 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme");
777 /* Return false */
778 jx9_result_bool(pCtx,0);
779 return JX9_OK;
780 }
781 if( !jx9_value_is_json_object(argv[1]) ){
782 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection scheme");
783 /* Return false */
784 jx9_result_bool(pCtx,0);
785 return JX9_OK;
786 }
787 zName = jx9_value_to_string(argv[0],&nByte);
788 if( nByte < 1){
789 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
790 /* Return false */
791 jx9_result_bool(pCtx,0);
792 return JX9_OK;
793 }
794 SyStringInitFromBuf(&sName,zName,nByte);
795 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
796 /* Fetch the collection */
797 rc = UNQLITE_NOOP;
798 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
799 if( pCol ){
800 /* Set the collection scheme */
801 rc = unqliteCollectionSetSchema(pCol,argv[1]);
802 }else{
803 jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING,
804 "No such collection '%z'",
805 &sName
806 );
807 }
808 /* Processing result */
809 jx9_result_bool(pCtx,rc == UNQLITE_OK);
810 return JX9_OK;
811}
812/*
813 * object db_get_schema(string $col_name)
814 * Return the schema associated with a given collection.
815 * Parameter
816 * col_name: Collection name
817 * Return
818 * Collection schema on success. null otherwise.
819 */
820static int unqliteBuiltin_db_get_schema(jx9_context *pCtx,int argc,jx9_value **argv)
821{
822 unqlite_col *pCol;
823 const char *zName;
824 unqlite_vm *pVm;
825 SyString sName;
826 int nByte;
827 /* Extract collection name */
828 if( argc < 1 ){
829 /* Missing arguments */
830 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme");
831 /* Return false */
832 jx9_result_bool(pCtx,0);
833 return JX9_OK;
834 }
835 zName = jx9_value_to_string(argv[0],&nByte);
836 if( nByte < 1){
837 jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name");
838 /* Return false */
839 jx9_result_bool(pCtx,0);
840 return JX9_OK;
841 }
842 SyStringInitFromBuf(&sName,zName,nByte);
843 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
844 /* Fetch the collection */
845 pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD);
846 if( pCol ){
847 /* Return the collection schema */
848 jx9_result_value(pCtx,&pCol->sSchema);
849 }else{
850 jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING,
851 "No such collection '%z'",
852 &sName
853 );
854 jx9_result_null(pCtx);
855 }
856 return JX9_OK;
857}
858/*
859 * bool db_begin(void)
860 * Manually begin a write transaction.
861 * Parameter
862 * None
863 * Return
864 * TRUE on success. FALSE otherwise.
865 */
866static int unqliteBuiltin_db_begin(jx9_context *pCtx,int argc,jx9_value **argv)
867{
868 unqlite_vm *pVm;
869 unqlite *pDb;
870 int rc;
871 SXUNUSED(argc); /* cc warning */
872 SXUNUSED(argv);
873 /* Point to the unqlite Vm */
874 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
875 /* Point to the underlying database handle */
876 pDb = pVm->pDb;
877 /* Begin the transaction */
878 rc = unqlitePagerBegin(pDb->sDB.pPager);
879 /* result */
880 jx9_result_bool(pCtx,rc == UNQLITE_OK );
881 return JX9_OK;
882}
883/*
884 * bool db_commit(void)
885 * Manually commit a transaction.
886 * Parameter
887 * None
888 * Return
889 * TRUE if the transaction was successfuly commited. FALSE otherwise.
890 */
891static int unqliteBuiltin_db_commit(jx9_context *pCtx,int argc,jx9_value **argv)
892{
893 unqlite_vm *pVm;
894 unqlite *pDb;
895 int rc;
896 SXUNUSED(argc); /* cc warning */
897 SXUNUSED(argv);
898 /* Point to the unqlite Vm */
899 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
900 /* Point to the underlying database handle */
901 pDb = pVm->pDb;
902 /* Commit the transaction if any */
903 rc = unqlitePagerCommit(pDb->sDB.pPager);
904 /* Commit result */
905 jx9_result_bool(pCtx,rc == UNQLITE_OK );
906 return JX9_OK;
907}
908/*
909 * bool db_rollback(void)
910 * Manually rollback a transaction.
911 * Parameter
912 * None
913 * Return
914 * TRUE if the transaction was successfuly rolled back. FALSE otherwise
915 */
916static int unqliteBuiltin_db_rollback(jx9_context *pCtx,int argc,jx9_value **argv)
917{
918 unqlite_vm *pVm;
919 unqlite *pDb;
920 int rc;
921 SXUNUSED(argc); /* cc warning */
922 SXUNUSED(argv);
923 /* Point to the unqlite Vm */
924 pVm = (unqlite_vm *)jx9_context_user_data(pCtx);
925 /* Point to the underlying database handle */
926 pDb = pVm->pDb;
927 /* Rollback the transaction if any */
928 rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE);
929 /* Rollback result */
930 jx9_result_bool(pCtx,rc == UNQLITE_OK );
931 return JX9_OK;
932}
933/*
934 * Register all the UnQLite foreign functions defined above.
935 */
936UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm)
937{
938 static const jx9_builtin_func aBuiltin[] = {
939 { "db_version" , unqliteBuiltin_db_version },
940 { "db_copyright", unqliteBuiltin_db_credits },
941 { "db_credits" , unqliteBuiltin_db_credits },
942 { "db_sig" , unqliteBuiltin_db_sig },
943 { "db_errlog", unqliteBuiltin_db_errlog },
944 { "collection_exists", unqliteBuiltin_collection_exists },
945 { "db_exists", unqliteBuiltin_collection_exists },
946 { "collection_create", unqliteBuiltin_collection_create },
947 { "db_create", unqliteBuiltin_collection_create },
948 { "db_fetch", unqliteBuiltin_db_fetch_next },
949 { "db_get", unqliteBuiltin_db_fetch_next },
950 { "db_fetch_by_id", unqliteBuiltin_db_fetch_by_id },
951 { "db_get_by_id", unqliteBuiltin_db_fetch_by_id },
952 { "db_fetch_all", unqliteBuiltin_db_fetch_all },
953 { "db_get_all", unqliteBuiltin_db_fetch_all },
954 { "db_last_record_id", unqliteBuiltin_db_last_record_id },
955 { "db_current_record_id", unqliteBuiltin_db_current_record_id },
956 { "db_reset_record_cursor", unqliteBuiltin_db_reset_record_cursor },
957 { "db_total_records", unqliteBuiltin_db_total_records },
958 { "db_creation_date", unqliteBuiltin_db_creation_date },
959 { "db_store", unqliteBuiltin_db_store },
960 { "db_put", unqliteBuiltin_db_store },
961 { "db_drop_collection", unqliteBuiltin_db_drop_col },
962 { "collection_delete", unqliteBuiltin_db_drop_col },
963 { "db_drop_record", unqliteBuiltin_db_drop_record },
964 { "db_set_schema", unqliteBuiltin_db_set_schema },
965 { "db_get_schema", unqliteBuiltin_db_get_schema },
966 { "db_begin", unqliteBuiltin_db_begin },
967 { "db_commit", unqliteBuiltin_db_commit },
968 { "db_rollback", unqliteBuiltin_db_rollback },
969 };
970 int rc = UNQLITE_OK;
971 sxu32 n;
972 /* Register the unQLite functions defined above in the Jx9 call table */
973 for( n = 0 ; n < SX_ARRAYSIZE(aBuiltin) ; ++n ){
974 rc = jx9_create_function(pVm->pJx9Vm,aBuiltin[n].zName,aBuiltin[n].xFunc,pVm);
975 }
976 return rc;
977}
diff --git a/common/unqlite/unqlite_vm.c b/common/unqlite/unqlite_vm.c
new file mode 100644
index 0000000..e025a1d
--- /dev/null
+++ b/common/unqlite/unqlite_vm.c
@@ -0,0 +1,894 @@
1/*
2 * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine.
3 * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/
4 * Version 1.1.6
5 * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES
6 * please contact Symisc Systems via:
7 * legal@symisc.net
8 * licensing@symisc.net
9 * contact@symisc.net
10 * or visit:
11 * http://unqlite.org/licensing.html
12 */
13 /* $SymiscID: unqlite_vm.c v1.0 Win7 2013-01-29 23:37 stable <chm@symisc.net> $ */
14#ifndef UNQLITE_AMALGAMATION
15#include "unqliteInt.h"
16#endif
17/* This file deals with low level stuff related to the unQLite Virtual Machine */
18
19/* Record ID as a hash value */
20#define COL_RECORD_HASH(RID) (RID)
21/*
22 * Fetch a record from a given collection.
23 */
24static unqlite_col_record * CollectionCacheFetchRecord(
25 unqlite_col *pCol, /* Target collection */
26 jx9_int64 nId /* Unique record ID */
27 )
28{
29 unqlite_col_record *pEntry;
30 if( pCol->nRec < 1 ){
31 /* Don't bother hashing */
32 return 0;
33 }
34 pEntry = pCol->apRecord[COL_RECORD_HASH(nId) & (pCol->nRecSize - 1)];
35 for(;;){
36 if( pEntry == 0 ){
37 break;
38 }
39 if( pEntry->nId == nId ){
40 /* Record found */
41 return pEntry;
42 }
43 /* Point to the next entry */
44 pEntry = pEntry->pNextCol;
45
46 }
47 /* No such record */
48 return 0;
49}
50/*
51 * Install a freshly created record in a given collection.
52 */
53static int CollectionCacheInstallRecord(
54 unqlite_col *pCol, /* Target collection */
55 jx9_int64 nId, /* Unique record ID */
56 jx9_value *pValue /* JSON value */
57 )
58{
59 unqlite_col_record *pRecord;
60 sxu32 iBucket;
61 /* Fetch the record first */
62 pRecord = CollectionCacheFetchRecord(pCol,nId);
63 if( pRecord ){
64 /* Record already installed, overwrite its old value */
65 jx9MemObjStore(pValue,&pRecord->sValue);
66 return UNQLITE_OK;
67 }
68 /* Allocate a new instance */
69 pRecord = (unqlite_col_record *)SyMemBackendPoolAlloc(&pCol->pVm->sAlloc,sizeof(unqlite_col_record));
70 if( pRecord == 0 ){
71 return UNQLITE_NOMEM;
72 }
73 /* Zero the structure */
74 SyZero(pRecord,sizeof(unqlite_col_record));
75 /* Fill in the structure */
76 jx9MemObjInit(pCol->pVm->pJx9Vm,&pRecord->sValue);
77 jx9MemObjStore(pValue,&pRecord->sValue);
78 pRecord->nId = nId;
79 pRecord->pCol = pCol;
80 /* Install in the corresponding bucket */
81 iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1);
82 pRecord->pNextCol = pCol->apRecord[iBucket];
83 if( pCol->apRecord[iBucket] ){
84 pCol->apRecord[iBucket]->pPrevCol = pRecord;
85 }
86 pCol->apRecord[iBucket] = pRecord;
87 /* Link */
88 MACRO_LD_PUSH(pCol->pList,pRecord);
89 pCol->nRec++;
90 if( (pCol->nRec >= pCol->nRecSize * 3) && pCol->nRec < 100000 ){
91 /* Allocate a new larger table */
92 sxu32 nNewSize = pCol->nRecSize << 1;
93 unqlite_col_record *pEntry;
94 unqlite_col_record **apNew;
95 sxu32 n;
96
97 apNew = (unqlite_col_record **)SyMemBackendAlloc(&pCol->pVm->sAlloc, nNewSize * sizeof(unqlite_col_record *));
98 if( apNew ){
99 /* Zero the new table */
100 SyZero((void *)apNew, nNewSize * sizeof(unqlite_col_record *));
101 /* Rehash all entries */
102 n = 0;
103 pEntry = pCol->pList;
104 for(;;){
105 /* Loop one */
106 if( n >= pCol->nRec ){
107 break;
108 }
109 pEntry->pNextCol = pEntry->pPrevCol = 0;
110 /* Install in the new bucket */
111 iBucket = COL_RECORD_HASH(pEntry->nId) & (nNewSize - 1);
112 pEntry->pNextCol = apNew[iBucket];
113 if( apNew[iBucket] ){
114 apNew[iBucket]->pPrevCol = pEntry;
115 }
116 apNew[iBucket] = pEntry;
117 /* Point to the next entry */
118 pEntry = pEntry->pNext;
119 n++;
120 }
121 /* Release the old table and reflect the change */
122 SyMemBackendFree(&pCol->pVm->sAlloc,(void *)pCol->apRecord);
123 pCol->apRecord = apNew;
124 pCol->nRecSize = nNewSize;
125 }
126 }
127 /* All done */
128 return UNQLITE_OK;
129}
130/*
131 * Remove a record from the collection table.
132 */
133UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(
134 unqlite_col *pCol, /* Target collection */
135 jx9_int64 nId /* Unique record ID */
136 )
137{
138 unqlite_col_record *pRecord;
139 /* Fetch the record first */
140 pRecord = CollectionCacheFetchRecord(pCol,nId);
141 if( pRecord == 0 ){
142 /* No such record */
143 return UNQLITE_NOTFOUND;
144 }
145 if( pRecord->pPrevCol ){
146 pRecord->pPrevCol->pNextCol = pRecord->pNextCol;
147 }else{
148 sxu32 iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1);
149 pCol->apRecord[iBucket] = pRecord->pNextCol;
150 }
151 if( pRecord->pNextCol ){
152 pRecord->pNextCol->pPrevCol = pRecord->pPrevCol;
153 }
154 /* Unlink */
155 MACRO_LD_REMOVE(pCol->pList,pRecord);
156 pCol->nRec--;
157 return UNQLITE_OK;
158}
159/*
160 * Discard a collection and its records.
161 */
162static int CollectionCacheRelease(unqlite_col *pCol)
163{
164 unqlite_col_record *pNext,*pRec = pCol->pList;
165 unqlite_vm *pVm = pCol->pVm;
166 sxu32 n;
167 /* Discard all records */
168 for( n = 0 ; n < pCol->nRec ; ++n ){
169 pNext = pRec->pNext;
170 jx9MemObjRelease(&pRec->sValue);
171 SyMemBackendPoolFree(&pVm->sAlloc,(void *)pRec);
172 /* Point to the next record */
173 pRec = pNext;
174 }
175 SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord);
176 pCol->nRec = pCol->nRecSize = 0;
177 pCol->pList = 0;
178 return UNQLITE_OK;
179}
180/*
181 * Install a freshly created collection in the unqlite VM.
182 */
183static int unqliteVmInstallCollection(
184 unqlite_vm *pVm, /* Target VM */
185 unqlite_col *pCol /* Collection to install */
186 )
187{
188 SyString *pName = &pCol->sName;
189 sxu32 iBucket;
190 /* Hash the collection name */
191 pCol->nHash = SyBinHash((const void *)pName->zString,pName->nByte);
192 /* Install it in the corresponding bucket */
193 iBucket = pCol->nHash & (pVm->iColSize - 1);
194 pCol->pNextCol = pVm->apCol[iBucket];
195 if( pVm->apCol[iBucket] ){
196 pVm->apCol[iBucket]->pPrevCol = pCol;
197 }
198 pVm->apCol[iBucket] = pCol;
199 /* Link to the list of active collections */
200 MACRO_LD_PUSH(pVm->pCol,pCol);
201 pVm->iCol++;
202 if( (pVm->iCol >= pVm->iColSize * 4) && pVm->iCol < 10000 ){
203 /* Grow the hashtable */
204 sxu32 nNewSize = pVm->iColSize << 1;
205 unqlite_col *pEntry;
206 unqlite_col **apNew;
207 sxu32 n;
208
209 apNew = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc, nNewSize * sizeof(unqlite_col *));
210 if( apNew ){
211 /* Zero the new table */
212 SyZero((void *)apNew, nNewSize * sizeof(unqlite_col *));
213 /* Rehash all entries */
214 n = 0;
215 pEntry = pVm->pCol;
216 for(;;){
217 /* Loop one */
218 if( n >= pVm->iCol ){
219 break;
220 }
221 pEntry->pNextCol = pEntry->pPrevCol = 0;
222 /* Install in the new bucket */
223 iBucket = pEntry->nHash & (nNewSize - 1);
224 pEntry->pNextCol = apNew[iBucket];
225 if( apNew[iBucket] ){
226 apNew[iBucket]->pPrevCol = pEntry;
227 }
228 apNew[iBucket] = pEntry;
229 /* Point to the next entry */
230 pEntry = pEntry->pNext;
231 n++;
232 }
233 /* Release the old table and reflect the change */
234 SyMemBackendFree(&pVm->sAlloc,(void *)pVm->apCol);
235 pVm->apCol = apNew;
236 pVm->iColSize = nNewSize;
237 }
238 }
239 return UNQLITE_OK;
240}
241/*
242 * Fetch a collection from the target VM.
243 */
244static unqlite_col * unqliteVmFetchCollection(
245 unqlite_vm *pVm, /* Target VM */
246 SyString *pName /* Lookup name */
247 )
248{
249 unqlite_col *pCol;
250 sxu32 nHash;
251 if( pVm->iCol < 1 ){
252 /* Don't bother hashing */
253 return 0;
254 }
255 nHash = SyBinHash((const void *)pName->zString,pName->nByte);
256 /* Perform the lookup */
257 pCol = pVm->apCol[nHash & ( pVm->iColSize - 1)];
258 for(;;){
259 if( pCol == 0 ){
260 break;
261 }
262 if( nHash == pCol->nHash && SyStringCmp(pName,&pCol->sName,SyMemcmp) == 0 ){
263 /* Collection found */
264 return pCol;
265 }
266 /* Point to the next entry */
267 pCol = pCol->pNextCol;
268 }
269 /* No such collection */
270 return 0;
271}
272/*
273 * Write and/or alter collection binary header.
274 */
275static int CollectionSetHeader(
276 unqlite_kv_engine *pEngine, /* Underlying KV storage engine */
277 unqlite_col *pCol, /* Target collection */
278 jx9_int64 iRec, /* Last record ID */
279 jx9_int64 iTotal, /* Total number of records in this collection */
280 jx9_value *pSchema /* Collection schema */
281 )
282{
283 SyBlob *pHeader = &pCol->sHeader;
284 unqlite_kv_methods *pMethods;
285 int iWrite = 0;
286 int rc;
287 if( pEngine == 0 ){
288 /* Default storage engine */
289 pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb);
290 }
291 pMethods = pEngine->pIo->pMethods;
292 if( SyBlobLength(pHeader) < 1 ){
293 Sytm *pCreate = &pCol->sCreation; /* Creation time */
294 unqlite_vfs *pVfs;
295 sxu32 iDos;
296 /* Magic number */
297 rc = SyBlobAppendBig16(pHeader,UNQLITE_COLLECTION_MAGIC);
298 if( rc != UNQLITE_OK ){
299 return rc;
300 }
301 /* Initial record ID */
302 rc = SyBlobAppendBig64(pHeader,0);
303 if( rc != UNQLITE_OK ){
304 return rc;
305 }
306 /* Total records in the collection */
307 rc = SyBlobAppendBig64(pHeader,0);
308 if( rc != UNQLITE_OK ){
309 return rc;
310 }
311 pVfs = (unqlite_vfs *)unqliteExportBuiltinVfs();
312 /* Creation time of the collection */
313 if( pVfs->xCurrentTime ){
314 /* Get the creation time */
315 pVfs->xCurrentTime(pVfs,pCreate);
316 }else{
317 /* Zero the structure */
318 SyZero(pCreate,sizeof(Sytm));
319 }
320 /* Convert to DOS time */
321 SyTimeFormatToDos(pCreate,&iDos);
322 rc = SyBlobAppendBig32(pHeader,iDos);
323 if( rc != UNQLITE_OK ){
324 return rc;
325 }
326 /* Offset to start writing collection schema */
327 pCol->nSchemaOfft = SyBlobLength(pHeader);
328 iWrite = 1;
329 }else{
330 unsigned char *zBinary = (unsigned char *)SyBlobData(pHeader);
331 /* Header update */
332 if( iRec >= 0 ){
333 /* Update record ID */
334 SyBigEndianPack64(&zBinary[2/* Magic number*/],(sxu64)iRec);
335 iWrite = 1;
336 }
337 if( iTotal >= 0 ){
338 /* Total records */
339 SyBigEndianPack64(&zBinary[2/* Magic number*/+8/* Record ID*/],(sxu64)iTotal);
340 iWrite = 1;
341 }
342 if( pSchema ){
343 /* Collection Schema */
344 SyBlobTruncate(pHeader,pCol->nSchemaOfft);
345 /* Encode the schema to FastJson */
346 rc = FastJsonEncode(pSchema,pHeader,0);
347 if( rc != UNQLITE_OK ){
348 return rc;
349 }
350 /* Copy the collection schema */
351 jx9MemObjStore(pSchema,&pCol->sSchema);
352 iWrite = 1;
353 }
354 }
355 if( iWrite ){
356 SyString *pId = &pCol->sName;
357 /* Reflect the disk and/or in-memory image */
358 rc = pMethods->xReplace(pEngine,
359 (const void *)pId->zString,pId->nByte,
360 SyBlobData(pHeader),SyBlobLength(pHeader)
361 );
362 if( rc != UNQLITE_OK ){
363 unqliteGenErrorFormat(pCol->pVm->pDb,
364 "Cannot save collection '%z' header in the underlying storage engine",
365 pId
366 );
367 return rc;
368 }
369 }
370 return UNQLITE_OK;
371}
372/*
373 * Load a binary collection from disk.
374 */
375static int CollectionLoadHeader(unqlite_col *pCol)
376{
377 SyBlob *pHeader = &pCol->sHeader;
378 unsigned char *zRaw,*zEnd;
379 sxu16 nMagic;
380 sxu32 iDos;
381 int rc;
382 SyBlobReset(pHeader);
383 /* Read the binary header */
384 rc = unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pHeader);
385 if( rc != UNQLITE_OK ){
386 return rc;
387 }
388 /* Perform a sanity check */
389 if( SyBlobLength(pHeader) < (2 /* magic */ + 8 /* record_id */ + 8 /* total_records */+ 4 /* DOS creation time*/) ){
390 return UNQLITE_CORRUPT;
391 }
392 zRaw = (unsigned char *)SyBlobData(pHeader);
393 zEnd = &zRaw[SyBlobLength(pHeader)];
394 /* Extract the magic number */
395 SyBigEndianUnpack16(zRaw,&nMagic);
396 if( nMagic != UNQLITE_COLLECTION_MAGIC ){
397 return UNQLITE_CORRUPT;
398 }
399 zRaw += 2; /* sizeof(sxu16) */
400 /* Extract the record ID */
401 SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nLastid);
402 zRaw += 8; /* sizeof(sxu64) */
403 /* Total records in the collection */
404 SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nTotRec);
405 /* Extract the collection creation date (DOS) */
406 zRaw += 8; /* sizeof(sxu64) */
407 SyBigEndianUnpack32(zRaw,&iDos);
408 SyDosTimeFormat(iDos,&pCol->sCreation);
409 zRaw += 4;
410 /* Check for a collection schema */
411 pCol->nSchemaOfft = (sxu32)(zRaw - (unsigned char *)SyBlobData(pHeader));
412 if( zRaw < zEnd ){
413 /* Decode the FastJson value */
414 FastJsonDecode((const void *)zRaw,(sxu32)(zEnd-zRaw),&pCol->sSchema,0,0);
415 }
416 return UNQLITE_OK;
417}
418/*
419 * Load or create a binary collection.
420 */
421static int unqliteVmLoadCollection(
422 unqlite_vm *pVm, /* Target VM */
423 const char *zName, /* Collection name */
424 sxu32 nByte, /* zName length */
425 int iFlag, /* Control flag */
426 unqlite_col **ppOut /* OUT: in-memory collection */
427 )
428{
429 unqlite_kv_methods *pMethods;
430 unqlite_kv_engine *pEngine;
431 unqlite_kv_cursor *pCursor;
432 unqlite *pDb = pVm->pDb;
433 unqlite_col *pCol = 0; /* cc warning */
434 int rc = SXERR_MEM;
435 char *zDup = 0;
436 /* Point to the underlying KV store */
437 pEngine = unqlitePagerGetKvEngine(pVm->pDb);
438 pMethods = pEngine->pIo->pMethods;
439 /* Allocate a new cursor */
440 rc = unqliteInitCursor(pDb,&pCursor);
441 if( rc != UNQLITE_OK ){
442 return rc;
443 }
444 if( (iFlag & UNQLITE_VM_COLLECTION_CREATE) == 0 ){
445 /* Seek to the desired location */
446 rc = pMethods->xSeek(pCursor,(const void *)zName,(unqlite_int64)nByte,UNQLITE_CURSOR_MATCH_EXACT);
447 if( rc != UNQLITE_OK ){
448 unqliteGenErrorFormat(pDb,"Collection '%.*s' not defined in the underlying database",nByte,zName);
449 unqliteReleaseCursor(pDb,pCursor);
450 return rc;
451 }
452 }
453 /* Allocate a new instance */
454 pCol = (unqlite_col *)SyMemBackendPoolAlloc(&pVm->sAlloc,sizeof(unqlite_col));
455 if( pCol == 0 ){
456 unqliteGenOutofMem(pDb);
457 rc = UNQLITE_NOMEM;
458 goto fail;
459 }
460 SyZero(pCol,sizeof(unqlite_col));
461 /* Fill in the structure */
462 SyBlobInit(&pCol->sWorker,&pVm->sAlloc);
463 SyBlobInit(&pCol->sHeader,&pVm->sAlloc);
464 pCol->pVm = pVm;
465 pCol->pCursor = pCursor;
466 /* Duplicate collection name */
467 zDup = SyMemBackendStrDup(&pVm->sAlloc,zName,nByte);
468 if( zDup == 0 ){
469 unqliteGenOutofMem(pDb);
470 rc = UNQLITE_NOMEM;
471 goto fail;
472 }
473 pCol->nRecSize = 64; /* Must be a power of two */
474 pCol->apRecord = (unqlite_col_record **)SyMemBackendAlloc(&pVm->sAlloc,pCol->nRecSize * sizeof(unqlite_col_record *));
475 if( pCol->apRecord == 0 ){
476 unqliteGenOutofMem(pDb);
477 rc = UNQLITE_NOMEM;
478 goto fail;
479 }
480 /* Zero the table */
481 SyZero((void *)pCol->apRecord,pCol->nRecSize * sizeof(unqlite_col_record *));
482 SyStringInitFromBuf(&pCol->sName,zDup,nByte);
483 jx9MemObjInit(pVm->pJx9Vm,&pCol->sSchema);
484 if( iFlag & UNQLITE_VM_COLLECTION_CREATE ){
485 /* Create a new collection */
486 if( pMethods->xReplace == 0 ){
487 /* Read-only KV engine: Generate an error message and return */
488 unqliteGenErrorFormat(pDb,
489 "Cannot create new collection '%z' due to a read-only Key/Value storage engine",
490 &pCol->sName
491 );
492 rc = UNQLITE_ABORT; /* Abort VM execution */
493 goto fail;
494 }
495 /* Write the collection header */
496 rc = CollectionSetHeader(pEngine,pCol,0,0,0);
497 if( rc != UNQLITE_OK ){
498 rc = UNQLITE_ABORT; /* Abort VM execution */
499 goto fail;
500 }
501 }else{
502 /* Read the collection header */
503 rc = CollectionLoadHeader(pCol);
504 if( rc != UNQLITE_OK ){
505 unqliteGenErrorFormat(pDb,"Corrupt collection '%z' header",&pCol->sName);
506 goto fail;
507 }
508 }
509 /* Finally install the collection */
510 unqliteVmInstallCollection(pVm,pCol);
511 /* All done */
512 if( ppOut ){
513 *ppOut = pCol;
514 }
515 return UNQLITE_OK;
516fail:
517 unqliteReleaseCursor(pDb,pCursor);
518 if( zDup ){
519 SyMemBackendFree(&pVm->sAlloc,zDup);
520 }
521 if( pCol ){
522 if( pCol->apRecord ){
523 SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord);
524 }
525 SyBlobRelease(&pCol->sHeader);
526 SyBlobRelease(&pCol->sWorker);
527 jx9MemObjRelease(&pCol->sSchema);
528 SyMemBackendPoolFree(&pVm->sAlloc,pCol);
529 }
530 return rc;
531}
532/*
533 * Fetch a collection.
534 */
535UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(
536 unqlite_vm *pVm, /* Target VM */
537 SyString *pName, /* Lookup key */
538 int iFlag /* Control flag */
539 )
540{
541 unqlite_col *pCol = 0; /* cc warning */
542 int rc;
543 /* Check if the collection is already loaded in memory */
544 pCol = unqliteVmFetchCollection(pVm,pName);
545 if( pCol ){
546 /* Already loaded in memory*/
547 return pCol;
548 }
549 if( (iFlag & UNQLITE_VM_AUTO_LOAD) == 0 ){
550 return 0;
551 }
552 /* Ask the storage engine for the collection */
553 rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,0,&pCol);
554 /* Return to the caller */
555 return rc == UNQLITE_OK ? pCol : 0;
556}
557/*
558 * Return the unique ID of the last inserted record.
559 */
560UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol)
561{
562 return pCol->nLastid == 0 ? 0 : (pCol->nLastid - 1);
563}
564/*
565 * Return the current record ID.
566 */
567UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol)
568{
569 return pCol->nCurid;
570}
571/*
572 * Return the total number of records in a given collection.
573 */
574UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol)
575{
576 return pCol->nTotRec;
577}
578/*
579 * Reset the record cursor.
580 */
581UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol)
582{
583 pCol->nCurid = 0;
584}
585/*
586 * Fetch a record by its unique ID.
587 */
588UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(
589 unqlite_col *pCol, /* Target collection */
590 jx9_int64 nId, /* Unique record ID */
591 jx9_value *pValue /* OUT: record value */
592 )
593{
594 SyBlob *pWorker = &pCol->sWorker;
595 unqlite_col_record *pRec;
596 int rc;
597 jx9_value_null(pValue);
598 /* Perform a cache lookup first */
599 pRec = CollectionCacheFetchRecord(pCol,nId);
600 if( pRec ){
601 /* Copy record value */
602 jx9MemObjStore(&pRec->sValue,pValue);
603 return UNQLITE_OK;
604 }
605 /* Reset the working buffer */
606 SyBlobReset(pWorker);
607 /* Generate the unique ID */
608 SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId);
609 /* Reset the cursor */
610 unqlite_kv_cursor_reset(pCol->pCursor);
611 /* Seek the cursor to the desired location */
612 rc = unqlite_kv_cursor_seek(pCol->pCursor,
613 SyBlobData(pWorker),SyBlobLength(pWorker),
614 UNQLITE_CURSOR_MATCH_EXACT
615 );
616 if( rc != UNQLITE_OK ){
617 return rc;
618 }
619 /* Consume the binary JSON */
620 SyBlobReset(pWorker);
621 unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pWorker);
622 if( SyBlobLength(pWorker) < 1 ){
623 unqliteGenErrorFormat(pCol->pVm->pDb,
624 "Empty record '%qd'",nId
625 );
626 jx9_value_null(pValue);
627 }else{
628 /* Decode the binary JSON */
629 rc = FastJsonDecode(SyBlobData(pWorker),SyBlobLength(pWorker),pValue,0,0);
630 if( rc == UNQLITE_OK ){
631 /* Install the record in the cache */
632 CollectionCacheInstallRecord(pCol,nId,pValue);
633 }
634 }
635 return rc;
636}
637/*
638 * Fetch the next record from a given collection.
639 */
640UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue)
641{
642 int rc;
643 for(;;){
644 if( pCol->nCurid >= pCol->nLastid ){
645 /* No more records, reset the record cursor ID */
646 pCol->nCurid = 0;
647 /* Return to the caller */
648 return SXERR_EOF;
649 }
650 rc = unqliteCollectionFetchRecordById(pCol,pCol->nCurid,pValue);
651 /* Increment the record ID */
652 pCol->nCurid++;
653 /* Lookup result */
654 if( rc == UNQLITE_OK || rc != UNQLITE_NOTFOUND ){
655 break;
656 }
657 }
658 return rc;
659}
660/*
661 * Create a new collection.
662 */
663UNQLITE_PRIVATE int unqliteCreateCollection(
664 unqlite_vm *pVm, /* Target VM */
665 SyString *pName /* Collection name */
666 )
667{
668 unqlite_col *pCol;
669 int rc;
670 /* Perform a lookup first */
671 pCol = unqliteCollectionFetch(pVm,pName,UNQLITE_VM_AUTO_LOAD);
672 if( pCol ){
673 return UNQLITE_EXISTS;
674 }
675 /* Now, safely create the collection */
676 rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_CREATE,0);
677 return rc;
678}
679/*
680 * Set a schema (JSON object) for a given collection.
681 */
682UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue)
683{
684 int rc;
685 if( !jx9_value_is_json_object(pValue) ){
686 /* Must be a JSON object */
687 return SXERR_INVALID;
688 }
689 rc = CollectionSetHeader(0,pCol,-1,-1,pValue);
690 return rc;
691}
692/*
693 * Perform a store operation on a given collection.
694 */
695static int CollectionStore(
696 unqlite_col *pCol, /* Target collection */
697 jx9_value *pValue /* JSON value to be stored */
698 )
699{
700 SyBlob *pWorker = &pCol->sWorker;
701 unqlite_kv_methods *pMethods;
702 unqlite_kv_engine *pEngine;
703 sxu32 nKeyLen;
704 int rc;
705 /* Point to the underlying KV store */
706 pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb);
707 pMethods = pEngine->pIo->pMethods;
708 if( pCol->nTotRec >= SXI64_HIGH ){
709 /* Collection limit reached. No more records */
710 unqliteGenErrorFormat(pCol->pVm->pDb,
711 "Collection '%z': Records limit reached",
712 &pCol->sName
713 );
714 return UNQLITE_LIMIT;
715 }
716 if( pMethods->xReplace == 0 ){
717 unqliteGenErrorFormat(pCol->pVm->pDb,
718 "Cannot store record into collection '%z' due to a read-only Key/Value storage engine",
719 &pCol->sName
720 );
721 return UNQLITE_READ_ONLY;
722 }
723 /* Reset the working buffer */
724 SyBlobReset(pWorker);
725 if( jx9_value_is_json_object(pValue) ){
726 jx9_value sId;
727 /* If the given type is a JSON object, then add the special __id field */
728 jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,pCol->nLastid);
729 jx9_array_add_strkey_elem(pValue,"__id",&sId);
730 jx9MemObjRelease(&sId);
731 }
732 /* Prepare the unique ID for this record */
733 SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,pCol->nLastid);
734 nKeyLen = SyBlobLength(pWorker);
735 if( nKeyLen < 1 ){
736 unqliteGenOutofMem(pCol->pVm->pDb);
737 return UNQLITE_NOMEM;
738 }
739 /* Turn to FastJson */
740 rc = FastJsonEncode(pValue,pWorker,0);
741 if( rc != UNQLITE_OK ){
742 return rc;
743 }
744 /* Finally perform the insertion */
745 rc = pMethods->xReplace(
746 pEngine,
747 SyBlobData(pWorker),nKeyLen,
748 SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen
749 );
750 if( rc == UNQLITE_OK ){
751 /* Save the value in the cache */
752 CollectionCacheInstallRecord(pCol,pCol->nLastid,pValue);
753 /* Increment the unique __id */
754 pCol->nLastid++;
755 pCol->nTotRec++;
756 /* Reflect the change */
757 rc = CollectionSetHeader(0,pCol,pCol->nLastid,pCol->nTotRec,0);
758 }
759 if( rc != UNQLITE_OK ){
760 unqliteGenErrorFormat(pCol->pVm->pDb,
761 "IO error while storing record into collection '%z'",
762 &pCol->sName
763 );
764 return rc;
765 }
766 return UNQLITE_OK;
767}
768/*
769 * Array walker callback (Refer to jx9_array_walk()).
770 */
771static int CollectionRecordArrayWalker(jx9_value *pKey,jx9_value *pData,void *pUserData)
772{
773 unqlite_col *pCol = (unqlite_col *)pUserData;
774 int rc;
775 /* Perform the insertion */
776 rc = CollectionStore(pCol,pData);
777 if( rc != UNQLITE_OK ){
778 SXUNUSED(pKey); /* cc warning */
779 }
780 return rc;
781}
782/*
783 * Perform a store operation on a given collection.
784 */
785UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag)
786{
787 int rc;
788 if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){
789 /* Iterate over the array and store its members in the collection */
790 rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol);
791 SXUNUSED(iFlag); /* cc warning */
792 }else{
793 rc = CollectionStore(pCol,pValue);
794 }
795 return rc;
796}
797/*
798 * Drop a record from a given collection.
799 */
800UNQLITE_PRIVATE int unqliteCollectionDropRecord(
801 unqlite_col *pCol, /* Target collection */
802 jx9_int64 nId, /* Unique ID of the record to be droped */
803 int wr_header, /* True to alter collection header */
804 int log_err /* True to log error */
805 )
806{
807 SyBlob *pWorker = &pCol->sWorker;
808 int rc;
809 /* Reset the working buffer */
810 SyBlobReset(pWorker);
811 /* Prepare the unique ID for this record */
812 SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId);
813 /* Reset the cursor */
814 unqlite_kv_cursor_reset(pCol->pCursor);
815 /* Seek the cursor to the desired location */
816 rc = unqlite_kv_cursor_seek(pCol->pCursor,
817 SyBlobData(pWorker),SyBlobLength(pWorker),
818 UNQLITE_CURSOR_MATCH_EXACT
819 );
820 if( rc != UNQLITE_OK ){
821 return rc;
822 }
823 /* Remove the record from the storage engine */
824 rc = unqlite_kv_cursor_delete_entry(pCol->pCursor);
825 /* Finally, Remove the record from the cache */
826 unqliteCollectionCacheRemoveRecord(pCol,nId);
827 if( rc == UNQLITE_OK ){
828 pCol->nTotRec--;
829 if( wr_header ){
830 /* Relect in the collection header */
831 rc = CollectionSetHeader(0,pCol,-1,pCol->nTotRec,0);
832 }
833 }else if( rc == UNQLITE_NOTIMPLEMENTED ){
834 if( log_err ){
835 unqliteGenErrorFormat(pCol->pVm->pDb,
836 "Cannot delete record from collection '%z' due to a read-only Key/Value storage engine",
837 &pCol->sName
838 );
839 }
840 }
841 return rc;
842}
843/*
844 * Drop a collection from the KV storage engine and the underlying
845 * unqlite VM.
846 */
847UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol)
848{
849 unqlite_vm *pVm = pCol->pVm;
850 jx9_int64 nId;
851 int rc;
852 /* Reset the cursor */
853 unqlite_kv_cursor_reset(pCol->pCursor);
854 /* Seek the cursor to the desired location */
855 rc = unqlite_kv_cursor_seek(pCol->pCursor,
856 SyStringData(&pCol->sName),SyStringLength(&pCol->sName),
857 UNQLITE_CURSOR_MATCH_EXACT
858 );
859 if( rc == UNQLITE_OK ){
860 /* Remove the record from the storage engine */
861 rc = unqlite_kv_cursor_delete_entry(pCol->pCursor);
862 }
863 if( rc != UNQLITE_OK ){
864 unqliteGenErrorFormat(pCol->pVm->pDb,
865 "Cannot remove collection '%z' due to a read-only Key/Value storage engine",
866 &pCol->sName
867 );
868 return rc;
869 }
870 /* Drop collection records */
871 for( nId = 0 ; nId < pCol->nLastid ; ++nId ){
872 unqliteCollectionDropRecord(pCol,nId,0,0);
873 }
874 /* Cleanup */
875 CollectionCacheRelease(pCol);
876 SyBlobRelease(&pCol->sHeader);
877 SyBlobRelease(&pCol->sWorker);
878 SyMemBackendFree(&pVm->sAlloc,(void *)SyStringData(&pCol->sName));
879 unqliteReleaseCursor(pVm->pDb,pCol->pCursor);
880 /* Unlink */
881 if( pCol->pPrevCol ){
882 pCol->pPrevCol->pNextCol = pCol->pNextCol;
883 }else{
884 sxu32 iBucket = pCol->nHash & (pVm->iColSize - 1);
885 pVm->apCol[iBucket] = pCol->pNextCol;
886 }
887 if( pCol->pNextCol ){
888 pCol->pNextCol->pPrevCol = pCol->pPrevCol;
889 }
890 MACRO_LD_REMOVE(pVm->pCol,pCol);
891 pVm->iCol--;
892 SyMemBackendPoolFree(&pVm->sAlloc,pCol);
893 return UNQLITE_OK;
894}