diff options
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) | |||
2 | generate_flatbuffers(commands/handshake | 2 | generate_flatbuffers(commands/handshake |
3 | commands/revisionupdate) | 3 | commands/revisionupdate) |
4 | 4 | ||
5 | if (STORAGE_KYOTO) | 5 | if (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) |
8 | else (STORAGE_KYOTO) | 8 | set(storage_SRCS ${storage_SRCS} storage_unqlite.cpp) |
9 | else (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) |
11 | endif (STORAGE_KYOTO) | 12 | endif (STORAGE_unqlite) |
12 | 13 | ||
13 | set(command_SRCS | 14 | set(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 | ||
20 | message("We have: ${storage_SRCS}") | ||
19 | add_library(${PROJECT_NAME} ${command_SRCS}) | 21 | add_library(${PROJECT_NAME} ${command_SRCS}) |
20 | SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) | 22 | SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX) |
21 | qt5_use_modules(${PROJECT_NAME} Widgets) | 23 | qt5_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 | |||
28 | class Storage::Private | ||
29 | { | ||
30 | public: | ||
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 | |||
41 | Storage::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 | |||
61 | Storage::Private::~Private() | ||
62 | { | ||
63 | if (dbOpen && inTransaction) { | ||
64 | db.end_transaction(false); | ||
65 | } | ||
66 | } | ||
67 | |||
68 | Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode) | ||
69 | : d(new Private(storageRoot, name, mode)) | ||
70 | { | ||
71 | } | ||
72 | |||
73 | Storage::~Storage() | ||
74 | { | ||
75 | delete d; | ||
76 | } | ||
77 | |||
78 | bool Storage::isInTransaction() const | ||
79 | { | ||
80 | return d->inTransaction; | ||
81 | } | ||
82 | |||
83 | bool 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 | |||
102 | bool 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 | |||
117 | void 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 | |||
127 | bool 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 | |||
137 | bool 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 | |||
147 | void 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 | |||
178 | void 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 | |||
215 | qint64 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 | |||
225 | void 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 | |||
14 | class Storage::Private | ||
15 | { | ||
16 | public: | ||
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 | |||
32 | Storage::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 | |||
58 | Storage::Private::~Private() | ||
59 | { | ||
60 | unqlite_close(db); | ||
61 | } | ||
62 | |||
63 | void 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 | |||
78 | void 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 | |||
97 | Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode) | ||
98 | : d(new Private(storageRoot, name, mode)) | ||
99 | { | ||
100 | } | ||
101 | |||
102 | Storage::~Storage() | ||
103 | { | ||
104 | if (d->inTransaction) { | ||
105 | abortTransaction(); | ||
106 | } | ||
107 | |||
108 | delete d; | ||
109 | } | ||
110 | |||
111 | bool Storage::isInTransaction() const | ||
112 | { | ||
113 | return d->inTransaction; | ||
114 | } | ||
115 | |||
116 | bool 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 | |||
135 | bool 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 | |||
155 | void Storage::abortTransaction() | ||
156 | { | ||
157 | if (!d->db || !d->inTransaction) { | ||
158 | return; | ||
159 | } | ||
160 | |||
161 | unqlite_rollback(d->db); | ||
162 | d->inTransaction = false; | ||
163 | } | ||
164 | |||
165 | bool 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 | |||
170 | bool 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 | |||
185 | void 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 | |||
200 | void 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 | |||
209 | void 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 | |||
235 | void 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 | |||
277 | qint64 Storage::diskUsage() const | ||
278 | { | ||
279 | QFileInfo info(d->storageRoot + '/' + d->name); | ||
280 | return info.size(); | ||
281 | } | ||
282 | |||
283 | void 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 | */ | ||
35 | static 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 | */ | ||
88 | UNQLITE_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 | */ | ||
113 | static 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 | */ | ||
255 | int 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 | */ | ||
278 | static 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 | ||
358 | End: | ||
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 */ | ||
366 | static int unqliteVmRelease(unqlite_vm *pVm); | ||
367 | /* | ||
368 | * Release a single instance of an unqlite database handle. | ||
369 | */ | ||
370 | static 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 | */ | ||
413 | static 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 | */ | ||
455 | int 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 | */ | ||
465 | int 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 | */ | ||
478 | int 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 | */ | ||
500 | const 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 | */ | ||
509 | const 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 | */ | ||
518 | const 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 | */ | ||
527 | const 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 | */ | ||
534 | static 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 | */ | ||
562 | static 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 | */ | ||
596 | static 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; | ||
639 | fail: | ||
640 | SyMemBackendRelease(&pVm->sAlloc); | ||
641 | SyMemBackendPoolFree(&pDb->sMem,pVm); | ||
642 | return UNQLITE_NOMEM; | ||
643 | } | ||
644 | /* | ||
645 | * Release an active VM. | ||
646 | */ | ||
647 | static 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 | */ | ||
661 | UNQLITE_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 | */ | ||
672 | UNQLITE_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 | */ | ||
684 | UNQLITE_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 | */ | ||
698 | UNQLITE_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 | */ | ||
707 | static 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 | */ | ||
768 | UNQLITE_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 | */ | ||
776 | int 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; | ||
833 | Release: | ||
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 | */ | ||
842 | int 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 | */ | ||
870 | int 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 | */ | ||
911 | int 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 | */ | ||
946 | int 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 | */ | ||
980 | static 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 | */ | ||
990 | int 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 | */ | ||
1018 | int 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 | */ | ||
1044 | int 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 | */ | ||
1092 | int 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 | */ | ||
1118 | int 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 | */ | ||
1144 | unqlite_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 | */ | ||
1172 | int 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 | */ | ||
1206 | int 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 | */ | ||
1232 | int 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 | */ | ||
1270 | int 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 | */ | ||
1296 | int 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 | */ | ||
1304 | int 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 | */ | ||
1312 | int 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 | */ | ||
1320 | int 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 | */ | ||
1328 | int 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 | */ | ||
1336 | int 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 | */ | ||
1344 | int 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 | */ | ||
1362 | int 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 | */ | ||
1370 | int 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 | */ | ||
1378 | int 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 | */ | ||
1386 | int 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 | */ | ||
1394 | int 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 | */ | ||
1402 | unqlite_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 | */ | ||
1410 | double 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 | */ | ||
1418 | const 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 | */ | ||
1426 | void * 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 | */ | ||
1434 | int 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 | */ | ||
1442 | int 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 | */ | ||
1450 | int 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 | */ | ||
1458 | int 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 | */ | ||
1466 | int 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 | */ | ||
1474 | int 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 | */ | ||
1482 | int 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 | */ | ||
1490 | int 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 | */ | ||
1511 | int 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 | */ | ||
1519 | int 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 | */ | ||
1527 | int 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 | */ | ||
1535 | int 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 | */ | ||
1543 | int 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 | */ | ||
1551 | int 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 | */ | ||
1559 | int 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 | */ | ||
1567 | int 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 | */ | ||
1575 | int 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 | */ | ||
1583 | int 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 | */ | ||
1591 | int 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 | */ | ||
1599 | int 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 | */ | ||
1607 | int 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 | */ | ||
1615 | int 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 | */ | ||
1623 | unqlite_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 | */ | ||
1631 | int 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 | */ | ||
1639 | int 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 | */ | ||
1647 | int 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 | */ | ||
1655 | int 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 | */ | ||
1663 | unqlite_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 | */ | ||
1688 | unqlite_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 | */ | ||
1713 | int 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 | */ | ||
1738 | int 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 | */ | ||
1746 | int 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 | */ | ||
1759 | int 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 | */ | ||
1767 | int 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 | */ | ||
1783 | unsigned 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 | */ | ||
1791 | int 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 | */ | ||
1799 | void * 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 | */ | ||
1807 | int 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 | */ | ||
1815 | void * 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 | */ | ||
1823 | void * 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 | */ | ||
1831 | unsigned 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 | */ | ||
1839 | const 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 | */ | ||
1847 | unqlite_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 | */ | ||
1855 | unqlite_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 | */ | ||
1863 | void 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 | */ | ||
1871 | void * 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 | */ | ||
1879 | void * 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 | */ | ||
1887 | void 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 | */ | ||
1895 | int 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 | */ | ||
1939 | int 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 | */ | ||
1992 | int 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 | */ | ||
2036 | int 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 | */ | ||
2089 | int 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 | */ | ||
2147 | int 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 | */ | ||
2193 | int 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 | */ | ||
2245 | int 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 | */ | ||
2283 | int 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 | */ | ||
2309 | int 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 | */ | ||
2335 | int 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 | */ | ||
2356 | int 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 | */ | ||
2377 | int 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 | */ | ||
2397 | int 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 | */ | ||
2418 | int 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 | */ | ||
2439 | int 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 | */ | ||
2460 | int 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 | */ | ||
2481 | int 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 | */ | ||
2505 | UNQLITE_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 | */ | ||
2520 | int 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 | */ | ||
2536 | int 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 | */ | ||
2567 | int 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 | */ | ||
2583 | int 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 | */ | ||
2614 | int 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 | */ | ||
2640 | int 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 | */ | ||
2666 | int 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 | */ | ||
2692 | UNQLITE_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 | */ | ||
2719 | UNQLITE_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 | */ | ||
2739 | UNQLITE_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 | */ | ||
2768 | UNQLITE_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 | */ | ||
30 | typedef struct bitvec_rec bitvec_rec; | ||
31 | struct bitvec_rec | ||
32 | { | ||
33 | pgno iPage; /* Page number */ | ||
34 | bitvec_rec *pNext,*pNextCol; /* Collison link */ | ||
35 | }; | ||
36 | struct 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 | */ | ||
47 | UNQLITE_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 | */ | ||
77 | UNQLITE_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 | */ | ||
123 | UNQLITE_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 | */ | ||
180 | UNQLITE_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 | */ | ||
48 | UNQLITE_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 | */ | ||
185 | UNQLITE_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 */ | ||
100 | typedef struct jx9_io_stream jx9_io_stream; | ||
101 | typedef struct jx9_context jx9_context; | ||
102 | typedef struct jx9_value jx9_value; | ||
103 | typedef struct jx9_vfs jx9_vfs; | ||
104 | typedef struct jx9_vm jx9_vm; | ||
105 | typedef 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 | ||
131 | typedef sxi64 jx9_real; | ||
132 | #else | ||
133 | typedef double jx9_real; | ||
134 | #endif | ||
135 | typedef 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 | */ | ||
243 | struct 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 | */ | ||
338 | struct 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 */ | ||
362 | JX9_PRIVATE int jx9_init(jx9 **ppEngine); | ||
363 | /*JX9_PRIVATE int jx9_config(jx9 *pEngine, int nConfigOp, ...);*/ | ||
364 | JX9_PRIVATE int jx9_release(jx9 *pEngine); | ||
365 | /* Compile Interfaces */ | ||
366 | JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm); | ||
367 | JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm); | ||
368 | /* Virtual Machine Handling Interfaces */ | ||
369 | JX9_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);*/ | ||
373 | JX9_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 */ | ||
376 | JX9_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);*/ | ||
378 | JX9_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 */ | ||
381 | JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue); | ||
382 | JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue); | ||
383 | JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue); | ||
384 | JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue); | ||
385 | JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen); | ||
386 | JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue); | ||
387 | JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict); | ||
388 | /* Setting The Result Of A Foreign Function */ | ||
389 | JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue); | ||
390 | JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue); | ||
391 | JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool); | ||
392 | JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value); | ||
393 | JX9_PRIVATE int jx9_result_null(jx9_context *pCtx); | ||
394 | JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen); | ||
395 | JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...); | ||
396 | JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue); | ||
397 | JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData); | ||
398 | /* Call Context Handling Interfaces */ | ||
399 | JX9_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, ...);*/ | ||
401 | JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr); | ||
402 | JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...); | ||
403 | JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx); | ||
404 | JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen); | ||
405 | JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx); | ||
406 | JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData); | ||
407 | JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx); | ||
408 | JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx); | ||
409 | JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx); | ||
410 | JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx); | ||
411 | /* Call Context Memory Management Interfaces */ | ||
412 | JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease); | ||
413 | JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte); | ||
414 | JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk); | ||
415 | /* On Demand Dynamically Typed Value Object allocation interfaces */ | ||
416 | JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm); | ||
417 | JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm); | ||
418 | JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue); | ||
419 | JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx); | ||
420 | JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx); | ||
421 | JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue); | ||
422 | /* Dynamically Typed Value Object Management Interfaces */ | ||
423 | JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue); | ||
424 | JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue); | ||
425 | JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool); | ||
426 | JX9_PRIVATE int jx9_value_null(jx9_value *pVal); | ||
427 | JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value); | ||
428 | JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen); | ||
429 | JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...); | ||
430 | JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal); | ||
431 | JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData); | ||
432 | JX9_PRIVATE int jx9_value_release(jx9_value *pVal); | ||
433 | /* JSON Array/Object Management Interfaces */ | ||
434 | JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte); | ||
435 | JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); | ||
436 | JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue); | ||
437 | JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue); | ||
438 | JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray); | ||
439 | /* Dynamically Typed Value Object Query Interfaces */ | ||
440 | JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal); | ||
441 | JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal); | ||
442 | JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal); | ||
443 | JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal); | ||
444 | JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal); | ||
445 | JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal); | ||
446 | JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal); | ||
447 | JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal); | ||
448 | JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal); | ||
449 | JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal); | ||
450 | JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal); | ||
451 | JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal); | ||
452 | /* Global Library Management Interfaces */ | ||
453 | /*JX9_PRIVATE int jx9_lib_init(void);*/ | ||
454 | JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...); | ||
455 | JX9_PRIVATE int jx9_lib_shutdown(void); | ||
456 | /*JX9_PRIVATE int jx9_lib_is_threadsafe(void);*/ | ||
457 | /*JX9_PRIVATE const char * jx9_lib_version(void);*/ | ||
458 | JX9_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 */ | ||
42 | typedef struct jx9_foreach_info jx9_foreach_info; | ||
43 | typedef struct jx9_foreach_step jx9_foreach_step; | ||
44 | typedef struct jx9_hashmap_node jx9_hashmap_node; | ||
45 | typedef 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 | ||
59 | typedef signed char sxi8; /* signed char */ | ||
60 | typedef unsigned char sxu8; /* unsigned char */ | ||
61 | typedef signed short int sxi16; /* 16 bits(2 bytes) signed integer */ | ||
62 | typedef unsigned short int sxu16; /* 16 bits(2 bytes) unsigned integer */ | ||
63 | typedef int sxi32; /* 32 bits(4 bytes) integer */ | ||
64 | typedef unsigned int sxu32; /* 32 bits(4 bytes) unsigned integer */ | ||
65 | typedef long sxptr; | ||
66 | typedef unsigned long sxuptr; | ||
67 | typedef long sxlong; | ||
68 | typedef unsigned long sxulong; | ||
69 | typedef sxi32 sxofft; | ||
70 | typedef sxi64 sxofft64; | ||
71 | typedef long double sxlongreal; | ||
72 | typedef 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 | |||
108 | typedef 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 ) | ||
150 | typedef struct SyMemBackend SyMemBackend; | ||
151 | typedef struct SyBlob SyBlob; | ||
152 | typedef struct SySet SySet; | ||
153 | /* Standard function signatures */ | ||
154 | typedef sxi32 (*ProcCmp)(const void *, const void *, sxu32); | ||
155 | typedef sxi32 (*ProcPatternMatch)(const char *, sxu32, const char *, sxu32, sxu32 *); | ||
156 | typedef sxi32 (*ProcSearch)(const void *, sxu32, const void *, sxu32, ProcCmp, sxu32 *); | ||
157 | typedef sxu32 (*ProcHash)(const void *, sxu32); | ||
158 | typedef sxi32 (*ProcHashSum)(const void *, sxu32, unsigned char *, sxu32); | ||
159 | typedef 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 | */ | ||
180 | struct 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 | */ | ||
202 | struct 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 */ | ||
228 | typedef union SyMemHeader SyMemHeader; | ||
229 | typedef struct SyMemBlock SyMemBlock; | ||
230 | struct 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 | */ | ||
242 | union 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 | }; | ||
247 | struct 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 | |||
344 | typedef struct SyHashEntry_Pr SyHashEntry_Pr; | ||
345 | typedef struct SyHashEntry SyHashEntry; | ||
346 | typedef struct SyHash SyHash; | ||
347 | /* | ||
348 | * Each public hashtable entry is represented by an instance | ||
349 | * of the following structure. | ||
350 | */ | ||
351 | struct 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 */ | ||
360 | struct 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 | */ | ||
386 | typedef struct SyPRNGCtx SyPRNGCtx; | ||
387 | struct SyPRNGCtx | ||
388 | { | ||
389 | sxu8 i, j; /* State variables */ | ||
390 | unsigned char s[256]; /* State variables */ | ||
391 | sxu16 nMagic; /* Sanity check */ | ||
392 | }; | ||
393 | typedef sxi32 (*ProcRandomSeed)(void *, unsigned int, void *); | ||
394 | /* High resolution timer.*/ | ||
395 | typedef struct sytime sytime; | ||
396 | struct sytime | ||
397 | { | ||
398 | long tm_sec; /* seconds */ | ||
399 | long tm_usec; /* microseconds */ | ||
400 | }; | ||
401 | /* Forward declaration */ | ||
402 | typedef struct SyStream SyStream; | ||
403 | typedef struct SyToken SyToken; | ||
404 | typedef struct SyLex SyLex; | ||
405 | /* | ||
406 | * Tokenizer callback signature. | ||
407 | */ | ||
408 | typedef sxi32 (*ProcTokenizer)(SyStream *, SyToken *, void *, void *); | ||
409 | /* | ||
410 | * Each token in the input is represented by an instance | ||
411 | * of the following structure. | ||
412 | */ | ||
413 | struct 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 | */ | ||
424 | struct 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 | */ | ||
436 | struct 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 | */ | ||
547 | typedef struct SyXMLRawStr SyXMLRawStr; | ||
548 | struct 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 | */ | ||
557 | typedef sxi32 (*ProcXMLStartTagHandler)(SyXMLRawStr *, SyXMLRawStr *, sxu32, SyXMLRawStr *, void *); | ||
558 | typedef sxi32 (*ProcXMLTextHandler)(SyXMLRawStr *, void *); | ||
559 | typedef sxi32 (*ProcXMLEndTagHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); | ||
560 | typedef sxi32 (*ProcXMLPIHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); | ||
561 | typedef sxi32 (*ProcXMLDoctypeHandler)(SyXMLRawStr *, void *); | ||
562 | typedef sxi32 (*ProcXMLSyntaxErrorHandler)(const char *, int, SyToken *, void *); | ||
563 | typedef sxi32 (*ProcXMLStartDocument)(void *); | ||
564 | typedef sxi32 (*ProcXMLNameSpaceStart)(SyXMLRawStr *, SyXMLRawStr *, void *); | ||
565 | typedef sxi32 (*ProcXMLNameSpaceEnd)(SyXMLRawStr *, void *); | ||
566 | typedef 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 */ | ||
575 | enum 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 | */ | ||
602 | typedef struct SyXMLParser SyXMLParser; | ||
603 | struct 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 */ | ||
691 | typedef struct MD5Context MD5Context; | ||
692 | struct MD5Context { | ||
693 | sxu32 buf[4]; | ||
694 | sxu32 bits[2]; | ||
695 | unsigned char in[64]; | ||
696 | }; | ||
697 | /* SHA1 context */ | ||
698 | typedef struct SHA1Context SHA1Context; | ||
699 | struct 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 | */ | ||
713 | struct 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 */ | ||
748 | typedef sxi32 (*ProcMemObjCast)(jx9_value *); | ||
749 | /* Forward reference */ | ||
750 | typedef struct jx9_output_consumer jx9_output_consumer; | ||
751 | typedef struct jx9_user_func jx9_user_func; | ||
752 | typedef 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 | */ | ||
761 | struct 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 | */ | ||
774 | struct 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 | */ | ||
783 | typedef 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 | */ | ||
790 | typedef struct jx9_constant jx9_constant; | ||
791 | struct 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 | }; | ||
797 | typedef 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 | */ | ||
804 | struct jx9_aux_data | ||
805 | { | ||
806 | void *pAuxData; /* Aux data */ | ||
807 | }; | ||
808 | /* Foreign functions signature */ | ||
809 | typedef 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 | */ | ||
816 | struct 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 | */ | ||
829 | struct 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 | */ | ||
848 | struct 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 | */ | ||
866 | struct 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 | */ | ||
888 | struct 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 | }; | ||
895 | struct 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 | */ | ||
911 | struct 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 */ | ||
925 | typedef sxi32 (*ProcErrorGen)(void *, sxi32, sxu32, const char *, ...); | ||
926 | typedef struct jx9_expr_node jx9_expr_node; | ||
927 | typedef struct jx9_expr_op jx9_expr_op; | ||
928 | typedef struct jx9_gen_state jx9_gen_state; | ||
929 | typedef struct GenBlock GenBlock; | ||
930 | typedef sxi32 (*ProcLangConstruct)(jx9_gen_state *); | ||
931 | typedef 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 | */ | ||
939 | struct 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 | */ | ||
951 | struct 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 | */ | ||
970 | struct 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 | */ | ||
993 | struct 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 */ | ||
1008 | typedef struct jx9_vm_func_static_var jx9_vm_func_static_var; | ||
1009 | typedef struct jx9_vm_func_arg jx9_vm_func_arg; | ||
1010 | typedef struct jx9_vm_func jx9_vm_func; | ||
1011 | typedef 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 | */ | ||
1032 | struct 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 | */ | ||
1052 | struct 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 | */ | ||
1069 | struct 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 */ | ||
1084 | typedef struct jx9_builtin_constant jx9_builtin_constant; | ||
1085 | typedef 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 | */ | ||
1092 | struct 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 | */ | ||
1103 | struct 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 | */ | ||
1114 | typedef struct VmInstr VmInstr; | ||
1115 | struct 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 */ | ||
1123 | typedef struct jx9_case_expr jx9_case_expr; | ||
1124 | typedef 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 | */ | ||
1129 | struct 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 | */ | ||
1138 | struct 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 | */ | ||
1155 | struct 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 | */ | ||
1208 | enum 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 | */ | ||
1230 | enum 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 | */ | ||
1305 | enum 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 */ | ||
1425 | JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap); | ||
1426 | JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName); | ||
1427 | JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName); | ||
1428 | /* json.c function prototypes */ | ||
1429 | JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut); | ||
1430 | JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte); | ||
1431 | /* memobj.c function prototypes */ | ||
1432 | JX9_PRIVATE sxi32 jx9MemObjDump(SyBlob *pOut, jx9_value *pObj); | ||
1433 | JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal); | ||
1434 | JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore); | ||
1435 | JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest); | ||
1436 | JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal); | ||
1437 | JX9_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 */ | ||
1440 | JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal); | ||
1441 | #endif | ||
1442 | JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal); | ||
1443 | JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal); | ||
1444 | JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj); | ||
1445 | JX9_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 */ | ||
1448 | JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap); | ||
1449 | #endif | ||
1450 | JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest); | ||
1451 | JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest); | ||
1452 | JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj); | ||
1453 | JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj); | ||
1454 | JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj); | ||
1455 | JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags); | ||
1456 | JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj); | ||
1457 | JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj); | ||
1458 | JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj); | ||
1459 | JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj); | ||
1460 | JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj); | ||
1461 | JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj); | ||
1462 | JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj); | ||
1463 | JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj); | ||
1464 | JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pData); | ||
1465 | /* lex.c function prototypes */ | ||
1466 | JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput, sxu32 nLen, SySet *pOut); | ||
1467 | /* vm.c function prototypes */ | ||
1468 | JX9_PRIVATE void jx9VmReleaseContextValue(jx9_context *pCtx, jx9_value *pValue); | ||
1469 | JX9_PRIVATE sxi32 jx9VmInitFuncState(jx9_vm *pVm, jx9_vm_func *pFunc, const char *zName, sxu32 nByte, | ||
1470 | sxi32 iFlags, void *pUserData); | ||
1471 | JX9_PRIVATE sxi32 jx9VmInstallUserFunction(jx9_vm *pVm, jx9_vm_func *pFunc, SyString *pName); | ||
1472 | JX9_PRIVATE sxi32 jx9VmRegisterConstant(jx9_vm *pVm, const SyString *pName, ProcConstant xExpand, void *pUserData); | ||
1473 | JX9_PRIVATE sxi32 jx9VmInstallForeignFunction(jx9_vm *pVm, const SyString *pName, ProcHostFunction xFunc, void *pUserData); | ||
1474 | JX9_PRIVATE sxi32 jx9VmBlobConsumer(const void *pSrc, unsigned int nLen, void *pUserData); | ||
1475 | JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIndex); | ||
1476 | JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex); | ||
1477 | JX9_PRIVATE sxi32 jx9VmOutputConsume(jx9_vm *pVm, SyString *pString); | ||
1478 | JX9_PRIVATE sxi32 jx9VmOutputConsumeAp(jx9_vm *pVm, const char *zFormat, va_list ap); | ||
1479 | JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap); | ||
1480 | JX9_PRIVATE sxi32 jx9VmThrowError(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zMessage); | ||
1481 | JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData); | ||
1482 | JX9_PRIVATE sxi32 jx9VmDump(jx9_vm *pVm, ProcConsumer xConsumer, void *pUserData); | ||
1483 | JX9_PRIVATE sxi32 jx9VmInit(jx9_vm *pVm, jx9 *pEngine); | ||
1484 | JX9_PRIVATE sxi32 jx9VmConfigure(jx9_vm *pVm, sxi32 nOp, va_list ap); | ||
1485 | JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm); | ||
1486 | JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar); | ||
1487 | JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm); | ||
1488 | JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm); | ||
1489 | JX9_PRIVATE sxi32 jx9VmMakeReady(jx9_vm *pVm); | ||
1490 | JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm); | ||
1491 | JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm); | ||
1492 | JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm); | ||
1493 | JX9_PRIVATE VmInstr *jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex); | ||
1494 | JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm); | ||
1495 | JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer); | ||
1496 | JX9_PRIVATE sxi32 jx9VmEmitInstr(jx9_vm *pVm, sxi32 iOp, sxi32 iP1, sxu32 iP2, void *p3, sxu32 *pIndex); | ||
1497 | JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm); | ||
1498 | JX9_PRIVATE sxi32 jx9VmCallUserFunction(jx9_vm *pVm, jx9_value *pFunc, int nArg, jx9_value **apArg, jx9_value *pResult); | ||
1499 | JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp(jx9_vm *pVm, jx9_value *pFunc, jx9_value *pResult, ...); | ||
1500 | JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm, sxu32 nObjIdx); | ||
1501 | JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen); | ||
1502 | JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue); | ||
1503 | JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew); | ||
1504 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1505 | JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(jx9_vm *pVm, const char **pzDevice, int nByte); | ||
1506 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
1507 | JX9_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 */ | ||
1513 | JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID); | ||
1514 | JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot); | ||
1515 | JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart, SyToken *pEnd, SyToken **ppNext); | ||
1516 | JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn, SyToken *pEnd, sxu32 nTokStart, sxu32 nTokEnd, SyToken **ppEnd); | ||
1517 | JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast); | ||
1518 | JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet); | ||
1519 | /* compile.c function prototypes */ | ||
1520 | JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType); | ||
1521 | JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1522 | JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1523 | JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1524 | JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1525 | JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1526 | JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1527 | JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1528 | JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
1529 | JX9_PRIVATE sxi32 jx9InitCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); | ||
1530 | JX9_PRIVATE sxi32 jx9ResetCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); | ||
1531 | JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...); | ||
1532 | JX9_PRIVATE sxi32 jx9CompileScript(jx9_vm *pVm, SyString *pScript, sxi32 iFlags); | ||
1533 | /* constant.c function prototypes */ | ||
1534 | JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm); | ||
1535 | /* builtin.c function prototypes */ | ||
1536 | JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm); | ||
1537 | /* hashmap.c function prototypes */ | ||
1538 | JX9_PRIVATE jx9_hashmap * jx9NewHashmap(jx9_vm *pVm, sxu32 (*xIntHash)(sxi64), sxu32 (*xBlobHash)(const void *, sxu32)); | ||
1539 | JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm); | ||
1540 | JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS); | ||
1541 | JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap); | ||
1542 | JX9_PRIVATE sxi32 jx9HashmapLookup(jx9_hashmap *pMap, jx9_value *pKey, jx9_hashmap_node **ppNode); | ||
1543 | JX9_PRIVATE sxi32 jx9HashmapInsert(jx9_hashmap *pMap, jx9_value *pKey, jx9_value *pVal); | ||
1544 | JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight); | ||
1545 | JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest); | ||
1546 | JX9_PRIVATE sxi32 jx9HashmapCmp(jx9_hashmap *pLeft, jx9_hashmap *pRight, int bStrict); | ||
1547 | JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap); | ||
1548 | JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap); | ||
1549 | JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode); | ||
1550 | JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore); | ||
1551 | JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode, jx9_value *pKey); | ||
1552 | JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm); | ||
1553 | JX9_PRIVATE sxi32 jx9HashmapWalk(jx9_hashmap *pMap, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); | ||
1554 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1555 | JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut); | ||
1556 | /* builtin.c function prototypes */ | ||
1557 | JX9_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); | ||
1559 | JX9_PRIVATE sxi32 jx9ProcessCsv(const char *zInput, int nByte, int delim, int encl, | ||
1560 | int escape, sxi32 (*xConsumer)(const char *, int, void *), void *pUserData); | ||
1561 | JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData); | ||
1562 | JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen); | ||
1563 | JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection); | ||
1564 | #endif | ||
1565 | /* vfs.c */ | ||
1566 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1567 | JX9_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); | ||
1569 | JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut); | ||
1570 | JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle); | ||
1571 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
1572 | JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen); | ||
1573 | JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm); | ||
1574 | JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void); | ||
1575 | JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm); | ||
1576 | JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm); | ||
1577 | JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm); | ||
1578 | /* lib.c function prototypes */ | ||
1579 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1580 | JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp); | ||
1581 | JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch); | ||
1582 | JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch); | ||
1583 | JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry); | ||
1584 | JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen); | ||
1585 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
1586 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1587 | JX9_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 | ||
1591 | JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen); | ||
1592 | JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len); | ||
1593 | JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx); | ||
1594 | JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx); | ||
1595 | JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]); | ||
1596 | JX9_PRIVATE void SHA1Init(SHA1Context *context); | ||
1597 | JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len); | ||
1598 | JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]); | ||
1599 | JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]); | ||
1600 | #endif | ||
1601 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
1602 | JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen); | ||
1603 | JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void *pUserData); | ||
1604 | JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...); | ||
1605 | JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap); | ||
1606 | JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...); | ||
1607 | JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...); | ||
1608 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1609 | JX9_PRIVATE const char *SyTimeGetMonth(sxi32 iMonth); | ||
1610 | JX9_PRIVATE const char *SyTimeGetDay(sxi32 iDay); | ||
1611 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
1612 | JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8); | ||
1613 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1614 | JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); | ||
1615 | #endif | ||
1616 | JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex); | ||
1617 | JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp); | ||
1618 | JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData); | ||
1619 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1620 | JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); | ||
1621 | JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); | ||
1622 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
1623 | JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen); | ||
1624 | JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
1625 | JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
1626 | JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
1627 | JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
1628 | JX9_PRIVATE sxi32 SyHexToint(sxi32 c); | ||
1629 | JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
1630 | JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
1631 | JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail); | ||
1632 | JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData); | ||
1633 | JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32(*xStep)(SyHashEntry *, void *), void *pUserData); | ||
1634 | JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData); | ||
1635 | JX9_PRIVATE SyHashEntry *SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen); | ||
1636 | JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash); | ||
1637 | JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp); | ||
1638 | JX9_PRIVATE void *SySetAt(SySet *pSet, sxu32 nIdx); | ||
1639 | JX9_PRIVATE void *SySetPop(SySet *pSet); | ||
1640 | JX9_PRIVATE void *SySetPeek(SySet *pSet); | ||
1641 | JX9_PRIVATE sxi32 SySetRelease(SySet *pSet); | ||
1642 | JX9_PRIVATE sxi32 SySetReset(SySet *pSet); | ||
1643 | JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet); | ||
1644 | JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry); | ||
1645 | JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem); | ||
1646 | JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem); | ||
1647 | JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize); | ||
1648 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1649 | JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft); | ||
1650 | #endif | ||
1651 | JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob); | ||
1652 | JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob); | ||
1653 | JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen); | ||
1654 | JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest); | ||
1655 | JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob); | ||
1656 | JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize); | ||
1657 | JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte); | ||
1658 | JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator); | ||
1659 | JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize); | ||
1660 | JX9_PRIVATE char *SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize); | ||
1661 | JX9_PRIVATE void *SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize); | ||
1662 | JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend); | ||
1663 | JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void *pUserData); | ||
1664 | JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void *pUserData); | ||
1665 | JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent); | ||
1666 | #if 0 | ||
1667 | /* Not used in the current release of the JX9 engine */ | ||
1668 | JX9_PRIVATE void *SyMemBackendPoolRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); | ||
1669 | #endif | ||
1670 | JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void *pChunk); | ||
1671 | JX9_PRIVATE void *SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte); | ||
1672 | JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void *pChunk); | ||
1673 | JX9_PRIVATE void *SyMemBackendRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); | ||
1674 | JX9_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte); | ||
1675 | JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen); | ||
1676 | JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize); | ||
1677 | JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize); | ||
1678 | JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen); | ||
1679 | JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen); | ||
1680 | #if !defined(JX9_DISABLE_BUILTIN_FUNC) || defined(__APPLE__) | ||
1681 | JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen); | ||
1682 | #endif | ||
1683 | JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos); | ||
1684 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
1685 | JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); | ||
1686 | #endif | ||
1687 | JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); | ||
1688 | JX9_PRIVATE sxu32 SyStrlen(const char *zSrc); | ||
1689 | #if defined(JX9_ENABLE_THREADS) | ||
1690 | JX9_PRIVATE const SyMutexMethods *SyMutexExportMethods(void); | ||
1691 | JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods); | ||
1692 | JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend); | ||
1693 | #endif | ||
1694 | JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb); | ||
1695 | JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB); | ||
1696 | JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb); | ||
1697 | JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB); | ||
1698 | JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64); | ||
1699 | JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64); | ||
1700 | JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64); | ||
1701 | JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32); | ||
1702 | JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16); | ||
1703 | JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut); | ||
1704 | JX9_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 | */ | ||
37 | static 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 | */ | ||
88 | JX9_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 | */ | ||
130 | static 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 | */ | ||
238 | JX9_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 | */ | ||
261 | static 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 | ||
329 | End: | ||
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 | */ | ||
339 | static 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 | */ | ||
365 | static 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 | */ | ||
403 | JX9_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 | */ | ||
416 | JX9_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 | */ | ||
424 | JX9_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; | ||
486 | Release: | ||
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 | */ | ||
495 | JX9_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 | */ | ||
542 | static 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; | ||
616 | Release: | ||
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 | */ | ||
626 | JX9_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 | */ | ||
664 | JX9_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 | */ | ||
721 | JX9_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 | */ | ||
751 | JX9_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 | */ | ||
800 | JX9_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 | } | ||
831 | JX9_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 | */ | ||
849 | JX9_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 | } | ||
884 | JX9_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 | */ | ||
901 | JX9_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 | */ | ||
921 | JX9_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 | */ | ||
947 | JX9_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 | */ | ||
964 | JX9_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 | */ | ||
977 | JX9_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 | */ | ||
990 | JX9_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 | */ | ||
1003 | JX9_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 | */ | ||
1016 | JX9_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 | */ | ||
1037 | JX9_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 | */ | ||
1049 | JX9_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 | */ | ||
1065 | JX9_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 | */ | ||
1073 | JX9_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 | */ | ||
1081 | JX9_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 | */ | ||
1089 | JX9_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 | */ | ||
1097 | JX9_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 | */ | ||
1107 | JX9_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 | */ | ||
1115 | JX9_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 | */ | ||
1136 | JX9_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 | */ | ||
1150 | JX9_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 | */ | ||
1158 | JX9_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 | */ | ||
1174 | JX9_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 | */ | ||
1190 | JX9_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 | */ | ||
1198 | JX9_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 | */ | ||
1224 | static 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 | */ | ||
1248 | JX9_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 | */ | ||
1265 | JX9_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 | */ | ||
1283 | JX9_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 | */ | ||
1314 | JX9_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 | */ | ||
1332 | JX9_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 | */ | ||
1347 | JX9_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 | */ | ||
1371 | JX9_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 | */ | ||
1386 | JX9_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 | */ | ||
1401 | JX9_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 | */ | ||
1413 | JX9_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 | */ | ||
1429 | JX9_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 | */ | ||
1439 | JX9_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 | */ | ||
1451 | JX9_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 | */ | ||
1459 | JX9_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 | */ | ||
1471 | JX9_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 | */ | ||
1481 | JX9_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 | */ | ||
1491 | JX9_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 | */ | ||
1499 | JX9_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 | */ | ||
1509 | JX9_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 | */ | ||
1521 | JX9_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 | */ | ||
1533 | JX9_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 | */ | ||
1545 | JX9_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 | */ | ||
1555 | JX9_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 | */ | ||
1569 | JX9_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 | */ | ||
1589 | JX9_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 | */ | ||
1607 | JX9_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 | */ | ||
1617 | JX9_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 | */ | ||
1630 | JX9_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 | */ | ||
1639 | JX9_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 | */ | ||
1647 | JX9_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 | */ | ||
1655 | JX9_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 | */ | ||
1663 | JX9_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 | */ | ||
1671 | JX9_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 | */ | ||
1679 | JX9_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 | */ | ||
1689 | JX9_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 | */ | ||
1699 | JX9_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 | */ | ||
1707 | JX9_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 | */ | ||
1715 | JX9_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 | */ | ||
1731 | JX9_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 | */ | ||
1739 | JX9_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 | */ | ||
35 | static 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 | */ | ||
55 | static 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 | */ | ||
75 | static 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 | */ | ||
93 | static 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 | */ | ||
111 | static 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 | */ | ||
129 | static 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 | */ | ||
147 | static 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 | */ | ||
165 | static 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 | */ | ||
183 | static 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 | */ | ||
201 | static 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 | */ | ||
218 | static 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 | */ | ||
239 | static 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 | */ | ||
260 | static 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 | */ | ||
282 | static 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 | */ | ||
313 | static 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 | */ | ||
336 | static 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 | */ | ||
359 | static 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 | */ | ||
382 | static 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 | */ | ||
405 | static 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 | */ | ||
428 | static 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 | */ | ||
451 | static 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 | */ | ||
474 | static 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 | */ | ||
497 | static 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 | */ | ||
520 | static 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 | */ | ||
543 | static 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 | */ | ||
566 | static 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 | */ | ||
589 | static 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 | */ | ||
613 | static 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 | */ | ||
637 | static 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 | */ | ||
672 | static 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 | */ | ||
700 | static 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 | */ | ||
728 | static 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 | */ | ||
751 | static 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 | */ | ||
769 | static 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 | */ | ||
797 | static 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 | */ | ||
829 | static 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 | */ | ||
877 | static 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 | */ | ||
900 | static 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 | */ | ||
923 | static 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 | */ | ||
946 | static 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 | */ | ||
999 | static 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 | */ | ||
1034 | static 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 | */ | ||
1073 | static 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 | */ | ||
1170 | static 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 | */ | ||
1250 | static 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 | */ | ||
1337 | static 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 | */ | ||
1418 | static 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) ==> '&' | ||
1471 | * '"' (double quote) ==> '"' when ENT_NOQUOTES is not set. | ||
1472 | * "'" (single quote) ==> ''' only when ENT_QUOTES is set. | ||
1473 | * '<' (less than) ==> '<' | ||
1474 | * '>' (greater than) ==> '>' | ||
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 | */ | ||
1490 | static 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 '&' */ | ||
1529 | jx9_result_string(pCtx, "&", (int)sizeof("&")-1); | ||
1530 | }else if( c == '<' ){ | ||
1531 | /* Expand '<' */ | ||
1532 | jx9_result_string(pCtx, "<", (int)sizeof("<")-1); | ||
1533 | }else if( c == '>' ){ | ||
1534 | /* Expand '>' */ | ||
1535 | jx9_result_string(pCtx, ">", (int)sizeof(">")-1); | ||
1536 | }else if( c == '\'' ){ | ||
1537 | if( iFlags & 0x02 /*ENT_QUOTES*/ ){ | ||
1538 | /* Expand ''' */ | ||
1539 | jx9_result_string(pCtx, "'", (int)sizeof("'")-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 '"' */ | ||
1547 | jx9_result_string(pCtx, """, (int)sizeof(""")-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 | */ | ||
1572 | static 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("&")-1 && SyStrnicmp(zIn, "&", sizeof("&")-1) == 0 ){ | ||
1608 | /* & ==> '&' */ | ||
1609 | jx9_result_string(pCtx, "&", (int)sizeof(char)); | ||
1610 | nJump = (int)sizeof("&")-1; | ||
1611 | }else if( nLen >= (int)sizeof("<")-1 && SyStrnicmp(zIn, "<", sizeof("<")-1) == 0 ){ | ||
1612 | /* < ==> < */ | ||
1613 | jx9_result_string(pCtx, "<", (int)sizeof(char)); | ||
1614 | nJump = (int)sizeof("<")-1; | ||
1615 | }else if( nLen >= (int)sizeof(">")-1 && SyStrnicmp(zIn, ">", sizeof(">")-1) == 0 ){ | ||
1616 | /* > ==> '>' */ | ||
1617 | jx9_result_string(pCtx, ">", (int)sizeof(char)); | ||
1618 | nJump = (int)sizeof(">")-1; | ||
1619 | }else if( nLen >= (int)sizeof(""")-1 && SyStrnicmp(zIn, """, sizeof(""")-1) == 0 ){ | ||
1620 | /* " ==> '"' */ | ||
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, """, (int)sizeof(""")-1); | ||
1626 | } | ||
1627 | nJump = (int)sizeof(""")-1; | ||
1628 | }else if( nLen >= (int)sizeof("'")-1 && SyStrnicmp(zIn, "'", sizeof("'")-1) == 0 ){ | ||
1629 | /* ' ==> ''' */ | ||
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, "'", (int)sizeof("'")-1); | ||
1636 | } | ||
1637 | nJump = (int)sizeof("'")-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 | */ | ||
1652 | static const char *azHtmlEscape[] = { | ||
1653 | "<", "<", ">", ">", "&", "&", """, "\"", "'", "'", | ||
1654 | "!", "!", "$", "$", "#", "#", "%", "%", "(", "(", | ||
1655 | ")", ")", "{", "{", "}", "}", "=", "=", "+", "+", | ||
1656 | "?", "?", "[", "[", "]", "]", "@", "@", ",", "," | ||
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 | */ | ||
1666 | static 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 | */ | ||
1714 | static 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: '<' ==> '<"] */ | ||
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 | */ | ||
1779 | static 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 | */ | ||
1855 | static 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 | */ | ||
1875 | static 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 | */ | ||
1903 | static 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 | */ | ||
1937 | static 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 | */ | ||
1966 | static 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 | */ | ||
1995 | struct 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 | */ | ||
2008 | static 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 | */ | ||
2059 | static 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 | */ | ||
2123 | static 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 | */ | ||
2195 | static 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 | */ | ||
2291 | static 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 | */ | ||
2383 | static 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 | */ | ||
2458 | static 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 | */ | ||
2529 | static 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 | */ | ||
2583 | static 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 | */ | ||
2637 | static 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 | */ | ||
2668 | static 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 | */ | ||
2687 | static 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 | */ | ||
2702 | static 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 */ | ||
2723 | typedef 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 | */ | ||
2730 | static 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 | */ | ||
2787 | static 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 | */ | ||
2840 | static 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 | */ | ||
2894 | static 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 | */ | ||
2955 | static 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 | */ | ||
3016 | static 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 | */ | ||
3099 | static 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 | */ | ||
3181 | static 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 | */ | ||
3229 | static 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 | */ | ||
3272 | static 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 | */ | ||
3322 | static 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 | */ | ||
3444 | typedef struct jx9_fmt_info jx9_fmt_info; | ||
3445 | struct 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 | */ | ||
3468 | static 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 | */ | ||
3486 | static 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 | */ | ||
3517 | JX9_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 | */ | ||
3979 | static 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 | */ | ||
3995 | static 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 | */ | ||
4018 | static 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 | */ | ||
4036 | static 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 | */ | ||
4068 | static 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 | */ | ||
4108 | static 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 | */ | ||
4149 | static 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 | */ | ||
4204 | static 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 | */ | ||
4248 | static 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 | */ | ||
4289 | static 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 | */ | ||
4316 | JX9_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 | */ | ||
4381 | JX9_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 | */ | ||
4413 | static 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 | */ | ||
4474 | static 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 | */ | ||
4517 | static 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 | */ | ||
4566 | JX9_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 | */ | ||
4635 | static 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 | */ | ||
4671 | static 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 | */ | ||
4745 | static 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 | */ | ||
4773 | static 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 | */ | ||
4805 | static 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 | */ | ||
4858 | static 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 | */ | ||
4949 | static 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 | */ | ||
5032 | static 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 | */ | ||
5085 | static 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 | */ | ||
5143 | static 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 | */ | ||
5210 | static 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 | */ | ||
5227 | static 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 */ | ||
5260 | typedef struct strtok_aux_data strtok_aux_data; | ||
5261 | struct 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 | */ | ||
5288 | static 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 | */ | ||
5399 | static 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 | */ | ||
5494 | typedef struct str_replace_data str_replace_data; | ||
5495 | struct 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 | */ | ||
5528 | static 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 | */ | ||
5563 | static 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 | */ | ||
5601 | static 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 | */ | ||
5657 | static 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 | */ | ||
5798 | static 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 | */ | ||
5882 | JX9_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 | */ | ||
6076 | static 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 | */ | ||
6108 | static 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 | */ | ||
6151 | static 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 | */ | ||
6194 | static 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 | */ | ||
6241 | static 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 | */ | ||
6289 | static 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 | */ | ||
6337 | static 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 | */ | ||
6386 | static 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 | */ | ||
6434 | static 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 | */ | ||
6483 | static 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 | */ | ||
6530 | static 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 | */ | ||
6573 | static 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 | */ | ||
6626 | struct 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 | */ | ||
6660 | static 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 | */ | ||
6686 | static 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 | */ | ||
6739 | static 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 | */ | ||
6841 | static 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 */ | ||
6889 | static 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 | */ | ||
6930 | static 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 | */ | ||
7185 | static 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 | */ | ||
7393 | static 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 | */ | ||
7454 | static 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 | */ | ||
7519 | static 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 | */ | ||
7590 | static 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 | */ | ||
7748 | static 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 | */ | ||
7924 | static 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 | */ | ||
7998 | static 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 | */ | ||
8014 | static 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 | */ | ||
8044 | static 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 | */ | ||
8075 | static 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 | */ | ||
8105 | static 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 */ | ||
8127 | static 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 | */ | ||
8287 | JX9_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 */ | ||
23 | typedef struct LangConstruct LangConstruct; | ||
24 | typedef 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 | */ | ||
43 | struct 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 | */ | ||
52 | struct 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 */ | ||
82 | static 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 | */ | ||
92 | static 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 | */ | ||
104 | static 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 | */ | ||
123 | static 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 | */ | ||
137 | static 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 | */ | ||
147 | static 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 | */ | ||
158 | static 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 | */ | ||
182 | static 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 | */ | ||
206 | static 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 | */ | ||
239 | static void GenStateReleaseBlock(GenBlock *pBlock) | ||
240 | { | ||
241 | SySetRelease(&pBlock->aPostContFix); | ||
242 | SySetRelease(&pBlock->aJumpFix); | ||
243 | } | ||
244 | /* | ||
245 | * Release a block. | ||
246 | */ | ||
247 | static 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 | */ | ||
257 | static 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 | */ | ||
285 | static 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 | */ | ||
307 | static 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 | */ | ||
341 | static 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 | */ | ||
366 | static 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 | */ | ||
410 | static 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 | */ | ||
446 | JX9_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 | */ | ||
539 | static 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 | */ | ||
572 | static 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 | */ | ||
626 | static 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 | */ | ||
880 | JX9_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 | */ | ||
891 | JX9_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 | */ | ||
970 | static 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 | */ | ||
991 | JX9_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 | */ | ||
1025 | static 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 | */ | ||
1042 | JX9_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 | */ | ||
1112 | JX9_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 | */ | ||
1189 | JX9_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 */ | ||
1240 | static 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 | */ | ||
1259 | JX9_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 | */ | ||
1314 | static 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 | */ | ||
1370 | static 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 */ | ||
1415 | static 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 | */ | ||
1424 | static 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 | */ | ||
1487 | static 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; | ||
1560 | Synchronize: | ||
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 | */ | ||
1589 | static 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 | */ | ||
1744 | static 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 | */ | ||
1780 | static 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; | ||
1955 | Synchronize: | ||
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 | */ | ||
1994 | static 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; | ||
2108 | Synchronize: | ||
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 | */ | ||
2132 | static 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 | */ | ||
2156 | static 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 | */ | ||
2183 | static 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; | ||
2268 | Synchronize: | ||
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 | */ | ||
2294 | static 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; | ||
2359 | Synchronize: | ||
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 | */ | ||
2386 | static 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 | */ | ||
2441 | static 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 | */ | ||
2488 | static 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 | */ | ||
2557 | static 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; | ||
2725 | Synchronize: | ||
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 | */ | ||
2777 | static 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 | */ | ||
2834 | static 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 | */ | ||
2972 | static 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 | */ | ||
3020 | static 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 */ | ||
3083 | OutOfMem: | ||
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 | */ | ||
3093 | static 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 | */ | ||
3143 | static 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 | */ | ||
3334 | static 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 | */ | ||
3423 | JX9_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 | */ | ||
3443 | static 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 | */ | ||
3463 | static 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 | */ | ||
3487 | static 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 | */ | ||
3554 | JX9_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 | */ | ||
3589 | JX9_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 | */ | ||
3613 | JX9_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 | */ | ||
3641 | JX9_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 | */ | ||
23 | static 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 | */ | ||
38 | static 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 | */ | ||
58 | static 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 | */ | ||
72 | static 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 | */ | ||
81 | static 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 | */ | ||
90 | static 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 | */ | ||
103 | static 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 | */ | ||
119 | static 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 | */ | ||
141 | static 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 | */ | ||
163 | static 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 | */ | ||
180 | static 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 | */ | ||
205 | static 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 | */ | ||
214 | static 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 | */ | ||
223 | static 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 | */ | ||
232 | static 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 | */ | ||
241 | static 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 | */ | ||
250 | static 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 | */ | ||
259 | static 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 | */ | ||
268 | static 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 | */ | ||
277 | static 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 | */ | ||
286 | static 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 | */ | ||
295 | static 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 | */ | ||
304 | static 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 | */ | ||
313 | static 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 | */ | ||
322 | static 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 | */ | ||
331 | static 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 | */ | ||
340 | static 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 | */ | ||
349 | static 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 | */ | ||
358 | static 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 | */ | ||
367 | static 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 | */ | ||
376 | static 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 | */ | ||
386 | static 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 | */ | ||
395 | static 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 | */ | ||
404 | static 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 | */ | ||
413 | static 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 | */ | ||
422 | static 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 | */ | ||
431 | static 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 | */ | ||
440 | static 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 | */ | ||
449 | static 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 | */ | ||
458 | static 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 | */ | ||
467 | static 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 | */ | ||
476 | static 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 | */ | ||
485 | static 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 | */ | ||
494 | static 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 | */ | ||
503 | static 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 | */ | ||
512 | static 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 | */ | ||
521 | static 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 | */ | ||
530 | static 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 | */ | ||
540 | static 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 | */ | ||
549 | static 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 | */ | ||
558 | static 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 | */ | ||
567 | static 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 | */ | ||
576 | static 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 | */ | ||
585 | static 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 | */ | ||
594 | static 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 | */ | ||
603 | static 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 | */ | ||
612 | static 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 | */ | ||
621 | static 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 | */ | ||
630 | static 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 | */ | ||
639 | static 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 | */ | ||
648 | static 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 | */ | ||
657 | static 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 | */ | ||
666 | static 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 | */ | ||
675 | static 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 | */ | ||
684 | static 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 | */ | ||
693 | static 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 | */ | ||
702 | static 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 | */ | ||
711 | static 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 | */ | ||
721 | static 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 | */ | ||
731 | static 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 | */ | ||
740 | static 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 | */ | ||
749 | static 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 | */ | ||
758 | static 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 | */ | ||
767 | static 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 | */ | ||
776 | static 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 | */ | ||
785 | static 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 | */ | ||
794 | static 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 | */ | ||
803 | static 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 | */ | ||
812 | static 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 | */ | ||
821 | static 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 | */ | ||
830 | static 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 | */ | ||
839 | static 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 | */ | ||
848 | static 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 | */ | ||
857 | static 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 | */ | ||
866 | static 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 | */ | ||
875 | static 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 | */ | ||
884 | static 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 | */ | ||
893 | static 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 | */ | ||
902 | static 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 | */ | ||
911 | static 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 | */ | ||
920 | static 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 | */ | ||
929 | static 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 | */ | ||
938 | static 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 | */ | ||
947 | static 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 | */ | ||
956 | static 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 | */ | ||
965 | static 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 | */ | ||
974 | static 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 | */ | ||
983 | static 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 | */ | ||
992 | static 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 | */ | ||
1001 | static 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 | */ | ||
1010 | static 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 | */ | ||
1019 | static 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 | */ | ||
1028 | static 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 | */ | ||
1037 | static 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 | */ | ||
1046 | static 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 | */ | ||
1055 | static 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 | */ | ||
1064 | static 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 | */ | ||
1073 | static 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 | */ | ||
1082 | static 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 | */ | ||
1091 | static 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 | */ | ||
1100 | static 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 | */ | ||
1109 | static 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 | */ | ||
1118 | static 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 | */ | ||
1127 | static 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 | */ | ||
1136 | static 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 | */ | ||
1145 | static 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 | */ | ||
1154 | static 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 | */ | ||
1165 | static 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 | */ | ||
1176 | static 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 | */ | ||
1187 | static 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 | */ | ||
1196 | static 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 | */ | ||
1205 | static 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 | */ | ||
1214 | static 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 | */ | ||
1223 | static 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 | */ | ||
1232 | static 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 | */ | ||
1241 | static 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 | */ | ||
1250 | static 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 | */ | ||
1259 | static 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 | */ | ||
1267 | static 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 | */ | ||
1413 | JX9_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 | */ | ||
24 | static sxu32 IntHash(sxi64 iKey) | ||
25 | { | ||
26 | return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8)); | ||
27 | } | ||
28 | /* | ||
29 | * Default hash function for string/BLOB keys. | ||
30 | */ | ||
31 | static 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 | */ | ||
50 | static 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 | */ | ||
92 | static 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 | */ | ||
115 | static 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 | */ | ||
137 | static 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 | */ | ||
159 | static 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 | */ | ||
200 | static 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 | */ | ||
258 | static 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 | */ | ||
296 | static 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 | */ | ||
335 | static 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 | */ | ||
376 | static 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 | */ | ||
418 | static 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 | */ | ||
446 | static 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); | ||
472 | result: | ||
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 | */ | ||
489 | static 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 | } | ||
531 | IntKey: | ||
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 | */ | ||
576 | static 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 | */ | ||
588 | static 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 | */ | ||
621 | static 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 | */ | ||
646 | static 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 | */ | ||
681 | static 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 | */ | ||
785 | JX9_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 | */ | ||
858 | static 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 | */ | ||
897 | JX9_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 | */ | ||
970 | JX9_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 | */ | ||
1022 | JX9_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 | */ | ||
1061 | JX9_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 | */ | ||
1110 | JX9_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 | */ | ||
1155 | JX9_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 | */ | ||
1167 | JX9_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 | */ | ||
1188 | JX9_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 | */ | ||
1201 | JX9_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 | */ | ||
1212 | JX9_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 | */ | ||
1226 | JX9_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 | */ | ||
1235 | JX9_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 | */ | ||
1251 | JX9_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 | */ | ||
1272 | JX9_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 */ | ||
1298 | typedef 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 | */ | ||
1313 | static 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 | ||
1355 | static 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 | */ | ||
1397 | static 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 | */ | ||
1436 | static 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 | */ | ||
1475 | static 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 | */ | ||
1511 | static 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 | */ | ||
1561 | static 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 | */ | ||
1605 | static 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 | */ | ||
1648 | static 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 | */ | ||
1693 | static 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 | */ | ||
1728 | static 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 | */ | ||
1756 | static 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 | */ | ||
1802 | static 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 | */ | ||
1839 | static 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 | */ | ||
1894 | static 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 | */ | ||
1939 | static 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 | */ | ||
1965 | static 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 | */ | ||
1991 | static 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 | */ | ||
2015 | static 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 | */ | ||
2045 | static 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 | */ | ||
2079 | static 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 | */ | ||
2124 | static 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 | */ | ||
2185 | static 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 | */ | ||
2241 | static 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 | */ | ||
2269 | static 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 | */ | ||
2317 | static 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 | */ | ||
2356 | static 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 | */ | ||
2397 | static 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 | */ | ||
2425 | static 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 | */ | ||
2504 | static 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 | */ | ||
2576 | static 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 | } | ||
2604 | static 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 | */ | ||
2635 | static 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 | */ | ||
2679 | static 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 | } | ||
2708 | static 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 | */ | ||
2740 | static 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 | */ | ||
2790 | static 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 | */ | ||
2857 | static 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 | */ | ||
2900 | static 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 | */ | ||
2934 | JX9_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 | */ | ||
2950 | JX9_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 */ | ||
28 | static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); | ||
29 | static 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 | */ | ||
34 | typedef struct json_private_data json_private_data; | ||
35 | struct 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 | */ | ||
58 | static 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 | */ | ||
134 | static 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 | */ | ||
157 | static 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 | */ | ||
193 | JX9_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 | */ | ||
226 | static 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 | */ | ||
403 | typedef int (*ProcJSONConsumer)(jx9_context *, jx9_value *, jx9_value *, void *); | ||
404 | /* | ||
405 | * JSON decoder state is kept in the following structure. | ||
406 | */ | ||
407 | typedef struct json_decoder json_decoder; | ||
408 | struct 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 */ | ||
420 | static 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 | */ | ||
425 | static 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 | */ | ||
479 | static 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 | */ | ||
663 | static 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 | */ | ||
675 | static 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 | */ | ||
687 | JX9_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 */ | ||
19 | static sxu32 keywordCode(const char *z,int n); | ||
20 | static 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 | */ | ||
26 | static 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 */ | ||
514 | static 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 | */ | ||
624 | static 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 | */ | ||
743 | JX9_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__) | ||
33 | struct SyMutex | ||
34 | { | ||
35 | CRITICAL_SECTION sMutex; | ||
36 | sxu32 nType; /* Mutex type, one of SXMUTEX_TYPE_* */ | ||
37 | }; | ||
38 | /* Preallocated static mutex */ | ||
39 | static 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 | }; | ||
47 | static BOOL winMutexInit = FALSE; | ||
48 | static LONG winMutexLock = 0; | ||
49 | |||
50 | static 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 | } | ||
68 | static 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 | } | ||
83 | static 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 | } | ||
103 | static 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 | } | ||
110 | static void WinMutexEnter(SyMutex *pMutex) | ||
111 | { | ||
112 | EnterCriticalSection(&pMutex->sMutex); | ||
113 | } | ||
114 | static 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 | } | ||
129 | static void WinMutexLeave(SyMutex *pMutex) | ||
130 | { | ||
131 | LeaveCriticalSection(&pMutex->sMutex); | ||
132 | } | ||
133 | /* Export Windows mutex interfaces */ | ||
134 | static 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 | }; | ||
143 | JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) | ||
144 | { | ||
145 | return &sWinMutexMethods; | ||
146 | } | ||
147 | #elif defined(__UNIXES__) | ||
148 | #include <pthread.h> | ||
149 | struct SyMutex | ||
150 | { | ||
151 | pthread_mutex_t sMutex; | ||
152 | sxu32 nType; | ||
153 | }; | ||
154 | static 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 | } | ||
192 | static 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 | } | ||
199 | static void UnixMutexEnter(SyMutex *pMutex) | ||
200 | { | ||
201 | pthread_mutex_lock(&pMutex->sMutex); | ||
202 | } | ||
203 | static void UnixMutexLeave(SyMutex *pMutex) | ||
204 | { | ||
205 | pthread_mutex_unlock(&pMutex->sMutex); | ||
206 | } | ||
207 | /* Export pthread mutex interfaces */ | ||
208 | static 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 | }; | ||
217 | JX9_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 | */ | ||
225 | struct SyMutex | ||
226 | { | ||
227 | sxu32 nType; | ||
228 | }; | ||
229 | static SyMutex * DummyMutexNew(int nType) | ||
230 | { | ||
231 | static SyMutex sMutex; | ||
232 | SXUNUSED(nType); | ||
233 | return &sMutex; | ||
234 | } | ||
235 | static void DummyMutexRelease(SyMutex *pMutex) | ||
236 | { | ||
237 | SXUNUSED(pMutex); | ||
238 | } | ||
239 | static void DummyMutexEnter(SyMutex *pMutex) | ||
240 | { | ||
241 | SXUNUSED(pMutex); | ||
242 | } | ||
243 | static void DummyMutexLeave(SyMutex *pMutex) | ||
244 | { | ||
245 | SXUNUSED(pMutex); | ||
246 | } | ||
247 | /* Export the dummy mutex interfaces */ | ||
248 | static 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 | }; | ||
257 | JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) | ||
258 | { | ||
259 | return &sDummyMutexMethods; | ||
260 | } | ||
261 | #endif /* __WINNT__ */ | ||
262 | #endif /* JX9_ENABLE_THREADS */ | ||
263 | static 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 | } | ||
273 | static 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 | } | ||
283 | static 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 */ | ||
292 | JX9_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 | } | ||
308 | JX9_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 | ||
323 | JX9_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 */ | ||
338 | JX9_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 | ||
354 | JX9_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 | ||
374 | JX9_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 | } | ||
391 | JX9_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 */ | ||
415 | JX9_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 | } | ||
432 | JX9_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 | } | ||
444 | JX9_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 | } | ||
455 | static 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 | } | ||
465 | static 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 | } | ||
480 | static void MemOSFree(void *pBlock) | ||
481 | { | ||
482 | void *pChunk; | ||
483 | pChunk = (void *)(((char *)pBlock)-sizeof(sxu32)); | ||
484 | SyOSHeapFree(pChunk); | ||
485 | } | ||
486 | static 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 */ | ||
493 | static const SyMemMethods sOSAllocMethods = { | ||
494 | MemOSAlloc, | ||
495 | MemOSRealloc, | ||
496 | MemOSFree, | ||
497 | MemOSChunkSize, | ||
498 | 0, | ||
499 | 0, | ||
500 | 0 | ||
501 | }; | ||
502 | static 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 | } | ||
531 | JX9_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 | } | ||
548 | static 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 | } | ||
591 | JX9_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 | } | ||
608 | static 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 | } | ||
630 | JX9_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) | ||
651 | JX9_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 | } | ||
668 | JX9_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)) | ||
691 | static 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 | } | ||
719 | static 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 | } | ||
758 | JX9_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 | } | ||
775 | static 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 | } | ||
796 | JX9_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 | ||
814 | static 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 | } | ||
852 | JX9_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 | ||
870 | JX9_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 | } | ||
894 | JX9_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 | } | ||
922 | JX9_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 | } | ||
949 | static 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 | } | ||
998 | JX9_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 | } | ||
1016 | JX9_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 | } | ||
1030 | JX9_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 | } | ||
1039 | JX9_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 | } | ||
1053 | JX9_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 | } | ||
1066 | JX9_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 | ||
1082 | static 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 | } | ||
1128 | JX9_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 | } | ||
1147 | JX9_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 | } | ||
1158 | JX9_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 | } | ||
1166 | JX9_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 | } | ||
1177 | JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen) | ||
1178 | { | ||
1179 | if( nNewLen < pBlob->nByte ){ | ||
1180 | pBlob->nByte = nNewLen; | ||
1181 | } | ||
1182 | return SXRET_OK; | ||
1183 | } | ||
1184 | JX9_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 | ||
1195 | JX9_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 */ | ||
1214 | JX9_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 | } | ||
1225 | JX9_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 | } | ||
1248 | JX9_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 | } | ||
1263 | JX9_PRIVATE sxi32 SySetReset(SySet *pSet) | ||
1264 | { | ||
1265 | pSet->nUsed = 0; | ||
1266 | pSet->nCursor = 0; | ||
1267 | return SXRET_OK; | ||
1268 | } | ||
1269 | JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet) | ||
1270 | { | ||
1271 | pSet->nCursor = 0; | ||
1272 | return SXRET_OK; | ||
1273 | } | ||
1274 | JX9_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 | } | ||
1289 | JX9_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 | } | ||
1300 | JX9_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 | } | ||
1309 | JX9_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 | } | ||
1321 | JX9_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 */ | ||
1332 | struct 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) | ||
1344 | JX9_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 | } | ||
1367 | JX9_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 | } | ||
1393 | static 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 | } | ||
1413 | JX9_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 | } | ||
1431 | static 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 | } | ||
1452 | JX9_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 | } | ||
1468 | JX9_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 | } | ||
1490 | static 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 | } | ||
1524 | static 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 | } | ||
1541 | JX9_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 */ | ||
1573 | JX9_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" | ||
1645 | JX9_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 | } | ||
1697 | JX9_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 | } | ||
1749 | JX9_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 | } | ||
1771 | JX9_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 | } | ||
1821 | JX9_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 | } | ||
1869 | JX9_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 | } | ||
1921 | JX9_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 */ | ||
2027 | JX9_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 | ||
2042 | JX9_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 | } | ||
2085 | JX9_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 ) | ||
2140 | JX9_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 | } | ||
2166 | JX9_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 | } | ||
2223 | JX9_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 == '.' ) | ||
2237 | JX9_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 */ | ||
2282 | static 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 | } | ||
2298 | JX9_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 | ||
2384 | static const char *zEngDay[] = { | ||
2385 | "Sunday", "Monday", "Tuesday", "Wednesday", | ||
2386 | "Thursday", "Friday", "Saturday" | ||
2387 | }; | ||
2388 | static const char *zEngMonth[] = { | ||
2389 | "January", "February", "March", "April", | ||
2390 | "May", "June", "July", "August", | ||
2391 | "September", "October", "November", "December" | ||
2392 | }; | ||
2393 | static const char * GetDay(sxi32 i) | ||
2394 | { | ||
2395 | return zEngDay[ i % 7 ]; | ||
2396 | } | ||
2397 | static const char * GetMonth(sxi32 i) | ||
2398 | { | ||
2399 | return zEngMonth[ i % 12 ]; | ||
2400 | } | ||
2401 | JX9_PRIVATE const char * SyTimeGetDay(sxi32 iDay) | ||
2402 | { | ||
2403 | return GetDay(iDay); | ||
2404 | } | ||
2405 | JX9_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 | */ | ||
2442 | typedef struct SyFmtInfo SyFmtInfo; | ||
2443 | struct 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 | }; | ||
2452 | typedef struct SyFmtConsumer SyFmtConsumer; | ||
2453 | struct 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 | ||
2467 | static 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 | */ | ||
2486 | static 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 | */ | ||
2492 | static 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 | } | ||
2973 | static 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 | } | ||
2995 | static 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 | } | ||
3026 | JX9_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 | } | ||
3040 | JX9_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 | } | ||
3054 | JX9_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 | } | ||
3065 | JX9_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)*/ | ||
3189 | static 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 | } | ||
3197 | static 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 | */ | ||
3209 | static 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 | } | ||
3234 | static 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 | } | ||
3242 | static 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 | } | ||
3267 | static 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 | */ | ||
3314 | static 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; | ||
3380 | update: | ||
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 | } | ||
3385 | static 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 | */ | ||
3404 | static 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 | } | ||
3476 | JX9_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 | } | ||
3531 | JX9_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 | } | ||
3576 | JX9_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 | ||
3634 | static 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 | } | ||
3673 | JX9_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 | */ | ||
3711 | static 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 | } | ||
3724 | JX9_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 | */ | ||
3768 | static 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 | */ | ||
3808 | static 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 | */ | ||
3894 | JX9_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 | */ | ||
3934 | JX9_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 | ||
3977 | JX9_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 | } | ||
3988 | JX9_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 | |||
4059 | static 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 | */ | ||
4120 | JX9_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 | */ | ||
4132 | JX9_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 | */ | ||
4153 | JX9_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 | |||
4179 | JX9_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 | } | ||
4187 | static 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) ) ) | ||
4254 | static 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 | } | ||
4271 | JX9_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 | ||
4278 | JX9_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 */ | ||
4305 | JX9_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 | } | ||
4312 | JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB) | ||
4313 | { | ||
4314 | *uNB = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); | ||
4315 | } | ||
4316 | JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb) | ||
4317 | { | ||
4318 | buf[1] = nb & 0xFF ; nb >>=8; | ||
4319 | buf[0] = (unsigned char)nb ; | ||
4320 | } | ||
4321 | JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB) | ||
4322 | { | ||
4323 | *uNB = buf[1] + (buf[0] << 8); | ||
4324 | } | ||
4325 | JX9_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 | } | ||
4336 | JX9_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 | } | ||
4343 | JX9_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 | } | ||
4351 | JX9_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 | } | ||
4359 | JX9_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 | } | ||
4367 | JX9_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 | } | ||
4374 | JX9_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 | |||
38 | It is possible to circumvent this licensing policy through the purchase of a commercial software | ||
39 | license from Symisc Systems consisting of terms and conditions which are negotiated at the time | ||
40 | of sale. | ||
41 | For 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 | */ | ||
34 | static 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 | */ | ||
69 | JX9_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 | */ | ||
102 | static 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 | */ | ||
117 | static 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 | */ | ||
151 | static 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 | */ | ||
198 | static 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 | */ | ||
232 | static 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 | */ | ||
281 | static 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 | */ | ||
305 | JX9_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 | */ | ||
320 | JX9_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 | */ | ||
334 | JX9_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 | */ | ||
348 | JX9_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 | */ | ||
363 | JX9_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 | */ | ||
374 | JX9_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 | */ | ||
411 | JX9_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 | */ | ||
432 | JX9_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 | */ | ||
465 | JX9_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 | */ | ||
507 | JX9_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 | */ | ||
553 | JX9_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 | */ | ||
564 | JX9_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 | */ | ||
578 | JX9_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 | */ | ||
593 | JX9_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 | */ | ||
609 | JX9_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 | */ | ||
625 | JX9_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 | */ | ||
640 | JX9_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 | */ | ||
661 | JX9_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 | */ | ||
680 | JX9_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 | */ | ||
696 | JX9_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 | */ | ||
726 | JX9_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 | */ | ||
745 | JX9_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 | */ | ||
811 | JX9_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 | */ | ||
922 | JX9_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 | */ | ||
1010 | JX9_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 | */ | ||
1039 | JX9_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 | */ | ||
33 | static 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 */ | ||
109 | static 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 | */ | ||
119 | JX9_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 | */ | ||
163 | JX9_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 | */ | ||
196 | JX9_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 | */ | ||
210 | JX9_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 | */ | ||
265 | static 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; | ||
309 | Synchronize: | ||
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 | */ | ||
320 | static 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 | */ | ||
419 | static 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 | */ | ||
546 | static 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 | */ | ||
577 | JX9_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 | */ | ||
593 | static 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 */ | ||
617 | static 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 | */ | ||
624 | static 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 | */ | ||
1135 | JX9_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 | */ | ||
24 | JX9_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 | */ | ||
70 | static 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 | */ | ||
108 | static 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 | */ | ||
145 | static 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 | */ | ||
180 | static 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 | */ | ||
218 | static 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 | */ | ||
263 | static 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 | */ | ||
316 | static 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 | */ | ||
354 | static 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 | */ | ||
394 | static 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 | */ | ||
441 | static 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 | */ | ||
478 | static 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 | */ | ||
518 | static 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 | */ | ||
561 | static 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 | */ | ||
603 | static 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 | */ | ||
643 | static 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 | */ | ||
681 | static 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 | */ | ||
719 | static 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 | */ | ||
757 | static 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 | */ | ||
795 | static 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 | */ | ||
833 | static 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 | */ | ||
871 | static 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 | */ | ||
909 | static 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 | */ | ||
947 | static 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 | */ | ||
985 | static 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 | */ | ||
1023 | static 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 | */ | ||
1061 | static 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 | */ | ||
1100 | static 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 | */ | ||
1152 | static 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 | */ | ||
1221 | static 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 | */ | ||
1276 | static 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 | */ | ||
1318 | static 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 | */ | ||
1394 | static 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 | */ | ||
1452 | static 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 | */ | ||
1487 | static 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 | */ | ||
1546 | typedef struct path_info path_info; | ||
1547 | struct 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 | */ | ||
1557 | static 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 | */ | ||
1628 | static 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 | */ | ||
1750 | typedef 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 | */ | ||
1758 | static 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 | */ | ||
1813 | static 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 | */ | ||
1923 | static 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 | */ | ||
1949 | static 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 | */ | ||
1990 | static 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 | */ | ||
2020 | static 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 | */ | ||
2061 | static 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 | */ | ||
2101 | static 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 | */ | ||
2134 | static 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 | */ | ||
2163 | static 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 | */ | ||
2192 | static 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 | */ | ||
2223 | static 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 | */ | ||
2254 | static 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 | */ | ||
2297 | static 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 | */ | ||
2412 | typedef struct io_private io_private; | ||
2413 | struct 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 */ | ||
2426 | static 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 | */ | ||
2440 | static 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 | */ | ||
2498 | static 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 | */ | ||
2557 | static 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 | */ | ||
2602 | static 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 | */ | ||
2651 | static 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 | */ | ||
2696 | static 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 | */ | ||
2751 | static 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 | */ | ||
2791 | static 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 | */ | ||
2813 | static 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 | */ | ||
2893 | JX9_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 | */ | ||
2970 | JX9_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 | */ | ||
2993 | JX9_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 | */ | ||
3011 | static 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 | */ | ||
3069 | static 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 | */ | ||
3127 | static 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 | */ | ||
3210 | static 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 | */ | ||
3316 | static 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 | */ | ||
3378 | static 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 | */ | ||
3426 | static 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 */ | ||
3460 | static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut); | ||
3461 | static 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 | */ | ||
3471 | static 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 | */ | ||
3518 | static 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 | */ | ||
3583 | static 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 | */ | ||
3660 | static 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 | */ | ||
3756 | static 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 | */ | ||
3860 | static 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 | */ | ||
3980 | static 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 | */ | ||
4069 | static 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 | */ | ||
4130 | static 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 | */ | ||
4200 | static 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 | */ | ||
4249 | static 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 */ | ||
4303 | struct 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 | */ | ||
4315 | static 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 | */ | ||
4373 | static 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 | */ | ||
4444 | typedef struct fprintf_data fprintf_data; | ||
4445 | struct 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 | */ | ||
4453 | static 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 | */ | ||
4479 | static 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 | */ | ||
4538 | static 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 | */ | ||
4619 | static 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 | */ | ||
4716 | static 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 | */ | ||
4727 | static 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 | */ | ||
4737 | static void ResetIOPrivate(io_private *pDev) | ||
4738 | { | ||
4739 | SyBlobReset(&pDev->sBuffer); | ||
4740 | pDev->nOfft = 0; | ||
4741 | } | ||
4742 | /* Forward declaration */ | ||
4743 | static 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 | */ | ||
4764 | static 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 | */ | ||
4837 | static 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 | */ | ||
4886 | static 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 | */ | ||
4903 | static 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 | */ | ||
4974 | static 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 | */ | ||
5052 | static 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 | */ | ||
5107 | typedef struct zip_raw_data zip_raw_data; | ||
5108 | struct 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 | */ | ||
5133 | static 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 | } | ||
5227 | success: | ||
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 | */ | ||
5243 | static 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 | */ | ||
5285 | static 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 | */ | ||
5333 | static 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 | */ | ||
5374 | static 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 | */ | ||
5407 | static 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 | */ | ||
5440 | static 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 | */ | ||
5471 | static 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 | */ | ||
5505 | static 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 | */ | ||
5567 | static 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 | */ | ||
5600 | static 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]*/ | ||
5650 | static 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 | */ | ||
5715 | static 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 | */ | ||
5739 | static 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 | */ | ||
5750 | static 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 *) */ | ||
5767 | static 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 *) */ | ||
5780 | static 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) */ | ||
5799 | static 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 *) */ | ||
5814 | static 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 *) */ | ||
5827 | static 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 *) */ | ||
5843 | static 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 *) */ | ||
5862 | static 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) */ | ||
5892 | static int WinVfs_Sleep(unsigned int uSec) | ||
5893 | { | ||
5894 | Sleep(uSec/1000/*uSec per Millisec */); | ||
5895 | return JX9_OK; | ||
5896 | } | ||
5897 | /* int (*xUnlink)(const char *) */ | ||
5898 | static 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 *) */ | ||
5911 | static 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 *) */ | ||
5941 | static 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 *) */ | ||
5971 | static 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 */ | ||
5987 | static 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 *) */ | ||
6001 | static 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 */ | ||
6029 | static 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 */ | ||
6040 | static 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) */ | ||
6049 | static 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 *) */ | ||
6080 | static 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 *) */ | ||
6108 | static 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 *) */ | ||
6136 | static 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 *) */ | ||
6165 | static 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 *) */ | ||
6222 | static 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 *) */ | ||
6238 | static 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 *) */ | ||
6254 | static 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 *) */ | ||
6279 | static 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 *) */ | ||
6300 | static 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 *) */ | ||
6331 | static 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 *) */ | ||
6354 | static 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 *) */ | ||
6361 | static 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) */ | ||
6399 | static void WinVfs_Unmap(void *pView, jx9_int64 nSize) | ||
6400 | { | ||
6401 | nSize = 0; /* Compiler warning */ | ||
6402 | UnmapViewOfFile(pView); | ||
6403 | } | ||
6404 | /* void (*xTempDir)(jx9_context *) */ | ||
6405 | static 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) */ | ||
6418 | static 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 */ | ||
6428 | static 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 **) */ | ||
6478 | static 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 | */ | ||
6541 | typedef struct WinDir_Info WinDir_Info; | ||
6542 | struct 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 **) */ | ||
6550 | static 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 *) */ | ||
6592 | static 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 *); */ | ||
6602 | static void WinFile_Close(void *pUserData) | ||
6603 | { | ||
6604 | HANDLE pHandle = (HANDLE)pUserData; | ||
6605 | CloseHandle(pHandle); | ||
6606 | } | ||
6607 | /* int (*xReadDir)(void *, jx9_context *) */ | ||
6608 | static 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 *) */ | ||
6648 | static 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); */ | ||
6660 | static 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); */ | ||
6673 | static 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) */ | ||
6701 | static 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) */ | ||
6726 | static 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 *) */ | ||
6749 | static 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) */ | ||
6760 | static 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 *); */ | ||
6775 | static 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 *) */ | ||
6783 | static 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 */ | ||
6828 | static 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 *) */ | ||
6870 | static 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 *) */ | ||
6877 | static 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) */ | ||
6890 | static 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 *) */ | ||
6898 | static 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 *) */ | ||
6905 | static 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 *) */ | ||
6917 | static 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 *) */ | ||
6924 | static 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) */ | ||
6943 | static int UnixVfs_Sleep(unsigned int uSec) | ||
6944 | { | ||
6945 | usleep(uSec); | ||
6946 | return JX9_OK; | ||
6947 | } | ||
6948 | /* int (*xUnlink)(const char *) */ | ||
6949 | static 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 *) */ | ||
6956 | static 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 *) */ | ||
6963 | static 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) */ | ||
6974 | static 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 *) */ | ||
6987 | static 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 *) */ | ||
6998 | static 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 *) */ | ||
7009 | static 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 *) */ | ||
7020 | static 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 *) */ | ||
7067 | static 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) */ | ||
7114 | static 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 *) */ | ||
7121 | static 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 *) */ | ||
7141 | static 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 *) */ | ||
7161 | static 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 *) */ | ||
7173 | static 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 *) */ | ||
7185 | static 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 *) */ | ||
7192 | static 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 *) */ | ||
7199 | static 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 *) */ | ||
7206 | static 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 *) */ | ||
7234 | static 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 *) */ | ||
7245 | static 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 *) */ | ||
7252 | static 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) */ | ||
7279 | static void UnixVfs_Unmap(void *pView, jx9_int64 nSize) | ||
7280 | { | ||
7281 | munmap(pView, (size_t)nSize); | ||
7282 | } | ||
7283 | /* void (*xTempDir)(jx9_context *) */ | ||
7284 | static 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) */ | ||
7313 | static unsigned int UnixVfs_ProcessId(void) | ||
7314 | { | ||
7315 | return (unsigned int)getpid(); | ||
7316 | } | ||
7317 | /* int (*xUid)(void) */ | ||
7318 | static int UnixVfs_uid(void) | ||
7319 | { | ||
7320 | return (int)getuid(); | ||
7321 | } | ||
7322 | /* int (*xGid)(void) */ | ||
7323 | static int UnixVfs_gid(void) | ||
7324 | { | ||
7325 | return (int)getgid(); | ||
7326 | } | ||
7327 | /* int (*xUmask)(int) */ | ||
7328 | static 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 *) */ | ||
7335 | static 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) */ | ||
7353 | static 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 *) */ | ||
7366 | static 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 */ | ||
7373 | static 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 **) */ | ||
7421 | static 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 **) */ | ||
7475 | static 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 *) */ | ||
7489 | static void UnixDir_Close(void *pUserData) | ||
7490 | { | ||
7491 | closedir((DIR *)pUserData); | ||
7492 | } | ||
7493 | /* void (*xClose)(void *); */ | ||
7494 | static void UnixFile_Close(void *pUserData) | ||
7495 | { | ||
7496 | close(SX_PTR_TO_INT(pUserData)); | ||
7497 | } | ||
7498 | /* int (*xReadDir)(void *, jx9_context *) */ | ||
7499 | static 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 *) */ | ||
7524 | static void UnixDir_Rewind(void *pUserData) | ||
7525 | { | ||
7526 | rewinddir((DIR *)pUserData); | ||
7527 | } | ||
7528 | /* jx9_int64 (*xRead)(void *, void *, jx9_int64); */ | ||
7529 | static 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); */ | ||
7540 | static 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) */ | ||
7566 | static 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) */ | ||
7588 | static 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 *) */ | ||
7607 | static 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) */ | ||
7614 | static 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 *); */ | ||
7624 | static 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 *) */ | ||
7631 | static 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 */ | ||
7678 | static 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 | */ | ||
7709 | JX9_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 | */ | ||
7756 | typedef 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 */ | ||
7763 | struct 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 | */ | ||
7775 | static 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 **) */ | ||
7828 | static 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) */ | ||
7858 | static 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) */ | ||
7895 | static 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 *) */ | ||
7941 | static 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 */ | ||
7953 | static 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 | */ | ||
7976 | static 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 | */ | ||
7994 | JX9_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 | */ | ||
8130 | JX9_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 | */ | ||
8162 | JX9_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 | */ | ||
8194 | JX9_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 | */ | ||
45 | struct 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 | */ | ||
61 | typedef struct VmSlot VmSlot; | ||
62 | struct 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 | */ | ||
73 | typedef struct SyhttpUri SyhttpUri; | ||
74 | struct 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 | */ | ||
93 | typedef struct SyhttpHeader SyhttpHeader; | ||
94 | struct 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 | */ | ||
125 | JX9_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 | */ | ||
175 | static 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 | */ | ||
216 | JX9_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 | */ | ||
253 | JX9_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 | */ | ||
281 | JX9_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 | */ | ||
312 | JX9_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 | */ | ||
343 | JX9_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 | */ | ||
357 | JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm) | ||
358 | { | ||
359 | return pVm->pByteContainer; | ||
360 | } | ||
361 | /* | ||
362 | * Extract the VM instruction rooted at nIndex. | ||
363 | */ | ||
364 | JX9_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 | */ | ||
373 | JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm) | ||
374 | { | ||
375 | return SySetUsed(pVm->pByteContainer); | ||
376 | } | ||
377 | /* | ||
378 | * Pop the last VM instruction. | ||
379 | */ | ||
380 | JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm) | ||
381 | { | ||
382 | return (VmInstr *)SySetPop(pVm->pByteContainer); | ||
383 | } | ||
384 | /* | ||
385 | * Peek the last VM instruction. | ||
386 | */ | ||
387 | JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm) | ||
388 | { | ||
389 | return (VmInstr *)SySetPeek(pVm->pByteContainer); | ||
390 | } | ||
391 | /* | ||
392 | * Allocate a new virtual machine frame. | ||
393 | */ | ||
394 | static 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 | */ | ||
418 | static 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 | */ | ||
444 | static 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 | */ | ||
474 | static 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 | */ | ||
501 | static 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 | */ | ||
527 | static 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 | */ | ||
605 | static 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 | */ | ||
610 | JX9_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 | */ | ||
633 | static 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 */ | ||
653 | static 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 | */ | ||
802 | JX9_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; | ||
889 | Err: | ||
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 | */ | ||
904 | JX9_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 | */ | ||
922 | static 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 */ | ||
949 | static 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 | */ | ||
955 | JX9_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 | */ | ||
1006 | JX9_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 | */ | ||
1022 | JX9_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 | */ | ||
1040 | static 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 | */ | ||
1062 | static 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 | */ | ||
1098 | JX9_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 | */ | ||
1124 | static 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 | */ | ||
1142 | JX9_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 | */ | ||
1174 | static 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 | */ | ||
1256 | static 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 | */ | ||
1281 | static 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 */ | ||
1316 | static 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 | */ | ||
1330 | JX9_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 */ | ||
1560 | static 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 | */ | ||
1568 | static 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 | */ | ||
1611 | static 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 | */ | ||
1633 | JX9_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 | */ | ||
1681 | static 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 | */ | ||
1733 | static 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 | */ | ||
1750 | JX9_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 */ | ||
1757 | static 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 | */ | ||
1771 | static 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 | */ | ||
1811 | case 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 | */ | ||
1831 | case 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 | */ | ||
1859 | case 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 | */ | ||
1868 | case 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 | */ | ||
1892 | case 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 | */ | ||
1916 | case JX9_OP_NOOP: | ||
1917 | break; | ||
1918 | /* | ||
1919 | * POP: P1 * * | ||
1920 | * | ||
1921 | * Pop P1 elements from the operand stack. | ||
1922 | */ | ||
1923 | case 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 | */ | ||
1937 | case 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 | */ | ||
1954 | case 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 | */ | ||
1971 | case 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 | */ | ||
1986 | case 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 | */ | ||
2001 | case 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 | */ | ||
2014 | case 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 | */ | ||
2028 | case 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 | */ | ||
2048 | case 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 | */ | ||
2086 | case 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 | */ | ||
2136 | case 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 | */ | ||
2183 | case 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 | */ | ||
2287 | case 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 | */ | ||
2348 | case 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 | */ | ||
2436 | case 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 | */ | ||
2485 | case 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 | */ | ||
2532 | case 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 | */ | ||
2552 | case 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 | */ | ||
2573 | case 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 | */ | ||
2591 | case 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 | */ | ||
2609 | case JX9_OP_MUL: | ||
2610 | case 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 | */ | ||
2664 | case 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 | */ | ||
2682 | case 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 | */ | ||
2711 | case 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 | */ | ||
2754 | case 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 | */ | ||
2807 | case 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 | */ | ||
2847 | case 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 | */ | ||
2892 | case 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 | */ | ||
2934 | case 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 | */ | ||
2992 | case JX9_OP_BAND: | ||
2993 | case JX9_OP_BOR: | ||
2994 | case 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 | */ | ||
3045 | case JX9_OP_BAND_STORE: | ||
3046 | case JX9_OP_BOR_STORE: | ||
3047 | case 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 | */ | ||
3100 | case JX9_OP_SHL: | ||
3101 | case 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 | */ | ||
3145 | case JX9_OP_SHL_STORE: | ||
3146 | case 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 | */ | ||
3187 | case 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 | */ | ||
3223 | case 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 | */ | ||
3265 | case JX9_OP_LAND: | ||
3266 | case 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 | */ | ||
3307 | case 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 | */ | ||
3344 | case JX9_OP_EQ: | ||
3345 | case 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 | */ | ||
3382 | case 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 | */ | ||
3416 | case 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 | */ | ||
3459 | case JX9_OP_LT: | ||
3460 | case 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 | */ | ||
3508 | case JX9_OP_GT: | ||
3509 | case 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 | */ | ||
3543 | case 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 | */ | ||
3619 | case 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 | */ | ||
3659 | case 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 | */ | ||
3697 | case 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 | */ | ||
3745 | case 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 | */ | ||
3770 | case 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 | */ | ||
4004 | case 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(;;) */ | ||
4037 | Done: | ||
4038 | SySetRelease(&aArg); | ||
4039 | return SXRET_OK; | ||
4040 | Abort: | ||
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 | */ | ||
4053 | static 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 | */ | ||
4074 | JX9_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 | */ | ||
4096 | JX9_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 | */ | ||
4114 | JX9_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 | */ | ||
4135 | JX9_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 | */ | ||
4162 | static 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 | */ | ||
4247 | JX9_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 | */ | ||
4263 | JX9_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 | */ | ||
4287 | static 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 | */ | ||
4315 | static 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 | */ | ||
4363 | static 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 | */ | ||
4406 | static 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 | */ | ||
4437 | JX9_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 | */ | ||
4469 | static 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 | */ | ||
4489 | static 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 | */ | ||
4515 | static 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 | */ | ||
4545 | JX9_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 | */ | ||
4606 | JX9_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 | */ | ||
4641 | static 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 | */ | ||
4666 | static 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 | */ | ||
4688 | static 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 | */ | ||
4720 | JX9_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 | */ | ||
4732 | JX9_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 | */ | ||
4757 | static 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 | */ | ||
4789 | static 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 | */ | ||
4809 | static 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 | */ | ||
4845 | static 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 | */ | ||
4881 | static 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 | */ | ||
4903 | JX9_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 | */ | ||
4927 | static 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 | */ | ||
4950 | static 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 | */ | ||
4968 | static 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 | */ | ||
5006 | static 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 */ | ||
5024 | static 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 | */ | ||
5053 | static 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 | */ | ||
5259 | typedef struct extract_aux_data extract_aux_data; | ||
5260 | struct 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 */ | ||
5270 | static 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 | */ | ||
5310 | static 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 | */ | ||
5345 | static 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 | */ | ||
5409 | static 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 | } | ||
5464 | Cleanup: | ||
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 | */ | ||
5473 | static 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 | */ | ||
5490 | JX9_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 | */ | ||
5555 | static 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 | */ | ||
5629 | static 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 | */ | ||
5663 | static 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 | */ | ||
5707 | static 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 | */ | ||
5725 | static 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 | */ | ||
5754 | struct 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 */ | ||
5762 | static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData); | ||
5763 | /* | ||
5764 | * Extract short or long argument option values. | ||
5765 | */ | ||
5766 | static 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 | */ | ||
5893 | static 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 | */ | ||
5976 | static 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 | */ | ||
6041 | static 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 | */ | ||
6106 | static 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 | } | ||
6154 | JX9_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 | */ | ||
6173 | static 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 | */ | ||
6209 | static 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 | */ | ||
6239 | static 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. */ | ||
6260 | static 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 | */ | ||
6308 | static 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 | */ | ||
6333 | JX9_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 | } | ||
6507 | PathSplit: | ||
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 | */ | ||
6540 | static 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 */ | ||
53 | typedef struct lhash_kv_engine lhash_kv_engine; | ||
54 | typedef 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 | */ | ||
59 | typedef struct lhcell lhcell; | ||
60 | struct 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 | */ | ||
81 | typedef struct lhphdr lhphdr; | ||
82 | struct 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 | */ | ||
92 | struct 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 | */ | ||
111 | typedef struct lhash_bmap_rec lhash_bmap_rec; | ||
112 | struct 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 | }; | ||
119 | typedef struct lhash_bmap_page lhash_bmap_page; | ||
120 | struct 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 | */ | ||
131 | struct 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 | */ | ||
155 | static 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 | */ | ||
179 | static 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 | */ | ||
248 | static 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 | */ | ||
288 | static 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 | */ | ||
305 | static 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 | */ | ||
329 | static 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 | */ | ||
401 | struct 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 | */ | ||
410 | static 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 */ | ||
431 | static 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 | */ | ||
435 | static 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 | */ | ||
482 | static 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 | */ | ||
540 | static 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 | */ | ||
579 | static 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 | */ | ||
620 | static 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 | */ | ||
638 | static 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 | */ | ||
670 | static 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 | */ | ||
715 | static 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 | */ | ||
798 | static 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 | */ | ||
878 | static 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 | */ | ||
956 | static 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 | */ | ||
1009 | static 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 | */ | ||
1045 | static 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 | */ | ||
1129 | static 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 | */ | ||
1218 | static 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 | */ | ||
1302 | static 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 | */ | ||
1333 | static 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 | */ | ||
1355 | static 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 | */ | ||
1484 | static 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 | */ | ||
1505 | static 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 */ | ||
1525 | static lhcell * lhFindSibeling(lhcell *pCell); | ||
1526 | /* | ||
1527 | * Unlink a cell. | ||
1528 | */ | ||
1529 | static 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 | */ | ||
1564 | static 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 | */ | ||
1599 | static 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 | */ | ||
1618 | static 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 | */ | ||
1651 | static 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 | */ | ||
1795 | static 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 | */ | ||
1957 | static 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 */ | ||
1989 | static 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 | */ | ||
1998 | static 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 | */ | ||
2071 | static 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; | ||
2144 | fail: | ||
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 | */ | ||
2152 | static 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 | */ | ||
2173 | static 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 | */ | ||
2214 | static 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; | ||
2276 | fail: | ||
2277 | SyBlobRelease(&sWorker); | ||
2278 | return rc; | ||
2279 | } | ||
2280 | /* | ||
2281 | * Perform the infamous linear hash split operation. | ||
2282 | */ | ||
2283 | static 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; | ||
2358 | fail: | ||
2359 | pEngine->pIo->xPageUnref(pNew->pRaw); | ||
2360 | return rc; | ||
2361 | } | ||
2362 | /* | ||
2363 | * Store a record in the target page. | ||
2364 | */ | ||
2365 | static 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 | */ | ||
2392 | static 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); | ||
2417 | retry: | ||
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 | */ | ||
2485 | static 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 | */ | ||
2498 | static 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 | */ | ||
2511 | static 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 | */ | ||
2549 | static 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 | */ | ||
2587 | static 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 | */ | ||
2614 | static 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 | */ | ||
2635 | static 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; | ||
2669 | err: | ||
2670 | SyMemBackendRelease(&pHash->sAllocator); | ||
2671 | return rc; | ||
2672 | } | ||
2673 | /* | ||
2674 | * Exported: xRelease() method. | ||
2675 | * Release the Key value storage engine. | ||
2676 | */ | ||
2677 | static 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 | */ | ||
2687 | static 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 | */ | ||
2723 | typedef struct lhash_kv_cursor lhash_kv_cursor; | ||
2724 | struct 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 | */ | ||
2743 | static 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 | */ | ||
2757 | static 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 | */ | ||
2796 | static 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 | */ | ||
2835 | static 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 | */ | ||
2843 | static 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 | */ | ||
2865 | static 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 | */ | ||
2887 | static void lhCursorReset(unqlite_kv_cursor *pCursor) | ||
2888 | { | ||
2889 | lhCursorFirst(pCursor); | ||
2890 | } | ||
2891 | /* | ||
2892 | * Point to the next record. | ||
2893 | */ | ||
2894 | static 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 | */ | ||
2916 | static 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 | */ | ||
2938 | static 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 | */ | ||
2956 | static 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 | */ | ||
2974 | static 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 | */ | ||
2997 | static 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 | */ | ||
3015 | static 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 | */ | ||
3033 | static 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 | */ | ||
3053 | UNQLITE_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 */ | ||
29 | typedef struct mem_hash_kv_engine mem_hash_kv_engine; | ||
30 | /* | ||
31 | * Each record is storead in an instance of the following structure. | ||
32 | */ | ||
33 | typedef struct mem_hash_record mem_hash_record; | ||
34 | struct 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 | */ | ||
49 | struct 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 | */ | ||
65 | static 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 | */ | ||
109 | static 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 | */ | ||
127 | static 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 | */ | ||
151 | static 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 | */ | ||
178 | static 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 | */ | ||
273 | typedef struct mem_hash_cursor mem_hash_cursor; | ||
274 | struct 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 | */ | ||
283 | static 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 | */ | ||
293 | static 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 | */ | ||
303 | static 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 | */ | ||
313 | static 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 | */ | ||
321 | static 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 | */ | ||
333 | static 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 | */ | ||
345 | static 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 | */ | ||
357 | static 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 | */ | ||
369 | static 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 | */ | ||
384 | static 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 | */ | ||
399 | static 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 | */ | ||
407 | static 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 | */ | ||
425 | static 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 | */ | ||
443 | static 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 | */ | ||
464 | static 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 | */ | ||
492 | static 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 | */ | ||
501 | static 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 | */ | ||
535 | static 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 | */ | ||
591 | static 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 | */ | ||
649 | UNQLITE_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 | */ | ||
24 | UNQLITE_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 | } | ||
28 | UNQLITE_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 | } | ||
32 | UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size) | ||
33 | { | ||
34 | return id->pMethods->xTruncate(id, size); | ||
35 | } | ||
36 | UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags) | ||
37 | { | ||
38 | return id->pMethods->xSync(id, flags); | ||
39 | } | ||
40 | UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize) | ||
41 | { | ||
42 | return id->pMethods->xFileSize(id, pSize); | ||
43 | } | ||
44 | UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType) | ||
45 | { | ||
46 | return id->pMethods->xLock(id, lockType); | ||
47 | } | ||
48 | UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType) | ||
49 | { | ||
50 | return id->pMethods->xUnlock(id, lockType); | ||
51 | } | ||
52 | UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut) | ||
53 | { | ||
54 | return id->pMethods->xCheckReservedLock(id, pResOut); | ||
55 | } | ||
56 | UNQLITE_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 | */ | ||
67 | UNQLITE_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 | } | ||
98 | UNQLITE_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 | } | ||
107 | UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync){ | ||
108 | return pVfs->xDelete(pVfs, zPath, dirSync); | ||
109 | } | ||
110 | UNQLITE_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 */ | ||
79 | typedef struct unixInodeInfo unixInodeInfo; /* An i-node */ | ||
80 | typedef 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 | */ | ||
87 | struct 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 | */ | ||
96 | typedef struct unixFile unixFile; | ||
97 | struct 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 | */ | ||
140 | static 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 | } | ||
149 | static 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 | */ | ||
168 | static 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 | */ | ||
303 | struct 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 | */ | ||
316 | struct 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 | |||
327 | static unixInodeInfo *inodeList = 0; | ||
328 | /* | ||
329 | * Local memory allocation stuff. | ||
330 | */ | ||
331 | static 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 | } | ||
339 | static 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 | */ | ||
354 | static 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 | */ | ||
380 | static 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 | */ | ||
408 | static 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 | */ | ||
486 | static 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 | */ | ||
545 | static 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 | } | ||
718 | end_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 | */ | ||
726 | static 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 | */ | ||
747 | static 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 | |||
881 | end_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 | */ | ||
895 | static 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 | */ | ||
905 | static 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 | */ | ||
932 | static 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 | */ | ||
975 | static 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 | */ | ||
1008 | static 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 | */ | ||
1037 | static 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 | */ | ||
1068 | static 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 | */ | ||
1133 | static 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 | */ | ||
1189 | static 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 | */ | ||
1233 | static 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 | */ | ||
1248 | static 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 | */ | ||
1280 | static 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 | */ | ||
1288 | static 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 | */ | ||
1310 | static 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 | */ | ||
1380 | static 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 | */ | ||
1415 | static 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 | */ | ||
1465 | static 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 | */ | ||
1501 | static 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); | ||
1603 | open_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 | */ | ||
1613 | static 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 | */ | ||
1649 | static 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 | */ | ||
1665 | static 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 | */ | ||
1687 | static 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 | */ | ||
1727 | static 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 | */ | ||
1750 | UNQLITE_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__ | ||
32 | typedef 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 | */ | ||
46 | typedef struct winFile winFile; | ||
47 | struct 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 | */ | ||
69 | static 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 | */ | ||
90 | static 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 | */ | ||
115 | static 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 | */ | ||
136 | char *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 | */ | ||
160 | static 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 | ||
193 | static 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 | */ | ||
208 | static 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 | */ | ||
237 | static 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 | */ | ||
273 | static 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 | */ | ||
299 | static 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 | */ | ||
312 | static 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 | */ | ||
337 | static 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 | */ | ||
352 | static 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 | */ | ||
386 | static 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 | */ | ||
497 | static 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 | */ | ||
523 | static 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 | */ | ||
555 | static 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 | */ | ||
562 | static 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 | */ | ||
584 | static 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 | ||
604 | static 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 | */ | ||
634 | static 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 | */ | ||
691 | static 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 | */ | ||
727 | static 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 | */ | ||
770 | static 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 | */ | ||
778 | static 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 | */ | ||
803 | static 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 | */ | ||
843 | static 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 | */ | ||
922 | UNQLITE_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 | */ | ||
146 | static 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 | */ | ||
159 | typedef struct Page Page; | ||
160 | struct 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 | */ | ||
193 | struct 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 | */ | ||
246 | static 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 | */ | ||
261 | static 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 | */ | ||
275 | static 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 | */ | ||
286 | static 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 | */ | ||
308 | static 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 | */ | ||
327 | static 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 | */ | ||
352 | static 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 | */ | ||
373 | static void page_ref(Page *pPage) | ||
374 | { | ||
375 | pPage->nRef++; | ||
376 | } | ||
377 | /* | ||
378 | * Release an in-memory page after its reference count reach zero. | ||
379 | */ | ||
380 | static 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 */ | ||
399 | static int pager_unlink_page(Pager *pPager,Page *pPage); | ||
400 | /* | ||
401 | * Decrement the reference count of a given page. | ||
402 | */ | ||
403 | static 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 | */ | ||
438 | static 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 | */ | ||
492 | static 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 | */ | ||
510 | static 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 | */ | ||
526 | static 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 | */ | ||
546 | static 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 | */ | ||
584 | static 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 | ||
626 | static 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 | */ | ||
667 | static 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 | ||
709 | static 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 | */ | ||
771 | static 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 | */ | ||
864 | static 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 | */ | ||
905 | static 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 | */ | ||
919 | static 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 | */ | ||
1003 | static 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 | } | ||
1044 | end_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 | */ | ||
1066 | static 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 | */ | ||
1085 | static 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 | */ | ||
1113 | static 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 | */ | ||
1144 | static 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 | */ | ||
1192 | static 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); | ||
1245 | fail: | ||
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 | */ | ||
1258 | static 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 | */ | ||
1297 | static 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 | */ | ||
1353 | static 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 | */ | ||
1422 | static 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 | */ | ||
1458 | static 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 | */ | ||
1531 | UNQLITE_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; | ||
1574 | fail: | ||
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 | */ | ||
1585 | static 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); | ||
1619 | finish: | ||
1620 | if( rc == UNQLITE_OK ){ | ||
1621 | pPager->iState = PAGER_WRITER_CACHEMOD; | ||
1622 | return UNQLITE_OK; | ||
1623 | } | ||
1624 | fail: | ||
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 | */ | ||
1638 | static 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 | */ | ||
1686 | static 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 | */ | ||
1740 | static 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 | */ | ||
1785 | static 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 | */ | ||
1826 | static 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 | */ | ||
1888 | static 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 | */ | ||
1913 | static 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 | */ | ||
1966 | UNQLITE_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; | ||
1983 | fail: | ||
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 | */ | ||
1992 | static 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 | */ | ||
2080 | UNQLITE_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 | */ | ||
2135 | static 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 | */ | ||
2149 | static 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 | */ | ||
2191 | static 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 | */ | ||
2243 | static 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 | */ | ||
2264 | UNQLITE_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 | */ | ||
2300 | UNQLITE_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 | */ | ||
2317 | static 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 */ | ||
2335 | static 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 | */ | ||
2340 | UNQLITE_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; | ||
2392 | fail: | ||
2393 | SyMemBackendFree(&pDb->sMem,pEngine); | ||
2394 | SyMemBackendFree(&pDb->sMem,pIo); | ||
2395 | return rc; | ||
2396 | } | ||
2397 | /* | ||
2398 | * Return the underlying KV storage engine instance. | ||
2399 | */ | ||
2400 | UNQLITE_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 | */ | ||
2413 | UNQLITE_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; | ||
2531 | fail: | ||
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 | */ | ||
2539 | UNQLITE_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 | */ | ||
2550 | UNQLITE_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 | */ | ||
2575 | UNQLITE_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 | */ | ||
2589 | UNQLITE_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 | */ | ||
2599 | static 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 | */ | ||
2608 | static 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 | */ | ||
2617 | static 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 | */ | ||
2633 | static 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 | */ | ||
2646 | static 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 | */ | ||
2659 | static 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 | */ | ||
2678 | static 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 | */ | ||
2712 | static 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 | */ | ||
2722 | static 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 | */ | ||
2732 | static 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 | */ | ||
2739 | static int unqliteKvIoPageSize(unqlite_kv_handle pHandle) | ||
2740 | { | ||
2741 | return ((Pager *)pHandle)->iPageSize; | ||
2742 | } | ||
2743 | /* | ||
2744 | * Refer to the declaration of the [Pager] structure | ||
2745 | */ | ||
2746 | static 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 | */ | ||
2754 | static 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 | */ | ||
2763 | static 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 | */ | ||
2772 | static 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 | */ | ||
2780 | static 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 | ||
91 | extern "C" { | ||
92 | #endif | ||
93 | /* Forward declaration to public objects */ | ||
94 | typedef struct unqlite_io_methods unqlite_io_methods; | ||
95 | typedef struct unqlite_kv_methods unqlite_kv_methods; | ||
96 | typedef struct unqlite_kv_engine unqlite_kv_engine; | ||
97 | typedef struct jx9_io_stream unqlite_io_stream; | ||
98 | typedef struct jx9_context unqlite_context; | ||
99 | typedef struct jx9_value unqlite_value; | ||
100 | typedef struct unqlite_vfs unqlite_vfs; | ||
101 | typedef struct unqlite_vm unqlite_vm; | ||
102 | typedef 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__) | ||
191 | typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ | ||
192 | typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ | ||
193 | #else | ||
194 | typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ | ||
195 | typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ | ||
196 | #endif /* _MSC_VER */ | ||
197 | /* Signature of the consumer routine */ | ||
198 | typedef int (*ProcConsumer)(const void *, unsigned int, void *); | ||
199 | /* Forward reference */ | ||
200 | typedef struct SyMutexMethods SyMutexMethods; | ||
201 | typedef struct SyMemMethods SyMemMethods; | ||
202 | typedef struct SyString SyString; | ||
203 | typedef struct syiovec syiovec; | ||
204 | typedef struct SyMutex SyMutex; | ||
205 | typedef struct Sytm Sytm; | ||
206 | /* Scatter and gather array. */ | ||
207 | struct 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 | }; | ||
218 | struct SyString | ||
219 | { | ||
220 | const char *zString; /* Raw string (may not be null terminated) */ | ||
221 | unsigned int nByte; /* Raw string length */ | ||
222 | }; | ||
223 | /* Time structure. */ | ||
224 | struct 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. */ | ||
267 | struct 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. */ | ||
278 | typedef int (*ProcMemError)(void *); | ||
279 | /* Mutex methods. */ | ||
280 | struct 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 | ||
343 | typedef sxi64 uqlite_real; | ||
344 | #else | ||
345 | typedef double unqlite_real; | ||
346 | #endif | ||
347 | typedef 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 | */ | ||
522 | typedef struct unqlite_file unqlite_file; | ||
523 | struct 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 | */ | ||
566 | struct 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 | */ | ||
612 | struct 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 | */ | ||
654 | typedef sxu64 pgno; | ||
655 | /* | ||
656 | * A database disk page is represented by an instance | ||
657 | * of the follwoing structure. | ||
658 | */ | ||
659 | typedef struct unqlite_page unqlite_page; | ||
660 | struct 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 | */ | ||
669 | typedef 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 | */ | ||
676 | typedef struct unqlite_kv_io unqlite_kv_io; | ||
677 | struct 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 | */ | ||
706 | typedef struct unqlite_kv_cursor unqlite_kv_cursor; | ||
707 | struct 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 | */ | ||
733 | struct 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 | */ | ||
746 | struct 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 */ | ||
805 | UNQLITE_APIEXPORT int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode); | ||
806 | UNQLITE_APIEXPORT int unqlite_config(unqlite *pDb,int nOp,...); | ||
807 | UNQLITE_APIEXPORT int unqlite_close(unqlite *pDb); | ||
808 | |||
809 | /* Key/Value (KV) Store Interfaces */ | ||
810 | UNQLITE_APIEXPORT int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); | ||
811 | UNQLITE_APIEXPORT int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); | ||
812 | UNQLITE_APIEXPORT int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); | ||
813 | UNQLITE_APIEXPORT int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); | ||
814 | UNQLITE_APIEXPORT int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 /* in|out */*pBufLen); | ||
815 | UNQLITE_APIEXPORT int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey, | ||
816 | int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
817 | UNQLITE_APIEXPORT int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen); | ||
818 | UNQLITE_APIEXPORT int unqlite_kv_config(unqlite *pDb,int iOp,...); | ||
819 | |||
820 | /* Document (JSON) Store Interfaces powered by the Jx9 Scripting Language */ | ||
821 | UNQLITE_APIEXPORT int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut); | ||
822 | UNQLITE_APIEXPORT int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut); | ||
823 | UNQLITE_APIEXPORT int unqlite_vm_config(unqlite_vm *pVm,int iOp,...); | ||
824 | UNQLITE_APIEXPORT int unqlite_vm_exec(unqlite_vm *pVm); | ||
825 | UNQLITE_APIEXPORT int unqlite_vm_reset(unqlite_vm *pVm); | ||
826 | UNQLITE_APIEXPORT int unqlite_vm_release(unqlite_vm *pVm); | ||
827 | UNQLITE_APIEXPORT int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData); | ||
828 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname); | ||
829 | |||
830 | /* Cursor Iterator Interfaces */ | ||
831 | UNQLITE_APIEXPORT int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut); | ||
832 | UNQLITE_APIEXPORT int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur); | ||
833 | UNQLITE_APIEXPORT int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos); | ||
834 | UNQLITE_APIEXPORT int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor); | ||
835 | UNQLITE_APIEXPORT int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor); | ||
836 | UNQLITE_APIEXPORT int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor); | ||
837 | UNQLITE_APIEXPORT int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor); | ||
838 | UNQLITE_APIEXPORT int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor); | ||
839 | UNQLITE_APIEXPORT int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte); | ||
840 | UNQLITE_APIEXPORT int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
841 | UNQLITE_APIEXPORT int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnData); | ||
842 | UNQLITE_APIEXPORT int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
843 | UNQLITE_APIEXPORT int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor); | ||
844 | UNQLITE_APIEXPORT int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor); | ||
845 | |||
846 | /* Manual Transaction Manager */ | ||
847 | UNQLITE_APIEXPORT int unqlite_begin(unqlite *pDb); | ||
848 | UNQLITE_APIEXPORT int unqlite_commit(unqlite *pDb); | ||
849 | UNQLITE_APIEXPORT int unqlite_rollback(unqlite *pDb); | ||
850 | |||
851 | /* Utility interfaces */ | ||
852 | UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize); | ||
853 | UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize); | ||
854 | UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size); | ||
855 | UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb); | ||
856 | |||
857 | /* In-process extending interfaces */ | ||
858 | UNQLITE_APIEXPORT int unqlite_create_function(unqlite_vm *pVm,const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData); | ||
859 | UNQLITE_APIEXPORT int unqlite_delete_function(unqlite_vm *pVm, const char *zName); | ||
860 | UNQLITE_APIEXPORT int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData); | ||
861 | UNQLITE_APIEXPORT int unqlite_delete_constant(unqlite_vm *pVm, const char *zName); | ||
862 | |||
863 | /* On Demand Object allocation interfaces */ | ||
864 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm); | ||
865 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm); | ||
866 | UNQLITE_APIEXPORT int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue); | ||
867 | UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx); | ||
868 | UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_array(unqlite_context *pCtx); | ||
869 | UNQLITE_APIEXPORT void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue); | ||
870 | |||
871 | /* Dynamically Typed Value Object Management Interfaces */ | ||
872 | UNQLITE_APIEXPORT int unqlite_value_int(unqlite_value *pVal, int iValue); | ||
873 | UNQLITE_APIEXPORT int unqlite_value_int64(unqlite_value *pVal, unqlite_int64 iValue); | ||
874 | UNQLITE_APIEXPORT int unqlite_value_bool(unqlite_value *pVal, int iBool); | ||
875 | UNQLITE_APIEXPORT int unqlite_value_null(unqlite_value *pVal); | ||
876 | UNQLITE_APIEXPORT int unqlite_value_double(unqlite_value *pVal, double Value); | ||
877 | UNQLITE_APIEXPORT int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen); | ||
878 | UNQLITE_APIEXPORT int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...); | ||
879 | UNQLITE_APIEXPORT int unqlite_value_reset_string_cursor(unqlite_value *pVal); | ||
880 | UNQLITE_APIEXPORT int unqlite_value_resource(unqlite_value *pVal, void *pUserData); | ||
881 | UNQLITE_APIEXPORT int unqlite_value_release(unqlite_value *pVal); | ||
882 | |||
883 | /* Foreign Function Parameter Values */ | ||
884 | UNQLITE_APIEXPORT int unqlite_value_to_int(unqlite_value *pValue); | ||
885 | UNQLITE_APIEXPORT int unqlite_value_to_bool(unqlite_value *pValue); | ||
886 | UNQLITE_APIEXPORT unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue); | ||
887 | UNQLITE_APIEXPORT double unqlite_value_to_double(unqlite_value *pValue); | ||
888 | UNQLITE_APIEXPORT const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen); | ||
889 | UNQLITE_APIEXPORT void * unqlite_value_to_resource(unqlite_value *pValue); | ||
890 | UNQLITE_APIEXPORT int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict); | ||
891 | |||
892 | /* Setting The Result Of A Foreign Function */ | ||
893 | UNQLITE_APIEXPORT int unqlite_result_int(unqlite_context *pCtx, int iValue); | ||
894 | UNQLITE_APIEXPORT int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue); | ||
895 | UNQLITE_APIEXPORT int unqlite_result_bool(unqlite_context *pCtx, int iBool); | ||
896 | UNQLITE_APIEXPORT int unqlite_result_double(unqlite_context *pCtx, double Value); | ||
897 | UNQLITE_APIEXPORT int unqlite_result_null(unqlite_context *pCtx); | ||
898 | UNQLITE_APIEXPORT int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen); | ||
899 | UNQLITE_APIEXPORT int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...); | ||
900 | UNQLITE_APIEXPORT int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue); | ||
901 | UNQLITE_APIEXPORT int unqlite_result_resource(unqlite_context *pCtx, void *pUserData); | ||
902 | |||
903 | /* Dynamically Typed Value Object Query Interfaces */ | ||
904 | UNQLITE_APIEXPORT int unqlite_value_is_int(unqlite_value *pVal); | ||
905 | UNQLITE_APIEXPORT int unqlite_value_is_float(unqlite_value *pVal); | ||
906 | UNQLITE_APIEXPORT int unqlite_value_is_bool(unqlite_value *pVal); | ||
907 | UNQLITE_APIEXPORT int unqlite_value_is_string(unqlite_value *pVal); | ||
908 | UNQLITE_APIEXPORT int unqlite_value_is_null(unqlite_value *pVal); | ||
909 | UNQLITE_APIEXPORT int unqlite_value_is_numeric(unqlite_value *pVal); | ||
910 | UNQLITE_APIEXPORT int unqlite_value_is_callable(unqlite_value *pVal); | ||
911 | UNQLITE_APIEXPORT int unqlite_value_is_scalar(unqlite_value *pVal); | ||
912 | UNQLITE_APIEXPORT int unqlite_value_is_json_array(unqlite_value *pVal); | ||
913 | UNQLITE_APIEXPORT int unqlite_value_is_json_object(unqlite_value *pVal); | ||
914 | UNQLITE_APIEXPORT int unqlite_value_is_resource(unqlite_value *pVal); | ||
915 | UNQLITE_APIEXPORT int unqlite_value_is_empty(unqlite_value *pVal); | ||
916 | |||
917 | /* JSON Array/Object Management Interfaces */ | ||
918 | UNQLITE_APIEXPORT unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte); | ||
919 | UNQLITE_APIEXPORT int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData); | ||
920 | UNQLITE_APIEXPORT int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue); | ||
921 | UNQLITE_APIEXPORT int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue); | ||
922 | UNQLITE_APIEXPORT int unqlite_array_count(unqlite_value *pArray); | ||
923 | |||
924 | /* Call Context Handling Interfaces */ | ||
925 | UNQLITE_APIEXPORT int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen); | ||
926 | UNQLITE_APIEXPORT int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...); | ||
927 | UNQLITE_APIEXPORT int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr); | ||
928 | UNQLITE_APIEXPORT int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...); | ||
929 | UNQLITE_APIEXPORT unsigned int unqlite_context_random_num(unqlite_context *pCtx); | ||
930 | UNQLITE_APIEXPORT int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen); | ||
931 | UNQLITE_APIEXPORT void * unqlite_context_user_data(unqlite_context *pCtx); | ||
932 | UNQLITE_APIEXPORT int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData); | ||
933 | UNQLITE_APIEXPORT void * unqlite_context_peek_aux_data(unqlite_context *pCtx); | ||
934 | UNQLITE_APIEXPORT unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx); | ||
935 | UNQLITE_APIEXPORT const char * unqlite_function_name(unqlite_context *pCtx); | ||
936 | |||
937 | /* Call Context Memory Management Interfaces */ | ||
938 | UNQLITE_APIEXPORT void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); | ||
939 | UNQLITE_APIEXPORT void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte); | ||
940 | UNQLITE_APIEXPORT void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk); | ||
941 | |||
942 | /* Global Library Management Interfaces */ | ||
943 | UNQLITE_APIEXPORT int unqlite_lib_config(int nConfigOp,...); | ||
944 | UNQLITE_APIEXPORT int unqlite_lib_init(void); | ||
945 | UNQLITE_APIEXPORT int unqlite_lib_shutdown(void); | ||
946 | UNQLITE_APIEXPORT int unqlite_lib_is_threadsafe(void); | ||
947 | UNQLITE_APIEXPORT const char * unqlite_lib_version(void); | ||
948 | UNQLITE_APIEXPORT const char * unqlite_lib_signature(void); | ||
949 | UNQLITE_APIEXPORT const char * unqlite_lib_ident(void); | ||
950 | UNQLITE_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 */ | ||
27 | typedef 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 | */ | ||
95 | typedef struct Pager Pager; | ||
96 | /* | ||
97 | * Each database file to be accessed by the system is an instance | ||
98 | * of the following structure. | ||
99 | */ | ||
100 | struct 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 | */ | ||
109 | struct 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 */ | ||
132 | typedef struct unqlite_col_record unqlite_col_record; | ||
133 | typedef struct unqlite_col unqlite_col; | ||
134 | /* | ||
135 | * Each an in-memory collection record is stored in an instance | ||
136 | * of the following structure. | ||
137 | */ | ||
138 | struct 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 | */ | ||
153 | struct 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 | */ | ||
179 | struct 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 */ | ||
224 | typedef struct Bitvec Bitvec; | ||
225 | /* Private library functions */ | ||
226 | /* api.c */ | ||
227 | UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void); | ||
228 | UNQLITE_PRIVATE int unqliteDataConsumer( | ||
229 | const void *pOut, /* Data to consume */ | ||
230 | unsigned int nLen, /* Data length */ | ||
231 | void *pUserData /* User private data */ | ||
232 | ); | ||
233 | UNQLITE_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 | ); | ||
237 | UNQLITE_PRIVATE int unqliteGetPageSize(void); | ||
238 | UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr); | ||
239 | UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...); | ||
240 | UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb); | ||
241 | /* unql_vm.c */ | ||
242 | UNQLITE_PRIVATE int unqliteCreateCollection(unqlite_vm *pVm,SyString *pName); | ||
243 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol); | ||
244 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol); | ||
245 | UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(unqlite_col *pCol,jx9_int64 nId); | ||
246 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol); | ||
247 | UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol); | ||
248 | UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue); | ||
249 | UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(unqlite_col *pCol,jx9_int64 nId,jx9_value *pValue); | ||
250 | UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(unqlite_vm *pVm,SyString *pCol,int iFlag); | ||
251 | UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue); | ||
252 | UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag); | ||
253 | UNQLITE_PRIVATE int unqliteCollectionDropRecord(unqlite_col *pCol,jx9_int64 nId,int wr_header,int log_err); | ||
254 | UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol); | ||
255 | /* unql_jx9.c */ | ||
256 | UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm); | ||
257 | /* fastjson.c */ | ||
258 | UNQLITE_PRIVATE sxi32 FastJsonEncode( | ||
259 | jx9_value *pValue, /* Value to encode */ | ||
260 | SyBlob *pOut, /* Store encoded value here */ | ||
261 | int iNest /* Nesting limit */ | ||
262 | ); | ||
263 | UNQLITE_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 ] */ | ||
271 | UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void); | ||
272 | /* mem_kv.c */ | ||
273 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void); | ||
274 | /* lhash_kv.c */ | ||
275 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void); | ||
276 | /* os.c */ | ||
277 | UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset); | ||
278 | UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset); | ||
279 | UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size); | ||
280 | UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags); | ||
281 | UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize); | ||
282 | UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType); | ||
283 | UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType); | ||
284 | UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut); | ||
285 | UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id); | ||
286 | UNQLITE_PRIVATE int unqliteOsOpen( | ||
287 | unqlite_vfs *pVfs, | ||
288 | SyMemBackend *pAlloc, | ||
289 | const char *zPath, | ||
290 | unqlite_file **ppOut, | ||
291 | unsigned int flags | ||
292 | ); | ||
293 | UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId); | ||
294 | UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync); | ||
295 | UNQLITE_PRIVATE int unqliteOsAccess(unqlite_vfs *pVfs,const char *zPath,int flags,int *pResOut); | ||
296 | /* bitmap.c */ | ||
297 | UNQLITE_PRIVATE Bitvec *unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize); | ||
298 | UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i); | ||
299 | UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i); | ||
300 | UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p); | ||
301 | /* pager.c */ | ||
302 | UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut); | ||
303 | UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur); | ||
304 | UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage); | ||
305 | UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager); | ||
306 | UNQLITE_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 | ); | ||
312 | UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods); | ||
313 | UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb); | ||
314 | UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager); | ||
315 | UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager); | ||
316 | UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine); | ||
317 | UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen); | ||
318 | UNQLITE_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 | */ | ||
29 | static 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 | */ | ||
44 | static 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 | */ | ||
68 | static 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 | */ | ||
83 | static 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 | */ | ||
99 | static 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 | */ | ||
138 | static 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 | */ | ||
178 | static 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 | */ | ||
239 | static 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 | */ | ||
303 | static 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 | */ | ||
389 | static 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 | */ | ||
431 | static 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 | */ | ||
473 | static 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 | */ | ||
516 | static 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 | */ | ||
560 | static 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 | */ | ||
607 | static 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 | */ | ||
666 | static 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 | */ | ||
714 | static 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 | */ | ||
765 | static 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 | */ | ||
820 | static 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 | */ | ||
866 | static 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 | */ | ||
891 | static 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 | */ | ||
916 | static 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 | */ | ||
936 | UNQLITE_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 | */ | ||
24 | static 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 | */ | ||
53 | static 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 | */ | ||
133 | UNQLITE_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 | */ | ||
162 | static 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 | */ | ||
183 | static 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 | */ | ||
244 | static 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 | */ | ||
275 | static 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 | */ | ||
375 | static 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 | */ | ||
421 | static 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; | ||
516 | fail: | ||
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 | */ | ||
535 | UNQLITE_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 | */ | ||
560 | UNQLITE_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 | */ | ||
567 | UNQLITE_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 | */ | ||
574 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol) | ||
575 | { | ||
576 | return pCol->nTotRec; | ||
577 | } | ||
578 | /* | ||
579 | * Reset the record cursor. | ||
580 | */ | ||
581 | UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol) | ||
582 | { | ||
583 | pCol->nCurid = 0; | ||
584 | } | ||
585 | /* | ||
586 | * Fetch a record by its unique ID. | ||
587 | */ | ||
588 | UNQLITE_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 | */ | ||
640 | UNQLITE_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 | */ | ||
663 | UNQLITE_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 | */ | ||
682 | UNQLITE_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 | */ | ||
695 | static 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 | */ | ||
771 | static 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 | */ | ||
785 | UNQLITE_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 | */ | ||
800 | UNQLITE_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 | */ | ||
847 | UNQLITE_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 | } | ||