diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-26 18:59:53 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2015-12-26 18:59:53 +0100 |
commit | fc7052f0970465d41dfd67c7e5db080498fd060f (patch) | |
tree | e953b25d3d6032fc5e00623e2466e67c042ae73d | |
parent | 010afd20f7c278a52536f5c37d0ade13b3017e69 (diff) | |
parent | 94385710e45c6289809934aecaffe793ad2f6369 (diff) | |
download | sink-fc7052f0970465d41dfd67c7e5db080498fd060f.tar.gz sink-fc7052f0970465d41dfd67c7e5db080498fd060f.zip |
Merge branch 'feature/remove_unqlite' into develop
-rw-r--r-- | common/CMakeLists.txt | 9 | ||||
-rw-r--r-- | common/storage_unqlite.cpp | 340 | ||||
-rw-r--r-- | common/unqlite/unqlite.c | 59972 | ||||
-rw-r--r-- | common/unqlite/unqlite.h | 949 |
4 files changed, 2 insertions, 61268 deletions
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 8312f13..f07772a 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt | |||
@@ -3,13 +3,8 @@ include_directories(domain) | |||
3 | 3 | ||
4 | project(akonadi2common) | 4 | project(akonadi2common) |
5 | 5 | ||
6 | if (STORAGE_unqlite) | 6 | set(storage_SRCS storage_lmdb.cpp) |
7 | add_definitions(-DUNQLITE_ENABLE_THREADS -fpermissive) | 7 | set(storage_LIBS lmdb) |
8 | set(storage_SRCS unqlite/unqlite.c storage_unqlite.cpp) | ||
9 | else (STORAGE_unqlite) | ||
10 | set(storage_SRCS storage_lmdb.cpp) | ||
11 | set(storage_LIBS lmdb) | ||
12 | endif (STORAGE_unqlite) | ||
13 | 8 | ||
14 | set(command_SRCS | 9 | set(command_SRCS |
15 | modelresult.cpp | 10 | modelresult.cpp |
diff --git a/common/storage_unqlite.cpp b/common/storage_unqlite.cpp deleted file mode 100644 index 6950acd..0000000 --- a/common/storage_unqlite.cpp +++ /dev/null | |||
@@ -1,340 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This library is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU Lesser General Public | ||
6 | * License as published by the Free Software Foundation; either | ||
7 | * version 2.1 of the License, or (at your option) version 3, or any | ||
8 | * later version accepted by the membership of KDE e.V. (or its | ||
9 | * successor approved by the membership of KDE e.V.), which shall | ||
10 | * act as a proxy defined in Section 6 of version 3 of the license. | ||
11 | * | ||
12 | * This library is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | |||
21 | #include "storage.h" | ||
22 | |||
23 | #include <iostream> | ||
24 | |||
25 | #include <QAtomicInt> | ||
26 | #include <QDebug> | ||
27 | #include <QDir> | ||
28 | #include <QReadWriteLock> | ||
29 | #include <QString> | ||
30 | #include <QTime> | ||
31 | |||
32 | extern "C" { | ||
33 | #include "unqlite/unqlite.h" | ||
34 | } | ||
35 | |||
36 | namespace Akonadi2 | ||
37 | { | ||
38 | |||
39 | static const char *s_unqliteDir = "/unqlite/"; | ||
40 | |||
41 | class Storage::Private | ||
42 | { | ||
43 | public: | ||
44 | Private(const QString &s, const QString &name, AccessMode m, bool allowDuplicates); | ||
45 | ~Private(); | ||
46 | |||
47 | void reportDbError(const char *functionName); | ||
48 | void reportDbError(const char *functionName, int errorCode, | ||
49 | const std::function<void(const Storage::Error &error)> &errorHandler); | ||
50 | |||
51 | QString storageRoot; | ||
52 | QString name; | ||
53 | AccessMode mode; | ||
54 | |||
55 | unqlite *db; | ||
56 | bool allowDuplicates; | ||
57 | bool inTransaction; | ||
58 | }; | ||
59 | |||
60 | Storage::Private::Private(const QString &s, const QString &n, AccessMode m, bool duplicates) | ||
61 | : storageRoot(s), | ||
62 | name(n), | ||
63 | mode(m), | ||
64 | db(0), | ||
65 | allowDuplicates(duplicates), //FIXME: currently does nothing ... should do what it says | ||
66 | inTransaction(false) | ||
67 | { | ||
68 | const QString fullPath(storageRoot + s_unqliteDir + name); | ||
69 | QDir dir; | ||
70 | dir.mkpath(storageRoot + s_unqliteDir); | ||
71 | |||
72 | //create file | ||
73 | int openFlags = UNQLITE_OPEN_CREATE; | ||
74 | if (mode == ReadOnly) { | ||
75 | openFlags |= UNQLITE_OPEN_READONLY | UNQLITE_OPEN_MMAP; | ||
76 | } else { | ||
77 | openFlags |= UNQLITE_OPEN_READWRITE; | ||
78 | } | ||
79 | |||
80 | int rc = unqlite_open(&db, fullPath.toStdString().data(), openFlags); | ||
81 | |||
82 | if (rc != UNQLITE_OK) { | ||
83 | reportDbError("unqlite_open"); | ||
84 | } | ||
85 | } | ||
86 | |||
87 | Storage::Private::~Private() | ||
88 | { | ||
89 | unqlite_close(db); | ||
90 | } | ||
91 | |||
92 | void Storage::Private::reportDbError(const char *functionName) | ||
93 | { | ||
94 | std::cerr << "ERROR: " << functionName; | ||
95 | if (db) { | ||
96 | const char *errorMessage; | ||
97 | int length; | ||
98 | /* Something goes wrong, extract database error log */ | ||
99 | unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &errorMessage, &length); | ||
100 | if (length > 0) { | ||
101 | std::cerr << ": " << errorMessage; | ||
102 | } | ||
103 | } | ||
104 | std::cerr << std::endl; | ||
105 | } | ||
106 | |||
107 | void Storage::Private::reportDbError(const char *functionName, int errorCode, | ||
108 | const std::function<void(const Storage::Error &error)> &errorHandler) | ||
109 | { | ||
110 | if (db) { | ||
111 | const char *errorMessage; | ||
112 | int length; | ||
113 | /* Something goes wrong, extract database error log */ | ||
114 | unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &errorMessage, &length); | ||
115 | if (length > 0) { | ||
116 | Error error(name.toStdString(), errorCode, errorMessage); | ||
117 | errorHandler(error); | ||
118 | return; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | Error error(name.toStdString(), errorCode, functionName); | ||
123 | errorHandler(error); | ||
124 | } | ||
125 | |||
126 | Storage::Storage(const QString &storageRoot, const QString &name, AccessMode mode, bool allowDuplicates) | ||
127 | : d(new Private(storageRoot, name, mode, allowDuplicates)) | ||
128 | { | ||
129 | } | ||
130 | |||
131 | Storage::~Storage() | ||
132 | { | ||
133 | if (d->inTransaction) { | ||
134 | abortTransaction(); | ||
135 | } | ||
136 | |||
137 | delete d; | ||
138 | } | ||
139 | |||
140 | bool Storage::isInTransaction() const | ||
141 | { | ||
142 | return d->inTransaction; | ||
143 | } | ||
144 | |||
145 | bool Storage::startTransaction(AccessMode type) | ||
146 | { | ||
147 | if (!d->db) { | ||
148 | return false; | ||
149 | } | ||
150 | |||
151 | if (d->inTransaction) { | ||
152 | return true; | ||
153 | } | ||
154 | |||
155 | d->inTransaction = unqlite_begin(d->db) == UNQLITE_OK; | ||
156 | |||
157 | if (!d->inTransaction) { | ||
158 | d->reportDbError("unqlite_begin"); | ||
159 | } | ||
160 | |||
161 | return d->inTransaction; | ||
162 | } | ||
163 | |||
164 | bool Storage::commitTransaction() | ||
165 | { | ||
166 | if (!d->db) { | ||
167 | return false; | ||
168 | } | ||
169 | |||
170 | if (!d->inTransaction) { | ||
171 | return true; | ||
172 | } | ||
173 | |||
174 | int rc = unqlite_commit(d->db); | ||
175 | d->inTransaction = false; | ||
176 | |||
177 | if (rc != UNQLITE_OK) { | ||
178 | d->reportDbError("unqlite_commit"); | ||
179 | } | ||
180 | |||
181 | return rc == UNQLITE_OK; | ||
182 | } | ||
183 | |||
184 | void Storage::abortTransaction() | ||
185 | { | ||
186 | if (!d->db || !d->inTransaction) { | ||
187 | return; | ||
188 | } | ||
189 | |||
190 | unqlite_rollback(d->db); | ||
191 | d->inTransaction = false; | ||
192 | } | ||
193 | |||
194 | bool Storage::write(const void *key, size_t keySize, const void *value, size_t valueSize) | ||
195 | { | ||
196 | if (!d->db) { | ||
197 | return false; | ||
198 | } | ||
199 | |||
200 | int rc = unqlite_kv_store(d->db, key, keySize, value, valueSize); | ||
201 | |||
202 | if (rc != UNQLITE_OK) { | ||
203 | d->reportDbError("unqlite_kv_store"); | ||
204 | } | ||
205 | |||
206 | return !rc; | ||
207 | } | ||
208 | |||
209 | bool Storage::write(const std::string &sKey, const std::string &sValue) | ||
210 | { | ||
211 | return write(sKey.data(), sKey.size(), sValue.data(), sKey.size()); | ||
212 | } | ||
213 | |||
214 | void Storage::read(const std::string &sKey, | ||
215 | const std::function<bool(const std::string &value)> &resultHandler, | ||
216 | const std::function<void(const Storage::Error &error)> &errorHandler) | ||
217 | { | ||
218 | read(sKey, | ||
219 | [&](void *ptr, int size) -> bool { | ||
220 | if (ptr) { | ||
221 | const std::string resultValue(static_cast<char*>(ptr), size); | ||
222 | return resultHandler(resultValue); | ||
223 | } | ||
224 | |||
225 | return true; | ||
226 | }, errorHandler); | ||
227 | } | ||
228 | |||
229 | void Storage::read(const std::string &sKey, | ||
230 | const std::function<bool(void *ptr, int size)> &resultHandler, | ||
231 | const std::function<void(const Storage::Error &error)> &errorHandler) | ||
232 | { | ||
233 | scan(sKey.data(), sKey.size(), [resultHandler](void *keyPtr, int keySize, void *valuePtr, int valueSize) { | ||
234 | return resultHandler(valuePtr, valueSize); | ||
235 | }, errorHandler); | ||
236 | } | ||
237 | |||
238 | void Storage::remove(const void *keyData, uint keySize) | ||
239 | { | ||
240 | remove(keyData, keySize, basicErrorHandler()); | ||
241 | } | ||
242 | |||
243 | void Storage::remove(const void *keyData, uint keySize, | ||
244 | const std::function<void(const Storage::Error &error)> &errorHandler) | ||
245 | { | ||
246 | if (!d->db) { | ||
247 | Error error(d->name.toStdString(), -1, "Not open"); | ||
248 | errorHandler(error); | ||
249 | return; | ||
250 | } | ||
251 | |||
252 | unqlite_kv_delete(d->db, keyData, keySize); | ||
253 | } | ||
254 | |||
255 | |||
256 | void fetchCursorData(unqlite_kv_cursor *cursor, | ||
257 | void **keyBuffer, int *keyBufferLength, void **dataBuffer, unqlite_int64 *dataBufferLength, | ||
258 | const std::function<bool(void *keyPtr, int keySize, void *valuePtr, int valueSize)> &resultHandler) | ||
259 | { | ||
260 | int keyLength = 0; | ||
261 | unqlite_int64 dataLength = 0; | ||
262 | // now fetch the data sizes | ||
263 | if (unqlite_kv_cursor_key(cursor, nullptr, &keyLength) == UNQLITE_OK && | ||
264 | unqlite_kv_cursor_data(cursor, nullptr, &dataLength) == UNQLITE_OK) { | ||
265 | if (keyLength > *keyBufferLength) { | ||
266 | *keyBuffer = realloc(*keyBuffer, keyLength); | ||
267 | *keyBufferLength = keyLength; | ||
268 | } | ||
269 | |||
270 | if (dataLength > *dataBufferLength) { | ||
271 | *dataBuffer = realloc(*dataBuffer, dataLength); | ||
272 | *dataBufferLength = dataLength; | ||
273 | } | ||
274 | |||
275 | if (unqlite_kv_cursor_key(cursor, *keyBuffer, &keyLength) == UNQLITE_OK && | ||
276 | unqlite_kv_cursor_data(cursor, *dataBuffer, &dataLength) == UNQLITE_OK) { | ||
277 | resultHandler(*keyBuffer, keyLength, *dataBuffer, dataLength); | ||
278 | } | ||
279 | } | ||
280 | } | ||
281 | |||
282 | void Storage::scan(const char *keyData, uint keySize, | ||
283 | const std::function<bool(void *keyPtr, int keySize, void *valuePtr, int valueSize)> &resultHandler, | ||
284 | const std::function<void(const Storage::Error &error)> &errorHandler) | ||
285 | { | ||
286 | if (!d->db) { | ||
287 | Error error(d->name.toStdString(), -1, "Not open"); | ||
288 | errorHandler(error); | ||
289 | return; | ||
290 | } | ||
291 | |||
292 | unqlite_kv_cursor *cursor; | ||
293 | |||
294 | int rc = unqlite_kv_cursor_init(d->db, &cursor); | ||
295 | if (rc != UNQLITE_OK) { | ||
296 | d->reportDbError("unqlite_kv_cursor_init", rc, errorHandler); | ||
297 | return; | ||
298 | } | ||
299 | |||
300 | void *keyBuffer = nullptr; | ||
301 | int keyBufferLength = 0; | ||
302 | void *dataBuffer = nullptr; | ||
303 | //FIXME: 64bit ints, but feeding int lenghts to the callbacks. can result in truncation | ||
304 | unqlite_int64 dataBufferLength = 0; | ||
305 | if (!keyData || keySize == 0) { | ||
306 | for (unqlite_kv_cursor_first_entry(cursor); unqlite_kv_cursor_valid_entry(cursor); unqlite_kv_cursor_next_entry(cursor)) { | ||
307 | fetchCursorData(cursor, &keyBuffer, &keyBufferLength, &dataBuffer, &dataBufferLength, resultHandler); | ||
308 | } | ||
309 | } else { | ||
310 | rc = unqlite_kv_cursor_seek(cursor, keyData, keySize, UNQLITE_CURSOR_MATCH_EXACT); | ||
311 | if (rc == UNQLITE_OK) { | ||
312 | fetchCursorData(cursor, &keyBuffer, &keyBufferLength, &dataBuffer, &dataBufferLength, resultHandler); | ||
313 | } else { | ||
314 | std::cout << "couldn't find value " << std::string(keyData, keySize) << std::endl; | ||
315 | } | ||
316 | |||
317 | } | ||
318 | |||
319 | free(keyBuffer); | ||
320 | free(dataBuffer); | ||
321 | unqlite_kv_cursor_release(d->db, cursor); | ||
322 | } | ||
323 | |||
324 | qint64 Storage::diskUsage() const | ||
325 | { | ||
326 | QFileInfo info(d->storageRoot + s_unqliteDir + d->name); | ||
327 | return info.size(); | ||
328 | } | ||
329 | |||
330 | bool Storage::exists() const | ||
331 | { | ||
332 | return d->db != 0; | ||
333 | } | ||
334 | |||
335 | void Storage::removeFromDisk() const | ||
336 | { | ||
337 | QFile::remove(d->storageRoot + s_unqliteDir + d->name); | ||
338 | } | ||
339 | |||
340 | } // namespace Akonadi2 | ||
diff --git a/common/unqlite/unqlite.c b/common/unqlite/unqlite.c deleted file mode 100644 index 7128a47..0000000 --- a/common/unqlite/unqlite.c +++ /dev/null | |||
@@ -1,59972 +0,0 @@ | |||
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 | /* | ||
14 | * Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>]. | ||
15 | * All rights reserved. | ||
16 | * | ||
17 | * Redistribution and use in source and binary forms, with or without | ||
18 | * modification, are permitted provided that the following conditions | ||
19 | * are met: | ||
20 | * 1. Redistributions of source code must retain the above copyright | ||
21 | * notice, this list of conditions and the following disclaimer. | ||
22 | * 2. Redistributions in binary form must reproduce the above copyright | ||
23 | * notice, this list of conditions and the following disclaimer in the | ||
24 | * documentation and/or other materials provided with the distribution. | ||
25 | * | ||
26 | * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS | ||
27 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
28 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR | ||
29 | * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS | ||
30 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
31 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
32 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
33 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
34 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||
35 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | ||
36 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
37 | */ | ||
38 | /* | ||
39 | * $SymiscID: unqlite.c v1.1.6 Unix|Win32/64 2013-07-08 03:38:18 stable <chm@symisc.net> $ | ||
40 | */ | ||
41 | /* This file is an amalgamation of many separate C source files from unqlite version 1.1.6 | ||
42 | * By combining all the individual C code files into this single large file, the entire code | ||
43 | * can be compiled as a single translation unit. This allows many compilers to do optimization's | ||
44 | * that would not be possible if the files were compiled separately. Performance improvements | ||
45 | * are commonly seen when unqlite is compiled as a single translation unit. | ||
46 | * | ||
47 | * This file is all you need to compile unqlite. To use unqlite in other programs, you need | ||
48 | * this file and the "unqlite.h" header file that defines the programming interface to the | ||
49 | * unqlite engine.(If you do not have the "unqlite.h" header file at hand, you will find | ||
50 | * a copy embedded within the text of this file.Search for "Header file: <unqlite.h>" to find | ||
51 | * the start of the embedded unqlite.h header file.) Additional code files may be needed if | ||
52 | * you want a wrapper to interface unqlite with your choice of programming language. | ||
53 | * To get the official documentation, please visit http://unqlite.org/ | ||
54 | */ | ||
55 | /* | ||
56 | * Make the sure the following directive is defined in the amalgamation build. | ||
57 | */ | ||
58 | #ifndef UNQLITE_AMALGAMATION | ||
59 | #define UNQLITE_AMALGAMATION | ||
60 | #define JX9_AMALGAMATION | ||
61 | /* Marker for routines not intended for external use */ | ||
62 | #define JX9_PRIVATE static | ||
63 | #endif /* UNQLITE_AMALGAMATION */ | ||
64 | /* | ||
65 | * Embedded header file for unqlite: <unqlite.h> | ||
66 | */ | ||
67 | /* | ||
68 | * ---------------------------------------------------------- | ||
69 | * File: unqlite.h | ||
70 | * MD5: d26e9847c6587edbbb183d0115d172cb | ||
71 | * ---------------------------------------------------------- | ||
72 | */ | ||
73 | /* This file was automatically generated. Do not edit (Except for compile time directives)! */ | ||
74 | #ifndef _UNQLITE_H_ | ||
75 | #define _UNQLITE_H_ | ||
76 | /* | ||
77 | * Symisc UnQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
78 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
79 | * Version 1.1.6 | ||
80 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
81 | * please contact Symisc Systems via: | ||
82 | * legal@symisc.net | ||
83 | * licensing@symisc.net | ||
84 | * contact@symisc.net | ||
85 | * or visit: | ||
86 | * http://unqlite.org/licensing.html | ||
87 | */ | ||
88 | /* | ||
89 | * Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>]. | ||
90 | * All rights reserved. | ||
91 | * | ||
92 | * Redistribution and use in source and binary forms, with or without | ||
93 | * modification, are permitted provided that the following conditions | ||
94 | * are met: | ||
95 | * 1. Redistributions of source code must retain the above copyright | ||
96 | * notice, this list of conditions and the following disclaimer. | ||
97 | * 2. Redistributions in binary form must reproduce the above copyright | ||
98 | * notice, this list of conditions and the following disclaimer in the | ||
99 | * documentation and/or other materials provided with the distribution. | ||
100 | * | ||
101 | * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS | ||
102 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
103 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR | ||
104 | * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS | ||
105 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
106 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
107 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
108 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
109 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||
110 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | ||
111 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
112 | */ | ||
113 | /* $SymiscID: unqlite.h v1.1 UNIX|WIN32/64 2012-11-02 02:10 stable <chm@symisc.net> $ */ | ||
114 | #include <stdarg.h> /* needed for the definition of va_list */ | ||
115 | /* | ||
116 | * Compile time engine version, signature, identification in the symisc source tree | ||
117 | * and copyright notice. | ||
118 | * Each macro have an equivalent C interface associated with it that provide the same | ||
119 | * information but are associated with the library instead of the header file. | ||
120 | * Refer to [unqlite_lib_version()], [unqlite_lib_signature()], [unqlite_lib_ident()] and | ||
121 | * [unqlite_lib_copyright()] for more information. | ||
122 | */ | ||
123 | /* | ||
124 | * The UNQLITE_VERSION C preprocessor macroevaluates to a string literal | ||
125 | * that is the unqlite version in the format "X.Y.Z" where X is the major | ||
126 | * version number and Y is the minor version number and Z is the release | ||
127 | * number. | ||
128 | */ | ||
129 | #define UNQLITE_VERSION "1.1.6" | ||
130 | /* | ||
131 | * The UNQLITE_VERSION_NUMBER C preprocessor macro resolves to an integer | ||
132 | * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same | ||
133 | * numbers used in [UNQLITE_VERSION]. | ||
134 | */ | ||
135 | #define UNQLITE_VERSION_NUMBER 1001006 | ||
136 | /* | ||
137 | * The UNQLITE_SIG C preprocessor macro evaluates to a string | ||
138 | * literal which is the public signature of the unqlite engine. | ||
139 | * This signature could be included for example in a host-application | ||
140 | * generated Server MIME header as follows: | ||
141 | * Server: YourWebServer/x.x unqlite/x.x.x \r\n | ||
142 | */ | ||
143 | #define UNQLITE_SIG "unqlite/1.1.6" | ||
144 | /* | ||
145 | * UnQLite identification in the Symisc source tree: | ||
146 | * Each particular check-in of a particular software released | ||
147 | * by symisc systems have an unique identifier associated with it. | ||
148 | * This macro hold the one associated with unqlite. | ||
149 | */ | ||
150 | #define UNQLITE_IDENT "unqlite:b172a1e2c3f62fb35c8e1fb2795121f82356cad6" | ||
151 | /* | ||
152 | * Copyright notice. | ||
153 | * If you have any questions about the licensing situation, please | ||
154 | * visit http://unqlite.org/licensing.html | ||
155 | * or contact Symisc Systems via: | ||
156 | * legal@symisc.net | ||
157 | * licensing@symisc.net | ||
158 | * contact@symisc.net | ||
159 | */ | ||
160 | #define UNQLITE_COPYRIGHT "Copyright (C) Symisc Systems, S.U.A.R.L [Mrad Chems Eddine <chm@symisc.net>] 2012-2013, http://unqlite.org/" | ||
161 | /* Make sure we can call this stuff from C++ */ | ||
162 | #ifdef __cplusplus | ||
163 | extern "C" { | ||
164 | #endif | ||
165 | /* Forward declaration to public objects */ | ||
166 | typedef struct unqlite_io_methods unqlite_io_methods; | ||
167 | typedef struct unqlite_kv_methods unqlite_kv_methods; | ||
168 | typedef struct unqlite_kv_engine unqlite_kv_engine; | ||
169 | typedef struct jx9_io_stream unqlite_io_stream; | ||
170 | typedef struct jx9_context unqlite_context; | ||
171 | typedef struct jx9_value unqlite_value; | ||
172 | typedef struct unqlite_vfs unqlite_vfs; | ||
173 | typedef struct unqlite_vm unqlite_vm; | ||
174 | typedef struct unqlite unqlite; | ||
175 | /* | ||
176 | * ------------------------------ | ||
177 | * Compile time directives | ||
178 | * ------------------------------ | ||
179 | * For most purposes, UnQLite can be built just fine using the default compilation options. | ||
180 | * However, if required, the compile-time options documented below can be used to omit UnQLite | ||
181 | * features (resulting in a smaller compiled library size) or to change the default values | ||
182 | * of some parameters. | ||
183 | * Every effort has been made to ensure that the various combinations of compilation options | ||
184 | * work harmoniously and produce a working library. | ||
185 | * | ||
186 | * UNQLITE_ENABLE_THREADS | ||
187 | * This option controls whether or not code is included in UnQLite to enable it to operate | ||
188 | * safely in a multithreaded environment. The default is not. All mutexing code is omitted | ||
189 | * and it is unsafe to use UnQLite in a multithreaded program. When compiled with the | ||
190 | * UNQLITE_ENABLE_THREADS directive enabled, UnQLite can be used in a multithreaded program | ||
191 | * and it is safe to share the same virtual machine and engine handle between two or more threads. | ||
192 | * The value of UNQLITE_ENABLE_THREADS can be determined at run-time using the unqlite_lib_is_threadsafe() | ||
193 | * interface. | ||
194 | * When UnQLite has been compiled with threading support then the threading mode can be altered | ||
195 | * at run-time using the unqlite_lib_config() interface together with one of these verbs: | ||
196 | * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE | ||
197 | * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI | ||
198 | * Platforms others than Windows and UNIX systems must install their own mutex subsystem via | ||
199 | * unqlite_lib_config() with a configuration verb set to UNQLITE_LIB_CONFIG_USER_MUTEX. | ||
200 | * Otherwise the library is not threadsafe. | ||
201 | * Note that you must link UnQLite with the POSIX threads library under UNIX systems (i.e: -lpthread). | ||
202 | * | ||
203 | * Options To Omit/Enable Features | ||
204 | * | ||
205 | * The following options can be used to reduce the size of the compiled library by omitting optional | ||
206 | * features. This is probably only useful in embedded systems where space is especially tight, as even | ||
207 | * with all features included the UnQLite library is relatively small. Don't forget to tell your | ||
208 | * compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler | ||
209 | * to optimize for size usually has a much larger impact on library footprint than employing | ||
210 | * any of these compile-time options. | ||
211 | * | ||
212 | * JX9_DISABLE_BUILTIN_FUNC | ||
213 | * Jx9 is shipped with more than 312 built-in functions suitable for most purposes like | ||
214 | * string and INI processing, ZIP extracting, Base64 encoding/decoding, JSON encoding/decoding | ||
215 | * and so forth. | ||
216 | * If this directive is enabled, then all built-in Jx9 functions are omitted from the build. | ||
217 | * Note that special functions such as db_create(), db_store(), db_fetch(), etc. are not omitted | ||
218 | * from the build and are not affected by this directive. | ||
219 | * | ||
220 | * JX9_ENABLE_MATH_FUNC | ||
221 | * If this directive is enabled, built-in math functions such as sqrt(), abs(), log(), ceil(), etc. | ||
222 | * are included in the build. Note that you may need to link UnQLite with the math library in same | ||
223 | * Linux/BSD flavor (i.e: -lm). | ||
224 | * | ||
225 | * JX9_DISABLE_DISK_IO | ||
226 | * If this directive is enabled, built-in VFS functions such as chdir(), mkdir(), chroot(), unlink(), | ||
227 | * sleep(), etc. are omitted from the build. | ||
228 | * | ||
229 | * UNQLITE_ENABLE_JX9_HASH_IO | ||
230 | * If this directive is enabled, built-in hash functions such as md5(), sha1(), md5_file(), crc32(), etc. | ||
231 | * are included in the build. | ||
232 | */ | ||
233 | /* Symisc public definitions */ | ||
234 | #if !defined(SYMISC_STANDARD_DEFS) | ||
235 | #define SYMISC_STANDARD_DEFS | ||
236 | #if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE) | ||
237 | /* Windows Systems */ | ||
238 | #if !defined(__WINNT__) | ||
239 | #define __WINNT__ | ||
240 | #endif | ||
241 | /* | ||
242 | * Determine if we are dealing with WindowsCE - which has a much | ||
243 | * reduced API. | ||
244 | */ | ||
245 | #if defined(_WIN32_WCE) | ||
246 | #ifndef __WIN_CE__ | ||
247 | #define __WIN_CE__ | ||
248 | #endif /* __WIN_CE__ */ | ||
249 | #endif /* _WIN32_WCE */ | ||
250 | #else | ||
251 | /* | ||
252 | * By default we will assume that we are compiling on a UNIX systems. | ||
253 | * Otherwise the OS_OTHER directive must be defined. | ||
254 | */ | ||
255 | #if !defined(OS_OTHER) | ||
256 | #if !defined(__UNIXES__) | ||
257 | #define __UNIXES__ | ||
258 | #endif /* __UNIXES__ */ | ||
259 | #else | ||
260 | #endif /* OS_OTHER */ | ||
261 | #endif /* __WINNT__/__UNIXES__ */ | ||
262 | #if defined(_MSC_VER) || defined(__BORLANDC__) | ||
263 | typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ | ||
264 | typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ | ||
265 | #else | ||
266 | typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ | ||
267 | typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ | ||
268 | #endif /* _MSC_VER */ | ||
269 | /* Signature of the consumer routine */ | ||
270 | typedef int (*ProcConsumer)(const void *, unsigned int, void *); | ||
271 | /* Forward reference */ | ||
272 | typedef struct SyMutexMethods SyMutexMethods; | ||
273 | typedef struct SyMemMethods SyMemMethods; | ||
274 | typedef struct SyString SyString; | ||
275 | typedef struct syiovec syiovec; | ||
276 | typedef struct SyMutex SyMutex; | ||
277 | typedef struct Sytm Sytm; | ||
278 | /* Scatter and gather array. */ | ||
279 | struct syiovec | ||
280 | { | ||
281 | #if defined (__WINNT__) | ||
282 | /* Same fields type and offset as WSABUF structure defined one winsock2 header */ | ||
283 | unsigned long nLen; | ||
284 | char *pBase; | ||
285 | #else | ||
286 | void *pBase; | ||
287 | unsigned long nLen; | ||
288 | #endif | ||
289 | }; | ||
290 | struct SyString | ||
291 | { | ||
292 | const char *zString; /* Raw string (may not be null terminated) */ | ||
293 | unsigned int nByte; /* Raw string length */ | ||
294 | }; | ||
295 | /* Time structure. */ | ||
296 | struct Sytm | ||
297 | { | ||
298 | int tm_sec; /* seconds (0 - 60) */ | ||
299 | int tm_min; /* minutes (0 - 59) */ | ||
300 | int tm_hour; /* hours (0 - 23) */ | ||
301 | int tm_mday; /* day of month (1 - 31) */ | ||
302 | int tm_mon; /* month of year (0 - 11) */ | ||
303 | int tm_year; /* year + 1900 */ | ||
304 | int tm_wday; /* day of week (Sunday = 0) */ | ||
305 | int tm_yday; /* day of year (0 - 365) */ | ||
306 | int tm_isdst; /* is summer time in effect? */ | ||
307 | char *tm_zone; /* abbreviation of timezone name */ | ||
308 | long tm_gmtoff; /* offset from UTC in seconds */ | ||
309 | }; | ||
310 | /* Convert a tm structure (struct tm *) found in <time.h> to a Sytm structure */ | ||
311 | #define STRUCT_TM_TO_SYTM(pTM, pSYTM) \ | ||
312 | (pSYTM)->tm_hour = (pTM)->tm_hour;\ | ||
313 | (pSYTM)->tm_min = (pTM)->tm_min;\ | ||
314 | (pSYTM)->tm_sec = (pTM)->tm_sec;\ | ||
315 | (pSYTM)->tm_mon = (pTM)->tm_mon;\ | ||
316 | (pSYTM)->tm_mday = (pTM)->tm_mday;\ | ||
317 | (pSYTM)->tm_year = (pTM)->tm_year + 1900;\ | ||
318 | (pSYTM)->tm_yday = (pTM)->tm_yday;\ | ||
319 | (pSYTM)->tm_wday = (pTM)->tm_wday;\ | ||
320 | (pSYTM)->tm_isdst = (pTM)->tm_isdst;\ | ||
321 | (pSYTM)->tm_gmtoff = 0;\ | ||
322 | (pSYTM)->tm_zone = 0; | ||
323 | |||
324 | /* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */ | ||
325 | #define SYSTEMTIME_TO_SYTM(pSYSTIME, pSYTM) \ | ||
326 | (pSYTM)->tm_hour = (pSYSTIME)->wHour;\ | ||
327 | (pSYTM)->tm_min = (pSYSTIME)->wMinute;\ | ||
328 | (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\ | ||
329 | (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\ | ||
330 | (pSYTM)->tm_mday = (pSYSTIME)->wDay;\ | ||
331 | (pSYTM)->tm_year = (pSYSTIME)->wYear;\ | ||
332 | (pSYTM)->tm_yday = 0;\ | ||
333 | (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\ | ||
334 | (pSYTM)->tm_gmtoff = 0;\ | ||
335 | (pSYTM)->tm_isdst = -1;\ | ||
336 | (pSYTM)->tm_zone = 0; | ||
337 | |||
338 | /* Dynamic memory allocation methods. */ | ||
339 | struct SyMemMethods | ||
340 | { | ||
341 | void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ | ||
342 | void * (*xRealloc)(void *, unsigned int); /* [Required:] Re-allocate a memory chunk */ | ||
343 | void (*xFree)(void *); /* [Required:] Release a memory chunk */ | ||
344 | unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ | ||
345 | int (*xInit)(void *); /* [Optional:] Initialization callback */ | ||
346 | void (*xRelease)(void *); /* [Optional:] Release callback */ | ||
347 | void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */ | ||
348 | }; | ||
349 | /* Out of memory callback signature. */ | ||
350 | typedef int (*ProcMemError)(void *); | ||
351 | /* Mutex methods. */ | ||
352 | struct SyMutexMethods | ||
353 | { | ||
354 | int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */ | ||
355 | void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */ | ||
356 | SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */ | ||
357 | void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */ | ||
358 | void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */ | ||
359 | int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */ | ||
360 | void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */ | ||
361 | }; | ||
362 | #if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec) | ||
363 | #define SX_APIIMPORT __declspec(dllimport) | ||
364 | #define SX_APIEXPORT __declspec(dllexport) | ||
365 | #else | ||
366 | #define SX_APIIMPORT | ||
367 | #define SX_APIEXPORT | ||
368 | #endif | ||
369 | /* Standard return values from Symisc public interfaces */ | ||
370 | #define SXRET_OK 0 /* Not an error */ | ||
371 | #define SXERR_MEM (-1) /* Out of memory */ | ||
372 | #define SXERR_IO (-2) /* IO error */ | ||
373 | #define SXERR_EMPTY (-3) /* Empty field */ | ||
374 | #define SXERR_LOCKED (-4) /* Locked operation */ | ||
375 | #define SXERR_ORANGE (-5) /* Out of range value */ | ||
376 | #define SXERR_NOTFOUND (-6) /* Item not found */ | ||
377 | #define SXERR_LIMIT (-7) /* Limit reached */ | ||
378 | #define SXERR_MORE (-8) /* Need more input */ | ||
379 | #define SXERR_INVALID (-9) /* Invalid parameter */ | ||
380 | #define SXERR_ABORT (-10) /* User callback request an operation abort */ | ||
381 | #define SXERR_EXISTS (-11) /* Item exists */ | ||
382 | #define SXERR_SYNTAX (-12) /* Syntax error */ | ||
383 | #define SXERR_UNKNOWN (-13) /* Unknown error */ | ||
384 | #define SXERR_BUSY (-14) /* Busy operation */ | ||
385 | #define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */ | ||
386 | #define SXERR_WILLBLOCK (-16) /* Operation will block */ | ||
387 | #define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */ | ||
388 | #define SXERR_EOF (-18) /* End of input */ | ||
389 | #define SXERR_PERM (-19) /* Permission error */ | ||
390 | #define SXERR_NOOP (-20) /* No-op */ | ||
391 | #define SXERR_FORMAT (-21) /* Invalid format */ | ||
392 | #define SXERR_NEXT (-22) /* Not an error */ | ||
393 | #define SXERR_OS (-23) /* System call return an error */ | ||
394 | #define SXERR_CORRUPT (-24) /* Corrupted pointer */ | ||
395 | #define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */ | ||
396 | #define SXERR_NOMATCH (-26) /* No match */ | ||
397 | #define SXERR_RESET (-27) /* Operation reset */ | ||
398 | #define SXERR_DONE (-28) /* Not an error */ | ||
399 | #define SXERR_SHORT (-29) /* Buffer too short */ | ||
400 | #define SXERR_PATH (-30) /* Path error */ | ||
401 | #define SXERR_TIMEOUT (-31) /* Timeout */ | ||
402 | #define SXERR_BIG (-32) /* Too big for processing */ | ||
403 | #define SXERR_RETRY (-33) /* Retry your call */ | ||
404 | #define SXERR_IGNORE (-63) /* Ignore */ | ||
405 | #endif /* SYMISC_PUBLIC_DEFS */ | ||
406 | /* | ||
407 | * Marker for exported interfaces. | ||
408 | */ | ||
409 | #define UNQLITE_APIEXPORT SX_APIEXPORT | ||
410 | /* | ||
411 | * If compiling for a processor that lacks floating point | ||
412 | * support, substitute integer for floating-point. | ||
413 | */ | ||
414 | #ifdef UNQLITE_OMIT_FLOATING_POINT | ||
415 | typedef sxi64 uqlite_real; | ||
416 | #else | ||
417 | typedef double unqlite_real; | ||
418 | #endif | ||
419 | typedef sxi64 unqlite_int64; | ||
420 | /* Standard UnQLite return values */ | ||
421 | #define UNQLITE_OK SXRET_OK /* Successful result */ | ||
422 | /* Beginning of error codes */ | ||
423 | #define UNQLITE_NOMEM SXERR_MEM /* Out of memory */ | ||
424 | #define UNQLITE_ABORT SXERR_ABORT /* Another thread have released this instance */ | ||
425 | #define UNQLITE_IOERR SXERR_IO /* IO error */ | ||
426 | #define UNQLITE_CORRUPT SXERR_CORRUPT /* Corrupt pointer */ | ||
427 | #define UNQLITE_LOCKED SXERR_LOCKED /* Forbidden Operation */ | ||
428 | #define UNQLITE_BUSY SXERR_BUSY /* The database file is locked */ | ||
429 | #define UNQLITE_DONE SXERR_DONE /* Operation done */ | ||
430 | #define UNQLITE_PERM SXERR_PERM /* Permission error */ | ||
431 | #define UNQLITE_NOTIMPLEMENTED SXERR_NOTIMPLEMENTED /* Method not implemented by the underlying Key/Value storage engine */ | ||
432 | #define UNQLITE_NOTFOUND SXERR_NOTFOUND /* No such record */ | ||
433 | #define UNQLITE_NOOP SXERR_NOOP /* No such method */ | ||
434 | #define UNQLITE_INVALID SXERR_INVALID /* Invalid parameter */ | ||
435 | #define UNQLITE_EOF SXERR_EOF /* End Of Input */ | ||
436 | #define UNQLITE_UNKNOWN SXERR_UNKNOWN /* Unknown configuration option */ | ||
437 | #define UNQLITE_LIMIT SXERR_LIMIT /* Database limit reached */ | ||
438 | #define UNQLITE_EXISTS SXERR_EXISTS /* Record exists */ | ||
439 | #define UNQLITE_EMPTY SXERR_EMPTY /* Empty record */ | ||
440 | #define UNQLITE_COMPILE_ERR (-70) /* Compilation error */ | ||
441 | #define UNQLITE_VM_ERR (-71) /* Virtual machine error */ | ||
442 | #define UNQLITE_FULL (-73) /* Full database (unlikely) */ | ||
443 | #define UNQLITE_CANTOPEN (-74) /* Unable to open the database file */ | ||
444 | #define UNQLITE_READ_ONLY (-75) /* Read only Key/Value storage engine */ | ||
445 | #define UNQLITE_LOCKERR (-76) /* Locking protocol error */ | ||
446 | /* end-of-error-codes */ | ||
447 | /* | ||
448 | * Database Handle Configuration Commands. | ||
449 | * | ||
450 | * The following set of constants are the available configuration verbs that can | ||
451 | * be used by the host-application to configure an UnQLite database handle. | ||
452 | * These constants must be passed as the second argument to [unqlite_config()]. | ||
453 | * | ||
454 | * Each options require a variable number of arguments. | ||
455 | * The [unqlite_config()] interface will return UNQLITE_OK on success, any other | ||
456 | * return value indicates failure. | ||
457 | * For a full discussion on the configuration verbs and their expected | ||
458 | * parameters, please refer to this page: | ||
459 | * http://unqlite.org/c_api/unqlite_config.html | ||
460 | */ | ||
461 | #define UNQLITE_CONFIG_JX9_ERR_LOG 1 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ | ||
462 | #define UNQLITE_CONFIG_MAX_PAGE_CACHE 2 /* ONE ARGUMENT: int nMaxPage */ | ||
463 | #define UNQLITE_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ | ||
464 | #define UNQLITE_CONFIG_KV_ENGINE 4 /* ONE ARGUMENT: const char *zKvName */ | ||
465 | #define UNQLITE_CONFIG_DISABLE_AUTO_COMMIT 5 /* NO ARGUMENTS */ | ||
466 | #define UNQLITE_CONFIG_GET_KV_NAME 6 /* ONE ARGUMENT: const char **pzPtr */ | ||
467 | /* | ||
468 | * UnQLite/Jx9 Virtual Machine Configuration Commands. | ||
469 | * | ||
470 | * The following set of constants are the available configuration verbs that can | ||
471 | * be used by the host-application to configure the Jx9 (Via UnQLite) Virtual machine. | ||
472 | * These constants must be passed as the second argument to the [unqlite_vm_config()] | ||
473 | * interface. | ||
474 | * Each options require a variable number of arguments. | ||
475 | * The [unqlite_vm_config()] interface will return UNQLITE_OK on success, any other return | ||
476 | * value indicates failure. | ||
477 | * There are many options but the most importants are: UNQLITE_VM_CONFIG_OUTPUT which install | ||
478 | * a VM output consumer callback, UNQLITE_VM_CONFIG_HTTP_REQUEST which parse and register | ||
479 | * a HTTP request and UNQLITE_VM_CONFIG_ARGV_ENTRY which populate the $argv array. | ||
480 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
481 | * refer to this page: | ||
482 | * http://unqlite.org/c_api/unqlite_vm_config.html | ||
483 | */ | ||
484 | #define UNQLITE_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ | ||
485 | #define UNQLITE_VM_CONFIG_IMPORT_PATH 2 /* ONE ARGUMENT: const char *zIncludePath */ | ||
486 | #define UNQLITE_VM_CONFIG_ERR_REPORT 3 /* NO ARGUMENTS: Report all run-time errors in the VM output */ | ||
487 | #define UNQLITE_VM_CONFIG_RECURSION_DEPTH 4 /* ONE ARGUMENT: int nMaxDepth */ | ||
488 | #define UNQLITE_VM_OUTPUT_LENGTH 5 /* ONE ARGUMENT: unsigned int *pLength */ | ||
489 | #define UNQLITE_VM_CONFIG_CREATE_VAR 6 /* TWO ARGUMENTS: const char *zName, unqlite_value *pValue */ | ||
490 | #define UNQLITE_VM_CONFIG_HTTP_REQUEST 7 /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ | ||
491 | #define UNQLITE_VM_CONFIG_SERVER_ATTR 8 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ | ||
492 | #define UNQLITE_VM_CONFIG_ENV_ATTR 9 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ | ||
493 | #define UNQLITE_VM_CONFIG_EXEC_VALUE 10 /* ONE ARGUMENT: unqlite_value **ppValue */ | ||
494 | #define UNQLITE_VM_CONFIG_IO_STREAM 11 /* ONE ARGUMENT: const unqlite_io_stream *pStream */ | ||
495 | #define UNQLITE_VM_CONFIG_ARGV_ENTRY 12 /* ONE ARGUMENT: const char *zValue */ | ||
496 | #define UNQLITE_VM_CONFIG_EXTRACT_OUTPUT 13 /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ | ||
497 | /* | ||
498 | * Storage engine configuration commands. | ||
499 | * | ||
500 | * The following set of constants are the available configuration verbs that can | ||
501 | * be used by the host-application to configure the underlying storage engine (i.e Hash, B+tree, R+tree). | ||
502 | * These constants must be passed as the first argument to [unqlite_kv_config()]. | ||
503 | * Each options require a variable number of arguments. | ||
504 | * The [unqlite_kv_config()] interface will return UNQLITE_OK on success, any other return | ||
505 | * value indicates failure. | ||
506 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
507 | * refer to this page: | ||
508 | * http://unqlite.org/c_api/unqlite_kv_config.html | ||
509 | */ | ||
510 | #define UNQLITE_KV_CONFIG_HASH_FUNC 1 /* ONE ARGUMENT: unsigned int (*xHash)(const void *,unsigned int) */ | ||
511 | #define UNQLITE_KV_CONFIG_CMP_FUNC 2 /* ONE ARGUMENT: int (*xCmp)(const void *,const void *,unsigned int) */ | ||
512 | /* | ||
513 | * Global Library Configuration Commands. | ||
514 | * | ||
515 | * The following set of constants are the available configuration verbs that can | ||
516 | * be used by the host-application to configure the whole library. | ||
517 | * These constants must be passed as the first argument to [unqlite_lib_config()]. | ||
518 | * | ||
519 | * Each options require a variable number of arguments. | ||
520 | * The [unqlite_lib_config()] interface will return UNQLITE_OK on success, any other return | ||
521 | * value indicates failure. | ||
522 | * Notes: | ||
523 | * The default configuration is recommended for most applications and so the call to | ||
524 | * [unqlite_lib_config()] is usually not necessary. It is provided to support rare | ||
525 | * applications with unusual needs. | ||
526 | * The [unqlite_lib_config()] interface is not threadsafe. The application must insure that | ||
527 | * no other [unqlite_*()] interfaces are invoked by other threads while [unqlite_lib_config()] | ||
528 | * is running. Furthermore, [unqlite_lib_config()] may only be invoked prior to library | ||
529 | * initialization using [unqlite_lib_init()] or [unqlite_init()] or after shutdown | ||
530 | * by [unqlite_lib_shutdown()]. If [unqlite_lib_config()] is called after [unqlite_lib_init()] | ||
531 | * or [unqlite_init()] and before [unqlite_lib_shutdown()] then it will return UNQLITE_LOCKED. | ||
532 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
533 | * refer to this page: | ||
534 | * http://unqlite.org/c_api/unqlite_lib.html | ||
535 | */ | ||
536 | #define UNQLITE_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ | ||
537 | #define UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ | ||
538 | #define UNQLITE_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ | ||
539 | #define UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ | ||
540 | #define UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ | ||
541 | #define UNQLITE_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const unqlite_vfs *pVfs */ | ||
542 | #define UNQLITE_LIB_CONFIG_STORAGE_ENGINE 7 /* ONE ARGUMENT: unqlite_kv_methods *pStorage */ | ||
543 | #define UNQLITE_LIB_CONFIG_PAGE_SIZE 8 /* ONE ARGUMENT: int iPageSize */ | ||
544 | /* | ||
545 | * These bit values are intended for use in the 3rd parameter to the [unqlite_open()] interface | ||
546 | * and in the 4th parameter to the xOpen method of the [unqlite_vfs] object. | ||
547 | */ | ||
548 | #define UNQLITE_OPEN_READONLY 0x00000001 /* Read only mode. Ok for [unqlite_open] */ | ||
549 | #define UNQLITE_OPEN_READWRITE 0x00000002 /* Ok for [unqlite_open] */ | ||
550 | #define UNQLITE_OPEN_CREATE 0x00000004 /* Ok for [unqlite_open] */ | ||
551 | #define UNQLITE_OPEN_EXCLUSIVE 0x00000008 /* VFS only */ | ||
552 | #define UNQLITE_OPEN_TEMP_DB 0x00000010 /* VFS only */ | ||
553 | #define UNQLITE_OPEN_NOMUTEX 0x00000020 /* Ok for [unqlite_open] */ | ||
554 | #define UNQLITE_OPEN_OMIT_JOURNALING 0x00000040 /* Omit journaling for this database. Ok for [unqlite_open] */ | ||
555 | #define UNQLITE_OPEN_IN_MEMORY 0x00000080 /* An in memory database. Ok for [unqlite_open]*/ | ||
556 | #define UNQLITE_OPEN_MMAP 0x00000100 /* Obtain a memory view of the whole file. Ok for [unqlite_open] */ | ||
557 | /* | ||
558 | * Synchronization Type Flags | ||
559 | * | ||
560 | * When UnQLite invokes the xSync() method of an [unqlite_io_methods] object it uses | ||
561 | * a combination of these integer values as the second argument. | ||
562 | * | ||
563 | * When the UNQLITE_SYNC_DATAONLY flag is used, it means that the sync operation only | ||
564 | * needs to flush data to mass storage. Inode information need not be flushed. | ||
565 | * If the lower four bits of the flag equal UNQLITE_SYNC_NORMAL, that means to use normal | ||
566 | * fsync() semantics. If the lower four bits equal UNQLITE_SYNC_FULL, that means to use | ||
567 | * Mac OS X style fullsync instead of fsync(). | ||
568 | */ | ||
569 | #define UNQLITE_SYNC_NORMAL 0x00002 | ||
570 | #define UNQLITE_SYNC_FULL 0x00003 | ||
571 | #define UNQLITE_SYNC_DATAONLY 0x00010 | ||
572 | /* | ||
573 | * File Locking Levels | ||
574 | * | ||
575 | * UnQLite uses one of these integer values as the second | ||
576 | * argument to calls it makes to the xLock() and xUnlock() methods | ||
577 | * of an [unqlite_io_methods] object. | ||
578 | */ | ||
579 | #define UNQLITE_LOCK_NONE 0 | ||
580 | #define UNQLITE_LOCK_SHARED 1 | ||
581 | #define UNQLITE_LOCK_RESERVED 2 | ||
582 | #define UNQLITE_LOCK_PENDING 3 | ||
583 | #define UNQLITE_LOCK_EXCLUSIVE 4 | ||
584 | /* | ||
585 | * CAPIREF: OS Interface: Open File Handle | ||
586 | * | ||
587 | * An [unqlite_file] object represents an open file in the [unqlite_vfs] OS interface | ||
588 | * layer. | ||
589 | * Individual OS interface implementations will want to subclass this object by appending | ||
590 | * additional fields for their own use. The pMethods entry is a pointer to an | ||
591 | * [unqlite_io_methods] object that defines methods for performing | ||
592 | * I/O operations on the open file. | ||
593 | */ | ||
594 | typedef struct unqlite_file unqlite_file; | ||
595 | struct unqlite_file { | ||
596 | const unqlite_io_methods *pMethods; /* Methods for an open file. MUST BE FIRST */ | ||
597 | }; | ||
598 | /* | ||
599 | * CAPIREF: OS Interface: File Methods Object | ||
600 | * | ||
601 | * Every file opened by the [unqlite_vfs] xOpen method populates an | ||
602 | * [unqlite_file] object (or, more commonly, a subclass of the | ||
603 | * [unqlite_file] object) with a pointer to an instance of this object. | ||
604 | * This object defines the methods used to perform various operations | ||
605 | * against the open file represented by the [unqlite_file] object. | ||
606 | * | ||
607 | * If the xOpen method sets the unqlite_file.pMethods element | ||
608 | * to a non-NULL pointer, then the unqlite_io_methods.xClose method | ||
609 | * may be invoked even if the xOpen reported that it failed. The | ||
610 | * only way to prevent a call to xClose following a failed xOpen | ||
611 | * is for the xOpen to set the unqlite_file.pMethods element to NULL. | ||
612 | * | ||
613 | * The flags argument to xSync may be one of [UNQLITE_SYNC_NORMAL] or | ||
614 | * [UNQLITE_SYNC_FULL]. The first choice is the normal fsync(). | ||
615 | * The second choice is a Mac OS X style fullsync. The [UNQLITE_SYNC_DATAONLY] | ||
616 | * flag may be ORed in to indicate that only the data of the file | ||
617 | * and not its inode needs to be synced. | ||
618 | * | ||
619 | * The integer values to xLock() and xUnlock() are one of | ||
620 | * | ||
621 | * UNQLITE_LOCK_NONE | ||
622 | * UNQLITE_LOCK_SHARED | ||
623 | * UNQLITE_LOCK_RESERVED | ||
624 | * UNQLITE_LOCK_PENDING | ||
625 | * UNQLITE_LOCK_EXCLUSIVE | ||
626 | * | ||
627 | * xLock() increases the lock. xUnlock() decreases the lock. | ||
628 | * The xCheckReservedLock() method checks whether any database connection, | ||
629 | * either in this process or in some other process, is holding a RESERVED, | ||
630 | * PENDING, or EXCLUSIVE lock on the file. It returns true if such a lock exists | ||
631 | * and false otherwise. | ||
632 | * | ||
633 | * The xSectorSize() method returns the sector size of the device that underlies | ||
634 | * the file. The sector size is the minimum write that can be performed without | ||
635 | * disturbing other bytes in the file. | ||
636 | * | ||
637 | */ | ||
638 | struct unqlite_io_methods { | ||
639 | int iVersion; /* Structure version number (currently 1) */ | ||
640 | int (*xClose)(unqlite_file*); | ||
641 | int (*xRead)(unqlite_file*, void*, unqlite_int64 iAmt, unqlite_int64 iOfst); | ||
642 | int (*xWrite)(unqlite_file*, const void*, unqlite_int64 iAmt, unqlite_int64 iOfst); | ||
643 | int (*xTruncate)(unqlite_file*, unqlite_int64 size); | ||
644 | int (*xSync)(unqlite_file*, int flags); | ||
645 | int (*xFileSize)(unqlite_file*, unqlite_int64 *pSize); | ||
646 | int (*xLock)(unqlite_file*, int); | ||
647 | int (*xUnlock)(unqlite_file*, int); | ||
648 | int (*xCheckReservedLock)(unqlite_file*, int *pResOut); | ||
649 | int (*xSectorSize)(unqlite_file*); | ||
650 | }; | ||
651 | /* | ||
652 | * CAPIREF: OS Interface Object | ||
653 | * | ||
654 | * An instance of the unqlite_vfs object defines the interface between | ||
655 | * the UnQLite core and the underlying operating system. The "vfs" | ||
656 | * in the name of the object stands for "Virtual File System". | ||
657 | * | ||
658 | * Only a single vfs can be registered within the UnQLite core. | ||
659 | * Vfs registration is done using the [unqlite_lib_config()] interface | ||
660 | * with a configuration verb set to UNQLITE_LIB_CONFIG_VFS. | ||
661 | * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users | ||
662 | * does not have to worry about registering and installing a vfs since UnQLite | ||
663 | * come with a built-in vfs for these platforms that implements most the methods | ||
664 | * defined below. | ||
665 | * | ||
666 | * Clients running on exotic systems (ie: Other than Windows and UNIX systems) | ||
667 | * must register their own vfs in order to be able to use the UnQLite library. | ||
668 | * | ||
669 | * The value of the iVersion field is initially 1 but may be larger in | ||
670 | * future versions of UnQLite. | ||
671 | * | ||
672 | * The szOsFile field is the size of the subclassed [unqlite_file] structure | ||
673 | * used by this VFS. mxPathname is the maximum length of a pathname in this VFS. | ||
674 | * | ||
675 | * At least szOsFile bytes of memory are allocated by UnQLite to hold the [unqlite_file] | ||
676 | * structure passed as the third argument to xOpen. The xOpen method does not have to | ||
677 | * allocate the structure; it should just fill it in. Note that the xOpen method must | ||
678 | * set the unqlite_file.pMethods to either a valid [unqlite_io_methods] object or to NULL. | ||
679 | * xOpen must do this even if the open fails. UnQLite expects that the unqlite_file.pMethods | ||
680 | * element will be valid after xOpen returns regardless of the success or failure of the | ||
681 | * xOpen call. | ||
682 | * | ||
683 | */ | ||
684 | struct unqlite_vfs { | ||
685 | const char *zName; /* Name of this virtual file system [i.e: Windows, UNIX, etc.] */ | ||
686 | int iVersion; /* Structure version number (currently 1) */ | ||
687 | int szOsFile; /* Size of subclassed unqlite_file */ | ||
688 | int mxPathname; /* Maximum file pathname length */ | ||
689 | int (*xOpen)(unqlite_vfs*, const char *zName, unqlite_file*,unsigned int flags); | ||
690 | int (*xDelete)(unqlite_vfs*, const char *zName, int syncDir); | ||
691 | int (*xAccess)(unqlite_vfs*, const char *zName, int flags, int *pResOut); | ||
692 | int (*xFullPathname)(unqlite_vfs*, const char *zName,int buf_len,char *zBuf); | ||
693 | int (*xTmpDir)(unqlite_vfs*,char *zBuf,int buf_len); | ||
694 | int (*xSleep)(unqlite_vfs*, int microseconds); | ||
695 | int (*xCurrentTime)(unqlite_vfs*,Sytm *pOut); | ||
696 | int (*xGetLastError)(unqlite_vfs*, int, char *); | ||
697 | }; | ||
698 | /* | ||
699 | * Flags for the xAccess VFS method | ||
700 | * | ||
701 | * These integer constants can be used as the third parameter to | ||
702 | * the xAccess method of an [unqlite_vfs] object. They determine | ||
703 | * what kind of permissions the xAccess method is looking for. | ||
704 | * With UNQLITE_ACCESS_EXISTS, the xAccess method | ||
705 | * simply checks whether the file exists. | ||
706 | * With UNQLITE_ACCESS_READWRITE, the xAccess method | ||
707 | * checks whether the named directory is both readable and writable | ||
708 | * (in other words, if files can be added, removed, and renamed within | ||
709 | * the directory). | ||
710 | * The UNQLITE_ACCESS_READWRITE constant is currently used only by the | ||
711 | * [temp_store_directory pragma], though this could change in a future | ||
712 | * release of UnQLite. | ||
713 | * With UNQLITE_ACCESS_READ, the xAccess method | ||
714 | * checks whether the file is readable. The UNQLITE_ACCESS_READ constant is | ||
715 | * currently unused, though it might be used in a future release of | ||
716 | * UnQLite. | ||
717 | */ | ||
718 | #define UNQLITE_ACCESS_EXISTS 0 | ||
719 | #define UNQLITE_ACCESS_READWRITE 1 | ||
720 | #define UNQLITE_ACCESS_READ 2 | ||
721 | /* | ||
722 | * The type used to represent a page number. The first page in a file | ||
723 | * is called page 1. 0 is used to represent "not a page". | ||
724 | * A page number is an unsigned 64-bit integer. | ||
725 | */ | ||
726 | typedef sxu64 pgno; | ||
727 | /* | ||
728 | * A database disk page is represented by an instance | ||
729 | * of the follwoing structure. | ||
730 | */ | ||
731 | typedef struct unqlite_page unqlite_page; | ||
732 | struct unqlite_page | ||
733 | { | ||
734 | unsigned char *zData; /* Content of this page */ | ||
735 | void *pUserData; /* Extra content */ | ||
736 | pgno pgno; /* Page number for this page */ | ||
737 | }; | ||
738 | /* | ||
739 | * UnQLite handle to the underlying Key/Value Storage Engine (See below). | ||
740 | */ | ||
741 | typedef void * unqlite_kv_handle; | ||
742 | /* | ||
743 | * UnQLite pager IO methods. | ||
744 | * | ||
745 | * An instance of the following structure define the exported methods of the UnQLite pager | ||
746 | * to the underlying Key/Value storage engine. | ||
747 | */ | ||
748 | typedef struct unqlite_kv_io unqlite_kv_io; | ||
749 | struct unqlite_kv_io | ||
750 | { | ||
751 | unqlite_kv_handle pHandle; /* UnQLite handle passed as the first parameter to the | ||
752 | * method defined below. | ||
753 | */ | ||
754 | unqlite_kv_methods *pMethods; /* Underlying storage engine */ | ||
755 | /* Pager methods */ | ||
756 | int (*xGet)(unqlite_kv_handle,pgno,unqlite_page **); | ||
757 | int (*xLookup)(unqlite_kv_handle,pgno,unqlite_page **); | ||
758 | int (*xNew)(unqlite_kv_handle,unqlite_page **); | ||
759 | int (*xWrite)(unqlite_page *); | ||
760 | int (*xDontWrite)(unqlite_page *); | ||
761 | int (*xDontJournal)(unqlite_page *); | ||
762 | int (*xDontMkHot)(unqlite_page *); | ||
763 | int (*xPageRef)(unqlite_page *); | ||
764 | int (*xPageUnref)(unqlite_page *); | ||
765 | int (*xPageSize)(unqlite_kv_handle); | ||
766 | int (*xReadOnly)(unqlite_kv_handle); | ||
767 | unsigned char * (*xTmpPage)(unqlite_kv_handle); | ||
768 | void (*xSetUnpin)(unqlite_kv_handle,void (*xPageUnpin)(void *)); | ||
769 | void (*xSetReload)(unqlite_kv_handle,void (*xPageReload)(void *)); | ||
770 | void (*xErr)(unqlite_kv_handle,const char *); | ||
771 | }; | ||
772 | /* | ||
773 | * Key/Value Storage Engine Cursor Object | ||
774 | * | ||
775 | * An instance of a subclass of the following object defines a cursor | ||
776 | * used to scan through a key-value storage engine. | ||
777 | */ | ||
778 | typedef struct unqlite_kv_cursor unqlite_kv_cursor; | ||
779 | struct unqlite_kv_cursor | ||
780 | { | ||
781 | unqlite_kv_engine *pStore; /* Must be first */ | ||
782 | /* Subclasses will typically add additional fields */ | ||
783 | }; | ||
784 | /* | ||
785 | * Possible seek positions. | ||
786 | */ | ||
787 | #define UNQLITE_CURSOR_MATCH_EXACT 1 | ||
788 | #define UNQLITE_CURSOR_MATCH_LE 2 | ||
789 | #define UNQLITE_CURSOR_MATCH_GE 3 | ||
790 | /* | ||
791 | * Key/Value Storage Engine. | ||
792 | * | ||
793 | * A Key-Value storage engine is defined by an instance of the following | ||
794 | * object. | ||
795 | * UnQLite works with run-time interchangeable storage engines (i.e. Hash, B+Tree, R+Tree, LSM, etc.). | ||
796 | * The storage engine works with key/value pairs where both the key | ||
797 | * and the value are byte arrays of arbitrary length and with no restrictions on content. | ||
798 | * UnQLite come with two built-in KV storage engine: A Virtual Linear Hash (VLH) storage | ||
799 | * engine is used for persistent on-disk databases with O(1) lookup time and an in-memory | ||
800 | * hash-table or Red-black tree storage engine is used for in-memory databases. | ||
801 | * Future versions of UnQLite might add other built-in storage engines (i.e. LSM). | ||
802 | * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] | ||
803 | * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. | ||
804 | */ | ||
805 | struct unqlite_kv_engine | ||
806 | { | ||
807 | const unqlite_kv_io *pIo; /* IO methods: MUST be first */ | ||
808 | /* Subclasses will typically add additional fields */ | ||
809 | }; | ||
810 | /* | ||
811 | * Key/Value Storage Engine Virtual Method Table. | ||
812 | * | ||
813 | * Key/Value storage engine methods is defined by an instance of the following | ||
814 | * object. | ||
815 | * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] | ||
816 | * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. | ||
817 | */ | ||
818 | struct unqlite_kv_methods | ||
819 | { | ||
820 | const char *zName; /* Storage engine name [i.e. Hash, B+tree, LSM, R-tree, Mem, etc.]*/ | ||
821 | int szKv; /* 'unqlite_kv_engine' subclass size */ | ||
822 | int szCursor; /* 'unqlite_kv_cursor' subclass size */ | ||
823 | int iVersion; /* Structure version, currently 1 */ | ||
824 | /* Storage engine methods */ | ||
825 | int (*xInit)(unqlite_kv_engine *,int iPageSize); | ||
826 | void (*xRelease)(unqlite_kv_engine *); | ||
827 | int (*xConfig)(unqlite_kv_engine *,int op,va_list ap); | ||
828 | int (*xOpen)(unqlite_kv_engine *,pgno); | ||
829 | int (*xReplace)( | ||
830 | unqlite_kv_engine *, | ||
831 | const void *pKey,int nKeyLen, | ||
832 | const void *pData,unqlite_int64 nDataLen | ||
833 | ); | ||
834 | int (*xAppend)( | ||
835 | unqlite_kv_engine *, | ||
836 | const void *pKey,int nKeyLen, | ||
837 | const void *pData,unqlite_int64 nDataLen | ||
838 | ); | ||
839 | void (*xCursorInit)(unqlite_kv_cursor *); | ||
840 | int (*xSeek)(unqlite_kv_cursor *,const void *pKey,int nByte,int iPos); /* Mandatory */ | ||
841 | int (*xFirst)(unqlite_kv_cursor *); | ||
842 | int (*xLast)(unqlite_kv_cursor *); | ||
843 | int (*xValid)(unqlite_kv_cursor *); | ||
844 | int (*xNext)(unqlite_kv_cursor *); | ||
845 | int (*xPrev)(unqlite_kv_cursor *); | ||
846 | int (*xDelete)(unqlite_kv_cursor *); | ||
847 | int (*xKeyLength)(unqlite_kv_cursor *,int *); | ||
848 | int (*xKey)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
849 | int (*xDataLength)(unqlite_kv_cursor *,unqlite_int64 *); | ||
850 | int (*xData)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
851 | void (*xReset)(unqlite_kv_cursor *); | ||
852 | void (*xCursorRelease)(unqlite_kv_cursor *); | ||
853 | }; | ||
854 | /* | ||
855 | * UnQLite journal file suffix. | ||
856 | */ | ||
857 | #ifndef UNQLITE_JOURNAL_FILE_SUFFIX | ||
858 | #define UNQLITE_JOURNAL_FILE_SUFFIX "_unqlite_journal" | ||
859 | #endif | ||
860 | /* | ||
861 | * Call Context - Error Message Serverity Level. | ||
862 | * | ||
863 | * The following constans are the allowed severity level that can | ||
864 | * passed as the second argument to the [unqlite_context_throw_error()] or | ||
865 | * [unqlite_context_throw_error_format()] interfaces. | ||
866 | * Refer to the official documentation for additional information. | ||
867 | */ | ||
868 | #define UNQLITE_CTX_ERR 1 /* Call context error such as unexpected number of arguments, invalid types and so on. */ | ||
869 | #define UNQLITE_CTX_WARNING 2 /* Call context Warning */ | ||
870 | #define UNQLITE_CTX_NOTICE 3 /* Call context Notice */ | ||
871 | /* | ||
872 | * C-API-REF: Please refer to the official documentation for interfaces | ||
873 | * purpose and expected parameters. | ||
874 | */ | ||
875 | |||
876 | /* Database Engine Handle */ | ||
877 | UNQLITE_APIEXPORT int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode); | ||
878 | UNQLITE_APIEXPORT int unqlite_config(unqlite *pDb,int nOp,...); | ||
879 | UNQLITE_APIEXPORT int unqlite_close(unqlite *pDb); | ||
880 | |||
881 | /* Key/Value (KV) Store Interfaces */ | ||
882 | UNQLITE_APIEXPORT int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); | ||
883 | UNQLITE_APIEXPORT int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); | ||
884 | UNQLITE_APIEXPORT int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); | ||
885 | UNQLITE_APIEXPORT int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); | ||
886 | UNQLITE_APIEXPORT int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 /* in|out */*pBufLen); | ||
887 | UNQLITE_APIEXPORT int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey, | ||
888 | int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
889 | UNQLITE_APIEXPORT int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen); | ||
890 | UNQLITE_APIEXPORT int unqlite_kv_config(unqlite *pDb,int iOp,...); | ||
891 | |||
892 | /* Document (JSON) Store Interfaces powered by the Jx9 Scripting Language */ | ||
893 | UNQLITE_APIEXPORT int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut); | ||
894 | UNQLITE_APIEXPORT int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut); | ||
895 | UNQLITE_APIEXPORT int unqlite_vm_config(unqlite_vm *pVm,int iOp,...); | ||
896 | UNQLITE_APIEXPORT int unqlite_vm_exec(unqlite_vm *pVm); | ||
897 | UNQLITE_APIEXPORT int unqlite_vm_reset(unqlite_vm *pVm); | ||
898 | UNQLITE_APIEXPORT int unqlite_vm_release(unqlite_vm *pVm); | ||
899 | UNQLITE_APIEXPORT int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData); | ||
900 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname); | ||
901 | |||
902 | /* Cursor Iterator Interfaces */ | ||
903 | UNQLITE_APIEXPORT int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut); | ||
904 | UNQLITE_APIEXPORT int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur); | ||
905 | UNQLITE_APIEXPORT int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos); | ||
906 | UNQLITE_APIEXPORT int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor); | ||
907 | UNQLITE_APIEXPORT int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor); | ||
908 | UNQLITE_APIEXPORT int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor); | ||
909 | UNQLITE_APIEXPORT int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor); | ||
910 | UNQLITE_APIEXPORT int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor); | ||
911 | UNQLITE_APIEXPORT int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte); | ||
912 | UNQLITE_APIEXPORT int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
913 | UNQLITE_APIEXPORT int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnData); | ||
914 | UNQLITE_APIEXPORT int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
915 | UNQLITE_APIEXPORT int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor); | ||
916 | UNQLITE_APIEXPORT int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor); | ||
917 | |||
918 | /* Manual Transaction Manager */ | ||
919 | UNQLITE_APIEXPORT int unqlite_begin(unqlite *pDb); | ||
920 | UNQLITE_APIEXPORT int unqlite_commit(unqlite *pDb); | ||
921 | UNQLITE_APIEXPORT int unqlite_rollback(unqlite *pDb); | ||
922 | |||
923 | /* Utility interfaces */ | ||
924 | UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize); | ||
925 | UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize); | ||
926 | UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size); | ||
927 | UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb); | ||
928 | |||
929 | /* In-process extending interfaces */ | ||
930 | UNQLITE_APIEXPORT int unqlite_create_function(unqlite_vm *pVm,const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData); | ||
931 | UNQLITE_APIEXPORT int unqlite_delete_function(unqlite_vm *pVm, const char *zName); | ||
932 | UNQLITE_APIEXPORT int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData); | ||
933 | UNQLITE_APIEXPORT int unqlite_delete_constant(unqlite_vm *pVm, const char *zName); | ||
934 | |||
935 | /* On Demand Object allocation interfaces */ | ||
936 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm); | ||
937 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm); | ||
938 | UNQLITE_APIEXPORT int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue); | ||
939 | UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx); | ||
940 | UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_array(unqlite_context *pCtx); | ||
941 | UNQLITE_APIEXPORT void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue); | ||
942 | |||
943 | /* Dynamically Typed Value Object Management Interfaces */ | ||
944 | UNQLITE_APIEXPORT int unqlite_value_int(unqlite_value *pVal, int iValue); | ||
945 | UNQLITE_APIEXPORT int unqlite_value_int64(unqlite_value *pVal, unqlite_int64 iValue); | ||
946 | UNQLITE_APIEXPORT int unqlite_value_bool(unqlite_value *pVal, int iBool); | ||
947 | UNQLITE_APIEXPORT int unqlite_value_null(unqlite_value *pVal); | ||
948 | UNQLITE_APIEXPORT int unqlite_value_double(unqlite_value *pVal, double Value); | ||
949 | UNQLITE_APIEXPORT int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen); | ||
950 | UNQLITE_APIEXPORT int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...); | ||
951 | UNQLITE_APIEXPORT int unqlite_value_reset_string_cursor(unqlite_value *pVal); | ||
952 | UNQLITE_APIEXPORT int unqlite_value_resource(unqlite_value *pVal, void *pUserData); | ||
953 | UNQLITE_APIEXPORT int unqlite_value_release(unqlite_value *pVal); | ||
954 | |||
955 | /* Foreign Function Parameter Values */ | ||
956 | UNQLITE_APIEXPORT int unqlite_value_to_int(unqlite_value *pValue); | ||
957 | UNQLITE_APIEXPORT int unqlite_value_to_bool(unqlite_value *pValue); | ||
958 | UNQLITE_APIEXPORT unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue); | ||
959 | UNQLITE_APIEXPORT double unqlite_value_to_double(unqlite_value *pValue); | ||
960 | UNQLITE_APIEXPORT const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen); | ||
961 | UNQLITE_APIEXPORT void * unqlite_value_to_resource(unqlite_value *pValue); | ||
962 | UNQLITE_APIEXPORT int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict); | ||
963 | |||
964 | /* Setting The Result Of A Foreign Function */ | ||
965 | UNQLITE_APIEXPORT int unqlite_result_int(unqlite_context *pCtx, int iValue); | ||
966 | UNQLITE_APIEXPORT int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue); | ||
967 | UNQLITE_APIEXPORT int unqlite_result_bool(unqlite_context *pCtx, int iBool); | ||
968 | UNQLITE_APIEXPORT int unqlite_result_double(unqlite_context *pCtx, double Value); | ||
969 | UNQLITE_APIEXPORT int unqlite_result_null(unqlite_context *pCtx); | ||
970 | UNQLITE_APIEXPORT int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen); | ||
971 | UNQLITE_APIEXPORT int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...); | ||
972 | UNQLITE_APIEXPORT int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue); | ||
973 | UNQLITE_APIEXPORT int unqlite_result_resource(unqlite_context *pCtx, void *pUserData); | ||
974 | |||
975 | /* Dynamically Typed Value Object Query Interfaces */ | ||
976 | UNQLITE_APIEXPORT int unqlite_value_is_int(unqlite_value *pVal); | ||
977 | UNQLITE_APIEXPORT int unqlite_value_is_float(unqlite_value *pVal); | ||
978 | UNQLITE_APIEXPORT int unqlite_value_is_bool(unqlite_value *pVal); | ||
979 | UNQLITE_APIEXPORT int unqlite_value_is_string(unqlite_value *pVal); | ||
980 | UNQLITE_APIEXPORT int unqlite_value_is_null(unqlite_value *pVal); | ||
981 | UNQLITE_APIEXPORT int unqlite_value_is_numeric(unqlite_value *pVal); | ||
982 | UNQLITE_APIEXPORT int unqlite_value_is_callable(unqlite_value *pVal); | ||
983 | UNQLITE_APIEXPORT int unqlite_value_is_scalar(unqlite_value *pVal); | ||
984 | UNQLITE_APIEXPORT int unqlite_value_is_json_array(unqlite_value *pVal); | ||
985 | UNQLITE_APIEXPORT int unqlite_value_is_json_object(unqlite_value *pVal); | ||
986 | UNQLITE_APIEXPORT int unqlite_value_is_resource(unqlite_value *pVal); | ||
987 | UNQLITE_APIEXPORT int unqlite_value_is_empty(unqlite_value *pVal); | ||
988 | |||
989 | /* JSON Array/Object Management Interfaces */ | ||
990 | UNQLITE_APIEXPORT unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte); | ||
991 | UNQLITE_APIEXPORT int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData); | ||
992 | UNQLITE_APIEXPORT int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue); | ||
993 | UNQLITE_APIEXPORT int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue); | ||
994 | UNQLITE_APIEXPORT int unqlite_array_count(unqlite_value *pArray); | ||
995 | |||
996 | /* Call Context Handling Interfaces */ | ||
997 | UNQLITE_APIEXPORT int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen); | ||
998 | UNQLITE_APIEXPORT int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...); | ||
999 | UNQLITE_APIEXPORT int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr); | ||
1000 | UNQLITE_APIEXPORT int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...); | ||
1001 | UNQLITE_APIEXPORT unsigned int unqlite_context_random_num(unqlite_context *pCtx); | ||
1002 | UNQLITE_APIEXPORT int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen); | ||
1003 | UNQLITE_APIEXPORT void * unqlite_context_user_data(unqlite_context *pCtx); | ||
1004 | UNQLITE_APIEXPORT int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData); | ||
1005 | UNQLITE_APIEXPORT void * unqlite_context_peek_aux_data(unqlite_context *pCtx); | ||
1006 | UNQLITE_APIEXPORT unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx); | ||
1007 | UNQLITE_APIEXPORT const char * unqlite_function_name(unqlite_context *pCtx); | ||
1008 | |||
1009 | /* Call Context Memory Management Interfaces */ | ||
1010 | UNQLITE_APIEXPORT void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); | ||
1011 | UNQLITE_APIEXPORT void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte); | ||
1012 | UNQLITE_APIEXPORT void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk); | ||
1013 | |||
1014 | /* Global Library Management Interfaces */ | ||
1015 | UNQLITE_APIEXPORT int unqlite_lib_config(int nConfigOp,...); | ||
1016 | UNQLITE_APIEXPORT int unqlite_lib_init(void); | ||
1017 | UNQLITE_APIEXPORT int unqlite_lib_shutdown(void); | ||
1018 | UNQLITE_APIEXPORT int unqlite_lib_is_threadsafe(void); | ||
1019 | UNQLITE_APIEXPORT const char * unqlite_lib_version(void); | ||
1020 | UNQLITE_APIEXPORT const char * unqlite_lib_signature(void); | ||
1021 | UNQLITE_APIEXPORT const char * unqlite_lib_ident(void); | ||
1022 | UNQLITE_APIEXPORT const char * unqlite_lib_copyright(void); | ||
1023 | #ifdef __cplusplus | ||
1024 | } | ||
1025 | #endif /* __cplusplus */ | ||
1026 | #endif /* _UNQLITE_H_ */ | ||
1027 | /* | ||
1028 | * ---------------------------------------------------------- | ||
1029 | * File: jx9.h | ||
1030 | * MD5: d23a1e182f596794001533e1d6aa16a0 | ||
1031 | * ---------------------------------------------------------- | ||
1032 | */ | ||
1033 | /* This file was automatically generated. Do not edit (except for compile time directive)! */ | ||
1034 | #ifndef _JX9H_ | ||
1035 | #define _JX9H_ | ||
1036 | /* | ||
1037 | * Symisc Jx9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
1038 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
1039 | * Version 1.7.2 | ||
1040 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
1041 | * please contact Symisc Systems via: | ||
1042 | * legal@symisc.net | ||
1043 | * licensing@symisc.net | ||
1044 | * contact@symisc.net | ||
1045 | * or visit: | ||
1046 | * http://jx9.symisc.net/ | ||
1047 | */ | ||
1048 | /* | ||
1049 | * Copyright (C) 2012, 2013 Symisc Systems. All rights reserved. | ||
1050 | * | ||
1051 | * Redistribution and use in source and binary forms, with or without | ||
1052 | * modification, are permitted provided that the following conditions | ||
1053 | * are met: | ||
1054 | * 1. Redistributions of source code must retain the above copyright | ||
1055 | * notice, this list of conditions and the following disclaimer. | ||
1056 | * 2. Redistributions in binary form must reproduce the above copyright | ||
1057 | * notice, this list of conditions and the following disclaimer in the | ||
1058 | * documentation and/or other materials provided with the distribution. | ||
1059 | * 3. Redistributions in any form must be accompanied by information on | ||
1060 | * how to obtain complete source code for the JX9 engine and any | ||
1061 | * accompanying software that uses the JX9 engine software. | ||
1062 | * The source code must either be included in the distribution | ||
1063 | * or be available for no more than the cost of distribution plus | ||
1064 | * a nominal fee, and must be freely redistributable under reasonable | ||
1065 | * conditions. For an executable file, complete source code means | ||
1066 | * the source code for all modules it contains.It does not include | ||
1067 | * source code for modules or files that typically accompany the major | ||
1068 | * components of the operating system on which the executable file runs. | ||
1069 | * | ||
1070 | * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS | ||
1071 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
1072 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR | ||
1073 | * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS | ||
1074 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
1075 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
1076 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
1077 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
1078 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||
1079 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | ||
1080 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
1081 | */ | ||
1082 | /* $SymiscID: jx9.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable <chm@symisc.net> $ */ | ||
1083 | #include "unqlite.h" | ||
1084 | /* | ||
1085 | * Compile time engine version, signature, identification in the symisc source tree | ||
1086 | * and copyright notice. | ||
1087 | * Each macro have an equivalent C interface associated with it that provide the same | ||
1088 | * information but are associated with the library instead of the header file. | ||
1089 | * Refer to [jx9_lib_version()], [jx9_lib_signature()], [jx9_lib_ident()] and | ||
1090 | * [jx9_lib_copyright()] for more information. | ||
1091 | */ | ||
1092 | /* | ||
1093 | * The JX9_VERSION C preprocessor macroevaluates to a string literal | ||
1094 | * that is the jx9 version in the format "X.Y.Z" where X is the major | ||
1095 | * version number and Y is the minor version number and Z is the release | ||
1096 | * number. | ||
1097 | */ | ||
1098 | #define JX9_VERSION "1.7.2" | ||
1099 | /* | ||
1100 | * The JX9_VERSION_NUMBER C preprocessor macro resolves to an integer | ||
1101 | * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same | ||
1102 | * numbers used in [JX9_VERSION]. | ||
1103 | */ | ||
1104 | #define JX9_VERSION_NUMBER 1007002 | ||
1105 | /* | ||
1106 | * The JX9_SIG C preprocessor macro evaluates to a string | ||
1107 | * literal which is the public signature of the jx9 engine. | ||
1108 | * This signature could be included for example in a host-application | ||
1109 | * generated Server MIME header as follows: | ||
1110 | * Server: YourWebServer/x.x Jx9/x.x.x \r\n | ||
1111 | */ | ||
1112 | #define JX9_SIG "Jx9/1.7.2" | ||
1113 | /* | ||
1114 | * JX9 identification in the Symisc source tree: | ||
1115 | * Each particular check-in of a particular software released | ||
1116 | * by symisc systems have an unique identifier associated with it. | ||
1117 | * This macro hold the one associated with jx9. | ||
1118 | */ | ||
1119 | #define JX9_IDENT "jx9:d217a6e8c7f10fb35a8becb2793101fd2036aeb7" | ||
1120 | /* | ||
1121 | * Copyright notice. | ||
1122 | * If you have any questions about the licensing situation, please | ||
1123 | * visit http://jx9.symisc.net/licensing.html | ||
1124 | * or contact Symisc Systems via: | ||
1125 | * legal@symisc.net | ||
1126 | * licensing@symisc.net | ||
1127 | * contact@symisc.net | ||
1128 | */ | ||
1129 | #define JX9_COPYRIGHT "Copyright (C) Symisc Systems 2012-2013, http://jx9.symisc.net/" | ||
1130 | |||
1131 | /* Forward declaration to public objects */ | ||
1132 | typedef struct jx9_io_stream jx9_io_stream; | ||
1133 | typedef struct jx9_context jx9_context; | ||
1134 | typedef struct jx9_value jx9_value; | ||
1135 | typedef struct jx9_vfs jx9_vfs; | ||
1136 | typedef struct jx9_vm jx9_vm; | ||
1137 | typedef struct jx9 jx9; | ||
1138 | |||
1139 | #include "unqlite.h" | ||
1140 | |||
1141 | #if !defined( UNQLITE_ENABLE_JX9_HASH_FUNC ) | ||
1142 | #define JX9_DISABLE_HASH_FUNC | ||
1143 | #endif /* UNQLITE_ENABLE_JX9_HASH_FUNC */ | ||
1144 | #ifdef UNQLITE_ENABLE_THREADS | ||
1145 | #define JX9_ENABLE_THREADS | ||
1146 | #endif /* UNQLITE_ENABLE_THREADS */ | ||
1147 | /* Standard JX9 return values */ | ||
1148 | #define JX9_OK SXRET_OK /* Successful result */ | ||
1149 | /* beginning-of-error-codes */ | ||
1150 | #define JX9_NOMEM UNQLITE_NOMEM /* Out of memory */ | ||
1151 | #define JX9_ABORT UNQLITE_ABORT /* Foreign Function request operation abort/Another thread have released this instance */ | ||
1152 | #define JX9_IO_ERR UNQLITE_IOERR /* IO error */ | ||
1153 | #define JX9_CORRUPT UNQLITE_CORRUPT /* Corrupt pointer/Unknown configuration option */ | ||
1154 | #define JX9_LOOKED UNQLITE_LOCKED /* Forbidden Operation */ | ||
1155 | #define JX9_COMPILE_ERR UNQLITE_COMPILE_ERR /* Compilation error */ | ||
1156 | #define JX9_VM_ERR UNQLITE_VM_ERR /* Virtual machine error */ | ||
1157 | /* end-of-error-codes */ | ||
1158 | /* | ||
1159 | * If compiling for a processor that lacks floating point | ||
1160 | * support, substitute integer for floating-point. | ||
1161 | */ | ||
1162 | #ifdef JX9_OMIT_FLOATING_POINT | ||
1163 | typedef sxi64 jx9_real; | ||
1164 | #else | ||
1165 | typedef double jx9_real; | ||
1166 | #endif | ||
1167 | typedef sxi64 jx9_int64; | ||
1168 | /* | ||
1169 | * Engine Configuration Commands. | ||
1170 | * | ||
1171 | * The following set of constants are the available configuration verbs that can | ||
1172 | * be used by the host-application to configure the JX9 engine. | ||
1173 | * These constants must be passed as the second argument to the [jx9_config()] | ||
1174 | * interface. | ||
1175 | * Each options require a variable number of arguments. | ||
1176 | * The [jx9_config()] interface will return JX9_OK on success, any other | ||
1177 | * return value indicates failure. | ||
1178 | * For a full discussion on the configuration verbs and their expected | ||
1179 | * parameters, please refer to this page: | ||
1180 | * http://jx9.symisc.net/c_api_func.html#jx9_config | ||
1181 | */ | ||
1182 | #define JX9_CONFIG_ERR_ABORT 1 /* RESERVED FOR FUTURE USE */ | ||
1183 | #define JX9_CONFIG_ERR_LOG 2 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ | ||
1184 | /* | ||
1185 | * Virtual Machine Configuration Commands. | ||
1186 | * | ||
1187 | * The following set of constants are the available configuration verbs that can | ||
1188 | * be used by the host-application to configure the JX9 Virtual machine. | ||
1189 | * These constants must be passed as the second argument to the [jx9_vm_config()] | ||
1190 | * interface. | ||
1191 | * Each options require a variable number of arguments. | ||
1192 | * The [jx9_vm_config()] interface will return JX9_OK on success, any other return | ||
1193 | * value indicates failure. | ||
1194 | * There are many options but the most importants are: JX9_VM_CONFIG_OUTPUT which install | ||
1195 | * a VM output consumer callback, JX9_VM_CONFIG_HTTP_REQUEST which parse and register | ||
1196 | * a HTTP request and JX9_VM_CONFIG_ARGV_ENTRY which populate the $argv array. | ||
1197 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
1198 | * refer to this page: | ||
1199 | * http://jx9.symisc.net/c_api_func.html#jx9_vm_config | ||
1200 | */ | ||
1201 | #define JX9_VM_CONFIG_OUTPUT UNQLITE_VM_CONFIG_OUTPUT /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ | ||
1202 | #define JX9_VM_CONFIG_IMPORT_PATH UNQLITE_VM_CONFIG_IMPORT_PATH /* ONE ARGUMENT: const char *zIncludePath */ | ||
1203 | #define JX9_VM_CONFIG_ERR_REPORT UNQLITE_VM_CONFIG_ERR_REPORT /* NO ARGUMENTS: Report all run-time errors in the VM output */ | ||
1204 | #define JX9_VM_CONFIG_RECURSION_DEPTH UNQLITE_VM_CONFIG_RECURSION_DEPTH /* ONE ARGUMENT: int nMaxDepth */ | ||
1205 | #define JX9_VM_OUTPUT_LENGTH UNQLITE_VM_OUTPUT_LENGTH /* ONE ARGUMENT: unsigned int *pLength */ | ||
1206 | #define JX9_VM_CONFIG_CREATE_VAR UNQLITE_VM_CONFIG_CREATE_VAR /* TWO ARGUMENTS: const char *zName, jx9_value *pValue */ | ||
1207 | #define JX9_VM_CONFIG_HTTP_REQUEST UNQLITE_VM_CONFIG_HTTP_REQUEST /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ | ||
1208 | #define JX9_VM_CONFIG_SERVER_ATTR UNQLITE_VM_CONFIG_SERVER_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ | ||
1209 | #define JX9_VM_CONFIG_ENV_ATTR UNQLITE_VM_CONFIG_ENV_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ | ||
1210 | #define JX9_VM_CONFIG_EXEC_VALUE UNQLITE_VM_CONFIG_EXEC_VALUE /* ONE ARGUMENT: jx9_value **ppValue */ | ||
1211 | #define JX9_VM_CONFIG_IO_STREAM UNQLITE_VM_CONFIG_IO_STREAM /* ONE ARGUMENT: const jx9_io_stream *pStream */ | ||
1212 | #define JX9_VM_CONFIG_ARGV_ENTRY UNQLITE_VM_CONFIG_ARGV_ENTRY /* ONE ARGUMENT: const char *zValue */ | ||
1213 | #define JX9_VM_CONFIG_EXTRACT_OUTPUT UNQLITE_VM_CONFIG_EXTRACT_OUTPUT /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ | ||
1214 | /* | ||
1215 | * Global Library Configuration Commands. | ||
1216 | * | ||
1217 | * The following set of constants are the available configuration verbs that can | ||
1218 | * be used by the host-application to configure the whole library. | ||
1219 | * These constants must be passed as the first argument to the [jx9_lib_config()] | ||
1220 | * interface. | ||
1221 | * Each options require a variable number of arguments. | ||
1222 | * The [jx9_lib_config()] interface will return JX9_OK on success, any other return | ||
1223 | * value indicates failure. | ||
1224 | * Notes: | ||
1225 | * The default configuration is recommended for most applications and so the call to | ||
1226 | * [jx9_lib_config()] is usually not necessary. It is provided to support rare | ||
1227 | * applications with unusual needs. | ||
1228 | * The [jx9_lib_config()] interface is not threadsafe. The application must insure that | ||
1229 | * no other [jx9_*()] interfaces are invoked by other threads while [jx9_lib_config()] | ||
1230 | * is running. Furthermore, [jx9_lib_config()] may only be invoked prior to library | ||
1231 | * initialization using [jx9_lib_init()] or [jx9_init()] or after shutdown | ||
1232 | * by [jx9_lib_shutdown()]. If [jx9_lib_config()] is called after [jx9_lib_init()] | ||
1233 | * or [jx9_init()] and before [jx9_lib_shutdown()] then it will return jx9LOCKED. | ||
1234 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
1235 | * refer to this page: | ||
1236 | * http://jx9.symisc.net/c_api_func.html#Global_Library_Management_Interfaces | ||
1237 | */ | ||
1238 | #define JX9_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ | ||
1239 | #define JX9_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ | ||
1240 | #define JX9_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ | ||
1241 | #define JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ | ||
1242 | #define JX9_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ | ||
1243 | #define JX9_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const jx9_vfs *pVfs */ | ||
1244 | /* | ||
1245 | * Call Context - Error Message Serverity Level. | ||
1246 | */ | ||
1247 | #define JX9_CTX_ERR UNQLITE_CTX_ERR /* Call context error such as unexpected number of arguments, invalid types and so on. */ | ||
1248 | #define JX9_CTX_WARNING UNQLITE_CTX_WARNING /* Call context Warning */ | ||
1249 | #define JX9_CTX_NOTICE UNQLITE_CTX_NOTICE /* Call context Notice */ | ||
1250 | /* Current VFS structure version*/ | ||
1251 | #define JX9_VFS_VERSION 2 | ||
1252 | /* | ||
1253 | * JX9 Virtual File System (VFS). | ||
1254 | * | ||
1255 | * An instance of the jx9_vfs object defines the interface between the JX9 core | ||
1256 | * and the underlying operating system. The "vfs" in the name of the object stands | ||
1257 | * for "virtual file system". The vfs is used to implement JX9 system functions | ||
1258 | * such as mkdir(), chdir(), stat(), get_user_name() and many more. | ||
1259 | * The value of the iVersion field is initially 2 but may be larger in future versions | ||
1260 | * of JX9. | ||
1261 | * Additional fields may be appended to this object when the iVersion value is increased. | ||
1262 | * Only a single vfs can be registered within the JX9 core. Vfs registration is done | ||
1263 | * using the jx9_lib_config() interface with a configuration verb set to JX9_LIB_CONFIG_VFS. | ||
1264 | * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users does not have to | ||
1265 | * worry about registering and installing a vfs since JX9 come with a built-in vfs for these | ||
1266 | * platforms which implement most the methods defined below. | ||
1267 | * Host-application running on exotic systems (ie: Other than Windows and UNIX systems) must | ||
1268 | * register their own vfs in order to be able to use and call JX9 system functions. | ||
1269 | * Also note that the jx9_compile_file() interface depend on the xMmap() method of the underlying | ||
1270 | * vfs which mean that this method must be available (Always the case using the built-in VFS) | ||
1271 | * in order to use this interface. | ||
1272 | * Developers wishing to implement their own vfs an contact symisc systems to obtain | ||
1273 | * the JX9 VFS C/C++ Specification manual. | ||
1274 | */ | ||
1275 | struct jx9_vfs | ||
1276 | { | ||
1277 | const char *zName; /* Underlying VFS name [i.e: FreeBSD/Linux/Windows...] */ | ||
1278 | int iVersion; /* Current VFS structure version [default 2] */ | ||
1279 | /* Directory functions */ | ||
1280 | int (*xChdir)(const char *); /* Change directory */ | ||
1281 | int (*xChroot)(const char *); /* Change the root directory */ | ||
1282 | int (*xGetcwd)(jx9_context *); /* Get the current working directory */ | ||
1283 | int (*xMkdir)(const char *, int, int); /* Make directory */ | ||
1284 | int (*xRmdir)(const char *); /* Remove directory */ | ||
1285 | int (*xIsdir)(const char *); /* Tells whether the filename is a directory */ | ||
1286 | int (*xRename)(const char *, const char *); /* Renames a file or directory */ | ||
1287 | int (*xRealpath)(const char *, jx9_context *); /* Return canonicalized absolute pathname*/ | ||
1288 | /* Systems functions */ | ||
1289 | int (*xSleep)(unsigned int); /* Delay execution in microseconds */ | ||
1290 | int (*xUnlink)(const char *); /* Deletes a file */ | ||
1291 | int (*xFileExists)(const char *); /* Checks whether a file or directory exists */ | ||
1292 | int (*xChmod)(const char *, int); /* Changes file mode */ | ||
1293 | int (*xChown)(const char *, const char *); /* Changes file owner */ | ||
1294 | int (*xChgrp)(const char *, const char *); /* Changes file group */ | ||
1295 | jx9_int64 (*xFreeSpace)(const char *); /* Available space on filesystem or disk partition */ | ||
1296 | jx9_int64 (*xTotalSpace)(const char *); /* Total space on filesystem or disk partition */ | ||
1297 | jx9_int64 (*xFileSize)(const char *); /* Gets file size */ | ||
1298 | jx9_int64 (*xFileAtime)(const char *); /* Gets last access time of file */ | ||
1299 | jx9_int64 (*xFileMtime)(const char *); /* Gets file modification time */ | ||
1300 | jx9_int64 (*xFileCtime)(const char *); /* Gets inode change time of file */ | ||
1301 | int (*xStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */ | ||
1302 | int (*xlStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */ | ||
1303 | int (*xIsfile)(const char *); /* Tells whether the filename is a regular file */ | ||
1304 | int (*xIslink)(const char *); /* Tells whether the filename is a symbolic link */ | ||
1305 | int (*xReadable)(const char *); /* Tells whether a file exists and is readable */ | ||
1306 | int (*xWritable)(const char *); /* Tells whether the filename is writable */ | ||
1307 | int (*xExecutable)(const char *); /* Tells whether the filename is executable */ | ||
1308 | int (*xFiletype)(const char *, jx9_context *); /* Gets file type [i.e: fifo, dir, file..] */ | ||
1309 | int (*xGetenv)(const char *, jx9_context *); /* Gets the value of an environment variable */ | ||
1310 | int (*xSetenv)(const char *, const char *); /* Sets the value of an environment variable */ | ||
1311 | int (*xTouch)(const char *, jx9_int64, jx9_int64); /* Sets access and modification time of file */ | ||
1312 | int (*xMmap)(const char *, void **, jx9_int64 *); /* Read-only memory map of the whole file */ | ||
1313 | void (*xUnmap)(void *, jx9_int64); /* Unmap a memory view */ | ||
1314 | int (*xLink)(const char *, const char *, int); /* Create hard or symbolic link */ | ||
1315 | int (*xUmask)(int); /* Change the current umask */ | ||
1316 | void (*xTempDir)(jx9_context *); /* Get path of the temporary directory */ | ||
1317 | unsigned int (*xProcessId)(void); /* Get running process ID */ | ||
1318 | int (*xUid)(void); /* user ID of the process */ | ||
1319 | int (*xGid)(void); /* group ID of the process */ | ||
1320 | void (*xUsername)(jx9_context *); /* Running username */ | ||
1321 | int (*xExec)(const char *, jx9_context *); /* Execute an external program */ | ||
1322 | }; | ||
1323 | /* Current JX9 IO stream structure version. */ | ||
1324 | #define JX9_IO_STREAM_VERSION 1 | ||
1325 | /* | ||
1326 | * Possible open mode flags that can be passed to the xOpen() routine | ||
1327 | * of the underlying IO stream device . | ||
1328 | * Refer to the JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html) | ||
1329 | * for additional information. | ||
1330 | */ | ||
1331 | #define JX9_IO_OPEN_RDONLY 0x001 /* Read-only open */ | ||
1332 | #define JX9_IO_OPEN_WRONLY 0x002 /* Write-only open */ | ||
1333 | #define JX9_IO_OPEN_RDWR 0x004 /* Read-write open. */ | ||
1334 | #define JX9_IO_OPEN_CREATE 0x008 /* If the file does not exist it will be created */ | ||
1335 | #define JX9_IO_OPEN_TRUNC 0x010 /* Truncate the file to zero length */ | ||
1336 | #define JX9_IO_OPEN_APPEND 0x020 /* Append mode.The file offset is positioned at the end of the file */ | ||
1337 | #define JX9_IO_OPEN_EXCL 0x040 /* Ensure that this call creates the file, the file must not exist before */ | ||
1338 | #define JX9_IO_OPEN_BINARY 0x080 /* Simple hint: Data is binary */ | ||
1339 | #define JX9_IO_OPEN_TEMP 0x100 /* Simple hint: Temporary file */ | ||
1340 | #define JX9_IO_OPEN_TEXT 0x200 /* Simple hint: Data is textual */ | ||
1341 | /* | ||
1342 | * JX9 IO Stream Device. | ||
1343 | * | ||
1344 | * An instance of the jx9_io_stream object defines the interface between the JX9 core | ||
1345 | * and the underlying stream device. | ||
1346 | * A stream is a smart mechanism for generalizing file, network, data compression | ||
1347 | * and other IO operations which share a common set of functions using an abstracted | ||
1348 | * unified interface. | ||
1349 | * A stream device is additional code which tells the stream how to handle specific | ||
1350 | * protocols/encodings. For example, the http device knows how to translate a URL | ||
1351 | * into an HTTP/1.1 request for a file on a remote server. | ||
1352 | * JX9 come with two built-in IO streams device: | ||
1353 | * The file:// stream which perform very efficient disk IO and the jx9:// stream | ||
1354 | * which is a special stream that allow access various I/O streams (See the JX9 official | ||
1355 | * documentation for more information on this stream). | ||
1356 | * A stream is referenced as: scheme://target | ||
1357 | * scheme(string) - The name of the wrapper to be used. Examples include: file, http, https, ftp, | ||
1358 | * ftps, compress.zlib, compress.bz2, and jx9. If no wrapper is specified, the function default | ||
1359 | * is used (typically file://). | ||
1360 | * target - Depends on the device used. For filesystem related streams this is typically a path | ||
1361 | * and filename of the desired file.For network related streams this is typically a hostname, often | ||
1362 | * with a path appended. | ||
1363 | * IO stream devices are registered using a call to jx9_vm_config() with a configuration verb | ||
1364 | * set to JX9_VM_CONFIG_IO_STREAM. | ||
1365 | * Currently the JX9 development team is working on the implementation of the http:// and ftp:// | ||
1366 | * IO stream protocols. These devices will be available in the next major release of the JX9 engine. | ||
1367 | * Developers wishing to implement their own IO stream devices must understand and follow | ||
1368 | * The JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html). | ||
1369 | */ | ||
1370 | struct jx9_io_stream | ||
1371 | { | ||
1372 | const char *zName; /* Underlying stream name [i.e: file/http/zip/jx9, ..] */ | ||
1373 | int iVersion; /* IO stream structure version [default 1]*/ | ||
1374 | int (*xOpen)(const char *, int, jx9_value *, void **); /* Open handle*/ | ||
1375 | int (*xOpenDir)(const char *, jx9_value *, void **); /* Open directory handle */ | ||
1376 | void (*xClose)(void *); /* Close file handle */ | ||
1377 | void (*xCloseDir)(void *); /* Close directory handle */ | ||
1378 | jx9_int64 (*xRead)(void *, void *, jx9_int64); /* Read from the open stream */ | ||
1379 | int (*xReadDir)(void *, jx9_context *); /* Read entry from directory handle */ | ||
1380 | jx9_int64 (*xWrite)(void *, const void *, jx9_int64); /* Write to the open stream */ | ||
1381 | int (*xSeek)(void *, jx9_int64, int); /* Seek on the open stream */ | ||
1382 | int (*xLock)(void *, int); /* Lock/Unlock the open stream */ | ||
1383 | void (*xRewindDir)(void *); /* Rewind directory handle */ | ||
1384 | jx9_int64 (*xTell)(void *); /* Current position of the stream read/write pointer */ | ||
1385 | int (*xTrunc)(void *, jx9_int64); /* Truncates the open stream to a given length */ | ||
1386 | int (*xSync)(void *); /* Flush open stream data */ | ||
1387 | int (*xStat)(void *, jx9_value *, jx9_value *); /* Stat an open stream handle */ | ||
1388 | }; | ||
1389 | /* | ||
1390 | * C-API-REF: Please refer to the official documentation for interfaces | ||
1391 | * purpose and expected parameters. | ||
1392 | */ | ||
1393 | /* Engine Handling Interfaces */ | ||
1394 | JX9_PRIVATE int jx9_init(jx9 **ppEngine); | ||
1395 | /*JX9_PRIVATE int jx9_config(jx9 *pEngine, int nConfigOp, ...);*/ | ||
1396 | JX9_PRIVATE int jx9_release(jx9 *pEngine); | ||
1397 | /* Compile Interfaces */ | ||
1398 | JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm); | ||
1399 | JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm); | ||
1400 | /* Virtual Machine Handling Interfaces */ | ||
1401 | JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...); | ||
1402 | /*JX9_PRIVATE int jx9_vm_exec(jx9_vm *pVm, int *pExitStatus);*/ | ||
1403 | /*JX9_PRIVATE jx9_value * jx9_vm_extract_variable(jx9_vm *pVm,const char *zVarname);*/ | ||
1404 | /*JX9_PRIVATE int jx9_vm_reset(jx9_vm *pVm);*/ | ||
1405 | JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm); | ||
1406 | /*JX9_PRIVATE int jx9_vm_dump_v2(jx9_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData);*/ | ||
1407 | /* In-process Extending Interfaces */ | ||
1408 | JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData); | ||
1409 | /*JX9_PRIVATE int jx9_delete_function(jx9_vm *pVm, const char *zName);*/ | ||
1410 | JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData); | ||
1411 | /*JX9_PRIVATE int jx9_delete_constant(jx9_vm *pVm, const char *zName);*/ | ||
1412 | /* Foreign Function Parameter Values */ | ||
1413 | JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue); | ||
1414 | JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue); | ||
1415 | JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue); | ||
1416 | JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue); | ||
1417 | JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen); | ||
1418 | JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue); | ||
1419 | JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict); | ||
1420 | /* Setting The Result Of A Foreign Function */ | ||
1421 | JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue); | ||
1422 | JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue); | ||
1423 | JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool); | ||
1424 | JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value); | ||
1425 | JX9_PRIVATE int jx9_result_null(jx9_context *pCtx); | ||
1426 | JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen); | ||
1427 | JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...); | ||
1428 | JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue); | ||
1429 | JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData); | ||
1430 | /* Call Context Handling Interfaces */ | ||
1431 | JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen); | ||
1432 | /*JX9_PRIVATE int jx9_context_output_format(jx9_context *pCtx, const char *zFormat, ...);*/ | ||
1433 | JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr); | ||
1434 | JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...); | ||
1435 | JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx); | ||
1436 | JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen); | ||
1437 | JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx); | ||
1438 | JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData); | ||
1439 | JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx); | ||
1440 | JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx); | ||
1441 | JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx); | ||
1442 | JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx); | ||
1443 | /* Call Context Memory Management Interfaces */ | ||
1444 | JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease); | ||
1445 | JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte); | ||
1446 | JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk); | ||
1447 | /* On Demand Dynamically Typed Value Object allocation interfaces */ | ||
1448 | JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm); | ||
1449 | JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm); | ||
1450 | JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue); | ||
1451 | JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx); | ||
1452 | JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx); | ||
1453 | JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue); | ||
1454 | /* Dynamically Typed Value Object Management Interfaces */ | ||
1455 | JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue); | ||
1456 | JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue); | ||
1457 | JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool); | ||
1458 | JX9_PRIVATE int jx9_value_null(jx9_value *pVal); | ||
1459 | JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value); | ||
1460 | JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen); | ||
1461 | JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...); | ||
1462 | JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal); | ||
1463 | JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData); | ||
1464 | JX9_PRIVATE int jx9_value_release(jx9_value *pVal); | ||
1465 | /* JSON Array/Object Management Interfaces */ | ||
1466 | JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte); | ||
1467 | JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); | ||
1468 | JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue); | ||
1469 | JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue); | ||
1470 | JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray); | ||
1471 | /* Dynamically Typed Value Object Query Interfaces */ | ||
1472 | JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal); | ||
1473 | JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal); | ||
1474 | JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal); | ||
1475 | JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal); | ||
1476 | JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal); | ||
1477 | JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal); | ||
1478 | JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal); | ||
1479 | JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal); | ||
1480 | JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal); | ||
1481 | JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal); | ||
1482 | JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal); | ||
1483 | JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal); | ||
1484 | /* Global Library Management Interfaces */ | ||
1485 | /*JX9_PRIVATE int jx9_lib_init(void);*/ | ||
1486 | JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...); | ||
1487 | JX9_PRIVATE int jx9_lib_shutdown(void); | ||
1488 | /*JX9_PRIVATE int jx9_lib_is_threadsafe(void);*/ | ||
1489 | /*JX9_PRIVATE const char * jx9_lib_version(void);*/ | ||
1490 | JX9_PRIVATE const char * jx9_lib_signature(void); | ||
1491 | /*JX9_PRIVATE const char * jx9_lib_ident(void);*/ | ||
1492 | /*JX9_PRIVATE const char * jx9_lib_copyright(void);*/ | ||
1493 | |||
1494 | #endif /* _JX9H_ */ | ||
1495 | |||
1496 | /* | ||
1497 | * ---------------------------------------------------------- | ||
1498 | * File: jx9Int.h | ||
1499 | * MD5: fb8dffc8ba1425a139091aa145067e16 | ||
1500 | * ---------------------------------------------------------- | ||
1501 | */ | ||
1502 | /* | ||
1503 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
1504 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
1505 | * Version 1.7.2 | ||
1506 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
1507 | * please contact Symisc Systems via: | ||
1508 | * legal@symisc.net | ||
1509 | * licensing@symisc.net | ||
1510 | * contact@symisc.net | ||
1511 | * or visit: | ||
1512 | * http://jx9.symisc.net/ | ||
1513 | */ | ||
1514 | /* $SymiscID: jx9Int.h v1.9 FreeBSD 2012-08-13 23:25 devel <chm@symisc.net> $ */ | ||
1515 | #ifndef __JX9INT_H__ | ||
1516 | #define __JX9INT_H__ | ||
1517 | /* Internal interface definitions for JX9. */ | ||
1518 | #ifdef JX9_AMALGAMATION | ||
1519 | #ifndef JX9_PRIVATE | ||
1520 | /* Marker for routines not intended for external use */ | ||
1521 | #define JX9_PRIVATE static | ||
1522 | #endif /* JX9_PRIVATE */ | ||
1523 | #else | ||
1524 | #define JX9_PRIVATE | ||
1525 | #include "jx9.h" | ||
1526 | #endif | ||
1527 | #ifndef JX9_PI | ||
1528 | /* Value of PI */ | ||
1529 | #define JX9_PI 3.1415926535898 | ||
1530 | #endif | ||
1531 | /* | ||
1532 | * Constants for the largest and smallest possible 64-bit signed integers. | ||
1533 | * These macros are designed to work correctly on both 32-bit and 64-bit | ||
1534 | * compilers. | ||
1535 | */ | ||
1536 | #ifndef LARGEST_INT64 | ||
1537 | #define LARGEST_INT64 (0xffffffff|(((sxi64)0x7fffffff)<<32)) | ||
1538 | #endif | ||
1539 | #ifndef SMALLEST_INT64 | ||
1540 | #define SMALLEST_INT64 (((sxi64)-1) - LARGEST_INT64) | ||
1541 | #endif | ||
1542 | /* Forward declaration of private structures */ | ||
1543 | typedef struct jx9_foreach_info jx9_foreach_info; | ||
1544 | typedef struct jx9_foreach_step jx9_foreach_step; | ||
1545 | typedef struct jx9_hashmap_node jx9_hashmap_node; | ||
1546 | typedef struct jx9_hashmap jx9_hashmap; | ||
1547 | /* Symisc Standard types */ | ||
1548 | #if !defined(SYMISC_STD_TYPES) | ||
1549 | #define SYMISC_STD_TYPES | ||
1550 | #ifdef __WINNT__ | ||
1551 | /* Disable nuisance warnings on Borland compilers */ | ||
1552 | #if defined(__BORLANDC__) | ||
1553 | #pragma warn -rch /* unreachable code */ | ||
1554 | #pragma warn -ccc /* Condition is always true or false */ | ||
1555 | #pragma warn -aus /* Assigned value is never used */ | ||
1556 | #pragma warn -csu /* Comparing signed and unsigned */ | ||
1557 | #pragma warn -spa /* Suspicious pointer arithmetic */ | ||
1558 | #endif | ||
1559 | #endif | ||
1560 | typedef signed char sxi8; /* signed char */ | ||
1561 | typedef unsigned char sxu8; /* unsigned char */ | ||
1562 | typedef signed short int sxi16; /* 16 bits(2 bytes) signed integer */ | ||
1563 | typedef unsigned short int sxu16; /* 16 bits(2 bytes) unsigned integer */ | ||
1564 | typedef int sxi32; /* 32 bits(4 bytes) integer */ | ||
1565 | typedef unsigned int sxu32; /* 32 bits(4 bytes) unsigned integer */ | ||
1566 | typedef long sxptr; | ||
1567 | typedef unsigned long sxuptr; | ||
1568 | typedef long sxlong; | ||
1569 | typedef unsigned long sxulong; | ||
1570 | typedef sxi32 sxofft; | ||
1571 | typedef sxi64 sxofft64; | ||
1572 | typedef long double sxlongreal; | ||
1573 | typedef double sxreal; | ||
1574 | #define SXI8_HIGH 0x7F | ||
1575 | #define SXU8_HIGH 0xFF | ||
1576 | #define SXI16_HIGH 0x7FFF | ||
1577 | #define SXU16_HIGH 0xFFFF | ||
1578 | #define SXI32_HIGH 0x7FFFFFFF | ||
1579 | #define SXU32_HIGH 0xFFFFFFFF | ||
1580 | #define SXI64_HIGH 0x7FFFFFFFFFFFFFFF | ||
1581 | #define SXU64_HIGH 0xFFFFFFFFFFFFFFFF | ||
1582 | #if !defined(TRUE) | ||
1583 | #define TRUE 1 | ||
1584 | #endif | ||
1585 | #if !defined(FALSE) | ||
1586 | #define FALSE 0 | ||
1587 | #endif | ||
1588 | /* | ||
1589 | * The following macros are used to cast pointers to integers and | ||
1590 | * integers to pointers. | ||
1591 | */ | ||
1592 | #if defined(__PTRDIFF_TYPE__) | ||
1593 | # define SX_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) | ||
1594 | # define SX_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) | ||
1595 | #elif !defined(__GNUC__) | ||
1596 | # define SX_INT_TO_PTR(X) ((void*)&((char*)0)[X]) | ||
1597 | # define SX_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) | ||
1598 | #else | ||
1599 | # define SX_INT_TO_PTR(X) ((void*)(X)) | ||
1600 | # define SX_PTR_TO_INT(X) ((int)(X)) | ||
1601 | #endif | ||
1602 | #define SXMIN(a, b) ((a < b) ? (a) : (b)) | ||
1603 | #define SXMAX(a, b) ((a < b) ? (b) : (a)) | ||
1604 | #endif /* SYMISC_STD_TYPES */ | ||
1605 | /* Symisc Run-time API private definitions */ | ||
1606 | #if !defined(SYMISC_PRIVATE_DEFS) | ||
1607 | #define SYMISC_PRIVATE_DEFS | ||
1608 | |||
1609 | typedef sxi32 (*ProcRawStrCmp)(const SyString *, const SyString *); | ||
1610 | #define SyStringData(RAW) ((RAW)->zString) | ||
1611 | #define SyStringLength(RAW) ((RAW)->nByte) | ||
1612 | #define SyStringInitFromBuf(RAW, ZBUF, NLEN){\ | ||
1613 | (RAW)->zString = (const char *)ZBUF;\ | ||
1614 | (RAW)->nByte = (sxu32)(NLEN);\ | ||
1615 | } | ||
1616 | #define SyStringUpdatePtr(RAW, NBYTES){\ | ||
1617 | if( NBYTES > (RAW)->nByte ){\ | ||
1618 | (RAW)->nByte = 0;\ | ||
1619 | }else{\ | ||
1620 | (RAW)->zString += NBYTES;\ | ||
1621 | (RAW)->nByte -= NBYTES;\ | ||
1622 | }\ | ||
1623 | } | ||
1624 | #define SyStringDupPtr(RAW1, RAW2)\ | ||
1625 | (RAW1)->zString = (RAW2)->zString;\ | ||
1626 | (RAW1)->nByte = (RAW2)->nByte; | ||
1627 | |||
1628 | #define SyStringTrimLeadingChar(RAW, CHAR)\ | ||
1629 | while((RAW)->nByte > 0 && (RAW)->zString[0] == CHAR ){\ | ||
1630 | (RAW)->zString++;\ | ||
1631 | (RAW)->nByte--;\ | ||
1632 | } | ||
1633 | #define SyStringTrimTrailingChar(RAW, CHAR)\ | ||
1634 | while((RAW)->nByte > 0 && (RAW)->zString[(RAW)->nByte - 1] == CHAR){\ | ||
1635 | (RAW)->nByte--;\ | ||
1636 | } | ||
1637 | #define SyStringCmp(RAW1, RAW2, xCMP)\ | ||
1638 | (((RAW1)->nByte == (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW1)->nByte - (RAW2)->nByte)) | ||
1639 | |||
1640 | #define SyStringCmp2(RAW1, RAW2, xCMP)\ | ||
1641 | (((RAW1)->nByte >= (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW2)->nByte - (RAW1)->nByte)) | ||
1642 | |||
1643 | #define SyStringCharCmp(RAW, CHAR) \ | ||
1644 | (((RAW)->nByte == sizeof(char)) ? ((RAW)->zString[0] == CHAR ? 0 : CHAR - (RAW)->zString[0]) : ((RAW)->zString[0] == CHAR ? 0 : (RAW)->nByte - sizeof(char))) | ||
1645 | |||
1646 | #define SX_ADDR(PTR) ((sxptr)PTR) | ||
1647 | #define SX_ARRAYSIZE(X) (sizeof(X)/sizeof(X[0])) | ||
1648 | #define SXUNUSED(P) (P = 0) | ||
1649 | #define SX_EMPTY(PTR) (PTR == 0) | ||
1650 | #define SX_EMPTY_STR(STR) (STR == 0 || STR[0] == 0 ) | ||
1651 | typedef struct SyMemBackend SyMemBackend; | ||
1652 | typedef struct SyBlob SyBlob; | ||
1653 | typedef struct SySet SySet; | ||
1654 | /* Standard function signatures */ | ||
1655 | typedef sxi32 (*ProcCmp)(const void *, const void *, sxu32); | ||
1656 | typedef sxi32 (*ProcPatternMatch)(const char *, sxu32, const char *, sxu32, sxu32 *); | ||
1657 | typedef sxi32 (*ProcSearch)(const void *, sxu32, const void *, sxu32, ProcCmp, sxu32 *); | ||
1658 | typedef sxu32 (*ProcHash)(const void *, sxu32); | ||
1659 | typedef sxi32 (*ProcHashSum)(const void *, sxu32, unsigned char *, sxu32); | ||
1660 | typedef sxi32 (*ProcSort)(void *, sxu32, sxu32, ProcCmp); | ||
1661 | #define MACRO_LIST_PUSH(Head, Item)\ | ||
1662 | Item->pNext = Head;\ | ||
1663 | Head = Item; | ||
1664 | #define MACRO_LD_PUSH(Head, Item)\ | ||
1665 | if( Head == 0 ){\ | ||
1666 | Head = Item;\ | ||
1667 | }else{\ | ||
1668 | Item->pNext = Head;\ | ||
1669 | Head->pPrev = Item;\ | ||
1670 | Head = Item;\ | ||
1671 | } | ||
1672 | #define MACRO_LD_REMOVE(Head, Item)\ | ||
1673 | if( Head == Item ){\ | ||
1674 | Head = Head->pNext;\ | ||
1675 | }\ | ||
1676 | if( Item->pPrev ){ Item->pPrev->pNext = Item->pNext;}\ | ||
1677 | if( Item->pNext ){ Item->pNext->pPrev = Item->pPrev;} | ||
1678 | /* | ||
1679 | * A generic dynamic set. | ||
1680 | */ | ||
1681 | struct SySet | ||
1682 | { | ||
1683 | SyMemBackend *pAllocator; /* Memory backend */ | ||
1684 | void *pBase; /* Base pointer */ | ||
1685 | sxu32 nUsed; /* Total number of used slots */ | ||
1686 | sxu32 nSize; /* Total number of available slots */ | ||
1687 | sxu32 eSize; /* Size of a single slot */ | ||
1688 | sxu32 nCursor; /* Loop cursor */ | ||
1689 | void *pUserData; /* User private data associated with this container */ | ||
1690 | }; | ||
1691 | #define SySetBasePtr(S) ((S)->pBase) | ||
1692 | #define SySetBasePtrJump(S, OFFT) (&((char *)(S)->pBase)[OFFT*(S)->eSize]) | ||
1693 | #define SySetUsed(S) ((S)->nUsed) | ||
1694 | #define SySetSize(S) ((S)->nSize) | ||
1695 | #define SySetElemSize(S) ((S)->eSize) | ||
1696 | #define SySetCursor(S) ((S)->nCursor) | ||
1697 | #define SySetGetAllocator(S) ((S)->pAllocator) | ||
1698 | #define SySetSetUserData(S, DATA) ((S)->pUserData = DATA) | ||
1699 | #define SySetGetUserData(S) ((S)->pUserData) | ||
1700 | /* | ||
1701 | * A variable length containers for generic data. | ||
1702 | */ | ||
1703 | struct SyBlob | ||
1704 | { | ||
1705 | SyMemBackend *pAllocator; /* Memory backend */ | ||
1706 | void *pBlob; /* Base pointer */ | ||
1707 | sxu32 nByte; /* Total number of used bytes */ | ||
1708 | sxu32 mByte; /* Total number of available bytes */ | ||
1709 | sxu32 nFlags; /* Blob internal flags, see below */ | ||
1710 | }; | ||
1711 | #define SXBLOB_LOCKED 0x01 /* Blob is locked [i.e: Cannot auto grow] */ | ||
1712 | #define SXBLOB_STATIC 0x02 /* Not allocated from heap */ | ||
1713 | #define SXBLOB_RDONLY 0x04 /* Read-Only data */ | ||
1714 | |||
1715 | #define SyBlobFreeSpace(BLOB) ((BLOB)->mByte - (BLOB)->nByte) | ||
1716 | #define SyBlobLength(BLOB) ((BLOB)->nByte) | ||
1717 | #define SyBlobData(BLOB) ((BLOB)->pBlob) | ||
1718 | #define SyBlobCurData(BLOB) ((void*)(&((char*)(BLOB)->pBlob)[(BLOB)->nByte])) | ||
1719 | #define SyBlobDataAt(BLOB, OFFT) ((void *)(&((char *)(BLOB)->pBlob)[OFFT])) | ||
1720 | #define SyBlobGetAllocator(BLOB) ((BLOB)->pAllocator) | ||
1721 | |||
1722 | #define SXMEM_POOL_INCR 3 | ||
1723 | #define SXMEM_POOL_NBUCKETS 12 | ||
1724 | #define SXMEM_BACKEND_MAGIC 0xBAC3E67D | ||
1725 | #define SXMEM_BACKEND_CORRUPT(BACKEND) (BACKEND == 0 || BACKEND->nMagic != SXMEM_BACKEND_MAGIC) | ||
1726 | |||
1727 | #define SXMEM_BACKEND_RETRY 3 | ||
1728 | /* A memory backend subsystem is defined by an instance of the following structures */ | ||
1729 | typedef union SyMemHeader SyMemHeader; | ||
1730 | typedef struct SyMemBlock SyMemBlock; | ||
1731 | struct SyMemBlock | ||
1732 | { | ||
1733 | SyMemBlock *pNext, *pPrev; /* Chain of allocated memory blocks */ | ||
1734 | #ifdef UNTRUST | ||
1735 | sxu32 nGuard; /* magic number associated with each valid block, so we | ||
1736 | * can detect misuse. | ||
1737 | */ | ||
1738 | #endif | ||
1739 | }; | ||
1740 | /* | ||
1741 | * Header associated with each valid memory pool block. | ||
1742 | */ | ||
1743 | union SyMemHeader | ||
1744 | { | ||
1745 | SyMemHeader *pNext; /* Next chunk of size 1 << (nBucket + SXMEM_POOL_INCR) in the list */ | ||
1746 | sxu32 nBucket; /* Bucket index in aPool[] */ | ||
1747 | }; | ||
1748 | struct SyMemBackend | ||
1749 | { | ||
1750 | const SyMutexMethods *pMutexMethods; /* Mutex methods */ | ||
1751 | const SyMemMethods *pMethods; /* Memory allocation methods */ | ||
1752 | SyMemBlock *pBlocks; /* List of valid memory blocks */ | ||
1753 | sxu32 nBlock; /* Total number of memory blocks allocated so far */ | ||
1754 | ProcMemError xMemError; /* Out-of memory callback */ | ||
1755 | void *pUserData; /* First arg to xMemError() */ | ||
1756 | SyMutex *pMutex; /* Per instance mutex */ | ||
1757 | sxu32 nMagic; /* Sanity check against misuse */ | ||
1758 | SyMemHeader *apPool[SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR]; /* Pool of memory chunks */ | ||
1759 | }; | ||
1760 | /* Mutex types */ | ||
1761 | #define SXMUTEX_TYPE_FAST 1 | ||
1762 | #define SXMUTEX_TYPE_RECURSIVE 2 | ||
1763 | #define SXMUTEX_TYPE_STATIC_1 3 | ||
1764 | #define SXMUTEX_TYPE_STATIC_2 4 | ||
1765 | #define SXMUTEX_TYPE_STATIC_3 5 | ||
1766 | #define SXMUTEX_TYPE_STATIC_4 6 | ||
1767 | #define SXMUTEX_TYPE_STATIC_5 7 | ||
1768 | #define SXMUTEX_TYPE_STATIC_6 8 | ||
1769 | |||
1770 | #define SyMutexGlobalInit(METHOD){\ | ||
1771 | if( (METHOD)->xGlobalInit ){\ | ||
1772 | (METHOD)->xGlobalInit();\ | ||
1773 | }\ | ||
1774 | } | ||
1775 | #define SyMutexGlobalRelease(METHOD){\ | ||
1776 | if( (METHOD)->xGlobalRelease ){\ | ||
1777 | (METHOD)->xGlobalRelease();\ | ||
1778 | }\ | ||
1779 | } | ||
1780 | #define SyMutexNew(METHOD, TYPE) (METHOD)->xNew(TYPE) | ||
1781 | #define SyMutexRelease(METHOD, MUTEX){\ | ||
1782 | if( MUTEX && (METHOD)->xRelease ){\ | ||
1783 | (METHOD)->xRelease(MUTEX);\ | ||
1784 | }\ | ||
1785 | } | ||
1786 | #define SyMutexEnter(METHOD, MUTEX){\ | ||
1787 | if( MUTEX ){\ | ||
1788 | (METHOD)->xEnter(MUTEX);\ | ||
1789 | }\ | ||
1790 | } | ||
1791 | #define SyMutexTryEnter(METHOD, MUTEX){\ | ||
1792 | if( MUTEX && (METHOD)->xTryEnter ){\ | ||
1793 | (METHOD)->xTryEnter(MUTEX);\ | ||
1794 | }\ | ||
1795 | } | ||
1796 | #define SyMutexLeave(METHOD, MUTEX){\ | ||
1797 | if( MUTEX ){\ | ||
1798 | (METHOD)->xLeave(MUTEX);\ | ||
1799 | }\ | ||
1800 | } | ||
1801 | /* Comparison, byte swap, byte copy macros */ | ||
1802 | #define SX_MACRO_FAST_CMP(X1, X2, SIZE, RC){\ | ||
1803 | register unsigned char *r1 = (unsigned char *)X1;\ | ||
1804 | register unsigned char *r2 = (unsigned char *)X2;\ | ||
1805 | register sxu32 LEN = SIZE;\ | ||
1806 | for(;;){\ | ||
1807 | if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ | ||
1808 | if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ | ||
1809 | if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ | ||
1810 | if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ | ||
1811 | }\ | ||
1812 | RC = !LEN ? 0 : r1[0] - r2[0];\ | ||
1813 | } | ||
1814 | #define SX_MACRO_FAST_MEMCPY(SRC, DST, SIZ){\ | ||
1815 | register unsigned char *xSrc = (unsigned char *)SRC;\ | ||
1816 | register unsigned char *xDst = (unsigned char *)DST;\ | ||
1817 | register sxu32 xLen = SIZ;\ | ||
1818 | for(;;){\ | ||
1819 | if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ | ||
1820 | if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ | ||
1821 | if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ | ||
1822 | if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ | ||
1823 | }\ | ||
1824 | } | ||
1825 | #define SX_MACRO_BYTE_SWAP(X, Y, Z){\ | ||
1826 | register unsigned char *s = (unsigned char *)X;\ | ||
1827 | register unsigned char *d = (unsigned char *)Y;\ | ||
1828 | sxu32 ZLong = Z; \ | ||
1829 | sxi32 c; \ | ||
1830 | for(;;){\ | ||
1831 | if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ | ||
1832 | if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ | ||
1833 | if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ | ||
1834 | if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ | ||
1835 | }\ | ||
1836 | } | ||
1837 | #define SX_MSEC_PER_SEC (1000) /* Millisec per seconds */ | ||
1838 | #define SX_USEC_PER_SEC (1000000) /* Microsec per seconds */ | ||
1839 | #define SX_NSEC_PER_SEC (1000000000) /* Nanosec per seconds */ | ||
1840 | #endif /* SYMISC_PRIVATE_DEFS */ | ||
1841 | /* Symisc Run-time API auxiliary definitions */ | ||
1842 | #if !defined(SYMISC_PRIVATE_AUX_DEFS) | ||
1843 | #define SYMISC_PRIVATE_AUX_DEFS | ||
1844 | |||
1845 | typedef struct SyHashEntry_Pr SyHashEntry_Pr; | ||
1846 | typedef struct SyHashEntry SyHashEntry; | ||
1847 | typedef struct SyHash SyHash; | ||
1848 | /* | ||
1849 | * Each public hashtable entry is represented by an instance | ||
1850 | * of the following structure. | ||
1851 | */ | ||
1852 | struct SyHashEntry | ||
1853 | { | ||
1854 | const void *pKey; /* Hash key */ | ||
1855 | sxu32 nKeyLen; /* Key length */ | ||
1856 | void *pUserData; /* User private data */ | ||
1857 | }; | ||
1858 | #define SyHashEntryGetUserData(ENTRY) ((ENTRY)->pUserData) | ||
1859 | #define SyHashEntryGetKey(ENTRY) ((ENTRY)->pKey) | ||
1860 | /* Each active hashtable is identified by an instance of the following structure */ | ||
1861 | struct SyHash | ||
1862 | { | ||
1863 | SyMemBackend *pAllocator; /* Memory backend */ | ||
1864 | ProcHash xHash; /* Hash function */ | ||
1865 | ProcCmp xCmp; /* Comparison function */ | ||
1866 | SyHashEntry_Pr *pList, *pCurrent; /* Linked list of hash entries user for linear traversal */ | ||
1867 | sxu32 nEntry; /* Total number of entries */ | ||
1868 | SyHashEntry_Pr **apBucket; /* Hash buckets */ | ||
1869 | sxu32 nBucketSize; /* Current bucket size */ | ||
1870 | }; | ||
1871 | #define SXHASH_BUCKET_SIZE 16 /* Initial bucket size: must be a power of two */ | ||
1872 | #define SXHASH_FILL_FACTOR 3 | ||
1873 | /* Hash access macro */ | ||
1874 | #define SyHashFunc(HASH) ((HASH)->xHash) | ||
1875 | #define SyHashCmpFunc(HASH) ((HASH)->xCmp) | ||
1876 | #define SyHashTotalEntry(HASH) ((HASH)->nEntry) | ||
1877 | #define SyHashGetPool(HASH) ((HASH)->pAllocator) | ||
1878 | /* | ||
1879 | * An instance of the following structure define a single context | ||
1880 | * for an Pseudo Random Number Generator. | ||
1881 | * | ||
1882 | * Nothing in this file or anywhere else in the library does any kind of | ||
1883 | * encryption. The RC4 algorithm is being used as a PRNG (pseudo-random | ||
1884 | * number generator) not as an encryption device. | ||
1885 | * This implementation is taken from the SQLite3 source tree. | ||
1886 | */ | ||
1887 | typedef struct SyPRNGCtx SyPRNGCtx; | ||
1888 | struct SyPRNGCtx | ||
1889 | { | ||
1890 | sxu8 i, j; /* State variables */ | ||
1891 | unsigned char s[256]; /* State variables */ | ||
1892 | sxu16 nMagic; /* Sanity check */ | ||
1893 | }; | ||
1894 | typedef sxi32 (*ProcRandomSeed)(void *, unsigned int, void *); | ||
1895 | /* High resolution timer.*/ | ||
1896 | typedef struct sytime sytime; | ||
1897 | struct sytime | ||
1898 | { | ||
1899 | long tm_sec; /* seconds */ | ||
1900 | long tm_usec; /* microseconds */ | ||
1901 | }; | ||
1902 | /* Forward declaration */ | ||
1903 | typedef struct SyStream SyStream; | ||
1904 | typedef struct SyToken SyToken; | ||
1905 | typedef struct SyLex SyLex; | ||
1906 | /* | ||
1907 | * Tokenizer callback signature. | ||
1908 | */ | ||
1909 | typedef sxi32 (*ProcTokenizer)(SyStream *, SyToken *, void *, void *); | ||
1910 | /* | ||
1911 | * Each token in the input is represented by an instance | ||
1912 | * of the following structure. | ||
1913 | */ | ||
1914 | struct SyToken | ||
1915 | { | ||
1916 | SyString sData; /* Token text and length */ | ||
1917 | sxu32 nType; /* Token type */ | ||
1918 | sxu32 nLine; /* Token line number */ | ||
1919 | void *pUserData; /* User private data associated with this token */ | ||
1920 | }; | ||
1921 | /* | ||
1922 | * During tokenization, information about the state of the input | ||
1923 | * stream is held in an instance of the following structure. | ||
1924 | */ | ||
1925 | struct SyStream | ||
1926 | { | ||
1927 | const unsigned char *zInput; /* Complete text of the input */ | ||
1928 | const unsigned char *zText; /* Current input we are processing */ | ||
1929 | const unsigned char *zEnd; /* End of input marker */ | ||
1930 | sxu32 nLine; /* Total number of processed lines */ | ||
1931 | sxu32 nIgn; /* Total number of ignored tokens */ | ||
1932 | SySet *pSet; /* Token containers */ | ||
1933 | }; | ||
1934 | /* | ||
1935 | * Each lexer is represented by an instance of the following structure. | ||
1936 | */ | ||
1937 | struct SyLex | ||
1938 | { | ||
1939 | SyStream sStream; /* Input stream */ | ||
1940 | ProcTokenizer xTokenizer; /* Tokenizer callback */ | ||
1941 | void * pUserData; /* Third argument to xTokenizer() */ | ||
1942 | SySet *pTokenSet; /* Token set */ | ||
1943 | }; | ||
1944 | #define SyLexTotalToken(LEX) SySetTotalEntry(&(LEX)->aTokenSet) | ||
1945 | #define SyLexTotalLines(LEX) ((LEX)->sStream.nLine) | ||
1946 | #define SyLexTotalIgnored(LEX) ((LEX)->sStream.nIgn) | ||
1947 | #define XLEX_IN_LEN(STREAM) (sxu32)(STREAM->zEnd - STREAM->zText) | ||
1948 | #endif /* SYMISC_PRIVATE_AUX_DEFS */ | ||
1949 | /* | ||
1950 | ** Notes on UTF-8 (According to SQLite3 authors): | ||
1951 | ** | ||
1952 | ** Byte-0 Byte-1 Byte-2 Byte-3 Value | ||
1953 | ** 0xxxxxxx 00000000 00000000 0xxxxxxx | ||
1954 | ** 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx | ||
1955 | ** 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx | ||
1956 | ** 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx | ||
1957 | ** | ||
1958 | */ | ||
1959 | /* | ||
1960 | ** Assuming zIn points to the first byte of a UTF-8 character, | ||
1961 | ** advance zIn to point to the first byte of the next UTF-8 character. | ||
1962 | */ | ||
1963 | #define SX_JMP_UTF8(zIn, zEnd)\ | ||
1964 | while(zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ zIn++; } | ||
1965 | #define SX_WRITE_UTF8(zOut, c) { \ | ||
1966 | if( c<0x00080 ){ \ | ||
1967 | *zOut++ = (sxu8)(c&0xFF); \ | ||
1968 | }else if( c<0x00800 ){ \ | ||
1969 | *zOut++ = 0xC0 + (sxu8)((c>>6)&0x1F); \ | ||
1970 | *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ | ||
1971 | }else if( c<0x10000 ){ \ | ||
1972 | *zOut++ = 0xE0 + (sxu8)((c>>12)&0x0F); \ | ||
1973 | *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ | ||
1974 | *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ | ||
1975 | }else{ \ | ||
1976 | *zOut++ = 0xF0 + (sxu8)((c>>18) & 0x07); \ | ||
1977 | *zOut++ = 0x80 + (sxu8)((c>>12) & 0x3F); \ | ||
1978 | *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ | ||
1979 | *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ | ||
1980 | } \ | ||
1981 | } | ||
1982 | /* Rely on the standard ctype */ | ||
1983 | #include <ctype.h> | ||
1984 | #define SyToUpper(c) toupper(c) | ||
1985 | #define SyToLower(c) tolower(c) | ||
1986 | #define SyisUpper(c) isupper(c) | ||
1987 | #define SyisLower(c) islower(c) | ||
1988 | #define SyisSpace(c) isspace(c) | ||
1989 | #define SyisBlank(c) isspace(c) | ||
1990 | #define SyisAlpha(c) isalpha(c) | ||
1991 | #define SyisDigit(c) isdigit(c) | ||
1992 | #define SyisHex(c) isxdigit(c) | ||
1993 | #define SyisPrint(c) isprint(c) | ||
1994 | #define SyisPunct(c) ispunct(c) | ||
1995 | #define SyisSpec(c) iscntrl(c) | ||
1996 | #define SyisCtrl(c) iscntrl(c) | ||
1997 | #define SyisAscii(c) isascii(c) | ||
1998 | #define SyisAlphaNum(c) isalnum(c) | ||
1999 | #define SyisGraph(c) isgraph(c) | ||
2000 | #define SyDigToHex(c) "0123456789ABCDEF"[c & 0x0F] | ||
2001 | #define SyDigToInt(c) ((c < 0xc0 && SyisDigit(c))? (c - '0') : 0 ) | ||
2002 | #define SyCharToUpper(c) ((c < 0xc0 && SyisLower(c))? SyToUpper(c) : c) | ||
2003 | #define SyCharToLower(c) ((c < 0xc0 && SyisUpper(c))? SyToLower(c) : c) | ||
2004 | /* Remove white space/NUL byte from a raw string */ | ||
2005 | #define SyStringLeftTrim(RAW)\ | ||
2006 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ | ||
2007 | (RAW)->nByte--;\ | ||
2008 | (RAW)->zString++;\ | ||
2009 | } | ||
2010 | #define SyStringLeftTrimSafe(RAW)\ | ||
2011 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && ((RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ | ||
2012 | (RAW)->nByte--;\ | ||
2013 | (RAW)->zString++;\ | ||
2014 | } | ||
2015 | #define SyStringRightTrim(RAW)\ | ||
2016 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ | ||
2017 | (RAW)->nByte--;\ | ||
2018 | } | ||
2019 | #define SyStringRightTrimSafe(RAW)\ | ||
2020 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ | ||
2021 | (( RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ | ||
2022 | (RAW)->nByte--;\ | ||
2023 | } | ||
2024 | |||
2025 | #define SyStringFullTrim(RAW)\ | ||
2026 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ | ||
2027 | (RAW)->nByte--;\ | ||
2028 | (RAW)->zString++;\ | ||
2029 | }\ | ||
2030 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ | ||
2031 | (RAW)->nByte--;\ | ||
2032 | } | ||
2033 | #define SyStringFullTrimSafe(RAW)\ | ||
2034 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && \ | ||
2035 | ( (RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ | ||
2036 | (RAW)->nByte--;\ | ||
2037 | (RAW)->zString++;\ | ||
2038 | }\ | ||
2039 | while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ | ||
2040 | ( (RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ | ||
2041 | (RAW)->nByte--;\ | ||
2042 | } | ||
2043 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
2044 | /* | ||
2045 | * An XML raw text, CDATA, tag name and son is parsed out and stored | ||
2046 | * in an instance of the following structure. | ||
2047 | */ | ||
2048 | typedef struct SyXMLRawStr SyXMLRawStr; | ||
2049 | struct SyXMLRawStr | ||
2050 | { | ||
2051 | const char *zString; /* Raw text [UTF-8 ENCODED EXCEPT CDATA] [NOT NULL TERMINATED] */ | ||
2052 | sxu32 nByte; /* Text length */ | ||
2053 | sxu32 nLine; /* Line number this text occurs */ | ||
2054 | }; | ||
2055 | /* | ||
2056 | * Event callback signatures. | ||
2057 | */ | ||
2058 | typedef sxi32 (*ProcXMLStartTagHandler)(SyXMLRawStr *, SyXMLRawStr *, sxu32, SyXMLRawStr *, void *); | ||
2059 | typedef sxi32 (*ProcXMLTextHandler)(SyXMLRawStr *, void *); | ||
2060 | typedef sxi32 (*ProcXMLEndTagHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); | ||
2061 | typedef sxi32 (*ProcXMLPIHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); | ||
2062 | typedef sxi32 (*ProcXMLDoctypeHandler)(SyXMLRawStr *, void *); | ||
2063 | typedef sxi32 (*ProcXMLSyntaxErrorHandler)(const char *, int, SyToken *, void *); | ||
2064 | typedef sxi32 (*ProcXMLStartDocument)(void *); | ||
2065 | typedef sxi32 (*ProcXMLNameSpaceStart)(SyXMLRawStr *, SyXMLRawStr *, void *); | ||
2066 | typedef sxi32 (*ProcXMLNameSpaceEnd)(SyXMLRawStr *, void *); | ||
2067 | typedef sxi32 (*ProcXMLEndDocument)(void *); | ||
2068 | /* XML processing control flags */ | ||
2069 | #define SXML_ENABLE_NAMESPACE 0x01 /* Parse XML with namespace support enbaled */ | ||
2070 | #define SXML_ENABLE_QUERY 0x02 /* Not used */ | ||
2071 | #define SXML_OPTION_CASE_FOLDING 0x04 /* Controls whether case-folding is enabled for this XML parser */ | ||
2072 | #define SXML_OPTION_SKIP_TAGSTART 0x08 /* Specify how many characters should be skipped in the beginning of a tag name.*/ | ||
2073 | #define SXML_OPTION_SKIP_WHITE 0x10 /* Whether to skip values consisting of whitespace characters. */ | ||
2074 | #define SXML_OPTION_TARGET_ENCODING 0x20 /* Default encoding: UTF-8 */ | ||
2075 | /* XML error codes */ | ||
2076 | enum xml_err_code{ | ||
2077 | SXML_ERROR_NONE = 1, | ||
2078 | SXML_ERROR_NO_MEMORY, | ||
2079 | SXML_ERROR_SYNTAX, | ||
2080 | SXML_ERROR_NO_ELEMENTS, | ||
2081 | SXML_ERROR_INVALID_TOKEN, | ||
2082 | SXML_ERROR_UNCLOSED_TOKEN, | ||
2083 | SXML_ERROR_PARTIAL_CHAR, | ||
2084 | SXML_ERROR_TAG_MISMATCH, | ||
2085 | SXML_ERROR_DUPLICATE_ATTRIBUTE, | ||
2086 | SXML_ERROR_JUNK_AFTER_DOC_ELEMENT, | ||
2087 | SXML_ERROR_PARAM_ENTITY_REF, | ||
2088 | SXML_ERROR_UNDEFINED_ENTITY, | ||
2089 | SXML_ERROR_RECURSIVE_ENTITY_REF, | ||
2090 | SXML_ERROR_ASYNC_ENTITY, | ||
2091 | SXML_ERROR_BAD_CHAR_REF, | ||
2092 | SXML_ERROR_BINARY_ENTITY_REF, | ||
2093 | SXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF, | ||
2094 | SXML_ERROR_MISPLACED_XML_PI, | ||
2095 | SXML_ERROR_UNKNOWN_ENCODING, | ||
2096 | SXML_ERROR_INCORRECT_ENCODING, | ||
2097 | SXML_ERROR_UNCLOSED_CDATA_SECTION, | ||
2098 | SXML_ERROR_EXTERNAL_ENTITY_HANDLING | ||
2099 | }; | ||
2100 | /* Each active XML SAX parser is represented by an instance | ||
2101 | * of the following structure. | ||
2102 | */ | ||
2103 | typedef struct SyXMLParser SyXMLParser; | ||
2104 | struct SyXMLParser | ||
2105 | { | ||
2106 | SyMemBackend *pAllocator; /* Memory backend */ | ||
2107 | void *pUserData; /* User private data forwarded varbatim by the XML parser | ||
2108 | * as the last argument to the users callbacks. | ||
2109 | */ | ||
2110 | SyHash hns; /* Namespace hashtable */ | ||
2111 | SySet sToken; /* XML tokens */ | ||
2112 | SyLex sLex; /* Lexical analyzer */ | ||
2113 | sxi32 nFlags; /* Control flags */ | ||
2114 | /* User callbacks */ | ||
2115 | ProcXMLStartTagHandler xStartTag; /* Start element handler */ | ||
2116 | ProcXMLEndTagHandler xEndTag; /* End element handler */ | ||
2117 | ProcXMLTextHandler xRaw; /* Raw text/CDATA handler */ | ||
2118 | ProcXMLDoctypeHandler xDoctype; /* DOCTYPE handler */ | ||
2119 | ProcXMLPIHandler xPi; /* Processing instruction (PI) handler*/ | ||
2120 | ProcXMLSyntaxErrorHandler xError; /* Error handler */ | ||
2121 | ProcXMLStartDocument xStartDoc; /* StartDoc handler */ | ||
2122 | ProcXMLEndDocument xEndDoc; /* EndDoc handler */ | ||
2123 | ProcXMLNameSpaceStart xNameSpace; /* Namespace declaration handler */ | ||
2124 | ProcXMLNameSpaceEnd xNameSpaceEnd; /* End namespace declaration handler */ | ||
2125 | }; | ||
2126 | /* | ||
2127 | * -------------- | ||
2128 | * Archive extractor: | ||
2129 | * -------------- | ||
2130 | * Each open ZIP/TAR archive is identified by an instance of the following structure. | ||
2131 | * That is, a process can open one or more archives and manipulates them in thread safe | ||
2132 | * way by simply working with pointers to the following structure. | ||
2133 | * Each entry in the archive is remembered in a hashtable. | ||
2134 | * Lookup is very fast and entry with the same name are chained together. | ||
2135 | */ | ||
2136 | typedef struct SyArchiveEntry SyArchiveEntry; | ||
2137 | typedef struct SyArchive SyArchive; | ||
2138 | struct SyArchive | ||
2139 | { | ||
2140 | SyMemBackend *pAllocator; /* Memory backend */ | ||
2141 | SyArchiveEntry *pCursor; /* Cursor for linear traversal of archive entries */ | ||
2142 | SyArchiveEntry *pList; /* Pointer to the List of the loaded archive */ | ||
2143 | SyArchiveEntry **apHash; /* Hashtable for archive entry */ | ||
2144 | ProcRawStrCmp xCmp; /* Hash comparison function */ | ||
2145 | ProcHash xHash; /* Hash Function */ | ||
2146 | sxu32 nSize; /* Hashtable size */ | ||
2147 | sxu32 nEntry; /* Total number of entries in the zip/tar archive */ | ||
2148 | sxu32 nLoaded; /* Total number of entries loaded in memory */ | ||
2149 | sxu32 nCentralOfft; /* Central directory offset(ZIP only. Otherwise Zero) */ | ||
2150 | sxu32 nCentralSize; /* Central directory size(ZIP only. Otherwise Zero) */ | ||
2151 | void *pUserData; /* Upper layer private data */ | ||
2152 | sxu32 nMagic; /* Sanity check */ | ||
2153 | |||
2154 | }; | ||
2155 | #define SXARCH_MAGIC 0xDEAD635A | ||
2156 | #define SXARCH_INVALID(ARCH) (ARCH == 0 || ARCH->nMagic != SXARCH_MAGIC) | ||
2157 | #define SXARCH_ENTRY_INVALID(ENTRY) (ENTRY == 0 || ENTRY->nMagic != SXARCH_MAGIC) | ||
2158 | #define SyArchiveHashFunc(ARCH) (ARCH)->xHash | ||
2159 | #define SyArchiveCmpFunc(ARCH) (ARCH)->xCmp | ||
2160 | #define SyArchiveUserData(ARCH) (ARCH)->pUserData | ||
2161 | #define SyArchiveSetUserData(ARCH, DATA) (ARCH)->pUserData = DATA | ||
2162 | /* | ||
2163 | * Each loaded archive record is identified by an instance | ||
2164 | * of the following structure. | ||
2165 | */ | ||
2166 | struct SyArchiveEntry | ||
2167 | { | ||
2168 | sxu32 nByte; /* Contents size before compression */ | ||
2169 | sxu32 nByteCompr; /* Contents size after compression */ | ||
2170 | sxu32 nReadCount; /* Read counter */ | ||
2171 | sxu32 nCrc; /* Contents CRC32 */ | ||
2172 | Sytm sFmt; /* Last-modification time */ | ||
2173 | sxu32 nOfft; /* Data offset. */ | ||
2174 | sxu16 nComprMeth; /* Compression method 0 == stored/8 == deflated and so on (see appnote.txt)*/ | ||
2175 | sxu16 nExtra; /* Extra size if any */ | ||
2176 | SyString sFileName; /* entry name & length */ | ||
2177 | sxu32 nDup; /* Total number of entries with the same name */ | ||
2178 | SyArchiveEntry *pNextHash, *pPrevHash; /* Hash collision chains */ | ||
2179 | SyArchiveEntry *pNextName; /* Next entry with the same name */ | ||
2180 | SyArchiveEntry *pNext, *pPrev; /* Next and previous entry in the list */ | ||
2181 | sxu32 nHash; /* Hash of the entry name */ | ||
2182 | void *pUserData; /* User data */ | ||
2183 | sxu32 nMagic; /* Sanity check */ | ||
2184 | }; | ||
2185 | /* | ||
2186 | * Extra flags for extending the file local header | ||
2187 | */ | ||
2188 | #define SXZIP_EXTRA_TIMESTAMP 0x001 /* Extended UNIX timestamp */ | ||
2189 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
2190 | #ifndef JX9_DISABLE_HASH_FUNC | ||
2191 | /* MD5 context */ | ||
2192 | typedef struct MD5Context MD5Context; | ||
2193 | struct MD5Context { | ||
2194 | sxu32 buf[4]; | ||
2195 | sxu32 bits[2]; | ||
2196 | unsigned char in[64]; | ||
2197 | }; | ||
2198 | /* SHA1 context */ | ||
2199 | typedef struct SHA1Context SHA1Context; | ||
2200 | struct SHA1Context { | ||
2201 | unsigned int state[5]; | ||
2202 | unsigned int count[2]; | ||
2203 | unsigned char buffer[64]; | ||
2204 | }; | ||
2205 | #endif /* JX9_DISABLE_HASH_FUNC */ | ||
2206 | /* JX9 private declaration */ | ||
2207 | /* | ||
2208 | * Memory Objects. | ||
2209 | * Internally, the JX9 virtual machine manipulates nearly all JX9 values | ||
2210 | * [i.e: string, int, float, resource, object, bool, null] as jx9_values structures. | ||
2211 | * Each jx9_values struct may cache multiple representations (string, integer etc.) | ||
2212 | * of the same value. | ||
2213 | */ | ||
2214 | struct jx9_value | ||
2215 | { | ||
2216 | union{ | ||
2217 | jx9_real rVal; /* Real value */ | ||
2218 | sxi64 iVal; /* Integer value */ | ||
2219 | void *pOther; /* Other values (Object, Array, Resource, Namespace, etc.) */ | ||
2220 | }x; | ||
2221 | sxi32 iFlags; /* Control flags (see below) */ | ||
2222 | jx9_vm *pVm; /* VM this instance belong */ | ||
2223 | SyBlob sBlob; /* String values */ | ||
2224 | sxu32 nIdx; /* Object index in the global pool */ | ||
2225 | }; | ||
2226 | /* Allowed value types. | ||
2227 | */ | ||
2228 | #define MEMOBJ_STRING 0x001 /* Memory value is a UTF-8 string */ | ||
2229 | #define MEMOBJ_INT 0x002 /* Memory value is an integer */ | ||
2230 | #define MEMOBJ_REAL 0x004 /* Memory value is a real number */ | ||
2231 | #define MEMOBJ_BOOL 0x008 /* Memory value is a boolean */ | ||
2232 | #define MEMOBJ_NULL 0x020 /* Memory value is NULL */ | ||
2233 | #define MEMOBJ_HASHMAP 0x040 /* Memory value is a hashmap (JSON representation of Array and Objects) */ | ||
2234 | #define MEMOBJ_RES 0x100 /* Memory value is a resource [User private data] */ | ||
2235 | /* Mask of all known types */ | ||
2236 | #define MEMOBJ_ALL (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES) | ||
2237 | /* Scalar variables | ||
2238 | * According to the JX9 language reference manual | ||
2239 | * Scalar variables are those containing an integer, float, string or boolean. | ||
2240 | * Types array, object and resource are not scalar. | ||
2241 | */ | ||
2242 | #define MEMOBJ_SCALAR (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) | ||
2243 | /* | ||
2244 | * The following macro clear the current jx9_value type and replace | ||
2245 | * it with the given one. | ||
2246 | */ | ||
2247 | #define MemObjSetType(OBJ, TYPE) ((OBJ)->iFlags = ((OBJ)->iFlags&~MEMOBJ_ALL)|TYPE) | ||
2248 | /* jx9_value cast method signature */ | ||
2249 | typedef sxi32 (*ProcMemObjCast)(jx9_value *); | ||
2250 | /* Forward reference */ | ||
2251 | typedef struct jx9_output_consumer jx9_output_consumer; | ||
2252 | typedef struct jx9_user_func jx9_user_func; | ||
2253 | typedef struct jx9_conf jx9_conf; | ||
2254 | /* | ||
2255 | * An instance of the following structure store the default VM output | ||
2256 | * consumer and it's private data. | ||
2257 | * Client-programs can register their own output consumer callback | ||
2258 | * via the [JX9_VM_CONFIG_OUTPUT] configuration directive. | ||
2259 | * Please refer to the official documentation for more information | ||
2260 | * on how to register an output consumer callback. | ||
2261 | */ | ||
2262 | struct jx9_output_consumer | ||
2263 | { | ||
2264 | ProcConsumer xConsumer; /* VM output consumer routine */ | ||
2265 | void *pUserData; /* Third argument to xConsumer() */ | ||
2266 | ProcConsumer xDef; /* Default output consumer routine */ | ||
2267 | void *pDefData; /* Third argument to xDef() */ | ||
2268 | }; | ||
2269 | /* | ||
2270 | * JX9 engine [i.e: jx9 instance] configuration is stored in | ||
2271 | * an instance of the following structure. | ||
2272 | * Please refer to the official documentation for more information | ||
2273 | * on how to configure your jx9 engine instance. | ||
2274 | */ | ||
2275 | struct jx9_conf | ||
2276 | { | ||
2277 | ProcConsumer xErr; /* Compile-time error consumer callback */ | ||
2278 | void *pErrData; /* Third argument to xErr() */ | ||
2279 | SyBlob sErrConsumer; /* Default error consumer */ | ||
2280 | }; | ||
2281 | /* | ||
2282 | * Signature of the C function responsible of expanding constant values. | ||
2283 | */ | ||
2284 | typedef void (*ProcConstant)(jx9_value *, void *); | ||
2285 | /* | ||
2286 | * Each registered constant [i.e: __TIME__, __DATE__, JX9_OS, INT_MAX, etc.] is stored | ||
2287 | * in an instance of the following structure. | ||
2288 | * Please refer to the official documentation for more information | ||
2289 | * on how to create/install foreign constants. | ||
2290 | */ | ||
2291 | typedef struct jx9_constant jx9_constant; | ||
2292 | struct jx9_constant | ||
2293 | { | ||
2294 | SyString sName; /* Constant name */ | ||
2295 | ProcConstant xExpand; /* Function responsible of expanding constant value */ | ||
2296 | void *pUserData; /* Last argument to xExpand() */ | ||
2297 | }; | ||
2298 | typedef struct jx9_aux_data jx9_aux_data; | ||
2299 | /* | ||
2300 | * Auxiliary data associated with each foreign function is stored | ||
2301 | * in a stack of the following structure. | ||
2302 | * Note that automatic tracked chunks are also stored in an instance | ||
2303 | * of this structure. | ||
2304 | */ | ||
2305 | struct jx9_aux_data | ||
2306 | { | ||
2307 | void *pAuxData; /* Aux data */ | ||
2308 | }; | ||
2309 | /* Foreign functions signature */ | ||
2310 | typedef int (*ProcHostFunction)(jx9_context *, int, jx9_value **); | ||
2311 | /* | ||
2312 | * Each installed foreign function is recored in an instance of the following | ||
2313 | * structure. | ||
2314 | * Please refer to the official documentation for more information on how | ||
2315 | * to create/install foreign functions. | ||
2316 | */ | ||
2317 | struct jx9_user_func | ||
2318 | { | ||
2319 | jx9_vm *pVm; /* VM that own this instance */ | ||
2320 | SyString sName; /* Foreign function name */ | ||
2321 | ProcHostFunction xFunc; /* Implementation of the foreign function */ | ||
2322 | void *pUserData; /* User private data [Refer to the official documentation for more information]*/ | ||
2323 | SySet aAux; /* Stack of auxiliary data [Refer to the official documentation for more information]*/ | ||
2324 | }; | ||
2325 | /* | ||
2326 | * The 'context' argument for an installable function. A pointer to an | ||
2327 | * instance of this structure is the first argument to the routines used | ||
2328 | * implement the foreign functions. | ||
2329 | */ | ||
2330 | struct jx9_context | ||
2331 | { | ||
2332 | jx9_user_func *pFunc; /* Function information. */ | ||
2333 | jx9_value *pRet; /* Return value is stored here. */ | ||
2334 | SySet sVar; /* Container of dynamically allocated jx9_values | ||
2335 | * [i.e: Garbage collection purposes.] | ||
2336 | */ | ||
2337 | SySet sChunk; /* Track dynamically allocated chunks [jx9_aux_data instance]. | ||
2338 | * [i.e: Garbage collection purposes.] | ||
2339 | */ | ||
2340 | jx9_vm *pVm; /* Virtual machine that own this context */ | ||
2341 | sxi32 iFlags; /* Call flags */ | ||
2342 | }; | ||
2343 | /* Hashmap control flags */ | ||
2344 | #define HASHMAP_JSON_OBJECT 0x001 /* Hashmap represent JSON Object*/ | ||
2345 | /* | ||
2346 | * Each hashmap entry [i.e: array(4, 5, 6)] is recorded in an instance | ||
2347 | * of the following structure. | ||
2348 | */ | ||
2349 | struct jx9_hashmap_node | ||
2350 | { | ||
2351 | jx9_hashmap *pMap; /* Hashmap that own this instance */ | ||
2352 | sxi32 iType; /* Node type */ | ||
2353 | union{ | ||
2354 | sxi64 iKey; /* Int key */ | ||
2355 | SyBlob sKey; /* Blob key */ | ||
2356 | }xKey; | ||
2357 | sxi32 iFlags; /* Control flags */ | ||
2358 | sxu32 nHash; /* Key hash value */ | ||
2359 | sxu32 nValIdx; /* Value stored in this node */ | ||
2360 | jx9_hashmap_node *pNext, *pPrev; /* Link to other entries [i.e: linear traversal] */ | ||
2361 | jx9_hashmap_node *pNextCollide, *pPrevCollide; /* Collision chain */ | ||
2362 | }; | ||
2363 | /* | ||
2364 | * Each active hashmap aka array in the JX9 jargon is represented | ||
2365 | * by an instance of the following structure. | ||
2366 | */ | ||
2367 | struct jx9_hashmap | ||
2368 | { | ||
2369 | jx9_vm *pVm; /* VM that own this instance */ | ||
2370 | jx9_hashmap_node **apBucket; /* Hash bucket */ | ||
2371 | jx9_hashmap_node *pFirst; /* First inserted entry */ | ||
2372 | jx9_hashmap_node *pLast; /* Last inserted entry */ | ||
2373 | jx9_hashmap_node *pCur; /* Current entry */ | ||
2374 | sxu32 nSize; /* Bucket size */ | ||
2375 | sxu32 nEntry; /* Total number of inserted entries */ | ||
2376 | sxu32 (*xIntHash)(sxi64); /* Hash function for int_keys */ | ||
2377 | sxu32 (*xBlobHash)(const void *, sxu32); /* Hash function for blob_keys */ | ||
2378 | sxi32 iFlags; /* Hashmap control flags */ | ||
2379 | sxi64 iNextIdx; /* Next available automatically assigned index */ | ||
2380 | sxi32 iRef; /* Reference count */ | ||
2381 | }; | ||
2382 | /* An instance of the following structure is the context | ||
2383 | * for the FOREACH_STEP/FOREACH_INIT VM instructions. | ||
2384 | * Those instructions are used to implement the 'foreach' | ||
2385 | * statement. | ||
2386 | * This structure is made available to these instructions | ||
2387 | * as the P3 operand. | ||
2388 | */ | ||
2389 | struct jx9_foreach_info | ||
2390 | { | ||
2391 | SyString sKey; /* Key name. Empty otherwise*/ | ||
2392 | SyString sValue; /* Value name */ | ||
2393 | sxi32 iFlags; /* Control flags */ | ||
2394 | SySet aStep; /* Stack of steps [i.e: jx9_foreach_step instance] */ | ||
2395 | }; | ||
2396 | struct jx9_foreach_step | ||
2397 | { | ||
2398 | sxi32 iFlags; /* Control flags (see below) */ | ||
2399 | /* Iterate on this map*/ | ||
2400 | jx9_hashmap *pMap; /* Hashmap [i.e: array in the JX9 jargon] iteration | ||
2401 | * Ex: foreach(array(1, 2, 3) as $key=>$value){} | ||
2402 | */ | ||
2403 | |||
2404 | }; | ||
2405 | /* Foreach step control flags */ | ||
2406 | #define JX9_4EACH_STEP_KEY 0x001 /* Make Key available */ | ||
2407 | /* | ||
2408 | * Each JX9 engine is identified by an instance of the following structure. | ||
2409 | * Please refer to the official documentation for more information | ||
2410 | * on how to configure your JX9 engine instance. | ||
2411 | */ | ||
2412 | struct jx9 | ||
2413 | { | ||
2414 | SyMemBackend sAllocator; /* Low level memory allocation subsystem */ | ||
2415 | const jx9_vfs *pVfs; /* Underlying Virtual File System */ | ||
2416 | jx9_conf xConf; /* Configuration */ | ||
2417 | #if defined(JX9_ENABLE_THREADS) | ||
2418 | SyMutex *pMutex; /* Per-engine mutex */ | ||
2419 | #endif | ||
2420 | jx9_vm *pVms; /* List of active VM */ | ||
2421 | sxi32 iVm; /* Total number of active VM */ | ||
2422 | jx9 *pNext, *pPrev; /* List of active engines */ | ||
2423 | sxu32 nMagic; /* Sanity check against misuse */ | ||
2424 | }; | ||
2425 | /* Code generation data structures */ | ||
2426 | typedef sxi32 (*ProcErrorGen)(void *, sxi32, sxu32, const char *, ...); | ||
2427 | typedef struct jx9_expr_node jx9_expr_node; | ||
2428 | typedef struct jx9_expr_op jx9_expr_op; | ||
2429 | typedef struct jx9_gen_state jx9_gen_state; | ||
2430 | typedef struct GenBlock GenBlock; | ||
2431 | typedef sxi32 (*ProcLangConstruct)(jx9_gen_state *); | ||
2432 | typedef sxi32 (*ProcNodeConstruct)(jx9_gen_state *, sxi32); | ||
2433 | /* | ||
2434 | * Each supported operator [i.e: +, -, ==, *, %, >>, >=, new, etc.] is represented | ||
2435 | * by an instance of the following structure. | ||
2436 | * The JX9 parser does not use any external tools and is 100% handcoded. | ||
2437 | * That is, the JX9 parser is thread-safe , full reentrant, produce consistant | ||
2438 | * compile-time errrors and at least 7 times faster than the standard JX9 parser. | ||
2439 | */ | ||
2440 | struct jx9_expr_op | ||
2441 | { | ||
2442 | SyString sOp; /* String representation of the operator [i.e: "+", "*", "=="...] */ | ||
2443 | sxi32 iOp; /* Operator ID */ | ||
2444 | sxi32 iPrec; /* Operator precedence: 1 == Highest */ | ||
2445 | sxi32 iAssoc; /* Operator associativity (either left, right or non-associative) */ | ||
2446 | sxi32 iVmOp; /* VM OP code for this operator [i.e: JX9_OP_EQ, JX9_OP_LT, JX9_OP_MUL...]*/ | ||
2447 | }; | ||
2448 | /* | ||
2449 | * Each expression node is parsed out and recorded | ||
2450 | * in an instance of the following structure. | ||
2451 | */ | ||
2452 | struct jx9_expr_node | ||
2453 | { | ||
2454 | const jx9_expr_op *pOp; /* Operator ID or NULL if literal, constant, variable, function or object method call */ | ||
2455 | jx9_expr_node *pLeft; /* Left expression tree */ | ||
2456 | jx9_expr_node *pRight; /* Right expression tree */ | ||
2457 | SyToken *pStart; /* Stream of tokens that belong to this node */ | ||
2458 | SyToken *pEnd; /* End of token stream */ | ||
2459 | sxi32 iFlags; /* Node construct flags */ | ||
2460 | ProcNodeConstruct xCode; /* C routine responsible of compiling this node */ | ||
2461 | SySet aNodeArgs; /* Node arguments. Only used by postfix operators [i.e: function call]*/ | ||
2462 | jx9_expr_node *pCond; /* Condition: Only used by the ternary operator '?:' */ | ||
2463 | }; | ||
2464 | /* Node Construct flags */ | ||
2465 | #define EXPR_NODE_PRE_INCR 0x01 /* Pre-icrement/decrement [i.e: ++$i, --$j] node */ | ||
2466 | /* | ||
2467 | * A block of instructions is recorded in an instance of the following structure. | ||
2468 | * This structure is used only during compile-time and have no meaning | ||
2469 | * during bytecode execution. | ||
2470 | */ | ||
2471 | struct GenBlock | ||
2472 | { | ||
2473 | jx9_gen_state *pGen; /* State of the code generator */ | ||
2474 | GenBlock *pParent; /* Upper block or NULL if global */ | ||
2475 | sxu32 nFirstInstr; /* First instruction to execute */ | ||
2476 | sxi32 iFlags; /* Block control flags (see below) */ | ||
2477 | SySet aJumpFix; /* Jump fixup (JumpFixup instance) */ | ||
2478 | void *pUserData; /* Upper layer private data */ | ||
2479 | /* The following two fields are used only when compiling | ||
2480 | * the 'do..while()' language construct. | ||
2481 | */ | ||
2482 | sxu8 bPostContinue; /* TRUE when compiling the do..while() statement */ | ||
2483 | SySet aPostContFix; /* Post-continue jump fix */ | ||
2484 | }; | ||
2485 | /* | ||
2486 | * Code generator state is remembered in an instance of the following | ||
2487 | * structure. We put the information in this structure and pass around | ||
2488 | * a pointer to this structure, rather than pass around all of the | ||
2489 | * information separately. This helps reduce the number of arguments | ||
2490 | * to generator functions. | ||
2491 | * This structure is used only during compile-time and have no meaning | ||
2492 | * during bytecode execution. | ||
2493 | */ | ||
2494 | struct jx9_gen_state | ||
2495 | { | ||
2496 | jx9_vm *pVm; /* VM that own this instance */ | ||
2497 | SyHash hLiteral; /* Constant string Literals table */ | ||
2498 | SyHash hNumLiteral; /* Numeric literals table */ | ||
2499 | SyHash hVar; /* Collected variable hashtable */ | ||
2500 | GenBlock *pCurrent; /* Current processed block */ | ||
2501 | GenBlock sGlobal; /* Global block */ | ||
2502 | ProcConsumer xErr; /* Error consumer callback */ | ||
2503 | void *pErrData; /* Third argument to xErr() */ | ||
2504 | SyToken *pIn; /* Current processed token */ | ||
2505 | SyToken *pEnd; /* Last token in the stream */ | ||
2506 | sxu32 nErr; /* Total number of compilation error */ | ||
2507 | }; | ||
2508 | /* Forward references */ | ||
2509 | typedef struct jx9_vm_func_static_var jx9_vm_func_static_var; | ||
2510 | typedef struct jx9_vm_func_arg jx9_vm_func_arg; | ||
2511 | typedef struct jx9_vm_func jx9_vm_func; | ||
2512 | typedef struct VmFrame VmFrame; | ||
2513 | /* | ||
2514 | * Each collected function argument is recorded in an instance | ||
2515 | * of the following structure. | ||
2516 | * Note that as an extension, JX9 implements full type hinting | ||
2517 | * which mean that any function can have it's own signature. | ||
2518 | * Example: | ||
2519 | * function foo(int $a, string $b, float $c, ClassInstance $d){} | ||
2520 | * This is how the powerful function overloading mechanism is | ||
2521 | * implemented. | ||
2522 | * Note that as an extension, JX9 allow function arguments to have | ||
2523 | * any complex default value associated with them unlike the standard | ||
2524 | * JX9 engine. | ||
2525 | * Example: | ||
2526 | * function foo(int $a = rand() & 1023){} | ||
2527 | * now, when foo is called without arguments [i.e: foo()] the | ||
2528 | * $a variable (first parameter) will be set to a random number | ||
2529 | * between 0 and 1023 inclusive. | ||
2530 | * Refer to the official documentation for more information on this | ||
2531 | * mechanism and other extension introduced by the JX9 engine. | ||
2532 | */ | ||
2533 | struct jx9_vm_func_arg | ||
2534 | { | ||
2535 | SyString sName; /* Argument name */ | ||
2536 | SySet aByteCode; /* Compiled default value associated with this argument */ | ||
2537 | sxu32 nType; /* Type of this argument [i.e: array, int, string, float, object, etc.] */ | ||
2538 | sxi32 iFlags; /* Configuration flags */ | ||
2539 | }; | ||
2540 | /* | ||
2541 | * Each static variable is parsed out and remembered in an instance | ||
2542 | * of the following structure. | ||
2543 | * Note that as an extension, JX9 allow static variable have | ||
2544 | * any complex default value associated with them unlike the standard | ||
2545 | * JX9 engine. | ||
2546 | * Example: | ||
2547 | * static $rand_str = 'JX9'.rand_str(3); // Concatenate 'JX9' with | ||
2548 | * // a random three characters(English alphabet) | ||
2549 | * dump($rand_str); | ||
2550 | * //You should see something like this | ||
2551 | * string(6 'JX9awt'); | ||
2552 | */ | ||
2553 | struct jx9_vm_func_static_var | ||
2554 | { | ||
2555 | SyString sName; /* Static variable name */ | ||
2556 | SySet aByteCode; /* Compiled initialization expression */ | ||
2557 | sxu32 nIdx; /* Object index in the global memory object container */ | ||
2558 | }; | ||
2559 | /* Function configuration flags */ | ||
2560 | #define VM_FUNC_ARG_HAS_DEF 0x001 /* Argument has default value associated with it */ | ||
2561 | #define VM_FUNC_ARG_IGNORE 0x002 /* Do not install argument in the current frame */ | ||
2562 | /* | ||
2563 | * Each user defined function is parsed out and stored in an instance | ||
2564 | * of the following structure. | ||
2565 | * JX9 introduced some powerfull extensions to the JX9 5 programming | ||
2566 | * language like function overloading, type hinting, complex default | ||
2567 | * arguments values and many more. | ||
2568 | * Please refer to the official documentation for more information. | ||
2569 | */ | ||
2570 | struct jx9_vm_func | ||
2571 | { | ||
2572 | SySet aArgs; /* Expected arguments (jx9_vm_func_arg instance) */ | ||
2573 | SySet aStatic; /* Static variable (jx9_vm_func_static_var instance) */ | ||
2574 | SyString sName; /* Function name */ | ||
2575 | SySet aByteCode; /* Compiled function body */ | ||
2576 | sxi32 iFlags; /* VM function configuration */ | ||
2577 | SyString sSignature; /* Function signature used to implement function overloading | ||
2578 | * (Refer to the official docuemntation for more information | ||
2579 | * on this powerfull feature) | ||
2580 | */ | ||
2581 | void *pUserData; /* Upper layer private data associated with this instance */ | ||
2582 | jx9_vm_func *pNextName; /* Next VM function with the same name as this one */ | ||
2583 | }; | ||
2584 | /* Forward reference */ | ||
2585 | typedef struct jx9_builtin_constant jx9_builtin_constant; | ||
2586 | typedef struct jx9_builtin_func jx9_builtin_func; | ||
2587 | /* | ||
2588 | * Each built-in foreign function (C function) is stored in an | ||
2589 | * instance of the following structure. | ||
2590 | * Please refer to the official documentation for more information | ||
2591 | * on how to create/install foreign functions. | ||
2592 | */ | ||
2593 | struct jx9_builtin_func | ||
2594 | { | ||
2595 | const char *zName; /* Function name [i.e: strlen(), rand(), array_merge(), etc.]*/ | ||
2596 | ProcHostFunction xFunc; /* C routine performing the computation */ | ||
2597 | }; | ||
2598 | /* | ||
2599 | * Each built-in foreign constant is stored in an instance | ||
2600 | * of the following structure. | ||
2601 | * Please refer to the official documentation for more information | ||
2602 | * on how to create/install foreign constants. | ||
2603 | */ | ||
2604 | struct jx9_builtin_constant | ||
2605 | { | ||
2606 | const char *zName; /* Constant name */ | ||
2607 | ProcConstant xExpand; /* C routine responsible of expanding constant value*/ | ||
2608 | }; | ||
2609 | /* | ||
2610 | * A single instruction of the virtual machine has an opcode | ||
2611 | * and as many as three operands. | ||
2612 | * Each VM instruction resulting from compiling a JX9 script | ||
2613 | * is stored in an instance of the following structure. | ||
2614 | */ | ||
2615 | typedef struct VmInstr VmInstr; | ||
2616 | struct VmInstr | ||
2617 | { | ||
2618 | sxu8 iOp; /* Operation to preform */ | ||
2619 | sxi32 iP1; /* First operand */ | ||
2620 | sxu32 iP2; /* Second operand (Often the jump destination) */ | ||
2621 | void *p3; /* Third operand (Often Upper layer private data) */ | ||
2622 | }; | ||
2623 | /* Forward reference */ | ||
2624 | typedef struct jx9_case_expr jx9_case_expr; | ||
2625 | typedef struct jx9_switch jx9_switch; | ||
2626 | /* | ||
2627 | * Each compiled case block in a swicth statement is compiled | ||
2628 | * and stored in an instance of the following structure. | ||
2629 | */ | ||
2630 | struct jx9_case_expr | ||
2631 | { | ||
2632 | SySet aByteCode; /* Compiled body of the case block */ | ||
2633 | sxu32 nStart; /* First instruction to execute */ | ||
2634 | }; | ||
2635 | /* | ||
2636 | * Each compiled switch statement is parsed out and stored | ||
2637 | * in an instance of the following structure. | ||
2638 | */ | ||
2639 | struct jx9_switch | ||
2640 | { | ||
2641 | SySet aCaseExpr; /* Compile case block */ | ||
2642 | sxu32 nOut; /* First instruction to execute after this statement */ | ||
2643 | sxu32 nDefault; /* First instruction to execute in the default block */ | ||
2644 | }; | ||
2645 | /* Assertion flags */ | ||
2646 | #define JX9_ASSERT_DISABLE 0x01 /* Disable assertion */ | ||
2647 | #define JX9_ASSERT_WARNING 0x02 /* Issue a warning for each failed assertion */ | ||
2648 | #define JX9_ASSERT_BAIL 0x04 /* Terminate execution on failed assertions */ | ||
2649 | #define JX9_ASSERT_QUIET_EVAL 0x08 /* Not used */ | ||
2650 | #define JX9_ASSERT_CALLBACK 0x10 /* Callback to call on failed assertions */ | ||
2651 | /* | ||
2652 | * An instance of the following structure hold the bytecode instructions | ||
2653 | * resulting from compiling a JX9 script. | ||
2654 | * This structure contains the complete state of the virtual machine. | ||
2655 | */ | ||
2656 | struct jx9_vm | ||
2657 | { | ||
2658 | SyMemBackend sAllocator; /* Memory backend */ | ||
2659 | #if defined(JX9_ENABLE_THREADS) | ||
2660 | SyMutex *pMutex; /* Recursive mutex associated with this VM. */ | ||
2661 | #endif | ||
2662 | jx9 *pEngine; /* Interpreter that own this VM */ | ||
2663 | SySet aByteCode; /* Default bytecode container */ | ||
2664 | SySet *pByteContainer; /* Current bytecode container */ | ||
2665 | VmFrame *pFrame; /* Stack of active frames */ | ||
2666 | SyPRNGCtx sPrng; /* PRNG context */ | ||
2667 | SySet aMemObj; /* Object allocation table */ | ||
2668 | SySet aLitObj; /* Literals allocation table */ | ||
2669 | jx9_value *aOps; /* Operand stack */ | ||
2670 | SySet aFreeObj; /* Stack of free memory objects */ | ||
2671 | SyHash hConstant; /* Host-application and user defined constants container */ | ||
2672 | SyHash hHostFunction; /* Host-application installable functions */ | ||
2673 | SyHash hFunction; /* Compiled functions */ | ||
2674 | SyHash hSuper; /* Global variable */ | ||
2675 | SyBlob sConsumer; /* Default VM consumer [i.e Redirect all VM output to this blob] */ | ||
2676 | SyBlob sWorker; /* General purpose working buffer */ | ||
2677 | SyBlob sArgv; /* $argv[] collector [refer to the [getopt()] implementation for more information] */ | ||
2678 | SySet aFiles; /* Stack of processed files */ | ||
2679 | SySet aPaths; /* Set of import paths */ | ||
2680 | SySet aIncluded; /* Set of included files */ | ||
2681 | SySet aIOstream; /* Installed IO stream container */ | ||
2682 | const jx9_io_stream *pDefStream; /* Default IO stream [i.e: typically this is the 'file://' stream] */ | ||
2683 | jx9_value sExec; /* Compiled script return value [Can be extracted via the JX9_VM_CONFIG_EXEC_VALUE directive]*/ | ||
2684 | void *pStdin; /* STDIN IO stream */ | ||
2685 | void *pStdout; /* STDOUT IO stream */ | ||
2686 | void *pStderr; /* STDERR IO stream */ | ||
2687 | int bErrReport; /* TRUE to report all runtime Error/Warning/Notice */ | ||
2688 | int nRecursionDepth; /* Current recursion depth */ | ||
2689 | int nMaxDepth; /* Maximum allowed recusion depth */ | ||
2690 | sxu32 nOutputLen; /* Total number of generated output */ | ||
2691 | jx9_output_consumer sVmConsumer; /* Registered output consumer callback */ | ||
2692 | int iAssertFlags; /* Assertion flags */ | ||
2693 | jx9_value sAssertCallback; /* Callback to call on failed assertions */ | ||
2694 | sxi32 iExitStatus; /* Script exit status */ | ||
2695 | jx9_gen_state sCodeGen; /* Code generator module */ | ||
2696 | jx9_vm *pNext, *pPrev; /* List of active VM's */ | ||
2697 | sxu32 nMagic; /* Sanity check against misuse */ | ||
2698 | }; | ||
2699 | /* | ||
2700 | * Allowed value for jx9_vm.nMagic | ||
2701 | */ | ||
2702 | #define JX9_VM_INIT 0xEA12CD72 /* VM correctly initialized */ | ||
2703 | #define JX9_VM_RUN 0xBA851227 /* VM ready to execute JX9 bytecode */ | ||
2704 | #define JX9_VM_EXEC 0xCDFE1DAD /* VM executing JX9 bytecode */ | ||
2705 | #define JX9_VM_STALE 0xDEAD2BAD /* Stale VM */ | ||
2706 | /* | ||
2707 | * Error codes according to the JX9 language reference manual. | ||
2708 | */ | ||
2709 | enum iErrCode | ||
2710 | { | ||
2711 | E_ERROR = 1, /* Fatal run-time errors. These indicate errors that can not be recovered | ||
2712 | * from, such as a memory allocation problem. Execution of the script is | ||
2713 | * halted. | ||
2714 | * The only fatal error under JX9 is an out-of-memory. All others erros | ||
2715 | * even a call to undefined function will not halt script execution. | ||
2716 | */ | ||
2717 | E_WARNING , /* Run-time warnings (non-fatal errors). Execution of the script is not halted. */ | ||
2718 | E_PARSE , /* Compile-time parse errors. Parse errors should only be generated by the parser.*/ | ||
2719 | E_NOTICE , /* Run-time notices. Indicate that the script encountered something that could | ||
2720 | * indicate an error, but could also happen in the normal course of running a script. | ||
2721 | */ | ||
2722 | }; | ||
2723 | /* | ||
2724 | * Each VM instruction resulting from compiling a JX9 script is represented | ||
2725 | * by one of the following OP codes. | ||
2726 | * The program consists of a linear sequence of operations. Each operation | ||
2727 | * has an opcode and 3 operands.Operands P1 is an integer. | ||
2728 | * Operand P2 is an unsigned integer and operand P3 is a memory address. | ||
2729 | * Few opcodes use all 3 operands. | ||
2730 | */ | ||
2731 | enum jx9_vm_op { | ||
2732 | JX9_OP_DONE = 1, /* Done */ | ||
2733 | JX9_OP_HALT, /* Halt */ | ||
2734 | JX9_OP_LOAD, /* Load memory object */ | ||
2735 | JX9_OP_LOADC, /* Load constant */ | ||
2736 | JX9_OP_LOAD_IDX, /* Load array entry */ | ||
2737 | JX9_OP_LOAD_MAP, /* Load hashmap('array') */ | ||
2738 | JX9_OP_NOOP, /* NOOP */ | ||
2739 | JX9_OP_JMP, /* Unconditional jump */ | ||
2740 | JX9_OP_JZ, /* Jump on zero (FALSE jump) */ | ||
2741 | JX9_OP_JNZ, /* Jump on non-zero (TRUE jump) */ | ||
2742 | JX9_OP_POP, /* Stack POP */ | ||
2743 | JX9_OP_CAT, /* Concatenation */ | ||
2744 | JX9_OP_CVT_INT, /* Integer cast */ | ||
2745 | JX9_OP_CVT_STR, /* String cast */ | ||
2746 | JX9_OP_CVT_REAL, /* Float cast */ | ||
2747 | JX9_OP_CALL, /* Function call */ | ||
2748 | JX9_OP_UMINUS, /* Unary minus '-'*/ | ||
2749 | JX9_OP_UPLUS, /* Unary plus '+'*/ | ||
2750 | JX9_OP_BITNOT, /* Bitwise not '~' */ | ||
2751 | JX9_OP_LNOT, /* Logical not '!' */ | ||
2752 | JX9_OP_MUL, /* Multiplication '*' */ | ||
2753 | JX9_OP_DIV, /* Division '/' */ | ||
2754 | JX9_OP_MOD, /* Modulus '%' */ | ||
2755 | JX9_OP_ADD, /* Add '+' */ | ||
2756 | JX9_OP_SUB, /* Sub '-' */ | ||
2757 | JX9_OP_SHL, /* Left shift '<<' */ | ||
2758 | JX9_OP_SHR, /* Right shift '>>' */ | ||
2759 | JX9_OP_LT, /* Less than '<' */ | ||
2760 | JX9_OP_LE, /* Less or equal '<=' */ | ||
2761 | JX9_OP_GT, /* Greater than '>' */ | ||
2762 | JX9_OP_GE, /* Greater or equal '>=' */ | ||
2763 | JX9_OP_EQ, /* Equal '==' */ | ||
2764 | JX9_OP_NEQ, /* Not equal '!=' */ | ||
2765 | JX9_OP_TEQ, /* Type equal '===' */ | ||
2766 | JX9_OP_TNE, /* Type not equal '!==' */ | ||
2767 | JX9_OP_BAND, /* Bitwise and '&' */ | ||
2768 | JX9_OP_BXOR, /* Bitwise xor '^' */ | ||
2769 | JX9_OP_BOR, /* Bitwise or '|' */ | ||
2770 | JX9_OP_LAND, /* Logical and '&&','and' */ | ||
2771 | JX9_OP_LOR, /* Logical or '||','or' */ | ||
2772 | JX9_OP_LXOR, /* Logical xor 'xor' */ | ||
2773 | JX9_OP_STORE, /* Store Object */ | ||
2774 | JX9_OP_STORE_IDX, /* Store indexed object */ | ||
2775 | JX9_OP_PULL, /* Stack pull */ | ||
2776 | JX9_OP_SWAP, /* Stack swap */ | ||
2777 | JX9_OP_YIELD, /* Stack yield */ | ||
2778 | JX9_OP_CVT_BOOL, /* Boolean cast */ | ||
2779 | JX9_OP_CVT_NUMC, /* Numeric (integer, real or both) type cast */ | ||
2780 | JX9_OP_INCR, /* Increment ++ */ | ||
2781 | JX9_OP_DECR, /* Decrement -- */ | ||
2782 | JX9_OP_ADD_STORE, /* Add and store '+=' */ | ||
2783 | JX9_OP_SUB_STORE, /* Sub and store '-=' */ | ||
2784 | JX9_OP_MUL_STORE, /* Mul and store '*=' */ | ||
2785 | JX9_OP_DIV_STORE, /* Div and store '/=' */ | ||
2786 | JX9_OP_MOD_STORE, /* Mod and store '%=' */ | ||
2787 | JX9_OP_CAT_STORE, /* Cat and store '.=' */ | ||
2788 | JX9_OP_SHL_STORE, /* Shift left and store '>>=' */ | ||
2789 | JX9_OP_SHR_STORE, /* Shift right and store '<<=' */ | ||
2790 | JX9_OP_BAND_STORE, /* Bitand and store '&=' */ | ||
2791 | JX9_OP_BOR_STORE, /* Bitor and store '|=' */ | ||
2792 | JX9_OP_BXOR_STORE, /* Bitxor and store '^=' */ | ||
2793 | JX9_OP_CONSUME, /* Consume VM output */ | ||
2794 | JX9_OP_MEMBER, /* Object member run-time access */ | ||
2795 | JX9_OP_UPLINK, /* Run-Time frame link */ | ||
2796 | JX9_OP_CVT_NULL, /* NULL cast */ | ||
2797 | JX9_OP_CVT_ARRAY, /* Array cast */ | ||
2798 | JX9_OP_FOREACH_INIT, /* For each init */ | ||
2799 | JX9_OP_FOREACH_STEP, /* For each step */ | ||
2800 | JX9_OP_SWITCH /* Switch operation */ | ||
2801 | }; | ||
2802 | /* -- END-OF INSTRUCTIONS -- */ | ||
2803 | /* | ||
2804 | * Expression Operators ID. | ||
2805 | */ | ||
2806 | enum jx9_expr_id { | ||
2807 | EXPR_OP_DOT, /* Member access */ | ||
2808 | EXPR_OP_DC, /* :: */ | ||
2809 | EXPR_OP_SUBSCRIPT, /* []: Subscripting */ | ||
2810 | EXPR_OP_FUNC_CALL, /* func_call() */ | ||
2811 | EXPR_OP_INCR, /* ++ */ | ||
2812 | EXPR_OP_DECR, /* -- */ | ||
2813 | EXPR_OP_BITNOT, /* ~ */ | ||
2814 | EXPR_OP_UMINUS, /* Unary minus */ | ||
2815 | EXPR_OP_UPLUS, /* Unary plus */ | ||
2816 | EXPR_OP_TYPECAST, /* Type cast [i.e: (int), (float), (string)...] */ | ||
2817 | EXPR_OP_ALT, /* @ */ | ||
2818 | EXPR_OP_INSTOF, /* instanceof */ | ||
2819 | EXPR_OP_LOGNOT, /* logical not ! */ | ||
2820 | EXPR_OP_MUL, /* Multiplication */ | ||
2821 | EXPR_OP_DIV, /* division */ | ||
2822 | EXPR_OP_MOD, /* Modulus */ | ||
2823 | EXPR_OP_ADD, /* Addition */ | ||
2824 | EXPR_OP_SUB, /* Substraction */ | ||
2825 | EXPR_OP_DDOT, /* Concatenation */ | ||
2826 | EXPR_OP_SHL, /* Left shift */ | ||
2827 | EXPR_OP_SHR, /* Right shift */ | ||
2828 | EXPR_OP_LT, /* Less than */ | ||
2829 | EXPR_OP_LE, /* Less equal */ | ||
2830 | EXPR_OP_GT, /* Greater than */ | ||
2831 | EXPR_OP_GE, /* Greater equal */ | ||
2832 | EXPR_OP_EQ, /* Equal == */ | ||
2833 | EXPR_OP_NE, /* Not equal != <> */ | ||
2834 | EXPR_OP_TEQ, /* Type equal === */ | ||
2835 | EXPR_OP_TNE, /* Type not equal !== */ | ||
2836 | EXPR_OP_SEQ, /* String equal 'eq' */ | ||
2837 | EXPR_OP_SNE, /* String not equal 'ne' */ | ||
2838 | EXPR_OP_BAND, /* Biwise and '&' */ | ||
2839 | EXPR_OP_REF, /* Reference operator '&' */ | ||
2840 | EXPR_OP_XOR, /* bitwise xor '^' */ | ||
2841 | EXPR_OP_BOR, /* bitwise or '|' */ | ||
2842 | EXPR_OP_LAND, /* Logical and '&&','and' */ | ||
2843 | EXPR_OP_LOR, /* Logical or '||','or'*/ | ||
2844 | EXPR_OP_LXOR, /* Logical xor 'xor' */ | ||
2845 | EXPR_OP_QUESTY, /* Ternary operator '?' */ | ||
2846 | EXPR_OP_ASSIGN, /* Assignment '=' */ | ||
2847 | EXPR_OP_ADD_ASSIGN, /* Combined operator: += */ | ||
2848 | EXPR_OP_SUB_ASSIGN, /* Combined operator: -= */ | ||
2849 | EXPR_OP_MUL_ASSIGN, /* Combined operator: *= */ | ||
2850 | EXPR_OP_DIV_ASSIGN, /* Combined operator: /= */ | ||
2851 | EXPR_OP_MOD_ASSIGN, /* Combined operator: %= */ | ||
2852 | EXPR_OP_DOT_ASSIGN, /* Combined operator: .= */ | ||
2853 | EXPR_OP_AND_ASSIGN, /* Combined operator: &= */ | ||
2854 | EXPR_OP_OR_ASSIGN, /* Combined operator: |= */ | ||
2855 | EXPR_OP_XOR_ASSIGN, /* Combined operator: ^= */ | ||
2856 | EXPR_OP_SHL_ASSIGN, /* Combined operator: <<= */ | ||
2857 | EXPR_OP_SHR_ASSIGN, /* Combined operator: >>= */ | ||
2858 | EXPR_OP_COMMA /* Comma expression */ | ||
2859 | }; | ||
2860 | /* | ||
2861 | * Lexer token codes | ||
2862 | * The following set of constants are the tokens recognized | ||
2863 | * by the lexer when processing JX9 input. | ||
2864 | * Important: Token values MUST BE A POWER OF TWO. | ||
2865 | */ | ||
2866 | #define JX9_TK_INTEGER 0x0000001 /* Integer */ | ||
2867 | #define JX9_TK_REAL 0x0000002 /* Real number */ | ||
2868 | #define JX9_TK_NUM (JX9_TK_INTEGER|JX9_TK_REAL) /* Numeric token, either integer or real */ | ||
2869 | #define JX9_TK_KEYWORD 0x0000004 /* Keyword [i.e: while, for, if, foreach...] */ | ||
2870 | #define JX9_TK_ID 0x0000008 /* Alphanumeric or UTF-8 stream */ | ||
2871 | #define JX9_TK_DOLLAR 0x0000010 /* '$' Dollar sign */ | ||
2872 | #define JX9_TK_OP 0x0000020 /* Operator [i.e: +, *, /...] */ | ||
2873 | #define JX9_TK_OCB 0x0000040 /* Open curly brace'{' */ | ||
2874 | #define JX9_TK_CCB 0x0000080 /* Closing curly brace'}' */ | ||
2875 | #define JX9_TK_DOT 0x0000100 /* Dot . */ | ||
2876 | #define JX9_TK_LPAREN 0x0000200 /* Left parenthesis '(' */ | ||
2877 | #define JX9_TK_RPAREN 0x0000400 /* Right parenthesis ')' */ | ||
2878 | #define JX9_TK_OSB 0x0000800 /* Open square bracket '[' */ | ||
2879 | #define JX9_TK_CSB 0x0001000 /* Closing square bracket ']' */ | ||
2880 | #define JX9_TK_DSTR 0x0002000 /* Double quoted string "$str" */ | ||
2881 | #define JX9_TK_SSTR 0x0004000 /* Single quoted string 'str' */ | ||
2882 | #define JX9_TK_NOWDOC 0x0010000 /* Nowdoc <<< */ | ||
2883 | #define JX9_TK_COMMA 0x0020000 /* Comma ',' */ | ||
2884 | #define JX9_TK_SEMI 0x0040000 /* Semi-colon ";" */ | ||
2885 | #define JX9_TK_BSTR 0x0080000 /* Backtick quoted string [i.e: Shell command `date`] */ | ||
2886 | #define JX9_TK_COLON 0x0100000 /* single Colon ':' */ | ||
2887 | #define JX9_TK_AMPER 0x0200000 /* Ampersand '&' */ | ||
2888 | #define JX9_TK_EQUAL 0x0400000 /* Equal '=' */ | ||
2889 | #define JX9_TK_OTHER 0x1000000 /* Other symbols */ | ||
2890 | /* | ||
2891 | * JX9 keyword. | ||
2892 | * These words have special meaning in JX9. Some of them represent things which look like | ||
2893 | * functions, some look like constants, and so on, but they're not, really: they are language constructs. | ||
2894 | * You cannot use any of the following words as constants, object names, function or method names. | ||
2895 | * Using them as variable names is generally OK, but could lead to confusion. | ||
2896 | */ | ||
2897 | #define JX9_TKWRD_SWITCH 1 /* switch */ | ||
2898 | #define JX9_TKWRD_PRINT 2 /* print */ | ||
2899 | #define JX9_TKWRD_ELIF 0x4000000 /* elseif: MUST BE A POWER OF TWO */ | ||
2900 | #define JX9_TKWRD_ELSE 0x8000000 /* else: MUST BE A POWER OF TWO */ | ||
2901 | #define JX9_TKWRD_IF 3 /* if */ | ||
2902 | #define JX9_TKWRD_STATIC 4 /* static */ | ||
2903 | #define JX9_TKWRD_CASE 5 /* case */ | ||
2904 | #define JX9_TKWRD_FUNCTION 6 /* function */ | ||
2905 | #define JX9_TKWRD_CONST 7 /* const */ | ||
2906 | /* The number '8' is reserved for JX9_TK_ID */ | ||
2907 | #define JX9_TKWRD_WHILE 9 /* while */ | ||
2908 | #define JX9_TKWRD_DEFAULT 10 /* default */ | ||
2909 | #define JX9_TKWRD_AS 11 /* as */ | ||
2910 | #define JX9_TKWRD_CONTINUE 12 /* continue */ | ||
2911 | #define JX9_TKWRD_EXIT 13 /* exit */ | ||
2912 | #define JX9_TKWRD_DIE 14 /* die */ | ||
2913 | #define JX9_TKWRD_IMPORT 15 /* import */ | ||
2914 | #define JX9_TKWRD_INCLUDE 16 /* include */ | ||
2915 | #define JX9_TKWRD_FOR 17 /* for */ | ||
2916 | #define JX9_TKWRD_FOREACH 18 /* foreach */ | ||
2917 | #define JX9_TKWRD_RETURN 19 /* return */ | ||
2918 | #define JX9_TKWRD_BREAK 20 /* break */ | ||
2919 | #define JX9_TKWRD_UPLINK 21 /* uplink */ | ||
2920 | #define JX9_TKWRD_BOOL 0x8000 /* bool: MUST BE A POWER OF TWO */ | ||
2921 | #define JX9_TKWRD_INT 0x10000 /* int: MUST BE A POWER OF TWO */ | ||
2922 | #define JX9_TKWRD_FLOAT 0x20000 /* float: MUST BE A POWER OF TWO */ | ||
2923 | #define JX9_TKWRD_STRING 0x40000 /* string: MUST BE A POWER OF TWO */ | ||
2924 | |||
2925 | /* api.c */ | ||
2926 | JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap); | ||
2927 | JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName); | ||
2928 | JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName); | ||
2929 | /* json.c function prototypes */ | ||
2930 | JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut); | ||
2931 | JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte); | ||
2932 | /* memobj.c function prototypes */ | ||
2933 | JX9_PRIVATE sxi32 jx9MemObjDump(SyBlob *pOut, jx9_value *pObj); | ||
2934 | JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal); | ||
2935 | JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore); | ||
2936 | JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest); | ||
2937 | JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal); | ||
2938 | JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray); | ||
2939 | #if 0 | ||
2940 | /* Not used in the current release of the JX9 engine */ | ||
2941 | JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal); | ||
2942 | #endif | ||
2943 | JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal); | ||
2944 | JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal); | ||
2945 | JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj); | ||
2946 | JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen); | ||
2947 | #if 0 | ||
2948 | /* Not used in the current release of the JX9 engine */ | ||
2949 | JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap); | ||
2950 | #endif | ||
2951 | JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest); | ||
2952 | JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest); | ||
2953 | JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj); | ||
2954 | JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj); | ||
2955 | JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj); | ||
2956 | JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags); | ||
2957 | JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj); | ||
2958 | JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj); | ||
2959 | JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj); | ||
2960 | JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj); | ||
2961 | JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj); | ||
2962 | JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj); | ||
2963 | JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj); | ||
2964 | JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj); | ||
2965 | JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pData); | ||
2966 | /* lex.c function prototypes */ | ||
2967 | JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput, sxu32 nLen, SySet *pOut); | ||
2968 | /* vm.c function prototypes */ | ||
2969 | JX9_PRIVATE void jx9VmReleaseContextValue(jx9_context *pCtx, jx9_value *pValue); | ||
2970 | JX9_PRIVATE sxi32 jx9VmInitFuncState(jx9_vm *pVm, jx9_vm_func *pFunc, const char *zName, sxu32 nByte, | ||
2971 | sxi32 iFlags, void *pUserData); | ||
2972 | JX9_PRIVATE sxi32 jx9VmInstallUserFunction(jx9_vm *pVm, jx9_vm_func *pFunc, SyString *pName); | ||
2973 | JX9_PRIVATE sxi32 jx9VmRegisterConstant(jx9_vm *pVm, const SyString *pName, ProcConstant xExpand, void *pUserData); | ||
2974 | JX9_PRIVATE sxi32 jx9VmInstallForeignFunction(jx9_vm *pVm, const SyString *pName, ProcHostFunction xFunc, void *pUserData); | ||
2975 | JX9_PRIVATE sxi32 jx9VmBlobConsumer(const void *pSrc, unsigned int nLen, void *pUserData); | ||
2976 | JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIndex); | ||
2977 | JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex); | ||
2978 | JX9_PRIVATE sxi32 jx9VmOutputConsume(jx9_vm *pVm, SyString *pString); | ||
2979 | JX9_PRIVATE sxi32 jx9VmOutputConsumeAp(jx9_vm *pVm, const char *zFormat, va_list ap); | ||
2980 | JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap); | ||
2981 | JX9_PRIVATE sxi32 jx9VmThrowError(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zMessage); | ||
2982 | JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData); | ||
2983 | JX9_PRIVATE sxi32 jx9VmDump(jx9_vm *pVm, ProcConsumer xConsumer, void *pUserData); | ||
2984 | JX9_PRIVATE sxi32 jx9VmInit(jx9_vm *pVm, jx9 *pEngine); | ||
2985 | JX9_PRIVATE sxi32 jx9VmConfigure(jx9_vm *pVm, sxi32 nOp, va_list ap); | ||
2986 | JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm); | ||
2987 | JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar); | ||
2988 | JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm); | ||
2989 | JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm); | ||
2990 | JX9_PRIVATE sxi32 jx9VmMakeReady(jx9_vm *pVm); | ||
2991 | JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm); | ||
2992 | JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm); | ||
2993 | JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm); | ||
2994 | JX9_PRIVATE VmInstr *jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex); | ||
2995 | JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm); | ||
2996 | JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer); | ||
2997 | JX9_PRIVATE sxi32 jx9VmEmitInstr(jx9_vm *pVm, sxi32 iOp, sxi32 iP1, sxu32 iP2, void *p3, sxu32 *pIndex); | ||
2998 | JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm); | ||
2999 | JX9_PRIVATE sxi32 jx9VmCallUserFunction(jx9_vm *pVm, jx9_value *pFunc, int nArg, jx9_value **apArg, jx9_value *pResult); | ||
3000 | JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp(jx9_vm *pVm, jx9_value *pFunc, jx9_value *pResult, ...); | ||
3001 | JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm, sxu32 nObjIdx); | ||
3002 | JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen); | ||
3003 | JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue); | ||
3004 | JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew); | ||
3005 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3006 | JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(jx9_vm *pVm, const char **pzDevice, int nByte); | ||
3007 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
3008 | JX9_PRIVATE int jx9Utf8Read( | ||
3009 | const unsigned char *z, /* First byte of UTF-8 character */ | ||
3010 | const unsigned char *zTerm, /* Pretend this byte is 0x00 */ | ||
3011 | const unsigned char **pzNext /* Write first byte past UTF-8 char here */ | ||
3012 | ); | ||
3013 | /* parse.c function prototypes */ | ||
3014 | JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID); | ||
3015 | JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot); | ||
3016 | JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart, SyToken *pEnd, SyToken **ppNext); | ||
3017 | JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn, SyToken *pEnd, sxu32 nTokStart, sxu32 nTokEnd, SyToken **ppEnd); | ||
3018 | JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast); | ||
3019 | JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet); | ||
3020 | /* compile.c function prototypes */ | ||
3021 | JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType); | ||
3022 | JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3023 | JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3024 | JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3025 | JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3026 | JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3027 | JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3028 | JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3029 | JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen, sxi32 iCompileFlag); | ||
3030 | JX9_PRIVATE sxi32 jx9InitCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); | ||
3031 | JX9_PRIVATE sxi32 jx9ResetCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); | ||
3032 | JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...); | ||
3033 | JX9_PRIVATE sxi32 jx9CompileScript(jx9_vm *pVm, SyString *pScript, sxi32 iFlags); | ||
3034 | /* constant.c function prototypes */ | ||
3035 | JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm); | ||
3036 | /* builtin.c function prototypes */ | ||
3037 | JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm); | ||
3038 | /* hashmap.c function prototypes */ | ||
3039 | JX9_PRIVATE jx9_hashmap * jx9NewHashmap(jx9_vm *pVm, sxu32 (*xIntHash)(sxi64), sxu32 (*xBlobHash)(const void *, sxu32)); | ||
3040 | JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm); | ||
3041 | JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS); | ||
3042 | JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap); | ||
3043 | JX9_PRIVATE sxi32 jx9HashmapLookup(jx9_hashmap *pMap, jx9_value *pKey, jx9_hashmap_node **ppNode); | ||
3044 | JX9_PRIVATE sxi32 jx9HashmapInsert(jx9_hashmap *pMap, jx9_value *pKey, jx9_value *pVal); | ||
3045 | JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight); | ||
3046 | JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest); | ||
3047 | JX9_PRIVATE sxi32 jx9HashmapCmp(jx9_hashmap *pLeft, jx9_hashmap *pRight, int bStrict); | ||
3048 | JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap); | ||
3049 | JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap); | ||
3050 | JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode); | ||
3051 | JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore); | ||
3052 | JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode, jx9_value *pKey); | ||
3053 | JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm); | ||
3054 | JX9_PRIVATE sxi32 jx9HashmapWalk(jx9_hashmap *pMap, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); | ||
3055 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3056 | JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut); | ||
3057 | /* builtin.c function prototypes */ | ||
3058 | JX9_PRIVATE sxi32 jx9InputFormat(int (*xConsumer)(jx9_context *, const char *, int, void *), | ||
3059 | jx9_context *pCtx, const char *zIn, int nByte, int nArg, jx9_value **apArg, void *pUserData, int vf); | ||
3060 | JX9_PRIVATE sxi32 jx9ProcessCsv(const char *zInput, int nByte, int delim, int encl, | ||
3061 | int escape, sxi32 (*xConsumer)(const char *, int, void *), void *pUserData); | ||
3062 | JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData); | ||
3063 | JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen); | ||
3064 | JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection); | ||
3065 | #endif | ||
3066 | /* vfs.c */ | ||
3067 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3068 | JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile, | ||
3069 | int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew); | ||
3070 | JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut); | ||
3071 | JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle); | ||
3072 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
3073 | JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen); | ||
3074 | JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm); | ||
3075 | JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void); | ||
3076 | JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm); | ||
3077 | JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm); | ||
3078 | JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm); | ||
3079 | /* lib.c function prototypes */ | ||
3080 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3081 | JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp); | ||
3082 | JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch); | ||
3083 | JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch); | ||
3084 | JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry); | ||
3085 | JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen); | ||
3086 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
3087 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3088 | JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData); | ||
3089 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
3090 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3091 | #ifndef JX9_DISABLE_HASH_FUNC | ||
3092 | JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen); | ||
3093 | JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len); | ||
3094 | JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx); | ||
3095 | JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx); | ||
3096 | JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]); | ||
3097 | JX9_PRIVATE void SHA1Init(SHA1Context *context); | ||
3098 | JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len); | ||
3099 | JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]); | ||
3100 | JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]); | ||
3101 | #endif | ||
3102 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
3103 | JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen); | ||
3104 | JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void *pUserData); | ||
3105 | JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...); | ||
3106 | JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap); | ||
3107 | JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...); | ||
3108 | JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...); | ||
3109 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3110 | JX9_PRIVATE const char *SyTimeGetMonth(sxi32 iMonth); | ||
3111 | JX9_PRIVATE const char *SyTimeGetDay(sxi32 iDay); | ||
3112 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
3113 | JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8); | ||
3114 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3115 | JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); | ||
3116 | #endif | ||
3117 | JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex); | ||
3118 | JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp); | ||
3119 | JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData); | ||
3120 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3121 | JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); | ||
3122 | JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); | ||
3123 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
3124 | JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen); | ||
3125 | JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
3126 | JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
3127 | JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
3128 | JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
3129 | JX9_PRIVATE sxi32 SyHexToint(sxi32 c); | ||
3130 | JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
3131 | JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); | ||
3132 | JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail); | ||
3133 | JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData); | ||
3134 | JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32(*xStep)(SyHashEntry *, void *), void *pUserData); | ||
3135 | JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData); | ||
3136 | JX9_PRIVATE SyHashEntry *SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen); | ||
3137 | JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash); | ||
3138 | JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp); | ||
3139 | JX9_PRIVATE void *SySetAt(SySet *pSet, sxu32 nIdx); | ||
3140 | JX9_PRIVATE void *SySetPop(SySet *pSet); | ||
3141 | JX9_PRIVATE void *SySetPeek(SySet *pSet); | ||
3142 | JX9_PRIVATE sxi32 SySetRelease(SySet *pSet); | ||
3143 | JX9_PRIVATE sxi32 SySetReset(SySet *pSet); | ||
3144 | JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet); | ||
3145 | JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry); | ||
3146 | JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem); | ||
3147 | JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem); | ||
3148 | JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize); | ||
3149 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3150 | JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft); | ||
3151 | #endif | ||
3152 | JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob); | ||
3153 | JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob); | ||
3154 | JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen); | ||
3155 | JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest); | ||
3156 | JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob); | ||
3157 | JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize); | ||
3158 | JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte); | ||
3159 | JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator); | ||
3160 | JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize); | ||
3161 | JX9_PRIVATE char *SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize); | ||
3162 | JX9_PRIVATE void *SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize); | ||
3163 | JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend); | ||
3164 | JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void *pUserData); | ||
3165 | JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void *pUserData); | ||
3166 | JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent); | ||
3167 | #if 0 | ||
3168 | /* Not used in the current release of the JX9 engine */ | ||
3169 | JX9_PRIVATE void *SyMemBackendPoolRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); | ||
3170 | #endif | ||
3171 | JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void *pChunk); | ||
3172 | JX9_PRIVATE void *SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte); | ||
3173 | JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void *pChunk); | ||
3174 | JX9_PRIVATE void *SyMemBackendRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); | ||
3175 | JX9_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte); | ||
3176 | JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen); | ||
3177 | JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize); | ||
3178 | JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize); | ||
3179 | JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen); | ||
3180 | JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen); | ||
3181 | #if !defined(JX9_DISABLE_BUILTIN_FUNC) || defined(__APPLE__) | ||
3182 | JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen); | ||
3183 | #endif | ||
3184 | JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos); | ||
3185 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
3186 | JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); | ||
3187 | #endif | ||
3188 | JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); | ||
3189 | JX9_PRIVATE sxu32 SyStrlen(const char *zSrc); | ||
3190 | #if defined(JX9_ENABLE_THREADS) | ||
3191 | JX9_PRIVATE const SyMutexMethods *SyMutexExportMethods(void); | ||
3192 | JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods); | ||
3193 | JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend); | ||
3194 | #endif | ||
3195 | JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb); | ||
3196 | JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB); | ||
3197 | JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb); | ||
3198 | JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB); | ||
3199 | JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64); | ||
3200 | JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64); | ||
3201 | JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64); | ||
3202 | JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32); | ||
3203 | JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16); | ||
3204 | JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut); | ||
3205 | JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut); | ||
3206 | #endif /* __JX9INT_H__ */ | ||
3207 | |||
3208 | /* | ||
3209 | * ---------------------------------------------------------- | ||
3210 | * File: unqliteInt.h | ||
3211 | * MD5: 325816ce05f6adbaab2c39a41875dedd | ||
3212 | * ---------------------------------------------------------- | ||
3213 | */ | ||
3214 | /* | ||
3215 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
3216 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
3217 | * Version 1.1.6 | ||
3218 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
3219 | * please contact Symisc Systems via: | ||
3220 | * legal@symisc.net | ||
3221 | * licensing@symisc.net | ||
3222 | * contact@symisc.net | ||
3223 | * or visit: | ||
3224 | * http://unqlite.org/licensing.html | ||
3225 | */ | ||
3226 | /* $SymiscID: unqliteInt.h v1.7 FreeBSD 2012-11-02 11:25 devel <chm@symisc.net> $ */ | ||
3227 | #ifndef __UNQLITEINT_H__ | ||
3228 | #define __UNQLITEINT_H__ | ||
3229 | /* Internal interface definitions for UnQLite. */ | ||
3230 | #ifdef UNQLITE_AMALGAMATION | ||
3231 | /* Marker for routines not intended for external use */ | ||
3232 | #define UNQLITE_PRIVATE static | ||
3233 | #define JX9_AMALGAMATION | ||
3234 | #else | ||
3235 | #define UNQLITE_PRIVATE | ||
3236 | #include "unqlite.h" | ||
3237 | #include "jx9Int.h" | ||
3238 | #endif | ||
3239 | /* forward declaration */ | ||
3240 | typedef struct unqlite_db unqlite_db; | ||
3241 | /* | ||
3242 | ** The following values may be passed as the second argument to | ||
3243 | ** UnqliteOsLock(). The various locks exhibit the following semantics: | ||
3244 | ** | ||
3245 | ** SHARED: Any number of processes may hold a SHARED lock simultaneously. | ||
3246 | ** RESERVED: A single process may hold a RESERVED lock on a file at | ||
3247 | ** any time. Other processes may hold and obtain new SHARED locks. | ||
3248 | ** PENDING: A single process may hold a PENDING lock on a file at | ||
3249 | ** any one time. Existing SHARED locks may persist, but no new | ||
3250 | ** SHARED locks may be obtained by other processes. | ||
3251 | ** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. | ||
3252 | ** | ||
3253 | ** PENDING_LOCK may not be passed directly to UnqliteOsLock(). Instead, a | ||
3254 | ** process that requests an EXCLUSIVE lock may actually obtain a PENDING | ||
3255 | ** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to | ||
3256 | ** UnqliteOsLock(). | ||
3257 | */ | ||
3258 | #define NO_LOCK 0 | ||
3259 | #define SHARED_LOCK 1 | ||
3260 | #define RESERVED_LOCK 2 | ||
3261 | #define PENDING_LOCK 3 | ||
3262 | #define EXCLUSIVE_LOCK 4 | ||
3263 | /* | ||
3264 | * UnQLite Locking Strategy (Same as SQLite3) | ||
3265 | * | ||
3266 | * The following #defines specify the range of bytes used for locking. | ||
3267 | * SHARED_SIZE is the number of bytes available in the pool from which | ||
3268 | * a random byte is selected for a shared lock. The pool of bytes for | ||
3269 | * shared locks begins at SHARED_FIRST. | ||
3270 | * | ||
3271 | * The same locking strategy and byte ranges are used for Unix and Windows. | ||
3272 | * This leaves open the possiblity of having clients on winNT, and | ||
3273 | * unix all talking to the same shared file and all locking correctly. | ||
3274 | * To do so would require that samba (or whatever | ||
3275 | * tool is being used for file sharing) implements locks correctly between | ||
3276 | * windows and unix. I'm guessing that isn't likely to happen, but by | ||
3277 | * using the same locking range we are at least open to the possibility. | ||
3278 | * | ||
3279 | * Locking in windows is mandatory. For this reason, we cannot store | ||
3280 | * actual data in the bytes used for locking. The pager never allocates | ||
3281 | * the pages involved in locking therefore. SHARED_SIZE is selected so | ||
3282 | * that all locks will fit on a single page even at the minimum page size. | ||
3283 | * PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE | ||
3284 | * is set high so that we don't have to allocate an unused page except | ||
3285 | * for very large databases. But one should test the page skipping logic | ||
3286 | * by setting PENDING_BYTE low and running the entire regression suite. | ||
3287 | * | ||
3288 | * Changing the value of PENDING_BYTE results in a subtly incompatible | ||
3289 | * file format. Depending on how it is changed, you might not notice | ||
3290 | * the incompatibility right away, even running a full regression test. | ||
3291 | * The default location of PENDING_BYTE is the first byte past the | ||
3292 | * 1GB boundary. | ||
3293 | */ | ||
3294 | #define PENDING_BYTE (0x40000000) | ||
3295 | #define RESERVED_BYTE (PENDING_BYTE+1) | ||
3296 | #define SHARED_FIRST (PENDING_BYTE+2) | ||
3297 | #define SHARED_SIZE 510 | ||
3298 | /* | ||
3299 | * The default size of a disk sector in bytes. | ||
3300 | */ | ||
3301 | #ifndef UNQLITE_DEFAULT_SECTOR_SIZE | ||
3302 | #define UNQLITE_DEFAULT_SECTOR_SIZE 512 | ||
3303 | #endif | ||
3304 | /* | ||
3305 | * Each open database file is managed by a separate instance | ||
3306 | * of the "Pager" structure. | ||
3307 | */ | ||
3308 | typedef struct Pager Pager; | ||
3309 | /* | ||
3310 | * Each database file to be accessed by the system is an instance | ||
3311 | * of the following structure. | ||
3312 | */ | ||
3313 | struct unqlite_db | ||
3314 | { | ||
3315 | Pager *pPager; /* Pager and Transaction manager */ | ||
3316 | jx9 *pJx9; /* Jx9 Engine handle */ | ||
3317 | unqlite_kv_cursor *pCursor; /* Database cursor for common usage */ | ||
3318 | }; | ||
3319 | /* | ||
3320 | * Each database connection is an instance of the following structure. | ||
3321 | */ | ||
3322 | struct unqlite | ||
3323 | { | ||
3324 | SyMemBackend sMem; /* Memory allocator subsystem */ | ||
3325 | SyBlob sErr; /* Error log */ | ||
3326 | unqlite_db sDB; /* Storage backend */ | ||
3327 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3328 | const SyMutexMethods *pMethods; /* Mutex methods */ | ||
3329 | SyMutex *pMutex; /* Per-handle mutex */ | ||
3330 | #endif | ||
3331 | unqlite_vm *pVms; /* List of active VM */ | ||
3332 | sxi32 iVm; /* Total number of active VM */ | ||
3333 | sxi32 iFlags; /* Control flags (See below) */ | ||
3334 | unqlite *pNext,*pPrev; /* List of active DB handles */ | ||
3335 | sxu32 nMagic; /* Sanity check against misuse */ | ||
3336 | }; | ||
3337 | #define UNQLITE_FL_DISABLE_AUTO_COMMIT 0x001 /* Disable auto-commit on close */ | ||
3338 | /* | ||
3339 | * VM control flags (Mostly related to collection handling). | ||
3340 | */ | ||
3341 | #define UNQLITE_VM_COLLECTION_CREATE 0x001 /* Create a new collection */ | ||
3342 | #define UNQLITE_VM_COLLECTION_OVERWRITE 0x002 /* Overwrite old collection */ | ||
3343 | #define UNQLITE_VM_AUTO_LOAD 0x004 /* Auto load a collection from the vfs */ | ||
3344 | /* Forward declaration */ | ||
3345 | typedef struct unqlite_col_record unqlite_col_record; | ||
3346 | typedef struct unqlite_col unqlite_col; | ||
3347 | /* | ||
3348 | * Each an in-memory collection record is stored in an instance | ||
3349 | * of the following structure. | ||
3350 | */ | ||
3351 | struct unqlite_col_record | ||
3352 | { | ||
3353 | unqlite_col *pCol; /* Collecion this record belong */ | ||
3354 | jx9_int64 nId; /* Unique record ID */ | ||
3355 | jx9_value sValue; /* In-memory value of the record */ | ||
3356 | unqlite_col_record *pNextCol,*pPrevCol; /* Collision chain */ | ||
3357 | unqlite_col_record *pNext,*pPrev; /* Linked list of records */ | ||
3358 | }; | ||
3359 | /* | ||
3360 | * Magic number to identify a valid collection on disk. | ||
3361 | */ | ||
3362 | #define UNQLITE_COLLECTION_MAGIC 0x611E /* sizeof(unsigned short) 2 bytes */ | ||
3363 | /* | ||
3364 | * A loaded collection is identified by an instance of the following structure. | ||
3365 | */ | ||
3366 | struct unqlite_col | ||
3367 | { | ||
3368 | unqlite_vm *pVm; /* VM that own this instance */ | ||
3369 | SyString sName; /* ID of the collection */ | ||
3370 | sxu32 nHash; /* sName hash */ | ||
3371 | jx9_value sSchema; /* Collection schema */ | ||
3372 | sxu32 nSchemaOfft; /* Shema offset in sHeader */ | ||
3373 | SyBlob sWorker; /* General purpose working buffer */ | ||
3374 | SyBlob sHeader; /* Collection binary header */ | ||
3375 | jx9_int64 nLastid; /* Last collection record ID */ | ||
3376 | jx9_int64 nCurid; /* Current record ID */ | ||
3377 | jx9_int64 nTotRec; /* Total number of records in the collection */ | ||
3378 | int iFlags; /* Control flags (see below) */ | ||
3379 | unqlite_col_record **apRecord; /* Hashtable of loaded records */ | ||
3380 | unqlite_col_record *pList; /* Linked list of records */ | ||
3381 | sxu32 nRec; /* Total number of records in apRecord[] */ | ||
3382 | sxu32 nRecSize; /* apRecord[] size */ | ||
3383 | Sytm sCreation; /* Colleation creation time */ | ||
3384 | unqlite_kv_cursor *pCursor; /* Cursor pointing to the raw binary data */ | ||
3385 | unqlite_col *pNext,*pPrev; /* Next and previous collection in the chain */ | ||
3386 | unqlite_col *pNextCol,*pPrevCol; /* Collision chain */ | ||
3387 | }; | ||
3388 | /* | ||
3389 | * Each unQLite Virtual Machine resulting from successful compilation of | ||
3390 | * a Jx9 script is represented by an instance of the following structure. | ||
3391 | */ | ||
3392 | struct unqlite_vm | ||
3393 | { | ||
3394 | unqlite *pDb; /* Database handle that own this instance */ | ||
3395 | SyMemBackend sAlloc; /* Private memory allocator */ | ||
3396 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3397 | SyMutex *pMutex; /* Recursive mutex associated with this VM. */ | ||
3398 | #endif | ||
3399 | unqlite_col **apCol; /* Table of loaded collections */ | ||
3400 | unqlite_col *pCol; /* List of loaded collections */ | ||
3401 | sxu32 iCol; /* Total number of loaded collections */ | ||
3402 | sxu32 iColSize; /* apCol[] size */ | ||
3403 | jx9_vm *pJx9Vm; /* Compiled Jx9 script*/ | ||
3404 | unqlite_vm *pNext,*pPrev; /* Linked list of active unQLite VM */ | ||
3405 | sxu32 nMagic; /* Magic number to avoid misuse */ | ||
3406 | }; | ||
3407 | /* | ||
3408 | * Database signature to identify a valid database image. | ||
3409 | */ | ||
3410 | #define UNQLITE_DB_SIG "unqlite" | ||
3411 | /* | ||
3412 | * Database magic number (4 bytes). | ||
3413 | */ | ||
3414 | #define UNQLITE_DB_MAGIC 0xDB7C2712 | ||
3415 | /* | ||
3416 | * Maximum page size in bytes. | ||
3417 | */ | ||
3418 | #ifdef UNQLITE_MAX_PAGE_SIZE | ||
3419 | # undef UNQLITE_MAX_PAGE_SIZE | ||
3420 | #endif | ||
3421 | #define UNQLITE_MAX_PAGE_SIZE 65536 /* 65K */ | ||
3422 | /* | ||
3423 | * Minimum page size in bytes. | ||
3424 | */ | ||
3425 | #ifdef UNQLITE_MIN_PAGE_SIZE | ||
3426 | # undef UNQLITE_MIN_PAGE_SIZE | ||
3427 | #endif | ||
3428 | #define UNQLITE_MIN_PAGE_SIZE 512 | ||
3429 | /* | ||
3430 | * The default size of a database page. | ||
3431 | */ | ||
3432 | #ifndef UNQLITE_DEFAULT_PAGE_SIZE | ||
3433 | # undef UNQLITE_DEFAULT_PAGE_SIZE | ||
3434 | #endif | ||
3435 | # define UNQLITE_DEFAULT_PAGE_SIZE 4096 /* 4K */ | ||
3436 | /* Forward declaration */ | ||
3437 | typedef struct Bitvec Bitvec; | ||
3438 | /* Private library functions */ | ||
3439 | /* api.c */ | ||
3440 | UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void); | ||
3441 | UNQLITE_PRIVATE int unqliteDataConsumer( | ||
3442 | const void *pOut, /* Data to consume */ | ||
3443 | unsigned int nLen, /* Data length */ | ||
3444 | void *pUserData /* User private data */ | ||
3445 | ); | ||
3446 | UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore( | ||
3447 | const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */ | ||
3448 | sxu32 nByte /* zName length */ | ||
3449 | ); | ||
3450 | UNQLITE_PRIVATE int unqliteGetPageSize(void); | ||
3451 | UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr); | ||
3452 | UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...); | ||
3453 | UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb); | ||
3454 | /* unql_vm.c */ | ||
3455 | UNQLITE_PRIVATE int unqliteCreateCollection(unqlite_vm *pVm,SyString *pName); | ||
3456 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol); | ||
3457 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol); | ||
3458 | UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(unqlite_col *pCol,jx9_int64 nId); | ||
3459 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol); | ||
3460 | UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol); | ||
3461 | UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue); | ||
3462 | UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(unqlite_col *pCol,jx9_int64 nId,jx9_value *pValue); | ||
3463 | UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(unqlite_vm *pVm,SyString *pCol,int iFlag); | ||
3464 | UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue); | ||
3465 | UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag); | ||
3466 | UNQLITE_PRIVATE int unqliteCollectionDropRecord(unqlite_col *pCol,jx9_int64 nId,int wr_header,int log_err); | ||
3467 | UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol); | ||
3468 | /* unql_jx9.c */ | ||
3469 | UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm); | ||
3470 | /* fastjson.c */ | ||
3471 | UNQLITE_PRIVATE sxi32 FastJsonEncode( | ||
3472 | jx9_value *pValue, /* Value to encode */ | ||
3473 | SyBlob *pOut, /* Store encoded value here */ | ||
3474 | int iNest /* Nesting limit */ | ||
3475 | ); | ||
3476 | UNQLITE_PRIVATE sxi32 FastJsonDecode( | ||
3477 | const void *pIn, /* Binary JSON */ | ||
3478 | sxu32 nByte, /* Chunk delimiter */ | ||
3479 | jx9_value *pOut, /* Decoded value */ | ||
3480 | const unsigned char **pzPtr, | ||
3481 | int iNest /* Nesting limit */ | ||
3482 | ); | ||
3483 | /* vfs.c [io_win.c, io_unix.c ] */ | ||
3484 | UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void); | ||
3485 | /* mem_kv.c */ | ||
3486 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void); | ||
3487 | /* lhash_kv.c */ | ||
3488 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void); | ||
3489 | /* os.c */ | ||
3490 | UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset); | ||
3491 | UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset); | ||
3492 | UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size); | ||
3493 | UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags); | ||
3494 | UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize); | ||
3495 | UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType); | ||
3496 | UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType); | ||
3497 | UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut); | ||
3498 | UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id); | ||
3499 | UNQLITE_PRIVATE int unqliteOsOpen( | ||
3500 | unqlite_vfs *pVfs, | ||
3501 | SyMemBackend *pAlloc, | ||
3502 | const char *zPath, | ||
3503 | unqlite_file **ppOut, | ||
3504 | unsigned int flags | ||
3505 | ); | ||
3506 | UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId); | ||
3507 | UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync); | ||
3508 | UNQLITE_PRIVATE int unqliteOsAccess(unqlite_vfs *pVfs,const char *zPath,int flags,int *pResOut); | ||
3509 | /* bitmap.c */ | ||
3510 | UNQLITE_PRIVATE Bitvec *unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize); | ||
3511 | UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i); | ||
3512 | UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i); | ||
3513 | UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p); | ||
3514 | /* pager.c */ | ||
3515 | UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut); | ||
3516 | UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur); | ||
3517 | UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage); | ||
3518 | UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager); | ||
3519 | UNQLITE_PRIVATE int unqlitePagerOpen( | ||
3520 | unqlite_vfs *pVfs, /* The virtual file system to use */ | ||
3521 | unqlite *pDb, /* Database handle */ | ||
3522 | const char *zFilename, /* Name of the database file to open */ | ||
3523 | unsigned int iFlags /* flags controlling this file */ | ||
3524 | ); | ||
3525 | UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods); | ||
3526 | UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb); | ||
3527 | UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager); | ||
3528 | UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager); | ||
3529 | UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine); | ||
3530 | UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen); | ||
3531 | UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager); | ||
3532 | #endif /* __UNQLITEINT_H__ */ | ||
3533 | /* | ||
3534 | * ---------------------------------------------------------- | ||
3535 | * File: api.c | ||
3536 | * MD5: d79e8404e50dacd0ea75635c1ebe553a | ||
3537 | * ---------------------------------------------------------- | ||
3538 | */ | ||
3539 | /* | ||
3540 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
3541 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
3542 | * Version 1.1.6 | ||
3543 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
3544 | * please contact Symisc Systems via: | ||
3545 | * legal@symisc.net | ||
3546 | * licensing@symisc.net | ||
3547 | * contact@symisc.net | ||
3548 | * or visit: | ||
3549 | * http://unqlite.org/licensing.html | ||
3550 | */ | ||
3551 | /* $SymiscID: api.c v2.0 FreeBSD 2012-11-08 23:07 stable <chm@symisc.net> $ */ | ||
3552 | #ifndef UNQLITE_AMALGAMATION | ||
3553 | #include "unqliteInt.h" | ||
3554 | #endif | ||
3555 | /* This file implement the public interfaces presented to host-applications. | ||
3556 | * Routines in other files are for internal use by UnQLite and should not be | ||
3557 | * accessed by users of the library. | ||
3558 | */ | ||
3559 | #define UNQLITE_DB_MISUSE(DB) (DB == 0 || DB->nMagic != UNQLITE_DB_MAGIC) | ||
3560 | #define UNQLITE_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE) | ||
3561 | /* If another thread have released a working instance, the following macros | ||
3562 | * evaluates to true. These macros are only used when the library | ||
3563 | * is built with threading support enabled. | ||
3564 | */ | ||
3565 | #define UNQLITE_THRD_DB_RELEASE(DB) (DB->nMagic != UNQLITE_DB_MAGIC) | ||
3566 | #define UNQLITE_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE) | ||
3567 | /* IMPLEMENTATION: unqlite@embedded@symisc 118-09-4785 */ | ||
3568 | /* | ||
3569 | * All global variables are collected in the structure named "sUnqlMPGlobal". | ||
3570 | * That way it is clear in the code when we are using static variable because | ||
3571 | * its name start with sUnqlMPGlobal. | ||
3572 | */ | ||
3573 | static struct unqlGlobal_Data | ||
3574 | { | ||
3575 | SyMemBackend sAllocator; /* Global low level memory allocator */ | ||
3576 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3577 | const SyMutexMethods *pMutexMethods; /* Mutex methods */ | ||
3578 | SyMutex *pMutex; /* Global mutex */ | ||
3579 | sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded | ||
3580 | * The threading level can be set using the [unqlite_lib_config()] | ||
3581 | * interface with a configuration verb set to | ||
3582 | * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE or | ||
3583 | * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI | ||
3584 | */ | ||
3585 | #endif | ||
3586 | SySet kv_storage; /* Installed KV storage engines */ | ||
3587 | int iPageSize; /* Default Page size */ | ||
3588 | unqlite_vfs *pVfs; /* Underlying virtual file system (Vfs) */ | ||
3589 | sxi32 nDB; /* Total number of active DB handles */ | ||
3590 | unqlite *pDB; /* List of active DB handles */ | ||
3591 | sxu32 nMagic; /* Sanity check against library misuse */ | ||
3592 | }sUnqlMPGlobal = { | ||
3593 | {0, 0, 0, 0, 0, 0, 0, 0, {0}}, | ||
3594 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3595 | 0, | ||
3596 | 0, | ||
3597 | 0, | ||
3598 | #endif | ||
3599 | {0, 0, 0, 0, 0, 0, 0 }, | ||
3600 | UNQLITE_DEFAULT_PAGE_SIZE, | ||
3601 | 0, | ||
3602 | 0, | ||
3603 | 0, | ||
3604 | 0 | ||
3605 | }; | ||
3606 | #define UNQLITE_LIB_MAGIC 0xEA1495BA | ||
3607 | #define UNQLITE_LIB_MISUSE (sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC) | ||
3608 | /* | ||
3609 | * Supported threading level. | ||
3610 | * These options have meaning only when the library is compiled with multi-threading | ||
3611 | * support. That is, the UNQLITE_ENABLE_THREADS compile time directive must be defined | ||
3612 | * when UnQLite is built. | ||
3613 | * UNQLITE_THREAD_LEVEL_SINGLE: | ||
3614 | * In this mode, mutexing is disabled and the library can only be used by a single thread. | ||
3615 | * UNQLITE_THREAD_LEVEL_MULTI | ||
3616 | * In this mode, all mutexes including the recursive mutexes on [unqlite] objects | ||
3617 | * are enabled so that the application is free to share the same database handle | ||
3618 | * between different threads at the same time. | ||
3619 | */ | ||
3620 | #define UNQLITE_THREAD_LEVEL_SINGLE 1 | ||
3621 | #define UNQLITE_THREAD_LEVEL_MULTI 2 | ||
3622 | /* | ||
3623 | * Find a Key Value storage engine from the set of installed engines. | ||
3624 | * Return a pointer to the storage engine methods on success. NULL on failure. | ||
3625 | */ | ||
3626 | UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore( | ||
3627 | const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */ | ||
3628 | sxu32 nByte /* zName length */ | ||
3629 | ) | ||
3630 | { | ||
3631 | unqlite_kv_methods **apStore,*pEntry; | ||
3632 | sxu32 n,nMax; | ||
3633 | /* Point to the set of installed engines */ | ||
3634 | apStore = (unqlite_kv_methods **)SySetBasePtr(&sUnqlMPGlobal.kv_storage); | ||
3635 | nMax = SySetUsed(&sUnqlMPGlobal.kv_storage); | ||
3636 | for( n = 0 ; n < nMax; ++n ){ | ||
3637 | pEntry = apStore[n]; | ||
3638 | if( nByte == SyStrlen(pEntry->zName) && SyStrnicmp(pEntry->zName,zName,nByte) == 0 ){ | ||
3639 | /* Storage engine found */ | ||
3640 | return pEntry; | ||
3641 | } | ||
3642 | } | ||
3643 | /* No such entry, return NULL */ | ||
3644 | return 0; | ||
3645 | } | ||
3646 | /* | ||
3647 | * Configure the UnQLite library. | ||
3648 | * Return UNQLITE_OK on success. Any other return value indicates failure. | ||
3649 | * Refer to [unqlite_lib_config()]. | ||
3650 | */ | ||
3651 | static sxi32 unqliteCoreConfigure(sxi32 nOp, va_list ap) | ||
3652 | { | ||
3653 | int rc = UNQLITE_OK; | ||
3654 | switch(nOp){ | ||
3655 | case UNQLITE_LIB_CONFIG_PAGE_SIZE: { | ||
3656 | /* Default page size: Must be a power of two */ | ||
3657 | int iPage = va_arg(ap,int); | ||
3658 | if( iPage >= UNQLITE_MIN_PAGE_SIZE && iPage <= UNQLITE_MAX_PAGE_SIZE ){ | ||
3659 | if( !(iPage & (iPage - 1)) ){ | ||
3660 | sUnqlMPGlobal.iPageSize = iPage; | ||
3661 | }else{ | ||
3662 | /* Invalid page size */ | ||
3663 | rc = UNQLITE_INVALID; | ||
3664 | } | ||
3665 | }else{ | ||
3666 | /* Invalid page size */ | ||
3667 | rc = UNQLITE_INVALID; | ||
3668 | } | ||
3669 | break; | ||
3670 | } | ||
3671 | case UNQLITE_LIB_CONFIG_STORAGE_ENGINE: { | ||
3672 | /* Install a key value storage engine */ | ||
3673 | unqlite_kv_methods *pMethods = va_arg(ap,unqlite_kv_methods *); | ||
3674 | /* Make sure we are delaing with a valid methods */ | ||
3675 | if( pMethods == 0 || SX_EMPTY_STR(pMethods->zName) || pMethods->xSeek == 0 || pMethods->xData == 0 | ||
3676 | || pMethods->xKey == 0 || pMethods->xDataLength == 0 || pMethods->xKeyLength == 0 | ||
3677 | || pMethods->szKv < (int)sizeof(unqlite_kv_engine) ){ | ||
3678 | rc = UNQLITE_INVALID; | ||
3679 | break; | ||
3680 | } | ||
3681 | /* Install it */ | ||
3682 | rc = SySetPut(&sUnqlMPGlobal.kv_storage,(const void *)&pMethods); | ||
3683 | break; | ||
3684 | } | ||
3685 | case UNQLITE_LIB_CONFIG_VFS:{ | ||
3686 | /* Install a virtual file system */ | ||
3687 | unqlite_vfs *pVfs = va_arg(ap,unqlite_vfs *); | ||
3688 | if( pVfs ){ | ||
3689 | sUnqlMPGlobal.pVfs = pVfs; | ||
3690 | } | ||
3691 | break; | ||
3692 | } | ||
3693 | case UNQLITE_LIB_CONFIG_USER_MALLOC: { | ||
3694 | /* Use an alternative low-level memory allocation routines */ | ||
3695 | const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *); | ||
3696 | /* Save the memory failure callback (if available) */ | ||
3697 | ProcMemError xMemErr = sUnqlMPGlobal.sAllocator.xMemError; | ||
3698 | void *pMemErr = sUnqlMPGlobal.sAllocator.pUserData; | ||
3699 | if( pMethods == 0 ){ | ||
3700 | /* Use the built-in memory allocation subsystem */ | ||
3701 | rc = SyMemBackendInit(&sUnqlMPGlobal.sAllocator, xMemErr, pMemErr); | ||
3702 | }else{ | ||
3703 | rc = SyMemBackendInitFromOthers(&sUnqlMPGlobal.sAllocator, pMethods, xMemErr, pMemErr); | ||
3704 | } | ||
3705 | break; | ||
3706 | } | ||
3707 | case UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK: { | ||
3708 | /* Memory failure callback */ | ||
3709 | ProcMemError xMemErr = va_arg(ap, ProcMemError); | ||
3710 | void *pUserData = va_arg(ap, void *); | ||
3711 | sUnqlMPGlobal.sAllocator.xMemError = xMemErr; | ||
3712 | sUnqlMPGlobal.sAllocator.pUserData = pUserData; | ||
3713 | break; | ||
3714 | } | ||
3715 | case UNQLITE_LIB_CONFIG_USER_MUTEX: { | ||
3716 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3717 | /* Use an alternative low-level mutex subsystem */ | ||
3718 | const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *); | ||
3719 | #if defined (UNTRUST) | ||
3720 | if( pMethods == 0 ){ | ||
3721 | rc = UNQLITE_CORRUPT; | ||
3722 | } | ||
3723 | #endif | ||
3724 | /* Sanity check */ | ||
3725 | if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){ | ||
3726 | /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */ | ||
3727 | rc = UNQLITE_CORRUPT; | ||
3728 | break; | ||
3729 | } | ||
3730 | if( sUnqlMPGlobal.pMutexMethods ){ | ||
3731 | /* Overwrite the previous mutex subsystem */ | ||
3732 | SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); | ||
3733 | if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){ | ||
3734 | sUnqlMPGlobal.pMutexMethods->xGlobalRelease(); | ||
3735 | } | ||
3736 | sUnqlMPGlobal.pMutex = 0; | ||
3737 | } | ||
3738 | /* Initialize and install the new mutex subsystem */ | ||
3739 | if( pMethods->xGlobalInit ){ | ||
3740 | rc = pMethods->xGlobalInit(); | ||
3741 | if ( rc != UNQLITE_OK ){ | ||
3742 | break; | ||
3743 | } | ||
3744 | } | ||
3745 | /* Create the global mutex */ | ||
3746 | sUnqlMPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); | ||
3747 | if( sUnqlMPGlobal.pMutex == 0 ){ | ||
3748 | /* | ||
3749 | * If the supplied mutex subsystem is so sick that we are unable to | ||
3750 | * create a single mutex, there is no much we can do here. | ||
3751 | */ | ||
3752 | if( pMethods->xGlobalRelease ){ | ||
3753 | pMethods->xGlobalRelease(); | ||
3754 | } | ||
3755 | rc = UNQLITE_CORRUPT; | ||
3756 | break; | ||
3757 | } | ||
3758 | sUnqlMPGlobal.pMutexMethods = pMethods; | ||
3759 | if( sUnqlMPGlobal.nThreadingLevel == 0 ){ | ||
3760 | /* Set a default threading level */ | ||
3761 | sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI; | ||
3762 | } | ||
3763 | #endif | ||
3764 | break; | ||
3765 | } | ||
3766 | case UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE: | ||
3767 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3768 | /* Single thread mode (Only one thread is allowed to play with the library) */ | ||
3769 | sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_SINGLE; | ||
3770 | jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE); | ||
3771 | #endif | ||
3772 | break; | ||
3773 | case UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI: | ||
3774 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3775 | /* Multi-threading mode (library is thread safe and database handles and virtual machines | ||
3776 | * may be shared between multiple threads). | ||
3777 | */ | ||
3778 | sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI; | ||
3779 | jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_MULTI); | ||
3780 | #endif | ||
3781 | break; | ||
3782 | default: | ||
3783 | /* Unknown configuration option */ | ||
3784 | rc = UNQLITE_CORRUPT; | ||
3785 | break; | ||
3786 | } | ||
3787 | return rc; | ||
3788 | } | ||
3789 | /* | ||
3790 | * [CAPIREF: unqlite_lib_config()] | ||
3791 | * Please refer to the official documentation for function purpose and expected parameters. | ||
3792 | */ | ||
3793 | int unqlite_lib_config(int nConfigOp,...) | ||
3794 | { | ||
3795 | va_list ap; | ||
3796 | int rc; | ||
3797 | if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){ | ||
3798 | /* Library is already initialized, this operation is forbidden */ | ||
3799 | return UNQLITE_LOCKED; | ||
3800 | } | ||
3801 | va_start(ap,nConfigOp); | ||
3802 | rc = unqliteCoreConfigure(nConfigOp,ap); | ||
3803 | va_end(ap); | ||
3804 | return rc; | ||
3805 | } | ||
3806 | /* | ||
3807 | * Global library initialization | ||
3808 | * Refer to [unqlite_lib_init()] | ||
3809 | * This routine must be called to initialize the memory allocation subsystem, the mutex | ||
3810 | * subsystem prior to doing any serious work with the library. The first thread to call | ||
3811 | * this routine does the initialization process and set the magic number so no body later | ||
3812 | * can re-initialize the library. If subsequent threads call this routine before the first | ||
3813 | * thread have finished the initialization process, then the subsequent threads must block | ||
3814 | * until the initialization process is done. | ||
3815 | */ | ||
3816 | static sxi32 unqliteCoreInitialize(void) | ||
3817 | { | ||
3818 | const unqlite_kv_methods *pMethods; | ||
3819 | const unqlite_vfs *pVfs; /* Built-in vfs */ | ||
3820 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3821 | const SyMutexMethods *pMutexMethods = 0; | ||
3822 | SyMutex *pMaster = 0; | ||
3823 | #endif | ||
3824 | int rc; | ||
3825 | /* | ||
3826 | * If the library is already initialized, then a call to this routine | ||
3827 | * is a no-op. | ||
3828 | */ | ||
3829 | if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){ | ||
3830 | return UNQLITE_OK; /* Already initialized */ | ||
3831 | } | ||
3832 | /* Point to the built-in vfs */ | ||
3833 | pVfs = unqliteExportBuiltinVfs(); | ||
3834 | /* Install it */ | ||
3835 | unqlite_lib_config(UNQLITE_LIB_CONFIG_VFS, pVfs); | ||
3836 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3837 | if( sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_SINGLE ){ | ||
3838 | pMutexMethods = sUnqlMPGlobal.pMutexMethods; | ||
3839 | if( pMutexMethods == 0 ){ | ||
3840 | /* Use the built-in mutex subsystem */ | ||
3841 | pMutexMethods = SyMutexExportMethods(); | ||
3842 | if( pMutexMethods == 0 ){ | ||
3843 | return UNQLITE_CORRUPT; /* Can't happen */ | ||
3844 | } | ||
3845 | /* Install the mutex subsystem */ | ||
3846 | rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MUTEX, pMutexMethods); | ||
3847 | if( rc != UNQLITE_OK ){ | ||
3848 | return rc; | ||
3849 | } | ||
3850 | } | ||
3851 | /* Obtain a static mutex so we can initialize the library without calling malloc() */ | ||
3852 | pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1); | ||
3853 | if( pMaster == 0 ){ | ||
3854 | return UNQLITE_CORRUPT; /* Can't happen */ | ||
3855 | } | ||
3856 | } | ||
3857 | /* Lock the master mutex */ | ||
3858 | rc = UNQLITE_OK; | ||
3859 | SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ | ||
3860 | if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ | ||
3861 | #endif | ||
3862 | if( sUnqlMPGlobal.sAllocator.pMethods == 0 ){ | ||
3863 | /* Install a memory subsystem */ | ||
3864 | rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */ | ||
3865 | if( rc != UNQLITE_OK ){ | ||
3866 | /* If we are unable to initialize the memory backend, there is no much we can do here.*/ | ||
3867 | goto End; | ||
3868 | } | ||
3869 | } | ||
3870 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3871 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ | ||
3872 | /* Protect the memory allocation subsystem */ | ||
3873 | rc = SyMemBackendMakeThreadSafe(&sUnqlMPGlobal.sAllocator, sUnqlMPGlobal.pMutexMethods); | ||
3874 | if( rc != UNQLITE_OK ){ | ||
3875 | goto End; | ||
3876 | } | ||
3877 | } | ||
3878 | #endif | ||
3879 | SySetInit(&sUnqlMPGlobal.kv_storage,&sUnqlMPGlobal.sAllocator,sizeof(unqlite_kv_methods *)); | ||
3880 | /* Install the built-in Key Value storage engines */ | ||
3881 | pMethods = unqliteExportMemKvStorage(); /* In-memory storage */ | ||
3882 | unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods); | ||
3883 | /* Default disk key/value storage engine */ | ||
3884 | pMethods = unqliteExportDiskKvStorage(); /* Disk storage */ | ||
3885 | unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods); | ||
3886 | /* Default page size */ | ||
3887 | if( sUnqlMPGlobal.iPageSize < UNQLITE_MIN_PAGE_SIZE ){ | ||
3888 | unqlite_lib_config(UNQLITE_LIB_CONFIG_PAGE_SIZE,UNQLITE_DEFAULT_PAGE_SIZE); | ||
3889 | } | ||
3890 | /* Our library is initialized, set the magic number */ | ||
3891 | sUnqlMPGlobal.nMagic = UNQLITE_LIB_MAGIC; | ||
3892 | rc = UNQLITE_OK; | ||
3893 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3894 | } /* sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC */ | ||
3895 | #endif | ||
3896 | End: | ||
3897 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3898 | /* Unlock the master mutex */ | ||
3899 | SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ | ||
3900 | #endif | ||
3901 | return rc; | ||
3902 | } | ||
3903 | /* Forward declaration */ | ||
3904 | static int unqliteVmRelease(unqlite_vm *pVm); | ||
3905 | /* | ||
3906 | * Release a single instance of an unqlite database handle. | ||
3907 | */ | ||
3908 | static int unqliteDbRelease(unqlite *pDb) | ||
3909 | { | ||
3910 | unqlite_db *pStore = &pDb->sDB; | ||
3911 | unqlite_vm *pVm,*pNext; | ||
3912 | int rc = UNQLITE_OK; | ||
3913 | if( (pDb->iFlags & UNQLITE_FL_DISABLE_AUTO_COMMIT) == 0 ){ | ||
3914 | /* Commit any outstanding transaction */ | ||
3915 | rc = unqlitePagerCommit(pStore->pPager); | ||
3916 | if( rc != UNQLITE_OK ){ | ||
3917 | /* Rollback the transaction */ | ||
3918 | rc = unqlitePagerRollback(pStore->pPager,FALSE); | ||
3919 | } | ||
3920 | }else{ | ||
3921 | /* Rollback any outstanding transaction */ | ||
3922 | rc = unqlitePagerRollback(pStore->pPager,FALSE); | ||
3923 | } | ||
3924 | /* Close the pager */ | ||
3925 | unqlitePagerClose(pStore->pPager); | ||
3926 | /* Release any active VM's */ | ||
3927 | pVm = pDb->pVms; | ||
3928 | for(;;){ | ||
3929 | if( pDb->iVm < 1 ){ | ||
3930 | break; | ||
3931 | } | ||
3932 | /* Point to the next entry */ | ||
3933 | pNext = pVm->pNext; | ||
3934 | unqliteVmRelease(pVm); | ||
3935 | pVm = pNext; | ||
3936 | pDb->iVm--; | ||
3937 | } | ||
3938 | /* Release the Jx9 handle */ | ||
3939 | jx9_release(pStore->pJx9); | ||
3940 | /* Set a dummy magic number */ | ||
3941 | pDb->nMagic = 0x7250; | ||
3942 | /* Release the whole memory subsystem */ | ||
3943 | SyMemBackendRelease(&pDb->sMem); | ||
3944 | /* Commit or rollback result */ | ||
3945 | return rc; | ||
3946 | } | ||
3947 | /* | ||
3948 | * Release all resources consumed by the library. | ||
3949 | * Note: This call is not thread safe. Refer to [unqlite_lib_shutdown()]. | ||
3950 | */ | ||
3951 | static void unqliteCoreShutdown(void) | ||
3952 | { | ||
3953 | unqlite *pDb, *pNext; | ||
3954 | /* Release all active databases handles */ | ||
3955 | pDb = sUnqlMPGlobal.pDB; | ||
3956 | for(;;){ | ||
3957 | if( sUnqlMPGlobal.nDB < 1 ){ | ||
3958 | break; | ||
3959 | } | ||
3960 | pNext = pDb->pNext; | ||
3961 | unqliteDbRelease(pDb); | ||
3962 | pDb = pNext; | ||
3963 | sUnqlMPGlobal.nDB--; | ||
3964 | } | ||
3965 | /* Release the storage methods container */ | ||
3966 | SySetRelease(&sUnqlMPGlobal.kv_storage); | ||
3967 | #if defined(UNQLITE_ENABLE_THREADS) | ||
3968 | /* Release the mutex subsystem */ | ||
3969 | if( sUnqlMPGlobal.pMutexMethods ){ | ||
3970 | if( sUnqlMPGlobal.pMutex ){ | ||
3971 | SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); | ||
3972 | sUnqlMPGlobal.pMutex = 0; | ||
3973 | } | ||
3974 | if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){ | ||
3975 | sUnqlMPGlobal.pMutexMethods->xGlobalRelease(); | ||
3976 | } | ||
3977 | sUnqlMPGlobal.pMutexMethods = 0; | ||
3978 | } | ||
3979 | sUnqlMPGlobal.nThreadingLevel = 0; | ||
3980 | #endif | ||
3981 | if( sUnqlMPGlobal.sAllocator.pMethods ){ | ||
3982 | /* Release the memory backend */ | ||
3983 | SyMemBackendRelease(&sUnqlMPGlobal.sAllocator); | ||
3984 | } | ||
3985 | sUnqlMPGlobal.nMagic = 0x1764; | ||
3986 | /* Finally, shutdown the Jx9 library */ | ||
3987 | jx9_lib_shutdown(); | ||
3988 | } | ||
3989 | /* | ||
3990 | * [CAPIREF: unqlite_lib_init()] | ||
3991 | * Please refer to the official documentation for function purpose and expected parameters. | ||
3992 | */ | ||
3993 | int unqlite_lib_init(void) | ||
3994 | { | ||
3995 | int rc; | ||
3996 | rc = unqliteCoreInitialize(); | ||
3997 | return rc; | ||
3998 | } | ||
3999 | /* | ||
4000 | * [CAPIREF: unqlite_lib_shutdown()] | ||
4001 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4002 | */ | ||
4003 | int unqlite_lib_shutdown(void) | ||
4004 | { | ||
4005 | if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ | ||
4006 | /* Already shut */ | ||
4007 | return UNQLITE_OK; | ||
4008 | } | ||
4009 | unqliteCoreShutdown(); | ||
4010 | return UNQLITE_OK; | ||
4011 | } | ||
4012 | /* | ||
4013 | * [CAPIREF: unqlite_lib_is_threadsafe()] | ||
4014 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4015 | */ | ||
4016 | int unqlite_lib_is_threadsafe(void) | ||
4017 | { | ||
4018 | if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ | ||
4019 | return 0; | ||
4020 | } | ||
4021 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4022 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ | ||
4023 | /* Muli-threading support is enabled */ | ||
4024 | return 1; | ||
4025 | }else{ | ||
4026 | /* Single-threading */ | ||
4027 | return 0; | ||
4028 | } | ||
4029 | #else | ||
4030 | return 0; | ||
4031 | #endif | ||
4032 | } | ||
4033 | /* | ||
4034 | * | ||
4035 | * [CAPIREF: unqlite_lib_version()] | ||
4036 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4037 | */ | ||
4038 | const char * unqlite_lib_version(void) | ||
4039 | { | ||
4040 | return UNQLITE_VERSION; | ||
4041 | } | ||
4042 | /* | ||
4043 | * | ||
4044 | * [CAPIREF: unqlite_lib_signature()] | ||
4045 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4046 | */ | ||
4047 | const char * unqlite_lib_signature(void) | ||
4048 | { | ||
4049 | return UNQLITE_SIG; | ||
4050 | } | ||
4051 | /* | ||
4052 | * | ||
4053 | * [CAPIREF: unqlite_lib_ident()] | ||
4054 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4055 | */ | ||
4056 | const char * unqlite_lib_ident(void) | ||
4057 | { | ||
4058 | return UNQLITE_IDENT; | ||
4059 | } | ||
4060 | /* | ||
4061 | * | ||
4062 | * [CAPIREF: unqlite_lib_copyright()] | ||
4063 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4064 | */ | ||
4065 | const char * unqlite_lib_copyright(void) | ||
4066 | { | ||
4067 | return UNQLITE_COPYRIGHT; | ||
4068 | } | ||
4069 | /* | ||
4070 | * Remove harmfull and/or stale flags passed to the [unqlite_open()] interface. | ||
4071 | */ | ||
4072 | static unsigned int unqliteSanityzeFlag(unsigned int iFlags) | ||
4073 | { | ||
4074 | iFlags &= ~UNQLITE_OPEN_EXCLUSIVE; /* Reserved flag */ | ||
4075 | if( iFlags & UNQLITE_OPEN_TEMP_DB ){ | ||
4076 | /* Omit journaling for temporary database */ | ||
4077 | iFlags |= UNQLITE_OPEN_OMIT_JOURNALING|UNQLITE_OPEN_CREATE; | ||
4078 | } | ||
4079 | if( (iFlags & (UNQLITE_OPEN_READONLY|UNQLITE_OPEN_READWRITE)) == 0 ){ | ||
4080 | /* Auto-append the R+W flag */ | ||
4081 | iFlags |= UNQLITE_OPEN_READWRITE; | ||
4082 | } | ||
4083 | if( iFlags & UNQLITE_OPEN_CREATE ){ | ||
4084 | iFlags &= ~(UNQLITE_OPEN_MMAP|UNQLITE_OPEN_READONLY); | ||
4085 | /* Auto-append the R+W flag */ | ||
4086 | iFlags |= UNQLITE_OPEN_READWRITE; | ||
4087 | }else{ | ||
4088 | if( iFlags & UNQLITE_OPEN_READONLY ){ | ||
4089 | iFlags &= ~UNQLITE_OPEN_READWRITE; | ||
4090 | }else if( iFlags & UNQLITE_OPEN_READWRITE ){ | ||
4091 | iFlags &= ~UNQLITE_OPEN_MMAP; | ||
4092 | } | ||
4093 | } | ||
4094 | return iFlags; | ||
4095 | } | ||
4096 | /* | ||
4097 | * This routine does the work of initializing a database handle on behalf | ||
4098 | * of [unqlite_open()]. | ||
4099 | */ | ||
4100 | static int unqliteInitDatabase( | ||
4101 | unqlite *pDB, /* Database handle */ | ||
4102 | SyMemBackend *pParent, /* Master memory backend */ | ||
4103 | const char *zFilename, /* Target database */ | ||
4104 | unsigned int iFlags /* Open flags */ | ||
4105 | ) | ||
4106 | { | ||
4107 | unqlite_db *pStorage = &pDB->sDB; | ||
4108 | int rc; | ||
4109 | /* Initialiaze the memory subsystem */ | ||
4110 | SyMemBackendInitFromParent(&pDB->sMem,pParent); | ||
4111 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4112 | /* No need for internal mutexes */ | ||
4113 | SyMemBackendDisbaleMutexing(&pDB->sMem); | ||
4114 | #endif | ||
4115 | SyBlobInit(&pDB->sErr,&pDB->sMem); | ||
4116 | /* Sanityze flags */ | ||
4117 | iFlags = unqliteSanityzeFlag(iFlags); | ||
4118 | /* Init the pager and the transaction manager */ | ||
4119 | rc = unqlitePagerOpen(sUnqlMPGlobal.pVfs,pDB,zFilename,iFlags); | ||
4120 | if( rc != UNQLITE_OK ){ | ||
4121 | return rc; | ||
4122 | } | ||
4123 | /* Allocate a new Jx9 engine handle */ | ||
4124 | rc = jx9_init(&pStorage->pJx9); | ||
4125 | if( rc != JX9_OK ){ | ||
4126 | return rc; | ||
4127 | } | ||
4128 | return UNQLITE_OK; | ||
4129 | } | ||
4130 | /* | ||
4131 | * Allocate and initialize a new UnQLite Virtual Mahcine and attach it | ||
4132 | * to the compiled Jx9 script. | ||
4133 | */ | ||
4134 | static int unqliteInitVm(unqlite *pDb,jx9_vm *pJx9Vm,unqlite_vm **ppOut) | ||
4135 | { | ||
4136 | unqlite_vm *pVm; | ||
4137 | |||
4138 | *ppOut = 0; | ||
4139 | /* Allocate a new VM instance */ | ||
4140 | pVm = (unqlite_vm *)SyMemBackendPoolAlloc(&pDb->sMem,sizeof(unqlite_vm)); | ||
4141 | if( pVm == 0 ){ | ||
4142 | return UNQLITE_NOMEM; | ||
4143 | } | ||
4144 | /* Zero the structure */ | ||
4145 | SyZero(pVm,sizeof(unqlite_vm)); | ||
4146 | /* Initialize */ | ||
4147 | SyMemBackendInitFromParent(&pVm->sAlloc,&pDb->sMem); | ||
4148 | /* Allocate a new collection table */ | ||
4149 | pVm->apCol = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc,32 * sizeof(unqlite_col *)); | ||
4150 | if( pVm->apCol == 0 ){ | ||
4151 | goto fail; | ||
4152 | } | ||
4153 | pVm->iColSize = 32; /* Must be a power of two */ | ||
4154 | /* Zero the table */ | ||
4155 | SyZero((void *)pVm->apCol,pVm->iColSize * sizeof(unqlite_col *)); | ||
4156 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4157 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ | ||
4158 | /* Associate a recursive mutex with this instance */ | ||
4159 | pVm->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); | ||
4160 | if( pVm->pMutex == 0 ){ | ||
4161 | goto fail; | ||
4162 | } | ||
4163 | } | ||
4164 | #endif | ||
4165 | /* Link the VM to the list of active virtual machines */ | ||
4166 | pVm->pJx9Vm = pJx9Vm; | ||
4167 | pVm->pDb = pDb; | ||
4168 | MACRO_LD_PUSH(pDb->pVms,pVm); | ||
4169 | pDb->iVm++; | ||
4170 | /* Register Jx9 functions */ | ||
4171 | unqliteRegisterJx9Functions(pVm); | ||
4172 | /* Set the magic number */ | ||
4173 | pVm->nMagic = JX9_VM_INIT; /* Same magic number as Jx9 */ | ||
4174 | /* All done */ | ||
4175 | *ppOut = pVm; | ||
4176 | return UNQLITE_OK; | ||
4177 | fail: | ||
4178 | SyMemBackendRelease(&pVm->sAlloc); | ||
4179 | SyMemBackendPoolFree(&pDb->sMem,pVm); | ||
4180 | return UNQLITE_NOMEM; | ||
4181 | } | ||
4182 | /* | ||
4183 | * Release an active VM. | ||
4184 | */ | ||
4185 | static int unqliteVmRelease(unqlite_vm *pVm) | ||
4186 | { | ||
4187 | /* Release the Jx9 VM */ | ||
4188 | jx9_vm_release(pVm->pJx9Vm); | ||
4189 | /* Release the private memory backend */ | ||
4190 | SyMemBackendRelease(&pVm->sAlloc); | ||
4191 | /* Upper layer will discard this VM from the list | ||
4192 | * of active VM. | ||
4193 | */ | ||
4194 | return UNQLITE_OK; | ||
4195 | } | ||
4196 | /* | ||
4197 | * Return the default page size. | ||
4198 | */ | ||
4199 | UNQLITE_PRIVATE int unqliteGetPageSize(void) | ||
4200 | { | ||
4201 | int iSize = sUnqlMPGlobal.iPageSize; | ||
4202 | if( iSize < UNQLITE_MIN_PAGE_SIZE || iSize > UNQLITE_MAX_PAGE_SIZE ){ | ||
4203 | iSize = UNQLITE_DEFAULT_PAGE_SIZE; | ||
4204 | } | ||
4205 | return iSize; | ||
4206 | } | ||
4207 | /* | ||
4208 | * Generate an error message. | ||
4209 | */ | ||
4210 | UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr) | ||
4211 | { | ||
4212 | int rc; | ||
4213 | /* Append the error message */ | ||
4214 | rc = SyBlobAppend(&pDb->sErr,(const void *)zErr,SyStrlen(zErr)); | ||
4215 | /* Append a new line */ | ||
4216 | SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char)); | ||
4217 | return rc; | ||
4218 | } | ||
4219 | /* | ||
4220 | * Generate an error message (Printf like). | ||
4221 | */ | ||
4222 | UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...) | ||
4223 | { | ||
4224 | va_list ap; | ||
4225 | int rc; | ||
4226 | va_start(ap,zFmt); | ||
4227 | rc = SyBlobFormatAp(&pDb->sErr,zFmt,ap); | ||
4228 | va_end(ap); | ||
4229 | /* Append a new line */ | ||
4230 | SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char)); | ||
4231 | return rc; | ||
4232 | } | ||
4233 | /* | ||
4234 | * Generate an error message (Out of memory). | ||
4235 | */ | ||
4236 | UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb) | ||
4237 | { | ||
4238 | int rc; | ||
4239 | rc = unqliteGenError(pDb,"unQLite is running out of memory"); | ||
4240 | return rc; | ||
4241 | } | ||
4242 | /* | ||
4243 | * Configure a working UnQLite database handle. | ||
4244 | */ | ||
4245 | static int unqliteConfigure(unqlite *pDb,int nOp,va_list ap) | ||
4246 | { | ||
4247 | int rc = UNQLITE_OK; | ||
4248 | switch(nOp){ | ||
4249 | case UNQLITE_CONFIG_JX9_ERR_LOG: | ||
4250 | /* Jx9 compile-time error log */ | ||
4251 | rc = jx9EngineConfig(pDb->sDB.pJx9,JX9_CONFIG_ERR_LOG,ap); | ||
4252 | break; | ||
4253 | case UNQLITE_CONFIG_MAX_PAGE_CACHE: { | ||
4254 | int max_page = va_arg(ap,int); | ||
4255 | /* Maximum number of page to cache (Simple hint). */ | ||
4256 | rc = unqlitePagerSetCachesize(pDb->sDB.pPager,max_page); | ||
4257 | break; | ||
4258 | } | ||
4259 | case UNQLITE_CONFIG_ERR_LOG: { | ||
4260 | /* Database error log if any */ | ||
4261 | const char **pzPtr = va_arg(ap, const char **); | ||
4262 | int *pLen = va_arg(ap, int *); | ||
4263 | if( pzPtr == 0 ){ | ||
4264 | rc = JX9_CORRUPT; | ||
4265 | break; | ||
4266 | } | ||
4267 | /* NULL terminate the error-log buffer */ | ||
4268 | SyBlobNullAppend(&pDb->sErr); | ||
4269 | /* Point to the error-log buffer */ | ||
4270 | *pzPtr = (const char *)SyBlobData(&pDb->sErr); | ||
4271 | if( pLen ){ | ||
4272 | if( SyBlobLength(&pDb->sErr) > 1 /* NULL '\0' terminator */ ){ | ||
4273 | *pLen = (int)SyBlobLength(&pDb->sErr); | ||
4274 | }else{ | ||
4275 | *pLen = 0; | ||
4276 | } | ||
4277 | } | ||
4278 | break; | ||
4279 | } | ||
4280 | case UNQLITE_CONFIG_DISABLE_AUTO_COMMIT:{ | ||
4281 | /* Disable auto-commit */ | ||
4282 | pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; | ||
4283 | break; | ||
4284 | } | ||
4285 | case UNQLITE_CONFIG_GET_KV_NAME: { | ||
4286 | /* Name of the underlying KV storage engine */ | ||
4287 | const char **pzPtr = va_arg(ap,const char **); | ||
4288 | if( pzPtr ){ | ||
4289 | unqlite_kv_engine *pEngine; | ||
4290 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
4291 | /* Point to the name */ | ||
4292 | *pzPtr = pEngine->pIo->pMethods->zName; | ||
4293 | } | ||
4294 | break; | ||
4295 | } | ||
4296 | default: | ||
4297 | /* Unknown configuration option */ | ||
4298 | rc = UNQLITE_UNKNOWN; | ||
4299 | break; | ||
4300 | } | ||
4301 | return rc; | ||
4302 | } | ||
4303 | /* | ||
4304 | * Export the global (master) memory allocator to submodules. | ||
4305 | */ | ||
4306 | UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void) | ||
4307 | { | ||
4308 | return &sUnqlMPGlobal.sAllocator; | ||
4309 | } | ||
4310 | /* | ||
4311 | * [CAPIREF: unqlite_open()] | ||
4312 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4313 | */ | ||
4314 | int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode) | ||
4315 | { | ||
4316 | unqlite *pHandle; | ||
4317 | int rc; | ||
4318 | #if defined(UNTRUST) | ||
4319 | if( ppDB == 0 ){ | ||
4320 | return UNQLITE_CORRUPT; | ||
4321 | } | ||
4322 | #endif | ||
4323 | *ppDB = 0; | ||
4324 | /* One-time automatic library initialization */ | ||
4325 | rc = unqliteCoreInitialize(); | ||
4326 | if( rc != UNQLITE_OK ){ | ||
4327 | return rc; | ||
4328 | } | ||
4329 | /* Allocate a new database handle */ | ||
4330 | pHandle = (unqlite *)SyMemBackendPoolAlloc(&sUnqlMPGlobal.sAllocator, sizeof(unqlite)); | ||
4331 | if( pHandle == 0 ){ | ||
4332 | return UNQLITE_NOMEM; | ||
4333 | } | ||
4334 | /* Zero the structure */ | ||
4335 | SyZero(pHandle,sizeof(unqlite)); | ||
4336 | if( iMode < 1 ){ | ||
4337 | /* Assume a read-only database */ | ||
4338 | iMode = UNQLITE_OPEN_READONLY|UNQLITE_OPEN_MMAP; | ||
4339 | } | ||
4340 | /* Init the database */ | ||
4341 | rc = unqliteInitDatabase(pHandle,&sUnqlMPGlobal.sAllocator,zFilename,iMode); | ||
4342 | if( rc != UNQLITE_OK ){ | ||
4343 | goto Release; | ||
4344 | } | ||
4345 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4346 | if( !(iMode & UNQLITE_OPEN_NOMUTEX) && (sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE) ){ | ||
4347 | /* Associate a recursive mutex with this instance */ | ||
4348 | pHandle->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); | ||
4349 | if( pHandle->pMutex == 0 ){ | ||
4350 | rc = UNQLITE_NOMEM; | ||
4351 | goto Release; | ||
4352 | } | ||
4353 | } | ||
4354 | #endif | ||
4355 | /* Link to the list of active DB handles */ | ||
4356 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4357 | /* Enter the global mutex */ | ||
4358 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ | ||
4359 | #endif | ||
4360 | MACRO_LD_PUSH(sUnqlMPGlobal.pDB,pHandle); | ||
4361 | sUnqlMPGlobal.nDB++; | ||
4362 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4363 | /* Leave the global mutex */ | ||
4364 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ | ||
4365 | #endif | ||
4366 | /* Set the magic number to identify a valid DB handle */ | ||
4367 | pHandle->nMagic = UNQLITE_DB_MAGIC; | ||
4368 | /* Make the handle available to the caller */ | ||
4369 | *ppDB = pHandle; | ||
4370 | return UNQLITE_OK; | ||
4371 | Release: | ||
4372 | SyMemBackendRelease(&pHandle->sMem); | ||
4373 | SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pHandle); | ||
4374 | return rc; | ||
4375 | } | ||
4376 | /* | ||
4377 | * [CAPIREF: unqlite_config()] | ||
4378 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4379 | */ | ||
4380 | int unqlite_config(unqlite *pDb,int nConfigOp,...) | ||
4381 | { | ||
4382 | va_list ap; | ||
4383 | int rc; | ||
4384 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
4385 | return UNQLITE_CORRUPT; | ||
4386 | } | ||
4387 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4388 | /* Acquire DB mutex */ | ||
4389 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4390 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4391 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
4392 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4393 | } | ||
4394 | #endif | ||
4395 | va_start(ap, nConfigOp); | ||
4396 | rc = unqliteConfigure(&(*pDb),nConfigOp, ap); | ||
4397 | va_end(ap); | ||
4398 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4399 | /* Leave DB mutex */ | ||
4400 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4401 | #endif | ||
4402 | return rc; | ||
4403 | } | ||
4404 | /* | ||
4405 | * [CAPIREF: unqlite_close()] | ||
4406 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4407 | */ | ||
4408 | int unqlite_close(unqlite *pDb) | ||
4409 | { | ||
4410 | int rc; | ||
4411 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
4412 | return UNQLITE_CORRUPT; | ||
4413 | } | ||
4414 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4415 | /* Acquire DB mutex */ | ||
4416 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4417 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4418 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
4419 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4420 | } | ||
4421 | #endif | ||
4422 | /* Release the database handle */ | ||
4423 | rc = unqliteDbRelease(pDb); | ||
4424 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4425 | /* Leave DB mutex */ | ||
4426 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4427 | /* Release DB mutex */ | ||
4428 | SyMutexRelease(sUnqlMPGlobal.pMutexMethods, pDb->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4429 | #endif | ||
4430 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4431 | /* Enter the global mutex */ | ||
4432 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ | ||
4433 | #endif | ||
4434 | /* Unlink from the list of active database handles */ | ||
4435 | MACRO_LD_REMOVE(sUnqlMPGlobal.pDB, pDb); | ||
4436 | sUnqlMPGlobal.nDB--; | ||
4437 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4438 | /* Leave the global mutex */ | ||
4439 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ | ||
4440 | #endif | ||
4441 | /* Release the memory chunk allocated to this handle */ | ||
4442 | SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pDb); | ||
4443 | return rc; | ||
4444 | } | ||
4445 | /* | ||
4446 | * [CAPIREF: unqlite_compile()] | ||
4447 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4448 | */ | ||
4449 | int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut) | ||
4450 | { | ||
4451 | jx9_vm *pVm; | ||
4452 | int rc; | ||
4453 | if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){ | ||
4454 | return UNQLITE_CORRUPT; | ||
4455 | } | ||
4456 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4457 | /* Acquire DB mutex */ | ||
4458 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4459 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4460 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
4461 | return UNQLITE_ABORT; | ||
4462 | } | ||
4463 | #endif | ||
4464 | /* Compile the Jx9 script first */ | ||
4465 | rc = jx9_compile(pDb->sDB.pJx9,zJx9,nByte,&pVm); | ||
4466 | if( rc == JX9_OK ){ | ||
4467 | /* Allocate a new unqlite VM instance */ | ||
4468 | rc = unqliteInitVm(pDb,pVm,ppOut); | ||
4469 | if( rc != UNQLITE_OK ){ | ||
4470 | /* Release the Jx9 VM */ | ||
4471 | jx9_vm_release(pVm); | ||
4472 | } | ||
4473 | } | ||
4474 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4475 | /* Leave DB mutex */ | ||
4476 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4477 | #endif | ||
4478 | return rc; | ||
4479 | } | ||
4480 | /* | ||
4481 | * [CAPIREF: unqlite_compile_file()] | ||
4482 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4483 | */ | ||
4484 | int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut) | ||
4485 | { | ||
4486 | jx9_vm *pVm; | ||
4487 | int rc; | ||
4488 | if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){ | ||
4489 | return UNQLITE_CORRUPT; | ||
4490 | } | ||
4491 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4492 | /* Acquire DB mutex */ | ||
4493 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4494 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4495 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
4496 | return UNQLITE_ABORT; | ||
4497 | } | ||
4498 | #endif | ||
4499 | /* Compile the Jx9 script first */ | ||
4500 | rc = jx9_compile_file(pDb->sDB.pJx9,zPath,&pVm); | ||
4501 | if( rc == JX9_OK ){ | ||
4502 | /* Allocate a new unqlite VM instance */ | ||
4503 | rc = unqliteInitVm(pDb,pVm,ppOut); | ||
4504 | if( rc != UNQLITE_OK ){ | ||
4505 | /* Release the Jx9 VM */ | ||
4506 | jx9_vm_release(pVm); | ||
4507 | } | ||
4508 | } | ||
4509 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4510 | /* Leave DB mutex */ | ||
4511 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4512 | #endif | ||
4513 | return rc; | ||
4514 | } | ||
4515 | /* | ||
4516 | * Configure an unqlite virtual machine (Mostly Jx9 VM) instance. | ||
4517 | */ | ||
4518 | static int unqliteVmConfig(unqlite_vm *pVm,sxi32 iOp,va_list ap) | ||
4519 | { | ||
4520 | int rc; | ||
4521 | rc = jx9VmConfigure(pVm->pJx9Vm,iOp,ap); | ||
4522 | return rc; | ||
4523 | } | ||
4524 | /* | ||
4525 | * [CAPIREF: unqlite_vm_config()] | ||
4526 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4527 | */ | ||
4528 | int unqlite_vm_config(unqlite_vm *pVm,int iOp,...) | ||
4529 | { | ||
4530 | va_list ap; | ||
4531 | int rc; | ||
4532 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4533 | return UNQLITE_CORRUPT; | ||
4534 | } | ||
4535 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4536 | /* Acquire VM mutex */ | ||
4537 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4538 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4539 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4540 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4541 | } | ||
4542 | #endif | ||
4543 | va_start(ap,iOp); | ||
4544 | rc = unqliteVmConfig(pVm,iOp,ap); | ||
4545 | va_end(ap); | ||
4546 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4547 | /* Leave DB mutex */ | ||
4548 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4549 | #endif | ||
4550 | return rc; | ||
4551 | } | ||
4552 | /* | ||
4553 | * [CAPIREF: unqlite_vm_exec()] | ||
4554 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4555 | */ | ||
4556 | int unqlite_vm_exec(unqlite_vm *pVm) | ||
4557 | { | ||
4558 | int rc; | ||
4559 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4560 | return UNQLITE_CORRUPT; | ||
4561 | } | ||
4562 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4563 | /* Acquire VM mutex */ | ||
4564 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4565 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4566 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4567 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4568 | } | ||
4569 | #endif | ||
4570 | /* Execute the Jx9 bytecode program */ | ||
4571 | rc = jx9VmByteCodeExec(pVm->pJx9Vm); | ||
4572 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4573 | /* Leave DB mutex */ | ||
4574 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4575 | #endif | ||
4576 | return rc; | ||
4577 | } | ||
4578 | /* | ||
4579 | * [CAPIREF: unqlite_vm_release()] | ||
4580 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4581 | */ | ||
4582 | int unqlite_vm_release(unqlite_vm *pVm) | ||
4583 | { | ||
4584 | int rc; | ||
4585 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4586 | return UNQLITE_CORRUPT; | ||
4587 | } | ||
4588 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4589 | /* Acquire VM mutex */ | ||
4590 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4591 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4592 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4593 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4594 | } | ||
4595 | #endif | ||
4596 | /* Release the VM */ | ||
4597 | rc = unqliteVmRelease(pVm); | ||
4598 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4599 | /* Leave VM mutex */ | ||
4600 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4601 | /* Release VM mutex */ | ||
4602 | SyMutexRelease(sUnqlMPGlobal.pMutexMethods,pVm->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4603 | #endif | ||
4604 | if( rc == UNQLITE_OK ){ | ||
4605 | unqlite *pDb = pVm->pDb; | ||
4606 | /* Unlink from the list of active VM's */ | ||
4607 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4608 | /* Acquire DB mutex */ | ||
4609 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4610 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4611 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
4612 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4613 | } | ||
4614 | #endif | ||
4615 | MACRO_LD_REMOVE(pDb->pVms, pVm); | ||
4616 | pDb->iVm--; | ||
4617 | /* Release the memory chunk allocated to this instance */ | ||
4618 | SyMemBackendPoolFree(&pDb->sMem,pVm); | ||
4619 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4620 | /* Leave DB mutex */ | ||
4621 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4622 | #endif | ||
4623 | } | ||
4624 | return rc; | ||
4625 | } | ||
4626 | /* | ||
4627 | * [CAPIREF: unqlite_vm_reset()] | ||
4628 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4629 | */ | ||
4630 | int unqlite_vm_reset(unqlite_vm *pVm) | ||
4631 | { | ||
4632 | int rc; | ||
4633 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4634 | return UNQLITE_CORRUPT; | ||
4635 | } | ||
4636 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4637 | /* Acquire VM mutex */ | ||
4638 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4639 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4640 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4641 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4642 | } | ||
4643 | #endif | ||
4644 | /* Reset the Jx9 VM */ | ||
4645 | rc = jx9VmReset(pVm->pJx9Vm); | ||
4646 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4647 | /* Leave DB mutex */ | ||
4648 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4649 | #endif | ||
4650 | return rc; | ||
4651 | } | ||
4652 | /* | ||
4653 | * [CAPIREF: unqlite_vm_dump()] | ||
4654 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4655 | */ | ||
4656 | int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData) | ||
4657 | { | ||
4658 | int rc; | ||
4659 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4660 | return UNQLITE_CORRUPT; | ||
4661 | } | ||
4662 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4663 | /* Acquire VM mutex */ | ||
4664 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4665 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4666 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4667 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4668 | } | ||
4669 | #endif | ||
4670 | /* Dump the Jx9 VM */ | ||
4671 | rc = jx9VmDump(pVm->pJx9Vm,xConsumer,pUserData); | ||
4672 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4673 | /* Leave DB mutex */ | ||
4674 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4675 | #endif | ||
4676 | return rc; | ||
4677 | } | ||
4678 | /* | ||
4679 | * [CAPIREF: unqlite_vm_extract_variable()] | ||
4680 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4681 | */ | ||
4682 | unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname) | ||
4683 | { | ||
4684 | unqlite_value *pValue; | ||
4685 | SyString sVariable; | ||
4686 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4687 | return 0; | ||
4688 | } | ||
4689 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4690 | /* Acquire VM mutex */ | ||
4691 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4692 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4693 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4694 | return 0; /* Another thread have released this instance */ | ||
4695 | } | ||
4696 | #endif | ||
4697 | /* Extract the target variable */ | ||
4698 | SyStringInitFromBuf(&sVariable,zVarname,SyStrlen(zVarname)); | ||
4699 | pValue = jx9VmExtractVariable(pVm->pJx9Vm,&sVariable); | ||
4700 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4701 | /* Leave DB mutex */ | ||
4702 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4703 | #endif | ||
4704 | return pValue; | ||
4705 | } | ||
4706 | /* | ||
4707 | * [CAPIREF: unqlite_create_function()] | ||
4708 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4709 | */ | ||
4710 | int unqlite_create_function(unqlite_vm *pVm, const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData) | ||
4711 | { | ||
4712 | SyString sName; | ||
4713 | int rc; | ||
4714 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4715 | return UNQLITE_CORRUPT; | ||
4716 | } | ||
4717 | SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); | ||
4718 | /* Remove leading and trailing white spaces */ | ||
4719 | SyStringFullTrim(&sName); | ||
4720 | /* Ticket 1433-003: NULL values are not allowed */ | ||
4721 | if( sName.nByte < 1 || xFunc == 0 ){ | ||
4722 | return UNQLITE_INVALID; | ||
4723 | } | ||
4724 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4725 | /* Acquire VM mutex */ | ||
4726 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4727 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4728 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4729 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4730 | } | ||
4731 | #endif | ||
4732 | /* Install the foreign function */ | ||
4733 | rc = jx9VmInstallForeignFunction(pVm->pJx9Vm,&sName,xFunc,pUserData); | ||
4734 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4735 | /* Leave DB mutex */ | ||
4736 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4737 | #endif | ||
4738 | return rc; | ||
4739 | } | ||
4740 | /* | ||
4741 | * [CAPIREF: unqlite_delete_function()] | ||
4742 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4743 | */ | ||
4744 | int unqlite_delete_function(unqlite_vm *pVm, const char *zName) | ||
4745 | { | ||
4746 | int rc; | ||
4747 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4748 | return UNQLITE_CORRUPT; | ||
4749 | } | ||
4750 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4751 | /* Acquire VM mutex */ | ||
4752 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4753 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4754 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4755 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4756 | } | ||
4757 | #endif | ||
4758 | /* Unlink the foreign function */ | ||
4759 | rc = jx9DeleteFunction(pVm->pJx9Vm,zName); | ||
4760 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4761 | /* Leave DB mutex */ | ||
4762 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4763 | #endif | ||
4764 | return rc; | ||
4765 | } | ||
4766 | /* | ||
4767 | * [CAPIREF: unqlite_create_constant()] | ||
4768 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4769 | */ | ||
4770 | int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData) | ||
4771 | { | ||
4772 | SyString sName; | ||
4773 | int rc; | ||
4774 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4775 | return UNQLITE_CORRUPT; | ||
4776 | } | ||
4777 | SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); | ||
4778 | /* Remove leading and trailing white spaces */ | ||
4779 | SyStringFullTrim(&sName); | ||
4780 | if( sName.nByte < 1 ){ | ||
4781 | /* Empty constant name */ | ||
4782 | return UNQLITE_INVALID; | ||
4783 | } | ||
4784 | /* TICKET 1433-003: NULL pointer is harmless operation */ | ||
4785 | if( xExpand == 0 ){ | ||
4786 | return UNQLITE_INVALID; | ||
4787 | } | ||
4788 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4789 | /* Acquire VM mutex */ | ||
4790 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4791 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4792 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4793 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4794 | } | ||
4795 | #endif | ||
4796 | /* Install the foreign constant */ | ||
4797 | rc = jx9VmRegisterConstant(pVm->pJx9Vm,&sName,xExpand,pUserData); | ||
4798 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4799 | /* Leave DB mutex */ | ||
4800 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4801 | #endif | ||
4802 | return rc; | ||
4803 | } | ||
4804 | /* | ||
4805 | * [CAPIREF: unqlite_delete_constant()] | ||
4806 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4807 | */ | ||
4808 | int unqlite_delete_constant(unqlite_vm *pVm, const char *zName) | ||
4809 | { | ||
4810 | int rc; | ||
4811 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
4812 | return UNQLITE_CORRUPT; | ||
4813 | } | ||
4814 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4815 | /* Acquire VM mutex */ | ||
4816 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4817 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
4818 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
4819 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
4820 | } | ||
4821 | #endif | ||
4822 | /* Unlink the foreign constant */ | ||
4823 | rc = Jx9DeleteConstant(pVm->pJx9Vm,zName); | ||
4824 | #if defined(UNQLITE_ENABLE_THREADS) | ||
4825 | /* Leave DB mutex */ | ||
4826 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
4827 | #endif | ||
4828 | return rc; | ||
4829 | } | ||
4830 | /* | ||
4831 | * [CAPIREF: unqlite_value_int()] | ||
4832 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4833 | */ | ||
4834 | int unqlite_value_int(unqlite_value *pVal, int iValue) | ||
4835 | { | ||
4836 | return jx9_value_int(pVal,iValue); | ||
4837 | } | ||
4838 | /* | ||
4839 | * [CAPIREF: unqlite_value_int64()] | ||
4840 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4841 | */ | ||
4842 | int unqlite_value_int64(unqlite_value *pVal,unqlite_int64 iValue) | ||
4843 | { | ||
4844 | return jx9_value_int64(pVal,iValue); | ||
4845 | } | ||
4846 | /* | ||
4847 | * [CAPIREF: unqlite_value_bool()] | ||
4848 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4849 | */ | ||
4850 | int unqlite_value_bool(unqlite_value *pVal, int iBool) | ||
4851 | { | ||
4852 | return jx9_value_bool(pVal,iBool); | ||
4853 | } | ||
4854 | /* | ||
4855 | * [CAPIREF: unqlite_value_null()] | ||
4856 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4857 | */ | ||
4858 | int unqlite_value_null(unqlite_value *pVal) | ||
4859 | { | ||
4860 | return jx9_value_null(pVal); | ||
4861 | } | ||
4862 | /* | ||
4863 | * [CAPIREF: unqlite_value_double()] | ||
4864 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4865 | */ | ||
4866 | int unqlite_value_double(unqlite_value *pVal, double Value) | ||
4867 | { | ||
4868 | return jx9_value_double(pVal,Value); | ||
4869 | } | ||
4870 | /* | ||
4871 | * [CAPIREF: unqlite_value_string()] | ||
4872 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4873 | */ | ||
4874 | int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen) | ||
4875 | { | ||
4876 | return jx9_value_string(pVal,zString,nLen); | ||
4877 | } | ||
4878 | /* | ||
4879 | * [CAPIREF: unqlite_value_string_format()] | ||
4880 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4881 | */ | ||
4882 | int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...) | ||
4883 | { | ||
4884 | va_list ap; | ||
4885 | int rc; | ||
4886 | if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ | ||
4887 | /* Invalidate any prior representation */ | ||
4888 | jx9MemObjRelease(pVal); | ||
4889 | MemObjSetType(pVal, MEMOBJ_STRING); | ||
4890 | } | ||
4891 | va_start(ap, zFormat); | ||
4892 | rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap); | ||
4893 | va_end(ap); | ||
4894 | return UNQLITE_OK; | ||
4895 | } | ||
4896 | /* | ||
4897 | * [CAPIREF: unqlite_value_reset_string_cursor()] | ||
4898 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4899 | */ | ||
4900 | int unqlite_value_reset_string_cursor(unqlite_value *pVal) | ||
4901 | { | ||
4902 | return jx9_value_reset_string_cursor(pVal); | ||
4903 | } | ||
4904 | /* | ||
4905 | * [CAPIREF: unqlite_value_resource()] | ||
4906 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4907 | */ | ||
4908 | int unqlite_value_resource(unqlite_value *pVal,void *pUserData) | ||
4909 | { | ||
4910 | return jx9_value_resource(pVal,pUserData); | ||
4911 | } | ||
4912 | /* | ||
4913 | * [CAPIREF: unqlite_value_release()] | ||
4914 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4915 | */ | ||
4916 | int unqlite_value_release(unqlite_value *pVal) | ||
4917 | { | ||
4918 | return jx9_value_release(pVal); | ||
4919 | } | ||
4920 | /* | ||
4921 | * [CAPIREF: unqlite_value_to_int()] | ||
4922 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4923 | */ | ||
4924 | int unqlite_value_to_int(unqlite_value *pValue) | ||
4925 | { | ||
4926 | return jx9_value_to_int(pValue); | ||
4927 | } | ||
4928 | /* | ||
4929 | * [CAPIREF: unqlite_value_to_bool()] | ||
4930 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4931 | */ | ||
4932 | int unqlite_value_to_bool(unqlite_value *pValue) | ||
4933 | { | ||
4934 | return jx9_value_to_bool(pValue); | ||
4935 | } | ||
4936 | /* | ||
4937 | * [CAPIREF: unqlite_value_to_int64()] | ||
4938 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4939 | */ | ||
4940 | unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue) | ||
4941 | { | ||
4942 | return jx9_value_to_int64(pValue); | ||
4943 | } | ||
4944 | /* | ||
4945 | * [CAPIREF: unqlite_value_to_double()] | ||
4946 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4947 | */ | ||
4948 | double unqlite_value_to_double(unqlite_value *pValue) | ||
4949 | { | ||
4950 | return jx9_value_to_double(pValue); | ||
4951 | } | ||
4952 | /* | ||
4953 | * [CAPIREF: unqlite_value_to_string()] | ||
4954 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4955 | */ | ||
4956 | const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen) | ||
4957 | { | ||
4958 | return jx9_value_to_string(pValue,pLen); | ||
4959 | } | ||
4960 | /* | ||
4961 | * [CAPIREF: unqlite_value_to_resource()] | ||
4962 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4963 | */ | ||
4964 | void * unqlite_value_to_resource(unqlite_value *pValue) | ||
4965 | { | ||
4966 | return jx9_value_to_resource(pValue); | ||
4967 | } | ||
4968 | /* | ||
4969 | * [CAPIREF: unqlite_value_compare()] | ||
4970 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4971 | */ | ||
4972 | int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict) | ||
4973 | { | ||
4974 | return jx9_value_compare(pLeft,pRight,bStrict); | ||
4975 | } | ||
4976 | /* | ||
4977 | * [CAPIREF: unqlite_result_int()] | ||
4978 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4979 | */ | ||
4980 | int unqlite_result_int(unqlite_context *pCtx, int iValue) | ||
4981 | { | ||
4982 | return jx9_result_int(pCtx,iValue); | ||
4983 | } | ||
4984 | /* | ||
4985 | * [CAPIREF: unqlite_result_int64()] | ||
4986 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4987 | */ | ||
4988 | int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue) | ||
4989 | { | ||
4990 | return jx9_result_int64(pCtx,iValue); | ||
4991 | } | ||
4992 | /* | ||
4993 | * [CAPIREF: unqlite_result_bool()] | ||
4994 | * Please refer to the official documentation for function purpose and expected parameters. | ||
4995 | */ | ||
4996 | int unqlite_result_bool(unqlite_context *pCtx, int iBool) | ||
4997 | { | ||
4998 | return jx9_result_bool(pCtx,iBool); | ||
4999 | } | ||
5000 | /* | ||
5001 | * [CAPIREF: unqlite_result_double()] | ||
5002 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5003 | */ | ||
5004 | int unqlite_result_double(unqlite_context *pCtx, double Value) | ||
5005 | { | ||
5006 | return jx9_result_double(pCtx,Value); | ||
5007 | } | ||
5008 | /* | ||
5009 | * [CAPIREF: unqlite_result_null()] | ||
5010 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5011 | */ | ||
5012 | int unqlite_result_null(unqlite_context *pCtx) | ||
5013 | { | ||
5014 | return jx9_result_null(pCtx); | ||
5015 | } | ||
5016 | /* | ||
5017 | * [CAPIREF: unqlite_result_string()] | ||
5018 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5019 | */ | ||
5020 | int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen) | ||
5021 | { | ||
5022 | return jx9_result_string(pCtx,zString,nLen); | ||
5023 | } | ||
5024 | /* | ||
5025 | * [CAPIREF: unqlite_result_string_format()] | ||
5026 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5027 | */ | ||
5028 | int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...) | ||
5029 | { | ||
5030 | jx9_value *p; | ||
5031 | va_list ap; | ||
5032 | int rc; | ||
5033 | p = pCtx->pRet; | ||
5034 | if( (p->iFlags & MEMOBJ_STRING) == 0 ){ | ||
5035 | /* Invalidate any prior representation */ | ||
5036 | jx9MemObjRelease(p); | ||
5037 | MemObjSetType(p, MEMOBJ_STRING); | ||
5038 | } | ||
5039 | /* Format the given string */ | ||
5040 | va_start(ap, zFormat); | ||
5041 | rc = SyBlobFormatAp(&p->sBlob, zFormat, ap); | ||
5042 | va_end(ap); | ||
5043 | return rc; | ||
5044 | } | ||
5045 | /* | ||
5046 | * [CAPIREF: unqlite_result_value()] | ||
5047 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5048 | */ | ||
5049 | int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue) | ||
5050 | { | ||
5051 | return jx9_result_value(pCtx,pValue); | ||
5052 | } | ||
5053 | /* | ||
5054 | * [CAPIREF: unqlite_result_resource()] | ||
5055 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5056 | */ | ||
5057 | int unqlite_result_resource(unqlite_context *pCtx, void *pUserData) | ||
5058 | { | ||
5059 | return jx9_result_resource(pCtx,pUserData); | ||
5060 | } | ||
5061 | /* | ||
5062 | * [CAPIREF: unqlite_value_is_int()] | ||
5063 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5064 | */ | ||
5065 | int unqlite_value_is_int(unqlite_value *pVal) | ||
5066 | { | ||
5067 | return jx9_value_is_int(pVal); | ||
5068 | } | ||
5069 | /* | ||
5070 | * [CAPIREF: unqlite_value_is_float()] | ||
5071 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5072 | */ | ||
5073 | int unqlite_value_is_float(unqlite_value *pVal) | ||
5074 | { | ||
5075 | return jx9_value_is_float(pVal); | ||
5076 | } | ||
5077 | /* | ||
5078 | * [CAPIREF: unqlite_value_is_bool()] | ||
5079 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5080 | */ | ||
5081 | int unqlite_value_is_bool(unqlite_value *pVal) | ||
5082 | { | ||
5083 | return jx9_value_is_bool(pVal); | ||
5084 | } | ||
5085 | /* | ||
5086 | * [CAPIREF: unqlite_value_is_string()] | ||
5087 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5088 | */ | ||
5089 | int unqlite_value_is_string(unqlite_value *pVal) | ||
5090 | { | ||
5091 | return jx9_value_is_string(pVal); | ||
5092 | } | ||
5093 | /* | ||
5094 | * [CAPIREF: unqlite_value_is_null()] | ||
5095 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5096 | */ | ||
5097 | int unqlite_value_is_null(unqlite_value *pVal) | ||
5098 | { | ||
5099 | return jx9_value_is_null(pVal); | ||
5100 | } | ||
5101 | /* | ||
5102 | * [CAPIREF: unqlite_value_is_numeric()] | ||
5103 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5104 | */ | ||
5105 | int unqlite_value_is_numeric(unqlite_value *pVal) | ||
5106 | { | ||
5107 | return jx9_value_is_numeric(pVal); | ||
5108 | } | ||
5109 | /* | ||
5110 | * [CAPIREF: unqlite_value_is_callable()] | ||
5111 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5112 | */ | ||
5113 | int unqlite_value_is_callable(unqlite_value *pVal) | ||
5114 | { | ||
5115 | return jx9_value_is_callable(pVal); | ||
5116 | } | ||
5117 | /* | ||
5118 | * [CAPIREF: unqlite_value_is_scalar()] | ||
5119 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5120 | */ | ||
5121 | int unqlite_value_is_scalar(unqlite_value *pVal) | ||
5122 | { | ||
5123 | return jx9_value_is_scalar(pVal); | ||
5124 | } | ||
5125 | /* | ||
5126 | * [CAPIREF: unqlite_value_is_json_array()] | ||
5127 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5128 | */ | ||
5129 | int unqlite_value_is_json_array(unqlite_value *pVal) | ||
5130 | { | ||
5131 | return jx9_value_is_json_array(pVal); | ||
5132 | } | ||
5133 | /* | ||
5134 | * [CAPIREF: unqlite_value_is_json_object()] | ||
5135 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5136 | */ | ||
5137 | int unqlite_value_is_json_object(unqlite_value *pVal) | ||
5138 | { | ||
5139 | return jx9_value_is_json_object(pVal); | ||
5140 | } | ||
5141 | /* | ||
5142 | * [CAPIREF: unqlite_value_is_resource()] | ||
5143 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5144 | */ | ||
5145 | int unqlite_value_is_resource(unqlite_value *pVal) | ||
5146 | { | ||
5147 | return jx9_value_is_resource(pVal); | ||
5148 | } | ||
5149 | /* | ||
5150 | * [CAPIREF: unqlite_value_is_empty()] | ||
5151 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5152 | */ | ||
5153 | int unqlite_value_is_empty(unqlite_value *pVal) | ||
5154 | { | ||
5155 | return jx9_value_is_empty(pVal); | ||
5156 | } | ||
5157 | /* | ||
5158 | * [CAPIREF: unqlite_array_fetch()] | ||
5159 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5160 | */ | ||
5161 | unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte) | ||
5162 | { | ||
5163 | return jx9_array_fetch(pArray,zKey,nByte); | ||
5164 | } | ||
5165 | /* | ||
5166 | * [CAPIREF: unqlite_array_walk()] | ||
5167 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5168 | */ | ||
5169 | int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData) | ||
5170 | { | ||
5171 | return jx9_array_walk(pArray,xWalk,pUserData); | ||
5172 | } | ||
5173 | /* | ||
5174 | * [CAPIREF: unqlite_array_add_elem()] | ||
5175 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5176 | */ | ||
5177 | int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue) | ||
5178 | { | ||
5179 | return jx9_array_add_elem(pArray,pKey,pValue); | ||
5180 | } | ||
5181 | /* | ||
5182 | * [CAPIREF: unqlite_array_add_strkey_elem()] | ||
5183 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5184 | */ | ||
5185 | int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue) | ||
5186 | { | ||
5187 | return jx9_array_add_strkey_elem(pArray,zKey,pValue); | ||
5188 | } | ||
5189 | /* | ||
5190 | * [CAPIREF: unqlite_array_count()] | ||
5191 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5192 | */ | ||
5193 | int unqlite_array_count(unqlite_value *pArray) | ||
5194 | { | ||
5195 | return (int)jx9_array_count(pArray); | ||
5196 | } | ||
5197 | /* | ||
5198 | * [CAPIREF: unqlite_vm_new_scalar()] | ||
5199 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5200 | */ | ||
5201 | unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm) | ||
5202 | { | ||
5203 | unqlite_value *pValue; | ||
5204 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
5205 | return 0; | ||
5206 | } | ||
5207 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5208 | /* Acquire VM mutex */ | ||
5209 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5210 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5211 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
5212 | return 0; /* Another thread have released this instance */ | ||
5213 | } | ||
5214 | #endif | ||
5215 | pValue = jx9_new_scalar(pVm->pJx9Vm); | ||
5216 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5217 | /* Leave DB mutex */ | ||
5218 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5219 | #endif | ||
5220 | return pValue; | ||
5221 | } | ||
5222 | /* | ||
5223 | * [CAPIREF: unqlite_vm_new_array()] | ||
5224 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5225 | */ | ||
5226 | unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm) | ||
5227 | { | ||
5228 | unqlite_value *pValue; | ||
5229 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
5230 | return 0; | ||
5231 | } | ||
5232 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5233 | /* Acquire VM mutex */ | ||
5234 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5235 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5236 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
5237 | return 0; /* Another thread have released this instance */ | ||
5238 | } | ||
5239 | #endif | ||
5240 | pValue = jx9_new_array(pVm->pJx9Vm); | ||
5241 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5242 | /* Leave DB mutex */ | ||
5243 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5244 | #endif | ||
5245 | return pValue; | ||
5246 | } | ||
5247 | /* | ||
5248 | * [CAPIREF: unqlite_vm_release_value()] | ||
5249 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5250 | */ | ||
5251 | int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue) | ||
5252 | { | ||
5253 | int rc; | ||
5254 | if( UNQLITE_VM_MISUSE(pVm) ){ | ||
5255 | return UNQLITE_CORRUPT; | ||
5256 | } | ||
5257 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5258 | /* Acquire VM mutex */ | ||
5259 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5260 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5261 | UNQLITE_THRD_VM_RELEASE(pVm) ){ | ||
5262 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5263 | } | ||
5264 | #endif | ||
5265 | rc = jx9_release_value(pVm->pJx9Vm,pValue); | ||
5266 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5267 | /* Leave DB mutex */ | ||
5268 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5269 | #endif | ||
5270 | return rc; | ||
5271 | } | ||
5272 | /* | ||
5273 | * [CAPIREF: unqlite_context_output()] | ||
5274 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5275 | */ | ||
5276 | int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen) | ||
5277 | { | ||
5278 | return jx9_context_output(pCtx,zString,nLen); | ||
5279 | } | ||
5280 | /* | ||
5281 | * [CAPIREF: unqlite_context_output_format()] | ||
5282 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5283 | */ | ||
5284 | int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...) | ||
5285 | { | ||
5286 | va_list ap; | ||
5287 | int rc; | ||
5288 | va_start(ap, zFormat); | ||
5289 | rc = jx9VmOutputConsumeAp(pCtx->pVm,zFormat, ap); | ||
5290 | va_end(ap); | ||
5291 | return rc; | ||
5292 | } | ||
5293 | /* | ||
5294 | * [CAPIREF: unqlite_context_throw_error()] | ||
5295 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5296 | */ | ||
5297 | int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr) | ||
5298 | { | ||
5299 | return jx9_context_throw_error(pCtx,iErr,zErr); | ||
5300 | } | ||
5301 | /* | ||
5302 | * [CAPIREF: unqlite_context_throw_error_format()] | ||
5303 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5304 | */ | ||
5305 | int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...) | ||
5306 | { | ||
5307 | va_list ap; | ||
5308 | int rc; | ||
5309 | if( zFormat == 0){ | ||
5310 | return JX9_OK; | ||
5311 | } | ||
5312 | va_start(ap, zFormat); | ||
5313 | rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap); | ||
5314 | va_end(ap); | ||
5315 | return rc; | ||
5316 | } | ||
5317 | /* | ||
5318 | * [CAPIREF: unqlite_context_random_num()] | ||
5319 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5320 | */ | ||
5321 | unsigned int unqlite_context_random_num(unqlite_context *pCtx) | ||
5322 | { | ||
5323 | return jx9_context_random_num(pCtx); | ||
5324 | } | ||
5325 | /* | ||
5326 | * [CAPIREF: unqlite_context_random_string()] | ||
5327 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5328 | */ | ||
5329 | int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen) | ||
5330 | { | ||
5331 | return jx9_context_random_string(pCtx,zBuf,nBuflen); | ||
5332 | } | ||
5333 | /* | ||
5334 | * [CAPIREF: unqlite_context_user_data()] | ||
5335 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5336 | */ | ||
5337 | void * unqlite_context_user_data(unqlite_context *pCtx) | ||
5338 | { | ||
5339 | return jx9_context_user_data(pCtx); | ||
5340 | } | ||
5341 | /* | ||
5342 | * [CAPIREF: unqlite_context_push_aux_data()] | ||
5343 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5344 | */ | ||
5345 | int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData) | ||
5346 | { | ||
5347 | return jx9_context_push_aux_data(pCtx,pUserData); | ||
5348 | } | ||
5349 | /* | ||
5350 | * [CAPIREF: unqlite_context_peek_aux_data()] | ||
5351 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5352 | */ | ||
5353 | void * unqlite_context_peek_aux_data(unqlite_context *pCtx) | ||
5354 | { | ||
5355 | return jx9_context_peek_aux_data(pCtx); | ||
5356 | } | ||
5357 | /* | ||
5358 | * [CAPIREF: unqlite_context_pop_aux_data()] | ||
5359 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5360 | */ | ||
5361 | void * unqlite_context_pop_aux_data(unqlite_context *pCtx) | ||
5362 | { | ||
5363 | return jx9_context_pop_aux_data(pCtx); | ||
5364 | } | ||
5365 | /* | ||
5366 | * [CAPIREF: unqlite_context_result_buf_length()] | ||
5367 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5368 | */ | ||
5369 | unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx) | ||
5370 | { | ||
5371 | return jx9_context_result_buf_length(pCtx); | ||
5372 | } | ||
5373 | /* | ||
5374 | * [CAPIREF: unqlite_function_name()] | ||
5375 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5376 | */ | ||
5377 | const char * unqlite_function_name(unqlite_context *pCtx) | ||
5378 | { | ||
5379 | return jx9_function_name(pCtx); | ||
5380 | } | ||
5381 | /* | ||
5382 | * [CAPIREF: unqlite_context_new_scalar()] | ||
5383 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5384 | */ | ||
5385 | unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx) | ||
5386 | { | ||
5387 | return jx9_context_new_scalar(pCtx); | ||
5388 | } | ||
5389 | /* | ||
5390 | * [CAPIREF: unqlite_context_new_array()] | ||
5391 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5392 | */ | ||
5393 | unqlite_value * unqlite_context_new_array(unqlite_context *pCtx) | ||
5394 | { | ||
5395 | return jx9_context_new_array(pCtx); | ||
5396 | } | ||
5397 | /* | ||
5398 | * [CAPIREF: unqlite_context_release_value()] | ||
5399 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5400 | */ | ||
5401 | void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue) | ||
5402 | { | ||
5403 | jx9_context_release_value(pCtx,pValue); | ||
5404 | } | ||
5405 | /* | ||
5406 | * [CAPIREF: unqlite_context_alloc_chunk()] | ||
5407 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5408 | */ | ||
5409 | void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease) | ||
5410 | { | ||
5411 | return jx9_context_alloc_chunk(pCtx,nByte,ZeroChunk,AutoRelease); | ||
5412 | } | ||
5413 | /* | ||
5414 | * [CAPIREF: unqlite_context_realloc_chunk()] | ||
5415 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5416 | */ | ||
5417 | void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte) | ||
5418 | { | ||
5419 | return jx9_context_realloc_chunk(pCtx,pChunk,nByte); | ||
5420 | } | ||
5421 | /* | ||
5422 | * [CAPIREF: unqlite_context_free_chunk()] | ||
5423 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5424 | */ | ||
5425 | void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk) | ||
5426 | { | ||
5427 | jx9_context_free_chunk(pCtx,pChunk); | ||
5428 | } | ||
5429 | /* | ||
5430 | * [CAPIREF: unqlite_kv_store()] | ||
5431 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5432 | */ | ||
5433 | int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen) | ||
5434 | { | ||
5435 | unqlite_kv_engine *pEngine; | ||
5436 | int rc; | ||
5437 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5438 | return UNQLITE_CORRUPT; | ||
5439 | } | ||
5440 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5441 | /* Acquire DB mutex */ | ||
5442 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5443 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5444 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5445 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5446 | } | ||
5447 | #endif | ||
5448 | /* Point to the underlying storage engine */ | ||
5449 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5450 | if( pEngine->pIo->pMethods->xReplace == 0 ){ | ||
5451 | /* Storage engine does not implement such method */ | ||
5452 | unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine"); | ||
5453 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5454 | }else{ | ||
5455 | if( nKeyLen < 0 ){ | ||
5456 | /* Assume a null terminated string and compute it's length */ | ||
5457 | nKeyLen = SyStrlen((const char *)pKey); | ||
5458 | } | ||
5459 | if( !nKeyLen ){ | ||
5460 | unqliteGenError(pDb,"Empty key"); | ||
5461 | rc = UNQLITE_EMPTY; | ||
5462 | }else{ | ||
5463 | /* Perform the requested operation */ | ||
5464 | rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,pData,nDataLen); | ||
5465 | } | ||
5466 | } | ||
5467 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5468 | /* Leave DB mutex */ | ||
5469 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5470 | #endif | ||
5471 | return rc; | ||
5472 | } | ||
5473 | /* | ||
5474 | * [CAPIREF: unqlite_kv_store_fmt()] | ||
5475 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5476 | */ | ||
5477 | int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...) | ||
5478 | { | ||
5479 | unqlite_kv_engine *pEngine; | ||
5480 | int rc; | ||
5481 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5482 | return UNQLITE_CORRUPT; | ||
5483 | } | ||
5484 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5485 | /* Acquire DB mutex */ | ||
5486 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5487 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5488 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5489 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5490 | } | ||
5491 | #endif | ||
5492 | /* Point to the underlying storage engine */ | ||
5493 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5494 | if( pEngine->pIo->pMethods->xReplace == 0 ){ | ||
5495 | /* Storage engine does not implement such method */ | ||
5496 | unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine"); | ||
5497 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5498 | }else{ | ||
5499 | if( nKeyLen < 0 ){ | ||
5500 | /* Assume a null terminated string and compute it's length */ | ||
5501 | nKeyLen = SyStrlen((const char *)pKey); | ||
5502 | } | ||
5503 | if( !nKeyLen ){ | ||
5504 | unqliteGenError(pDb,"Empty key"); | ||
5505 | rc = UNQLITE_EMPTY; | ||
5506 | }else{ | ||
5507 | SyBlob sWorker; /* Working buffer */ | ||
5508 | va_list ap; | ||
5509 | SyBlobInit(&sWorker,&pDb->sMem); | ||
5510 | /* Format the data */ | ||
5511 | va_start(ap,zFormat); | ||
5512 | SyBlobFormatAp(&sWorker,zFormat,ap); | ||
5513 | va_end(ap); | ||
5514 | /* Perform the requested operation */ | ||
5515 | rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker)); | ||
5516 | /* Clean up */ | ||
5517 | SyBlobRelease(&sWorker); | ||
5518 | } | ||
5519 | } | ||
5520 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5521 | /* Leave DB mutex */ | ||
5522 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5523 | #endif | ||
5524 | return rc; | ||
5525 | } | ||
5526 | /* | ||
5527 | * [CAPIREF: unqlite_kv_append()] | ||
5528 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5529 | */ | ||
5530 | int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen) | ||
5531 | { | ||
5532 | unqlite_kv_engine *pEngine; | ||
5533 | int rc; | ||
5534 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5535 | return UNQLITE_CORRUPT; | ||
5536 | } | ||
5537 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5538 | /* Acquire DB mutex */ | ||
5539 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5540 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5541 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5542 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5543 | } | ||
5544 | #endif | ||
5545 | /* Point to the underlying storage engine */ | ||
5546 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5547 | if( pEngine->pIo->pMethods->xAppend == 0 ){ | ||
5548 | /* Storage engine does not implement such method */ | ||
5549 | unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine"); | ||
5550 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5551 | }else{ | ||
5552 | if( nKeyLen < 0 ){ | ||
5553 | /* Assume a null terminated string and compute it's length */ | ||
5554 | nKeyLen = SyStrlen((const char *)pKey); | ||
5555 | } | ||
5556 | if( !nKeyLen ){ | ||
5557 | unqliteGenError(pDb,"Empty key"); | ||
5558 | rc = UNQLITE_EMPTY; | ||
5559 | }else{ | ||
5560 | /* Perform the requested operation */ | ||
5561 | rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,pData,nDataLen); | ||
5562 | } | ||
5563 | } | ||
5564 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5565 | /* Leave DB mutex */ | ||
5566 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5567 | #endif | ||
5568 | return rc; | ||
5569 | } | ||
5570 | /* | ||
5571 | * [CAPIREF: unqlite_kv_append_fmt()] | ||
5572 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5573 | */ | ||
5574 | int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...) | ||
5575 | { | ||
5576 | unqlite_kv_engine *pEngine; | ||
5577 | int rc; | ||
5578 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5579 | return UNQLITE_CORRUPT; | ||
5580 | } | ||
5581 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5582 | /* Acquire DB mutex */ | ||
5583 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5584 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5585 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5586 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5587 | } | ||
5588 | #endif | ||
5589 | /* Point to the underlying storage engine */ | ||
5590 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5591 | if( pEngine->pIo->pMethods->xAppend == 0 ){ | ||
5592 | /* Storage engine does not implement such method */ | ||
5593 | unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine"); | ||
5594 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5595 | }else{ | ||
5596 | if( nKeyLen < 0 ){ | ||
5597 | /* Assume a null terminated string and compute it's length */ | ||
5598 | nKeyLen = SyStrlen((const char *)pKey); | ||
5599 | } | ||
5600 | if( !nKeyLen ){ | ||
5601 | unqliteGenError(pDb,"Empty key"); | ||
5602 | rc = UNQLITE_EMPTY; | ||
5603 | }else{ | ||
5604 | SyBlob sWorker; /* Working buffer */ | ||
5605 | va_list ap; | ||
5606 | SyBlobInit(&sWorker,&pDb->sMem); | ||
5607 | /* Format the data */ | ||
5608 | va_start(ap,zFormat); | ||
5609 | SyBlobFormatAp(&sWorker,zFormat,ap); | ||
5610 | va_end(ap); | ||
5611 | /* Perform the requested operation */ | ||
5612 | rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker)); | ||
5613 | /* Clean up */ | ||
5614 | SyBlobRelease(&sWorker); | ||
5615 | } | ||
5616 | } | ||
5617 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5618 | /* Leave DB mutex */ | ||
5619 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5620 | #endif | ||
5621 | return rc; | ||
5622 | } | ||
5623 | /* | ||
5624 | * [CAPIREF: unqlite_kv_fetch()] | ||
5625 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5626 | */ | ||
5627 | int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 *pBufLen) | ||
5628 | { | ||
5629 | unqlite_kv_methods *pMethods; | ||
5630 | unqlite_kv_engine *pEngine; | ||
5631 | unqlite_kv_cursor *pCur; | ||
5632 | int rc; | ||
5633 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5634 | return UNQLITE_CORRUPT; | ||
5635 | } | ||
5636 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5637 | /* Acquire DB mutex */ | ||
5638 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5639 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5640 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5641 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5642 | } | ||
5643 | #endif | ||
5644 | /* Point to the underlying storage engine */ | ||
5645 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5646 | pMethods = pEngine->pIo->pMethods; | ||
5647 | pCur = pDb->sDB.pCursor; | ||
5648 | if( nKeyLen < 0 ){ | ||
5649 | /* Assume a null terminated string and compute it's length */ | ||
5650 | nKeyLen = SyStrlen((const char *)pKey); | ||
5651 | } | ||
5652 | if( !nKeyLen ){ | ||
5653 | unqliteGenError(pDb,"Empty key"); | ||
5654 | rc = UNQLITE_EMPTY; | ||
5655 | }else{ | ||
5656 | /* Seek to the record position */ | ||
5657 | rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); | ||
5658 | } | ||
5659 | if( rc == UNQLITE_OK ){ | ||
5660 | if( pBuf == 0 ){ | ||
5661 | /* Data length only */ | ||
5662 | rc = pMethods->xDataLength(pCur,pBufLen); | ||
5663 | }else{ | ||
5664 | SyBlob sBlob; | ||
5665 | /* Initialize the data consumer */ | ||
5666 | SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)*pBufLen); | ||
5667 | /* Consume the data */ | ||
5668 | rc = pMethods->xData(pCur,unqliteDataConsumer,&sBlob); | ||
5669 | /* Data length */ | ||
5670 | *pBufLen = (unqlite_int64)SyBlobLength(&sBlob); | ||
5671 | /* Cleanup */ | ||
5672 | SyBlobRelease(&sBlob); | ||
5673 | } | ||
5674 | } | ||
5675 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5676 | /* Leave DB mutex */ | ||
5677 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5678 | #endif | ||
5679 | return rc; | ||
5680 | } | ||
5681 | /* | ||
5682 | * [CAPIREF: unqlite_kv_fetch_callback()] | ||
5683 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5684 | */ | ||
5685 | int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey,int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
5686 | { | ||
5687 | unqlite_kv_methods *pMethods; | ||
5688 | unqlite_kv_engine *pEngine; | ||
5689 | unqlite_kv_cursor *pCur; | ||
5690 | int rc; | ||
5691 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5692 | return UNQLITE_CORRUPT; | ||
5693 | } | ||
5694 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5695 | /* Acquire DB mutex */ | ||
5696 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5697 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5698 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5699 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5700 | } | ||
5701 | #endif | ||
5702 | /* Point to the underlying storage engine */ | ||
5703 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5704 | pMethods = pEngine->pIo->pMethods; | ||
5705 | pCur = pDb->sDB.pCursor; | ||
5706 | if( nKeyLen < 0 ){ | ||
5707 | /* Assume a null terminated string and compute it's length */ | ||
5708 | nKeyLen = SyStrlen((const char *)pKey); | ||
5709 | } | ||
5710 | if( !nKeyLen ){ | ||
5711 | unqliteGenError(pDb,"Empty key"); | ||
5712 | rc = UNQLITE_EMPTY; | ||
5713 | }else{ | ||
5714 | /* Seek to the record position */ | ||
5715 | rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); | ||
5716 | } | ||
5717 | if( rc == UNQLITE_OK && xConsumer ){ | ||
5718 | /* Consume the data directly */ | ||
5719 | rc = pMethods->xData(pCur,xConsumer,pUserData); | ||
5720 | } | ||
5721 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5722 | /* Leave DB mutex */ | ||
5723 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5724 | #endif | ||
5725 | return rc; | ||
5726 | } | ||
5727 | /* | ||
5728 | * [CAPIREF: unqlite_kv_delete()] | ||
5729 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5730 | */ | ||
5731 | int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen) | ||
5732 | { | ||
5733 | unqlite_kv_methods *pMethods; | ||
5734 | unqlite_kv_engine *pEngine; | ||
5735 | unqlite_kv_cursor *pCur; | ||
5736 | int rc; | ||
5737 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5738 | return UNQLITE_CORRUPT; | ||
5739 | } | ||
5740 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5741 | /* Acquire DB mutex */ | ||
5742 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5743 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5744 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5745 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5746 | } | ||
5747 | #endif | ||
5748 | /* Point to the underlying storage engine */ | ||
5749 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5750 | pMethods = pEngine->pIo->pMethods; | ||
5751 | pCur = pDb->sDB.pCursor; | ||
5752 | if( pMethods->xDelete == 0 ){ | ||
5753 | /* Storage engine does not implement such method */ | ||
5754 | unqliteGenError(pDb,"xDelete() method not implemented in the underlying storage engine"); | ||
5755 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5756 | }else{ | ||
5757 | if( nKeyLen < 0 ){ | ||
5758 | /* Assume a null terminated string and compute it's length */ | ||
5759 | nKeyLen = SyStrlen((const char *)pKey); | ||
5760 | } | ||
5761 | if( !nKeyLen ){ | ||
5762 | unqliteGenError(pDb,"Empty key"); | ||
5763 | rc = UNQLITE_EMPTY; | ||
5764 | }else{ | ||
5765 | /* Seek to the record position */ | ||
5766 | rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); | ||
5767 | } | ||
5768 | if( rc == UNQLITE_OK ){ | ||
5769 | /* Exact match found, delete the entry */ | ||
5770 | rc = pMethods->xDelete(pCur); | ||
5771 | } | ||
5772 | } | ||
5773 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5774 | /* Leave DB mutex */ | ||
5775 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5776 | #endif | ||
5777 | return rc; | ||
5778 | } | ||
5779 | /* | ||
5780 | * [CAPIREF: unqlite_kv_config()] | ||
5781 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5782 | */ | ||
5783 | int unqlite_kv_config(unqlite *pDb,int iOp,...) | ||
5784 | { | ||
5785 | unqlite_kv_engine *pEngine; | ||
5786 | int rc; | ||
5787 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
5788 | return UNQLITE_CORRUPT; | ||
5789 | } | ||
5790 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5791 | /* Acquire DB mutex */ | ||
5792 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5793 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5794 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5795 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5796 | } | ||
5797 | #endif | ||
5798 | /* Point to the underlying storage engine */ | ||
5799 | pEngine = unqlitePagerGetKvEngine(pDb); | ||
5800 | if( pEngine->pIo->pMethods->xConfig == 0 ){ | ||
5801 | /* Storage engine does not implements such method */ | ||
5802 | unqliteGenError(pDb,"xConfig() method not implemented in the underlying storage engine"); | ||
5803 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5804 | }else{ | ||
5805 | va_list ap; | ||
5806 | /* Configure the storage engine */ | ||
5807 | va_start(ap,iOp); | ||
5808 | rc = pEngine->pIo->pMethods->xConfig(pEngine,iOp,ap); | ||
5809 | va_end(ap); | ||
5810 | } | ||
5811 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5812 | /* Leave DB mutex */ | ||
5813 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5814 | #endif | ||
5815 | return rc; | ||
5816 | } | ||
5817 | /* | ||
5818 | * [CAPIREF: unqlite_kv_cursor_init()] | ||
5819 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5820 | */ | ||
5821 | int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut) | ||
5822 | { | ||
5823 | int rc; | ||
5824 | if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0 /* Noop */){ | ||
5825 | return UNQLITE_CORRUPT; | ||
5826 | } | ||
5827 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5828 | /* Acquire DB mutex */ | ||
5829 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5830 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5831 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5832 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5833 | } | ||
5834 | #endif | ||
5835 | /* Allocate a new cursor */ | ||
5836 | rc = unqliteInitCursor(pDb,ppOut); | ||
5837 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5838 | /* Leave DB mutex */ | ||
5839 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5840 | #endif | ||
5841 | return rc; | ||
5842 | } | ||
5843 | /* | ||
5844 | * [CAPIREF: unqlite_kv_cursor_release()] | ||
5845 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5846 | */ | ||
5847 | int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur) | ||
5848 | { | ||
5849 | int rc; | ||
5850 | if( UNQLITE_DB_MISUSE(pDb) || pCur == 0 /* Noop */){ | ||
5851 | return UNQLITE_CORRUPT; | ||
5852 | } | ||
5853 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5854 | /* Acquire DB mutex */ | ||
5855 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5856 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
5857 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
5858 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
5859 | } | ||
5860 | #endif | ||
5861 | /* Release the cursor */ | ||
5862 | rc = unqliteReleaseCursor(pDb,pCur); | ||
5863 | #if defined(UNQLITE_ENABLE_THREADS) | ||
5864 | /* Leave DB mutex */ | ||
5865 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
5866 | #endif | ||
5867 | return rc; | ||
5868 | } | ||
5869 | /* | ||
5870 | * [CAPIREF: unqlite_kv_cursor_first_entry()] | ||
5871 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5872 | */ | ||
5873 | int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor) | ||
5874 | { | ||
5875 | int rc; | ||
5876 | #ifdef UNTRUST | ||
5877 | if( pCursor == 0 ){ | ||
5878 | return UNQLITE_CORRUPT; | ||
5879 | } | ||
5880 | #endif | ||
5881 | /* Check if the requested method is implemented by the underlying storage engine */ | ||
5882 | if( pCursor->pStore->pIo->pMethods->xFirst == 0 ){ | ||
5883 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5884 | }else{ | ||
5885 | /* Seek to the first entry */ | ||
5886 | rc = pCursor->pStore->pIo->pMethods->xFirst(pCursor); | ||
5887 | } | ||
5888 | return rc; | ||
5889 | } | ||
5890 | /* | ||
5891 | * [CAPIREF: unqlite_kv_cursor_last_entry()] | ||
5892 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5893 | */ | ||
5894 | int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor) | ||
5895 | { | ||
5896 | int rc; | ||
5897 | #ifdef UNTRUST | ||
5898 | if( pCursor == 0 ){ | ||
5899 | return UNQLITE_CORRUPT; | ||
5900 | } | ||
5901 | #endif | ||
5902 | /* Check if the requested method is implemented by the underlying storage engine */ | ||
5903 | if( pCursor->pStore->pIo->pMethods->xLast == 0 ){ | ||
5904 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5905 | }else{ | ||
5906 | /* Seek to the last entry */ | ||
5907 | rc = pCursor->pStore->pIo->pMethods->xLast(pCursor); | ||
5908 | } | ||
5909 | return rc; | ||
5910 | } | ||
5911 | /* | ||
5912 | * [CAPIREF: unqlite_kv_cursor_valid_entry()] | ||
5913 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5914 | */ | ||
5915 | int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor) | ||
5916 | { | ||
5917 | int rc; | ||
5918 | #ifdef UNTRUST | ||
5919 | if( pCursor == 0 ){ | ||
5920 | return UNQLITE_CORRUPT; | ||
5921 | } | ||
5922 | #endif | ||
5923 | /* Check if the requested method is implemented by the underlying storage engine */ | ||
5924 | if( pCursor->pStore->pIo->pMethods->xValid == 0 ){ | ||
5925 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5926 | }else{ | ||
5927 | rc = pCursor->pStore->pIo->pMethods->xValid(pCursor); | ||
5928 | } | ||
5929 | return rc; | ||
5930 | } | ||
5931 | /* | ||
5932 | * [CAPIREF: unqlite_kv_cursor_next_entry()] | ||
5933 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5934 | */ | ||
5935 | int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor) | ||
5936 | { | ||
5937 | int rc; | ||
5938 | #ifdef UNTRUST | ||
5939 | if( pCursor == 0 ){ | ||
5940 | return UNQLITE_CORRUPT; | ||
5941 | } | ||
5942 | #endif | ||
5943 | /* Check if the requested method is implemented by the underlying storage engine */ | ||
5944 | if( pCursor->pStore->pIo->pMethods->xNext == 0 ){ | ||
5945 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5946 | }else{ | ||
5947 | /* Seek to the next entry */ | ||
5948 | rc = pCursor->pStore->pIo->pMethods->xNext(pCursor); | ||
5949 | } | ||
5950 | return rc; | ||
5951 | } | ||
5952 | /* | ||
5953 | * [CAPIREF: unqlite_kv_cursor_prev_entry()] | ||
5954 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5955 | */ | ||
5956 | int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor) | ||
5957 | { | ||
5958 | int rc; | ||
5959 | #ifdef UNTRUST | ||
5960 | if( pCursor == 0 ){ | ||
5961 | return UNQLITE_CORRUPT; | ||
5962 | } | ||
5963 | #endif | ||
5964 | /* Check if the requested method is implemented by the underlying storage engine */ | ||
5965 | if( pCursor->pStore->pIo->pMethods->xPrev == 0 ){ | ||
5966 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5967 | }else{ | ||
5968 | /* Seek to the previous entry */ | ||
5969 | rc = pCursor->pStore->pIo->pMethods->xPrev(pCursor); | ||
5970 | } | ||
5971 | return rc; | ||
5972 | } | ||
5973 | /* | ||
5974 | * [CAPIREF: unqlite_kv_cursor_delete_entry()] | ||
5975 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5976 | */ | ||
5977 | int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor) | ||
5978 | { | ||
5979 | int rc; | ||
5980 | #ifdef UNTRUST | ||
5981 | if( pCursor == 0 ){ | ||
5982 | return UNQLITE_CORRUPT; | ||
5983 | } | ||
5984 | #endif | ||
5985 | /* Check if the requested method is implemented by the underlying storage engine */ | ||
5986 | if( pCursor->pStore->pIo->pMethods->xDelete == 0 ){ | ||
5987 | rc = UNQLITE_NOTIMPLEMENTED; | ||
5988 | }else{ | ||
5989 | /* Delete the entry */ | ||
5990 | rc = pCursor->pStore->pIo->pMethods->xDelete(pCursor); | ||
5991 | } | ||
5992 | return rc; | ||
5993 | } | ||
5994 | /* | ||
5995 | * [CAPIREF: unqlite_kv_cursor_reset()] | ||
5996 | * Please refer to the official documentation for function purpose and expected parameters. | ||
5997 | */ | ||
5998 | int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor) | ||
5999 | { | ||
6000 | int rc = UNQLITE_OK; | ||
6001 | #ifdef UNTRUST | ||
6002 | if( pCursor == 0 ){ | ||
6003 | return UNQLITE_CORRUPT; | ||
6004 | } | ||
6005 | #endif | ||
6006 | /* Check if the requested method is implemented by the underlying storage engine */ | ||
6007 | if( pCursor->pStore->pIo->pMethods->xReset == 0 ){ | ||
6008 | rc = UNQLITE_NOTIMPLEMENTED; | ||
6009 | }else{ | ||
6010 | /* Reset */ | ||
6011 | pCursor->pStore->pIo->pMethods->xReset(pCursor); | ||
6012 | } | ||
6013 | return rc; | ||
6014 | } | ||
6015 | /* | ||
6016 | * [CAPIREF: unqlite_kv_cursor_seek()] | ||
6017 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6018 | */ | ||
6019 | int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos) | ||
6020 | { | ||
6021 | int rc = UNQLITE_OK; | ||
6022 | #ifdef UNTRUST | ||
6023 | if( pCursor == 0 ){ | ||
6024 | return UNQLITE_CORRUPT; | ||
6025 | } | ||
6026 | #endif | ||
6027 | if( nKeyLen < 0 ){ | ||
6028 | /* Assume a null terminated string and compute it's length */ | ||
6029 | nKeyLen = SyStrlen((const char *)pKey); | ||
6030 | } | ||
6031 | if( !nKeyLen ){ | ||
6032 | rc = UNQLITE_EMPTY; | ||
6033 | }else{ | ||
6034 | /* Seek to the desired location */ | ||
6035 | rc = pCursor->pStore->pIo->pMethods->xSeek(pCursor,pKey,nKeyLen,iPos); | ||
6036 | } | ||
6037 | return rc; | ||
6038 | } | ||
6039 | /* | ||
6040 | * Default data consumer callback. That is, all retrieved is redirected to this | ||
6041 | * routine which store the output in an internal blob. | ||
6042 | */ | ||
6043 | UNQLITE_PRIVATE int unqliteDataConsumer( | ||
6044 | const void *pOut, /* Data to consume */ | ||
6045 | unsigned int nLen, /* Data length */ | ||
6046 | void *pUserData /* User private data */ | ||
6047 | ) | ||
6048 | { | ||
6049 | sxi32 rc; | ||
6050 | /* Store the output in an internal BLOB */ | ||
6051 | rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen); | ||
6052 | return rc; | ||
6053 | } | ||
6054 | /* | ||
6055 | * [CAPIREF: unqlite_kv_cursor_data_callback()] | ||
6056 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6057 | */ | ||
6058 | int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
6059 | { | ||
6060 | int rc; | ||
6061 | #ifdef UNTRUST | ||
6062 | if( pCursor == 0 ){ | ||
6063 | return UNQLITE_CORRUPT; | ||
6064 | } | ||
6065 | #endif | ||
6066 | /* Consume the key directly */ | ||
6067 | rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,xConsumer,pUserData); | ||
6068 | return rc; | ||
6069 | } | ||
6070 | /* | ||
6071 | * [CAPIREF: unqlite_kv_cursor_key()] | ||
6072 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6073 | */ | ||
6074 | int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte) | ||
6075 | { | ||
6076 | int rc; | ||
6077 | #ifdef UNTRUST | ||
6078 | if( pCursor == 0 ){ | ||
6079 | return UNQLITE_CORRUPT; | ||
6080 | } | ||
6081 | #endif | ||
6082 | if( pBuf == 0 ){ | ||
6083 | /* Key length only */ | ||
6084 | rc = pCursor->pStore->pIo->pMethods->xKeyLength(pCursor,pnByte); | ||
6085 | }else{ | ||
6086 | SyBlob sBlob; | ||
6087 | if( (*pnByte) < 0 ){ | ||
6088 | return UNQLITE_CORRUPT; | ||
6089 | } | ||
6090 | /* Initialize the data consumer */ | ||
6091 | SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte)); | ||
6092 | /* Consume the key */ | ||
6093 | rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,unqliteDataConsumer,&sBlob); | ||
6094 | /* Key length */ | ||
6095 | *pnByte = SyBlobLength(&sBlob); | ||
6096 | /* Cleanup */ | ||
6097 | SyBlobRelease(&sBlob); | ||
6098 | } | ||
6099 | return rc; | ||
6100 | } | ||
6101 | /* | ||
6102 | * [CAPIREF: unqlite_kv_cursor_data_callback()] | ||
6103 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6104 | */ | ||
6105 | int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
6106 | { | ||
6107 | int rc; | ||
6108 | #ifdef UNTRUST | ||
6109 | if( pCursor == 0 ){ | ||
6110 | return UNQLITE_CORRUPT; | ||
6111 | } | ||
6112 | #endif | ||
6113 | /* Consume the data directly */ | ||
6114 | rc = pCursor->pStore->pIo->pMethods->xData(pCursor,xConsumer,pUserData); | ||
6115 | return rc; | ||
6116 | } | ||
6117 | /* | ||
6118 | * [CAPIREF: unqlite_kv_cursor_data()] | ||
6119 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6120 | */ | ||
6121 | int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnByte) | ||
6122 | { | ||
6123 | int rc; | ||
6124 | #ifdef UNTRUST | ||
6125 | if( pCursor == 0 ){ | ||
6126 | return UNQLITE_CORRUPT; | ||
6127 | } | ||
6128 | #endif | ||
6129 | if( pBuf == 0 ){ | ||
6130 | /* Data length only */ | ||
6131 | rc = pCursor->pStore->pIo->pMethods->xDataLength(pCursor,pnByte); | ||
6132 | }else{ | ||
6133 | SyBlob sBlob; | ||
6134 | if( (*pnByte) < 0 ){ | ||
6135 | return UNQLITE_CORRUPT; | ||
6136 | } | ||
6137 | /* Initialize the data consumer */ | ||
6138 | SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte)); | ||
6139 | /* Consume the data */ | ||
6140 | rc = pCursor->pStore->pIo->pMethods->xData(pCursor,unqliteDataConsumer,&sBlob); | ||
6141 | /* Data length */ | ||
6142 | *pnByte = SyBlobLength(&sBlob); | ||
6143 | /* Cleanup */ | ||
6144 | SyBlobRelease(&sBlob); | ||
6145 | } | ||
6146 | return rc; | ||
6147 | } | ||
6148 | /* | ||
6149 | * [CAPIREF: unqlite_begin()] | ||
6150 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6151 | */ | ||
6152 | int unqlite_begin(unqlite *pDb) | ||
6153 | { | ||
6154 | int rc; | ||
6155 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
6156 | return UNQLITE_CORRUPT; | ||
6157 | } | ||
6158 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6159 | /* Acquire DB mutex */ | ||
6160 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6161 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
6162 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
6163 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
6164 | } | ||
6165 | #endif | ||
6166 | /* Begin the write transaction */ | ||
6167 | rc = unqlitePagerBegin(pDb->sDB.pPager); | ||
6168 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6169 | /* Leave DB mutex */ | ||
6170 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6171 | #endif | ||
6172 | return rc; | ||
6173 | } | ||
6174 | /* | ||
6175 | * [CAPIREF: unqlite_commit()] | ||
6176 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6177 | */ | ||
6178 | int unqlite_commit(unqlite *pDb) | ||
6179 | { | ||
6180 | int rc; | ||
6181 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
6182 | return UNQLITE_CORRUPT; | ||
6183 | } | ||
6184 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6185 | /* Acquire DB mutex */ | ||
6186 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6187 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
6188 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
6189 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
6190 | } | ||
6191 | #endif | ||
6192 | /* Commit the transaction */ | ||
6193 | rc = unqlitePagerCommit(pDb->sDB.pPager); | ||
6194 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6195 | /* Leave DB mutex */ | ||
6196 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6197 | #endif | ||
6198 | return rc; | ||
6199 | } | ||
6200 | /* | ||
6201 | * [CAPIREF: unqlite_rollback()] | ||
6202 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6203 | */ | ||
6204 | int unqlite_rollback(unqlite *pDb) | ||
6205 | { | ||
6206 | int rc; | ||
6207 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
6208 | return UNQLITE_CORRUPT; | ||
6209 | } | ||
6210 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6211 | /* Acquire DB mutex */ | ||
6212 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6213 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
6214 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
6215 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
6216 | } | ||
6217 | #endif | ||
6218 | /* Rollback the transaction */ | ||
6219 | rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE); | ||
6220 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6221 | /* Leave DB mutex */ | ||
6222 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6223 | #endif | ||
6224 | return rc; | ||
6225 | } | ||
6226 | /* | ||
6227 | * [CAPIREF: unqlite_util_load_mmaped_file()] | ||
6228 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6229 | */ | ||
6230 | UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize) | ||
6231 | { | ||
6232 | const jx9_vfs *pVfs; | ||
6233 | int rc; | ||
6234 | if( SX_EMPTY_STR(zFile) || ppMap == 0 || pFileSize == 0){ | ||
6235 | /* Sanity check */ | ||
6236 | return UNQLITE_CORRUPT; | ||
6237 | } | ||
6238 | *ppMap = 0; | ||
6239 | /* Extract the Jx9 Vfs */ | ||
6240 | pVfs = jx9ExportBuiltinVfs(); | ||
6241 | /* | ||
6242 | * Check if the underlying vfs implement the memory map routines | ||
6243 | * [i.e: mmap() under UNIX/MapViewOfFile() under windows]. | ||
6244 | */ | ||
6245 | if( pVfs == 0 || pVfs->xMmap == 0 ){ | ||
6246 | rc = UNQLITE_NOTIMPLEMENTED; | ||
6247 | }else{ | ||
6248 | /* Try to get a read-only memory view of the whole file */ | ||
6249 | rc = pVfs->xMmap(zFile,ppMap,pFileSize); | ||
6250 | } | ||
6251 | return rc; | ||
6252 | } | ||
6253 | /* | ||
6254 | * [CAPIREF: unqlite_util_release_mmaped_file()] | ||
6255 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6256 | */ | ||
6257 | UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize) | ||
6258 | { | ||
6259 | const jx9_vfs *pVfs; | ||
6260 | int rc = UNQLITE_OK; | ||
6261 | if( pMap == 0 ){ | ||
6262 | return UNQLITE_OK; | ||
6263 | } | ||
6264 | /* Extract the Jx9 Vfs */ | ||
6265 | pVfs = jx9ExportBuiltinVfs(); | ||
6266 | if( pVfs == 0 || pVfs->xUnmap == 0 ){ | ||
6267 | rc = UNQLITE_NOTIMPLEMENTED; | ||
6268 | }else{ | ||
6269 | pVfs->xUnmap(pMap,iFileSize); | ||
6270 | } | ||
6271 | return rc; | ||
6272 | } | ||
6273 | /* | ||
6274 | * [CAPIREF: unqlite_util_random_string()] | ||
6275 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6276 | */ | ||
6277 | UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size) | ||
6278 | { | ||
6279 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
6280 | return UNQLITE_CORRUPT; | ||
6281 | } | ||
6282 | if( zBuf == 0 || buf_size < 3 ){ | ||
6283 | /* Buffer must be long enough to hold three bytes */ | ||
6284 | return UNQLITE_INVALID; | ||
6285 | } | ||
6286 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6287 | /* Acquire DB mutex */ | ||
6288 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6289 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
6290 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
6291 | return UNQLITE_ABORT; /* Another thread have released this instance */ | ||
6292 | } | ||
6293 | #endif | ||
6294 | /* Generate the random string */ | ||
6295 | unqlitePagerRandomString(pDb->sDB.pPager,zBuf,buf_size); | ||
6296 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6297 | /* Leave DB mutex */ | ||
6298 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6299 | #endif | ||
6300 | return UNQLITE_OK; | ||
6301 | } | ||
6302 | /* | ||
6303 | * [CAPIREF: unqlite_util_random_num()] | ||
6304 | * Please refer to the official documentation for function purpose and expected parameters. | ||
6305 | */ | ||
6306 | UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb) | ||
6307 | { | ||
6308 | sxu32 iNum; | ||
6309 | if( UNQLITE_DB_MISUSE(pDb) ){ | ||
6310 | return 0; | ||
6311 | } | ||
6312 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6313 | /* Acquire DB mutex */ | ||
6314 | SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6315 | if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && | ||
6316 | UNQLITE_THRD_DB_RELEASE(pDb) ){ | ||
6317 | return 0; /* Another thread have released this instance */ | ||
6318 | } | ||
6319 | #endif | ||
6320 | /* Generate the random number */ | ||
6321 | iNum = unqlitePagerRandomNum(pDb->sDB.pPager); | ||
6322 | #if defined(UNQLITE_ENABLE_THREADS) | ||
6323 | /* Leave DB mutex */ | ||
6324 | SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ | ||
6325 | #endif | ||
6326 | return iNum; | ||
6327 | } | ||
6328 | /* | ||
6329 | * ---------------------------------------------------------- | ||
6330 | * File: bitvec.c | ||
6331 | * MD5: 7e3376710d8454ebcf8c77baacca880f | ||
6332 | * ---------------------------------------------------------- | ||
6333 | */ | ||
6334 | /* | ||
6335 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
6336 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
6337 | * Version 1.1.6 | ||
6338 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
6339 | * please contact Symisc Systems via: | ||
6340 | * legal@symisc.net | ||
6341 | * licensing@symisc.net | ||
6342 | * contact@symisc.net | ||
6343 | * or visit: | ||
6344 | * http://unqlite.org/licensing.html | ||
6345 | */ | ||
6346 | /* $SymiscID: bitvec.c v1.0 Win7 2013-02-27 15:16 stable <chm@symisc.net> $ */ | ||
6347 | #ifndef UNQLITE_AMALGAMATION | ||
6348 | #include "unqliteInt.h" | ||
6349 | #endif | ||
6350 | |||
6351 | /** This file implements an object that represents a dynmaic | ||
6352 | ** bitmap. | ||
6353 | ** | ||
6354 | ** A bitmap is used to record which pages of a database file have been | ||
6355 | ** journalled during a transaction, or which pages have the "dont-write" | ||
6356 | ** property. Usually only a few pages are meet either condition. | ||
6357 | ** So the bitmap is usually sparse and has low cardinality. | ||
6358 | */ | ||
6359 | /* | ||
6360 | * Actually, this is not a bitmap but a simple hashtable where page | ||
6361 | * number (64-bit unsigned integers) are used as the lookup keys. | ||
6362 | */ | ||
6363 | typedef struct bitvec_rec bitvec_rec; | ||
6364 | struct bitvec_rec | ||
6365 | { | ||
6366 | pgno iPage; /* Page number */ | ||
6367 | bitvec_rec *pNext,*pNextCol; /* Collison link */ | ||
6368 | }; | ||
6369 | struct Bitvec | ||
6370 | { | ||
6371 | SyMemBackend *pAlloc; /* Memory allocator */ | ||
6372 | sxu32 nRec; /* Total number of records */ | ||
6373 | sxu32 nSize; /* Table size */ | ||
6374 | bitvec_rec **apRec; /* Record table */ | ||
6375 | bitvec_rec *pList; /* List of records */ | ||
6376 | }; | ||
6377 | /* | ||
6378 | * Allocate a new bitvec instance. | ||
6379 | */ | ||
6380 | UNQLITE_PRIVATE Bitvec * unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize) | ||
6381 | { | ||
6382 | bitvec_rec **apNew; | ||
6383 | Bitvec *p; | ||
6384 | |||
6385 | p = (Bitvec *)SyMemBackendAlloc(pAlloc,sizeof(*p) ); | ||
6386 | if( p == 0 ){ | ||
6387 | SXUNUSED(iSize); /* cc warning */ | ||
6388 | return 0; | ||
6389 | } | ||
6390 | /* Zero the structure */ | ||
6391 | SyZero(p,sizeof(Bitvec)); | ||
6392 | /* Allocate a new table */ | ||
6393 | p->nSize = 64; /* Must be a power of two */ | ||
6394 | apNew = (bitvec_rec **)SyMemBackendAlloc(pAlloc,p->nSize * sizeof(bitvec_rec *)); | ||
6395 | if( apNew == 0 ){ | ||
6396 | SyMemBackendFree(pAlloc,p); | ||
6397 | return 0; | ||
6398 | } | ||
6399 | /* Zero the new table */ | ||
6400 | SyZero((void *)apNew,p->nSize * sizeof(bitvec_rec *)); | ||
6401 | /* Fill-in */ | ||
6402 | p->apRec = apNew; | ||
6403 | p->pAlloc = pAlloc; | ||
6404 | return p; | ||
6405 | } | ||
6406 | /* | ||
6407 | * Check if the given page number is already installed in the table. | ||
6408 | * Return true if installed. False otherwise. | ||
6409 | */ | ||
6410 | UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i) | ||
6411 | { | ||
6412 | bitvec_rec *pRec; | ||
6413 | /* Point to the desired bucket */ | ||
6414 | pRec = p->apRec[i & (p->nSize - 1)]; | ||
6415 | for(;;){ | ||
6416 | if( pRec == 0 ){ break; } | ||
6417 | if( pRec->iPage == i ){ | ||
6418 | /* Page found */ | ||
6419 | return 1; | ||
6420 | } | ||
6421 | /* Point to the next entry */ | ||
6422 | pRec = pRec->pNextCol; | ||
6423 | |||
6424 | if( pRec == 0 ){ break; } | ||
6425 | if( pRec->iPage == i ){ | ||
6426 | /* Page found */ | ||
6427 | return 1; | ||
6428 | } | ||
6429 | /* Point to the next entry */ | ||
6430 | pRec = pRec->pNextCol; | ||
6431 | |||
6432 | |||
6433 | if( pRec == 0 ){ break; } | ||
6434 | if( pRec->iPage == i ){ | ||
6435 | /* Page found */ | ||
6436 | return 1; | ||
6437 | } | ||
6438 | /* Point to the next entry */ | ||
6439 | pRec = pRec->pNextCol; | ||
6440 | |||
6441 | |||
6442 | if( pRec == 0 ){ break; } | ||
6443 | if( pRec->iPage == i ){ | ||
6444 | /* Page found */ | ||
6445 | return 1; | ||
6446 | } | ||
6447 | /* Point to the next entry */ | ||
6448 | pRec = pRec->pNextCol; | ||
6449 | } | ||
6450 | /* No such entry */ | ||
6451 | return 0; | ||
6452 | } | ||
6453 | /* | ||
6454 | * Install a given page number in our bitmap (Actually, our hashtable). | ||
6455 | */ | ||
6456 | UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i) | ||
6457 | { | ||
6458 | bitvec_rec *pRec; | ||
6459 | sxi32 iBuck; | ||
6460 | /* Allocate a new instance */ | ||
6461 | pRec = (bitvec_rec *)SyMemBackendPoolAlloc(p->pAlloc,sizeof(bitvec_rec)); | ||
6462 | if( pRec == 0 ){ | ||
6463 | return UNQLITE_NOMEM; | ||
6464 | } | ||
6465 | /* Zero the structure */ | ||
6466 | SyZero(pRec,sizeof(bitvec_rec)); | ||
6467 | /* Fill-in */ | ||
6468 | pRec->iPage = i; | ||
6469 | iBuck = i & (p->nSize - 1); | ||
6470 | pRec->pNextCol = p->apRec[iBuck]; | ||
6471 | p->apRec[iBuck] = pRec; | ||
6472 | pRec->pNext = p->pList; | ||
6473 | p->pList = pRec; | ||
6474 | p->nRec++; | ||
6475 | if( p->nRec >= (p->nSize * 3) && p->nRec < 100000 ){ | ||
6476 | /* Grow the hashtable */ | ||
6477 | sxu32 nNewSize = p->nSize << 1; | ||
6478 | bitvec_rec *pEntry,**apNew; | ||
6479 | sxu32 n; | ||
6480 | apNew = (bitvec_rec **)SyMemBackendAlloc(p->pAlloc, nNewSize * sizeof(bitvec_rec *)); | ||
6481 | if( apNew ){ | ||
6482 | sxu32 iBucket; | ||
6483 | /* Zero the new table */ | ||
6484 | SyZero((void *)apNew, nNewSize * sizeof(bitvec_rec *)); | ||
6485 | /* Rehash all entries */ | ||
6486 | n = 0; | ||
6487 | pEntry = p->pList; | ||
6488 | for(;;){ | ||
6489 | /* Loop one */ | ||
6490 | if( n >= p->nRec ){ | ||
6491 | break; | ||
6492 | } | ||
6493 | pEntry->pNextCol = 0; | ||
6494 | /* Install in the new bucket */ | ||
6495 | iBucket = pEntry->iPage & (nNewSize - 1); | ||
6496 | pEntry->pNextCol = apNew[iBucket]; | ||
6497 | apNew[iBucket] = pEntry; | ||
6498 | /* Point to the next entry */ | ||
6499 | pEntry = pEntry->pNext; | ||
6500 | n++; | ||
6501 | } | ||
6502 | /* Release the old table and reflect the change */ | ||
6503 | SyMemBackendFree(p->pAlloc,(void *)p->apRec); | ||
6504 | p->apRec = apNew; | ||
6505 | p->nSize = nNewSize; | ||
6506 | } | ||
6507 | } | ||
6508 | return UNQLITE_OK; | ||
6509 | } | ||
6510 | /* | ||
6511 | * Destroy a bitvec instance. Reclaim all memory used. | ||
6512 | */ | ||
6513 | UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p) | ||
6514 | { | ||
6515 | bitvec_rec *pNext,*pRec = p->pList; | ||
6516 | SyMemBackend *pAlloc = p->pAlloc; | ||
6517 | |||
6518 | for(;;){ | ||
6519 | if( p->nRec < 1 ){ | ||
6520 | break; | ||
6521 | } | ||
6522 | pNext = pRec->pNext; | ||
6523 | SyMemBackendPoolFree(pAlloc,(void *)pRec); | ||
6524 | pRec = pNext; | ||
6525 | p->nRec--; | ||
6526 | |||
6527 | if( p->nRec < 1 ){ | ||
6528 | break; | ||
6529 | } | ||
6530 | pNext = pRec->pNext; | ||
6531 | SyMemBackendPoolFree(pAlloc,(void *)pRec); | ||
6532 | pRec = pNext; | ||
6533 | p->nRec--; | ||
6534 | |||
6535 | |||
6536 | if( p->nRec < 1 ){ | ||
6537 | break; | ||
6538 | } | ||
6539 | pNext = pRec->pNext; | ||
6540 | SyMemBackendPoolFree(pAlloc,(void *)pRec); | ||
6541 | pRec = pNext; | ||
6542 | p->nRec--; | ||
6543 | |||
6544 | |||
6545 | if( p->nRec < 1 ){ | ||
6546 | break; | ||
6547 | } | ||
6548 | pNext = pRec->pNext; | ||
6549 | SyMemBackendPoolFree(pAlloc,(void *)pRec); | ||
6550 | pRec = pNext; | ||
6551 | p->nRec--; | ||
6552 | } | ||
6553 | SyMemBackendFree(pAlloc,(void *)p->apRec); | ||
6554 | SyMemBackendFree(pAlloc,p); | ||
6555 | } | ||
6556 | /* | ||
6557 | * ---------------------------------------------------------- | ||
6558 | * File: fastjson.c | ||
6559 | * MD5: 3693c0022edc7d37b65124d7aef68397 | ||
6560 | * ---------------------------------------------------------- | ||
6561 | */ | ||
6562 | /* | ||
6563 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
6564 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
6565 | * Version 1.1.6 | ||
6566 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
6567 | * please contact Symisc Systems via: | ||
6568 | * legal@symisc.net | ||
6569 | * licensing@symisc.net | ||
6570 | * contact@symisc.net | ||
6571 | * or visit: | ||
6572 | * http://unqlite.org/licensing.html | ||
6573 | */ | ||
6574 | /* $SymiscID: fastjson.c v1.1 FreeBSD 2012-12-05 22:52 stable <chm@symisc.net> $ */ | ||
6575 | #ifndef UNQLITE_AMALGAMATION | ||
6576 | #include "unqliteInt.h" | ||
6577 | #endif | ||
6578 | /* JSON binary encoding, decoding and stuff like that */ | ||
6579 | #ifndef UNQLITE_FAST_JSON_NEST_LIMIT | ||
6580 | #if defined(__WINNT__) || defined(__UNIXES__) | ||
6581 | #define UNQLITE_FAST_JSON_NEST_LIMIT 64 /* Nesting limit */ | ||
6582 | #else | ||
6583 | #define UNQLITE_FAST_JSON_NEST_LIMIT 32 /* Nesting limit */ | ||
6584 | #endif | ||
6585 | #endif /* UNQLITE_FAST_JSON_NEST_LIMIT */ | ||
6586 | /* | ||
6587 | * JSON to Binary using the FastJSON implementation (BigEndian). | ||
6588 | */ | ||
6589 | /* | ||
6590 | * FastJSON implemented binary token. | ||
6591 | */ | ||
6592 | #define FJSON_DOC_START 1 /* { */ | ||
6593 | #define FJSON_DOC_END 2 /* } */ | ||
6594 | #define FJSON_ARRAY_START 3 /* [ */ | ||
6595 | #define FJSON_ARRAY_END 4 /* ] */ | ||
6596 | #define FJSON_COLON 5 /* : */ | ||
6597 | #define FJSON_COMMA 6 /* , */ | ||
6598 | #define FJSON_ID 7 /* ID + 4 Bytes length */ | ||
6599 | #define FJSON_STRING 8 /* String + 4 bytes length */ | ||
6600 | #define FJSON_BYTE 9 /* Byte */ | ||
6601 | #define FJSON_INT64 10 /* Integer 64 + 8 bytes */ | ||
6602 | #define FJSON_REAL 18 /* Floating point value + 2 bytes */ | ||
6603 | #define FJSON_NULL 23 /* NULL */ | ||
6604 | #define FJSON_TRUE 24 /* TRUE */ | ||
6605 | #define FJSON_FALSE 25 /* FALSE */ | ||
6606 | /* | ||
6607 | * Encode a Jx9 value to binary JSON. | ||
6608 | */ | ||
6609 | UNQLITE_PRIVATE sxi32 FastJsonEncode( | ||
6610 | jx9_value *pValue, /* Value to encode */ | ||
6611 | SyBlob *pOut, /* Store encoded value here */ | ||
6612 | int iNest /* Nesting limit */ | ||
6613 | ) | ||
6614 | { | ||
6615 | sxi32 iType = pValue ? pValue->iFlags : MEMOBJ_NULL; | ||
6616 | sxi32 rc = SXRET_OK; | ||
6617 | int c; | ||
6618 | if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ | ||
6619 | /* Nesting limit reached */ | ||
6620 | return SXERR_LIMIT; | ||
6621 | } | ||
6622 | if( iType & (MEMOBJ_NULL|MEMOBJ_RES) ){ | ||
6623 | /* | ||
6624 | * Resources are encoded as null also. | ||
6625 | */ | ||
6626 | c = FJSON_NULL; | ||
6627 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6628 | }else if( iType & MEMOBJ_BOOL ){ | ||
6629 | c = pValue->x.iVal ? FJSON_TRUE : FJSON_FALSE; | ||
6630 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6631 | }else if( iType & MEMOBJ_STRING ){ | ||
6632 | unsigned char zBuf[sizeof(sxu32)]; /* String length */ | ||
6633 | c = FJSON_STRING; | ||
6634 | SyBigEndianPack32(zBuf,SyBlobLength(&pValue->sBlob)); | ||
6635 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6636 | if( rc == SXRET_OK ){ | ||
6637 | rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); | ||
6638 | if( rc == SXRET_OK ){ | ||
6639 | rc = SyBlobAppend(pOut,SyBlobData(&pValue->sBlob),SyBlobLength(&pValue->sBlob)); | ||
6640 | } | ||
6641 | } | ||
6642 | }else if( iType & MEMOBJ_INT ){ | ||
6643 | unsigned char zBuf[8]; | ||
6644 | /* 64bit big endian integer */ | ||
6645 | c = FJSON_INT64; | ||
6646 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6647 | if( rc == SXRET_OK ){ | ||
6648 | SyBigEndianPack64(zBuf,(sxu64)pValue->x.iVal); | ||
6649 | rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); | ||
6650 | } | ||
6651 | }else if( iType & MEMOBJ_REAL ){ | ||
6652 | /* Real number */ | ||
6653 | c = FJSON_REAL; | ||
6654 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6655 | if( rc == SXRET_OK ){ | ||
6656 | sxu32 iOfft = SyBlobLength(pOut); | ||
6657 | rc = SyBlobAppendBig16(pOut,0); | ||
6658 | if( rc == SXRET_OK ){ | ||
6659 | unsigned char *zBlob; | ||
6660 | SyBlobFormat(pOut,"%.15g",pValue->x.rVal); | ||
6661 | zBlob = (unsigned char *)SyBlobDataAt(pOut,iOfft); | ||
6662 | SyBigEndianPack16(zBlob,(sxu16)(SyBlobLength(pOut) - ( 2 + iOfft))); | ||
6663 | } | ||
6664 | } | ||
6665 | }else if( iType & MEMOBJ_HASHMAP ){ | ||
6666 | /* A JSON object or array */ | ||
6667 | jx9_hashmap *pMap = (jx9_hashmap *)pValue->x.pOther; | ||
6668 | jx9_hashmap_node *pNode; | ||
6669 | jx9_value *pEntry; | ||
6670 | /* Reset the hashmap loop cursor */ | ||
6671 | jx9HashmapResetLoopCursor(pMap); | ||
6672 | if( pMap->iFlags & HASHMAP_JSON_OBJECT ){ | ||
6673 | jx9_value sKey; | ||
6674 | /* A JSON object */ | ||
6675 | c = FJSON_DOC_START; /* { */ | ||
6676 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6677 | if( rc == SXRET_OK ){ | ||
6678 | jx9MemObjInit(pMap->pVm,&sKey); | ||
6679 | /* Encode object entries */ | ||
6680 | while((pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ | ||
6681 | /* Extract the key */ | ||
6682 | jx9HashmapExtractNodeKey(pNode,&sKey); | ||
6683 | /* Encode it */ | ||
6684 | rc = FastJsonEncode(&sKey,pOut,iNest+1); | ||
6685 | if( rc != SXRET_OK ){ | ||
6686 | break; | ||
6687 | } | ||
6688 | c = FJSON_COLON; | ||
6689 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6690 | if( rc != SXRET_OK ){ | ||
6691 | break; | ||
6692 | } | ||
6693 | /* Extract the value */ | ||
6694 | pEntry = jx9HashmapGetNodeValue(pNode); | ||
6695 | /* Encode it */ | ||
6696 | rc = FastJsonEncode(pEntry,pOut,iNest+1); | ||
6697 | if( rc != SXRET_OK ){ | ||
6698 | break; | ||
6699 | } | ||
6700 | /* Delimit the entry */ | ||
6701 | c = FJSON_COMMA; | ||
6702 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6703 | if( rc != SXRET_OK ){ | ||
6704 | break; | ||
6705 | } | ||
6706 | } | ||
6707 | jx9MemObjRelease(&sKey); | ||
6708 | if( rc == SXRET_OK ){ | ||
6709 | c = FJSON_DOC_END; /* } */ | ||
6710 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6711 | } | ||
6712 | } | ||
6713 | }else{ | ||
6714 | /* A JSON array */ | ||
6715 | c = FJSON_ARRAY_START; /* [ */ | ||
6716 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6717 | if( rc == SXRET_OK ){ | ||
6718 | /* Encode array entries */ | ||
6719 | while( (pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ | ||
6720 | /* Extract the value */ | ||
6721 | pEntry = jx9HashmapGetNodeValue(pNode); | ||
6722 | /* Encode it */ | ||
6723 | rc = FastJsonEncode(pEntry,pOut,iNest+1); | ||
6724 | if( rc != SXRET_OK ){ | ||
6725 | break; | ||
6726 | } | ||
6727 | /* Delimit the entry */ | ||
6728 | c = FJSON_COMMA; | ||
6729 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6730 | if( rc != SXRET_OK ){ | ||
6731 | break; | ||
6732 | } | ||
6733 | } | ||
6734 | if( rc == SXRET_OK ){ | ||
6735 | c = FJSON_ARRAY_END; /* ] */ | ||
6736 | rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); | ||
6737 | } | ||
6738 | } | ||
6739 | } | ||
6740 | } | ||
6741 | return rc; | ||
6742 | } | ||
6743 | /* | ||
6744 | * Decode a FastJSON binary blob. | ||
6745 | */ | ||
6746 | UNQLITE_PRIVATE sxi32 FastJsonDecode( | ||
6747 | const void *pIn, /* Binary JSON */ | ||
6748 | sxu32 nByte, /* Chunk delimiter */ | ||
6749 | jx9_value *pOut, /* Decoded value */ | ||
6750 | const unsigned char **pzPtr, | ||
6751 | int iNest /* Nesting limit */ | ||
6752 | ) | ||
6753 | { | ||
6754 | const unsigned char *zIn = (const unsigned char *)pIn; | ||
6755 | const unsigned char *zEnd = &zIn[nByte]; | ||
6756 | sxi32 rc = SXRET_OK; | ||
6757 | int c; | ||
6758 | if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ | ||
6759 | /* Nesting limit reached */ | ||
6760 | return SXERR_LIMIT; | ||
6761 | } | ||
6762 | c = zIn[0]; | ||
6763 | /* Advance the stream cursor */ | ||
6764 | zIn++; | ||
6765 | /* Process the binary token */ | ||
6766 | switch(c){ | ||
6767 | case FJSON_NULL: | ||
6768 | /* null */ | ||
6769 | jx9_value_null(pOut); | ||
6770 | break; | ||
6771 | case FJSON_FALSE: | ||
6772 | /* Boolean FALSE */ | ||
6773 | jx9_value_bool(pOut,0); | ||
6774 | break; | ||
6775 | case FJSON_TRUE: | ||
6776 | /* Boolean TRUE */ | ||
6777 | jx9_value_bool(pOut,1); | ||
6778 | break; | ||
6779 | case FJSON_INT64: { | ||
6780 | /* 64Bit integer */ | ||
6781 | sxu64 iVal; | ||
6782 | /* Sanity check */ | ||
6783 | if( &zIn[8] >= zEnd ){ | ||
6784 | /* Corrupt chunk */ | ||
6785 | rc = SXERR_CORRUPT; | ||
6786 | break; | ||
6787 | } | ||
6788 | SyBigEndianUnpack64(zIn,&iVal); | ||
6789 | /* Advance the pointer */ | ||
6790 | zIn += 8; | ||
6791 | jx9_value_int64(pOut,(jx9_int64)iVal); | ||
6792 | break; | ||
6793 | } | ||
6794 | case FJSON_REAL: { | ||
6795 | /* Real number */ | ||
6796 | double iVal = 0; /* cc warning */ | ||
6797 | sxu16 iLen; | ||
6798 | /* Sanity check */ | ||
6799 | if( &zIn[2] >= zEnd ){ | ||
6800 | /* Corrupt chunk */ | ||
6801 | rc = SXERR_CORRUPT; | ||
6802 | break; | ||
6803 | } | ||
6804 | SyBigEndianUnpack16(zIn,&iLen); | ||
6805 | if( &zIn[iLen] >= zEnd ){ | ||
6806 | /* Corrupt chunk */ | ||
6807 | rc = SXERR_CORRUPT; | ||
6808 | break; | ||
6809 | } | ||
6810 | zIn += 2; | ||
6811 | SyStrToReal((const char *)zIn,(sxu32)iLen,&iVal,0); | ||
6812 | /* Advance the pointer */ | ||
6813 | zIn += iLen; | ||
6814 | jx9_value_double(pOut,iVal); | ||
6815 | break; | ||
6816 | } | ||
6817 | case FJSON_STRING: { | ||
6818 | /* UTF-8/Binary chunk */ | ||
6819 | sxu32 iLength; | ||
6820 | /* Sanity check */ | ||
6821 | if( &zIn[4] >= zEnd ){ | ||
6822 | /* Corrupt chunk */ | ||
6823 | rc = SXERR_CORRUPT; | ||
6824 | break; | ||
6825 | } | ||
6826 | SyBigEndianUnpack32(zIn,&iLength); | ||
6827 | if( &zIn[iLength] >= zEnd ){ | ||
6828 | /* Corrupt chunk */ | ||
6829 | rc = SXERR_CORRUPT; | ||
6830 | break; | ||
6831 | } | ||
6832 | zIn += 4; | ||
6833 | /* Invalidate any prior representation */ | ||
6834 | if( pOut->iFlags & MEMOBJ_STRING ){ | ||
6835 | /* Reset the string cursor */ | ||
6836 | SyBlobReset(&pOut->sBlob); | ||
6837 | } | ||
6838 | rc = jx9MemObjStringAppend(pOut,(const char *)zIn,iLength); | ||
6839 | /* Update pointer */ | ||
6840 | zIn += iLength; | ||
6841 | break; | ||
6842 | } | ||
6843 | case FJSON_ARRAY_START: { | ||
6844 | /* Binary JSON array */ | ||
6845 | jx9_hashmap *pMap; | ||
6846 | jx9_value sVal; | ||
6847 | /* Allocate a new hashmap */ | ||
6848 | pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); | ||
6849 | if( pMap == 0 ){ | ||
6850 | rc = SXERR_MEM; | ||
6851 | break; | ||
6852 | } | ||
6853 | jx9MemObjInit(pOut->pVm,&sVal); | ||
6854 | jx9MemObjRelease(pOut); | ||
6855 | MemObjSetType(pOut,MEMOBJ_HASHMAP); | ||
6856 | pOut->x.pOther = pMap; | ||
6857 | rc = SXRET_OK; | ||
6858 | for(;;){ | ||
6859 | /* Jump leading binary commas */ | ||
6860 | while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ | ||
6861 | zIn++; | ||
6862 | } | ||
6863 | if( zIn >= zEnd || zIn[0] == FJSON_ARRAY_END ){ | ||
6864 | if( zIn < zEnd ){ | ||
6865 | zIn++; /* Jump the trailing binary ] */ | ||
6866 | } | ||
6867 | break; | ||
6868 | } | ||
6869 | /* Decode the value */ | ||
6870 | rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); | ||
6871 | if( rc != SXRET_OK ){ | ||
6872 | break; | ||
6873 | } | ||
6874 | /* Insert the decoded value */ | ||
6875 | rc = jx9HashmapInsert(pMap,0,&sVal); | ||
6876 | if( rc != UNQLITE_OK ){ | ||
6877 | break; | ||
6878 | } | ||
6879 | } | ||
6880 | if( rc != SXRET_OK ){ | ||
6881 | jx9MemObjRelease(pOut); | ||
6882 | } | ||
6883 | jx9MemObjRelease(&sVal); | ||
6884 | break; | ||
6885 | } | ||
6886 | case FJSON_DOC_START: { | ||
6887 | /* Binary JSON object */ | ||
6888 | jx9_value sVal,sKey; | ||
6889 | jx9_hashmap *pMap; | ||
6890 | /* Allocate a new hashmap */ | ||
6891 | pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); | ||
6892 | if( pMap == 0 ){ | ||
6893 | rc = SXERR_MEM; | ||
6894 | break; | ||
6895 | } | ||
6896 | jx9MemObjInit(pOut->pVm,&sVal); | ||
6897 | jx9MemObjInit(pOut->pVm,&sKey); | ||
6898 | jx9MemObjRelease(pOut); | ||
6899 | MemObjSetType(pOut,MEMOBJ_HASHMAP); | ||
6900 | pOut->x.pOther = pMap; | ||
6901 | rc = SXRET_OK; | ||
6902 | for(;;){ | ||
6903 | /* Jump leading binary commas */ | ||
6904 | while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ | ||
6905 | zIn++; | ||
6906 | } | ||
6907 | if( zIn >= zEnd || zIn[0] == FJSON_DOC_END ){ | ||
6908 | if( zIn < zEnd ){ | ||
6909 | zIn++; /* Jump the trailing binary } */ | ||
6910 | } | ||
6911 | break; | ||
6912 | } | ||
6913 | /* Extract the key */ | ||
6914 | rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sKey,&zIn,iNest+1); | ||
6915 | if( rc != UNQLITE_OK ){ | ||
6916 | break; | ||
6917 | } | ||
6918 | if( zIn >= zEnd || zIn[0] != FJSON_COLON ){ | ||
6919 | rc = UNQLITE_CORRUPT; | ||
6920 | break; | ||
6921 | } | ||
6922 | zIn++; /* Jump the binary colon ':' */ | ||
6923 | if( zIn >= zEnd ){ | ||
6924 | rc = UNQLITE_CORRUPT; | ||
6925 | break; | ||
6926 | } | ||
6927 | /* Decode the value */ | ||
6928 | rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); | ||
6929 | if( rc != SXRET_OK ){ | ||
6930 | break; | ||
6931 | } | ||
6932 | /* Insert the key and its associated value */ | ||
6933 | rc = jx9HashmapInsert(pMap,&sKey,&sVal); | ||
6934 | if( rc != UNQLITE_OK ){ | ||
6935 | break; | ||
6936 | } | ||
6937 | } | ||
6938 | if( rc != SXRET_OK ){ | ||
6939 | jx9MemObjRelease(pOut); | ||
6940 | } | ||
6941 | jx9MemObjRelease(&sVal); | ||
6942 | jx9MemObjRelease(&sKey); | ||
6943 | break; | ||
6944 | } | ||
6945 | default: | ||
6946 | /* Corrupt data */ | ||
6947 | rc = SXERR_CORRUPT; | ||
6948 | break; | ||
6949 | } | ||
6950 | if( pzPtr ){ | ||
6951 | *pzPtr = zIn; | ||
6952 | } | ||
6953 | return rc; | ||
6954 | } | ||
6955 | /* | ||
6956 | * ---------------------------------------------------------- | ||
6957 | * File: jx9_api.c | ||
6958 | * MD5: 73cba599c009cee0ff878666d0543438 | ||
6959 | * ---------------------------------------------------------- | ||
6960 | */ | ||
6961 | /* | ||
6962 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
6963 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
6964 | * Version 1.7.2 | ||
6965 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
6966 | * please contact Symisc Systems via: | ||
6967 | * legal@symisc.net | ||
6968 | * licensing@symisc.net | ||
6969 | * contact@symisc.net | ||
6970 | * or visit: | ||
6971 | * http://jx9.symisc.net/ | ||
6972 | */ | ||
6973 | /* $SymiscID: api.c v1.7 FreeBSD 2012-12-18 06:54 stable <chm@symisc.net> $ */ | ||
6974 | #ifndef JX9_AMALGAMATION | ||
6975 | #include "jx9Int.h" | ||
6976 | #endif | ||
6977 | /* This file implement the public interfaces presented to host-applications. | ||
6978 | * Routines in other files are for internal use by JX9 and should not be | ||
6979 | * accessed by users of the library. | ||
6980 | */ | ||
6981 | #define JX9_ENGINE_MAGIC 0xF874BCD7 | ||
6982 | #define JX9_ENGINE_MISUSE(ENGINE) (ENGINE == 0 || ENGINE->nMagic != JX9_ENGINE_MAGIC) | ||
6983 | #define JX9_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE) | ||
6984 | /* If another thread have released a working instance, the following macros | ||
6985 | * evaluates to true. These macros are only used when the library | ||
6986 | * is built with threading support enabled which is not the case in | ||
6987 | * the default built. | ||
6988 | */ | ||
6989 | #define JX9_THRD_ENGINE_RELEASE(ENGINE) (ENGINE->nMagic != JX9_ENGINE_MAGIC) | ||
6990 | #define JX9_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE) | ||
6991 | /* IMPLEMENTATION: jx9@embedded@symisc 311-12-32 */ | ||
6992 | /* | ||
6993 | * All global variables are collected in the structure named "sJx9MPGlobal". | ||
6994 | * That way it is clear in the code when we are using static variable because | ||
6995 | * its name start with sJx9MPGlobal. | ||
6996 | */ | ||
6997 | static struct Jx9Global_Data | ||
6998 | { | ||
6999 | SyMemBackend sAllocator; /* Global low level memory allocator */ | ||
7000 | #if defined(JX9_ENABLE_THREADS) | ||
7001 | const SyMutexMethods *pMutexMethods; /* Mutex methods */ | ||
7002 | SyMutex *pMutex; /* Global mutex */ | ||
7003 | sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded | ||
7004 | * The threading level can be set using the [jx9_lib_config()] | ||
7005 | * interface with a configuration verb set to | ||
7006 | * JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE or | ||
7007 | * JX9_LIB_CONFIG_THREAD_LEVEL_MULTI | ||
7008 | */ | ||
7009 | #endif | ||
7010 | const jx9_vfs *pVfs; /* Underlying virtual file system */ | ||
7011 | sxi32 nEngine; /* Total number of active engines */ | ||
7012 | jx9 *pEngines; /* List of active engine */ | ||
7013 | sxu32 nMagic; /* Sanity check against library misuse */ | ||
7014 | }sJx9MPGlobal = { | ||
7015 | {0, 0, 0, 0, 0, 0, 0, 0, {0}}, | ||
7016 | #if defined(JX9_ENABLE_THREADS) | ||
7017 | 0, | ||
7018 | 0, | ||
7019 | 0, | ||
7020 | #endif | ||
7021 | 0, | ||
7022 | 0, | ||
7023 | 0, | ||
7024 | 0 | ||
7025 | }; | ||
7026 | #define JX9_LIB_MAGIC 0xEA1495BA | ||
7027 | #define JX9_LIB_MISUSE (sJx9MPGlobal.nMagic != JX9_LIB_MAGIC) | ||
7028 | /* | ||
7029 | * Supported threading level. | ||
7030 | * These options have meaning only when the library is compiled with multi-threading | ||
7031 | * support.That is, the JX9_ENABLE_THREADS compile time directive must be defined | ||
7032 | * when JX9 is built. | ||
7033 | * JX9_THREAD_LEVEL_SINGLE: | ||
7034 | * In this mode, mutexing is disabled and the library can only be used by a single thread. | ||
7035 | * JX9_THREAD_LEVEL_MULTI | ||
7036 | * In this mode, all mutexes including the recursive mutexes on [jx9] objects | ||
7037 | * are enabled so that the application is free to share the same engine | ||
7038 | * between different threads at the same time. | ||
7039 | */ | ||
7040 | #define JX9_THREAD_LEVEL_SINGLE 1 | ||
7041 | #define JX9_THREAD_LEVEL_MULTI 2 | ||
7042 | /* | ||
7043 | * Configure a running JX9 engine instance. | ||
7044 | * return JX9_OK on success.Any other return | ||
7045 | * value indicates failure. | ||
7046 | * Refer to [jx9_config()]. | ||
7047 | */ | ||
7048 | JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap) | ||
7049 | { | ||
7050 | jx9_conf *pConf = &pEngine->xConf; | ||
7051 | int rc = JX9_OK; | ||
7052 | /* Perform the requested operation */ | ||
7053 | switch(nOp){ | ||
7054 | case JX9_CONFIG_ERR_LOG:{ | ||
7055 | /* Extract compile-time error log if any */ | ||
7056 | const char **pzPtr = va_arg(ap, const char **); | ||
7057 | int *pLen = va_arg(ap, int *); | ||
7058 | if( pzPtr == 0 ){ | ||
7059 | rc = JX9_CORRUPT; | ||
7060 | break; | ||
7061 | } | ||
7062 | /* NULL terminate the error-log buffer */ | ||
7063 | SyBlobNullAppend(&pConf->sErrConsumer); | ||
7064 | /* Point to the error-log buffer */ | ||
7065 | *pzPtr = (const char *)SyBlobData(&pConf->sErrConsumer); | ||
7066 | if( pLen ){ | ||
7067 | if( SyBlobLength(&pConf->sErrConsumer) > 1 /* NULL '\0' terminator */ ){ | ||
7068 | *pLen = (int)SyBlobLength(&pConf->sErrConsumer); | ||
7069 | }else{ | ||
7070 | *pLen = 0; | ||
7071 | } | ||
7072 | } | ||
7073 | break; | ||
7074 | } | ||
7075 | case JX9_CONFIG_ERR_ABORT: | ||
7076 | /* Reserved for future use */ | ||
7077 | break; | ||
7078 | default: | ||
7079 | /* Unknown configuration verb */ | ||
7080 | rc = JX9_CORRUPT; | ||
7081 | break; | ||
7082 | } /* Switch() */ | ||
7083 | return rc; | ||
7084 | } | ||
7085 | /* | ||
7086 | * Configure the JX9 library. | ||
7087 | * Return JX9_OK on success. Any other return value indicates failure. | ||
7088 | * Refer to [jx9_lib_config()]. | ||
7089 | */ | ||
7090 | static sxi32 Jx9CoreConfigure(sxi32 nOp, va_list ap) | ||
7091 | { | ||
7092 | int rc = JX9_OK; | ||
7093 | switch(nOp){ | ||
7094 | case JX9_LIB_CONFIG_VFS:{ | ||
7095 | /* Install a virtual file system */ | ||
7096 | const jx9_vfs *pVfs = va_arg(ap, const jx9_vfs *); | ||
7097 | sJx9MPGlobal.pVfs = pVfs; | ||
7098 | break; | ||
7099 | } | ||
7100 | case JX9_LIB_CONFIG_USER_MALLOC: { | ||
7101 | /* Use an alternative low-level memory allocation routines */ | ||
7102 | const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *); | ||
7103 | /* Save the memory failure callback (if available) */ | ||
7104 | ProcMemError xMemErr = sJx9MPGlobal.sAllocator.xMemError; | ||
7105 | void *pMemErr = sJx9MPGlobal.sAllocator.pUserData; | ||
7106 | if( pMethods == 0 ){ | ||
7107 | /* Use the built-in memory allocation subsystem */ | ||
7108 | rc = SyMemBackendInit(&sJx9MPGlobal.sAllocator, xMemErr, pMemErr); | ||
7109 | }else{ | ||
7110 | rc = SyMemBackendInitFromOthers(&sJx9MPGlobal.sAllocator, pMethods, xMemErr, pMemErr); | ||
7111 | } | ||
7112 | break; | ||
7113 | } | ||
7114 | case JX9_LIB_CONFIG_MEM_ERR_CALLBACK: { | ||
7115 | /* Memory failure callback */ | ||
7116 | ProcMemError xMemErr = va_arg(ap, ProcMemError); | ||
7117 | void *pUserData = va_arg(ap, void *); | ||
7118 | sJx9MPGlobal.sAllocator.xMemError = xMemErr; | ||
7119 | sJx9MPGlobal.sAllocator.pUserData = pUserData; | ||
7120 | break; | ||
7121 | } | ||
7122 | case JX9_LIB_CONFIG_USER_MUTEX: { | ||
7123 | #if defined(JX9_ENABLE_THREADS) | ||
7124 | /* Use an alternative low-level mutex subsystem */ | ||
7125 | const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *); | ||
7126 | #if defined (UNTRUST) | ||
7127 | if( pMethods == 0 ){ | ||
7128 | rc = JX9_CORRUPT; | ||
7129 | } | ||
7130 | #endif | ||
7131 | /* Sanity check */ | ||
7132 | if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){ | ||
7133 | /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */ | ||
7134 | rc = JX9_CORRUPT; | ||
7135 | break; | ||
7136 | } | ||
7137 | if( sJx9MPGlobal.pMutexMethods ){ | ||
7138 | /* Overwrite the previous mutex subsystem */ | ||
7139 | SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); | ||
7140 | if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){ | ||
7141 | sJx9MPGlobal.pMutexMethods->xGlobalRelease(); | ||
7142 | } | ||
7143 | sJx9MPGlobal.pMutex = 0; | ||
7144 | } | ||
7145 | /* Initialize and install the new mutex subsystem */ | ||
7146 | if( pMethods->xGlobalInit ){ | ||
7147 | rc = pMethods->xGlobalInit(); | ||
7148 | if ( rc != JX9_OK ){ | ||
7149 | break; | ||
7150 | } | ||
7151 | } | ||
7152 | /* Create the global mutex */ | ||
7153 | sJx9MPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); | ||
7154 | if( sJx9MPGlobal.pMutex == 0 ){ | ||
7155 | /* | ||
7156 | * If the supplied mutex subsystem is so sick that we are unable to | ||
7157 | * create a single mutex, there is no much we can do here. | ||
7158 | */ | ||
7159 | if( pMethods->xGlobalRelease ){ | ||
7160 | pMethods->xGlobalRelease(); | ||
7161 | } | ||
7162 | rc = JX9_CORRUPT; | ||
7163 | break; | ||
7164 | } | ||
7165 | sJx9MPGlobal.pMutexMethods = pMethods; | ||
7166 | if( sJx9MPGlobal.nThreadingLevel == 0 ){ | ||
7167 | /* Set a default threading level */ | ||
7168 | sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI; | ||
7169 | } | ||
7170 | #endif | ||
7171 | break; | ||
7172 | } | ||
7173 | case JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE: | ||
7174 | #if defined(JX9_ENABLE_THREADS) | ||
7175 | /* Single thread mode(Only one thread is allowed to play with the library) */ | ||
7176 | sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_SINGLE; | ||
7177 | #endif | ||
7178 | break; | ||
7179 | case JX9_LIB_CONFIG_THREAD_LEVEL_MULTI: | ||
7180 | #if defined(JX9_ENABLE_THREADS) | ||
7181 | /* Multi-threading mode (library is thread safe and JX9 engines and virtual machines | ||
7182 | * may be shared between multiple threads). | ||
7183 | */ | ||
7184 | sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI; | ||
7185 | #endif | ||
7186 | break; | ||
7187 | default: | ||
7188 | /* Unknown configuration option */ | ||
7189 | rc = JX9_CORRUPT; | ||
7190 | break; | ||
7191 | } | ||
7192 | return rc; | ||
7193 | } | ||
7194 | /* | ||
7195 | * [CAPIREF: jx9_lib_config()] | ||
7196 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7197 | */ | ||
7198 | JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...) | ||
7199 | { | ||
7200 | va_list ap; | ||
7201 | int rc; | ||
7202 | if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){ | ||
7203 | /* Library is already initialized, this operation is forbidden */ | ||
7204 | return JX9_LOOKED; | ||
7205 | } | ||
7206 | va_start(ap, nConfigOp); | ||
7207 | rc = Jx9CoreConfigure(nConfigOp, ap); | ||
7208 | va_end(ap); | ||
7209 | return rc; | ||
7210 | } | ||
7211 | /* | ||
7212 | * Global library initialization | ||
7213 | * Refer to [jx9_lib_init()] | ||
7214 | * This routine must be called to initialize the memory allocation subsystem, the mutex | ||
7215 | * subsystem prior to doing any serious work with the library.The first thread to call | ||
7216 | * this routine does the initialization process and set the magic number so no body later | ||
7217 | * can re-initialize the library.If subsequent threads call this routine before the first | ||
7218 | * thread have finished the initialization process, then the subsequent threads must block | ||
7219 | * until the initialization process is done. | ||
7220 | */ | ||
7221 | static sxi32 Jx9CoreInitialize(void) | ||
7222 | { | ||
7223 | const jx9_vfs *pVfs; /* Built-in vfs */ | ||
7224 | #if defined(JX9_ENABLE_THREADS) | ||
7225 | const SyMutexMethods *pMutexMethods = 0; | ||
7226 | SyMutex *pMaster = 0; | ||
7227 | #endif | ||
7228 | int rc; | ||
7229 | /* | ||
7230 | * If the library is already initialized, then a call to this routine | ||
7231 | * is a no-op. | ||
7232 | */ | ||
7233 | if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){ | ||
7234 | return JX9_OK; /* Already initialized */ | ||
7235 | } | ||
7236 | /* Point to the built-in vfs */ | ||
7237 | pVfs = jx9ExportBuiltinVfs(); | ||
7238 | /* Install it */ | ||
7239 | jx9_lib_config(JX9_LIB_CONFIG_VFS, pVfs); | ||
7240 | #if defined(JX9_ENABLE_THREADS) | ||
7241 | if( sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_SINGLE ){ | ||
7242 | pMutexMethods = sJx9MPGlobal.pMutexMethods; | ||
7243 | if( pMutexMethods == 0 ){ | ||
7244 | /* Use the built-in mutex subsystem */ | ||
7245 | pMutexMethods = SyMutexExportMethods(); | ||
7246 | if( pMutexMethods == 0 ){ | ||
7247 | return JX9_CORRUPT; /* Can't happen */ | ||
7248 | } | ||
7249 | /* Install the mutex subsystem */ | ||
7250 | rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MUTEX, pMutexMethods); | ||
7251 | if( rc != JX9_OK ){ | ||
7252 | return rc; | ||
7253 | } | ||
7254 | } | ||
7255 | /* Obtain a static mutex so we can initialize the library without calling malloc() */ | ||
7256 | pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1); | ||
7257 | if( pMaster == 0 ){ | ||
7258 | return JX9_CORRUPT; /* Can't happen */ | ||
7259 | } | ||
7260 | } | ||
7261 | /* Lock the master mutex */ | ||
7262 | rc = JX9_OK; | ||
7263 | SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ | ||
7264 | if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){ | ||
7265 | #endif | ||
7266 | if( sJx9MPGlobal.sAllocator.pMethods == 0 ){ | ||
7267 | /* Install a memory subsystem */ | ||
7268 | rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */ | ||
7269 | if( rc != JX9_OK ){ | ||
7270 | /* If we are unable to initialize the memory backend, there is no much we can do here.*/ | ||
7271 | goto End; | ||
7272 | } | ||
7273 | } | ||
7274 | #if defined(JX9_ENABLE_THREADS) | ||
7275 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ | ||
7276 | /* Protect the memory allocation subsystem */ | ||
7277 | rc = SyMemBackendMakeThreadSafe(&sJx9MPGlobal.sAllocator, sJx9MPGlobal.pMutexMethods); | ||
7278 | if( rc != JX9_OK ){ | ||
7279 | goto End; | ||
7280 | } | ||
7281 | } | ||
7282 | #endif | ||
7283 | /* Our library is initialized, set the magic number */ | ||
7284 | sJx9MPGlobal.nMagic = JX9_LIB_MAGIC; | ||
7285 | rc = JX9_OK; | ||
7286 | #if defined(JX9_ENABLE_THREADS) | ||
7287 | } /* sJx9MPGlobal.nMagic != JX9_LIB_MAGIC */ | ||
7288 | #endif | ||
7289 | End: | ||
7290 | #if defined(JX9_ENABLE_THREADS) | ||
7291 | /* Unlock the master mutex */ | ||
7292 | SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ | ||
7293 | #endif | ||
7294 | return rc; | ||
7295 | } | ||
7296 | /* | ||
7297 | * Release an active JX9 engine and it's associated active virtual machines. | ||
7298 | */ | ||
7299 | static sxi32 EngineRelease(jx9 *pEngine) | ||
7300 | { | ||
7301 | jx9_vm *pVm, *pNext; | ||
7302 | /* Release all active VM */ | ||
7303 | pVm = pEngine->pVms; | ||
7304 | for(;;){ | ||
7305 | if( pEngine->iVm < 1 ){ | ||
7306 | break; | ||
7307 | } | ||
7308 | pNext = pVm->pNext; | ||
7309 | jx9VmRelease(pVm); | ||
7310 | pVm = pNext; | ||
7311 | pEngine->iVm--; | ||
7312 | } | ||
7313 | /* Set a dummy magic number */ | ||
7314 | pEngine->nMagic = 0x7635; | ||
7315 | /* Release the private memory subsystem */ | ||
7316 | SyMemBackendRelease(&pEngine->sAllocator); | ||
7317 | return JX9_OK; | ||
7318 | } | ||
7319 | /* | ||
7320 | * Release all resources consumed by the library. | ||
7321 | * If JX9 is already shut when this routine is invoked then this | ||
7322 | * routine is a harmless no-op. | ||
7323 | * Note: This call is not thread safe. Refer to [jx9_lib_shutdown()]. | ||
7324 | */ | ||
7325 | static void JX9CoreShutdown(void) | ||
7326 | { | ||
7327 | jx9 *pEngine, *pNext; | ||
7328 | /* Release all active engines first */ | ||
7329 | pEngine = sJx9MPGlobal.pEngines; | ||
7330 | for(;;){ | ||
7331 | if( sJx9MPGlobal.nEngine < 1 ){ | ||
7332 | break; | ||
7333 | } | ||
7334 | pNext = pEngine->pNext; | ||
7335 | EngineRelease(pEngine); | ||
7336 | pEngine = pNext; | ||
7337 | sJx9MPGlobal.nEngine--; | ||
7338 | } | ||
7339 | #if defined(JX9_ENABLE_THREADS) | ||
7340 | /* Release the mutex subsystem */ | ||
7341 | if( sJx9MPGlobal.pMutexMethods ){ | ||
7342 | if( sJx9MPGlobal.pMutex ){ | ||
7343 | SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); | ||
7344 | sJx9MPGlobal.pMutex = 0; | ||
7345 | } | ||
7346 | if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){ | ||
7347 | sJx9MPGlobal.pMutexMethods->xGlobalRelease(); | ||
7348 | } | ||
7349 | sJx9MPGlobal.pMutexMethods = 0; | ||
7350 | } | ||
7351 | sJx9MPGlobal.nThreadingLevel = 0; | ||
7352 | #endif | ||
7353 | if( sJx9MPGlobal.sAllocator.pMethods ){ | ||
7354 | /* Release the memory backend */ | ||
7355 | SyMemBackendRelease(&sJx9MPGlobal.sAllocator); | ||
7356 | } | ||
7357 | sJx9MPGlobal.nMagic = 0x1928; | ||
7358 | } | ||
7359 | /* | ||
7360 | * [CAPIREF: jx9_lib_shutdown()] | ||
7361 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7362 | */ | ||
7363 | JX9_PRIVATE int jx9_lib_shutdown(void) | ||
7364 | { | ||
7365 | if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){ | ||
7366 | /* Already shut */ | ||
7367 | return JX9_OK; | ||
7368 | } | ||
7369 | JX9CoreShutdown(); | ||
7370 | return JX9_OK; | ||
7371 | } | ||
7372 | /* | ||
7373 | * [CAPIREF: jx9_lib_signature()] | ||
7374 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7375 | */ | ||
7376 | JX9_PRIVATE const char * jx9_lib_signature(void) | ||
7377 | { | ||
7378 | return JX9_SIG; | ||
7379 | } | ||
7380 | /* | ||
7381 | * [CAPIREF: jx9_init()] | ||
7382 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7383 | */ | ||
7384 | JX9_PRIVATE int jx9_init(jx9 **ppEngine) | ||
7385 | { | ||
7386 | jx9 *pEngine; | ||
7387 | int rc; | ||
7388 | #if defined(UNTRUST) | ||
7389 | if( ppEngine == 0 ){ | ||
7390 | return JX9_CORRUPT; | ||
7391 | } | ||
7392 | #endif | ||
7393 | *ppEngine = 0; | ||
7394 | /* One-time automatic library initialization */ | ||
7395 | rc = Jx9CoreInitialize(); | ||
7396 | if( rc != JX9_OK ){ | ||
7397 | return rc; | ||
7398 | } | ||
7399 | /* Allocate a new engine */ | ||
7400 | pEngine = (jx9 *)SyMemBackendPoolAlloc(&sJx9MPGlobal.sAllocator, sizeof(jx9)); | ||
7401 | if( pEngine == 0 ){ | ||
7402 | return JX9_NOMEM; | ||
7403 | } | ||
7404 | /* Zero the structure */ | ||
7405 | SyZero(pEngine, sizeof(jx9)); | ||
7406 | /* Initialize engine fields */ | ||
7407 | pEngine->nMagic = JX9_ENGINE_MAGIC; | ||
7408 | rc = SyMemBackendInitFromParent(&pEngine->sAllocator, &sJx9MPGlobal.sAllocator); | ||
7409 | if( rc != JX9_OK ){ | ||
7410 | goto Release; | ||
7411 | } | ||
7412 | #if defined(JX9_ENABLE_THREADS) | ||
7413 | SyMemBackendDisbaleMutexing(&pEngine->sAllocator); | ||
7414 | #endif | ||
7415 | /* Default configuration */ | ||
7416 | SyBlobInit(&pEngine->xConf.sErrConsumer, &pEngine->sAllocator); | ||
7417 | /* Install a default compile-time error consumer routine */ | ||
7418 | pEngine->xConf.xErr = jx9VmBlobConsumer; | ||
7419 | pEngine->xConf.pErrData = &pEngine->xConf.sErrConsumer; | ||
7420 | /* Built-in vfs */ | ||
7421 | pEngine->pVfs = sJx9MPGlobal.pVfs; | ||
7422 | #if defined(JX9_ENABLE_THREADS) | ||
7423 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ | ||
7424 | /* Associate a recursive mutex with this instance */ | ||
7425 | pEngine->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); | ||
7426 | if( pEngine->pMutex == 0 ){ | ||
7427 | rc = JX9_NOMEM; | ||
7428 | goto Release; | ||
7429 | } | ||
7430 | } | ||
7431 | #endif | ||
7432 | /* Link to the list of active engines */ | ||
7433 | #if defined(JX9_ENABLE_THREADS) | ||
7434 | /* Enter the global mutex */ | ||
7435 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ | ||
7436 | #endif | ||
7437 | MACRO_LD_PUSH(sJx9MPGlobal.pEngines, pEngine); | ||
7438 | sJx9MPGlobal.nEngine++; | ||
7439 | #if defined(JX9_ENABLE_THREADS) | ||
7440 | /* Leave the global mutex */ | ||
7441 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ | ||
7442 | #endif | ||
7443 | /* Write a pointer to the new instance */ | ||
7444 | *ppEngine = pEngine; | ||
7445 | return JX9_OK; | ||
7446 | Release: | ||
7447 | SyMemBackendRelease(&pEngine->sAllocator); | ||
7448 | SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator,pEngine); | ||
7449 | return rc; | ||
7450 | } | ||
7451 | /* | ||
7452 | * [CAPIREF: jx9_release()] | ||
7453 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7454 | */ | ||
7455 | JX9_PRIVATE int jx9_release(jx9 *pEngine) | ||
7456 | { | ||
7457 | int rc; | ||
7458 | if( JX9_ENGINE_MISUSE(pEngine) ){ | ||
7459 | return JX9_CORRUPT; | ||
7460 | } | ||
7461 | #if defined(JX9_ENABLE_THREADS) | ||
7462 | /* Acquire engine mutex */ | ||
7463 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7464 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7465 | JX9_THRD_ENGINE_RELEASE(pEngine) ){ | ||
7466 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7467 | } | ||
7468 | #endif | ||
7469 | /* Release the engine */ | ||
7470 | rc = EngineRelease(&(*pEngine)); | ||
7471 | #if defined(JX9_ENABLE_THREADS) | ||
7472 | /* Leave engine mutex */ | ||
7473 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7474 | /* Release engine mutex */ | ||
7475 | SyMutexRelease(sJx9MPGlobal.pMutexMethods, pEngine->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7476 | #endif | ||
7477 | #if defined(JX9_ENABLE_THREADS) | ||
7478 | /* Enter the global mutex */ | ||
7479 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ | ||
7480 | #endif | ||
7481 | /* Unlink from the list of active engines */ | ||
7482 | MACRO_LD_REMOVE(sJx9MPGlobal.pEngines, pEngine); | ||
7483 | sJx9MPGlobal.nEngine--; | ||
7484 | #if defined(JX9_ENABLE_THREADS) | ||
7485 | /* Leave the global mutex */ | ||
7486 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ | ||
7487 | #endif | ||
7488 | /* Release the memory chunk allocated to this engine */ | ||
7489 | SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator, pEngine); | ||
7490 | return rc; | ||
7491 | } | ||
7492 | /* | ||
7493 | * Compile a raw JX9 script. | ||
7494 | * To execute a JX9 code, it must first be compiled into a bytecode program using this routine. | ||
7495 | * If something goes wrong [i.e: compile-time error], your error log [i.e: error consumer callback] | ||
7496 | * should display the appropriate error message and this function set ppVm to null and return | ||
7497 | * an error code that is different from JX9_OK. Otherwise when the script is successfully compiled | ||
7498 | * ppVm should hold the JX9 bytecode and it's safe to call [jx9_vm_exec(), jx9_vm_reset(), etc.]. | ||
7499 | * This API does not actually evaluate the JX9 code. It merely compile and prepares the JX9 script | ||
7500 | * for evaluation. | ||
7501 | */ | ||
7502 | static sxi32 ProcessScript( | ||
7503 | jx9 *pEngine, /* Running JX9 engine */ | ||
7504 | jx9_vm **ppVm, /* OUT: A pointer to the virtual machine */ | ||
7505 | SyString *pScript, /* Raw JX9 script to compile */ | ||
7506 | sxi32 iFlags, /* Compile-time flags */ | ||
7507 | const char *zFilePath /* File path if script come from a file. NULL otherwise */ | ||
7508 | ) | ||
7509 | { | ||
7510 | jx9_vm *pVm; | ||
7511 | int rc; | ||
7512 | /* Allocate a new virtual machine */ | ||
7513 | pVm = (jx9_vm *)SyMemBackendPoolAlloc(&pEngine->sAllocator, sizeof(jx9_vm)); | ||
7514 | if( pVm == 0 ){ | ||
7515 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
7516 | * a tiny chunk of memory, there is no much we can do here. */ | ||
7517 | if( ppVm ){ | ||
7518 | *ppVm = 0; | ||
7519 | } | ||
7520 | return JX9_NOMEM; | ||
7521 | } | ||
7522 | if( iFlags < 0 ){ | ||
7523 | /* Default compile-time flags */ | ||
7524 | iFlags = 0; | ||
7525 | } | ||
7526 | /* Initialize the Virtual Machine */ | ||
7527 | rc = jx9VmInit(pVm, &(*pEngine)); | ||
7528 | if( rc != JX9_OK ){ | ||
7529 | SyMemBackendPoolFree(&pEngine->sAllocator, pVm); | ||
7530 | if( ppVm ){ | ||
7531 | *ppVm = 0; | ||
7532 | } | ||
7533 | return JX9_VM_ERR; | ||
7534 | } | ||
7535 | if( zFilePath ){ | ||
7536 | /* Push processed file path */ | ||
7537 | jx9VmPushFilePath(pVm, zFilePath, -1, TRUE, 0); | ||
7538 | } | ||
7539 | /* Reset the error message consumer */ | ||
7540 | SyBlobReset(&pEngine->xConf.sErrConsumer); | ||
7541 | /* Compile the script */ | ||
7542 | jx9CompileScript(pVm, &(*pScript), iFlags); | ||
7543 | if( pVm->sCodeGen.nErr > 0 || pVm == 0){ | ||
7544 | sxu32 nErr = pVm->sCodeGen.nErr; | ||
7545 | /* Compilation error or null ppVm pointer, release this VM */ | ||
7546 | SyMemBackendRelease(&pVm->sAllocator); | ||
7547 | SyMemBackendPoolFree(&pEngine->sAllocator, pVm); | ||
7548 | if( ppVm ){ | ||
7549 | *ppVm = 0; | ||
7550 | } | ||
7551 | return nErr > 0 ? JX9_COMPILE_ERR : JX9_OK; | ||
7552 | } | ||
7553 | /* Prepare the virtual machine for bytecode execution */ | ||
7554 | rc = jx9VmMakeReady(pVm); | ||
7555 | if( rc != JX9_OK ){ | ||
7556 | goto Release; | ||
7557 | } | ||
7558 | /* Install local import path which is the current directory */ | ||
7559 | jx9_vm_config(pVm, JX9_VM_CONFIG_IMPORT_PATH, "./"); | ||
7560 | #if defined(JX9_ENABLE_THREADS) | ||
7561 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ | ||
7562 | /* Associate a recursive mutex with this instance */ | ||
7563 | pVm->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); | ||
7564 | if( pVm->pMutex == 0 ){ | ||
7565 | goto Release; | ||
7566 | } | ||
7567 | } | ||
7568 | #endif | ||
7569 | /* Script successfully compiled, link to the list of active virtual machines */ | ||
7570 | MACRO_LD_PUSH(pEngine->pVms, pVm); | ||
7571 | pEngine->iVm++; | ||
7572 | /* Point to the freshly created VM */ | ||
7573 | *ppVm = pVm; | ||
7574 | /* Ready to execute JX9 bytecode */ | ||
7575 | return JX9_OK; | ||
7576 | Release: | ||
7577 | SyMemBackendRelease(&pVm->sAllocator); | ||
7578 | SyMemBackendPoolFree(&pEngine->sAllocator, pVm); | ||
7579 | *ppVm = 0; | ||
7580 | return JX9_VM_ERR; | ||
7581 | } | ||
7582 | /* | ||
7583 | * [CAPIREF: jx9_compile()] | ||
7584 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7585 | */ | ||
7586 | JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm) | ||
7587 | { | ||
7588 | SyString sScript; | ||
7589 | int rc; | ||
7590 | if( JX9_ENGINE_MISUSE(pEngine) ){ | ||
7591 | return JX9_CORRUPT; | ||
7592 | } | ||
7593 | if( zSource == 0 ){ | ||
7594 | /* Empty Jx9 statement ';' */ | ||
7595 | zSource = ";"; | ||
7596 | nLen = (int)sizeof(char); | ||
7597 | } | ||
7598 | if( nLen < 0 ){ | ||
7599 | /* Compute input length automatically */ | ||
7600 | nLen = (int)SyStrlen(zSource); | ||
7601 | } | ||
7602 | SyStringInitFromBuf(&sScript, zSource, nLen); | ||
7603 | #if defined(JX9_ENABLE_THREADS) | ||
7604 | /* Acquire engine mutex */ | ||
7605 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7606 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7607 | JX9_THRD_ENGINE_RELEASE(pEngine) ){ | ||
7608 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7609 | } | ||
7610 | #endif | ||
7611 | /* Compile the script */ | ||
7612 | rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,0,0); | ||
7613 | #if defined(JX9_ENABLE_THREADS) | ||
7614 | /* Leave engine mutex */ | ||
7615 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7616 | #endif | ||
7617 | /* Compilation result */ | ||
7618 | return rc; | ||
7619 | } | ||
7620 | /* | ||
7621 | * [CAPIREF: jx9_compile_file()] | ||
7622 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7623 | */ | ||
7624 | JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm) | ||
7625 | { | ||
7626 | const jx9_vfs *pVfs; | ||
7627 | int rc; | ||
7628 | if( ppOutVm ){ | ||
7629 | *ppOutVm = 0; | ||
7630 | } | ||
7631 | rc = JX9_OK; /* cc warning */ | ||
7632 | if( JX9_ENGINE_MISUSE(pEngine) || SX_EMPTY_STR(zFilePath) ){ | ||
7633 | return JX9_CORRUPT; | ||
7634 | } | ||
7635 | #if defined(JX9_ENABLE_THREADS) | ||
7636 | /* Acquire engine mutex */ | ||
7637 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7638 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7639 | JX9_THRD_ENGINE_RELEASE(pEngine) ){ | ||
7640 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7641 | } | ||
7642 | #endif | ||
7643 | /* | ||
7644 | * Check if the underlying vfs implement the memory map | ||
7645 | * [i.e: mmap() under UNIX/MapViewOfFile() under windows] function. | ||
7646 | */ | ||
7647 | pVfs = pEngine->pVfs; | ||
7648 | if( pVfs == 0 || pVfs->xMmap == 0 ){ | ||
7649 | /* Memory map routine not implemented */ | ||
7650 | rc = JX9_IO_ERR; | ||
7651 | }else{ | ||
7652 | void *pMapView = 0; /* cc warning */ | ||
7653 | jx9_int64 nSize = 0; /* cc warning */ | ||
7654 | SyString sScript; | ||
7655 | /* Try to get a memory view of the whole file */ | ||
7656 | rc = pVfs->xMmap(zFilePath, &pMapView, &nSize); | ||
7657 | if( rc != JX9_OK ){ | ||
7658 | /* Assume an IO error */ | ||
7659 | rc = JX9_IO_ERR; | ||
7660 | }else{ | ||
7661 | /* Compile the file */ | ||
7662 | SyStringInitFromBuf(&sScript, pMapView, nSize); | ||
7663 | rc = ProcessScript(&(*pEngine), ppOutVm, &sScript,0,zFilePath); | ||
7664 | /* Release the memory view of the whole file */ | ||
7665 | if( pVfs->xUnmap ){ | ||
7666 | pVfs->xUnmap(pMapView, nSize); | ||
7667 | } | ||
7668 | } | ||
7669 | } | ||
7670 | #if defined(JX9_ENABLE_THREADS) | ||
7671 | /* Leave engine mutex */ | ||
7672 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7673 | #endif | ||
7674 | /* Compilation result */ | ||
7675 | return rc; | ||
7676 | } | ||
7677 | /* | ||
7678 | * [CAPIREF: jx9_vm_config()] | ||
7679 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7680 | */ | ||
7681 | JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...) | ||
7682 | { | ||
7683 | va_list ap; | ||
7684 | int rc; | ||
7685 | /* Ticket 1433-002: NULL VM is harmless operation */ | ||
7686 | if ( JX9_VM_MISUSE(pVm) ){ | ||
7687 | return JX9_CORRUPT; | ||
7688 | } | ||
7689 | #if defined(JX9_ENABLE_THREADS) | ||
7690 | /* Acquire VM mutex */ | ||
7691 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7692 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7693 | JX9_THRD_VM_RELEASE(pVm) ){ | ||
7694 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7695 | } | ||
7696 | #endif | ||
7697 | /* Confiugure the virtual machine */ | ||
7698 | va_start(ap, iConfigOp); | ||
7699 | rc = jx9VmConfigure(&(*pVm), iConfigOp, ap); | ||
7700 | va_end(ap); | ||
7701 | #if defined(JX9_ENABLE_THREADS) | ||
7702 | /* Leave VM mutex */ | ||
7703 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7704 | #endif | ||
7705 | return rc; | ||
7706 | } | ||
7707 | /* | ||
7708 | * [CAPIREF: jx9_vm_release()] | ||
7709 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7710 | */ | ||
7711 | JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm) | ||
7712 | { | ||
7713 | jx9 *pEngine; | ||
7714 | int rc; | ||
7715 | /* Ticket 1433-002: NULL VM is harmless operation */ | ||
7716 | if ( JX9_VM_MISUSE(pVm) ){ | ||
7717 | return JX9_CORRUPT; | ||
7718 | } | ||
7719 | #if defined(JX9_ENABLE_THREADS) | ||
7720 | /* Acquire VM mutex */ | ||
7721 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7722 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7723 | JX9_THRD_VM_RELEASE(pVm) ){ | ||
7724 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7725 | } | ||
7726 | #endif | ||
7727 | pEngine = pVm->pEngine; | ||
7728 | rc = jx9VmRelease(&(*pVm)); | ||
7729 | #if defined(JX9_ENABLE_THREADS) | ||
7730 | /* Leave VM mutex */ | ||
7731 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7732 | /* Release VM mutex */ | ||
7733 | SyMutexRelease(sJx9MPGlobal.pMutexMethods, pVm->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7734 | #endif | ||
7735 | if( rc == JX9_OK ){ | ||
7736 | /* Unlink from the list of active VM */ | ||
7737 | #if defined(JX9_ENABLE_THREADS) | ||
7738 | /* Acquire engine mutex */ | ||
7739 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7740 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7741 | JX9_THRD_ENGINE_RELEASE(pEngine) ){ | ||
7742 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7743 | } | ||
7744 | #endif | ||
7745 | MACRO_LD_REMOVE(pEngine->pVms, pVm); | ||
7746 | pEngine->iVm--; | ||
7747 | /* Release the memory chunk allocated to this VM */ | ||
7748 | SyMemBackendPoolFree(&pEngine->sAllocator, pVm); | ||
7749 | #if defined(JX9_ENABLE_THREADS) | ||
7750 | /* Leave engine mutex */ | ||
7751 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7752 | #endif | ||
7753 | } | ||
7754 | return rc; | ||
7755 | } | ||
7756 | /* | ||
7757 | * [CAPIREF: jx9_create_function()] | ||
7758 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7759 | */ | ||
7760 | JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData) | ||
7761 | { | ||
7762 | SyString sName; | ||
7763 | int rc; | ||
7764 | /* Ticket 1433-002: NULL VM is harmless operation */ | ||
7765 | if ( JX9_VM_MISUSE(pVm) ){ | ||
7766 | return JX9_CORRUPT; | ||
7767 | } | ||
7768 | SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); | ||
7769 | /* Remove leading and trailing white spaces */ | ||
7770 | SyStringFullTrim(&sName); | ||
7771 | /* Ticket 1433-003: NULL values are not allowed */ | ||
7772 | if( sName.nByte < 1 || xFunc == 0 ){ | ||
7773 | return JX9_CORRUPT; | ||
7774 | } | ||
7775 | #if defined(JX9_ENABLE_THREADS) | ||
7776 | /* Acquire VM mutex */ | ||
7777 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7778 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7779 | JX9_THRD_VM_RELEASE(pVm) ){ | ||
7780 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7781 | } | ||
7782 | #endif | ||
7783 | /* Install the foreign function */ | ||
7784 | rc = jx9VmInstallForeignFunction(&(*pVm), &sName, xFunc, pUserData); | ||
7785 | #if defined(JX9_ENABLE_THREADS) | ||
7786 | /* Leave VM mutex */ | ||
7787 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7788 | #endif | ||
7789 | return rc; | ||
7790 | } | ||
7791 | JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName) | ||
7792 | { | ||
7793 | jx9_user_func *pFunc = 0; /* cc warning */ | ||
7794 | int rc; | ||
7795 | /* Perform the deletion */ | ||
7796 | rc = SyHashDeleteEntry(&pVm->hHostFunction, (const void *)zName, SyStrlen(zName), (void **)&pFunc); | ||
7797 | if( rc == JX9_OK ){ | ||
7798 | /* Release internal fields */ | ||
7799 | SySetRelease(&pFunc->aAux); | ||
7800 | SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName)); | ||
7801 | SyMemBackendPoolFree(&pVm->sAllocator, pFunc); | ||
7802 | } | ||
7803 | return rc; | ||
7804 | } | ||
7805 | /* | ||
7806 | * [CAPIREF: jx9_create_constant()] | ||
7807 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7808 | */ | ||
7809 | JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData) | ||
7810 | { | ||
7811 | SyString sName; | ||
7812 | int rc; | ||
7813 | /* Ticket 1433-002: NULL VM is harmless operation */ | ||
7814 | if ( JX9_VM_MISUSE(pVm) ){ | ||
7815 | return JX9_CORRUPT; | ||
7816 | } | ||
7817 | SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); | ||
7818 | /* Remove leading and trailing white spaces */ | ||
7819 | SyStringFullTrim(&sName); | ||
7820 | if( sName.nByte < 1 ){ | ||
7821 | /* Empty constant name */ | ||
7822 | return JX9_CORRUPT; | ||
7823 | } | ||
7824 | /* TICKET 1433-003: NULL pointer is harmless operation */ | ||
7825 | if( xExpand == 0 ){ | ||
7826 | return JX9_CORRUPT; | ||
7827 | } | ||
7828 | #if defined(JX9_ENABLE_THREADS) | ||
7829 | /* Acquire VM mutex */ | ||
7830 | SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7831 | if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && | ||
7832 | JX9_THRD_VM_RELEASE(pVm) ){ | ||
7833 | return JX9_ABORT; /* Another thread have released this instance */ | ||
7834 | } | ||
7835 | #endif | ||
7836 | /* Perform the registration */ | ||
7837 | rc = jx9VmRegisterConstant(&(*pVm), &sName, xExpand, pUserData); | ||
7838 | #if defined(JX9_ENABLE_THREADS) | ||
7839 | /* Leave VM mutex */ | ||
7840 | SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ | ||
7841 | #endif | ||
7842 | return rc; | ||
7843 | } | ||
7844 | JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName) | ||
7845 | { | ||
7846 | jx9_constant *pCons; | ||
7847 | int rc; | ||
7848 | /* Query the constant hashtable */ | ||
7849 | rc = SyHashDeleteEntry(&pVm->hConstant, (const void *)zName, SyStrlen(zName), (void **)&pCons); | ||
7850 | if( rc == JX9_OK ){ | ||
7851 | /* Perform the deletion */ | ||
7852 | SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pCons->sName)); | ||
7853 | SyMemBackendPoolFree(&pVm->sAllocator, pCons); | ||
7854 | } | ||
7855 | return rc; | ||
7856 | } | ||
7857 | /* | ||
7858 | * [CAPIREF: jx9_new_scalar()] | ||
7859 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7860 | */ | ||
7861 | JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm) | ||
7862 | { | ||
7863 | jx9_value *pObj; | ||
7864 | /* Ticket 1433-002: NULL VM is harmless operation */ | ||
7865 | if ( JX9_VM_MISUSE(pVm) ){ | ||
7866 | return 0; | ||
7867 | } | ||
7868 | /* Allocate a new scalar variable */ | ||
7869 | pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value)); | ||
7870 | if( pObj == 0 ){ | ||
7871 | return 0; | ||
7872 | } | ||
7873 | /* Nullify the new scalar */ | ||
7874 | jx9MemObjInit(pVm, pObj); | ||
7875 | return pObj; | ||
7876 | } | ||
7877 | /* | ||
7878 | * [CAPIREF: jx9_new_array()] | ||
7879 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7880 | */ | ||
7881 | JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm) | ||
7882 | { | ||
7883 | jx9_hashmap *pMap; | ||
7884 | jx9_value *pObj; | ||
7885 | /* Ticket 1433-002: NULL VM is harmless operation */ | ||
7886 | if ( JX9_VM_MISUSE(pVm) ){ | ||
7887 | return 0; | ||
7888 | } | ||
7889 | /* Create a new hashmap first */ | ||
7890 | pMap = jx9NewHashmap(&(*pVm), 0, 0); | ||
7891 | if( pMap == 0 ){ | ||
7892 | return 0; | ||
7893 | } | ||
7894 | /* Associate a new jx9_value with this hashmap */ | ||
7895 | pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value)); | ||
7896 | if( pObj == 0 ){ | ||
7897 | jx9HashmapRelease(pMap, TRUE); | ||
7898 | return 0; | ||
7899 | } | ||
7900 | jx9MemObjInitFromArray(pVm, pObj, pMap); | ||
7901 | return pObj; | ||
7902 | } | ||
7903 | /* | ||
7904 | * [CAPIREF: jx9_release_value()] | ||
7905 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7906 | */ | ||
7907 | JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue) | ||
7908 | { | ||
7909 | /* Ticket 1433-002: NULL VM is a harmless operation */ | ||
7910 | if ( JX9_VM_MISUSE(pVm) ){ | ||
7911 | return JX9_CORRUPT; | ||
7912 | } | ||
7913 | if( pValue ){ | ||
7914 | /* Release the value */ | ||
7915 | jx9MemObjRelease(pValue); | ||
7916 | SyMemBackendPoolFree(&pVm->sAllocator, pValue); | ||
7917 | } | ||
7918 | return JX9_OK; | ||
7919 | } | ||
7920 | /* | ||
7921 | * [CAPIREF: jx9_value_to_int()] | ||
7922 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7923 | */ | ||
7924 | JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue) | ||
7925 | { | ||
7926 | int rc; | ||
7927 | rc = jx9MemObjToInteger(pValue); | ||
7928 | if( rc != JX9_OK ){ | ||
7929 | return 0; | ||
7930 | } | ||
7931 | return (int)pValue->x.iVal; | ||
7932 | } | ||
7933 | /* | ||
7934 | * [CAPIREF: jx9_value_to_bool()] | ||
7935 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7936 | */ | ||
7937 | JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue) | ||
7938 | { | ||
7939 | int rc; | ||
7940 | rc = jx9MemObjToBool(pValue); | ||
7941 | if( rc != JX9_OK ){ | ||
7942 | return 0; | ||
7943 | } | ||
7944 | return (int)pValue->x.iVal; | ||
7945 | } | ||
7946 | /* | ||
7947 | * [CAPIREF: jx9_value_to_int64()] | ||
7948 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7949 | */ | ||
7950 | JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue) | ||
7951 | { | ||
7952 | int rc; | ||
7953 | rc = jx9MemObjToInteger(pValue); | ||
7954 | if( rc != JX9_OK ){ | ||
7955 | return 0; | ||
7956 | } | ||
7957 | return pValue->x.iVal; | ||
7958 | } | ||
7959 | /* | ||
7960 | * [CAPIREF: jx9_value_to_double()] | ||
7961 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7962 | */ | ||
7963 | JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue) | ||
7964 | { | ||
7965 | int rc; | ||
7966 | rc = jx9MemObjToReal(pValue); | ||
7967 | if( rc != JX9_OK ){ | ||
7968 | return (double)0; | ||
7969 | } | ||
7970 | return (double)pValue->x.rVal; | ||
7971 | } | ||
7972 | /* | ||
7973 | * [CAPIREF: jx9_value_to_string()] | ||
7974 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7975 | */ | ||
7976 | JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen) | ||
7977 | { | ||
7978 | jx9MemObjToString(pValue); | ||
7979 | if( SyBlobLength(&pValue->sBlob) > 0 ){ | ||
7980 | SyBlobNullAppend(&pValue->sBlob); | ||
7981 | if( pLen ){ | ||
7982 | *pLen = (int)SyBlobLength(&pValue->sBlob); | ||
7983 | } | ||
7984 | return (const char *)SyBlobData(&pValue->sBlob); | ||
7985 | }else{ | ||
7986 | /* Return the empty string */ | ||
7987 | if( pLen ){ | ||
7988 | *pLen = 0; | ||
7989 | } | ||
7990 | return ""; | ||
7991 | } | ||
7992 | } | ||
7993 | /* | ||
7994 | * [CAPIREF: jx9_value_to_resource()] | ||
7995 | * Please refer to the official documentation for function purpose and expected parameters. | ||
7996 | */ | ||
7997 | JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue) | ||
7998 | { | ||
7999 | if( (pValue->iFlags & MEMOBJ_RES) == 0 ){ | ||
8000 | /* Not a resource, return NULL */ | ||
8001 | return 0; | ||
8002 | } | ||
8003 | return pValue->x.pOther; | ||
8004 | } | ||
8005 | /* | ||
8006 | * [CAPIREF: jx9_value_compare()] | ||
8007 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8008 | */ | ||
8009 | JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict) | ||
8010 | { | ||
8011 | int rc; | ||
8012 | if( pLeft == 0 || pRight == 0 ){ | ||
8013 | /* TICKET 1433-24: NULL values is harmless operation */ | ||
8014 | return 1; | ||
8015 | } | ||
8016 | /* Perform the comparison */ | ||
8017 | rc = jx9MemObjCmp(&(*pLeft), &(*pRight), bStrict, 0); | ||
8018 | /* Comparison result */ | ||
8019 | return rc; | ||
8020 | } | ||
8021 | /* | ||
8022 | * [CAPIREF: jx9_result_int()] | ||
8023 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8024 | */ | ||
8025 | JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue) | ||
8026 | { | ||
8027 | return jx9_value_int(pCtx->pRet, iValue); | ||
8028 | } | ||
8029 | /* | ||
8030 | * [CAPIREF: jx9_result_int64()] | ||
8031 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8032 | */ | ||
8033 | JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue) | ||
8034 | { | ||
8035 | return jx9_value_int64(pCtx->pRet, iValue); | ||
8036 | } | ||
8037 | /* | ||
8038 | * [CAPIREF: jx9_result_bool()] | ||
8039 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8040 | */ | ||
8041 | JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool) | ||
8042 | { | ||
8043 | return jx9_value_bool(pCtx->pRet, iBool); | ||
8044 | } | ||
8045 | /* | ||
8046 | * [CAPIREF: jx9_result_double()] | ||
8047 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8048 | */ | ||
8049 | JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value) | ||
8050 | { | ||
8051 | return jx9_value_double(pCtx->pRet, Value); | ||
8052 | } | ||
8053 | /* | ||
8054 | * [CAPIREF: jx9_result_null()] | ||
8055 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8056 | */ | ||
8057 | JX9_PRIVATE int jx9_result_null(jx9_context *pCtx) | ||
8058 | { | ||
8059 | /* Invalidate any prior representation and set the NULL flag */ | ||
8060 | jx9MemObjRelease(pCtx->pRet); | ||
8061 | return JX9_OK; | ||
8062 | } | ||
8063 | /* | ||
8064 | * [CAPIREF: jx9_result_string()] | ||
8065 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8066 | */ | ||
8067 | JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen) | ||
8068 | { | ||
8069 | return jx9_value_string(pCtx->pRet, zString, nLen); | ||
8070 | } | ||
8071 | /* | ||
8072 | * [CAPIREF: jx9_result_string_format()] | ||
8073 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8074 | */ | ||
8075 | JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...) | ||
8076 | { | ||
8077 | jx9_value *p; | ||
8078 | va_list ap; | ||
8079 | int rc; | ||
8080 | p = pCtx->pRet; | ||
8081 | if( (p->iFlags & MEMOBJ_STRING) == 0 ){ | ||
8082 | /* Invalidate any prior representation */ | ||
8083 | jx9MemObjRelease(p); | ||
8084 | MemObjSetType(p, MEMOBJ_STRING); | ||
8085 | } | ||
8086 | /* Format the given string */ | ||
8087 | va_start(ap, zFormat); | ||
8088 | rc = SyBlobFormatAp(&p->sBlob, zFormat, ap); | ||
8089 | va_end(ap); | ||
8090 | return rc; | ||
8091 | } | ||
8092 | /* | ||
8093 | * [CAPIREF: jx9_result_value()] | ||
8094 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8095 | */ | ||
8096 | JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue) | ||
8097 | { | ||
8098 | int rc = JX9_OK; | ||
8099 | if( pValue == 0 ){ | ||
8100 | jx9MemObjRelease(pCtx->pRet); | ||
8101 | }else{ | ||
8102 | rc = jx9MemObjStore(pValue, pCtx->pRet); | ||
8103 | } | ||
8104 | return rc; | ||
8105 | } | ||
8106 | /* | ||
8107 | * [CAPIREF: jx9_result_resource()] | ||
8108 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8109 | */ | ||
8110 | JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData) | ||
8111 | { | ||
8112 | return jx9_value_resource(pCtx->pRet, pUserData); | ||
8113 | } | ||
8114 | /* | ||
8115 | * [CAPIREF: jx9_context_new_scalar()] | ||
8116 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8117 | */ | ||
8118 | JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx) | ||
8119 | { | ||
8120 | jx9_value *pVal; | ||
8121 | pVal = jx9_new_scalar(pCtx->pVm); | ||
8122 | if( pVal ){ | ||
8123 | /* Record value address so it can be freed automatically | ||
8124 | * when the calling function returns. | ||
8125 | */ | ||
8126 | SySetPut(&pCtx->sVar, (const void *)&pVal); | ||
8127 | } | ||
8128 | return pVal; | ||
8129 | } | ||
8130 | /* | ||
8131 | * [CAPIREF: jx9_context_new_array()] | ||
8132 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8133 | */ | ||
8134 | JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx) | ||
8135 | { | ||
8136 | jx9_value *pVal; | ||
8137 | pVal = jx9_new_array(pCtx->pVm); | ||
8138 | if( pVal ){ | ||
8139 | /* Record value address so it can be freed automatically | ||
8140 | * when the calling function returns. | ||
8141 | */ | ||
8142 | SySetPut(&pCtx->sVar, (const void *)&pVal); | ||
8143 | } | ||
8144 | return pVal; | ||
8145 | } | ||
8146 | /* | ||
8147 | * [CAPIREF: jx9_context_release_value()] | ||
8148 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8149 | */ | ||
8150 | JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue) | ||
8151 | { | ||
8152 | jx9VmReleaseContextValue(&(*pCtx), pValue); | ||
8153 | } | ||
8154 | /* | ||
8155 | * [CAPIREF: jx9_context_alloc_chunk()] | ||
8156 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8157 | */ | ||
8158 | JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease) | ||
8159 | { | ||
8160 | void *pChunk; | ||
8161 | pChunk = SyMemBackendAlloc(&pCtx->pVm->sAllocator, nByte); | ||
8162 | if( pChunk ){ | ||
8163 | if( ZeroChunk ){ | ||
8164 | /* Zero the memory chunk */ | ||
8165 | SyZero(pChunk, nByte); | ||
8166 | } | ||
8167 | if( AutoRelease ){ | ||
8168 | jx9_aux_data sAux; | ||
8169 | /* Track the chunk so that it can be released automatically | ||
8170 | * upon this context is destroyed. | ||
8171 | */ | ||
8172 | sAux.pAuxData = pChunk; | ||
8173 | SySetPut(&pCtx->sChunk, (const void *)&sAux); | ||
8174 | } | ||
8175 | } | ||
8176 | return pChunk; | ||
8177 | } | ||
8178 | /* | ||
8179 | * Check if the given chunk address is registered in the call context | ||
8180 | * chunk container. | ||
8181 | * Return TRUE if registered.FALSE otherwise. | ||
8182 | * Refer to [jx9_context_realloc_chunk(), jx9_context_free_chunk()]. | ||
8183 | */ | ||
8184 | static jx9_aux_data * ContextFindChunk(jx9_context *pCtx, void *pChunk) | ||
8185 | { | ||
8186 | jx9_aux_data *aAux, *pAux; | ||
8187 | sxu32 n; | ||
8188 | if( SySetUsed(&pCtx->sChunk) < 1 ){ | ||
8189 | /* Don't bother processing, the container is empty */ | ||
8190 | return 0; | ||
8191 | } | ||
8192 | /* Perform the lookup */ | ||
8193 | aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk); | ||
8194 | for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ | ||
8195 | pAux = &aAux[n]; | ||
8196 | if( pAux->pAuxData == pChunk ){ | ||
8197 | /* Chunk found */ | ||
8198 | return pAux; | ||
8199 | } | ||
8200 | } | ||
8201 | /* No such allocated chunk */ | ||
8202 | return 0; | ||
8203 | } | ||
8204 | /* | ||
8205 | * [CAPIREF: jx9_context_realloc_chunk()] | ||
8206 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8207 | */ | ||
8208 | JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte) | ||
8209 | { | ||
8210 | jx9_aux_data *pAux; | ||
8211 | void *pNew; | ||
8212 | pNew = SyMemBackendRealloc(&pCtx->pVm->sAllocator, pChunk, nByte); | ||
8213 | if( pNew ){ | ||
8214 | pAux = ContextFindChunk(pCtx, pChunk); | ||
8215 | if( pAux ){ | ||
8216 | pAux->pAuxData = pNew; | ||
8217 | } | ||
8218 | } | ||
8219 | return pNew; | ||
8220 | } | ||
8221 | /* | ||
8222 | * [CAPIREF: jx9_context_free_chunk()] | ||
8223 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8224 | */ | ||
8225 | JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk) | ||
8226 | { | ||
8227 | jx9_aux_data *pAux; | ||
8228 | if( pChunk == 0 ){ | ||
8229 | /* TICKET-1433-93: NULL chunk is a harmless operation */ | ||
8230 | return; | ||
8231 | } | ||
8232 | pAux = ContextFindChunk(pCtx, pChunk); | ||
8233 | if( pAux ){ | ||
8234 | /* Mark as destroyed */ | ||
8235 | pAux->pAuxData = 0; | ||
8236 | } | ||
8237 | SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk); | ||
8238 | } | ||
8239 | /* | ||
8240 | * [CAPIREF: jx9_array_fetch()] | ||
8241 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8242 | */ | ||
8243 | JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte) | ||
8244 | { | ||
8245 | jx9_hashmap_node *pNode; | ||
8246 | jx9_value *pValue; | ||
8247 | jx9_value skey; | ||
8248 | int rc; | ||
8249 | /* Make sure we are dealing with a valid hashmap */ | ||
8250 | if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
8251 | return 0; | ||
8252 | } | ||
8253 | if( nByte < 0 ){ | ||
8254 | nByte = (int)SyStrlen(zKey); | ||
8255 | } | ||
8256 | /* Convert the key to a jx9_value */ | ||
8257 | jx9MemObjInit(pArray->pVm, &skey); | ||
8258 | jx9MemObjStringAppend(&skey, zKey, (sxu32)nByte); | ||
8259 | /* Perform the lookup */ | ||
8260 | rc = jx9HashmapLookup((jx9_hashmap *)pArray->x.pOther, &skey, &pNode); | ||
8261 | jx9MemObjRelease(&skey); | ||
8262 | if( rc != JX9_OK ){ | ||
8263 | /* No such entry */ | ||
8264 | return 0; | ||
8265 | } | ||
8266 | /* Extract the target value */ | ||
8267 | pValue = (jx9_value *)SySetAt(&pArray->pVm->aMemObj, pNode->nValIdx); | ||
8268 | return pValue; | ||
8269 | } | ||
8270 | /* | ||
8271 | * [CAPIREF: jx9_array_walk()] | ||
8272 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8273 | */ | ||
8274 | JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *pValue, jx9_value *, void *), void *pUserData) | ||
8275 | { | ||
8276 | int rc; | ||
8277 | if( xWalk == 0 ){ | ||
8278 | return JX9_CORRUPT; | ||
8279 | } | ||
8280 | /* Make sure we are dealing with a valid hashmap */ | ||
8281 | if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
8282 | return JX9_CORRUPT; | ||
8283 | } | ||
8284 | /* Start the walk process */ | ||
8285 | rc = jx9HashmapWalk((jx9_hashmap *)pArray->x.pOther, xWalk, pUserData); | ||
8286 | return rc != JX9_OK ? JX9_ABORT /* User callback request an operation abort*/ : JX9_OK; | ||
8287 | } | ||
8288 | /* | ||
8289 | * [CAPIREF: jx9_array_add_elem()] | ||
8290 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8291 | */ | ||
8292 | JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue) | ||
8293 | { | ||
8294 | int rc; | ||
8295 | /* Make sure we are dealing with a valid hashmap */ | ||
8296 | if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
8297 | return JX9_CORRUPT; | ||
8298 | } | ||
8299 | /* Perform the insertion */ | ||
8300 | rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &(*pKey), &(*pValue)); | ||
8301 | return rc; | ||
8302 | } | ||
8303 | /* | ||
8304 | * [CAPIREF: jx9_array_add_strkey_elem()] | ||
8305 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8306 | */ | ||
8307 | JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue) | ||
8308 | { | ||
8309 | int rc; | ||
8310 | /* Make sure we are dealing with a valid hashmap */ | ||
8311 | if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
8312 | return JX9_CORRUPT; | ||
8313 | } | ||
8314 | /* Perform the insertion */ | ||
8315 | if( SX_EMPTY_STR(zKey) ){ | ||
8316 | /* Empty key, assign an automatic index */ | ||
8317 | rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, 0, &(*pValue)); | ||
8318 | }else{ | ||
8319 | jx9_value sKey; | ||
8320 | jx9MemObjInitFromString(pArray->pVm, &sKey, 0); | ||
8321 | jx9MemObjStringAppend(&sKey, zKey, (sxu32)SyStrlen(zKey)); | ||
8322 | rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &sKey, &(*pValue)); | ||
8323 | jx9MemObjRelease(&sKey); | ||
8324 | } | ||
8325 | return rc; | ||
8326 | } | ||
8327 | /* | ||
8328 | * [CAPIREF: jx9_array_count()] | ||
8329 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8330 | */ | ||
8331 | JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray) | ||
8332 | { | ||
8333 | jx9_hashmap *pMap; | ||
8334 | /* Make sure we are dealing with a valid hashmap */ | ||
8335 | if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
8336 | return 0; | ||
8337 | } | ||
8338 | /* Point to the internal representation of the hashmap */ | ||
8339 | pMap = (jx9_hashmap *)pArray->x.pOther; | ||
8340 | return pMap->nEntry; | ||
8341 | } | ||
8342 | /* | ||
8343 | * [CAPIREF: jx9_context_output()] | ||
8344 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8345 | */ | ||
8346 | JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen) | ||
8347 | { | ||
8348 | SyString sData; | ||
8349 | int rc; | ||
8350 | if( nLen < 0 ){ | ||
8351 | nLen = (int)SyStrlen(zString); | ||
8352 | } | ||
8353 | SyStringInitFromBuf(&sData, zString, nLen); | ||
8354 | rc = jx9VmOutputConsume(pCtx->pVm, &sData); | ||
8355 | return rc; | ||
8356 | } | ||
8357 | /* | ||
8358 | * [CAPIREF: jx9_context_throw_error()] | ||
8359 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8360 | */ | ||
8361 | JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr) | ||
8362 | { | ||
8363 | int rc = JX9_OK; | ||
8364 | if( zErr ){ | ||
8365 | rc = jx9VmThrowError(pCtx->pVm, &pCtx->pFunc->sName, iErr, zErr); | ||
8366 | } | ||
8367 | return rc; | ||
8368 | } | ||
8369 | /* | ||
8370 | * [CAPIREF: jx9_context_throw_error_format()] | ||
8371 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8372 | */ | ||
8373 | JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...) | ||
8374 | { | ||
8375 | va_list ap; | ||
8376 | int rc; | ||
8377 | if( zFormat == 0){ | ||
8378 | return JX9_OK; | ||
8379 | } | ||
8380 | va_start(ap, zFormat); | ||
8381 | rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap); | ||
8382 | va_end(ap); | ||
8383 | return rc; | ||
8384 | } | ||
8385 | /* | ||
8386 | * [CAPIREF: jx9_context_random_num()] | ||
8387 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8388 | */ | ||
8389 | JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx) | ||
8390 | { | ||
8391 | sxu32 n; | ||
8392 | n = jx9VmRandomNum(pCtx->pVm); | ||
8393 | return n; | ||
8394 | } | ||
8395 | /* | ||
8396 | * [CAPIREF: jx9_context_random_string()] | ||
8397 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8398 | */ | ||
8399 | JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen) | ||
8400 | { | ||
8401 | if( nBuflen < 3 ){ | ||
8402 | return JX9_CORRUPT; | ||
8403 | } | ||
8404 | jx9VmRandomString(pCtx->pVm, zBuf, nBuflen); | ||
8405 | return JX9_OK; | ||
8406 | } | ||
8407 | /* | ||
8408 | * [CAPIREF: jx9_context_user_data()] | ||
8409 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8410 | */ | ||
8411 | JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx) | ||
8412 | { | ||
8413 | return pCtx->pFunc->pUserData; | ||
8414 | } | ||
8415 | /* | ||
8416 | * [CAPIREF: jx9_context_push_aux_data()] | ||
8417 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8418 | */ | ||
8419 | JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData) | ||
8420 | { | ||
8421 | jx9_aux_data sAux; | ||
8422 | int rc; | ||
8423 | sAux.pAuxData = pUserData; | ||
8424 | rc = SySetPut(&pCtx->pFunc->aAux, (const void *)&sAux); | ||
8425 | return rc; | ||
8426 | } | ||
8427 | /* | ||
8428 | * [CAPIREF: jx9_context_peek_aux_data()] | ||
8429 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8430 | */ | ||
8431 | JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx) | ||
8432 | { | ||
8433 | jx9_aux_data *pAux; | ||
8434 | pAux = (jx9_aux_data *)SySetPeek(&pCtx->pFunc->aAux); | ||
8435 | return pAux ? pAux->pAuxData : 0; | ||
8436 | } | ||
8437 | /* | ||
8438 | * [CAPIREF: jx9_context_pop_aux_data()] | ||
8439 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8440 | */ | ||
8441 | JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx) | ||
8442 | { | ||
8443 | jx9_aux_data *pAux; | ||
8444 | pAux = (jx9_aux_data *)SySetPop(&pCtx->pFunc->aAux); | ||
8445 | return pAux ? pAux->pAuxData : 0; | ||
8446 | } | ||
8447 | /* | ||
8448 | * [CAPIREF: jx9_context_result_buf_length()] | ||
8449 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8450 | */ | ||
8451 | JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx) | ||
8452 | { | ||
8453 | return SyBlobLength(&pCtx->pRet->sBlob); | ||
8454 | } | ||
8455 | /* | ||
8456 | * [CAPIREF: jx9_function_name()] | ||
8457 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8458 | */ | ||
8459 | JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx) | ||
8460 | { | ||
8461 | SyString *pName; | ||
8462 | pName = &pCtx->pFunc->sName; | ||
8463 | return pName->zString; | ||
8464 | } | ||
8465 | /* | ||
8466 | * [CAPIREF: jx9_value_int()] | ||
8467 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8468 | */ | ||
8469 | JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue) | ||
8470 | { | ||
8471 | /* Invalidate any prior representation */ | ||
8472 | jx9MemObjRelease(pVal); | ||
8473 | pVal->x.iVal = (jx9_int64)iValue; | ||
8474 | MemObjSetType(pVal, MEMOBJ_INT); | ||
8475 | return JX9_OK; | ||
8476 | } | ||
8477 | /* | ||
8478 | * [CAPIREF: jx9_value_int64()] | ||
8479 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8480 | */ | ||
8481 | JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue) | ||
8482 | { | ||
8483 | /* Invalidate any prior representation */ | ||
8484 | jx9MemObjRelease(pVal); | ||
8485 | pVal->x.iVal = iValue; | ||
8486 | MemObjSetType(pVal, MEMOBJ_INT); | ||
8487 | return JX9_OK; | ||
8488 | } | ||
8489 | /* | ||
8490 | * [CAPIREF: jx9_value_bool()] | ||
8491 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8492 | */ | ||
8493 | JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool) | ||
8494 | { | ||
8495 | /* Invalidate any prior representation */ | ||
8496 | jx9MemObjRelease(pVal); | ||
8497 | pVal->x.iVal = iBool ? 1 : 0; | ||
8498 | MemObjSetType(pVal, MEMOBJ_BOOL); | ||
8499 | return JX9_OK; | ||
8500 | } | ||
8501 | /* | ||
8502 | * [CAPIREF: jx9_value_null()] | ||
8503 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8504 | */ | ||
8505 | JX9_PRIVATE int jx9_value_null(jx9_value *pVal) | ||
8506 | { | ||
8507 | /* Invalidate any prior representation and set the NULL flag */ | ||
8508 | jx9MemObjRelease(pVal); | ||
8509 | return JX9_OK; | ||
8510 | } | ||
8511 | /* | ||
8512 | * [CAPIREF: jx9_value_double()] | ||
8513 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8514 | */ | ||
8515 | JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value) | ||
8516 | { | ||
8517 | /* Invalidate any prior representation */ | ||
8518 | jx9MemObjRelease(pVal); | ||
8519 | pVal->x.rVal = (jx9_real)Value; | ||
8520 | MemObjSetType(pVal, MEMOBJ_REAL); | ||
8521 | /* Try to get an integer representation also */ | ||
8522 | jx9MemObjTryInteger(pVal); | ||
8523 | return JX9_OK; | ||
8524 | } | ||
8525 | /* | ||
8526 | * [CAPIREF: jx9_value_string()] | ||
8527 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8528 | */ | ||
8529 | JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen) | ||
8530 | { | ||
8531 | if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ | ||
8532 | /* Invalidate any prior representation */ | ||
8533 | jx9MemObjRelease(pVal); | ||
8534 | MemObjSetType(pVal, MEMOBJ_STRING); | ||
8535 | } | ||
8536 | if( zString ){ | ||
8537 | if( nLen < 0 ){ | ||
8538 | /* Compute length automatically */ | ||
8539 | nLen = (int)SyStrlen(zString); | ||
8540 | } | ||
8541 | SyBlobAppend(&pVal->sBlob, (const void *)zString, (sxu32)nLen); | ||
8542 | } | ||
8543 | return JX9_OK; | ||
8544 | } | ||
8545 | /* | ||
8546 | * [CAPIREF: jx9_value_string_format()] | ||
8547 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8548 | */ | ||
8549 | JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...) | ||
8550 | { | ||
8551 | va_list ap; | ||
8552 | int rc; | ||
8553 | if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ | ||
8554 | /* Invalidate any prior representation */ | ||
8555 | jx9MemObjRelease(pVal); | ||
8556 | MemObjSetType(pVal, MEMOBJ_STRING); | ||
8557 | } | ||
8558 | va_start(ap, zFormat); | ||
8559 | rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap); | ||
8560 | va_end(ap); | ||
8561 | return JX9_OK; | ||
8562 | } | ||
8563 | /* | ||
8564 | * [CAPIREF: jx9_value_reset_string_cursor()] | ||
8565 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8566 | */ | ||
8567 | JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal) | ||
8568 | { | ||
8569 | /* Reset the string cursor */ | ||
8570 | SyBlobReset(&pVal->sBlob); | ||
8571 | return JX9_OK; | ||
8572 | } | ||
8573 | /* | ||
8574 | * [CAPIREF: jx9_value_resource()] | ||
8575 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8576 | */ | ||
8577 | JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData) | ||
8578 | { | ||
8579 | /* Invalidate any prior representation */ | ||
8580 | jx9MemObjRelease(pVal); | ||
8581 | /* Reflect the new type */ | ||
8582 | pVal->x.pOther = pUserData; | ||
8583 | MemObjSetType(pVal, MEMOBJ_RES); | ||
8584 | return JX9_OK; | ||
8585 | } | ||
8586 | /* | ||
8587 | * [CAPIREF: jx9_value_release()] | ||
8588 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8589 | */ | ||
8590 | JX9_PRIVATE int jx9_value_release(jx9_value *pVal) | ||
8591 | { | ||
8592 | jx9MemObjRelease(pVal); | ||
8593 | return JX9_OK; | ||
8594 | } | ||
8595 | /* | ||
8596 | * [CAPIREF: jx9_value_is_int()] | ||
8597 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8598 | */ | ||
8599 | JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal) | ||
8600 | { | ||
8601 | return (pVal->iFlags & MEMOBJ_INT) ? TRUE : FALSE; | ||
8602 | } | ||
8603 | /* | ||
8604 | * [CAPIREF: jx9_value_is_float()] | ||
8605 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8606 | */ | ||
8607 | JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal) | ||
8608 | { | ||
8609 | return (pVal->iFlags & MEMOBJ_REAL) ? TRUE : FALSE; | ||
8610 | } | ||
8611 | /* | ||
8612 | * [CAPIREF: jx9_value_is_bool()] | ||
8613 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8614 | */ | ||
8615 | JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal) | ||
8616 | { | ||
8617 | return (pVal->iFlags & MEMOBJ_BOOL) ? TRUE : FALSE; | ||
8618 | } | ||
8619 | /* | ||
8620 | * [CAPIREF: jx9_value_is_string()] | ||
8621 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8622 | */ | ||
8623 | JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal) | ||
8624 | { | ||
8625 | return (pVal->iFlags & MEMOBJ_STRING) ? TRUE : FALSE; | ||
8626 | } | ||
8627 | /* | ||
8628 | * [CAPIREF: jx9_value_is_null()] | ||
8629 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8630 | */ | ||
8631 | JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal) | ||
8632 | { | ||
8633 | return (pVal->iFlags & MEMOBJ_NULL) ? TRUE : FALSE; | ||
8634 | } | ||
8635 | /* | ||
8636 | * [CAPIREF: jx9_value_is_numeric()] | ||
8637 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8638 | */ | ||
8639 | JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal) | ||
8640 | { | ||
8641 | int rc; | ||
8642 | rc = jx9MemObjIsNumeric(pVal); | ||
8643 | return rc; | ||
8644 | } | ||
8645 | /* | ||
8646 | * [CAPIREF: jx9_value_is_callable()] | ||
8647 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8648 | */ | ||
8649 | JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal) | ||
8650 | { | ||
8651 | int rc; | ||
8652 | rc = jx9VmIsCallable(pVal->pVm, pVal); | ||
8653 | return rc; | ||
8654 | } | ||
8655 | /* | ||
8656 | * [CAPIREF: jx9_value_is_scalar()] | ||
8657 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8658 | */ | ||
8659 | JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal) | ||
8660 | { | ||
8661 | return (pVal->iFlags & MEMOBJ_SCALAR) ? TRUE : FALSE; | ||
8662 | } | ||
8663 | /* | ||
8664 | * [CAPIREF: jx9_value_is_json_array()] | ||
8665 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8666 | */ | ||
8667 | JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal) | ||
8668 | { | ||
8669 | return (pVal->iFlags & MEMOBJ_HASHMAP) ? TRUE : FALSE; | ||
8670 | } | ||
8671 | /* | ||
8672 | * [CAPIREF: jx9_value_is_json_object()] | ||
8673 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8674 | */ | ||
8675 | JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal) | ||
8676 | { | ||
8677 | jx9_hashmap *pMap; | ||
8678 | if( (pVal->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
8679 | return FALSE; | ||
8680 | } | ||
8681 | pMap = (jx9_hashmap *)pVal->x.pOther; | ||
8682 | if( (pMap->iFlags & HASHMAP_JSON_OBJECT) == 0 ){ | ||
8683 | return FALSE; | ||
8684 | } | ||
8685 | return TRUE; | ||
8686 | } | ||
8687 | /* | ||
8688 | * [CAPIREF: jx9_value_is_resource()] | ||
8689 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8690 | */ | ||
8691 | JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal) | ||
8692 | { | ||
8693 | return (pVal->iFlags & MEMOBJ_RES) ? TRUE : FALSE; | ||
8694 | } | ||
8695 | /* | ||
8696 | * [CAPIREF: jx9_value_is_empty()] | ||
8697 | * Please refer to the official documentation for function purpose and expected parameters. | ||
8698 | */ | ||
8699 | JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal) | ||
8700 | { | ||
8701 | int rc; | ||
8702 | rc = jx9MemObjIsEmpty(pVal); | ||
8703 | return rc; | ||
8704 | } | ||
8705 | /* | ||
8706 | * ---------------------------------------------------------- | ||
8707 | * File: jx9_builtin.c | ||
8708 | * MD5: 97ae6ddf8ded9fe14634060675e12f80 | ||
8709 | * ---------------------------------------------------------- | ||
8710 | */ | ||
8711 | /* | ||
8712 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
8713 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
8714 | * Version 1.7.2 | ||
8715 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
8716 | * please contact Symisc Systems via: | ||
8717 | * legal@symisc.net | ||
8718 | * licensing@symisc.net | ||
8719 | * contact@symisc.net | ||
8720 | * or visit: | ||
8721 | * http://jx9.symisc.net/ | ||
8722 | */ | ||
8723 | /* $SymiscID: builtin.c v1.7 Win7 2012-12-13 00:01 stable <chm@symisc.net> $ */ | ||
8724 | #ifndef JX9_AMALGAMATION | ||
8725 | #include "jx9Int.h" | ||
8726 | #endif | ||
8727 | /* This file implement built-in 'foreign' functions for the JX9 engine */ | ||
8728 | /* | ||
8729 | * Section: | ||
8730 | * Variable handling Functions. | ||
8731 | * Authors: | ||
8732 | * Symisc Systems, devel@symisc.net. | ||
8733 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
8734 | * Status: | ||
8735 | * Stable. | ||
8736 | */ | ||
8737 | /* | ||
8738 | * bool is_bool($var) | ||
8739 | * Finds out whether a variable is a boolean. | ||
8740 | * Parameters | ||
8741 | * $var: The variable being evaluated. | ||
8742 | * Return | ||
8743 | * TRUE if var is a boolean. False otherwise. | ||
8744 | */ | ||
8745 | static int jx9Builtin_is_bool(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8746 | { | ||
8747 | int res = 0; /* Assume false by default */ | ||
8748 | if( nArg > 0 ){ | ||
8749 | res = jx9_value_is_bool(apArg[0]); | ||
8750 | } | ||
8751 | /* Query result */ | ||
8752 | jx9_result_bool(pCtx, res); | ||
8753 | return JX9_OK; | ||
8754 | } | ||
8755 | /* | ||
8756 | * bool is_float($var) | ||
8757 | * bool is_real($var) | ||
8758 | * bool is_double($var) | ||
8759 | * Finds out whether a variable is a float. | ||
8760 | * Parameters | ||
8761 | * $var: The variable being evaluated. | ||
8762 | * Return | ||
8763 | * TRUE if var is a float. False otherwise. | ||
8764 | */ | ||
8765 | static int jx9Builtin_is_float(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8766 | { | ||
8767 | int res = 0; /* Assume false by default */ | ||
8768 | if( nArg > 0 ){ | ||
8769 | res = jx9_value_is_float(apArg[0]); | ||
8770 | } | ||
8771 | /* Query result */ | ||
8772 | jx9_result_bool(pCtx, res); | ||
8773 | return JX9_OK; | ||
8774 | } | ||
8775 | /* | ||
8776 | * bool is_int($var) | ||
8777 | * bool is_integer($var) | ||
8778 | * bool is_long($var) | ||
8779 | * Finds out whether a variable is an integer. | ||
8780 | * Parameters | ||
8781 | * $var: The variable being evaluated. | ||
8782 | * Return | ||
8783 | * TRUE if var is an integer. False otherwise. | ||
8784 | */ | ||
8785 | static int jx9Builtin_is_int(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8786 | { | ||
8787 | int res = 0; /* Assume false by default */ | ||
8788 | if( nArg > 0 ){ | ||
8789 | res = jx9_value_is_int(apArg[0]); | ||
8790 | } | ||
8791 | /* Query result */ | ||
8792 | jx9_result_bool(pCtx, res); | ||
8793 | return JX9_OK; | ||
8794 | } | ||
8795 | /* | ||
8796 | * bool is_string($var) | ||
8797 | * Finds out whether a variable is a string. | ||
8798 | * Parameters | ||
8799 | * $var: The variable being evaluated. | ||
8800 | * Return | ||
8801 | * TRUE if var is string. False otherwise. | ||
8802 | */ | ||
8803 | static int jx9Builtin_is_string(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8804 | { | ||
8805 | int res = 0; /* Assume false by default */ | ||
8806 | if( nArg > 0 ){ | ||
8807 | res = jx9_value_is_string(apArg[0]); | ||
8808 | } | ||
8809 | /* Query result */ | ||
8810 | jx9_result_bool(pCtx, res); | ||
8811 | return JX9_OK; | ||
8812 | } | ||
8813 | /* | ||
8814 | * bool is_null($var) | ||
8815 | * Finds out whether a variable is NULL. | ||
8816 | * Parameters | ||
8817 | * $var: The variable being evaluated. | ||
8818 | * Return | ||
8819 | * TRUE if var is NULL. False otherwise. | ||
8820 | */ | ||
8821 | static int jx9Builtin_is_null(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8822 | { | ||
8823 | int res = 0; /* Assume false by default */ | ||
8824 | if( nArg > 0 ){ | ||
8825 | res = jx9_value_is_null(apArg[0]); | ||
8826 | } | ||
8827 | /* Query result */ | ||
8828 | jx9_result_bool(pCtx, res); | ||
8829 | return JX9_OK; | ||
8830 | } | ||
8831 | /* | ||
8832 | * bool is_numeric($var) | ||
8833 | * Find out whether a variable is NULL. | ||
8834 | * Parameters | ||
8835 | * $var: The variable being evaluated. | ||
8836 | * Return | ||
8837 | * True if var is numeric. False otherwise. | ||
8838 | */ | ||
8839 | static int jx9Builtin_is_numeric(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8840 | { | ||
8841 | int res = 0; /* Assume false by default */ | ||
8842 | if( nArg > 0 ){ | ||
8843 | res = jx9_value_is_numeric(apArg[0]); | ||
8844 | } | ||
8845 | /* Query result */ | ||
8846 | jx9_result_bool(pCtx, res); | ||
8847 | return JX9_OK; | ||
8848 | } | ||
8849 | /* | ||
8850 | * bool is_scalar($var) | ||
8851 | * Find out whether a variable is a scalar. | ||
8852 | * Parameters | ||
8853 | * $var: The variable being evaluated. | ||
8854 | * Return | ||
8855 | * True if var is scalar. False otherwise. | ||
8856 | */ | ||
8857 | static int jx9Builtin_is_scalar(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8858 | { | ||
8859 | int res = 0; /* Assume false by default */ | ||
8860 | if( nArg > 0 ){ | ||
8861 | res = jx9_value_is_scalar(apArg[0]); | ||
8862 | } | ||
8863 | /* Query result */ | ||
8864 | jx9_result_bool(pCtx, res); | ||
8865 | return JX9_OK; | ||
8866 | } | ||
8867 | /* | ||
8868 | * bool is_array($var) | ||
8869 | * Find out whether a variable is an array. | ||
8870 | * Parameters | ||
8871 | * $var: The variable being evaluated. | ||
8872 | * Return | ||
8873 | * True if var is an array. False otherwise. | ||
8874 | */ | ||
8875 | static int jx9Builtin_is_array(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8876 | { | ||
8877 | int res = 0; /* Assume false by default */ | ||
8878 | if( nArg > 0 ){ | ||
8879 | res = jx9_value_is_json_array(apArg[0]); | ||
8880 | } | ||
8881 | /* Query result */ | ||
8882 | jx9_result_bool(pCtx, res); | ||
8883 | return JX9_OK; | ||
8884 | } | ||
8885 | /* | ||
8886 | * bool is_object($var) | ||
8887 | * Find out whether a variable is an object. | ||
8888 | * Parameters | ||
8889 | * $var: The variable being evaluated. | ||
8890 | * Return | ||
8891 | * True if var is an object. False otherwise. | ||
8892 | */ | ||
8893 | static int jx9Builtin_is_object(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8894 | { | ||
8895 | int res = 0; /* Assume false by default */ | ||
8896 | if( nArg > 0 ){ | ||
8897 | res = jx9_value_is_json_object(apArg[0]); | ||
8898 | } | ||
8899 | /* Query result */ | ||
8900 | jx9_result_bool(pCtx, res); | ||
8901 | return JX9_OK; | ||
8902 | } | ||
8903 | /* | ||
8904 | * bool is_resource($var) | ||
8905 | * Find out whether a variable is a resource. | ||
8906 | * Parameters | ||
8907 | * $var: The variable being evaluated. | ||
8908 | * Return | ||
8909 | * True if a resource. False otherwise. | ||
8910 | */ | ||
8911 | static int jx9Builtin_is_resource(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8912 | { | ||
8913 | int res = 0; /* Assume false by default */ | ||
8914 | if( nArg > 0 ){ | ||
8915 | res = jx9_value_is_resource(apArg[0]); | ||
8916 | } | ||
8917 | jx9_result_bool(pCtx, res); | ||
8918 | return JX9_OK; | ||
8919 | } | ||
8920 | /* | ||
8921 | * float floatval($var) | ||
8922 | * Get float value of a variable. | ||
8923 | * Parameter | ||
8924 | * $var: The variable being processed. | ||
8925 | * Return | ||
8926 | * the float value of a variable. | ||
8927 | */ | ||
8928 | static int jx9Builtin_floatval(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8929 | { | ||
8930 | if( nArg < 1 ){ | ||
8931 | /* return 0.0 */ | ||
8932 | jx9_result_double(pCtx, 0); | ||
8933 | }else{ | ||
8934 | double dval; | ||
8935 | /* Perform the cast */ | ||
8936 | dval = jx9_value_to_double(apArg[0]); | ||
8937 | jx9_result_double(pCtx, dval); | ||
8938 | } | ||
8939 | return JX9_OK; | ||
8940 | } | ||
8941 | /* | ||
8942 | * int intval($var) | ||
8943 | * Get integer value of a variable. | ||
8944 | * Parameter | ||
8945 | * $var: The variable being processed. | ||
8946 | * Return | ||
8947 | * the int value of a variable. | ||
8948 | */ | ||
8949 | static int jx9Builtin_intval(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8950 | { | ||
8951 | if( nArg < 1 ){ | ||
8952 | /* return 0 */ | ||
8953 | jx9_result_int(pCtx, 0); | ||
8954 | }else{ | ||
8955 | sxi64 iVal; | ||
8956 | /* Perform the cast */ | ||
8957 | iVal = jx9_value_to_int64(apArg[0]); | ||
8958 | jx9_result_int64(pCtx, iVal); | ||
8959 | } | ||
8960 | return JX9_OK; | ||
8961 | } | ||
8962 | /* | ||
8963 | * string strval($var) | ||
8964 | * Get the string representation of a variable. | ||
8965 | * Parameter | ||
8966 | * $var: The variable being processed. | ||
8967 | * Return | ||
8968 | * the string value of a variable. | ||
8969 | */ | ||
8970 | static int jx9Builtin_strval(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8971 | { | ||
8972 | if( nArg < 1 ){ | ||
8973 | /* return NULL */ | ||
8974 | jx9_result_null(pCtx); | ||
8975 | }else{ | ||
8976 | const char *zVal; | ||
8977 | int iLen = 0; /* cc -O6 warning */ | ||
8978 | /* Perform the cast */ | ||
8979 | zVal = jx9_value_to_string(apArg[0], &iLen); | ||
8980 | jx9_result_string(pCtx, zVal, iLen); | ||
8981 | } | ||
8982 | return JX9_OK; | ||
8983 | } | ||
8984 | /* | ||
8985 | * bool empty($var) | ||
8986 | * Determine whether a variable is empty. | ||
8987 | * Parameters | ||
8988 | * $var: The variable being checked. | ||
8989 | * Return | ||
8990 | * 0 if var has a non-empty and non-zero value.1 otherwise. | ||
8991 | */ | ||
8992 | static int jx9Builtin_empty(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
8993 | { | ||
8994 | int res = 1; /* Assume empty by default */ | ||
8995 | if( nArg > 0 ){ | ||
8996 | res = jx9_value_is_empty(apArg[0]); | ||
8997 | } | ||
8998 | jx9_result_bool(pCtx, res); | ||
8999 | return JX9_OK; | ||
9000 | |||
9001 | } | ||
9002 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
9003 | #ifdef JX9_ENABLE_MATH_FUNC | ||
9004 | /* | ||
9005 | * Section: | ||
9006 | * Math Functions. | ||
9007 | * Authors: | ||
9008 | * Symisc Systems, devel@symisc.net. | ||
9009 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
9010 | * Status: | ||
9011 | * Stable. | ||
9012 | */ | ||
9013 | #include <stdlib.h> /* abs */ | ||
9014 | #include <math.h> | ||
9015 | /* | ||
9016 | * float sqrt(float $arg ) | ||
9017 | * Square root of the given number. | ||
9018 | * Parameter | ||
9019 | * The number to process. | ||
9020 | * Return | ||
9021 | * The square root of arg or the special value Nan of failure. | ||
9022 | */ | ||
9023 | static int jx9Builtin_sqrt(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9024 | { | ||
9025 | double r, x; | ||
9026 | if( nArg < 1 ){ | ||
9027 | /* Missing argument, return 0 */ | ||
9028 | jx9_result_int(pCtx, 0); | ||
9029 | return JX9_OK; | ||
9030 | } | ||
9031 | x = jx9_value_to_double(apArg[0]); | ||
9032 | /* Perform the requested operation */ | ||
9033 | r = sqrt(x); | ||
9034 | /* store the result back */ | ||
9035 | jx9_result_double(pCtx, r); | ||
9036 | return JX9_OK; | ||
9037 | } | ||
9038 | /* | ||
9039 | * float exp(float $arg ) | ||
9040 | * Calculates the exponent of e. | ||
9041 | * Parameter | ||
9042 | * The number to process. | ||
9043 | * Return | ||
9044 | * 'e' raised to the power of arg. | ||
9045 | */ | ||
9046 | static int jx9Builtin_exp(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9047 | { | ||
9048 | double r, x; | ||
9049 | if( nArg < 1 ){ | ||
9050 | /* Missing argument, return 0 */ | ||
9051 | jx9_result_int(pCtx, 0); | ||
9052 | return JX9_OK; | ||
9053 | } | ||
9054 | x = jx9_value_to_double(apArg[0]); | ||
9055 | /* Perform the requested operation */ | ||
9056 | r = exp(x); | ||
9057 | /* store the result back */ | ||
9058 | jx9_result_double(pCtx, r); | ||
9059 | return JX9_OK; | ||
9060 | } | ||
9061 | /* | ||
9062 | * float floor(float $arg ) | ||
9063 | * Round fractions down. | ||
9064 | * Parameter | ||
9065 | * The number to process. | ||
9066 | * Return | ||
9067 | * Returns the next lowest integer value by rounding down value if necessary. | ||
9068 | */ | ||
9069 | static int jx9Builtin_floor(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9070 | { | ||
9071 | double r, x; | ||
9072 | if( nArg < 1 ){ | ||
9073 | /* Missing argument, return 0 */ | ||
9074 | jx9_result_int(pCtx, 0); | ||
9075 | return JX9_OK; | ||
9076 | } | ||
9077 | x = jx9_value_to_double(apArg[0]); | ||
9078 | /* Perform the requested operation */ | ||
9079 | r = floor(x); | ||
9080 | /* store the result back */ | ||
9081 | jx9_result_double(pCtx, r); | ||
9082 | return JX9_OK; | ||
9083 | } | ||
9084 | /* | ||
9085 | * float cos(float $arg ) | ||
9086 | * Cosine. | ||
9087 | * Parameter | ||
9088 | * The number to process. | ||
9089 | * Return | ||
9090 | * The cosine of arg. | ||
9091 | */ | ||
9092 | static int jx9Builtin_cos(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9093 | { | ||
9094 | double r, x; | ||
9095 | if( nArg < 1 ){ | ||
9096 | /* Missing argument, return 0 */ | ||
9097 | jx9_result_int(pCtx, 0); | ||
9098 | return JX9_OK; | ||
9099 | } | ||
9100 | x = jx9_value_to_double(apArg[0]); | ||
9101 | /* Perform the requested operation */ | ||
9102 | r = cos(x); | ||
9103 | /* store the result back */ | ||
9104 | jx9_result_double(pCtx, r); | ||
9105 | return JX9_OK; | ||
9106 | } | ||
9107 | /* | ||
9108 | * float acos(float $arg ) | ||
9109 | * Arc cosine. | ||
9110 | * Parameter | ||
9111 | * The number to process. | ||
9112 | * Return | ||
9113 | * The arc cosine of arg. | ||
9114 | */ | ||
9115 | static int jx9Builtin_acos(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9116 | { | ||
9117 | double r, x; | ||
9118 | if( nArg < 1 ){ | ||
9119 | /* Missing argument, return 0 */ | ||
9120 | jx9_result_int(pCtx, 0); | ||
9121 | return JX9_OK; | ||
9122 | } | ||
9123 | x = jx9_value_to_double(apArg[0]); | ||
9124 | /* Perform the requested operation */ | ||
9125 | r = acos(x); | ||
9126 | /* store the result back */ | ||
9127 | jx9_result_double(pCtx, r); | ||
9128 | return JX9_OK; | ||
9129 | } | ||
9130 | /* | ||
9131 | * float cosh(float $arg ) | ||
9132 | * Hyperbolic cosine. | ||
9133 | * Parameter | ||
9134 | * The number to process. | ||
9135 | * Return | ||
9136 | * The hyperbolic cosine of arg. | ||
9137 | */ | ||
9138 | static int jx9Builtin_cosh(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9139 | { | ||
9140 | double r, x; | ||
9141 | if( nArg < 1 ){ | ||
9142 | /* Missing argument, return 0 */ | ||
9143 | jx9_result_int(pCtx, 0); | ||
9144 | return JX9_OK; | ||
9145 | } | ||
9146 | x = jx9_value_to_double(apArg[0]); | ||
9147 | /* Perform the requested operation */ | ||
9148 | r = cosh(x); | ||
9149 | /* store the result back */ | ||
9150 | jx9_result_double(pCtx, r); | ||
9151 | return JX9_OK; | ||
9152 | } | ||
9153 | /* | ||
9154 | * float sin(float $arg ) | ||
9155 | * Sine. | ||
9156 | * Parameter | ||
9157 | * The number to process. | ||
9158 | * Return | ||
9159 | * The sine of arg. | ||
9160 | */ | ||
9161 | static int jx9Builtin_sin(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9162 | { | ||
9163 | double r, x; | ||
9164 | if( nArg < 1 ){ | ||
9165 | /* Missing argument, return 0 */ | ||
9166 | jx9_result_int(pCtx, 0); | ||
9167 | return JX9_OK; | ||
9168 | } | ||
9169 | x = jx9_value_to_double(apArg[0]); | ||
9170 | /* Perform the requested operation */ | ||
9171 | r = sin(x); | ||
9172 | /* store the result back */ | ||
9173 | jx9_result_double(pCtx, r); | ||
9174 | return JX9_OK; | ||
9175 | } | ||
9176 | /* | ||
9177 | * float asin(float $arg ) | ||
9178 | * Arc sine. | ||
9179 | * Parameter | ||
9180 | * The number to process. | ||
9181 | * Return | ||
9182 | * The arc sine of arg. | ||
9183 | */ | ||
9184 | static int jx9Builtin_asin(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9185 | { | ||
9186 | double r, x; | ||
9187 | if( nArg < 1 ){ | ||
9188 | /* Missing argument, return 0 */ | ||
9189 | jx9_result_int(pCtx, 0); | ||
9190 | return JX9_OK; | ||
9191 | } | ||
9192 | x = jx9_value_to_double(apArg[0]); | ||
9193 | /* Perform the requested operation */ | ||
9194 | r = asin(x); | ||
9195 | /* store the result back */ | ||
9196 | jx9_result_double(pCtx, r); | ||
9197 | return JX9_OK; | ||
9198 | } | ||
9199 | /* | ||
9200 | * float sinh(float $arg ) | ||
9201 | * Hyperbolic sine. | ||
9202 | * Parameter | ||
9203 | * The number to process. | ||
9204 | * Return | ||
9205 | * The hyperbolic sine of arg. | ||
9206 | */ | ||
9207 | static int jx9Builtin_sinh(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9208 | { | ||
9209 | double r, x; | ||
9210 | if( nArg < 1 ){ | ||
9211 | /* Missing argument, return 0 */ | ||
9212 | jx9_result_int(pCtx, 0); | ||
9213 | return JX9_OK; | ||
9214 | } | ||
9215 | x = jx9_value_to_double(apArg[0]); | ||
9216 | /* Perform the requested operation */ | ||
9217 | r = sinh(x); | ||
9218 | /* store the result back */ | ||
9219 | jx9_result_double(pCtx, r); | ||
9220 | return JX9_OK; | ||
9221 | } | ||
9222 | /* | ||
9223 | * float ceil(float $arg ) | ||
9224 | * Round fractions up. | ||
9225 | * Parameter | ||
9226 | * The number to process. | ||
9227 | * Return | ||
9228 | * The next highest integer value by rounding up value if necessary. | ||
9229 | */ | ||
9230 | static int jx9Builtin_ceil(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9231 | { | ||
9232 | double r, x; | ||
9233 | if( nArg < 1 ){ | ||
9234 | /* Missing argument, return 0 */ | ||
9235 | jx9_result_int(pCtx, 0); | ||
9236 | return JX9_OK; | ||
9237 | } | ||
9238 | x = jx9_value_to_double(apArg[0]); | ||
9239 | /* Perform the requested operation */ | ||
9240 | r = ceil(x); | ||
9241 | /* store the result back */ | ||
9242 | jx9_result_double(pCtx, r); | ||
9243 | return JX9_OK; | ||
9244 | } | ||
9245 | /* | ||
9246 | * float tan(float $arg ) | ||
9247 | * Tangent. | ||
9248 | * Parameter | ||
9249 | * The number to process. | ||
9250 | * Return | ||
9251 | * The tangent of arg. | ||
9252 | */ | ||
9253 | static int jx9Builtin_tan(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9254 | { | ||
9255 | double r, x; | ||
9256 | if( nArg < 1 ){ | ||
9257 | /* Missing argument, return 0 */ | ||
9258 | jx9_result_int(pCtx, 0); | ||
9259 | return JX9_OK; | ||
9260 | } | ||
9261 | x = jx9_value_to_double(apArg[0]); | ||
9262 | /* Perform the requested operation */ | ||
9263 | r = tan(x); | ||
9264 | /* store the result back */ | ||
9265 | jx9_result_double(pCtx, r); | ||
9266 | return JX9_OK; | ||
9267 | } | ||
9268 | /* | ||
9269 | * float atan(float $arg ) | ||
9270 | * Arc tangent. | ||
9271 | * Parameter | ||
9272 | * The number to process. | ||
9273 | * Return | ||
9274 | * The arc tangent of arg. | ||
9275 | */ | ||
9276 | static int jx9Builtin_atan(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9277 | { | ||
9278 | double r, x; | ||
9279 | if( nArg < 1 ){ | ||
9280 | /* Missing argument, return 0 */ | ||
9281 | jx9_result_int(pCtx, 0); | ||
9282 | return JX9_OK; | ||
9283 | } | ||
9284 | x = jx9_value_to_double(apArg[0]); | ||
9285 | /* Perform the requested operation */ | ||
9286 | r = atan(x); | ||
9287 | /* store the result back */ | ||
9288 | jx9_result_double(pCtx, r); | ||
9289 | return JX9_OK; | ||
9290 | } | ||
9291 | /* | ||
9292 | * float tanh(float $arg ) | ||
9293 | * Hyperbolic tangent. | ||
9294 | * Parameter | ||
9295 | * The number to process. | ||
9296 | * Return | ||
9297 | * The Hyperbolic tangent of arg. | ||
9298 | */ | ||
9299 | static int jx9Builtin_tanh(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9300 | { | ||
9301 | double r, x; | ||
9302 | if( nArg < 1 ){ | ||
9303 | /* Missing argument, return 0 */ | ||
9304 | jx9_result_int(pCtx, 0); | ||
9305 | return JX9_OK; | ||
9306 | } | ||
9307 | x = jx9_value_to_double(apArg[0]); | ||
9308 | /* Perform the requested operation */ | ||
9309 | r = tanh(x); | ||
9310 | /* store the result back */ | ||
9311 | jx9_result_double(pCtx, r); | ||
9312 | return JX9_OK; | ||
9313 | } | ||
9314 | /* | ||
9315 | * float atan2(float $y, float $x) | ||
9316 | * Arc tangent of two variable. | ||
9317 | * Parameter | ||
9318 | * $y = Dividend parameter. | ||
9319 | * $x = Divisor parameter. | ||
9320 | * Return | ||
9321 | * The arc tangent of y/x in radian. | ||
9322 | */ | ||
9323 | static int jx9Builtin_atan2(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9324 | { | ||
9325 | double r, x, y; | ||
9326 | if( nArg < 2 ){ | ||
9327 | /* Missing arguments, return 0 */ | ||
9328 | jx9_result_int(pCtx, 0); | ||
9329 | return JX9_OK; | ||
9330 | } | ||
9331 | y = jx9_value_to_double(apArg[0]); | ||
9332 | x = jx9_value_to_double(apArg[1]); | ||
9333 | /* Perform the requested operation */ | ||
9334 | r = atan2(y, x); | ||
9335 | /* store the result back */ | ||
9336 | jx9_result_double(pCtx, r); | ||
9337 | return JX9_OK; | ||
9338 | } | ||
9339 | /* | ||
9340 | * float/int64 abs(float/int64 $arg ) | ||
9341 | * Absolute value. | ||
9342 | * Parameter | ||
9343 | * The number to process. | ||
9344 | * Return | ||
9345 | * The absolute value of number. | ||
9346 | */ | ||
9347 | static int jx9Builtin_abs(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9348 | { | ||
9349 | int is_float; | ||
9350 | if( nArg < 1 ){ | ||
9351 | /* Missing argument, return 0 */ | ||
9352 | jx9_result_int(pCtx, 0); | ||
9353 | return JX9_OK; | ||
9354 | } | ||
9355 | is_float = jx9_value_is_float(apArg[0]); | ||
9356 | if( is_float ){ | ||
9357 | double r, x; | ||
9358 | x = jx9_value_to_double(apArg[0]); | ||
9359 | /* Perform the requested operation */ | ||
9360 | r = fabs(x); | ||
9361 | jx9_result_double(pCtx, r); | ||
9362 | }else{ | ||
9363 | int r, x; | ||
9364 | x = jx9_value_to_int(apArg[0]); | ||
9365 | /* Perform the requested operation */ | ||
9366 | r = abs(x); | ||
9367 | jx9_result_int(pCtx, r); | ||
9368 | } | ||
9369 | return JX9_OK; | ||
9370 | } | ||
9371 | /* | ||
9372 | * float log(float $arg, [int/float $base]) | ||
9373 | * Natural logarithm. | ||
9374 | * Parameter | ||
9375 | * $arg: The number to process. | ||
9376 | * $base: The optional logarithmic base to use. (only base-10 is supported) | ||
9377 | * Return | ||
9378 | * The logarithm of arg to base, if given, or the natural logarithm. | ||
9379 | * Note: | ||
9380 | * only Natural log and base-10 log are supported. | ||
9381 | */ | ||
9382 | static int jx9Builtin_log(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9383 | { | ||
9384 | double r, x; | ||
9385 | if( nArg < 1 ){ | ||
9386 | /* Missing argument, return 0 */ | ||
9387 | jx9_result_int(pCtx, 0); | ||
9388 | return JX9_OK; | ||
9389 | } | ||
9390 | x = jx9_value_to_double(apArg[0]); | ||
9391 | /* Perform the requested operation */ | ||
9392 | if( nArg == 2 && jx9_value_is_numeric(apArg[1]) && jx9_value_to_int(apArg[1]) == 10 ){ | ||
9393 | /* Base-10 log */ | ||
9394 | r = log10(x); | ||
9395 | }else{ | ||
9396 | r = log(x); | ||
9397 | } | ||
9398 | /* store the result back */ | ||
9399 | jx9_result_double(pCtx, r); | ||
9400 | return JX9_OK; | ||
9401 | } | ||
9402 | /* | ||
9403 | * float log10(float $arg ) | ||
9404 | * Base-10 logarithm. | ||
9405 | * Parameter | ||
9406 | * The number to process. | ||
9407 | * Return | ||
9408 | * The Base-10 logarithm of the given number. | ||
9409 | */ | ||
9410 | static int jx9Builtin_log10(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9411 | { | ||
9412 | double r, x; | ||
9413 | if( nArg < 1 ){ | ||
9414 | /* Missing argument, return 0 */ | ||
9415 | jx9_result_int(pCtx, 0); | ||
9416 | return JX9_OK; | ||
9417 | } | ||
9418 | x = jx9_value_to_double(apArg[0]); | ||
9419 | /* Perform the requested operation */ | ||
9420 | r = log10(x); | ||
9421 | /* store the result back */ | ||
9422 | jx9_result_double(pCtx, r); | ||
9423 | return JX9_OK; | ||
9424 | } | ||
9425 | /* | ||
9426 | * number pow(number $base, number $exp) | ||
9427 | * Exponential expression. | ||
9428 | * Parameter | ||
9429 | * base | ||
9430 | * The base to use. | ||
9431 | * exp | ||
9432 | * The exponent. | ||
9433 | * Return | ||
9434 | * base raised to the power of exp. | ||
9435 | * If the result can be represented as integer it will be returned | ||
9436 | * as type integer, else it will be returned as type float. | ||
9437 | */ | ||
9438 | static int jx9Builtin_pow(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9439 | { | ||
9440 | double r, x, y; | ||
9441 | if( nArg < 1 ){ | ||
9442 | /* Missing argument, return 0 */ | ||
9443 | jx9_result_int(pCtx, 0); | ||
9444 | return JX9_OK; | ||
9445 | } | ||
9446 | x = jx9_value_to_double(apArg[0]); | ||
9447 | y = jx9_value_to_double(apArg[1]); | ||
9448 | /* Perform the requested operation */ | ||
9449 | r = pow(x, y); | ||
9450 | jx9_result_double(pCtx, r); | ||
9451 | return JX9_OK; | ||
9452 | } | ||
9453 | /* | ||
9454 | * float pi(void) | ||
9455 | * Returns an approximation of pi. | ||
9456 | * Note | ||
9457 | * you can use the M_PI constant which yields identical results to pi(). | ||
9458 | * Return | ||
9459 | * The value of pi as float. | ||
9460 | */ | ||
9461 | static int jx9Builtin_pi(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9462 | { | ||
9463 | SXUNUSED(nArg); /* cc warning */ | ||
9464 | SXUNUSED(apArg); | ||
9465 | jx9_result_double(pCtx, JX9_PI); | ||
9466 | return JX9_OK; | ||
9467 | } | ||
9468 | /* | ||
9469 | * float fmod(float $x, float $y) | ||
9470 | * Returns the floating point remainder (modulo) of the division of the arguments. | ||
9471 | * Parameters | ||
9472 | * $x | ||
9473 | * The dividend | ||
9474 | * $y | ||
9475 | * The divisor | ||
9476 | * Return | ||
9477 | * The floating point remainder of x/y. | ||
9478 | */ | ||
9479 | static int jx9Builtin_fmod(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9480 | { | ||
9481 | double x, y, r; | ||
9482 | if( nArg < 2 ){ | ||
9483 | /* Missing arguments */ | ||
9484 | jx9_result_double(pCtx, 0); | ||
9485 | return JX9_OK; | ||
9486 | } | ||
9487 | /* Extract given arguments */ | ||
9488 | x = jx9_value_to_double(apArg[0]); | ||
9489 | y = jx9_value_to_double(apArg[1]); | ||
9490 | /* Perform the requested operation */ | ||
9491 | r = fmod(x, y); | ||
9492 | /* Processing result */ | ||
9493 | jx9_result_double(pCtx, r); | ||
9494 | return JX9_OK; | ||
9495 | } | ||
9496 | /* | ||
9497 | * float hypot(float $x, float $y) | ||
9498 | * Calculate the length of the hypotenuse of a right-angle triangle . | ||
9499 | * Parameters | ||
9500 | * $x | ||
9501 | * Length of first side | ||
9502 | * $y | ||
9503 | * Length of first side | ||
9504 | * Return | ||
9505 | * Calculated length of the hypotenuse. | ||
9506 | */ | ||
9507 | static int jx9Builtin_hypot(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9508 | { | ||
9509 | double x, y, r; | ||
9510 | if( nArg < 2 ){ | ||
9511 | /* Missing arguments */ | ||
9512 | jx9_result_double(pCtx, 0); | ||
9513 | return JX9_OK; | ||
9514 | } | ||
9515 | /* Extract given arguments */ | ||
9516 | x = jx9_value_to_double(apArg[0]); | ||
9517 | y = jx9_value_to_double(apArg[1]); | ||
9518 | /* Perform the requested operation */ | ||
9519 | r = hypot(x, y); | ||
9520 | /* Processing result */ | ||
9521 | jx9_result_double(pCtx, r); | ||
9522 | return JX9_OK; | ||
9523 | } | ||
9524 | #endif /* JX9_ENABLE_MATH_FUNC */ | ||
9525 | /* | ||
9526 | * float round ( float $val [, int $precision = 0 [, int $mode = JX9_ROUND_HALF_UP ]] ) | ||
9527 | * Exponential expression. | ||
9528 | * Parameter | ||
9529 | * $val | ||
9530 | * The value to round. | ||
9531 | * $precision | ||
9532 | * The optional number of decimal digits to round to. | ||
9533 | * $mode | ||
9534 | * One of JX9_ROUND_HALF_UP, JX9_ROUND_HALF_DOWN, JX9_ROUND_HALF_EVEN, or JX9_ROUND_HALF_ODD. | ||
9535 | * (not supported). | ||
9536 | * Return | ||
9537 | * The rounded value. | ||
9538 | */ | ||
9539 | static int jx9Builtin_round(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9540 | { | ||
9541 | int n = 0; | ||
9542 | double r; | ||
9543 | if( nArg < 1 ){ | ||
9544 | /* Missing argument, return 0 */ | ||
9545 | jx9_result_int(pCtx, 0); | ||
9546 | return JX9_OK; | ||
9547 | } | ||
9548 | /* Extract the precision if available */ | ||
9549 | if( nArg > 1 ){ | ||
9550 | n = jx9_value_to_int(apArg[1]); | ||
9551 | if( n>30 ){ | ||
9552 | n = 30; | ||
9553 | } | ||
9554 | if( n<0 ){ | ||
9555 | n = 0; | ||
9556 | } | ||
9557 | } | ||
9558 | r = jx9_value_to_double(apArg[0]); | ||
9559 | /* If Y==0 and X will fit in a 64-bit int, | ||
9560 | * handle the rounding directly.Otherwise | ||
9561 | * use our own cutsom printf [i.e:SyBufferFormat()]. | ||
9562 | */ | ||
9563 | if( n==0 && r>=0 && r<LARGEST_INT64-1 ){ | ||
9564 | r = (double)((jx9_int64)(r+0.5)); | ||
9565 | }else if( n==0 && r<0 && (-r)<LARGEST_INT64-1 ){ | ||
9566 | r = -(double)((jx9_int64)((-r)+0.5)); | ||
9567 | }else{ | ||
9568 | char zBuf[256]; | ||
9569 | sxu32 nLen; | ||
9570 | nLen = SyBufferFormat(zBuf, sizeof(zBuf), "%.*f", n, r); | ||
9571 | /* Convert the string to real number */ | ||
9572 | SyStrToReal(zBuf, nLen, (void *)&r, 0); | ||
9573 | } | ||
9574 | /* Return thr rounded value */ | ||
9575 | jx9_result_double(pCtx, r); | ||
9576 | return JX9_OK; | ||
9577 | } | ||
9578 | /* | ||
9579 | * string dechex(int $number) | ||
9580 | * Decimal to hexadecimal. | ||
9581 | * Parameters | ||
9582 | * $number | ||
9583 | * Decimal value to convert | ||
9584 | * Return | ||
9585 | * Hexadecimal string representation of number | ||
9586 | */ | ||
9587 | static int jx9Builtin_dechex(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9588 | { | ||
9589 | int iVal; | ||
9590 | if( nArg < 1 ){ | ||
9591 | /* Missing arguments, return null */ | ||
9592 | jx9_result_null(pCtx); | ||
9593 | return JX9_OK; | ||
9594 | } | ||
9595 | /* Extract the given number */ | ||
9596 | iVal = jx9_value_to_int(apArg[0]); | ||
9597 | /* Format */ | ||
9598 | jx9_result_string_format(pCtx, "%x", iVal); | ||
9599 | return JX9_OK; | ||
9600 | } | ||
9601 | /* | ||
9602 | * string decoct(int $number) | ||
9603 | * Decimal to Octal. | ||
9604 | * Parameters | ||
9605 | * $number | ||
9606 | * Decimal value to convert | ||
9607 | * Return | ||
9608 | * Octal string representation of number | ||
9609 | */ | ||
9610 | static int jx9Builtin_decoct(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9611 | { | ||
9612 | int iVal; | ||
9613 | if( nArg < 1 ){ | ||
9614 | /* Missing arguments, return null */ | ||
9615 | jx9_result_null(pCtx); | ||
9616 | return JX9_OK; | ||
9617 | } | ||
9618 | /* Extract the given number */ | ||
9619 | iVal = jx9_value_to_int(apArg[0]); | ||
9620 | /* Format */ | ||
9621 | jx9_result_string_format(pCtx, "%o", iVal); | ||
9622 | return JX9_OK; | ||
9623 | } | ||
9624 | /* | ||
9625 | * string decbin(int $number) | ||
9626 | * Decimal to binary. | ||
9627 | * Parameters | ||
9628 | * $number | ||
9629 | * Decimal value to convert | ||
9630 | * Return | ||
9631 | * Binary string representation of number | ||
9632 | */ | ||
9633 | static int jx9Builtin_decbin(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9634 | { | ||
9635 | int iVal; | ||
9636 | if( nArg < 1 ){ | ||
9637 | /* Missing arguments, return null */ | ||
9638 | jx9_result_null(pCtx); | ||
9639 | return JX9_OK; | ||
9640 | } | ||
9641 | /* Extract the given number */ | ||
9642 | iVal = jx9_value_to_int(apArg[0]); | ||
9643 | /* Format */ | ||
9644 | jx9_result_string_format(pCtx, "%B", iVal); | ||
9645 | return JX9_OK; | ||
9646 | } | ||
9647 | /* | ||
9648 | * int64 hexdec(string $hex_string) | ||
9649 | * Hexadecimal to decimal. | ||
9650 | * Parameters | ||
9651 | * $hex_string | ||
9652 | * The hexadecimal string to convert | ||
9653 | * Return | ||
9654 | * The decimal representation of hex_string | ||
9655 | */ | ||
9656 | static int jx9Builtin_hexdec(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9657 | { | ||
9658 | const char *zString, *zEnd; | ||
9659 | jx9_int64 iVal; | ||
9660 | int nLen; | ||
9661 | if( nArg < 1 ){ | ||
9662 | /* Missing arguments, return -1 */ | ||
9663 | jx9_result_int(pCtx, -1); | ||
9664 | return JX9_OK; | ||
9665 | } | ||
9666 | iVal = 0; | ||
9667 | if( jx9_value_is_string(apArg[0]) ){ | ||
9668 | /* Extract the given string */ | ||
9669 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
9670 | /* Delimit the string */ | ||
9671 | zEnd = &zString[nLen]; | ||
9672 | /* Ignore non hex-stream */ | ||
9673 | while( zString < zEnd ){ | ||
9674 | if( (unsigned char)zString[0] >= 0xc0 ){ | ||
9675 | /* UTF-8 stream */ | ||
9676 | zString++; | ||
9677 | while( zString < zEnd && (((unsigned char)zString[0] & 0xc0) == 0x80) ){ | ||
9678 | zString++; | ||
9679 | } | ||
9680 | }else{ | ||
9681 | if( SyisHex(zString[0]) ){ | ||
9682 | break; | ||
9683 | } | ||
9684 | /* Ignore */ | ||
9685 | zString++; | ||
9686 | } | ||
9687 | } | ||
9688 | if( zString < zEnd ){ | ||
9689 | /* Cast */ | ||
9690 | SyHexStrToInt64(zString, (sxu32)(zEnd-zString), (void *)&iVal, 0); | ||
9691 | } | ||
9692 | }else{ | ||
9693 | /* Extract as a 64-bit integer */ | ||
9694 | iVal = jx9_value_to_int64(apArg[0]); | ||
9695 | } | ||
9696 | /* Return the number */ | ||
9697 | jx9_result_int64(pCtx, iVal); | ||
9698 | return JX9_OK; | ||
9699 | } | ||
9700 | /* | ||
9701 | * int64 bindec(string $bin_string) | ||
9702 | * Binary to decimal. | ||
9703 | * Parameters | ||
9704 | * $bin_string | ||
9705 | * The binary string to convert | ||
9706 | * Return | ||
9707 | * Returns the decimal equivalent of the binary number represented by the binary_string argument. | ||
9708 | */ | ||
9709 | static int jx9Builtin_bindec(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9710 | { | ||
9711 | const char *zString; | ||
9712 | jx9_int64 iVal; | ||
9713 | int nLen; | ||
9714 | if( nArg < 1 ){ | ||
9715 | /* Missing arguments, return -1 */ | ||
9716 | jx9_result_int(pCtx, -1); | ||
9717 | return JX9_OK; | ||
9718 | } | ||
9719 | iVal = 0; | ||
9720 | if( jx9_value_is_string(apArg[0]) ){ | ||
9721 | /* Extract the given string */ | ||
9722 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
9723 | if( nLen > 0 ){ | ||
9724 | /* Perform a binary cast */ | ||
9725 | SyBinaryStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0); | ||
9726 | } | ||
9727 | }else{ | ||
9728 | /* Extract as a 64-bit integer */ | ||
9729 | iVal = jx9_value_to_int64(apArg[0]); | ||
9730 | } | ||
9731 | /* Return the number */ | ||
9732 | jx9_result_int64(pCtx, iVal); | ||
9733 | return JX9_OK; | ||
9734 | } | ||
9735 | /* | ||
9736 | * int64 octdec(string $oct_string) | ||
9737 | * Octal to decimal. | ||
9738 | * Parameters | ||
9739 | * $oct_string | ||
9740 | * The octal string to convert | ||
9741 | * Return | ||
9742 | * Returns the decimal equivalent of the octal number represented by the octal_string argument. | ||
9743 | */ | ||
9744 | static int jx9Builtin_octdec(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9745 | { | ||
9746 | const char *zString; | ||
9747 | jx9_int64 iVal; | ||
9748 | int nLen; | ||
9749 | if( nArg < 1 ){ | ||
9750 | /* Missing arguments, return -1 */ | ||
9751 | jx9_result_int(pCtx, -1); | ||
9752 | return JX9_OK; | ||
9753 | } | ||
9754 | iVal = 0; | ||
9755 | if( jx9_value_is_string(apArg[0]) ){ | ||
9756 | /* Extract the given string */ | ||
9757 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
9758 | if( nLen > 0 ){ | ||
9759 | /* Perform the cast */ | ||
9760 | SyOctalStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0); | ||
9761 | } | ||
9762 | }else{ | ||
9763 | /* Extract as a 64-bit integer */ | ||
9764 | iVal = jx9_value_to_int64(apArg[0]); | ||
9765 | } | ||
9766 | /* Return the number */ | ||
9767 | jx9_result_int64(pCtx, iVal); | ||
9768 | return JX9_OK; | ||
9769 | } | ||
9770 | /* | ||
9771 | * string base_convert(string $number, int $frombase, int $tobase) | ||
9772 | * Convert a number between arbitrary bases. | ||
9773 | * Parameters | ||
9774 | * $number | ||
9775 | * The number to convert | ||
9776 | * $frombase | ||
9777 | * The base number is in | ||
9778 | * $tobase | ||
9779 | * The base to convert number to | ||
9780 | * Return | ||
9781 | * Number converted to base tobase | ||
9782 | */ | ||
9783 | static int jx9Builtin_base_convert(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9784 | { | ||
9785 | int nLen, iFbase, iTobase; | ||
9786 | const char *zNum; | ||
9787 | jx9_int64 iNum; | ||
9788 | if( nArg < 3 ){ | ||
9789 | /* Return the empty string*/ | ||
9790 | jx9_result_string(pCtx, "", 0); | ||
9791 | return JX9_OK; | ||
9792 | } | ||
9793 | /* Base numbers */ | ||
9794 | iFbase = jx9_value_to_int(apArg[1]); | ||
9795 | iTobase = jx9_value_to_int(apArg[2]); | ||
9796 | if( jx9_value_is_string(apArg[0]) ){ | ||
9797 | /* Extract the target number */ | ||
9798 | zNum = jx9_value_to_string(apArg[0], &nLen); | ||
9799 | if( nLen < 1 ){ | ||
9800 | /* Return the empty string*/ | ||
9801 | jx9_result_string(pCtx, "", 0); | ||
9802 | return JX9_OK; | ||
9803 | } | ||
9804 | /* Base conversion */ | ||
9805 | switch(iFbase){ | ||
9806 | case 16: | ||
9807 | /* Hex */ | ||
9808 | SyHexStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); | ||
9809 | break; | ||
9810 | case 8: | ||
9811 | /* Octal */ | ||
9812 | SyOctalStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); | ||
9813 | break; | ||
9814 | case 2: | ||
9815 | /* Binary */ | ||
9816 | SyBinaryStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); | ||
9817 | break; | ||
9818 | default: | ||
9819 | /* Decimal */ | ||
9820 | SyStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); | ||
9821 | break; | ||
9822 | } | ||
9823 | }else{ | ||
9824 | iNum = jx9_value_to_int64(apArg[0]); | ||
9825 | } | ||
9826 | switch(iTobase){ | ||
9827 | case 16: | ||
9828 | /* Hex */ | ||
9829 | jx9_result_string_format(pCtx, "%qx", iNum); /* Quad hex */ | ||
9830 | break; | ||
9831 | case 8: | ||
9832 | /* Octal */ | ||
9833 | jx9_result_string_format(pCtx, "%qo", iNum); /* Quad octal */ | ||
9834 | break; | ||
9835 | case 2: | ||
9836 | /* Binary */ | ||
9837 | jx9_result_string_format(pCtx, "%qB", iNum); /* Quad binary */ | ||
9838 | break; | ||
9839 | default: | ||
9840 | /* Decimal */ | ||
9841 | jx9_result_string_format(pCtx, "%qd", iNum); /* Quad decimal */ | ||
9842 | break; | ||
9843 | } | ||
9844 | return JX9_OK; | ||
9845 | } | ||
9846 | /* | ||
9847 | * Section: | ||
9848 | * String handling Functions. | ||
9849 | * Authors: | ||
9850 | * Symisc Systems, devel@symisc.net. | ||
9851 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
9852 | * Status: | ||
9853 | * Stable. | ||
9854 | */ | ||
9855 | /* | ||
9856 | * string substr(string $string, int $start[, int $length ]) | ||
9857 | * Return part of a string. | ||
9858 | * Parameters | ||
9859 | * $string | ||
9860 | * The input string. Must be one character or longer. | ||
9861 | * $start | ||
9862 | * If start is non-negative, the returned string will start at the start'th position | ||
9863 | * in string, counting from zero. For instance, in the string 'abcdef', the character | ||
9864 | * at position 0 is 'a', the character at position 2 is 'c', and so forth. | ||
9865 | * If start is negative, the returned string will start at the start'th character | ||
9866 | * from the end of string. | ||
9867 | * If string is less than or equal to start characters long, FALSE will be returned. | ||
9868 | * $length | ||
9869 | * If length is given and is positive, the string returned will contain at most length | ||
9870 | * characters beginning from start (depending on the length of string). | ||
9871 | * If length is given and is negative, then that many characters will be omitted from | ||
9872 | * the end of string (after the start position has been calculated when a start is negative). | ||
9873 | * If start denotes the position of this truncation or beyond, false will be returned. | ||
9874 | * If length is given and is 0, FALSE or NULL an empty string will be returned. | ||
9875 | * If length is omitted, the substring starting from start until the end of the string | ||
9876 | * will be returned. | ||
9877 | * Return | ||
9878 | * Returns the extracted part of string, or FALSE on failure or an empty string. | ||
9879 | */ | ||
9880 | static int jx9Builtin_substr(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9881 | { | ||
9882 | const char *zSource, *zOfft; | ||
9883 | int nOfft, nLen, nSrcLen; | ||
9884 | if( nArg < 2 ){ | ||
9885 | /* return FALSE */ | ||
9886 | jx9_result_bool(pCtx, 0); | ||
9887 | return JX9_OK; | ||
9888 | } | ||
9889 | /* Extract the target string */ | ||
9890 | zSource = jx9_value_to_string(apArg[0], &nSrcLen); | ||
9891 | if( nSrcLen < 1 ){ | ||
9892 | /* Empty string, return FALSE */ | ||
9893 | jx9_result_bool(pCtx, 0); | ||
9894 | return JX9_OK; | ||
9895 | } | ||
9896 | nLen = nSrcLen; /* cc warning */ | ||
9897 | /* Extract the offset */ | ||
9898 | nOfft = jx9_value_to_int(apArg[1]); | ||
9899 | if( nOfft < 0 ){ | ||
9900 | zOfft = &zSource[nSrcLen+nOfft]; | ||
9901 | if( zOfft < zSource ){ | ||
9902 | /* Invalid offset */ | ||
9903 | jx9_result_bool(pCtx, 0); | ||
9904 | return JX9_OK; | ||
9905 | } | ||
9906 | nLen = (int)(&zSource[nSrcLen]-zOfft); | ||
9907 | nOfft = (int)(zOfft-zSource); | ||
9908 | }else if( nOfft >= nSrcLen ){ | ||
9909 | /* Invalid offset */ | ||
9910 | jx9_result_bool(pCtx, 0); | ||
9911 | return JX9_OK; | ||
9912 | }else{ | ||
9913 | zOfft = &zSource[nOfft]; | ||
9914 | nLen = nSrcLen - nOfft; | ||
9915 | } | ||
9916 | if( nArg > 2 ){ | ||
9917 | /* Extract the length */ | ||
9918 | nLen = jx9_value_to_int(apArg[2]); | ||
9919 | if( nLen == 0 ){ | ||
9920 | /* Invalid length, return an empty string */ | ||
9921 | jx9_result_string(pCtx, "", 0); | ||
9922 | return JX9_OK; | ||
9923 | }else if( nLen < 0 ){ | ||
9924 | nLen = nSrcLen + nLen - nOfft; | ||
9925 | if( nLen < 1 ){ | ||
9926 | /* Invalid length */ | ||
9927 | nLen = nSrcLen - nOfft; | ||
9928 | } | ||
9929 | } | ||
9930 | if( nLen + nOfft > nSrcLen ){ | ||
9931 | /* Invalid length */ | ||
9932 | nLen = nSrcLen - nOfft; | ||
9933 | } | ||
9934 | } | ||
9935 | /* Return the substring */ | ||
9936 | jx9_result_string(pCtx, zOfft, nLen); | ||
9937 | return JX9_OK; | ||
9938 | } | ||
9939 | /* | ||
9940 | * int substr_compare(string $main_str, string $str , int $offset[, int $length[, bool $case_insensitivity = false ]]) | ||
9941 | * Binary safe comparison of two strings from an offset, up to length characters. | ||
9942 | * Parameters | ||
9943 | * $main_str | ||
9944 | * The main string being compared. | ||
9945 | * $str | ||
9946 | * The secondary string being compared. | ||
9947 | * $offset | ||
9948 | * The start position for the comparison. If negative, it starts counting from | ||
9949 | * the end of the string. | ||
9950 | * $length | ||
9951 | * The length of the comparison. The default value is the largest of the length | ||
9952 | * of the str compared to the length of main_str less the offset. | ||
9953 | * $case_insensitivity | ||
9954 | * If case_insensitivity is TRUE, comparison is case insensitive. | ||
9955 | * Return | ||
9956 | * Returns < 0 if main_str from position offset is less than str, > 0 if it is greater than | ||
9957 | * str, and 0 if they are equal. If offset is equal to or greater than the length of main_str | ||
9958 | * or length is set and is less than 1, substr_compare() prints a warning and returns FALSE. | ||
9959 | */ | ||
9960 | static int jx9Builtin_substr_compare(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
9961 | { | ||
9962 | const char *zSource, *zOfft, *zSub; | ||
9963 | int nOfft, nLen, nSrcLen, nSublen; | ||
9964 | int iCase = 0; | ||
9965 | int rc; | ||
9966 | if( nArg < 3 ){ | ||
9967 | /* Missing arguments, return FALSE */ | ||
9968 | jx9_result_bool(pCtx, 0); | ||
9969 | return JX9_OK; | ||
9970 | } | ||
9971 | /* Extract the target string */ | ||
9972 | zSource = jx9_value_to_string(apArg[0], &nSrcLen); | ||
9973 | if( nSrcLen < 1 ){ | ||
9974 | /* Empty string, return FALSE */ | ||
9975 | jx9_result_bool(pCtx, 0); | ||
9976 | return JX9_OK; | ||
9977 | } | ||
9978 | nLen = nSrcLen; /* cc warning */ | ||
9979 | /* Extract the substring */ | ||
9980 | zSub = jx9_value_to_string(apArg[1], &nSublen); | ||
9981 | if( nSublen < 1 || nSublen > nSrcLen){ | ||
9982 | /* Empty string, return FALSE */ | ||
9983 | jx9_result_bool(pCtx, 0); | ||
9984 | return JX9_OK; | ||
9985 | } | ||
9986 | /* Extract the offset */ | ||
9987 | nOfft = jx9_value_to_int(apArg[2]); | ||
9988 | if( nOfft < 0 ){ | ||
9989 | zOfft = &zSource[nSrcLen+nOfft]; | ||
9990 | if( zOfft < zSource ){ | ||
9991 | /* Invalid offset */ | ||
9992 | jx9_result_bool(pCtx, 0); | ||
9993 | return JX9_OK; | ||
9994 | } | ||
9995 | nLen = (int)(&zSource[nSrcLen]-zOfft); | ||
9996 | nOfft = (int)(zOfft-zSource); | ||
9997 | }else if( nOfft >= nSrcLen ){ | ||
9998 | /* Invalid offset */ | ||
9999 | jx9_result_bool(pCtx, 0); | ||
10000 | return JX9_OK; | ||
10001 | }else{ | ||
10002 | zOfft = &zSource[nOfft]; | ||
10003 | nLen = nSrcLen - nOfft; | ||
10004 | } | ||
10005 | if( nArg > 3 ){ | ||
10006 | /* Extract the length */ | ||
10007 | nLen = jx9_value_to_int(apArg[3]); | ||
10008 | if( nLen < 1 ){ | ||
10009 | /* Invalid length */ | ||
10010 | jx9_result_int(pCtx, 1); | ||
10011 | return JX9_OK; | ||
10012 | }else if( nLen + nOfft > nSrcLen ){ | ||
10013 | /* Invalid length */ | ||
10014 | nLen = nSrcLen - nOfft; | ||
10015 | } | ||
10016 | if( nArg > 4 ){ | ||
10017 | /* Case-sensitive or not */ | ||
10018 | iCase = jx9_value_to_bool(apArg[4]); | ||
10019 | } | ||
10020 | } | ||
10021 | /* Perform the comparison */ | ||
10022 | if( iCase ){ | ||
10023 | rc = SyStrnicmp(zOfft, zSub, (sxu32)nLen); | ||
10024 | }else{ | ||
10025 | rc = SyStrncmp(zOfft, zSub, (sxu32)nLen); | ||
10026 | } | ||
10027 | /* Comparison result */ | ||
10028 | jx9_result_int(pCtx, rc); | ||
10029 | return JX9_OK; | ||
10030 | } | ||
10031 | /* | ||
10032 | * int substr_count(string $haystack, string $needle[, int $offset = 0 [, int $length ]]) | ||
10033 | * Count the number of substring occurrences. | ||
10034 | * Parameters | ||
10035 | * $haystack | ||
10036 | * The string to search in | ||
10037 | * $needle | ||
10038 | * The substring to search for | ||
10039 | * $offset | ||
10040 | * The offset where to start counting | ||
10041 | * $length (NOT USED) | ||
10042 | * The maximum length after the specified offset to search for the substring. | ||
10043 | * It outputs a warning if the offset plus the length is greater than the haystack length. | ||
10044 | * Return | ||
10045 | * Toral number of substring occurrences. | ||
10046 | */ | ||
10047 | static int jx9Builtin_substr_count(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10048 | { | ||
10049 | const char *zText, *zPattern, *zEnd; | ||
10050 | int nTextlen, nPatlen; | ||
10051 | int iCount = 0; | ||
10052 | sxu32 nOfft; | ||
10053 | sxi32 rc; | ||
10054 | if( nArg < 2 ){ | ||
10055 | /* Missing arguments */ | ||
10056 | jx9_result_int(pCtx, 0); | ||
10057 | return JX9_OK; | ||
10058 | } | ||
10059 | /* Point to the haystack */ | ||
10060 | zText = jx9_value_to_string(apArg[0], &nTextlen); | ||
10061 | /* Point to the neddle */ | ||
10062 | zPattern = jx9_value_to_string(apArg[1], &nPatlen); | ||
10063 | if( nTextlen < 1 || nPatlen < 1 || nPatlen > nTextlen ){ | ||
10064 | /* NOOP, return zero */ | ||
10065 | jx9_result_int(pCtx, 0); | ||
10066 | return JX9_OK; | ||
10067 | } | ||
10068 | if( nArg > 2 ){ | ||
10069 | int nOfft; | ||
10070 | /* Extract the offset */ | ||
10071 | nOfft = jx9_value_to_int(apArg[2]); | ||
10072 | if( nOfft < 0 || nOfft > nTextlen ){ | ||
10073 | /* Invalid offset, return zero */ | ||
10074 | jx9_result_int(pCtx, 0); | ||
10075 | return JX9_OK; | ||
10076 | } | ||
10077 | /* Point to the desired offset */ | ||
10078 | zText = &zText[nOfft]; | ||
10079 | /* Adjust length */ | ||
10080 | nTextlen -= nOfft; | ||
10081 | } | ||
10082 | /* Point to the end of the string */ | ||
10083 | zEnd = &zText[nTextlen]; | ||
10084 | if( nArg > 3 ){ | ||
10085 | int nLen; | ||
10086 | /* Extract the length */ | ||
10087 | nLen = jx9_value_to_int(apArg[3]); | ||
10088 | if( nLen < 0 || nLen > nTextlen ){ | ||
10089 | /* Invalid length, return 0 */ | ||
10090 | jx9_result_int(pCtx, 0); | ||
10091 | return JX9_OK; | ||
10092 | } | ||
10093 | /* Adjust pointer */ | ||
10094 | nTextlen = nLen; | ||
10095 | zEnd = &zText[nTextlen]; | ||
10096 | } | ||
10097 | /* Perform the search */ | ||
10098 | for(;;){ | ||
10099 | rc = SyBlobSearch((const void *)zText, (sxu32)(zEnd-zText), (const void *)zPattern, nPatlen, &nOfft); | ||
10100 | if( rc != SXRET_OK ){ | ||
10101 | /* Pattern not found, break immediately */ | ||
10102 | break; | ||
10103 | } | ||
10104 | /* Increment counter and update the offset */ | ||
10105 | iCount++; | ||
10106 | zText += nOfft + nPatlen; | ||
10107 | if( zText >= zEnd ){ | ||
10108 | break; | ||
10109 | } | ||
10110 | } | ||
10111 | /* Pattern count */ | ||
10112 | jx9_result_int(pCtx, iCount); | ||
10113 | return JX9_OK; | ||
10114 | } | ||
10115 | /* | ||
10116 | * string chunk_split(string $body[, int $chunklen = 76 [, string $end = "\r\n" ]]) | ||
10117 | * Split a string into smaller chunks. | ||
10118 | * Parameters | ||
10119 | * $body | ||
10120 | * The string to be chunked. | ||
10121 | * $chunklen | ||
10122 | * The chunk length. | ||
10123 | * $end | ||
10124 | * The line ending sequence. | ||
10125 | * Return | ||
10126 | * The chunked string or NULL on failure. | ||
10127 | */ | ||
10128 | static int jx9Builtin_chunk_split(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10129 | { | ||
10130 | const char *zIn, *zEnd, *zSep = "\r\n"; | ||
10131 | int nSepLen, nChunkLen, nLen; | ||
10132 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
10133 | /* Nothing to split, return null */ | ||
10134 | jx9_result_null(pCtx); | ||
10135 | return JX9_OK; | ||
10136 | } | ||
10137 | /* initialize/Extract arguments */ | ||
10138 | nSepLen = (int)sizeof("\r\n") - 1; | ||
10139 | nChunkLen = 76; | ||
10140 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
10141 | zEnd = &zIn[nLen]; | ||
10142 | if( nArg > 1 ){ | ||
10143 | /* Chunk length */ | ||
10144 | nChunkLen = jx9_value_to_int(apArg[1]); | ||
10145 | if( nChunkLen < 1 ){ | ||
10146 | /* Switch back to the default length */ | ||
10147 | nChunkLen = 76; | ||
10148 | } | ||
10149 | if( nArg > 2 ){ | ||
10150 | /* Separator */ | ||
10151 | zSep = jx9_value_to_string(apArg[2], &nSepLen); | ||
10152 | if( nSepLen < 1 ){ | ||
10153 | /* Switch back to the default separator */ | ||
10154 | zSep = "\r\n"; | ||
10155 | nSepLen = (int)sizeof("\r\n") - 1; | ||
10156 | } | ||
10157 | } | ||
10158 | } | ||
10159 | /* Perform the requested operation */ | ||
10160 | if( nChunkLen > nLen ){ | ||
10161 | /* Nothing to split, return the string and the separator */ | ||
10162 | jx9_result_string_format(pCtx, "%.*s%.*s", nLen, zIn, nSepLen, zSep); | ||
10163 | return JX9_OK; | ||
10164 | } | ||
10165 | while( zIn < zEnd ){ | ||
10166 | if( nChunkLen > (int)(zEnd-zIn) ){ | ||
10167 | nChunkLen = (int)(zEnd - zIn); | ||
10168 | } | ||
10169 | /* Append the chunk and the separator */ | ||
10170 | jx9_result_string_format(pCtx, "%.*s%.*s", nChunkLen, zIn, nSepLen, zSep); | ||
10171 | /* Point beyond the chunk */ | ||
10172 | zIn += nChunkLen; | ||
10173 | } | ||
10174 | return JX9_OK; | ||
10175 | } | ||
10176 | /* | ||
10177 | * string htmlspecialchars(string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $charset]]) | ||
10178 | * HTML escaping of special characters. | ||
10179 | * The translations performed are: | ||
10180 | * '&' (ampersand) ==> '&' | ||
10181 | * '"' (double quote) ==> '"' when ENT_NOQUOTES is not set. | ||
10182 | * "'" (single quote) ==> ''' only when ENT_QUOTES is set. | ||
10183 | * '<' (less than) ==> '<' | ||
10184 | * '>' (greater than) ==> '>' | ||
10185 | * Parameters | ||
10186 | * $string | ||
10187 | * The string being converted. | ||
10188 | * $flags | ||
10189 | * A bitmask of one or more of the following flags, which specify how to handle quotes. | ||
10190 | * The default is ENT_COMPAT | ENT_HTML401. | ||
10191 | * ENT_COMPAT Will convert double-quotes and leave single-quotes alone. | ||
10192 | * ENT_QUOTES Will convert both double and single quotes. | ||
10193 | * ENT_NOQUOTES Will leave both double and single quotes unconverted. | ||
10194 | * ENT_IGNORE Silently discard invalid code unit sequences instead of returning an empty string. | ||
10195 | * $charset | ||
10196 | * Defines character set used in conversion. The default character set is ISO-8859-1. (Not used) | ||
10197 | * Return | ||
10198 | * The escaped string or NULL on failure. | ||
10199 | */ | ||
10200 | static int jx9Builtin_htmlspecialchars(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10201 | { | ||
10202 | const char *zCur, *zIn, *zEnd; | ||
10203 | int iFlags = 0x01|0x40; /* ENT_COMPAT | ENT_HTML401 */ | ||
10204 | int nLen, c; | ||
10205 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
10206 | /* Missing/Invalid arguments, return NULL */ | ||
10207 | jx9_result_null(pCtx); | ||
10208 | return JX9_OK; | ||
10209 | } | ||
10210 | /* Extract the target string */ | ||
10211 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
10212 | zEnd = &zIn[nLen]; | ||
10213 | /* Extract the flags if available */ | ||
10214 | if( nArg > 1 ){ | ||
10215 | iFlags = jx9_value_to_int(apArg[1]); | ||
10216 | if( iFlags < 0 ){ | ||
10217 | iFlags = 0x01|0x40; | ||
10218 | } | ||
10219 | } | ||
10220 | /* Perform the requested operation */ | ||
10221 | for(;;){ | ||
10222 | if( zIn >= zEnd ){ | ||
10223 | break; | ||
10224 | } | ||
10225 | zCur = zIn; | ||
10226 | while( zIn < zEnd && zIn[0] != '&' && zIn[0] != '\'' && zIn[0] != '"' && zIn[0] != '<' && zIn[0] != '>' ){ | ||
10227 | zIn++; | ||
10228 | } | ||
10229 | if( zCur < zIn ){ | ||
10230 | /* Append the raw string verbatim */ | ||
10231 | jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); | ||
10232 | } | ||
10233 | if( zIn >= zEnd ){ | ||
10234 | break; | ||
10235 | } | ||
10236 | c = zIn[0]; | ||
10237 | if( c == '&' ){ | ||
10238 | /* Expand '&' */ | ||
10239 | jx9_result_string(pCtx, "&", (int)sizeof("&")-1); | ||
10240 | }else if( c == '<' ){ | ||
10241 | /* Expand '<' */ | ||
10242 | jx9_result_string(pCtx, "<", (int)sizeof("<")-1); | ||
10243 | }else if( c == '>' ){ | ||
10244 | /* Expand '>' */ | ||
10245 | jx9_result_string(pCtx, ">", (int)sizeof(">")-1); | ||
10246 | }else if( c == '\'' ){ | ||
10247 | if( iFlags & 0x02 /*ENT_QUOTES*/ ){ | ||
10248 | /* Expand ''' */ | ||
10249 | jx9_result_string(pCtx, "'", (int)sizeof("'")-1); | ||
10250 | }else{ | ||
10251 | /* Leave the single quote untouched */ | ||
10252 | jx9_result_string(pCtx, "'", (int)sizeof(char)); | ||
10253 | } | ||
10254 | }else if( c == '"' ){ | ||
10255 | if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ | ||
10256 | /* Expand '"' */ | ||
10257 | jx9_result_string(pCtx, """, (int)sizeof(""")-1); | ||
10258 | }else{ | ||
10259 | /* Leave the double quote untouched */ | ||
10260 | jx9_result_string(pCtx, "\"", (int)sizeof(char)); | ||
10261 | } | ||
10262 | } | ||
10263 | /* Ignore the unsafe HTML character */ | ||
10264 | zIn++; | ||
10265 | } | ||
10266 | return JX9_OK; | ||
10267 | } | ||
10268 | /* | ||
10269 | * string htmlspecialchars_decode(string $string[, int $quote_style = ENT_COMPAT ]) | ||
10270 | * Unescape HTML entities. | ||
10271 | * Parameters | ||
10272 | * $string | ||
10273 | * The string to decode | ||
10274 | * $quote_style | ||
10275 | * The quote style. One of the following constants: | ||
10276 | * ENT_COMPAT Will convert double-quotes and leave single-quotes alone (default) | ||
10277 | * ENT_QUOTES Will convert both double and single quotes | ||
10278 | * ENT_NOQUOTES Will leave both double and single quotes unconverted | ||
10279 | * Return | ||
10280 | * The unescaped string or NULL on failure. | ||
10281 | */ | ||
10282 | static int jx9Builtin_htmlspecialchars_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10283 | { | ||
10284 | const char *zCur, *zIn, *zEnd; | ||
10285 | int iFlags = 0x01; /* ENT_COMPAT */ | ||
10286 | int nLen, nJump; | ||
10287 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
10288 | /* Missing/Invalid arguments, return NULL */ | ||
10289 | jx9_result_null(pCtx); | ||
10290 | return JX9_OK; | ||
10291 | } | ||
10292 | /* Extract the target string */ | ||
10293 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
10294 | zEnd = &zIn[nLen]; | ||
10295 | /* Extract the flags if available */ | ||
10296 | if( nArg > 1 ){ | ||
10297 | iFlags = jx9_value_to_int(apArg[1]); | ||
10298 | if( iFlags < 0 ){ | ||
10299 | iFlags = 0x01; | ||
10300 | } | ||
10301 | } | ||
10302 | /* Perform the requested operation */ | ||
10303 | for(;;){ | ||
10304 | if( zIn >= zEnd ){ | ||
10305 | break; | ||
10306 | } | ||
10307 | zCur = zIn; | ||
10308 | while( zIn < zEnd && zIn[0] != '&' ){ | ||
10309 | zIn++; | ||
10310 | } | ||
10311 | if( zCur < zIn ){ | ||
10312 | /* Append the raw string verbatim */ | ||
10313 | jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); | ||
10314 | } | ||
10315 | nLen = (int)(zEnd-zIn); | ||
10316 | nJump = (int)sizeof(char); | ||
10317 | if( nLen >= (int)sizeof("&")-1 && SyStrnicmp(zIn, "&", sizeof("&")-1) == 0 ){ | ||
10318 | /* & ==> '&' */ | ||
10319 | jx9_result_string(pCtx, "&", (int)sizeof(char)); | ||
10320 | nJump = (int)sizeof("&")-1; | ||
10321 | }else if( nLen >= (int)sizeof("<")-1 && SyStrnicmp(zIn, "<", sizeof("<")-1) == 0 ){ | ||
10322 | /* < ==> < */ | ||
10323 | jx9_result_string(pCtx, "<", (int)sizeof(char)); | ||
10324 | nJump = (int)sizeof("<")-1; | ||
10325 | }else if( nLen >= (int)sizeof(">")-1 && SyStrnicmp(zIn, ">", sizeof(">")-1) == 0 ){ | ||
10326 | /* > ==> '>' */ | ||
10327 | jx9_result_string(pCtx, ">", (int)sizeof(char)); | ||
10328 | nJump = (int)sizeof(">")-1; | ||
10329 | }else if( nLen >= (int)sizeof(""")-1 && SyStrnicmp(zIn, """, sizeof(""")-1) == 0 ){ | ||
10330 | /* " ==> '"' */ | ||
10331 | if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ | ||
10332 | jx9_result_string(pCtx, "\"", (int)sizeof(char)); | ||
10333 | }else{ | ||
10334 | /* Leave untouched */ | ||
10335 | jx9_result_string(pCtx, """, (int)sizeof(""")-1); | ||
10336 | } | ||
10337 | nJump = (int)sizeof(""")-1; | ||
10338 | }else if( nLen >= (int)sizeof("'")-1 && SyStrnicmp(zIn, "'", sizeof("'")-1) == 0 ){ | ||
10339 | /* ' ==> ''' */ | ||
10340 | if( iFlags & 0x02 /*ENT_QUOTES*/ ){ | ||
10341 | /* Expand ''' */ | ||
10342 | jx9_result_string(pCtx, "'", (int)sizeof(char)); | ||
10343 | }else{ | ||
10344 | /* Leave untouched */ | ||
10345 | jx9_result_string(pCtx, "'", (int)sizeof("'")-1); | ||
10346 | } | ||
10347 | nJump = (int)sizeof("'")-1; | ||
10348 | }else if( nLen >= (int)sizeof(char) ){ | ||
10349 | /* expand '&' */ | ||
10350 | jx9_result_string(pCtx, "&", (int)sizeof(char)); | ||
10351 | }else{ | ||
10352 | /* No more input to process */ | ||
10353 | break; | ||
10354 | } | ||
10355 | zIn += nJump; | ||
10356 | } | ||
10357 | return JX9_OK; | ||
10358 | } | ||
10359 | /* HTML encoding/Decoding table | ||
10360 | * Source: Symisc RunTime API.[chm@symisc.net] | ||
10361 | */ | ||
10362 | static const char *azHtmlEscape[] = { | ||
10363 | "<", "<", ">", ">", "&", "&", """, "\"", "'", "'", | ||
10364 | "!", "!", "$", "$", "#", "#", "%", "%", "(", "(", | ||
10365 | ")", ")", "{", "{", "}", "}", "=", "=", "+", "+", | ||
10366 | "?", "?", "[", "[", "]", "]", "@", "@", ",", "," | ||
10367 | }; | ||
10368 | /* | ||
10369 | * array get_html_translation_table(void) | ||
10370 | * Returns the translation table used by htmlspecialchars() and htmlentities(). | ||
10371 | * Parameters | ||
10372 | * None | ||
10373 | * Return | ||
10374 | * The translation table as an array or NULL on failure. | ||
10375 | */ | ||
10376 | static int jx9Builtin_get_html_translation_table(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10377 | { | ||
10378 | jx9_value *pArray, *pValue; | ||
10379 | sxu32 n; | ||
10380 | /* Element value */ | ||
10381 | pValue = jx9_context_new_scalar(pCtx); | ||
10382 | if( pValue == 0 ){ | ||
10383 | SXUNUSED(nArg); /* cc warning */ | ||
10384 | SXUNUSED(apArg); | ||
10385 | /* Return NULL */ | ||
10386 | jx9_result_null(pCtx); | ||
10387 | return JX9_OK; | ||
10388 | } | ||
10389 | /* Create a new array */ | ||
10390 | pArray = jx9_context_new_array(pCtx); | ||
10391 | if( pArray == 0 ){ | ||
10392 | /* Return NULL */ | ||
10393 | jx9_result_null(pCtx); | ||
10394 | return JX9_OK; | ||
10395 | } | ||
10396 | /* Make the table */ | ||
10397 | for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ | ||
10398 | /* Prepare the value */ | ||
10399 | jx9_value_string(pValue, azHtmlEscape[n], -1 /* Compute length automatically */); | ||
10400 | /* Insert the value */ | ||
10401 | jx9_array_add_strkey_elem(pArray, azHtmlEscape[n+1], pValue); | ||
10402 | /* Reset the string cursor */ | ||
10403 | jx9_value_reset_string_cursor(pValue); | ||
10404 | } | ||
10405 | /* | ||
10406 | * Return the array. | ||
10407 | * Don't worry about freeing memory, everything will be automatically | ||
10408 | * released upon we return from this function. | ||
10409 | */ | ||
10410 | jx9_result_value(pCtx, pArray); | ||
10411 | return JX9_OK; | ||
10412 | } | ||
10413 | /* | ||
10414 | * string htmlentities( string $string [, int $flags = ENT_COMPAT | ENT_HTML401]); | ||
10415 | * Convert all applicable characters to HTML entities | ||
10416 | * Parameters | ||
10417 | * $string | ||
10418 | * The input string. | ||
10419 | * $flags | ||
10420 | * A bitmask of one or more of the flags (see block-comment on jx9Builtin_htmlspecialchars()) | ||
10421 | * Return | ||
10422 | * The encoded string. | ||
10423 | */ | ||
10424 | static int jx9Builtin_htmlentities(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10425 | { | ||
10426 | int iFlags = 0x01; /* ENT_COMPAT */ | ||
10427 | const char *zIn, *zEnd; | ||
10428 | int nLen, c; | ||
10429 | sxu32 n; | ||
10430 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
10431 | /* Missing/Invalid arguments, return NULL */ | ||
10432 | jx9_result_null(pCtx); | ||
10433 | return JX9_OK; | ||
10434 | } | ||
10435 | /* Extract the target string */ | ||
10436 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
10437 | zEnd = &zIn[nLen]; | ||
10438 | /* Extract the flags if available */ | ||
10439 | if( nArg > 1 ){ | ||
10440 | iFlags = jx9_value_to_int(apArg[1]); | ||
10441 | if( iFlags < 0 ){ | ||
10442 | iFlags = 0x01; | ||
10443 | } | ||
10444 | } | ||
10445 | /* Perform the requested operation */ | ||
10446 | for(;;){ | ||
10447 | if( zIn >= zEnd ){ | ||
10448 | /* No more input to process */ | ||
10449 | break; | ||
10450 | } | ||
10451 | c = zIn[0]; | ||
10452 | /* Perform a linear lookup on the decoding table */ | ||
10453 | for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ | ||
10454 | if( azHtmlEscape[n+1][0] == c ){ | ||
10455 | /* Got one */ | ||
10456 | break; | ||
10457 | } | ||
10458 | } | ||
10459 | if( n < SX_ARRAYSIZE(azHtmlEscape) ){ | ||
10460 | /* Output the safe sequence [i.e: '<' ==> '<"] */ | ||
10461 | if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ | ||
10462 | /* Expand the double quote verbatim */ | ||
10463 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
10464 | }else if(c == '\'' && ((iFlags & 0x02 /*ENT_QUOTES*/) == 0 || (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ | ||
10465 | /* expand single quote verbatim */ | ||
10466 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
10467 | }else{ | ||
10468 | jx9_result_string(pCtx, azHtmlEscape[n], -1/*Compute length automatically */); | ||
10469 | } | ||
10470 | }else{ | ||
10471 | /* Output character verbatim */ | ||
10472 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
10473 | } | ||
10474 | zIn++; | ||
10475 | } | ||
10476 | return JX9_OK; | ||
10477 | } | ||
10478 | /* | ||
10479 | * string html_entity_decode(string $string [, int $quote_style = ENT_COMPAT [, string $charset = 'UTF-8' ]]) | ||
10480 | * Perform the reverse operation of html_entity_decode(). | ||
10481 | * Parameters | ||
10482 | * $string | ||
10483 | * The input string. | ||
10484 | * $flags | ||
10485 | * A bitmask of one or more of the flags (see comment on jx9Builtin_htmlspecialchars()) | ||
10486 | * Return | ||
10487 | * The decoded string. | ||
10488 | */ | ||
10489 | static int jx9Builtin_html_entity_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10490 | { | ||
10491 | const char *zCur, *zIn, *zEnd; | ||
10492 | int iFlags = 0x01; /* ENT_COMPAT */ | ||
10493 | int nLen; | ||
10494 | sxu32 n; | ||
10495 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
10496 | /* Missing/Invalid arguments, return NULL */ | ||
10497 | jx9_result_null(pCtx); | ||
10498 | return JX9_OK; | ||
10499 | } | ||
10500 | /* Extract the target string */ | ||
10501 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
10502 | zEnd = &zIn[nLen]; | ||
10503 | /* Extract the flags if available */ | ||
10504 | if( nArg > 1 ){ | ||
10505 | iFlags = jx9_value_to_int(apArg[1]); | ||
10506 | if( iFlags < 0 ){ | ||
10507 | iFlags = 0x01; | ||
10508 | } | ||
10509 | } | ||
10510 | /* Perform the requested operation */ | ||
10511 | for(;;){ | ||
10512 | if( zIn >= zEnd ){ | ||
10513 | /* No more input to process */ | ||
10514 | break; | ||
10515 | } | ||
10516 | zCur = zIn; | ||
10517 | while( zIn < zEnd && zIn[0] != '&' ){ | ||
10518 | zIn++; | ||
10519 | } | ||
10520 | if( zCur < zIn ){ | ||
10521 | /* Append raw string verbatim */ | ||
10522 | jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); | ||
10523 | } | ||
10524 | if( zIn >= zEnd ){ | ||
10525 | break; | ||
10526 | } | ||
10527 | nLen = (int)(zEnd-zIn); | ||
10528 | /* Find an encoded sequence */ | ||
10529 | for(n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ | ||
10530 | int iLen = (int)SyStrlen(azHtmlEscape[n]); | ||
10531 | if( nLen >= iLen && SyStrnicmp(zIn, azHtmlEscape[n], (sxu32)iLen) == 0 ){ | ||
10532 | /* Got one */ | ||
10533 | zIn += iLen; | ||
10534 | break; | ||
10535 | } | ||
10536 | } | ||
10537 | if( n < SX_ARRAYSIZE(azHtmlEscape) ){ | ||
10538 | int c = azHtmlEscape[n+1][0]; | ||
10539 | /* Output the decoded character */ | ||
10540 | if( c == '\'' && ((iFlags & 0x02) == 0 /*ENT_QUOTES*/|| (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ | ||
10541 | /* Do not process single quotes */ | ||
10542 | jx9_result_string(pCtx, azHtmlEscape[n], -1); | ||
10543 | }else if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ | ||
10544 | /* Do not process double quotes */ | ||
10545 | jx9_result_string(pCtx, azHtmlEscape[n], -1); | ||
10546 | }else{ | ||
10547 | jx9_result_string(pCtx, azHtmlEscape[n+1], -1); /* Compute length automatically */ | ||
10548 | } | ||
10549 | }else{ | ||
10550 | /* Append '&' */ | ||
10551 | jx9_result_string(pCtx, "&", (int)sizeof(char)); | ||
10552 | zIn++; | ||
10553 | } | ||
10554 | } | ||
10555 | return JX9_OK; | ||
10556 | } | ||
10557 | /* | ||
10558 | * int strlen($string) | ||
10559 | * return the length of the given string. | ||
10560 | * Parameter | ||
10561 | * string: The string being measured for length. | ||
10562 | * Return | ||
10563 | * length of the given string. | ||
10564 | */ | ||
10565 | static int jx9Builtin_strlen(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10566 | { | ||
10567 | int iLen = 0; | ||
10568 | if( nArg > 0 ){ | ||
10569 | jx9_value_to_string(apArg[0], &iLen); | ||
10570 | } | ||
10571 | /* String length */ | ||
10572 | jx9_result_int(pCtx, iLen); | ||
10573 | return JX9_OK; | ||
10574 | } | ||
10575 | /* | ||
10576 | * int strcmp(string $str1, string $str2) | ||
10577 | * Perform a binary safe string comparison. | ||
10578 | * Parameter | ||
10579 | * str1: The first string | ||
10580 | * str2: The second string | ||
10581 | * Return | ||
10582 | * Returns < 0 if str1 is less than str2; > 0 if str1 is greater | ||
10583 | * than str2, and 0 if they are equal. | ||
10584 | */ | ||
10585 | static int jx9Builtin_strcmp(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10586 | { | ||
10587 | const char *z1, *z2; | ||
10588 | int n1, n2; | ||
10589 | int res; | ||
10590 | if( nArg < 2 ){ | ||
10591 | res = nArg == 0 ? 0 : 1; | ||
10592 | jx9_result_int(pCtx, res); | ||
10593 | return JX9_OK; | ||
10594 | } | ||
10595 | /* Perform the comparison */ | ||
10596 | z1 = jx9_value_to_string(apArg[0], &n1); | ||
10597 | z2 = jx9_value_to_string(apArg[1], &n2); | ||
10598 | res = SyStrncmp(z1, z2, (sxu32)(SXMAX(n1, n2))); | ||
10599 | /* Comparison result */ | ||
10600 | jx9_result_int(pCtx, res); | ||
10601 | return JX9_OK; | ||
10602 | } | ||
10603 | /* | ||
10604 | * int strncmp(string $str1, string $str2, int n) | ||
10605 | * Perform a binary safe string comparison of the first n characters. | ||
10606 | * Parameter | ||
10607 | * str1: The first string | ||
10608 | * str2: The second string | ||
10609 | * Return | ||
10610 | * Returns < 0 if str1 is less than str2; > 0 if str1 is greater | ||
10611 | * than str2, and 0 if they are equal. | ||
10612 | */ | ||
10613 | static int jx9Builtin_strncmp(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10614 | { | ||
10615 | const char *z1, *z2; | ||
10616 | int res; | ||
10617 | int n; | ||
10618 | if( nArg < 3 ){ | ||
10619 | /* Perform a standard comparison */ | ||
10620 | return jx9Builtin_strcmp(pCtx, nArg, apArg); | ||
10621 | } | ||
10622 | /* Desired comparison length */ | ||
10623 | n = jx9_value_to_int(apArg[2]); | ||
10624 | if( n < 0 ){ | ||
10625 | /* Invalid length */ | ||
10626 | jx9_result_int(pCtx, -1); | ||
10627 | return JX9_OK; | ||
10628 | } | ||
10629 | /* Perform the comparison */ | ||
10630 | z1 = jx9_value_to_string(apArg[0], 0); | ||
10631 | z2 = jx9_value_to_string(apArg[1], 0); | ||
10632 | res = SyStrncmp(z1, z2, (sxu32)n); | ||
10633 | /* Comparison result */ | ||
10634 | jx9_result_int(pCtx, res); | ||
10635 | return JX9_OK; | ||
10636 | } | ||
10637 | /* | ||
10638 | * int strcasecmp(string $str1, string $str2, int n) | ||
10639 | * Perform a binary safe case-insensitive string comparison. | ||
10640 | * Parameter | ||
10641 | * str1: The first string | ||
10642 | * str2: The second string | ||
10643 | * Return | ||
10644 | * Returns < 0 if str1 is less than str2; > 0 if str1 is greater | ||
10645 | * than str2, and 0 if they are equal. | ||
10646 | */ | ||
10647 | static int jx9Builtin_strcasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10648 | { | ||
10649 | const char *z1, *z2; | ||
10650 | int n1, n2; | ||
10651 | int res; | ||
10652 | if( nArg < 2 ){ | ||
10653 | res = nArg == 0 ? 0 : 1; | ||
10654 | jx9_result_int(pCtx, res); | ||
10655 | return JX9_OK; | ||
10656 | } | ||
10657 | /* Perform the comparison */ | ||
10658 | z1 = jx9_value_to_string(apArg[0], &n1); | ||
10659 | z2 = jx9_value_to_string(apArg[1], &n2); | ||
10660 | res = SyStrnicmp(z1, z2, (sxu32)(SXMAX(n1, n2))); | ||
10661 | /* Comparison result */ | ||
10662 | jx9_result_int(pCtx, res); | ||
10663 | return JX9_OK; | ||
10664 | } | ||
10665 | /* | ||
10666 | * int strncasecmp(string $str1, string $str2, int n) | ||
10667 | * Perform a binary safe case-insensitive string comparison of the first n characters. | ||
10668 | * Parameter | ||
10669 | * $str1: The first string | ||
10670 | * $str2: The second string | ||
10671 | * $len: The length of strings to be used in the comparison. | ||
10672 | * Return | ||
10673 | * Returns < 0 if str1 is less than str2; > 0 if str1 is greater | ||
10674 | * than str2, and 0 if they are equal. | ||
10675 | */ | ||
10676 | static int jx9Builtin_strncasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10677 | { | ||
10678 | const char *z1, *z2; | ||
10679 | int res; | ||
10680 | int n; | ||
10681 | if( nArg < 3 ){ | ||
10682 | /* Perform a standard comparison */ | ||
10683 | return jx9Builtin_strcasecmp(pCtx, nArg, apArg); | ||
10684 | } | ||
10685 | /* Desired comparison length */ | ||
10686 | n = jx9_value_to_int(apArg[2]); | ||
10687 | if( n < 0 ){ | ||
10688 | /* Invalid length */ | ||
10689 | jx9_result_int(pCtx, -1); | ||
10690 | return JX9_OK; | ||
10691 | } | ||
10692 | /* Perform the comparison */ | ||
10693 | z1 = jx9_value_to_string(apArg[0], 0); | ||
10694 | z2 = jx9_value_to_string(apArg[1], 0); | ||
10695 | res = SyStrnicmp(z1, z2, (sxu32)n); | ||
10696 | /* Comparison result */ | ||
10697 | jx9_result_int(pCtx, res); | ||
10698 | return JX9_OK; | ||
10699 | } | ||
10700 | /* | ||
10701 | * Implode context [i.e: it's private data]. | ||
10702 | * A pointer to the following structure is forwarded | ||
10703 | * verbatim to the array walker callback defined below. | ||
10704 | */ | ||
10705 | struct implode_data { | ||
10706 | jx9_context *pCtx; /* Call context */ | ||
10707 | int bRecursive; /* TRUE if recursive implode [this is a symisc eXtension] */ | ||
10708 | const char *zSep; /* Arguments separator if any */ | ||
10709 | int nSeplen; /* Separator length */ | ||
10710 | int bFirst; /* TRUE if first call */ | ||
10711 | int nRecCount; /* Recursion count to avoid infinite loop */ | ||
10712 | }; | ||
10713 | /* | ||
10714 | * Implode walker callback for the [jx9_array_walk()] interface. | ||
10715 | * The following routine is invoked for each array entry passed | ||
10716 | * to the implode() function. | ||
10717 | */ | ||
10718 | static int implode_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData) | ||
10719 | { | ||
10720 | struct implode_data *pData = (struct implode_data *)pUserData; | ||
10721 | const char *zData; | ||
10722 | int nLen; | ||
10723 | if( pData->bRecursive && jx9_value_is_json_array(pValue) && pData->nRecCount < 32 ){ | ||
10724 | if( pData->nSeplen > 0 ){ | ||
10725 | if( !pData->bFirst ){ | ||
10726 | /* append the separator first */ | ||
10727 | jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen); | ||
10728 | }else{ | ||
10729 | pData->bFirst = 0; | ||
10730 | } | ||
10731 | } | ||
10732 | /* Recurse */ | ||
10733 | pData->bFirst = 1; | ||
10734 | pData->nRecCount++; | ||
10735 | jx9HashmapWalk((jx9_hashmap *)pValue->x.pOther, implode_callback, pData); | ||
10736 | pData->nRecCount--; | ||
10737 | return JX9_OK; | ||
10738 | } | ||
10739 | /* Extract the string representation of the entry value */ | ||
10740 | zData = jx9_value_to_string(pValue, &nLen); | ||
10741 | if( nLen > 0 ){ | ||
10742 | if( pData->nSeplen > 0 ){ | ||
10743 | if( !pData->bFirst ){ | ||
10744 | /* append the separator first */ | ||
10745 | jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen); | ||
10746 | }else{ | ||
10747 | pData->bFirst = 0; | ||
10748 | } | ||
10749 | } | ||
10750 | jx9_result_string(pData->pCtx, zData, nLen); | ||
10751 | }else{ | ||
10752 | SXUNUSED(pKey); /* cc warning */ | ||
10753 | } | ||
10754 | return JX9_OK; | ||
10755 | } | ||
10756 | /* | ||
10757 | * string implode(string $glue, array $pieces, ...) | ||
10758 | * string implode(array $pieces, ...) | ||
10759 | * Join array elements with a string. | ||
10760 | * $glue | ||
10761 | * Defaults to an empty string. This is not the preferred usage of implode() as glue | ||
10762 | * would be the second parameter and thus, the bad prototype would be used. | ||
10763 | * $pieces | ||
10764 | * The array of strings to implode. | ||
10765 | * Return | ||
10766 | * Returns a string containing a string representation of all the array elements in the same | ||
10767 | * order, with the glue string between each element. | ||
10768 | */ | ||
10769 | static int jx9Builtin_implode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10770 | { | ||
10771 | struct implode_data imp_data; | ||
10772 | int i = 1; | ||
10773 | if( nArg < 1 ){ | ||
10774 | /* Missing argument, return NULL */ | ||
10775 | jx9_result_null(pCtx); | ||
10776 | return JX9_OK; | ||
10777 | } | ||
10778 | /* Prepare the implode context */ | ||
10779 | imp_data.pCtx = pCtx; | ||
10780 | imp_data.bRecursive = 0; | ||
10781 | imp_data.bFirst = 1; | ||
10782 | imp_data.nRecCount = 0; | ||
10783 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
10784 | imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen); | ||
10785 | }else{ | ||
10786 | imp_data.zSep = 0; | ||
10787 | imp_data.nSeplen = 0; | ||
10788 | i = 0; | ||
10789 | } | ||
10790 | jx9_result_string(pCtx, "", 0); /* Set an empty stirng */ | ||
10791 | /* Start the 'join' process */ | ||
10792 | while( i < nArg ){ | ||
10793 | if( jx9_value_is_json_array(apArg[i]) ){ | ||
10794 | /* Iterate throw array entries */ | ||
10795 | jx9_array_walk(apArg[i], implode_callback, &imp_data); | ||
10796 | }else{ | ||
10797 | const char *zData; | ||
10798 | int nLen; | ||
10799 | /* Extract the string representation of the jx9 value */ | ||
10800 | zData = jx9_value_to_string(apArg[i], &nLen); | ||
10801 | if( nLen > 0 ){ | ||
10802 | if( imp_data.nSeplen > 0 ){ | ||
10803 | if( !imp_data.bFirst ){ | ||
10804 | /* append the separator first */ | ||
10805 | jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen); | ||
10806 | }else{ | ||
10807 | imp_data.bFirst = 0; | ||
10808 | } | ||
10809 | } | ||
10810 | jx9_result_string(pCtx, zData, nLen); | ||
10811 | } | ||
10812 | } | ||
10813 | i++; | ||
10814 | } | ||
10815 | return JX9_OK; | ||
10816 | } | ||
10817 | /* | ||
10818 | * string implode_recursive(string $glue, array $pieces, ...) | ||
10819 | * Purpose | ||
10820 | * Same as implode() but recurse on arrays. | ||
10821 | * Example: | ||
10822 | * $a = array('usr', array('home', 'dean')); | ||
10823 | * print implode_recursive("/", $a); | ||
10824 | * Will output | ||
10825 | * usr/home/dean. | ||
10826 | * While the standard implode would produce. | ||
10827 | * usr/Array. | ||
10828 | * Parameter | ||
10829 | * Refer to implode(). | ||
10830 | * Return | ||
10831 | * Refer to implode(). | ||
10832 | */ | ||
10833 | static int jx9Builtin_implode_recursive(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10834 | { | ||
10835 | struct implode_data imp_data; | ||
10836 | int i = 1; | ||
10837 | if( nArg < 1 ){ | ||
10838 | /* Missing argument, return NULL */ | ||
10839 | jx9_result_null(pCtx); | ||
10840 | return JX9_OK; | ||
10841 | } | ||
10842 | /* Prepare the implode context */ | ||
10843 | imp_data.pCtx = pCtx; | ||
10844 | imp_data.bRecursive = 1; | ||
10845 | imp_data.bFirst = 1; | ||
10846 | imp_data.nRecCount = 0; | ||
10847 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
10848 | imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen); | ||
10849 | }else{ | ||
10850 | imp_data.zSep = 0; | ||
10851 | imp_data.nSeplen = 0; | ||
10852 | i = 0; | ||
10853 | } | ||
10854 | jx9_result_string(pCtx, "", 0); /* Set an empty stirng */ | ||
10855 | /* Start the 'join' process */ | ||
10856 | while( i < nArg ){ | ||
10857 | if( jx9_value_is_json_array(apArg[i]) ){ | ||
10858 | /* Iterate throw array entries */ | ||
10859 | jx9_array_walk(apArg[i], implode_callback, &imp_data); | ||
10860 | }else{ | ||
10861 | const char *zData; | ||
10862 | int nLen; | ||
10863 | /* Extract the string representation of the jx9 value */ | ||
10864 | zData = jx9_value_to_string(apArg[i], &nLen); | ||
10865 | if( nLen > 0 ){ | ||
10866 | if( imp_data.nSeplen > 0 ){ | ||
10867 | if( !imp_data.bFirst ){ | ||
10868 | /* append the separator first */ | ||
10869 | jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen); | ||
10870 | }else{ | ||
10871 | imp_data.bFirst = 0; | ||
10872 | } | ||
10873 | } | ||
10874 | jx9_result_string(pCtx, zData, nLen); | ||
10875 | } | ||
10876 | } | ||
10877 | i++; | ||
10878 | } | ||
10879 | return JX9_OK; | ||
10880 | } | ||
10881 | /* | ||
10882 | * array explode(string $delimiter, string $string[, int $limit ]) | ||
10883 | * Returns an array of strings, each of which is a substring of string | ||
10884 | * formed by splitting it on boundaries formed by the string delimiter. | ||
10885 | * Parameters | ||
10886 | * $delimiter | ||
10887 | * The boundary string. | ||
10888 | * $string | ||
10889 | * The input string. | ||
10890 | * $limit | ||
10891 | * If limit is set and positive, the returned array will contain a maximum | ||
10892 | * of limit elements with the last element containing the rest of string. | ||
10893 | * If the limit parameter is negative, all fields except the last -limit are returned. | ||
10894 | * If the limit parameter is zero, then this is treated as 1. | ||
10895 | * Returns | ||
10896 | * Returns an array of strings created by splitting the string parameter | ||
10897 | * on boundaries formed by the delimiter. | ||
10898 | * If delimiter is an empty string (""), explode() will return FALSE. | ||
10899 | * If delimiter contains a value that is not contained in string and a negative | ||
10900 | * limit is used, then an empty array will be returned, otherwise an array containing string | ||
10901 | * will be returned. | ||
10902 | * NOTE: | ||
10903 | * Negative limit is not supported. | ||
10904 | */ | ||
10905 | static int jx9Builtin_explode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
10906 | { | ||
10907 | const char *zDelim, *zString, *zCur, *zEnd; | ||
10908 | int nDelim, nStrlen, iLimit; | ||
10909 | jx9_value *pArray; | ||
10910 | jx9_value *pValue; | ||
10911 | sxu32 nOfft; | ||
10912 | sxi32 rc; | ||
10913 | if( nArg < 2 ){ | ||
10914 | /* Missing arguments, return FALSE */ | ||
10915 | jx9_result_bool(pCtx, 0); | ||
10916 | return JX9_OK; | ||
10917 | } | ||
10918 | /* Extract the delimiter */ | ||
10919 | zDelim = jx9_value_to_string(apArg[0], &nDelim); | ||
10920 | if( nDelim < 1 ){ | ||
10921 | /* Empty delimiter, return FALSE */ | ||
10922 | jx9_result_bool(pCtx, 0); | ||
10923 | return JX9_OK; | ||
10924 | } | ||
10925 | /* Extract the string */ | ||
10926 | zString = jx9_value_to_string(apArg[1], &nStrlen); | ||
10927 | if( nStrlen < 1 ){ | ||
10928 | /* Empty delimiter, return FALSE */ | ||
10929 | jx9_result_bool(pCtx, 0); | ||
10930 | return JX9_OK; | ||
10931 | } | ||
10932 | /* Point to the end of the string */ | ||
10933 | zEnd = &zString[nStrlen]; | ||
10934 | /* Create the array */ | ||
10935 | pArray = jx9_context_new_array(pCtx); | ||
10936 | pValue = jx9_context_new_scalar(pCtx); | ||
10937 | if( pArray == 0 || pValue == 0 ){ | ||
10938 | /* Out of memory, return FALSE */ | ||
10939 | jx9_result_bool(pCtx, 0); | ||
10940 | return JX9_OK; | ||
10941 | } | ||
10942 | /* Set a defualt limit */ | ||
10943 | iLimit = SXI32_HIGH; | ||
10944 | if( nArg > 2 ){ | ||
10945 | iLimit = jx9_value_to_int(apArg[2]); | ||
10946 | if( iLimit < 0 ){ | ||
10947 | iLimit = -iLimit; | ||
10948 | } | ||
10949 | if( iLimit == 0 ){ | ||
10950 | iLimit = 1; | ||
10951 | } | ||
10952 | iLimit--; | ||
10953 | } | ||
10954 | /* Start exploding */ | ||
10955 | for(;;){ | ||
10956 | if( zString >= zEnd ){ | ||
10957 | /* No more entry to process */ | ||
10958 | break; | ||
10959 | } | ||
10960 | rc = SyBlobSearch(zString, (sxu32)(zEnd-zString), zDelim, nDelim, &nOfft); | ||
10961 | if( rc != SXRET_OK || iLimit <= (int)jx9_array_count(pArray) ){ | ||
10962 | /* Limit reached, insert the rest of the string and break */ | ||
10963 | if( zEnd > zString ){ | ||
10964 | jx9_value_string(pValue, zString, (int)(zEnd-zString)); | ||
10965 | jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue); | ||
10966 | } | ||
10967 | break; | ||
10968 | } | ||
10969 | /* Point to the desired offset */ | ||
10970 | zCur = &zString[nOfft]; | ||
10971 | if( zCur > zString ){ | ||
10972 | /* Perform the store operation */ | ||
10973 | jx9_value_string(pValue, zString, (int)(zCur-zString)); | ||
10974 | jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue); | ||
10975 | } | ||
10976 | /* Point beyond the delimiter */ | ||
10977 | zString = &zCur[nDelim]; | ||
10978 | /* Reset the cursor */ | ||
10979 | jx9_value_reset_string_cursor(pValue); | ||
10980 | } | ||
10981 | /* Return the freshly created array */ | ||
10982 | jx9_result_value(pCtx, pArray); | ||
10983 | /* NOTE that every allocated jx9_value will be automatically | ||
10984 | * released as soon we return from this foregin function. | ||
10985 | */ | ||
10986 | return JX9_OK; | ||
10987 | } | ||
10988 | /* | ||
10989 | * string trim(string $str[, string $charlist ]) | ||
10990 | * Strip whitespace (or other characters) from the beginning and end of a string. | ||
10991 | * Parameters | ||
10992 | * $str | ||
10993 | * The string that will be trimmed. | ||
10994 | * $charlist | ||
10995 | * Optionally, the stripped characters can also be specified using the charlist parameter. | ||
10996 | * Simply list all characters that you want to be stripped. | ||
10997 | * With .. you can specify a range of characters. | ||
10998 | * Returns. | ||
10999 | * Thr processed string. | ||
11000 | */ | ||
11001 | static int jx9Builtin_trim(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11002 | { | ||
11003 | const char *zString; | ||
11004 | int nLen; | ||
11005 | if( nArg < 1 ){ | ||
11006 | /* Missing arguments, return null */ | ||
11007 | jx9_result_null(pCtx); | ||
11008 | return JX9_OK; | ||
11009 | } | ||
11010 | /* Extract the target string */ | ||
11011 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
11012 | if( nLen < 1 ){ | ||
11013 | /* Empty string, return */ | ||
11014 | jx9_result_string(pCtx, "", 0); | ||
11015 | return JX9_OK; | ||
11016 | } | ||
11017 | /* Start the trim process */ | ||
11018 | if( nArg < 2 ){ | ||
11019 | SyString sStr; | ||
11020 | /* Remove white spaces and NUL bytes */ | ||
11021 | SyStringInitFromBuf(&sStr, zString, nLen); | ||
11022 | SyStringFullTrimSafe(&sStr); | ||
11023 | jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); | ||
11024 | }else{ | ||
11025 | /* Char list */ | ||
11026 | const char *zList; | ||
11027 | int nListlen; | ||
11028 | zList = jx9_value_to_string(apArg[1], &nListlen); | ||
11029 | if( nListlen < 1 ){ | ||
11030 | /* Return the string unchanged */ | ||
11031 | jx9_result_string(pCtx, zString, nLen); | ||
11032 | }else{ | ||
11033 | const char *zEnd = &zString[nLen]; | ||
11034 | const char *zCur = zString; | ||
11035 | const char *zPtr; | ||
11036 | int i; | ||
11037 | /* Left trim */ | ||
11038 | for(;;){ | ||
11039 | if( zCur >= zEnd ){ | ||
11040 | break; | ||
11041 | } | ||
11042 | zPtr = zCur; | ||
11043 | for( i = 0 ; i < nListlen ; i++ ){ | ||
11044 | if( zCur < zEnd && zCur[0] == zList[i] ){ | ||
11045 | zCur++; | ||
11046 | } | ||
11047 | } | ||
11048 | if( zCur == zPtr ){ | ||
11049 | /* No match, break immediately */ | ||
11050 | break; | ||
11051 | } | ||
11052 | } | ||
11053 | /* Right trim */ | ||
11054 | zEnd--; | ||
11055 | for(;;){ | ||
11056 | if( zEnd <= zCur ){ | ||
11057 | break; | ||
11058 | } | ||
11059 | zPtr = zEnd; | ||
11060 | for( i = 0 ; i < nListlen ; i++ ){ | ||
11061 | if( zEnd > zCur && zEnd[0] == zList[i] ){ | ||
11062 | zEnd--; | ||
11063 | } | ||
11064 | } | ||
11065 | if( zEnd == zPtr ){ | ||
11066 | break; | ||
11067 | } | ||
11068 | } | ||
11069 | if( zCur >= zEnd ){ | ||
11070 | /* Return the empty string */ | ||
11071 | jx9_result_string(pCtx, "", 0); | ||
11072 | }else{ | ||
11073 | zEnd++; | ||
11074 | jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); | ||
11075 | } | ||
11076 | } | ||
11077 | } | ||
11078 | return JX9_OK; | ||
11079 | } | ||
11080 | /* | ||
11081 | * string rtrim(string $str[, string $charlist ]) | ||
11082 | * Strip whitespace (or other characters) from the end of a string. | ||
11083 | * Parameters | ||
11084 | * $str | ||
11085 | * The string that will be trimmed. | ||
11086 | * $charlist | ||
11087 | * Optionally, the stripped characters can also be specified using the charlist parameter. | ||
11088 | * Simply list all characters that you want to be stripped. | ||
11089 | * With .. you can specify a range of characters. | ||
11090 | * Returns. | ||
11091 | * Thr processed string. | ||
11092 | */ | ||
11093 | static int jx9Builtin_rtrim(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11094 | { | ||
11095 | const char *zString; | ||
11096 | int nLen; | ||
11097 | if( nArg < 1 ){ | ||
11098 | /* Missing arguments, return null */ | ||
11099 | jx9_result_null(pCtx); | ||
11100 | return JX9_OK; | ||
11101 | } | ||
11102 | /* Extract the target string */ | ||
11103 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
11104 | if( nLen < 1 ){ | ||
11105 | /* Empty string, return */ | ||
11106 | jx9_result_string(pCtx, "", 0); | ||
11107 | return JX9_OK; | ||
11108 | } | ||
11109 | /* Start the trim process */ | ||
11110 | if( nArg < 2 ){ | ||
11111 | SyString sStr; | ||
11112 | /* Remove white spaces and NUL bytes*/ | ||
11113 | SyStringInitFromBuf(&sStr, zString, nLen); | ||
11114 | SyStringRightTrimSafe(&sStr); | ||
11115 | jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); | ||
11116 | }else{ | ||
11117 | /* Char list */ | ||
11118 | const char *zList; | ||
11119 | int nListlen; | ||
11120 | zList = jx9_value_to_string(apArg[1], &nListlen); | ||
11121 | if( nListlen < 1 ){ | ||
11122 | /* Return the string unchanged */ | ||
11123 | jx9_result_string(pCtx, zString, nLen); | ||
11124 | }else{ | ||
11125 | const char *zEnd = &zString[nLen - 1]; | ||
11126 | const char *zCur = zString; | ||
11127 | const char *zPtr; | ||
11128 | int i; | ||
11129 | /* Right trim */ | ||
11130 | for(;;){ | ||
11131 | if( zEnd <= zCur ){ | ||
11132 | break; | ||
11133 | } | ||
11134 | zPtr = zEnd; | ||
11135 | for( i = 0 ; i < nListlen ; i++ ){ | ||
11136 | if( zEnd > zCur && zEnd[0] == zList[i] ){ | ||
11137 | zEnd--; | ||
11138 | } | ||
11139 | } | ||
11140 | if( zEnd == zPtr ){ | ||
11141 | break; | ||
11142 | } | ||
11143 | } | ||
11144 | if( zEnd <= zCur ){ | ||
11145 | /* Return the empty string */ | ||
11146 | jx9_result_string(pCtx, "", 0); | ||
11147 | }else{ | ||
11148 | zEnd++; | ||
11149 | jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); | ||
11150 | } | ||
11151 | } | ||
11152 | } | ||
11153 | return JX9_OK; | ||
11154 | } | ||
11155 | /* | ||
11156 | * string ltrim(string $str[, string $charlist ]) | ||
11157 | * Strip whitespace (or other characters) from the beginning and end of a string. | ||
11158 | * Parameters | ||
11159 | * $str | ||
11160 | * The string that will be trimmed. | ||
11161 | * $charlist | ||
11162 | * Optionally, the stripped characters can also be specified using the charlist parameter. | ||
11163 | * Simply list all characters that you want to be stripped. | ||
11164 | * With .. you can specify a range of characters. | ||
11165 | * Returns. | ||
11166 | * The processed string. | ||
11167 | */ | ||
11168 | static int jx9Builtin_ltrim(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11169 | { | ||
11170 | const char *zString; | ||
11171 | int nLen; | ||
11172 | if( nArg < 1 ){ | ||
11173 | /* Missing arguments, return null */ | ||
11174 | jx9_result_null(pCtx); | ||
11175 | return JX9_OK; | ||
11176 | } | ||
11177 | /* Extract the target string */ | ||
11178 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
11179 | if( nLen < 1 ){ | ||
11180 | /* Empty string, return */ | ||
11181 | jx9_result_string(pCtx, "", 0); | ||
11182 | return JX9_OK; | ||
11183 | } | ||
11184 | /* Start the trim process */ | ||
11185 | if( nArg < 2 ){ | ||
11186 | SyString sStr; | ||
11187 | /* Remove white spaces and NUL byte */ | ||
11188 | SyStringInitFromBuf(&sStr, zString, nLen); | ||
11189 | SyStringLeftTrimSafe(&sStr); | ||
11190 | jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); | ||
11191 | }else{ | ||
11192 | /* Char list */ | ||
11193 | const char *zList; | ||
11194 | int nListlen; | ||
11195 | zList = jx9_value_to_string(apArg[1], &nListlen); | ||
11196 | if( nListlen < 1 ){ | ||
11197 | /* Return the string unchanged */ | ||
11198 | jx9_result_string(pCtx, zString, nLen); | ||
11199 | }else{ | ||
11200 | const char *zEnd = &zString[nLen]; | ||
11201 | const char *zCur = zString; | ||
11202 | const char *zPtr; | ||
11203 | int i; | ||
11204 | /* Left trim */ | ||
11205 | for(;;){ | ||
11206 | if( zCur >= zEnd ){ | ||
11207 | break; | ||
11208 | } | ||
11209 | zPtr = zCur; | ||
11210 | for( i = 0 ; i < nListlen ; i++ ){ | ||
11211 | if( zCur < zEnd && zCur[0] == zList[i] ){ | ||
11212 | zCur++; | ||
11213 | } | ||
11214 | } | ||
11215 | if( zCur == zPtr ){ | ||
11216 | /* No match, break immediately */ | ||
11217 | break; | ||
11218 | } | ||
11219 | } | ||
11220 | if( zCur >= zEnd ){ | ||
11221 | /* Return the empty string */ | ||
11222 | jx9_result_string(pCtx, "", 0); | ||
11223 | }else{ | ||
11224 | jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); | ||
11225 | } | ||
11226 | } | ||
11227 | } | ||
11228 | return JX9_OK; | ||
11229 | } | ||
11230 | /* | ||
11231 | * string strtolower(string $str) | ||
11232 | * Make a string lowercase. | ||
11233 | * Parameters | ||
11234 | * $str | ||
11235 | * The input string. | ||
11236 | * Returns. | ||
11237 | * The lowercased string. | ||
11238 | */ | ||
11239 | static int jx9Builtin_strtolower(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11240 | { | ||
11241 | const char *zString, *zCur, *zEnd; | ||
11242 | int nLen; | ||
11243 | if( nArg < 1 ){ | ||
11244 | /* Missing arguments, return null */ | ||
11245 | jx9_result_null(pCtx); | ||
11246 | return JX9_OK; | ||
11247 | } | ||
11248 | /* Extract the target string */ | ||
11249 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
11250 | if( nLen < 1 ){ | ||
11251 | /* Empty string, return */ | ||
11252 | jx9_result_string(pCtx, "", 0); | ||
11253 | return JX9_OK; | ||
11254 | } | ||
11255 | /* Perform the requested operation */ | ||
11256 | zEnd = &zString[nLen]; | ||
11257 | for(;;){ | ||
11258 | if( zString >= zEnd ){ | ||
11259 | /* No more input, break immediately */ | ||
11260 | break; | ||
11261 | } | ||
11262 | if( (unsigned char)zString[0] >= 0xc0 ){ | ||
11263 | /* UTF-8 stream, output verbatim */ | ||
11264 | zCur = zString; | ||
11265 | zString++; | ||
11266 | while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ | ||
11267 | zString++; | ||
11268 | } | ||
11269 | /* Append UTF-8 stream */ | ||
11270 | jx9_result_string(pCtx, zCur, (int)(zString-zCur)); | ||
11271 | }else{ | ||
11272 | int c = zString[0]; | ||
11273 | if( SyisUpper(c) ){ | ||
11274 | c = SyToLower(zString[0]); | ||
11275 | } | ||
11276 | /* Append character */ | ||
11277 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
11278 | /* Advance the cursor */ | ||
11279 | zString++; | ||
11280 | } | ||
11281 | } | ||
11282 | return JX9_OK; | ||
11283 | } | ||
11284 | /* | ||
11285 | * string strtolower(string $str) | ||
11286 | * Make a string uppercase. | ||
11287 | * Parameters | ||
11288 | * $str | ||
11289 | * The input string. | ||
11290 | * Returns. | ||
11291 | * The uppercased string. | ||
11292 | */ | ||
11293 | static int jx9Builtin_strtoupper(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11294 | { | ||
11295 | const char *zString, *zCur, *zEnd; | ||
11296 | int nLen; | ||
11297 | if( nArg < 1 ){ | ||
11298 | /* Missing arguments, return null */ | ||
11299 | jx9_result_null(pCtx); | ||
11300 | return JX9_OK; | ||
11301 | } | ||
11302 | /* Extract the target string */ | ||
11303 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
11304 | if( nLen < 1 ){ | ||
11305 | /* Empty string, return */ | ||
11306 | jx9_result_string(pCtx, "", 0); | ||
11307 | return JX9_OK; | ||
11308 | } | ||
11309 | /* Perform the requested operation */ | ||
11310 | zEnd = &zString[nLen]; | ||
11311 | for(;;){ | ||
11312 | if( zString >= zEnd ){ | ||
11313 | /* No more input, break immediately */ | ||
11314 | break; | ||
11315 | } | ||
11316 | if( (unsigned char)zString[0] >= 0xc0 ){ | ||
11317 | /* UTF-8 stream, output verbatim */ | ||
11318 | zCur = zString; | ||
11319 | zString++; | ||
11320 | while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ | ||
11321 | zString++; | ||
11322 | } | ||
11323 | /* Append UTF-8 stream */ | ||
11324 | jx9_result_string(pCtx, zCur, (int)(zString-zCur)); | ||
11325 | }else{ | ||
11326 | int c = zString[0]; | ||
11327 | if( SyisLower(c) ){ | ||
11328 | c = SyToUpper(zString[0]); | ||
11329 | } | ||
11330 | /* Append character */ | ||
11331 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
11332 | /* Advance the cursor */ | ||
11333 | zString++; | ||
11334 | } | ||
11335 | } | ||
11336 | return JX9_OK; | ||
11337 | } | ||
11338 | /* | ||
11339 | * int ord(string $string) | ||
11340 | * Returns the ASCII value of the first character of string. | ||
11341 | * Parameters | ||
11342 | * $str | ||
11343 | * The input string. | ||
11344 | * Returns. | ||
11345 | * The ASCII value as an integer. | ||
11346 | */ | ||
11347 | static int jx9Builtin_ord(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11348 | { | ||
11349 | const char *zString; | ||
11350 | int nLen, c; | ||
11351 | if( nArg < 1 ){ | ||
11352 | /* Missing arguments, return -1 */ | ||
11353 | jx9_result_int(pCtx, -1); | ||
11354 | return JX9_OK; | ||
11355 | } | ||
11356 | /* Extract the target string */ | ||
11357 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
11358 | if( nLen < 1 ){ | ||
11359 | /* Empty string, return -1 */ | ||
11360 | jx9_result_int(pCtx, -1); | ||
11361 | return JX9_OK; | ||
11362 | } | ||
11363 | /* Extract the ASCII value of the first character */ | ||
11364 | c = zString[0]; | ||
11365 | /* Return that value */ | ||
11366 | jx9_result_int(pCtx, c); | ||
11367 | return JX9_OK; | ||
11368 | } | ||
11369 | /* | ||
11370 | * string chr(int $ascii) | ||
11371 | * Returns a one-character string containing the character specified by ascii. | ||
11372 | * Parameters | ||
11373 | * $ascii | ||
11374 | * The ascii code. | ||
11375 | * Returns. | ||
11376 | * The specified character. | ||
11377 | */ | ||
11378 | static int jx9Builtin_chr(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11379 | { | ||
11380 | int c; | ||
11381 | if( nArg < 1 ){ | ||
11382 | /* Missing arguments, return null */ | ||
11383 | jx9_result_null(pCtx); | ||
11384 | return JX9_OK; | ||
11385 | } | ||
11386 | /* Extract the ASCII value */ | ||
11387 | c = jx9_value_to_int(apArg[0]); | ||
11388 | /* Return the specified character */ | ||
11389 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
11390 | return JX9_OK; | ||
11391 | } | ||
11392 | /* | ||
11393 | * Binary to hex consumer callback. | ||
11394 | * This callback is the default consumer used by the hash functions | ||
11395 | * [i.e: bin2hex(), md5(), sha1(), md5_file() ... ] defined below. | ||
11396 | */ | ||
11397 | static int HashConsumer(const void *pData, unsigned int nLen, void *pUserData) | ||
11398 | { | ||
11399 | /* Append hex chunk verbatim */ | ||
11400 | jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); | ||
11401 | return SXRET_OK; | ||
11402 | } | ||
11403 | /* | ||
11404 | * string bin2hex(string $str) | ||
11405 | * Convert binary data into hexadecimal representation. | ||
11406 | * Parameters | ||
11407 | * $str | ||
11408 | * The input string. | ||
11409 | * Returns. | ||
11410 | * Returns the hexadecimal representation of the given string. | ||
11411 | */ | ||
11412 | static int jx9Builtin_bin2hex(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11413 | { | ||
11414 | const char *zString; | ||
11415 | int nLen; | ||
11416 | if( nArg < 1 ){ | ||
11417 | /* Missing arguments, return null */ | ||
11418 | jx9_result_null(pCtx); | ||
11419 | return JX9_OK; | ||
11420 | } | ||
11421 | /* Extract the target string */ | ||
11422 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
11423 | if( nLen < 1 ){ | ||
11424 | /* Empty string, return */ | ||
11425 | jx9_result_string(pCtx, "", 0); | ||
11426 | return JX9_OK; | ||
11427 | } | ||
11428 | /* Perform the requested operation */ | ||
11429 | SyBinToHexConsumer((const void *)zString, (sxu32)nLen, HashConsumer, pCtx); | ||
11430 | return JX9_OK; | ||
11431 | } | ||
11432 | /* Search callback signature */ | ||
11433 | typedef sxi32 (*ProcStringMatch)(const void *, sxu32, const void *, sxu32, sxu32 *); | ||
11434 | /* | ||
11435 | * Case-insensitive pattern match. | ||
11436 | * Brute force is the default search method used here. | ||
11437 | * This is due to the fact that brute-forcing works quite | ||
11438 | * well for short/medium texts on modern hardware. | ||
11439 | */ | ||
11440 | static sxi32 iPatternMatch(const void *pText, sxu32 nLen, const void *pPattern, sxu32 iPatLen, sxu32 *pOfft) | ||
11441 | { | ||
11442 | const char *zpIn = (const char *)pPattern; | ||
11443 | const char *zIn = (const char *)pText; | ||
11444 | const char *zpEnd = &zpIn[iPatLen]; | ||
11445 | const char *zEnd = &zIn[nLen]; | ||
11446 | const char *zPtr, *zPtr2; | ||
11447 | int c, d; | ||
11448 | if( iPatLen > nLen ){ | ||
11449 | /* Don't bother processing */ | ||
11450 | return SXERR_NOTFOUND; | ||
11451 | } | ||
11452 | for(;;){ | ||
11453 | if( zIn >= zEnd ){ | ||
11454 | break; | ||
11455 | } | ||
11456 | c = SyToLower(zIn[0]); | ||
11457 | d = SyToLower(zpIn[0]); | ||
11458 | if( c == d ){ | ||
11459 | zPtr = &zIn[1]; | ||
11460 | zPtr2 = &zpIn[1]; | ||
11461 | for(;;){ | ||
11462 | if( zPtr2 >= zpEnd ){ | ||
11463 | /* Pattern found */ | ||
11464 | if( pOfft ){ *pOfft = (sxu32)(zIn-(const char *)pText); } | ||
11465 | return SXRET_OK; | ||
11466 | } | ||
11467 | if( zPtr >= zEnd ){ | ||
11468 | break; | ||
11469 | } | ||
11470 | c = SyToLower(zPtr[0]); | ||
11471 | d = SyToLower(zPtr2[0]); | ||
11472 | if( c != d ){ | ||
11473 | break; | ||
11474 | } | ||
11475 | zPtr++; zPtr2++; | ||
11476 | } | ||
11477 | } | ||
11478 | zIn++; | ||
11479 | } | ||
11480 | /* Pattern not found */ | ||
11481 | return SXERR_NOTFOUND; | ||
11482 | } | ||
11483 | /* | ||
11484 | * string strstr(string $haystack, string $needle[, bool $before_needle = false ]) | ||
11485 | * Find the first occurrence of a string. | ||
11486 | * Parameters | ||
11487 | * $haystack | ||
11488 | * The input string. | ||
11489 | * $needle | ||
11490 | * Search pattern (must be a string). | ||
11491 | * $before_needle | ||
11492 | * If TRUE, strstr() returns the part of the haystack before the first occurrence | ||
11493 | * of the needle (excluding the needle). | ||
11494 | * Return | ||
11495 | * Returns the portion of string, or FALSE if needle is not found. | ||
11496 | */ | ||
11497 | static int jx9Builtin_strstr(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11498 | { | ||
11499 | ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ | ||
11500 | const char *zBlob, *zPattern; | ||
11501 | int nLen, nPatLen; | ||
11502 | sxu32 nOfft; | ||
11503 | sxi32 rc; | ||
11504 | if( nArg < 2 ){ | ||
11505 | /* Missing arguments, return FALSE */ | ||
11506 | jx9_result_bool(pCtx, 0); | ||
11507 | return JX9_OK; | ||
11508 | } | ||
11509 | /* Extract the needle and the haystack */ | ||
11510 | zBlob = jx9_value_to_string(apArg[0], &nLen); | ||
11511 | zPattern = jx9_value_to_string(apArg[1], &nPatLen); | ||
11512 | nOfft = 0; /* cc warning */ | ||
11513 | if( nLen > 0 && nPatLen > 0 ){ | ||
11514 | int before = 0; | ||
11515 | /* Perform the lookup */ | ||
11516 | rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); | ||
11517 | if( rc != SXRET_OK ){ | ||
11518 | /* Pattern not found, return FALSE */ | ||
11519 | jx9_result_bool(pCtx, 0); | ||
11520 | return JX9_OK; | ||
11521 | } | ||
11522 | /* Return the portion of the string */ | ||
11523 | if( nArg > 2 ){ | ||
11524 | before = jx9_value_to_int(apArg[2]); | ||
11525 | } | ||
11526 | if( before ){ | ||
11527 | jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob)); | ||
11528 | }else{ | ||
11529 | jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); | ||
11530 | } | ||
11531 | }else{ | ||
11532 | jx9_result_bool(pCtx, 0); | ||
11533 | } | ||
11534 | return JX9_OK; | ||
11535 | } | ||
11536 | /* | ||
11537 | * string stristr(string $haystack, string $needle[, bool $before_needle = false ]) | ||
11538 | * Case-insensitive strstr(). | ||
11539 | * Parameters | ||
11540 | * $haystack | ||
11541 | * The input string. | ||
11542 | * $needle | ||
11543 | * Search pattern (must be a string). | ||
11544 | * $before_needle | ||
11545 | * If TRUE, strstr() returns the part of the haystack before the first occurrence | ||
11546 | * of the needle (excluding the needle). | ||
11547 | * Return | ||
11548 | * Returns the portion of string, or FALSE if needle is not found. | ||
11549 | */ | ||
11550 | static int jx9Builtin_stristr(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11551 | { | ||
11552 | ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ | ||
11553 | const char *zBlob, *zPattern; | ||
11554 | int nLen, nPatLen; | ||
11555 | sxu32 nOfft; | ||
11556 | sxi32 rc; | ||
11557 | if( nArg < 2 ){ | ||
11558 | /* Missing arguments, return FALSE */ | ||
11559 | jx9_result_bool(pCtx, 0); | ||
11560 | return JX9_OK; | ||
11561 | } | ||
11562 | /* Extract the needle and the haystack */ | ||
11563 | zBlob = jx9_value_to_string(apArg[0], &nLen); | ||
11564 | zPattern = jx9_value_to_string(apArg[1], &nPatLen); | ||
11565 | nOfft = 0; /* cc warning */ | ||
11566 | if( nLen > 0 && nPatLen > 0 ){ | ||
11567 | int before = 0; | ||
11568 | /* Perform the lookup */ | ||
11569 | rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); | ||
11570 | if( rc != SXRET_OK ){ | ||
11571 | /* Pattern not found, return FALSE */ | ||
11572 | jx9_result_bool(pCtx, 0); | ||
11573 | return JX9_OK; | ||
11574 | } | ||
11575 | /* Return the portion of the string */ | ||
11576 | if( nArg > 2 ){ | ||
11577 | before = jx9_value_to_int(apArg[2]); | ||
11578 | } | ||
11579 | if( before ){ | ||
11580 | jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob)); | ||
11581 | }else{ | ||
11582 | jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); | ||
11583 | } | ||
11584 | }else{ | ||
11585 | jx9_result_bool(pCtx, 0); | ||
11586 | } | ||
11587 | return JX9_OK; | ||
11588 | } | ||
11589 | /* | ||
11590 | * int strpos(string $haystack, string $needle [, int $offset = 0 ] ) | ||
11591 | * Returns the numeric position of the first occurrence of needle in the haystack string. | ||
11592 | * Parameters | ||
11593 | * $haystack | ||
11594 | * The input string. | ||
11595 | * $needle | ||
11596 | * Search pattern (must be a string). | ||
11597 | * $offset | ||
11598 | * This optional offset parameter allows you to specify which character in haystack | ||
11599 | * to start searching. The position returned is still relative to the beginning | ||
11600 | * of haystack. | ||
11601 | * Return | ||
11602 | * Returns the position as an integer.If needle is not found, strpos() will return FALSE. | ||
11603 | */ | ||
11604 | static int jx9Builtin_strpos(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11605 | { | ||
11606 | ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ | ||
11607 | const char *zBlob, *zPattern; | ||
11608 | int nLen, nPatLen, nStart; | ||
11609 | sxu32 nOfft; | ||
11610 | sxi32 rc; | ||
11611 | if( nArg < 2 ){ | ||
11612 | /* Missing arguments, return FALSE */ | ||
11613 | jx9_result_bool(pCtx, 0); | ||
11614 | return JX9_OK; | ||
11615 | } | ||
11616 | /* Extract the needle and the haystack */ | ||
11617 | zBlob = jx9_value_to_string(apArg[0], &nLen); | ||
11618 | zPattern = jx9_value_to_string(apArg[1], &nPatLen); | ||
11619 | nOfft = 0; /* cc warning */ | ||
11620 | nStart = 0; | ||
11621 | /* Peek the starting offset if available */ | ||
11622 | if( nArg > 2 ){ | ||
11623 | nStart = jx9_value_to_int(apArg[2]); | ||
11624 | if( nStart < 0 ){ | ||
11625 | nStart = -nStart; | ||
11626 | } | ||
11627 | if( nStart >= nLen ){ | ||
11628 | /* Invalid offset */ | ||
11629 | nStart = 0; | ||
11630 | }else{ | ||
11631 | zBlob += nStart; | ||
11632 | nLen -= nStart; | ||
11633 | } | ||
11634 | } | ||
11635 | if( nLen > 0 && nPatLen > 0 ){ | ||
11636 | /* Perform the lookup */ | ||
11637 | rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); | ||
11638 | if( rc != SXRET_OK ){ | ||
11639 | /* Pattern not found, return FALSE */ | ||
11640 | jx9_result_bool(pCtx, 0); | ||
11641 | return JX9_OK; | ||
11642 | } | ||
11643 | /* Return the pattern position */ | ||
11644 | jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart)); | ||
11645 | }else{ | ||
11646 | jx9_result_bool(pCtx, 0); | ||
11647 | } | ||
11648 | return JX9_OK; | ||
11649 | } | ||
11650 | /* | ||
11651 | * int stripos(string $haystack, string $needle [, int $offset = 0 ] ) | ||
11652 | * Case-insensitive strpos. | ||
11653 | * Parameters | ||
11654 | * $haystack | ||
11655 | * The input string. | ||
11656 | * $needle | ||
11657 | * Search pattern (must be a string). | ||
11658 | * $offset | ||
11659 | * This optional offset parameter allows you to specify which character in haystack | ||
11660 | * to start searching. The position returned is still relative to the beginning | ||
11661 | * of haystack. | ||
11662 | * Return | ||
11663 | * Returns the position as an integer.If needle is not found, strpos() will return FALSE. | ||
11664 | */ | ||
11665 | static int jx9Builtin_stripos(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11666 | { | ||
11667 | ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ | ||
11668 | const char *zBlob, *zPattern; | ||
11669 | int nLen, nPatLen, nStart; | ||
11670 | sxu32 nOfft; | ||
11671 | sxi32 rc; | ||
11672 | if( nArg < 2 ){ | ||
11673 | /* Missing arguments, return FALSE */ | ||
11674 | jx9_result_bool(pCtx, 0); | ||
11675 | return JX9_OK; | ||
11676 | } | ||
11677 | /* Extract the needle and the haystack */ | ||
11678 | zBlob = jx9_value_to_string(apArg[0], &nLen); | ||
11679 | zPattern = jx9_value_to_string(apArg[1], &nPatLen); | ||
11680 | nOfft = 0; /* cc warning */ | ||
11681 | nStart = 0; | ||
11682 | /* Peek the starting offset if available */ | ||
11683 | if( nArg > 2 ){ | ||
11684 | nStart = jx9_value_to_int(apArg[2]); | ||
11685 | if( nStart < 0 ){ | ||
11686 | nStart = -nStart; | ||
11687 | } | ||
11688 | if( nStart >= nLen ){ | ||
11689 | /* Invalid offset */ | ||
11690 | nStart = 0; | ||
11691 | }else{ | ||
11692 | zBlob += nStart; | ||
11693 | nLen -= nStart; | ||
11694 | } | ||
11695 | } | ||
11696 | if( nLen > 0 && nPatLen > 0 ){ | ||
11697 | /* Perform the lookup */ | ||
11698 | rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); | ||
11699 | if( rc != SXRET_OK ){ | ||
11700 | /* Pattern not found, return FALSE */ | ||
11701 | jx9_result_bool(pCtx, 0); | ||
11702 | return JX9_OK; | ||
11703 | } | ||
11704 | /* Return the pattern position */ | ||
11705 | jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart)); | ||
11706 | }else{ | ||
11707 | jx9_result_bool(pCtx, 0); | ||
11708 | } | ||
11709 | return JX9_OK; | ||
11710 | } | ||
11711 | /* | ||
11712 | * int strrpos(string $haystack, string $needle [, int $offset = 0 ] ) | ||
11713 | * Find the numeric position of the last occurrence of needle in the haystack string. | ||
11714 | * Parameters | ||
11715 | * $haystack | ||
11716 | * The input string. | ||
11717 | * $needle | ||
11718 | * Search pattern (must be a string). | ||
11719 | * $offset | ||
11720 | * If specified, search will start this number of characters counted from the beginning | ||
11721 | * of the string. If the value is negative, search will instead start from that many | ||
11722 | * characters from the end of the string, searching backwards. | ||
11723 | * Return | ||
11724 | * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. | ||
11725 | */ | ||
11726 | static int jx9Builtin_strrpos(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11727 | { | ||
11728 | const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd; | ||
11729 | ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ | ||
11730 | int nLen, nPatLen; | ||
11731 | sxu32 nOfft; | ||
11732 | sxi32 rc; | ||
11733 | if( nArg < 2 ){ | ||
11734 | /* Missing arguments, return FALSE */ | ||
11735 | jx9_result_bool(pCtx, 0); | ||
11736 | return JX9_OK; | ||
11737 | } | ||
11738 | /* Extract the needle and the haystack */ | ||
11739 | zBlob = jx9_value_to_string(apArg[0], &nLen); | ||
11740 | zPattern = jx9_value_to_string(apArg[1], &nPatLen); | ||
11741 | /* Point to the end of the pattern */ | ||
11742 | zPtr = &zBlob[nLen - 1]; | ||
11743 | zEnd = &zBlob[nLen]; | ||
11744 | /* Save the starting posistion */ | ||
11745 | zStart = zBlob; | ||
11746 | nOfft = 0; /* cc warning */ | ||
11747 | /* Peek the starting offset if available */ | ||
11748 | if( nArg > 2 ){ | ||
11749 | int nStart; | ||
11750 | nStart = jx9_value_to_int(apArg[2]); | ||
11751 | if( nStart < 0 ){ | ||
11752 | nStart = -nStart; | ||
11753 | if( nStart >= nLen ){ | ||
11754 | /* Invalid offset */ | ||
11755 | jx9_result_bool(pCtx, 0); | ||
11756 | return JX9_OK; | ||
11757 | }else{ | ||
11758 | nLen -= nStart; | ||
11759 | zPtr = &zBlob[nLen - 1]; | ||
11760 | zEnd = &zBlob[nLen]; | ||
11761 | } | ||
11762 | }else{ | ||
11763 | if( nStart >= nLen ){ | ||
11764 | /* Invalid offset */ | ||
11765 | jx9_result_bool(pCtx, 0); | ||
11766 | return JX9_OK; | ||
11767 | }else{ | ||
11768 | zBlob += nStart; | ||
11769 | nLen -= nStart; | ||
11770 | } | ||
11771 | } | ||
11772 | } | ||
11773 | if( nLen > 0 && nPatLen > 0 ){ | ||
11774 | /* Perform the lookup */ | ||
11775 | for(;;){ | ||
11776 | if( zBlob >= zPtr ){ | ||
11777 | break; | ||
11778 | } | ||
11779 | rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft); | ||
11780 | if( rc == SXRET_OK ){ | ||
11781 | /* Pattern found, return it's position */ | ||
11782 | jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart)); | ||
11783 | return JX9_OK; | ||
11784 | } | ||
11785 | zPtr--; | ||
11786 | } | ||
11787 | /* Pattern not found, return FALSE */ | ||
11788 | jx9_result_bool(pCtx, 0); | ||
11789 | }else{ | ||
11790 | jx9_result_bool(pCtx, 0); | ||
11791 | } | ||
11792 | return JX9_OK; | ||
11793 | } | ||
11794 | /* | ||
11795 | * int strripos(string $haystack, string $needle [, int $offset = 0 ] ) | ||
11796 | * Case-insensitive strrpos. | ||
11797 | * Parameters | ||
11798 | * $haystack | ||
11799 | * The input string. | ||
11800 | * $needle | ||
11801 | * Search pattern (must be a string). | ||
11802 | * $offset | ||
11803 | * If specified, search will start this number of characters counted from the beginning | ||
11804 | * of the string. If the value is negative, search will instead start from that many | ||
11805 | * characters from the end of the string, searching backwards. | ||
11806 | * Return | ||
11807 | * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. | ||
11808 | */ | ||
11809 | static int jx9Builtin_strripos(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11810 | { | ||
11811 | const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd; | ||
11812 | ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ | ||
11813 | int nLen, nPatLen; | ||
11814 | sxu32 nOfft; | ||
11815 | sxi32 rc; | ||
11816 | if( nArg < 2 ){ | ||
11817 | /* Missing arguments, return FALSE */ | ||
11818 | jx9_result_bool(pCtx, 0); | ||
11819 | return JX9_OK; | ||
11820 | } | ||
11821 | /* Extract the needle and the haystack */ | ||
11822 | zBlob = jx9_value_to_string(apArg[0], &nLen); | ||
11823 | zPattern = jx9_value_to_string(apArg[1], &nPatLen); | ||
11824 | /* Point to the end of the pattern */ | ||
11825 | zPtr = &zBlob[nLen - 1]; | ||
11826 | zEnd = &zBlob[nLen]; | ||
11827 | /* Save the starting posistion */ | ||
11828 | zStart = zBlob; | ||
11829 | nOfft = 0; /* cc warning */ | ||
11830 | /* Peek the starting offset if available */ | ||
11831 | if( nArg > 2 ){ | ||
11832 | int nStart; | ||
11833 | nStart = jx9_value_to_int(apArg[2]); | ||
11834 | if( nStart < 0 ){ | ||
11835 | nStart = -nStart; | ||
11836 | if( nStart >= nLen ){ | ||
11837 | /* Invalid offset */ | ||
11838 | jx9_result_bool(pCtx, 0); | ||
11839 | return JX9_OK; | ||
11840 | }else{ | ||
11841 | nLen -= nStart; | ||
11842 | zPtr = &zBlob[nLen - 1]; | ||
11843 | zEnd = &zBlob[nLen]; | ||
11844 | } | ||
11845 | }else{ | ||
11846 | if( nStart >= nLen ){ | ||
11847 | /* Invalid offset */ | ||
11848 | jx9_result_bool(pCtx, 0); | ||
11849 | return JX9_OK; | ||
11850 | }else{ | ||
11851 | zBlob += nStart; | ||
11852 | nLen -= nStart; | ||
11853 | } | ||
11854 | } | ||
11855 | } | ||
11856 | if( nLen > 0 && nPatLen > 0 ){ | ||
11857 | /* Perform the lookup */ | ||
11858 | for(;;){ | ||
11859 | if( zBlob >= zPtr ){ | ||
11860 | break; | ||
11861 | } | ||
11862 | rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft); | ||
11863 | if( rc == SXRET_OK ){ | ||
11864 | /* Pattern found, return it's position */ | ||
11865 | jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart)); | ||
11866 | return JX9_OK; | ||
11867 | } | ||
11868 | zPtr--; | ||
11869 | } | ||
11870 | /* Pattern not found, return FALSE */ | ||
11871 | jx9_result_bool(pCtx, 0); | ||
11872 | }else{ | ||
11873 | jx9_result_bool(pCtx, 0); | ||
11874 | } | ||
11875 | return JX9_OK; | ||
11876 | } | ||
11877 | /* | ||
11878 | * int strrchr(string $haystack, mixed $needle) | ||
11879 | * Find the last occurrence of a character in a string. | ||
11880 | * Parameters | ||
11881 | * $haystack | ||
11882 | * The input string. | ||
11883 | * $needle | ||
11884 | * If needle contains more than one character, only the first is used. | ||
11885 | * This behavior is different from that of strstr(). | ||
11886 | * If needle is not a string, it is converted to an integer and applied | ||
11887 | * as the ordinal value of a character. | ||
11888 | * Return | ||
11889 | * This function returns the portion of string, or FALSE if needle is not found. | ||
11890 | */ | ||
11891 | static int jx9Builtin_strrchr(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11892 | { | ||
11893 | const char *zBlob; | ||
11894 | int nLen, c; | ||
11895 | if( nArg < 2 ){ | ||
11896 | /* Missing arguments, return FALSE */ | ||
11897 | jx9_result_bool(pCtx, 0); | ||
11898 | return JX9_OK; | ||
11899 | } | ||
11900 | /* Extract the haystack */ | ||
11901 | zBlob = jx9_value_to_string(apArg[0], &nLen); | ||
11902 | c = 0; /* cc warning */ | ||
11903 | if( nLen > 0 ){ | ||
11904 | sxu32 nOfft; | ||
11905 | sxi32 rc; | ||
11906 | if( jx9_value_is_string(apArg[1]) ){ | ||
11907 | const char *zPattern; | ||
11908 | zPattern = jx9_value_to_string(apArg[1], 0); /* Never fail, so there is no need to check | ||
11909 | * for NULL pointer. | ||
11910 | */ | ||
11911 | c = zPattern[0]; | ||
11912 | }else{ | ||
11913 | /* Int cast */ | ||
11914 | c = jx9_value_to_int(apArg[1]); | ||
11915 | } | ||
11916 | /* Perform the lookup */ | ||
11917 | rc = SyByteFind2(zBlob, (sxu32)nLen, c, &nOfft); | ||
11918 | if( rc != SXRET_OK ){ | ||
11919 | /* No such entry, return FALSE */ | ||
11920 | jx9_result_bool(pCtx, 0); | ||
11921 | return JX9_OK; | ||
11922 | } | ||
11923 | /* Return the string portion */ | ||
11924 | jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); | ||
11925 | }else{ | ||
11926 | jx9_result_bool(pCtx, 0); | ||
11927 | } | ||
11928 | return JX9_OK; | ||
11929 | } | ||
11930 | /* | ||
11931 | * string strrev(string $string) | ||
11932 | * Reverse a string. | ||
11933 | * Parameters | ||
11934 | * $string | ||
11935 | * String to be reversed. | ||
11936 | * Return | ||
11937 | * The reversed string. | ||
11938 | */ | ||
11939 | static int jx9Builtin_strrev(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11940 | { | ||
11941 | const char *zIn, *zEnd; | ||
11942 | int nLen, c; | ||
11943 | if( nArg < 1 ){ | ||
11944 | /* Missing arguments, return NULL */ | ||
11945 | jx9_result_null(pCtx); | ||
11946 | return JX9_OK; | ||
11947 | } | ||
11948 | /* Extract the target string */ | ||
11949 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
11950 | if( nLen < 1 ){ | ||
11951 | /* Empty string Return null */ | ||
11952 | jx9_result_null(pCtx); | ||
11953 | return JX9_OK; | ||
11954 | } | ||
11955 | /* Perform the requested operation */ | ||
11956 | zEnd = &zIn[nLen - 1]; | ||
11957 | for(;;){ | ||
11958 | if( zEnd < zIn ){ | ||
11959 | /* No more input to process */ | ||
11960 | break; | ||
11961 | } | ||
11962 | /* Append current character */ | ||
11963 | c = zEnd[0]; | ||
11964 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
11965 | zEnd--; | ||
11966 | } | ||
11967 | return JX9_OK; | ||
11968 | } | ||
11969 | /* | ||
11970 | * string str_repeat(string $input, int $multiplier) | ||
11971 | * Returns input repeated multiplier times. | ||
11972 | * Parameters | ||
11973 | * $string | ||
11974 | * String to be repeated. | ||
11975 | * $multiplier | ||
11976 | * Number of time the input string should be repeated. | ||
11977 | * multiplier has to be greater than or equal to 0. If the multiplier is set | ||
11978 | * to 0, the function will return an empty string. | ||
11979 | * Return | ||
11980 | * The repeated string. | ||
11981 | */ | ||
11982 | static int jx9Builtin_str_repeat(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
11983 | { | ||
11984 | const char *zIn; | ||
11985 | int nLen, nMul; | ||
11986 | int rc; | ||
11987 | if( nArg < 2 ){ | ||
11988 | /* Missing arguments, return NULL */ | ||
11989 | jx9_result_null(pCtx); | ||
11990 | return JX9_OK; | ||
11991 | } | ||
11992 | /* Extract the target string */ | ||
11993 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
11994 | if( nLen < 1 ){ | ||
11995 | /* Empty string.Return null */ | ||
11996 | jx9_result_null(pCtx); | ||
11997 | return JX9_OK; | ||
11998 | } | ||
11999 | /* Extract the multiplier */ | ||
12000 | nMul = jx9_value_to_int(apArg[1]); | ||
12001 | if( nMul < 1 ){ | ||
12002 | /* Return the empty string */ | ||
12003 | jx9_result_string(pCtx, "", 0); | ||
12004 | return JX9_OK; | ||
12005 | } | ||
12006 | /* Perform the requested operation */ | ||
12007 | for(;;){ | ||
12008 | if( nMul < 1 ){ | ||
12009 | break; | ||
12010 | } | ||
12011 | /* Append the copy */ | ||
12012 | rc = jx9_result_string(pCtx, zIn, nLen); | ||
12013 | if( rc != JX9_OK ){ | ||
12014 | /* Out of memory, break immediately */ | ||
12015 | break; | ||
12016 | } | ||
12017 | nMul--; | ||
12018 | } | ||
12019 | return JX9_OK; | ||
12020 | } | ||
12021 | /* | ||
12022 | * string nl2br(string $string[, bool $is_xhtml = true ]) | ||
12023 | * Inserts HTML line breaks before all newlines in a string. | ||
12024 | * Parameters | ||
12025 | * $string | ||
12026 | * The input string. | ||
12027 | * $is_xhtml | ||
12028 | * Whenever to use XHTML compatible line breaks or not. | ||
12029 | * Return | ||
12030 | * The processed string. | ||
12031 | */ | ||
12032 | static int jx9Builtin_nl2br(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12033 | { | ||
12034 | const char *zIn, *zCur, *zEnd; | ||
12035 | int is_xhtml = 0; | ||
12036 | int nLen; | ||
12037 | if( nArg < 1 ){ | ||
12038 | /* Missing arguments, return the empty string */ | ||
12039 | jx9_result_string(pCtx, "", 0); | ||
12040 | return JX9_OK; | ||
12041 | } | ||
12042 | /* Extract the target string */ | ||
12043 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
12044 | if( nLen < 1 ){ | ||
12045 | /* Empty string, return null */ | ||
12046 | jx9_result_null(pCtx); | ||
12047 | return JX9_OK; | ||
12048 | } | ||
12049 | if( nArg > 1 ){ | ||
12050 | is_xhtml = jx9_value_to_bool(apArg[1]); | ||
12051 | } | ||
12052 | zEnd = &zIn[nLen]; | ||
12053 | /* Perform the requested operation */ | ||
12054 | for(;;){ | ||
12055 | zCur = zIn; | ||
12056 | /* Delimit the string */ | ||
12057 | while( zIn < zEnd && (zIn[0] != '\n'&& zIn[0] != '\r') ){ | ||
12058 | zIn++; | ||
12059 | } | ||
12060 | if( zCur < zIn ){ | ||
12061 | /* Output chunk verbatim */ | ||
12062 | jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); | ||
12063 | } | ||
12064 | if( zIn >= zEnd ){ | ||
12065 | /* No more input to process */ | ||
12066 | break; | ||
12067 | } | ||
12068 | /* Output the HTML line break */ | ||
12069 | if( is_xhtml ){ | ||
12070 | jx9_result_string(pCtx, "<br>", (int)sizeof("<br>")-1); | ||
12071 | }else{ | ||
12072 | jx9_result_string(pCtx, "<br/>", (int)sizeof("<br/>")-1); | ||
12073 | } | ||
12074 | zCur = zIn; | ||
12075 | /* Append trailing line */ | ||
12076 | while( zIn < zEnd && (zIn[0] == '\n' || zIn[0] == '\r') ){ | ||
12077 | zIn++; | ||
12078 | } | ||
12079 | if( zCur < zIn ){ | ||
12080 | /* Output chunk verbatim */ | ||
12081 | jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); | ||
12082 | } | ||
12083 | } | ||
12084 | return JX9_OK; | ||
12085 | } | ||
12086 | /* | ||
12087 | * Format a given string and invoke the given callback on each processed chunk. | ||
12088 | * According to the JX9 reference manual. | ||
12089 | * The format string is composed of zero or more directives: ordinary characters | ||
12090 | * (excluding %) that are copied directly to the result, and conversion | ||
12091 | * specifications, each of which results in fetching its own parameter. | ||
12092 | * This applies to both sprintf() and printf(). | ||
12093 | * Each conversion specification consists of a percent sign (%), followed by one | ||
12094 | * or more of these elements, in order: | ||
12095 | * An optional sign specifier that forces a sign (- or +) to be used on a number. | ||
12096 | * By default, only the - sign is used on a number if it's negative. This specifier forces | ||
12097 | * positive numbers to have the + sign attached as well. | ||
12098 | * An optional padding specifier that says what character will be used for padding | ||
12099 | * the results to the right string size. This may be a space character or a 0 (zero character). | ||
12100 | * The default is to pad with spaces. An alternate padding character can be specified by prefixing | ||
12101 | * it with a single quote ('). See the examples below. | ||
12102 | * An optional alignment specifier that says if the result should be left-justified or right-justified. | ||
12103 | * The default is right-justified; a - character here will make it left-justified. | ||
12104 | * An optional number, a width specifier that says how many characters (minimum) this conversion | ||
12105 | * should result in. | ||
12106 | * An optional precision specifier in the form of a period (`.') followed by an optional decimal | ||
12107 | * digit string that says how many decimal digits should be displayed for floating-point numbers. | ||
12108 | * When using this specifier on a string, it acts as a cutoff point, setting a maximum character | ||
12109 | * limit to the string. | ||
12110 | * A type specifier that says what type the argument data should be treated as. Possible types: | ||
12111 | * % - a literal percent character. No argument is required. | ||
12112 | * b - the argument is treated as an integer, and presented as a binary number. | ||
12113 | * c - the argument is treated as an integer, and presented as the character with that ASCII value. | ||
12114 | * d - the argument is treated as an integer, and presented as a (signed) decimal number. | ||
12115 | * e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands | ||
12116 | * for the number of digits after the decimal point. | ||
12117 | * E - like %e but uses uppercase letter (e.g. 1.2E+2). | ||
12118 | * u - the argument is treated as an integer, and presented as an unsigned decimal number. | ||
12119 | * f - the argument is treated as a float, and presented as a floating-point number (locale aware). | ||
12120 | * F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). | ||
12121 | * g - shorter of %e and %f. | ||
12122 | * G - shorter of %E and %f. | ||
12123 | * o - the argument is treated as an integer, and presented as an octal number. | ||
12124 | * s - the argument is treated as and presented as a string. | ||
12125 | * x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters). | ||
12126 | * X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters). | ||
12127 | */ | ||
12128 | /* | ||
12129 | * This implementation is based on the one found in the SQLite3 source tree. | ||
12130 | */ | ||
12131 | #define JX9_FMT_BUFSIZ 1024 /* Conversion buffer size */ | ||
12132 | /* | ||
12133 | ** Conversion types fall into various categories as defined by the | ||
12134 | ** following enumeration. | ||
12135 | */ | ||
12136 | #define JX9_FMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ | ||
12137 | #define JX9_FMT_FLOAT 2 /* Floating point.%f */ | ||
12138 | #define JX9_FMT_EXP 3 /* Exponentional notation.%e and %E */ | ||
12139 | #define JX9_FMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ | ||
12140 | #define JX9_FMT_SIZE 5 /* Total number of characters processed so far.%n */ | ||
12141 | #define JX9_FMT_STRING 6 /* Strings.%s */ | ||
12142 | #define JX9_FMT_PERCENT 7 /* Percent symbol.%% */ | ||
12143 | #define JX9_FMT_CHARX 8 /* Characters.%c */ | ||
12144 | #define JX9_FMT_ERROR 9 /* Used to indicate no such conversion type */ | ||
12145 | /* | ||
12146 | ** Allowed values for jx9_fmt_info.flags | ||
12147 | */ | ||
12148 | #define JX9_FMT_FLAG_SIGNED 0x01 | ||
12149 | #define JX9_FMT_FLAG_UNSIGNED 0x02 | ||
12150 | /* | ||
12151 | ** Each builtin conversion character (ex: the 'd' in "%d") is described | ||
12152 | ** by an instance of the following structure | ||
12153 | */ | ||
12154 | typedef struct jx9_fmt_info jx9_fmt_info; | ||
12155 | struct jx9_fmt_info | ||
12156 | { | ||
12157 | char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */ | ||
12158 | sxu8 base; /* The base for radix conversion */ | ||
12159 | int flags; /* One or more of JX9_FMT_FLAG_ constants below */ | ||
12160 | sxu8 type; /* Conversion paradigm */ | ||
12161 | char *charset; /* The character set for conversion */ | ||
12162 | char *prefix; /* Prefix on non-zero values in alt format */ | ||
12163 | }; | ||
12164 | #ifndef JX9_OMIT_FLOATING_POINT | ||
12165 | /* | ||
12166 | ** "*val" is a double such that 0.1 <= *val < 10.0 | ||
12167 | ** Return the ascii code for the leading digit of *val, then | ||
12168 | ** multiply "*val" by 10.0 to renormalize. | ||
12169 | ** | ||
12170 | ** Example: | ||
12171 | ** input: *val = 3.14159 | ||
12172 | ** output: *val = 1.4159 function return = '3' | ||
12173 | ** | ||
12174 | ** The counter *cnt is incremented each time. After counter exceeds | ||
12175 | ** 16 (the number of significant digits in a 64-bit float) '0' is | ||
12176 | ** always returned. | ||
12177 | */ | ||
12178 | static int vxGetdigit(sxlongreal *val, int *cnt) | ||
12179 | { | ||
12180 | sxlongreal d; | ||
12181 | int digit; | ||
12182 | |||
12183 | if( (*cnt)++ >= 16 ){ | ||
12184 | return '0'; | ||
12185 | } | ||
12186 | digit = (int)*val; | ||
12187 | d = digit; | ||
12188 | *val = (*val - d)*10.0; | ||
12189 | return digit + '0' ; | ||
12190 | } | ||
12191 | #endif /* JX9_OMIT_FLOATING_POINT */ | ||
12192 | /* | ||
12193 | * The following table is searched linearly, so it is good to put the most frequently | ||
12194 | * used conversion types first. | ||
12195 | */ | ||
12196 | static const jx9_fmt_info aFmt[] = { | ||
12197 | { 'd', 10, JX9_FMT_FLAG_SIGNED, JX9_FMT_RADIX, "0123456789", 0 }, | ||
12198 | { 's', 0, 0, JX9_FMT_STRING, 0, 0 }, | ||
12199 | { 'c', 0, 0, JX9_FMT_CHARX, 0, 0 }, | ||
12200 | { 'x', 16, 0, JX9_FMT_RADIX, "0123456789abcdef", "x0" }, | ||
12201 | { 'X', 16, 0, JX9_FMT_RADIX, "0123456789ABCDEF", "X0" }, | ||
12202 | { 'b', 2, 0, JX9_FMT_RADIX, "01", "b0"}, | ||
12203 | { 'o', 8, 0, JX9_FMT_RADIX, "01234567", "0" }, | ||
12204 | { 'u', 10, 0, JX9_FMT_RADIX, "0123456789", 0 }, | ||
12205 | { 'f', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 }, | ||
12206 | { 'F', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 }, | ||
12207 | { 'e', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "e", 0 }, | ||
12208 | { 'E', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "E", 0 }, | ||
12209 | { 'g', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "e", 0 }, | ||
12210 | { 'G', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "E", 0 }, | ||
12211 | { '%', 0, 0, JX9_FMT_PERCENT, 0, 0 } | ||
12212 | }; | ||
12213 | /* | ||
12214 | * Format a given string. | ||
12215 | * The root program. All variations call this core. | ||
12216 | * INPUTS: | ||
12217 | * xConsumer This is a pointer to a function taking four arguments | ||
12218 | * 1. A pointer to the call context. | ||
12219 | * 2. A pointer to the list of characters to be output | ||
12220 | * (Note, this list is NOT null terminated.) | ||
12221 | * 3. An integer number of characters to be output. | ||
12222 | * (Note: This number might be zero.) | ||
12223 | * 4. Upper layer private data. | ||
12224 | * zIn This is the format string, as in the usual print. | ||
12225 | * apArg This is a pointer to a list of arguments. | ||
12226 | */ | ||
12227 | JX9_PRIVATE sxi32 jx9InputFormat( | ||
12228 | int (*xConsumer)(jx9_context *, const char *, int, void *), /* Format consumer */ | ||
12229 | jx9_context *pCtx, /* call context */ | ||
12230 | const char *zIn, /* Format string */ | ||
12231 | int nByte, /* Format string length */ | ||
12232 | int nArg, /* Total argument of the given arguments */ | ||
12233 | jx9_value **apArg, /* User arguments */ | ||
12234 | void *pUserData, /* Last argument to xConsumer() */ | ||
12235 | int vf /* TRUE if called from vfprintf, vsprintf context */ | ||
12236 | ) | ||
12237 | { | ||
12238 | char spaces[] = " "; | ||
12239 | #define etSPACESIZE ((int)sizeof(spaces)-1) | ||
12240 | const char *zCur, *zEnd = &zIn[nByte]; | ||
12241 | char *zBuf, zWorker[JX9_FMT_BUFSIZ]; /* Working buffer */ | ||
12242 | const jx9_fmt_info *pInfo; /* Pointer to the appropriate info structure */ | ||
12243 | int flag_alternateform; /* True if "#" flag is present */ | ||
12244 | int flag_leftjustify; /* True if "-" flag is present */ | ||
12245 | int flag_blanksign; /* True if " " flag is present */ | ||
12246 | int flag_plussign; /* True if "+" flag is present */ | ||
12247 | int flag_zeropad; /* True if field width constant starts with zero */ | ||
12248 | jx9_value *pArg; /* Current processed argument */ | ||
12249 | jx9_int64 iVal; | ||
12250 | int precision; /* Precision of the current field */ | ||
12251 | char *zExtra; | ||
12252 | int c, rc, n; | ||
12253 | int length; /* Length of the field */ | ||
12254 | int prefix; | ||
12255 | sxu8 xtype; /* Conversion paradigm */ | ||
12256 | int width; /* Width of the current field */ | ||
12257 | int idx; | ||
12258 | n = (vf == TRUE) ? 0 : 1; | ||
12259 | #define NEXT_ARG ( n < nArg ? apArg[n++] : 0 ) | ||
12260 | /* Start the format process */ | ||
12261 | for(;;){ | ||
12262 | zCur = zIn; | ||
12263 | while( zIn < zEnd && zIn[0] != '%' ){ | ||
12264 | zIn++; | ||
12265 | } | ||
12266 | if( zCur < zIn ){ | ||
12267 | /* Consume chunk verbatim */ | ||
12268 | rc = xConsumer(pCtx, zCur, (int)(zIn-zCur), pUserData); | ||
12269 | if( rc == SXERR_ABORT ){ | ||
12270 | /* Callback request an operation abort */ | ||
12271 | break; | ||
12272 | } | ||
12273 | } | ||
12274 | if( zIn >= zEnd ){ | ||
12275 | /* No more input to process, break immediately */ | ||
12276 | break; | ||
12277 | } | ||
12278 | /* Find out what flags are present */ | ||
12279 | flag_leftjustify = flag_plussign = flag_blanksign = | ||
12280 | flag_alternateform = flag_zeropad = 0; | ||
12281 | zIn++; /* Jump the precent sign */ | ||
12282 | do{ | ||
12283 | c = zIn[0]; | ||
12284 | switch( c ){ | ||
12285 | case '-': flag_leftjustify = 1; c = 0; break; | ||
12286 | case '+': flag_plussign = 1; c = 0; break; | ||
12287 | case ' ': flag_blanksign = 1; c = 0; break; | ||
12288 | case '#': flag_alternateform = 1; c = 0; break; | ||
12289 | case '0': flag_zeropad = 1; c = 0; break; | ||
12290 | case '\'': | ||
12291 | zIn++; | ||
12292 | if( zIn < zEnd ){ | ||
12293 | /* An alternate padding character can be specified by prefixing it with a single quote (') */ | ||
12294 | c = zIn[0]; | ||
12295 | for(idx = 0 ; idx < etSPACESIZE ; ++idx ){ | ||
12296 | spaces[idx] = (char)c; | ||
12297 | } | ||
12298 | c = 0; | ||
12299 | } | ||
12300 | break; | ||
12301 | default: break; | ||
12302 | } | ||
12303 | }while( c==0 && (zIn++ < zEnd) ); | ||
12304 | /* Get the field width */ | ||
12305 | width = 0; | ||
12306 | while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ | ||
12307 | width = width*10 + (zIn[0] - '0'); | ||
12308 | zIn++; | ||
12309 | } | ||
12310 | if( zIn < zEnd && zIn[0] == '$' ){ | ||
12311 | /* Position specifer */ | ||
12312 | if( width > 0 ){ | ||
12313 | n = width; | ||
12314 | if( vf && n > 0 ){ | ||
12315 | n--; | ||
12316 | } | ||
12317 | } | ||
12318 | zIn++; | ||
12319 | width = 0; | ||
12320 | if( zIn < zEnd && zIn[0] == '0' ){ | ||
12321 | flag_zeropad = 1; | ||
12322 | zIn++; | ||
12323 | } | ||
12324 | while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ | ||
12325 | width = width*10 + (zIn[0] - '0'); | ||
12326 | zIn++; | ||
12327 | } | ||
12328 | } | ||
12329 | if( width > JX9_FMT_BUFSIZ-10 ){ | ||
12330 | width = JX9_FMT_BUFSIZ-10; | ||
12331 | } | ||
12332 | /* Get the precision */ | ||
12333 | precision = -1; | ||
12334 | if( zIn < zEnd && zIn[0] == '.' ){ | ||
12335 | precision = 0; | ||
12336 | zIn++; | ||
12337 | while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ | ||
12338 | precision = precision*10 + (zIn[0] - '0'); | ||
12339 | zIn++; | ||
12340 | } | ||
12341 | } | ||
12342 | if( zIn >= zEnd ){ | ||
12343 | /* No more input */ | ||
12344 | break; | ||
12345 | } | ||
12346 | /* Fetch the info entry for the field */ | ||
12347 | pInfo = 0; | ||
12348 | xtype = JX9_FMT_ERROR; | ||
12349 | c = zIn[0]; | ||
12350 | zIn++; /* Jump the format specifer */ | ||
12351 | for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ | ||
12352 | if( c==aFmt[idx].fmttype ){ | ||
12353 | pInfo = &aFmt[idx]; | ||
12354 | xtype = pInfo->type; | ||
12355 | break; | ||
12356 | } | ||
12357 | } | ||
12358 | zBuf = zWorker; /* Point to the working buffer */ | ||
12359 | length = 0; | ||
12360 | zExtra = 0; | ||
12361 | /* | ||
12362 | ** At this point, variables are initialized as follows: | ||
12363 | ** | ||
12364 | ** flag_alternateform TRUE if a '#' is present. | ||
12365 | ** flag_plussign TRUE if a '+' is present. | ||
12366 | ** flag_leftjustify TRUE if a '-' is present or if the | ||
12367 | ** field width was negative. | ||
12368 | ** flag_zeropad TRUE if the width began with 0. | ||
12369 | ** the conversion character. | ||
12370 | ** flag_blanksign TRUE if a ' ' is present. | ||
12371 | ** width The specified field width. This is | ||
12372 | ** always non-negative. Zero is the default. | ||
12373 | ** precision The specified precision. The default | ||
12374 | ** is -1. | ||
12375 | */ | ||
12376 | switch(xtype){ | ||
12377 | case JX9_FMT_PERCENT: | ||
12378 | /* A literal percent character */ | ||
12379 | zWorker[0] = '%'; | ||
12380 | length = (int)sizeof(char); | ||
12381 | break; | ||
12382 | case JX9_FMT_CHARX: | ||
12383 | /* The argument is treated as an integer, and presented as the character | ||
12384 | * with that ASCII value | ||
12385 | */ | ||
12386 | pArg = NEXT_ARG; | ||
12387 | if( pArg == 0 ){ | ||
12388 | c = 0; | ||
12389 | }else{ | ||
12390 | c = jx9_value_to_int(pArg); | ||
12391 | } | ||
12392 | /* NUL byte is an acceptable value */ | ||
12393 | zWorker[0] = (char)c; | ||
12394 | length = (int)sizeof(char); | ||
12395 | break; | ||
12396 | case JX9_FMT_STRING: | ||
12397 | /* the argument is treated as and presented as a string */ | ||
12398 | pArg = NEXT_ARG; | ||
12399 | if( pArg == 0 ){ | ||
12400 | length = 0; | ||
12401 | }else{ | ||
12402 | zBuf = (char *)jx9_value_to_string(pArg, &length); | ||
12403 | } | ||
12404 | if( length < 1 ){ | ||
12405 | zBuf = " "; | ||
12406 | length = (int)sizeof(char); | ||
12407 | } | ||
12408 | if( precision>=0 && precision<length ){ | ||
12409 | length = precision; | ||
12410 | } | ||
12411 | if( flag_zeropad ){ | ||
12412 | /* zero-padding works on strings too */ | ||
12413 | for(idx = 0 ; idx < etSPACESIZE ; ++idx ){ | ||
12414 | spaces[idx] = '0'; | ||
12415 | } | ||
12416 | } | ||
12417 | break; | ||
12418 | case JX9_FMT_RADIX: | ||
12419 | pArg = NEXT_ARG; | ||
12420 | if( pArg == 0 ){ | ||
12421 | iVal = 0; | ||
12422 | }else{ | ||
12423 | iVal = jx9_value_to_int64(pArg); | ||
12424 | } | ||
12425 | /* Limit the precision to prevent overflowing buf[] during conversion */ | ||
12426 | if( precision>JX9_FMT_BUFSIZ-40 ){ | ||
12427 | precision = JX9_FMT_BUFSIZ-40; | ||
12428 | } | ||
12429 | #if 1 | ||
12430 | /* For the format %#x, the value zero is printed "0" not "0x0". | ||
12431 | ** I think this is stupid.*/ | ||
12432 | if( iVal==0 ) flag_alternateform = 0; | ||
12433 | #else | ||
12434 | /* More sensible: turn off the prefix for octal (to prevent "00"), | ||
12435 | ** but leave the prefix for hex.*/ | ||
12436 | if( iVal==0 && pInfo->base==8 ) flag_alternateform = 0; | ||
12437 | #endif | ||
12438 | if( pInfo->flags & JX9_FMT_FLAG_SIGNED ){ | ||
12439 | if( iVal<0 ){ | ||
12440 | iVal = -iVal; | ||
12441 | /* Ticket 1433-003 */ | ||
12442 | if( iVal < 0 ){ | ||
12443 | /* Overflow */ | ||
12444 | iVal= 0x7FFFFFFFFFFFFFFF; | ||
12445 | } | ||
12446 | prefix = '-'; | ||
12447 | }else if( flag_plussign ) prefix = '+'; | ||
12448 | else if( flag_blanksign ) prefix = ' '; | ||
12449 | else prefix = 0; | ||
12450 | }else{ | ||
12451 | if( iVal<0 ){ | ||
12452 | iVal = -iVal; | ||
12453 | /* Ticket 1433-003 */ | ||
12454 | if( iVal < 0 ){ | ||
12455 | /* Overflow */ | ||
12456 | iVal= 0x7FFFFFFFFFFFFFFF; | ||
12457 | } | ||
12458 | } | ||
12459 | prefix = 0; | ||
12460 | } | ||
12461 | if( flag_zeropad && precision<width-(prefix!=0) ){ | ||
12462 | precision = width-(prefix!=0); | ||
12463 | } | ||
12464 | zBuf = &zWorker[JX9_FMT_BUFSIZ-1]; | ||
12465 | { | ||
12466 | register char *cset; /* Use registers for speed */ | ||
12467 | register int base; | ||
12468 | cset = pInfo->charset; | ||
12469 | base = pInfo->base; | ||
12470 | do{ /* Convert to ascii */ | ||
12471 | *(--zBuf) = cset[iVal%base]; | ||
12472 | iVal = iVal/base; | ||
12473 | }while( iVal>0 ); | ||
12474 | } | ||
12475 | length = &zWorker[JX9_FMT_BUFSIZ-1]-zBuf; | ||
12476 | for(idx=precision-length; idx>0; idx--){ | ||
12477 | *(--zBuf) = '0'; /* Zero pad */ | ||
12478 | } | ||
12479 | if( prefix ) *(--zBuf) = (char)prefix; /* Add sign */ | ||
12480 | if( flag_alternateform && pInfo->prefix ){ /* Add "0" or "0x" */ | ||
12481 | char *pre, x; | ||
12482 | pre = pInfo->prefix; | ||
12483 | if( *zBuf!=pre[0] ){ | ||
12484 | for(pre=pInfo->prefix; (x=(*pre))!=0; pre++) *(--zBuf) = x; | ||
12485 | } | ||
12486 | } | ||
12487 | length = &zWorker[JX9_FMT_BUFSIZ-1]-zBuf; | ||
12488 | break; | ||
12489 | case JX9_FMT_FLOAT: | ||
12490 | case JX9_FMT_EXP: | ||
12491 | case JX9_FMT_GENERIC:{ | ||
12492 | #ifndef JX9_OMIT_FLOATING_POINT | ||
12493 | long double realvalue; | ||
12494 | int exp; /* exponent of real numbers */ | ||
12495 | double rounder; /* Used for rounding floating point values */ | ||
12496 | int flag_dp; /* True if decimal point should be shown */ | ||
12497 | int flag_rtz; /* True if trailing zeros should be removed */ | ||
12498 | int flag_exp; /* True to force display of the exponent */ | ||
12499 | int nsd; /* Number of significant digits returned */ | ||
12500 | pArg = NEXT_ARG; | ||
12501 | if( pArg == 0 ){ | ||
12502 | realvalue = 0; | ||
12503 | }else{ | ||
12504 | realvalue = jx9_value_to_double(pArg); | ||
12505 | } | ||
12506 | if( precision<0 ) precision = 6; /* Set default precision */ | ||
12507 | if( precision>JX9_FMT_BUFSIZ-40) precision = JX9_FMT_BUFSIZ-40; | ||
12508 | if( realvalue<0.0 ){ | ||
12509 | realvalue = -realvalue; | ||
12510 | prefix = '-'; | ||
12511 | }else{ | ||
12512 | if( flag_plussign ) prefix = '+'; | ||
12513 | else if( flag_blanksign ) prefix = ' '; | ||
12514 | else prefix = 0; | ||
12515 | } | ||
12516 | if( pInfo->type==JX9_FMT_GENERIC && precision>0 ) precision--; | ||
12517 | rounder = 0.0; | ||
12518 | #if 0 | ||
12519 | /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ | ||
12520 | for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); | ||
12521 | #else | ||
12522 | /* It makes more sense to use 0.5 */ | ||
12523 | for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); | ||
12524 | #endif | ||
12525 | if( pInfo->type==JX9_FMT_FLOAT ) realvalue += rounder; | ||
12526 | /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ | ||
12527 | exp = 0; | ||
12528 | if( realvalue>0.0 ){ | ||
12529 | while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } | ||
12530 | while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } | ||
12531 | while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } | ||
12532 | while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } | ||
12533 | if( exp>350 || exp<-350 ){ | ||
12534 | zBuf = "NaN"; | ||
12535 | length = 3; | ||
12536 | break; | ||
12537 | } | ||
12538 | } | ||
12539 | zBuf = zWorker; | ||
12540 | /* | ||
12541 | ** If the field type is etGENERIC, then convert to either etEXP | ||
12542 | ** or etFLOAT, as appropriate. | ||
12543 | */ | ||
12544 | flag_exp = xtype==JX9_FMT_EXP; | ||
12545 | if( xtype!=JX9_FMT_FLOAT ){ | ||
12546 | realvalue += rounder; | ||
12547 | if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } | ||
12548 | } | ||
12549 | if( xtype==JX9_FMT_GENERIC ){ | ||
12550 | flag_rtz = !flag_alternateform; | ||
12551 | if( exp<-4 || exp>precision ){ | ||
12552 | xtype = JX9_FMT_EXP; | ||
12553 | }else{ | ||
12554 | precision = precision - exp; | ||
12555 | xtype = JX9_FMT_FLOAT; | ||
12556 | } | ||
12557 | }else{ | ||
12558 | flag_rtz = 0; | ||
12559 | } | ||
12560 | /* | ||
12561 | ** The "exp+precision" test causes output to be of type etEXP if | ||
12562 | ** the precision is too large to fit in buf[]. | ||
12563 | */ | ||
12564 | nsd = 0; | ||
12565 | if( xtype==JX9_FMT_FLOAT && exp+precision<JX9_FMT_BUFSIZ-30 ){ | ||
12566 | flag_dp = (precision>0 || flag_alternateform); | ||
12567 | if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ | ||
12568 | if( exp<0 ) *(zBuf++) = '0'; /* Digits before "." */ | ||
12569 | else for(; exp>=0; exp--) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); | ||
12570 | if( flag_dp ) *(zBuf++) = '.'; /* The decimal point */ | ||
12571 | for(exp++; exp<0 && precision>0; precision--, exp++){ | ||
12572 | *(zBuf++) = '0'; | ||
12573 | } | ||
12574 | while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); | ||
12575 | *(zBuf--) = 0; /* Null terminate */ | ||
12576 | if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ | ||
12577 | while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; | ||
12578 | if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; | ||
12579 | } | ||
12580 | zBuf++; /* point to next free slot */ | ||
12581 | }else{ /* etEXP or etGENERIC */ | ||
12582 | flag_dp = (precision>0 || flag_alternateform); | ||
12583 | if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ | ||
12584 | *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); /* First digit */ | ||
12585 | if( flag_dp ) *(zBuf++) = '.'; /* Decimal point */ | ||
12586 | while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); | ||
12587 | zBuf--; /* point to last digit */ | ||
12588 | if( flag_rtz && flag_dp ){ /* Remove tail zeros */ | ||
12589 | while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; | ||
12590 | if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; | ||
12591 | } | ||
12592 | zBuf++; /* point to next free slot */ | ||
12593 | if( exp || flag_exp ){ | ||
12594 | *(zBuf++) = pInfo->charset[0]; | ||
12595 | if( exp<0 ){ *(zBuf++) = '-'; exp = -exp; } /* sign of exp */ | ||
12596 | else { *(zBuf++) = '+'; } | ||
12597 | if( exp>=100 ){ | ||
12598 | *(zBuf++) = (char)((exp/100)+'0'); /* 100's digit */ | ||
12599 | exp %= 100; | ||
12600 | } | ||
12601 | *(zBuf++) = (char)(exp/10+'0'); /* 10's digit */ | ||
12602 | *(zBuf++) = (char)(exp%10+'0'); /* 1's digit */ | ||
12603 | } | ||
12604 | } | ||
12605 | /* The converted number is in buf[] and zero terminated.Output it. | ||
12606 | ** Note that the number is in the usual order, not reversed as with | ||
12607 | ** integer conversions.*/ | ||
12608 | length = (int)(zBuf-zWorker); | ||
12609 | zBuf = zWorker; | ||
12610 | /* Special case: Add leading zeros if the flag_zeropad flag is | ||
12611 | ** set and we are not left justified */ | ||
12612 | if( flag_zeropad && !flag_leftjustify && length < width){ | ||
12613 | int i; | ||
12614 | int nPad = width - length; | ||
12615 | for(i=width; i>=nPad; i--){ | ||
12616 | zBuf[i] = zBuf[i-nPad]; | ||
12617 | } | ||
12618 | i = prefix!=0; | ||
12619 | while( nPad-- ) zBuf[i++] = '0'; | ||
12620 | length = width; | ||
12621 | } | ||
12622 | #else | ||
12623 | zBuf = " "; | ||
12624 | length = (int)sizeof(char); | ||
12625 | #endif /* JX9_OMIT_FLOATING_POINT */ | ||
12626 | break; | ||
12627 | } | ||
12628 | default: | ||
12629 | /* Invalid format specifer */ | ||
12630 | zWorker[0] = '?'; | ||
12631 | length = (int)sizeof(char); | ||
12632 | break; | ||
12633 | } | ||
12634 | /* | ||
12635 | ** The text of the conversion is pointed to by "zBuf" and is | ||
12636 | ** "length" characters long.The field width is "width".Do | ||
12637 | ** the output. | ||
12638 | */ | ||
12639 | if( !flag_leftjustify ){ | ||
12640 | register int nspace; | ||
12641 | nspace = width-length; | ||
12642 | if( nspace>0 ){ | ||
12643 | while( nspace>=etSPACESIZE ){ | ||
12644 | rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData); | ||
12645 | if( rc != SXRET_OK ){ | ||
12646 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
12647 | } | ||
12648 | nspace -= etSPACESIZE; | ||
12649 | } | ||
12650 | if( nspace>0 ){ | ||
12651 | rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData); | ||
12652 | if( rc != SXRET_OK ){ | ||
12653 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
12654 | } | ||
12655 | } | ||
12656 | } | ||
12657 | } | ||
12658 | if( length>0 ){ | ||
12659 | rc = xConsumer(pCtx, zBuf, (unsigned int)length, pUserData); | ||
12660 | if( rc != SXRET_OK ){ | ||
12661 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
12662 | } | ||
12663 | } | ||
12664 | if( flag_leftjustify ){ | ||
12665 | register int nspace; | ||
12666 | nspace = width-length; | ||
12667 | if( nspace>0 ){ | ||
12668 | while( nspace>=etSPACESIZE ){ | ||
12669 | rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData); | ||
12670 | if( rc != SXRET_OK ){ | ||
12671 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
12672 | } | ||
12673 | nspace -= etSPACESIZE; | ||
12674 | } | ||
12675 | if( nspace>0 ){ | ||
12676 | rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData); | ||
12677 | if( rc != SXRET_OK ){ | ||
12678 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
12679 | } | ||
12680 | } | ||
12681 | } | ||
12682 | } | ||
12683 | }/* for(;;) */ | ||
12684 | return SXRET_OK; | ||
12685 | } | ||
12686 | /* | ||
12687 | * Callback [i.e: Formatted input consumer] of the sprintf function. | ||
12688 | */ | ||
12689 | static int sprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) | ||
12690 | { | ||
12691 | /* Consume directly */ | ||
12692 | jx9_result_string(pCtx, zInput, nLen); | ||
12693 | SXUNUSED(pUserData); /* cc warning */ | ||
12694 | return JX9_OK; | ||
12695 | } | ||
12696 | /* | ||
12697 | * string sprintf(string $format[, mixed $args [, mixed $... ]]) | ||
12698 | * Return a formatted string. | ||
12699 | * Parameters | ||
12700 | * $format | ||
12701 | * The format string (see block comment above) | ||
12702 | * Return | ||
12703 | * A string produced according to the formatting string format. | ||
12704 | */ | ||
12705 | static int jx9Builtin_sprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12706 | { | ||
12707 | const char *zFormat; | ||
12708 | int nLen; | ||
12709 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
12710 | /* Missing/Invalid arguments, return the empty string */ | ||
12711 | jx9_result_string(pCtx, "", 0); | ||
12712 | return JX9_OK; | ||
12713 | } | ||
12714 | /* Extract the string format */ | ||
12715 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
12716 | if( nLen < 1 ){ | ||
12717 | /* Empty string */ | ||
12718 | jx9_result_string(pCtx, "", 0); | ||
12719 | return JX9_OK; | ||
12720 | } | ||
12721 | /* Format the string */ | ||
12722 | jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, nArg, apArg, 0, FALSE); | ||
12723 | return JX9_OK; | ||
12724 | } | ||
12725 | /* | ||
12726 | * Callback [i.e: Formatted input consumer] of the printf function. | ||
12727 | */ | ||
12728 | static int printfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) | ||
12729 | { | ||
12730 | jx9_int64 *pCounter = (jx9_int64 *)pUserData; | ||
12731 | /* Call the VM output consumer directly */ | ||
12732 | jx9_context_output(pCtx, zInput, nLen); | ||
12733 | /* Increment counter */ | ||
12734 | *pCounter += nLen; | ||
12735 | return JX9_OK; | ||
12736 | } | ||
12737 | /* | ||
12738 | * int64 printf(string $format[, mixed $args[, mixed $... ]]) | ||
12739 | * Output a formatted string. | ||
12740 | * Parameters | ||
12741 | * $format | ||
12742 | * See sprintf() for a description of format. | ||
12743 | * Return | ||
12744 | * The length of the outputted string. | ||
12745 | */ | ||
12746 | static int jx9Builtin_printf(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12747 | { | ||
12748 | jx9_int64 nCounter = 0; | ||
12749 | const char *zFormat; | ||
12750 | int nLen; | ||
12751 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
12752 | /* Missing/Invalid arguments, return 0 */ | ||
12753 | jx9_result_int(pCtx, 0); | ||
12754 | return JX9_OK; | ||
12755 | } | ||
12756 | /* Extract the string format */ | ||
12757 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
12758 | if( nLen < 1 ){ | ||
12759 | /* Empty string */ | ||
12760 | jx9_result_int(pCtx, 0); | ||
12761 | return JX9_OK; | ||
12762 | } | ||
12763 | /* Format the string */ | ||
12764 | jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, nArg, apArg, (void *)&nCounter, FALSE); | ||
12765 | /* Return the length of the outputted string */ | ||
12766 | jx9_result_int64(pCtx, nCounter); | ||
12767 | return JX9_OK; | ||
12768 | } | ||
12769 | /* | ||
12770 | * int vprintf(string $format, array $args) | ||
12771 | * Output a formatted string. | ||
12772 | * Parameters | ||
12773 | * $format | ||
12774 | * See sprintf() for a description of format. | ||
12775 | * Return | ||
12776 | * The length of the outputted string. | ||
12777 | */ | ||
12778 | static int jx9Builtin_vprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12779 | { | ||
12780 | jx9_int64 nCounter = 0; | ||
12781 | const char *zFormat; | ||
12782 | jx9_hashmap *pMap; | ||
12783 | SySet sArg; | ||
12784 | int nLen, n; | ||
12785 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ | ||
12786 | /* Missing/Invalid arguments, return 0 */ | ||
12787 | jx9_result_int(pCtx, 0); | ||
12788 | return JX9_OK; | ||
12789 | } | ||
12790 | /* Extract the string format */ | ||
12791 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
12792 | if( nLen < 1 ){ | ||
12793 | /* Empty string */ | ||
12794 | jx9_result_int(pCtx, 0); | ||
12795 | return JX9_OK; | ||
12796 | } | ||
12797 | /* Point to the hashmap */ | ||
12798 | pMap = (jx9_hashmap *)apArg[1]->x.pOther; | ||
12799 | /* Extract arguments from the hashmap */ | ||
12800 | n = jx9HashmapValuesToSet(pMap, &sArg); | ||
12801 | /* Format the string */ | ||
12802 | jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&nCounter, TRUE); | ||
12803 | /* Return the length of the outputted string */ | ||
12804 | jx9_result_int64(pCtx, nCounter); | ||
12805 | /* Release the container */ | ||
12806 | SySetRelease(&sArg); | ||
12807 | return JX9_OK; | ||
12808 | } | ||
12809 | /* | ||
12810 | * int vsprintf(string $format, array $args) | ||
12811 | * Output a formatted string. | ||
12812 | * Parameters | ||
12813 | * $format | ||
12814 | * See sprintf() for a description of format. | ||
12815 | * Return | ||
12816 | * A string produced according to the formatting string format. | ||
12817 | */ | ||
12818 | static int jx9Builtin_vsprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12819 | { | ||
12820 | const char *zFormat; | ||
12821 | jx9_hashmap *pMap; | ||
12822 | SySet sArg; | ||
12823 | int nLen, n; | ||
12824 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ | ||
12825 | /* Missing/Invalid arguments, return the empty string */ | ||
12826 | jx9_result_string(pCtx, "", 0); | ||
12827 | return JX9_OK; | ||
12828 | } | ||
12829 | /* Extract the string format */ | ||
12830 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
12831 | if( nLen < 1 ){ | ||
12832 | /* Empty string */ | ||
12833 | jx9_result_string(pCtx, "", 0); | ||
12834 | return JX9_OK; | ||
12835 | } | ||
12836 | /* Point to hashmap */ | ||
12837 | pMap = (jx9_hashmap *)apArg[1]->x.pOther; | ||
12838 | /* Extract arguments from the hashmap */ | ||
12839 | n = jx9HashmapValuesToSet(pMap, &sArg); | ||
12840 | /* Format the string */ | ||
12841 | jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), 0, TRUE); | ||
12842 | /* Release the container */ | ||
12843 | SySetRelease(&sArg); | ||
12844 | return JX9_OK; | ||
12845 | } | ||
12846 | /* | ||
12847 | * string size_format(int64 $size) | ||
12848 | * Return a smart string represenation of the given size [i.e: 64-bit integer] | ||
12849 | * Example: | ||
12850 | * print size_format(1*1024*1024*1024);// 1GB | ||
12851 | * print size_format(512*1024*1024); // 512 MB | ||
12852 | * print size_format(file_size(/path/to/my/file_8192)); //8KB | ||
12853 | * Parameter | ||
12854 | * $size | ||
12855 | * Entity size in bytes. | ||
12856 | * Return | ||
12857 | * Formatted string representation of the given size. | ||
12858 | */ | ||
12859 | static int jx9Builtin_size_format(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12860 | { | ||
12861 | /*Kilo*/ /*Mega*/ /*Giga*/ /*Tera*/ /*Peta*/ /*Exa*/ /*Zeta*/ | ||
12862 | static const char zUnit[] = {"KMGTPEZ"}; | ||
12863 | sxi32 nRest, i_32; | ||
12864 | jx9_int64 iSize; | ||
12865 | int c = -1; /* index in zUnit[] */ | ||
12866 | |||
12867 | if( nArg < 1 ){ | ||
12868 | /* Missing argument, return the empty string */ | ||
12869 | jx9_result_string(pCtx, "", 0); | ||
12870 | return JX9_OK; | ||
12871 | } | ||
12872 | /* Extract the given size */ | ||
12873 | iSize = jx9_value_to_int64(apArg[0]); | ||
12874 | if( iSize < 100 /* Bytes */ ){ | ||
12875 | /* Don't bother formatting, return immediately */ | ||
12876 | jx9_result_string(pCtx, "0.1 KB", (int)sizeof("0.1 KB")-1); | ||
12877 | return JX9_OK; | ||
12878 | } | ||
12879 | for(;;){ | ||
12880 | nRest = (sxi32)(iSize & 0x3FF); | ||
12881 | iSize >>= 10; | ||
12882 | c++; | ||
12883 | if( (iSize & (~0 ^ 1023)) == 0 ){ | ||
12884 | break; | ||
12885 | } | ||
12886 | } | ||
12887 | nRest /= 100; | ||
12888 | if( nRest > 9 ){ | ||
12889 | nRest = 9; | ||
12890 | } | ||
12891 | if( iSize > 999 ){ | ||
12892 | c++; | ||
12893 | nRest = 9; | ||
12894 | iSize = 0; | ||
12895 | } | ||
12896 | i_32 = (sxi32)iSize; | ||
12897 | /* Format */ | ||
12898 | jx9_result_string_format(pCtx, "%d.%d %cB", i_32, nRest, zUnit[c]); | ||
12899 | return JX9_OK; | ||
12900 | } | ||
12901 | #if !defined(JX9_DISABLE_HASH_FUNC) | ||
12902 | /* | ||
12903 | * string md5(string $str[, bool $raw_output = false]) | ||
12904 | * Calculate the md5 hash of a string. | ||
12905 | * Parameter | ||
12906 | * $str | ||
12907 | * Input string | ||
12908 | * $raw_output | ||
12909 | * If the optional raw_output is set to TRUE, then the md5 digest | ||
12910 | * is instead returned in raw binary format with a length of 16. | ||
12911 | * Return | ||
12912 | * MD5 Hash as a 32-character hexadecimal string. | ||
12913 | */ | ||
12914 | static int jx9Builtin_md5(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12915 | { | ||
12916 | unsigned char zDigest[16]; | ||
12917 | int raw_output = FALSE; | ||
12918 | const void *pIn; | ||
12919 | int nLen; | ||
12920 | if( nArg < 1 ){ | ||
12921 | /* Missing arguments, return the empty string */ | ||
12922 | jx9_result_string(pCtx, "", 0); | ||
12923 | return JX9_OK; | ||
12924 | } | ||
12925 | /* Extract the input string */ | ||
12926 | pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); | ||
12927 | if( nLen < 1 ){ | ||
12928 | /* Empty string */ | ||
12929 | jx9_result_string(pCtx, "", 0); | ||
12930 | return JX9_OK; | ||
12931 | } | ||
12932 | if( nArg > 1 && jx9_value_is_bool(apArg[1])){ | ||
12933 | raw_output = jx9_value_to_bool(apArg[1]); | ||
12934 | } | ||
12935 | /* Compute the MD5 digest */ | ||
12936 | SyMD5Compute(pIn, (sxu32)nLen, zDigest); | ||
12937 | if( raw_output ){ | ||
12938 | /* Output raw digest */ | ||
12939 | jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest)); | ||
12940 | }else{ | ||
12941 | /* Perform a binary to hex conversion */ | ||
12942 | SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx); | ||
12943 | } | ||
12944 | return JX9_OK; | ||
12945 | } | ||
12946 | /* | ||
12947 | * string sha1(string $str[, bool $raw_output = false]) | ||
12948 | * Calculate the sha1 hash of a string. | ||
12949 | * Parameter | ||
12950 | * $str | ||
12951 | * Input string | ||
12952 | * $raw_output | ||
12953 | * If the optional raw_output is set to TRUE, then the md5 digest | ||
12954 | * is instead returned in raw binary format with a length of 16. | ||
12955 | * Return | ||
12956 | * SHA1 Hash as a 40-character hexadecimal string. | ||
12957 | */ | ||
12958 | static int jx9Builtin_sha1(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
12959 | { | ||
12960 | unsigned char zDigest[20]; | ||
12961 | int raw_output = FALSE; | ||
12962 | const void *pIn; | ||
12963 | int nLen; | ||
12964 | if( nArg < 1 ){ | ||
12965 | /* Missing arguments, return the empty string */ | ||
12966 | jx9_result_string(pCtx, "", 0); | ||
12967 | return JX9_OK; | ||
12968 | } | ||
12969 | /* Extract the input string */ | ||
12970 | pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); | ||
12971 | if( nLen < 1 ){ | ||
12972 | /* Empty string */ | ||
12973 | jx9_result_string(pCtx, "", 0); | ||
12974 | return JX9_OK; | ||
12975 | } | ||
12976 | if( nArg > 1 && jx9_value_is_bool(apArg[1])){ | ||
12977 | raw_output = jx9_value_to_bool(apArg[1]); | ||
12978 | } | ||
12979 | /* Compute the SHA1 digest */ | ||
12980 | SySha1Compute(pIn, (sxu32)nLen, zDigest); | ||
12981 | if( raw_output ){ | ||
12982 | /* Output raw digest */ | ||
12983 | jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest)); | ||
12984 | }else{ | ||
12985 | /* Perform a binary to hex conversion */ | ||
12986 | SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx); | ||
12987 | } | ||
12988 | return JX9_OK; | ||
12989 | } | ||
12990 | /* | ||
12991 | * int64 crc32(string $str) | ||
12992 | * Calculates the crc32 polynomial of a strin. | ||
12993 | * Parameter | ||
12994 | * $str | ||
12995 | * Input string | ||
12996 | * Return | ||
12997 | * CRC32 checksum of the given input (64-bit integer). | ||
12998 | */ | ||
12999 | static int jx9Builtin_crc32(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13000 | { | ||
13001 | const void *pIn; | ||
13002 | sxu32 nCRC; | ||
13003 | int nLen; | ||
13004 | if( nArg < 1 ){ | ||
13005 | /* Missing arguments, return 0 */ | ||
13006 | jx9_result_int(pCtx, 0); | ||
13007 | return JX9_OK; | ||
13008 | } | ||
13009 | /* Extract the input string */ | ||
13010 | pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); | ||
13011 | if( nLen < 1 ){ | ||
13012 | /* Empty string */ | ||
13013 | jx9_result_int(pCtx, 0); | ||
13014 | return JX9_OK; | ||
13015 | } | ||
13016 | /* Calculate the sum */ | ||
13017 | nCRC = SyCrc32(pIn, (sxu32)nLen); | ||
13018 | /* Return the CRC32 as 64-bit integer */ | ||
13019 | jx9_result_int64(pCtx, (jx9_int64)nCRC^ 0xFFFFFFFF); | ||
13020 | return JX9_OK; | ||
13021 | } | ||
13022 | #endif /* JX9_DISABLE_HASH_FUNC */ | ||
13023 | /* | ||
13024 | * Parse a CSV string and invoke the supplied callback for each processed xhunk. | ||
13025 | */ | ||
13026 | JX9_PRIVATE sxi32 jx9ProcessCsv( | ||
13027 | const char *zInput, /* Raw input */ | ||
13028 | int nByte, /* Input length */ | ||
13029 | int delim, /* Delimiter */ | ||
13030 | int encl, /* Enclosure */ | ||
13031 | int escape, /* Escape character */ | ||
13032 | sxi32 (*xConsumer)(const char *, int, void *), /* User callback */ | ||
13033 | void *pUserData /* Last argument to xConsumer() */ | ||
13034 | ) | ||
13035 | { | ||
13036 | const char *zEnd = &zInput[nByte]; | ||
13037 | const char *zIn = zInput; | ||
13038 | const char *zPtr; | ||
13039 | int isEnc; | ||
13040 | /* Start processing */ | ||
13041 | for(;;){ | ||
13042 | if( zIn >= zEnd ){ | ||
13043 | /* No more input to process */ | ||
13044 | break; | ||
13045 | } | ||
13046 | isEnc = 0; | ||
13047 | zPtr = zIn; | ||
13048 | /* Find the first delimiter */ | ||
13049 | while( zIn < zEnd ){ | ||
13050 | if( zIn[0] == delim && !isEnc){ | ||
13051 | /* Delimiter found, break imediately */ | ||
13052 | break; | ||
13053 | }else if( zIn[0] == encl ){ | ||
13054 | /* Inside enclosure? */ | ||
13055 | isEnc = !isEnc; | ||
13056 | }else if( zIn[0] == escape ){ | ||
13057 | /* Escape sequence */ | ||
13058 | zIn++; | ||
13059 | } | ||
13060 | /* Advance the cursor */ | ||
13061 | zIn++; | ||
13062 | } | ||
13063 | if( zIn > zPtr ){ | ||
13064 | int nByte = (int)(zIn-zPtr); | ||
13065 | sxi32 rc; | ||
13066 | /* Invoke the supllied callback */ | ||
13067 | if( zPtr[0] == encl ){ | ||
13068 | zPtr++; | ||
13069 | nByte-=2; | ||
13070 | } | ||
13071 | if( nByte > 0 ){ | ||
13072 | rc = xConsumer(zPtr, nByte, pUserData); | ||
13073 | if( rc == SXERR_ABORT ){ | ||
13074 | /* User callback request an operation abort */ | ||
13075 | break; | ||
13076 | } | ||
13077 | } | ||
13078 | } | ||
13079 | /* Ignore trailing delimiter */ | ||
13080 | while( zIn < zEnd && zIn[0] == delim ){ | ||
13081 | zIn++; | ||
13082 | } | ||
13083 | } | ||
13084 | return SXRET_OK; | ||
13085 | } | ||
13086 | /* | ||
13087 | * Default consumer callback for the CSV parsing routine defined above. | ||
13088 | * All the processed input is insereted into an array passed as the last | ||
13089 | * argument to this callback. | ||
13090 | */ | ||
13091 | JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData) | ||
13092 | { | ||
13093 | jx9_value *pArray = (jx9_value *)pUserData; | ||
13094 | jx9_value sEntry; | ||
13095 | SyString sToken; | ||
13096 | /* Insert the token in the given array */ | ||
13097 | SyStringInitFromBuf(&sToken, zToken, nTokenLen); | ||
13098 | /* Remove trailing and leading white spcaces and null bytes */ | ||
13099 | SyStringFullTrimSafe(&sToken); | ||
13100 | if( sToken.nByte < 1){ | ||
13101 | return SXRET_OK; | ||
13102 | } | ||
13103 | jx9MemObjInitFromString(pArray->pVm, &sEntry, &sToken); | ||
13104 | jx9_array_add_elem(pArray, 0, &sEntry); | ||
13105 | jx9MemObjRelease(&sEntry); | ||
13106 | return SXRET_OK; | ||
13107 | } | ||
13108 | /* | ||
13109 | * array str_getcsv(string $input[, string $delimiter = ', '[, string $enclosure = '"' [, string $escape='\\']]]) | ||
13110 | * Parse a CSV string into an array. | ||
13111 | * Parameters | ||
13112 | * $input | ||
13113 | * The string to parse. | ||
13114 | * $delimiter | ||
13115 | * Set the field delimiter (one character only). | ||
13116 | * $enclosure | ||
13117 | * Set the field enclosure character (one character only). | ||
13118 | * $escape | ||
13119 | * Set the escape character (one character only). Defaults as a backslash (\) | ||
13120 | * Return | ||
13121 | * An indexed array containing the CSV fields or NULL on failure. | ||
13122 | */ | ||
13123 | static int jx9Builtin_str_getcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13124 | { | ||
13125 | const char *zInput, *zPtr; | ||
13126 | jx9_value *pArray; | ||
13127 | int delim = ','; /* Delimiter */ | ||
13128 | int encl = '"' ; /* Enclosure */ | ||
13129 | int escape = '\\'; /* Escape character */ | ||
13130 | int nLen; | ||
13131 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
13132 | /* Missing/Invalid arguments, return NULL */ | ||
13133 | jx9_result_null(pCtx); | ||
13134 | return JX9_OK; | ||
13135 | } | ||
13136 | /* Extract the raw input */ | ||
13137 | zInput = jx9_value_to_string(apArg[0], &nLen); | ||
13138 | if( nArg > 1 ){ | ||
13139 | int i; | ||
13140 | if( jx9_value_is_string(apArg[1]) ){ | ||
13141 | /* Extract the delimiter */ | ||
13142 | zPtr = jx9_value_to_string(apArg[1], &i); | ||
13143 | if( i > 0 ){ | ||
13144 | delim = zPtr[0]; | ||
13145 | } | ||
13146 | } | ||
13147 | if( nArg > 2 ){ | ||
13148 | if( jx9_value_is_string(apArg[2]) ){ | ||
13149 | /* Extract the enclosure */ | ||
13150 | zPtr = jx9_value_to_string(apArg[2], &i); | ||
13151 | if( i > 0 ){ | ||
13152 | encl = zPtr[0]; | ||
13153 | } | ||
13154 | } | ||
13155 | if( nArg > 3 ){ | ||
13156 | if( jx9_value_is_string(apArg[3]) ){ | ||
13157 | /* Extract the escape character */ | ||
13158 | zPtr = jx9_value_to_string(apArg[3], &i); | ||
13159 | if( i > 0 ){ | ||
13160 | escape = zPtr[0]; | ||
13161 | } | ||
13162 | } | ||
13163 | } | ||
13164 | } | ||
13165 | } | ||
13166 | /* Create our array */ | ||
13167 | pArray = jx9_context_new_array(pCtx); | ||
13168 | if( pArray == 0 ){ | ||
13169 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
13170 | jx9_result_null(pCtx); | ||
13171 | return JX9_OK; | ||
13172 | } | ||
13173 | /* Parse the raw input */ | ||
13174 | jx9ProcessCsv(zInput, nLen, delim, encl, escape, jx9CsvConsumer, pArray); | ||
13175 | /* Return the freshly created array */ | ||
13176 | jx9_result_value(pCtx, pArray); | ||
13177 | return JX9_OK; | ||
13178 | } | ||
13179 | /* | ||
13180 | * Extract a tag name from a raw HTML input and insert it in the given | ||
13181 | * container. | ||
13182 | * Refer to [strip_tags()]. | ||
13183 | */ | ||
13184 | static sxi32 AddTag(SySet *pSet, const char *zTag, int nByte) | ||
13185 | { | ||
13186 | const char *zEnd = &zTag[nByte]; | ||
13187 | const char *zPtr; | ||
13188 | SyString sEntry; | ||
13189 | /* Strip tags */ | ||
13190 | for(;;){ | ||
13191 | while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' | ||
13192 | || zTag[0] == '!' || zTag[0] == '-' || ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ | ||
13193 | zTag++; | ||
13194 | } | ||
13195 | if( zTag >= zEnd ){ | ||
13196 | break; | ||
13197 | } | ||
13198 | zPtr = zTag; | ||
13199 | /* Delimit the tag */ | ||
13200 | while(zTag < zEnd ){ | ||
13201 | if( (unsigned char)zTag[0] >= 0xc0 ){ | ||
13202 | /* UTF-8 stream */ | ||
13203 | zTag++; | ||
13204 | SX_JMP_UTF8(zTag, zEnd); | ||
13205 | }else if( !SyisAlphaNum(zTag[0]) ){ | ||
13206 | break; | ||
13207 | }else{ | ||
13208 | zTag++; | ||
13209 | } | ||
13210 | } | ||
13211 | if( zTag > zPtr ){ | ||
13212 | /* Perform the insertion */ | ||
13213 | SyStringInitFromBuf(&sEntry, zPtr, (int)(zTag-zPtr)); | ||
13214 | SyStringFullTrim(&sEntry); | ||
13215 | SySetPut(pSet, (const void *)&sEntry); | ||
13216 | } | ||
13217 | /* Jump the trailing '>' */ | ||
13218 | zTag++; | ||
13219 | } | ||
13220 | return SXRET_OK; | ||
13221 | } | ||
13222 | /* | ||
13223 | * Check if the given HTML tag name is present in the given container. | ||
13224 | * Return SXRET_OK if present.SXERR_NOTFOUND otherwise. | ||
13225 | * Refer to [strip_tags()]. | ||
13226 | */ | ||
13227 | static sxi32 FindTag(SySet *pSet, const char *zTag, int nByte) | ||
13228 | { | ||
13229 | if( SySetUsed(pSet) > 0 ){ | ||
13230 | const char *zCur, *zEnd = &zTag[nByte]; | ||
13231 | SyString sTag; | ||
13232 | while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' || | ||
13233 | ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ | ||
13234 | zTag++; | ||
13235 | } | ||
13236 | /* Delimit the tag */ | ||
13237 | zCur = zTag; | ||
13238 | while(zTag < zEnd ){ | ||
13239 | if( (unsigned char)zTag[0] >= 0xc0 ){ | ||
13240 | /* UTF-8 stream */ | ||
13241 | zTag++; | ||
13242 | SX_JMP_UTF8(zTag, zEnd); | ||
13243 | }else if( !SyisAlphaNum(zTag[0]) ){ | ||
13244 | break; | ||
13245 | }else{ | ||
13246 | zTag++; | ||
13247 | } | ||
13248 | } | ||
13249 | SyStringInitFromBuf(&sTag, zCur, zTag-zCur); | ||
13250 | /* Trim leading white spaces and null bytes */ | ||
13251 | SyStringLeftTrimSafe(&sTag); | ||
13252 | if( sTag.nByte > 0 ){ | ||
13253 | SyString *aEntry, *pEntry; | ||
13254 | sxi32 rc; | ||
13255 | sxu32 n; | ||
13256 | /* Perform the lookup */ | ||
13257 | aEntry = (SyString *)SySetBasePtr(pSet); | ||
13258 | for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ | ||
13259 | pEntry = &aEntry[n]; | ||
13260 | /* Do the comparison */ | ||
13261 | rc = SyStringCmp(pEntry, &sTag, SyStrnicmp); | ||
13262 | if( !rc ){ | ||
13263 | return SXRET_OK; | ||
13264 | } | ||
13265 | } | ||
13266 | } | ||
13267 | } | ||
13268 | /* No such tag */ | ||
13269 | return SXERR_NOTFOUND; | ||
13270 | } | ||
13271 | /* | ||
13272 | * This function tries to return a string [i.e: in the call context result buffer] | ||
13273 | * with all NUL bytes, HTML and JX9 tags stripped from a given string. | ||
13274 | * Refer to [strip_tags()]. | ||
13275 | */ | ||
13276 | JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen) | ||
13277 | { | ||
13278 | const char *zEnd = &zIn[nByte]; | ||
13279 | const char *zPtr, *zTag; | ||
13280 | SySet sSet; | ||
13281 | /* initialize the set of allowed tags */ | ||
13282 | SySetInit(&sSet, &pCtx->pVm->sAllocator, sizeof(SyString)); | ||
13283 | if( nTaglen > 0 ){ | ||
13284 | /* Set of allowed tags */ | ||
13285 | AddTag(&sSet, zTaglist, nTaglen); | ||
13286 | } | ||
13287 | /* Set the empty string */ | ||
13288 | jx9_result_string(pCtx, "", 0); | ||
13289 | /* Start processing */ | ||
13290 | for(;;){ | ||
13291 | if(zIn >= zEnd){ | ||
13292 | /* No more input to process */ | ||
13293 | break; | ||
13294 | } | ||
13295 | zPtr = zIn; | ||
13296 | /* Find a tag */ | ||
13297 | while( zIn < zEnd && zIn[0] != '<' && zIn[0] != 0 /* NUL byte */ ){ | ||
13298 | zIn++; | ||
13299 | } | ||
13300 | if( zIn > zPtr ){ | ||
13301 | /* Consume raw input */ | ||
13302 | jx9_result_string(pCtx, zPtr, (int)(zIn-zPtr)); | ||
13303 | } | ||
13304 | /* Ignore trailing null bytes */ | ||
13305 | while( zIn < zEnd && zIn[0] == 0 ){ | ||
13306 | zIn++; | ||
13307 | } | ||
13308 | if(zIn >= zEnd){ | ||
13309 | /* No more input to process */ | ||
13310 | break; | ||
13311 | } | ||
13312 | if( zIn[0] == '<' ){ | ||
13313 | sxi32 rc; | ||
13314 | zTag = zIn++; | ||
13315 | /* Delimit the tag */ | ||
13316 | while( zIn < zEnd && zIn[0] != '>' ){ | ||
13317 | zIn++; | ||
13318 | } | ||
13319 | if( zIn < zEnd ){ | ||
13320 | zIn++; /* Ignore the trailing closing tag */ | ||
13321 | } | ||
13322 | /* Query the set */ | ||
13323 | rc = FindTag(&sSet, zTag, (int)(zIn-zTag)); | ||
13324 | if( rc == SXRET_OK ){ | ||
13325 | /* Keep the tag */ | ||
13326 | jx9_result_string(pCtx, zTag, (int)(zIn-zTag)); | ||
13327 | } | ||
13328 | } | ||
13329 | } | ||
13330 | /* Cleanup */ | ||
13331 | SySetRelease(&sSet); | ||
13332 | return SXRET_OK; | ||
13333 | } | ||
13334 | /* | ||
13335 | * string strip_tags(string $str[, string $allowable_tags]) | ||
13336 | * Strip HTML and JX9 tags from a string. | ||
13337 | * Parameters | ||
13338 | * $str | ||
13339 | * The input string. | ||
13340 | * $allowable_tags | ||
13341 | * You can use the optional second parameter to specify tags which should not be stripped. | ||
13342 | * Return | ||
13343 | * Returns the stripped string. | ||
13344 | */ | ||
13345 | static int jx9Builtin_strip_tags(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13346 | { | ||
13347 | const char *zTaglist = 0; | ||
13348 | const char *zString; | ||
13349 | int nTaglen = 0; | ||
13350 | int nLen; | ||
13351 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
13352 | /* Missing/Invalid arguments, return the empty string */ | ||
13353 | jx9_result_string(pCtx, "", 0); | ||
13354 | return JX9_OK; | ||
13355 | } | ||
13356 | /* Point to the raw string */ | ||
13357 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
13358 | if( nArg > 1 && jx9_value_is_string(apArg[1]) ){ | ||
13359 | /* Allowed tag */ | ||
13360 | zTaglist = jx9_value_to_string(apArg[1], &nTaglen); | ||
13361 | } | ||
13362 | /* Process input */ | ||
13363 | jx9StripTagsFromString(pCtx, zString, nLen, zTaglist, nTaglen); | ||
13364 | return JX9_OK; | ||
13365 | } | ||
13366 | /* | ||
13367 | * array str_split(string $string[, int $split_length = 1 ]) | ||
13368 | * Convert a string to an array. | ||
13369 | * Parameters | ||
13370 | * $str | ||
13371 | * The input string. | ||
13372 | * $split_length | ||
13373 | * Maximum length of the chunk. | ||
13374 | * Return | ||
13375 | * If the optional split_length parameter is specified, the returned array | ||
13376 | * will be broken down into chunks with each being split_length in length, otherwise | ||
13377 | * each chunk will be one character in length. FALSE is returned if split_length is less than 1. | ||
13378 | * If the split_length length exceeds the length of string, the entire string is returned | ||
13379 | * as the first (and only) array element. | ||
13380 | */ | ||
13381 | static int jx9Builtin_str_split(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13382 | { | ||
13383 | const char *zString, *zEnd; | ||
13384 | jx9_value *pArray, *pValue; | ||
13385 | int split_len; | ||
13386 | int nLen; | ||
13387 | if( nArg < 1 ){ | ||
13388 | /* Missing arguments, return FALSE */ | ||
13389 | jx9_result_bool(pCtx, 0); | ||
13390 | return JX9_OK; | ||
13391 | } | ||
13392 | /* Point to the target string */ | ||
13393 | zString = jx9_value_to_string(apArg[0], &nLen); | ||
13394 | if( nLen < 1 ){ | ||
13395 | /* Nothing to process, return FALSE */ | ||
13396 | jx9_result_bool(pCtx, 0); | ||
13397 | return JX9_OK; | ||
13398 | } | ||
13399 | split_len = (int)sizeof(char); | ||
13400 | if( nArg > 1 ){ | ||
13401 | /* Split length */ | ||
13402 | split_len = jx9_value_to_int(apArg[1]); | ||
13403 | if( split_len < 1 ){ | ||
13404 | /* Invalid length, return FALSE */ | ||
13405 | jx9_result_bool(pCtx, 0); | ||
13406 | return JX9_OK; | ||
13407 | } | ||
13408 | if( split_len > nLen ){ | ||
13409 | split_len = nLen; | ||
13410 | } | ||
13411 | } | ||
13412 | /* Create the array and the scalar value */ | ||
13413 | pArray = jx9_context_new_array(pCtx); | ||
13414 | /*Chunk value */ | ||
13415 | pValue = jx9_context_new_scalar(pCtx); | ||
13416 | if( pValue == 0 || pArray == 0 ){ | ||
13417 | /* Return FALSE */ | ||
13418 | jx9_result_bool(pCtx, 0); | ||
13419 | return JX9_OK; | ||
13420 | } | ||
13421 | /* Point to the end of the string */ | ||
13422 | zEnd = &zString[nLen]; | ||
13423 | /* Perform the requested operation */ | ||
13424 | for(;;){ | ||
13425 | int nMax; | ||
13426 | if( zString >= zEnd ){ | ||
13427 | /* No more input to process */ | ||
13428 | break; | ||
13429 | } | ||
13430 | nMax = (int)(zEnd-zString); | ||
13431 | if( nMax < split_len ){ | ||
13432 | split_len = nMax; | ||
13433 | } | ||
13434 | /* Copy the current chunk */ | ||
13435 | jx9_value_string(pValue, zString, split_len); | ||
13436 | /* Insert it */ | ||
13437 | jx9_array_add_elem(pArray, 0, pValue); /* Will make it's own copy */ | ||
13438 | /* reset the string cursor */ | ||
13439 | jx9_value_reset_string_cursor(pValue); | ||
13440 | /* Update position */ | ||
13441 | zString += split_len; | ||
13442 | } | ||
13443 | /* | ||
13444 | * Return the array. | ||
13445 | * Don't worry about freeing memory, everything will be automatically released | ||
13446 | * upon we return from this function. | ||
13447 | */ | ||
13448 | jx9_result_value(pCtx, pArray); | ||
13449 | return JX9_OK; | ||
13450 | } | ||
13451 | /* | ||
13452 | * Tokenize a raw string and extract the first non-space token. | ||
13453 | * Refer to [strspn()]. | ||
13454 | */ | ||
13455 | static sxi32 ExtractNonSpaceToken(const char **pzIn, const char *zEnd, SyString *pOut) | ||
13456 | { | ||
13457 | const char *zIn = *pzIn; | ||
13458 | const char *zPtr; | ||
13459 | /* Ignore leading white spaces */ | ||
13460 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ | ||
13461 | zIn++; | ||
13462 | } | ||
13463 | if( zIn >= zEnd ){ | ||
13464 | /* End of input */ | ||
13465 | return SXERR_EOF; | ||
13466 | } | ||
13467 | zPtr = zIn; | ||
13468 | /* Extract the token */ | ||
13469 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && !SyisSpace(zIn[0]) ){ | ||
13470 | zIn++; | ||
13471 | } | ||
13472 | SyStringInitFromBuf(pOut, zPtr, zIn-zPtr); | ||
13473 | /* Synchronize pointers */ | ||
13474 | *pzIn = zIn; | ||
13475 | /* Return to the caller */ | ||
13476 | return SXRET_OK; | ||
13477 | } | ||
13478 | /* | ||
13479 | * Check if the given string contains only characters from the given mask. | ||
13480 | * return the longest match. | ||
13481 | * Refer to [strspn()]. | ||
13482 | */ | ||
13483 | static int LongestStringMask(const char *zString, int nLen, const char *zMask, int nMaskLen) | ||
13484 | { | ||
13485 | const char *zEnd = &zString[nLen]; | ||
13486 | const char *zIn = zString; | ||
13487 | int i, c; | ||
13488 | for(;;){ | ||
13489 | if( zString >= zEnd ){ | ||
13490 | break; | ||
13491 | } | ||
13492 | /* Extract current character */ | ||
13493 | c = zString[0]; | ||
13494 | /* Perform the lookup */ | ||
13495 | for( i = 0 ; i < nMaskLen ; i++ ){ | ||
13496 | if( c == zMask[i] ){ | ||
13497 | /* Character found */ | ||
13498 | break; | ||
13499 | } | ||
13500 | } | ||
13501 | if( i >= nMaskLen ){ | ||
13502 | /* Character not in the current mask, break immediately */ | ||
13503 | break; | ||
13504 | } | ||
13505 | /* Advance cursor */ | ||
13506 | zString++; | ||
13507 | } | ||
13508 | /* Longest match */ | ||
13509 | return (int)(zString-zIn); | ||
13510 | } | ||
13511 | /* | ||
13512 | * Do the reverse operation of the previous function [i.e: LongestStringMask()]. | ||
13513 | * Refer to [strcspn()]. | ||
13514 | */ | ||
13515 | static int LongestStringMask2(const char *zString, int nLen, const char *zMask, int nMaskLen) | ||
13516 | { | ||
13517 | const char *zEnd = &zString[nLen]; | ||
13518 | const char *zIn = zString; | ||
13519 | int i, c; | ||
13520 | for(;;){ | ||
13521 | if( zString >= zEnd ){ | ||
13522 | break; | ||
13523 | } | ||
13524 | /* Extract current character */ | ||
13525 | c = zString[0]; | ||
13526 | /* Perform the lookup */ | ||
13527 | for( i = 0 ; i < nMaskLen ; i++ ){ | ||
13528 | if( c == zMask[i] ){ | ||
13529 | break; | ||
13530 | } | ||
13531 | } | ||
13532 | if( i < nMaskLen ){ | ||
13533 | /* Character in the current mask, break immediately */ | ||
13534 | break; | ||
13535 | } | ||
13536 | /* Advance cursor */ | ||
13537 | zString++; | ||
13538 | } | ||
13539 | /* Longest match */ | ||
13540 | return (int)(zString-zIn); | ||
13541 | } | ||
13542 | /* | ||
13543 | * int strspn(string $str, string $mask[, int $start[, int $length]]) | ||
13544 | * Finds the length of the initial segment of a string consisting entirely | ||
13545 | * of characters contained within a given mask. | ||
13546 | * Parameters | ||
13547 | * $str | ||
13548 | * The input string. | ||
13549 | * $mask | ||
13550 | * The list of allowable characters. | ||
13551 | * $start | ||
13552 | * The position in subject to start searching. | ||
13553 | * If start is given and is non-negative, then strspn() will begin examining | ||
13554 | * subject at the start'th position. For instance, in the string 'abcdef', the character | ||
13555 | * at position 0 is 'a', the character at position 2 is 'c', and so forth. | ||
13556 | * If start is given and is negative, then strspn() will begin examining subject at the | ||
13557 | * start'th position from the end of subject. | ||
13558 | * $length | ||
13559 | * The length of the segment from subject to examine. | ||
13560 | * If length is given and is non-negative, then subject will be examined for length | ||
13561 | * characters after the starting position. | ||
13562 | * If lengthis given and is negative, then subject will be examined from the starting | ||
13563 | * position up to length characters from the end of subject. | ||
13564 | * Return | ||
13565 | * Returns the length of the initial segment of subject which consists entirely of characters | ||
13566 | * in mask. | ||
13567 | */ | ||
13568 | static int jx9Builtin_strspn(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13569 | { | ||
13570 | const char *zString, *zMask, *zEnd; | ||
13571 | int iMasklen, iLen; | ||
13572 | SyString sToken; | ||
13573 | int iCount = 0; | ||
13574 | int rc; | ||
13575 | if( nArg < 2 ){ | ||
13576 | /* Missing agruments, return zero */ | ||
13577 | jx9_result_int(pCtx, 0); | ||
13578 | return JX9_OK; | ||
13579 | } | ||
13580 | /* Extract the target string */ | ||
13581 | zString = jx9_value_to_string(apArg[0], &iLen); | ||
13582 | /* Extract the mask */ | ||
13583 | zMask = jx9_value_to_string(apArg[1], &iMasklen); | ||
13584 | if( iLen < 1 || iMasklen < 1 ){ | ||
13585 | /* Nothing to process, return zero */ | ||
13586 | jx9_result_int(pCtx, 0); | ||
13587 | return JX9_OK; | ||
13588 | } | ||
13589 | if( nArg > 2 ){ | ||
13590 | int nOfft; | ||
13591 | /* Extract the offset */ | ||
13592 | nOfft = jx9_value_to_int(apArg[2]); | ||
13593 | if( nOfft < 0 ){ | ||
13594 | const char *zBase = &zString[iLen + nOfft]; | ||
13595 | if( zBase > zString ){ | ||
13596 | iLen = (int)(&zString[iLen]-zBase); | ||
13597 | zString = zBase; | ||
13598 | }else{ | ||
13599 | /* Invalid offset */ | ||
13600 | jx9_result_int(pCtx, 0); | ||
13601 | return JX9_OK; | ||
13602 | } | ||
13603 | }else{ | ||
13604 | if( nOfft >= iLen ){ | ||
13605 | /* Invalid offset */ | ||
13606 | jx9_result_int(pCtx, 0); | ||
13607 | return JX9_OK; | ||
13608 | }else{ | ||
13609 | /* Update offset */ | ||
13610 | zString += nOfft; | ||
13611 | iLen -= nOfft; | ||
13612 | } | ||
13613 | } | ||
13614 | if( nArg > 3 ){ | ||
13615 | int iUserlen; | ||
13616 | /* Extract the desired length */ | ||
13617 | iUserlen = jx9_value_to_int(apArg[3]); | ||
13618 | if( iUserlen > 0 && iUserlen < iLen ){ | ||
13619 | iLen = iUserlen; | ||
13620 | } | ||
13621 | } | ||
13622 | } | ||
13623 | /* Point to the end of the string */ | ||
13624 | zEnd = &zString[iLen]; | ||
13625 | /* Extract the first non-space token */ | ||
13626 | rc = ExtractNonSpaceToken(&zString, zEnd, &sToken); | ||
13627 | if( rc == SXRET_OK && sToken.nByte > 0 ){ | ||
13628 | /* Compare against the current mask */ | ||
13629 | iCount = LongestStringMask(sToken.zString, (int)sToken.nByte, zMask, iMasklen); | ||
13630 | } | ||
13631 | /* Longest match */ | ||
13632 | jx9_result_int(pCtx, iCount); | ||
13633 | return JX9_OK; | ||
13634 | } | ||
13635 | /* | ||
13636 | * int strcspn(string $str, string $mask[, int $start[, int $length]]) | ||
13637 | * Find length of initial segment not matching mask. | ||
13638 | * Parameters | ||
13639 | * $str | ||
13640 | * The input string. | ||
13641 | * $mask | ||
13642 | * The list of not allowed characters. | ||
13643 | * $start | ||
13644 | * The position in subject to start searching. | ||
13645 | * If start is given and is non-negative, then strspn() will begin examining | ||
13646 | * subject at the start'th position. For instance, in the string 'abcdef', the character | ||
13647 | * at position 0 is 'a', the character at position 2 is 'c', and so forth. | ||
13648 | * If start is given and is negative, then strspn() will begin examining subject at the | ||
13649 | * start'th position from the end of subject. | ||
13650 | * $length | ||
13651 | * The length of the segment from subject to examine. | ||
13652 | * If length is given and is non-negative, then subject will be examined for length | ||
13653 | * characters after the starting position. | ||
13654 | * If lengthis given and is negative, then subject will be examined from the starting | ||
13655 | * position up to length characters from the end of subject. | ||
13656 | * Return | ||
13657 | * Returns the length of the segment as an integer. | ||
13658 | */ | ||
13659 | static int jx9Builtin_strcspn(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13660 | { | ||
13661 | const char *zString, *zMask, *zEnd; | ||
13662 | int iMasklen, iLen; | ||
13663 | SyString sToken; | ||
13664 | int iCount = 0; | ||
13665 | int rc; | ||
13666 | if( nArg < 2 ){ | ||
13667 | /* Missing agruments, return zero */ | ||
13668 | jx9_result_int(pCtx, 0); | ||
13669 | return JX9_OK; | ||
13670 | } | ||
13671 | /* Extract the target string */ | ||
13672 | zString = jx9_value_to_string(apArg[0], &iLen); | ||
13673 | /* Extract the mask */ | ||
13674 | zMask = jx9_value_to_string(apArg[1], &iMasklen); | ||
13675 | if( iLen < 1 ){ | ||
13676 | /* Nothing to process, return zero */ | ||
13677 | jx9_result_int(pCtx, 0); | ||
13678 | return JX9_OK; | ||
13679 | } | ||
13680 | if( iMasklen < 1 ){ | ||
13681 | /* No given mask, return the string length */ | ||
13682 | jx9_result_int(pCtx, iLen); | ||
13683 | return JX9_OK; | ||
13684 | } | ||
13685 | if( nArg > 2 ){ | ||
13686 | int nOfft; | ||
13687 | /* Extract the offset */ | ||
13688 | nOfft = jx9_value_to_int(apArg[2]); | ||
13689 | if( nOfft < 0 ){ | ||
13690 | const char *zBase = &zString[iLen + nOfft]; | ||
13691 | if( zBase > zString ){ | ||
13692 | iLen = (int)(&zString[iLen]-zBase); | ||
13693 | zString = zBase; | ||
13694 | }else{ | ||
13695 | /* Invalid offset */ | ||
13696 | jx9_result_int(pCtx, 0); | ||
13697 | return JX9_OK; | ||
13698 | } | ||
13699 | }else{ | ||
13700 | if( nOfft >= iLen ){ | ||
13701 | /* Invalid offset */ | ||
13702 | jx9_result_int(pCtx, 0); | ||
13703 | return JX9_OK; | ||
13704 | }else{ | ||
13705 | /* Update offset */ | ||
13706 | zString += nOfft; | ||
13707 | iLen -= nOfft; | ||
13708 | } | ||
13709 | } | ||
13710 | if( nArg > 3 ){ | ||
13711 | int iUserlen; | ||
13712 | /* Extract the desired length */ | ||
13713 | iUserlen = jx9_value_to_int(apArg[3]); | ||
13714 | if( iUserlen > 0 && iUserlen < iLen ){ | ||
13715 | iLen = iUserlen; | ||
13716 | } | ||
13717 | } | ||
13718 | } | ||
13719 | /* Point to the end of the string */ | ||
13720 | zEnd = &zString[iLen]; | ||
13721 | /* Extract the first non-space token */ | ||
13722 | rc = ExtractNonSpaceToken(&zString, zEnd, &sToken); | ||
13723 | if( rc == SXRET_OK && sToken.nByte > 0 ){ | ||
13724 | /* Compare against the current mask */ | ||
13725 | iCount = LongestStringMask2(sToken.zString, (int)sToken.nByte, zMask, iMasklen); | ||
13726 | } | ||
13727 | /* Longest match */ | ||
13728 | jx9_result_int(pCtx, iCount); | ||
13729 | return JX9_OK; | ||
13730 | } | ||
13731 | /* | ||
13732 | * string strpbrk(string $haystack, string $char_list) | ||
13733 | * Search a string for any of a set of characters. | ||
13734 | * Parameters | ||
13735 | * $haystack | ||
13736 | * The string where char_list is looked for. | ||
13737 | * $char_list | ||
13738 | * This parameter is case sensitive. | ||
13739 | * Return | ||
13740 | * Returns a string starting from the character found, or FALSE if it is not found. | ||
13741 | */ | ||
13742 | static int jx9Builtin_strpbrk(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13743 | { | ||
13744 | const char *zString, *zList, *zEnd; | ||
13745 | int iLen, iListLen, i, c; | ||
13746 | sxu32 nOfft, nMax; | ||
13747 | sxi32 rc; | ||
13748 | if( nArg < 2 ){ | ||
13749 | /* Missing arguments, return FALSE */ | ||
13750 | jx9_result_bool(pCtx, 0); | ||
13751 | return JX9_OK; | ||
13752 | } | ||
13753 | /* Extract the haystack and the char list */ | ||
13754 | zString = jx9_value_to_string(apArg[0], &iLen); | ||
13755 | zList = jx9_value_to_string(apArg[1], &iListLen); | ||
13756 | if( iLen < 1 ){ | ||
13757 | /* Nothing to process, return FALSE */ | ||
13758 | jx9_result_bool(pCtx, 0); | ||
13759 | return JX9_OK; | ||
13760 | } | ||
13761 | /* Point to the end of the string */ | ||
13762 | zEnd = &zString[iLen]; | ||
13763 | nOfft = nMax = SXU32_HIGH; | ||
13764 | /* perform the requested operation */ | ||
13765 | for( i = 0 ; i < iListLen ; i++ ){ | ||
13766 | c = zList[i]; | ||
13767 | rc = SyByteFind(zString, (sxu32)iLen, c, &nMax); | ||
13768 | if( rc == SXRET_OK ){ | ||
13769 | if( nMax < nOfft ){ | ||
13770 | nOfft = nMax; | ||
13771 | } | ||
13772 | } | ||
13773 | } | ||
13774 | if( nOfft == SXU32_HIGH ){ | ||
13775 | /* No such substring, return FALSE */ | ||
13776 | jx9_result_bool(pCtx, 0); | ||
13777 | }else{ | ||
13778 | /* Return the substring */ | ||
13779 | jx9_result_string(pCtx, &zString[nOfft], (int)(zEnd-&zString[nOfft])); | ||
13780 | } | ||
13781 | return JX9_OK; | ||
13782 | } | ||
13783 | /* | ||
13784 | * string soundex(string $str) | ||
13785 | * Calculate the soundex key of a string. | ||
13786 | * Parameters | ||
13787 | * $str | ||
13788 | * The input string. | ||
13789 | * Return | ||
13790 | * Returns the soundex key as a string. | ||
13791 | * Note: | ||
13792 | * This implementation is based on the one found in the SQLite3 | ||
13793 | * source tree. | ||
13794 | */ | ||
13795 | static int jx9Builtin_soundex(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13796 | { | ||
13797 | const unsigned char *zIn; | ||
13798 | char zResult[8]; | ||
13799 | int i, j; | ||
13800 | static const unsigned char iCode[] = { | ||
13801 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
13802 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
13803 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
13804 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
13805 | 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, | ||
13806 | 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, | ||
13807 | 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, | ||
13808 | 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, | ||
13809 | }; | ||
13810 | if( nArg < 1 ){ | ||
13811 | /* Missing arguments, return the empty string */ | ||
13812 | jx9_result_string(pCtx, "", 0); | ||
13813 | return JX9_OK; | ||
13814 | } | ||
13815 | zIn = (unsigned char *)jx9_value_to_string(apArg[0], 0); | ||
13816 | for(i=0; zIn[i] && zIn[i] < 0xc0 && !SyisAlpha(zIn[i]); i++){} | ||
13817 | if( zIn[i] ){ | ||
13818 | unsigned char prevcode = iCode[zIn[i]&0x7f]; | ||
13819 | zResult[0] = (char)SyToUpper(zIn[i]); | ||
13820 | for(j=1; j<4 && zIn[i]; i++){ | ||
13821 | int code = iCode[zIn[i]&0x7f]; | ||
13822 | if( code>0 ){ | ||
13823 | if( code!=prevcode ){ | ||
13824 | prevcode = (unsigned char)code; | ||
13825 | zResult[j++] = (char)code + '0'; | ||
13826 | } | ||
13827 | }else{ | ||
13828 | prevcode = 0; | ||
13829 | } | ||
13830 | } | ||
13831 | while( j<4 ){ | ||
13832 | zResult[j++] = '0'; | ||
13833 | } | ||
13834 | jx9_result_string(pCtx, zResult, 4); | ||
13835 | }else{ | ||
13836 | jx9_result_string(pCtx, "?000", 4); | ||
13837 | } | ||
13838 | return JX9_OK; | ||
13839 | } | ||
13840 | /* | ||
13841 | * string wordwrap(string $str[, int $width = 75[, string $break = "\n"]]) | ||
13842 | * Wraps a string to a given number of characters. | ||
13843 | * Parameters | ||
13844 | * $str | ||
13845 | * The input string. | ||
13846 | * $width | ||
13847 | * The column width. | ||
13848 | * $break | ||
13849 | * The line is broken using the optional break parameter. | ||
13850 | * Return | ||
13851 | * Returns the given string wrapped at the specified column. | ||
13852 | */ | ||
13853 | static int jx9Builtin_wordwrap(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13854 | { | ||
13855 | const char *zIn, *zEnd, *zBreak; | ||
13856 | int iLen, iBreaklen, iChunk; | ||
13857 | if( nArg < 1 ){ | ||
13858 | /* Missing arguments, return the empty string */ | ||
13859 | jx9_result_string(pCtx, "", 0); | ||
13860 | return JX9_OK; | ||
13861 | } | ||
13862 | /* Extract the input string */ | ||
13863 | zIn = jx9_value_to_string(apArg[0], &iLen); | ||
13864 | if( iLen < 1 ){ | ||
13865 | /* Nothing to process, return the empty string */ | ||
13866 | jx9_result_string(pCtx, "", 0); | ||
13867 | return JX9_OK; | ||
13868 | } | ||
13869 | /* Chunk length */ | ||
13870 | iChunk = 75; | ||
13871 | iBreaklen = 0; | ||
13872 | zBreak = ""; /* cc warning */ | ||
13873 | if( nArg > 1 ){ | ||
13874 | iChunk = jx9_value_to_int(apArg[1]); | ||
13875 | if( iChunk < 1 ){ | ||
13876 | iChunk = 75; | ||
13877 | } | ||
13878 | if( nArg > 2 ){ | ||
13879 | zBreak = jx9_value_to_string(apArg[2], &iBreaklen); | ||
13880 | } | ||
13881 | } | ||
13882 | if( iBreaklen < 1 ){ | ||
13883 | /* Set a default column break */ | ||
13884 | #ifdef __WINNT__ | ||
13885 | zBreak = "\r\n"; | ||
13886 | iBreaklen = (int)sizeof("\r\n")-1; | ||
13887 | #else | ||
13888 | zBreak = "\n"; | ||
13889 | iBreaklen = (int)sizeof(char); | ||
13890 | #endif | ||
13891 | } | ||
13892 | /* Perform the requested operation */ | ||
13893 | zEnd = &zIn[iLen]; | ||
13894 | for(;;){ | ||
13895 | int nMax; | ||
13896 | if( zIn >= zEnd ){ | ||
13897 | /* No more input to process */ | ||
13898 | break; | ||
13899 | } | ||
13900 | nMax = (int)(zEnd-zIn); | ||
13901 | if( iChunk > nMax ){ | ||
13902 | iChunk = nMax; | ||
13903 | } | ||
13904 | /* Append the column first */ | ||
13905 | jx9_result_string(pCtx, zIn, iChunk); /* Will make it's own copy */ | ||
13906 | /* Advance the cursor */ | ||
13907 | zIn += iChunk; | ||
13908 | if( zIn < zEnd ){ | ||
13909 | /* Append the line break */ | ||
13910 | jx9_result_string(pCtx, zBreak, iBreaklen); | ||
13911 | } | ||
13912 | } | ||
13913 | return JX9_OK; | ||
13914 | } | ||
13915 | /* | ||
13916 | * Check if the given character is a member of the given mask. | ||
13917 | * Return TRUE on success. FALSE otherwise. | ||
13918 | * Refer to [strtok()]. | ||
13919 | */ | ||
13920 | static int CheckMask(int c, const char *zMask, int nMasklen, int *pOfft) | ||
13921 | { | ||
13922 | int i; | ||
13923 | for( i = 0 ; i < nMasklen ; ++i ){ | ||
13924 | if( c == zMask[i] ){ | ||
13925 | if( pOfft ){ | ||
13926 | *pOfft = i; | ||
13927 | } | ||
13928 | return TRUE; | ||
13929 | } | ||
13930 | } | ||
13931 | return FALSE; | ||
13932 | } | ||
13933 | /* | ||
13934 | * Extract a single token from the input stream. | ||
13935 | * Refer to [strtok()]. | ||
13936 | */ | ||
13937 | static sxi32 ExtractToken(const char **pzIn, const char *zEnd, const char *zMask, int nMasklen, SyString *pOut) | ||
13938 | { | ||
13939 | const char *zIn = *pzIn; | ||
13940 | const char *zPtr; | ||
13941 | /* Ignore leading delimiter */ | ||
13942 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && CheckMask(zIn[0], zMask, nMasklen, 0) ){ | ||
13943 | zIn++; | ||
13944 | } | ||
13945 | if( zIn >= zEnd ){ | ||
13946 | /* End of input */ | ||
13947 | return SXERR_EOF; | ||
13948 | } | ||
13949 | zPtr = zIn; | ||
13950 | /* Extract the token */ | ||
13951 | while( zIn < zEnd ){ | ||
13952 | if( (unsigned char)zIn[0] >= 0xc0 ){ | ||
13953 | /* UTF-8 stream */ | ||
13954 | zIn++; | ||
13955 | SX_JMP_UTF8(zIn, zEnd); | ||
13956 | }else{ | ||
13957 | if( CheckMask(zIn[0], zMask, nMasklen, 0) ){ | ||
13958 | break; | ||
13959 | } | ||
13960 | zIn++; | ||
13961 | } | ||
13962 | } | ||
13963 | SyStringInitFromBuf(pOut, zPtr, zIn-zPtr); | ||
13964 | /* Update the cursor */ | ||
13965 | *pzIn = zIn; | ||
13966 | /* Return to the caller */ | ||
13967 | return SXRET_OK; | ||
13968 | } | ||
13969 | /* strtok auxiliary private data */ | ||
13970 | typedef struct strtok_aux_data strtok_aux_data; | ||
13971 | struct strtok_aux_data | ||
13972 | { | ||
13973 | const char *zDup; /* Complete duplicate of the input */ | ||
13974 | const char *zIn; /* Current input stream */ | ||
13975 | const char *zEnd; /* End of input */ | ||
13976 | }; | ||
13977 | /* | ||
13978 | * string strtok(string $str, string $token) | ||
13979 | * string strtok(string $token) | ||
13980 | * strtok() splits a string (str) into smaller strings (tokens), with each token | ||
13981 | * being delimited by any character from token. That is, if you have a string like | ||
13982 | * "This is an example string" you could tokenize this string into its individual | ||
13983 | * words by using the space character as the token. | ||
13984 | * Note that only the first call to strtok uses the string argument. Every subsequent | ||
13985 | * call to strtok only needs the token to use, as it keeps track of where it is in | ||
13986 | * the current string. To start over, or to tokenize a new string you simply call strtok | ||
13987 | * with the string argument again to initialize it. Note that you may put multiple tokens | ||
13988 | * in the token parameter. The string will be tokenized when any one of the characters in | ||
13989 | * the argument are found. | ||
13990 | * Parameters | ||
13991 | * $str | ||
13992 | * The string being split up into smaller strings (tokens). | ||
13993 | * $token | ||
13994 | * The delimiter used when splitting up str. | ||
13995 | * Return | ||
13996 | * Current token or FALSE on EOF. | ||
13997 | */ | ||
13998 | static int jx9Builtin_strtok(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
13999 | { | ||
14000 | strtok_aux_data *pAux; | ||
14001 | const char *zMask; | ||
14002 | SyString sToken; | ||
14003 | int nMasklen; | ||
14004 | sxi32 rc; | ||
14005 | if( nArg < 2 ){ | ||
14006 | /* Extract top aux data */ | ||
14007 | pAux = (strtok_aux_data *)jx9_context_peek_aux_data(pCtx); | ||
14008 | if( pAux == 0 ){ | ||
14009 | /* No aux data, return FALSE */ | ||
14010 | jx9_result_bool(pCtx, 0); | ||
14011 | return JX9_OK; | ||
14012 | } | ||
14013 | nMasklen = 0; | ||
14014 | zMask = ""; /* cc warning */ | ||
14015 | if( nArg > 0 ){ | ||
14016 | /* Extract the mask */ | ||
14017 | zMask = jx9_value_to_string(apArg[0], &nMasklen); | ||
14018 | } | ||
14019 | if( nMasklen < 1 ){ | ||
14020 | /* Invalid mask, return FALSE */ | ||
14021 | jx9_context_free_chunk(pCtx, (void *)pAux->zDup); | ||
14022 | jx9_context_free_chunk(pCtx, pAux); | ||
14023 | (void)jx9_context_pop_aux_data(pCtx); | ||
14024 | jx9_result_bool(pCtx, 0); | ||
14025 | return JX9_OK; | ||
14026 | } | ||
14027 | /* Extract the token */ | ||
14028 | rc = ExtractToken(&pAux->zIn, pAux->zEnd, zMask, nMasklen, &sToken); | ||
14029 | if( rc != SXRET_OK ){ | ||
14030 | /* EOF , discard the aux data */ | ||
14031 | jx9_context_free_chunk(pCtx, (void *)pAux->zDup); | ||
14032 | jx9_context_free_chunk(pCtx, pAux); | ||
14033 | (void)jx9_context_pop_aux_data(pCtx); | ||
14034 | jx9_result_bool(pCtx, 0); | ||
14035 | }else{ | ||
14036 | /* Return the extracted token */ | ||
14037 | jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte); | ||
14038 | } | ||
14039 | }else{ | ||
14040 | const char *zInput, *zCur; | ||
14041 | char *zDup; | ||
14042 | int nLen; | ||
14043 | /* Extract the raw input */ | ||
14044 | zCur = zInput = jx9_value_to_string(apArg[0], &nLen); | ||
14045 | if( nLen < 1 ){ | ||
14046 | /* Empty input, return FALSE */ | ||
14047 | jx9_result_bool(pCtx, 0); | ||
14048 | return JX9_OK; | ||
14049 | } | ||
14050 | /* Extract the mask */ | ||
14051 | zMask = jx9_value_to_string(apArg[1], &nMasklen); | ||
14052 | if( nMasklen < 1 ){ | ||
14053 | /* Set a default mask */ | ||
14054 | #define TOK_MASK " \n\t\r\f" | ||
14055 | zMask = TOK_MASK; | ||
14056 | nMasklen = (int)sizeof(TOK_MASK) - 1; | ||
14057 | #undef TOK_MASK | ||
14058 | } | ||
14059 | /* Extract a single token */ | ||
14060 | rc = ExtractToken(&zInput, &zInput[nLen], zMask, nMasklen, &sToken); | ||
14061 | if( rc != SXRET_OK ){ | ||
14062 | /* Empty input */ | ||
14063 | jx9_result_bool(pCtx, 0); | ||
14064 | return JX9_OK; | ||
14065 | }else{ | ||
14066 | /* Return the extracted token */ | ||
14067 | jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte); | ||
14068 | } | ||
14069 | /* Create our auxilliary data and copy the input */ | ||
14070 | pAux = (strtok_aux_data *)jx9_context_alloc_chunk(pCtx, sizeof(strtok_aux_data), TRUE, FALSE); | ||
14071 | if( pAux ){ | ||
14072 | nLen -= (int)(zInput-zCur); | ||
14073 | if( nLen < 1 ){ | ||
14074 | jx9_context_free_chunk(pCtx, pAux); | ||
14075 | return JX9_OK; | ||
14076 | } | ||
14077 | /* Duplicate input */ | ||
14078 | zDup = (char *)jx9_context_alloc_chunk(pCtx, (unsigned int)(nLen+1), TRUE, FALSE); | ||
14079 | if( zDup ){ | ||
14080 | SyMemcpy(zInput, zDup, (sxu32)nLen); | ||
14081 | /* Register the aux data */ | ||
14082 | pAux->zDup = pAux->zIn = zDup; | ||
14083 | pAux->zEnd = &zDup[nLen]; | ||
14084 | jx9_context_push_aux_data(pCtx, pAux); | ||
14085 | } | ||
14086 | } | ||
14087 | } | ||
14088 | return JX9_OK; | ||
14089 | } | ||
14090 | /* | ||
14091 | * string str_pad(string $input, int $pad_length[, string $pad_string = " " [, int $pad_type = STR_PAD_RIGHT]]) | ||
14092 | * Pad a string to a certain length with another string | ||
14093 | * Parameters | ||
14094 | * $input | ||
14095 | * The input string. | ||
14096 | * $pad_length | ||
14097 | * If the value of pad_length is negative, less than, or equal to the length of the input | ||
14098 | * string, no padding takes place. | ||
14099 | * $pad_string | ||
14100 | * Note: | ||
14101 | * The pad_string WIIL NOT BE truncated if the required number of padding characters can't be evenly | ||
14102 | * divided by the pad_string's length. | ||
14103 | * $pad_type | ||
14104 | * Optional argument pad_type can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type | ||
14105 | * is not specified it is assumed to be STR_PAD_RIGHT. | ||
14106 | * Return | ||
14107 | * The padded string. | ||
14108 | */ | ||
14109 | static int jx9Builtin_str_pad(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14110 | { | ||
14111 | int iLen, iPadlen, iType, i, iDiv, iStrpad, iRealPad, jPad; | ||
14112 | const char *zIn, *zPad; | ||
14113 | if( nArg < 2 ){ | ||
14114 | /* Missing arguments, return the empty string */ | ||
14115 | jx9_result_string(pCtx, "", 0); | ||
14116 | return JX9_OK; | ||
14117 | } | ||
14118 | /* Extract the target string */ | ||
14119 | zIn = jx9_value_to_string(apArg[0], &iLen); | ||
14120 | /* Padding length */ | ||
14121 | iRealPad = iPadlen = jx9_value_to_int(apArg[1]); | ||
14122 | if( iPadlen > 0 ){ | ||
14123 | iPadlen -= iLen; | ||
14124 | } | ||
14125 | if( iPadlen < 1 ){ | ||
14126 | /* Return the string verbatim */ | ||
14127 | jx9_result_string(pCtx, zIn, iLen); | ||
14128 | return JX9_OK; | ||
14129 | } | ||
14130 | zPad = " "; /* Whitespace padding */ | ||
14131 | iStrpad = (int)sizeof(char); | ||
14132 | iType = 1 ; /* STR_PAD_RIGHT */ | ||
14133 | if( nArg > 2 ){ | ||
14134 | /* Padding string */ | ||
14135 | zPad = jx9_value_to_string(apArg[2], &iStrpad); | ||
14136 | if( iStrpad < 1 ){ | ||
14137 | /* Empty string */ | ||
14138 | zPad = " "; /* Whitespace padding */ | ||
14139 | iStrpad = (int)sizeof(char); | ||
14140 | } | ||
14141 | if( nArg > 3 ){ | ||
14142 | /* Padd type */ | ||
14143 | iType = jx9_value_to_int(apArg[3]); | ||
14144 | if( iType != 0 /* STR_PAD_LEFT */ && iType != 2 /* STR_PAD_BOTH */ ){ | ||
14145 | iType = 1 ; /* STR_PAD_RIGHT */ | ||
14146 | } | ||
14147 | } | ||
14148 | } | ||
14149 | iDiv = 1; | ||
14150 | if( iType == 2 ){ | ||
14151 | iDiv = 2; /* STR_PAD_BOTH */ | ||
14152 | } | ||
14153 | /* Perform the requested operation */ | ||
14154 | if( iType == 0 /* STR_PAD_LEFT */ || iType == 2 /* STR_PAD_BOTH */ ){ | ||
14155 | jPad = iStrpad; | ||
14156 | for( i = 0 ; i < iPadlen/iDiv ; i += jPad ){ | ||
14157 | /* Padding */ | ||
14158 | if( (int)jx9_context_result_buf_length(pCtx) + iLen + jPad >= iRealPad ){ | ||
14159 | break; | ||
14160 | } | ||
14161 | jx9_result_string(pCtx, zPad, jPad); | ||
14162 | } | ||
14163 | if( iType == 0 /* STR_PAD_LEFT */ ){ | ||
14164 | while( (int)jx9_context_result_buf_length(pCtx) + iLen < iRealPad ){ | ||
14165 | jPad = iRealPad - (iLen + (int)jx9_context_result_buf_length(pCtx) ); | ||
14166 | if( jPad > iStrpad ){ | ||
14167 | jPad = iStrpad; | ||
14168 | } | ||
14169 | if( jPad < 1){ | ||
14170 | break; | ||
14171 | } | ||
14172 | jx9_result_string(pCtx, zPad, jPad); | ||
14173 | } | ||
14174 | } | ||
14175 | } | ||
14176 | if( iLen > 0 ){ | ||
14177 | /* Append the input string */ | ||
14178 | jx9_result_string(pCtx, zIn, iLen); | ||
14179 | } | ||
14180 | if( iType == 1 /* STR_PAD_RIGHT */ || iType == 2 /* STR_PAD_BOTH */ ){ | ||
14181 | for( i = 0 ; i < iPadlen/iDiv ; i += iStrpad ){ | ||
14182 | /* Padding */ | ||
14183 | if( (int)jx9_context_result_buf_length(pCtx) + iStrpad >= iRealPad ){ | ||
14184 | break; | ||
14185 | } | ||
14186 | jx9_result_string(pCtx, zPad, iStrpad); | ||
14187 | } | ||
14188 | while( (int)jx9_context_result_buf_length(pCtx) < iRealPad ){ | ||
14189 | jPad = iRealPad - (int)jx9_context_result_buf_length(pCtx); | ||
14190 | if( jPad > iStrpad ){ | ||
14191 | jPad = iStrpad; | ||
14192 | } | ||
14193 | if( jPad < 1){ | ||
14194 | break; | ||
14195 | } | ||
14196 | jx9_result_string(pCtx, zPad, jPad); | ||
14197 | } | ||
14198 | } | ||
14199 | return JX9_OK; | ||
14200 | } | ||
14201 | /* | ||
14202 | * String replacement private data. | ||
14203 | */ | ||
14204 | typedef struct str_replace_data str_replace_data; | ||
14205 | struct str_replace_data | ||
14206 | { | ||
14207 | /* The following two fields are only used by the strtr function */ | ||
14208 | SyBlob *pWorker; /* Working buffer */ | ||
14209 | ProcStringMatch xMatch; /* Pattern match routine */ | ||
14210 | /* The following two fields are only used by the str_replace function */ | ||
14211 | SySet *pCollector; /* Argument collector*/ | ||
14212 | jx9_context *pCtx; /* Call context */ | ||
14213 | }; | ||
14214 | /* | ||
14215 | * Remove a substring. | ||
14216 | */ | ||
14217 | #define STRDEL(SRC, SLEN, OFFT, ILEN){\ | ||
14218 | for(;;){\ | ||
14219 | if( OFFT + ILEN >= SLEN ) break; SRC[OFFT] = SRC[OFFT+ILEN]; ++OFFT;\ | ||
14220 | }\ | ||
14221 | } | ||
14222 | /* | ||
14223 | * Shift right and insert algorithm. | ||
14224 | */ | ||
14225 | #define SHIFTRANDINSERT(SRC, LEN, OFFT, ENTRY, ELEN){\ | ||
14226 | sxu32 INLEN = LEN - OFFT;\ | ||
14227 | for(;;){\ | ||
14228 | if( LEN > 0 ){ LEN--; } if(INLEN < 1 ) break; SRC[LEN + ELEN] = SRC[LEN] ; --INLEN; \ | ||
14229 | }\ | ||
14230 | for(;;){\ | ||
14231 | if(ELEN < 1)break; SRC[OFFT] = ENTRY[0]; OFFT++; ENTRY++; --ELEN;\ | ||
14232 | }\ | ||
14233 | } | ||
14234 | /* | ||
14235 | * Replace all occurrences of the search string at offset (nOfft) with the given | ||
14236 | * replacement string [i.e: zReplace]. | ||
14237 | */ | ||
14238 | static int StringReplace(SyBlob *pWorker, sxu32 nOfft, int nLen, const char *zReplace, int nReplen) | ||
14239 | { | ||
14240 | char *zInput = (char *)SyBlobData(pWorker); | ||
14241 | sxu32 n, m; | ||
14242 | n = SyBlobLength(pWorker); | ||
14243 | m = nOfft; | ||
14244 | /* Delete the old entry */ | ||
14245 | STRDEL(zInput, n, m, nLen); | ||
14246 | SyBlobLength(pWorker) -= nLen; | ||
14247 | if( nReplen > 0 ){ | ||
14248 | sxi32 iRep = nReplen; | ||
14249 | sxi32 rc; | ||
14250 | /* | ||
14251 | * Make sure the working buffer is big enough to hold the replacement | ||
14252 | * string. | ||
14253 | */ | ||
14254 | rc = SyBlobAppend(pWorker, 0/* Grow without an append operation*/, (sxu32)nReplen); | ||
14255 | if( rc != SXRET_OK ){ | ||
14256 | /* Simply ignore any memory failure problem */ | ||
14257 | return SXRET_OK; | ||
14258 | } | ||
14259 | /* Perform the insertion now */ | ||
14260 | zInput = (char *)SyBlobData(pWorker); | ||
14261 | n = SyBlobLength(pWorker); | ||
14262 | SHIFTRANDINSERT(zInput, n, nOfft, zReplace, iRep); | ||
14263 | SyBlobLength(pWorker) += nReplen; | ||
14264 | } | ||
14265 | return SXRET_OK; | ||
14266 | } | ||
14267 | /* | ||
14268 | * String replacement walker callback. | ||
14269 | * The following callback is invoked for each array entry that hold | ||
14270 | * the replace string. | ||
14271 | * Refer to the strtr() implementation for more information. | ||
14272 | */ | ||
14273 | static int StringReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData) | ||
14274 | { | ||
14275 | str_replace_data *pRepData = (str_replace_data *)pUserData; | ||
14276 | const char *zTarget, *zReplace; | ||
14277 | SyBlob *pWorker; | ||
14278 | int tLen, nLen; | ||
14279 | sxu32 nOfft; | ||
14280 | sxi32 rc; | ||
14281 | /* Point to the working buffer */ | ||
14282 | pWorker = pRepData->pWorker; | ||
14283 | if( !jx9_value_is_string(pKey) ){ | ||
14284 | /* Target and replace must be a string */ | ||
14285 | return JX9_OK; | ||
14286 | } | ||
14287 | /* Extract the target and the replace */ | ||
14288 | zTarget = jx9_value_to_string(pKey, &tLen); | ||
14289 | if( tLen < 1 ){ | ||
14290 | /* Empty target, return immediately */ | ||
14291 | return JX9_OK; | ||
14292 | } | ||
14293 | /* Perform a pattern search */ | ||
14294 | rc = pRepData->xMatch(SyBlobData(pWorker), SyBlobLength(pWorker), (const void *)zTarget, (sxu32)tLen, &nOfft); | ||
14295 | if( rc != SXRET_OK ){ | ||
14296 | /* Pattern not found */ | ||
14297 | return JX9_OK; | ||
14298 | } | ||
14299 | /* Extract the replace string */ | ||
14300 | zReplace = jx9_value_to_string(pData, &nLen); | ||
14301 | /* Perform the replace process */ | ||
14302 | StringReplace(pWorker, nOfft, tLen, zReplace, nLen); | ||
14303 | /* All done */ | ||
14304 | return JX9_OK; | ||
14305 | } | ||
14306 | /* | ||
14307 | * The following walker callback is invoked by the str_rplace() function inorder | ||
14308 | * to collect search/replace string. | ||
14309 | * This callback is invoked only if the given argument is of type array. | ||
14310 | */ | ||
14311 | static int StrReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData) | ||
14312 | { | ||
14313 | str_replace_data *pRep = (str_replace_data *)pUserData; | ||
14314 | SyString sWorker; | ||
14315 | const char *zIn; | ||
14316 | int nByte; | ||
14317 | /* Extract a string representation of the given argument */ | ||
14318 | zIn = jx9_value_to_string(pData, &nByte); | ||
14319 | SyStringInitFromBuf(&sWorker, 0, 0); | ||
14320 | if( nByte > 0 ){ | ||
14321 | char *zDup; | ||
14322 | /* Duplicate the chunk */ | ||
14323 | zDup = (char *)jx9_context_alloc_chunk(pRep->pCtx, (unsigned int)nByte, FALSE, | ||
14324 | TRUE /* Release the chunk automatically, upon this context is destroyd */ | ||
14325 | ); | ||
14326 | if( zDup == 0 ){ | ||
14327 | /* Ignore any memory failure problem */ | ||
14328 | jx9_context_throw_error(pRep->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
14329 | return JX9_OK; | ||
14330 | } | ||
14331 | SyMemcpy(zIn, zDup, (sxu32)nByte); | ||
14332 | /* Save the chunk */ | ||
14333 | SyStringInitFromBuf(&sWorker, zDup, nByte); | ||
14334 | } | ||
14335 | /* Save for later processing */ | ||
14336 | SySetPut(pRep->pCollector, (const void *)&sWorker); | ||
14337 | /* All done */ | ||
14338 | SXUNUSED(pKey); /* cc warning */ | ||
14339 | return JX9_OK; | ||
14340 | } | ||
14341 | /* | ||
14342 | * mixed str_replace(mixed $search, mixed $replace, mixed $subject[, int &$count ]) | ||
14343 | * mixed str_ireplace(mixed $search, mixed $replace, mixed $subject[, int &$count ]) | ||
14344 | * Replace all occurrences of the search string with the replacement string. | ||
14345 | * Parameters | ||
14346 | * If search and replace are arrays, then str_replace() takes a value from each | ||
14347 | * array and uses them to search and replace on subject. If replace has fewer values | ||
14348 | * than search, then an empty string is used for the rest of replacement values. | ||
14349 | * If search is an array and replace is a string, then this replacement string is used | ||
14350 | * for every value of search. The converse would not make sense, though. | ||
14351 | * If search or replace are arrays, their elements are processed first to last. | ||
14352 | * $search | ||
14353 | * The value being searched for, otherwise known as the needle. An array may be used | ||
14354 | * to designate multiple needles. | ||
14355 | * $replace | ||
14356 | * The replacement value that replaces found search values. An array may be used | ||
14357 | * to designate multiple replacements. | ||
14358 | * $subject | ||
14359 | * The string or array being searched and replaced on, otherwise known as the haystack. | ||
14360 | * If subject is an array, then the search and replace is performed with every entry | ||
14361 | * of subject, and the return value is an array as well. | ||
14362 | * $count (Not used) | ||
14363 | * If passed, this will be set to the number of replacements performed. | ||
14364 | * Return | ||
14365 | * This function returns a string or an array with the replaced values. | ||
14366 | */ | ||
14367 | static int jx9Builtin_str_replace(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14368 | { | ||
14369 | SyString sTemp, *pSearch, *pReplace; | ||
14370 | ProcStringMatch xMatch; | ||
14371 | const char *zIn, *zFunc; | ||
14372 | str_replace_data sRep; | ||
14373 | SyBlob sWorker; | ||
14374 | SySet sReplace; | ||
14375 | SySet sSearch; | ||
14376 | int rep_str; | ||
14377 | int nByte; | ||
14378 | sxi32 rc; | ||
14379 | if( nArg < 3 ){ | ||
14380 | /* Missing/Invalid arguments, return null */ | ||
14381 | jx9_result_null(pCtx); | ||
14382 | return JX9_OK; | ||
14383 | } | ||
14384 | /* Initialize fields */ | ||
14385 | SySetInit(&sSearch, &pCtx->pVm->sAllocator, sizeof(SyString)); | ||
14386 | SySetInit(&sReplace, &pCtx->pVm->sAllocator, sizeof(SyString)); | ||
14387 | SyBlobInit(&sWorker, &pCtx->pVm->sAllocator); | ||
14388 | SyZero(&sRep, sizeof(str_replace_data)); | ||
14389 | sRep.pCtx = pCtx; | ||
14390 | sRep.pCollector = &sSearch; | ||
14391 | rep_str = 0; | ||
14392 | /* Extract the subject */ | ||
14393 | zIn = jx9_value_to_string(apArg[2], &nByte); | ||
14394 | if( nByte < 1 ){ | ||
14395 | /* Nothing to replace, return the empty string */ | ||
14396 | jx9_result_string(pCtx, "", 0); | ||
14397 | return JX9_OK; | ||
14398 | } | ||
14399 | /* Copy the subject */ | ||
14400 | SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nByte); | ||
14401 | /* Search string */ | ||
14402 | if( jx9_value_is_json_array(apArg[0]) ){ | ||
14403 | /* Collect search string */ | ||
14404 | jx9_array_walk(apArg[0], StrReplaceWalker, &sRep); | ||
14405 | }else{ | ||
14406 | /* Single pattern */ | ||
14407 | zIn = jx9_value_to_string(apArg[0], &nByte); | ||
14408 | if( nByte < 1 ){ | ||
14409 | /* Return the subject untouched since no search string is available */ | ||
14410 | jx9_result_value(pCtx, apArg[2]/* Subject as thrird argument*/); | ||
14411 | return JX9_OK; | ||
14412 | } | ||
14413 | SyStringInitFromBuf(&sTemp, zIn, nByte); | ||
14414 | /* Save for later processing */ | ||
14415 | SySetPut(&sSearch, (const void *)&sTemp); | ||
14416 | } | ||
14417 | /* Replace string */ | ||
14418 | if( jx9_value_is_json_array(apArg[1]) ){ | ||
14419 | /* Collect replace string */ | ||
14420 | sRep.pCollector = &sReplace; | ||
14421 | jx9_array_walk(apArg[1], StrReplaceWalker, &sRep); | ||
14422 | }else{ | ||
14423 | /* Single needle */ | ||
14424 | zIn = jx9_value_to_string(apArg[1], &nByte); | ||
14425 | rep_str = 1; | ||
14426 | SyStringInitFromBuf(&sTemp, zIn, nByte); | ||
14427 | /* Save for later processing */ | ||
14428 | SySetPut(&sReplace, (const void *)&sTemp); | ||
14429 | } | ||
14430 | /* Reset loop cursors */ | ||
14431 | SySetResetCursor(&sSearch); | ||
14432 | SySetResetCursor(&sReplace); | ||
14433 | pReplace = pSearch = 0; /* cc warning */ | ||
14434 | SyStringInitFromBuf(&sTemp, "", 0); | ||
14435 | /* Extract function name */ | ||
14436 | zFunc = jx9_function_name(pCtx); | ||
14437 | /* Set the default pattern match routine */ | ||
14438 | xMatch = SyBlobSearch; | ||
14439 | if( SyStrncmp(zFunc, "str_ireplace", sizeof("str_ireplace") - 1) == 0 ){ | ||
14440 | /* Case insensitive pattern match */ | ||
14441 | xMatch = iPatternMatch; | ||
14442 | } | ||
14443 | /* Start the replace process */ | ||
14444 | while( SXRET_OK == SySetGetNextEntry(&sSearch, (void **)&pSearch) ){ | ||
14445 | sxu32 nCount, nOfft; | ||
14446 | if( pSearch->nByte < 1 ){ | ||
14447 | /* Empty string, ignore */ | ||
14448 | continue; | ||
14449 | } | ||
14450 | /* Extract the replace string */ | ||
14451 | if( rep_str ){ | ||
14452 | pReplace = (SyString *)SySetPeek(&sReplace); | ||
14453 | }else{ | ||
14454 | if( SXRET_OK != SySetGetNextEntry(&sReplace, (void **)&pReplace) ){ | ||
14455 | /* Sepecial case when 'replace set' has fewer values than the search set. | ||
14456 | * An empty string is used for the rest of replacement values | ||
14457 | */ | ||
14458 | pReplace = 0; | ||
14459 | } | ||
14460 | } | ||
14461 | if( pReplace == 0 ){ | ||
14462 | /* Use an empty string instead */ | ||
14463 | pReplace = &sTemp; | ||
14464 | } | ||
14465 | nOfft = nCount = 0; | ||
14466 | for(;;){ | ||
14467 | if( nCount >= SyBlobLength(&sWorker) ){ | ||
14468 | break; | ||
14469 | } | ||
14470 | /* Perform a pattern lookup */ | ||
14471 | rc = xMatch(SyBlobDataAt(&sWorker, nCount), SyBlobLength(&sWorker) - nCount, (const void *)pSearch->zString, | ||
14472 | pSearch->nByte, &nOfft); | ||
14473 | if( rc != SXRET_OK ){ | ||
14474 | /* Pattern not found */ | ||
14475 | break; | ||
14476 | } | ||
14477 | /* Perform the replace operation */ | ||
14478 | StringReplace(&sWorker, nCount+nOfft, (int)pSearch->nByte, pReplace->zString, (int)pReplace->nByte); | ||
14479 | /* Increment offset counter */ | ||
14480 | nCount += nOfft + pReplace->nByte; | ||
14481 | } | ||
14482 | } | ||
14483 | /* All done, clean-up the mess left behind */ | ||
14484 | jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker), (int)SyBlobLength(&sWorker)); | ||
14485 | SySetRelease(&sSearch); | ||
14486 | SySetRelease(&sReplace); | ||
14487 | SyBlobRelease(&sWorker); | ||
14488 | return JX9_OK; | ||
14489 | } | ||
14490 | /* | ||
14491 | * string strtr(string $str, string $from, string $to) | ||
14492 | * string strtr(string $str, array $replace_pairs) | ||
14493 | * Translate characters or replace substrings. | ||
14494 | * Parameters | ||
14495 | * $str | ||
14496 | * The string being translated. | ||
14497 | * $from | ||
14498 | * The string being translated to to. | ||
14499 | * $to | ||
14500 | * The string replacing from. | ||
14501 | * $replace_pairs | ||
14502 | * The replace_pairs parameter may be used instead of to and | ||
14503 | * from, in which case it's an array in the form array('from' => 'to', ...). | ||
14504 | * Return | ||
14505 | * The translated string. | ||
14506 | * If replace_pairs contains a key which is an empty string (""), FALSE will be returned. | ||
14507 | */ | ||
14508 | static int jx9Builtin_strtr(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14509 | { | ||
14510 | const char *zIn; | ||
14511 | int nLen; | ||
14512 | if( nArg < 1 ){ | ||
14513 | /* Nothing to replace, return FALSE */ | ||
14514 | jx9_result_bool(pCtx, 0); | ||
14515 | return JX9_OK; | ||
14516 | } | ||
14517 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
14518 | if( nLen < 1 || nArg < 2 ){ | ||
14519 | /* Invalid arguments */ | ||
14520 | jx9_result_string(pCtx, zIn, nLen); | ||
14521 | return JX9_OK; | ||
14522 | } | ||
14523 | if( nArg == 2 && jx9_value_is_json_array(apArg[1]) ){ | ||
14524 | str_replace_data sRepData; | ||
14525 | SyBlob sWorker; | ||
14526 | /* Initilaize the working buffer */ | ||
14527 | SyBlobInit(&sWorker, &pCtx->pVm->sAllocator); | ||
14528 | /* Copy raw string */ | ||
14529 | SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nLen); | ||
14530 | /* Init our replace data instance */ | ||
14531 | sRepData.pWorker = &sWorker; | ||
14532 | sRepData.xMatch = SyBlobSearch; | ||
14533 | /* Iterate throw array entries and perform the replace operation.*/ | ||
14534 | jx9_array_walk(apArg[1], StringReplaceWalker, &sRepData); | ||
14535 | /* All done, return the result string */ | ||
14536 | jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker), | ||
14537 | (int)SyBlobLength(&sWorker)); /* Will make it's own copy */ | ||
14538 | /* Clean-up */ | ||
14539 | SyBlobRelease(&sWorker); | ||
14540 | }else{ | ||
14541 | int i, flen, tlen, c, iOfft; | ||
14542 | const char *zFrom, *zTo; | ||
14543 | if( nArg < 3 ){ | ||
14544 | /* Nothing to replace */ | ||
14545 | jx9_result_string(pCtx, zIn, nLen); | ||
14546 | return JX9_OK; | ||
14547 | } | ||
14548 | /* Extract given arguments */ | ||
14549 | zFrom = jx9_value_to_string(apArg[1], &flen); | ||
14550 | zTo = jx9_value_to_string(apArg[2], &tlen); | ||
14551 | if( flen < 1 || tlen < 1 ){ | ||
14552 | /* Nothing to replace */ | ||
14553 | jx9_result_string(pCtx, zIn, nLen); | ||
14554 | return JX9_OK; | ||
14555 | } | ||
14556 | /* Start the replace process */ | ||
14557 | for( i = 0 ; i < nLen ; ++i ){ | ||
14558 | c = zIn[i]; | ||
14559 | if( CheckMask(c, zFrom, flen, &iOfft) ){ | ||
14560 | if ( iOfft < tlen ){ | ||
14561 | c = zTo[iOfft]; | ||
14562 | } | ||
14563 | } | ||
14564 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
14565 | |||
14566 | } | ||
14567 | } | ||
14568 | return JX9_OK; | ||
14569 | } | ||
14570 | /* | ||
14571 | * Parse an INI string. | ||
14572 | * According to wikipedia | ||
14573 | * The INI file format is an informal standard for configuration files for some platforms or software. | ||
14574 | * INI files are simple text files with a basic structure composed of "sections" and "properties". | ||
14575 | * Format | ||
14576 | * Properties | ||
14577 | * The basic element contained in an INI file is the property. Every property has a name and a value | ||
14578 | * delimited by an equals sign (=). The name appears to the left of the equals sign. | ||
14579 | * Example: | ||
14580 | * name=value | ||
14581 | * Sections | ||
14582 | * Properties may be grouped into arbitrarily named sections. The section name appears on a line by itself | ||
14583 | * in square brackets ([ and ]). All properties after the section declaration are associated with that section. | ||
14584 | * There is no explicit "end of section" delimiter; sections end at the next section declaration | ||
14585 | * or the end of the file. Sections may not be nested. | ||
14586 | * Example: | ||
14587 | * [section] | ||
14588 | * Comments | ||
14589 | * Semicolons (;) at the beginning of the line indicate a comment. Comment lines are ignored. | ||
14590 | * This function return an array holding parsed values on success.FALSE otherwise. | ||
14591 | */ | ||
14592 | JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection) | ||
14593 | { | ||
14594 | jx9_value *pCur, *pArray, *pSection, *pWorker, *pValue; | ||
14595 | const char *zCur, *zEnd = &zIn[nByte]; | ||
14596 | SyHashEntry *pEntry; | ||
14597 | SyString sEntry; | ||
14598 | SyHash sHash; | ||
14599 | int c; | ||
14600 | /* Create an empty array and worker variables */ | ||
14601 | pArray = jx9_context_new_array(pCtx); | ||
14602 | pWorker = jx9_context_new_scalar(pCtx); | ||
14603 | pValue = jx9_context_new_scalar(pCtx); | ||
14604 | if( pArray == 0 || pWorker == 0 || pValue == 0){ | ||
14605 | /* Out of memory */ | ||
14606 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
14607 | /* Return FALSE */ | ||
14608 | jx9_result_bool(pCtx, 0); | ||
14609 | return JX9_OK; | ||
14610 | } | ||
14611 | SyHashInit(&sHash, &pCtx->pVm->sAllocator, 0, 0); | ||
14612 | pCur = pArray; | ||
14613 | /* Start the parse process */ | ||
14614 | for(;;){ | ||
14615 | /* Ignore leading white spaces */ | ||
14616 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0])){ | ||
14617 | zIn++; | ||
14618 | } | ||
14619 | if( zIn >= zEnd ){ | ||
14620 | /* No more input to process */ | ||
14621 | break; | ||
14622 | } | ||
14623 | if( zIn[0] == ';' || zIn[0] == '#' ){ | ||
14624 | /* Comment til the end of line */ | ||
14625 | zIn++; | ||
14626 | while(zIn < zEnd && zIn[0] != '\n' ){ | ||
14627 | zIn++; | ||
14628 | } | ||
14629 | continue; | ||
14630 | } | ||
14631 | /* Reset the string cursor of the working variable */ | ||
14632 | jx9_value_reset_string_cursor(pWorker); | ||
14633 | if( zIn[0] == '[' ){ | ||
14634 | /* Section: Extract the section name */ | ||
14635 | zIn++; | ||
14636 | zCur = zIn; | ||
14637 | while( zIn < zEnd && zIn[0] != ']' ){ | ||
14638 | zIn++; | ||
14639 | } | ||
14640 | if( zIn > zCur && bProcessSection ){ | ||
14641 | /* Save the section name */ | ||
14642 | SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur)); | ||
14643 | SyStringFullTrim(&sEntry); | ||
14644 | jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); | ||
14645 | if( sEntry.nByte > 0 ){ | ||
14646 | /* Associate an array with the section */ | ||
14647 | pSection = jx9_context_new_array(pCtx); | ||
14648 | if( pSection ){ | ||
14649 | jx9_array_add_elem(pArray, pWorker/*Section name*/, pSection); | ||
14650 | pCur = pSection; | ||
14651 | } | ||
14652 | } | ||
14653 | } | ||
14654 | zIn++; /* Trailing square brackets ']' */ | ||
14655 | }else{ | ||
14656 | jx9_value *pOldCur; | ||
14657 | int is_array; | ||
14658 | int iLen; | ||
14659 | /* Properties */ | ||
14660 | is_array = 0; | ||
14661 | zCur = zIn; | ||
14662 | iLen = 0; /* cc warning */ | ||
14663 | pOldCur = pCur; | ||
14664 | while( zIn < zEnd && zIn[0] != '=' ){ | ||
14665 | if( zIn[0] == '[' && !is_array ){ | ||
14666 | /* Array */ | ||
14667 | iLen = (int)(zIn-zCur); | ||
14668 | is_array = 1; | ||
14669 | if( iLen > 0 ){ | ||
14670 | jx9_value *pvArr = 0; /* cc warning */ | ||
14671 | /* Query the hashtable */ | ||
14672 | SyStringInitFromBuf(&sEntry, zCur, iLen); | ||
14673 | SyStringFullTrim(&sEntry); | ||
14674 | pEntry = SyHashGet(&sHash, (const void *)sEntry.zString, sEntry.nByte); | ||
14675 | if( pEntry ){ | ||
14676 | pvArr = (jx9_value *)SyHashEntryGetUserData(pEntry); | ||
14677 | }else{ | ||
14678 | /* Create an empty array */ | ||
14679 | pvArr = jx9_context_new_array(pCtx); | ||
14680 | if( pvArr ){ | ||
14681 | /* Save the entry */ | ||
14682 | SyHashInsert(&sHash, (const void *)sEntry.zString, sEntry.nByte, pvArr); | ||
14683 | /* Insert the entry */ | ||
14684 | jx9_value_reset_string_cursor(pWorker); | ||
14685 | jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); | ||
14686 | jx9_array_add_elem(pCur, pWorker, pvArr); | ||
14687 | jx9_value_reset_string_cursor(pWorker); | ||
14688 | } | ||
14689 | } | ||
14690 | if( pvArr ){ | ||
14691 | pCur = pvArr; | ||
14692 | } | ||
14693 | } | ||
14694 | while ( zIn < zEnd && zIn[0] != ']' ){ | ||
14695 | zIn++; | ||
14696 | } | ||
14697 | } | ||
14698 | zIn++; | ||
14699 | } | ||
14700 | if( !is_array ){ | ||
14701 | iLen = (int)(zIn-zCur); | ||
14702 | } | ||
14703 | /* Trim the key */ | ||
14704 | SyStringInitFromBuf(&sEntry, zCur, iLen); | ||
14705 | SyStringFullTrim(&sEntry); | ||
14706 | if( sEntry.nByte > 0 ){ | ||
14707 | if( !is_array ){ | ||
14708 | /* Save the key name */ | ||
14709 | jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); | ||
14710 | } | ||
14711 | /* extract key value */ | ||
14712 | jx9_value_reset_string_cursor(pValue); | ||
14713 | zIn++; /* '=' */ | ||
14714 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ | ||
14715 | zIn++; | ||
14716 | } | ||
14717 | if( zIn < zEnd ){ | ||
14718 | zCur = zIn; | ||
14719 | c = zIn[0]; | ||
14720 | if( c == '"' || c == '\'' ){ | ||
14721 | zIn++; | ||
14722 | /* Delimit the value */ | ||
14723 | while( zIn < zEnd ){ | ||
14724 | if ( zIn[0] == c && zIn[-1] != '\\' ){ | ||
14725 | break; | ||
14726 | } | ||
14727 | zIn++; | ||
14728 | } | ||
14729 | if( zIn < zEnd ){ | ||
14730 | zIn++; | ||
14731 | } | ||
14732 | }else{ | ||
14733 | while( zIn < zEnd ){ | ||
14734 | if( zIn[0] == '\n' ){ | ||
14735 | if( zIn[-1] != '\\' ){ | ||
14736 | break; | ||
14737 | } | ||
14738 | }else if( zIn[0] == ';' || zIn[0] == '#' ){ | ||
14739 | /* Inline comments */ | ||
14740 | break; | ||
14741 | } | ||
14742 | zIn++; | ||
14743 | } | ||
14744 | } | ||
14745 | /* Trim the value */ | ||
14746 | SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur)); | ||
14747 | SyStringFullTrim(&sEntry); | ||
14748 | if( c == '"' || c == '\'' ){ | ||
14749 | SyStringTrimLeadingChar(&sEntry, c); | ||
14750 | SyStringTrimTrailingChar(&sEntry, c); | ||
14751 | } | ||
14752 | if( sEntry.nByte > 0 ){ | ||
14753 | jx9_value_string(pValue, sEntry.zString, (int)sEntry.nByte); | ||
14754 | } | ||
14755 | /* Insert the key and it's value */ | ||
14756 | jx9_array_add_elem(pCur, is_array ? 0 /*Automatic index assign */: pWorker, pValue); | ||
14757 | } | ||
14758 | }else{ | ||
14759 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && ( SyisSpace(zIn[0]) || zIn[0] == '=' ) ){ | ||
14760 | zIn++; | ||
14761 | } | ||
14762 | } | ||
14763 | pCur = pOldCur; | ||
14764 | } | ||
14765 | } | ||
14766 | SyHashRelease(&sHash); | ||
14767 | /* Return the parse of the INI string */ | ||
14768 | jx9_result_value(pCtx, pArray); | ||
14769 | return SXRET_OK; | ||
14770 | } | ||
14771 | /* | ||
14772 | * array parse_ini_string(string $ini[, bool $process_sections = false[, int $scanner_mode = INI_SCANNER_NORMAL ]]) | ||
14773 | * Parse a configuration string. | ||
14774 | * Parameters | ||
14775 | * $ini | ||
14776 | * The contents of the ini file being parsed. | ||
14777 | * $process_sections | ||
14778 | * By setting the process_sections parameter to TRUE, you get a multidimensional array, with the section names | ||
14779 | * and settings included. The default for process_sections is FALSE. | ||
14780 | * $scanner_mode (Not used) | ||
14781 | * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. If INI_SCANNER_RAW is supplied | ||
14782 | * then option values will not be parsed. | ||
14783 | * Return | ||
14784 | * The settings are returned as an associative array on success, and FALSE on failure. | ||
14785 | */ | ||
14786 | static int jx9Builtin_parse_ini_string(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14787 | { | ||
14788 | const char *zIni; | ||
14789 | int nByte; | ||
14790 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
14791 | /* Missing/Invalid arguments, return FALSE*/ | ||
14792 | jx9_result_bool(pCtx, 0); | ||
14793 | return JX9_OK; | ||
14794 | } | ||
14795 | /* Extract the raw INI buffer */ | ||
14796 | zIni = jx9_value_to_string(apArg[0], &nByte); | ||
14797 | /* Process the INI buffer*/ | ||
14798 | jx9ParseIniString(pCtx, zIni, (sxu32)nByte, (nArg > 1) ? jx9_value_to_bool(apArg[1]) : 0); | ||
14799 | return JX9_OK; | ||
14800 | } | ||
14801 | /* | ||
14802 | * Ctype Functions. | ||
14803 | * Authors: | ||
14804 | * Symisc Systems, devel@symisc.net. | ||
14805 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
14806 | * Status: | ||
14807 | * Stable. | ||
14808 | */ | ||
14809 | /* | ||
14810 | * bool ctype_alnum(string $text) | ||
14811 | * Checks if all of the characters in the provided string, text, are alphanumeric. | ||
14812 | * Parameters | ||
14813 | * $text | ||
14814 | * The tested string. | ||
14815 | * Return | ||
14816 | * TRUE if every character in text is either a letter or a digit, FALSE otherwise. | ||
14817 | */ | ||
14818 | static int jx9Builtin_ctype_alnum(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14819 | { | ||
14820 | const unsigned char *zIn, *zEnd; | ||
14821 | int nLen; | ||
14822 | if( nArg < 1 ){ | ||
14823 | /* Missing arguments, return FALSE */ | ||
14824 | jx9_result_bool(pCtx, 0); | ||
14825 | return JX9_OK; | ||
14826 | } | ||
14827 | /* Extract the target string */ | ||
14828 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
14829 | zEnd = &zIn[nLen]; | ||
14830 | if( nLen < 1 ){ | ||
14831 | /* Empty string, return FALSE */ | ||
14832 | jx9_result_bool(pCtx, 0); | ||
14833 | return JX9_OK; | ||
14834 | } | ||
14835 | /* Perform the requested operation */ | ||
14836 | for(;;){ | ||
14837 | if( zIn >= zEnd ){ | ||
14838 | /* If we reach the end of the string, then the test succeeded. */ | ||
14839 | jx9_result_bool(pCtx, 1); | ||
14840 | return JX9_OK; | ||
14841 | } | ||
14842 | if( !SyisAlphaNum(zIn[0]) ){ | ||
14843 | break; | ||
14844 | } | ||
14845 | /* Point to the next character */ | ||
14846 | zIn++; | ||
14847 | } | ||
14848 | /* The test failed, return FALSE */ | ||
14849 | jx9_result_bool(pCtx, 0); | ||
14850 | return JX9_OK; | ||
14851 | } | ||
14852 | /* | ||
14853 | * bool ctype_alpha(string $text) | ||
14854 | * Checks if all of the characters in the provided string, text, are alphabetic. | ||
14855 | * Parameters | ||
14856 | * $text | ||
14857 | * The tested string. | ||
14858 | * Return | ||
14859 | * TRUE if every character in text is a letter from the current locale, FALSE otherwise. | ||
14860 | */ | ||
14861 | static int jx9Builtin_ctype_alpha(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14862 | { | ||
14863 | const unsigned char *zIn, *zEnd; | ||
14864 | int nLen; | ||
14865 | if( nArg < 1 ){ | ||
14866 | /* Missing arguments, return FALSE */ | ||
14867 | jx9_result_bool(pCtx, 0); | ||
14868 | return JX9_OK; | ||
14869 | } | ||
14870 | /* Extract the target string */ | ||
14871 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
14872 | zEnd = &zIn[nLen]; | ||
14873 | if( nLen < 1 ){ | ||
14874 | /* Empty string, return FALSE */ | ||
14875 | jx9_result_bool(pCtx, 0); | ||
14876 | return JX9_OK; | ||
14877 | } | ||
14878 | /* Perform the requested operation */ | ||
14879 | for(;;){ | ||
14880 | if( zIn >= zEnd ){ | ||
14881 | /* If we reach the end of the string, then the test succeeded. */ | ||
14882 | jx9_result_bool(pCtx, 1); | ||
14883 | return JX9_OK; | ||
14884 | } | ||
14885 | if( !SyisAlpha(zIn[0]) ){ | ||
14886 | break; | ||
14887 | } | ||
14888 | /* Point to the next character */ | ||
14889 | zIn++; | ||
14890 | } | ||
14891 | /* The test failed, return FALSE */ | ||
14892 | jx9_result_bool(pCtx, 0); | ||
14893 | return JX9_OK; | ||
14894 | } | ||
14895 | /* | ||
14896 | * bool ctype_cntrl(string $text) | ||
14897 | * Checks if all of the characters in the provided string, text, are control characters. | ||
14898 | * Parameters | ||
14899 | * $text | ||
14900 | * The tested string. | ||
14901 | * Return | ||
14902 | * TRUE if every character in text is a control characters, FALSE otherwise. | ||
14903 | */ | ||
14904 | static int jx9Builtin_ctype_cntrl(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14905 | { | ||
14906 | const unsigned char *zIn, *zEnd; | ||
14907 | int nLen; | ||
14908 | if( nArg < 1 ){ | ||
14909 | /* Missing arguments, return FALSE */ | ||
14910 | jx9_result_bool(pCtx, 0); | ||
14911 | return JX9_OK; | ||
14912 | } | ||
14913 | /* Extract the target string */ | ||
14914 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
14915 | zEnd = &zIn[nLen]; | ||
14916 | if( nLen < 1 ){ | ||
14917 | /* Empty string, return FALSE */ | ||
14918 | jx9_result_bool(pCtx, 0); | ||
14919 | return JX9_OK; | ||
14920 | } | ||
14921 | /* Perform the requested operation */ | ||
14922 | for(;;){ | ||
14923 | if( zIn >= zEnd ){ | ||
14924 | /* If we reach the end of the string, then the test succeeded. */ | ||
14925 | jx9_result_bool(pCtx, 1); | ||
14926 | return JX9_OK; | ||
14927 | } | ||
14928 | if( zIn[0] >= 0xc0 ){ | ||
14929 | /* UTF-8 stream */ | ||
14930 | break; | ||
14931 | } | ||
14932 | if( !SyisCtrl(zIn[0]) ){ | ||
14933 | break; | ||
14934 | } | ||
14935 | /* Point to the next character */ | ||
14936 | zIn++; | ||
14937 | } | ||
14938 | /* The test failed, return FALSE */ | ||
14939 | jx9_result_bool(pCtx, 0); | ||
14940 | return JX9_OK; | ||
14941 | } | ||
14942 | /* | ||
14943 | * bool ctype_digit(string $text) | ||
14944 | * Checks if all of the characters in the provided string, text, are numerical. | ||
14945 | * Parameters | ||
14946 | * $text | ||
14947 | * The tested string. | ||
14948 | * Return | ||
14949 | * TRUE if every character in the string text is a decimal digit, FALSE otherwise. | ||
14950 | */ | ||
14951 | static int jx9Builtin_ctype_digit(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
14952 | { | ||
14953 | const unsigned char *zIn, *zEnd; | ||
14954 | int nLen; | ||
14955 | if( nArg < 1 ){ | ||
14956 | /* Missing arguments, return FALSE */ | ||
14957 | jx9_result_bool(pCtx, 0); | ||
14958 | return JX9_OK; | ||
14959 | } | ||
14960 | /* Extract the target string */ | ||
14961 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
14962 | zEnd = &zIn[nLen]; | ||
14963 | if( nLen < 1 ){ | ||
14964 | /* Empty string, return FALSE */ | ||
14965 | jx9_result_bool(pCtx, 0); | ||
14966 | return JX9_OK; | ||
14967 | } | ||
14968 | /* Perform the requested operation */ | ||
14969 | for(;;){ | ||
14970 | if( zIn >= zEnd ){ | ||
14971 | /* If we reach the end of the string, then the test succeeded. */ | ||
14972 | jx9_result_bool(pCtx, 1); | ||
14973 | return JX9_OK; | ||
14974 | } | ||
14975 | if( zIn[0] >= 0xc0 ){ | ||
14976 | /* UTF-8 stream */ | ||
14977 | break; | ||
14978 | } | ||
14979 | if( !SyisDigit(zIn[0]) ){ | ||
14980 | break; | ||
14981 | } | ||
14982 | /* Point to the next character */ | ||
14983 | zIn++; | ||
14984 | } | ||
14985 | /* The test failed, return FALSE */ | ||
14986 | jx9_result_bool(pCtx, 0); | ||
14987 | return JX9_OK; | ||
14988 | } | ||
14989 | /* | ||
14990 | * bool ctype_xdigit(string $text) | ||
14991 | * Check for character(s) representing a hexadecimal digit. | ||
14992 | * Parameters | ||
14993 | * $text | ||
14994 | * The tested string. | ||
14995 | * Return | ||
14996 | * Returns TRUE if every character in text is a hexadecimal 'digit', that is | ||
14997 | * a decimal digit or a character from [A-Fa-f] , FALSE otherwise. | ||
14998 | */ | ||
14999 | static int jx9Builtin_ctype_xdigit(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15000 | { | ||
15001 | const unsigned char *zIn, *zEnd; | ||
15002 | int nLen; | ||
15003 | if( nArg < 1 ){ | ||
15004 | /* Missing arguments, return FALSE */ | ||
15005 | jx9_result_bool(pCtx, 0); | ||
15006 | return JX9_OK; | ||
15007 | } | ||
15008 | /* Extract the target string */ | ||
15009 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
15010 | zEnd = &zIn[nLen]; | ||
15011 | if( nLen < 1 ){ | ||
15012 | /* Empty string, return FALSE */ | ||
15013 | jx9_result_bool(pCtx, 0); | ||
15014 | return JX9_OK; | ||
15015 | } | ||
15016 | /* Perform the requested operation */ | ||
15017 | for(;;){ | ||
15018 | if( zIn >= zEnd ){ | ||
15019 | /* If we reach the end of the string, then the test succeeded. */ | ||
15020 | jx9_result_bool(pCtx, 1); | ||
15021 | return JX9_OK; | ||
15022 | } | ||
15023 | if( zIn[0] >= 0xc0 ){ | ||
15024 | /* UTF-8 stream */ | ||
15025 | break; | ||
15026 | } | ||
15027 | if( !SyisHex(zIn[0]) ){ | ||
15028 | break; | ||
15029 | } | ||
15030 | /* Point to the next character */ | ||
15031 | zIn++; | ||
15032 | } | ||
15033 | /* The test failed, return FALSE */ | ||
15034 | jx9_result_bool(pCtx, 0); | ||
15035 | return JX9_OK; | ||
15036 | } | ||
15037 | /* | ||
15038 | * bool ctype_graph(string $text) | ||
15039 | * Checks if all of the characters in the provided string, text, creates visible output. | ||
15040 | * Parameters | ||
15041 | * $text | ||
15042 | * The tested string. | ||
15043 | * Return | ||
15044 | * Returns TRUE if every character in text is printable and actually creates visible output | ||
15045 | * (no white space), FALSE otherwise. | ||
15046 | */ | ||
15047 | static int jx9Builtin_ctype_graph(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15048 | { | ||
15049 | const unsigned char *zIn, *zEnd; | ||
15050 | int nLen; | ||
15051 | if( nArg < 1 ){ | ||
15052 | /* Missing arguments, return FALSE */ | ||
15053 | jx9_result_bool(pCtx, 0); | ||
15054 | return JX9_OK; | ||
15055 | } | ||
15056 | /* Extract the target string */ | ||
15057 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
15058 | zEnd = &zIn[nLen]; | ||
15059 | if( nLen < 1 ){ | ||
15060 | /* Empty string, return FALSE */ | ||
15061 | jx9_result_bool(pCtx, 0); | ||
15062 | return JX9_OK; | ||
15063 | } | ||
15064 | /* Perform the requested operation */ | ||
15065 | for(;;){ | ||
15066 | if( zIn >= zEnd ){ | ||
15067 | /* If we reach the end of the string, then the test succeeded. */ | ||
15068 | jx9_result_bool(pCtx, 1); | ||
15069 | return JX9_OK; | ||
15070 | } | ||
15071 | if( zIn[0] >= 0xc0 ){ | ||
15072 | /* UTF-8 stream */ | ||
15073 | break; | ||
15074 | } | ||
15075 | if( !SyisGraph(zIn[0]) ){ | ||
15076 | break; | ||
15077 | } | ||
15078 | /* Point to the next character */ | ||
15079 | zIn++; | ||
15080 | } | ||
15081 | /* The test failed, return FALSE */ | ||
15082 | jx9_result_bool(pCtx, 0); | ||
15083 | return JX9_OK; | ||
15084 | } | ||
15085 | /* | ||
15086 | * bool ctype_print(string $text) | ||
15087 | * Checks if all of the characters in the provided string, text, are printable. | ||
15088 | * Parameters | ||
15089 | * $text | ||
15090 | * The tested string. | ||
15091 | * Return | ||
15092 | * Returns TRUE if every character in text will actually create output (including blanks). | ||
15093 | * Returns FALSE if text contains control characters or characters that do not have any output | ||
15094 | * or control function at all. | ||
15095 | */ | ||
15096 | static int jx9Builtin_ctype_print(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15097 | { | ||
15098 | const unsigned char *zIn, *zEnd; | ||
15099 | int nLen; | ||
15100 | if( nArg < 1 ){ | ||
15101 | /* Missing arguments, return FALSE */ | ||
15102 | jx9_result_bool(pCtx, 0); | ||
15103 | return JX9_OK; | ||
15104 | } | ||
15105 | /* Extract the target string */ | ||
15106 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
15107 | zEnd = &zIn[nLen]; | ||
15108 | if( nLen < 1 ){ | ||
15109 | /* Empty string, return FALSE */ | ||
15110 | jx9_result_bool(pCtx, 0); | ||
15111 | return JX9_OK; | ||
15112 | } | ||
15113 | /* Perform the requested operation */ | ||
15114 | for(;;){ | ||
15115 | if( zIn >= zEnd ){ | ||
15116 | /* If we reach the end of the string, then the test succeeded. */ | ||
15117 | jx9_result_bool(pCtx, 1); | ||
15118 | return JX9_OK; | ||
15119 | } | ||
15120 | if( zIn[0] >= 0xc0 ){ | ||
15121 | /* UTF-8 stream */ | ||
15122 | break; | ||
15123 | } | ||
15124 | if( !SyisPrint(zIn[0]) ){ | ||
15125 | break; | ||
15126 | } | ||
15127 | /* Point to the next character */ | ||
15128 | zIn++; | ||
15129 | } | ||
15130 | /* The test failed, return FALSE */ | ||
15131 | jx9_result_bool(pCtx, 0); | ||
15132 | return JX9_OK; | ||
15133 | } | ||
15134 | /* | ||
15135 | * bool ctype_punct(string $text) | ||
15136 | * Checks if all of the characters in the provided string, text, are punctuation character. | ||
15137 | * Parameters | ||
15138 | * $text | ||
15139 | * The tested string. | ||
15140 | * Return | ||
15141 | * Returns TRUE if every character in text is printable, but neither letter | ||
15142 | * digit or blank, FALSE otherwise. | ||
15143 | */ | ||
15144 | static int jx9Builtin_ctype_punct(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15145 | { | ||
15146 | const unsigned char *zIn, *zEnd; | ||
15147 | int nLen; | ||
15148 | if( nArg < 1 ){ | ||
15149 | /* Missing arguments, return FALSE */ | ||
15150 | jx9_result_bool(pCtx, 0); | ||
15151 | return JX9_OK; | ||
15152 | } | ||
15153 | /* Extract the target string */ | ||
15154 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
15155 | zEnd = &zIn[nLen]; | ||
15156 | if( nLen < 1 ){ | ||
15157 | /* Empty string, return FALSE */ | ||
15158 | jx9_result_bool(pCtx, 0); | ||
15159 | return JX9_OK; | ||
15160 | } | ||
15161 | /* Perform the requested operation */ | ||
15162 | for(;;){ | ||
15163 | if( zIn >= zEnd ){ | ||
15164 | /* If we reach the end of the string, then the test succeeded. */ | ||
15165 | jx9_result_bool(pCtx, 1); | ||
15166 | return JX9_OK; | ||
15167 | } | ||
15168 | if( zIn[0] >= 0xc0 ){ | ||
15169 | /* UTF-8 stream */ | ||
15170 | break; | ||
15171 | } | ||
15172 | if( !SyisPunct(zIn[0]) ){ | ||
15173 | break; | ||
15174 | } | ||
15175 | /* Point to the next character */ | ||
15176 | zIn++; | ||
15177 | } | ||
15178 | /* The test failed, return FALSE */ | ||
15179 | jx9_result_bool(pCtx, 0); | ||
15180 | return JX9_OK; | ||
15181 | } | ||
15182 | /* | ||
15183 | * bool ctype_space(string $text) | ||
15184 | * Checks if all of the characters in the provided string, text, creates whitespace. | ||
15185 | * Parameters | ||
15186 | * $text | ||
15187 | * The tested string. | ||
15188 | * Return | ||
15189 | * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. | ||
15190 | * Besides the blank character this also includes tab, vertical tab, line feed, carriage return | ||
15191 | * and form feed characters. | ||
15192 | */ | ||
15193 | static int jx9Builtin_ctype_space(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15194 | { | ||
15195 | const unsigned char *zIn, *zEnd; | ||
15196 | int nLen; | ||
15197 | if( nArg < 1 ){ | ||
15198 | /* Missing arguments, return FALSE */ | ||
15199 | jx9_result_bool(pCtx, 0); | ||
15200 | return JX9_OK; | ||
15201 | } | ||
15202 | /* Extract the target string */ | ||
15203 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
15204 | zEnd = &zIn[nLen]; | ||
15205 | if( nLen < 1 ){ | ||
15206 | /* Empty string, return FALSE */ | ||
15207 | jx9_result_bool(pCtx, 0); | ||
15208 | return JX9_OK; | ||
15209 | } | ||
15210 | /* Perform the requested operation */ | ||
15211 | for(;;){ | ||
15212 | if( zIn >= zEnd ){ | ||
15213 | /* If we reach the end of the string, then the test succeeded. */ | ||
15214 | jx9_result_bool(pCtx, 1); | ||
15215 | return JX9_OK; | ||
15216 | } | ||
15217 | if( zIn[0] >= 0xc0 ){ | ||
15218 | /* UTF-8 stream */ | ||
15219 | break; | ||
15220 | } | ||
15221 | if( !SyisSpace(zIn[0]) ){ | ||
15222 | break; | ||
15223 | } | ||
15224 | /* Point to the next character */ | ||
15225 | zIn++; | ||
15226 | } | ||
15227 | /* The test failed, return FALSE */ | ||
15228 | jx9_result_bool(pCtx, 0); | ||
15229 | return JX9_OK; | ||
15230 | } | ||
15231 | /* | ||
15232 | * bool ctype_lower(string $text) | ||
15233 | * Checks if all of the characters in the provided string, text, are lowercase letters. | ||
15234 | * Parameters | ||
15235 | * $text | ||
15236 | * The tested string. | ||
15237 | * Return | ||
15238 | * Returns TRUE if every character in text is a lowercase letter in the current locale. | ||
15239 | */ | ||
15240 | static int jx9Builtin_ctype_lower(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15241 | { | ||
15242 | const unsigned char *zIn, *zEnd; | ||
15243 | int nLen; | ||
15244 | if( nArg < 1 ){ | ||
15245 | /* Missing arguments, return FALSE */ | ||
15246 | jx9_result_bool(pCtx, 0); | ||
15247 | return JX9_OK; | ||
15248 | } | ||
15249 | /* Extract the target string */ | ||
15250 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
15251 | zEnd = &zIn[nLen]; | ||
15252 | if( nLen < 1 ){ | ||
15253 | /* Empty string, return FALSE */ | ||
15254 | jx9_result_bool(pCtx, 0); | ||
15255 | return JX9_OK; | ||
15256 | } | ||
15257 | /* Perform the requested operation */ | ||
15258 | for(;;){ | ||
15259 | if( zIn >= zEnd ){ | ||
15260 | /* If we reach the end of the string, then the test succeeded. */ | ||
15261 | jx9_result_bool(pCtx, 1); | ||
15262 | return JX9_OK; | ||
15263 | } | ||
15264 | if( !SyisLower(zIn[0]) ){ | ||
15265 | break; | ||
15266 | } | ||
15267 | /* Point to the next character */ | ||
15268 | zIn++; | ||
15269 | } | ||
15270 | /* The test failed, return FALSE */ | ||
15271 | jx9_result_bool(pCtx, 0); | ||
15272 | return JX9_OK; | ||
15273 | } | ||
15274 | /* | ||
15275 | * bool ctype_upper(string $text) | ||
15276 | * Checks if all of the characters in the provided string, text, are uppercase letters. | ||
15277 | * Parameters | ||
15278 | * $text | ||
15279 | * The tested string. | ||
15280 | * Return | ||
15281 | * Returns TRUE if every character in text is a uppercase letter in the current locale. | ||
15282 | */ | ||
15283 | static int jx9Builtin_ctype_upper(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15284 | { | ||
15285 | const unsigned char *zIn, *zEnd; | ||
15286 | int nLen; | ||
15287 | if( nArg < 1 ){ | ||
15288 | /* Missing arguments, return FALSE */ | ||
15289 | jx9_result_bool(pCtx, 0); | ||
15290 | return JX9_OK; | ||
15291 | } | ||
15292 | /* Extract the target string */ | ||
15293 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); | ||
15294 | zEnd = &zIn[nLen]; | ||
15295 | if( nLen < 1 ){ | ||
15296 | /* Empty string, return FALSE */ | ||
15297 | jx9_result_bool(pCtx, 0); | ||
15298 | return JX9_OK; | ||
15299 | } | ||
15300 | /* Perform the requested operation */ | ||
15301 | for(;;){ | ||
15302 | if( zIn >= zEnd ){ | ||
15303 | /* If we reach the end of the string, then the test succeeded. */ | ||
15304 | jx9_result_bool(pCtx, 1); | ||
15305 | return JX9_OK; | ||
15306 | } | ||
15307 | if( !SyisUpper(zIn[0]) ){ | ||
15308 | break; | ||
15309 | } | ||
15310 | /* Point to the next character */ | ||
15311 | zIn++; | ||
15312 | } | ||
15313 | /* The test failed, return FALSE */ | ||
15314 | jx9_result_bool(pCtx, 0); | ||
15315 | return JX9_OK; | ||
15316 | } | ||
15317 | /* | ||
15318 | * Date/Time functions | ||
15319 | * Authors: | ||
15320 | * Symisc Systems, devel@symisc.net. | ||
15321 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
15322 | * Status: | ||
15323 | * Devel. | ||
15324 | */ | ||
15325 | #include <time.h> | ||
15326 | #ifdef __WINNT__ | ||
15327 | /* GetSystemTime() */ | ||
15328 | #include <Windows.h> | ||
15329 | #ifdef _WIN32_WCE | ||
15330 | /* | ||
15331 | ** WindowsCE does not have a localtime() function. So create a | ||
15332 | ** substitute. | ||
15333 | ** Taken from the SQLite3 source tree. | ||
15334 | ** Status: Public domain | ||
15335 | */ | ||
15336 | struct tm *__cdecl localtime(const time_t *t) | ||
15337 | { | ||
15338 | static struct tm y; | ||
15339 | FILETIME uTm, lTm; | ||
15340 | SYSTEMTIME pTm; | ||
15341 | jx9_int64 t64; | ||
15342 | t64 = *t; | ||
15343 | t64 = (t64 + 11644473600)*10000000; | ||
15344 | uTm.dwLowDateTime = (DWORD)(t64 & 0xFFFFFFFF); | ||
15345 | uTm.dwHighDateTime= (DWORD)(t64 >> 32); | ||
15346 | FileTimeToLocalFileTime(&uTm, &lTm); | ||
15347 | FileTimeToSystemTime(&lTm, &pTm); | ||
15348 | y.tm_year = pTm.wYear - 1900; | ||
15349 | y.tm_mon = pTm.wMonth - 1; | ||
15350 | y.tm_wday = pTm.wDayOfWeek; | ||
15351 | y.tm_mday = pTm.wDay; | ||
15352 | y.tm_hour = pTm.wHour; | ||
15353 | y.tm_min = pTm.wMinute; | ||
15354 | y.tm_sec = pTm.wSecond; | ||
15355 | return &y; | ||
15356 | } | ||
15357 | #endif /*_WIN32_WCE */ | ||
15358 | #elif defined(__UNIXES__) | ||
15359 | #include <sys/time.h> | ||
15360 | #endif /* __WINNT__*/ | ||
15361 | /* | ||
15362 | * int64 time(void) | ||
15363 | * Current Unix timestamp | ||
15364 | * Parameters | ||
15365 | * None. | ||
15366 | * Return | ||
15367 | * Returns the current time measured in the number of seconds | ||
15368 | * since the Unix Epoch (January 1 1970 00:00:00 GMT). | ||
15369 | */ | ||
15370 | static int jx9Builtin_time(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15371 | { | ||
15372 | time_t tt; | ||
15373 | SXUNUSED(nArg); /* cc warning */ | ||
15374 | SXUNUSED(apArg); | ||
15375 | /* Extract the current time */ | ||
15376 | time(&tt); | ||
15377 | /* Return as 64-bit integer */ | ||
15378 | jx9_result_int64(pCtx, (jx9_int64)tt); | ||
15379 | return JX9_OK; | ||
15380 | } | ||
15381 | /* | ||
15382 | * string/float microtime([ bool $get_as_float = false ]) | ||
15383 | * microtime() returns the current Unix timestamp with microseconds. | ||
15384 | * Parameters | ||
15385 | * $get_as_float | ||
15386 | * If used and set to TRUE, microtime() will return a float instead of a string | ||
15387 | * as described in the return values section below. | ||
15388 | * Return | ||
15389 | * By default, microtime() returns a string in the form "msec sec", where sec | ||
15390 | * is the current time measured in the number of seconds since the Unix | ||
15391 | * epoch (0:00:00 January 1, 1970 GMT), and msec is the number of microseconds | ||
15392 | * that have elapsed since sec expressed in seconds. | ||
15393 | * If get_as_float is set to TRUE, then microtime() returns a float, which represents | ||
15394 | * the current time in seconds since the Unix epoch accurate to the nearest microsecond. | ||
15395 | */ | ||
15396 | static int jx9Builtin_microtime(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15397 | { | ||
15398 | int bFloat = 0; | ||
15399 | sytime sTime; | ||
15400 | #if defined(__UNIXES__) | ||
15401 | struct timeval tv; | ||
15402 | gettimeofday(&tv, 0); | ||
15403 | sTime.tm_sec = (long)tv.tv_sec; | ||
15404 | sTime.tm_usec = (long)tv.tv_usec; | ||
15405 | #else | ||
15406 | time_t tt; | ||
15407 | time(&tt); | ||
15408 | sTime.tm_sec = (long)tt; | ||
15409 | sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); | ||
15410 | #endif /* __UNIXES__ */ | ||
15411 | if( nArg > 0 ){ | ||
15412 | bFloat = jx9_value_to_bool(apArg[0]); | ||
15413 | } | ||
15414 | if( bFloat ){ | ||
15415 | /* Return as float */ | ||
15416 | jx9_result_double(pCtx, (double)sTime.tm_sec); | ||
15417 | }else{ | ||
15418 | /* Return as string */ | ||
15419 | jx9_result_string_format(pCtx, "%ld %ld", sTime.tm_usec, sTime.tm_sec); | ||
15420 | } | ||
15421 | return JX9_OK; | ||
15422 | } | ||
15423 | /* | ||
15424 | * array getdate ([ int $timestamp = time() ]) | ||
15425 | * Get date/time information. | ||
15426 | * Parameter | ||
15427 | * $timestamp: The optional timestamp parameter is an integer Unix timestamp | ||
15428 | * that defaults to the current local time if a timestamp is not given. | ||
15429 | * In other words, it defaults to the value of time(). | ||
15430 | * Returns | ||
15431 | * Returns an associative array of information related to the timestamp. | ||
15432 | * Elements from the returned associative array are as follows: | ||
15433 | * KEY VALUE | ||
15434 | * --------- ------- | ||
15435 | * "seconds" Numeric representation of seconds 0 to 59 | ||
15436 | * "minutes" Numeric representation of minutes 0 to 59 | ||
15437 | * "hours" Numeric representation of hours 0 to 23 | ||
15438 | * "mday" Numeric representation of the day of the month 1 to 31 | ||
15439 | * "wday" Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) | ||
15440 | * "mon" Numeric representation of a month 1 through 12 | ||
15441 | * "year" A full numeric representation of a year, 4 digits Examples: 1999 or 2003 | ||
15442 | * "yday" Numeric representation of the day of the year 0 through 365 | ||
15443 | * "weekday" A full textual representation of the day of the week Sunday through Saturday | ||
15444 | * "month" A full textual representation of a month, such as January or March January through December | ||
15445 | * 0 Seconds since the Unix Epoch, similar to the values returned by time() and used by date(). | ||
15446 | * NOTE: | ||
15447 | * NULL is returned on failure. | ||
15448 | */ | ||
15449 | static int jx9Builtin_getdate(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15450 | { | ||
15451 | jx9_value *pValue, *pArray; | ||
15452 | Sytm sTm; | ||
15453 | if( nArg < 1 ){ | ||
15454 | #ifdef __WINNT__ | ||
15455 | SYSTEMTIME sOS; | ||
15456 | GetSystemTime(&sOS); | ||
15457 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
15458 | #else | ||
15459 | struct tm *pTm; | ||
15460 | time_t t; | ||
15461 | time(&t); | ||
15462 | pTm = localtime(&t); | ||
15463 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
15464 | #endif | ||
15465 | }else{ | ||
15466 | /* Use the given timestamp */ | ||
15467 | time_t t; | ||
15468 | struct tm *pTm; | ||
15469 | #ifdef __WINNT__ | ||
15470 | #ifdef _MSC_VER | ||
15471 | #if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ | ||
15472 | #pragma warning(disable:4996) /* _CRT_SECURE...*/ | ||
15473 | #endif | ||
15474 | #endif | ||
15475 | #endif | ||
15476 | if( jx9_value_is_int(apArg[0]) ){ | ||
15477 | t = (time_t)jx9_value_to_int64(apArg[0]); | ||
15478 | pTm = localtime(&t); | ||
15479 | if( pTm == 0 ){ | ||
15480 | time(&t); | ||
15481 | } | ||
15482 | }else{ | ||
15483 | time(&t); | ||
15484 | } | ||
15485 | pTm = localtime(&t); | ||
15486 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
15487 | } | ||
15488 | /* Element value */ | ||
15489 | pValue = jx9_context_new_scalar(pCtx); | ||
15490 | if( pValue == 0 ){ | ||
15491 | /* Return NULL */ | ||
15492 | jx9_result_null(pCtx); | ||
15493 | return JX9_OK; | ||
15494 | } | ||
15495 | /* Create a new array */ | ||
15496 | pArray = jx9_context_new_array(pCtx); | ||
15497 | if( pArray == 0 ){ | ||
15498 | /* Return NULL */ | ||
15499 | jx9_result_null(pCtx); | ||
15500 | return JX9_OK; | ||
15501 | } | ||
15502 | /* Fill the array */ | ||
15503 | /* Seconds */ | ||
15504 | jx9_value_int(pValue, sTm.tm_sec); | ||
15505 | jx9_array_add_strkey_elem(pArray, "seconds", pValue); | ||
15506 | /* Minutes */ | ||
15507 | jx9_value_int(pValue, sTm.tm_min); | ||
15508 | jx9_array_add_strkey_elem(pArray, "minutes", pValue); | ||
15509 | /* Hours */ | ||
15510 | jx9_value_int(pValue, sTm.tm_hour); | ||
15511 | jx9_array_add_strkey_elem(pArray, "hours", pValue); | ||
15512 | /* mday */ | ||
15513 | jx9_value_int(pValue, sTm.tm_mday); | ||
15514 | jx9_array_add_strkey_elem(pArray, "mday", pValue); | ||
15515 | /* wday */ | ||
15516 | jx9_value_int(pValue, sTm.tm_wday); | ||
15517 | jx9_array_add_strkey_elem(pArray, "wday", pValue); | ||
15518 | /* mon */ | ||
15519 | jx9_value_int(pValue, sTm.tm_mon+1); | ||
15520 | jx9_array_add_strkey_elem(pArray, "mon", pValue); | ||
15521 | /* year */ | ||
15522 | jx9_value_int(pValue, sTm.tm_year); | ||
15523 | jx9_array_add_strkey_elem(pArray, "year", pValue); | ||
15524 | /* yday */ | ||
15525 | jx9_value_int(pValue, sTm.tm_yday); | ||
15526 | jx9_array_add_strkey_elem(pArray, "yday", pValue); | ||
15527 | /* Weekday */ | ||
15528 | jx9_value_string(pValue, SyTimeGetDay(sTm.tm_wday), -1); | ||
15529 | jx9_array_add_strkey_elem(pArray, "weekday", pValue); | ||
15530 | /* Month */ | ||
15531 | jx9_value_reset_string_cursor(pValue); | ||
15532 | jx9_value_string(pValue, SyTimeGetMonth(sTm.tm_mon), -1); | ||
15533 | jx9_array_add_strkey_elem(pArray, "month", pValue); | ||
15534 | /* Seconds since the epoch */ | ||
15535 | jx9_value_int64(pValue, (jx9_int64)time(0)); | ||
15536 | jx9_array_add_elem(pArray, 0 /* Index zero */, pValue); | ||
15537 | /* Return the freshly created array */ | ||
15538 | jx9_result_value(pCtx, pArray); | ||
15539 | return JX9_OK; | ||
15540 | } | ||
15541 | /* | ||
15542 | * mixed gettimeofday([ bool $return_float = false ] ) | ||
15543 | * Returns an associative array containing the data returned from the system call. | ||
15544 | * Parameters | ||
15545 | * $return_float | ||
15546 | * When set to TRUE, a float instead of an array is returned. | ||
15547 | * Return | ||
15548 | * By default an array is returned. If return_float is set, then | ||
15549 | * a float is returned. | ||
15550 | */ | ||
15551 | static int jx9Builtin_gettimeofday(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
15552 | { | ||
15553 | int bFloat = 0; | ||
15554 | sytime sTime; | ||
15555 | #if defined(__UNIXES__) | ||
15556 | struct timeval tv; | ||
15557 | gettimeofday(&tv, 0); | ||
15558 | sTime.tm_sec = (long)tv.tv_sec; | ||
15559 | sTime.tm_usec = (long)tv.tv_usec; | ||
15560 | #else | ||
15561 | time_t tt; | ||
15562 | time(&tt); | ||
15563 | sTime.tm_sec = (long)tt; | ||
15564 | sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); | ||
15565 | #endif /* __UNIXES__ */ | ||
15566 | if( nArg > 0 ){ | ||
15567 | bFloat = jx9_value_to_bool(apArg[0]); | ||
15568 | } | ||
15569 | if( bFloat ){ | ||
15570 | /* Return as float */ | ||
15571 | jx9_result_double(pCtx, (double)sTime.tm_sec); | ||
15572 | }else{ | ||
15573 | /* Return an associative array */ | ||
15574 | jx9_value *pValue, *pArray; | ||
15575 | /* Create a new array */ | ||
15576 | pArray = jx9_context_new_array(pCtx); | ||
15577 | /* Element value */ | ||
15578 | pValue = jx9_context_new_scalar(pCtx); | ||
15579 | if( pValue == 0 || pArray == 0 ){ | ||
15580 | /* Return NULL */ | ||
15581 | jx9_result_null(pCtx); | ||
15582 | return JX9_OK; | ||
15583 | } | ||
15584 | /* Fill the array */ | ||
15585 | /* sec */ | ||
15586 | jx9_value_int64(pValue, sTime.tm_sec); | ||
15587 | jx9_array_add_strkey_elem(pArray, "sec", pValue); | ||
15588 | /* usec */ | ||
15589 | jx9_value_int64(pValue, sTime.tm_usec); | ||
15590 | jx9_array_add_strkey_elem(pArray, "usec", pValue); | ||
15591 | /* Return the array */ | ||
15592 | jx9_result_value(pCtx, pArray); | ||
15593 | } | ||
15594 | return JX9_OK; | ||
15595 | } | ||
15596 | /* Check if the given year is leap or not */ | ||
15597 | #define IS_LEAP_YEAR(YEAR) (YEAR % 400 ? ( YEAR % 100 ? ( YEAR % 4 ? 0 : 1 ) : 0 ) : 1) | ||
15598 | /* ISO-8601 numeric representation of the day of the week */ | ||
15599 | static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 }; | ||
15600 | /* | ||
15601 | * Format a given date string. | ||
15602 | * Supported format: (Taken from JX9 online docs) | ||
15603 | * character Description | ||
15604 | * d Day of the month | ||
15605 | * D A textual representation of a days | ||
15606 | * j Day of the month without leading zeros | ||
15607 | * l A full textual representation of the day of the week | ||
15608 | * N ISO-8601 numeric representation of the day of the week | ||
15609 | * w Numeric representation of the day of the week | ||
15610 | * z The day of the year (starting from 0) | ||
15611 | * F A full textual representation of a month, such as January or March | ||
15612 | * m Numeric representation of a month, with leading zeros 01 through 12 | ||
15613 | * M A short textual representation of a month, three letters Jan through Dec | ||
15614 | * n Numeric representation of a month, without leading zeros 1 through 12 | ||
15615 | * t Number of days in the given month 28 through 31 | ||
15616 | * L Whether it's a leap year 1 if it is a leap year, 0 otherwise. | ||
15617 | * o ISO-8601 year number. This has the same value as Y, except that if the ISO week number | ||
15618 | * (W) belongs to the previous or next year, that year is used instead. (added in JX9 5.1.0) Examples: 1999 or 2003 | ||
15619 | * Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003 | ||
15620 | * y A two digit representation of a year Examples: 99 or 03 | ||
15621 | * a Lowercase Ante meridiem and Post meridiem am or pm | ||
15622 | * A Uppercase Ante meridiem and Post meridiem AM or PM | ||
15623 | * g 12-hour format of an hour without leading zeros 1 through 12 | ||
15624 | * G 24-hour format of an hour without leading zeros 0 through 23 | ||
15625 | * h 12-hour format of an hour with leading zeros 01 through 12 | ||
15626 | * H 24-hour format of an hour with leading zeros 00 through 23 | ||
15627 | * i Minutes with leading zeros 00 to 59 | ||
15628 | * s Seconds, with leading zeros 00 through 59 | ||
15629 | * u Microseconds Example: 654321 | ||
15630 | * e Timezone identifier Examples: UTC, GMT, Atlantic/Azores | ||
15631 | * I (capital i) Whether or not the date is in daylight saving time 1 if Daylight Saving Time, 0 otherwise. | ||
15632 | * r RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 +0200 | ||
15633 | * U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) | ||
15634 | * S English ordinal suffix for the day of the month, 2 characters | ||
15635 | * O Difference to Greenwich time (GMT) in hours | ||
15636 | * Z Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those | ||
15637 | * east of UTC is always positive. | ||
15638 | * c ISO 8601 date | ||
15639 | */ | ||
15640 | static sxi32 DateFormat(jx9_context *pCtx, const char *zIn, int nLen, Sytm *pTm) | ||
15641 | { | ||
15642 | const char *zEnd = &zIn[nLen]; | ||
15643 | const char *zCur; | ||
15644 | /* Start the format process */ | ||
15645 | for(;;){ | ||
15646 | if( zIn >= zEnd ){ | ||
15647 | /* No more input to process */ | ||
15648 | break; | ||
15649 | } | ||
15650 | switch(zIn[0]){ | ||
15651 | case 'd': | ||
15652 | /* Day of the month, 2 digits with leading zeros */ | ||
15653 | jx9_result_string_format(pCtx, "%02d", pTm->tm_mday); | ||
15654 | break; | ||
15655 | case 'D': | ||
15656 | /*A textual representation of a day, three letters*/ | ||
15657 | zCur = SyTimeGetDay(pTm->tm_wday); | ||
15658 | jx9_result_string(pCtx, zCur, 3); | ||
15659 | break; | ||
15660 | case 'j': | ||
15661 | /* Day of the month without leading zeros */ | ||
15662 | jx9_result_string_format(pCtx, "%d", pTm->tm_mday); | ||
15663 | break; | ||
15664 | case 'l': | ||
15665 | /* A full textual representation of the day of the week */ | ||
15666 | zCur = SyTimeGetDay(pTm->tm_wday); | ||
15667 | jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/); | ||
15668 | break; | ||
15669 | case 'N':{ | ||
15670 | /* ISO-8601 numeric representation of the day of the week */ | ||
15671 | jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]); | ||
15672 | break; | ||
15673 | } | ||
15674 | case 'w': | ||
15675 | /*Numeric representation of the day of the week*/ | ||
15676 | jx9_result_string_format(pCtx, "%d", pTm->tm_wday); | ||
15677 | break; | ||
15678 | case 'z': | ||
15679 | /*The day of the year*/ | ||
15680 | jx9_result_string_format(pCtx, "%d", pTm->tm_yday); | ||
15681 | break; | ||
15682 | case 'F': | ||
15683 | /*A full textual representation of a month, such as January or March*/ | ||
15684 | zCur = SyTimeGetMonth(pTm->tm_mon); | ||
15685 | jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/); | ||
15686 | break; | ||
15687 | case 'm': | ||
15688 | /*Numeric representation of a month, with leading zeros*/ | ||
15689 | jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1); | ||
15690 | break; | ||
15691 | case 'M': | ||
15692 | /*A short textual representation of a month, three letters*/ | ||
15693 | zCur = SyTimeGetMonth(pTm->tm_mon); | ||
15694 | jx9_result_string(pCtx, zCur, 3); | ||
15695 | break; | ||
15696 | case 'n': | ||
15697 | /*Numeric representation of a month, without leading zeros*/ | ||
15698 | jx9_result_string_format(pCtx, "%d", pTm->tm_mon + 1); | ||
15699 | break; | ||
15700 | case 't':{ | ||
15701 | static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; | ||
15702 | int nDays = aMonDays[pTm->tm_mon % 12 ]; | ||
15703 | if( pTm->tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(pTm->tm_year) ){ | ||
15704 | nDays = 28; | ||
15705 | } | ||
15706 | /*Number of days in the given month*/ | ||
15707 | jx9_result_string_format(pCtx, "%d", nDays); | ||
15708 | break; | ||
15709 | } | ||
15710 | case 'L':{ | ||
15711 | int isLeap = IS_LEAP_YEAR(pTm->tm_year); | ||
15712 | /* Whether it's a leap year */ | ||
15713 | jx9_result_string_format(pCtx, "%d", isLeap); | ||
15714 | break; | ||
15715 | } | ||
15716 | case 'o': | ||
15717 | /* ISO-8601 year number.*/ | ||
15718 | jx9_result_string_format(pCtx, "%4d", pTm->tm_year); | ||
15719 | break; | ||
15720 | case 'Y': | ||
15721 | /* A full numeric representation of a year, 4 digits */ | ||
15722 | jx9_result_string_format(pCtx, "%4d", pTm->tm_year); | ||
15723 | break; | ||
15724 | case 'y': | ||
15725 | /*A two digit representation of a year*/ | ||
15726 | jx9_result_string_format(pCtx, "%02d", pTm->tm_year%100); | ||
15727 | break; | ||
15728 | case 'a': | ||
15729 | /* Lowercase Ante meridiem and Post meridiem */ | ||
15730 | jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", 2); | ||
15731 | break; | ||
15732 | case 'A': | ||
15733 | /* Uppercase Ante meridiem and Post meridiem */ | ||
15734 | jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", 2); | ||
15735 | break; | ||
15736 | case 'g': | ||
15737 | /* 12-hour format of an hour without leading zeros*/ | ||
15738 | jx9_result_string_format(pCtx, "%d", 1+(pTm->tm_hour%12)); | ||
15739 | break; | ||
15740 | case 'G': | ||
15741 | /* 24-hour format of an hour without leading zeros */ | ||
15742 | jx9_result_string_format(pCtx, "%d", pTm->tm_hour); | ||
15743 | break; | ||
15744 | case 'h': | ||
15745 | /* 12-hour format of an hour with leading zeros */ | ||
15746 | jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12)); | ||
15747 | break; | ||
15748 | case 'H': | ||
15749 | /* 24-hour format of an hour with leading zeros */ | ||
15750 | jx9_result_string_format(pCtx, "%02d", pTm->tm_hour); | ||
15751 | break; | ||
15752 | case 'i': | ||
15753 | /* Minutes with leading zeros */ | ||
15754 | jx9_result_string_format(pCtx, "%02d", pTm->tm_min); | ||
15755 | break; | ||
15756 | case 's': | ||
15757 | /* second with leading zeros */ | ||
15758 | jx9_result_string_format(pCtx, "%02d", pTm->tm_sec); | ||
15759 | break; | ||
15760 | case 'u': | ||
15761 | /* Microseconds */ | ||
15762 | jx9_result_string_format(pCtx, "%u", pTm->tm_sec * SX_USEC_PER_SEC); | ||
15763 | break; | ||
15764 | case 'S':{ | ||
15765 | /* English ordinal suffix for the day of the month, 2 characters */ | ||
15766 | static const char zSuffix[] = "thstndrdthththththth"; | ||
15767 | int v = pTm->tm_mday; | ||
15768 | jx9_result_string(pCtx, &zSuffix[2 * (int)(v / 10 % 10 != 1 ? v % 10 : 0)], (int)sizeof(char) * 2); | ||
15769 | break; | ||
15770 | } | ||
15771 | case 'e': | ||
15772 | /* Timezone identifier */ | ||
15773 | zCur = pTm->tm_zone; | ||
15774 | if( zCur == 0 ){ | ||
15775 | /* Assume GMT */ | ||
15776 | zCur = "GMT"; | ||
15777 | } | ||
15778 | jx9_result_string(pCtx, zCur, -1); | ||
15779 | break; | ||
15780 | case 'I': | ||
15781 | /* Whether or not the date is in daylight saving time */ | ||
15782 | #ifdef __WINNT__ | ||
15783 | #ifdef _MSC_VER | ||
15784 | #ifndef _WIN32_WCE | ||
15785 | _get_daylight(&pTm->tm_isdst); | ||
15786 | #endif | ||
15787 | #endif | ||
15788 | #endif | ||
15789 | jx9_result_string_format(pCtx, "%d", pTm->tm_isdst == 1); | ||
15790 | break; | ||
15791 | case 'r': | ||
15792 | /* RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 */ | ||
15793 | jx9_result_string_format(pCtx, "%.3s, %02d %.3s %4d %02d:%02d:%02d", | ||
15794 | SyTimeGetDay(pTm->tm_wday), | ||
15795 | pTm->tm_mday, | ||
15796 | SyTimeGetMonth(pTm->tm_mon), | ||
15797 | pTm->tm_year, | ||
15798 | pTm->tm_hour, | ||
15799 | pTm->tm_min, | ||
15800 | pTm->tm_sec | ||
15801 | ); | ||
15802 | break; | ||
15803 | case 'U':{ | ||
15804 | time_t tt; | ||
15805 | /* Seconds since the Unix Epoch */ | ||
15806 | time(&tt); | ||
15807 | jx9_result_string_format(pCtx, "%u", (unsigned int)tt); | ||
15808 | break; | ||
15809 | } | ||
15810 | case 'O': | ||
15811 | case 'P': | ||
15812 | /* Difference to Greenwich time (GMT) in hours */ | ||
15813 | jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff); | ||
15814 | break; | ||
15815 | case 'Z': | ||
15816 | /* Timezone offset in seconds. The offset for timezones west of UTC | ||
15817 | * is always negative, and for those east of UTC is always positive. | ||
15818 | */ | ||
15819 | jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff); | ||
15820 | break; | ||
15821 | case 'c': | ||
15822 | /* ISO 8601 date */ | ||
15823 | jx9_result_string_format(pCtx, "%4d-%02d-%02dT%02d:%02d:%02d%+05d", | ||
15824 | pTm->tm_year, | ||
15825 | pTm->tm_mon+1, | ||
15826 | pTm->tm_mday, | ||
15827 | pTm->tm_hour, | ||
15828 | pTm->tm_min, | ||
15829 | pTm->tm_sec, | ||
15830 | pTm->tm_gmtoff | ||
15831 | ); | ||
15832 | break; | ||
15833 | case '\\': | ||
15834 | zIn++; | ||
15835 | /* Expand verbatim */ | ||
15836 | if( zIn < zEnd ){ | ||
15837 | jx9_result_string(pCtx, zIn, (int)sizeof(char)); | ||
15838 | } | ||
15839 | break; | ||
15840 | default: | ||
15841 | /* Unknown format specifer, expand verbatim */ | ||
15842 | jx9_result_string(pCtx, zIn, (int)sizeof(char)); | ||
15843 | break; | ||
15844 | } | ||
15845 | /* Point to the next character */ | ||
15846 | zIn++; | ||
15847 | } | ||
15848 | return SXRET_OK; | ||
15849 | } | ||
15850 | /* | ||
15851 | * JX9 implementation of the strftime() function. | ||
15852 | * The following formats are supported: | ||
15853 | * %a An abbreviated textual representation of the day | ||
15854 | * %A A full textual representation of the day | ||
15855 | * %d Two-digit day of the month (with leading zeros) | ||
15856 | * %e Day of the month, with a space preceding single digits. | ||
15857 | * %j Day of the year, 3 digits with leading zeros | ||
15858 | * %u ISO-8601 numeric representation of the day of the week 1 (for Monday) though 7 (for Sunday) | ||
15859 | * %w Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) | ||
15860 | * %U Week number of the given year, starting with the first Sunday as the first week | ||
15861 | * %V ISO-8601:1988 week number of the given year, starting with the first week of the year with at least | ||
15862 | * 4 weekdays, with Monday being the start of the week. | ||
15863 | * %W A numeric representation of the week of the year | ||
15864 | * %b Abbreviated month name, based on the locale | ||
15865 | * %B Full month name, based on the locale | ||
15866 | * %h Abbreviated month name, based on the locale (an alias of %b) | ||
15867 | * %m Two digit representation of the month | ||
15868 | * %C Two digit representation of the century (year divided by 100, truncated to an integer) | ||
15869 | * %g Two digit representation of the year going by ISO-8601:1988 standards (see %V) | ||
15870 | * %G The full four-digit version of %g | ||
15871 | * %y Two digit representation of the year | ||
15872 | * %Y Four digit representation for the year | ||
15873 | * %H Two digit representation of the hour in 24-hour format | ||
15874 | * %I Two digit representation of the hour in 12-hour format | ||
15875 | * %l (lower-case 'L') Hour in 12-hour format, with a space preceeding single digits | ||
15876 | * %M Two digit representation of the minute | ||
15877 | * %p UPPER-CASE 'AM' or 'PM' based on the given time | ||
15878 | * %P lower-case 'am' or 'pm' based on the given time | ||
15879 | * %r Same as "%I:%M:%S %p" | ||
15880 | * %R Same as "%H:%M" | ||
15881 | * %S Two digit representation of the second | ||
15882 | * %T Same as "%H:%M:%S" | ||
15883 | * %X Preferred time representation based on locale, without the date | ||
15884 | * %z Either the time zone offset from UTC or the abbreviation | ||
15885 | * %Z The time zone offset/abbreviation option NOT given by %z | ||
15886 | * %c Preferred date and time stamp based on local | ||
15887 | * %D Same as "%m/%d/%y" | ||
15888 | * %F Same as "%Y-%m-%d" | ||
15889 | * %s Unix Epoch Time timestamp (same as the time() function) | ||
15890 | * %x Preferred date representation based on locale, without the time | ||
15891 | * %n A newline character ("\n") | ||
15892 | * %t A Tab character ("\t") | ||
15893 | * %% A literal percentage character ("%") | ||
15894 | */ | ||
15895 | static int jx9Strftime( | ||
15896 | jx9_context *pCtx, /* Call context */ | ||
15897 | const char *zIn, /* Input string */ | ||
15898 | int nLen, /* Input length */ | ||
15899 | Sytm *pTm /* Parse of the given time */ | ||
15900 | ) | ||
15901 | { | ||
15902 | const char *zCur, *zEnd = &zIn[nLen]; | ||
15903 | int c; | ||
15904 | /* Start the format process */ | ||
15905 | for(;;){ | ||
15906 | zCur = zIn; | ||
15907 | while(zIn < zEnd && zIn[0] != '%' ){ | ||
15908 | zIn++; | ||
15909 | } | ||
15910 | if( zIn > zCur ){ | ||
15911 | /* Consume input verbatim */ | ||
15912 | jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); | ||
15913 | } | ||
15914 | zIn++; /* Jump the percent sign */ | ||
15915 | if( zIn >= zEnd ){ | ||
15916 | /* No more input to process */ | ||
15917 | break; | ||
15918 | } | ||
15919 | c = zIn[0]; | ||
15920 | /* Act according to the current specifer */ | ||
15921 | switch(c){ | ||
15922 | case '%': | ||
15923 | /* A literal percentage character ("%") */ | ||
15924 | jx9_result_string(pCtx, "%", (int)sizeof(char)); | ||
15925 | break; | ||
15926 | case 't': | ||
15927 | /* A Tab character */ | ||
15928 | jx9_result_string(pCtx, "\t", (int)sizeof(char)); | ||
15929 | break; | ||
15930 | case 'n': | ||
15931 | /* A newline character */ | ||
15932 | jx9_result_string(pCtx, "\n", (int)sizeof(char)); | ||
15933 | break; | ||
15934 | case 'a': | ||
15935 | /* An abbreviated textual representation of the day */ | ||
15936 | jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), (int)sizeof(char)*3); | ||
15937 | break; | ||
15938 | case 'A': | ||
15939 | /* A full textual representation of the day */ | ||
15940 | jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), -1/*Compute length automatically*/); | ||
15941 | break; | ||
15942 | case 'e': | ||
15943 | /* Day of the month, 2 digits with leading space for single digit*/ | ||
15944 | jx9_result_string_format(pCtx, "%2d", pTm->tm_mday); | ||
15945 | break; | ||
15946 | case 'd': | ||
15947 | /* Two-digit day of the month (with leading zeros) */ | ||
15948 | jx9_result_string_format(pCtx, "%02d", pTm->tm_mon+1); | ||
15949 | break; | ||
15950 | case 'j': | ||
15951 | /*The day of the year, 3 digits with leading zeros*/ | ||
15952 | jx9_result_string_format(pCtx, "%03d", pTm->tm_yday); | ||
15953 | break; | ||
15954 | case 'u': | ||
15955 | /* ISO-8601 numeric representation of the day of the week */ | ||
15956 | jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]); | ||
15957 | break; | ||
15958 | case 'w': | ||
15959 | /* Numeric representation of the day of the week */ | ||
15960 | jx9_result_string_format(pCtx, "%d", pTm->tm_wday); | ||
15961 | break; | ||
15962 | case 'b': | ||
15963 | case 'h': | ||
15964 | /*A short textual representation of a month, three letters (Not based on locale)*/ | ||
15965 | jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), (int)sizeof(char)*3); | ||
15966 | break; | ||
15967 | case 'B': | ||
15968 | /* Full month name (Not based on locale) */ | ||
15969 | jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), -1/*Compute length automatically*/); | ||
15970 | break; | ||
15971 | case 'm': | ||
15972 | /*Numeric representation of a month, with leading zeros*/ | ||
15973 | jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1); | ||
15974 | break; | ||
15975 | case 'C': | ||
15976 | /* Two digit representation of the century */ | ||
15977 | jx9_result_string_format(pCtx, "%2d", pTm->tm_year/100); | ||
15978 | break; | ||
15979 | case 'y': | ||
15980 | case 'g': | ||
15981 | /* Two digit representation of the year */ | ||
15982 | jx9_result_string_format(pCtx, "%2d", pTm->tm_year%100); | ||
15983 | break; | ||
15984 | case 'Y': | ||
15985 | case 'G': | ||
15986 | /* Four digit representation of the year */ | ||
15987 | jx9_result_string_format(pCtx, "%4d", pTm->tm_year); | ||
15988 | break; | ||
15989 | case 'I': | ||
15990 | /* 12-hour format of an hour with leading zeros */ | ||
15991 | jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12)); | ||
15992 | break; | ||
15993 | case 'l': | ||
15994 | /* 12-hour format of an hour with leading space */ | ||
15995 | jx9_result_string_format(pCtx, "%2d", 1+(pTm->tm_hour%12)); | ||
15996 | break; | ||
15997 | case 'H': | ||
15998 | /* 24-hour format of an hour with leading zeros */ | ||
15999 | jx9_result_string_format(pCtx, "%02d", pTm->tm_hour); | ||
16000 | break; | ||
16001 | case 'M': | ||
16002 | /* Minutes with leading zeros */ | ||
16003 | jx9_result_string_format(pCtx, "%02d", pTm->tm_min); | ||
16004 | break; | ||
16005 | case 'S': | ||
16006 | /* Seconds with leading zeros */ | ||
16007 | jx9_result_string_format(pCtx, "%02d", pTm->tm_sec); | ||
16008 | break; | ||
16009 | case 'z': | ||
16010 | case 'Z': | ||
16011 | /* Timezone identifier */ | ||
16012 | zCur = pTm->tm_zone; | ||
16013 | if( zCur == 0 ){ | ||
16014 | /* Assume GMT */ | ||
16015 | zCur = "GMT"; | ||
16016 | } | ||
16017 | jx9_result_string(pCtx, zCur, -1); | ||
16018 | break; | ||
16019 | case 'T': | ||
16020 | case 'X': | ||
16021 | /* Same as "%H:%M:%S" */ | ||
16022 | jx9_result_string_format(pCtx, "%02d:%02d:%02d", pTm->tm_hour, pTm->tm_min, pTm->tm_sec); | ||
16023 | break; | ||
16024 | case 'R': | ||
16025 | /* Same as "%H:%M" */ | ||
16026 | jx9_result_string_format(pCtx, "%02d:%02d", pTm->tm_hour, pTm->tm_min); | ||
16027 | break; | ||
16028 | case 'P': | ||
16029 | /* Lowercase Ante meridiem and Post meridiem */ | ||
16030 | jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", (int)sizeof(char)*2); | ||
16031 | break; | ||
16032 | case 'p': | ||
16033 | /* Uppercase Ante meridiem and Post meridiem */ | ||
16034 | jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", (int)sizeof(char)*2); | ||
16035 | break; | ||
16036 | case 'r': | ||
16037 | /* Same as "%I:%M:%S %p" */ | ||
16038 | jx9_result_string_format(pCtx, "%02d:%02d:%02d %s", | ||
16039 | 1+(pTm->tm_hour%12), | ||
16040 | pTm->tm_min, | ||
16041 | pTm->tm_sec, | ||
16042 | pTm->tm_hour > 12 ? "PM" : "AM" | ||
16043 | ); | ||
16044 | break; | ||
16045 | case 'D': | ||
16046 | case 'x': | ||
16047 | /* Same as "%m/%d/%y" */ | ||
16048 | jx9_result_string_format(pCtx, "%02d/%02d/%02d", | ||
16049 | pTm->tm_mon+1, | ||
16050 | pTm->tm_mday, | ||
16051 | pTm->tm_year%100 | ||
16052 | ); | ||
16053 | break; | ||
16054 | case 'F': | ||
16055 | /* Same as "%Y-%m-%d" */ | ||
16056 | jx9_result_string_format(pCtx, "%d-%02d-%02d", | ||
16057 | pTm->tm_year, | ||
16058 | pTm->tm_mon+1, | ||
16059 | pTm->tm_mday | ||
16060 | ); | ||
16061 | break; | ||
16062 | case 'c': | ||
16063 | jx9_result_string_format(pCtx, "%d-%02d-%02d %02d:%02d:%02d", | ||
16064 | pTm->tm_year, | ||
16065 | pTm->tm_mon+1, | ||
16066 | pTm->tm_mday, | ||
16067 | pTm->tm_hour, | ||
16068 | pTm->tm_min, | ||
16069 | pTm->tm_sec | ||
16070 | ); | ||
16071 | break; | ||
16072 | case 's':{ | ||
16073 | time_t tt; | ||
16074 | /* Seconds since the Unix Epoch */ | ||
16075 | time(&tt); | ||
16076 | jx9_result_string_format(pCtx, "%u", (unsigned int)tt); | ||
16077 | break; | ||
16078 | } | ||
16079 | default: | ||
16080 | /* unknown specifer, simply ignore*/ | ||
16081 | break; | ||
16082 | } | ||
16083 | /* Advance the cursor */ | ||
16084 | zIn++; | ||
16085 | } | ||
16086 | return SXRET_OK; | ||
16087 | } | ||
16088 | /* | ||
16089 | * string date(string $format [, int $timestamp = time() ] ) | ||
16090 | * Returns a string formatted according to the given format string using | ||
16091 | * the given integer timestamp or the current time if no timestamp is given. | ||
16092 | * In other words, timestamp is optional and defaults to the value of time(). | ||
16093 | * Parameters | ||
16094 | * $format | ||
16095 | * The format of the outputted date string (See code above) | ||
16096 | * $timestamp | ||
16097 | * The optional timestamp parameter is an integer Unix timestamp | ||
16098 | * that defaults to the current local time if a timestamp is not given. | ||
16099 | * In other words, it defaults to the value of time(). | ||
16100 | * Return | ||
16101 | * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. | ||
16102 | */ | ||
16103 | static int jx9Builtin_date(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16104 | { | ||
16105 | const char *zFormat; | ||
16106 | int nLen; | ||
16107 | Sytm sTm; | ||
16108 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
16109 | /* Missing/Invalid argument, return FALSE */ | ||
16110 | jx9_result_bool(pCtx, 0); | ||
16111 | return JX9_OK; | ||
16112 | } | ||
16113 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
16114 | if( nLen < 1 ){ | ||
16115 | /* Don't bother processing return the empty string */ | ||
16116 | jx9_result_string(pCtx, "", 0); | ||
16117 | } | ||
16118 | if( nArg < 2 ){ | ||
16119 | #ifdef __WINNT__ | ||
16120 | SYSTEMTIME sOS; | ||
16121 | GetSystemTime(&sOS); | ||
16122 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
16123 | #else | ||
16124 | struct tm *pTm; | ||
16125 | time_t t; | ||
16126 | time(&t); | ||
16127 | pTm = localtime(&t); | ||
16128 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16129 | #endif | ||
16130 | }else{ | ||
16131 | /* Use the given timestamp */ | ||
16132 | time_t t; | ||
16133 | struct tm *pTm; | ||
16134 | if( jx9_value_is_int(apArg[1]) ){ | ||
16135 | t = (time_t)jx9_value_to_int64(apArg[1]); | ||
16136 | pTm = localtime(&t); | ||
16137 | if( pTm == 0 ){ | ||
16138 | time(&t); | ||
16139 | } | ||
16140 | }else{ | ||
16141 | time(&t); | ||
16142 | } | ||
16143 | pTm = localtime(&t); | ||
16144 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16145 | } | ||
16146 | /* Format the given string */ | ||
16147 | DateFormat(pCtx, zFormat, nLen, &sTm); | ||
16148 | return JX9_OK; | ||
16149 | } | ||
16150 | /* | ||
16151 | * string strftime(string $format [, int $timestamp = time() ] ) | ||
16152 | * Format a local time/date (PLATFORM INDEPENDANT IMPLEENTATION NOT BASED ON LOCALE) | ||
16153 | * Parameters | ||
16154 | * $format | ||
16155 | * The format of the outputted date string (See code above) | ||
16156 | * $timestamp | ||
16157 | * The optional timestamp parameter is an integer Unix timestamp | ||
16158 | * that defaults to the current local time if a timestamp is not given. | ||
16159 | * In other words, it defaults to the value of time(). | ||
16160 | * Return | ||
16161 | * Returns a string formatted according format using the given timestamp | ||
16162 | * or the current local time if no timestamp is given. | ||
16163 | */ | ||
16164 | static int jx9Builtin_strftime(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16165 | { | ||
16166 | const char *zFormat; | ||
16167 | int nLen; | ||
16168 | Sytm sTm; | ||
16169 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
16170 | /* Missing/Invalid argument, return FALSE */ | ||
16171 | jx9_result_bool(pCtx, 0); | ||
16172 | return JX9_OK; | ||
16173 | } | ||
16174 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
16175 | if( nLen < 1 ){ | ||
16176 | /* Don't bother processing return FALSE */ | ||
16177 | jx9_result_bool(pCtx, 0); | ||
16178 | } | ||
16179 | if( nArg < 2 ){ | ||
16180 | #ifdef __WINNT__ | ||
16181 | SYSTEMTIME sOS; | ||
16182 | GetSystemTime(&sOS); | ||
16183 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
16184 | #else | ||
16185 | struct tm *pTm; | ||
16186 | time_t t; | ||
16187 | time(&t); | ||
16188 | pTm = localtime(&t); | ||
16189 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16190 | #endif | ||
16191 | }else{ | ||
16192 | /* Use the given timestamp */ | ||
16193 | time_t t; | ||
16194 | struct tm *pTm; | ||
16195 | if( jx9_value_is_int(apArg[1]) ){ | ||
16196 | t = (time_t)jx9_value_to_int64(apArg[1]); | ||
16197 | pTm = localtime(&t); | ||
16198 | if( pTm == 0 ){ | ||
16199 | time(&t); | ||
16200 | } | ||
16201 | }else{ | ||
16202 | time(&t); | ||
16203 | } | ||
16204 | pTm = localtime(&t); | ||
16205 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16206 | } | ||
16207 | /* Format the given string */ | ||
16208 | jx9Strftime(pCtx, zFormat, nLen, &sTm); | ||
16209 | if( jx9_context_result_buf_length(pCtx) < 1 ){ | ||
16210 | /* Nothing was formatted, return FALSE */ | ||
16211 | jx9_result_bool(pCtx, 0); | ||
16212 | } | ||
16213 | return JX9_OK; | ||
16214 | } | ||
16215 | /* | ||
16216 | * string gmdate(string $format [, int $timestamp = time() ] ) | ||
16217 | * Identical to the date() function except that the time returned | ||
16218 | * is Greenwich Mean Time (GMT). | ||
16219 | * Parameters | ||
16220 | * $format | ||
16221 | * The format of the outputted date string (See code above) | ||
16222 | * $timestamp | ||
16223 | * The optional timestamp parameter is an integer Unix timestamp | ||
16224 | * that defaults to the current local time if a timestamp is not given. | ||
16225 | * In other words, it defaults to the value of time(). | ||
16226 | * Return | ||
16227 | * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. | ||
16228 | */ | ||
16229 | static int jx9Builtin_gmdate(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16230 | { | ||
16231 | const char *zFormat; | ||
16232 | int nLen; | ||
16233 | Sytm sTm; | ||
16234 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
16235 | /* Missing/Invalid argument, return FALSE */ | ||
16236 | jx9_result_bool(pCtx, 0); | ||
16237 | return JX9_OK; | ||
16238 | } | ||
16239 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
16240 | if( nLen < 1 ){ | ||
16241 | /* Don't bother processing return the empty string */ | ||
16242 | jx9_result_string(pCtx, "", 0); | ||
16243 | } | ||
16244 | if( nArg < 2 ){ | ||
16245 | #ifdef __WINNT__ | ||
16246 | SYSTEMTIME sOS; | ||
16247 | GetSystemTime(&sOS); | ||
16248 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
16249 | #else | ||
16250 | struct tm *pTm; | ||
16251 | time_t t; | ||
16252 | time(&t); | ||
16253 | pTm = gmtime(&t); | ||
16254 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16255 | #endif | ||
16256 | }else{ | ||
16257 | /* Use the given timestamp */ | ||
16258 | time_t t; | ||
16259 | struct tm *pTm; | ||
16260 | if( jx9_value_is_int(apArg[1]) ){ | ||
16261 | t = (time_t)jx9_value_to_int64(apArg[1]); | ||
16262 | pTm = gmtime(&t); | ||
16263 | if( pTm == 0 ){ | ||
16264 | time(&t); | ||
16265 | } | ||
16266 | }else{ | ||
16267 | time(&t); | ||
16268 | } | ||
16269 | pTm = gmtime(&t); | ||
16270 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16271 | } | ||
16272 | /* Format the given string */ | ||
16273 | DateFormat(pCtx, zFormat, nLen, &sTm); | ||
16274 | return JX9_OK; | ||
16275 | } | ||
16276 | /* | ||
16277 | * array localtime([ int $timestamp = time() [, bool $is_associative = false ]]) | ||
16278 | * Return the local time. | ||
16279 | * Parameter | ||
16280 | * $timestamp: The optional timestamp parameter is an integer Unix timestamp | ||
16281 | * that defaults to the current local time if a timestamp is not given. | ||
16282 | * In other words, it defaults to the value of time(). | ||
16283 | * $is_associative | ||
16284 | * If set to FALSE or not supplied then the array is returned as a regular, numerically | ||
16285 | * indexed array. If the argument is set to TRUE then localtime() returns an associative | ||
16286 | * array containing all the different elements of the structure returned by the C function | ||
16287 | * call to localtime. The names of the different keys of the associative array are as follows: | ||
16288 | * "tm_sec" - seconds, 0 to 59 | ||
16289 | * "tm_min" - minutes, 0 to 59 | ||
16290 | * "tm_hour" - hours, 0 to 23 | ||
16291 | * "tm_mday" - day of the month, 1 to 31 | ||
16292 | * "tm_mon" - month of the year, 0 (Jan) to 11 (Dec) | ||
16293 | * "tm_year" - years since 1900 | ||
16294 | * "tm_wday" - day of the week, 0 (Sun) to 6 (Sat) | ||
16295 | * "tm_yday" - day of the year, 0 to 365 | ||
16296 | * "tm_isdst" - is daylight savings time in effect? Positive if yes, 0 if not, negative if unknown. | ||
16297 | * Returns | ||
16298 | * An associative array of information related to the timestamp. | ||
16299 | */ | ||
16300 | static int jx9Builtin_localtime(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16301 | { | ||
16302 | jx9_value *pValue, *pArray; | ||
16303 | int isAssoc = 0; | ||
16304 | Sytm sTm; | ||
16305 | if( nArg < 1 ){ | ||
16306 | #ifdef __WINNT__ | ||
16307 | SYSTEMTIME sOS; | ||
16308 | GetSystemTime(&sOS); /* TODO(chems): GMT not local */ | ||
16309 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
16310 | #else | ||
16311 | struct tm *pTm; | ||
16312 | time_t t; | ||
16313 | time(&t); | ||
16314 | pTm = localtime(&t); | ||
16315 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16316 | #endif | ||
16317 | }else{ | ||
16318 | /* Use the given timestamp */ | ||
16319 | time_t t; | ||
16320 | struct tm *pTm; | ||
16321 | if( jx9_value_is_int(apArg[0]) ){ | ||
16322 | t = (time_t)jx9_value_to_int64(apArg[0]); | ||
16323 | pTm = localtime(&t); | ||
16324 | if( pTm == 0 ){ | ||
16325 | time(&t); | ||
16326 | } | ||
16327 | }else{ | ||
16328 | time(&t); | ||
16329 | } | ||
16330 | pTm = localtime(&t); | ||
16331 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16332 | } | ||
16333 | /* Element value */ | ||
16334 | pValue = jx9_context_new_scalar(pCtx); | ||
16335 | if( pValue == 0 ){ | ||
16336 | /* Return NULL */ | ||
16337 | jx9_result_null(pCtx); | ||
16338 | return JX9_OK; | ||
16339 | } | ||
16340 | /* Create a new array */ | ||
16341 | pArray = jx9_context_new_array(pCtx); | ||
16342 | if( pArray == 0 ){ | ||
16343 | /* Return NULL */ | ||
16344 | jx9_result_null(pCtx); | ||
16345 | return JX9_OK; | ||
16346 | } | ||
16347 | if( nArg > 1 ){ | ||
16348 | isAssoc = jx9_value_to_bool(apArg[1]); | ||
16349 | } | ||
16350 | /* Fill the array */ | ||
16351 | /* Seconds */ | ||
16352 | jx9_value_int(pValue, sTm.tm_sec); | ||
16353 | if( isAssoc ){ | ||
16354 | jx9_array_add_strkey_elem(pArray, "tm_sec", pValue); | ||
16355 | }else{ | ||
16356 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16357 | } | ||
16358 | /* Minutes */ | ||
16359 | jx9_value_int(pValue, sTm.tm_min); | ||
16360 | if( isAssoc ){ | ||
16361 | jx9_array_add_strkey_elem(pArray, "tm_min", pValue); | ||
16362 | }else{ | ||
16363 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16364 | } | ||
16365 | /* Hours */ | ||
16366 | jx9_value_int(pValue, sTm.tm_hour); | ||
16367 | if( isAssoc ){ | ||
16368 | jx9_array_add_strkey_elem(pArray, "tm_hour", pValue); | ||
16369 | }else{ | ||
16370 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16371 | } | ||
16372 | /* mday */ | ||
16373 | jx9_value_int(pValue, sTm.tm_mday); | ||
16374 | if( isAssoc ){ | ||
16375 | jx9_array_add_strkey_elem(pArray, "tm_mday", pValue); | ||
16376 | }else{ | ||
16377 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16378 | } | ||
16379 | /* mon */ | ||
16380 | jx9_value_int(pValue, sTm.tm_mon); | ||
16381 | if( isAssoc ){ | ||
16382 | jx9_array_add_strkey_elem(pArray, "tm_mon", pValue); | ||
16383 | }else{ | ||
16384 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16385 | } | ||
16386 | /* year since 1900 */ | ||
16387 | jx9_value_int(pValue, sTm.tm_year-1900); | ||
16388 | if( isAssoc ){ | ||
16389 | jx9_array_add_strkey_elem(pArray, "tm_year", pValue); | ||
16390 | }else{ | ||
16391 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16392 | } | ||
16393 | /* wday */ | ||
16394 | jx9_value_int(pValue, sTm.tm_wday); | ||
16395 | if( isAssoc ){ | ||
16396 | jx9_array_add_strkey_elem(pArray, "tm_wday", pValue); | ||
16397 | }else{ | ||
16398 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16399 | } | ||
16400 | /* yday */ | ||
16401 | jx9_value_int(pValue, sTm.tm_yday); | ||
16402 | if( isAssoc ){ | ||
16403 | jx9_array_add_strkey_elem(pArray, "tm_yday", pValue); | ||
16404 | }else{ | ||
16405 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16406 | } | ||
16407 | /* isdst */ | ||
16408 | #ifdef __WINNT__ | ||
16409 | #ifdef _MSC_VER | ||
16410 | #ifndef _WIN32_WCE | ||
16411 | _get_daylight(&sTm.tm_isdst); | ||
16412 | #endif | ||
16413 | #endif | ||
16414 | #endif | ||
16415 | jx9_value_int(pValue, sTm.tm_isdst); | ||
16416 | if( isAssoc ){ | ||
16417 | jx9_array_add_strkey_elem(pArray, "tm_isdst", pValue); | ||
16418 | }else{ | ||
16419 | jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); | ||
16420 | } | ||
16421 | /* Return the array */ | ||
16422 | jx9_result_value(pCtx, pArray); | ||
16423 | return JX9_OK; | ||
16424 | } | ||
16425 | /* | ||
16426 | * int idate(string $format [, int $timestamp = time() ]) | ||
16427 | * Returns a number formatted according to the given format string | ||
16428 | * using the given integer timestamp or the current local time if | ||
16429 | * no timestamp is given. In other words, timestamp is optional and defaults | ||
16430 | * to the value of time(). | ||
16431 | * Unlike the function date(), idate() accepts just one char in the format | ||
16432 | * parameter. | ||
16433 | * $Parameters | ||
16434 | * Supported format | ||
16435 | * d Day of the month | ||
16436 | * h Hour (12 hour format) | ||
16437 | * H Hour (24 hour format) | ||
16438 | * i Minutes | ||
16439 | * I (uppercase i)1 if DST is activated, 0 otherwise | ||
16440 | * L (uppercase l) returns 1 for leap year, 0 otherwise | ||
16441 | * m Month number | ||
16442 | * s Seconds | ||
16443 | * t Days in current month | ||
16444 | * U Seconds since the Unix Epoch - January 1 1970 00:00:00 UTC - this is the same as time() | ||
16445 | * w Day of the week (0 on Sunday) | ||
16446 | * W ISO-8601 week number of year, weeks starting on Monday | ||
16447 | * y Year (1 or 2 digits - check note below) | ||
16448 | * Y Year (4 digits) | ||
16449 | * z Day of the year | ||
16450 | * Z Timezone offset in seconds | ||
16451 | * $timestamp | ||
16452 | * The optional timestamp parameter is an integer Unix timestamp that defaults | ||
16453 | * to the current local time if a timestamp is not given. In other words, it defaults | ||
16454 | * to the value of time(). | ||
16455 | * Return | ||
16456 | * An integer. | ||
16457 | */ | ||
16458 | static int jx9Builtin_idate(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16459 | { | ||
16460 | const char *zFormat; | ||
16461 | jx9_int64 iVal = 0; | ||
16462 | int nLen; | ||
16463 | Sytm sTm; | ||
16464 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
16465 | /* Missing/Invalid argument, return -1 */ | ||
16466 | jx9_result_int(pCtx, -1); | ||
16467 | return JX9_OK; | ||
16468 | } | ||
16469 | zFormat = jx9_value_to_string(apArg[0], &nLen); | ||
16470 | if( nLen < 1 ){ | ||
16471 | /* Don't bother processing return -1*/ | ||
16472 | jx9_result_int(pCtx, -1); | ||
16473 | } | ||
16474 | if( nArg < 2 ){ | ||
16475 | #ifdef __WINNT__ | ||
16476 | SYSTEMTIME sOS; | ||
16477 | GetSystemTime(&sOS); | ||
16478 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
16479 | #else | ||
16480 | struct tm *pTm; | ||
16481 | time_t t; | ||
16482 | time(&t); | ||
16483 | pTm = localtime(&t); | ||
16484 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16485 | #endif | ||
16486 | }else{ | ||
16487 | /* Use the given timestamp */ | ||
16488 | time_t t; | ||
16489 | struct tm *pTm; | ||
16490 | if( jx9_value_is_int(apArg[1]) ){ | ||
16491 | t = (time_t)jx9_value_to_int64(apArg[1]); | ||
16492 | pTm = localtime(&t); | ||
16493 | if( pTm == 0 ){ | ||
16494 | time(&t); | ||
16495 | } | ||
16496 | }else{ | ||
16497 | time(&t); | ||
16498 | } | ||
16499 | pTm = localtime(&t); | ||
16500 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
16501 | } | ||
16502 | /* Perform the requested operation */ | ||
16503 | switch(zFormat[0]){ | ||
16504 | case 'd': | ||
16505 | /* Day of the month */ | ||
16506 | iVal = sTm.tm_mday; | ||
16507 | break; | ||
16508 | case 'h': | ||
16509 | /* Hour (12 hour format)*/ | ||
16510 | iVal = 1 + (sTm.tm_hour % 12); | ||
16511 | break; | ||
16512 | case 'H': | ||
16513 | /* Hour (24 hour format)*/ | ||
16514 | iVal = sTm.tm_hour; | ||
16515 | break; | ||
16516 | case 'i': | ||
16517 | /*Minutes*/ | ||
16518 | iVal = sTm.tm_min; | ||
16519 | break; | ||
16520 | case 'I': | ||
16521 | /* returns 1 if DST is activated, 0 otherwise */ | ||
16522 | #ifdef __WINNT__ | ||
16523 | #ifdef _MSC_VER | ||
16524 | #ifndef _WIN32_WCE | ||
16525 | _get_daylight(&sTm.tm_isdst); | ||
16526 | #endif | ||
16527 | #endif | ||
16528 | #endif | ||
16529 | iVal = sTm.tm_isdst; | ||
16530 | break; | ||
16531 | case 'L': | ||
16532 | /* returns 1 for leap year, 0 otherwise */ | ||
16533 | iVal = IS_LEAP_YEAR(sTm.tm_year); | ||
16534 | break; | ||
16535 | case 'm': | ||
16536 | /* Month number*/ | ||
16537 | iVal = sTm.tm_mon; | ||
16538 | break; | ||
16539 | case 's': | ||
16540 | /*Seconds*/ | ||
16541 | iVal = sTm.tm_sec; | ||
16542 | break; | ||
16543 | case 't':{ | ||
16544 | /*Days in current month*/ | ||
16545 | static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; | ||
16546 | int nDays = aMonDays[sTm.tm_mon % 12 ]; | ||
16547 | if( sTm.tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(sTm.tm_year) ){ | ||
16548 | nDays = 28; | ||
16549 | } | ||
16550 | iVal = nDays; | ||
16551 | break; | ||
16552 | } | ||
16553 | case 'U': | ||
16554 | /*Seconds since the Unix Epoch*/ | ||
16555 | iVal = (jx9_int64)time(0); | ||
16556 | break; | ||
16557 | case 'w': | ||
16558 | /* Day of the week (0 on Sunday) */ | ||
16559 | iVal = sTm.tm_wday; | ||
16560 | break; | ||
16561 | case 'W': { | ||
16562 | /* ISO-8601 week number of year, weeks starting on Monday */ | ||
16563 | static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 }; | ||
16564 | iVal = aISO8601[sTm.tm_wday % 7 ]; | ||
16565 | break; | ||
16566 | } | ||
16567 | case 'y': | ||
16568 | /* Year (2 digits) */ | ||
16569 | iVal = sTm.tm_year % 100; | ||
16570 | break; | ||
16571 | case 'Y': | ||
16572 | /* Year (4 digits) */ | ||
16573 | iVal = sTm.tm_year; | ||
16574 | break; | ||
16575 | case 'z': | ||
16576 | /* Day of the year */ | ||
16577 | iVal = sTm.tm_yday; | ||
16578 | break; | ||
16579 | case 'Z': | ||
16580 | /*Timezone offset in seconds*/ | ||
16581 | iVal = sTm.tm_gmtoff; | ||
16582 | break; | ||
16583 | default: | ||
16584 | /* unknown format, throw a warning */ | ||
16585 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Unknown date format token"); | ||
16586 | break; | ||
16587 | } | ||
16588 | /* Return the time value */ | ||
16589 | jx9_result_int64(pCtx, iVal); | ||
16590 | return JX9_OK; | ||
16591 | } | ||
16592 | /* | ||
16593 | * int mktime/gmmktime([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") | ||
16594 | * [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] ) | ||
16595 | * Returns the Unix timestamp corresponding to the arguments given. This timestamp is a 64bit integer | ||
16596 | * containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time | ||
16597 | * specified. | ||
16598 | * Arguments may be left out in order from right to left; any arguments thus omitted will be set to | ||
16599 | * the current value according to the local date and time. | ||
16600 | * Parameters | ||
16601 | * $hour | ||
16602 | * The number of the hour relevant to the start of the day determined by month, day and year. | ||
16603 | * Negative values reference the hour before midnight of the day in question. Values greater | ||
16604 | * than 23 reference the appropriate hour in the following day(s). | ||
16605 | * $minute | ||
16606 | * The number of the minute relevant to the start of the hour. Negative values reference | ||
16607 | * the minute in the previous hour. Values greater than 59 reference the appropriate minute | ||
16608 | * in the following hour(s). | ||
16609 | * $second | ||
16610 | * The number of seconds relevant to the start of the minute. Negative values reference | ||
16611 | * the second in the previous minute. Values greater than 59 reference the appropriate | ||
16612 | * second in the following minute(s). | ||
16613 | * $month | ||
16614 | * The number of the month relevant to the end of the previous year. Values 1 to 12 reference | ||
16615 | * the normal calendar months of the year in question. Values less than 1 (including negative values) | ||
16616 | * reference the months in the previous year in reverse order, so 0 is December, -1 is November)... | ||
16617 | * $day | ||
16618 | * The number of the day relevant to the end of the previous month. Values 1 to 28, 29, 30 or 31 | ||
16619 | * (depending upon the month) reference the normal days in the relevant month. Values less than 1 | ||
16620 | * (including negative values) reference the days in the previous month, so 0 is the last day | ||
16621 | * of the previous month, -1 is the day before that, etc. Values greater than the number of days | ||
16622 | * in the relevant month reference the appropriate day in the following month(s). | ||
16623 | * $year | ||
16624 | * The number of the year, may be a two or four digit value, with values between 0-69 mapping | ||
16625 | * to 2000-2069 and 70-100 to 1970-2000. On systems where time_t is a 32bit signed integer, as | ||
16626 | * most common today, the valid range for year is somewhere between 1901 and 2038. | ||
16627 | * $is_dst | ||
16628 | * This parameter can be set to 1 if the time is during daylight savings time (DST), 0 if it is not, | ||
16629 | * or -1 (the default) if it is unknown whether the time is within daylight savings time or not. | ||
16630 | * Return | ||
16631 | * mktime() returns the Unix timestamp of the arguments given. | ||
16632 | * If the arguments are invalid, the function returns FALSE | ||
16633 | */ | ||
16634 | static int jx9Builtin_mktime(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16635 | { | ||
16636 | const char *zFunction; | ||
16637 | jx9_int64 iVal = 0; | ||
16638 | struct tm *pTm; | ||
16639 | time_t t; | ||
16640 | /* Extract function name */ | ||
16641 | zFunction = jx9_function_name(pCtx); | ||
16642 | /* Get the current time */ | ||
16643 | time(&t); | ||
16644 | if( zFunction[0] == 'g' /* gmmktime */ ){ | ||
16645 | pTm = gmtime(&t); | ||
16646 | }else{ | ||
16647 | /* localtime */ | ||
16648 | pTm = localtime(&t); | ||
16649 | } | ||
16650 | if( nArg > 0 ){ | ||
16651 | int iVal; | ||
16652 | /* Hour */ | ||
16653 | iVal = jx9_value_to_int(apArg[0]); | ||
16654 | pTm->tm_hour = iVal; | ||
16655 | if( nArg > 1 ){ | ||
16656 | /* Minutes */ | ||
16657 | iVal = jx9_value_to_int(apArg[1]); | ||
16658 | pTm->tm_min = iVal; | ||
16659 | if( nArg > 2 ){ | ||
16660 | /* Seconds */ | ||
16661 | iVal = jx9_value_to_int(apArg[2]); | ||
16662 | pTm->tm_sec = iVal; | ||
16663 | if( nArg > 3 ){ | ||
16664 | /* Month */ | ||
16665 | iVal = jx9_value_to_int(apArg[3]); | ||
16666 | pTm->tm_mon = iVal - 1; | ||
16667 | if( nArg > 4 ){ | ||
16668 | /* mday */ | ||
16669 | iVal = jx9_value_to_int(apArg[4]); | ||
16670 | pTm->tm_mday = iVal; | ||
16671 | if( nArg > 5 ){ | ||
16672 | /* Year */ | ||
16673 | iVal = jx9_value_to_int(apArg[5]); | ||
16674 | if( iVal > 1900 ){ | ||
16675 | iVal -= 1900; | ||
16676 | } | ||
16677 | pTm->tm_year = iVal; | ||
16678 | if( nArg > 6 ){ | ||
16679 | /* is_dst */ | ||
16680 | iVal = jx9_value_to_bool(apArg[6]); | ||
16681 | pTm->tm_isdst = iVal; | ||
16682 | } | ||
16683 | } | ||
16684 | } | ||
16685 | } | ||
16686 | } | ||
16687 | } | ||
16688 | } | ||
16689 | /* Make the time */ | ||
16690 | iVal = (jx9_int64)mktime(pTm); | ||
16691 | /* Return the timesatmp as a 64bit integer */ | ||
16692 | jx9_result_int64(pCtx, iVal); | ||
16693 | return JX9_OK; | ||
16694 | } | ||
16695 | /* | ||
16696 | * Section: | ||
16697 | * URL handling Functions. | ||
16698 | * Authors: | ||
16699 | * Symisc Systems, devel@symisc.net. | ||
16700 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
16701 | * Status: | ||
16702 | * Stable. | ||
16703 | */ | ||
16704 | /* | ||
16705 | * Output consumer callback for the standard Symisc routines. | ||
16706 | * [i.e: SyBase64Encode(), SyBase64Decode(), SyUriEncode(), ...]. | ||
16707 | */ | ||
16708 | static int Consumer(const void *pData, unsigned int nLen, void *pUserData) | ||
16709 | { | ||
16710 | /* Store in the call context result buffer */ | ||
16711 | jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); | ||
16712 | return SXRET_OK; | ||
16713 | } | ||
16714 | /* | ||
16715 | * string base64_encode(string $data) | ||
16716 | * string convert_uuencode(string $data) | ||
16717 | * Encodes data with MIME base64 | ||
16718 | * Parameter | ||
16719 | * $data | ||
16720 | * Data to encode | ||
16721 | * Return | ||
16722 | * Encoded data or FALSE on failure. | ||
16723 | */ | ||
16724 | static int jx9Builtin_base64_encode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16725 | { | ||
16726 | const char *zIn; | ||
16727 | int nLen; | ||
16728 | if( nArg < 1 ){ | ||
16729 | /* Missing arguments, return FALSE */ | ||
16730 | jx9_result_bool(pCtx, 0); | ||
16731 | return JX9_OK; | ||
16732 | } | ||
16733 | /* Extract the input string */ | ||
16734 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
16735 | if( nLen < 1 ){ | ||
16736 | /* Nothing to process, return FALSE */ | ||
16737 | jx9_result_bool(pCtx, 0); | ||
16738 | return JX9_OK; | ||
16739 | } | ||
16740 | /* Perform the BASE64 encoding */ | ||
16741 | SyBase64Encode(zIn, (sxu32)nLen, Consumer, pCtx); | ||
16742 | return JX9_OK; | ||
16743 | } | ||
16744 | /* | ||
16745 | * string base64_decode(string $data) | ||
16746 | * string convert_uudecode(string $data) | ||
16747 | * Decodes data encoded with MIME base64 | ||
16748 | * Parameter | ||
16749 | * $data | ||
16750 | * Encoded data. | ||
16751 | * Return | ||
16752 | * Returns the original data or FALSE on failure. | ||
16753 | */ | ||
16754 | static int jx9Builtin_base64_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16755 | { | ||
16756 | const char *zIn; | ||
16757 | int nLen; | ||
16758 | if( nArg < 1 ){ | ||
16759 | /* Missing arguments, return FALSE */ | ||
16760 | jx9_result_bool(pCtx, 0); | ||
16761 | return JX9_OK; | ||
16762 | } | ||
16763 | /* Extract the input string */ | ||
16764 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
16765 | if( nLen < 1 ){ | ||
16766 | /* Nothing to process, return FALSE */ | ||
16767 | jx9_result_bool(pCtx, 0); | ||
16768 | return JX9_OK; | ||
16769 | } | ||
16770 | /* Perform the BASE64 decoding */ | ||
16771 | SyBase64Decode(zIn, (sxu32)nLen, Consumer, pCtx); | ||
16772 | return JX9_OK; | ||
16773 | } | ||
16774 | /* | ||
16775 | * string urlencode(string $str) | ||
16776 | * URL encoding | ||
16777 | * Parameter | ||
16778 | * $data | ||
16779 | * Input string. | ||
16780 | * Return | ||
16781 | * Returns a string in which all non-alphanumeric characters except -_. have | ||
16782 | * been replaced with a percent (%) sign followed by two hex digits and spaces | ||
16783 | * encoded as plus (+) signs. | ||
16784 | */ | ||
16785 | static int jx9Builtin_urlencode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16786 | { | ||
16787 | const char *zIn; | ||
16788 | int nLen; | ||
16789 | if( nArg < 1 ){ | ||
16790 | /* Missing arguments, return FALSE */ | ||
16791 | jx9_result_bool(pCtx, 0); | ||
16792 | return JX9_OK; | ||
16793 | } | ||
16794 | /* Extract the input string */ | ||
16795 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
16796 | if( nLen < 1 ){ | ||
16797 | /* Nothing to process, return FALSE */ | ||
16798 | jx9_result_bool(pCtx, 0); | ||
16799 | return JX9_OK; | ||
16800 | } | ||
16801 | /* Perform the URL encoding */ | ||
16802 | SyUriEncode(zIn, (sxu32)nLen, Consumer, pCtx); | ||
16803 | return JX9_OK; | ||
16804 | } | ||
16805 | /* | ||
16806 | * string urldecode(string $str) | ||
16807 | * Decodes any %## encoding in the given string. | ||
16808 | * Plus symbols ('+') are decoded to a space character. | ||
16809 | * Parameter | ||
16810 | * $data | ||
16811 | * Input string. | ||
16812 | * Return | ||
16813 | * Decoded URL or FALSE on failure. | ||
16814 | */ | ||
16815 | static int jx9Builtin_urldecode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
16816 | { | ||
16817 | const char *zIn; | ||
16818 | int nLen; | ||
16819 | if( nArg < 1 ){ | ||
16820 | /* Missing arguments, return FALSE */ | ||
16821 | jx9_result_bool(pCtx, 0); | ||
16822 | return JX9_OK; | ||
16823 | } | ||
16824 | /* Extract the input string */ | ||
16825 | zIn = jx9_value_to_string(apArg[0], &nLen); | ||
16826 | if( nLen < 1 ){ | ||
16827 | /* Nothing to process, return FALSE */ | ||
16828 | jx9_result_bool(pCtx, 0); | ||
16829 | return JX9_OK; | ||
16830 | } | ||
16831 | /* Perform the URL decoding */ | ||
16832 | SyUriDecode(zIn, (sxu32)nLen, Consumer, pCtx, TRUE); | ||
16833 | return JX9_OK; | ||
16834 | } | ||
16835 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
16836 | /* Table of the built-in functions */ | ||
16837 | static const jx9_builtin_func aBuiltInFunc[] = { | ||
16838 | /* Variable handling functions */ | ||
16839 | { "is_bool" , jx9Builtin_is_bool }, | ||
16840 | { "is_float" , jx9Builtin_is_float }, | ||
16841 | { "is_real" , jx9Builtin_is_float }, | ||
16842 | { "is_double" , jx9Builtin_is_float }, | ||
16843 | { "is_int" , jx9Builtin_is_int }, | ||
16844 | { "is_integer" , jx9Builtin_is_int }, | ||
16845 | { "is_long" , jx9Builtin_is_int }, | ||
16846 | { "is_string" , jx9Builtin_is_string }, | ||
16847 | { "is_null" , jx9Builtin_is_null }, | ||
16848 | { "is_numeric" , jx9Builtin_is_numeric }, | ||
16849 | { "is_scalar" , jx9Builtin_is_scalar }, | ||
16850 | { "is_array" , jx9Builtin_is_array }, | ||
16851 | { "is_object" , jx9Builtin_is_object }, | ||
16852 | { "is_resource", jx9Builtin_is_resource }, | ||
16853 | { "douleval" , jx9Builtin_floatval }, | ||
16854 | { "floatval" , jx9Builtin_floatval }, | ||
16855 | { "intval" , jx9Builtin_intval }, | ||
16856 | { "strval" , jx9Builtin_strval }, | ||
16857 | { "empty" , jx9Builtin_empty }, | ||
16858 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
16859 | #ifdef JX9_ENABLE_MATH_FUNC | ||
16860 | /* Math functions */ | ||
16861 | { "abs" , jx9Builtin_abs }, | ||
16862 | { "sqrt" , jx9Builtin_sqrt }, | ||
16863 | { "exp" , jx9Builtin_exp }, | ||
16864 | { "floor", jx9Builtin_floor }, | ||
16865 | { "cos" , jx9Builtin_cos }, | ||
16866 | { "sin" , jx9Builtin_sin }, | ||
16867 | { "acos" , jx9Builtin_acos }, | ||
16868 | { "asin" , jx9Builtin_asin }, | ||
16869 | { "cosh" , jx9Builtin_cosh }, | ||
16870 | { "sinh" , jx9Builtin_sinh }, | ||
16871 | { "ceil" , jx9Builtin_ceil }, | ||
16872 | { "tan" , jx9Builtin_tan }, | ||
16873 | { "tanh" , jx9Builtin_tanh }, | ||
16874 | { "atan" , jx9Builtin_atan }, | ||
16875 | { "atan2", jx9Builtin_atan2 }, | ||
16876 | { "log" , jx9Builtin_log }, | ||
16877 | { "log10" , jx9Builtin_log10 }, | ||
16878 | { "pow" , jx9Builtin_pow }, | ||
16879 | { "pi", jx9Builtin_pi }, | ||
16880 | { "fmod", jx9Builtin_fmod }, | ||
16881 | { "hypot", jx9Builtin_hypot }, | ||
16882 | #endif /* JX9_ENABLE_MATH_FUNC */ | ||
16883 | { "round", jx9Builtin_round }, | ||
16884 | { "dechex", jx9Builtin_dechex }, | ||
16885 | { "decoct", jx9Builtin_decoct }, | ||
16886 | { "decbin", jx9Builtin_decbin }, | ||
16887 | { "hexdec", jx9Builtin_hexdec }, | ||
16888 | { "bindec", jx9Builtin_bindec }, | ||
16889 | { "octdec", jx9Builtin_octdec }, | ||
16890 | { "base_convert", jx9Builtin_base_convert }, | ||
16891 | /* String handling functions */ | ||
16892 | { "substr", jx9Builtin_substr }, | ||
16893 | { "substr_compare", jx9Builtin_substr_compare }, | ||
16894 | { "substr_count", jx9Builtin_substr_count }, | ||
16895 | { "chunk_split", jx9Builtin_chunk_split}, | ||
16896 | { "htmlspecialchars", jx9Builtin_htmlspecialchars }, | ||
16897 | { "htmlspecialchars_decode", jx9Builtin_htmlspecialchars_decode }, | ||
16898 | { "get_html_translation_table", jx9Builtin_get_html_translation_table }, | ||
16899 | { "htmlentities", jx9Builtin_htmlentities}, | ||
16900 | { "html_entity_decode", jx9Builtin_html_entity_decode}, | ||
16901 | { "strlen" , jx9Builtin_strlen }, | ||
16902 | { "strcmp" , jx9Builtin_strcmp }, | ||
16903 | { "strcoll" , jx9Builtin_strcmp }, | ||
16904 | { "strncmp" , jx9Builtin_strncmp }, | ||
16905 | { "strcasecmp" , jx9Builtin_strcasecmp }, | ||
16906 | { "strncasecmp", jx9Builtin_strncasecmp}, | ||
16907 | { "implode" , jx9Builtin_implode }, | ||
16908 | { "join" , jx9Builtin_implode }, | ||
16909 | { "implode_recursive" , jx9Builtin_implode_recursive }, | ||
16910 | { "join_recursive" , jx9Builtin_implode_recursive }, | ||
16911 | { "explode" , jx9Builtin_explode }, | ||
16912 | { "trim" , jx9Builtin_trim }, | ||
16913 | { "rtrim" , jx9Builtin_rtrim }, | ||
16914 | { "chop" , jx9Builtin_rtrim }, | ||
16915 | { "ltrim" , jx9Builtin_ltrim }, | ||
16916 | { "strtolower", jx9Builtin_strtolower }, | ||
16917 | { "mb_strtolower", jx9Builtin_strtolower }, /* Only UTF-8 encoding is supported */ | ||
16918 | { "strtoupper", jx9Builtin_strtoupper }, | ||
16919 | { "mb_strtoupper", jx9Builtin_strtoupper }, /* Only UTF-8 encoding is supported */ | ||
16920 | { "ord", jx9Builtin_ord }, | ||
16921 | { "chr", jx9Builtin_chr }, | ||
16922 | { "bin2hex", jx9Builtin_bin2hex }, | ||
16923 | { "strstr", jx9Builtin_strstr }, | ||
16924 | { "stristr", jx9Builtin_stristr }, | ||
16925 | { "strchr", jx9Builtin_strstr }, | ||
16926 | { "strpos", jx9Builtin_strpos }, | ||
16927 | { "stripos", jx9Builtin_stripos }, | ||
16928 | { "strrpos", jx9Builtin_strrpos }, | ||
16929 | { "strripos", jx9Builtin_strripos }, | ||
16930 | { "strrchr", jx9Builtin_strrchr }, | ||
16931 | { "strrev", jx9Builtin_strrev }, | ||
16932 | { "str_repeat", jx9Builtin_str_repeat }, | ||
16933 | { "nl2br", jx9Builtin_nl2br }, | ||
16934 | { "sprintf", jx9Builtin_sprintf }, | ||
16935 | { "printf", jx9Builtin_printf }, | ||
16936 | { "vprintf", jx9Builtin_vprintf }, | ||
16937 | { "vsprintf", jx9Builtin_vsprintf }, | ||
16938 | { "size_format", jx9Builtin_size_format}, | ||
16939 | #if !defined(JX9_DISABLE_HASH_FUNC) | ||
16940 | { "md5", jx9Builtin_md5 }, | ||
16941 | { "sha1", jx9Builtin_sha1 }, | ||
16942 | { "crc32", jx9Builtin_crc32 }, | ||
16943 | #endif /* JX9_DISABLE_HASH_FUNC */ | ||
16944 | { "str_getcsv", jx9Builtin_str_getcsv }, | ||
16945 | { "strip_tags", jx9Builtin_strip_tags }, | ||
16946 | { "str_split", jx9Builtin_str_split }, | ||
16947 | { "strspn", jx9Builtin_strspn }, | ||
16948 | { "strcspn", jx9Builtin_strcspn }, | ||
16949 | { "strpbrk", jx9Builtin_strpbrk }, | ||
16950 | { "soundex", jx9Builtin_soundex }, | ||
16951 | { "wordwrap", jx9Builtin_wordwrap }, | ||
16952 | { "strtok", jx9Builtin_strtok }, | ||
16953 | { "str_pad", jx9Builtin_str_pad }, | ||
16954 | { "str_replace", jx9Builtin_str_replace}, | ||
16955 | { "str_ireplace", jx9Builtin_str_replace}, | ||
16956 | { "strtr", jx9Builtin_strtr }, | ||
16957 | { "parse_ini_string", jx9Builtin_parse_ini_string}, | ||
16958 | /* Ctype functions */ | ||
16959 | { "ctype_alnum", jx9Builtin_ctype_alnum }, | ||
16960 | { "ctype_alpha", jx9Builtin_ctype_alpha }, | ||
16961 | { "ctype_cntrl", jx9Builtin_ctype_cntrl }, | ||
16962 | { "ctype_digit", jx9Builtin_ctype_digit }, | ||
16963 | { "ctype_xdigit", jx9Builtin_ctype_xdigit}, | ||
16964 | { "ctype_graph", jx9Builtin_ctype_graph }, | ||
16965 | { "ctype_print", jx9Builtin_ctype_print }, | ||
16966 | { "ctype_punct", jx9Builtin_ctype_punct }, | ||
16967 | { "ctype_space", jx9Builtin_ctype_space }, | ||
16968 | { "ctype_lower", jx9Builtin_ctype_lower }, | ||
16969 | { "ctype_upper", jx9Builtin_ctype_upper }, | ||
16970 | /* Time functions */ | ||
16971 | { "time" , jx9Builtin_time }, | ||
16972 | { "microtime", jx9Builtin_microtime }, | ||
16973 | { "getdate" , jx9Builtin_getdate }, | ||
16974 | { "gettimeofday", jx9Builtin_gettimeofday }, | ||
16975 | { "date", jx9Builtin_date }, | ||
16976 | { "strftime", jx9Builtin_strftime }, | ||
16977 | { "idate", jx9Builtin_idate }, | ||
16978 | { "gmdate", jx9Builtin_gmdate }, | ||
16979 | { "localtime", jx9Builtin_localtime }, | ||
16980 | { "mktime", jx9Builtin_mktime }, | ||
16981 | { "gmmktime", jx9Builtin_mktime }, | ||
16982 | /* URL functions */ | ||
16983 | { "base64_encode", jx9Builtin_base64_encode }, | ||
16984 | { "base64_decode", jx9Builtin_base64_decode }, | ||
16985 | { "convert_uuencode", jx9Builtin_base64_encode }, | ||
16986 | { "convert_uudecode", jx9Builtin_base64_decode }, | ||
16987 | { "urlencode", jx9Builtin_urlencode }, | ||
16988 | { "urldecode", jx9Builtin_urldecode }, | ||
16989 | { "rawurlencode", jx9Builtin_urlencode }, | ||
16990 | { "rawurldecode", jx9Builtin_urldecode }, | ||
16991 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
16992 | }; | ||
16993 | /* | ||
16994 | * Register the built-in functions defined above, the array functions | ||
16995 | * defined in hashmap.c and the IO functions defined in vfs.c. | ||
16996 | */ | ||
16997 | JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm) | ||
16998 | { | ||
16999 | sxu32 n; | ||
17000 | for( n = 0 ; n < SX_ARRAYSIZE(aBuiltInFunc) ; ++n ){ | ||
17001 | jx9_create_function(&(*pVm), aBuiltInFunc[n].zName, aBuiltInFunc[n].xFunc, 0); | ||
17002 | } | ||
17003 | /* Register hashmap functions [i.e: sort(), count(), array_diff(), ...] */ | ||
17004 | jx9RegisterHashmapFunctions(&(*pVm)); | ||
17005 | /* Register IO functions [i.e: fread(), fwrite(), chdir(), mkdir(), file(), ...] */ | ||
17006 | jx9RegisterIORoutine(&(*pVm)); | ||
17007 | } | ||
17008 | |||
17009 | /* | ||
17010 | * ---------------------------------------------------------- | ||
17011 | * File: jx9_compile.c | ||
17012 | * MD5: 562e73eb7214f890e71713c6b97a7863 | ||
17013 | * ---------------------------------------------------------- | ||
17014 | */ | ||
17015 | /* | ||
17016 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
17017 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
17018 | * Version 1.7.2 | ||
17019 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
17020 | * please contact Symisc Systems via: | ||
17021 | * legal@symisc.net | ||
17022 | * licensing@symisc.net | ||
17023 | * contact@symisc.net | ||
17024 | * or visit: | ||
17025 | * http://jx9.symisc.net/ | ||
17026 | */ | ||
17027 | /* $SymiscID: compile.c v1.7 FreeBSD 2012-12-11 21:46 stable <chm@symisc.net> $ */ | ||
17028 | #ifndef JX9_AMALGAMATION | ||
17029 | #include "jx9Int.h" | ||
17030 | #endif | ||
17031 | /* | ||
17032 | * This file implement a thread-safe and full-reentrant compiler for the JX9 engine. | ||
17033 | * That is, routines defined in this file takes a stream of tokens and output | ||
17034 | * JX9 bytecode instructions. | ||
17035 | */ | ||
17036 | /* Forward declaration */ | ||
17037 | typedef struct LangConstruct LangConstruct; | ||
17038 | typedef struct JumpFixup JumpFixup; | ||
17039 | /* Block [i.e: set of statements] control flags */ | ||
17040 | #define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for, while, ...] */ | ||
17041 | #define GEN_BLOCK_PROTECTED 0x002 /* Protected block */ | ||
17042 | #define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/ | ||
17043 | #define GEN_BLOCK_FUNC 0x008 /* Function body */ | ||
17044 | #define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/ | ||
17045 | #define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */ | ||
17046 | #define GEN_BLOCK_EXPR 0x040 /* Expression */ | ||
17047 | #define GEN_BLOCK_STD 0x080 /* Standard block */ | ||
17048 | #define GEN_BLOCK_SWITCH 0x100 /* Switch statement */ | ||
17049 | /* | ||
17050 | * Compilation of some JX9 constructs such as if, for, while, the logical or | ||
17051 | * (||) and logical and (&&) operators in expressions requires the | ||
17052 | * generation of forward jumps. | ||
17053 | * Since the destination PC target of these jumps isn't known when the jumps | ||
17054 | * are emitted, we record each forward jump in an instance of the following | ||
17055 | * structure. Those jumps are fixed later when the jump destination is resolved. | ||
17056 | */ | ||
17057 | struct JumpFixup | ||
17058 | { | ||
17059 | sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ | ||
17060 | sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ | ||
17061 | }; | ||
17062 | /* | ||
17063 | * Each language construct is represented by an instance | ||
17064 | * of the following structure. | ||
17065 | */ | ||
17066 | struct LangConstruct | ||
17067 | { | ||
17068 | sxu32 nID; /* Language construct ID [i.e: JX9_TKWRD_WHILE, JX9_TKWRD_FOR, JX9_TKWRD_IF...] */ | ||
17069 | ProcLangConstruct xConstruct; /* C function implementing the language construct */ | ||
17070 | }; | ||
17071 | /* Compilation flags */ | ||
17072 | #define JX9_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */ | ||
17073 | /* Token stream synchronization macros */ | ||
17074 | #define SWAP_TOKEN_STREAM(GEN, START, END)\ | ||
17075 | pTmp = GEN->pEnd;\ | ||
17076 | pGen->pIn = START;\ | ||
17077 | pGen->pEnd = END | ||
17078 | #define UPDATE_TOKEN_STREAM(GEN)\ | ||
17079 | if( GEN->pIn < pTmp ){\ | ||
17080 | GEN->pIn++;\ | ||
17081 | }\ | ||
17082 | GEN->pEnd = pTmp | ||
17083 | #define SWAP_DELIMITER(GEN, START, END)\ | ||
17084 | pTmpIn = GEN->pIn;\ | ||
17085 | pTmpEnd = GEN->pEnd;\ | ||
17086 | GEN->pIn = START;\ | ||
17087 | GEN->pEnd = END | ||
17088 | #define RE_SWAP_DELIMITER(GEN)\ | ||
17089 | GEN->pIn = pTmpIn;\ | ||
17090 | GEN->pEnd = pTmpEnd | ||
17091 | /* Flags related to expression compilation */ | ||
17092 | #define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */ | ||
17093 | #define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'JX9_OP_LOAD' VM instruction for more information */ | ||
17094 | #define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by object attributes) */ | ||
17095 | /* Forward declaration */ | ||
17096 | static sxi32 jx9CompileExpr( | ||
17097 | jx9_gen_state *pGen, /* Code generator state */ | ||
17098 | sxi32 iFlags, /* Control flags */ | ||
17099 | sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ | ||
17100 | ); | ||
17101 | |||
17102 | /* | ||
17103 | * Recover from a compile-time error. In other words synchronize | ||
17104 | * the token stream cursor with the first semi-colon seen. | ||
17105 | */ | ||
17106 | static sxi32 jx9ErrorRecover(jx9_gen_state *pGen) | ||
17107 | { | ||
17108 | /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ | ||
17109 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI /*';'*/) == 0){ | ||
17110 | pGen->pIn++; | ||
17111 | } | ||
17112 | return SXRET_OK; | ||
17113 | } | ||
17114 | /* | ||
17115 | * Check if the given identifier name is reserved or not. | ||
17116 | * Return TRUE if reserved.FALSE otherwise. | ||
17117 | */ | ||
17118 | static int GenStateIsReservedID(SyString *pName) | ||
17119 | { | ||
17120 | if( pName->nByte == sizeof("null") - 1 ){ | ||
17121 | if( SyStrnicmp(pName->zString, "null", sizeof("null")-1) == 0 ){ | ||
17122 | return TRUE; | ||
17123 | }else if( SyStrnicmp(pName->zString, "true", sizeof("true")-1) == 0 ){ | ||
17124 | return TRUE; | ||
17125 | } | ||
17126 | }else if( pName->nByte == sizeof("false") - 1 ){ | ||
17127 | if( SyStrnicmp(pName->zString, "false", sizeof("false")-1) == 0 ){ | ||
17128 | return TRUE; | ||
17129 | } | ||
17130 | } | ||
17131 | /* Not a reserved constant */ | ||
17132 | return FALSE; | ||
17133 | } | ||
17134 | /* | ||
17135 | * Check if a given token value is installed in the literal table. | ||
17136 | */ | ||
17137 | static sxi32 GenStateFindLiteral(jx9_gen_state *pGen, const SyString *pValue, sxu32 *pIdx) | ||
17138 | { | ||
17139 | SyHashEntry *pEntry; | ||
17140 | pEntry = SyHashGet(&pGen->hLiteral, (const void *)pValue->zString, pValue->nByte); | ||
17141 | if( pEntry == 0 ){ | ||
17142 | return SXERR_NOTFOUND; | ||
17143 | } | ||
17144 | *pIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); | ||
17145 | return SXRET_OK; | ||
17146 | } | ||
17147 | /* | ||
17148 | * Install a given constant index in the literal table. | ||
17149 | * In order to be installed, the jx9_value must be of type string. | ||
17150 | */ | ||
17151 | static sxi32 GenStateInstallLiteral(jx9_gen_state *pGen,jx9_value *pObj, sxu32 nIdx) | ||
17152 | { | ||
17153 | if( SyBlobLength(&pObj->sBlob) > 0 ){ | ||
17154 | SyHashInsert(&pGen->hLiteral, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), SX_INT_TO_PTR(nIdx)); | ||
17155 | } | ||
17156 | return SXRET_OK; | ||
17157 | } | ||
17158 | /* | ||
17159 | * Generate a fatal error. | ||
17160 | */ | ||
17161 | static sxi32 GenStateOutOfMem(jx9_gen_state *pGen) | ||
17162 | { | ||
17163 | jx9GenCompileError(pGen,E_ERROR,1,"Fatal, Jx9 compiler is running out of memory"); | ||
17164 | /* Abort compilation immediately */ | ||
17165 | return SXERR_ABORT; | ||
17166 | } | ||
17167 | /* | ||
17168 | * Fetch a block that correspond to the given criteria from the stack of | ||
17169 | * compiled blocks. | ||
17170 | * Return a pointer to that block on success. NULL otherwise. | ||
17171 | */ | ||
17172 | static GenBlock * GenStateFetchBlock(GenBlock *pCurrent, sxi32 iBlockType, sxi32 iCount) | ||
17173 | { | ||
17174 | GenBlock *pBlock = pCurrent; | ||
17175 | for(;;){ | ||
17176 | if( pBlock->iFlags & iBlockType ){ | ||
17177 | iCount--; /* Decrement nesting level */ | ||
17178 | if( iCount < 1 ){ | ||
17179 | /* Block meet with the desired criteria */ | ||
17180 | return pBlock; | ||
17181 | } | ||
17182 | } | ||
17183 | /* Point to the upper block */ | ||
17184 | pBlock = pBlock->pParent; | ||
17185 | if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){ | ||
17186 | /* Forbidden */ | ||
17187 | break; | ||
17188 | } | ||
17189 | } | ||
17190 | /* No such block */ | ||
17191 | return 0; | ||
17192 | } | ||
17193 | /* | ||
17194 | * Initialize a freshly allocated block instance. | ||
17195 | */ | ||
17196 | static void GenStateInitBlock( | ||
17197 | jx9_gen_state *pGen, /* Code generator state */ | ||
17198 | GenBlock *pBlock, /* Target block */ | ||
17199 | sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ | ||
17200 | sxu32 nFirstInstr, /* First instruction to compile */ | ||
17201 | void *pUserData /* Upper layer private data */ | ||
17202 | ) | ||
17203 | { | ||
17204 | /* Initialize block fields */ | ||
17205 | pBlock->nFirstInstr = nFirstInstr; | ||
17206 | pBlock->pUserData = pUserData; | ||
17207 | pBlock->pGen = pGen; | ||
17208 | pBlock->iFlags = iType; | ||
17209 | pBlock->pParent = 0; | ||
17210 | pBlock->bPostContinue = 0; | ||
17211 | SySetInit(&pBlock->aJumpFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); | ||
17212 | SySetInit(&pBlock->aPostContFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); | ||
17213 | } | ||
17214 | /* | ||
17215 | * Allocate a new block instance. | ||
17216 | * Return SXRET_OK and write a pointer to the new instantiated block | ||
17217 | * on success.Otherwise generate a compile-time error and abort | ||
17218 | * processing on failure. | ||
17219 | */ | ||
17220 | static sxi32 GenStateEnterBlock( | ||
17221 | jx9_gen_state *pGen, /* Code generator state */ | ||
17222 | sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ | ||
17223 | sxu32 nFirstInstr, /* First instruction to compile */ | ||
17224 | void *pUserData, /* Upper layer private data */ | ||
17225 | GenBlock **ppBlock /* OUT: instantiated block */ | ||
17226 | ) | ||
17227 | { | ||
17228 | GenBlock *pBlock; | ||
17229 | /* Allocate a new block instance */ | ||
17230 | pBlock = (GenBlock *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(GenBlock)); | ||
17231 | if( pBlock == 0 ){ | ||
17232 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
17233 | * a tiny chunk of memory, there is no much we can do here. | ||
17234 | */ | ||
17235 | return GenStateOutOfMem(pGen); | ||
17236 | } | ||
17237 | /* Zero the structure */ | ||
17238 | SyZero(pBlock, sizeof(GenBlock)); | ||
17239 | GenStateInitBlock(&(*pGen), pBlock, iType, nFirstInstr, pUserData); | ||
17240 | /* Link to the parent block */ | ||
17241 | pBlock->pParent = pGen->pCurrent; | ||
17242 | /* Mark as the current block */ | ||
17243 | pGen->pCurrent = pBlock; | ||
17244 | if( ppBlock ){ | ||
17245 | /* Write a pointer to the new instance */ | ||
17246 | *ppBlock = pBlock; | ||
17247 | } | ||
17248 | return SXRET_OK; | ||
17249 | } | ||
17250 | /* | ||
17251 | * Release block fields without freeing the whole instance. | ||
17252 | */ | ||
17253 | static void GenStateReleaseBlock(GenBlock *pBlock) | ||
17254 | { | ||
17255 | SySetRelease(&pBlock->aPostContFix); | ||
17256 | SySetRelease(&pBlock->aJumpFix); | ||
17257 | } | ||
17258 | /* | ||
17259 | * Release a block. | ||
17260 | */ | ||
17261 | static void GenStateFreeBlock(GenBlock *pBlock) | ||
17262 | { | ||
17263 | jx9_gen_state *pGen = pBlock->pGen; | ||
17264 | GenStateReleaseBlock(&(*pBlock)); | ||
17265 | /* Free the instance */ | ||
17266 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pBlock); | ||
17267 | } | ||
17268 | /* | ||
17269 | * POP and release a block from the stack of compiled blocks. | ||
17270 | */ | ||
17271 | static sxi32 GenStateLeaveBlock(jx9_gen_state *pGen, GenBlock **ppBlock) | ||
17272 | { | ||
17273 | GenBlock *pBlock = pGen->pCurrent; | ||
17274 | if( pBlock == 0 ){ | ||
17275 | /* No more block to pop */ | ||
17276 | return SXERR_EMPTY; | ||
17277 | } | ||
17278 | /* Point to the upper block */ | ||
17279 | pGen->pCurrent = pBlock->pParent; | ||
17280 | if( ppBlock ){ | ||
17281 | /* Write a pointer to the popped block */ | ||
17282 | *ppBlock = pBlock; | ||
17283 | }else{ | ||
17284 | /* Safely release the block */ | ||
17285 | GenStateFreeBlock(&(*pBlock)); | ||
17286 | } | ||
17287 | return SXRET_OK; | ||
17288 | } | ||
17289 | /* | ||
17290 | * Emit a forward jump. | ||
17291 | * Notes on forward jumps | ||
17292 | * Compilation of some JX9 constructs such as if, for, while and the logical or | ||
17293 | * (||) and logical and (&&) operators in expressions requires the | ||
17294 | * generation of forward jumps. | ||
17295 | * Since the destination PC target of these jumps isn't known when the jumps | ||
17296 | * are emitted, we record each forward jump in an instance of the following | ||
17297 | * structure. Those jumps are fixed later when the jump destination is resolved. | ||
17298 | */ | ||
17299 | static sxi32 GenStateNewJumpFixup(GenBlock *pBlock, sxi32 nJumpType, sxu32 nInstrIdx) | ||
17300 | { | ||
17301 | JumpFixup sJumpFix; | ||
17302 | sxi32 rc; | ||
17303 | /* Init the JumpFixup structure */ | ||
17304 | sJumpFix.nJumpType = nJumpType; | ||
17305 | sJumpFix.nInstrIdx = nInstrIdx; | ||
17306 | /* Insert in the jump fixup table */ | ||
17307 | rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix); | ||
17308 | return rc; | ||
17309 | } | ||
17310 | /* | ||
17311 | * Fix a forward jump now the jump destination is resolved. | ||
17312 | * Return the total number of fixed jumps. | ||
17313 | * Notes on forward jumps: | ||
17314 | * Compilation of some JX9 constructs such as if, for, while and the logical or | ||
17315 | * (||) and logical and (&&) operators in expressions requires the | ||
17316 | * generation of forward jumps. | ||
17317 | * Since the destination PC target of these jumps isn't known when the jumps | ||
17318 | * are emitted, we record each forward jump in an instance of the following | ||
17319 | * structure.Those jumps are fixed later when the jump destination is resolved. | ||
17320 | */ | ||
17321 | static sxu32 GenStateFixJumps(GenBlock *pBlock, sxi32 nJumpType, sxu32 nJumpDest) | ||
17322 | { | ||
17323 | JumpFixup *aFix; | ||
17324 | VmInstr *pInstr; | ||
17325 | sxu32 nFixed; | ||
17326 | sxu32 n; | ||
17327 | /* Point to the jump fixup table */ | ||
17328 | aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix); | ||
17329 | /* Fix the desired jumps */ | ||
17330 | for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){ | ||
17331 | if( aFix[n].nJumpType < 0 ){ | ||
17332 | /* Already fixed */ | ||
17333 | continue; | ||
17334 | } | ||
17335 | if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){ | ||
17336 | /* Not of our interest */ | ||
17337 | continue; | ||
17338 | } | ||
17339 | /* Point to the instruction to fix */ | ||
17340 | pInstr = jx9VmGetInstr(pBlock->pGen->pVm, aFix[n].nInstrIdx); | ||
17341 | if( pInstr ){ | ||
17342 | pInstr->iP2 = nJumpDest; | ||
17343 | nFixed++; | ||
17344 | /* Mark as fixed */ | ||
17345 | aFix[n].nJumpType = -1; | ||
17346 | } | ||
17347 | } | ||
17348 | /* Total number of fixed jumps */ | ||
17349 | return nFixed; | ||
17350 | } | ||
17351 | /* | ||
17352 | * Reserve a room for a numeric constant [i.e: 64-bit integer or real number] | ||
17353 | * in the constant table. | ||
17354 | */ | ||
17355 | static jx9_value * GenStateInstallNumLiteral(jx9_gen_state *pGen, sxu32 *pIdx) | ||
17356 | { | ||
17357 | jx9_value *pObj; | ||
17358 | sxu32 nIdx = 0; /* cc warning */ | ||
17359 | /* Reserve a new constant */ | ||
17360 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17361 | if( pObj == 0 ){ | ||
17362 | GenStateOutOfMem(pGen); | ||
17363 | return 0; | ||
17364 | } | ||
17365 | *pIdx = nIdx; | ||
17366 | /* TODO(chems): Create a numeric table (64bit int keys) same as | ||
17367 | * the constant string iterals table [optimization purposes]. | ||
17368 | */ | ||
17369 | return pObj; | ||
17370 | } | ||
17371 | /* | ||
17372 | * Compile a numeric [i.e: integer or real] literal. | ||
17373 | * Notes on the integer type. | ||
17374 | * According to the JX9 language reference manual | ||
17375 | * Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) | ||
17376 | * or binary (base 2) notation, optionally preceded by a sign (- or +). | ||
17377 | * To use octal notation, precede the number with a 0 (zero). To use hexadecimal | ||
17378 | * notation precede the number with 0x. To use binary notation precede the number with 0b. | ||
17379 | */ | ||
17380 | static sxi32 jx9CompileNumLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
17381 | { | ||
17382 | SyToken *pToken = pGen->pIn; /* Raw token */ | ||
17383 | sxu32 nIdx = 0; | ||
17384 | if( pToken->nType & JX9_TK_INTEGER ){ | ||
17385 | jx9_value *pObj; | ||
17386 | sxi64 iValue; | ||
17387 | iValue = jx9TokenValueToInt64(&pToken->sData); | ||
17388 | pObj = GenStateInstallNumLiteral(&(*pGen), &nIdx); | ||
17389 | if( pObj == 0 ){ | ||
17390 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
17391 | return SXERR_ABORT; | ||
17392 | } | ||
17393 | jx9MemObjInitFromInt(pGen->pVm, pObj, iValue); | ||
17394 | }else{ | ||
17395 | /* Real number */ | ||
17396 | jx9_value *pObj; | ||
17397 | /* Reserve a new constant */ | ||
17398 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17399 | if( pObj == 0 ){ | ||
17400 | return GenStateOutOfMem(pGen); | ||
17401 | } | ||
17402 | jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); | ||
17403 | jx9MemObjToReal(pObj); | ||
17404 | } | ||
17405 | /* Emit the load constant instruction */ | ||
17406 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
17407 | /* Node successfully compiled */ | ||
17408 | return SXRET_OK; | ||
17409 | } | ||
17410 | /* | ||
17411 | * Compile a nowdoc string. | ||
17412 | * According to the JX9 language reference manual: | ||
17413 | * | ||
17414 | * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. | ||
17415 | * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. | ||
17416 | * The construct is ideal for embedding JX9 code or other large blocks of text without the | ||
17417 | * need for escaping. It shares some features in common with the SGML <![CDATA[ ]]> | ||
17418 | * construct, in that it declares a block of text which is not for parsing. | ||
17419 | * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier | ||
17420 | * which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc | ||
17421 | * identifiers also apply to nowdoc identifiers, especially those regarding the appearance | ||
17422 | * of the closing identifier. | ||
17423 | */ | ||
17424 | static sxi32 jx9CompileNowdoc(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
17425 | { | ||
17426 | SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ | ||
17427 | jx9_value *pObj; | ||
17428 | sxu32 nIdx; | ||
17429 | nIdx = 0; /* Prevent compiler warning */ | ||
17430 | if( pStr->nByte <= 0 ){ | ||
17431 | /* Empty string, load NULL */ | ||
17432 | jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC, 0, 0, 0, 0); | ||
17433 | return SXRET_OK; | ||
17434 | } | ||
17435 | /* Reserve a new constant */ | ||
17436 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17437 | if( pObj == 0 ){ | ||
17438 | jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "JX9 engine is running out of memory"); | ||
17439 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
17440 | return SXERR_ABORT; | ||
17441 | } | ||
17442 | /* No processing is done here, simply a memcpy() operation */ | ||
17443 | jx9MemObjInitFromString(pGen->pVm, pObj, pStr); | ||
17444 | /* Emit the load constant instruction */ | ||
17445 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
17446 | /* Node successfully compiled */ | ||
17447 | return SXRET_OK; | ||
17448 | } | ||
17449 | /* | ||
17450 | * Compile a single quoted string. | ||
17451 | * According to the JX9 language reference manual: | ||
17452 | * | ||
17453 | * The simplest way to specify a string is to enclose it in single quotes (the character ' ). | ||
17454 | * To specify a literal single quote, escape it with a backslash (\). To specify a literal | ||
17455 | * backslash, double it (\\). All other instances of backslash will be treated as a literal | ||
17456 | * backslash: this means that the other escape sequences you might be used to, such as \r | ||
17457 | * or \n, will be output literally as specified rather than having any special meaning. | ||
17458 | * | ||
17459 | */ | ||
17460 | JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
17461 | { | ||
17462 | SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ | ||
17463 | const char *zIn, *zCur, *zEnd; | ||
17464 | jx9_value *pObj; | ||
17465 | sxu32 nIdx; | ||
17466 | nIdx = 0; /* Prevent compiler warning */ | ||
17467 | /* Delimit the string */ | ||
17468 | zIn = pStr->zString; | ||
17469 | zEnd = &zIn[pStr->nByte]; | ||
17470 | if( zIn >= zEnd ){ | ||
17471 | /* Empty string, load NULL */ | ||
17472 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
17473 | return SXRET_OK; | ||
17474 | } | ||
17475 | if( SXRET_OK == GenStateFindLiteral(&(*pGen), pStr, &nIdx) ){ | ||
17476 | /* Already processed, emit the load constant instruction | ||
17477 | * and return. | ||
17478 | */ | ||
17479 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
17480 | return SXRET_OK; | ||
17481 | } | ||
17482 | /* Reserve a new constant */ | ||
17483 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17484 | if( pObj == 0 ){ | ||
17485 | jx9GenCompileError(&(*pGen), E_ERROR, 1, "JX9 engine is running out of memory"); | ||
17486 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
17487 | return SXERR_ABORT; | ||
17488 | } | ||
17489 | jx9MemObjInitFromString(pGen->pVm, pObj, 0); | ||
17490 | /* Compile the node */ | ||
17491 | for(;;){ | ||
17492 | if( zIn >= zEnd ){ | ||
17493 | /* End of input */ | ||
17494 | break; | ||
17495 | } | ||
17496 | zCur = zIn; | ||
17497 | while( zIn < zEnd && zIn[0] != '\\' ){ | ||
17498 | zIn++; | ||
17499 | } | ||
17500 | if( zIn > zCur ){ | ||
17501 | /* Append raw contents*/ | ||
17502 | jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); | ||
17503 | } | ||
17504 | zIn++; | ||
17505 | if( zIn < zEnd ){ | ||
17506 | if( zIn[0] == '\\' ){ | ||
17507 | /* A literal backslash */ | ||
17508 | jx9MemObjStringAppend(pObj, "\\", sizeof(char)); | ||
17509 | }else if( zIn[0] == '\'' ){ | ||
17510 | /* A single quote */ | ||
17511 | jx9MemObjStringAppend(pObj, "'", sizeof(char)); | ||
17512 | }else{ | ||
17513 | /* verbatim copy */ | ||
17514 | zIn--; | ||
17515 | jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2); | ||
17516 | zIn++; | ||
17517 | } | ||
17518 | } | ||
17519 | /* Advance the stream cursor */ | ||
17520 | zIn++; | ||
17521 | } | ||
17522 | /* Emit the load constant instruction */ | ||
17523 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
17524 | if( pStr->nByte < 1024 ){ | ||
17525 | /* Install in the literal table */ | ||
17526 | GenStateInstallLiteral(pGen, pObj, nIdx); | ||
17527 | } | ||
17528 | /* Node successfully compiled */ | ||
17529 | return SXRET_OK; | ||
17530 | } | ||
17531 | /* | ||
17532 | * Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string. | ||
17533 | * According to the JX9 language reference manual | ||
17534 | * When a string is specified in double quotes or with heredoc, variables are parsed within it. | ||
17535 | * There are two types of syntax: a simple one and a complex one. The simple syntax is the most | ||
17536 | * common and convenient. It provides a way to embed a variable, an array value, or an object | ||
17537 | * property in a string with a minimum of effort. | ||
17538 | * Simple syntax | ||
17539 | * If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible | ||
17540 | * to form a valid variable name. Enclose the variable name in curly braces to explicitly specify | ||
17541 | * the end of the name. | ||
17542 | * Similarly, an array index or an object property can be parsed. With array indices, the closing | ||
17543 | * square bracket (]) marks the end of the index. The same rules apply to object properties | ||
17544 | * as to simple variables. | ||
17545 | * Complex (curly) syntax | ||
17546 | * This isn't called complex because the syntax is complex, but because it allows for the use | ||
17547 | * of complex expressions. | ||
17548 | * Any scalar variable, array element or object property with a string representation can be | ||
17549 | * included via this syntax. Simply write the expression the same way as it would appear outside | ||
17550 | * the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only | ||
17551 | * be recognised when the $ immediately follows the {. Use {\$ to get a literal {$ | ||
17552 | */ | ||
17553 | static sxi32 GenStateProcessStringExpression( | ||
17554 | jx9_gen_state *pGen, /* Code generator state */ | ||
17555 | const char *zIn, /* Raw expression */ | ||
17556 | const char *zEnd /* End of the expression */ | ||
17557 | ) | ||
17558 | { | ||
17559 | SyToken *pTmpIn, *pTmpEnd; | ||
17560 | SySet sToken; | ||
17561 | sxi32 rc; | ||
17562 | /* Initialize the token set */ | ||
17563 | SySetInit(&sToken, &pGen->pVm->sAllocator, sizeof(SyToken)); | ||
17564 | /* Preallocate some slots */ | ||
17565 | SySetAlloc(&sToken, 0x08); | ||
17566 | /* Tokenize the text */ | ||
17567 | jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken); | ||
17568 | /* Swap delimiter */ | ||
17569 | pTmpIn = pGen->pIn; | ||
17570 | pTmpEnd = pGen->pEnd; | ||
17571 | pGen->pIn = (SyToken *)SySetBasePtr(&sToken); | ||
17572 | pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)]; | ||
17573 | /* Compile the expression */ | ||
17574 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
17575 | /* Restore token stream */ | ||
17576 | pGen->pIn = pTmpIn; | ||
17577 | pGen->pEnd = pTmpEnd; | ||
17578 | /* Release the token set */ | ||
17579 | SySetRelease(&sToken); | ||
17580 | /* Compilation result */ | ||
17581 | return rc; | ||
17582 | } | ||
17583 | /* | ||
17584 | * Reserve a new constant for a double quoted/heredoc string. | ||
17585 | */ | ||
17586 | static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount) | ||
17587 | { | ||
17588 | jx9_value *pConstObj; | ||
17589 | sxu32 nIdx = 0; | ||
17590 | /* Reserve a new constant */ | ||
17591 | pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17592 | if( pConstObj == 0 ){ | ||
17593 | GenStateOutOfMem(&(*pGen)); | ||
17594 | return 0; | ||
17595 | } | ||
17596 | (*pCount)++; | ||
17597 | jx9MemObjInitFromString(pGen->pVm, pConstObj, 0); | ||
17598 | /* Emit the load constant instruction */ | ||
17599 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
17600 | return pConstObj; | ||
17601 | } | ||
17602 | /* | ||
17603 | * Compile a double quoted/heredoc string. | ||
17604 | * According to the JX9 language reference manual | ||
17605 | * Heredoc | ||
17606 | * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier | ||
17607 | * is provided, then a newline. The string itself follows, and then the same identifier again | ||
17608 | * to close the quotation. | ||
17609 | * The closing identifier must begin in the first column of the line. Also, the identifier must | ||
17610 | * follow the same naming rules as any other label in JX9: it must contain only alphanumeric | ||
17611 | * characters and underscores, and must start with a non-digit character or underscore. | ||
17612 | * Warning | ||
17613 | * It is very important to note that the line with the closing identifier must contain | ||
17614 | * no other characters, except possibly a semicolon (;). That means especially that the identifier | ||
17615 | * may not be indented, and there may not be any spaces or tabs before or after the semicolon. | ||
17616 | * It's also important to realize that the first character before the closing identifier must | ||
17617 | * be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X. | ||
17618 | * The closing delimiter (possibly followed by a semicolon) must also be followed by a newline. | ||
17619 | * If this rule is broken and the closing identifier is not "clean", it will not be considered a closing | ||
17620 | * identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before | ||
17621 | * the end of the current file, a parse error will result at the last line. | ||
17622 | * Heredocs can not be used for initializing object properties. | ||
17623 | * Double quoted | ||
17624 | * If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters: | ||
17625 | * Escaped characters Sequence Meaning | ||
17626 | * \n linefeed (LF or 0x0A (10) in ASCII) | ||
17627 | * \r carriage return (CR or 0x0D (13) in ASCII) | ||
17628 | * \t horizontal tab (HT or 0x09 (9) in ASCII) | ||
17629 | * \v vertical tab (VT or 0x0B (11) in ASCII) | ||
17630 | * \f form feed (FF or 0x0C (12) in ASCII) | ||
17631 | * \\ backslash | ||
17632 | * \$ dollar sign | ||
17633 | * \" double-quote | ||
17634 | * \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation | ||
17635 | * \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation | ||
17636 | * As in single quoted strings, escaping any other character will result in the backslash being printed too. | ||
17637 | * The most important feature of double-quoted strings is the fact that variable names will be expanded. | ||
17638 | * See string parsing for details. | ||
17639 | */ | ||
17640 | static sxi32 GenStateCompileString(jx9_gen_state *pGen) | ||
17641 | { | ||
17642 | SyString *pStr = &pGen->pIn->sData; /* Raw token value */ | ||
17643 | const char *zIn, *zCur, *zEnd; | ||
17644 | jx9_value *pObj = 0; | ||
17645 | sxi32 iCons; | ||
17646 | sxi32 rc; | ||
17647 | /* Delimit the string */ | ||
17648 | zIn = pStr->zString; | ||
17649 | zEnd = &zIn[pStr->nByte]; | ||
17650 | if( zIn >= zEnd ){ | ||
17651 | /* Empty string, load NULL */ | ||
17652 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
17653 | return SXRET_OK; | ||
17654 | } | ||
17655 | zCur = 0; | ||
17656 | /* Compile the node */ | ||
17657 | iCons = 0; | ||
17658 | for(;;){ | ||
17659 | zCur = zIn; | ||
17660 | while( zIn < zEnd && zIn[0] != '\\' ){ | ||
17661 | if(zIn[0] == '$' && &zIn[1] < zEnd && | ||
17662 | (((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){ | ||
17663 | break; | ||
17664 | } | ||
17665 | zIn++; | ||
17666 | } | ||
17667 | if( zIn > zCur ){ | ||
17668 | if( pObj == 0 ){ | ||
17669 | pObj = GenStateNewStrObj(&(*pGen), &iCons); | ||
17670 | if( pObj == 0 ){ | ||
17671 | return SXERR_ABORT; | ||
17672 | } | ||
17673 | } | ||
17674 | jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); | ||
17675 | } | ||
17676 | if( zIn >= zEnd ){ | ||
17677 | break; | ||
17678 | } | ||
17679 | if( zIn[0] == '\\' ){ | ||
17680 | const char *zPtr = 0; | ||
17681 | sxu32 n; | ||
17682 | zIn++; | ||
17683 | if( zIn >= zEnd ){ | ||
17684 | break; | ||
17685 | } | ||
17686 | if( pObj == 0 ){ | ||
17687 | pObj = GenStateNewStrObj(&(*pGen), &iCons); | ||
17688 | if( pObj == 0 ){ | ||
17689 | return SXERR_ABORT; | ||
17690 | } | ||
17691 | } | ||
17692 | n = sizeof(char); /* size of conversion */ | ||
17693 | switch( zIn[0] ){ | ||
17694 | case '$': | ||
17695 | /* Dollar sign */ | ||
17696 | jx9MemObjStringAppend(pObj, "$", sizeof(char)); | ||
17697 | break; | ||
17698 | case '\\': | ||
17699 | /* A literal backslash */ | ||
17700 | jx9MemObjStringAppend(pObj, "\\", sizeof(char)); | ||
17701 | break; | ||
17702 | case 'a': | ||
17703 | /* The "alert" character (BEL)[ctrl+g] ASCII code 7 */ | ||
17704 | jx9MemObjStringAppend(pObj, "\a", sizeof(char)); | ||
17705 | break; | ||
17706 | case 'b': | ||
17707 | /* Backspace (BS)[ctrl+h] ASCII code 8 */ | ||
17708 | jx9MemObjStringAppend(pObj, "\b", sizeof(char)); | ||
17709 | break; | ||
17710 | case 'f': | ||
17711 | /* Form-feed (FF)[ctrl+l] ASCII code 12 */ | ||
17712 | jx9MemObjStringAppend(pObj, "\f", sizeof(char)); | ||
17713 | break; | ||
17714 | case 'n': | ||
17715 | /* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */ | ||
17716 | jx9MemObjStringAppend(pObj, "\n", sizeof(char)); | ||
17717 | break; | ||
17718 | case 'r': | ||
17719 | /* Carriage return (CR)[ctrl+m] ASCII code 13 */ | ||
17720 | jx9MemObjStringAppend(pObj, "\r", sizeof(char)); | ||
17721 | break; | ||
17722 | case 't': | ||
17723 | /* Horizontal tab (HT)[ctrl+i] ASCII code 9 */ | ||
17724 | jx9MemObjStringAppend(pObj, "\t", sizeof(char)); | ||
17725 | break; | ||
17726 | case 'v': | ||
17727 | /* Vertical tab(VT)[ctrl+k] ASCII code 11 */ | ||
17728 | jx9MemObjStringAppend(pObj, "\v", sizeof(char)); | ||
17729 | break; | ||
17730 | case '\'': | ||
17731 | /* Single quote */ | ||
17732 | jx9MemObjStringAppend(pObj, "'", sizeof(char)); | ||
17733 | break; | ||
17734 | case '"': | ||
17735 | /* Double quote */ | ||
17736 | jx9MemObjStringAppend(pObj, "\"", sizeof(char)); | ||
17737 | break; | ||
17738 | case '0': | ||
17739 | /* NUL byte */ | ||
17740 | jx9MemObjStringAppend(pObj, "\0", sizeof(char)); | ||
17741 | break; | ||
17742 | case 'x': | ||
17743 | if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){ | ||
17744 | int c; | ||
17745 | /* Hex digit */ | ||
17746 | c = SyHexToint(zIn[1]) << 4; | ||
17747 | if( &zIn[2] < zEnd ){ | ||
17748 | c += SyHexToint(zIn[2]); | ||
17749 | } | ||
17750 | /* Output char */ | ||
17751 | jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); | ||
17752 | n += sizeof(char) * 2; | ||
17753 | }else{ | ||
17754 | /* Output literal character */ | ||
17755 | jx9MemObjStringAppend(pObj, "x", sizeof(char)); | ||
17756 | } | ||
17757 | break; | ||
17758 | case 'o': | ||
17759 | if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){ | ||
17760 | /* Octal digit stream */ | ||
17761 | int c; | ||
17762 | c = 0; | ||
17763 | zIn++; | ||
17764 | for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){ | ||
17765 | if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){ | ||
17766 | break; | ||
17767 | } | ||
17768 | c = c * 8 + (zPtr[0] - '0'); | ||
17769 | } | ||
17770 | if ( c > 0 ){ | ||
17771 | jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); | ||
17772 | } | ||
17773 | n = (sxu32)(zPtr-zIn); | ||
17774 | }else{ | ||
17775 | /* Output literal character */ | ||
17776 | jx9MemObjStringAppend(pObj, "o", sizeof(char)); | ||
17777 | } | ||
17778 | break; | ||
17779 | default: | ||
17780 | /* Output without a slash */ | ||
17781 | jx9MemObjStringAppend(pObj, zIn, sizeof(char)); | ||
17782 | break; | ||
17783 | } | ||
17784 | /* Advance the stream cursor */ | ||
17785 | zIn += n; | ||
17786 | continue; | ||
17787 | } | ||
17788 | if( zIn[0] == '{' ){ | ||
17789 | /* Curly syntax */ | ||
17790 | const char *zExpr; | ||
17791 | sxi32 iNest = 1; | ||
17792 | zIn++; | ||
17793 | zExpr = zIn; | ||
17794 | /* Synchronize with the next closing curly braces */ | ||
17795 | while( zIn < zEnd ){ | ||
17796 | if( zIn[0] == '{' ){ | ||
17797 | /* Increment nesting level */ | ||
17798 | iNest++; | ||
17799 | }else if(zIn[0] == '}' ){ | ||
17800 | /* Decrement nesting level */ | ||
17801 | iNest--; | ||
17802 | if( iNest <= 0 ){ | ||
17803 | break; | ||
17804 | } | ||
17805 | } | ||
17806 | zIn++; | ||
17807 | } | ||
17808 | /* Process the expression */ | ||
17809 | rc = GenStateProcessStringExpression(&(*pGen),zExpr,zIn); | ||
17810 | if( rc == SXERR_ABORT ){ | ||
17811 | return SXERR_ABORT; | ||
17812 | } | ||
17813 | if( rc != SXERR_EMPTY ){ | ||
17814 | ++iCons; | ||
17815 | } | ||
17816 | if( zIn < zEnd ){ | ||
17817 | /* Jump the trailing curly */ | ||
17818 | zIn++; | ||
17819 | } | ||
17820 | }else{ | ||
17821 | /* Simple syntax */ | ||
17822 | const char *zExpr = zIn; | ||
17823 | /* Assemble variable name */ | ||
17824 | for(;;){ | ||
17825 | /* Jump leading dollars */ | ||
17826 | while( zIn < zEnd && zIn[0] == '$' ){ | ||
17827 | zIn++; | ||
17828 | } | ||
17829 | for(;;){ | ||
17830 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){ | ||
17831 | zIn++; | ||
17832 | } | ||
17833 | if((unsigned char)zIn[0] >= 0xc0 ){ | ||
17834 | /* UTF-8 stream */ | ||
17835 | zIn++; | ||
17836 | while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ | ||
17837 | zIn++; | ||
17838 | } | ||
17839 | continue; | ||
17840 | } | ||
17841 | break; | ||
17842 | } | ||
17843 | if( zIn >= zEnd ){ | ||
17844 | break; | ||
17845 | } | ||
17846 | if( zIn[0] == '[' ){ | ||
17847 | sxi32 iSquare = 1; | ||
17848 | zIn++; | ||
17849 | while( zIn < zEnd ){ | ||
17850 | if( zIn[0] == '[' ){ | ||
17851 | iSquare++; | ||
17852 | }else if (zIn[0] == ']' ){ | ||
17853 | iSquare--; | ||
17854 | if( iSquare <= 0 ){ | ||
17855 | break; | ||
17856 | } | ||
17857 | } | ||
17858 | zIn++; | ||
17859 | } | ||
17860 | if( zIn < zEnd ){ | ||
17861 | zIn++; | ||
17862 | } | ||
17863 | break; | ||
17864 | }else if( zIn[0] == '.' ){ | ||
17865 | /* Member access operator '.' */ | ||
17866 | zIn++; | ||
17867 | }else{ | ||
17868 | break; | ||
17869 | } | ||
17870 | } | ||
17871 | /* Process the expression */ | ||
17872 | rc = GenStateProcessStringExpression(&(*pGen),zExpr, zIn); | ||
17873 | if( rc == SXERR_ABORT ){ | ||
17874 | return SXERR_ABORT; | ||
17875 | } | ||
17876 | if( rc != SXERR_EMPTY ){ | ||
17877 | ++iCons; | ||
17878 | } | ||
17879 | } | ||
17880 | /* Invalidate the previously used constant */ | ||
17881 | pObj = 0; | ||
17882 | }/*for(;;)*/ | ||
17883 | if( iCons > 1 ){ | ||
17884 | /* Concatenate all compiled constants */ | ||
17885 | jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0); | ||
17886 | } | ||
17887 | /* Node successfully compiled */ | ||
17888 | return SXRET_OK; | ||
17889 | } | ||
17890 | /* | ||
17891 | * Compile a double quoted string. | ||
17892 | * See the block-comment above for more information. | ||
17893 | */ | ||
17894 | JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
17895 | { | ||
17896 | sxi32 rc; | ||
17897 | rc = GenStateCompileString(&(*pGen)); | ||
17898 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
17899 | /* Compilation result */ | ||
17900 | return rc; | ||
17901 | } | ||
17902 | /* | ||
17903 | * Compile a literal which is an identifier(name) for simple values. | ||
17904 | */ | ||
17905 | JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
17906 | { | ||
17907 | SyToken *pToken = pGen->pIn; | ||
17908 | jx9_value *pObj; | ||
17909 | SyString *pStr; | ||
17910 | sxu32 nIdx; | ||
17911 | /* Extract token value */ | ||
17912 | pStr = &pToken->sData; | ||
17913 | /* Deal with the reserved literals [i.e: null, false, true, ...] first */ | ||
17914 | if( pStr->nByte == sizeof("NULL") - 1 ){ | ||
17915 | if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){ | ||
17916 | /* NULL constant are always indexed at 0 */ | ||
17917 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
17918 | return SXRET_OK; | ||
17919 | }else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){ | ||
17920 | /* TRUE constant are always indexed at 1 */ | ||
17921 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1, 0, 0); | ||
17922 | return SXRET_OK; | ||
17923 | } | ||
17924 | }else if (pStr->nByte == sizeof("FALSE") - 1 && | ||
17925 | SyStrnicmp(pStr->zString, "false", sizeof("FALSE")-1) == 0 ){ | ||
17926 | /* FALSE constant are always indexed at 2 */ | ||
17927 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 2, 0, 0); | ||
17928 | return SXRET_OK; | ||
17929 | }else if(pStr->nByte == sizeof("__LINE__") - 1 && | ||
17930 | SyMemcmp(pStr->zString, "__LINE__", sizeof("__LINE__")-1) == 0 ){ | ||
17931 | /* TICKET 1433-004: __LINE__ constant must be resolved at compile time, not run time */ | ||
17932 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17933 | if( pObj == 0 ){ | ||
17934 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
17935 | return GenStateOutOfMem(pGen); | ||
17936 | } | ||
17937 | jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine); | ||
17938 | /* Emit the load constant instruction */ | ||
17939 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
17940 | return SXRET_OK; | ||
17941 | }else if( pStr->nByte == sizeof("__FUNCTION__") - 1 && | ||
17942 | SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-1) == 0 ){ | ||
17943 | GenBlock *pBlock = pGen->pCurrent; | ||
17944 | /* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time, not run time */ | ||
17945 | while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){ | ||
17946 | /* Point to the upper block */ | ||
17947 | pBlock = pBlock->pParent; | ||
17948 | } | ||
17949 | if( pBlock == 0 ){ | ||
17950 | /* Called in the global scope, load NULL */ | ||
17951 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); | ||
17952 | }else{ | ||
17953 | /* Extract the target function/method */ | ||
17954 | jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData; | ||
17955 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17956 | if( pObj == 0 ){ | ||
17957 | return GenStateOutOfMem(pGen); | ||
17958 | } | ||
17959 | jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName); | ||
17960 | /* Emit the load constant instruction */ | ||
17961 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
17962 | } | ||
17963 | return SXRET_OK; | ||
17964 | } | ||
17965 | /* Query literal table */ | ||
17966 | if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){ | ||
17967 | jx9_value *pObj; | ||
17968 | /* Unknown literal, install it in the literal table */ | ||
17969 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
17970 | if( pObj == 0 ){ | ||
17971 | return GenStateOutOfMem(pGen); | ||
17972 | } | ||
17973 | jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); | ||
17974 | GenStateInstallLiteral(&(*pGen), pObj, nIdx); | ||
17975 | } | ||
17976 | /* Emit the load constant instruction */ | ||
17977 | jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0); | ||
17978 | /* Node successfully compiled */ | ||
17979 | return SXRET_OK; | ||
17980 | } | ||
17981 | /* | ||
17982 | * Compile an array entry whether it is a key or a value. | ||
17983 | */ | ||
17984 | static sxi32 GenStateCompileJSONEntry( | ||
17985 | jx9_gen_state *pGen, /* Code generator state */ | ||
17986 | SyToken *pIn, /* Token stream */ | ||
17987 | SyToken *pEnd, /* End of the token stream */ | ||
17988 | sxi32 iFlags, /* Compilation flags */ | ||
17989 | sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */ | ||
17990 | ) | ||
17991 | { | ||
17992 | SyToken *pTmpIn, *pTmpEnd; | ||
17993 | sxi32 rc; | ||
17994 | /* Swap token stream */ | ||
17995 | SWAP_DELIMITER(pGen, pIn, pEnd); | ||
17996 | /* Compile the expression*/ | ||
17997 | rc = jx9CompileExpr(&(*pGen), iFlags, xValidator); | ||
17998 | /* Restore token stream */ | ||
17999 | RE_SWAP_DELIMITER(pGen); | ||
18000 | return rc; | ||
18001 | } | ||
18002 | /* | ||
18003 | * Compile a Jx9 JSON Array. | ||
18004 | */ | ||
18005 | JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
18006 | { | ||
18007 | sxi32 nPair = 0; | ||
18008 | SyToken *pCur; | ||
18009 | sxi32 rc; | ||
18010 | |||
18011 | pGen->pIn++; /* Jump the open square bracket '['*/ | ||
18012 | pGen->pEnd--; | ||
18013 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
18014 | for(;;){ | ||
18015 | /* Jump leading commas */ | ||
18016 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ | ||
18017 | pGen->pIn++; | ||
18018 | } | ||
18019 | pCur = pGen->pIn; | ||
18020 | if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ | ||
18021 | /* No more entry to process */ | ||
18022 | break; | ||
18023 | } | ||
18024 | /* Compile entry */ | ||
18025 | rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); | ||
18026 | if( rc == SXERR_ABORT ){ | ||
18027 | return SXERR_ABORT; | ||
18028 | } | ||
18029 | nPair++; | ||
18030 | } | ||
18031 | /* Emit the load map instruction */ | ||
18032 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0); | ||
18033 | /* Node successfully compiled */ | ||
18034 | return SXRET_OK; | ||
18035 | } | ||
18036 | /* | ||
18037 | * Node validator for a given JSON key. | ||
18038 | */ | ||
18039 | static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) | ||
18040 | { | ||
18041 | sxi32 rc = SXRET_OK; | ||
18042 | if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString | ||
18043 | && pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){ | ||
18044 | /* Unexpected expression */ | ||
18045 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0, | ||
18046 | "JSON Object: Unexpected expression, key must be of type string, literal or simple variable"); | ||
18047 | if( rc != SXERR_ABORT ){ | ||
18048 | rc = SXERR_INVALID; | ||
18049 | } | ||
18050 | } | ||
18051 | return rc; | ||
18052 | } | ||
18053 | /* | ||
18054 | * Compile a Jx9 JSON Object | ||
18055 | */ | ||
18056 | JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag) | ||
18057 | { | ||
18058 | SyToken *pKey, *pCur; | ||
18059 | sxi32 nPair = 0; | ||
18060 | sxi32 rc; | ||
18061 | |||
18062 | pGen->pIn++; /* Jump the open querly braces '{'*/ | ||
18063 | pGen->pEnd--; | ||
18064 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
18065 | for(;;){ | ||
18066 | /* Jump leading commas */ | ||
18067 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ | ||
18068 | pGen->pIn++; | ||
18069 | } | ||
18070 | pCur = pGen->pIn; | ||
18071 | if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ | ||
18072 | /* No more entry to process */ | ||
18073 | break; | ||
18074 | } | ||
18075 | /* Compile the key */ | ||
18076 | pKey = pCur; | ||
18077 | while( pCur < pGen->pIn ){ | ||
18078 | if( pCur->nType & JX9_TK_COLON /*':'*/ ){ | ||
18079 | break; | ||
18080 | } | ||
18081 | pCur++; | ||
18082 | } | ||
18083 | rc = SXERR_EMPTY; | ||
18084 | if( pCur < pGen->pIn ){ | ||
18085 | if( &pCur[1] >= pGen->pIn ){ | ||
18086 | /* Missing value */ | ||
18087 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value"); | ||
18088 | if( rc == SXERR_ABORT ){ | ||
18089 | return SXERR_ABORT; | ||
18090 | } | ||
18091 | return SXRET_OK; | ||
18092 | } | ||
18093 | /* Compile the expression holding the key */ | ||
18094 | rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur, | ||
18095 | EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */, | ||
18096 | GenStateJSONObjectKeyNodeValidator /* Node validator callback */ | ||
18097 | ); | ||
18098 | if( rc == SXERR_ABORT ){ | ||
18099 | return SXERR_ABORT; | ||
18100 | } | ||
18101 | pCur++; /* Jump the double colon ':' */ | ||
18102 | }else if( pKey == pCur ){ | ||
18103 | /* Key is omitted, emit an error */ | ||
18104 | jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key"); | ||
18105 | pCur++; /* Jump the double colon ':' */ | ||
18106 | }else{ | ||
18107 | /* Reset back the cursor and point to the entry value */ | ||
18108 | pCur = pKey; | ||
18109 | } | ||
18110 | /* Compile indice value */ | ||
18111 | rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); | ||
18112 | if( rc == SXERR_ABORT ){ | ||
18113 | return SXERR_ABORT; | ||
18114 | } | ||
18115 | nPair++; | ||
18116 | } | ||
18117 | /* Emit the load map instruction */ | ||
18118 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0); | ||
18119 | /* Node successfully compiled */ | ||
18120 | return SXRET_OK; | ||
18121 | } | ||
18122 | /* | ||
18123 | * Compile a function [i.e: print, exit(), include(), ...] which is a langauge | ||
18124 | * construct. | ||
18125 | */ | ||
18126 | JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
18127 | { | ||
18128 | SyString *pName; | ||
18129 | sxu32 nKeyID; | ||
18130 | sxi32 rc; | ||
18131 | /* Name of the language construct [i.e: print, die...]*/ | ||
18132 | pName = &pGen->pIn->sData; | ||
18133 | nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
18134 | pGen->pIn++; /* Jump the language construct keyword */ | ||
18135 | if( nKeyID == JX9_TKWRD_PRINT ){ | ||
18136 | SyToken *pTmp, *pNext = 0; | ||
18137 | /* Compile arguments one after one */ | ||
18138 | pTmp = pGen->pEnd; | ||
18139 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0); | ||
18140 | while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ | ||
18141 | if( pGen->pIn < pNext ){ | ||
18142 | pGen->pEnd = pNext; | ||
18143 | rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); | ||
18144 | if( rc == SXERR_ABORT ){ | ||
18145 | return SXERR_ABORT; | ||
18146 | } | ||
18147 | if( rc != SXERR_EMPTY ){ | ||
18148 | /* Ticket 1433-008: Optimization #1: Consume input directly | ||
18149 | * without the overhead of a function call. | ||
18150 | * This is a very powerful optimization that improve | ||
18151 | * performance greatly. | ||
18152 | */ | ||
18153 | jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0); | ||
18154 | } | ||
18155 | } | ||
18156 | /* Jump trailing commas */ | ||
18157 | while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){ | ||
18158 | pNext++; | ||
18159 | } | ||
18160 | pGen->pIn = pNext; | ||
18161 | } | ||
18162 | /* Restore token stream */ | ||
18163 | pGen->pEnd = pTmp; | ||
18164 | }else{ | ||
18165 | sxi32 nArg = 0; | ||
18166 | sxu32 nIdx = 0; | ||
18167 | rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); | ||
18168 | if( rc == SXERR_ABORT ){ | ||
18169 | return SXERR_ABORT; | ||
18170 | }else if(rc != SXERR_EMPTY ){ | ||
18171 | nArg = 1; | ||
18172 | } | ||
18173 | if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){ | ||
18174 | jx9_value *pObj; | ||
18175 | /* Emit the call instruction */ | ||
18176 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
18177 | if( pObj == 0 ){ | ||
18178 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
18179 | return GenStateOutOfMem(pGen); | ||
18180 | } | ||
18181 | jx9MemObjInitFromString(pGen->pVm, pObj, pName); | ||
18182 | /* Install in the literal table */ | ||
18183 | GenStateInstallLiteral(&(*pGen), pObj, nIdx); | ||
18184 | } | ||
18185 | /* Emit the call instruction */ | ||
18186 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
18187 | jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0); | ||
18188 | } | ||
18189 | /* Node successfully compiled */ | ||
18190 | return SXRET_OK; | ||
18191 | } | ||
18192 | /* | ||
18193 | * Compile a node holding a variable declaration. | ||
18194 | * According to the J9X language reference | ||
18195 | * Variables in JX9 are represented by a dollar sign followed by the name of the variable. | ||
18196 | * The variable name is case-sensitive. | ||
18197 | * Variable names follow the same rules as other labels in JX9. A valid variable name | ||
18198 | * starts with a letter, underscore or any UTF-8 stream, followed by any number of letters | ||
18199 | * numbers, or underscores. | ||
18200 | * By default, variables are always assigned by value unless the target value is a JSON | ||
18201 | * array or a JSON object which is passed by reference. | ||
18202 | */ | ||
18203 | JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
18204 | { | ||
18205 | sxu32 nLine = pGen->pIn->nLine; | ||
18206 | SyHashEntry *pEntry; | ||
18207 | SyString *pName; | ||
18208 | char *zName = 0; | ||
18209 | sxi32 iP1; | ||
18210 | void *p3; | ||
18211 | sxi32 rc; | ||
18212 | |||
18213 | pGen->pIn++; /* Jump the dollar sign '$' */ | ||
18214 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
18215 | /* Invalid variable name */ | ||
18216 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name"); | ||
18217 | if( rc == SXERR_ABORT ){ | ||
18218 | /* Error count limit reached, abort immediately */ | ||
18219 | return SXERR_ABORT; | ||
18220 | } | ||
18221 | return SXRET_OK; | ||
18222 | } | ||
18223 | /* Extract variable name */ | ||
18224 | pName = &pGen->pIn->sData; | ||
18225 | /* Advance the stream cursor */ | ||
18226 | pGen->pIn++; | ||
18227 | pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte); | ||
18228 | if( pEntry == 0 ){ | ||
18229 | /* Duplicate name */ | ||
18230 | zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); | ||
18231 | if( zName == 0 ){ | ||
18232 | return GenStateOutOfMem(pGen); | ||
18233 | } | ||
18234 | /* Install in the hashtable */ | ||
18235 | SyHashInsert(&pGen->hVar, zName, pName->nByte, zName); | ||
18236 | }else{ | ||
18237 | /* Name already available */ | ||
18238 | zName = (char *)pEntry->pUserData; | ||
18239 | } | ||
18240 | p3 = (void *)zName; | ||
18241 | iP1 = 0; | ||
18242 | if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){ | ||
18243 | if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){ | ||
18244 | /* Read-only load.In other words do not create the variable if inexistant */ | ||
18245 | iP1 = 1; | ||
18246 | } | ||
18247 | } | ||
18248 | /* Emit the load instruction */ | ||
18249 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0); | ||
18250 | /* Node successfully compiled */ | ||
18251 | return SXRET_OK; | ||
18252 | } | ||
18253 | /* Forward declaration */ | ||
18254 | static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc); | ||
18255 | /* | ||
18256 | * Compile an annoynmous function or a closure. | ||
18257 | * According to the JX9 language reference | ||
18258 | * Anonymous functions, also known as closures, allow the creation of functions | ||
18259 | * which have no specified name. They are most useful as the value of callback | ||
18260 | * parameters, but they have many other uses. Closures can also be used as | ||
18261 | * the values of variables; Assigning a closure to a variable uses the same | ||
18262 | * syntax as any other assignment, including the trailing semicolon: | ||
18263 | * Example Anonymous function variable assignment example | ||
18264 | * $greet = function($name) | ||
18265 | * { | ||
18266 | * printf("Hello %s\r\n", $name); | ||
18267 | * }; | ||
18268 | * $greet('World'); | ||
18269 | * $greet('JX9'); | ||
18270 | * Note that the implementation of annoynmous function and closure under | ||
18271 | * JX9 is completely different from the one used by the engine. | ||
18272 | */ | ||
18273 | JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag) | ||
18274 | { | ||
18275 | jx9_vm_func *pAnnonFunc; /* Annonymous function body */ | ||
18276 | char zName[512]; /* Unique lambda name */ | ||
18277 | static int iCnt = 1; /* There is no worry about thread-safety here, because only | ||
18278 | * one thread is allowed to compile the script. | ||
18279 | */ | ||
18280 | jx9_value *pObj; | ||
18281 | SyString sName; | ||
18282 | sxu32 nIdx; | ||
18283 | sxu32 nLen; | ||
18284 | sxi32 rc; | ||
18285 | |||
18286 | pGen->pIn++; /* Jump the 'function' keyword */ | ||
18287 | if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ | ||
18288 | pGen->pIn++; | ||
18289 | } | ||
18290 | /* Reserve a constant for the lambda */ | ||
18291 | pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); | ||
18292 | if( pObj == 0 ){ | ||
18293 | GenStateOutOfMem(pGen); | ||
18294 | SXUNUSED(iCompileFlag); /* cc warning */ | ||
18295 | return SXERR_ABORT; | ||
18296 | } | ||
18297 | /* Generate a unique name */ | ||
18298 | nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); | ||
18299 | /* Make sure the generated name is unique */ | ||
18300 | while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){ | ||
18301 | nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); | ||
18302 | } | ||
18303 | SyStringInitFromBuf(&sName, zName, nLen); | ||
18304 | jx9MemObjInitFromString(pGen->pVm, pObj, &sName); | ||
18305 | /* Compile the lambda body */ | ||
18306 | rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc); | ||
18307 | if( rc == SXERR_ABORT ){ | ||
18308 | return SXERR_ABORT; | ||
18309 | } | ||
18310 | /* Emit the load constant instruction */ | ||
18311 | jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); | ||
18312 | /* Node successfully compiled */ | ||
18313 | return SXRET_OK; | ||
18314 | } | ||
18315 | /* | ||
18316 | * Compile the 'continue' statement. | ||
18317 | * According to the JX9 language reference | ||
18318 | * continue is used within looping structures to skip the rest of the current loop iteration | ||
18319 | * and continue execution at the condition evaluation and then the beginning of the next | ||
18320 | * iteration. | ||
18321 | * Note: Note that in JX9 the switch statement is considered a looping structure for | ||
18322 | * the purposes of continue. | ||
18323 | * continue accepts an optional numeric argument which tells it how many levels | ||
18324 | * of enclosing loops it should skip to the end of. | ||
18325 | * Note: | ||
18326 | * continue 0; and continue 1; is the same as running continue;. | ||
18327 | */ | ||
18328 | static sxi32 jx9CompileContinue(jx9_gen_state *pGen) | ||
18329 | { | ||
18330 | GenBlock *pLoop; /* Target loop */ | ||
18331 | sxi32 iLevel; /* How many nesting loop to skip */ | ||
18332 | sxu32 nLine; | ||
18333 | sxi32 rc; | ||
18334 | nLine = pGen->pIn->nLine; | ||
18335 | iLevel = 0; | ||
18336 | /* Jump the 'continue' keyword */ | ||
18337 | pGen->pIn++; | ||
18338 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ | ||
18339 | /* optional numeric argument which tells us how many levels | ||
18340 | * of enclosing loops we should skip to the end of. | ||
18341 | */ | ||
18342 | iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); | ||
18343 | if( iLevel < 2 ){ | ||
18344 | iLevel = 0; | ||
18345 | } | ||
18346 | pGen->pIn++; /* Jump the optional numeric argument */ | ||
18347 | } | ||
18348 | /* Point to the target loop */ | ||
18349 | pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); | ||
18350 | if( pLoop == 0 ){ | ||
18351 | /* Illegal continue */ | ||
18352 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch"); | ||
18353 | if( rc == SXERR_ABORT ){ | ||
18354 | /* Error count limit reached, abort immediately */ | ||
18355 | return SXERR_ABORT; | ||
18356 | } | ||
18357 | }else{ | ||
18358 | sxu32 nInstrIdx = 0; | ||
18359 | /* Emit the unconditional jump to the beginning of the target loop */ | ||
18360 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx); | ||
18361 | if( pLoop->bPostContinue == TRUE ){ | ||
18362 | JumpFixup sJumpFix; | ||
18363 | /* Post-continue */ | ||
18364 | sJumpFix.nJumpType = JX9_OP_JMP; | ||
18365 | sJumpFix.nInstrIdx = nInstrIdx; | ||
18366 | SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix); | ||
18367 | } | ||
18368 | } | ||
18369 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
18370 | /* Not so fatal, emit a warning only */ | ||
18371 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement"); | ||
18372 | } | ||
18373 | /* Statement successfully compiled */ | ||
18374 | return SXRET_OK; | ||
18375 | } | ||
18376 | /* | ||
18377 | * Compile the 'break' statement. | ||
18378 | * According to the JX9 language reference | ||
18379 | * break ends execution of the current for, foreach, while, do-while or switch | ||
18380 | * structure. | ||
18381 | * break accepts an optional numeric argument which tells it how many nested | ||
18382 | * enclosing structures are to be broken out of. | ||
18383 | */ | ||
18384 | static sxi32 jx9CompileBreak(jx9_gen_state *pGen) | ||
18385 | { | ||
18386 | GenBlock *pLoop; /* Target loop */ | ||
18387 | sxi32 iLevel; /* How many nesting loop to skip */ | ||
18388 | sxu32 nLine; | ||
18389 | sxi32 rc; | ||
18390 | nLine = pGen->pIn->nLine; | ||
18391 | iLevel = 0; | ||
18392 | /* Jump the 'break' keyword */ | ||
18393 | pGen->pIn++; | ||
18394 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ | ||
18395 | /* optional numeric argument which tells us how many levels | ||
18396 | * of enclosing loops we should skip to the end of. | ||
18397 | */ | ||
18398 | iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); | ||
18399 | if( iLevel < 2 ){ | ||
18400 | iLevel = 0; | ||
18401 | } | ||
18402 | pGen->pIn++; /* Jump the optional numeric argument */ | ||
18403 | } | ||
18404 | /* Extract the target loop */ | ||
18405 | pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); | ||
18406 | if( pLoop == 0 ){ | ||
18407 | /* Illegal break */ | ||
18408 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch"); | ||
18409 | if( rc == SXERR_ABORT ){ | ||
18410 | /* Error count limit reached, abort immediately */ | ||
18411 | return SXERR_ABORT; | ||
18412 | } | ||
18413 | }else{ | ||
18414 | sxu32 nInstrIdx; | ||
18415 | rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx); | ||
18416 | if( rc == SXRET_OK ){ | ||
18417 | /* Fix the jump later when the jump destination is resolved */ | ||
18418 | GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx); | ||
18419 | } | ||
18420 | } | ||
18421 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
18422 | /* Not so fatal, emit a warning only */ | ||
18423 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement"); | ||
18424 | } | ||
18425 | /* Statement successfully compiled */ | ||
18426 | return SXRET_OK; | ||
18427 | } | ||
18428 | /* Forward declaration */ | ||
18429 | static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags); | ||
18430 | /* | ||
18431 | * Compile a JX9 block. | ||
18432 | * A block is simply one or more JX9 statements and expressions to compile | ||
18433 | * optionally delimited by braces {}. | ||
18434 | * Return SXRET_OK on success. Any other return value indicates failure | ||
18435 | * and this function takes care of generating the appropriate error | ||
18436 | * message. | ||
18437 | */ | ||
18438 | static sxi32 jx9CompileBlock( | ||
18439 | jx9_gen_state *pGen /* Code generator state */ | ||
18440 | ) | ||
18441 | { | ||
18442 | sxi32 rc; | ||
18443 | if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){ | ||
18444 | sxu32 nLine = pGen->pIn->nLine; | ||
18445 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(pGen->pVm), 0, 0); | ||
18446 | if( rc != SXRET_OK ){ | ||
18447 | return SXERR_ABORT; | ||
18448 | } | ||
18449 | pGen->pIn++; | ||
18450 | /* Compile until we hit the closing braces '}' */ | ||
18451 | for(;;){ | ||
18452 | if( pGen->pIn >= pGen->pEnd ){ | ||
18453 | /* No more token to process. Missing closing braces */ | ||
18454 | jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'"); | ||
18455 | break; | ||
18456 | } | ||
18457 | if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){ | ||
18458 | /* Closing braces found, break immediately*/ | ||
18459 | pGen->pIn++; | ||
18460 | break; | ||
18461 | } | ||
18462 | /* Compile a single statement */ | ||
18463 | rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); | ||
18464 | if( rc == SXERR_ABORT ){ | ||
18465 | return SXERR_ABORT; | ||
18466 | } | ||
18467 | } | ||
18468 | GenStateLeaveBlock(&(*pGen), 0); | ||
18469 | }else{ | ||
18470 | /* Compile a single statement */ | ||
18471 | rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); | ||
18472 | if( rc == SXERR_ABORT ){ | ||
18473 | return SXERR_ABORT; | ||
18474 | } | ||
18475 | } | ||
18476 | /* Jump trailing semi-colons ';' */ | ||
18477 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ | ||
18478 | pGen->pIn++; | ||
18479 | } | ||
18480 | return SXRET_OK; | ||
18481 | } | ||
18482 | /* | ||
18483 | * Compile the gentle 'while' statement. | ||
18484 | * According to the JX9 language reference | ||
18485 | * while loops are the simplest type of loop in JX9.They behave just like their C counterparts. | ||
18486 | * The basic form of a while statement is: | ||
18487 | * while (expr) | ||
18488 | * statement | ||
18489 | * The meaning of a while statement is simple. It tells JX9 to execute the nested statement(s) | ||
18490 | * repeatedly, as long as the while expression evaluates to TRUE. The value of the expression | ||
18491 | * is checked each time at the beginning of the loop, so even if this value changes during | ||
18492 | * the execution of the nested statement(s), execution will not stop until the end of the iteration | ||
18493 | * (each time JX9 runs the statements in the loop is one iteration). Sometimes, if the while | ||
18494 | * expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once. | ||
18495 | * Like with the if statement, you can group multiple statements within the same while loop by surrounding | ||
18496 | * a group of statements with curly braces, or by using the alternate syntax: | ||
18497 | * while (expr): | ||
18498 | * statement | ||
18499 | * endwhile; | ||
18500 | */ | ||
18501 | static sxi32 jx9CompileWhile(jx9_gen_state *pGen) | ||
18502 | { | ||
18503 | GenBlock *pWhileBlock = 0; | ||
18504 | SyToken *pTmp, *pEnd = 0; | ||
18505 | sxu32 nFalseJump; | ||
18506 | sxu32 nLine; | ||
18507 | sxi32 rc; | ||
18508 | nLine = pGen->pIn->nLine; | ||
18509 | /* Jump the 'while' keyword */ | ||
18510 | pGen->pIn++; | ||
18511 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
18512 | /* Syntax error */ | ||
18513 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword"); | ||
18514 | if( rc == SXERR_ABORT ){ | ||
18515 | /* Error count limit reached, abort immediately */ | ||
18516 | return SXERR_ABORT; | ||
18517 | } | ||
18518 | goto Synchronize; | ||
18519 | } | ||
18520 | /* Jump the left parenthesis '(' */ | ||
18521 | pGen->pIn++; | ||
18522 | /* Create the loop block */ | ||
18523 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock); | ||
18524 | if( rc != SXRET_OK ){ | ||
18525 | return SXERR_ABORT; | ||
18526 | } | ||
18527 | /* Delimit the condition */ | ||
18528 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
18529 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
18530 | /* Empty expression */ | ||
18531 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword"); | ||
18532 | if( rc == SXERR_ABORT ){ | ||
18533 | /* Error count limit reached, abort immediately */ | ||
18534 | return SXERR_ABORT; | ||
18535 | } | ||
18536 | } | ||
18537 | /* Swap token streams */ | ||
18538 | pTmp = pGen->pEnd; | ||
18539 | pGen->pEnd = pEnd; | ||
18540 | /* Compile the expression */ | ||
18541 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
18542 | if( rc == SXERR_ABORT ){ | ||
18543 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
18544 | return SXERR_ABORT; | ||
18545 | } | ||
18546 | /* Update token stream */ | ||
18547 | while(pGen->pIn < pEnd ){ | ||
18548 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); | ||
18549 | if( rc == SXERR_ABORT ){ | ||
18550 | return SXERR_ABORT; | ||
18551 | } | ||
18552 | pGen->pIn++; | ||
18553 | } | ||
18554 | /* Synchronize pointers */ | ||
18555 | pGen->pIn = &pEnd[1]; | ||
18556 | pGen->pEnd = pTmp; | ||
18557 | /* Emit the false jump */ | ||
18558 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); | ||
18559 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
18560 | GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump); | ||
18561 | /* Compile the loop body */ | ||
18562 | rc = jx9CompileBlock(&(*pGen)); | ||
18563 | if( rc == SXERR_ABORT ){ | ||
18564 | return SXERR_ABORT; | ||
18565 | } | ||
18566 | /* Emit the unconditional jump to the start of the loop */ | ||
18567 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0); | ||
18568 | /* Fix all jumps now the destination is resolved */ | ||
18569 | GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm)); | ||
18570 | /* Release the loop block */ | ||
18571 | GenStateLeaveBlock(pGen, 0); | ||
18572 | /* Statement successfully compiled */ | ||
18573 | return SXRET_OK; | ||
18574 | Synchronize: | ||
18575 | /* Synchronize with the first semi-colon ';' so we can avoid | ||
18576 | * compiling this erroneous block. | ||
18577 | */ | ||
18578 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
18579 | pGen->pIn++; | ||
18580 | } | ||
18581 | return SXRET_OK; | ||
18582 | } | ||
18583 | /* | ||
18584 | * Compile the complex and powerful 'for' statement. | ||
18585 | * According to the JX9 language reference | ||
18586 | * for loops are the most complex loops in JX9. They behave like their C counterparts. | ||
18587 | * The syntax of a for loop is: | ||
18588 | * for (expr1; expr2; expr3) | ||
18589 | * statement | ||
18590 | * The first expression (expr1) is evaluated (executed) once unconditionally at | ||
18591 | * the beginning of the loop. | ||
18592 | * In the beginning of each iteration, expr2 is evaluated. If it evaluates to | ||
18593 | * TRUE, the loop continues and the nested statement(s) are executed. If it evaluates | ||
18594 | * to FALSE, the execution of the loop ends. | ||
18595 | * At the end of each iteration, expr3 is evaluated (executed). | ||
18596 | * Each of the expressions can be empty or contain multiple expressions separated by commas. | ||
18597 | * In expr2, all expressions separated by a comma are evaluated but the result is taken | ||
18598 | * from the last part. expr2 being empty means the loop should be run indefinitely | ||
18599 | * (JX9 implicitly considers it as TRUE, like C). This may not be as useless as you might | ||
18600 | * think, since often you'd want to end the loop using a conditional break statement instead | ||
18601 | * of using the for truth expression. | ||
18602 | */ | ||
18603 | static sxi32 jx9CompileFor(jx9_gen_state *pGen) | ||
18604 | { | ||
18605 | SyToken *pTmp, *pPostStart, *pEnd = 0; | ||
18606 | GenBlock *pForBlock = 0; | ||
18607 | sxu32 nFalseJump; | ||
18608 | sxu32 nLine; | ||
18609 | sxi32 rc; | ||
18610 | nLine = pGen->pIn->nLine; | ||
18611 | /* Jump the 'for' keyword */ | ||
18612 | pGen->pIn++; | ||
18613 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
18614 | /* Syntax error */ | ||
18615 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword"); | ||
18616 | if( rc == SXERR_ABORT ){ | ||
18617 | /* Error count limit reached, abort immediately */ | ||
18618 | return SXERR_ABORT; | ||
18619 | } | ||
18620 | return SXRET_OK; | ||
18621 | } | ||
18622 | /* Jump the left parenthesis '(' */ | ||
18623 | pGen->pIn++; | ||
18624 | /* Delimit the init-expr;condition;post-expr */ | ||
18625 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
18626 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
18627 | /* Empty expression */ | ||
18628 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression"); | ||
18629 | if( rc == SXERR_ABORT ){ | ||
18630 | /* Error count limit reached, abort immediately */ | ||
18631 | return SXERR_ABORT; | ||
18632 | } | ||
18633 | /* Synchronize */ | ||
18634 | pGen->pIn = pEnd; | ||
18635 | if( pGen->pIn < pGen->pEnd ){ | ||
18636 | pGen->pIn++; | ||
18637 | } | ||
18638 | return SXRET_OK; | ||
18639 | } | ||
18640 | /* Swap token streams */ | ||
18641 | pTmp = pGen->pEnd; | ||
18642 | pGen->pEnd = pEnd; | ||
18643 | /* Compile initialization expressions if available */ | ||
18644 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
18645 | /* Pop operand lvalues */ | ||
18646 | if( rc == SXERR_ABORT ){ | ||
18647 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
18648 | return SXERR_ABORT; | ||
18649 | }else if( rc != SXERR_EMPTY ){ | ||
18650 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
18651 | } | ||
18652 | if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
18653 | /* Syntax error */ | ||
18654 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, | ||
18655 | "for: Expected ';' after initialization expressions"); | ||
18656 | if( rc == SXERR_ABORT ){ | ||
18657 | /* Error count limit reached, abort immediately */ | ||
18658 | return SXERR_ABORT; | ||
18659 | } | ||
18660 | return SXRET_OK; | ||
18661 | } | ||
18662 | /* Jump the trailing ';' */ | ||
18663 | pGen->pIn++; | ||
18664 | /* Create the loop block */ | ||
18665 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock); | ||
18666 | if( rc != SXRET_OK ){ | ||
18667 | return SXERR_ABORT; | ||
18668 | } | ||
18669 | /* Deffer continue jumps */ | ||
18670 | pForBlock->bPostContinue = TRUE; | ||
18671 | /* Compile the condition */ | ||
18672 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
18673 | if( rc == SXERR_ABORT ){ | ||
18674 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
18675 | return SXERR_ABORT; | ||
18676 | }else if( rc != SXERR_EMPTY ){ | ||
18677 | /* Emit the false jump */ | ||
18678 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); | ||
18679 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
18680 | GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump); | ||
18681 | } | ||
18682 | if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
18683 | /* Syntax error */ | ||
18684 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, | ||
18685 | "for: Expected ';' after conditionals expressions"); | ||
18686 | if( rc == SXERR_ABORT ){ | ||
18687 | /* Error count limit reached, abort immediately */ | ||
18688 | return SXERR_ABORT; | ||
18689 | } | ||
18690 | return SXRET_OK; | ||
18691 | } | ||
18692 | /* Jump the trailing ';' */ | ||
18693 | pGen->pIn++; | ||
18694 | /* Save the post condition stream */ | ||
18695 | pPostStart = pGen->pIn; | ||
18696 | /* Compile the loop body */ | ||
18697 | pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */ | ||
18698 | pGen->pEnd = pTmp; | ||
18699 | rc = jx9CompileBlock(&(*pGen)); | ||
18700 | if( rc == SXERR_ABORT ){ | ||
18701 | return SXERR_ABORT; | ||
18702 | } | ||
18703 | /* Fix post-continue jumps */ | ||
18704 | if( SySetUsed(&pForBlock->aPostContFix) > 0 ){ | ||
18705 | JumpFixup *aPost; | ||
18706 | VmInstr *pInstr; | ||
18707 | sxu32 nJumpDest; | ||
18708 | sxu32 n; | ||
18709 | aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix); | ||
18710 | nJumpDest = jx9VmInstrLength(pGen->pVm); | ||
18711 | for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){ | ||
18712 | pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx); | ||
18713 | if( pInstr ){ | ||
18714 | /* Fix jump */ | ||
18715 | pInstr->iP2 = nJumpDest; | ||
18716 | } | ||
18717 | } | ||
18718 | } | ||
18719 | /* compile the post-expressions if available */ | ||
18720 | while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){ | ||
18721 | pPostStart++; | ||
18722 | } | ||
18723 | if( pPostStart < pEnd ){ | ||
18724 | SyToken *pTmpIn, *pTmpEnd; | ||
18725 | SWAP_DELIMITER(pGen, pPostStart, pEnd); | ||
18726 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
18727 | if( pGen->pIn < pGen->pEnd ){ | ||
18728 | /* Syntax error */ | ||
18729 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions"); | ||
18730 | if( rc == SXERR_ABORT ){ | ||
18731 | /* Error count limit reached, abort immediately */ | ||
18732 | return SXERR_ABORT; | ||
18733 | } | ||
18734 | return SXRET_OK; | ||
18735 | } | ||
18736 | RE_SWAP_DELIMITER(pGen); | ||
18737 | if( rc == SXERR_ABORT ){ | ||
18738 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
18739 | return SXERR_ABORT; | ||
18740 | }else if( rc != SXERR_EMPTY){ | ||
18741 | /* Pop operand lvalue */ | ||
18742 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
18743 | } | ||
18744 | } | ||
18745 | /* Emit the unconditional jump to the start of the loop */ | ||
18746 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0); | ||
18747 | /* Fix all jumps now the destination is resolved */ | ||
18748 | GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm)); | ||
18749 | /* Release the loop block */ | ||
18750 | GenStateLeaveBlock(pGen, 0); | ||
18751 | /* Statement successfully compiled */ | ||
18752 | return SXRET_OK; | ||
18753 | } | ||
18754 | /* Expression tree validator callback used by the 'foreach' statement. | ||
18755 | * Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...] | ||
18756 | * are allowed. | ||
18757 | */ | ||
18758 | static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) | ||
18759 | { | ||
18760 | sxi32 rc = SXRET_OK; /* Assume a valid expression tree */ | ||
18761 | if( pRoot->xCode != jx9CompileVariable ){ | ||
18762 | /* Unexpected expression */ | ||
18763 | rc = jx9GenCompileError(&(*pGen), | ||
18764 | E_ERROR, | ||
18765 | pRoot->pStart? pRoot->pStart->nLine : 0, | ||
18766 | "foreach: Expecting a variable name" | ||
18767 | ); | ||
18768 | if( rc != SXERR_ABORT ){ | ||
18769 | rc = SXERR_INVALID; | ||
18770 | } | ||
18771 | } | ||
18772 | return rc; | ||
18773 | } | ||
18774 | /* | ||
18775 | * Compile the 'foreach' statement. | ||
18776 | * According to the JX9 language reference | ||
18777 | * The foreach construct simply gives an easy way to iterate over arrays. foreach works | ||
18778 | * only on arrays (and objects), and will issue an error when you try to use it on a variable | ||
18779 | * with a different data type or an uninitialized variable. There are two syntaxes; the second | ||
18780 | * is a minor but useful extension of the first: | ||
18781 | * foreach (json_array_json_object as $value) | ||
18782 | * statement | ||
18783 | * foreach (json_array_json_objec as $key,$value) | ||
18784 | * statement | ||
18785 | * The first form loops over the array given by array_expression. On each loop, the value | ||
18786 | * of the current element is assigned to $value and the internal array pointer is advanced | ||
18787 | * by one (so on the next loop, you'll be looking at the next element). | ||
18788 | * The second form does the same thing, except that the current element's key will be assigned | ||
18789 | * to the variable $key on each loop. | ||
18790 | * Note: | ||
18791 | * When foreach first starts executing, the internal array pointer is automatically reset to the | ||
18792 | * first element of the array. This means that you do not need to call reset() before a foreach loop. | ||
18793 | */ | ||
18794 | static sxi32 jx9CompileForeach(jx9_gen_state *pGen) | ||
18795 | { | ||
18796 | SyToken *pCur, *pTmp, *pEnd = 0; | ||
18797 | GenBlock *pForeachBlock = 0; | ||
18798 | jx9_foreach_info *pInfo; | ||
18799 | sxu32 nFalseJump; | ||
18800 | VmInstr *pInstr; | ||
18801 | sxu32 nLine; | ||
18802 | sxi32 rc; | ||
18803 | nLine = pGen->pIn->nLine; | ||
18804 | /* Jump the 'foreach' keyword */ | ||
18805 | pGen->pIn++; | ||
18806 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
18807 | /* Syntax error */ | ||
18808 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('"); | ||
18809 | if( rc == SXERR_ABORT ){ | ||
18810 | /* Error count limit reached, abort immediately */ | ||
18811 | return SXERR_ABORT; | ||
18812 | } | ||
18813 | goto Synchronize; | ||
18814 | } | ||
18815 | /* Jump the left parenthesis '(' */ | ||
18816 | pGen->pIn++; | ||
18817 | /* Create the loop block */ | ||
18818 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock); | ||
18819 | if( rc != SXRET_OK ){ | ||
18820 | return SXERR_ABORT; | ||
18821 | } | ||
18822 | /* Delimit the expression */ | ||
18823 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
18824 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
18825 | /* Empty expression */ | ||
18826 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression"); | ||
18827 | if( rc == SXERR_ABORT ){ | ||
18828 | /* Error count limit reached, abort immediately */ | ||
18829 | return SXERR_ABORT; | ||
18830 | } | ||
18831 | /* Synchronize */ | ||
18832 | pGen->pIn = pEnd; | ||
18833 | if( pGen->pIn < pGen->pEnd ){ | ||
18834 | pGen->pIn++; | ||
18835 | } | ||
18836 | return SXRET_OK; | ||
18837 | } | ||
18838 | /* Compile the array expression */ | ||
18839 | pCur = pGen->pIn; | ||
18840 | while( pCur < pEnd ){ | ||
18841 | if( pCur->nType & JX9_TK_KEYWORD ){ | ||
18842 | sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData); | ||
18843 | if( nKeywrd == JX9_TKWRD_AS ){ | ||
18844 | /* Break with the first 'as' found */ | ||
18845 | break; | ||
18846 | } | ||
18847 | } | ||
18848 | /* Advance the stream cursor */ | ||
18849 | pCur++; | ||
18850 | } | ||
18851 | if( pCur <= pGen->pIn ){ | ||
18852 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, | ||
18853 | "foreach: Missing array/object expression"); | ||
18854 | if( rc == SXERR_ABORT ){ | ||
18855 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
18856 | return SXERR_ABORT; | ||
18857 | } | ||
18858 | goto Synchronize; | ||
18859 | } | ||
18860 | /* Swap token streams */ | ||
18861 | pTmp = pGen->pEnd; | ||
18862 | pGen->pEnd = pCur; | ||
18863 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
18864 | if( rc == SXERR_ABORT ){ | ||
18865 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
18866 | return SXERR_ABORT; | ||
18867 | } | ||
18868 | /* Update token stream */ | ||
18869 | while(pGen->pIn < pCur ){ | ||
18870 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData); | ||
18871 | if( rc == SXERR_ABORT ){ | ||
18872 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
18873 | return SXERR_ABORT; | ||
18874 | } | ||
18875 | pGen->pIn++; | ||
18876 | } | ||
18877 | pCur++; /* Jump the 'as' keyword */ | ||
18878 | pGen->pIn = pCur; | ||
18879 | if( pGen->pIn >= pEnd ){ | ||
18880 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair"); | ||
18881 | if( rc == SXERR_ABORT ){ | ||
18882 | return SXERR_ABORT; | ||
18883 | } | ||
18884 | } | ||
18885 | /* Create the foreach context */ | ||
18886 | pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info)); | ||
18887 | if( pInfo == 0 ){ | ||
18888 | jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory"); | ||
18889 | return SXERR_ABORT; | ||
18890 | } | ||
18891 | /* Zero the structure */ | ||
18892 | SyZero(pInfo, sizeof(jx9_foreach_info)); | ||
18893 | /* Initialize structure fields */ | ||
18894 | SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *)); | ||
18895 | /* Check if we have a key field */ | ||
18896 | while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){ | ||
18897 | pCur++; | ||
18898 | } | ||
18899 | if( pCur < pEnd ){ | ||
18900 | /* Compile the expression holding the key name */ | ||
18901 | if( pGen->pIn >= pCur ){ | ||
18902 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key"); | ||
18903 | if( rc == SXERR_ABORT ){ | ||
18904 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
18905 | return SXERR_ABORT; | ||
18906 | } | ||
18907 | }else{ | ||
18908 | pGen->pEnd = pCur; | ||
18909 | rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); | ||
18910 | if( rc == SXERR_ABORT ){ | ||
18911 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
18912 | return SXERR_ABORT; | ||
18913 | } | ||
18914 | pInstr = jx9VmPopInstr(pGen->pVm); | ||
18915 | if( pInstr->p3 ){ | ||
18916 | /* Record key name */ | ||
18917 | SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3)); | ||
18918 | } | ||
18919 | pInfo->iFlags |= JX9_4EACH_STEP_KEY; | ||
18920 | } | ||
18921 | pGen->pIn = &pCur[1]; /* Jump the arrow */ | ||
18922 | } | ||
18923 | pGen->pEnd = pEnd; | ||
18924 | if( pGen->pIn >= pEnd ){ | ||
18925 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value"); | ||
18926 | if( rc == SXERR_ABORT ){ | ||
18927 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
18928 | return SXERR_ABORT; | ||
18929 | } | ||
18930 | goto Synchronize; | ||
18931 | } | ||
18932 | /* Compile the expression holding the value name */ | ||
18933 | rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); | ||
18934 | if( rc == SXERR_ABORT ){ | ||
18935 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
18936 | return SXERR_ABORT; | ||
18937 | } | ||
18938 | pInstr = jx9VmPopInstr(pGen->pVm); | ||
18939 | if( pInstr->p3 ){ | ||
18940 | /* Record value name */ | ||
18941 | SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3)); | ||
18942 | } | ||
18943 | /* Emit the 'FOREACH_INIT' instruction */ | ||
18944 | jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump); | ||
18945 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
18946 | GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump); | ||
18947 | /* Record the first instruction to execute */ | ||
18948 | pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm); | ||
18949 | /* Emit the FOREACH_STEP instruction */ | ||
18950 | jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump); | ||
18951 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
18952 | GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump); | ||
18953 | /* Compile the loop body */ | ||
18954 | pGen->pIn = &pEnd[1]; | ||
18955 | pGen->pEnd = pTmp; | ||
18956 | rc = jx9CompileBlock(&(*pGen)); | ||
18957 | if( rc == SXERR_ABORT ){ | ||
18958 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
18959 | return SXERR_ABORT; | ||
18960 | } | ||
18961 | /* Emit the unconditional jump to the start of the loop */ | ||
18962 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0); | ||
18963 | /* Fix all jumps now the destination is resolved */ | ||
18964 | GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm)); | ||
18965 | /* Release the loop block */ | ||
18966 | GenStateLeaveBlock(pGen, 0); | ||
18967 | /* Statement successfully compiled */ | ||
18968 | return SXRET_OK; | ||
18969 | Synchronize: | ||
18970 | /* Synchronize with the first semi-colon ';' so we can avoid | ||
18971 | * compiling this erroneous block. | ||
18972 | */ | ||
18973 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
18974 | pGen->pIn++; | ||
18975 | } | ||
18976 | return SXRET_OK; | ||
18977 | } | ||
18978 | /* | ||
18979 | * Compile the infamous if/elseif/else if/else statements. | ||
18980 | * According to the JX9 language reference | ||
18981 | * The if construct is one of the most important features of many languages JX9 included. | ||
18982 | * It allows for conditional execution of code fragments. JX9 features an if structure | ||
18983 | * that is similar to that of C: | ||
18984 | * if (expr) | ||
18985 | * statement | ||
18986 | * else construct: | ||
18987 | * Often you'd want to execute a statement if a certain condition is met, and a different | ||
18988 | * statement if the condition is not met. This is what else is for. else extends an if statement | ||
18989 | * to execute a statement in case the expression in the if statement evaluates to FALSE. | ||
18990 | * For example, the following code would display a is greater than b if $a is greater than | ||
18991 | * $b, and a is NOT greater than b otherwise. | ||
18992 | * The else statement is only executed if the if expression evaluated to FALSE, and if there | ||
18993 | * were any elseif expressions - only if they evaluated to FALSE as well | ||
18994 | * elseif | ||
18995 | * elseif, as its name suggests, is a combination of if and else. Like else, it extends | ||
18996 | * an if statement to execute a different statement in case the original if expression evaluates | ||
18997 | * to FALSE. However, unlike else, it will execute that alternative expression only if the elseif | ||
18998 | * conditional expression evaluates to TRUE. For example, the following code would display a is bigger | ||
18999 | * than b, a equal to b or a is smaller than b: | ||
19000 | * if ($a > $b) { | ||
19001 | * print "a is bigger than b"; | ||
19002 | * } elseif ($a == $b) { | ||
19003 | * print "a is equal to b"; | ||
19004 | * } else { | ||
19005 | * print "a is smaller than b"; | ||
19006 | * } | ||
19007 | */ | ||
19008 | static sxi32 jx9CompileIf(jx9_gen_state *pGen) | ||
19009 | { | ||
19010 | SyToken *pToken, *pTmp, *pEnd = 0; | ||
19011 | GenBlock *pCondBlock = 0; | ||
19012 | sxu32 nJumpIdx; | ||
19013 | sxu32 nKeyID; | ||
19014 | sxi32 rc; | ||
19015 | /* Jump the 'if' keyword */ | ||
19016 | pGen->pIn++; | ||
19017 | pToken = pGen->pIn; | ||
19018 | /* Create the conditional block */ | ||
19019 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(pGen->pVm), 0, &pCondBlock); | ||
19020 | if( rc != SXRET_OK ){ | ||
19021 | return SXERR_ABORT; | ||
19022 | } | ||
19023 | /* Process as many [if/else if/elseif/else] blocks as we can */ | ||
19024 | for(;;){ | ||
19025 | if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_LPAREN) == 0 ){ | ||
19026 | /* Syntax error */ | ||
19027 | if( pToken >= pGen->pEnd ){ | ||
19028 | pToken--; | ||
19029 | } | ||
19030 | rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('"); | ||
19031 | if( rc == SXERR_ABORT ){ | ||
19032 | /* Error count limit reached, abort immediately */ | ||
19033 | return SXERR_ABORT; | ||
19034 | } | ||
19035 | goto Synchronize; | ||
19036 | } | ||
19037 | /* Jump the left parenthesis '(' */ | ||
19038 | pToken++; | ||
19039 | /* Delimit the condition */ | ||
19040 | jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
19041 | if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){ | ||
19042 | /* Syntax error */ | ||
19043 | if( pToken >= pGen->pEnd ){ | ||
19044 | pToken--; | ||
19045 | } | ||
19046 | rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'"); | ||
19047 | if( rc == SXERR_ABORT ){ | ||
19048 | /* Error count limit reached, abort immediately */ | ||
19049 | return SXERR_ABORT; | ||
19050 | } | ||
19051 | goto Synchronize; | ||
19052 | } | ||
19053 | /* Swap token streams */ | ||
19054 | SWAP_TOKEN_STREAM(pGen, pToken, pEnd); | ||
19055 | /* Compile the condition */ | ||
19056 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19057 | /* Update token stream */ | ||
19058 | while(pGen->pIn < pEnd ){ | ||
19059 | jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); | ||
19060 | pGen->pIn++; | ||
19061 | } | ||
19062 | pGen->pIn = &pEnd[1]; | ||
19063 | pGen->pEnd = pTmp; | ||
19064 | if( rc == SXERR_ABORT ){ | ||
19065 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
19066 | return SXERR_ABORT; | ||
19067 | } | ||
19068 | /* Emit the false jump */ | ||
19069 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx); | ||
19070 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
19071 | GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx); | ||
19072 | /* Compile the body */ | ||
19073 | rc = jx9CompileBlock(&(*pGen)); | ||
19074 | if( rc == SXERR_ABORT ){ | ||
19075 | return SXERR_ABORT; | ||
19076 | } | ||
19077 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ | ||
19078 | break; | ||
19079 | } | ||
19080 | /* Ensure that the keyword ID is 'else if' or 'else' */ | ||
19081 | nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
19082 | if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){ | ||
19083 | break; | ||
19084 | } | ||
19085 | /* Emit the unconditional jump */ | ||
19086 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx); | ||
19087 | /* Save the instruction index so we can fix it later when the jump destination is resolved */ | ||
19088 | GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx); | ||
19089 | if( nKeyID & JX9_TKWRD_ELSE ){ | ||
19090 | pToken = &pGen->pIn[1]; | ||
19091 | if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 || | ||
19092 | SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){ | ||
19093 | break; | ||
19094 | } | ||
19095 | pGen->pIn++; /* Jump the 'else' keyword */ | ||
19096 | } | ||
19097 | pGen->pIn++; /* Jump the 'elseif/if' keyword */ | ||
19098 | /* Synchronize cursors */ | ||
19099 | pToken = pGen->pIn; | ||
19100 | /* Fix the false jump */ | ||
19101 | GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); | ||
19102 | } /* For(;;) */ | ||
19103 | /* Fix the false jump */ | ||
19104 | GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); | ||
19105 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) && | ||
19106 | (SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){ | ||
19107 | /* Compile the else block */ | ||
19108 | pGen->pIn++; | ||
19109 | rc = jx9CompileBlock(&(*pGen)); | ||
19110 | if( rc == SXERR_ABORT ){ | ||
19111 | |||
19112 | return SXERR_ABORT; | ||
19113 | } | ||
19114 | } | ||
19115 | nJumpIdx = jx9VmInstrLength(pGen->pVm); | ||
19116 | /* Fix all unconditional jumps now the destination is resolved */ | ||
19117 | GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx); | ||
19118 | /* Release the conditional block */ | ||
19119 | GenStateLeaveBlock(pGen, 0); | ||
19120 | /* Statement successfully compiled */ | ||
19121 | return SXRET_OK; | ||
19122 | Synchronize: | ||
19123 | /* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block. | ||
19124 | */ | ||
19125 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
19126 | pGen->pIn++; | ||
19127 | } | ||
19128 | return SXRET_OK; | ||
19129 | } | ||
19130 | /* | ||
19131 | * Compile the return statement. | ||
19132 | * According to the JX9 language reference | ||
19133 | * If called from within a function, the return() statement immediately ends execution | ||
19134 | * of the current function, and returns its argument as the value of the function call. | ||
19135 | * return() will also end the execution of an eval() statement or script file. | ||
19136 | * If called from the global scope, then execution of the current script file is ended. | ||
19137 | * If the current script file was include()ed or require()ed, then control is passed back | ||
19138 | * to the calling file. Furthermore, if the current script file was include()ed, then the value | ||
19139 | * given to return() will be returned as the value of the include() call. If return() is called | ||
19140 | * from within the main script file, then script execution end. | ||
19141 | * Note that since return() is a language construct and not a function, the parentheses | ||
19142 | * surrounding its arguments are not required. It is common to leave them out, and you actually | ||
19143 | * should do so as JX9 has less work to do in this case. | ||
19144 | * Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead.. | ||
19145 | */ | ||
19146 | static sxi32 jx9CompileReturn(jx9_gen_state *pGen) | ||
19147 | { | ||
19148 | sxi32 nRet = 0; /* TRUE if there is a return value */ | ||
19149 | sxi32 rc; | ||
19150 | /* Jump the 'return' keyword */ | ||
19151 | pGen->pIn++; | ||
19152 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
19153 | /* Compile the expression */ | ||
19154 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19155 | if( rc == SXERR_ABORT ){ | ||
19156 | return SXERR_ABORT; | ||
19157 | }else if(rc != SXERR_EMPTY ){ | ||
19158 | nRet = 1; | ||
19159 | } | ||
19160 | } | ||
19161 | /* Emit the done instruction */ | ||
19162 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, nRet, 0, 0, 0); | ||
19163 | return SXRET_OK; | ||
19164 | } | ||
19165 | /* | ||
19166 | * Compile the die/exit language construct. | ||
19167 | * The role of these constructs is to terminate execution of the script. | ||
19168 | * Shutdown functions will always be executed even if exit() is called. | ||
19169 | */ | ||
19170 | static sxi32 jx9CompileHalt(jx9_gen_state *pGen) | ||
19171 | { | ||
19172 | sxi32 nExpr = 0; | ||
19173 | sxi32 rc; | ||
19174 | /* Jump the die/exit keyword */ | ||
19175 | pGen->pIn++; | ||
19176 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
19177 | /* Compile the expression */ | ||
19178 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19179 | if( rc == SXERR_ABORT ){ | ||
19180 | return SXERR_ABORT; | ||
19181 | }else if(rc != SXERR_EMPTY ){ | ||
19182 | nExpr = 1; | ||
19183 | } | ||
19184 | } | ||
19185 | /* Emit the HALT instruction */ | ||
19186 | jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0); | ||
19187 | return SXRET_OK; | ||
19188 | } | ||
19189 | /* | ||
19190 | * Compile the static statement. | ||
19191 | * According to the JX9 language reference | ||
19192 | * Another important feature of variable scoping is the static variable. | ||
19193 | * A static variable exists only in a local function scope, but it does not lose its value | ||
19194 | * when program execution leaves this scope. | ||
19195 | * Static variables also provide one way to deal with recursive functions. | ||
19196 | */ | ||
19197 | static sxi32 jx9CompileStatic(jx9_gen_state *pGen) | ||
19198 | { | ||
19199 | jx9_vm_func_static_var sStatic; /* Structure describing the static variable */ | ||
19200 | jx9_vm_func *pFunc; /* Enclosing function */ | ||
19201 | GenBlock *pBlock; | ||
19202 | SyString *pName; | ||
19203 | char *zDup; | ||
19204 | sxu32 nLine; | ||
19205 | sxi32 rc; | ||
19206 | /* Jump the static keyword */ | ||
19207 | nLine = pGen->pIn->nLine; | ||
19208 | pGen->pIn++; | ||
19209 | /* Extract the enclosing function if any */ | ||
19210 | pBlock = pGen->pCurrent; | ||
19211 | while( pBlock ){ | ||
19212 | if( pBlock->iFlags & GEN_BLOCK_FUNC){ | ||
19213 | break; | ||
19214 | } | ||
19215 | /* Point to the upper block */ | ||
19216 | pBlock = pBlock->pParent; | ||
19217 | } | ||
19218 | if( pBlock == 0 ){ | ||
19219 | /* Static statement, called outside of a function body, treat it as a simple variable. */ | ||
19220 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ | ||
19221 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); | ||
19222 | if( rc == SXERR_ABORT ){ | ||
19223 | return SXERR_ABORT; | ||
19224 | } | ||
19225 | goto Synchronize; | ||
19226 | } | ||
19227 | /* Compile the expression holding the variable */ | ||
19228 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19229 | if( rc == SXERR_ABORT ){ | ||
19230 | return SXERR_ABORT; | ||
19231 | }else if( rc != SXERR_EMPTY ){ | ||
19232 | /* Emit the POP instruction */ | ||
19233 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
19234 | } | ||
19235 | return SXRET_OK; | ||
19236 | } | ||
19237 | pFunc = (jx9_vm_func *)pBlock->pUserData; | ||
19238 | /* Make sure we are dealing with a valid statement */ | ||
19239 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd || | ||
19240 | (pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
19241 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); | ||
19242 | if( rc == SXERR_ABORT ){ | ||
19243 | return SXERR_ABORT; | ||
19244 | } | ||
19245 | goto Synchronize; | ||
19246 | } | ||
19247 | pGen->pIn++; | ||
19248 | /* Extract variable name */ | ||
19249 | pName = &pGen->pIn->sData; | ||
19250 | pGen->pIn++; /* Jump the var name */ | ||
19251 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){ | ||
19252 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData); | ||
19253 | goto Synchronize; | ||
19254 | } | ||
19255 | /* Initialize the structure describing the static variable */ | ||
19256 | SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
19257 | sStatic.nIdx = SXU32_HIGH; /* Not yet created */ | ||
19258 | /* Duplicate variable name */ | ||
19259 | zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); | ||
19260 | if( zDup == 0 ){ | ||
19261 | jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory"); | ||
19262 | return SXERR_ABORT; | ||
19263 | } | ||
19264 | SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte); | ||
19265 | /* Check if we have an expression to compile */ | ||
19266 | if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){ | ||
19267 | SySet *pInstrContainer; | ||
19268 | pGen->pIn++; /* Jump the equal '=' sign */ | ||
19269 | /* Swap bytecode container */ | ||
19270 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
19271 | jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode); | ||
19272 | /* Compile the expression */ | ||
19273 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19274 | /* Emit the done instruction */ | ||
19275 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
19276 | /* Restore default bytecode container */ | ||
19277 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
19278 | } | ||
19279 | /* Finally save the compiled static variable in the appropriate container */ | ||
19280 | SySetPut(&pFunc->aStatic, (const void *)&sStatic); | ||
19281 | return SXRET_OK; | ||
19282 | Synchronize: | ||
19283 | /* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous | ||
19284 | * statement. | ||
19285 | */ | ||
19286 | while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
19287 | pGen->pIn++; | ||
19288 | } | ||
19289 | return SXRET_OK; | ||
19290 | } | ||
19291 | /* | ||
19292 | * Compile the 'const' statement. | ||
19293 | * According to the JX9 language reference | ||
19294 | * A constant is an identifier (name) for a simple value. As the name suggests, that value | ||
19295 | * cannot change during the execution of the script (except for magic constants, which aren't actually constants). | ||
19296 | * A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. | ||
19297 | * The name of a constant follows the same rules as any label in JX9. A valid constant name starts | ||
19298 | * with a letter or underscore, followed by any number of letters, numbers, or underscores. | ||
19299 | * As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* | ||
19300 | * Syntax | ||
19301 | * You can define a constant by using the define()-function or by using the const keyword outside | ||
19302 | * a object definition. Once a constant is defined, it can never be changed or undefined. | ||
19303 | * You can get the value of a constant by simply specifying its name. Unlike with variables | ||
19304 | * you should not prepend a constant with a $. You can also use the function constant() to read | ||
19305 | * a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants() | ||
19306 | * to get a list of all defined constants. | ||
19307 | */ | ||
19308 | static sxi32 jx9CompileConstant(jx9_gen_state *pGen) | ||
19309 | { | ||
19310 | SySet *pConsCode, *pInstrContainer; | ||
19311 | sxu32 nLine = pGen->pIn->nLine; | ||
19312 | SyString *pName; | ||
19313 | sxi32 rc; | ||
19314 | pGen->pIn++; /* Jump the 'const' keyword */ | ||
19315 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
19316 | /* Invalid constant name */ | ||
19317 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name"); | ||
19318 | if( rc == SXERR_ABORT ){ | ||
19319 | /* Error count limit reached, abort immediately */ | ||
19320 | return SXERR_ABORT; | ||
19321 | } | ||
19322 | goto Synchronize; | ||
19323 | } | ||
19324 | /* Peek constant name */ | ||
19325 | pName = &pGen->pIn->sData; | ||
19326 | /* Make sure the constant name isn't reserved */ | ||
19327 | if( GenStateIsReservedID(pName) ){ | ||
19328 | /* Reserved constant */ | ||
19329 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName); | ||
19330 | if( rc == SXERR_ABORT ){ | ||
19331 | /* Error count limit reached, abort immediately */ | ||
19332 | return SXERR_ABORT; | ||
19333 | } | ||
19334 | goto Synchronize; | ||
19335 | } | ||
19336 | pGen->pIn++; | ||
19337 | if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){ | ||
19338 | /* Invalid statement*/ | ||
19339 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name"); | ||
19340 | if( rc == SXERR_ABORT ){ | ||
19341 | /* Error count limit reached, abort immediately */ | ||
19342 | return SXERR_ABORT; | ||
19343 | } | ||
19344 | goto Synchronize; | ||
19345 | } | ||
19346 | pGen->pIn++; /*Jump the equal sign */ | ||
19347 | /* Allocate a new constant value container */ | ||
19348 | pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet)); | ||
19349 | if( pConsCode == 0 ){ | ||
19350 | return GenStateOutOfMem(pGen); | ||
19351 | } | ||
19352 | SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
19353 | /* Swap bytecode container */ | ||
19354 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
19355 | jx9VmSetByteCodeContainer(pGen->pVm, pConsCode); | ||
19356 | /* Compile constant value */ | ||
19357 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19358 | /* Emit the done instruction */ | ||
19359 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
19360 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
19361 | if( rc == SXERR_ABORT ){ | ||
19362 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
19363 | return SXERR_ABORT; | ||
19364 | } | ||
19365 | SySetSetUserData(pConsCode, pGen->pVm); | ||
19366 | /* Register the constant */ | ||
19367 | rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode); | ||
19368 | if( rc != SXRET_OK ){ | ||
19369 | SySetRelease(pConsCode); | ||
19370 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode); | ||
19371 | } | ||
19372 | return SXRET_OK; | ||
19373 | Synchronize: | ||
19374 | /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ | ||
19375 | while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
19376 | pGen->pIn++; | ||
19377 | } | ||
19378 | return SXRET_OK; | ||
19379 | } | ||
19380 | /* | ||
19381 | * Compile the uplink construct. | ||
19382 | * According to the JX9 language reference | ||
19383 | * In JX9 global variables must be declared uplink inside a function if they are going | ||
19384 | * to be used in that function. | ||
19385 | * Example #1 Using global | ||
19386 | * $a = 1; | ||
19387 | * $b = 2; | ||
19388 | * function Sum() | ||
19389 | * { | ||
19390 | * uplink $a, $b; | ||
19391 | * $b = $a + $b; | ||
19392 | * } | ||
19393 | * Sum(); | ||
19394 | * print $b; | ||
19395 | * ?> | ||
19396 | * The above script will output 3. By declaring $a and $b global within the function | ||
19397 | * all references to either variable will refer to the global version. There is no limit | ||
19398 | * to the number of global variables that can be manipulated by a function. | ||
19399 | */ | ||
19400 | static sxi32 jx9CompileUplink(jx9_gen_state *pGen) | ||
19401 | { | ||
19402 | SyToken *pTmp, *pNext = 0; | ||
19403 | sxi32 nExpr; | ||
19404 | sxi32 rc; | ||
19405 | /* Jump the 'uplink' keyword */ | ||
19406 | pGen->pIn++; | ||
19407 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){ | ||
19408 | /* Nothing to process */ | ||
19409 | return SXRET_OK; | ||
19410 | } | ||
19411 | pTmp = pGen->pEnd; | ||
19412 | nExpr = 0; | ||
19413 | while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ | ||
19414 | if( pGen->pIn < pNext ){ | ||
19415 | pGen->pEnd = pNext; | ||
19416 | if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ | ||
19417 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name"); | ||
19418 | if( rc == SXERR_ABORT ){ | ||
19419 | return SXERR_ABORT; | ||
19420 | } | ||
19421 | }else{ | ||
19422 | pGen->pIn++; | ||
19423 | if( pGen->pIn >= pGen->pEnd ){ | ||
19424 | /* Emit a warning */ | ||
19425 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name"); | ||
19426 | }else{ | ||
19427 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19428 | if( rc == SXERR_ABORT ){ | ||
19429 | return SXERR_ABORT; | ||
19430 | }else if(rc != SXERR_EMPTY ){ | ||
19431 | nExpr++; | ||
19432 | } | ||
19433 | } | ||
19434 | } | ||
19435 | } | ||
19436 | /* Next expression in the stream */ | ||
19437 | pGen->pIn = pNext; | ||
19438 | /* Jump trailing commas */ | ||
19439 | while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){ | ||
19440 | pGen->pIn++; | ||
19441 | } | ||
19442 | } | ||
19443 | /* Restore token stream */ | ||
19444 | pGen->pEnd = pTmp; | ||
19445 | if( nExpr > 0 ){ | ||
19446 | /* Emit the uplink instruction */ | ||
19447 | jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0); | ||
19448 | } | ||
19449 | return SXRET_OK; | ||
19450 | } | ||
19451 | /* | ||
19452 | * Compile a switch block. | ||
19453 | * (See block-comment below for more information) | ||
19454 | */ | ||
19455 | static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart) | ||
19456 | { | ||
19457 | sxi32 rc = SXRET_OK; | ||
19458 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){ | ||
19459 | /* Unexpected token */ | ||
19460 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); | ||
19461 | if( rc == SXERR_ABORT ){ | ||
19462 | return SXERR_ABORT; | ||
19463 | } | ||
19464 | pGen->pIn++; | ||
19465 | } | ||
19466 | pGen->pIn++; | ||
19467 | /* First instruction to execute in this block. */ | ||
19468 | *pBlockStart = jx9VmInstrLength(pGen->pVm); | ||
19469 | /* Compile the block until we hit a case/default/endswitch keyword | ||
19470 | * or the '}' token */ | ||
19471 | for(;;){ | ||
19472 | if( pGen->pIn >= pGen->pEnd ){ | ||
19473 | /* No more input to process */ | ||
19474 | break; | ||
19475 | } | ||
19476 | rc = SXRET_OK; | ||
19477 | if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ | ||
19478 | if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){ | ||
19479 | rc = SXERR_EOF; | ||
19480 | break; | ||
19481 | } | ||
19482 | }else{ | ||
19483 | sxi32 nKwrd; | ||
19484 | /* Extract the keyword */ | ||
19485 | nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
19486 | if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){ | ||
19487 | break; | ||
19488 | } | ||
19489 | } | ||
19490 | /* Compile block */ | ||
19491 | rc = jx9CompileBlock(&(*pGen)); | ||
19492 | if( rc == SXERR_ABORT ){ | ||
19493 | return SXERR_ABORT; | ||
19494 | } | ||
19495 | } | ||
19496 | return rc; | ||
19497 | } | ||
19498 | /* | ||
19499 | * Compile a case eXpression. | ||
19500 | * (See block-comment below for more information) | ||
19501 | */ | ||
19502 | static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_case_expr *pExpr) | ||
19503 | { | ||
19504 | SySet *pInstrContainer; | ||
19505 | SyToken *pEnd, *pTmp; | ||
19506 | sxi32 iNest = 0; | ||
19507 | sxi32 rc; | ||
19508 | /* Delimit the expression */ | ||
19509 | pEnd = pGen->pIn; | ||
19510 | while( pEnd < pGen->pEnd ){ | ||
19511 | if( pEnd->nType & JX9_TK_LPAREN /*(*/ ){ | ||
19512 | /* Increment nesting level */ | ||
19513 | iNest++; | ||
19514 | }else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){ | ||
19515 | /* Decrement nesting level */ | ||
19516 | iNest--; | ||
19517 | }else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){ | ||
19518 | break; | ||
19519 | } | ||
19520 | pEnd++; | ||
19521 | } | ||
19522 | if( pGen->pIn >= pEnd ){ | ||
19523 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression"); | ||
19524 | if( rc == SXERR_ABORT ){ | ||
19525 | /* Error count limit reached, abort immediately */ | ||
19526 | return SXERR_ABORT; | ||
19527 | } | ||
19528 | } | ||
19529 | /* Swap token stream */ | ||
19530 | pTmp = pGen->pEnd; | ||
19531 | pGen->pEnd = pEnd; | ||
19532 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
19533 | jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode); | ||
19534 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19535 | /* Emit the done instruction */ | ||
19536 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
19537 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
19538 | /* Update token stream */ | ||
19539 | pGen->pIn = pEnd; | ||
19540 | pGen->pEnd = pTmp; | ||
19541 | if( rc == SXERR_ABORT ){ | ||
19542 | return SXERR_ABORT; | ||
19543 | } | ||
19544 | return SXRET_OK; | ||
19545 | } | ||
19546 | /* | ||
19547 | * Compile the smart switch statement. | ||
19548 | * According to the JX9 language reference manual | ||
19549 | * The switch statement is similar to a series of IF statements on the same expression. | ||
19550 | * In many occasions, you may want to compare the same variable (or expression) with many | ||
19551 | * different values, and execute a different piece of code depending on which value it equals to. | ||
19552 | * This is exactly what the switch statement is for. | ||
19553 | * Note: Note that unlike some other languages, the continue statement applies to switch and acts | ||
19554 | * similar to break. If you have a switch inside a loop and wish to continue to the next iteration | ||
19555 | * of the outer loop, use continue 2. | ||
19556 | * Note that switch/case does loose comparision. | ||
19557 | * It is important to understand how the switch statement is executed in order to avoid mistakes. | ||
19558 | * The switch statement executes line by line (actually, statement by statement). | ||
19559 | * In the beginning, no code is executed. Only when a case statement is found with a value that | ||
19560 | * matches the value of the switch expression does JX9 begin to execute the statements. | ||
19561 | * JX9 continues to execute the statements until the end of the switch block, or the first time | ||
19562 | * it sees a break statement. If you don't write a break statement at the end of a case's statement list. | ||
19563 | * In a switch statement, the condition is evaluated only once and the result is compared to each | ||
19564 | * case statement. In an elseif statement, the condition is evaluated again. If your condition | ||
19565 | * is more complicated than a simple compare and/or is in a tight loop, a switch may be faster. | ||
19566 | * The statement list for a case can also be empty, which simply passes control into the statement | ||
19567 | * list for the next case. | ||
19568 | * The case expression may be any expression that evaluates to a simple type, that is, integer | ||
19569 | * or floating-point numbers and strings. | ||
19570 | */ | ||
19571 | static sxi32 jx9CompileSwitch(jx9_gen_state *pGen) | ||
19572 | { | ||
19573 | GenBlock *pSwitchBlock; | ||
19574 | SyToken *pTmp, *pEnd; | ||
19575 | jx9_switch *pSwitch; | ||
19576 | sxu32 nLine; | ||
19577 | sxi32 rc; | ||
19578 | nLine = pGen->pIn->nLine; | ||
19579 | /* Jump the 'switch' keyword */ | ||
19580 | pGen->pIn++; | ||
19581 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
19582 | /* Syntax error */ | ||
19583 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword"); | ||
19584 | if( rc == SXERR_ABORT ){ | ||
19585 | /* Error count limit reached, abort immediately */ | ||
19586 | return SXERR_ABORT; | ||
19587 | } | ||
19588 | goto Synchronize; | ||
19589 | } | ||
19590 | /* Jump the left parenthesis '(' */ | ||
19591 | pGen->pIn++; | ||
19592 | pEnd = 0; /* cc warning */ | ||
19593 | /* Create the loop block */ | ||
19594 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH, | ||
19595 | jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock); | ||
19596 | if( rc != SXRET_OK ){ | ||
19597 | return SXERR_ABORT; | ||
19598 | } | ||
19599 | /* Delimit the condition */ | ||
19600 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
19601 | if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ | ||
19602 | /* Empty expression */ | ||
19603 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword"); | ||
19604 | if( rc == SXERR_ABORT ){ | ||
19605 | /* Error count limit reached, abort immediately */ | ||
19606 | return SXERR_ABORT; | ||
19607 | } | ||
19608 | } | ||
19609 | /* Swap token streams */ | ||
19610 | pTmp = pGen->pEnd; | ||
19611 | pGen->pEnd = pEnd; | ||
19612 | /* Compile the expression */ | ||
19613 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19614 | if( rc == SXERR_ABORT ){ | ||
19615 | /* Expression handler request an operation abort [i.e: Out-of-memory] */ | ||
19616 | return SXERR_ABORT; | ||
19617 | } | ||
19618 | /* Update token stream */ | ||
19619 | while(pGen->pIn < pEnd ){ | ||
19620 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, | ||
19621 | "Switch: Unexpected token '%z'", &pGen->pIn->sData); | ||
19622 | if( rc == SXERR_ABORT ){ | ||
19623 | return SXERR_ABORT; | ||
19624 | } | ||
19625 | pGen->pIn++; | ||
19626 | } | ||
19627 | pGen->pIn = &pEnd[1]; | ||
19628 | pGen->pEnd = pTmp; | ||
19629 | if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd || | ||
19630 | (pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){ | ||
19631 | pTmp = pGen->pIn; | ||
19632 | if( pTmp >= pGen->pEnd ){ | ||
19633 | pTmp--; | ||
19634 | } | ||
19635 | /* Unexpected token */ | ||
19636 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData); | ||
19637 | if( rc == SXERR_ABORT ){ | ||
19638 | return SXERR_ABORT; | ||
19639 | } | ||
19640 | goto Synchronize; | ||
19641 | } | ||
19642 | pGen->pIn++; /* Jump the leading curly braces/colons */ | ||
19643 | /* Create the switch blocks container */ | ||
19644 | pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch)); | ||
19645 | if( pSwitch == 0 ){ | ||
19646 | /* Abort compilation */ | ||
19647 | return GenStateOutOfMem(pGen); | ||
19648 | } | ||
19649 | /* Zero the structure */ | ||
19650 | SyZero(pSwitch, sizeof(jx9_switch)); | ||
19651 | /* Initialize fields */ | ||
19652 | SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr)); | ||
19653 | /* Emit the switch instruction */ | ||
19654 | jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0); | ||
19655 | /* Compile case blocks */ | ||
19656 | for(;;){ | ||
19657 | sxu32 nKwrd; | ||
19658 | if( pGen->pIn >= pGen->pEnd ){ | ||
19659 | /* No more input to process */ | ||
19660 | break; | ||
19661 | } | ||
19662 | if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ | ||
19663 | if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){ | ||
19664 | /* Unexpected token */ | ||
19665 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", | ||
19666 | &pGen->pIn->sData); | ||
19667 | if( rc == SXERR_ABORT ){ | ||
19668 | return SXERR_ABORT; | ||
19669 | } | ||
19670 | /* FALL THROUGH */ | ||
19671 | } | ||
19672 | /* Block compiled */ | ||
19673 | break; | ||
19674 | } | ||
19675 | /* Extract the keyword */ | ||
19676 | nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
19677 | if( nKwrd == JX9_TKWRD_DEFAULT ){ | ||
19678 | /* | ||
19679 | * Accroding to the JX9 language reference manual | ||
19680 | * A special case is the default case. This case matches anything | ||
19681 | * that wasn't matched by the other cases. | ||
19682 | */ | ||
19683 | if( pSwitch->nDefault > 0 ){ | ||
19684 | /* Default case already compiled */ | ||
19685 | rc = jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled"); | ||
19686 | if( rc == SXERR_ABORT ){ | ||
19687 | return SXERR_ABORT; | ||
19688 | } | ||
19689 | } | ||
19690 | pGen->pIn++; /* Jump the 'default' keyword */ | ||
19691 | /* Compile the default block */ | ||
19692 | rc = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault); | ||
19693 | if( rc == SXERR_ABORT){ | ||
19694 | return SXERR_ABORT; | ||
19695 | }else if( rc == SXERR_EOF ){ | ||
19696 | break; | ||
19697 | } | ||
19698 | }else if( nKwrd == JX9_TKWRD_CASE ){ | ||
19699 | jx9_case_expr sCase; | ||
19700 | /* Standard case block */ | ||
19701 | pGen->pIn++; /* Jump the 'case' keyword */ | ||
19702 | /* initialize the structure */ | ||
19703 | SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
19704 | /* Compile the case expression */ | ||
19705 | rc = GenStateCompileCaseExpr(pGen, &sCase); | ||
19706 | if( rc == SXERR_ABORT ){ | ||
19707 | return SXERR_ABORT; | ||
19708 | } | ||
19709 | /* Compile the case block */ | ||
19710 | rc = GenStateCompileSwitchBlock(pGen,&sCase.nStart); | ||
19711 | /* Insert in the switch container */ | ||
19712 | SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase); | ||
19713 | if( rc == SXERR_ABORT){ | ||
19714 | return SXERR_ABORT; | ||
19715 | }else if( rc == SXERR_EOF ){ | ||
19716 | break; | ||
19717 | } | ||
19718 | }else{ | ||
19719 | /* Unexpected token */ | ||
19720 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", | ||
19721 | &pGen->pIn->sData); | ||
19722 | if( rc == SXERR_ABORT ){ | ||
19723 | return SXERR_ABORT; | ||
19724 | } | ||
19725 | break; | ||
19726 | } | ||
19727 | } | ||
19728 | /* Fix all jumps now the destination is resolved */ | ||
19729 | pSwitch->nOut = jx9VmInstrLength(pGen->pVm); | ||
19730 | GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm)); | ||
19731 | /* Release the loop block */ | ||
19732 | GenStateLeaveBlock(pGen, 0); | ||
19733 | if( pGen->pIn < pGen->pEnd ){ | ||
19734 | /* Jump the trailing curly braces */ | ||
19735 | pGen->pIn++; | ||
19736 | } | ||
19737 | /* Statement successfully compiled */ | ||
19738 | return SXRET_OK; | ||
19739 | Synchronize: | ||
19740 | /* Synchronize with the first semi-colon */ | ||
19741 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ | ||
19742 | pGen->pIn++; | ||
19743 | } | ||
19744 | return SXRET_OK; | ||
19745 | } | ||
19746 | /* | ||
19747 | * Process default argument values. That is, a function may define C++-style default value | ||
19748 | * as follows: | ||
19749 | * function makecoffee($type = "cappuccino") | ||
19750 | * { | ||
19751 | * return "Making a cup of $type.\n"; | ||
19752 | * } | ||
19753 | * Some features: | ||
19754 | * 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous | ||
19755 | * functions, array member, ..] | ||
19756 | * 2 -) Full type hinting: (Arguments are automatically casted to the desired type) | ||
19757 | * Example: | ||
19758 | * function a(string $a){} function b(int $a, string $c, float $d){} | ||
19759 | * 3 -) Function overloading!! | ||
19760 | * Example: | ||
19761 | * function foo($a) { | ||
19762 | * return $a.JX9_EOL; | ||
19763 | * } | ||
19764 | * function foo($a, $b) { | ||
19765 | * return $a + $b; | ||
19766 | * } | ||
19767 | * print foo(5); // Prints "5" | ||
19768 | * print foo(5, 2); // Prints "7" | ||
19769 | * // Same arg | ||
19770 | * function foo(string $a) | ||
19771 | * { | ||
19772 | * print "a is a string\n"; | ||
19773 | * dump($a); | ||
19774 | * } | ||
19775 | * function foo(int $a) | ||
19776 | * { | ||
19777 | * print "a is integer\n"; | ||
19778 | * dump($a); | ||
19779 | * } | ||
19780 | * function foo(array $a) | ||
19781 | * { | ||
19782 | * print "a is an array\n"; | ||
19783 | * dump($a); | ||
19784 | * } | ||
19785 | * foo('This is a great feature'); // a is a string [first foo] | ||
19786 | * foo(52); // a is integer [second foo] | ||
19787 | * foo(array(14, __TIME__, __DATE__)); // a is an array [third foo] | ||
19788 | * Please refer to the official documentation for more information on the powerful extension | ||
19789 | * introduced by the JX9 engine. | ||
19790 | */ | ||
19791 | static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd) | ||
19792 | { | ||
19793 | SyToken *pTmpIn, *pTmpEnd; | ||
19794 | SySet *pInstrContainer; | ||
19795 | sxi32 rc; | ||
19796 | /* Swap token stream */ | ||
19797 | SWAP_DELIMITER(pGen, pIn, pEnd); | ||
19798 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
19799 | jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode); | ||
19800 | /* Compile the expression holding the argument value */ | ||
19801 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
19802 | /* Emit the done instruction */ | ||
19803 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); | ||
19804 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
19805 | RE_SWAP_DELIMITER(pGen); | ||
19806 | if( rc == SXERR_ABORT ){ | ||
19807 | return SXERR_ABORT; | ||
19808 | } | ||
19809 | return SXRET_OK; | ||
19810 | } | ||
19811 | /* | ||
19812 | * Collect function arguments one after one. | ||
19813 | * According to the JX9 language reference manual. | ||
19814 | * Information may be passed to functions via the argument list, which is a comma-delimited | ||
19815 | * list of expressions. | ||
19816 | * JX9 supports passing arguments by value (the default), passing by reference | ||
19817 | * and default argument values. Variable-length argument lists are also supported, | ||
19818 | * see also the function references for func_num_args(), func_get_arg(), and func_get_args() | ||
19819 | * for more information. | ||
19820 | * Example #1 Passing arrays to functions | ||
19821 | * <?jx9 | ||
19822 | * function takes_array($input) | ||
19823 | * { | ||
19824 | * print "$input[0] + $input[1] = ", $input[0]+$input[1]; | ||
19825 | * } | ||
19826 | * ?> | ||
19827 | * Making arguments be passed by reference | ||
19828 | * By default, function arguments are passed by value (so that if the value of the argument | ||
19829 | * within the function is changed, it does not get changed outside of the function). | ||
19830 | * To allow a function to modify its arguments, they must be passed by reference. | ||
19831 | * To have an argument to a function always passed by reference, prepend an ampersand (&) | ||
19832 | * to the argument name in the function definition: | ||
19833 | * Example #2 Passing function parameters by reference | ||
19834 | * <?jx9 | ||
19835 | * function add_some_extra(&$string) | ||
19836 | * { | ||
19837 | * $string .= 'and something extra.'; | ||
19838 | * } | ||
19839 | * $str = 'This is a string, '; | ||
19840 | * add_some_extra($str); | ||
19841 | * print $str; // outputs 'This is a string, and something extra.' | ||
19842 | * ?> | ||
19843 | * | ||
19844 | * JX9 have introduced powerful extension including full type hinting, function overloading | ||
19845 | * complex agrument values.Please refer to the official documentation for more information | ||
19846 | * on these extension. | ||
19847 | */ | ||
19848 | static sxi32 GenStateCollectFuncArgs(jx9_vm_func *pFunc, jx9_gen_state *pGen, SyToken *pEnd) | ||
19849 | { | ||
19850 | jx9_vm_func_arg sArg; /* Current processed argument */ | ||
19851 | SyToken *pCur, *pIn; /* Token stream */ | ||
19852 | SyBlob sSig; /* Function signature */ | ||
19853 | char *zDup; /* Copy of argument name */ | ||
19854 | sxi32 rc; | ||
19855 | |||
19856 | pIn = pGen->pIn; | ||
19857 | pCur = 0; | ||
19858 | SyBlobInit(&sSig, &pGen->pVm->sAllocator); | ||
19859 | /* Process arguments one after one */ | ||
19860 | for(;;){ | ||
19861 | if( pIn >= pEnd ){ | ||
19862 | /* No more arguments to process */ | ||
19863 | break; | ||
19864 | } | ||
19865 | SyZero(&sArg, sizeof(jx9_vm_func_arg)); | ||
19866 | SySetInit(&sArg.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); | ||
19867 | if( pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ | ||
19868 | if( pIn->nType & JX9_TK_KEYWORD ){ | ||
19869 | sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData)); | ||
19870 | if( nKey & JX9_TKWRD_BOOL ){ | ||
19871 | sArg.nType = MEMOBJ_BOOL; | ||
19872 | }else if( nKey & JX9_TKWRD_INT ){ | ||
19873 | sArg.nType = MEMOBJ_INT; | ||
19874 | }else if( nKey & JX9_TKWRD_STRING ){ | ||
19875 | sArg.nType = MEMOBJ_STRING; | ||
19876 | }else if( nKey & JX9_TKWRD_FLOAT ){ | ||
19877 | sArg.nType = MEMOBJ_REAL; | ||
19878 | }else{ | ||
19879 | jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, | ||
19880 | "Invalid argument type '%z', Automatic cast will not be performed", | ||
19881 | &pIn->sData); | ||
19882 | } | ||
19883 | } | ||
19884 | pIn++; | ||
19885 | } | ||
19886 | if( pIn >= pEnd ){ | ||
19887 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Missing argument name"); | ||
19888 | return rc; | ||
19889 | } | ||
19890 | if( pIn >= pEnd || (pIn->nType & JX9_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
19891 | /* Invalid argument */ | ||
19892 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid argument name"); | ||
19893 | return rc; | ||
19894 | } | ||
19895 | pIn++; /* Jump the dollar sign */ | ||
19896 | /* Copy argument name */ | ||
19897 | zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, SyStringData(&pIn->sData), SyStringLength(&pIn->sData)); | ||
19898 | if( zDup == 0 ){ | ||
19899 | return GenStateOutOfMem(pGen); | ||
19900 | } | ||
19901 | SyStringInitFromBuf(&sArg.sName, zDup, SyStringLength(&pIn->sData)); | ||
19902 | pIn++; | ||
19903 | if( pIn < pEnd ){ | ||
19904 | if( pIn->nType & JX9_TK_EQUAL ){ | ||
19905 | SyToken *pDefend; | ||
19906 | sxi32 iNest = 0; | ||
19907 | pIn++; /* Jump the equal sign */ | ||
19908 | pDefend = pIn; | ||
19909 | /* Process the default value associated with this argument */ | ||
19910 | while( pDefend < pEnd ){ | ||
19911 | if( (pDefend->nType & JX9_TK_COMMA) && iNest <= 0 ){ | ||
19912 | break; | ||
19913 | } | ||
19914 | if( pDefend->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*[*/) ){ | ||
19915 | /* Increment nesting level */ | ||
19916 | iNest++; | ||
19917 | }else if( pDefend->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*]*/) ){ | ||
19918 | /* Decrement nesting level */ | ||
19919 | iNest--; | ||
19920 | } | ||
19921 | pDefend++; | ||
19922 | } | ||
19923 | if( pIn >= pDefend ){ | ||
19924 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Missing argument default value"); | ||
19925 | return rc; | ||
19926 | } | ||
19927 | /* Process default value */ | ||
19928 | rc = GenStateProcessArgValue(&(*pGen), &sArg, pIn, pDefend); | ||
19929 | if( rc != SXRET_OK ){ | ||
19930 | return rc; | ||
19931 | } | ||
19932 | /* Point beyond the default value */ | ||
19933 | pIn = pDefend; | ||
19934 | } | ||
19935 | if( pIn < pEnd && (pIn->nType & JX9_TK_COMMA) == 0 ){ | ||
19936 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Unexpected token '%z'", &pIn->sData); | ||
19937 | return rc; | ||
19938 | } | ||
19939 | pIn++; /* Jump the trailing comma */ | ||
19940 | } | ||
19941 | /* Append argument signature */ | ||
19942 | if( sArg.nType > 0 ){ | ||
19943 | int c; | ||
19944 | c = 'n'; /* cc warning */ | ||
19945 | /* Type leading character */ | ||
19946 | switch(sArg.nType){ | ||
19947 | case MEMOBJ_HASHMAP: | ||
19948 | /* Hashmap aka 'array' */ | ||
19949 | c = 'h'; | ||
19950 | break; | ||
19951 | case MEMOBJ_INT: | ||
19952 | /* Integer */ | ||
19953 | c = 'i'; | ||
19954 | break; | ||
19955 | case MEMOBJ_BOOL: | ||
19956 | /* Bool */ | ||
19957 | c = 'b'; | ||
19958 | break; | ||
19959 | case MEMOBJ_REAL: | ||
19960 | /* Float */ | ||
19961 | c = 'f'; | ||
19962 | break; | ||
19963 | case MEMOBJ_STRING: | ||
19964 | /* String */ | ||
19965 | c = 's'; | ||
19966 | break; | ||
19967 | default: | ||
19968 | break; | ||
19969 | } | ||
19970 | SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); | ||
19971 | } | ||
19972 | /* Save in the argument set */ | ||
19973 | SySetPut(&pFunc->aArgs, (const void *)&sArg); | ||
19974 | } | ||
19975 | if( SyBlobLength(&sSig) > 0 ){ | ||
19976 | /* Save function signature */ | ||
19977 | SyStringInitFromBuf(&pFunc->sSignature, SyBlobData(&sSig), SyBlobLength(&sSig)); | ||
19978 | } | ||
19979 | return SXRET_OK; | ||
19980 | } | ||
19981 | /* | ||
19982 | * Compile function [i.e: standard function, annonymous function or closure ] body. | ||
19983 | * Return SXRET_OK on success. Any other return value indicates failure | ||
19984 | * and this routine takes care of generating the appropriate error message. | ||
19985 | */ | ||
19986 | static sxi32 GenStateCompileFuncBody( | ||
19987 | jx9_gen_state *pGen, /* Code generator state */ | ||
19988 | jx9_vm_func *pFunc /* Function state */ | ||
19989 | ) | ||
19990 | { | ||
19991 | SySet *pInstrContainer; /* Instruction container */ | ||
19992 | GenBlock *pBlock; | ||
19993 | sxi32 rc; | ||
19994 | /* Attach the new function */ | ||
19995 | rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC,jx9VmInstrLength(pGen->pVm), pFunc, &pBlock); | ||
19996 | if( rc != SXRET_OK ){ | ||
19997 | return GenStateOutOfMem(pGen); | ||
19998 | } | ||
19999 | /* Swap bytecode containers */ | ||
20000 | pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); | ||
20001 | jx9VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode); | ||
20002 | /* Compile the body */ | ||
20003 | jx9CompileBlock(&(*pGen)); | ||
20004 | /* Emit the final return if not yet done */ | ||
20005 | jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, 0, 0, 0, 0); | ||
20006 | /* Restore the default container */ | ||
20007 | jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); | ||
20008 | /* Leave function block */ | ||
20009 | GenStateLeaveBlock(&(*pGen), 0); | ||
20010 | if( rc == SXERR_ABORT ){ | ||
20011 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
20012 | return SXERR_ABORT; | ||
20013 | } | ||
20014 | /* All done, function body compiled */ | ||
20015 | return SXRET_OK; | ||
20016 | } | ||
20017 | /* | ||
20018 | * Compile a JX9 function whether is a Standard or Annonymous function. | ||
20019 | * According to the JX9 language reference manual. | ||
20020 | * Function names follow the same rules as other labels in JX9. A valid function name | ||
20021 | * starts with a letter or underscore, followed by any number of letters, numbers, or | ||
20022 | * underscores. As a regular expression, it would be expressed thus: | ||
20023 | * [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. | ||
20024 | * Functions need not be defined before they are referenced. | ||
20025 | * All functions and objectes in JX9 have the global scope - they can be called outside | ||
20026 | * a function even if they were defined inside and vice versa. | ||
20027 | * It is possible to call recursive functions in JX9. However avoid recursive function/method | ||
20028 | * calls with over 32-64 recursion levels. | ||
20029 | * | ||
20030 | * JX9 have introduced powerful extension including full type hinting, function overloading, | ||
20031 | * complex agrument values and more. Please refer to the official documentation for more information | ||
20032 | * on these extension. | ||
20033 | */ | ||
20034 | static sxi32 GenStateCompileFunc( | ||
20035 | jx9_gen_state *pGen, /* Code generator state */ | ||
20036 | SyString *pName, /* Function name. NULL otherwise */ | ||
20037 | sxi32 iFlags, /* Control flags */ | ||
20038 | jx9_vm_func **ppFunc /* OUT: function state */ | ||
20039 | ) | ||
20040 | { | ||
20041 | jx9_vm_func *pFunc; | ||
20042 | SyToken *pEnd; | ||
20043 | sxu32 nLine; | ||
20044 | char *zName; | ||
20045 | sxi32 rc; | ||
20046 | /* Extract line number */ | ||
20047 | nLine = pGen->pIn->nLine; | ||
20048 | /* Jump the left parenthesis '(' */ | ||
20049 | pGen->pIn++; | ||
20050 | /* Delimit the function signature */ | ||
20051 | jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); | ||
20052 | if( pEnd >= pGen->pEnd ){ | ||
20053 | /* Syntax error */ | ||
20054 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after function '%z' signature", pName); | ||
20055 | if( rc == SXERR_ABORT ){ | ||
20056 | /* Error count limit reached, abort immediately */ | ||
20057 | return SXERR_ABORT; | ||
20058 | } | ||
20059 | pGen->pIn = pGen->pEnd; | ||
20060 | return SXRET_OK; | ||
20061 | } | ||
20062 | /* Create the function state */ | ||
20063 | pFunc = (jx9_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_vm_func)); | ||
20064 | if( pFunc == 0 ){ | ||
20065 | goto OutOfMem; | ||
20066 | } | ||
20067 | /* function ID */ | ||
20068 | zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); | ||
20069 | if( zName == 0 ){ | ||
20070 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
20071 | goto OutOfMem; | ||
20072 | } | ||
20073 | /* Initialize the function state */ | ||
20074 | jx9VmInitFuncState(pGen->pVm, pFunc, zName, pName->nByte, iFlags, 0); | ||
20075 | if( pGen->pIn < pEnd ){ | ||
20076 | /* Collect function arguments */ | ||
20077 | rc = GenStateCollectFuncArgs(pFunc, &(*pGen), pEnd); | ||
20078 | if( rc == SXERR_ABORT ){ | ||
20079 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
20080 | return SXERR_ABORT; | ||
20081 | } | ||
20082 | } | ||
20083 | /* Compile function body */ | ||
20084 | pGen->pIn = &pEnd[1]; | ||
20085 | /* Compile the body */ | ||
20086 | rc = GenStateCompileFuncBody(&(*pGen), pFunc); | ||
20087 | if( rc == SXERR_ABORT ){ | ||
20088 | return SXERR_ABORT; | ||
20089 | } | ||
20090 | if( ppFunc ){ | ||
20091 | *ppFunc = pFunc; | ||
20092 | } | ||
20093 | /* Finally register the function */ | ||
20094 | rc = jx9VmInstallUserFunction(pGen->pVm, pFunc, 0); | ||
20095 | return rc; | ||
20096 | /* Fall through if something goes wrong */ | ||
20097 | OutOfMem: | ||
20098 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
20099 | * a tiny chunk of memory, there is no much we can do here. | ||
20100 | */ | ||
20101 | return GenStateOutOfMem(pGen); | ||
20102 | } | ||
20103 | /* | ||
20104 | * Compile a standard JX9 function. | ||
20105 | * Refer to the block-comment above for more information. | ||
20106 | */ | ||
20107 | static sxi32 jx9CompileFunction(jx9_gen_state *pGen) | ||
20108 | { | ||
20109 | SyString *pName; | ||
20110 | sxi32 iFlags; | ||
20111 | sxu32 nLine; | ||
20112 | sxi32 rc; | ||
20113 | |||
20114 | nLine = pGen->pIn->nLine; | ||
20115 | pGen->pIn++; /* Jump the 'function' keyword */ | ||
20116 | iFlags = 0; | ||
20117 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ | ||
20118 | /* Invalid function name */ | ||
20119 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Invalid function name"); | ||
20120 | if( rc == SXERR_ABORT ){ | ||
20121 | return SXERR_ABORT; | ||
20122 | } | ||
20123 | /* Sychronize with the next semi-colon or braces*/ | ||
20124 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
20125 | pGen->pIn++; | ||
20126 | } | ||
20127 | return SXRET_OK; | ||
20128 | } | ||
20129 | pName = &pGen->pIn->sData; | ||
20130 | nLine = pGen->pIn->nLine; | ||
20131 | /* Jump the function name */ | ||
20132 | pGen->pIn++; | ||
20133 | if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
20134 | /* Syntax error */ | ||
20135 | rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after function name '%z'", pName); | ||
20136 | if( rc == SXERR_ABORT ){ | ||
20137 | /* Error count limit reached, abort immediately */ | ||
20138 | return SXERR_ABORT; | ||
20139 | } | ||
20140 | /* Sychronize with the next semi-colon or '{' */ | ||
20141 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ | ||
20142 | pGen->pIn++; | ||
20143 | } | ||
20144 | return SXRET_OK; | ||
20145 | } | ||
20146 | /* Compile function body */ | ||
20147 | rc = GenStateCompileFunc(&(*pGen),pName,iFlags,0); | ||
20148 | return rc; | ||
20149 | } | ||
20150 | /* | ||
20151 | * Generate bytecode for a given expression tree. | ||
20152 | * If something goes wrong while generating bytecode | ||
20153 | * for the expression tree (A very unlikely scenario) | ||
20154 | * this function takes care of generating the appropriate | ||
20155 | * error message. | ||
20156 | */ | ||
20157 | static sxi32 GenStateEmitExprCode( | ||
20158 | jx9_gen_state *pGen, /* Code generator state */ | ||
20159 | jx9_expr_node *pNode, /* Root of the expression tree */ | ||
20160 | sxi32 iFlags /* Control flags */ | ||
20161 | ) | ||
20162 | { | ||
20163 | VmInstr *pInstr; | ||
20164 | sxu32 nJmpIdx; | ||
20165 | sxi32 iP1 = 0; | ||
20166 | sxu32 iP2 = 0; | ||
20167 | void *p3 = 0; | ||
20168 | sxi32 iVmOp; | ||
20169 | sxi32 rc; | ||
20170 | if( pNode->xCode ){ | ||
20171 | SyToken *pTmpIn, *pTmpEnd; | ||
20172 | /* Compile node */ | ||
20173 | SWAP_DELIMITER(pGen, pNode->pStart, pNode->pEnd); | ||
20174 | rc = pNode->xCode(&(*pGen), iFlags); | ||
20175 | RE_SWAP_DELIMITER(pGen); | ||
20176 | return rc; | ||
20177 | } | ||
20178 | if( pNode->pOp == 0 ){ | ||
20179 | jx9GenCompileError(&(*pGen), E_ERROR, pNode->pStart->nLine, | ||
20180 | "Invalid expression node, JX9 is aborting compilation"); | ||
20181 | return SXERR_ABORT; | ||
20182 | } | ||
20183 | iVmOp = pNode->pOp->iVmOp; | ||
20184 | if( pNode->pOp->iOp == EXPR_OP_QUESTY ){ | ||
20185 | sxu32 nJz, nJmp; | ||
20186 | /* Ternary operator require special handling */ | ||
20187 | /* Phase#1: Compile the condition */ | ||
20188 | rc = GenStateEmitExprCode(&(*pGen), pNode->pCond, iFlags); | ||
20189 | if( rc != SXRET_OK ){ | ||
20190 | return rc; | ||
20191 | } | ||
20192 | nJz = nJmp = 0; /* cc -O6 warning */ | ||
20193 | /* Phase#2: Emit the false jump */ | ||
20194 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJz); | ||
20195 | if( pNode->pLeft ){ | ||
20196 | /* Phase#3: Compile the 'then' expression */ | ||
20197 | rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); | ||
20198 | if( rc != SXRET_OK ){ | ||
20199 | return rc; | ||
20200 | } | ||
20201 | } | ||
20202 | /* Phase#4: Emit the unconditional jump */ | ||
20203 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJmp); | ||
20204 | /* Phase#5: Fix the false jump now the jump destination is resolved. */ | ||
20205 | pInstr = jx9VmGetInstr(pGen->pVm, nJz); | ||
20206 | if( pInstr ){ | ||
20207 | pInstr->iP2 = jx9VmInstrLength(pGen->pVm); | ||
20208 | } | ||
20209 | /* Phase#6: Compile the 'else' expression */ | ||
20210 | if( pNode->pRight ){ | ||
20211 | rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); | ||
20212 | if( rc != SXRET_OK ){ | ||
20213 | return rc; | ||
20214 | } | ||
20215 | } | ||
20216 | if( nJmp > 0 ){ | ||
20217 | /* Phase#7: Fix the unconditional jump */ | ||
20218 | pInstr = jx9VmGetInstr(pGen->pVm, nJmp); | ||
20219 | if( pInstr ){ | ||
20220 | pInstr->iP2 = jx9VmInstrLength(pGen->pVm); | ||
20221 | } | ||
20222 | } | ||
20223 | /* All done */ | ||
20224 | return SXRET_OK; | ||
20225 | } | ||
20226 | /* Generate code for the left tree */ | ||
20227 | if( pNode->pLeft ){ | ||
20228 | if( iVmOp == JX9_OP_CALL ){ | ||
20229 | jx9_expr_node **apNode; | ||
20230 | sxi32 n; | ||
20231 | /* Recurse and generate bytecodes for function arguments */ | ||
20232 | apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); | ||
20233 | /* Read-only load */ | ||
20234 | iFlags |= EXPR_FLAG_RDONLY_LOAD; | ||
20235 | for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ | ||
20236 | rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); | ||
20237 | if( rc != SXRET_OK ){ | ||
20238 | return rc; | ||
20239 | } | ||
20240 | } | ||
20241 | /* Total number of given arguments */ | ||
20242 | iP1 = (sxi32)SySetUsed(&pNode->aNodeArgs); | ||
20243 | /* Remove stale flags now */ | ||
20244 | iFlags &= ~EXPR_FLAG_RDONLY_LOAD; | ||
20245 | } | ||
20246 | rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); | ||
20247 | if( rc != SXRET_OK ){ | ||
20248 | return rc; | ||
20249 | } | ||
20250 | if( iVmOp == JX9_OP_CALL ){ | ||
20251 | pInstr = jx9VmPeekInstr(pGen->pVm); | ||
20252 | if( pInstr ){ | ||
20253 | if ( pInstr->iOp == JX9_OP_LOADC ){ | ||
20254 | /* Prevent constant expansion */ | ||
20255 | pInstr->iP1 = 0; | ||
20256 | }else if( pInstr->iOp == JX9_OP_MEMBER /* $a.b(1, 2, 3) */ ){ | ||
20257 | /* Annonymous function call, flag that */ | ||
20258 | pInstr->iP2 = 1; | ||
20259 | } | ||
20260 | } | ||
20261 | }else if( iVmOp == JX9_OP_LOAD_IDX ){ | ||
20262 | jx9_expr_node **apNode; | ||
20263 | sxi32 n; | ||
20264 | /* Recurse and generate bytecodes for array index */ | ||
20265 | apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); | ||
20266 | for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ | ||
20267 | rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); | ||
20268 | if( rc != SXRET_OK ){ | ||
20269 | return rc; | ||
20270 | } | ||
20271 | } | ||
20272 | if( SySetUsed(&pNode->aNodeArgs) > 0 ){ | ||
20273 | iP1 = 1; /* Node have an index associated with it */ | ||
20274 | } | ||
20275 | if( iFlags & EXPR_FLAG_LOAD_IDX_STORE ){ | ||
20276 | /* Create an empty entry when the desired index is not found */ | ||
20277 | iP2 = 1; | ||
20278 | } | ||
20279 | }else if( pNode->pOp->iOp == EXPR_OP_COMMA ){ | ||
20280 | /* POP the left node */ | ||
20281 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
20282 | } | ||
20283 | } | ||
20284 | rc = SXRET_OK; | ||
20285 | nJmpIdx = 0; | ||
20286 | /* Generate code for the right tree */ | ||
20287 | if( pNode->pRight ){ | ||
20288 | if( iVmOp == JX9_OP_LAND ){ | ||
20289 | /* Emit the false jump so we can short-circuit the logical and */ | ||
20290 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); | ||
20291 | }else if (iVmOp == JX9_OP_LOR ){ | ||
20292 | /* Emit the true jump so we can short-circuit the logical or*/ | ||
20293 | jx9VmEmitInstr(pGen->pVm, JX9_OP_JNZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); | ||
20294 | }else if( pNode->pOp->iPrec == 18 /* Combined binary operators [i.e: =, '.=', '+=', *=' ...] precedence */ ){ | ||
20295 | iFlags |= EXPR_FLAG_LOAD_IDX_STORE; | ||
20296 | } | ||
20297 | rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); | ||
20298 | if( iVmOp == JX9_OP_STORE ){ | ||
20299 | pInstr = jx9VmPeekInstr(pGen->pVm); | ||
20300 | if( pInstr ){ | ||
20301 | if(pInstr->iOp == JX9_OP_MEMBER ){ | ||
20302 | /* Perform a member store operation [i.e: $this.x = 50] */ | ||
20303 | iP2 = 1; | ||
20304 | }else{ | ||
20305 | if( pInstr->iOp == JX9_OP_LOAD_IDX ){ | ||
20306 | /* Transform the STORE instruction to STORE_IDX instruction */ | ||
20307 | iVmOp = JX9_OP_STORE_IDX; | ||
20308 | iP1 = pInstr->iP1; | ||
20309 | }else{ | ||
20310 | p3 = pInstr->p3; | ||
20311 | } | ||
20312 | /* POP the last dynamic load instruction */ | ||
20313 | (void)jx9VmPopInstr(pGen->pVm); | ||
20314 | } | ||
20315 | } | ||
20316 | } | ||
20317 | } | ||
20318 | if( iVmOp > 0 ){ | ||
20319 | if( iVmOp == JX9_OP_INCR || iVmOp == JX9_OP_DECR ){ | ||
20320 | if( pNode->iFlags & EXPR_NODE_PRE_INCR ){ | ||
20321 | /* Pre-increment/decrement operator [i.e: ++$i, --$j ] */ | ||
20322 | iP1 = 1; | ||
20323 | } | ||
20324 | } | ||
20325 | /* Finally, emit the VM instruction associated with this operator */ | ||
20326 | jx9VmEmitInstr(pGen->pVm, iVmOp, iP1, iP2, p3, 0); | ||
20327 | if( nJmpIdx > 0 ){ | ||
20328 | /* Fix short-circuited jumps now the destination is resolved */ | ||
20329 | pInstr = jx9VmGetInstr(pGen->pVm, nJmpIdx); | ||
20330 | if( pInstr ){ | ||
20331 | pInstr->iP2 = jx9VmInstrLength(pGen->pVm); | ||
20332 | } | ||
20333 | } | ||
20334 | } | ||
20335 | return rc; | ||
20336 | } | ||
20337 | /* | ||
20338 | * Compile a JX9 expression. | ||
20339 | * According to the JX9 language reference manual: | ||
20340 | * Expressions are the most important building stones of JX9. | ||
20341 | * In JX9, almost anything you write is an expression. | ||
20342 | * The simplest yet most accurate way to define an expression | ||
20343 | * is "anything that has a value". | ||
20344 | * If something goes wrong while compiling the expression, this | ||
20345 | * function takes care of generating the appropriate error | ||
20346 | * message. | ||
20347 | */ | ||
20348 | static sxi32 jx9CompileExpr( | ||
20349 | jx9_gen_state *pGen, /* Code generator state */ | ||
20350 | sxi32 iFlags, /* Control flags */ | ||
20351 | sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ | ||
20352 | ) | ||
20353 | { | ||
20354 | jx9_expr_node *pRoot; | ||
20355 | SySet sExprNode; | ||
20356 | SyToken *pEnd; | ||
20357 | sxi32 nExpr; | ||
20358 | sxi32 iNest; | ||
20359 | sxi32 rc; | ||
20360 | /* Initialize worker variables */ | ||
20361 | nExpr = 0; | ||
20362 | pRoot = 0; | ||
20363 | SySetInit(&sExprNode, &pGen->pVm->sAllocator, sizeof(jx9_expr_node *)); | ||
20364 | SySetAlloc(&sExprNode, 0x10); | ||
20365 | rc = SXRET_OK; | ||
20366 | /* Delimit the expression */ | ||
20367 | pEnd = pGen->pIn; | ||
20368 | iNest = 0; | ||
20369 | while( pEnd < pGen->pEnd ){ | ||
20370 | if( pEnd->nType & JX9_TK_OCB /* '{' */ ){ | ||
20371 | /* Ticket 1433-30: Annonymous/Closure functions body */ | ||
20372 | iNest++; | ||
20373 | }else if(pEnd->nType & JX9_TK_CCB /* '}' */ ){ | ||
20374 | iNest--; | ||
20375 | }else if( pEnd->nType & JX9_TK_SEMI /* ';' */ ){ | ||
20376 | if( iNest <= 0 ){ | ||
20377 | break; | ||
20378 | } | ||
20379 | } | ||
20380 | pEnd++; | ||
20381 | } | ||
20382 | if( iFlags & EXPR_FLAG_COMMA_STATEMENT ){ | ||
20383 | SyToken *pEnd2 = pGen->pIn; | ||
20384 | iNest = 0; | ||
20385 | /* Stop at the first comma */ | ||
20386 | while( pEnd2 < pEnd ){ | ||
20387 | if( pEnd2->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_LPAREN/*'('*/) ){ | ||
20388 | iNest++; | ||
20389 | }else if(pEnd2->nType & (JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*']'*/|JX9_TK_RPAREN/*')'*/)){ | ||
20390 | iNest--; | ||
20391 | }else if( pEnd2->nType & JX9_TK_COMMA /*','*/ ){ | ||
20392 | if( iNest <= 0 ){ | ||
20393 | break; | ||
20394 | } | ||
20395 | } | ||
20396 | pEnd2++; | ||
20397 | } | ||
20398 | if( pEnd2 <pEnd ){ | ||
20399 | pEnd = pEnd2; | ||
20400 | } | ||
20401 | } | ||
20402 | if( pEnd > pGen->pIn ){ | ||
20403 | SyToken *pTmp = pGen->pEnd; | ||
20404 | /* Swap delimiter */ | ||
20405 | pGen->pEnd = pEnd; | ||
20406 | /* Try to get an expression tree */ | ||
20407 | rc = jx9ExprMakeTree(&(*pGen), &sExprNode, &pRoot); | ||
20408 | if( rc == SXRET_OK && pRoot ){ | ||
20409 | rc = SXRET_OK; | ||
20410 | if( xTreeValidator ){ | ||
20411 | /* Call the upper layer validator callback */ | ||
20412 | rc = xTreeValidator(&(*pGen), pRoot); | ||
20413 | } | ||
20414 | if( rc != SXERR_ABORT ){ | ||
20415 | /* Generate code for the given tree */ | ||
20416 | rc = GenStateEmitExprCode(&(*pGen), pRoot, iFlags); | ||
20417 | } | ||
20418 | nExpr = 1; | ||
20419 | } | ||
20420 | /* Release the whole tree */ | ||
20421 | jx9ExprFreeTree(&(*pGen), &sExprNode); | ||
20422 | /* Synchronize token stream */ | ||
20423 | pGen->pEnd = pTmp; | ||
20424 | pGen->pIn = pEnd; | ||
20425 | if( rc == SXERR_ABORT ){ | ||
20426 | SySetRelease(&sExprNode); | ||
20427 | return SXERR_ABORT; | ||
20428 | } | ||
20429 | } | ||
20430 | SySetRelease(&sExprNode); | ||
20431 | return nExpr > 0 ? SXRET_OK : SXERR_EMPTY; | ||
20432 | } | ||
20433 | /* | ||
20434 | * Return a pointer to the node construct handler associated | ||
20435 | * with a given node type [i.e: string, integer, float, ...]. | ||
20436 | */ | ||
20437 | JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType) | ||
20438 | { | ||
20439 | if( nNodeType & JX9_TK_NUM ){ | ||
20440 | /* Numeric literal: Either real or integer */ | ||
20441 | return jx9CompileNumLiteral; | ||
20442 | }else if( nNodeType & JX9_TK_DSTR ){ | ||
20443 | /* Double quoted string */ | ||
20444 | return jx9CompileString; | ||
20445 | }else if( nNodeType & JX9_TK_SSTR ){ | ||
20446 | /* Single quoted string */ | ||
20447 | return jx9CompileSimpleString; | ||
20448 | }else if( nNodeType & JX9_TK_NOWDOC ){ | ||
20449 | /* Nowdoc */ | ||
20450 | return jx9CompileNowdoc; | ||
20451 | } | ||
20452 | return 0; | ||
20453 | } | ||
20454 | /* | ||
20455 | * Jx9 Language construct table. | ||
20456 | */ | ||
20457 | static const LangConstruct aLangConstruct[] = { | ||
20458 | { JX9_TKWRD_IF, jx9CompileIf }, | ||
20459 | { JX9_TKWRD_FUNCTION, jx9CompileFunction }, | ||
20460 | { JX9_TKWRD_FOREACH, jx9CompileForeach }, | ||
20461 | { JX9_TKWRD_WHILE, jx9CompileWhile }, | ||
20462 | { JX9_TKWRD_FOR, jx9CompileFor }, | ||
20463 | { JX9_TKWRD_SWITCH, jx9CompileSwitch }, | ||
20464 | { JX9_TKWRD_DIE, jx9CompileHalt }, | ||
20465 | { JX9_TKWRD_EXIT, jx9CompileHalt }, | ||
20466 | { JX9_TKWRD_RETURN, jx9CompileReturn }, | ||
20467 | { JX9_TKWRD_BREAK, jx9CompileBreak }, | ||
20468 | { JX9_TKWRD_CONTINUE, jx9CompileContinue }, | ||
20469 | { JX9_TKWRD_STATIC, jx9CompileStatic }, | ||
20470 | { JX9_TKWRD_UPLINK, jx9CompileUplink }, | ||
20471 | { JX9_TKWRD_CONST, jx9CompileConstant }, | ||
20472 | }; | ||
20473 | /* | ||
20474 | * Return a pointer to the statement handler routine associated | ||
20475 | * with a given JX9 keyword [i.e: if, for, while, ...]. | ||
20476 | */ | ||
20477 | static ProcLangConstruct GenStateGetStatementHandler( | ||
20478 | sxu32 nKeywordID /* Keyword ID*/ | ||
20479 | ) | ||
20480 | { | ||
20481 | sxu32 n = 0; | ||
20482 | for(;;){ | ||
20483 | if( n >= SX_ARRAYSIZE(aLangConstruct) ){ | ||
20484 | break; | ||
20485 | } | ||
20486 | if( aLangConstruct[n].nID == nKeywordID ){ | ||
20487 | /* Return a pointer to the handler. | ||
20488 | */ | ||
20489 | return aLangConstruct[n].xConstruct; | ||
20490 | } | ||
20491 | n++; | ||
20492 | } | ||
20493 | /* Not a language construct */ | ||
20494 | return 0; | ||
20495 | } | ||
20496 | /* | ||
20497 | * Compile a jx9 program. | ||
20498 | * If something goes wrong while compiling the Jx9 chunk, this function | ||
20499 | * takes care of generating the appropriate error message. | ||
20500 | */ | ||
20501 | static sxi32 GenStateCompileChunk( | ||
20502 | jx9_gen_state *pGen, /* Code generator state */ | ||
20503 | sxi32 iFlags /* Compile flags */ | ||
20504 | ) | ||
20505 | { | ||
20506 | ProcLangConstruct xCons; | ||
20507 | sxi32 rc; | ||
20508 | rc = SXRET_OK; /* Prevent compiler warning */ | ||
20509 | for(;;){ | ||
20510 | if( pGen->pIn >= pGen->pEnd ){ | ||
20511 | /* No more input to process */ | ||
20512 | break; | ||
20513 | } | ||
20514 | xCons = 0; | ||
20515 | if( pGen->pIn->nType & JX9_TK_KEYWORD ){ | ||
20516 | sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); | ||
20517 | /* Try to extract a language construct handler */ | ||
20518 | xCons = GenStateGetStatementHandler(nKeyword); | ||
20519 | if( xCons == 0 && !jx9IsLangConstruct(nKeyword) ){ | ||
20520 | rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, | ||
20521 | "Syntax error: Unexpected keyword '%z'", | ||
20522 | &pGen->pIn->sData); | ||
20523 | if( rc == SXERR_ABORT ){ | ||
20524 | break; | ||
20525 | } | ||
20526 | /* Synchronize with the first semi-colon and avoid compiling | ||
20527 | * this erroneous statement. | ||
20528 | */ | ||
20529 | xCons = jx9ErrorRecover; | ||
20530 | } | ||
20531 | } | ||
20532 | if( xCons == 0 ){ | ||
20533 | /* Assume an expression an try to compile it */ | ||
20534 | rc = jx9CompileExpr(&(*pGen), 0, 0); | ||
20535 | if( rc != SXERR_EMPTY ){ | ||
20536 | /* Pop l-value */ | ||
20537 | jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); | ||
20538 | } | ||
20539 | }else{ | ||
20540 | /* Go compile the sucker */ | ||
20541 | rc = xCons(&(*pGen)); | ||
20542 | } | ||
20543 | if( rc == SXERR_ABORT ){ | ||
20544 | /* Request to abort compilation */ | ||
20545 | break; | ||
20546 | } | ||
20547 | /* Ignore trailing semi-colons ';' */ | ||
20548 | while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ | ||
20549 | pGen->pIn++; | ||
20550 | } | ||
20551 | if( iFlags & JX9_COMPILE_SINGLE_STMT ){ | ||
20552 | /* Compile a single statement and return */ | ||
20553 | break; | ||
20554 | } | ||
20555 | /* LOOP ONE */ | ||
20556 | /* LOOP TWO */ | ||
20557 | /* LOOP THREE */ | ||
20558 | /* LOOP FOUR */ | ||
20559 | } | ||
20560 | /* Return compilation status */ | ||
20561 | return rc; | ||
20562 | } | ||
20563 | /* | ||
20564 | * Compile a raw chunk. The raw chunk can contain JX9 code embedded | ||
20565 | * in HTML, XML and so on. This function handle all the stuff. | ||
20566 | * This is the only compile interface exported from this file. | ||
20567 | */ | ||
20568 | JX9_PRIVATE sxi32 jx9CompileScript( | ||
20569 | jx9_vm *pVm, /* Generate JX9 bytecodes for this Virtual Machine */ | ||
20570 | SyString *pScript, /* Script to compile */ | ||
20571 | sxi32 iFlags /* Compile flags */ | ||
20572 | ) | ||
20573 | { | ||
20574 | jx9_gen_state *pGen; | ||
20575 | SySet aToken; | ||
20576 | sxi32 rc; | ||
20577 | if( pScript->nByte < 1 ){ | ||
20578 | /* Nothing to compile */ | ||
20579 | return JX9_OK; | ||
20580 | } | ||
20581 | /* Initialize the tokens containers */ | ||
20582 | SySetInit(&aToken, &pVm->sAllocator, sizeof(SyToken)); | ||
20583 | SySetAlloc(&aToken, 0xc0); | ||
20584 | pGen = &pVm->sCodeGen; | ||
20585 | rc = JX9_OK; | ||
20586 | /* Tokenize the JX9 chunk first */ | ||
20587 | jx9Tokenize(pScript->zString,pScript->nByte,&aToken); | ||
20588 | if( SySetUsed(&aToken) < 1 ){ | ||
20589 | return SXERR_EMPTY; | ||
20590 | } | ||
20591 | /* Point to the head and tail of the token stream. */ | ||
20592 | pGen->pIn = (SyToken *)SySetBasePtr(&aToken); | ||
20593 | pGen->pEnd = &pGen->pIn[SySetUsed(&aToken)]; | ||
20594 | /* Compile the chunk */ | ||
20595 | rc = GenStateCompileChunk(pGen,iFlags); | ||
20596 | /* Cleanup */ | ||
20597 | SySetRelease(&aToken); | ||
20598 | return rc; | ||
20599 | } | ||
20600 | /* | ||
20601 | * Utility routines.Initialize the code generator. | ||
20602 | */ | ||
20603 | JX9_PRIVATE sxi32 jx9InitCodeGenerator( | ||
20604 | jx9_vm *pVm, /* Target VM */ | ||
20605 | ProcConsumer xErr, /* Error log consumer callabck */ | ||
20606 | void *pErrData /* Last argument to xErr() */ | ||
20607 | ) | ||
20608 | { | ||
20609 | jx9_gen_state *pGen = &pVm->sCodeGen; | ||
20610 | /* Zero the structure */ | ||
20611 | SyZero(pGen, sizeof(jx9_gen_state)); | ||
20612 | /* Initial state */ | ||
20613 | pGen->pVm = &(*pVm); | ||
20614 | pGen->xErr = xErr; | ||
20615 | pGen->pErrData = pErrData; | ||
20616 | SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0); | ||
20617 | SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0); | ||
20618 | /* Create the global scope */ | ||
20619 | GenStateInitBlock(pGen, &pGen->sGlobal,GEN_BLOCK_GLOBAL,jx9VmInstrLength(&(*pVm)), 0); | ||
20620 | /* Point to the global scope */ | ||
20621 | pGen->pCurrent = &pGen->sGlobal; | ||
20622 | return SXRET_OK; | ||
20623 | } | ||
20624 | /* | ||
20625 | * Utility routines. Reset the code generator to it's initial state. | ||
20626 | */ | ||
20627 | JX9_PRIVATE sxi32 jx9ResetCodeGenerator( | ||
20628 | jx9_vm *pVm, /* Target VM */ | ||
20629 | ProcConsumer xErr, /* Error log consumer callabck */ | ||
20630 | void *pErrData /* Last argument to xErr() */ | ||
20631 | ) | ||
20632 | { | ||
20633 | jx9_gen_state *pGen = &pVm->sCodeGen; | ||
20634 | GenBlock *pBlock, *pParent; | ||
20635 | /* Point to the global scope */ | ||
20636 | pBlock = pGen->pCurrent; | ||
20637 | while( pBlock->pParent != 0 ){ | ||
20638 | pParent = pBlock->pParent; | ||
20639 | GenStateFreeBlock(pBlock); | ||
20640 | pBlock = pParent; | ||
20641 | } | ||
20642 | pGen->xErr = xErr; | ||
20643 | pGen->pErrData = pErrData; | ||
20644 | pGen->pCurrent = &pGen->sGlobal; | ||
20645 | pGen->pIn = pGen->pEnd = 0; | ||
20646 | pGen->nErr = 0; | ||
20647 | return SXRET_OK; | ||
20648 | } | ||
20649 | /* | ||
20650 | * Generate a compile-time error message. | ||
20651 | * If the error count limit is reached (usually 15 error message) | ||
20652 | * this function return SXERR_ABORT.In that case upper-layers must | ||
20653 | * abort compilation immediately. | ||
20654 | */ | ||
20655 | JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...) | ||
20656 | { | ||
20657 | SyBlob *pWorker = &pGen->pVm->pEngine->xConf.sErrConsumer; | ||
20658 | const char *zErr = "Error"; | ||
20659 | va_list ap; | ||
20660 | if( nErrType == E_ERROR ){ | ||
20661 | /* Increment the error counter */ | ||
20662 | pGen->nErr++; | ||
20663 | if( pGen->nErr > 15 ){ | ||
20664 | /* Error count limit reached */ | ||
20665 | SyBlobFormat(pWorker, "%u Error count limit reached, JX9 is aborting compilation\n", nLine); | ||
20666 | /* Abort immediately */ | ||
20667 | return SXERR_ABORT; | ||
20668 | } | ||
20669 | } | ||
20670 | switch(nErrType){ | ||
20671 | case E_WARNING: zErr = "Warning"; break; | ||
20672 | case E_PARSE: zErr = "Parse error"; break; | ||
20673 | case E_NOTICE: zErr = "Notice"; break; | ||
20674 | default: | ||
20675 | break; | ||
20676 | } | ||
20677 | /* Format the error message */ | ||
20678 | SyBlobFormat(pWorker, "%u %s: ", nLine, zErr); | ||
20679 | va_start(ap, zFormat); | ||
20680 | SyBlobFormatAp(pWorker, zFormat, ap); | ||
20681 | va_end(ap); | ||
20682 | /* Append a new line */ | ||
20683 | SyBlobAppend(pWorker, (const void *)"\n", sizeof(char)); | ||
20684 | return JX9_OK; | ||
20685 | } | ||
20686 | /* | ||
20687 | * ---------------------------------------------------------- | ||
20688 | * File: jx9_const.c | ||
20689 | * MD5: f3980b00dd1eda0bb2b749424a8dfffe | ||
20690 | * ---------------------------------------------------------- | ||
20691 | */ | ||
20692 | /* | ||
20693 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
20694 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
20695 | * Version 1.7.2 | ||
20696 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
20697 | * please contact Symisc Systems via: | ||
20698 | * legal@symisc.net | ||
20699 | * licensing@symisc.net | ||
20700 | * contact@symisc.net | ||
20701 | * or visit: | ||
20702 | * http://jx9.symisc.net/ | ||
20703 | */ | ||
20704 | /* $SymiscID: const.c v1.7 Win7 2012-12-13 00:01 stable <chm@symisc.net> $ */ | ||
20705 | #ifndef JX9_AMALGAMATION | ||
20706 | #include "jx9Int.h" | ||
20707 | #endif | ||
20708 | /* This file implement built-in constants for the JX9 engine. */ | ||
20709 | /* | ||
20710 | * JX9_VERSION | ||
20711 | * __JX9__ | ||
20712 | * Expand the current version of the JX9 engine. | ||
20713 | */ | ||
20714 | static void JX9_VER_Const(jx9_value *pVal, void *pUnused) | ||
20715 | { | ||
20716 | SXUNUSED(pUnused); | ||
20717 | jx9_value_string(pVal, jx9_lib_signature(), -1/*Compute length automatically*/); | ||
20718 | } | ||
20719 | #ifdef __WINNT__ | ||
20720 | #include <Windows.h> | ||
20721 | #elif defined(__UNIXES__) | ||
20722 | #include <sys/utsname.h> | ||
20723 | #endif | ||
20724 | /* | ||
20725 | * JX9_OS | ||
20726 | * __OS__ | ||
20727 | * Expand the name of the host Operating System. | ||
20728 | */ | ||
20729 | static void JX9_OS_Const(jx9_value *pVal, void *pUnused) | ||
20730 | { | ||
20731 | #if defined(__WINNT__) | ||
20732 | jx9_value_string(pVal, "WinNT", (int)sizeof("WinNT")-1); | ||
20733 | #elif defined(__UNIXES__) | ||
20734 | struct utsname sInfo; | ||
20735 | if( uname(&sInfo) != 0 ){ | ||
20736 | jx9_value_string(pVal, "Unix", (int)sizeof("Unix")-1); | ||
20737 | }else{ | ||
20738 | jx9_value_string(pVal, sInfo.sysname, -1); | ||
20739 | } | ||
20740 | #else | ||
20741 | jx9_value_string(pVal,"Host OS", (int)sizeof("Host OS")-1); | ||
20742 | #endif | ||
20743 | SXUNUSED(pUnused); | ||
20744 | } | ||
20745 | /* | ||
20746 | * JX9_EOL | ||
20747 | * Expand the correct 'End Of Line' symbol for this platform. | ||
20748 | */ | ||
20749 | static void JX9_EOL_Const(jx9_value *pVal, void *pUnused) | ||
20750 | { | ||
20751 | SXUNUSED(pUnused); | ||
20752 | #ifdef __WINNT__ | ||
20753 | jx9_value_string(pVal, "\r\n", (int)sizeof("\r\n")-1); | ||
20754 | #else | ||
20755 | jx9_value_string(pVal, "\n", (int)sizeof(char)); | ||
20756 | #endif | ||
20757 | } | ||
20758 | /* | ||
20759 | * JX9_INT_MAX | ||
20760 | * Expand the largest integer supported. | ||
20761 | * Note that JX9 deals with 64-bit integer for all platforms. | ||
20762 | */ | ||
20763 | static void JX9_INTMAX_Const(jx9_value *pVal, void *pUnused) | ||
20764 | { | ||
20765 | SXUNUSED(pUnused); | ||
20766 | jx9_value_int64(pVal, SXI64_HIGH); | ||
20767 | } | ||
20768 | /* | ||
20769 | * JX9_INT_SIZE | ||
20770 | * Expand the size in bytes of a 64-bit integer. | ||
20771 | */ | ||
20772 | static void JX9_INTSIZE_Const(jx9_value *pVal, void *pUnused) | ||
20773 | { | ||
20774 | SXUNUSED(pUnused); | ||
20775 | jx9_value_int64(pVal, sizeof(sxi64)); | ||
20776 | } | ||
20777 | /* | ||
20778 | * DIRECTORY_SEPARATOR. | ||
20779 | * Expand the directory separator character. | ||
20780 | */ | ||
20781 | static void JX9_DIRSEP_Const(jx9_value *pVal, void *pUnused) | ||
20782 | { | ||
20783 | SXUNUSED(pUnused); | ||
20784 | #ifdef __WINNT__ | ||
20785 | jx9_value_string(pVal, "\\", (int)sizeof(char)); | ||
20786 | #else | ||
20787 | jx9_value_string(pVal, "/", (int)sizeof(char)); | ||
20788 | #endif | ||
20789 | } | ||
20790 | /* | ||
20791 | * PATH_SEPARATOR. | ||
20792 | * Expand the path separator character. | ||
20793 | */ | ||
20794 | static void JX9_PATHSEP_Const(jx9_value *pVal, void *pUnused) | ||
20795 | { | ||
20796 | SXUNUSED(pUnused); | ||
20797 | #ifdef __WINNT__ | ||
20798 | jx9_value_string(pVal, ";", (int)sizeof(char)); | ||
20799 | #else | ||
20800 | jx9_value_string(pVal, ":", (int)sizeof(char)); | ||
20801 | #endif | ||
20802 | } | ||
20803 | #ifndef __WINNT__ | ||
20804 | #include <time.h> | ||
20805 | #endif | ||
20806 | /* | ||
20807 | * __TIME__ | ||
20808 | * Expand the current time (GMT). | ||
20809 | */ | ||
20810 | static void JX9_TIME_Const(jx9_value *pVal, void *pUnused) | ||
20811 | { | ||
20812 | Sytm sTm; | ||
20813 | #ifdef __WINNT__ | ||
20814 | SYSTEMTIME sOS; | ||
20815 | GetSystemTime(&sOS); | ||
20816 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
20817 | #else | ||
20818 | struct tm *pTm; | ||
20819 | time_t t; | ||
20820 | time(&t); | ||
20821 | pTm = gmtime(&t); | ||
20822 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
20823 | #endif | ||
20824 | SXUNUSED(pUnused); /* cc warning */ | ||
20825 | /* Expand */ | ||
20826 | jx9_value_string_format(pVal, "%02d:%02d:%02d", sTm.tm_hour, sTm.tm_min, sTm.tm_sec); | ||
20827 | } | ||
20828 | /* | ||
20829 | * __DATE__ | ||
20830 | * Expand the current date in the ISO-8601 format. | ||
20831 | */ | ||
20832 | static void JX9_DATE_Const(jx9_value *pVal, void *pUnused) | ||
20833 | { | ||
20834 | Sytm sTm; | ||
20835 | #ifdef __WINNT__ | ||
20836 | SYSTEMTIME sOS; | ||
20837 | GetSystemTime(&sOS); | ||
20838 | SYSTEMTIME_TO_SYTM(&sOS, &sTm); | ||
20839 | #else | ||
20840 | struct tm *pTm; | ||
20841 | time_t t; | ||
20842 | time(&t); | ||
20843 | pTm = gmtime(&t); | ||
20844 | STRUCT_TM_TO_SYTM(pTm, &sTm); | ||
20845 | #endif | ||
20846 | SXUNUSED(pUnused); /* cc warning */ | ||
20847 | /* Expand */ | ||
20848 | jx9_value_string_format(pVal, "%04d-%02d-%02d", sTm.tm_year, sTm.tm_mon+1, sTm.tm_mday); | ||
20849 | } | ||
20850 | /* | ||
20851 | * __FILE__ | ||
20852 | * Path of the processed script. | ||
20853 | */ | ||
20854 | static void JX9_FILE_Const(jx9_value *pVal, void *pUserData) | ||
20855 | { | ||
20856 | jx9_vm *pVm = (jx9_vm *)pUserData; | ||
20857 | SyString *pFile; | ||
20858 | /* Peek the top entry */ | ||
20859 | pFile = (SyString *)SySetPeek(&pVm->aFiles); | ||
20860 | if( pFile == 0 ){ | ||
20861 | /* Expand the magic word: ":MEMORY:" */ | ||
20862 | jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1); | ||
20863 | }else{ | ||
20864 | jx9_value_string(pVal, pFile->zString, pFile->nByte); | ||
20865 | } | ||
20866 | } | ||
20867 | /* | ||
20868 | * __DIR__ | ||
20869 | * Directory holding the processed script. | ||
20870 | */ | ||
20871 | static void JX9_DIR_Const(jx9_value *pVal, void *pUserData) | ||
20872 | { | ||
20873 | jx9_vm *pVm = (jx9_vm *)pUserData; | ||
20874 | SyString *pFile; | ||
20875 | /* Peek the top entry */ | ||
20876 | pFile = (SyString *)SySetPeek(&pVm->aFiles); | ||
20877 | if( pFile == 0 ){ | ||
20878 | /* Expand the magic word: ":MEMORY:" */ | ||
20879 | jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1); | ||
20880 | }else{ | ||
20881 | if( pFile->nByte > 0 ){ | ||
20882 | const char *zDir; | ||
20883 | int nLen; | ||
20884 | zDir = jx9ExtractDirName(pFile->zString, (int)pFile->nByte, &nLen); | ||
20885 | jx9_value_string(pVal, zDir, nLen); | ||
20886 | }else{ | ||
20887 | /* Expand '.' as the current directory*/ | ||
20888 | jx9_value_string(pVal, ".", (int)sizeof(char)); | ||
20889 | } | ||
20890 | } | ||
20891 | } | ||
20892 | /* | ||
20893 | * E_ERROR | ||
20894 | * Expands 1 | ||
20895 | */ | ||
20896 | static void JX9_E_ERROR_Const(jx9_value *pVal, void *pUserData) | ||
20897 | { | ||
20898 | jx9_value_int(pVal, 1); | ||
20899 | SXUNUSED(pUserData); | ||
20900 | } | ||
20901 | /* | ||
20902 | * E_WARNING | ||
20903 | * Expands 2 | ||
20904 | */ | ||
20905 | static void JX9_E_WARNING_Const(jx9_value *pVal, void *pUserData) | ||
20906 | { | ||
20907 | jx9_value_int(pVal, 2); | ||
20908 | SXUNUSED(pUserData); | ||
20909 | } | ||
20910 | /* | ||
20911 | * E_PARSE | ||
20912 | * Expands 4 | ||
20913 | */ | ||
20914 | static void JX9_E_PARSE_Const(jx9_value *pVal, void *pUserData) | ||
20915 | { | ||
20916 | jx9_value_int(pVal, 4); | ||
20917 | SXUNUSED(pUserData); | ||
20918 | } | ||
20919 | /* | ||
20920 | * E_NOTICE | ||
20921 | * Expands 8 | ||
20922 | */ | ||
20923 | static void JX9_E_NOTICE_Const(jx9_value *pVal, void *pUserData) | ||
20924 | { | ||
20925 | jx9_value_int(pVal, 8); | ||
20926 | SXUNUSED(pUserData); | ||
20927 | } | ||
20928 | /* | ||
20929 | * CASE_LOWER | ||
20930 | * Expands 0. | ||
20931 | */ | ||
20932 | static void JX9_CASE_LOWER_Const(jx9_value *pVal, void *pUserData) | ||
20933 | { | ||
20934 | jx9_value_int(pVal, 0); | ||
20935 | SXUNUSED(pUserData); | ||
20936 | } | ||
20937 | /* | ||
20938 | * CASE_UPPER | ||
20939 | * Expands 1. | ||
20940 | */ | ||
20941 | static void JX9_CASE_UPPER_Const(jx9_value *pVal, void *pUserData) | ||
20942 | { | ||
20943 | jx9_value_int(pVal, 1); | ||
20944 | SXUNUSED(pUserData); | ||
20945 | } | ||
20946 | /* | ||
20947 | * STR_PAD_LEFT | ||
20948 | * Expands 0. | ||
20949 | */ | ||
20950 | static void JX9_STR_PAD_LEFT_Const(jx9_value *pVal, void *pUserData) | ||
20951 | { | ||
20952 | jx9_value_int(pVal, 0); | ||
20953 | SXUNUSED(pUserData); | ||
20954 | } | ||
20955 | /* | ||
20956 | * STR_PAD_RIGHT | ||
20957 | * Expands 1. | ||
20958 | */ | ||
20959 | static void JX9_STR_PAD_RIGHT_Const(jx9_value *pVal, void *pUserData) | ||
20960 | { | ||
20961 | jx9_value_int(pVal, 1); | ||
20962 | SXUNUSED(pUserData); | ||
20963 | } | ||
20964 | /* | ||
20965 | * STR_PAD_BOTH | ||
20966 | * Expands 2. | ||
20967 | */ | ||
20968 | static void JX9_STR_PAD_BOTH_Const(jx9_value *pVal, void *pUserData) | ||
20969 | { | ||
20970 | jx9_value_int(pVal, 2); | ||
20971 | SXUNUSED(pUserData); | ||
20972 | } | ||
20973 | /* | ||
20974 | * COUNT_NORMAL | ||
20975 | * Expands 0 | ||
20976 | */ | ||
20977 | static void JX9_COUNT_NORMAL_Const(jx9_value *pVal, void *pUserData) | ||
20978 | { | ||
20979 | jx9_value_int(pVal, 0); | ||
20980 | SXUNUSED(pUserData); | ||
20981 | } | ||
20982 | /* | ||
20983 | * COUNT_RECURSIVE | ||
20984 | * Expands 1. | ||
20985 | */ | ||
20986 | static void JX9_COUNT_RECURSIVE_Const(jx9_value *pVal, void *pUserData) | ||
20987 | { | ||
20988 | jx9_value_int(pVal, 1); | ||
20989 | SXUNUSED(pUserData); | ||
20990 | } | ||
20991 | /* | ||
20992 | * SORT_ASC | ||
20993 | * Expands 1. | ||
20994 | */ | ||
20995 | static void JX9_SORT_ASC_Const(jx9_value *pVal, void *pUserData) | ||
20996 | { | ||
20997 | jx9_value_int(pVal, 1); | ||
20998 | SXUNUSED(pUserData); | ||
20999 | } | ||
21000 | /* | ||
21001 | * SORT_DESC | ||
21002 | * Expands 2. | ||
21003 | */ | ||
21004 | static void JX9_SORT_DESC_Const(jx9_value *pVal, void *pUserData) | ||
21005 | { | ||
21006 | jx9_value_int(pVal, 2); | ||
21007 | SXUNUSED(pUserData); | ||
21008 | } | ||
21009 | /* | ||
21010 | * SORT_REGULAR | ||
21011 | * Expands 3. | ||
21012 | */ | ||
21013 | static void JX9_SORT_REG_Const(jx9_value *pVal, void *pUserData) | ||
21014 | { | ||
21015 | jx9_value_int(pVal, 3); | ||
21016 | SXUNUSED(pUserData); | ||
21017 | } | ||
21018 | /* | ||
21019 | * SORT_NUMERIC | ||
21020 | * Expands 4. | ||
21021 | */ | ||
21022 | static void JX9_SORT_NUMERIC_Const(jx9_value *pVal, void *pUserData) | ||
21023 | { | ||
21024 | jx9_value_int(pVal, 4); | ||
21025 | SXUNUSED(pUserData); | ||
21026 | } | ||
21027 | /* | ||
21028 | * SORT_STRING | ||
21029 | * Expands 5. | ||
21030 | */ | ||
21031 | static void JX9_SORT_STRING_Const(jx9_value *pVal, void *pUserData) | ||
21032 | { | ||
21033 | jx9_value_int(pVal, 5); | ||
21034 | SXUNUSED(pUserData); | ||
21035 | } | ||
21036 | /* | ||
21037 | * JX9_ROUND_HALF_UP | ||
21038 | * Expands 1. | ||
21039 | */ | ||
21040 | static void JX9_JX9_ROUND_HALF_UP_Const(jx9_value *pVal, void *pUserData) | ||
21041 | { | ||
21042 | jx9_value_int(pVal, 1); | ||
21043 | SXUNUSED(pUserData); | ||
21044 | } | ||
21045 | /* | ||
21046 | * SJX9_ROUND_HALF_DOWN | ||
21047 | * Expands 2. | ||
21048 | */ | ||
21049 | static void JX9_JX9_ROUND_HALF_DOWN_Const(jx9_value *pVal, void *pUserData) | ||
21050 | { | ||
21051 | jx9_value_int(pVal, 2); | ||
21052 | SXUNUSED(pUserData); | ||
21053 | } | ||
21054 | /* | ||
21055 | * JX9_ROUND_HALF_EVEN | ||
21056 | * Expands 3. | ||
21057 | */ | ||
21058 | static void JX9_JX9_ROUND_HALF_EVEN_Const(jx9_value *pVal, void *pUserData) | ||
21059 | { | ||
21060 | jx9_value_int(pVal, 3); | ||
21061 | SXUNUSED(pUserData); | ||
21062 | } | ||
21063 | /* | ||
21064 | * JX9_ROUND_HALF_ODD | ||
21065 | * Expands 4. | ||
21066 | */ | ||
21067 | static void JX9_JX9_ROUND_HALF_ODD_Const(jx9_value *pVal, void *pUserData) | ||
21068 | { | ||
21069 | jx9_value_int(pVal, 4); | ||
21070 | SXUNUSED(pUserData); | ||
21071 | } | ||
21072 | #ifdef JX9_ENABLE_MATH_FUNC | ||
21073 | /* | ||
21074 | * PI | ||
21075 | * Expand the value of pi. | ||
21076 | */ | ||
21077 | static void JX9_M_PI_Const(jx9_value *pVal, void *pUserData) | ||
21078 | { | ||
21079 | SXUNUSED(pUserData); /* cc warning */ | ||
21080 | jx9_value_double(pVal, JX9_PI); | ||
21081 | } | ||
21082 | /* | ||
21083 | * M_E | ||
21084 | * Expand 2.7182818284590452354 | ||
21085 | */ | ||
21086 | static void JX9_M_E_Const(jx9_value *pVal, void *pUserData) | ||
21087 | { | ||
21088 | SXUNUSED(pUserData); /* cc warning */ | ||
21089 | jx9_value_double(pVal, 2.7182818284590452354); | ||
21090 | } | ||
21091 | /* | ||
21092 | * M_LOG2E | ||
21093 | * Expand 2.7182818284590452354 | ||
21094 | */ | ||
21095 | static void JX9_M_LOG2E_Const(jx9_value *pVal, void *pUserData) | ||
21096 | { | ||
21097 | SXUNUSED(pUserData); /* cc warning */ | ||
21098 | jx9_value_double(pVal, 1.4426950408889634074); | ||
21099 | } | ||
21100 | /* | ||
21101 | * M_LOG10E | ||
21102 | * Expand 0.4342944819032518276 | ||
21103 | */ | ||
21104 | static void JX9_M_LOG10E_Const(jx9_value *pVal, void *pUserData) | ||
21105 | { | ||
21106 | SXUNUSED(pUserData); /* cc warning */ | ||
21107 | jx9_value_double(pVal, 0.4342944819032518276); | ||
21108 | } | ||
21109 | /* | ||
21110 | * M_LN2 | ||
21111 | * Expand 0.69314718055994530942 | ||
21112 | */ | ||
21113 | static void JX9_M_LN2_Const(jx9_value *pVal, void *pUserData) | ||
21114 | { | ||
21115 | SXUNUSED(pUserData); /* cc warning */ | ||
21116 | jx9_value_double(pVal, 0.69314718055994530942); | ||
21117 | } | ||
21118 | /* | ||
21119 | * M_LN10 | ||
21120 | * Expand 2.30258509299404568402 | ||
21121 | */ | ||
21122 | static void JX9_M_LN10_Const(jx9_value *pVal, void *pUserData) | ||
21123 | { | ||
21124 | SXUNUSED(pUserData); /* cc warning */ | ||
21125 | jx9_value_double(pVal, 2.30258509299404568402); | ||
21126 | } | ||
21127 | /* | ||
21128 | * M_PI_2 | ||
21129 | * Expand 1.57079632679489661923 | ||
21130 | */ | ||
21131 | static void JX9_M_PI_2_Const(jx9_value *pVal, void *pUserData) | ||
21132 | { | ||
21133 | SXUNUSED(pUserData); /* cc warning */ | ||
21134 | jx9_value_double(pVal, 1.57079632679489661923); | ||
21135 | } | ||
21136 | /* | ||
21137 | * M_PI_4 | ||
21138 | * Expand 0.78539816339744830962 | ||
21139 | */ | ||
21140 | static void JX9_M_PI_4_Const(jx9_value *pVal, void *pUserData) | ||
21141 | { | ||
21142 | SXUNUSED(pUserData); /* cc warning */ | ||
21143 | jx9_value_double(pVal, 0.78539816339744830962); | ||
21144 | } | ||
21145 | /* | ||
21146 | * M_1_PI | ||
21147 | * Expand 0.31830988618379067154 | ||
21148 | */ | ||
21149 | static void JX9_M_1_PI_Const(jx9_value *pVal, void *pUserData) | ||
21150 | { | ||
21151 | SXUNUSED(pUserData); /* cc warning */ | ||
21152 | jx9_value_double(pVal, 0.31830988618379067154); | ||
21153 | } | ||
21154 | /* | ||
21155 | * M_2_PI | ||
21156 | * Expand 0.63661977236758134308 | ||
21157 | */ | ||
21158 | static void JX9_M_2_PI_Const(jx9_value *pVal, void *pUserData) | ||
21159 | { | ||
21160 | SXUNUSED(pUserData); /* cc warning */ | ||
21161 | jx9_value_double(pVal, 0.63661977236758134308); | ||
21162 | } | ||
21163 | /* | ||
21164 | * M_SQRTPI | ||
21165 | * Expand 1.77245385090551602729 | ||
21166 | */ | ||
21167 | static void JX9_M_SQRTPI_Const(jx9_value *pVal, void *pUserData) | ||
21168 | { | ||
21169 | SXUNUSED(pUserData); /* cc warning */ | ||
21170 | jx9_value_double(pVal, 1.77245385090551602729); | ||
21171 | } | ||
21172 | /* | ||
21173 | * M_2_SQRTPI | ||
21174 | * Expand 1.12837916709551257390 | ||
21175 | */ | ||
21176 | static void JX9_M_2_SQRTPI_Const(jx9_value *pVal, void *pUserData) | ||
21177 | { | ||
21178 | SXUNUSED(pUserData); /* cc warning */ | ||
21179 | jx9_value_double(pVal, 1.12837916709551257390); | ||
21180 | } | ||
21181 | /* | ||
21182 | * M_SQRT2 | ||
21183 | * Expand 1.41421356237309504880 | ||
21184 | */ | ||
21185 | static void JX9_M_SQRT2_Const(jx9_value *pVal, void *pUserData) | ||
21186 | { | ||
21187 | SXUNUSED(pUserData); /* cc warning */ | ||
21188 | jx9_value_double(pVal, 1.41421356237309504880); | ||
21189 | } | ||
21190 | /* | ||
21191 | * M_SQRT3 | ||
21192 | * Expand 1.73205080756887729352 | ||
21193 | */ | ||
21194 | static void JX9_M_SQRT3_Const(jx9_value *pVal, void *pUserData) | ||
21195 | { | ||
21196 | SXUNUSED(pUserData); /* cc warning */ | ||
21197 | jx9_value_double(pVal, 1.73205080756887729352); | ||
21198 | } | ||
21199 | /* | ||
21200 | * M_SQRT1_2 | ||
21201 | * Expand 0.70710678118654752440 | ||
21202 | */ | ||
21203 | static void JX9_M_SQRT1_2_Const(jx9_value *pVal, void *pUserData) | ||
21204 | { | ||
21205 | SXUNUSED(pUserData); /* cc warning */ | ||
21206 | jx9_value_double(pVal, 0.70710678118654752440); | ||
21207 | } | ||
21208 | /* | ||
21209 | * M_LNPI | ||
21210 | * Expand 1.14472988584940017414 | ||
21211 | */ | ||
21212 | static void JX9_M_LNPI_Const(jx9_value *pVal, void *pUserData) | ||
21213 | { | ||
21214 | SXUNUSED(pUserData); /* cc warning */ | ||
21215 | jx9_value_double(pVal, 1.14472988584940017414); | ||
21216 | } | ||
21217 | /* | ||
21218 | * M_EULER | ||
21219 | * Expand 0.57721566490153286061 | ||
21220 | */ | ||
21221 | static void JX9_M_EULER_Const(jx9_value *pVal, void *pUserData) | ||
21222 | { | ||
21223 | SXUNUSED(pUserData); /* cc warning */ | ||
21224 | jx9_value_double(pVal, 0.57721566490153286061); | ||
21225 | } | ||
21226 | #endif /* JX9_DISABLE_BUILTIN_MATH */ | ||
21227 | /* | ||
21228 | * DATE_ATOM | ||
21229 | * Expand Atom (example: 2005-08-15T15:52:01+00:00) | ||
21230 | */ | ||
21231 | static void JX9_DATE_ATOM_Const(jx9_value *pVal, void *pUserData) | ||
21232 | { | ||
21233 | SXUNUSED(pUserData); /* cc warning */ | ||
21234 | jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/); | ||
21235 | } | ||
21236 | /* | ||
21237 | * DATE_COOKIE | ||
21238 | * HTTP Cookies (example: Monday, 15-Aug-05 15:52:01 UTC) | ||
21239 | */ | ||
21240 | static void JX9_DATE_COOKIE_Const(jx9_value *pVal, void *pUserData) | ||
21241 | { | ||
21242 | SXUNUSED(pUserData); /* cc warning */ | ||
21243 | jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/); | ||
21244 | } | ||
21245 | /* | ||
21246 | * DATE_ISO8601 | ||
21247 | * ISO-8601 (example: 2005-08-15T15:52:01+0000) | ||
21248 | */ | ||
21249 | static void JX9_DATE_ISO8601_Const(jx9_value *pVal, void *pUserData) | ||
21250 | { | ||
21251 | SXUNUSED(pUserData); /* cc warning */ | ||
21252 | jx9_value_string(pVal, "Y-m-d\\TH:i:sO", -1/*Compute length automatically*/); | ||
21253 | } | ||
21254 | /* | ||
21255 | * DATE_RFC822 | ||
21256 | * RFC 822 (example: Mon, 15 Aug 05 15:52:01 +0000) | ||
21257 | */ | ||
21258 | static void JX9_DATE_RFC822_Const(jx9_value *pVal, void *pUserData) | ||
21259 | { | ||
21260 | SXUNUSED(pUserData); /* cc warning */ | ||
21261 | jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/); | ||
21262 | } | ||
21263 | /* | ||
21264 | * DATE_RFC850 | ||
21265 | * RFC 850 (example: Monday, 15-Aug-05 15:52:01 UTC) | ||
21266 | */ | ||
21267 | static void JX9_DATE_RFC850_Const(jx9_value *pVal, void *pUserData) | ||
21268 | { | ||
21269 | SXUNUSED(pUserData); /* cc warning */ | ||
21270 | jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/); | ||
21271 | } | ||
21272 | /* | ||
21273 | * DATE_RFC1036 | ||
21274 | * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) | ||
21275 | */ | ||
21276 | static void JX9_DATE_RFC1036_Const(jx9_value *pVal, void *pUserData) | ||
21277 | { | ||
21278 | SXUNUSED(pUserData); /* cc warning */ | ||
21279 | jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/); | ||
21280 | } | ||
21281 | /* | ||
21282 | * DATE_RFC1123 | ||
21283 | * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) | ||
21284 | */ | ||
21285 | static void JX9_DATE_RFC1123_Const(jx9_value *pVal, void *pUserData) | ||
21286 | { | ||
21287 | SXUNUSED(pUserData); /* cc warning */ | ||
21288 | jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); | ||
21289 | } | ||
21290 | /* | ||
21291 | * DATE_RFC2822 | ||
21292 | * RFC 2822 (Mon, 15 Aug 2005 15:52:01 +0000) | ||
21293 | */ | ||
21294 | static void JX9_DATE_RFC2822_Const(jx9_value *pVal, void *pUserData) | ||
21295 | { | ||
21296 | SXUNUSED(pUserData); /* cc warning */ | ||
21297 | jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); | ||
21298 | } | ||
21299 | /* | ||
21300 | * DATE_RSS | ||
21301 | * RSS (Mon, 15 Aug 2005 15:52:01 +0000) | ||
21302 | */ | ||
21303 | static void JX9_DATE_RSS_Const(jx9_value *pVal, void *pUserData) | ||
21304 | { | ||
21305 | SXUNUSED(pUserData); /* cc warning */ | ||
21306 | jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); | ||
21307 | } | ||
21308 | /* | ||
21309 | * DATE_W3C | ||
21310 | * World Wide Web Consortium (example: 2005-08-15T15:52:01+00:00) | ||
21311 | */ | ||
21312 | static void JX9_DATE_W3C_Const(jx9_value *pVal, void *pUserData) | ||
21313 | { | ||
21314 | SXUNUSED(pUserData); /* cc warning */ | ||
21315 | jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/); | ||
21316 | } | ||
21317 | /* | ||
21318 | * ENT_COMPAT | ||
21319 | * Expand 0x01 (Must be a power of two) | ||
21320 | */ | ||
21321 | static void JX9_ENT_COMPAT_Const(jx9_value *pVal, void *pUserData) | ||
21322 | { | ||
21323 | SXUNUSED(pUserData); /* cc warning */ | ||
21324 | jx9_value_int(pVal, 0x01); | ||
21325 | } | ||
21326 | /* | ||
21327 | * ENT_QUOTES | ||
21328 | * Expand 0x02 (Must be a power of two) | ||
21329 | */ | ||
21330 | static void JX9_ENT_QUOTES_Const(jx9_value *pVal, void *pUserData) | ||
21331 | { | ||
21332 | SXUNUSED(pUserData); /* cc warning */ | ||
21333 | jx9_value_int(pVal, 0x02); | ||
21334 | } | ||
21335 | /* | ||
21336 | * ENT_NOQUOTES | ||
21337 | * Expand 0x04 (Must be a power of two) | ||
21338 | */ | ||
21339 | static void JX9_ENT_NOQUOTES_Const(jx9_value *pVal, void *pUserData) | ||
21340 | { | ||
21341 | SXUNUSED(pUserData); /* cc warning */ | ||
21342 | jx9_value_int(pVal, 0x04); | ||
21343 | } | ||
21344 | /* | ||
21345 | * ENT_IGNORE | ||
21346 | * Expand 0x08 (Must be a power of two) | ||
21347 | */ | ||
21348 | static void JX9_ENT_IGNORE_Const(jx9_value *pVal, void *pUserData) | ||
21349 | { | ||
21350 | SXUNUSED(pUserData); /* cc warning */ | ||
21351 | jx9_value_int(pVal, 0x08); | ||
21352 | } | ||
21353 | /* | ||
21354 | * ENT_SUBSTITUTE | ||
21355 | * Expand 0x10 (Must be a power of two) | ||
21356 | */ | ||
21357 | static void JX9_ENT_SUBSTITUTE_Const(jx9_value *pVal, void *pUserData) | ||
21358 | { | ||
21359 | SXUNUSED(pUserData); /* cc warning */ | ||
21360 | jx9_value_int(pVal, 0x10); | ||
21361 | } | ||
21362 | /* | ||
21363 | * ENT_DISALLOWED | ||
21364 | * Expand 0x20 (Must be a power of two) | ||
21365 | */ | ||
21366 | static void JX9_ENT_DISALLOWED_Const(jx9_value *pVal, void *pUserData) | ||
21367 | { | ||
21368 | SXUNUSED(pUserData); /* cc warning */ | ||
21369 | jx9_value_int(pVal, 0x20); | ||
21370 | } | ||
21371 | /* | ||
21372 | * ENT_HTML401 | ||
21373 | * Expand 0x40 (Must be a power of two) | ||
21374 | */ | ||
21375 | static void JX9_ENT_HTML401_Const(jx9_value *pVal, void *pUserData) | ||
21376 | { | ||
21377 | SXUNUSED(pUserData); /* cc warning */ | ||
21378 | jx9_value_int(pVal, 0x40); | ||
21379 | } | ||
21380 | /* | ||
21381 | * ENT_XML1 | ||
21382 | * Expand 0x80 (Must be a power of two) | ||
21383 | */ | ||
21384 | static void JX9_ENT_XML1_Const(jx9_value *pVal, void *pUserData) | ||
21385 | { | ||
21386 | SXUNUSED(pUserData); /* cc warning */ | ||
21387 | jx9_value_int(pVal, 0x80); | ||
21388 | } | ||
21389 | /* | ||
21390 | * ENT_XHTML | ||
21391 | * Expand 0x100 (Must be a power of two) | ||
21392 | */ | ||
21393 | static void JX9_ENT_XHTML_Const(jx9_value *pVal, void *pUserData) | ||
21394 | { | ||
21395 | SXUNUSED(pUserData); /* cc warning */ | ||
21396 | jx9_value_int(pVal, 0x100); | ||
21397 | } | ||
21398 | /* | ||
21399 | * ENT_HTML5 | ||
21400 | * Expand 0x200 (Must be a power of two) | ||
21401 | */ | ||
21402 | static void JX9_ENT_HTML5_Const(jx9_value *pVal, void *pUserData) | ||
21403 | { | ||
21404 | SXUNUSED(pUserData); /* cc warning */ | ||
21405 | jx9_value_int(pVal, 0x200); | ||
21406 | } | ||
21407 | /* | ||
21408 | * ISO-8859-1 | ||
21409 | * ISO_8859_1 | ||
21410 | * Expand 1 | ||
21411 | */ | ||
21412 | static void JX9_ISO88591_Const(jx9_value *pVal, void *pUserData) | ||
21413 | { | ||
21414 | SXUNUSED(pUserData); /* cc warning */ | ||
21415 | jx9_value_int(pVal, 1); | ||
21416 | } | ||
21417 | /* | ||
21418 | * UTF-8 | ||
21419 | * UTF8 | ||
21420 | * Expand 2 | ||
21421 | */ | ||
21422 | static void JX9_UTF8_Const(jx9_value *pVal, void *pUserData) | ||
21423 | { | ||
21424 | SXUNUSED(pUserData); /* cc warning */ | ||
21425 | jx9_value_int(pVal, 1); | ||
21426 | } | ||
21427 | /* | ||
21428 | * HTML_ENTITIES | ||
21429 | * Expand 1 | ||
21430 | */ | ||
21431 | static void JX9_HTML_ENTITIES_Const(jx9_value *pVal, void *pUserData) | ||
21432 | { | ||
21433 | SXUNUSED(pUserData); /* cc warning */ | ||
21434 | jx9_value_int(pVal, 1); | ||
21435 | } | ||
21436 | /* | ||
21437 | * HTML_SPECIALCHARS | ||
21438 | * Expand 2 | ||
21439 | */ | ||
21440 | static void JX9_HTML_SPECIALCHARS_Const(jx9_value *pVal, void *pUserData) | ||
21441 | { | ||
21442 | SXUNUSED(pUserData); /* cc warning */ | ||
21443 | jx9_value_int(pVal, 2); | ||
21444 | } | ||
21445 | /* | ||
21446 | * JX9_URL_SCHEME. | ||
21447 | * Expand 1 | ||
21448 | */ | ||
21449 | static void JX9_JX9_URL_SCHEME_Const(jx9_value *pVal, void *pUserData) | ||
21450 | { | ||
21451 | SXUNUSED(pUserData); /* cc warning */ | ||
21452 | jx9_value_int(pVal, 1); | ||
21453 | } | ||
21454 | /* | ||
21455 | * JX9_URL_HOST. | ||
21456 | * Expand 2 | ||
21457 | */ | ||
21458 | static void JX9_JX9_URL_HOST_Const(jx9_value *pVal, void *pUserData) | ||
21459 | { | ||
21460 | SXUNUSED(pUserData); /* cc warning */ | ||
21461 | jx9_value_int(pVal, 2); | ||
21462 | } | ||
21463 | /* | ||
21464 | * JX9_URL_PORT. | ||
21465 | * Expand 3 | ||
21466 | */ | ||
21467 | static void JX9_JX9_URL_PORT_Const(jx9_value *pVal, void *pUserData) | ||
21468 | { | ||
21469 | SXUNUSED(pUserData); /* cc warning */ | ||
21470 | jx9_value_int(pVal, 3); | ||
21471 | } | ||
21472 | /* | ||
21473 | * JX9_URL_USER. | ||
21474 | * Expand 4 | ||
21475 | */ | ||
21476 | static void JX9_JX9_URL_USER_Const(jx9_value *pVal, void *pUserData) | ||
21477 | { | ||
21478 | SXUNUSED(pUserData); /* cc warning */ | ||
21479 | jx9_value_int(pVal, 4); | ||
21480 | } | ||
21481 | /* | ||
21482 | * JX9_URL_PASS. | ||
21483 | * Expand 5 | ||
21484 | */ | ||
21485 | static void JX9_JX9_URL_PASS_Const(jx9_value *pVal, void *pUserData) | ||
21486 | { | ||
21487 | SXUNUSED(pUserData); /* cc warning */ | ||
21488 | jx9_value_int(pVal, 5); | ||
21489 | } | ||
21490 | /* | ||
21491 | * JX9_URL_PATH. | ||
21492 | * Expand 6 | ||
21493 | */ | ||
21494 | static void JX9_JX9_URL_PATH_Const(jx9_value *pVal, void *pUserData) | ||
21495 | { | ||
21496 | SXUNUSED(pUserData); /* cc warning */ | ||
21497 | jx9_value_int(pVal, 6); | ||
21498 | } | ||
21499 | /* | ||
21500 | * JX9_URL_QUERY. | ||
21501 | * Expand 7 | ||
21502 | */ | ||
21503 | static void JX9_JX9_URL_QUERY_Const(jx9_value *pVal, void *pUserData) | ||
21504 | { | ||
21505 | SXUNUSED(pUserData); /* cc warning */ | ||
21506 | jx9_value_int(pVal, 7); | ||
21507 | } | ||
21508 | /* | ||
21509 | * JX9_URL_FRAGMENT. | ||
21510 | * Expand 8 | ||
21511 | */ | ||
21512 | static void JX9_JX9_URL_FRAGMENT_Const(jx9_value *pVal, void *pUserData) | ||
21513 | { | ||
21514 | SXUNUSED(pUserData); /* cc warning */ | ||
21515 | jx9_value_int(pVal, 8); | ||
21516 | } | ||
21517 | /* | ||
21518 | * JX9_QUERY_RFC1738 | ||
21519 | * Expand 1 | ||
21520 | */ | ||
21521 | static void JX9_JX9_QUERY_RFC1738_Const(jx9_value *pVal, void *pUserData) | ||
21522 | { | ||
21523 | SXUNUSED(pUserData); /* cc warning */ | ||
21524 | jx9_value_int(pVal, 1); | ||
21525 | } | ||
21526 | /* | ||
21527 | * JX9_QUERY_RFC3986 | ||
21528 | * Expand 1 | ||
21529 | */ | ||
21530 | static void JX9_JX9_QUERY_RFC3986_Const(jx9_value *pVal, void *pUserData) | ||
21531 | { | ||
21532 | SXUNUSED(pUserData); /* cc warning */ | ||
21533 | jx9_value_int(pVal, 2); | ||
21534 | } | ||
21535 | /* | ||
21536 | * FNM_NOESCAPE | ||
21537 | * Expand 0x01 (Must be a power of two) | ||
21538 | */ | ||
21539 | static void JX9_FNM_NOESCAPE_Const(jx9_value *pVal, void *pUserData) | ||
21540 | { | ||
21541 | SXUNUSED(pUserData); /* cc warning */ | ||
21542 | jx9_value_int(pVal, 0x01); | ||
21543 | } | ||
21544 | /* | ||
21545 | * FNM_PATHNAME | ||
21546 | * Expand 0x02 (Must be a power of two) | ||
21547 | */ | ||
21548 | static void JX9_FNM_PATHNAME_Const(jx9_value *pVal, void *pUserData) | ||
21549 | { | ||
21550 | SXUNUSED(pUserData); /* cc warning */ | ||
21551 | jx9_value_int(pVal, 0x02); | ||
21552 | } | ||
21553 | /* | ||
21554 | * FNM_PERIOD | ||
21555 | * Expand 0x04 (Must be a power of two) | ||
21556 | */ | ||
21557 | static void JX9_FNM_PERIOD_Const(jx9_value *pVal, void *pUserData) | ||
21558 | { | ||
21559 | SXUNUSED(pUserData); /* cc warning */ | ||
21560 | jx9_value_int(pVal, 0x04); | ||
21561 | } | ||
21562 | /* | ||
21563 | * FNM_CASEFOLD | ||
21564 | * Expand 0x08 (Must be a power of two) | ||
21565 | */ | ||
21566 | static void JX9_FNM_CASEFOLD_Const(jx9_value *pVal, void *pUserData) | ||
21567 | { | ||
21568 | SXUNUSED(pUserData); /* cc warning */ | ||
21569 | jx9_value_int(pVal, 0x08); | ||
21570 | } | ||
21571 | /* | ||
21572 | * PATHINFO_DIRNAME | ||
21573 | * Expand 1. | ||
21574 | */ | ||
21575 | static void JX9_PATHINFO_DIRNAME_Const(jx9_value *pVal, void *pUserData) | ||
21576 | { | ||
21577 | SXUNUSED(pUserData); /* cc warning */ | ||
21578 | jx9_value_int(pVal, 1); | ||
21579 | } | ||
21580 | /* | ||
21581 | * PATHINFO_BASENAME | ||
21582 | * Expand 2. | ||
21583 | */ | ||
21584 | static void JX9_PATHINFO_BASENAME_Const(jx9_value *pVal, void *pUserData) | ||
21585 | { | ||
21586 | SXUNUSED(pUserData); /* cc warning */ | ||
21587 | jx9_value_int(pVal, 2); | ||
21588 | } | ||
21589 | /* | ||
21590 | * PATHINFO_EXTENSION | ||
21591 | * Expand 3. | ||
21592 | */ | ||
21593 | static void JX9_PATHINFO_EXTENSION_Const(jx9_value *pVal, void *pUserData) | ||
21594 | { | ||
21595 | SXUNUSED(pUserData); /* cc warning */ | ||
21596 | jx9_value_int(pVal, 3); | ||
21597 | } | ||
21598 | /* | ||
21599 | * PATHINFO_FILENAME | ||
21600 | * Expand 4. | ||
21601 | */ | ||
21602 | static void JX9_PATHINFO_FILENAME_Const(jx9_value *pVal, void *pUserData) | ||
21603 | { | ||
21604 | SXUNUSED(pUserData); /* cc warning */ | ||
21605 | jx9_value_int(pVal, 4); | ||
21606 | } | ||
21607 | /* | ||
21608 | * ASSERT_ACTIVE. | ||
21609 | * Expand the value of JX9_ASSERT_ACTIVE defined in jx9Int.h | ||
21610 | */ | ||
21611 | static void JX9_ASSERT_ACTIVE_Const(jx9_value *pVal, void *pUserData) | ||
21612 | { | ||
21613 | SXUNUSED(pUserData); /* cc warning */ | ||
21614 | jx9_value_int(pVal, JX9_ASSERT_DISABLE); | ||
21615 | } | ||
21616 | /* | ||
21617 | * ASSERT_WARNING. | ||
21618 | * Expand the value of JX9_ASSERT_WARNING defined in jx9Int.h | ||
21619 | */ | ||
21620 | static void JX9_ASSERT_WARNING_Const(jx9_value *pVal, void *pUserData) | ||
21621 | { | ||
21622 | SXUNUSED(pUserData); /* cc warning */ | ||
21623 | jx9_value_int(pVal, JX9_ASSERT_WARNING); | ||
21624 | } | ||
21625 | /* | ||
21626 | * ASSERT_BAIL. | ||
21627 | * Expand the value of JX9_ASSERT_BAIL defined in jx9Int.h | ||
21628 | */ | ||
21629 | static void JX9_ASSERT_BAIL_Const(jx9_value *pVal, void *pUserData) | ||
21630 | { | ||
21631 | SXUNUSED(pUserData); /* cc warning */ | ||
21632 | jx9_value_int(pVal, JX9_ASSERT_BAIL); | ||
21633 | } | ||
21634 | /* | ||
21635 | * ASSERT_QUIET_EVAL. | ||
21636 | * Expand the value of JX9_ASSERT_QUIET_EVAL defined in jx9Int.h | ||
21637 | */ | ||
21638 | static void JX9_ASSERT_QUIET_EVAL_Const(jx9_value *pVal, void *pUserData) | ||
21639 | { | ||
21640 | SXUNUSED(pUserData); /* cc warning */ | ||
21641 | jx9_value_int(pVal, JX9_ASSERT_QUIET_EVAL); | ||
21642 | } | ||
21643 | /* | ||
21644 | * ASSERT_CALLBACK. | ||
21645 | * Expand the value of JX9_ASSERT_CALLBACK defined in jx9Int.h | ||
21646 | */ | ||
21647 | static void JX9_ASSERT_CALLBACK_Const(jx9_value *pVal, void *pUserData) | ||
21648 | { | ||
21649 | SXUNUSED(pUserData); /* cc warning */ | ||
21650 | jx9_value_int(pVal, JX9_ASSERT_CALLBACK); | ||
21651 | } | ||
21652 | /* | ||
21653 | * SEEK_SET. | ||
21654 | * Expand 0 | ||
21655 | */ | ||
21656 | static void JX9_SEEK_SET_Const(jx9_value *pVal, void *pUserData) | ||
21657 | { | ||
21658 | SXUNUSED(pUserData); /* cc warning */ | ||
21659 | jx9_value_int(pVal, 0); | ||
21660 | } | ||
21661 | /* | ||
21662 | * SEEK_CUR. | ||
21663 | * Expand 1 | ||
21664 | */ | ||
21665 | static void JX9_SEEK_CUR_Const(jx9_value *pVal, void *pUserData) | ||
21666 | { | ||
21667 | SXUNUSED(pUserData); /* cc warning */ | ||
21668 | jx9_value_int(pVal, 1); | ||
21669 | } | ||
21670 | /* | ||
21671 | * SEEK_END. | ||
21672 | * Expand 2 | ||
21673 | */ | ||
21674 | static void JX9_SEEK_END_Const(jx9_value *pVal, void *pUserData) | ||
21675 | { | ||
21676 | SXUNUSED(pUserData); /* cc warning */ | ||
21677 | jx9_value_int(pVal, 2); | ||
21678 | } | ||
21679 | /* | ||
21680 | * LOCK_SH. | ||
21681 | * Expand 2 | ||
21682 | */ | ||
21683 | static void JX9_LOCK_SH_Const(jx9_value *pVal, void *pUserData) | ||
21684 | { | ||
21685 | SXUNUSED(pUserData); /* cc warning */ | ||
21686 | jx9_value_int(pVal, 1); | ||
21687 | } | ||
21688 | /* | ||
21689 | * LOCK_NB. | ||
21690 | * Expand 5 | ||
21691 | */ | ||
21692 | static void JX9_LOCK_NB_Const(jx9_value *pVal, void *pUserData) | ||
21693 | { | ||
21694 | SXUNUSED(pUserData); /* cc warning */ | ||
21695 | jx9_value_int(pVal, 5); | ||
21696 | } | ||
21697 | /* | ||
21698 | * LOCK_EX. | ||
21699 | * Expand 0x01 (MUST BE A POWER OF TWO) | ||
21700 | */ | ||
21701 | static void JX9_LOCK_EX_Const(jx9_value *pVal, void *pUserData) | ||
21702 | { | ||
21703 | SXUNUSED(pUserData); /* cc warning */ | ||
21704 | jx9_value_int(pVal, 0x01); | ||
21705 | } | ||
21706 | /* | ||
21707 | * LOCK_UN. | ||
21708 | * Expand 0 | ||
21709 | */ | ||
21710 | static void JX9_LOCK_UN_Const(jx9_value *pVal, void *pUserData) | ||
21711 | { | ||
21712 | SXUNUSED(pUserData); /* cc warning */ | ||
21713 | jx9_value_int(pVal, 0); | ||
21714 | } | ||
21715 | /* | ||
21716 | * FILE_USE_INC_PATH | ||
21717 | * Expand 0x01 (Must be a power of two) | ||
21718 | */ | ||
21719 | static void JX9_FILE_USE_INCLUDE_PATH_Const(jx9_value *pVal, void *pUserData) | ||
21720 | { | ||
21721 | SXUNUSED(pUserData); /* cc warning */ | ||
21722 | jx9_value_int(pVal, 0x1); | ||
21723 | } | ||
21724 | /* | ||
21725 | * FILE_IGN_NL | ||
21726 | * Expand 0x02 (Must be a power of two) | ||
21727 | */ | ||
21728 | static void JX9_FILE_IGNORE_NEW_LINES_Const(jx9_value *pVal, void *pUserData) | ||
21729 | { | ||
21730 | SXUNUSED(pUserData); /* cc warning */ | ||
21731 | jx9_value_int(pVal, 0x2); | ||
21732 | } | ||
21733 | /* | ||
21734 | * FILE_SKIP_EL | ||
21735 | * Expand 0x04 (Must be a power of two) | ||
21736 | */ | ||
21737 | static void JX9_FILE_SKIP_EMPTY_LINES_Const(jx9_value *pVal, void *pUserData) | ||
21738 | { | ||
21739 | SXUNUSED(pUserData); /* cc warning */ | ||
21740 | jx9_value_int(pVal, 0x4); | ||
21741 | } | ||
21742 | /* | ||
21743 | * FILE_APPEND | ||
21744 | * Expand 0x08 (Must be a power of two) | ||
21745 | */ | ||
21746 | static void JX9_FILE_APPEND_Const(jx9_value *pVal, void *pUserData) | ||
21747 | { | ||
21748 | SXUNUSED(pUserData); /* cc warning */ | ||
21749 | jx9_value_int(pVal, 0x08); | ||
21750 | } | ||
21751 | /* | ||
21752 | * SCANDIR_SORT_ASCENDING | ||
21753 | * Expand 0 | ||
21754 | */ | ||
21755 | static void JX9_SCANDIR_SORT_ASCENDING_Const(jx9_value *pVal, void *pUserData) | ||
21756 | { | ||
21757 | SXUNUSED(pUserData); /* cc warning */ | ||
21758 | jx9_value_int(pVal, 0); | ||
21759 | } | ||
21760 | /* | ||
21761 | * SCANDIR_SORT_DESCENDING | ||
21762 | * Expand 1 | ||
21763 | */ | ||
21764 | static void JX9_SCANDIR_SORT_DESCENDING_Const(jx9_value *pVal, void *pUserData) | ||
21765 | { | ||
21766 | SXUNUSED(pUserData); /* cc warning */ | ||
21767 | jx9_value_int(pVal, 1); | ||
21768 | } | ||
21769 | /* | ||
21770 | * SCANDIR_SORT_NONE | ||
21771 | * Expand 2 | ||
21772 | */ | ||
21773 | static void JX9_SCANDIR_SORT_NONE_Const(jx9_value *pVal, void *pUserData) | ||
21774 | { | ||
21775 | SXUNUSED(pUserData); /* cc warning */ | ||
21776 | jx9_value_int(pVal, 2); | ||
21777 | } | ||
21778 | /* | ||
21779 | * GLOB_MARK | ||
21780 | * Expand 0x01 (must be a power of two) | ||
21781 | */ | ||
21782 | static void JX9_GLOB_MARK_Const(jx9_value *pVal, void *pUserData) | ||
21783 | { | ||
21784 | SXUNUSED(pUserData); /* cc warning */ | ||
21785 | jx9_value_int(pVal, 0x01); | ||
21786 | } | ||
21787 | /* | ||
21788 | * GLOB_NOSORT | ||
21789 | * Expand 0x02 (must be a power of two) | ||
21790 | */ | ||
21791 | static void JX9_GLOB_NOSORT_Const(jx9_value *pVal, void *pUserData) | ||
21792 | { | ||
21793 | SXUNUSED(pUserData); /* cc warning */ | ||
21794 | jx9_value_int(pVal, 0x02); | ||
21795 | } | ||
21796 | /* | ||
21797 | * GLOB_NOCHECK | ||
21798 | * Expand 0x04 (must be a power of two) | ||
21799 | */ | ||
21800 | static void JX9_GLOB_NOCHECK_Const(jx9_value *pVal, void *pUserData) | ||
21801 | { | ||
21802 | SXUNUSED(pUserData); /* cc warning */ | ||
21803 | jx9_value_int(pVal, 0x04); | ||
21804 | } | ||
21805 | /* | ||
21806 | * GLOB_NOESCAPE | ||
21807 | * Expand 0x08 (must be a power of two) | ||
21808 | */ | ||
21809 | static void JX9_GLOB_NOESCAPE_Const(jx9_value *pVal, void *pUserData) | ||
21810 | { | ||
21811 | SXUNUSED(pUserData); /* cc warning */ | ||
21812 | jx9_value_int(pVal, 0x08); | ||
21813 | } | ||
21814 | /* | ||
21815 | * GLOB_BRACE | ||
21816 | * Expand 0x10 (must be a power of two) | ||
21817 | */ | ||
21818 | static void JX9_GLOB_BRACE_Const(jx9_value *pVal, void *pUserData) | ||
21819 | { | ||
21820 | SXUNUSED(pUserData); /* cc warning */ | ||
21821 | jx9_value_int(pVal, 0x10); | ||
21822 | } | ||
21823 | /* | ||
21824 | * GLOB_ONLYDIR | ||
21825 | * Expand 0x20 (must be a power of two) | ||
21826 | */ | ||
21827 | static void JX9_GLOB_ONLYDIR_Const(jx9_value *pVal, void *pUserData) | ||
21828 | { | ||
21829 | SXUNUSED(pUserData); /* cc warning */ | ||
21830 | jx9_value_int(pVal, 0x20); | ||
21831 | } | ||
21832 | /* | ||
21833 | * GLOB_ERR | ||
21834 | * Expand 0x40 (must be a power of two) | ||
21835 | */ | ||
21836 | static void JX9_GLOB_ERR_Const(jx9_value *pVal, void *pUserData) | ||
21837 | { | ||
21838 | SXUNUSED(pUserData); /* cc warning */ | ||
21839 | jx9_value_int(pVal, 0x40); | ||
21840 | } | ||
21841 | /* | ||
21842 | * STDIN | ||
21843 | * Expand the STDIN handle as a resource. | ||
21844 | */ | ||
21845 | static void JX9_STDIN_Const(jx9_value *pVal, void *pUserData) | ||
21846 | { | ||
21847 | jx9_vm *pVm = (jx9_vm *)pUserData; | ||
21848 | void *pResource; | ||
21849 | pResource = jx9ExportStdin(pVm); | ||
21850 | jx9_value_resource(pVal, pResource); | ||
21851 | } | ||
21852 | /* | ||
21853 | * STDOUT | ||
21854 | * Expand the STDOUT handle as a resource. | ||
21855 | */ | ||
21856 | static void JX9_STDOUT_Const(jx9_value *pVal, void *pUserData) | ||
21857 | { | ||
21858 | jx9_vm *pVm = (jx9_vm *)pUserData; | ||
21859 | void *pResource; | ||
21860 | pResource = jx9ExportStdout(pVm); | ||
21861 | jx9_value_resource(pVal, pResource); | ||
21862 | } | ||
21863 | /* | ||
21864 | * STDERR | ||
21865 | * Expand the STDERR handle as a resource. | ||
21866 | */ | ||
21867 | static void JX9_STDERR_Const(jx9_value *pVal, void *pUserData) | ||
21868 | { | ||
21869 | jx9_vm *pVm = (jx9_vm *)pUserData; | ||
21870 | void *pResource; | ||
21871 | pResource = jx9ExportStderr(pVm); | ||
21872 | jx9_value_resource(pVal, pResource); | ||
21873 | } | ||
21874 | /* | ||
21875 | * INI_SCANNER_NORMAL | ||
21876 | * Expand 1 | ||
21877 | */ | ||
21878 | static void JX9_INI_SCANNER_NORMAL_Const(jx9_value *pVal, void *pUserData) | ||
21879 | { | ||
21880 | SXUNUSED(pUserData); /* cc warning */ | ||
21881 | jx9_value_int(pVal, 1); | ||
21882 | } | ||
21883 | /* | ||
21884 | * INI_SCANNER_RAW | ||
21885 | * Expand 2 | ||
21886 | */ | ||
21887 | static void JX9_INI_SCANNER_RAW_Const(jx9_value *pVal, void *pUserData) | ||
21888 | { | ||
21889 | SXUNUSED(pUserData); /* cc warning */ | ||
21890 | jx9_value_int(pVal, 2); | ||
21891 | } | ||
21892 | /* | ||
21893 | * EXTR_OVERWRITE | ||
21894 | * Expand 0x01 (Must be a power of two) | ||
21895 | */ | ||
21896 | static void JX9_EXTR_OVERWRITE_Const(jx9_value *pVal, void *pUserData) | ||
21897 | { | ||
21898 | SXUNUSED(pUserData); /* cc warning */ | ||
21899 | jx9_value_int(pVal, 0x1); | ||
21900 | } | ||
21901 | /* | ||
21902 | * EXTR_SKIP | ||
21903 | * Expand 0x02 (Must be a power of two) | ||
21904 | */ | ||
21905 | static void JX9_EXTR_SKIP_Const(jx9_value *pVal, void *pUserData) | ||
21906 | { | ||
21907 | SXUNUSED(pUserData); /* cc warning */ | ||
21908 | jx9_value_int(pVal, 0x2); | ||
21909 | } | ||
21910 | /* | ||
21911 | * EXTR_PREFIX_SAME | ||
21912 | * Expand 0x04 (Must be a power of two) | ||
21913 | */ | ||
21914 | static void JX9_EXTR_PREFIX_SAME_Const(jx9_value *pVal, void *pUserData) | ||
21915 | { | ||
21916 | SXUNUSED(pUserData); /* cc warning */ | ||
21917 | jx9_value_int(pVal, 0x4); | ||
21918 | } | ||
21919 | /* | ||
21920 | * EXTR_PREFIX_ALL | ||
21921 | * Expand 0x08 (Must be a power of two) | ||
21922 | */ | ||
21923 | static void JX9_EXTR_PREFIX_ALL_Const(jx9_value *pVal, void *pUserData) | ||
21924 | { | ||
21925 | SXUNUSED(pUserData); /* cc warning */ | ||
21926 | jx9_value_int(pVal, 0x8); | ||
21927 | } | ||
21928 | /* | ||
21929 | * EXTR_PREFIX_INVALID | ||
21930 | * Expand 0x10 (Must be a power of two) | ||
21931 | */ | ||
21932 | static void JX9_EXTR_PREFIX_INVALID_Const(jx9_value *pVal, void *pUserData) | ||
21933 | { | ||
21934 | SXUNUSED(pUserData); /* cc warning */ | ||
21935 | jx9_value_int(pVal, 0x10); | ||
21936 | } | ||
21937 | /* | ||
21938 | * EXTR_IF_EXISTS | ||
21939 | * Expand 0x20 (Must be a power of two) | ||
21940 | */ | ||
21941 | static void JX9_EXTR_IF_EXISTS_Const(jx9_value *pVal, void *pUserData) | ||
21942 | { | ||
21943 | SXUNUSED(pUserData); /* cc warning */ | ||
21944 | jx9_value_int(pVal, 0x20); | ||
21945 | } | ||
21946 | /* | ||
21947 | * EXTR_PREFIX_IF_EXISTS | ||
21948 | * Expand 0x40 (Must be a power of two) | ||
21949 | */ | ||
21950 | static void JX9_EXTR_PREFIX_IF_EXISTS_Const(jx9_value *pVal, void *pUserData) | ||
21951 | { | ||
21952 | SXUNUSED(pUserData); /* cc warning */ | ||
21953 | jx9_value_int(pVal, 0x40); | ||
21954 | } | ||
21955 | /* | ||
21956 | * Table of built-in constants. | ||
21957 | */ | ||
21958 | static const jx9_builtin_constant aBuiltIn[] = { | ||
21959 | {"JX9_VERSION", JX9_VER_Const }, | ||
21960 | {"JX9_ENGINE", JX9_VER_Const }, | ||
21961 | {"__JX9__", JX9_VER_Const }, | ||
21962 | {"JX9_OS", JX9_OS_Const }, | ||
21963 | {"__OS__", JX9_OS_Const }, | ||
21964 | {"JX9_EOL", JX9_EOL_Const }, | ||
21965 | {"JX9_INT_MAX", JX9_INTMAX_Const }, | ||
21966 | {"MAXINT", JX9_INTMAX_Const }, | ||
21967 | {"JX9_INT_SIZE", JX9_INTSIZE_Const }, | ||
21968 | {"PATH_SEPARATOR", JX9_PATHSEP_Const }, | ||
21969 | {"DIRECTORY_SEPARATOR", JX9_DIRSEP_Const }, | ||
21970 | {"DIR_SEP", JX9_DIRSEP_Const }, | ||
21971 | {"__TIME__", JX9_TIME_Const }, | ||
21972 | {"__DATE__", JX9_DATE_Const }, | ||
21973 | {"__FILE__", JX9_FILE_Const }, | ||
21974 | {"__DIR__", JX9_DIR_Const }, | ||
21975 | {"E_ERROR", JX9_E_ERROR_Const }, | ||
21976 | {"E_WARNING", JX9_E_WARNING_Const}, | ||
21977 | {"E_PARSE", JX9_E_PARSE_Const }, | ||
21978 | {"E_NOTICE", JX9_E_NOTICE_Const }, | ||
21979 | {"CASE_LOWER", JX9_CASE_LOWER_Const }, | ||
21980 | {"CASE_UPPER", JX9_CASE_UPPER_Const }, | ||
21981 | {"STR_PAD_LEFT", JX9_STR_PAD_LEFT_Const }, | ||
21982 | {"STR_PAD_RIGHT", JX9_STR_PAD_RIGHT_Const}, | ||
21983 | {"STR_PAD_BOTH", JX9_STR_PAD_BOTH_Const }, | ||
21984 | {"COUNT_NORMAL", JX9_COUNT_NORMAL_Const }, | ||
21985 | {"COUNT_RECURSIVE", JX9_COUNT_RECURSIVE_Const }, | ||
21986 | {"SORT_ASC", JX9_SORT_ASC_Const }, | ||
21987 | {"SORT_DESC", JX9_SORT_DESC_Const }, | ||
21988 | {"SORT_REGULAR", JX9_SORT_REG_Const }, | ||
21989 | {"SORT_NUMERIC", JX9_SORT_NUMERIC_Const }, | ||
21990 | {"SORT_STRING", JX9_SORT_STRING_Const }, | ||
21991 | {"JX9_ROUND_HALF_DOWN", JX9_JX9_ROUND_HALF_DOWN_Const }, | ||
21992 | {"JX9_ROUND_HALF_EVEN", JX9_JX9_ROUND_HALF_EVEN_Const }, | ||
21993 | {"JX9_ROUND_HALF_UP", JX9_JX9_ROUND_HALF_UP_Const }, | ||
21994 | {"JX9_ROUND_HALF_ODD", JX9_JX9_ROUND_HALF_ODD_Const }, | ||
21995 | #ifdef JX9_ENABLE_MATH_FUNC | ||
21996 | {"PI", JX9_M_PI_Const }, | ||
21997 | {"M_E", JX9_M_E_Const }, | ||
21998 | {"M_LOG2E", JX9_M_LOG2E_Const }, | ||
21999 | {"M_LOG10E", JX9_M_LOG10E_Const }, | ||
22000 | {"M_LN2", JX9_M_LN2_Const }, | ||
22001 | {"M_LN10", JX9_M_LN10_Const }, | ||
22002 | {"M_PI_2", JX9_M_PI_2_Const }, | ||
22003 | {"M_PI_4", JX9_M_PI_4_Const }, | ||
22004 | {"M_1_PI", JX9_M_1_PI_Const }, | ||
22005 | {"M_2_PI", JX9_M_2_PI_Const }, | ||
22006 | {"M_SQRTPI", JX9_M_SQRTPI_Const }, | ||
22007 | {"M_2_SQRTPI", JX9_M_2_SQRTPI_Const }, | ||
22008 | {"M_SQRT2", JX9_M_SQRT2_Const }, | ||
22009 | {"M_SQRT3", JX9_M_SQRT3_Const }, | ||
22010 | {"M_SQRT1_2", JX9_M_SQRT1_2_Const }, | ||
22011 | {"M_LNPI", JX9_M_LNPI_Const }, | ||
22012 | {"M_EULER", JX9_M_EULER_Const }, | ||
22013 | #endif /* JX9_ENABLE_MATH_FUNC */ | ||
22014 | {"DATE_ATOM", JX9_DATE_ATOM_Const }, | ||
22015 | {"DATE_COOKIE", JX9_DATE_COOKIE_Const }, | ||
22016 | {"DATE_ISO8601", JX9_DATE_ISO8601_Const }, | ||
22017 | {"DATE_RFC822", JX9_DATE_RFC822_Const }, | ||
22018 | {"DATE_RFC850", JX9_DATE_RFC850_Const }, | ||
22019 | {"DATE_RFC1036", JX9_DATE_RFC1036_Const }, | ||
22020 | {"DATE_RFC1123", JX9_DATE_RFC1123_Const }, | ||
22021 | {"DATE_RFC2822", JX9_DATE_RFC2822_Const }, | ||
22022 | {"DATE_RFC3339", JX9_DATE_ATOM_Const }, | ||
22023 | {"DATE_RSS", JX9_DATE_RSS_Const }, | ||
22024 | {"DATE_W3C", JX9_DATE_W3C_Const }, | ||
22025 | {"ENT_COMPAT", JX9_ENT_COMPAT_Const }, | ||
22026 | {"ENT_QUOTES", JX9_ENT_QUOTES_Const }, | ||
22027 | {"ENT_NOQUOTES", JX9_ENT_NOQUOTES_Const }, | ||
22028 | {"ENT_IGNORE", JX9_ENT_IGNORE_Const }, | ||
22029 | {"ENT_SUBSTITUTE", JX9_ENT_SUBSTITUTE_Const}, | ||
22030 | {"ENT_DISALLOWED", JX9_ENT_DISALLOWED_Const}, | ||
22031 | {"ENT_HTML401", JX9_ENT_HTML401_Const }, | ||
22032 | {"ENT_XML1", JX9_ENT_XML1_Const }, | ||
22033 | {"ENT_XHTML", JX9_ENT_XHTML_Const }, | ||
22034 | {"ENT_HTML5", JX9_ENT_HTML5_Const }, | ||
22035 | {"ISO-8859-1", JX9_ISO88591_Const }, | ||
22036 | {"ISO_8859_1", JX9_ISO88591_Const }, | ||
22037 | {"UTF-8", JX9_UTF8_Const }, | ||
22038 | {"UTF8", JX9_UTF8_Const }, | ||
22039 | {"HTML_ENTITIES", JX9_HTML_ENTITIES_Const}, | ||
22040 | {"HTML_SPECIALCHARS", JX9_HTML_SPECIALCHARS_Const }, | ||
22041 | {"JX9_URL_SCHEME", JX9_JX9_URL_SCHEME_Const}, | ||
22042 | {"JX9_URL_HOST", JX9_JX9_URL_HOST_Const}, | ||
22043 | {"JX9_URL_PORT", JX9_JX9_URL_PORT_Const}, | ||
22044 | {"JX9_URL_USER", JX9_JX9_URL_USER_Const}, | ||
22045 | {"JX9_URL_PASS", JX9_JX9_URL_PASS_Const}, | ||
22046 | {"JX9_URL_PATH", JX9_JX9_URL_PATH_Const}, | ||
22047 | {"JX9_URL_QUERY", JX9_JX9_URL_QUERY_Const}, | ||
22048 | {"JX9_URL_FRAGMENT", JX9_JX9_URL_FRAGMENT_Const}, | ||
22049 | {"JX9_QUERY_RFC1738", JX9_JX9_QUERY_RFC1738_Const}, | ||
22050 | {"JX9_QUERY_RFC3986", JX9_JX9_QUERY_RFC3986_Const}, | ||
22051 | {"FNM_NOESCAPE", JX9_FNM_NOESCAPE_Const }, | ||
22052 | {"FNM_PATHNAME", JX9_FNM_PATHNAME_Const }, | ||
22053 | {"FNM_PERIOD", JX9_FNM_PERIOD_Const }, | ||
22054 | {"FNM_CASEFOLD", JX9_FNM_CASEFOLD_Const }, | ||
22055 | {"PATHINFO_DIRNAME", JX9_PATHINFO_DIRNAME_Const }, | ||
22056 | {"PATHINFO_BASENAME", JX9_PATHINFO_BASENAME_Const }, | ||
22057 | {"PATHINFO_EXTENSION", JX9_PATHINFO_EXTENSION_Const}, | ||
22058 | {"PATHINFO_FILENAME", JX9_PATHINFO_FILENAME_Const }, | ||
22059 | {"ASSERT_ACTIVE", JX9_ASSERT_ACTIVE_Const }, | ||
22060 | {"ASSERT_WARNING", JX9_ASSERT_WARNING_Const }, | ||
22061 | {"ASSERT_BAIL", JX9_ASSERT_BAIL_Const }, | ||
22062 | {"ASSERT_QUIET_EVAL", JX9_ASSERT_QUIET_EVAL_Const }, | ||
22063 | {"ASSERT_CALLBACK", JX9_ASSERT_CALLBACK_Const }, | ||
22064 | {"SEEK_SET", JX9_SEEK_SET_Const }, | ||
22065 | {"SEEK_CUR", JX9_SEEK_CUR_Const }, | ||
22066 | {"SEEK_END", JX9_SEEK_END_Const }, | ||
22067 | {"LOCK_EX", JX9_LOCK_EX_Const }, | ||
22068 | {"LOCK_SH", JX9_LOCK_SH_Const }, | ||
22069 | {"LOCK_NB", JX9_LOCK_NB_Const }, | ||
22070 | {"LOCK_UN", JX9_LOCK_UN_Const }, | ||
22071 | {"FILE_USE_INC_PATH", JX9_FILE_USE_INCLUDE_PATH_Const}, | ||
22072 | {"FILE_IGN_NL", JX9_FILE_IGNORE_NEW_LINES_Const}, | ||
22073 | {"FILE_SKIP_EL", JX9_FILE_SKIP_EMPTY_LINES_Const}, | ||
22074 | {"FILE_APPEND", JX9_FILE_APPEND_Const }, | ||
22075 | {"SCANDIR_SORT_ASC", JX9_SCANDIR_SORT_ASCENDING_Const }, | ||
22076 | {"SCANDIR_SORT_DESC", JX9_SCANDIR_SORT_DESCENDING_Const }, | ||
22077 | {"SCANDIR_SORT_NONE", JX9_SCANDIR_SORT_NONE_Const }, | ||
22078 | {"GLOB_MARK", JX9_GLOB_MARK_Const }, | ||
22079 | {"GLOB_NOSORT", JX9_GLOB_NOSORT_Const }, | ||
22080 | {"GLOB_NOCHECK", JX9_GLOB_NOCHECK_Const }, | ||
22081 | {"GLOB_NOESCAPE", JX9_GLOB_NOESCAPE_Const}, | ||
22082 | {"GLOB_BRACE", JX9_GLOB_BRACE_Const }, | ||
22083 | {"GLOB_ONLYDIR", JX9_GLOB_ONLYDIR_Const }, | ||
22084 | {"GLOB_ERR", JX9_GLOB_ERR_Const }, | ||
22085 | {"STDIN", JX9_STDIN_Const }, | ||
22086 | {"stdin", JX9_STDIN_Const }, | ||
22087 | {"STDOUT", JX9_STDOUT_Const }, | ||
22088 | {"stdout", JX9_STDOUT_Const }, | ||
22089 | {"STDERR", JX9_STDERR_Const }, | ||
22090 | {"stderr", JX9_STDERR_Const }, | ||
22091 | {"INI_SCANNER_NORMAL", JX9_INI_SCANNER_NORMAL_Const }, | ||
22092 | {"INI_SCANNER_RAW", JX9_INI_SCANNER_RAW_Const }, | ||
22093 | {"EXTR_OVERWRITE", JX9_EXTR_OVERWRITE_Const }, | ||
22094 | {"EXTR_SKIP", JX9_EXTR_SKIP_Const }, | ||
22095 | {"EXTR_PREFIX_SAME", JX9_EXTR_PREFIX_SAME_Const }, | ||
22096 | {"EXTR_PREFIX_ALL", JX9_EXTR_PREFIX_ALL_Const }, | ||
22097 | {"EXTR_PREFIX_INVALID", JX9_EXTR_PREFIX_INVALID_Const }, | ||
22098 | {"EXTR_IF_EXISTS", JX9_EXTR_IF_EXISTS_Const }, | ||
22099 | {"EXTR_PREFIX_IF_EXISTS", JX9_EXTR_PREFIX_IF_EXISTS_Const} | ||
22100 | }; | ||
22101 | /* | ||
22102 | * Register the built-in constants defined above. | ||
22103 | */ | ||
22104 | JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm) | ||
22105 | { | ||
22106 | sxu32 n; | ||
22107 | /* | ||
22108 | * Note that all built-in constants have access to the jx9 virtual machine | ||
22109 | * that trigger the constant invocation as their private data. | ||
22110 | */ | ||
22111 | for( n = 0 ; n < SX_ARRAYSIZE(aBuiltIn) ; ++n ){ | ||
22112 | jx9_create_constant(&(*pVm), aBuiltIn[n].zName, aBuiltIn[n].xExpand, &(*pVm)); | ||
22113 | } | ||
22114 | } | ||
22115 | /* | ||
22116 | * ---------------------------------------------------------- | ||
22117 | * File: jx9_hashmap.c | ||
22118 | * MD5: 4e93d15cd37e6093e25d8ede3064e210 | ||
22119 | * ---------------------------------------------------------- | ||
22120 | */ | ||
22121 | /* | ||
22122 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
22123 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
22124 | * Version 1.7.2 | ||
22125 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
22126 | * please contact Symisc Systems via: | ||
22127 | * legal@symisc.net | ||
22128 | * licensing@symisc.net | ||
22129 | * contact@symisc.net | ||
22130 | * or visit: | ||
22131 | * http://jx9.symisc.net/ | ||
22132 | */ | ||
22133 | /* $SymiscID: hashmap.c v2.6 Win7 2012-12-11 00:50 stable <chm@symisc.net> $ */ | ||
22134 | #ifndef JX9_AMALGAMATION | ||
22135 | #include "jx9Int.h" | ||
22136 | #endif | ||
22137 | /* This file implement generic hashmaps used to represent JSON arrays and objects */ | ||
22138 | /* Allowed node types */ | ||
22139 | #define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */ | ||
22140 | #define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */ | ||
22141 | /* | ||
22142 | * Default hash function for int [i.e; 64-bit integer] keys. | ||
22143 | */ | ||
22144 | static sxu32 IntHash(sxi64 iKey) | ||
22145 | { | ||
22146 | return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8)); | ||
22147 | } | ||
22148 | /* | ||
22149 | * Default hash function for string/BLOB keys. | ||
22150 | */ | ||
22151 | static sxu32 BinHash(const void *pSrc, sxu32 nLen) | ||
22152 | { | ||
22153 | register unsigned char *zIn = (unsigned char *)pSrc; | ||
22154 | unsigned char *zEnd; | ||
22155 | sxu32 nH = 5381; | ||
22156 | zEnd = &zIn[nLen]; | ||
22157 | for(;;){ | ||
22158 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
22159 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
22160 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
22161 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
22162 | } | ||
22163 | return nH; | ||
22164 | } | ||
22165 | /* | ||
22166 | * Return the total number of entries in a given hashmap. | ||
22167 | * If bRecurisve is set to TRUE then recurse on hashmap entries. | ||
22168 | * If the nesting limit is reached, this function abort immediately. | ||
22169 | */ | ||
22170 | static sxi64 HashmapCount(jx9_hashmap *pMap, int bRecursive, int iRecCount) | ||
22171 | { | ||
22172 | sxi64 iCount = 0; | ||
22173 | if( !bRecursive ){ | ||
22174 | iCount = pMap->nEntry; | ||
22175 | }else{ | ||
22176 | /* Recursive hashmap walk */ | ||
22177 | jx9_hashmap_node *pEntry = pMap->pLast; | ||
22178 | jx9_value *pElem; | ||
22179 | sxu32 n = 0; | ||
22180 | for(;;){ | ||
22181 | if( n >= pMap->nEntry ){ | ||
22182 | break; | ||
22183 | } | ||
22184 | /* Point to the element value */ | ||
22185 | pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pEntry->nValIdx); | ||
22186 | if( pElem ){ | ||
22187 | if( pElem->iFlags & MEMOBJ_HASHMAP ){ | ||
22188 | if( iRecCount > 31 ){ | ||
22189 | /* Nesting limit reached */ | ||
22190 | return iCount; | ||
22191 | } | ||
22192 | /* Recurse */ | ||
22193 | iRecCount++; | ||
22194 | iCount += HashmapCount((jx9_hashmap *)pElem->x.pOther, TRUE, iRecCount); | ||
22195 | iRecCount--; | ||
22196 | } | ||
22197 | } | ||
22198 | /* Point to the next entry */ | ||
22199 | pEntry = pEntry->pNext; | ||
22200 | ++n; | ||
22201 | } | ||
22202 | /* Update count */ | ||
22203 | iCount += pMap->nEntry; | ||
22204 | } | ||
22205 | return iCount; | ||
22206 | } | ||
22207 | /* | ||
22208 | * Allocate a new hashmap node with a 64-bit integer key. | ||
22209 | * If something goes wrong [i.e: out of memory], this function return NULL. | ||
22210 | * Otherwise a fresh [jx9_hashmap_node] instance is returned. | ||
22211 | */ | ||
22212 | static jx9_hashmap_node * HashmapNewIntNode(jx9_hashmap *pMap, sxi64 iKey, sxu32 nHash, sxu32 nValIdx) | ||
22213 | { | ||
22214 | jx9_hashmap_node *pNode; | ||
22215 | /* Allocate a new node */ | ||
22216 | pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); | ||
22217 | if( pNode == 0 ){ | ||
22218 | return 0; | ||
22219 | } | ||
22220 | /* Zero the stucture */ | ||
22221 | SyZero(pNode, sizeof(jx9_hashmap_node)); | ||
22222 | /* Fill in the structure */ | ||
22223 | pNode->pMap = &(*pMap); | ||
22224 | pNode->iType = HASHMAP_INT_NODE; | ||
22225 | pNode->nHash = nHash; | ||
22226 | pNode->xKey.iKey = iKey; | ||
22227 | pNode->nValIdx = nValIdx; | ||
22228 | return pNode; | ||
22229 | } | ||
22230 | /* | ||
22231 | * Allocate a new hashmap node with a BLOB key. | ||
22232 | * If something goes wrong [i.e: out of memory], this function return NULL. | ||
22233 | * Otherwise a fresh [jx9_hashmap_node] instance is returned. | ||
22234 | */ | ||
22235 | static jx9_hashmap_node * HashmapNewBlobNode(jx9_hashmap *pMap, const void *pKey, sxu32 nKeyLen, sxu32 nHash, sxu32 nValIdx) | ||
22236 | { | ||
22237 | jx9_hashmap_node *pNode; | ||
22238 | /* Allocate a new node */ | ||
22239 | pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); | ||
22240 | if( pNode == 0 ){ | ||
22241 | return 0; | ||
22242 | } | ||
22243 | /* Zero the stucture */ | ||
22244 | SyZero(pNode, sizeof(jx9_hashmap_node)); | ||
22245 | /* Fill in the structure */ | ||
22246 | pNode->pMap = &(*pMap); | ||
22247 | pNode->iType = HASHMAP_BLOB_NODE; | ||
22248 | pNode->nHash = nHash; | ||
22249 | SyBlobInit(&pNode->xKey.sKey, &pMap->pVm->sAllocator); | ||
22250 | SyBlobAppend(&pNode->xKey.sKey, pKey, nKeyLen); | ||
22251 | pNode->nValIdx = nValIdx; | ||
22252 | return pNode; | ||
22253 | } | ||
22254 | /* | ||
22255 | * link a hashmap node to the given bucket index (last argument to this function). | ||
22256 | */ | ||
22257 | static void HashmapNodeLink(jx9_hashmap *pMap, jx9_hashmap_node *pNode, sxu32 nBucketIdx) | ||
22258 | { | ||
22259 | /* Link */ | ||
22260 | if( pMap->apBucket[nBucketIdx] != 0 ){ | ||
22261 | pNode->pNextCollide = pMap->apBucket[nBucketIdx]; | ||
22262 | pMap->apBucket[nBucketIdx]->pPrevCollide = pNode; | ||
22263 | } | ||
22264 | pMap->apBucket[nBucketIdx] = pNode; | ||
22265 | /* Link to the map list */ | ||
22266 | if( pMap->pFirst == 0 ){ | ||
22267 | pMap->pFirst = pMap->pLast = pNode; | ||
22268 | /* Point to the first inserted node */ | ||
22269 | pMap->pCur = pNode; | ||
22270 | }else{ | ||
22271 | MACRO_LD_PUSH(pMap->pLast, pNode); | ||
22272 | } | ||
22273 | ++pMap->nEntry; | ||
22274 | } | ||
22275 | /* | ||
22276 | * Unlink a node from the hashmap. | ||
22277 | * If the node count reaches zero then release the whole hash-bucket. | ||
22278 | */ | ||
22279 | static void jx9HashmapUnlinkNode(jx9_hashmap_node *pNode) | ||
22280 | { | ||
22281 | jx9_hashmap *pMap = pNode->pMap; | ||
22282 | jx9_vm *pVm = pMap->pVm; | ||
22283 | /* Unlink from the corresponding bucket */ | ||
22284 | if( pNode->pPrevCollide == 0 ){ | ||
22285 | pMap->apBucket[pNode->nHash & (pMap->nSize - 1)] = pNode->pNextCollide; | ||
22286 | }else{ | ||
22287 | pNode->pPrevCollide->pNextCollide = pNode->pNextCollide; | ||
22288 | } | ||
22289 | if( pNode->pNextCollide ){ | ||
22290 | pNode->pNextCollide->pPrevCollide = pNode->pPrevCollide; | ||
22291 | } | ||
22292 | if( pMap->pFirst == pNode ){ | ||
22293 | pMap->pFirst = pNode->pPrev; | ||
22294 | } | ||
22295 | if( pMap->pCur == pNode ){ | ||
22296 | /* Advance the node cursor */ | ||
22297 | pMap->pCur = pMap->pCur->pPrev; /* Reverse link */ | ||
22298 | } | ||
22299 | /* Unlink from the map list */ | ||
22300 | MACRO_LD_REMOVE(pMap->pLast, pNode); | ||
22301 | /* Restore to the free list */ | ||
22302 | jx9VmUnsetMemObj(pVm, pNode->nValIdx); | ||
22303 | if( pNode->iType == HASHMAP_BLOB_NODE ){ | ||
22304 | SyBlobRelease(&pNode->xKey.sKey); | ||
22305 | } | ||
22306 | SyMemBackendPoolFree(&pVm->sAllocator, pNode); | ||
22307 | pMap->nEntry--; | ||
22308 | if( pMap->nEntry < 1 ){ | ||
22309 | /* Free the hash-bucket */ | ||
22310 | SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); | ||
22311 | pMap->apBucket = 0; | ||
22312 | pMap->nSize = 0; | ||
22313 | pMap->pFirst = pMap->pLast = pMap->pCur = 0; | ||
22314 | } | ||
22315 | } | ||
22316 | #define HASHMAP_FILL_FACTOR 3 | ||
22317 | /* | ||
22318 | * Grow the hash-table and rehash all entries. | ||
22319 | */ | ||
22320 | static sxi32 HashmapGrowBucket(jx9_hashmap *pMap) | ||
22321 | { | ||
22322 | if( pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR ){ | ||
22323 | jx9_hashmap_node **apOld = pMap->apBucket; | ||
22324 | jx9_hashmap_node *pEntry, **apNew; | ||
22325 | sxu32 nNew = pMap->nSize << 1; | ||
22326 | sxu32 nBucket; | ||
22327 | sxu32 n; | ||
22328 | if( nNew < 1 ){ | ||
22329 | nNew = 16; | ||
22330 | } | ||
22331 | /* Allocate a new bucket */ | ||
22332 | apNew = (jx9_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator, nNew * sizeof(jx9_hashmap_node *)); | ||
22333 | if( apNew == 0 ){ | ||
22334 | if( pMap->nSize < 1 ){ | ||
22335 | return SXERR_MEM; /* Fatal */ | ||
22336 | } | ||
22337 | /* Not so fatal here, simply a performance hit */ | ||
22338 | return SXRET_OK; | ||
22339 | } | ||
22340 | /* Zero the table */ | ||
22341 | SyZero((void *)apNew, nNew * sizeof(jx9_hashmap_node *)); | ||
22342 | /* Reflect the change */ | ||
22343 | pMap->apBucket = apNew; | ||
22344 | pMap->nSize = nNew; | ||
22345 | if( apOld == 0 ){ | ||
22346 | /* First allocated table [i.e: no entry], return immediately */ | ||
22347 | return SXRET_OK; | ||
22348 | } | ||
22349 | /* Rehash old entries */ | ||
22350 | pEntry = pMap->pFirst; | ||
22351 | n = 0; | ||
22352 | for( ;; ){ | ||
22353 | if( n >= pMap->nEntry ){ | ||
22354 | break; | ||
22355 | } | ||
22356 | /* Clear the old collision link */ | ||
22357 | pEntry->pNextCollide = pEntry->pPrevCollide = 0; | ||
22358 | /* Link to the new bucket */ | ||
22359 | nBucket = pEntry->nHash & (nNew - 1); | ||
22360 | if( pMap->apBucket[nBucket] != 0 ){ | ||
22361 | pEntry->pNextCollide = pMap->apBucket[nBucket]; | ||
22362 | pMap->apBucket[nBucket]->pPrevCollide = pEntry; | ||
22363 | } | ||
22364 | pMap->apBucket[nBucket] = pEntry; | ||
22365 | /* Point to the next entry */ | ||
22366 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
22367 | n++; | ||
22368 | } | ||
22369 | /* Free the old table */ | ||
22370 | SyMemBackendFree(&pMap->pVm->sAllocator, (void *)apOld); | ||
22371 | } | ||
22372 | return SXRET_OK; | ||
22373 | } | ||
22374 | /* | ||
22375 | * Insert a 64-bit integer key and it's associated value (if any) in the given | ||
22376 | * hashmap. | ||
22377 | */ | ||
22378 | static sxi32 HashmapInsertIntKey(jx9_hashmap *pMap,sxi64 iKey,jx9_value *pValue) | ||
22379 | { | ||
22380 | jx9_hashmap_node *pNode; | ||
22381 | jx9_value *pObj; | ||
22382 | sxu32 nIdx; | ||
22383 | sxu32 nHash; | ||
22384 | sxi32 rc; | ||
22385 | /* Reserve a jx9_value for the value */ | ||
22386 | pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); | ||
22387 | if( pObj == 0 ){ | ||
22388 | return SXERR_MEM; | ||
22389 | } | ||
22390 | if( pValue ){ | ||
22391 | /* Duplicate the value */ | ||
22392 | jx9MemObjStore(pValue, pObj); | ||
22393 | } | ||
22394 | /* Hash the key */ | ||
22395 | nHash = pMap->xIntHash(iKey); | ||
22396 | /* Allocate a new int node */ | ||
22397 | pNode = HashmapNewIntNode(&(*pMap), iKey, nHash, nIdx); | ||
22398 | if( pNode == 0 ){ | ||
22399 | return SXERR_MEM; | ||
22400 | } | ||
22401 | /* Make sure the bucket is big enough to hold the new entry */ | ||
22402 | rc = HashmapGrowBucket(&(*pMap)); | ||
22403 | if( rc != SXRET_OK ){ | ||
22404 | SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); | ||
22405 | return rc; | ||
22406 | } | ||
22407 | /* Perform the insertion */ | ||
22408 | HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); | ||
22409 | /* All done */ | ||
22410 | return SXRET_OK; | ||
22411 | } | ||
22412 | /* | ||
22413 | * Insert a BLOB key and it's associated value (if any) in the given | ||
22414 | * hashmap. | ||
22415 | */ | ||
22416 | static sxi32 HashmapInsertBlobKey(jx9_hashmap *pMap,const void *pKey,sxu32 nKeyLen,jx9_value *pValue) | ||
22417 | { | ||
22418 | jx9_hashmap_node *pNode; | ||
22419 | jx9_value *pObj; | ||
22420 | sxu32 nHash; | ||
22421 | sxu32 nIdx; | ||
22422 | sxi32 rc; | ||
22423 | /* Reserve a jx9_value for the value */ | ||
22424 | pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); | ||
22425 | if( pObj == 0 ){ | ||
22426 | return SXERR_MEM; | ||
22427 | } | ||
22428 | if( pValue ){ | ||
22429 | /* Duplicate the value */ | ||
22430 | jx9MemObjStore(pValue, pObj); | ||
22431 | } | ||
22432 | /* Hash the key */ | ||
22433 | nHash = pMap->xBlobHash(pKey, nKeyLen); | ||
22434 | /* Allocate a new blob node */ | ||
22435 | pNode = HashmapNewBlobNode(&(*pMap), pKey, nKeyLen, nHash, nIdx); | ||
22436 | if( pNode == 0 ){ | ||
22437 | return SXERR_MEM; | ||
22438 | } | ||
22439 | /* Make sure the bucket is big enough to hold the new entry */ | ||
22440 | rc = HashmapGrowBucket(&(*pMap)); | ||
22441 | if( rc != SXRET_OK ){ | ||
22442 | SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); | ||
22443 | return rc; | ||
22444 | } | ||
22445 | /* Perform the insertion */ | ||
22446 | HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); | ||
22447 | /* All done */ | ||
22448 | return SXRET_OK; | ||
22449 | } | ||
22450 | /* | ||
22451 | * Check if a given 64-bit integer key exists in the given hashmap. | ||
22452 | * Write a pointer to the target node on success. Otherwise | ||
22453 | * SXERR_NOTFOUND is returned on failure. | ||
22454 | */ | ||
22455 | static sxi32 HashmapLookupIntKey( | ||
22456 | jx9_hashmap *pMap, /* Target hashmap */ | ||
22457 | sxi64 iKey, /* lookup key */ | ||
22458 | jx9_hashmap_node **ppNode /* OUT: target node on success */ | ||
22459 | ) | ||
22460 | { | ||
22461 | jx9_hashmap_node *pNode; | ||
22462 | sxu32 nHash; | ||
22463 | if( pMap->nEntry < 1 ){ | ||
22464 | /* Don't bother hashing, there is no entry anyway */ | ||
22465 | return SXERR_NOTFOUND; | ||
22466 | } | ||
22467 | /* Hash the key first */ | ||
22468 | nHash = pMap->xIntHash(iKey); | ||
22469 | /* Point to the appropriate bucket */ | ||
22470 | pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; | ||
22471 | /* Perform the lookup */ | ||
22472 | for(;;){ | ||
22473 | if( pNode == 0 ){ | ||
22474 | break; | ||
22475 | } | ||
22476 | if( pNode->iType == HASHMAP_INT_NODE | ||
22477 | && pNode->nHash == nHash | ||
22478 | && pNode->xKey.iKey == iKey ){ | ||
22479 | /* Node found */ | ||
22480 | if( ppNode ){ | ||
22481 | *ppNode = pNode; | ||
22482 | } | ||
22483 | return SXRET_OK; | ||
22484 | } | ||
22485 | /* Follow the collision link */ | ||
22486 | pNode = pNode->pNextCollide; | ||
22487 | } | ||
22488 | /* No such entry */ | ||
22489 | return SXERR_NOTFOUND; | ||
22490 | } | ||
22491 | /* | ||
22492 | * Check if a given BLOB key exists in the given hashmap. | ||
22493 | * Write a pointer to the target node on success. Otherwise | ||
22494 | * SXERR_NOTFOUND is returned on failure. | ||
22495 | */ | ||
22496 | static sxi32 HashmapLookupBlobKey( | ||
22497 | jx9_hashmap *pMap, /* Target hashmap */ | ||
22498 | const void *pKey, /* Lookup key */ | ||
22499 | sxu32 nKeyLen, /* Key length in bytes */ | ||
22500 | jx9_hashmap_node **ppNode /* OUT: target node on success */ | ||
22501 | ) | ||
22502 | { | ||
22503 | jx9_hashmap_node *pNode; | ||
22504 | sxu32 nHash; | ||
22505 | if( pMap->nEntry < 1 ){ | ||
22506 | /* Don't bother hashing, there is no entry anyway */ | ||
22507 | return SXERR_NOTFOUND; | ||
22508 | } | ||
22509 | /* Hash the key first */ | ||
22510 | nHash = pMap->xBlobHash(pKey, nKeyLen); | ||
22511 | /* Point to the appropriate bucket */ | ||
22512 | pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; | ||
22513 | /* Perform the lookup */ | ||
22514 | for(;;){ | ||
22515 | if( pNode == 0 ){ | ||
22516 | break; | ||
22517 | } | ||
22518 | if( pNode->iType == HASHMAP_BLOB_NODE | ||
22519 | && pNode->nHash == nHash | ||
22520 | && SyBlobLength(&pNode->xKey.sKey) == nKeyLen | ||
22521 | && SyMemcmp(SyBlobData(&pNode->xKey.sKey), pKey, nKeyLen) == 0 ){ | ||
22522 | /* Node found */ | ||
22523 | if( ppNode ){ | ||
22524 | *ppNode = pNode; | ||
22525 | } | ||
22526 | return SXRET_OK; | ||
22527 | } | ||
22528 | /* Follow the collision link */ | ||
22529 | pNode = pNode->pNextCollide; | ||
22530 | } | ||
22531 | /* No such entry */ | ||
22532 | return SXERR_NOTFOUND; | ||
22533 | } | ||
22534 | /* | ||
22535 | * Check if the given BLOB key looks like a decimal number. | ||
22536 | * Retrurn TRUE on success.FALSE otherwise. | ||
22537 | */ | ||
22538 | static int HashmapIsIntKey(SyBlob *pKey) | ||
22539 | { | ||
22540 | const char *zIn = (const char *)SyBlobData(pKey); | ||
22541 | const char *zEnd = &zIn[SyBlobLength(pKey)]; | ||
22542 | if( (int)(zEnd-zIn) > 1 && zIn[0] == '0' ){ | ||
22543 | /* Octal not decimal number */ | ||
22544 | return FALSE; | ||
22545 | } | ||
22546 | if( (zIn[0] == '-' || zIn[0] == '+') && &zIn[1] < zEnd ){ | ||
22547 | zIn++; | ||
22548 | } | ||
22549 | for(;;){ | ||
22550 | if( zIn >= zEnd ){ | ||
22551 | return TRUE; | ||
22552 | } | ||
22553 | if( (unsigned char)zIn[0] >= 0xc0 /* UTF-8 stream */ || !SyisDigit(zIn[0]) ){ | ||
22554 | break; | ||
22555 | } | ||
22556 | zIn++; | ||
22557 | } | ||
22558 | /* Key does not look like a decimal number */ | ||
22559 | return FALSE; | ||
22560 | } | ||
22561 | /* | ||
22562 | * Check if a given key exists in the given hashmap. | ||
22563 | * Write a pointer to the target node on success. | ||
22564 | * Otherwise SXERR_NOTFOUND is returned on failure. | ||
22565 | */ | ||
22566 | static sxi32 HashmapLookup( | ||
22567 | jx9_hashmap *pMap, /* Target hashmap */ | ||
22568 | jx9_value *pKey, /* Lookup key */ | ||
22569 | jx9_hashmap_node **ppNode /* OUT: target node on success */ | ||
22570 | ) | ||
22571 | { | ||
22572 | jx9_hashmap_node *pNode = 0; /* cc -O6 warning */ | ||
22573 | sxi32 rc; | ||
22574 | if( pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES) ){ | ||
22575 | if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ | ||
22576 | /* Force a string cast */ | ||
22577 | jx9MemObjToString(&(*pKey)); | ||
22578 | } | ||
22579 | if( SyBlobLength(&pKey->sBlob) > 0 ){ | ||
22580 | /* Perform a blob lookup */ | ||
22581 | rc = HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode); | ||
22582 | goto result; | ||
22583 | } | ||
22584 | } | ||
22585 | /* Perform an int lookup */ | ||
22586 | if((pKey->iFlags & MEMOBJ_INT) == 0 ){ | ||
22587 | /* Force an integer cast */ | ||
22588 | jx9MemObjToInteger(pKey); | ||
22589 | } | ||
22590 | /* Perform an int lookup */ | ||
22591 | rc = HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode); | ||
22592 | result: | ||
22593 | if( rc == SXRET_OK ){ | ||
22594 | /* Node found */ | ||
22595 | if( ppNode ){ | ||
22596 | *ppNode = pNode; | ||
22597 | } | ||
22598 | return SXRET_OK; | ||
22599 | } | ||
22600 | /* No such entry */ | ||
22601 | return SXERR_NOTFOUND; | ||
22602 | } | ||
22603 | /* | ||
22604 | * Insert a given key and it's associated value (if any) in the given | ||
22605 | * hashmap. | ||
22606 | * If a node with the given key already exists in the database | ||
22607 | * then this function overwrite the old value. | ||
22608 | */ | ||
22609 | static sxi32 HashmapInsert( | ||
22610 | jx9_hashmap *pMap, /* Target hashmap */ | ||
22611 | jx9_value *pKey, /* Lookup key */ | ||
22612 | jx9_value *pVal /* Node value */ | ||
22613 | ) | ||
22614 | { | ||
22615 | jx9_hashmap_node *pNode = 0; | ||
22616 | sxi32 rc = SXRET_OK; | ||
22617 | if( pMap->nEntry < 1 && pKey && (pKey->iFlags & MEMOBJ_STRING) ){ | ||
22618 | pMap->iFlags |= HASHMAP_JSON_OBJECT; | ||
22619 | } | ||
22620 | if( pKey && (pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES)) ){ | ||
22621 | if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ | ||
22622 | /* Force a string cast */ | ||
22623 | jx9MemObjToString(&(*pKey)); | ||
22624 | } | ||
22625 | if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){ | ||
22626 | if(SyBlobLength(&pKey->sBlob) < 1){ | ||
22627 | /* Automatic index assign */ | ||
22628 | pKey = 0; | ||
22629 | } | ||
22630 | goto IntKey; | ||
22631 | } | ||
22632 | if( SXRET_OK == HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), | ||
22633 | SyBlobLength(&pKey->sBlob), &pNode) ){ | ||
22634 | /* Overwrite the old value */ | ||
22635 | jx9_value *pElem; | ||
22636 | pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); | ||
22637 | if( pElem ){ | ||
22638 | if( pVal ){ | ||
22639 | jx9MemObjStore(pVal, pElem); | ||
22640 | }else{ | ||
22641 | /* Nullify the entry */ | ||
22642 | jx9MemObjToNull(pElem); | ||
22643 | } | ||
22644 | } | ||
22645 | return SXRET_OK; | ||
22646 | } | ||
22647 | /* Perform a blob-key insertion */ | ||
22648 | rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&(*pVal)); | ||
22649 | return rc; | ||
22650 | } | ||
22651 | IntKey: | ||
22652 | if( pKey ){ | ||
22653 | if((pKey->iFlags & MEMOBJ_INT) == 0 ){ | ||
22654 | /* Force an integer cast */ | ||
22655 | jx9MemObjToInteger(pKey); | ||
22656 | } | ||
22657 | if( SXRET_OK == HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode) ){ | ||
22658 | /* Overwrite the old value */ | ||
22659 | jx9_value *pElem; | ||
22660 | pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); | ||
22661 | if( pElem ){ | ||
22662 | if( pVal ){ | ||
22663 | jx9MemObjStore(pVal, pElem); | ||
22664 | }else{ | ||
22665 | /* Nullify the entry */ | ||
22666 | jx9MemObjToNull(pElem); | ||
22667 | } | ||
22668 | } | ||
22669 | return SXRET_OK; | ||
22670 | } | ||
22671 | /* Perform a 64-bit-int-key insertion */ | ||
22672 | rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, &(*pVal)); | ||
22673 | if( rc == SXRET_OK ){ | ||
22674 | if( pKey->x.iVal >= pMap->iNextIdx ){ | ||
22675 | /* Increment the automatic index */ | ||
22676 | pMap->iNextIdx = pKey->x.iVal + 1; | ||
22677 | /* Make sure the automatic index is not reserved */ | ||
22678 | while( SXRET_OK == HashmapLookupIntKey(&(*pMap), pMap->iNextIdx, 0) ){ | ||
22679 | pMap->iNextIdx++; | ||
22680 | } | ||
22681 | } | ||
22682 | } | ||
22683 | }else{ | ||
22684 | /* Assign an automatic index */ | ||
22685 | rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,&(*pVal)); | ||
22686 | if( rc == SXRET_OK ){ | ||
22687 | ++pMap->iNextIdx; | ||
22688 | } | ||
22689 | } | ||
22690 | /* Insertion result */ | ||
22691 | return rc; | ||
22692 | } | ||
22693 | /* | ||
22694 | * Extract node value. | ||
22695 | */ | ||
22696 | static jx9_value * HashmapExtractNodeValue(jx9_hashmap_node *pNode) | ||
22697 | { | ||
22698 | /* Point to the desired object */ | ||
22699 | jx9_value *pObj; | ||
22700 | pObj = (jx9_value *)SySetAt(&pNode->pMap->pVm->aMemObj, pNode->nValIdx); | ||
22701 | return pObj; | ||
22702 | } | ||
22703 | /* | ||
22704 | * Insert a node in the given hashmap. | ||
22705 | * If a node with the given key already exists in the database | ||
22706 | * then this function overwrite the old value. | ||
22707 | */ | ||
22708 | static sxi32 HashmapInsertNode(jx9_hashmap *pMap, jx9_hashmap_node *pNode, int bPreserve) | ||
22709 | { | ||
22710 | jx9_value *pObj; | ||
22711 | sxi32 rc; | ||
22712 | /* Extract the node value */ | ||
22713 | pObj = HashmapExtractNodeValue(&(*pNode)); | ||
22714 | if( pObj == 0 ){ | ||
22715 | return SXERR_EMPTY; | ||
22716 | } | ||
22717 | /* Preserve key */ | ||
22718 | if( pNode->iType == HASHMAP_INT_NODE){ | ||
22719 | /* Int64 key */ | ||
22720 | if( !bPreserve ){ | ||
22721 | /* Assign an automatic index */ | ||
22722 | rc = HashmapInsert(&(*pMap), 0, pObj); | ||
22723 | }else{ | ||
22724 | rc = HashmapInsertIntKey(&(*pMap), pNode->xKey.iKey, pObj); | ||
22725 | } | ||
22726 | }else{ | ||
22727 | /* Blob key */ | ||
22728 | rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pNode->xKey.sKey), | ||
22729 | SyBlobLength(&pNode->xKey.sKey), pObj); | ||
22730 | } | ||
22731 | return rc; | ||
22732 | } | ||
22733 | /* | ||
22734 | * Compare two node values. | ||
22735 | * Return 0 if the node values are equals, > 0 if pLeft is greater than pRight | ||
22736 | * or < 0 if pRight is greater than pLeft. | ||
22737 | * For a full description on jx9_values comparison, refer to the implementation | ||
22738 | * of the [jx9MemObjCmp()] function defined in memobj.c or the official | ||
22739 | * documenation. | ||
22740 | */ | ||
22741 | static sxi32 HashmapNodeCmp(jx9_hashmap_node *pLeft, jx9_hashmap_node *pRight, int bStrict) | ||
22742 | { | ||
22743 | jx9_value sObj1, sObj2; | ||
22744 | sxi32 rc; | ||
22745 | if( pLeft == pRight ){ | ||
22746 | /* | ||
22747 | * Same node.Refer to the sort() implementation defined | ||
22748 | * below for more information on this sceanario. | ||
22749 | */ | ||
22750 | return 0; | ||
22751 | } | ||
22752 | /* Do the comparison */ | ||
22753 | jx9MemObjInit(pLeft->pMap->pVm, &sObj1); | ||
22754 | jx9MemObjInit(pLeft->pMap->pVm, &sObj2); | ||
22755 | jx9HashmapExtractNodeValue(pLeft, &sObj1, FALSE); | ||
22756 | jx9HashmapExtractNodeValue(pRight, &sObj2, FALSE); | ||
22757 | rc = jx9MemObjCmp(&sObj1, &sObj2, bStrict, 0); | ||
22758 | jx9MemObjRelease(&sObj1); | ||
22759 | jx9MemObjRelease(&sObj2); | ||
22760 | return rc; | ||
22761 | } | ||
22762 | /* | ||
22763 | * Rehash a node with a 64-bit integer key. | ||
22764 | * Refer to [merge_sort(), array_shift()] implementations for more information. | ||
22765 | */ | ||
22766 | static void HashmapRehashIntNode(jx9_hashmap_node *pEntry) | ||
22767 | { | ||
22768 | jx9_hashmap *pMap = pEntry->pMap; | ||
22769 | sxu32 nBucket; | ||
22770 | /* Remove old collision links */ | ||
22771 | if( pEntry->pPrevCollide ){ | ||
22772 | pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; | ||
22773 | }else{ | ||
22774 | pMap->apBucket[pEntry->nHash & (pMap->nSize - 1)] = pEntry->pNextCollide; | ||
22775 | } | ||
22776 | if( pEntry->pNextCollide ){ | ||
22777 | pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; | ||
22778 | } | ||
22779 | pEntry->pNextCollide = pEntry->pPrevCollide = 0; | ||
22780 | /* Compute the new hash */ | ||
22781 | pEntry->nHash = pMap->xIntHash(pMap->iNextIdx); | ||
22782 | pEntry->xKey.iKey = pMap->iNextIdx; | ||
22783 | nBucket = pEntry->nHash & (pMap->nSize - 1); | ||
22784 | /* Link to the new bucket */ | ||
22785 | pEntry->pNextCollide = pMap->apBucket[nBucket]; | ||
22786 | if( pMap->apBucket[nBucket] ){ | ||
22787 | pMap->apBucket[nBucket]->pPrevCollide = pEntry; | ||
22788 | } | ||
22789 | pEntry->pNextCollide = pMap->apBucket[nBucket]; | ||
22790 | pMap->apBucket[nBucket] = pEntry; | ||
22791 | /* Increment the automatic index */ | ||
22792 | pMap->iNextIdx++; | ||
22793 | } | ||
22794 | /* | ||
22795 | * Perform a linear search on a given hashmap. | ||
22796 | * Write a pointer to the target node on success. | ||
22797 | * Otherwise SXERR_NOTFOUND is returned on failure. | ||
22798 | * Refer to [array_intersect(), array_diff(), in_array(), ...] implementations | ||
22799 | * for more information. | ||
22800 | */ | ||
22801 | static int HashmapFindValue( | ||
22802 | jx9_hashmap *pMap, /* Target hashmap */ | ||
22803 | jx9_value *pNeedle, /* Lookup key */ | ||
22804 | jx9_hashmap_node **ppNode, /* OUT: target node on success */ | ||
22805 | int bStrict /* TRUE for strict comparison */ | ||
22806 | ) | ||
22807 | { | ||
22808 | jx9_hashmap_node *pEntry; | ||
22809 | jx9_value sVal, *pVal; | ||
22810 | jx9_value sNeedle; | ||
22811 | sxi32 rc; | ||
22812 | sxu32 n; | ||
22813 | /* Perform a linear search since we cannot sort the hashmap based on values */ | ||
22814 | pEntry = pMap->pFirst; | ||
22815 | n = pMap->nEntry; | ||
22816 | jx9MemObjInit(pMap->pVm, &sVal); | ||
22817 | jx9MemObjInit(pMap->pVm, &sNeedle); | ||
22818 | for(;;){ | ||
22819 | if( n < 1 ){ | ||
22820 | break; | ||
22821 | } | ||
22822 | /* Extract node value */ | ||
22823 | pVal = HashmapExtractNodeValue(pEntry); | ||
22824 | if( pVal ){ | ||
22825 | if( (pVal->iFlags|pNeedle->iFlags) & MEMOBJ_NULL ){ | ||
22826 | sxi32 iF1 = pVal->iFlags; | ||
22827 | sxi32 iF2 = pNeedle->iFlags; | ||
22828 | if( iF1 == iF2 ){ | ||
22829 | /* NULL values are equals */ | ||
22830 | if( ppNode ){ | ||
22831 | *ppNode = pEntry; | ||
22832 | } | ||
22833 | return SXRET_OK; | ||
22834 | } | ||
22835 | }else{ | ||
22836 | /* Duplicate value */ | ||
22837 | jx9MemObjLoad(pVal, &sVal); | ||
22838 | jx9MemObjLoad(pNeedle, &sNeedle); | ||
22839 | rc = jx9MemObjCmp(&sNeedle, &sVal, bStrict, 0); | ||
22840 | jx9MemObjRelease(&sVal); | ||
22841 | jx9MemObjRelease(&sNeedle); | ||
22842 | if( rc == 0 ){ | ||
22843 | if( ppNode ){ | ||
22844 | *ppNode = pEntry; | ||
22845 | } | ||
22846 | /* Match found*/ | ||
22847 | return SXRET_OK; | ||
22848 | } | ||
22849 | } | ||
22850 | } | ||
22851 | /* Point to the next entry */ | ||
22852 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
22853 | n--; | ||
22854 | } | ||
22855 | /* No such entry */ | ||
22856 | return SXERR_NOTFOUND; | ||
22857 | } | ||
22858 | /* | ||
22859 | * Compare two hashmaps. | ||
22860 | * Return 0 if the hashmaps are equals.Any other value indicates inequality. | ||
22861 | * Note on array comparison operators. | ||
22862 | * According to the JX9 language reference manual. | ||
22863 | * Array Operators Example Name Result | ||
22864 | * $a + $b Union Union of $a and $b. | ||
22865 | * $a == $b Equality TRUE if $a and $b have the same key/value pairs. | ||
22866 | * $a === $b Identity TRUE if $a and $b have the same key/value pairs in the same | ||
22867 | * order and of the same types. | ||
22868 | * $a != $b Inequality TRUE if $a is not equal to $b. | ||
22869 | * $a <> $b Inequality TRUE if $a is not equal to $b. | ||
22870 | * $a !== $b Non-identity TRUE if $a is not identical to $b. | ||
22871 | * The + operator returns the right-hand array appended to the left-hand array; | ||
22872 | * For keys that exist in both arrays, the elements from the left-hand array will be used | ||
22873 | * and the matching elements from the right-hand array will be ignored. | ||
22874 | * <?jx9 | ||
22875 | * $a = array("a" => "apple", "b" => "banana"); | ||
22876 | * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); | ||
22877 | * $c = $a + $b; // Union of $a and $b | ||
22878 | * print "Union of \$a and \$b: \n"; | ||
22879 | * dump($c); | ||
22880 | * $c = $b + $a; // Union of $b and $a | ||
22881 | * print "Union of \$b and \$a: \n"; | ||
22882 | * dump($c); | ||
22883 | * ?> | ||
22884 | * When executed, this script will print the following: | ||
22885 | * Union of $a and $b: | ||
22886 | * array(3) { | ||
22887 | * ["a"]=> | ||
22888 | * string(5) "apple" | ||
22889 | * ["b"]=> | ||
22890 | * string(6) "banana" | ||
22891 | * ["c"]=> | ||
22892 | * string(6) "cherry" | ||
22893 | * } | ||
22894 | * Union of $b and $a: | ||
22895 | * array(3) { | ||
22896 | * ["a"]=> | ||
22897 | * string(4) "pear" | ||
22898 | * ["b"]=> | ||
22899 | * string(10) "strawberry" | ||
22900 | * ["c"]=> | ||
22901 | * string(6) "cherry" | ||
22902 | * } | ||
22903 | * Elements of arrays are equal for the comparison if they have the same key and value. | ||
22904 | */ | ||
22905 | JX9_PRIVATE sxi32 jx9HashmapCmp( | ||
22906 | jx9_hashmap *pLeft, /* Left hashmap */ | ||
22907 | jx9_hashmap *pRight, /* Right hashmap */ | ||
22908 | int bStrict /* TRUE for strict comparison */ | ||
22909 | ) | ||
22910 | { | ||
22911 | jx9_hashmap_node *pLe, *pRe; | ||
22912 | sxi32 rc; | ||
22913 | sxu32 n; | ||
22914 | if( pLeft == pRight ){ | ||
22915 | /* Same hashmap instance. This can easily happen since hashmaps are passed by reference. | ||
22916 | * Unlike the engine. | ||
22917 | */ | ||
22918 | return 0; | ||
22919 | } | ||
22920 | if( pLeft->nEntry != pRight->nEntry ){ | ||
22921 | /* Must have the same number of entries */ | ||
22922 | return pLeft->nEntry > pRight->nEntry ? 1 : -1; | ||
22923 | } | ||
22924 | /* Point to the first inserted entry of the left hashmap */ | ||
22925 | pLe = pLeft->pFirst; | ||
22926 | pRe = 0; /* cc warning */ | ||
22927 | /* Perform the comparison */ | ||
22928 | n = pLeft->nEntry; | ||
22929 | for(;;){ | ||
22930 | if( n < 1 ){ | ||
22931 | break; | ||
22932 | } | ||
22933 | if( pLe->iType == HASHMAP_INT_NODE){ | ||
22934 | /* Int key */ | ||
22935 | rc = HashmapLookupIntKey(&(*pRight), pLe->xKey.iKey, &pRe); | ||
22936 | }else{ | ||
22937 | SyBlob *pKey = &pLe->xKey.sKey; | ||
22938 | /* Blob key */ | ||
22939 | rc = HashmapLookupBlobKey(&(*pRight), SyBlobData(pKey), SyBlobLength(pKey), &pRe); | ||
22940 | } | ||
22941 | if( rc != SXRET_OK ){ | ||
22942 | /* No such entry in the right side */ | ||
22943 | return 1; | ||
22944 | } | ||
22945 | rc = 0; | ||
22946 | if( bStrict ){ | ||
22947 | /* Make sure, the keys are of the same type */ | ||
22948 | if( pLe->iType != pRe->iType ){ | ||
22949 | rc = 1; | ||
22950 | } | ||
22951 | } | ||
22952 | if( !rc ){ | ||
22953 | /* Compare nodes */ | ||
22954 | rc = HashmapNodeCmp(pLe, pRe, bStrict); | ||
22955 | } | ||
22956 | if( rc != 0 ){ | ||
22957 | /* Nodes key/value differ */ | ||
22958 | return rc; | ||
22959 | } | ||
22960 | /* Point to the next entry */ | ||
22961 | pLe = pLe->pPrev; /* Reverse link */ | ||
22962 | n--; | ||
22963 | } | ||
22964 | return 0; /* Hashmaps are equals */ | ||
22965 | } | ||
22966 | /* | ||
22967 | * Merge two hashmaps. | ||
22968 | * Note on the merge process | ||
22969 | * According to the JX9 language reference manual. | ||
22970 | * Merges the elements of two arrays together so that the values of one are appended | ||
22971 | * to the end of the previous one. It returns the resulting array (pDest). | ||
22972 | * If the input arrays have the same string keys, then the later value for that key | ||
22973 | * will overwrite the previous one. If, however, the arrays contain numeric keys | ||
22974 | * the later value will not overwrite the original value, but will be appended. | ||
22975 | * Values in the input array with numeric keys will be renumbered with incrementing | ||
22976 | * keys starting from zero in the result array. | ||
22977 | */ | ||
22978 | static sxi32 HashmapMerge(jx9_hashmap *pSrc, jx9_hashmap *pDest) | ||
22979 | { | ||
22980 | jx9_hashmap_node *pEntry; | ||
22981 | jx9_value sKey, *pVal; | ||
22982 | sxi32 rc; | ||
22983 | sxu32 n; | ||
22984 | if( pSrc == pDest ){ | ||
22985 | /* Same map. This can easily happen since hashmaps are passed by reference. | ||
22986 | * Unlike the engine. | ||
22987 | */ | ||
22988 | return SXRET_OK; | ||
22989 | } | ||
22990 | /* Point to the first inserted entry in the source */ | ||
22991 | pEntry = pSrc->pFirst; | ||
22992 | /* Perform the merge */ | ||
22993 | for( n = 0 ; n < pSrc->nEntry ; ++n ){ | ||
22994 | /* Extract the node value */ | ||
22995 | pVal = HashmapExtractNodeValue(pEntry); | ||
22996 | if( pEntry->iType == HASHMAP_BLOB_NODE ){ | ||
22997 | /* Blob key insertion */ | ||
22998 | jx9MemObjInitFromString(pDest->pVm, &sKey, 0); | ||
22999 | jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); | ||
23000 | rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); | ||
23001 | jx9MemObjRelease(&sKey); | ||
23002 | }else{ | ||
23003 | rc = HashmapInsert(&(*pDest), 0/* Automatic index assign */, pVal); | ||
23004 | } | ||
23005 | if( rc != SXRET_OK ){ | ||
23006 | return rc; | ||
23007 | } | ||
23008 | /* Point to the next entry */ | ||
23009 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
23010 | } | ||
23011 | return SXRET_OK; | ||
23012 | } | ||
23013 | /* | ||
23014 | * Duplicate the contents of a hashmap. Store the copy in pDest. | ||
23015 | * Refer to the [array_pad(), array_copy(), ...] implementation for more information. | ||
23016 | */ | ||
23017 | JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest) | ||
23018 | { | ||
23019 | jx9_hashmap_node *pEntry; | ||
23020 | jx9_value sKey, *pVal; | ||
23021 | sxi32 rc; | ||
23022 | sxu32 n; | ||
23023 | if( pSrc == pDest ){ | ||
23024 | /* Same map. This can easily happen since hashmaps are passed by reference. | ||
23025 | * Unlike the engine. | ||
23026 | */ | ||
23027 | return SXRET_OK; | ||
23028 | } | ||
23029 | /* Point to the first inserted entry in the source */ | ||
23030 | pEntry = pSrc->pFirst; | ||
23031 | /* Perform the duplication */ | ||
23032 | for( n = 0 ; n < pSrc->nEntry ; ++n ){ | ||
23033 | /* Extract the node value */ | ||
23034 | pVal = HashmapExtractNodeValue(pEntry); | ||
23035 | if( pEntry->iType == HASHMAP_BLOB_NODE ){ | ||
23036 | /* Blob key insertion */ | ||
23037 | jx9MemObjInitFromString(pDest->pVm, &sKey, 0); | ||
23038 | jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); | ||
23039 | rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); | ||
23040 | jx9MemObjRelease(&sKey); | ||
23041 | }else{ | ||
23042 | /* Int key insertion */ | ||
23043 | rc = HashmapInsertIntKey(&(*pDest), pEntry->xKey.iKey, pVal); | ||
23044 | } | ||
23045 | if( rc != SXRET_OK ){ | ||
23046 | return rc; | ||
23047 | } | ||
23048 | /* Point to the next entry */ | ||
23049 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
23050 | } | ||
23051 | return SXRET_OK; | ||
23052 | } | ||
23053 | /* | ||
23054 | * Perform the union of two hashmaps. | ||
23055 | * This operation is performed only if the user uses the '+' operator | ||
23056 | * with a variable holding an array as follows: | ||
23057 | * <?jx9 | ||
23058 | * $a = array("a" => "apple", "b" => "banana"); | ||
23059 | * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); | ||
23060 | * $c = $a + $b; // Union of $a and $b | ||
23061 | * print "Union of \$a and \$b: \n"; | ||
23062 | * dump($c); | ||
23063 | * $c = $b + $a; // Union of $b and $a | ||
23064 | * print "Union of \$b and \$a: \n"; | ||
23065 | * dump($c); | ||
23066 | * ?> | ||
23067 | * When executed, this script will print the following: | ||
23068 | * Union of $a and $b: | ||
23069 | * array(3) { | ||
23070 | * ["a"]=> | ||
23071 | * string(5) "apple" | ||
23072 | * ["b"]=> | ||
23073 | * string(6) "banana" | ||
23074 | * ["c"]=> | ||
23075 | * string(6) "cherry" | ||
23076 | * } | ||
23077 | * Union of $b and $a: | ||
23078 | * array(3) { | ||
23079 | * ["a"]=> | ||
23080 | * string(4) "pear" | ||
23081 | * ["b"]=> | ||
23082 | * string(10) "strawberry" | ||
23083 | * ["c"]=> | ||
23084 | * string(6) "cherry" | ||
23085 | * } | ||
23086 | * The + operator returns the right-hand array appended to the left-hand array; | ||
23087 | * For keys that exist in both arrays, the elements from the left-hand array will be used | ||
23088 | * and the matching elements from the right-hand array will be ignored. | ||
23089 | */ | ||
23090 | JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight) | ||
23091 | { | ||
23092 | jx9_hashmap_node *pEntry; | ||
23093 | sxi32 rc = SXRET_OK; | ||
23094 | jx9_value *pObj; | ||
23095 | sxu32 n; | ||
23096 | if( pLeft == pRight ){ | ||
23097 | /* Same map. This can easily happen since hashmaps are passed by reference. | ||
23098 | * Unlike the engine. | ||
23099 | */ | ||
23100 | return SXRET_OK; | ||
23101 | } | ||
23102 | /* Perform the union */ | ||
23103 | pEntry = pRight->pFirst; | ||
23104 | for(n = 0 ; n < pRight->nEntry ; ++n ){ | ||
23105 | /* Make sure the given key does not exists in the left array */ | ||
23106 | if( pEntry->iType == HASHMAP_BLOB_NODE ){ | ||
23107 | /* BLOB key */ | ||
23108 | if( SXRET_OK != | ||
23109 | HashmapLookupBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), 0) ){ | ||
23110 | pObj = HashmapExtractNodeValue(pEntry); | ||
23111 | if( pObj ){ | ||
23112 | /* Perform the insertion */ | ||
23113 | rc = HashmapInsertBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), | ||
23114 | SyBlobLength(&pEntry->xKey.sKey),pObj); | ||
23115 | if( rc != SXRET_OK ){ | ||
23116 | return rc; | ||
23117 | } | ||
23118 | } | ||
23119 | } | ||
23120 | }else{ | ||
23121 | /* INT key */ | ||
23122 | if( SXRET_OK != HashmapLookupIntKey(&(*pLeft), pEntry->xKey.iKey, 0) ){ | ||
23123 | pObj = HashmapExtractNodeValue(pEntry); | ||
23124 | if( pObj ){ | ||
23125 | /* Perform the insertion */ | ||
23126 | rc = HashmapInsertIntKey(&(*pLeft), pEntry->xKey.iKey, pObj); | ||
23127 | if( rc != SXRET_OK ){ | ||
23128 | return rc; | ||
23129 | } | ||
23130 | } | ||
23131 | } | ||
23132 | } | ||
23133 | /* Point to the next entry */ | ||
23134 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
23135 | } | ||
23136 | return SXRET_OK; | ||
23137 | } | ||
23138 | /* | ||
23139 | * Allocate a new hashmap. | ||
23140 | * Return a pointer to the freshly allocated hashmap on success.NULL otherwise. | ||
23141 | */ | ||
23142 | JX9_PRIVATE jx9_hashmap * jx9NewHashmap( | ||
23143 | jx9_vm *pVm, /* VM that trigger the hashmap creation */ | ||
23144 | sxu32 (*xIntHash)(sxi64), /* Hash function for int keys.NULL otherwise*/ | ||
23145 | sxu32 (*xBlobHash)(const void *, sxu32) /* Hash function for BLOB keys.NULL otherwise */ | ||
23146 | ) | ||
23147 | { | ||
23148 | jx9_hashmap *pMap; | ||
23149 | /* Allocate a new instance */ | ||
23150 | pMap = (jx9_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_hashmap)); | ||
23151 | if( pMap == 0 ){ | ||
23152 | return 0; | ||
23153 | } | ||
23154 | /* Zero the structure */ | ||
23155 | SyZero(pMap, sizeof(jx9_hashmap)); | ||
23156 | /* Fill in the structure */ | ||
23157 | pMap->pVm = &(*pVm); | ||
23158 | pMap->iRef = 1; | ||
23159 | /* pMap->iFlags = 0; */ | ||
23160 | /* Default hash functions */ | ||
23161 | pMap->xIntHash = xIntHash ? xIntHash : IntHash; | ||
23162 | pMap->xBlobHash = xBlobHash ? xBlobHash : BinHash; | ||
23163 | return pMap; | ||
23164 | } | ||
23165 | /* | ||
23166 | * Install superglobals in the given virtual machine. | ||
23167 | * Note on superglobals. | ||
23168 | * According to the JX9 language reference manual. | ||
23169 | * Superglobals are built-in variables that are always available in all scopes. | ||
23170 | * Description | ||
23171 | * All predefined variables in JX9 are "superglobals", which means they | ||
23172 | * are available in all scopes throughout a script. | ||
23173 | * These variables are: | ||
23174 | * $_SERVER | ||
23175 | * $_GET | ||
23176 | * $_POST | ||
23177 | * $_FILES | ||
23178 | * $_REQUEST | ||
23179 | * $_ENV | ||
23180 | */ | ||
23181 | JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm) | ||
23182 | { | ||
23183 | static const char * azSuper[] = { | ||
23184 | "_SERVER", /* $_SERVER */ | ||
23185 | "_GET", /* $_GET */ | ||
23186 | "_POST", /* $_POST */ | ||
23187 | "_FILES", /* $_FILES */ | ||
23188 | "_REQUEST", /* $_REQUEST */ | ||
23189 | "_COOKIE", /* $_COOKIE */ | ||
23190 | "_ENV", /* $_ENV */ | ||
23191 | "_HEADER", /* $_HEADER */ | ||
23192 | "argv" /* $argv */ | ||
23193 | }; | ||
23194 | SyString *pFile; | ||
23195 | sxi32 rc; | ||
23196 | sxu32 n; | ||
23197 | /* Install globals variable now */ | ||
23198 | for( n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++ ){ | ||
23199 | jx9_value *pSuper; | ||
23200 | /* Request an empty array */ | ||
23201 | pSuper = jx9_new_array(&(*pVm)); | ||
23202 | if( pSuper == 0 ){ | ||
23203 | return SXERR_MEM; | ||
23204 | } | ||
23205 | /* Install */ | ||
23206 | rc = jx9_vm_config(&(*pVm),JX9_VM_CONFIG_CREATE_VAR, azSuper[n]/* Super-global name*/, pSuper/* Super-global value */); | ||
23207 | if( rc != SXRET_OK ){ | ||
23208 | return rc; | ||
23209 | } | ||
23210 | /* Release the value now it have been installed */ | ||
23211 | jx9_release_value(&(*pVm), pSuper); | ||
23212 | } | ||
23213 | /* Set some $_SERVER entries */ | ||
23214 | pFile = (SyString *)SySetPeek(&pVm->aFiles); | ||
23215 | /* | ||
23216 | * 'SCRIPT_FILENAME' | ||
23217 | * The absolute pathname of the currently executing script. | ||
23218 | */ | ||
23219 | jx9_vm_config(pVm, JX9_VM_CONFIG_SERVER_ATTR, | ||
23220 | "SCRIPT_FILENAME", | ||
23221 | pFile ? pFile->zString : ":Memory:", | ||
23222 | pFile ? pFile->nByte : sizeof(":Memory:") - 1 | ||
23223 | ); | ||
23224 | /* All done, all global variables are installed now */ | ||
23225 | return SXRET_OK; | ||
23226 | } | ||
23227 | /* | ||
23228 | * Release a hashmap. | ||
23229 | */ | ||
23230 | JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS) | ||
23231 | { | ||
23232 | jx9_hashmap_node *pEntry, *pNext; | ||
23233 | jx9_vm *pVm = pMap->pVm; | ||
23234 | sxu32 n; | ||
23235 | /* Start the release process */ | ||
23236 | n = 0; | ||
23237 | pEntry = pMap->pFirst; | ||
23238 | for(;;){ | ||
23239 | if( n >= pMap->nEntry ){ | ||
23240 | break; | ||
23241 | } | ||
23242 | pNext = pEntry->pPrev; /* Reverse link */ | ||
23243 | /* Restore the jx9_value to the free list */ | ||
23244 | jx9VmUnsetMemObj(pVm, pEntry->nValIdx); | ||
23245 | /* Release the node */ | ||
23246 | if( pEntry->iType == HASHMAP_BLOB_NODE ){ | ||
23247 | SyBlobRelease(&pEntry->xKey.sKey); | ||
23248 | } | ||
23249 | SyMemBackendPoolFree(&pVm->sAllocator, pEntry); | ||
23250 | /* Point to the next entry */ | ||
23251 | pEntry = pNext; | ||
23252 | n++; | ||
23253 | } | ||
23254 | if( pMap->nEntry > 0 ){ | ||
23255 | /* Release the hash bucket */ | ||
23256 | SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); | ||
23257 | } | ||
23258 | if( FreeDS ){ | ||
23259 | /* Free the whole instance */ | ||
23260 | SyMemBackendPoolFree(&pVm->sAllocator, pMap); | ||
23261 | }else{ | ||
23262 | /* Keep the instance but reset it's fields */ | ||
23263 | pMap->apBucket = 0; | ||
23264 | pMap->iNextIdx = 0; | ||
23265 | pMap->nEntry = pMap->nSize = 0; | ||
23266 | pMap->pFirst = pMap->pLast = pMap->pCur = 0; | ||
23267 | } | ||
23268 | return SXRET_OK; | ||
23269 | } | ||
23270 | /* | ||
23271 | * Decrement the reference count of a given hashmap. | ||
23272 | * If the count reaches zero which mean no more variables | ||
23273 | * are pointing to this hashmap, then release the whole instance. | ||
23274 | */ | ||
23275 | JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap) | ||
23276 | { | ||
23277 | pMap->iRef--; | ||
23278 | if( pMap->iRef < 1 ){ | ||
23279 | jx9HashmapRelease(pMap, TRUE); | ||
23280 | } | ||
23281 | } | ||
23282 | /* | ||
23283 | * Check if a given key exists in the given hashmap. | ||
23284 | * Write a pointer to the target node on success. | ||
23285 | * Otherwise SXERR_NOTFOUND is returned on failure. | ||
23286 | */ | ||
23287 | JX9_PRIVATE sxi32 jx9HashmapLookup( | ||
23288 | jx9_hashmap *pMap, /* Target hashmap */ | ||
23289 | jx9_value *pKey, /* Lookup key */ | ||
23290 | jx9_hashmap_node **ppNode /* OUT: Target node on success */ | ||
23291 | ) | ||
23292 | { | ||
23293 | sxi32 rc; | ||
23294 | if( pMap->nEntry < 1 ){ | ||
23295 | /* TICKET 1433-25: Don't bother hashing, the hashmap is empty anyway. | ||
23296 | */ | ||
23297 | return SXERR_NOTFOUND; | ||
23298 | } | ||
23299 | rc = HashmapLookup(&(*pMap), &(*pKey), ppNode); | ||
23300 | return rc; | ||
23301 | } | ||
23302 | /* | ||
23303 | * Insert a given key and it's associated value (if any) in the given | ||
23304 | * hashmap. | ||
23305 | * If a node with the given key already exists in the database | ||
23306 | * then this function overwrite the old value. | ||
23307 | */ | ||
23308 | JX9_PRIVATE sxi32 jx9HashmapInsert( | ||
23309 | jx9_hashmap *pMap, /* Target hashmap */ | ||
23310 | jx9_value *pKey, /* Lookup key */ | ||
23311 | jx9_value *pVal /* Node value.NULL otherwise */ | ||
23312 | ) | ||
23313 | { | ||
23314 | sxi32 rc; | ||
23315 | rc = HashmapInsert(&(*pMap), &(*pKey), &(*pVal)); | ||
23316 | return rc; | ||
23317 | } | ||
23318 | /* | ||
23319 | * Reset the node cursor of a given hashmap. | ||
23320 | */ | ||
23321 | JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap) | ||
23322 | { | ||
23323 | /* Reset the loop cursor */ | ||
23324 | pMap->pCur = pMap->pFirst; | ||
23325 | } | ||
23326 | /* | ||
23327 | * Return a pointer to the node currently pointed by the node cursor. | ||
23328 | * If the cursor reaches the end of the list, then this function | ||
23329 | * return NULL. | ||
23330 | * Note that the node cursor is automatically advanced by this function. | ||
23331 | */ | ||
23332 | JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap) | ||
23333 | { | ||
23334 | jx9_hashmap_node *pCur = pMap->pCur; | ||
23335 | if( pCur == 0 ){ | ||
23336 | /* End of the list, return null */ | ||
23337 | return 0; | ||
23338 | } | ||
23339 | /* Advance the node cursor */ | ||
23340 | pMap->pCur = pCur->pPrev; /* Reverse link */ | ||
23341 | return pCur; | ||
23342 | } | ||
23343 | /* | ||
23344 | * Extract a node value. | ||
23345 | */ | ||
23346 | JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode) | ||
23347 | { | ||
23348 | jx9_value *pValue; | ||
23349 | pValue = HashmapExtractNodeValue(pNode); | ||
23350 | return pValue; | ||
23351 | } | ||
23352 | /* | ||
23353 | * Extract a node value (Second). | ||
23354 | */ | ||
23355 | JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore) | ||
23356 | { | ||
23357 | jx9_value *pEntry = HashmapExtractNodeValue(pNode); | ||
23358 | if( pEntry ){ | ||
23359 | if( bStore ){ | ||
23360 | jx9MemObjStore(pEntry, pValue); | ||
23361 | }else{ | ||
23362 | jx9MemObjLoad(pEntry, pValue); | ||
23363 | } | ||
23364 | }else{ | ||
23365 | jx9MemObjRelease(pValue); | ||
23366 | } | ||
23367 | } | ||
23368 | /* | ||
23369 | * Extract a node key. | ||
23370 | */ | ||
23371 | JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode,jx9_value *pKey) | ||
23372 | { | ||
23373 | /* Fill with the current key */ | ||
23374 | if( pNode->iType == HASHMAP_INT_NODE ){ | ||
23375 | if( SyBlobLength(&pKey->sBlob) > 0 ){ | ||
23376 | SyBlobRelease(&pKey->sBlob); | ||
23377 | } | ||
23378 | pKey->x.iVal = pNode->xKey.iKey; | ||
23379 | MemObjSetType(pKey, MEMOBJ_INT); | ||
23380 | }else{ | ||
23381 | SyBlobReset(&pKey->sBlob); | ||
23382 | SyBlobAppend(&pKey->sBlob, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey)); | ||
23383 | MemObjSetType(pKey, MEMOBJ_STRING); | ||
23384 | } | ||
23385 | } | ||
23386 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
23387 | /* | ||
23388 | * Store the address of nodes value in the given container. | ||
23389 | * Refer to the [vfprintf(), vprintf(), vsprintf()] implementations | ||
23390 | * defined in 'builtin.c' for more information. | ||
23391 | */ | ||
23392 | JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut) | ||
23393 | { | ||
23394 | jx9_hashmap_node *pEntry = pMap->pFirst; | ||
23395 | jx9_value *pValue; | ||
23396 | sxu32 n; | ||
23397 | /* Initialize the container */ | ||
23398 | SySetInit(pOut, &pMap->pVm->sAllocator, sizeof(jx9_value *)); | ||
23399 | for(n = 0 ; n < pMap->nEntry ; n++ ){ | ||
23400 | /* Extract node value */ | ||
23401 | pValue = HashmapExtractNodeValue(pEntry); | ||
23402 | if( pValue ){ | ||
23403 | SySetPut(pOut, (const void *)&pValue); | ||
23404 | } | ||
23405 | /* Point to the next entry */ | ||
23406 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
23407 | } | ||
23408 | /* Total inserted entries */ | ||
23409 | return (int)SySetUsed(pOut); | ||
23410 | } | ||
23411 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
23412 | /* | ||
23413 | * Merge sort. | ||
23414 | * The merge sort implementation is based on the one found in the SQLite3 source tree. | ||
23415 | * Status: Public domain | ||
23416 | */ | ||
23417 | /* Node comparison callback signature */ | ||
23418 | typedef sxi32 (*ProcNodeCmp)(jx9_hashmap_node *, jx9_hashmap_node *, void *); | ||
23419 | /* | ||
23420 | ** Inputs: | ||
23421 | ** a: A sorted, null-terminated linked list. (May be null). | ||
23422 | ** b: A sorted, null-terminated linked list. (May be null). | ||
23423 | ** cmp: A pointer to the comparison function. | ||
23424 | ** | ||
23425 | ** Return Value: | ||
23426 | ** A pointer to the head of a sorted list containing the elements | ||
23427 | ** of both a and b. | ||
23428 | ** | ||
23429 | ** Side effects: | ||
23430 | ** The "next", "prev" pointers for elements in the lists a and b are | ||
23431 | ** changed. | ||
23432 | */ | ||
23433 | static jx9_hashmap_node * HashmapNodeMerge(jx9_hashmap_node *pA, jx9_hashmap_node *pB, ProcNodeCmp xCmp, void *pCmpData) | ||
23434 | { | ||
23435 | jx9_hashmap_node result, *pTail; | ||
23436 | /* Prevent compiler warning */ | ||
23437 | result.pNext = result.pPrev = 0; | ||
23438 | pTail = &result; | ||
23439 | while( pA && pB ){ | ||
23440 | if( xCmp(pA, pB, pCmpData) < 0 ){ | ||
23441 | pTail->pPrev = pA; | ||
23442 | pA->pNext = pTail; | ||
23443 | pTail = pA; | ||
23444 | pA = pA->pPrev; | ||
23445 | }else{ | ||
23446 | pTail->pPrev = pB; | ||
23447 | pB->pNext = pTail; | ||
23448 | pTail = pB; | ||
23449 | pB = pB->pPrev; | ||
23450 | } | ||
23451 | } | ||
23452 | if( pA ){ | ||
23453 | pTail->pPrev = pA; | ||
23454 | pA->pNext = pTail; | ||
23455 | }else if( pB ){ | ||
23456 | pTail->pPrev = pB; | ||
23457 | pB->pNext = pTail; | ||
23458 | }else{ | ||
23459 | pTail->pPrev = pTail->pNext = 0; | ||
23460 | } | ||
23461 | return result.pPrev; | ||
23462 | } | ||
23463 | /* | ||
23464 | ** Inputs: | ||
23465 | ** Map: Input hashmap | ||
23466 | ** cmp: A comparison function. | ||
23467 | ** | ||
23468 | ** Return Value: | ||
23469 | ** Sorted hashmap. | ||
23470 | ** | ||
23471 | ** Side effects: | ||
23472 | ** The "next" pointers for elements in list are changed. | ||
23473 | */ | ||
23474 | #define N_SORT_BUCKET 32 | ||
23475 | static sxi32 HashmapMergeSort(jx9_hashmap *pMap, ProcNodeCmp xCmp, void *pCmpData) | ||
23476 | { | ||
23477 | jx9_hashmap_node *a[N_SORT_BUCKET], *p, *pIn; | ||
23478 | sxu32 i; | ||
23479 | SyZero(a, sizeof(a)); | ||
23480 | /* Point to the first inserted entry */ | ||
23481 | pIn = pMap->pFirst; | ||
23482 | while( pIn ){ | ||
23483 | p = pIn; | ||
23484 | pIn = p->pPrev; | ||
23485 | p->pPrev = 0; | ||
23486 | for(i=0; i<N_SORT_BUCKET-1; i++){ | ||
23487 | if( a[i]==0 ){ | ||
23488 | a[i] = p; | ||
23489 | break; | ||
23490 | }else{ | ||
23491 | p = HashmapNodeMerge(a[i], p, xCmp, pCmpData); | ||
23492 | a[i] = 0; | ||
23493 | } | ||
23494 | } | ||
23495 | if( i==N_SORT_BUCKET-1 ){ | ||
23496 | /* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list. | ||
23497 | * But that is impossible. | ||
23498 | */ | ||
23499 | a[i] = HashmapNodeMerge(a[i], p, xCmp, pCmpData); | ||
23500 | } | ||
23501 | } | ||
23502 | p = a[0]; | ||
23503 | for(i=1; i<N_SORT_BUCKET; i++){ | ||
23504 | p = HashmapNodeMerge(p, a[i], xCmp, pCmpData); | ||
23505 | } | ||
23506 | p->pNext = 0; | ||
23507 | /* Reflect the change */ | ||
23508 | pMap->pFirst = p; | ||
23509 | /* Reset the loop cursor */ | ||
23510 | pMap->pCur = pMap->pFirst; | ||
23511 | return SXRET_OK; | ||
23512 | } | ||
23513 | /* | ||
23514 | * Node comparison callback. | ||
23515 | * used-by: [sort(), asort(), ...] | ||
23516 | */ | ||
23517 | static sxi32 HashmapCmpCallback1(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) | ||
23518 | { | ||
23519 | jx9_value sA, sB; | ||
23520 | sxi32 iFlags; | ||
23521 | int rc; | ||
23522 | if( pCmpData == 0 ){ | ||
23523 | /* Perform a standard comparison */ | ||
23524 | rc = HashmapNodeCmp(pA, pB, FALSE); | ||
23525 | return rc; | ||
23526 | } | ||
23527 | iFlags = SX_PTR_TO_INT(pCmpData); | ||
23528 | /* Duplicate node values */ | ||
23529 | jx9MemObjInit(pA->pMap->pVm, &sA); | ||
23530 | jx9MemObjInit(pA->pMap->pVm, &sB); | ||
23531 | jx9HashmapExtractNodeValue(pA, &sA, FALSE); | ||
23532 | jx9HashmapExtractNodeValue(pB, &sB, FALSE); | ||
23533 | if( iFlags == 5 ){ | ||
23534 | /* String cast */ | ||
23535 | if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ | ||
23536 | jx9MemObjToString(&sA); | ||
23537 | } | ||
23538 | if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ | ||
23539 | jx9MemObjToString(&sB); | ||
23540 | } | ||
23541 | }else{ | ||
23542 | /* Numeric cast */ | ||
23543 | jx9MemObjToNumeric(&sA); | ||
23544 | jx9MemObjToNumeric(&sB); | ||
23545 | } | ||
23546 | /* Perform the comparison */ | ||
23547 | rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); | ||
23548 | jx9MemObjRelease(&sA); | ||
23549 | jx9MemObjRelease(&sB); | ||
23550 | return rc; | ||
23551 | } | ||
23552 | /* | ||
23553 | * Node comparison callback. | ||
23554 | * Used by: [rsort(), arsort()]; | ||
23555 | */ | ||
23556 | static sxi32 HashmapCmpCallback3(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) | ||
23557 | { | ||
23558 | jx9_value sA, sB; | ||
23559 | sxi32 iFlags; | ||
23560 | int rc; | ||
23561 | if( pCmpData == 0 ){ | ||
23562 | /* Perform a standard comparison */ | ||
23563 | rc = HashmapNodeCmp(pA, pB, FALSE); | ||
23564 | return -rc; | ||
23565 | } | ||
23566 | iFlags = SX_PTR_TO_INT(pCmpData); | ||
23567 | /* Duplicate node values */ | ||
23568 | jx9MemObjInit(pA->pMap->pVm, &sA); | ||
23569 | jx9MemObjInit(pA->pMap->pVm, &sB); | ||
23570 | jx9HashmapExtractNodeValue(pA, &sA, FALSE); | ||
23571 | jx9HashmapExtractNodeValue(pB, &sB, FALSE); | ||
23572 | if( iFlags == 5 ){ | ||
23573 | /* String cast */ | ||
23574 | if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ | ||
23575 | jx9MemObjToString(&sA); | ||
23576 | } | ||
23577 | if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ | ||
23578 | jx9MemObjToString(&sB); | ||
23579 | } | ||
23580 | }else{ | ||
23581 | /* Numeric cast */ | ||
23582 | jx9MemObjToNumeric(&sA); | ||
23583 | jx9MemObjToNumeric(&sB); | ||
23584 | } | ||
23585 | /* Perform the comparison */ | ||
23586 | rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); | ||
23587 | jx9MemObjRelease(&sA); | ||
23588 | jx9MemObjRelease(&sB); | ||
23589 | return -rc; | ||
23590 | } | ||
23591 | /* | ||
23592 | * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. | ||
23593 | * used-by: [usort(), uasort()] | ||
23594 | */ | ||
23595 | static sxi32 HashmapCmpCallback4(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) | ||
23596 | { | ||
23597 | jx9_value sResult, *pCallback; | ||
23598 | jx9_value *pV1, *pV2; | ||
23599 | jx9_value *apArg[2]; /* Callback arguments */ | ||
23600 | sxi32 rc; | ||
23601 | /* Point to the desired callback */ | ||
23602 | pCallback = (jx9_value *)pCmpData; | ||
23603 | /* initialize the result value */ | ||
23604 | jx9MemObjInit(pA->pMap->pVm, &sResult); | ||
23605 | /* Extract nodes values */ | ||
23606 | pV1 = HashmapExtractNodeValue(pA); | ||
23607 | pV2 = HashmapExtractNodeValue(pB); | ||
23608 | apArg[0] = pV1; | ||
23609 | apArg[1] = pV2; | ||
23610 | /* Invoke the callback */ | ||
23611 | rc = jx9VmCallUserFunction(pA->pMap->pVm, pCallback, 2, apArg, &sResult); | ||
23612 | if( rc != SXRET_OK ){ | ||
23613 | /* An error occured while calling user defined function [i.e: not defined] */ | ||
23614 | rc = -1; /* Set a dummy result */ | ||
23615 | }else{ | ||
23616 | /* Extract callback result */ | ||
23617 | if((sResult.iFlags & MEMOBJ_INT) == 0 ){ | ||
23618 | /* Perform an int cast */ | ||
23619 | jx9MemObjToInteger(&sResult); | ||
23620 | } | ||
23621 | rc = (sxi32)sResult.x.iVal; | ||
23622 | } | ||
23623 | jx9MemObjRelease(&sResult); | ||
23624 | /* Callback result */ | ||
23625 | return rc; | ||
23626 | } | ||
23627 | /* | ||
23628 | * Rehash all nodes keys after a merge-sort have been applied. | ||
23629 | * Used by [sort(), usort() and rsort()]. | ||
23630 | */ | ||
23631 | static void HashmapSortRehash(jx9_hashmap *pMap) | ||
23632 | { | ||
23633 | jx9_hashmap_node *p, *pLast; | ||
23634 | sxu32 i; | ||
23635 | /* Rehash all entries */ | ||
23636 | pLast = p = pMap->pFirst; | ||
23637 | pMap->iNextIdx = 0; /* Reset the automatic index */ | ||
23638 | i = 0; | ||
23639 | for( ;; ){ | ||
23640 | if( i >= pMap->nEntry ){ | ||
23641 | pMap->pLast = pLast; /* Fix the last link broken by the merge-sort */ | ||
23642 | break; | ||
23643 | } | ||
23644 | if( p->iType == HASHMAP_BLOB_NODE ){ | ||
23645 | /* Do not maintain index association as requested by the JX9 specification */ | ||
23646 | SyBlobRelease(&p->xKey.sKey); | ||
23647 | /* Change key type */ | ||
23648 | p->iType = HASHMAP_INT_NODE; | ||
23649 | } | ||
23650 | HashmapRehashIntNode(p); | ||
23651 | /* Point to the next entry */ | ||
23652 | i++; | ||
23653 | pLast = p; | ||
23654 | p = p->pPrev; /* Reverse link */ | ||
23655 | } | ||
23656 | } | ||
23657 | /* | ||
23658 | * Array functions implementation. | ||
23659 | * Authors: | ||
23660 | * Symisc Systems, devel@symisc.net. | ||
23661 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
23662 | * Status: | ||
23663 | * Stable. | ||
23664 | */ | ||
23665 | /* | ||
23666 | * bool sort(array &$array[, int $sort_flags = SORT_REGULAR ] ) | ||
23667 | * Sort an array. | ||
23668 | * Parameters | ||
23669 | * $array | ||
23670 | * The input array. | ||
23671 | * $sort_flags | ||
23672 | * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: | ||
23673 | * Sorting type flags: | ||
23674 | * SORT_REGULAR - compare items normally (don't change types) | ||
23675 | * SORT_NUMERIC - compare items numerically | ||
23676 | * SORT_STRING - compare items as strings | ||
23677 | * Return | ||
23678 | * TRUE on success or FALSE on failure. | ||
23679 | * | ||
23680 | */ | ||
23681 | static int jx9_hashmap_sort(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23682 | { | ||
23683 | jx9_hashmap *pMap; | ||
23684 | /* Make sure we are dealing with a valid hashmap */ | ||
23685 | if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ | ||
23686 | /* Missing/Invalid arguments, return FALSE */ | ||
23687 | jx9_result_bool(pCtx, 0); | ||
23688 | return JX9_OK; | ||
23689 | } | ||
23690 | /* Point to the internal representation of the input hashmap */ | ||
23691 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
23692 | if( pMap->nEntry > 1 ){ | ||
23693 | sxi32 iCmpFlags = 0; | ||
23694 | if( nArg > 1 ){ | ||
23695 | /* Extract comparison flags */ | ||
23696 | iCmpFlags = jx9_value_to_int(apArg[1]); | ||
23697 | if( iCmpFlags == 3 /* SORT_REGULAR */ ){ | ||
23698 | iCmpFlags = 0; /* Standard comparison */ | ||
23699 | } | ||
23700 | } | ||
23701 | /* Do the merge sort */ | ||
23702 | HashmapMergeSort(pMap, HashmapCmpCallback1, SX_INT_TO_PTR(iCmpFlags)); | ||
23703 | /* Rehash [Do not maintain index association as requested by the JX9 specification] */ | ||
23704 | HashmapSortRehash(pMap); | ||
23705 | } | ||
23706 | /* All done, return TRUE */ | ||
23707 | jx9_result_bool(pCtx, 1); | ||
23708 | return JX9_OK; | ||
23709 | } | ||
23710 | /* | ||
23711 | * bool rsort(array &$array[, int $sort_flags = SORT_REGULAR ] ) | ||
23712 | * Sort an array in reverse order. | ||
23713 | * Parameters | ||
23714 | * $array | ||
23715 | * The input array. | ||
23716 | * $sort_flags | ||
23717 | * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: | ||
23718 | * Sorting type flags: | ||
23719 | * SORT_REGULAR - compare items normally (don't change types) | ||
23720 | * SORT_NUMERIC - compare items numerically | ||
23721 | * SORT_STRING - compare items as strings | ||
23722 | * Return | ||
23723 | * TRUE on success or FALSE on failure. | ||
23724 | */ | ||
23725 | static int jx9_hashmap_rsort(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23726 | { | ||
23727 | jx9_hashmap *pMap; | ||
23728 | /* Make sure we are dealing with a valid hashmap */ | ||
23729 | if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ | ||
23730 | /* Missing/Invalid arguments, return FALSE */ | ||
23731 | jx9_result_bool(pCtx, 0); | ||
23732 | return JX9_OK; | ||
23733 | } | ||
23734 | /* Point to the internal representation of the input hashmap */ | ||
23735 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
23736 | if( pMap->nEntry > 1 ){ | ||
23737 | sxi32 iCmpFlags = 0; | ||
23738 | if( nArg > 1 ){ | ||
23739 | /* Extract comparison flags */ | ||
23740 | iCmpFlags = jx9_value_to_int(apArg[1]); | ||
23741 | if( iCmpFlags == 3 /* SORT_REGULAR */ ){ | ||
23742 | iCmpFlags = 0; /* Standard comparison */ | ||
23743 | } | ||
23744 | } | ||
23745 | /* Do the merge sort */ | ||
23746 | HashmapMergeSort(pMap, HashmapCmpCallback3, SX_INT_TO_PTR(iCmpFlags)); | ||
23747 | /* Rehash [Do not maintain index association as requested by the JX9 specification] */ | ||
23748 | HashmapSortRehash(pMap); | ||
23749 | } | ||
23750 | /* All done, return TRUE */ | ||
23751 | jx9_result_bool(pCtx, 1); | ||
23752 | return JX9_OK; | ||
23753 | } | ||
23754 | /* | ||
23755 | * bool usort(array &$array, callable $cmp_function) | ||
23756 | * Sort an array by values using a user-defined comparison function. | ||
23757 | * Parameters | ||
23758 | * $array | ||
23759 | * The input array. | ||
23760 | * $cmp_function | ||
23761 | * The comparison function must return an integer less than, equal to, or greater | ||
23762 | * than zero if the first argument is considered to be respectively less than, equal | ||
23763 | * to, or greater than the second. | ||
23764 | * int callback ( mixed $a, mixed $b ) | ||
23765 | * Return | ||
23766 | * TRUE on success or FALSE on failure. | ||
23767 | */ | ||
23768 | static int jx9_hashmap_usort(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23769 | { | ||
23770 | jx9_hashmap *pMap; | ||
23771 | /* Make sure we are dealing with a valid hashmap */ | ||
23772 | if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ | ||
23773 | /* Missing/Invalid arguments, return FALSE */ | ||
23774 | jx9_result_bool(pCtx, 0); | ||
23775 | return JX9_OK; | ||
23776 | } | ||
23777 | /* Point to the internal representation of the input hashmap */ | ||
23778 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
23779 | if( pMap->nEntry > 1 ){ | ||
23780 | jx9_value *pCallback = 0; | ||
23781 | ProcNodeCmp xCmp; | ||
23782 | xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ | ||
23783 | if( nArg > 1 && jx9_value_is_callable(apArg[1]) ){ | ||
23784 | /* Point to the desired callback */ | ||
23785 | pCallback = apArg[1]; | ||
23786 | }else{ | ||
23787 | /* Use the default comparison function */ | ||
23788 | xCmp = HashmapCmpCallback1; | ||
23789 | } | ||
23790 | /* Do the merge sort */ | ||
23791 | HashmapMergeSort(pMap, xCmp, pCallback); | ||
23792 | /* Rehash [Do not maintain index association as requested by the JX9 specification] */ | ||
23793 | HashmapSortRehash(pMap); | ||
23794 | } | ||
23795 | /* All done, return TRUE */ | ||
23796 | jx9_result_bool(pCtx, 1); | ||
23797 | return JX9_OK; | ||
23798 | } | ||
23799 | /* | ||
23800 | * int count(array $var [, int $mode = COUNT_NORMAL ]) | ||
23801 | * Count all elements in an array, or something in an object. | ||
23802 | * Parameters | ||
23803 | * $var | ||
23804 | * The array or the object. | ||
23805 | * $mode | ||
23806 | * If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count() | ||
23807 | * will recursively count the array. This is particularly useful for counting | ||
23808 | * all the elements of a multidimensional array. count() does not detect infinite | ||
23809 | * recursion. | ||
23810 | * Return | ||
23811 | * Returns the number of elements in the array. | ||
23812 | */ | ||
23813 | static int jx9_hashmap_count(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23814 | { | ||
23815 | int bRecursive = FALSE; | ||
23816 | sxi64 iCount; | ||
23817 | if( nArg < 1 ){ | ||
23818 | /* Missing arguments, return 0 */ | ||
23819 | jx9_result_int(pCtx, 0); | ||
23820 | return JX9_OK; | ||
23821 | } | ||
23822 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
23823 | /* TICKET 1433-19: Handle objects */ | ||
23824 | int res = !jx9_value_is_null(apArg[0]); | ||
23825 | jx9_result_int(pCtx, res); | ||
23826 | return JX9_OK; | ||
23827 | } | ||
23828 | if( nArg > 1 ){ | ||
23829 | /* Recursive count? */ | ||
23830 | bRecursive = jx9_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */; | ||
23831 | } | ||
23832 | /* Count */ | ||
23833 | iCount = HashmapCount((jx9_hashmap *)apArg[0]->x.pOther, bRecursive, 0); | ||
23834 | jx9_result_int64(pCtx, iCount); | ||
23835 | return JX9_OK; | ||
23836 | } | ||
23837 | /* | ||
23838 | * bool array_key_exists(value $key, array $search) | ||
23839 | * Checks if the given key or index exists in the array. | ||
23840 | * Parameters | ||
23841 | * $key | ||
23842 | * Value to check. | ||
23843 | * $search | ||
23844 | * An array with keys to check. | ||
23845 | * Return | ||
23846 | * TRUE on success or FALSE on failure. | ||
23847 | */ | ||
23848 | static int jx9_hashmap_key_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23849 | { | ||
23850 | sxi32 rc; | ||
23851 | if( nArg < 2 ){ | ||
23852 | /* Missing arguments, return FALSE */ | ||
23853 | jx9_result_bool(pCtx, 0); | ||
23854 | return JX9_OK; | ||
23855 | } | ||
23856 | /* Make sure we are dealing with a valid hashmap */ | ||
23857 | if( !jx9_value_is_json_array(apArg[1]) ){ | ||
23858 | /* Invalid argument, return FALSE */ | ||
23859 | jx9_result_bool(pCtx, 0); | ||
23860 | return JX9_OK; | ||
23861 | } | ||
23862 | /* Perform the lookup */ | ||
23863 | rc = jx9HashmapLookup((jx9_hashmap *)apArg[1]->x.pOther, apArg[0], 0); | ||
23864 | /* lookup result */ | ||
23865 | jx9_result_bool(pCtx, rc == SXRET_OK ? 1 : 0); | ||
23866 | return JX9_OK; | ||
23867 | } | ||
23868 | /* | ||
23869 | * value array_pop(array $array) | ||
23870 | * POP the last inserted element from the array. | ||
23871 | * Parameter | ||
23872 | * The array to get the value from. | ||
23873 | * Return | ||
23874 | * Poped value or NULL on failure. | ||
23875 | */ | ||
23876 | static int jx9_hashmap_pop(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23877 | { | ||
23878 | jx9_hashmap *pMap; | ||
23879 | if( nArg < 1 ){ | ||
23880 | /* Missing arguments, return null */ | ||
23881 | jx9_result_null(pCtx); | ||
23882 | return JX9_OK; | ||
23883 | } | ||
23884 | /* Make sure we are dealing with a valid hashmap */ | ||
23885 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
23886 | /* Invalid argument, return null */ | ||
23887 | jx9_result_null(pCtx); | ||
23888 | return JX9_OK; | ||
23889 | } | ||
23890 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
23891 | if( pMap->nEntry < 1 ){ | ||
23892 | /* Noting to pop, return NULL */ | ||
23893 | jx9_result_null(pCtx); | ||
23894 | }else{ | ||
23895 | jx9_hashmap_node *pLast = pMap->pLast; | ||
23896 | jx9_value *pObj; | ||
23897 | pObj = HashmapExtractNodeValue(pLast); | ||
23898 | if( pObj ){ | ||
23899 | /* Node value */ | ||
23900 | jx9_result_value(pCtx, pObj); | ||
23901 | /* Unlink the node */ | ||
23902 | jx9HashmapUnlinkNode(pLast); | ||
23903 | }else{ | ||
23904 | jx9_result_null(pCtx); | ||
23905 | } | ||
23906 | /* Reset the cursor */ | ||
23907 | pMap->pCur = pMap->pFirst; | ||
23908 | } | ||
23909 | return JX9_OK; | ||
23910 | } | ||
23911 | /* | ||
23912 | * int array_push($array, $var, ...) | ||
23913 | * Push one or more elements onto the end of array. (Stack insertion) | ||
23914 | * Parameters | ||
23915 | * array | ||
23916 | * The input array. | ||
23917 | * var | ||
23918 | * On or more value to push. | ||
23919 | * Return | ||
23920 | * New array count (including old items). | ||
23921 | */ | ||
23922 | static int jx9_hashmap_push(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23923 | { | ||
23924 | jx9_hashmap *pMap; | ||
23925 | sxi32 rc; | ||
23926 | int i; | ||
23927 | if( nArg < 1 ){ | ||
23928 | /* Missing arguments, return 0 */ | ||
23929 | jx9_result_int(pCtx, 0); | ||
23930 | return JX9_OK; | ||
23931 | } | ||
23932 | /* Make sure we are dealing with a valid hashmap */ | ||
23933 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
23934 | /* Invalid argument, return 0 */ | ||
23935 | jx9_result_int(pCtx, 0); | ||
23936 | return JX9_OK; | ||
23937 | } | ||
23938 | /* Point to the internal representation of the input hashmap */ | ||
23939 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
23940 | /* Start pushing given values */ | ||
23941 | for( i = 1 ; i < nArg ; ++i ){ | ||
23942 | rc = jx9HashmapInsert(pMap, 0, apArg[i]); | ||
23943 | if( rc != SXRET_OK ){ | ||
23944 | break; | ||
23945 | } | ||
23946 | } | ||
23947 | /* Return the new count */ | ||
23948 | jx9_result_int64(pCtx, (sxi64)pMap->nEntry); | ||
23949 | return JX9_OK; | ||
23950 | } | ||
23951 | /* | ||
23952 | * value array_shift(array $array) | ||
23953 | * Shift an element off the beginning of array. | ||
23954 | * Parameter | ||
23955 | * The array to get the value from. | ||
23956 | * Return | ||
23957 | * Shifted value or NULL on failure. | ||
23958 | */ | ||
23959 | static int jx9_hashmap_shift(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
23960 | { | ||
23961 | jx9_hashmap *pMap; | ||
23962 | if( nArg < 1 ){ | ||
23963 | /* Missing arguments, return null */ | ||
23964 | jx9_result_null(pCtx); | ||
23965 | return JX9_OK; | ||
23966 | } | ||
23967 | /* Make sure we are dealing with a valid hashmap */ | ||
23968 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
23969 | /* Invalid argument, return null */ | ||
23970 | jx9_result_null(pCtx); | ||
23971 | return JX9_OK; | ||
23972 | } | ||
23973 | /* Point to the internal representation of the hashmap */ | ||
23974 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
23975 | if( pMap->nEntry < 1 ){ | ||
23976 | /* Empty hashmap, return NULL */ | ||
23977 | jx9_result_null(pCtx); | ||
23978 | }else{ | ||
23979 | jx9_hashmap_node *pEntry = pMap->pFirst; | ||
23980 | jx9_value *pObj; | ||
23981 | sxu32 n; | ||
23982 | pObj = HashmapExtractNodeValue(pEntry); | ||
23983 | if( pObj ){ | ||
23984 | /* Node value */ | ||
23985 | jx9_result_value(pCtx, pObj); | ||
23986 | /* Unlink the first node */ | ||
23987 | jx9HashmapUnlinkNode(pEntry); | ||
23988 | }else{ | ||
23989 | jx9_result_null(pCtx); | ||
23990 | } | ||
23991 | /* Rehash all int keys */ | ||
23992 | n = pMap->nEntry; | ||
23993 | pEntry = pMap->pFirst; | ||
23994 | pMap->iNextIdx = 0; /* Reset the automatic index */ | ||
23995 | for(;;){ | ||
23996 | if( n < 1 ){ | ||
23997 | break; | ||
23998 | } | ||
23999 | if( pEntry->iType == HASHMAP_INT_NODE ){ | ||
24000 | HashmapRehashIntNode(pEntry); | ||
24001 | } | ||
24002 | /* Point to the next entry */ | ||
24003 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24004 | n--; | ||
24005 | } | ||
24006 | /* Reset the cursor */ | ||
24007 | pMap->pCur = pMap->pFirst; | ||
24008 | } | ||
24009 | return JX9_OK; | ||
24010 | } | ||
24011 | /* | ||
24012 | * Extract the node cursor value. | ||
24013 | */ | ||
24014 | static sxi32 HashmapCurrentValue(jx9_context *pCtx, jx9_hashmap *pMap, int iDirection) | ||
24015 | { | ||
24016 | jx9_hashmap_node *pCur = pMap->pCur; | ||
24017 | jx9_value *pVal; | ||
24018 | if( pCur == 0 ){ | ||
24019 | /* Cursor does not point to anything, return FALSE */ | ||
24020 | jx9_result_bool(pCtx, 0); | ||
24021 | return JX9_OK; | ||
24022 | } | ||
24023 | if( iDirection != 0 ){ | ||
24024 | if( iDirection > 0 ){ | ||
24025 | /* Point to the next entry */ | ||
24026 | pMap->pCur = pCur->pPrev; /* Reverse link */ | ||
24027 | pCur = pMap->pCur; | ||
24028 | }else{ | ||
24029 | /* Point to the previous entry */ | ||
24030 | pMap->pCur = pCur->pNext; /* Reverse link */ | ||
24031 | pCur = pMap->pCur; | ||
24032 | } | ||
24033 | if( pCur == 0 ){ | ||
24034 | /* End of input reached, return FALSE */ | ||
24035 | jx9_result_bool(pCtx, 0); | ||
24036 | return JX9_OK; | ||
24037 | } | ||
24038 | } | ||
24039 | /* Point to the desired element */ | ||
24040 | pVal = HashmapExtractNodeValue(pCur); | ||
24041 | if( pVal ){ | ||
24042 | jx9_result_value(pCtx, pVal); | ||
24043 | }else{ | ||
24044 | jx9_result_bool(pCtx, 0); | ||
24045 | } | ||
24046 | return JX9_OK; | ||
24047 | } | ||
24048 | /* | ||
24049 | * value current(array $array) | ||
24050 | * Return the current element in an array. | ||
24051 | * Parameter | ||
24052 | * $input: The input array. | ||
24053 | * Return | ||
24054 | * The current() function simply returns the value of the array element that's currently | ||
24055 | * being pointed to by the internal pointer. It does not move the pointer in any way. | ||
24056 | * If the internal pointer points beyond the end of the elements list or the array | ||
24057 | * is empty, current() returns FALSE. | ||
24058 | */ | ||
24059 | static int jx9_hashmap_current(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24060 | { | ||
24061 | if( nArg < 1 ){ | ||
24062 | /* Missing arguments, return FALSE */ | ||
24063 | jx9_result_bool(pCtx, 0); | ||
24064 | return JX9_OK; | ||
24065 | } | ||
24066 | /* Make sure we are dealing with a valid hashmap */ | ||
24067 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24068 | /* Invalid argument, return FALSE */ | ||
24069 | jx9_result_bool(pCtx, 0); | ||
24070 | return JX9_OK; | ||
24071 | } | ||
24072 | HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 0); | ||
24073 | return JX9_OK; | ||
24074 | } | ||
24075 | /* | ||
24076 | * value next(array $input) | ||
24077 | * Advance the internal array pointer of an array. | ||
24078 | * Parameter | ||
24079 | * $input: The input array. | ||
24080 | * Return | ||
24081 | * next() behaves like current(), with one difference. It advances the internal array | ||
24082 | * pointer one place forward before returning the element value. That means it returns | ||
24083 | * the next array value and advances the internal array pointer by one. | ||
24084 | */ | ||
24085 | static int jx9_hashmap_next(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24086 | { | ||
24087 | if( nArg < 1 ){ | ||
24088 | /* Missing arguments, return FALSE */ | ||
24089 | jx9_result_bool(pCtx, 0); | ||
24090 | return JX9_OK; | ||
24091 | } | ||
24092 | /* Make sure we are dealing with a valid hashmap */ | ||
24093 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24094 | /* Invalid argument, return FALSE */ | ||
24095 | jx9_result_bool(pCtx, 0); | ||
24096 | return JX9_OK; | ||
24097 | } | ||
24098 | HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 1); | ||
24099 | return JX9_OK; | ||
24100 | } | ||
24101 | /* | ||
24102 | * value prev(array $input) | ||
24103 | * Rewind the internal array pointer. | ||
24104 | * Parameter | ||
24105 | * $input: The input array. | ||
24106 | * Return | ||
24107 | * Returns the array value in the previous place that's pointed | ||
24108 | * to by the internal array pointer, or FALSE if there are no more | ||
24109 | * elements. | ||
24110 | */ | ||
24111 | static int jx9_hashmap_prev(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24112 | { | ||
24113 | if( nArg < 1 ){ | ||
24114 | /* Missing arguments, return FALSE */ | ||
24115 | jx9_result_bool(pCtx, 0); | ||
24116 | return JX9_OK; | ||
24117 | } | ||
24118 | /* Make sure we are dealing with a valid hashmap */ | ||
24119 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24120 | /* Invalid argument, return FALSE */ | ||
24121 | jx9_result_bool(pCtx, 0); | ||
24122 | return JX9_OK; | ||
24123 | } | ||
24124 | HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, -1); | ||
24125 | return JX9_OK; | ||
24126 | } | ||
24127 | /* | ||
24128 | * value end(array $input) | ||
24129 | * Set the internal pointer of an array to its last element. | ||
24130 | * Parameter | ||
24131 | * $input: The input array. | ||
24132 | * Return | ||
24133 | * Returns the value of the last element or FALSE for empty array. | ||
24134 | */ | ||
24135 | static int jx9_hashmap_end(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24136 | { | ||
24137 | jx9_hashmap *pMap; | ||
24138 | if( nArg < 1 ){ | ||
24139 | /* Missing arguments, return FALSE */ | ||
24140 | jx9_result_bool(pCtx, 0); | ||
24141 | return JX9_OK; | ||
24142 | } | ||
24143 | /* Make sure we are dealing with a valid hashmap */ | ||
24144 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24145 | /* Invalid argument, return FALSE */ | ||
24146 | jx9_result_bool(pCtx, 0); | ||
24147 | return JX9_OK; | ||
24148 | } | ||
24149 | /* Point to the internal representation of the input hashmap */ | ||
24150 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24151 | /* Point to the last node */ | ||
24152 | pMap->pCur = pMap->pLast; | ||
24153 | /* Return the last node value */ | ||
24154 | HashmapCurrentValue(&(*pCtx), pMap, 0); | ||
24155 | return JX9_OK; | ||
24156 | } | ||
24157 | /* | ||
24158 | * value reset(array $array ) | ||
24159 | * Set the internal pointer of an array to its first element. | ||
24160 | * Parameter | ||
24161 | * $input: The input array. | ||
24162 | * Return | ||
24163 | * Returns the value of the first array element, or FALSE if the array is empty. | ||
24164 | */ | ||
24165 | static int jx9_hashmap_reset(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24166 | { | ||
24167 | jx9_hashmap *pMap; | ||
24168 | if( nArg < 1 ){ | ||
24169 | /* Missing arguments, return FALSE */ | ||
24170 | jx9_result_bool(pCtx, 0); | ||
24171 | return JX9_OK; | ||
24172 | } | ||
24173 | /* Make sure we are dealing with a valid hashmap */ | ||
24174 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24175 | /* Invalid argument, return FALSE */ | ||
24176 | jx9_result_bool(pCtx, 0); | ||
24177 | return JX9_OK; | ||
24178 | } | ||
24179 | /* Point to the internal representation of the input hashmap */ | ||
24180 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24181 | /* Point to the first node */ | ||
24182 | pMap->pCur = pMap->pFirst; | ||
24183 | /* Return the last node value if available */ | ||
24184 | HashmapCurrentValue(&(*pCtx), pMap, 0); | ||
24185 | return JX9_OK; | ||
24186 | } | ||
24187 | /* | ||
24188 | * value key(array $array) | ||
24189 | * Fetch a key from an array | ||
24190 | * Parameter | ||
24191 | * $input | ||
24192 | * The input array. | ||
24193 | * Return | ||
24194 | * The key() function simply returns the key of the array element that's currently | ||
24195 | * being pointed to by the internal pointer. It does not move the pointer in any way. | ||
24196 | * If the internal pointer points beyond the end of the elements list or the array | ||
24197 | * is empty, key() returns NULL. | ||
24198 | */ | ||
24199 | static int jx9_hashmap_simple_key(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24200 | { | ||
24201 | jx9_hashmap_node *pCur; | ||
24202 | jx9_hashmap *pMap; | ||
24203 | if( nArg < 1 ){ | ||
24204 | /* Missing arguments, return NULL */ | ||
24205 | jx9_result_null(pCtx); | ||
24206 | return JX9_OK; | ||
24207 | } | ||
24208 | /* Make sure we are dealing with a valid hashmap */ | ||
24209 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24210 | /* Invalid argument, return NULL */ | ||
24211 | jx9_result_null(pCtx); | ||
24212 | return JX9_OK; | ||
24213 | } | ||
24214 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24215 | pCur = pMap->pCur; | ||
24216 | if( pCur == 0 ){ | ||
24217 | /* Cursor does not point to anything, return NULL */ | ||
24218 | jx9_result_null(pCtx); | ||
24219 | return JX9_OK; | ||
24220 | } | ||
24221 | if( pCur->iType == HASHMAP_INT_NODE){ | ||
24222 | /* Key is integer */ | ||
24223 | jx9_result_int64(pCtx, pCur->xKey.iKey); | ||
24224 | }else{ | ||
24225 | /* Key is blob */ | ||
24226 | jx9_result_string(pCtx, | ||
24227 | (const char *)SyBlobData(&pCur->xKey.sKey), (int)SyBlobLength(&pCur->xKey.sKey)); | ||
24228 | } | ||
24229 | return JX9_OK; | ||
24230 | } | ||
24231 | /* | ||
24232 | * array each(array $input) | ||
24233 | * Return the current key and value pair from an array and advance the array cursor. | ||
24234 | * Parameter | ||
24235 | * $input | ||
24236 | * The input array. | ||
24237 | * Return | ||
24238 | * Returns the current key and value pair from the array array. This pair is returned | ||
24239 | * in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key | ||
24240 | * contain the key name of the array element, and 1 and value contain the data. | ||
24241 | * If the internal pointer for the array points past the end of the array contents | ||
24242 | * each() returns FALSE. | ||
24243 | */ | ||
24244 | static int jx9_hashmap_each(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24245 | { | ||
24246 | jx9_hashmap_node *pCur; | ||
24247 | jx9_hashmap *pMap; | ||
24248 | jx9_value *pArray; | ||
24249 | jx9_value *pVal; | ||
24250 | jx9_value sKey; | ||
24251 | if( nArg < 1 ){ | ||
24252 | /* Missing arguments, return FALSE */ | ||
24253 | jx9_result_bool(pCtx, 0); | ||
24254 | return JX9_OK; | ||
24255 | } | ||
24256 | /* Make sure we are dealing with a valid hashmap */ | ||
24257 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24258 | /* Invalid argument, return FALSE */ | ||
24259 | jx9_result_bool(pCtx, 0); | ||
24260 | return JX9_OK; | ||
24261 | } | ||
24262 | /* Point to the internal representation that describe the input hashmap */ | ||
24263 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24264 | if( pMap->pCur == 0 ){ | ||
24265 | /* Cursor does not point to anything, return FALSE */ | ||
24266 | jx9_result_bool(pCtx, 0); | ||
24267 | return JX9_OK; | ||
24268 | } | ||
24269 | pCur = pMap->pCur; | ||
24270 | /* Create a new array */ | ||
24271 | pArray = jx9_context_new_array(pCtx); | ||
24272 | if( pArray == 0 ){ | ||
24273 | jx9_result_bool(pCtx, 0); | ||
24274 | return JX9_OK; | ||
24275 | } | ||
24276 | pVal = HashmapExtractNodeValue(pCur); | ||
24277 | /* Insert the current value */ | ||
24278 | jx9_array_add_strkey_elem(pArray, "1", pVal); | ||
24279 | jx9_array_add_strkey_elem(pArray, "value", pVal); | ||
24280 | /* Make the key */ | ||
24281 | if( pCur->iType == HASHMAP_INT_NODE ){ | ||
24282 | jx9MemObjInitFromInt(pMap->pVm, &sKey, pCur->xKey.iKey); | ||
24283 | }else{ | ||
24284 | jx9MemObjInitFromString(pMap->pVm, &sKey, 0); | ||
24285 | jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pCur->xKey.sKey), SyBlobLength(&pCur->xKey.sKey)); | ||
24286 | } | ||
24287 | /* Insert the current key */ | ||
24288 | jx9_array_add_elem(pArray, 0, &sKey); | ||
24289 | jx9_array_add_strkey_elem(pArray, "key", &sKey); | ||
24290 | jx9MemObjRelease(&sKey); | ||
24291 | /* Advance the cursor */ | ||
24292 | pMap->pCur = pCur->pPrev; /* Reverse link */ | ||
24293 | /* Return the current entry */ | ||
24294 | jx9_result_value(pCtx, pArray); | ||
24295 | return JX9_OK; | ||
24296 | } | ||
24297 | /* | ||
24298 | * array array_values(array $input) | ||
24299 | * Returns all the values from the input array and indexes numerically the array. | ||
24300 | * Parameters | ||
24301 | * input: The input array. | ||
24302 | * Return | ||
24303 | * An indexed array of values or NULL on failure. | ||
24304 | */ | ||
24305 | static int jx9_hashmap_values(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24306 | { | ||
24307 | jx9_hashmap_node *pNode; | ||
24308 | jx9_hashmap *pMap; | ||
24309 | jx9_value *pArray; | ||
24310 | jx9_value *pObj; | ||
24311 | sxu32 n; | ||
24312 | if( nArg < 1 ){ | ||
24313 | /* Missing arguments, return NULL */ | ||
24314 | jx9_result_null(pCtx); | ||
24315 | return JX9_OK; | ||
24316 | } | ||
24317 | /* Make sure we are dealing with a valid hashmap */ | ||
24318 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24319 | /* Invalid argument, return NULL */ | ||
24320 | jx9_result_null(pCtx); | ||
24321 | return JX9_OK; | ||
24322 | } | ||
24323 | /* Point to the internal representation that describe the input hashmap */ | ||
24324 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24325 | /* Create a new array */ | ||
24326 | pArray = jx9_context_new_array(pCtx); | ||
24327 | if( pArray == 0 ){ | ||
24328 | jx9_result_null(pCtx); | ||
24329 | return JX9_OK; | ||
24330 | } | ||
24331 | /* Perform the requested operation */ | ||
24332 | pNode = pMap->pFirst; | ||
24333 | for( n = 0 ; n < pMap->nEntry ; ++n ){ | ||
24334 | pObj = HashmapExtractNodeValue(pNode); | ||
24335 | if( pObj ){ | ||
24336 | /* perform the insertion */ | ||
24337 | jx9_array_add_elem(pArray, 0/* Automatic index assign */, pObj); | ||
24338 | } | ||
24339 | /* Point to the next entry */ | ||
24340 | pNode = pNode->pPrev; /* Reverse link */ | ||
24341 | } | ||
24342 | /* return the new array */ | ||
24343 | jx9_result_value(pCtx, pArray); | ||
24344 | return JX9_OK; | ||
24345 | } | ||
24346 | /* | ||
24347 | * bool array_same(array $arr1, array $arr2) | ||
24348 | * Return TRUE if the given arrays are the same instance. | ||
24349 | * This function is useful under JX9 since arrays and objects | ||
24350 | * are passed by reference. | ||
24351 | * Parameters | ||
24352 | * $arr1 | ||
24353 | * First array | ||
24354 | * $arr2 | ||
24355 | * Second array | ||
24356 | * Return | ||
24357 | * TRUE if the arrays are the same instance. FALSE otherwise. | ||
24358 | * Note | ||
24359 | * This function is a symisc eXtension. | ||
24360 | */ | ||
24361 | static int jx9_hashmap_same(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24362 | { | ||
24363 | jx9_hashmap *p1, *p2; | ||
24364 | int rc; | ||
24365 | if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ | ||
24366 | /* Missing or invalid arguments, return FALSE*/ | ||
24367 | jx9_result_bool(pCtx, 0); | ||
24368 | return JX9_OK; | ||
24369 | } | ||
24370 | /* Point to the hashmaps */ | ||
24371 | p1 = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24372 | p2 = (jx9_hashmap *)apArg[1]->x.pOther; | ||
24373 | rc = (p1 == p2); | ||
24374 | /* Same instance? */ | ||
24375 | jx9_result_bool(pCtx, rc); | ||
24376 | return JX9_OK; | ||
24377 | } | ||
24378 | /* | ||
24379 | * array array_merge(array $array1, ...) | ||
24380 | * Merge one or more arrays. | ||
24381 | * Parameters | ||
24382 | * $array1 | ||
24383 | * Initial array to merge. | ||
24384 | * ... | ||
24385 | * More array to merge. | ||
24386 | * Return | ||
24387 | * The resulting array. | ||
24388 | */ | ||
24389 | static int jx9_hashmap_merge(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24390 | { | ||
24391 | jx9_hashmap *pMap, *pSrc; | ||
24392 | jx9_value *pArray; | ||
24393 | int i; | ||
24394 | if( nArg < 1 ){ | ||
24395 | /* Missing arguments, return NULL */ | ||
24396 | jx9_result_null(pCtx); | ||
24397 | return JX9_OK; | ||
24398 | } | ||
24399 | /* Create a new array */ | ||
24400 | pArray = jx9_context_new_array(pCtx); | ||
24401 | if( pArray == 0 ){ | ||
24402 | jx9_result_null(pCtx); | ||
24403 | return JX9_OK; | ||
24404 | } | ||
24405 | /* Point to the internal representation of the hashmap */ | ||
24406 | pMap = (jx9_hashmap *)pArray->x.pOther; | ||
24407 | /* Start merging */ | ||
24408 | for( i = 0 ; i < nArg ; i++ ){ | ||
24409 | /* Make sure we are dealing with a valid hashmap */ | ||
24410 | if( !jx9_value_is_json_array(apArg[i]) ){ | ||
24411 | /* Insert scalar value */ | ||
24412 | jx9_array_add_elem(pArray, 0, apArg[i]); | ||
24413 | }else{ | ||
24414 | pSrc = (jx9_hashmap *)apArg[i]->x.pOther; | ||
24415 | /* Merge the two hashmaps */ | ||
24416 | HashmapMerge(pSrc, pMap); | ||
24417 | } | ||
24418 | } | ||
24419 | /* Return the freshly created array */ | ||
24420 | jx9_result_value(pCtx, pArray); | ||
24421 | return JX9_OK; | ||
24422 | } | ||
24423 | /* | ||
24424 | * bool in_array(value $needle, array $haystack[, bool $strict = FALSE ]) | ||
24425 | * Checks if a value exists in an array. | ||
24426 | * Parameters | ||
24427 | * $needle | ||
24428 | * The searched value. | ||
24429 | * Note: | ||
24430 | * If needle is a string, the comparison is done in a case-sensitive manner. | ||
24431 | * $haystack | ||
24432 | * The target array. | ||
24433 | * $strict | ||
24434 | * If the third parameter strict is set to TRUE then the in_array() function | ||
24435 | * will also check the types of the needle in the haystack. | ||
24436 | */ | ||
24437 | static int jx9_hashmap_in_array(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24438 | { | ||
24439 | jx9_value *pNeedle; | ||
24440 | int bStrict; | ||
24441 | int rc; | ||
24442 | if( nArg < 2 ){ | ||
24443 | /* Missing argument, return FALSE */ | ||
24444 | jx9_result_bool(pCtx, 0); | ||
24445 | return JX9_OK; | ||
24446 | } | ||
24447 | pNeedle = apArg[0]; | ||
24448 | bStrict = 0; | ||
24449 | if( nArg > 2 ){ | ||
24450 | bStrict = jx9_value_to_bool(apArg[2]); | ||
24451 | } | ||
24452 | if( !jx9_value_is_json_array(apArg[1]) ){ | ||
24453 | /* haystack must be an array, perform a standard comparison */ | ||
24454 | rc = jx9_value_compare(pNeedle, apArg[1], bStrict); | ||
24455 | /* Set the comparison result */ | ||
24456 | jx9_result_bool(pCtx, rc == 0); | ||
24457 | return JX9_OK; | ||
24458 | } | ||
24459 | /* Perform the lookup */ | ||
24460 | rc = HashmapFindValue((jx9_hashmap *)apArg[1]->x.pOther, pNeedle, 0, bStrict); | ||
24461 | /* Lookup result */ | ||
24462 | jx9_result_bool(pCtx, rc == SXRET_OK); | ||
24463 | return JX9_OK; | ||
24464 | } | ||
24465 | /* | ||
24466 | * array array_copy(array $source) | ||
24467 | * Make a blind copy of the target array. | ||
24468 | * Parameters | ||
24469 | * $source | ||
24470 | * Target array | ||
24471 | * Return | ||
24472 | * Copy of the target array on success. NULL otherwise. | ||
24473 | * Note | ||
24474 | * This function is a symisc eXtension. | ||
24475 | */ | ||
24476 | static int jx9_hashmap_copy(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24477 | { | ||
24478 | jx9_hashmap *pMap; | ||
24479 | jx9_value *pArray; | ||
24480 | if( nArg < 1 ){ | ||
24481 | /* Missing arguments, return NULL */ | ||
24482 | jx9_result_null(pCtx); | ||
24483 | return JX9_OK; | ||
24484 | } | ||
24485 | /* Create a new array */ | ||
24486 | pArray = jx9_context_new_array(pCtx); | ||
24487 | if( pArray == 0 ){ | ||
24488 | jx9_result_null(pCtx); | ||
24489 | return JX9_OK; | ||
24490 | } | ||
24491 | /* Point to the internal representation of the hashmap */ | ||
24492 | pMap = (jx9_hashmap *)pArray->x.pOther; | ||
24493 | if( jx9_value_is_json_array(apArg[0])){ | ||
24494 | /* Point to the internal representation of the source */ | ||
24495 | jx9_hashmap *pSrc = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24496 | /* Perform the copy */ | ||
24497 | jx9HashmapDup(pSrc, pMap); | ||
24498 | }else{ | ||
24499 | /* Simple insertion */ | ||
24500 | jx9HashmapInsert(pMap, 0/* Automatic index assign*/, apArg[0]); | ||
24501 | } | ||
24502 | /* Return the duplicated array */ | ||
24503 | jx9_result_value(pCtx, pArray); | ||
24504 | return JX9_OK; | ||
24505 | } | ||
24506 | /* | ||
24507 | * bool array_erase(array $source) | ||
24508 | * Remove all elements from a given array. | ||
24509 | * Parameters | ||
24510 | * $source | ||
24511 | * Target array | ||
24512 | * Return | ||
24513 | * TRUE on success. FALSE otherwise. | ||
24514 | * Note | ||
24515 | * This function is a symisc eXtension. | ||
24516 | */ | ||
24517 | static int jx9_hashmap_erase(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24518 | { | ||
24519 | jx9_hashmap *pMap; | ||
24520 | if( nArg < 1 ){ | ||
24521 | /* Missing arguments */ | ||
24522 | jx9_result_bool(pCtx, 0); | ||
24523 | return JX9_OK; | ||
24524 | } | ||
24525 | /* Point to the target hashmap */ | ||
24526 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24527 | /* Erase */ | ||
24528 | jx9HashmapRelease(pMap, FALSE); | ||
24529 | return JX9_OK; | ||
24530 | } | ||
24531 | /* | ||
24532 | * array array_diff(array $array1, array $array2, ...) | ||
24533 | * Computes the difference of arrays. | ||
24534 | * Parameters | ||
24535 | * $array1 | ||
24536 | * The array to compare from | ||
24537 | * $array2 | ||
24538 | * An array to compare against | ||
24539 | * $... | ||
24540 | * More arrays to compare against | ||
24541 | * Return | ||
24542 | * Returns an array containing all the entries from array1 that | ||
24543 | * are not present in any of the other arrays. | ||
24544 | */ | ||
24545 | static int jx9_hashmap_diff(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24546 | { | ||
24547 | jx9_hashmap_node *pEntry; | ||
24548 | jx9_hashmap *pSrc, *pMap; | ||
24549 | jx9_value *pArray; | ||
24550 | jx9_value *pVal; | ||
24551 | sxi32 rc; | ||
24552 | sxu32 n; | ||
24553 | int i; | ||
24554 | if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ | ||
24555 | /* Missing arguments, return NULL */ | ||
24556 | jx9_result_null(pCtx); | ||
24557 | return JX9_OK; | ||
24558 | } | ||
24559 | if( nArg == 1 ){ | ||
24560 | /* Return the first array since we cannot perform a diff */ | ||
24561 | jx9_result_value(pCtx, apArg[0]); | ||
24562 | return JX9_OK; | ||
24563 | } | ||
24564 | /* Create a new array */ | ||
24565 | pArray = jx9_context_new_array(pCtx); | ||
24566 | if( pArray == 0 ){ | ||
24567 | jx9_result_null(pCtx); | ||
24568 | return JX9_OK; | ||
24569 | } | ||
24570 | /* Point to the internal representation of the source hashmap */ | ||
24571 | pSrc = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24572 | /* Perform the diff */ | ||
24573 | pEntry = pSrc->pFirst; | ||
24574 | n = pSrc->nEntry; | ||
24575 | for(;;){ | ||
24576 | if( n < 1 ){ | ||
24577 | break; | ||
24578 | } | ||
24579 | /* Extract the node value */ | ||
24580 | pVal = HashmapExtractNodeValue(pEntry); | ||
24581 | if( pVal ){ | ||
24582 | for( i = 1 ; i < nArg ; i++ ){ | ||
24583 | if( !jx9_value_is_json_array(apArg[i])) { | ||
24584 | /* ignore */ | ||
24585 | continue; | ||
24586 | } | ||
24587 | /* Point to the internal representation of the hashmap */ | ||
24588 | pMap = (jx9_hashmap *)apArg[i]->x.pOther; | ||
24589 | /* Perform the lookup */ | ||
24590 | rc = HashmapFindValue(pMap, pVal, 0, TRUE); | ||
24591 | if( rc == SXRET_OK ){ | ||
24592 | /* Value exist */ | ||
24593 | break; | ||
24594 | } | ||
24595 | } | ||
24596 | if( i >= nArg ){ | ||
24597 | /* Perform the insertion */ | ||
24598 | HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); | ||
24599 | } | ||
24600 | } | ||
24601 | /* Point to the next entry */ | ||
24602 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24603 | n--; | ||
24604 | } | ||
24605 | /* Return the freshly created array */ | ||
24606 | jx9_result_value(pCtx, pArray); | ||
24607 | return JX9_OK; | ||
24608 | } | ||
24609 | /* | ||
24610 | * array array_intersect(array $array1 , array $array2, ...) | ||
24611 | * Computes the intersection of arrays. | ||
24612 | * Parameters | ||
24613 | * $array1 | ||
24614 | * The array to compare from | ||
24615 | * $array2 | ||
24616 | * An array to compare against | ||
24617 | * $... | ||
24618 | * More arrays to compare against | ||
24619 | * Return | ||
24620 | * Returns an array containing all of the values in array1 whose values exist | ||
24621 | * in all of the parameters. . | ||
24622 | * Note that NULL is returned on failure. | ||
24623 | */ | ||
24624 | static int jx9_hashmap_intersect(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24625 | { | ||
24626 | jx9_hashmap_node *pEntry; | ||
24627 | jx9_hashmap *pSrc, *pMap; | ||
24628 | jx9_value *pArray; | ||
24629 | jx9_value *pVal; | ||
24630 | sxi32 rc; | ||
24631 | sxu32 n; | ||
24632 | int i; | ||
24633 | if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ | ||
24634 | /* Missing arguments, return NULL */ | ||
24635 | jx9_result_null(pCtx); | ||
24636 | return JX9_OK; | ||
24637 | } | ||
24638 | if( nArg == 1 ){ | ||
24639 | /* Return the first array since we cannot perform a diff */ | ||
24640 | jx9_result_value(pCtx, apArg[0]); | ||
24641 | return JX9_OK; | ||
24642 | } | ||
24643 | /* Create a new array */ | ||
24644 | pArray = jx9_context_new_array(pCtx); | ||
24645 | if( pArray == 0 ){ | ||
24646 | jx9_result_null(pCtx); | ||
24647 | return JX9_OK; | ||
24648 | } | ||
24649 | /* Point to the internal representation of the source hashmap */ | ||
24650 | pSrc = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24651 | /* Perform the intersection */ | ||
24652 | pEntry = pSrc->pFirst; | ||
24653 | n = pSrc->nEntry; | ||
24654 | for(;;){ | ||
24655 | if( n < 1 ){ | ||
24656 | break; | ||
24657 | } | ||
24658 | /* Extract the node value */ | ||
24659 | pVal = HashmapExtractNodeValue(pEntry); | ||
24660 | if( pVal ){ | ||
24661 | for( i = 1 ; i < nArg ; i++ ){ | ||
24662 | if( !jx9_value_is_json_array(apArg[i])) { | ||
24663 | /* ignore */ | ||
24664 | continue; | ||
24665 | } | ||
24666 | /* Point to the internal representation of the hashmap */ | ||
24667 | pMap = (jx9_hashmap *)apArg[i]->x.pOther; | ||
24668 | /* Perform the lookup */ | ||
24669 | rc = HashmapFindValue(pMap, pVal, 0, TRUE); | ||
24670 | if( rc != SXRET_OK ){ | ||
24671 | /* Value does not exist */ | ||
24672 | break; | ||
24673 | } | ||
24674 | } | ||
24675 | if( i >= nArg ){ | ||
24676 | /* Perform the insertion */ | ||
24677 | HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); | ||
24678 | } | ||
24679 | } | ||
24680 | /* Point to the next entry */ | ||
24681 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24682 | n--; | ||
24683 | } | ||
24684 | /* Return the freshly created array */ | ||
24685 | jx9_result_value(pCtx, pArray); | ||
24686 | return JX9_OK; | ||
24687 | } | ||
24688 | /* | ||
24689 | * number array_sum(array $array ) | ||
24690 | * Calculate the sum of values in an array. | ||
24691 | * Parameters | ||
24692 | * $array: The input array. | ||
24693 | * Return | ||
24694 | * Returns the sum of values as an integer or float. | ||
24695 | */ | ||
24696 | static void DoubleSum(jx9_context *pCtx, jx9_hashmap *pMap) | ||
24697 | { | ||
24698 | jx9_hashmap_node *pEntry; | ||
24699 | jx9_value *pObj; | ||
24700 | double dSum = 0; | ||
24701 | sxu32 n; | ||
24702 | pEntry = pMap->pFirst; | ||
24703 | for( n = 0 ; n < pMap->nEntry ; n++ ){ | ||
24704 | pObj = HashmapExtractNodeValue(pEntry); | ||
24705 | if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ | ||
24706 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
24707 | dSum += pObj->x.rVal; | ||
24708 | }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ | ||
24709 | dSum += (double)pObj->x.iVal; | ||
24710 | }else if( pObj->iFlags & MEMOBJ_STRING ){ | ||
24711 | if( SyBlobLength(&pObj->sBlob) > 0 ){ | ||
24712 | double dv = 0; | ||
24713 | SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); | ||
24714 | dSum += dv; | ||
24715 | } | ||
24716 | } | ||
24717 | } | ||
24718 | /* Point to the next entry */ | ||
24719 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24720 | } | ||
24721 | /* Return sum */ | ||
24722 | jx9_result_double(pCtx, dSum); | ||
24723 | } | ||
24724 | static void Int64Sum(jx9_context *pCtx, jx9_hashmap *pMap) | ||
24725 | { | ||
24726 | jx9_hashmap_node *pEntry; | ||
24727 | jx9_value *pObj; | ||
24728 | sxi64 nSum = 0; | ||
24729 | sxu32 n; | ||
24730 | pEntry = pMap->pFirst; | ||
24731 | for( n = 0 ; n < pMap->nEntry ; n++ ){ | ||
24732 | pObj = HashmapExtractNodeValue(pEntry); | ||
24733 | if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ | ||
24734 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
24735 | nSum += (sxi64)pObj->x.rVal; | ||
24736 | }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ | ||
24737 | nSum += pObj->x.iVal; | ||
24738 | }else if( pObj->iFlags & MEMOBJ_STRING ){ | ||
24739 | if( SyBlobLength(&pObj->sBlob) > 0 ){ | ||
24740 | sxi64 nv = 0; | ||
24741 | SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); | ||
24742 | nSum += nv; | ||
24743 | } | ||
24744 | } | ||
24745 | } | ||
24746 | /* Point to the next entry */ | ||
24747 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24748 | } | ||
24749 | /* Return sum */ | ||
24750 | jx9_result_int64(pCtx, nSum); | ||
24751 | } | ||
24752 | /* number array_sum(array $array ) | ||
24753 | * (See block-coment above) | ||
24754 | */ | ||
24755 | static int jx9_hashmap_sum(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24756 | { | ||
24757 | jx9_hashmap *pMap; | ||
24758 | jx9_value *pObj; | ||
24759 | if( nArg < 1 ){ | ||
24760 | /* Missing arguments, return 0 */ | ||
24761 | jx9_result_int(pCtx, 0); | ||
24762 | return JX9_OK; | ||
24763 | } | ||
24764 | /* Make sure we are dealing with a valid hashmap */ | ||
24765 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24766 | /* Invalid argument, return 0 */ | ||
24767 | jx9_result_int(pCtx, 0); | ||
24768 | return JX9_OK; | ||
24769 | } | ||
24770 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24771 | if( pMap->nEntry < 1 ){ | ||
24772 | /* Nothing to compute, return 0 */ | ||
24773 | jx9_result_int(pCtx, 0); | ||
24774 | return JX9_OK; | ||
24775 | } | ||
24776 | /* If the first element is of type float, then perform floating | ||
24777 | * point computaion.Otherwise switch to int64 computaion. | ||
24778 | */ | ||
24779 | pObj = HashmapExtractNodeValue(pMap->pFirst); | ||
24780 | if( pObj == 0 ){ | ||
24781 | jx9_result_int(pCtx, 0); | ||
24782 | return JX9_OK; | ||
24783 | } | ||
24784 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
24785 | DoubleSum(pCtx, pMap); | ||
24786 | }else{ | ||
24787 | Int64Sum(pCtx, pMap); | ||
24788 | } | ||
24789 | return JX9_OK; | ||
24790 | } | ||
24791 | /* | ||
24792 | * number array_product(array $array ) | ||
24793 | * Calculate the product of values in an array. | ||
24794 | * Parameters | ||
24795 | * $array: The input array. | ||
24796 | * Return | ||
24797 | * Returns the product of values as an integer or float. | ||
24798 | */ | ||
24799 | static void DoubleProd(jx9_context *pCtx, jx9_hashmap *pMap) | ||
24800 | { | ||
24801 | jx9_hashmap_node *pEntry; | ||
24802 | jx9_value *pObj; | ||
24803 | double dProd; | ||
24804 | sxu32 n; | ||
24805 | pEntry = pMap->pFirst; | ||
24806 | dProd = 1; | ||
24807 | for( n = 0 ; n < pMap->nEntry ; n++ ){ | ||
24808 | pObj = HashmapExtractNodeValue(pEntry); | ||
24809 | if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ | ||
24810 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
24811 | dProd *= pObj->x.rVal; | ||
24812 | }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ | ||
24813 | dProd *= (double)pObj->x.iVal; | ||
24814 | }else if( pObj->iFlags & MEMOBJ_STRING ){ | ||
24815 | if( SyBlobLength(&pObj->sBlob) > 0 ){ | ||
24816 | double dv = 0; | ||
24817 | SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); | ||
24818 | dProd *= dv; | ||
24819 | } | ||
24820 | } | ||
24821 | } | ||
24822 | /* Point to the next entry */ | ||
24823 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24824 | } | ||
24825 | /* Return product */ | ||
24826 | jx9_result_double(pCtx, dProd); | ||
24827 | } | ||
24828 | static void Int64Prod(jx9_context *pCtx, jx9_hashmap *pMap) | ||
24829 | { | ||
24830 | jx9_hashmap_node *pEntry; | ||
24831 | jx9_value *pObj; | ||
24832 | sxi64 nProd; | ||
24833 | sxu32 n; | ||
24834 | pEntry = pMap->pFirst; | ||
24835 | nProd = 1; | ||
24836 | for( n = 0 ; n < pMap->nEntry ; n++ ){ | ||
24837 | pObj = HashmapExtractNodeValue(pEntry); | ||
24838 | if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ | ||
24839 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
24840 | nProd *= (sxi64)pObj->x.rVal; | ||
24841 | }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ | ||
24842 | nProd *= pObj->x.iVal; | ||
24843 | }else if( pObj->iFlags & MEMOBJ_STRING ){ | ||
24844 | if( SyBlobLength(&pObj->sBlob) > 0 ){ | ||
24845 | sxi64 nv = 0; | ||
24846 | SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); | ||
24847 | nProd *= nv; | ||
24848 | } | ||
24849 | } | ||
24850 | } | ||
24851 | /* Point to the next entry */ | ||
24852 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24853 | } | ||
24854 | /* Return product */ | ||
24855 | jx9_result_int64(pCtx, nProd); | ||
24856 | } | ||
24857 | /* number array_product(array $array ) | ||
24858 | * (See block-block comment above) | ||
24859 | */ | ||
24860 | static int jx9_hashmap_product(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24861 | { | ||
24862 | jx9_hashmap *pMap; | ||
24863 | jx9_value *pObj; | ||
24864 | if( nArg < 1 ){ | ||
24865 | /* Missing arguments, return 0 */ | ||
24866 | jx9_result_int(pCtx, 0); | ||
24867 | return JX9_OK; | ||
24868 | } | ||
24869 | /* Make sure we are dealing with a valid hashmap */ | ||
24870 | if( !jx9_value_is_json_array(apArg[0]) ){ | ||
24871 | /* Invalid argument, return 0 */ | ||
24872 | jx9_result_int(pCtx, 0); | ||
24873 | return JX9_OK; | ||
24874 | } | ||
24875 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24876 | if( pMap->nEntry < 1 ){ | ||
24877 | /* Nothing to compute, return 0 */ | ||
24878 | jx9_result_int(pCtx, 0); | ||
24879 | return JX9_OK; | ||
24880 | } | ||
24881 | /* If the first element is of type float, then perform floating | ||
24882 | * point computaion.Otherwise switch to int64 computaion. | ||
24883 | */ | ||
24884 | pObj = HashmapExtractNodeValue(pMap->pFirst); | ||
24885 | if( pObj == 0 ){ | ||
24886 | jx9_result_int(pCtx, 0); | ||
24887 | return JX9_OK; | ||
24888 | } | ||
24889 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
24890 | DoubleProd(pCtx, pMap); | ||
24891 | }else{ | ||
24892 | Int64Prod(pCtx, pMap); | ||
24893 | } | ||
24894 | return JX9_OK; | ||
24895 | } | ||
24896 | /* | ||
24897 | * array array_map(callback $callback, array $arr1) | ||
24898 | * Applies the callback to the elements of the given arrays. | ||
24899 | * Parameters | ||
24900 | * $callback | ||
24901 | * Callback function to run for each element in each array. | ||
24902 | * $arr1 | ||
24903 | * An array to run through the callback function. | ||
24904 | * Return | ||
24905 | * Returns an array containing all the elements of arr1 after applying | ||
24906 | * the callback function to each one. | ||
24907 | * NOTE: | ||
24908 | * array_map() passes only a single value to the callback. | ||
24909 | */ | ||
24910 | static int jx9_hashmap_map(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24911 | { | ||
24912 | jx9_value *pArray, *pValue, sKey, sResult; | ||
24913 | jx9_hashmap_node *pEntry; | ||
24914 | jx9_hashmap *pMap; | ||
24915 | sxu32 n; | ||
24916 | if( nArg < 2 || !jx9_value_is_json_array(apArg[1]) ){ | ||
24917 | /* Invalid arguments, return NULL */ | ||
24918 | jx9_result_null(pCtx); | ||
24919 | return JX9_OK; | ||
24920 | } | ||
24921 | /* Create a new array */ | ||
24922 | pArray = jx9_context_new_array(pCtx); | ||
24923 | if( pArray == 0 ){ | ||
24924 | jx9_result_null(pCtx); | ||
24925 | return JX9_OK; | ||
24926 | } | ||
24927 | /* Point to the internal representation of the input hashmap */ | ||
24928 | pMap = (jx9_hashmap *)apArg[1]->x.pOther; | ||
24929 | jx9MemObjInit(pMap->pVm, &sResult); | ||
24930 | jx9MemObjInit(pMap->pVm, &sKey); | ||
24931 | /* Perform the requested operation */ | ||
24932 | pEntry = pMap->pFirst; | ||
24933 | for( n = 0 ; n < pMap->nEntry ; n++ ){ | ||
24934 | /* Extrcat the node value */ | ||
24935 | pValue = HashmapExtractNodeValue(pEntry); | ||
24936 | if( pValue ){ | ||
24937 | sxi32 rc; | ||
24938 | /* Invoke the supplied callback */ | ||
24939 | rc = jx9VmCallUserFunction(pMap->pVm, apArg[0], 1, &pValue, &sResult); | ||
24940 | /* Extract the node key */ | ||
24941 | jx9HashmapExtractNodeKey(pEntry, &sKey); | ||
24942 | if( rc != SXRET_OK ){ | ||
24943 | /* An error occured while invoking the supplied callback [i.e: not defined] */ | ||
24944 | jx9_array_add_elem(pArray, &sKey, pValue); /* Keep the same value */ | ||
24945 | }else{ | ||
24946 | /* Insert the callback return value */ | ||
24947 | jx9_array_add_elem(pArray, &sKey, &sResult); | ||
24948 | } | ||
24949 | jx9MemObjRelease(&sKey); | ||
24950 | jx9MemObjRelease(&sResult); | ||
24951 | } | ||
24952 | /* Point to the next entry */ | ||
24953 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
24954 | } | ||
24955 | jx9_result_value(pCtx, pArray); | ||
24956 | return JX9_OK; | ||
24957 | } | ||
24958 | /* | ||
24959 | * bool array_walk(array &$array, callback $funcname [, value $userdata ] ) | ||
24960 | * Apply a user function to every member of an array. | ||
24961 | * Parameters | ||
24962 | * $array | ||
24963 | * The input array. | ||
24964 | * $funcname | ||
24965 | * Typically, funcname takes on two parameters.The array parameter's value being | ||
24966 | * the first, and the key/index second. | ||
24967 | * Note: | ||
24968 | * If funcname needs to be working with the actual values of the array, specify the first | ||
24969 | * parameter of funcname as a reference. Then, any changes made to those elements will | ||
24970 | * be made in the original array itself. | ||
24971 | * $userdata | ||
24972 | * If the optional userdata parameter is supplied, it will be passed as the third parameter | ||
24973 | * to the callback funcname. | ||
24974 | * Return | ||
24975 | * Returns TRUE on success or FALSE on failure. | ||
24976 | */ | ||
24977 | static int jx9_hashmap_walk(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
24978 | { | ||
24979 | jx9_value *pValue, *pUserData, sKey; | ||
24980 | jx9_hashmap_node *pEntry; | ||
24981 | jx9_hashmap *pMap; | ||
24982 | sxi32 rc; | ||
24983 | sxu32 n; | ||
24984 | if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) ){ | ||
24985 | /* Invalid/Missing arguments, return FALSE */ | ||
24986 | jx9_result_bool(pCtx, 0); | ||
24987 | return JX9_OK; | ||
24988 | } | ||
24989 | pUserData = nArg > 2 ? apArg[2] : 0; | ||
24990 | /* Point to the internal representation of the input hashmap */ | ||
24991 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
24992 | jx9MemObjInit(pMap->pVm, &sKey); | ||
24993 | /* Perform the desired operation */ | ||
24994 | pEntry = pMap->pFirst; | ||
24995 | for( n = 0 ; n < pMap->nEntry ; n++ ){ | ||
24996 | /* Extract the node value */ | ||
24997 | pValue = HashmapExtractNodeValue(pEntry); | ||
24998 | if( pValue ){ | ||
24999 | /* Extract the entry key */ | ||
25000 | jx9HashmapExtractNodeKey(pEntry, &sKey); | ||
25001 | /* Invoke the supplied callback */ | ||
25002 | rc = jx9VmCallUserFunctionAp(pMap->pVm, apArg[1], 0, pValue, &sKey, pUserData, 0); | ||
25003 | jx9MemObjRelease(&sKey); | ||
25004 | if( rc != SXRET_OK ){ | ||
25005 | /* An error occured while invoking the supplied callback [i.e: not defined] */ | ||
25006 | jx9_result_bool(pCtx, 0); /* return FALSE */ | ||
25007 | return JX9_OK; | ||
25008 | } | ||
25009 | } | ||
25010 | /* Point to the next entry */ | ||
25011 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
25012 | } | ||
25013 | /* All done, return TRUE */ | ||
25014 | jx9_result_bool(pCtx, 1); | ||
25015 | return JX9_OK; | ||
25016 | } | ||
25017 | /* | ||
25018 | * Table of built-in hashmap functions. | ||
25019 | */ | ||
25020 | static const jx9_builtin_func aHashmapFunc[] = { | ||
25021 | {"count", jx9_hashmap_count }, | ||
25022 | {"sizeof", jx9_hashmap_count }, | ||
25023 | {"array_key_exists", jx9_hashmap_key_exists }, | ||
25024 | {"array_pop", jx9_hashmap_pop }, | ||
25025 | {"array_push", jx9_hashmap_push }, | ||
25026 | {"array_shift", jx9_hashmap_shift }, | ||
25027 | {"array_product", jx9_hashmap_product }, | ||
25028 | {"array_sum", jx9_hashmap_sum }, | ||
25029 | {"array_values", jx9_hashmap_values }, | ||
25030 | {"array_same", jx9_hashmap_same }, | ||
25031 | {"array_merge", jx9_hashmap_merge }, | ||
25032 | {"array_diff", jx9_hashmap_diff }, | ||
25033 | {"array_intersect", jx9_hashmap_intersect}, | ||
25034 | {"in_array", jx9_hashmap_in_array }, | ||
25035 | {"array_copy", jx9_hashmap_copy }, | ||
25036 | {"array_erase", jx9_hashmap_erase }, | ||
25037 | {"array_map", jx9_hashmap_map }, | ||
25038 | {"array_walk", jx9_hashmap_walk }, | ||
25039 | {"sort", jx9_hashmap_sort }, | ||
25040 | {"rsort", jx9_hashmap_rsort }, | ||
25041 | {"usort", jx9_hashmap_usort }, | ||
25042 | {"current", jx9_hashmap_current }, | ||
25043 | {"each", jx9_hashmap_each }, | ||
25044 | {"pos", jx9_hashmap_current }, | ||
25045 | {"next", jx9_hashmap_next }, | ||
25046 | {"prev", jx9_hashmap_prev }, | ||
25047 | {"end", jx9_hashmap_end }, | ||
25048 | {"reset", jx9_hashmap_reset }, | ||
25049 | {"key", jx9_hashmap_simple_key } | ||
25050 | }; | ||
25051 | /* | ||
25052 | * Register the built-in hashmap functions defined above. | ||
25053 | */ | ||
25054 | JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm) | ||
25055 | { | ||
25056 | sxu32 n; | ||
25057 | for( n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++ ){ | ||
25058 | jx9_create_function(&(*pVm), aHashmapFunc[n].zName, aHashmapFunc[n].xFunc, 0); | ||
25059 | } | ||
25060 | } | ||
25061 | /* | ||
25062 | * Iterate throw hashmap entries and invoke the given callback [i.e: xWalk()] for each | ||
25063 | * retrieved entry. | ||
25064 | * Note that argument are passed to the callback by copy. That is, any modification to | ||
25065 | * the entry value in the callback body will not alter the real value. | ||
25066 | * If the callback wishes to abort processing [i.e: it's invocation] it must return | ||
25067 | * a value different from JX9_OK. | ||
25068 | * Refer to [jx9_array_walk()] for more information. | ||
25069 | */ | ||
25070 | JX9_PRIVATE sxi32 jx9HashmapWalk( | ||
25071 | jx9_hashmap *pMap, /* Target hashmap */ | ||
25072 | int (*xWalk)(jx9_value *, jx9_value *, void *), /* Walker callback */ | ||
25073 | void *pUserData /* Last argument to xWalk() */ | ||
25074 | ) | ||
25075 | { | ||
25076 | jx9_hashmap_node *pEntry; | ||
25077 | jx9_value sKey, sValue; | ||
25078 | sxi32 rc; | ||
25079 | sxu32 n; | ||
25080 | /* Initialize walker parameter */ | ||
25081 | rc = SXRET_OK; | ||
25082 | jx9MemObjInit(pMap->pVm, &sKey); | ||
25083 | jx9MemObjInit(pMap->pVm, &sValue); | ||
25084 | n = pMap->nEntry; | ||
25085 | pEntry = pMap->pFirst; | ||
25086 | /* Start the iteration process */ | ||
25087 | for(;;){ | ||
25088 | if( n < 1 ){ | ||
25089 | break; | ||
25090 | } | ||
25091 | /* Extract a copy of the key and a copy the current value */ | ||
25092 | jx9HashmapExtractNodeKey(pEntry, &sKey); | ||
25093 | jx9HashmapExtractNodeValue(pEntry, &sValue, FALSE); | ||
25094 | /* Invoke the user callback */ | ||
25095 | rc = xWalk(&sKey, &sValue, pUserData); | ||
25096 | /* Release the copy of the key and the value */ | ||
25097 | jx9MemObjRelease(&sKey); | ||
25098 | jx9MemObjRelease(&sValue); | ||
25099 | if( rc != JX9_OK ){ | ||
25100 | /* Callback request an operation abort */ | ||
25101 | return SXERR_ABORT; | ||
25102 | } | ||
25103 | /* Point to the next entry */ | ||
25104 | pEntry = pEntry->pPrev; /* Reverse link */ | ||
25105 | n--; | ||
25106 | } | ||
25107 | /* All done */ | ||
25108 | return SXRET_OK; | ||
25109 | } | ||
25110 | /* | ||
25111 | * ---------------------------------------------------------- | ||
25112 | * File: jx9_json.c | ||
25113 | * MD5: 31a27f8797418de511c669feed763341 | ||
25114 | * ---------------------------------------------------------- | ||
25115 | */ | ||
25116 | /* | ||
25117 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
25118 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
25119 | * Version 1.7.2 | ||
25120 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
25121 | * please contact Symisc Systems via: | ||
25122 | * legal@symisc.net | ||
25123 | * licensing@symisc.net | ||
25124 | * contact@symisc.net | ||
25125 | * or visit: | ||
25126 | * http://jx9.symisc.net/ | ||
25127 | */ | ||
25128 | /* $SymiscID: json.c v1.0 FreeBSD 2012-12-16 00:28 stable <chm@symisc.net> $ */ | ||
25129 | #ifndef JX9_AMALGAMATION | ||
25130 | #include "jx9Int.h" | ||
25131 | #endif | ||
25132 | /* This file deals with JSON serialization, decoding and stuff like that. */ | ||
25133 | /* | ||
25134 | * Section: | ||
25135 | * JSON encoding/decoding routines. | ||
25136 | * Authors: | ||
25137 | * Symisc Systems, devel@symisc.net. | ||
25138 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
25139 | * Status: | ||
25140 | * Devel. | ||
25141 | */ | ||
25142 | /* Forward reference */ | ||
25143 | static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); | ||
25144 | static int VmJsonObjectEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); | ||
25145 | /* | ||
25146 | * JSON encoder state is stored in an instance | ||
25147 | * of the following structure. | ||
25148 | */ | ||
25149 | typedef struct json_private_data json_private_data; | ||
25150 | struct json_private_data | ||
25151 | { | ||
25152 | SyBlob *pOut; /* Output consumer buffer */ | ||
25153 | int isFirst; /* True if first encoded entry */ | ||
25154 | int iFlags; /* JSON encoding flags */ | ||
25155 | int nRecCount; /* Recursion count */ | ||
25156 | }; | ||
25157 | /* | ||
25158 | * Returns the JSON representation of a value.In other word perform a JSON encoding operation. | ||
25159 | * According to wikipedia | ||
25160 | * JSON's basic types are: | ||
25161 | * Number (double precision floating-point format in JavaScript, generally depends on implementation) | ||
25162 | * String (double-quoted Unicode, with backslash escaping) | ||
25163 | * Boolean (true or false) | ||
25164 | * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values | ||
25165 | * do not need to be of the same type) | ||
25166 | * Object (an unordered collection of key:value pairs with the ':' character separating the key | ||
25167 | * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should | ||
25168 | * be distinct from each other) | ||
25169 | * null (empty) | ||
25170 | * Non-significant white space may be added freely around the "structural characters" | ||
25171 | * (i.e. the brackets "[{]}", colon ":" and comma ","). | ||
25172 | */ | ||
25173 | static sxi32 VmJsonEncode( | ||
25174 | jx9_value *pIn, /* Encode this value */ | ||
25175 | json_private_data *pData /* Context data */ | ||
25176 | ){ | ||
25177 | SyBlob *pOut = pData->pOut; | ||
25178 | int nByte; | ||
25179 | if( jx9_value_is_null(pIn) || jx9_value_is_resource(pIn)){ | ||
25180 | /* null */ | ||
25181 | SyBlobAppend(pOut, "null", sizeof("null")-1); | ||
25182 | }else if( jx9_value_is_bool(pIn) ){ | ||
25183 | int iBool = jx9_value_to_bool(pIn); | ||
25184 | sxu32 iLen; | ||
25185 | /* true/false */ | ||
25186 | iLen = iBool ? sizeof("true") : sizeof("false"); | ||
25187 | SyBlobAppend(pOut, iBool ? "true" : "false", iLen-1); | ||
25188 | }else if( jx9_value_is_numeric(pIn) && !jx9_value_is_string(pIn) ){ | ||
25189 | const char *zNum; | ||
25190 | /* Get a string representation of the number */ | ||
25191 | zNum = jx9_value_to_string(pIn, &nByte); | ||
25192 | SyBlobAppend(pOut,zNum,nByte); | ||
25193 | }else if( jx9_value_is_string(pIn) ){ | ||
25194 | const char *zIn, *zEnd; | ||
25195 | int c; | ||
25196 | /* Encode the string */ | ||
25197 | zIn = jx9_value_to_string(pIn, &nByte); | ||
25198 | zEnd = &zIn[nByte]; | ||
25199 | /* Append the double quote */ | ||
25200 | SyBlobAppend(pOut,"\"", sizeof(char)); | ||
25201 | for(;;){ | ||
25202 | if( zIn >= zEnd ){ | ||
25203 | /* No more input to process */ | ||
25204 | break; | ||
25205 | } | ||
25206 | c = zIn[0]; | ||
25207 | /* Advance the stream cursor */ | ||
25208 | zIn++; | ||
25209 | if( c == '"' || c == '\\' ){ | ||
25210 | /* Unescape the character */ | ||
25211 | SyBlobAppend(pOut,"\\", sizeof(char)); | ||
25212 | } | ||
25213 | /* Append character verbatim */ | ||
25214 | SyBlobAppend(pOut,(const char *)&c,sizeof(char)); | ||
25215 | } | ||
25216 | /* Append the double quote */ | ||
25217 | SyBlobAppend(pOut,"\"",sizeof(char)); | ||
25218 | }else if( jx9_value_is_json_array(pIn) ){ | ||
25219 | /* Encode the array/object */ | ||
25220 | pData->isFirst = 1; | ||
25221 | if( jx9_value_is_json_object(pIn) ){ | ||
25222 | /* Encode the object instance */ | ||
25223 | pData->isFirst = 1; | ||
25224 | /* Append the curly braces */ | ||
25225 | SyBlobAppend(pOut, "{",sizeof(char)); | ||
25226 | /* Iterate throw object attribute */ | ||
25227 | jx9_array_walk(pIn,VmJsonObjectEncode, pData); | ||
25228 | /* Append the closing curly braces */ | ||
25229 | SyBlobAppend(pOut, "}",sizeof(char)); | ||
25230 | }else{ | ||
25231 | /* Append the square bracket or curly braces */ | ||
25232 | SyBlobAppend(pOut,"[",sizeof(char)); | ||
25233 | /* Iterate throw array entries */ | ||
25234 | jx9_array_walk(pIn, VmJsonArrayEncode, pData); | ||
25235 | /* Append the closing square bracket or curly braces */ | ||
25236 | SyBlobAppend(pOut,"]",sizeof(char)); | ||
25237 | } | ||
25238 | }else{ | ||
25239 | /* Can't happen */ | ||
25240 | SyBlobAppend(pOut,"null",sizeof("null")-1); | ||
25241 | } | ||
25242 | /* All done */ | ||
25243 | return JX9_OK; | ||
25244 | } | ||
25245 | /* | ||
25246 | * The following walker callback is invoked each time we need | ||
25247 | * to encode an array to JSON. | ||
25248 | */ | ||
25249 | static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData) | ||
25250 | { | ||
25251 | json_private_data *pJson = (json_private_data *)pUserData; | ||
25252 | if( pJson->nRecCount > 31 ){ | ||
25253 | /* Recursion limit reached, return immediately */ | ||
25254 | SXUNUSED(pKey); /* cc warning */ | ||
25255 | return JX9_OK; | ||
25256 | } | ||
25257 | if( !pJson->isFirst ){ | ||
25258 | /* Append the colon first */ | ||
25259 | SyBlobAppend(pJson->pOut,",",(int)sizeof(char)); | ||
25260 | } | ||
25261 | /* Encode the value */ | ||
25262 | pJson->nRecCount++; | ||
25263 | VmJsonEncode(pValue, pJson); | ||
25264 | pJson->nRecCount--; | ||
25265 | pJson->isFirst = 0; | ||
25266 | return JX9_OK; | ||
25267 | } | ||
25268 | /* | ||
25269 | * The following walker callback is invoked each time we need to encode | ||
25270 | * a object instance [i.e: Object in the JX9 jargon] to JSON. | ||
25271 | */ | ||
25272 | static int VmJsonObjectEncode(jx9_value *pKey,jx9_value *pValue,void *pUserData) | ||
25273 | { | ||
25274 | json_private_data *pJson = (json_private_data *)pUserData; | ||
25275 | const char *zKey; | ||
25276 | int nByte; | ||
25277 | if( pJson->nRecCount > 31 ){ | ||
25278 | /* Recursion limit reached, return immediately */ | ||
25279 | return JX9_OK; | ||
25280 | } | ||
25281 | if( !pJson->isFirst ){ | ||
25282 | /* Append the colon first */ | ||
25283 | SyBlobAppend(pJson->pOut,",",sizeof(char)); | ||
25284 | } | ||
25285 | /* Extract a string representation of the key */ | ||
25286 | zKey = jx9_value_to_string(pKey, &nByte); | ||
25287 | /* Append the key and the double colon */ | ||
25288 | if( nByte > 0 ){ | ||
25289 | SyBlobAppend(pJson->pOut,"\"",sizeof(char)); | ||
25290 | SyBlobAppend(pJson->pOut,zKey,(sxu32)nByte); | ||
25291 | SyBlobAppend(pJson->pOut,"\"",sizeof(char)); | ||
25292 | }else{ | ||
25293 | /* Can't happen */ | ||
25294 | SyBlobAppend(pJson->pOut,"null",sizeof("null")-1); | ||
25295 | } | ||
25296 | SyBlobAppend(pJson->pOut,":",sizeof(char)); | ||
25297 | /* Encode the value */ | ||
25298 | pJson->nRecCount++; | ||
25299 | VmJsonEncode(pValue, pJson); | ||
25300 | pJson->nRecCount--; | ||
25301 | pJson->isFirst = 0; | ||
25302 | return JX9_OK; | ||
25303 | } | ||
25304 | /* | ||
25305 | * Returns a string containing the JSON representation of value. | ||
25306 | * In other words, perform the serialization of the given JSON object. | ||
25307 | */ | ||
25308 | JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut) | ||
25309 | { | ||
25310 | json_private_data sJson; | ||
25311 | /* Prepare the JSON data */ | ||
25312 | sJson.nRecCount = 0; | ||
25313 | sJson.pOut = pOut; | ||
25314 | sJson.isFirst = 1; | ||
25315 | sJson.iFlags = 0; | ||
25316 | /* Perform the encoding operation */ | ||
25317 | VmJsonEncode(pValue, &sJson); | ||
25318 | /* All done */ | ||
25319 | return JX9_OK; | ||
25320 | } | ||
25321 | /* Possible tokens from the JSON tokenization process */ | ||
25322 | #define JSON_TK_TRUE 0x001 /* Boolean true */ | ||
25323 | #define JSON_TK_FALSE 0x002 /* Boolean false */ | ||
25324 | #define JSON_TK_STR 0x004 /* String enclosed in double quotes */ | ||
25325 | #define JSON_TK_NULL 0x008 /* null */ | ||
25326 | #define JSON_TK_NUM 0x010 /* Numeric */ | ||
25327 | #define JSON_TK_OCB 0x020 /* Open curly braces '{' */ | ||
25328 | #define JSON_TK_CCB 0x040 /* Closing curly braces '}' */ | ||
25329 | #define JSON_TK_OSB 0x080 /* Open square bracke '[' */ | ||
25330 | #define JSON_TK_CSB 0x100 /* Closing square bracket ']' */ | ||
25331 | #define JSON_TK_COLON 0x200 /* Single colon ':' */ | ||
25332 | #define JSON_TK_COMMA 0x400 /* Single comma ',' */ | ||
25333 | #define JSON_TK_ID 0x800 /* ID */ | ||
25334 | #define JSON_TK_INVALID 0x1000 /* Unexpected token */ | ||
25335 | /* | ||
25336 | * Tokenize an entire JSON input. | ||
25337 | * Get a single low-level token from the input file. | ||
25338 | * Update the stream pointer so that it points to the first | ||
25339 | * character beyond the extracted token. | ||
25340 | */ | ||
25341 | static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, void *pCtxData) | ||
25342 | { | ||
25343 | int *pJsonErr = (int *)pUserData; | ||
25344 | SyString *pStr; | ||
25345 | int c; | ||
25346 | /* Ignore leading white spaces */ | ||
25347 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ | ||
25348 | /* Advance the stream cursor */ | ||
25349 | if( pStream->zText[0] == '\n' ){ | ||
25350 | /* Update line counter */ | ||
25351 | pStream->nLine++; | ||
25352 | } | ||
25353 | pStream->zText++; | ||
25354 | } | ||
25355 | if( pStream->zText >= pStream->zEnd ){ | ||
25356 | /* End of input reached */ | ||
25357 | SXUNUSED(pCtxData); /* cc warning */ | ||
25358 | return SXERR_EOF; | ||
25359 | } | ||
25360 | /* Record token starting position and line */ | ||
25361 | pToken->nLine = pStream->nLine; | ||
25362 | pToken->pUserData = 0; | ||
25363 | pStr = &pToken->sData; | ||
25364 | SyStringInitFromBuf(pStr, pStream->zText, 0); | ||
25365 | if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ | ||
25366 | /* The following code fragment is taken verbatim from the xPP source tree. | ||
25367 | * xPP is a modern embeddable macro processor with advanced features useful for | ||
25368 | * application seeking for a production quality, ready to use macro processor. | ||
25369 | * xPP is a widely used library developed and maintened by Symisc Systems. | ||
25370 | * You can reach the xPP home page by following this link: | ||
25371 | * http://xpp.symisc.net/ | ||
25372 | */ | ||
25373 | const unsigned char *zIn; | ||
25374 | /* Isolate UTF-8 or alphanumeric stream */ | ||
25375 | if( pStream->zText[0] < 0xc0 ){ | ||
25376 | pStream->zText++; | ||
25377 | } | ||
25378 | for(;;){ | ||
25379 | zIn = pStream->zText; | ||
25380 | if( zIn[0] >= 0xc0 ){ | ||
25381 | zIn++; | ||
25382 | /* UTF-8 stream */ | ||
25383 | while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ | ||
25384 | zIn++; | ||
25385 | } | ||
25386 | } | ||
25387 | /* Skip alphanumeric stream */ | ||
25388 | while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ | ||
25389 | zIn++; | ||
25390 | } | ||
25391 | if( zIn == pStream->zText ){ | ||
25392 | /* Not an UTF-8 or alphanumeric stream */ | ||
25393 | break; | ||
25394 | } | ||
25395 | /* Synchronize pointers */ | ||
25396 | pStream->zText = zIn; | ||
25397 | } | ||
25398 | /* Record token length */ | ||
25399 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); | ||
25400 | /* A simple identifier */ | ||
25401 | pToken->nType = JSON_TK_ID; | ||
25402 | if( pStr->nByte == sizeof("true") -1 && SyStrnicmp(pStr->zString, "true", sizeof("true")-1) == 0 ){ | ||
25403 | /* boolean true */ | ||
25404 | pToken->nType = JSON_TK_TRUE; | ||
25405 | }else if( pStr->nByte == sizeof("false") -1 && SyStrnicmp(pStr->zString,"false", sizeof("false")-1) == 0 ){ | ||
25406 | /* boolean false */ | ||
25407 | pToken->nType = JSON_TK_FALSE; | ||
25408 | }else if( pStr->nByte == sizeof("null") -1 && SyStrnicmp(pStr->zString,"null", sizeof("null")-1) == 0 ){ | ||
25409 | /* NULL */ | ||
25410 | pToken->nType = JSON_TK_NULL; | ||
25411 | } | ||
25412 | return SXRET_OK; | ||
25413 | } | ||
25414 | if( pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']' | ||
25415 | || pStream->zText[0] == ':' || pStream->zText[0] == ',' ){ | ||
25416 | /* Single character */ | ||
25417 | c = pStream->zText[0]; | ||
25418 | /* Set token type */ | ||
25419 | switch(c){ | ||
25420 | case '[': pToken->nType = JSON_TK_OSB; break; | ||
25421 | case '{': pToken->nType = JSON_TK_OCB; break; | ||
25422 | case '}': pToken->nType = JSON_TK_CCB; break; | ||
25423 | case ']': pToken->nType = JSON_TK_CSB; break; | ||
25424 | case ':': pToken->nType = JSON_TK_COLON; break; | ||
25425 | case ',': pToken->nType = JSON_TK_COMMA; break; | ||
25426 | default: | ||
25427 | break; | ||
25428 | } | ||
25429 | /* Advance the stream cursor */ | ||
25430 | pStream->zText++; | ||
25431 | }else if( pStream->zText[0] == '"') { | ||
25432 | /* JSON string */ | ||
25433 | pStream->zText++; | ||
25434 | pStr->zString++; | ||
25435 | /* Delimit the string */ | ||
25436 | while( pStream->zText < pStream->zEnd ){ | ||
25437 | if( pStream->zText[0] == '"' && pStream->zText[-1] != '\\' ){ | ||
25438 | break; | ||
25439 | } | ||
25440 | if( pStream->zText[0] == '\n' ){ | ||
25441 | /* Update line counter */ | ||
25442 | pStream->nLine++; | ||
25443 | } | ||
25444 | pStream->zText++; | ||
25445 | } | ||
25446 | if( pStream->zText >= pStream->zEnd ){ | ||
25447 | /* Missing closing '"' */ | ||
25448 | pToken->nType = JSON_TK_INVALID; | ||
25449 | *pJsonErr = SXERR_SYNTAX; | ||
25450 | }else{ | ||
25451 | pToken->nType = JSON_TK_STR; | ||
25452 | pStream->zText++; /* Jump the closing double quotes */ | ||
25453 | } | ||
25454 | }else if( pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
25455 | /* Number */ | ||
25456 | pStream->zText++; | ||
25457 | pToken->nType = JSON_TK_NUM; | ||
25458 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
25459 | pStream->zText++; | ||
25460 | } | ||
25461 | if( pStream->zText < pStream->zEnd ){ | ||
25462 | c = pStream->zText[0]; | ||
25463 | if( c == '.' ){ | ||
25464 | /* Real number */ | ||
25465 | pStream->zText++; | ||
25466 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
25467 | pStream->zText++; | ||
25468 | } | ||
25469 | if( pStream->zText < pStream->zEnd ){ | ||
25470 | c = pStream->zText[0]; | ||
25471 | if( c=='e' || c=='E' ){ | ||
25472 | pStream->zText++; | ||
25473 | if( pStream->zText < pStream->zEnd ){ | ||
25474 | c = pStream->zText[0]; | ||
25475 | if( c =='+' || c=='-' ){ | ||
25476 | pStream->zText++; | ||
25477 | } | ||
25478 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
25479 | pStream->zText++; | ||
25480 | } | ||
25481 | } | ||
25482 | } | ||
25483 | } | ||
25484 | }else if( c=='e' || c=='E' ){ | ||
25485 | /* Real number */ | ||
25486 | pStream->zText++; | ||
25487 | if( pStream->zText < pStream->zEnd ){ | ||
25488 | c = pStream->zText[0]; | ||
25489 | if( c =='+' || c=='-' ){ | ||
25490 | pStream->zText++; | ||
25491 | } | ||
25492 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
25493 | pStream->zText++; | ||
25494 | } | ||
25495 | } | ||
25496 | } | ||
25497 | } | ||
25498 | }else{ | ||
25499 | /* Unexpected token */ | ||
25500 | pToken->nType = JSON_TK_INVALID; | ||
25501 | /* Advance the stream cursor */ | ||
25502 | pStream->zText++; | ||
25503 | *pJsonErr = SXERR_SYNTAX; | ||
25504 | /* Abort processing immediatley */ | ||
25505 | return SXERR_ABORT; | ||
25506 | } | ||
25507 | /* record token length */ | ||
25508 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); | ||
25509 | if( pToken->nType == JSON_TK_STR ){ | ||
25510 | pStr->nByte--; | ||
25511 | } | ||
25512 | /* Return to the lexer */ | ||
25513 | return SXRET_OK; | ||
25514 | } | ||
25515 | /* | ||
25516 | * JSON decoded input consumer callback signature. | ||
25517 | */ | ||
25518 | typedef int (*ProcJSONConsumer)(jx9_context *, jx9_value *, jx9_value *, void *); | ||
25519 | /* | ||
25520 | * JSON decoder state is kept in the following structure. | ||
25521 | */ | ||
25522 | typedef struct json_decoder json_decoder; | ||
25523 | struct json_decoder | ||
25524 | { | ||
25525 | jx9_context *pCtx; /* Call context */ | ||
25526 | ProcJSONConsumer xConsumer; /* Consumer callback */ | ||
25527 | void *pUserData; /* Last argument to xConsumer() */ | ||
25528 | int iFlags; /* Configuration flags */ | ||
25529 | SyToken *pIn; /* Token stream */ | ||
25530 | SyToken *pEnd; /* End of the token stream */ | ||
25531 | int rec_count; /* Current nesting level */ | ||
25532 | int *pErr; /* JSON decoding error if any */ | ||
25533 | }; | ||
25534 | /* Forward declaration */ | ||
25535 | static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData); | ||
25536 | /* | ||
25537 | * Dequote [i.e: Resolve all backslash escapes ] a JSON string and store | ||
25538 | * the result in the given jx9_value. | ||
25539 | */ | ||
25540 | static void VmJsonDequoteString(const SyString *pStr, jx9_value *pWorker) | ||
25541 | { | ||
25542 | const char *zIn = pStr->zString; | ||
25543 | const char *zEnd = &pStr->zString[pStr->nByte]; | ||
25544 | const char *zCur; | ||
25545 | int c; | ||
25546 | /* Mark the value as a string */ | ||
25547 | jx9_value_string(pWorker, "", 0); /* Empty string */ | ||
25548 | for(;;){ | ||
25549 | zCur = zIn; | ||
25550 | while( zIn < zEnd && zIn[0] != '\\' ){ | ||
25551 | zIn++; | ||
25552 | } | ||
25553 | if( zIn > zCur ){ | ||
25554 | /* Append chunk verbatim */ | ||
25555 | jx9_value_string(pWorker, zCur, (int)(zIn-zCur)); | ||
25556 | } | ||
25557 | zIn++; | ||
25558 | if( zIn >= zEnd ){ | ||
25559 | /* End of the input reached */ | ||
25560 | break; | ||
25561 | } | ||
25562 | c = zIn[0]; | ||
25563 | /* Unescape the character */ | ||
25564 | switch(c){ | ||
25565 | case '"': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; | ||
25566 | case '\\': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; | ||
25567 | case 'n': jx9_value_string(pWorker, "\n", (int)sizeof(char)); break; | ||
25568 | case 'r': jx9_value_string(pWorker, "\r", (int)sizeof(char)); break; | ||
25569 | case 't': jx9_value_string(pWorker, "\t", (int)sizeof(char)); break; | ||
25570 | case 'f': jx9_value_string(pWorker, "\f", (int)sizeof(char)); break; | ||
25571 | default: | ||
25572 | jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); | ||
25573 | break; | ||
25574 | } | ||
25575 | /* Advance the stream cursor */ | ||
25576 | zIn++; | ||
25577 | } | ||
25578 | } | ||
25579 | /* | ||
25580 | * Returns a jx9_value holding the image of a JSON string. In other word perform a JSON decoding operation. | ||
25581 | * According to wikipedia | ||
25582 | * JSON's basic types are: | ||
25583 | * Number (double precision floating-point format in JavaScript, generally depends on implementation) | ||
25584 | * String (double-quoted Unicode, with backslash escaping) | ||
25585 | * Boolean (true or false) | ||
25586 | * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values | ||
25587 | * do not need to be of the same type) | ||
25588 | * Object (an unordered collection of key:value pairs with the ':' character separating the key | ||
25589 | * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should | ||
25590 | * be distinct from each other) | ||
25591 | * null (empty) | ||
25592 | * Non-significant white space may be added freely around the "structural characters" (i.e. the brackets "[{]}", colon ":" and comma ", "). | ||
25593 | */ | ||
25594 | static sxi32 VmJsonDecode( | ||
25595 | json_decoder *pDecoder, /* JSON decoder */ | ||
25596 | jx9_value *pArrayKey /* Key for the decoded array */ | ||
25597 | ){ | ||
25598 | jx9_value *pWorker; /* Worker variable */ | ||
25599 | sxi32 rc; | ||
25600 | /* Check if we do not nest to much */ | ||
25601 | if( pDecoder->rec_count > 31 ){ | ||
25602 | /* Nesting limit reached, abort decoding immediately */ | ||
25603 | return SXERR_ABORT; | ||
25604 | } | ||
25605 | if( pDecoder->pIn->nType & (JSON_TK_STR|JSON_TK_ID|JSON_TK_TRUE|JSON_TK_FALSE|JSON_TK_NULL|JSON_TK_NUM) ){ | ||
25606 | /* Scalar value */ | ||
25607 | pWorker = jx9_context_new_scalar(pDecoder->pCtx); | ||
25608 | if( pWorker == 0 ){ | ||
25609 | jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
25610 | /* Abort the decoding operation immediately */ | ||
25611 | return SXERR_ABORT; | ||
25612 | } | ||
25613 | /* Reflect the JSON image */ | ||
25614 | if( pDecoder->pIn->nType & JSON_TK_NULL ){ | ||
25615 | /* Nullify the value.*/ | ||
25616 | jx9_value_null(pWorker); | ||
25617 | }else if( pDecoder->pIn->nType & (JSON_TK_TRUE|JSON_TK_FALSE) ){ | ||
25618 | /* Boolean value */ | ||
25619 | jx9_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0 ); | ||
25620 | }else if( pDecoder->pIn->nType & JSON_TK_NUM ){ | ||
25621 | SyString *pStr = &pDecoder->pIn->sData; | ||
25622 | /* | ||
25623 | * Numeric value. | ||
25624 | * Get a string representation first then try to get a numeric | ||
25625 | * value. | ||
25626 | */ | ||
25627 | jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); | ||
25628 | /* Obtain a numeric representation */ | ||
25629 | jx9MemObjToNumeric(pWorker); | ||
25630 | }else if( pDecoder->pIn->nType & JSON_TK_ID ){ | ||
25631 | SyString *pStr = &pDecoder->pIn->sData; | ||
25632 | jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); | ||
25633 | }else{ | ||
25634 | /* Dequote the string */ | ||
25635 | VmJsonDequoteString(&pDecoder->pIn->sData, pWorker); | ||
25636 | } | ||
25637 | /* Invoke the consumer callback */ | ||
25638 | rc = pDecoder->xConsumer(pDecoder->pCtx, pArrayKey, pWorker, pDecoder->pUserData); | ||
25639 | if( rc == SXERR_ABORT ){ | ||
25640 | return SXERR_ABORT; | ||
25641 | } | ||
25642 | /* All done, advance the stream cursor */ | ||
25643 | pDecoder->pIn++; | ||
25644 | }else if( pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) { | ||
25645 | ProcJSONConsumer xOld; | ||
25646 | void *pOld; | ||
25647 | /* Array representation*/ | ||
25648 | pDecoder->pIn++; | ||
25649 | /* Create a working array */ | ||
25650 | pWorker = jx9_context_new_array(pDecoder->pCtx); | ||
25651 | if( pWorker == 0 ){ | ||
25652 | jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
25653 | /* Abort the decoding operation immediately */ | ||
25654 | return SXERR_ABORT; | ||
25655 | } | ||
25656 | /* Save the old consumer */ | ||
25657 | xOld = pDecoder->xConsumer; | ||
25658 | pOld = pDecoder->pUserData; | ||
25659 | /* Set the new consumer */ | ||
25660 | pDecoder->xConsumer = VmJsonArrayDecoder; | ||
25661 | pDecoder->pUserData = pWorker; | ||
25662 | /* Decode the array */ | ||
25663 | for(;;){ | ||
25664 | /* Jump trailing comma. Note that the standard JX9 engine will not let you | ||
25665 | * do this. | ||
25666 | */ | ||
25667 | while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ | ||
25668 | pDecoder->pIn++; | ||
25669 | } | ||
25670 | if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/ ){ | ||
25671 | if( pDecoder->pIn < pDecoder->pEnd ){ | ||
25672 | pDecoder->pIn++; /* Jump the trailing ']' */ | ||
25673 | } | ||
25674 | break; | ||
25675 | } | ||
25676 | /* Recurse and decode the entry */ | ||
25677 | pDecoder->rec_count++; | ||
25678 | rc = VmJsonDecode(pDecoder, 0); | ||
25679 | pDecoder->rec_count--; | ||
25680 | if( rc == SXERR_ABORT ){ | ||
25681 | /* Abort processing immediately */ | ||
25682 | return SXERR_ABORT; | ||
25683 | } | ||
25684 | /*The cursor is automatically advanced by the VmJsonDecode() function */ | ||
25685 | if( (pDecoder->pIn < pDecoder->pEnd) && | ||
25686 | ((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/|JSON_TK_COMMA/*','*/))==0) ){ | ||
25687 | /* Unexpected token, abort immediatley */ | ||
25688 | *pDecoder->pErr = SXERR_SYNTAX; | ||
25689 | return SXERR_ABORT; | ||
25690 | } | ||
25691 | } | ||
25692 | /* Restore the old consumer */ | ||
25693 | pDecoder->xConsumer = xOld; | ||
25694 | pDecoder->pUserData = pOld; | ||
25695 | /* Invoke the old consumer on the decoded array */ | ||
25696 | xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); | ||
25697 | }else if( pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) { | ||
25698 | ProcJSONConsumer xOld; | ||
25699 | jx9_value *pKey; | ||
25700 | void *pOld; | ||
25701 | /* Object representation*/ | ||
25702 | pDecoder->pIn++; | ||
25703 | /* Create a working array */ | ||
25704 | pWorker = jx9_context_new_array(pDecoder->pCtx); | ||
25705 | pKey = jx9_context_new_scalar(pDecoder->pCtx); | ||
25706 | if( pWorker == 0 || pKey == 0){ | ||
25707 | jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
25708 | /* Abort the decoding operation immediately */ | ||
25709 | return SXERR_ABORT; | ||
25710 | } | ||
25711 | /* Save the old consumer */ | ||
25712 | xOld = pDecoder->xConsumer; | ||
25713 | pOld = pDecoder->pUserData; | ||
25714 | /* Set the new consumer */ | ||
25715 | pDecoder->xConsumer = VmJsonArrayDecoder; | ||
25716 | pDecoder->pUserData = pWorker; | ||
25717 | /* Decode the object */ | ||
25718 | for(;;){ | ||
25719 | /* Jump trailing comma. Note that the standard JX9 engine will not let you | ||
25720 | * do this. | ||
25721 | */ | ||
25722 | while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ | ||
25723 | pDecoder->pIn++; | ||
25724 | } | ||
25725 | if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/ ){ | ||
25726 | if( pDecoder->pIn < pDecoder->pEnd ){ | ||
25727 | pDecoder->pIn++; /* Jump the trailing ']' */ | ||
25728 | } | ||
25729 | break; | ||
25730 | } | ||
25731 | if( (pDecoder->pIn->nType & (JSON_TK_ID|JSON_TK_STR)) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd | ||
25732 | || (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0){ | ||
25733 | /* Syntax error, return immediately */ | ||
25734 | *pDecoder->pErr = SXERR_SYNTAX; | ||
25735 | return SXERR_ABORT; | ||
25736 | } | ||
25737 | if( pDecoder->pIn->nType & JSON_TK_ID ){ | ||
25738 | SyString *pStr = &pDecoder->pIn->sData; | ||
25739 | jx9_value_string(pKey, pStr->zString, (int)pStr->nByte); | ||
25740 | }else{ | ||
25741 | /* Dequote the key */ | ||
25742 | VmJsonDequoteString(&pDecoder->pIn->sData, pKey); | ||
25743 | } | ||
25744 | /* Jump the key and the colon */ | ||
25745 | pDecoder->pIn += 2; | ||
25746 | /* Recurse and decode the value */ | ||
25747 | pDecoder->rec_count++; | ||
25748 | rc = VmJsonDecode(pDecoder, pKey); | ||
25749 | pDecoder->rec_count--; | ||
25750 | if( rc == SXERR_ABORT ){ | ||
25751 | /* Abort processing immediately */ | ||
25752 | return SXERR_ABORT; | ||
25753 | } | ||
25754 | /* Reset the internal buffer of the key */ | ||
25755 | jx9_value_reset_string_cursor(pKey); | ||
25756 | /*The cursor is automatically advanced by the VmJsonDecode() function */ | ||
25757 | } | ||
25758 | /* Restore the old consumer */ | ||
25759 | pDecoder->xConsumer = xOld; | ||
25760 | pDecoder->pUserData = pOld; | ||
25761 | /* Invoke the old consumer on the decoded object*/ | ||
25762 | xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); | ||
25763 | /* Release the key */ | ||
25764 | jx9_context_release_value(pDecoder->pCtx, pKey); | ||
25765 | }else{ | ||
25766 | /* Unexpected token */ | ||
25767 | return SXERR_ABORT; /* Abort immediately */ | ||
25768 | } | ||
25769 | /* Release the worker variable */ | ||
25770 | jx9_context_release_value(pDecoder->pCtx, pWorker); | ||
25771 | return SXRET_OK; | ||
25772 | } | ||
25773 | /* | ||
25774 | * The following JSON decoder callback is invoked each time | ||
25775 | * a JSON array representation [i.e: [15, "hello", FALSE] ] | ||
25776 | * is being decoded. | ||
25777 | */ | ||
25778 | static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) | ||
25779 | { | ||
25780 | jx9_value *pArray = (jx9_value *)pUserData; | ||
25781 | /* Insert the entry */ | ||
25782 | jx9_array_add_elem(pArray, pKey, pWorker); /* Will make it's own copy */ | ||
25783 | SXUNUSED(pCtx); /* cc warning */ | ||
25784 | /* All done */ | ||
25785 | return SXRET_OK; | ||
25786 | } | ||
25787 | /* | ||
25788 | * Standard JSON decoder callback. | ||
25789 | */ | ||
25790 | static int VmJsonDefaultDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) | ||
25791 | { | ||
25792 | /* Return the value directly */ | ||
25793 | jx9_result_value(pCtx, pWorker); /* Will make it's own copy */ | ||
25794 | SXUNUSED(pKey); /* cc warning */ | ||
25795 | SXUNUSED(pUserData); | ||
25796 | /* All done */ | ||
25797 | return SXRET_OK; | ||
25798 | } | ||
25799 | /* | ||
25800 | * Exported JSON decoding interface | ||
25801 | */ | ||
25802 | JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte) | ||
25803 | { | ||
25804 | jx9_vm *pVm = pCtx->pVm; | ||
25805 | json_decoder sDecoder; | ||
25806 | SySet sToken; | ||
25807 | SyLex sLex; | ||
25808 | sxi32 rc; | ||
25809 | /* Tokenize the input */ | ||
25810 | SySetInit(&sToken, &pVm->sAllocator, sizeof(SyToken)); | ||
25811 | rc = SXRET_OK; | ||
25812 | SyLexInit(&sLex, &sToken, VmJsonTokenize, &rc); | ||
25813 | SyLexTokenizeInput(&sLex,zJSON,(sxu32)nByte, 0, 0, 0); | ||
25814 | if( rc != SXRET_OK ){ | ||
25815 | /* Something goes wrong while tokenizing input. [i.e: Unexpected token] */ | ||
25816 | SyLexRelease(&sLex); | ||
25817 | SySetRelease(&sToken); | ||
25818 | /* return NULL */ | ||
25819 | jx9_result_null(pCtx); | ||
25820 | return JX9_OK; | ||
25821 | } | ||
25822 | /* Fill the decoder */ | ||
25823 | sDecoder.pCtx = pCtx; | ||
25824 | sDecoder.pErr = &rc; | ||
25825 | sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken); | ||
25826 | sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)]; | ||
25827 | sDecoder.iFlags = 0; | ||
25828 | sDecoder.rec_count = 0; | ||
25829 | /* Set a default consumer */ | ||
25830 | sDecoder.xConsumer = VmJsonDefaultDecoder; | ||
25831 | sDecoder.pUserData = 0; | ||
25832 | /* Decode the raw JSON input */ | ||
25833 | rc = VmJsonDecode(&sDecoder, 0); | ||
25834 | if( rc == SXERR_ABORT ){ | ||
25835 | /* | ||
25836 | * Something goes wrong while decoding JSON input.Return NULL. | ||
25837 | */ | ||
25838 | jx9_result_null(pCtx); | ||
25839 | } | ||
25840 | /* Clean-up the mess left behind */ | ||
25841 | SyLexRelease(&sLex); | ||
25842 | SySetRelease(&sToken); | ||
25843 | /* All done */ | ||
25844 | return JX9_OK; | ||
25845 | } | ||
25846 | /* | ||
25847 | * ---------------------------------------------------------- | ||
25848 | * File: jx9_lex.c | ||
25849 | * MD5: a79518c0635dbaf5dcfaca62efa2faf8 | ||
25850 | * ---------------------------------------------------------- | ||
25851 | */ | ||
25852 | /* | ||
25853 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
25854 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
25855 | * Version 1.7.2 | ||
25856 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
25857 | * please contact Symisc Systems via: | ||
25858 | * legal@symisc.net | ||
25859 | * licensing@symisc.net | ||
25860 | * contact@symisc.net | ||
25861 | * or visit: | ||
25862 | * http://jx9.symisc.net/ | ||
25863 | */ | ||
25864 | /* $SymiscID: lex.c v1.0 FreeBSD 2012-12-09 00:19 stable <chm@symisc.net> $ */ | ||
25865 | #ifndef JX9_AMALGAMATION | ||
25866 | #include "jx9Int.h" | ||
25867 | #endif | ||
25868 | /* This file implements a thread-safe and full reentrant lexical analyzer for the Jx9 programming language */ | ||
25869 | /* Forward declarations */ | ||
25870 | static sxu32 keywordCode(const char *z,int n); | ||
25871 | static sxi32 LexExtractNowdoc(SyStream *pStream,SyToken *pToken); | ||
25872 | /* | ||
25873 | * Tokenize a raw jx9 input. | ||
25874 | * Get a single low-level token from the input file. Update the stream pointer so that | ||
25875 | * it points to the first character beyond the extracted token. | ||
25876 | */ | ||
25877 | static sxi32 jx9TokenizeInput(SyStream *pStream,SyToken *pToken,void *pUserData,void *pCtxData) | ||
25878 | { | ||
25879 | SyString *pStr; | ||
25880 | sxi32 rc; | ||
25881 | /* Ignore leading white spaces */ | ||
25882 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ | ||
25883 | /* Advance the stream cursor */ | ||
25884 | if( pStream->zText[0] == '\n' ){ | ||
25885 | /* Update line counter */ | ||
25886 | pStream->nLine++; | ||
25887 | } | ||
25888 | pStream->zText++; | ||
25889 | } | ||
25890 | if( pStream->zText >= pStream->zEnd ){ | ||
25891 | /* End of input reached */ | ||
25892 | return SXERR_EOF; | ||
25893 | } | ||
25894 | /* Record token starting position and line */ | ||
25895 | pToken->nLine = pStream->nLine; | ||
25896 | pToken->pUserData = 0; | ||
25897 | pStr = &pToken->sData; | ||
25898 | SyStringInitFromBuf(pStr, pStream->zText, 0); | ||
25899 | if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ | ||
25900 | /* The following code fragment is taken verbatim from the xPP source tree. | ||
25901 | * xPP is a modern embeddable macro processor with advanced features useful for | ||
25902 | * application seeking for a production quality, ready to use macro processor. | ||
25903 | * xPP is a widely used library developed and maintened by Symisc Systems. | ||
25904 | * You can reach the xPP home page by following this link: | ||
25905 | * http://xpp.symisc.net/ | ||
25906 | */ | ||
25907 | const unsigned char *zIn; | ||
25908 | sxu32 nKeyword; | ||
25909 | /* Isolate UTF-8 or alphanumeric stream */ | ||
25910 | if( pStream->zText[0] < 0xc0 ){ | ||
25911 | pStream->zText++; | ||
25912 | } | ||
25913 | for(;;){ | ||
25914 | zIn = pStream->zText; | ||
25915 | if( zIn[0] >= 0xc0 ){ | ||
25916 | zIn++; | ||
25917 | /* UTF-8 stream */ | ||
25918 | while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ | ||
25919 | zIn++; | ||
25920 | } | ||
25921 | } | ||
25922 | /* Skip alphanumeric stream */ | ||
25923 | while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ | ||
25924 | zIn++; | ||
25925 | } | ||
25926 | if( zIn == pStream->zText ){ | ||
25927 | /* Not an UTF-8 or alphanumeric stream */ | ||
25928 | break; | ||
25929 | } | ||
25930 | /* Synchronize pointers */ | ||
25931 | pStream->zText = zIn; | ||
25932 | } | ||
25933 | /* Record token length */ | ||
25934 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); | ||
25935 | nKeyword = keywordCode(pStr->zString, (int)pStr->nByte); | ||
25936 | if( nKeyword != JX9_TK_ID ){ | ||
25937 | /* We are dealing with a keyword [i.e: if, function, CREATE, ...], save the keyword ID */ | ||
25938 | pToken->nType = JX9_TK_KEYWORD; | ||
25939 | pToken->pUserData = SX_INT_TO_PTR(nKeyword); | ||
25940 | }else{ | ||
25941 | /* A simple identifier */ | ||
25942 | pToken->nType = JX9_TK_ID; | ||
25943 | } | ||
25944 | }else{ | ||
25945 | sxi32 c; | ||
25946 | /* Non-alpha stream */ | ||
25947 | if( pStream->zText[0] == '#' || | ||
25948 | ( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '/') ){ | ||
25949 | pStream->zText++; | ||
25950 | /* Inline comments */ | ||
25951 | while( pStream->zText < pStream->zEnd && pStream->zText[0] != '\n' ){ | ||
25952 | pStream->zText++; | ||
25953 | } | ||
25954 | /* Tell the upper-layer to ignore this token */ | ||
25955 | return SXERR_CONTINUE; | ||
25956 | }else if( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '*' ){ | ||
25957 | pStream->zText += 2; | ||
25958 | /* Block comment */ | ||
25959 | while( pStream->zText < pStream->zEnd ){ | ||
25960 | if( pStream->zText[0] == '*' ){ | ||
25961 | if( &pStream->zText[1] >= pStream->zEnd || pStream->zText[1] == '/' ){ | ||
25962 | break; | ||
25963 | } | ||
25964 | } | ||
25965 | if( pStream->zText[0] == '\n' ){ | ||
25966 | pStream->nLine++; | ||
25967 | } | ||
25968 | pStream->zText++; | ||
25969 | } | ||
25970 | pStream->zText += 2; | ||
25971 | /* Tell the upper-layer to ignore this token */ | ||
25972 | return SXERR_CONTINUE; | ||
25973 | }else if( SyisDigit(pStream->zText[0]) ){ | ||
25974 | pStream->zText++; | ||
25975 | /* Decimal digit stream */ | ||
25976 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
25977 | pStream->zText++; | ||
25978 | } | ||
25979 | /* Mark the token as integer until we encounter a real number */ | ||
25980 | pToken->nType = JX9_TK_INTEGER; | ||
25981 | if( pStream->zText < pStream->zEnd ){ | ||
25982 | c = pStream->zText[0]; | ||
25983 | if( c == '.' ){ | ||
25984 | /* Real number */ | ||
25985 | pStream->zText++; | ||
25986 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
25987 | pStream->zText++; | ||
25988 | } | ||
25989 | if( pStream->zText < pStream->zEnd ){ | ||
25990 | c = pStream->zText[0]; | ||
25991 | if( c=='e' || c=='E' ){ | ||
25992 | pStream->zText++; | ||
25993 | if( pStream->zText < pStream->zEnd ){ | ||
25994 | c = pStream->zText[0]; | ||
25995 | if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && | ||
25996 | pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ | ||
25997 | pStream->zText++; | ||
25998 | } | ||
25999 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
26000 | pStream->zText++; | ||
26001 | } | ||
26002 | } | ||
26003 | } | ||
26004 | } | ||
26005 | pToken->nType = JX9_TK_REAL; | ||
26006 | }else if( c=='e' || c=='E' ){ | ||
26007 | SXUNUSED(pUserData); /* Prevent compiler warning */ | ||
26008 | SXUNUSED(pCtxData); | ||
26009 | pStream->zText++; | ||
26010 | if( pStream->zText < pStream->zEnd ){ | ||
26011 | c = pStream->zText[0]; | ||
26012 | if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && | ||
26013 | pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ | ||
26014 | pStream->zText++; | ||
26015 | } | ||
26016 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ | ||
26017 | pStream->zText++; | ||
26018 | } | ||
26019 | } | ||
26020 | pToken->nType = JX9_TK_REAL; | ||
26021 | }else if( c == 'x' || c == 'X' ){ | ||
26022 | /* Hex digit stream */ | ||
26023 | pStream->zText++; | ||
26024 | while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisHex(pStream->zText[0]) ){ | ||
26025 | pStream->zText++; | ||
26026 | } | ||
26027 | }else if(c == 'b' || c == 'B' ){ | ||
26028 | /* Binary digit stream */ | ||
26029 | pStream->zText++; | ||
26030 | while( pStream->zText < pStream->zEnd && (pStream->zText[0] == '0' || pStream->zText[0] == '1') ){ | ||
26031 | pStream->zText++; | ||
26032 | } | ||
26033 | } | ||
26034 | } | ||
26035 | /* Record token length */ | ||
26036 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); | ||
26037 | return SXRET_OK; | ||
26038 | } | ||
26039 | c = pStream->zText[0]; | ||
26040 | pStream->zText++; /* Advance the stream cursor */ | ||
26041 | /* Assume we are dealing with an operator*/ | ||
26042 | pToken->nType = JX9_TK_OP; | ||
26043 | switch(c){ | ||
26044 | case '$': pToken->nType = JX9_TK_DOLLAR; break; | ||
26045 | case '{': pToken->nType = JX9_TK_OCB; break; | ||
26046 | case '}': pToken->nType = JX9_TK_CCB; break; | ||
26047 | case '(': pToken->nType = JX9_TK_LPAREN; break; | ||
26048 | case '[': pToken->nType |= JX9_TK_OSB; break; /* Bitwise operation here, since the square bracket token '[' | ||
26049 | * is a potential operator [i.e: subscripting] */ | ||
26050 | case ']': pToken->nType = JX9_TK_CSB; break; | ||
26051 | case ')': { | ||
26052 | SySet *pTokSet = pStream->pSet; | ||
26053 | /* Assemble type cast operators [i.e: (int), (float), (bool)...] */ | ||
26054 | if( pTokSet->nUsed >= 2 ){ | ||
26055 | SyToken *pTmp; | ||
26056 | /* Peek the last recongnized token */ | ||
26057 | pTmp = (SyToken *)SySetPeek(pTokSet); | ||
26058 | if( pTmp->nType & JX9_TK_KEYWORD ){ | ||
26059 | sxi32 nID = SX_PTR_TO_INT(pTmp->pUserData); | ||
26060 | if( (sxu32)nID & (JX9_TKWRD_INT|JX9_TKWRD_FLOAT|JX9_TKWRD_STRING|JX9_TKWRD_BOOL) ){ | ||
26061 | pTmp = (SyToken *)SySetAt(pTokSet, pTokSet->nUsed - 2); | ||
26062 | if( pTmp->nType & JX9_TK_LPAREN ){ | ||
26063 | /* Merge the three tokens '(' 'TYPE' ')' into a single one */ | ||
26064 | const char * zTypeCast = "(int)"; | ||
26065 | if( nID & JX9_TKWRD_FLOAT ){ | ||
26066 | zTypeCast = "(float)"; | ||
26067 | }else if( nID & JX9_TKWRD_BOOL ){ | ||
26068 | zTypeCast = "(bool)"; | ||
26069 | }else if( nID & JX9_TKWRD_STRING ){ | ||
26070 | zTypeCast = "(string)"; | ||
26071 | } | ||
26072 | /* Reflect the change */ | ||
26073 | pToken->nType = JX9_TK_OP; | ||
26074 | SyStringInitFromBuf(&pToken->sData, zTypeCast, SyStrlen(zTypeCast)); | ||
26075 | /* Save the instance associated with the type cast operator */ | ||
26076 | pToken->pUserData = (void *)jx9ExprExtractOperator(&pToken->sData, 0); | ||
26077 | /* Remove the two previous tokens */ | ||
26078 | pTokSet->nUsed -= 2; | ||
26079 | return SXRET_OK; | ||
26080 | } | ||
26081 | } | ||
26082 | } | ||
26083 | } | ||
26084 | pToken->nType = JX9_TK_RPAREN; | ||
26085 | break; | ||
26086 | } | ||
26087 | case '\'':{ | ||
26088 | /* Single quoted string */ | ||
26089 | pStr->zString++; | ||
26090 | while( pStream->zText < pStream->zEnd ){ | ||
26091 | if( pStream->zText[0] == '\'' ){ | ||
26092 | if( pStream->zText[-1] != '\\' ){ | ||
26093 | break; | ||
26094 | }else{ | ||
26095 | const unsigned char *zPtr = &pStream->zText[-2]; | ||
26096 | sxi32 i = 1; | ||
26097 | while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ | ||
26098 | zPtr--; | ||
26099 | i++; | ||
26100 | } | ||
26101 | if((i&1)==0){ | ||
26102 | break; | ||
26103 | } | ||
26104 | } | ||
26105 | } | ||
26106 | if( pStream->zText[0] == '\n' ){ | ||
26107 | pStream->nLine++; | ||
26108 | } | ||
26109 | pStream->zText++; | ||
26110 | } | ||
26111 | /* Record token length and type */ | ||
26112 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); | ||
26113 | pToken->nType = JX9_TK_SSTR; | ||
26114 | /* Jump the trailing single quote */ | ||
26115 | pStream->zText++; | ||
26116 | return SXRET_OK; | ||
26117 | } | ||
26118 | case '"':{ | ||
26119 | sxi32 iNest; | ||
26120 | /* Double quoted string */ | ||
26121 | pStr->zString++; | ||
26122 | while( pStream->zText < pStream->zEnd ){ | ||
26123 | if( pStream->zText[0] == '{' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '$'){ | ||
26124 | iNest = 1; | ||
26125 | pStream->zText++; | ||
26126 | /* TICKET 1433-40: Hnadle braces'{}' in double quoted string where everything is allowed */ | ||
26127 | while(pStream->zText < pStream->zEnd ){ | ||
26128 | if( pStream->zText[0] == '{' ){ | ||
26129 | iNest++; | ||
26130 | }else if (pStream->zText[0] == '}' ){ | ||
26131 | iNest--; | ||
26132 | if( iNest <= 0 ){ | ||
26133 | pStream->zText++; | ||
26134 | break; | ||
26135 | } | ||
26136 | }else if( pStream->zText[0] == '\n' ){ | ||
26137 | pStream->nLine++; | ||
26138 | } | ||
26139 | pStream->zText++; | ||
26140 | } | ||
26141 | if( pStream->zText >= pStream->zEnd ){ | ||
26142 | break; | ||
26143 | } | ||
26144 | } | ||
26145 | if( pStream->zText[0] == '"' ){ | ||
26146 | if( pStream->zText[-1] != '\\' ){ | ||
26147 | break; | ||
26148 | }else{ | ||
26149 | const unsigned char *zPtr = &pStream->zText[-2]; | ||
26150 | sxi32 i = 1; | ||
26151 | while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ | ||
26152 | zPtr--; | ||
26153 | i++; | ||
26154 | } | ||
26155 | if((i&1)==0){ | ||
26156 | break; | ||
26157 | } | ||
26158 | } | ||
26159 | } | ||
26160 | if( pStream->zText[0] == '\n' ){ | ||
26161 | pStream->nLine++; | ||
26162 | } | ||
26163 | pStream->zText++; | ||
26164 | } | ||
26165 | /* Record token length and type */ | ||
26166 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); | ||
26167 | pToken->nType = JX9_TK_DSTR; | ||
26168 | /* Jump the trailing quote */ | ||
26169 | pStream->zText++; | ||
26170 | return SXRET_OK; | ||
26171 | } | ||
26172 | case ':': | ||
26173 | pToken->nType = JX9_TK_COLON; /* Single colon */ | ||
26174 | break; | ||
26175 | case ',': pToken->nType |= JX9_TK_COMMA; break; /* Comma is also an operator */ | ||
26176 | case ';': pToken->nType = JX9_TK_SEMI; break; | ||
26177 | /* Handle combined operators [i.e: +=, ===, !=== ...] */ | ||
26178 | case '=': | ||
26179 | pToken->nType |= JX9_TK_EQUAL; | ||
26180 | if( pStream->zText < pStream->zEnd ){ | ||
26181 | if( pStream->zText[0] == '=' ){ | ||
26182 | pToken->nType &= ~JX9_TK_EQUAL; | ||
26183 | /* Current operator: == */ | ||
26184 | pStream->zText++; | ||
26185 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26186 | /* Current operator: === */ | ||
26187 | pStream->zText++; | ||
26188 | } | ||
26189 | } | ||
26190 | } | ||
26191 | break; | ||
26192 | case '!': | ||
26193 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26194 | /* Current operator: != */ | ||
26195 | pStream->zText++; | ||
26196 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26197 | /* Current operator: !== */ | ||
26198 | pStream->zText++; | ||
26199 | } | ||
26200 | } | ||
26201 | break; | ||
26202 | case '&': | ||
26203 | pToken->nType |= JX9_TK_AMPER; | ||
26204 | if( pStream->zText < pStream->zEnd ){ | ||
26205 | if( pStream->zText[0] == '&' ){ | ||
26206 | pToken->nType &= ~JX9_TK_AMPER; | ||
26207 | /* Current operator: && */ | ||
26208 | pStream->zText++; | ||
26209 | }else if( pStream->zText[0] == '=' ){ | ||
26210 | pToken->nType &= ~JX9_TK_AMPER; | ||
26211 | /* Current operator: &= */ | ||
26212 | pStream->zText++; | ||
26213 | } | ||
26214 | } | ||
26215 | case '.': | ||
26216 | if( pStream->zText < pStream->zEnd && (pStream->zText[0] == '.' || pStream->zText[0] == '=') ){ | ||
26217 | /* Concatenation operator: '..' or '.=' */ | ||
26218 | pStream->zText++; | ||
26219 | } | ||
26220 | break; | ||
26221 | case '|': | ||
26222 | if( pStream->zText < pStream->zEnd ){ | ||
26223 | if( pStream->zText[0] == '|' ){ | ||
26224 | /* Current operator: || */ | ||
26225 | pStream->zText++; | ||
26226 | }else if( pStream->zText[0] == '=' ){ | ||
26227 | /* Current operator: |= */ | ||
26228 | pStream->zText++; | ||
26229 | } | ||
26230 | } | ||
26231 | break; | ||
26232 | case '+': | ||
26233 | if( pStream->zText < pStream->zEnd ){ | ||
26234 | if( pStream->zText[0] == '+' ){ | ||
26235 | /* Current operator: ++ */ | ||
26236 | pStream->zText++; | ||
26237 | }else if( pStream->zText[0] == '=' ){ | ||
26238 | /* Current operator: += */ | ||
26239 | pStream->zText++; | ||
26240 | } | ||
26241 | } | ||
26242 | break; | ||
26243 | case '-': | ||
26244 | if( pStream->zText < pStream->zEnd ){ | ||
26245 | if( pStream->zText[0] == '-' ){ | ||
26246 | /* Current operator: -- */ | ||
26247 | pStream->zText++; | ||
26248 | }else if( pStream->zText[0] == '=' ){ | ||
26249 | /* Current operator: -= */ | ||
26250 | pStream->zText++; | ||
26251 | }else if( pStream->zText[0] == '>' ){ | ||
26252 | /* Current operator: -> */ | ||
26253 | pStream->zText++; | ||
26254 | } | ||
26255 | } | ||
26256 | break; | ||
26257 | case '*': | ||
26258 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26259 | /* Current operator: *= */ | ||
26260 | pStream->zText++; | ||
26261 | } | ||
26262 | break; | ||
26263 | case '/': | ||
26264 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26265 | /* Current operator: /= */ | ||
26266 | pStream->zText++; | ||
26267 | } | ||
26268 | break; | ||
26269 | case '%': | ||
26270 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26271 | /* Current operator: %= */ | ||
26272 | pStream->zText++; | ||
26273 | } | ||
26274 | break; | ||
26275 | case '^': | ||
26276 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26277 | /* Current operator: ^= */ | ||
26278 | pStream->zText++; | ||
26279 | } | ||
26280 | break; | ||
26281 | case '<': | ||
26282 | if( pStream->zText < pStream->zEnd ){ | ||
26283 | if( pStream->zText[0] == '<' ){ | ||
26284 | /* Current operator: << */ | ||
26285 | pStream->zText++; | ||
26286 | if( pStream->zText < pStream->zEnd ){ | ||
26287 | if( pStream->zText[0] == '=' ){ | ||
26288 | /* Current operator: <<= */ | ||
26289 | pStream->zText++; | ||
26290 | }else if( pStream->zText[0] == '<' ){ | ||
26291 | /* Current Token: <<< */ | ||
26292 | pStream->zText++; | ||
26293 | /* This may be the beginning of a Heredoc/Nowdoc string, try to delimit it */ | ||
26294 | rc = LexExtractNowdoc(&(*pStream), &(*pToken)); | ||
26295 | if( rc == SXRET_OK ){ | ||
26296 | /* Here/Now doc successfuly extracted */ | ||
26297 | return SXRET_OK; | ||
26298 | } | ||
26299 | } | ||
26300 | } | ||
26301 | }else if( pStream->zText[0] == '>' ){ | ||
26302 | /* Current operator: <> */ | ||
26303 | pStream->zText++; | ||
26304 | }else if( pStream->zText[0] == '=' ){ | ||
26305 | /* Current operator: <= */ | ||
26306 | pStream->zText++; | ||
26307 | } | ||
26308 | } | ||
26309 | break; | ||
26310 | case '>': | ||
26311 | if( pStream->zText < pStream->zEnd ){ | ||
26312 | if( pStream->zText[0] == '>' ){ | ||
26313 | /* Current operator: >> */ | ||
26314 | pStream->zText++; | ||
26315 | if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ | ||
26316 | /* Current operator: >>= */ | ||
26317 | pStream->zText++; | ||
26318 | } | ||
26319 | }else if( pStream->zText[0] == '=' ){ | ||
26320 | /* Current operator: >= */ | ||
26321 | pStream->zText++; | ||
26322 | } | ||
26323 | } | ||
26324 | break; | ||
26325 | default: | ||
26326 | break; | ||
26327 | } | ||
26328 | if( pStr->nByte <= 0 ){ | ||
26329 | /* Record token length */ | ||
26330 | pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); | ||
26331 | } | ||
26332 | if( pToken->nType & JX9_TK_OP ){ | ||
26333 | const jx9_expr_op *pOp; | ||
26334 | /* Check if the extracted token is an operator */ | ||
26335 | pOp = jx9ExprExtractOperator(pStr, (SyToken *)SySetPeek(pStream->pSet)); | ||
26336 | if( pOp == 0 ){ | ||
26337 | /* Not an operator */ | ||
26338 | pToken->nType &= ~JX9_TK_OP; | ||
26339 | if( pToken->nType <= 0 ){ | ||
26340 | pToken->nType = JX9_TK_OTHER; | ||
26341 | } | ||
26342 | }else{ | ||
26343 | /* Save the instance associated with this operator for later processing */ | ||
26344 | pToken->pUserData = (void *)pOp; | ||
26345 | } | ||
26346 | } | ||
26347 | } | ||
26348 | /* Tell the upper-layer to save the extracted token for later processing */ | ||
26349 | return SXRET_OK; | ||
26350 | } | ||
26351 | /***** This file contains automatically generated code ****** | ||
26352 | ** | ||
26353 | ** The code in this file has been automatically generated by | ||
26354 | ** | ||
26355 | ** $Header: /sqlite/sqlite/tool/mkkeywordhash.c,v 1.38 2011/12/21 01:00:46 <chm@symisc.net> $ | ||
26356 | ** | ||
26357 | ** The code in this file implements a function that determines whether | ||
26358 | ** or not a given identifier is really a JX9 keyword. The same thing | ||
26359 | ** might be implemented more directly using a hand-written hash table. | ||
26360 | ** But by using this automatically generated code, the size of the code | ||
26361 | ** is substantially reduced. This is important for embedded applications | ||
26362 | ** on platforms with limited memory. | ||
26363 | */ | ||
26364 | /* Hash score: 35 */ | ||
26365 | static sxu32 keywordCode(const char *z, int n) | ||
26366 | { | ||
26367 | /* zText[] encodes 188 bytes of keywords in 128 bytes */ | ||
26368 | /* printegereturnconstaticaselseifloatincludefaultDIEXITcontinue */ | ||
26369 | /* diewhileASPRINTbooleanbreakforeachfunctionimportstringswitch */ | ||
26370 | /* uplink */ | ||
26371 | static const char zText[127] = { | ||
26372 | 'p','r','i','n','t','e','g','e','r','e','t','u','r','n','c','o','n','s', | ||
26373 | 't','a','t','i','c','a','s','e','l','s','e','i','f','l','o','a','t','i', | ||
26374 | 'n','c','l','u','d','e','f','a','u','l','t','D','I','E','X','I','T','c', | ||
26375 | 'o','n','t','i','n','u','e','d','i','e','w','h','i','l','e','A','S','P', | ||
26376 | 'R','I','N','T','b','o','o','l','e','a','n','b','r','e','a','k','f','o', | ||
26377 | 'r','e','a','c','h','f','u','n','c','t','i','o','n','i','m','p','o','r', | ||
26378 | 't','s','t','r','i','n','g','s','w','i','t','c','h','u','p','l','i','n', | ||
26379 | 'k', | ||
26380 | }; | ||
26381 | static const unsigned char aHash[59] = { | ||
26382 | 0, 0, 0, 0, 15, 0, 30, 0, 0, 2, 19, 18, 0, | ||
26383 | 0, 10, 3, 12, 0, 28, 29, 23, 0, 13, 22, 0, 0, | ||
26384 | 14, 24, 25, 31, 11, 0, 0, 0, 0, 1, 5, 0, 0, | ||
26385 | 20, 0, 27, 9, 0, 0, 0, 8, 0, 0, 26, 6, 0, | ||
26386 | 0, 17, 0, 0, 0, 0, 0, | ||
26387 | }; | ||
26388 | static const unsigned char aNext[31] = { | ||
26389 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, | ||
26390 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 21, 7, | ||
26391 | 0, 0, 0, 0, 0, | ||
26392 | }; | ||
26393 | static const unsigned char aLen[31] = { | ||
26394 | 5, 7, 3, 6, 5, 6, 4, 2, 6, 4, 2, 5, 7, | ||
26395 | 7, 3, 4, 8, 3, 5, 2, 5, 4, 7, 5, 3, 7, | ||
26396 | 8, 6, 6, 6, 6, | ||
26397 | }; | ||
26398 | static const sxu16 aOffset[31] = { | ||
26399 | 0, 2, 2, 8, 14, 17, 22, 23, 25, 25, 29, 30, 35, | ||
26400 | 40, 47, 49, 53, 61, 64, 69, 71, 76, 76, 83, 88, 88, | ||
26401 | 95, 103, 109, 115, 121, | ||
26402 | }; | ||
26403 | static const sxu32 aCode[31] = { | ||
26404 | JX9_TKWRD_PRINT, JX9_TKWRD_INT, JX9_TKWRD_INT, JX9_TKWRD_RETURN, JX9_TKWRD_CONST, | ||
26405 | JX9_TKWRD_STATIC, JX9_TKWRD_CASE, JX9_TKWRD_AS, JX9_TKWRD_ELIF, JX9_TKWRD_ELSE, | ||
26406 | JX9_TKWRD_IF, JX9_TKWRD_FLOAT, JX9_TKWRD_INCLUDE, JX9_TKWRD_DEFAULT, JX9_TKWRD_DIE, | ||
26407 | JX9_TKWRD_EXIT, JX9_TKWRD_CONTINUE, JX9_TKWRD_DIE, JX9_TKWRD_WHILE, JX9_TKWRD_AS, | ||
26408 | JX9_TKWRD_PRINT, JX9_TKWRD_BOOL, JX9_TKWRD_BOOL, JX9_TKWRD_BREAK, JX9_TKWRD_FOR, | ||
26409 | JX9_TKWRD_FOREACH, JX9_TKWRD_FUNCTION, JX9_TKWRD_IMPORT, JX9_TKWRD_STRING, JX9_TKWRD_SWITCH, | ||
26410 | JX9_TKWRD_UPLINK, | ||
26411 | }; | ||
26412 | int h, i; | ||
26413 | if( n<2 ) return JX9_TK_ID; | ||
26414 | h = (((int)z[0]*4) ^ ((int)z[n-1]*3) ^ n) % 59; | ||
26415 | for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){ | ||
26416 | if( (int)aLen[i]==n && SyMemcmp(&zText[aOffset[i]],z,n)==0 ){ | ||
26417 | /* JX9_TKWRD_PRINT */ | ||
26418 | /* JX9_TKWRD_INT */ | ||
26419 | /* JX9_TKWRD_INT */ | ||
26420 | /* JX9_TKWRD_RETURN */ | ||
26421 | /* JX9_TKWRD_CONST */ | ||
26422 | /* JX9_TKWRD_STATIC */ | ||
26423 | /* JX9_TKWRD_CASE */ | ||
26424 | /* JX9_TKWRD_AS */ | ||
26425 | /* JX9_TKWRD_ELIF */ | ||
26426 | /* JX9_TKWRD_ELSE */ | ||
26427 | /* JX9_TKWRD_IF */ | ||
26428 | /* JX9_TKWRD_FLOAT */ | ||
26429 | /* JX9_TKWRD_INCLUDE */ | ||
26430 | /* JX9_TKWRD_DEFAULT */ | ||
26431 | /* JX9_TKWRD_DIE */ | ||
26432 | /* JX9_TKWRD_EXIT */ | ||
26433 | /* JX9_TKWRD_CONTINUE */ | ||
26434 | /* JX9_TKWRD_DIE */ | ||
26435 | /* JX9_TKWRD_WHILE */ | ||
26436 | /* JX9_TKWRD_AS */ | ||
26437 | /* JX9_TKWRD_PRINT */ | ||
26438 | /* JX9_TKWRD_BOOL */ | ||
26439 | /* JX9_TKWRD_BOOL */ | ||
26440 | /* JX9_TKWRD_BREAK */ | ||
26441 | /* JX9_TKWRD_FOR */ | ||
26442 | /* JX9_TKWRD_FOREACH */ | ||
26443 | /* JX9_TKWRD_FUNCTION */ | ||
26444 | /* JX9_TKWRD_IMPORT */ | ||
26445 | /* JX9_TKWRD_STRING */ | ||
26446 | /* JX9_TKWRD_SWITCH */ | ||
26447 | /* JX9_TKWRD_UPLINK */ | ||
26448 | return aCode[i]; | ||
26449 | } | ||
26450 | } | ||
26451 | return JX9_TK_ID; | ||
26452 | } | ||
26453 | /* | ||
26454 | * Extract a heredoc/nowdoc text from a raw JX9 input. | ||
26455 | * According to the JX9 language reference manual: | ||
26456 | * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier | ||
26457 | * is provided, then a newline. The string itself follows, and then the same identifier again | ||
26458 | * to close the quotation. | ||
26459 | * The closing identifier must begin in the first column of the line. Also, the identifier must | ||
26460 | * follow the same naming rules as any other label in JX9: it must contain only alphanumeric | ||
26461 | * characters and underscores, and must start with a non-digit character or underscore. | ||
26462 | * Heredoc text behaves just like a double-quoted string, without the double quotes. | ||
26463 | * This means that quotes in a heredoc do not need to be escaped, but the escape codes listed | ||
26464 | * above can still be used. Variables are expanded, but the same care must be taken when expressing | ||
26465 | * complex variables inside a heredoc as with strings. | ||
26466 | * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. | ||
26467 | * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. | ||
26468 | * The construct is ideal for embedding JX9 code or other large blocks of text without the need | ||
26469 | * for escaping. It shares some features in common with the SGML <![CDATA[ ]]> construct, in that | ||
26470 | * it declares a block of text which is not for parsing. | ||
26471 | * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier which follows | ||
26472 | * is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc identifiers also apply to nowdoc | ||
26473 | * identifiers, especially those regarding the appearance of the closing identifier. | ||
26474 | */ | ||
26475 | static sxi32 LexExtractNowdoc(SyStream *pStream, SyToken *pToken) | ||
26476 | { | ||
26477 | const unsigned char *zIn = pStream->zText; | ||
26478 | const unsigned char *zEnd = pStream->zEnd; | ||
26479 | const unsigned char *zPtr; | ||
26480 | SyString sDelim; | ||
26481 | SyString sStr; | ||
26482 | /* Jump leading white spaces */ | ||
26483 | while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ | ||
26484 | zIn++; | ||
26485 | } | ||
26486 | if( zIn >= zEnd ){ | ||
26487 | /* A simple symbol, return immediately */ | ||
26488 | return SXERR_CONTINUE; | ||
26489 | } | ||
26490 | if( zIn[0] == '\'' || zIn[0] == '"' ){ | ||
26491 | zIn++; | ||
26492 | } | ||
26493 | if( zIn[0] < 0xc0 && !SyisAlphaNum(zIn[0]) && zIn[0] != '_' ){ | ||
26494 | /* Invalid delimiter, return immediately */ | ||
26495 | return SXERR_CONTINUE; | ||
26496 | } | ||
26497 | /* Isolate the identifier */ | ||
26498 | sDelim.zString = (const char *)zIn; | ||
26499 | for(;;){ | ||
26500 | zPtr = zIn; | ||
26501 | /* Skip alphanumeric stream */ | ||
26502 | while( zPtr < zEnd && zPtr[0] < 0xc0 && (SyisAlphaNum(zPtr[0]) || zPtr[0] == '_') ){ | ||
26503 | zPtr++; | ||
26504 | } | ||
26505 | if( zPtr < zEnd && zPtr[0] >= 0xc0 ){ | ||
26506 | zPtr++; | ||
26507 | /* UTF-8 stream */ | ||
26508 | while( zPtr < zEnd && ((zPtr[0] & 0xc0) == 0x80) ){ | ||
26509 | zPtr++; | ||
26510 | } | ||
26511 | } | ||
26512 | if( zPtr == zIn ){ | ||
26513 | /* Not an UTF-8 or alphanumeric stream */ | ||
26514 | break; | ||
26515 | } | ||
26516 | /* Synchronize pointers */ | ||
26517 | zIn = zPtr; | ||
26518 | } | ||
26519 | /* Get the identifier length */ | ||
26520 | sDelim.nByte = (sxu32)((const char *)zIn-sDelim.zString); | ||
26521 | if( zIn[0] == '"' || zIn[0] == '\'' ){ | ||
26522 | /* Jump the trailing single quote */ | ||
26523 | zIn++; | ||
26524 | } | ||
26525 | /* Jump trailing white spaces */ | ||
26526 | while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ | ||
26527 | zIn++; | ||
26528 | } | ||
26529 | if( sDelim.nByte <= 0 || zIn >= zEnd || zIn[0] != '\n' ){ | ||
26530 | /* Invalid syntax */ | ||
26531 | return SXERR_CONTINUE; | ||
26532 | } | ||
26533 | pStream->nLine++; /* Increment line counter */ | ||
26534 | zIn++; | ||
26535 | /* Isolate the delimited string */ | ||
26536 | sStr.zString = (const char *)zIn; | ||
26537 | /* Go and found the closing delimiter */ | ||
26538 | for(;;){ | ||
26539 | /* Synchronize with the next line */ | ||
26540 | while( zIn < zEnd && zIn[0] != '\n' ){ | ||
26541 | zIn++; | ||
26542 | } | ||
26543 | if( zIn >= zEnd ){ | ||
26544 | /* End of the input reached, break immediately */ | ||
26545 | pStream->zText = pStream->zEnd; | ||
26546 | break; | ||
26547 | } | ||
26548 | pStream->nLine++; /* Increment line counter */ | ||
26549 | zIn++; | ||
26550 | if( (sxu32)(zEnd - zIn) >= sDelim.nByte && SyMemcmp((const void *)sDelim.zString, (const void *)zIn, sDelim.nByte) == 0 ){ | ||
26551 | zPtr = &zIn[sDelim.nByte]; | ||
26552 | while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ | ||
26553 | zPtr++; | ||
26554 | } | ||
26555 | if( zPtr >= zEnd ){ | ||
26556 | /* End of input */ | ||
26557 | pStream->zText = zPtr; | ||
26558 | break; | ||
26559 | } | ||
26560 | if( zPtr[0] == ';' ){ | ||
26561 | const unsigned char *zCur = zPtr; | ||
26562 | zPtr++; | ||
26563 | while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ | ||
26564 | zPtr++; | ||
26565 | } | ||
26566 | if( zPtr >= zEnd || zPtr[0] == '\n' ){ | ||
26567 | /* Closing delimiter found, break immediately */ | ||
26568 | pStream->zText = zCur; /* Keep the semi-colon */ | ||
26569 | break; | ||
26570 | } | ||
26571 | }else if( zPtr[0] == '\n' ){ | ||
26572 | /* Closing delimiter found, break immediately */ | ||
26573 | pStream->zText = zPtr; /* Synchronize with the stream cursor */ | ||
26574 | break; | ||
26575 | } | ||
26576 | /* Synchronize pointers and continue searching */ | ||
26577 | zIn = zPtr; | ||
26578 | } | ||
26579 | } /* For(;;) */ | ||
26580 | /* Get the delimited string length */ | ||
26581 | sStr.nByte = (sxu32)((const char *)zIn-sStr.zString); | ||
26582 | /* Record token type and length */ | ||
26583 | pToken->nType = JX9_TK_NOWDOC; | ||
26584 | SyStringDupPtr(&pToken->sData, &sStr); | ||
26585 | /* Remove trailing white spaces */ | ||
26586 | SyStringRightTrim(&pToken->sData); | ||
26587 | /* All done */ | ||
26588 | return SXRET_OK; | ||
26589 | } | ||
26590 | /* | ||
26591 | * Tokenize a raw jx9 input. | ||
26592 | * This is the public tokenizer called by most code generator routines. | ||
26593 | */ | ||
26594 | JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput,sxu32 nLen,SySet *pOut) | ||
26595 | { | ||
26596 | SyLex sLexer; | ||
26597 | sxi32 rc; | ||
26598 | /* Initialize the lexer */ | ||
26599 | rc = SyLexInit(&sLexer, &(*pOut),jx9TokenizeInput,0); | ||
26600 | if( rc != SXRET_OK ){ | ||
26601 | return rc; | ||
26602 | } | ||
26603 | /* Tokenize input */ | ||
26604 | rc = SyLexTokenizeInput(&sLexer, zInput, nLen, 0, 0, 0); | ||
26605 | /* Release the lexer */ | ||
26606 | SyLexRelease(&sLexer); | ||
26607 | /* Tokenization result */ | ||
26608 | return rc; | ||
26609 | } | ||
26610 | |||
26611 | /* | ||
26612 | * ---------------------------------------------------------- | ||
26613 | * File: jx9_lib.c | ||
26614 | * MD5: a684fb6677b1ab0110d03536f1280c50 | ||
26615 | * ---------------------------------------------------------- | ||
26616 | */ | ||
26617 | /* | ||
26618 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
26619 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
26620 | * Version 1.7.2 | ||
26621 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
26622 | * please contact Symisc Systems via: | ||
26623 | * legal@symisc.net | ||
26624 | * licensing@symisc.net | ||
26625 | * contact@symisc.net | ||
26626 | * or visit: | ||
26627 | * http://jx9.symisc.net/ | ||
26628 | */ | ||
26629 | /* $SymiscID: lib.c v5.1 Win7 2012-08-08 04:19 stable <chm@symisc.net> $ */ | ||
26630 | /* | ||
26631 | * Symisc Run-Time API: A modern thread safe replacement of the standard libc | ||
26632 | * Copyright (C) Symisc Systems 2007-2012, http://www.symisc.net/ | ||
26633 | * | ||
26634 | * The Symisc Run-Time API is an independent project developed by symisc systems | ||
26635 | * internally as a secure replacement of the standard libc. | ||
26636 | * The library is re-entrant, thread-safe and platform independent. | ||
26637 | */ | ||
26638 | #ifndef JX9_AMALGAMATION | ||
26639 | #include "jx9Int.h" | ||
26640 | #endif | ||
26641 | #if defined(__WINNT__) | ||
26642 | #include <Windows.h> | ||
26643 | #else | ||
26644 | #include <stdlib.h> | ||
26645 | #endif | ||
26646 | #if defined(JX9_ENABLE_THREADS) | ||
26647 | /* SyRunTimeApi: sxmutex.c */ | ||
26648 | #if defined(__WINNT__) | ||
26649 | struct SyMutex | ||
26650 | { | ||
26651 | CRITICAL_SECTION sMutex; | ||
26652 | sxu32 nType; /* Mutex type, one of SXMUTEX_TYPE_* */ | ||
26653 | }; | ||
26654 | /* Preallocated static mutex */ | ||
26655 | static SyMutex aStaticMutexes[] = { | ||
26656 | {{0}, SXMUTEX_TYPE_STATIC_1}, | ||
26657 | {{0}, SXMUTEX_TYPE_STATIC_2}, | ||
26658 | {{0}, SXMUTEX_TYPE_STATIC_3}, | ||
26659 | {{0}, SXMUTEX_TYPE_STATIC_4}, | ||
26660 | {{0}, SXMUTEX_TYPE_STATIC_5}, | ||
26661 | {{0}, SXMUTEX_TYPE_STATIC_6} | ||
26662 | }; | ||
26663 | static BOOL winMutexInit = FALSE; | ||
26664 | static LONG winMutexLock = 0; | ||
26665 | |||
26666 | static sxi32 WinMutexGlobaInit(void) | ||
26667 | { | ||
26668 | LONG rc; | ||
26669 | rc = InterlockedCompareExchange(&winMutexLock, 1, 0); | ||
26670 | if ( rc == 0 ){ | ||
26671 | sxu32 n; | ||
26672 | for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ | ||
26673 | InitializeCriticalSection(&aStaticMutexes[n].sMutex); | ||
26674 | } | ||
26675 | winMutexInit = TRUE; | ||
26676 | }else{ | ||
26677 | /* Someone else is doing this for us */ | ||
26678 | while( winMutexInit == FALSE ){ | ||
26679 | Sleep(1); | ||
26680 | } | ||
26681 | } | ||
26682 | return SXRET_OK; | ||
26683 | } | ||
26684 | static void WinMutexGlobalRelease(void) | ||
26685 | { | ||
26686 | LONG rc; | ||
26687 | rc = InterlockedCompareExchange(&winMutexLock, 0, 1); | ||
26688 | if( rc == 1 ){ | ||
26689 | /* The first to decrement to zero does the actual global release */ | ||
26690 | if( winMutexInit == TRUE ){ | ||
26691 | sxu32 n; | ||
26692 | for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ | ||
26693 | DeleteCriticalSection(&aStaticMutexes[n].sMutex); | ||
26694 | } | ||
26695 | winMutexInit = FALSE; | ||
26696 | } | ||
26697 | } | ||
26698 | } | ||
26699 | static SyMutex * WinMutexNew(int nType) | ||
26700 | { | ||
26701 | SyMutex *pMutex = 0; | ||
26702 | if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ | ||
26703 | /* Allocate a new mutex */ | ||
26704 | pMutex = (SyMutex *)HeapAlloc(GetProcessHeap(), 0, sizeof(SyMutex)); | ||
26705 | if( pMutex == 0 ){ | ||
26706 | return 0; | ||
26707 | } | ||
26708 | InitializeCriticalSection(&pMutex->sMutex); | ||
26709 | }else{ | ||
26710 | /* Use a pre-allocated static mutex */ | ||
26711 | if( nType > SXMUTEX_TYPE_STATIC_6 ){ | ||
26712 | nType = SXMUTEX_TYPE_STATIC_6; | ||
26713 | } | ||
26714 | pMutex = &aStaticMutexes[nType - 3]; | ||
26715 | } | ||
26716 | pMutex->nType = nType; | ||
26717 | return pMutex; | ||
26718 | } | ||
26719 | static void WinMutexRelease(SyMutex *pMutex) | ||
26720 | { | ||
26721 | if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ | ||
26722 | DeleteCriticalSection(&pMutex->sMutex); | ||
26723 | HeapFree(GetProcessHeap(), 0, pMutex); | ||
26724 | } | ||
26725 | } | ||
26726 | static void WinMutexEnter(SyMutex *pMutex) | ||
26727 | { | ||
26728 | EnterCriticalSection(&pMutex->sMutex); | ||
26729 | } | ||
26730 | static sxi32 WinMutexTryEnter(SyMutex *pMutex) | ||
26731 | { | ||
26732 | #ifdef _WIN32_WINNT | ||
26733 | BOOL rc; | ||
26734 | /* Only WindowsNT platforms */ | ||
26735 | rc = TryEnterCriticalSection(&pMutex->sMutex); | ||
26736 | if( rc ){ | ||
26737 | return SXRET_OK; | ||
26738 | }else{ | ||
26739 | return SXERR_BUSY; | ||
26740 | } | ||
26741 | #else | ||
26742 | return SXERR_NOTIMPLEMENTED; | ||
26743 | #endif | ||
26744 | } | ||
26745 | static void WinMutexLeave(SyMutex *pMutex) | ||
26746 | { | ||
26747 | LeaveCriticalSection(&pMutex->sMutex); | ||
26748 | } | ||
26749 | /* Export Windows mutex interfaces */ | ||
26750 | static const SyMutexMethods sWinMutexMethods = { | ||
26751 | WinMutexGlobaInit, /* xGlobalInit() */ | ||
26752 | WinMutexGlobalRelease, /* xGlobalRelease() */ | ||
26753 | WinMutexNew, /* xNew() */ | ||
26754 | WinMutexRelease, /* xRelease() */ | ||
26755 | WinMutexEnter, /* xEnter() */ | ||
26756 | WinMutexTryEnter, /* xTryEnter() */ | ||
26757 | WinMutexLeave /* xLeave() */ | ||
26758 | }; | ||
26759 | JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) | ||
26760 | { | ||
26761 | return &sWinMutexMethods; | ||
26762 | } | ||
26763 | #elif defined(__UNIXES__) | ||
26764 | #include <pthread.h> | ||
26765 | struct SyMutex | ||
26766 | { | ||
26767 | pthread_mutex_t sMutex; | ||
26768 | sxu32 nType; | ||
26769 | }; | ||
26770 | static SyMutex * UnixMutexNew(int nType) | ||
26771 | { | ||
26772 | static SyMutex aStaticMutexes[] = { | ||
26773 | {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_1}, | ||
26774 | {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_2}, | ||
26775 | {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_3}, | ||
26776 | {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_4}, | ||
26777 | {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_5}, | ||
26778 | {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_6} | ||
26779 | }; | ||
26780 | SyMutex *pMutex; | ||
26781 | |||
26782 | if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ | ||
26783 | pthread_mutexattr_t sRecursiveAttr; | ||
26784 | /* Allocate a new mutex */ | ||
26785 | pMutex = (SyMutex *)malloc(sizeof(SyMutex)); | ||
26786 | if( pMutex == 0 ){ | ||
26787 | return 0; | ||
26788 | } | ||
26789 | if( nType == SXMUTEX_TYPE_RECURSIVE ){ | ||
26790 | pthread_mutexattr_init(&sRecursiveAttr); | ||
26791 | pthread_mutexattr_settype(&sRecursiveAttr, PTHREAD_MUTEX_RECURSIVE); | ||
26792 | } | ||
26793 | pthread_mutex_init(&pMutex->sMutex, nType == SXMUTEX_TYPE_RECURSIVE ? &sRecursiveAttr : 0 ); | ||
26794 | if( nType == SXMUTEX_TYPE_RECURSIVE ){ | ||
26795 | pthread_mutexattr_destroy(&sRecursiveAttr); | ||
26796 | } | ||
26797 | }else{ | ||
26798 | /* Use a pre-allocated static mutex */ | ||
26799 | if( nType > SXMUTEX_TYPE_STATIC_6 ){ | ||
26800 | nType = SXMUTEX_TYPE_STATIC_6; | ||
26801 | } | ||
26802 | pMutex = &aStaticMutexes[nType - 3]; | ||
26803 | } | ||
26804 | pMutex->nType = nType; | ||
26805 | |||
26806 | return pMutex; | ||
26807 | } | ||
26808 | static void UnixMutexRelease(SyMutex *pMutex) | ||
26809 | { | ||
26810 | if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ | ||
26811 | pthread_mutex_destroy(&pMutex->sMutex); | ||
26812 | free(pMutex); | ||
26813 | } | ||
26814 | } | ||
26815 | static void UnixMutexEnter(SyMutex *pMutex) | ||
26816 | { | ||
26817 | pthread_mutex_lock(&pMutex->sMutex); | ||
26818 | } | ||
26819 | static void UnixMutexLeave(SyMutex *pMutex) | ||
26820 | { | ||
26821 | pthread_mutex_unlock(&pMutex->sMutex); | ||
26822 | } | ||
26823 | /* Export pthread mutex interfaces */ | ||
26824 | static const SyMutexMethods sPthreadMutexMethods = { | ||
26825 | 0, /* xGlobalInit() */ | ||
26826 | 0, /* xGlobalRelease() */ | ||
26827 | UnixMutexNew, /* xNew() */ | ||
26828 | UnixMutexRelease, /* xRelease() */ | ||
26829 | UnixMutexEnter, /* xEnter() */ | ||
26830 | 0, /* xTryEnter() */ | ||
26831 | UnixMutexLeave /* xLeave() */ | ||
26832 | }; | ||
26833 | JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) | ||
26834 | { | ||
26835 | return &sPthreadMutexMethods; | ||
26836 | } | ||
26837 | #else | ||
26838 | /* Host application must register their own mutex subsystem if the target | ||
26839 | * platform is not an UNIX-like or windows systems. | ||
26840 | */ | ||
26841 | struct SyMutex | ||
26842 | { | ||
26843 | sxu32 nType; | ||
26844 | }; | ||
26845 | static SyMutex * DummyMutexNew(int nType) | ||
26846 | { | ||
26847 | static SyMutex sMutex; | ||
26848 | SXUNUSED(nType); | ||
26849 | return &sMutex; | ||
26850 | } | ||
26851 | static void DummyMutexRelease(SyMutex *pMutex) | ||
26852 | { | ||
26853 | SXUNUSED(pMutex); | ||
26854 | } | ||
26855 | static void DummyMutexEnter(SyMutex *pMutex) | ||
26856 | { | ||
26857 | SXUNUSED(pMutex); | ||
26858 | } | ||
26859 | static void DummyMutexLeave(SyMutex *pMutex) | ||
26860 | { | ||
26861 | SXUNUSED(pMutex); | ||
26862 | } | ||
26863 | /* Export the dummy mutex interfaces */ | ||
26864 | static const SyMutexMethods sDummyMutexMethods = { | ||
26865 | 0, /* xGlobalInit() */ | ||
26866 | 0, /* xGlobalRelease() */ | ||
26867 | DummyMutexNew, /* xNew() */ | ||
26868 | DummyMutexRelease, /* xRelease() */ | ||
26869 | DummyMutexEnter, /* xEnter() */ | ||
26870 | 0, /* xTryEnter() */ | ||
26871 | DummyMutexLeave /* xLeave() */ | ||
26872 | }; | ||
26873 | JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) | ||
26874 | { | ||
26875 | return &sDummyMutexMethods; | ||
26876 | } | ||
26877 | #endif /* __WINNT__ */ | ||
26878 | #endif /* JX9_ENABLE_THREADS */ | ||
26879 | static void * SyOSHeapAlloc(sxu32 nByte) | ||
26880 | { | ||
26881 | void *pNew; | ||
26882 | #if defined(__WINNT__) | ||
26883 | pNew = HeapAlloc(GetProcessHeap(), 0, nByte); | ||
26884 | #else | ||
26885 | pNew = malloc((size_t)nByte); | ||
26886 | #endif | ||
26887 | return pNew; | ||
26888 | } | ||
26889 | static void * SyOSHeapRealloc(void *pOld, sxu32 nByte) | ||
26890 | { | ||
26891 | void *pNew; | ||
26892 | #if defined(__WINNT__) | ||
26893 | pNew = HeapReAlloc(GetProcessHeap(), 0, pOld, nByte); | ||
26894 | #else | ||
26895 | pNew = realloc(pOld, (size_t)nByte); | ||
26896 | #endif | ||
26897 | return pNew; | ||
26898 | } | ||
26899 | static void SyOSHeapFree(void *pPtr) | ||
26900 | { | ||
26901 | #if defined(__WINNT__) | ||
26902 | HeapFree(GetProcessHeap(), 0, pPtr); | ||
26903 | #else | ||
26904 | free(pPtr); | ||
26905 | #endif | ||
26906 | } | ||
26907 | /* SyRunTimeApi:sxstr.c */ | ||
26908 | JX9_PRIVATE sxu32 SyStrlen(const char *zSrc) | ||
26909 | { | ||
26910 | register const char *zIn = zSrc; | ||
26911 | #if defined(UNTRUST) | ||
26912 | if( zIn == 0 ){ | ||
26913 | return 0; | ||
26914 | } | ||
26915 | #endif | ||
26916 | for(;;){ | ||
26917 | if( !zIn[0] ){ break; } zIn++; | ||
26918 | if( !zIn[0] ){ break; } zIn++; | ||
26919 | if( !zIn[0] ){ break; } zIn++; | ||
26920 | if( !zIn[0] ){ break; } zIn++; | ||
26921 | } | ||
26922 | return (sxu32)(zIn - zSrc); | ||
26923 | } | ||
26924 | JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos) | ||
26925 | { | ||
26926 | const char *zIn = zStr; | ||
26927 | const char *zEnd; | ||
26928 | |||
26929 | zEnd = &zIn[nLen]; | ||
26930 | for(;;){ | ||
26931 | if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; | ||
26932 | if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; | ||
26933 | if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; | ||
26934 | if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; | ||
26935 | } | ||
26936 | return SXERR_NOTFOUND; | ||
26937 | } | ||
26938 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
26939 | JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos) | ||
26940 | { | ||
26941 | const char *zIn = zStr; | ||
26942 | const char *zEnd; | ||
26943 | |||
26944 | zEnd = &zIn[nLen - 1]; | ||
26945 | for( ;; ){ | ||
26946 | if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; | ||
26947 | if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; | ||
26948 | if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; | ||
26949 | if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; | ||
26950 | } | ||
26951 | return SXERR_NOTFOUND; | ||
26952 | } | ||
26953 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
26954 | JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos) | ||
26955 | { | ||
26956 | const char *zIn = zSrc; | ||
26957 | const char *zPtr; | ||
26958 | const char *zEnd; | ||
26959 | sxi32 c; | ||
26960 | zEnd = &zSrc[nLen]; | ||
26961 | for(;;){ | ||
26962 | 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++; | ||
26963 | 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++; | ||
26964 | 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++; | ||
26965 | 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++; | ||
26966 | } | ||
26967 | return SXERR_NOTFOUND; | ||
26968 | } | ||
26969 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
26970 | JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen) | ||
26971 | { | ||
26972 | const unsigned char *zP = (const unsigned char *)zLeft; | ||
26973 | const unsigned char *zQ = (const unsigned char *)zRight; | ||
26974 | |||
26975 | if( SX_EMPTY_STR(zP) || SX_EMPTY_STR(zQ) ){ | ||
26976 | return SX_EMPTY_STR(zP) ? (SX_EMPTY_STR(zQ) ? 0 : -1) :1; | ||
26977 | } | ||
26978 | if( nLen <= 0 ){ | ||
26979 | return 0; | ||
26980 | } | ||
26981 | for(;;){ | ||
26982 | if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; | ||
26983 | if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; | ||
26984 | if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; | ||
26985 | if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; | ||
26986 | } | ||
26987 | return (sxi32)(zP[0] - zQ[0]); | ||
26988 | } | ||
26989 | #endif | ||
26990 | JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen) | ||
26991 | { | ||
26992 | register unsigned char *p = (unsigned char *)zLeft; | ||
26993 | register unsigned char *q = (unsigned char *)zRight; | ||
26994 | |||
26995 | if( SX_EMPTY_STR(p) || SX_EMPTY_STR(q) ){ | ||
26996 | return SX_EMPTY_STR(p)? SX_EMPTY_STR(q) ? 0 : -1 :1; | ||
26997 | } | ||
26998 | for(;;){ | ||
26999 | if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; | ||
27000 | if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; | ||
27001 | if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; | ||
27002 | if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; | ||
27003 | |||
27004 | } | ||
27005 | return (sxi32)(SyCharToLower(p[0]) - SyCharToLower(q[0])); | ||
27006 | } | ||
27007 | JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen) | ||
27008 | { | ||
27009 | unsigned char *zBuf = (unsigned char *)zDest; | ||
27010 | unsigned char *zIn = (unsigned char *)zSrc; | ||
27011 | unsigned char *zEnd; | ||
27012 | #if defined(UNTRUST) | ||
27013 | if( zSrc == (const char *)zDest ){ | ||
27014 | return 0; | ||
27015 | } | ||
27016 | #endif | ||
27017 | if( nLen <= 0 ){ | ||
27018 | nLen = SyStrlen(zSrc); | ||
27019 | } | ||
27020 | zEnd = &zBuf[nDestLen - 1]; /* reserve a room for the null terminator */ | ||
27021 | for(;;){ | ||
27022 | if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; | ||
27023 | if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; | ||
27024 | if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; | ||
27025 | if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; | ||
27026 | } | ||
27027 | zBuf[0] = 0; | ||
27028 | return (sxu32)(zBuf-(unsigned char *)zDest); | ||
27029 | } | ||
27030 | /* SyRunTimeApi:sxmem.c */ | ||
27031 | JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize) | ||
27032 | { | ||
27033 | register unsigned char *zSrc = (unsigned char *)pSrc; | ||
27034 | unsigned char *zEnd; | ||
27035 | #if defined(UNTRUST) | ||
27036 | if( zSrc == 0 || nSize <= 0 ){ | ||
27037 | return ; | ||
27038 | } | ||
27039 | #endif | ||
27040 | zEnd = &zSrc[nSize]; | ||
27041 | for(;;){ | ||
27042 | if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; | ||
27043 | if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; | ||
27044 | if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; | ||
27045 | if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; | ||
27046 | } | ||
27047 | } | ||
27048 | JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize) | ||
27049 | { | ||
27050 | sxi32 rc; | ||
27051 | if( nSize <= 0 ){ | ||
27052 | return 0; | ||
27053 | } | ||
27054 | if( pB1 == 0 || pB2 == 0 ){ | ||
27055 | return pB1 != 0 ? 1 : (pB2 == 0 ? 0 : -1); | ||
27056 | } | ||
27057 | SX_MACRO_FAST_CMP(pB1, pB2, nSize, rc); | ||
27058 | return rc; | ||
27059 | } | ||
27060 | JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen) | ||
27061 | { | ||
27062 | if( pSrc == 0 || pDest == 0 ){ | ||
27063 | return 0; | ||
27064 | } | ||
27065 | if( pSrc == (const void *)pDest ){ | ||
27066 | return nLen; | ||
27067 | } | ||
27068 | SX_MACRO_FAST_MEMCPY(pSrc, pDest, nLen); | ||
27069 | return nLen; | ||
27070 | } | ||
27071 | static void * MemOSAlloc(sxu32 nBytes) | ||
27072 | { | ||
27073 | sxu32 *pChunk; | ||
27074 | pChunk = (sxu32 *)SyOSHeapAlloc(nBytes + sizeof(sxu32)); | ||
27075 | if( pChunk == 0 ){ | ||
27076 | return 0; | ||
27077 | } | ||
27078 | pChunk[0] = nBytes; | ||
27079 | return (void *)&pChunk[1]; | ||
27080 | } | ||
27081 | static void * MemOSRealloc(void *pOld, sxu32 nBytes) | ||
27082 | { | ||
27083 | sxu32 *pOldChunk; | ||
27084 | sxu32 *pChunk; | ||
27085 | pOldChunk = (sxu32 *)(((char *)pOld)-sizeof(sxu32)); | ||
27086 | if( pOldChunk[0] >= nBytes ){ | ||
27087 | return pOld; | ||
27088 | } | ||
27089 | pChunk = (sxu32 *)SyOSHeapRealloc(pOldChunk, nBytes + sizeof(sxu32)); | ||
27090 | if( pChunk == 0 ){ | ||
27091 | return 0; | ||
27092 | } | ||
27093 | pChunk[0] = nBytes; | ||
27094 | return (void *)&pChunk[1]; | ||
27095 | } | ||
27096 | static void MemOSFree(void *pBlock) | ||
27097 | { | ||
27098 | void *pChunk; | ||
27099 | pChunk = (void *)(((char *)pBlock)-sizeof(sxu32)); | ||
27100 | SyOSHeapFree(pChunk); | ||
27101 | } | ||
27102 | static sxu32 MemOSChunkSize(void *pBlock) | ||
27103 | { | ||
27104 | sxu32 *pChunk; | ||
27105 | pChunk = (sxu32 *)(((char *)pBlock)-sizeof(sxu32)); | ||
27106 | return pChunk[0]; | ||
27107 | } | ||
27108 | /* Export OS allocation methods */ | ||
27109 | static const SyMemMethods sOSAllocMethods = { | ||
27110 | MemOSAlloc, | ||
27111 | MemOSRealloc, | ||
27112 | MemOSFree, | ||
27113 | MemOSChunkSize, | ||
27114 | 0, | ||
27115 | 0, | ||
27116 | 0 | ||
27117 | }; | ||
27118 | static void * MemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte) | ||
27119 | { | ||
27120 | SyMemBlock *pBlock; | ||
27121 | sxi32 nRetry = 0; | ||
27122 | |||
27123 | /* Append an extra block so we can tracks allocated chunks and avoid memory | ||
27124 | * leaks. | ||
27125 | */ | ||
27126 | nByte += sizeof(SyMemBlock); | ||
27127 | for(;;){ | ||
27128 | pBlock = (SyMemBlock *)pBackend->pMethods->xAlloc(nByte); | ||
27129 | if( pBlock != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY | ||
27130 | || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ | ||
27131 | break; | ||
27132 | } | ||
27133 | nRetry++; | ||
27134 | } | ||
27135 | if( pBlock == 0 ){ | ||
27136 | return 0; | ||
27137 | } | ||
27138 | pBlock->pNext = pBlock->pPrev = 0; | ||
27139 | /* Link to the list of already tracked blocks */ | ||
27140 | MACRO_LD_PUSH(pBackend->pBlocks, pBlock); | ||
27141 | #if defined(UNTRUST) | ||
27142 | pBlock->nGuard = SXMEM_BACKEND_MAGIC; | ||
27143 | #endif | ||
27144 | pBackend->nBlock++; | ||
27145 | return (void *)&pBlock[1]; | ||
27146 | } | ||
27147 | JX9_PRIVATE void * SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte) | ||
27148 | { | ||
27149 | void *pChunk; | ||
27150 | #if defined(UNTRUST) | ||
27151 | if( SXMEM_BACKEND_CORRUPT(pBackend) ){ | ||
27152 | return 0; | ||
27153 | } | ||
27154 | #endif | ||
27155 | if( pBackend->pMutexMethods ){ | ||
27156 | SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); | ||
27157 | } | ||
27158 | pChunk = MemBackendAlloc(&(*pBackend), nByte); | ||
27159 | if( pBackend->pMutexMethods ){ | ||
27160 | SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); | ||
27161 | } | ||
27162 | return pChunk; | ||
27163 | } | ||
27164 | static void * MemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) | ||
27165 | { | ||
27166 | SyMemBlock *pBlock, *pNew, *pPrev, *pNext; | ||
27167 | sxu32 nRetry = 0; | ||
27168 | |||
27169 | if( pOld == 0 ){ | ||
27170 | return MemBackendAlloc(&(*pBackend), nByte); | ||
27171 | } | ||
27172 | pBlock = (SyMemBlock *)(((char *)pOld) - sizeof(SyMemBlock)); | ||
27173 | #if defined(UNTRUST) | ||
27174 | if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ | ||
27175 | return 0; | ||
27176 | } | ||
27177 | #endif | ||
27178 | nByte += sizeof(SyMemBlock); | ||
27179 | pPrev = pBlock->pPrev; | ||
27180 | pNext = pBlock->pNext; | ||
27181 | for(;;){ | ||
27182 | pNew = (SyMemBlock *)pBackend->pMethods->xRealloc(pBlock, nByte); | ||
27183 | if( pNew != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY || | ||
27184 | SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ | ||
27185 | break; | ||
27186 | } | ||
27187 | nRetry++; | ||
27188 | } | ||
27189 | if( pNew == 0 ){ | ||
27190 | return 0; | ||
27191 | } | ||
27192 | if( pNew != pBlock ){ | ||
27193 | if( pPrev == 0 ){ | ||
27194 | pBackend->pBlocks = pNew; | ||
27195 | }else{ | ||
27196 | pPrev->pNext = pNew; | ||
27197 | } | ||
27198 | if( pNext ){ | ||
27199 | pNext->pPrev = pNew; | ||
27200 | } | ||
27201 | #if defined(UNTRUST) | ||
27202 | pNew->nGuard = SXMEM_BACKEND_MAGIC; | ||
27203 | #endif | ||
27204 | } | ||
27205 | return (void *)&pNew[1]; | ||
27206 | } | ||
27207 | JX9_PRIVATE void * SyMemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) | ||
27208 | { | ||
27209 | void *pChunk; | ||
27210 | #if defined(UNTRUST) | ||
27211 | if( SXMEM_BACKEND_CORRUPT(pBackend) ){ | ||
27212 | return 0; | ||
27213 | } | ||
27214 | #endif | ||
27215 | if( pBackend->pMutexMethods ){ | ||
27216 | SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); | ||
27217 | } | ||
27218 | pChunk = MemBackendRealloc(&(*pBackend), pOld, nByte); | ||
27219 | if( pBackend->pMutexMethods ){ | ||
27220 | SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); | ||
27221 | } | ||
27222 | return pChunk; | ||
27223 | } | ||
27224 | static sxi32 MemBackendFree(SyMemBackend *pBackend, void * pChunk) | ||
27225 | { | ||
27226 | SyMemBlock *pBlock; | ||
27227 | pBlock = (SyMemBlock *)(((char *)pChunk) - sizeof(SyMemBlock)); | ||
27228 | #if defined(UNTRUST) | ||
27229 | if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ | ||
27230 | return SXERR_CORRUPT; | ||
27231 | } | ||
27232 | #endif | ||
27233 | /* Unlink from the list of active blocks */ | ||
27234 | if( pBackend->nBlock > 0 ){ | ||
27235 | /* Release the block */ | ||
27236 | #if defined(UNTRUST) | ||
27237 | /* Mark as stale block */ | ||
27238 | pBlock->nGuard = 0x635B; | ||
27239 | #endif | ||
27240 | MACRO_LD_REMOVE(pBackend->pBlocks, pBlock); | ||
27241 | pBackend->nBlock--; | ||
27242 | pBackend->pMethods->xFree(pBlock); | ||
27243 | } | ||
27244 | return SXRET_OK; | ||
27245 | } | ||
27246 | JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void * pChunk) | ||
27247 | { | ||
27248 | sxi32 rc; | ||
27249 | #if defined(UNTRUST) | ||
27250 | if( SXMEM_BACKEND_CORRUPT(pBackend) ){ | ||
27251 | return SXERR_CORRUPT; | ||
27252 | } | ||
27253 | #endif | ||
27254 | if( pChunk == 0 ){ | ||
27255 | return SXRET_OK; | ||
27256 | } | ||
27257 | if( pBackend->pMutexMethods ){ | ||
27258 | SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); | ||
27259 | } | ||
27260 | rc = MemBackendFree(&(*pBackend), pChunk); | ||
27261 | if( pBackend->pMutexMethods ){ | ||
27262 | SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); | ||
27263 | } | ||
27264 | return rc; | ||
27265 | } | ||
27266 | #if defined(JX9_ENABLE_THREADS) | ||
27267 | JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods) | ||
27268 | { | ||
27269 | SyMutex *pMutex; | ||
27270 | #if defined(UNTRUST) | ||
27271 | if( SXMEM_BACKEND_CORRUPT(pBackend) || pMethods == 0 || pMethods->xNew == 0){ | ||
27272 | return SXERR_CORRUPT; | ||
27273 | } | ||
27274 | #endif | ||
27275 | pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); | ||
27276 | if( pMutex == 0 ){ | ||
27277 | return SXERR_OS; | ||
27278 | } | ||
27279 | /* Attach the mutex to the memory backend */ | ||
27280 | pBackend->pMutex = pMutex; | ||
27281 | pBackend->pMutexMethods = pMethods; | ||
27282 | return SXRET_OK; | ||
27283 | } | ||
27284 | JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend) | ||
27285 | { | ||
27286 | #if defined(UNTRUST) | ||
27287 | if( SXMEM_BACKEND_CORRUPT(pBackend) ){ | ||
27288 | return SXERR_CORRUPT; | ||
27289 | } | ||
27290 | #endif | ||
27291 | if( pBackend->pMutex == 0 ){ | ||
27292 | /* There is no mutex subsystem at all */ | ||
27293 | return SXRET_OK; | ||
27294 | } | ||
27295 | SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex); | ||
27296 | pBackend->pMutexMethods = 0; | ||
27297 | pBackend->pMutex = 0; | ||
27298 | return SXRET_OK; | ||
27299 | } | ||
27300 | #endif | ||
27301 | /* | ||
27302 | * Memory pool allocator | ||
27303 | */ | ||
27304 | #define SXMEM_POOL_MAGIC 0xDEAD | ||
27305 | #define SXMEM_POOL_MAXALLOC (1<<(SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR)) | ||
27306 | #define SXMEM_POOL_MINALLOC (1<<(SXMEM_POOL_INCR)) | ||
27307 | static sxi32 MemPoolBucketAlloc(SyMemBackend *pBackend, sxu32 nBucket) | ||
27308 | { | ||
27309 | char *zBucket, *zBucketEnd; | ||
27310 | SyMemHeader *pHeader; | ||
27311 | sxu32 nBucketSize; | ||
27312 | |||
27313 | /* Allocate one big block first */ | ||
27314 | zBucket = (char *)MemBackendAlloc(&(*pBackend), SXMEM_POOL_MAXALLOC); | ||
27315 | if( zBucket == 0 ){ | ||
27316 | return SXERR_MEM; | ||
27317 | } | ||
27318 | zBucketEnd = &zBucket[SXMEM_POOL_MAXALLOC]; | ||
27319 | /* Divide the big block into mini bucket pool */ | ||
27320 | nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); | ||
27321 | pBackend->apPool[nBucket] = pHeader = (SyMemHeader *)zBucket; | ||
27322 | for(;;){ | ||
27323 | if( &zBucket[nBucketSize] >= zBucketEnd ){ | ||
27324 | break; | ||
27325 | } | ||
27326 | pHeader->pNext = (SyMemHeader *)&zBucket[nBucketSize]; | ||
27327 | /* Advance the cursor to the next available chunk */ | ||
27328 | pHeader = pHeader->pNext; | ||
27329 | zBucket += nBucketSize; | ||
27330 | } | ||
27331 | pHeader->pNext = 0; | ||
27332 | |||
27333 | return SXRET_OK; | ||
27334 | } | ||
27335 | static void * MemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte) | ||
27336 | { | ||
27337 | SyMemHeader *pBucket, *pNext; | ||
27338 | sxu32 nBucketSize; | ||
27339 | sxu32 nBucket; | ||
27340 | |||
27341 | if( nByte + sizeof(SyMemHeader) >= SXMEM_POOL_MAXALLOC ){ | ||
27342 | /* Allocate a big chunk directly */ | ||
27343 | pBucket = (SyMemHeader *)MemBackendAlloc(&(*pBackend), nByte+sizeof(SyMemHeader)); | ||
27344 | if( pBucket == 0 ){ | ||
27345 | return 0; | ||
27346 | } | ||
27347 | /* Record as big block */ | ||
27348 | pBucket->nBucket = (sxu32)(SXMEM_POOL_MAGIC << 16) | SXU16_HIGH; | ||
27349 | return (void *)(pBucket+1); | ||
27350 | } | ||
27351 | /* Locate the appropriate bucket */ | ||
27352 | nBucket = 0; | ||
27353 | nBucketSize = SXMEM_POOL_MINALLOC; | ||
27354 | while( nByte + sizeof(SyMemHeader) > nBucketSize ){ | ||
27355 | nBucketSize <<= 1; | ||
27356 | nBucket++; | ||
27357 | } | ||
27358 | pBucket = pBackend->apPool[nBucket]; | ||
27359 | if( pBucket == 0 ){ | ||
27360 | sxi32 rc; | ||
27361 | rc = MemPoolBucketAlloc(&(*pBackend), nBucket); | ||
27362 | if( rc != SXRET_OK ){ | ||
27363 | return 0; | ||
27364 | } | ||
27365 | pBucket = pBackend->apPool[nBucket]; | ||
27366 | } | ||
27367 | /* Remove from the free list */ | ||
27368 | pNext = pBucket->pNext; | ||
27369 | pBackend->apPool[nBucket] = pNext; | ||
27370 | /* Record bucket&magic number */ | ||
27371 | pBucket->nBucket = (SXMEM_POOL_MAGIC << 16) | nBucket; | ||
27372 | return (void *)&pBucket[1]; | ||
27373 | } | ||
27374 | JX9_PRIVATE void * SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte) | ||
27375 | { | ||
27376 | void *pChunk; | ||
27377 | #if defined(UNTRUST) | ||
27378 | if( SXMEM_BACKEND_CORRUPT(pBackend) ){ | ||
27379 | return 0; | ||
27380 | } | ||
27381 | #endif | ||
27382 | if( pBackend->pMutexMethods ){ | ||
27383 | SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); | ||
27384 | } | ||
27385 | pChunk = MemBackendPoolAlloc(&(*pBackend), nByte); | ||
27386 | if( pBackend->pMutexMethods ){ | ||
27387 | SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); | ||
27388 | } | ||
27389 | return pChunk; | ||
27390 | } | ||
27391 | static sxi32 MemBackendPoolFree(SyMemBackend *pBackend, void * pChunk) | ||
27392 | { | ||
27393 | SyMemHeader *pHeader; | ||
27394 | sxu32 nBucket; | ||
27395 | /* Get the corresponding bucket */ | ||
27396 | pHeader = (SyMemHeader *)(((char *)pChunk) - sizeof(SyMemHeader)); | ||
27397 | /* Sanity check to avoid misuse */ | ||
27398 | if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ | ||
27399 | return SXERR_CORRUPT; | ||
27400 | } | ||
27401 | nBucket = pHeader->nBucket & 0xFFFF; | ||
27402 | if( nBucket == SXU16_HIGH ){ | ||
27403 | /* Free the big block */ | ||
27404 | MemBackendFree(&(*pBackend), pHeader); | ||
27405 | }else{ | ||
27406 | /* Return to the free list */ | ||
27407 | pHeader->pNext = pBackend->apPool[nBucket & 0x0f]; | ||
27408 | pBackend->apPool[nBucket & 0x0f] = pHeader; | ||
27409 | } | ||
27410 | return SXRET_OK; | ||
27411 | } | ||
27412 | JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void * pChunk) | ||
27413 | { | ||
27414 | sxi32 rc; | ||
27415 | #if defined(UNTRUST) | ||
27416 | if( SXMEM_BACKEND_CORRUPT(pBackend) || pChunk == 0 ){ | ||
27417 | return SXERR_CORRUPT; | ||
27418 | } | ||
27419 | #endif | ||
27420 | if( pBackend->pMutexMethods ){ | ||
27421 | SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); | ||
27422 | } | ||
27423 | rc = MemBackendPoolFree(&(*pBackend), pChunk); | ||
27424 | if( pBackend->pMutexMethods ){ | ||
27425 | SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); | ||
27426 | } | ||
27427 | return rc; | ||
27428 | } | ||
27429 | #if 0 | ||
27430 | static void * MemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) | ||
27431 | { | ||
27432 | sxu32 nBucket, nBucketSize; | ||
27433 | SyMemHeader *pHeader; | ||
27434 | void * pNew; | ||
27435 | |||
27436 | if( pOld == 0 ){ | ||
27437 | /* Allocate a new pool */ | ||
27438 | pNew = MemBackendPoolAlloc(&(*pBackend), nByte); | ||
27439 | return pNew; | ||
27440 | } | ||
27441 | /* Get the corresponding bucket */ | ||
27442 | pHeader = (SyMemHeader *)(((char *)pOld) - sizeof(SyMemHeader)); | ||
27443 | /* Sanity check to avoid misuse */ | ||
27444 | if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ | ||
27445 | return 0; | ||
27446 | } | ||
27447 | nBucket = pHeader->nBucket & 0xFFFF; | ||
27448 | if( nBucket == SXU16_HIGH ){ | ||
27449 | /* Big block */ | ||
27450 | return MemBackendRealloc(&(*pBackend), pHeader, nByte); | ||
27451 | } | ||
27452 | nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); | ||
27453 | if( nBucketSize >= nByte + sizeof(SyMemHeader) ){ | ||
27454 | /* The old bucket can honor the requested size */ | ||
27455 | return pOld; | ||
27456 | } | ||
27457 | /* Allocate a new pool */ | ||
27458 | pNew = MemBackendPoolAlloc(&(*pBackend), nByte); | ||
27459 | if( pNew == 0 ){ | ||
27460 | return 0; | ||
27461 | } | ||
27462 | /* Copy the old data into the new block */ | ||
27463 | SyMemcpy(pOld, pNew, nBucketSize); | ||
27464 | /* Free the stale block */ | ||
27465 | MemBackendPoolFree(&(*pBackend), pOld); | ||
27466 | return pNew; | ||
27467 | } | ||
27468 | JX9_PRIVATE void * SyMemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) | ||
27469 | { | ||
27470 | void *pChunk; | ||
27471 | #if defined(UNTRUST) | ||
27472 | if( SXMEM_BACKEND_CORRUPT(pBackend) ){ | ||
27473 | return 0; | ||
27474 | } | ||
27475 | #endif | ||
27476 | if( pBackend->pMutexMethods ){ | ||
27477 | SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); | ||
27478 | } | ||
27479 | pChunk = MemBackendPoolRealloc(&(*pBackend), pOld, nByte); | ||
27480 | if( pBackend->pMutexMethods ){ | ||
27481 | SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); | ||
27482 | } | ||
27483 | return pChunk; | ||
27484 | } | ||
27485 | #endif | ||
27486 | JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void * pUserData) | ||
27487 | { | ||
27488 | #if defined(UNTRUST) | ||
27489 | if( pBackend == 0 ){ | ||
27490 | return SXERR_EMPTY; | ||
27491 | } | ||
27492 | #endif | ||
27493 | /* Zero the allocator first */ | ||
27494 | SyZero(&(*pBackend), sizeof(SyMemBackend)); | ||
27495 | pBackend->xMemError = xMemErr; | ||
27496 | pBackend->pUserData = pUserData; | ||
27497 | /* Switch to the OS memory allocator */ | ||
27498 | pBackend->pMethods = &sOSAllocMethods; | ||
27499 | if( pBackend->pMethods->xInit ){ | ||
27500 | /* Initialize the backend */ | ||
27501 | if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ | ||
27502 | return SXERR_ABORT; | ||
27503 | } | ||
27504 | } | ||
27505 | #if defined(UNTRUST) | ||
27506 | pBackend->nMagic = SXMEM_BACKEND_MAGIC; | ||
27507 | #endif | ||
27508 | return SXRET_OK; | ||
27509 | } | ||
27510 | JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void * pUserData) | ||
27511 | { | ||
27512 | #if defined(UNTRUST) | ||
27513 | if( pBackend == 0 || pMethods == 0){ | ||
27514 | return SXERR_EMPTY; | ||
27515 | } | ||
27516 | #endif | ||
27517 | if( pMethods->xAlloc == 0 || pMethods->xRealloc == 0 || pMethods->xFree == 0 || pMethods->xChunkSize == 0 ){ | ||
27518 | /* mandatory methods are missing */ | ||
27519 | return SXERR_INVALID; | ||
27520 | } | ||
27521 | /* Zero the allocator first */ | ||
27522 | SyZero(&(*pBackend), sizeof(SyMemBackend)); | ||
27523 | pBackend->xMemError = xMemErr; | ||
27524 | pBackend->pUserData = pUserData; | ||
27525 | /* Switch to the host application memory allocator */ | ||
27526 | pBackend->pMethods = pMethods; | ||
27527 | if( pBackend->pMethods->xInit ){ | ||
27528 | /* Initialize the backend */ | ||
27529 | if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ | ||
27530 | return SXERR_ABORT; | ||
27531 | } | ||
27532 | } | ||
27533 | #if defined(UNTRUST) | ||
27534 | pBackend->nMagic = SXMEM_BACKEND_MAGIC; | ||
27535 | #endif | ||
27536 | return SXRET_OK; | ||
27537 | } | ||
27538 | JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent) | ||
27539 | { | ||
27540 | sxu8 bInheritMutex; | ||
27541 | #if defined(UNTRUST) | ||
27542 | if( pBackend == 0 || SXMEM_BACKEND_CORRUPT(pParent) ){ | ||
27543 | return SXERR_CORRUPT; | ||
27544 | } | ||
27545 | #endif | ||
27546 | /* Zero the allocator first */ | ||
27547 | SyZero(&(*pBackend), sizeof(SyMemBackend)); | ||
27548 | pBackend->pMethods = pParent->pMethods; | ||
27549 | pBackend->xMemError = pParent->xMemError; | ||
27550 | pBackend->pUserData = pParent->pUserData; | ||
27551 | bInheritMutex = pParent->pMutexMethods ? TRUE : FALSE; | ||
27552 | if( bInheritMutex ){ | ||
27553 | pBackend->pMutexMethods = pParent->pMutexMethods; | ||
27554 | /* Create a private mutex */ | ||
27555 | pBackend->pMutex = pBackend->pMutexMethods->xNew(SXMUTEX_TYPE_FAST); | ||
27556 | if( pBackend->pMutex == 0){ | ||
27557 | return SXERR_OS; | ||
27558 | } | ||
27559 | } | ||
27560 | #if defined(UNTRUST) | ||
27561 | pBackend->nMagic = SXMEM_BACKEND_MAGIC; | ||
27562 | #endif | ||
27563 | return SXRET_OK; | ||
27564 | } | ||
27565 | static sxi32 MemBackendRelease(SyMemBackend *pBackend) | ||
27566 | { | ||
27567 | SyMemBlock *pBlock, *pNext; | ||
27568 | |||
27569 | pBlock = pBackend->pBlocks; | ||
27570 | for(;;){ | ||
27571 | if( pBackend->nBlock == 0 ){ | ||
27572 | break; | ||
27573 | } | ||
27574 | pNext = pBlock->pNext; | ||
27575 | pBackend->pMethods->xFree(pBlock); | ||
27576 | pBlock = pNext; | ||
27577 | pBackend->nBlock--; | ||
27578 | /* LOOP ONE */ | ||
27579 | if( pBackend->nBlock == 0 ){ | ||
27580 | break; | ||
27581 | } | ||
27582 | pNext = pBlock->pNext; | ||
27583 | pBackend->pMethods->xFree(pBlock); | ||
27584 | pBlock = pNext; | ||
27585 | pBackend->nBlock--; | ||
27586 | /* LOOP TWO */ | ||
27587 | if( pBackend->nBlock == 0 ){ | ||
27588 | break; | ||
27589 | } | ||
27590 | pNext = pBlock->pNext; | ||
27591 | pBackend->pMethods->xFree(pBlock); | ||
27592 | pBlock = pNext; | ||
27593 | pBackend->nBlock--; | ||
27594 | /* LOOP THREE */ | ||
27595 | if( pBackend->nBlock == 0 ){ | ||
27596 | break; | ||
27597 | } | ||
27598 | pNext = pBlock->pNext; | ||
27599 | pBackend->pMethods->xFree(pBlock); | ||
27600 | pBlock = pNext; | ||
27601 | pBackend->nBlock--; | ||
27602 | /* LOOP FOUR */ | ||
27603 | } | ||
27604 | if( pBackend->pMethods->xRelease ){ | ||
27605 | pBackend->pMethods->xRelease(pBackend->pMethods->pUserData); | ||
27606 | } | ||
27607 | pBackend->pMethods = 0; | ||
27608 | pBackend->pBlocks = 0; | ||
27609 | #if defined(UNTRUST) | ||
27610 | pBackend->nMagic = 0x2626; | ||
27611 | #endif | ||
27612 | return SXRET_OK; | ||
27613 | } | ||
27614 | JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend) | ||
27615 | { | ||
27616 | sxi32 rc; | ||
27617 | #if defined(UNTRUST) | ||
27618 | if( SXMEM_BACKEND_CORRUPT(pBackend) ){ | ||
27619 | return SXERR_INVALID; | ||
27620 | } | ||
27621 | #endif | ||
27622 | if( pBackend->pMutexMethods ){ | ||
27623 | SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); | ||
27624 | } | ||
27625 | rc = MemBackendRelease(&(*pBackend)); | ||
27626 | if( pBackend->pMutexMethods ){ | ||
27627 | SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); | ||
27628 | SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex); | ||
27629 | } | ||
27630 | return rc; | ||
27631 | } | ||
27632 | JX9_PRIVATE void * SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize) | ||
27633 | { | ||
27634 | void *pNew; | ||
27635 | #if defined(UNTRUST) | ||
27636 | if( pSrc == 0 || nSize <= 0 ){ | ||
27637 | return 0; | ||
27638 | } | ||
27639 | #endif | ||
27640 | pNew = SyMemBackendAlloc(&(*pBackend), nSize); | ||
27641 | if( pNew ){ | ||
27642 | SyMemcpy(pSrc, pNew, nSize); | ||
27643 | } | ||
27644 | return pNew; | ||
27645 | } | ||
27646 | JX9_PRIVATE char * SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize) | ||
27647 | { | ||
27648 | char *zDest; | ||
27649 | zDest = (char *)SyMemBackendAlloc(&(*pBackend), nSize + 1); | ||
27650 | if( zDest ){ | ||
27651 | Systrcpy(zDest, nSize+1, zSrc, nSize); | ||
27652 | } | ||
27653 | return zDest; | ||
27654 | } | ||
27655 | JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize) | ||
27656 | { | ||
27657 | #if defined(UNTRUST) | ||
27658 | if( pBlob == 0 || pBuffer == 0 || nSize < 1 ){ | ||
27659 | return SXERR_EMPTY; | ||
27660 | } | ||
27661 | #endif | ||
27662 | pBlob->pBlob = pBuffer; | ||
27663 | pBlob->mByte = nSize; | ||
27664 | pBlob->nByte = 0; | ||
27665 | pBlob->pAllocator = 0; | ||
27666 | pBlob->nFlags = SXBLOB_LOCKED|SXBLOB_STATIC; | ||
27667 | return SXRET_OK; | ||
27668 | } | ||
27669 | JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator) | ||
27670 | { | ||
27671 | #if defined(UNTRUST) | ||
27672 | if( pBlob == 0 ){ | ||
27673 | return SXERR_EMPTY; | ||
27674 | } | ||
27675 | #endif | ||
27676 | pBlob->pBlob = 0; | ||
27677 | pBlob->mByte = pBlob->nByte = 0; | ||
27678 | pBlob->pAllocator = &(*pAllocator); | ||
27679 | pBlob->nFlags = 0; | ||
27680 | return SXRET_OK; | ||
27681 | } | ||
27682 | JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte) | ||
27683 | { | ||
27684 | #if defined(UNTRUST) | ||
27685 | if( pBlob == 0 ){ | ||
27686 | return SXERR_EMPTY; | ||
27687 | } | ||
27688 | #endif | ||
27689 | pBlob->pBlob = (void *)pData; | ||
27690 | pBlob->nByte = nByte; | ||
27691 | pBlob->mByte = 0; | ||
27692 | pBlob->nFlags |= SXBLOB_RDONLY; | ||
27693 | return SXRET_OK; | ||
27694 | } | ||
27695 | #ifndef SXBLOB_MIN_GROWTH | ||
27696 | #define SXBLOB_MIN_GROWTH 16 | ||
27697 | #endif | ||
27698 | static sxi32 BlobPrepareGrow(SyBlob *pBlob, sxu32 *pByte) | ||
27699 | { | ||
27700 | sxu32 nByte; | ||
27701 | void *pNew; | ||
27702 | nByte = *pByte; | ||
27703 | if( pBlob->nFlags & (SXBLOB_LOCKED|SXBLOB_STATIC) ){ | ||
27704 | if ( SyBlobFreeSpace(pBlob) < nByte ){ | ||
27705 | *pByte = SyBlobFreeSpace(pBlob); | ||
27706 | if( (*pByte) == 0 ){ | ||
27707 | return SXERR_SHORT; | ||
27708 | } | ||
27709 | } | ||
27710 | return SXRET_OK; | ||
27711 | } | ||
27712 | if( pBlob->nFlags & SXBLOB_RDONLY ){ | ||
27713 | /* Make a copy of the read-only item */ | ||
27714 | if( pBlob->nByte > 0 ){ | ||
27715 | pNew = SyMemBackendDup(pBlob->pAllocator, pBlob->pBlob, pBlob->nByte); | ||
27716 | if( pNew == 0 ){ | ||
27717 | return SXERR_MEM; | ||
27718 | } | ||
27719 | pBlob->pBlob = pNew; | ||
27720 | pBlob->mByte = pBlob->nByte; | ||
27721 | }else{ | ||
27722 | pBlob->pBlob = 0; | ||
27723 | pBlob->mByte = 0; | ||
27724 | } | ||
27725 | /* Remove the read-only flag */ | ||
27726 | pBlob->nFlags &= ~SXBLOB_RDONLY; | ||
27727 | } | ||
27728 | if( SyBlobFreeSpace(pBlob) >= nByte ){ | ||
27729 | return SXRET_OK; | ||
27730 | } | ||
27731 | if( pBlob->mByte > 0 ){ | ||
27732 | nByte = nByte + pBlob->mByte * 2 + SXBLOB_MIN_GROWTH; | ||
27733 | }else if ( nByte < SXBLOB_MIN_GROWTH ){ | ||
27734 | nByte = SXBLOB_MIN_GROWTH; | ||
27735 | } | ||
27736 | pNew = SyMemBackendRealloc(pBlob->pAllocator, pBlob->pBlob, nByte); | ||
27737 | if( pNew == 0 ){ | ||
27738 | return SXERR_MEM; | ||
27739 | } | ||
27740 | pBlob->pBlob = pNew; | ||
27741 | pBlob->mByte = nByte; | ||
27742 | return SXRET_OK; | ||
27743 | } | ||
27744 | JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize) | ||
27745 | { | ||
27746 | sxu8 *zBlob; | ||
27747 | sxi32 rc; | ||
27748 | if( nSize < 1 ){ | ||
27749 | return SXRET_OK; | ||
27750 | } | ||
27751 | rc = BlobPrepareGrow(&(*pBlob), &nSize); | ||
27752 | if( SXRET_OK != rc ){ | ||
27753 | return rc; | ||
27754 | } | ||
27755 | if( pData ){ | ||
27756 | zBlob = (sxu8 *)pBlob->pBlob ; | ||
27757 | zBlob = &zBlob[pBlob->nByte]; | ||
27758 | pBlob->nByte += nSize; | ||
27759 | SX_MACRO_FAST_MEMCPY(pData, zBlob, nSize); | ||
27760 | } | ||
27761 | return SXRET_OK; | ||
27762 | } | ||
27763 | JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob) | ||
27764 | { | ||
27765 | sxi32 rc; | ||
27766 | sxu32 n; | ||
27767 | n = pBlob->nByte; | ||
27768 | rc = SyBlobAppend(&(*pBlob), (const void *)"\0", sizeof(char)); | ||
27769 | if (rc == SXRET_OK ){ | ||
27770 | pBlob->nByte = n; | ||
27771 | } | ||
27772 | return rc; | ||
27773 | } | ||
27774 | JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest) | ||
27775 | { | ||
27776 | sxi32 rc = SXRET_OK; | ||
27777 | if( pSrc->nByte > 0 ){ | ||
27778 | rc = SyBlobAppend(&(*pDest), pSrc->pBlob, pSrc->nByte); | ||
27779 | } | ||
27780 | return rc; | ||
27781 | } | ||
27782 | JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob) | ||
27783 | { | ||
27784 | pBlob->nByte = 0; | ||
27785 | if( pBlob->nFlags & SXBLOB_RDONLY ){ | ||
27786 | /* Read-only (Not malloced chunk) */ | ||
27787 | pBlob->pBlob = 0; | ||
27788 | pBlob->mByte = 0; | ||
27789 | pBlob->nFlags &= ~SXBLOB_RDONLY; | ||
27790 | } | ||
27791 | return SXRET_OK; | ||
27792 | } | ||
27793 | JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen) | ||
27794 | { | ||
27795 | if( nNewLen < pBlob->nByte ){ | ||
27796 | pBlob->nByte = nNewLen; | ||
27797 | } | ||
27798 | return SXRET_OK; | ||
27799 | } | ||
27800 | JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob) | ||
27801 | { | ||
27802 | if( (pBlob->nFlags & (SXBLOB_STATIC|SXBLOB_RDONLY)) == 0 && pBlob->mByte > 0 ){ | ||
27803 | SyMemBackendFree(pBlob->pAllocator, pBlob->pBlob); | ||
27804 | } | ||
27805 | pBlob->pBlob = 0; | ||
27806 | pBlob->nByte = pBlob->mByte = 0; | ||
27807 | pBlob->nFlags = 0; | ||
27808 | return SXRET_OK; | ||
27809 | } | ||
27810 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
27811 | JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft) | ||
27812 | { | ||
27813 | const char *zIn = (const char *)pBlob; | ||
27814 | const char *zEnd; | ||
27815 | sxi32 rc; | ||
27816 | if( pLen > nLen ){ | ||
27817 | return SXERR_NOTFOUND; | ||
27818 | } | ||
27819 | zEnd = &zIn[nLen-pLen]; | ||
27820 | for(;;){ | ||
27821 | 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++; | ||
27822 | 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++; | ||
27823 | 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++; | ||
27824 | 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++; | ||
27825 | } | ||
27826 | return SXERR_NOTFOUND; | ||
27827 | } | ||
27828 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
27829 | /* SyRunTimeApi:sxds.c */ | ||
27830 | JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize) | ||
27831 | { | ||
27832 | pSet->nSize = 0 ; | ||
27833 | pSet->nUsed = 0; | ||
27834 | pSet->nCursor = 0; | ||
27835 | pSet->eSize = ElemSize; | ||
27836 | pSet->pAllocator = pAllocator; | ||
27837 | pSet->pBase = 0; | ||
27838 | pSet->pUserData = 0; | ||
27839 | return SXRET_OK; | ||
27840 | } | ||
27841 | JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem) | ||
27842 | { | ||
27843 | unsigned char *zbase; | ||
27844 | if( pSet->nUsed >= pSet->nSize ){ | ||
27845 | void *pNew; | ||
27846 | if( pSet->pAllocator == 0 ){ | ||
27847 | return SXERR_LOCKED; | ||
27848 | } | ||
27849 | if( pSet->nSize <= 0 ){ | ||
27850 | pSet->nSize = 4; | ||
27851 | } | ||
27852 | pNew = SyMemBackendRealloc(pSet->pAllocator, pSet->pBase, pSet->eSize * pSet->nSize * 2); | ||
27853 | if( pNew == 0 ){ | ||
27854 | return SXERR_MEM; | ||
27855 | } | ||
27856 | pSet->pBase = pNew; | ||
27857 | pSet->nSize <<= 1; | ||
27858 | } | ||
27859 | zbase = (unsigned char *)pSet->pBase; | ||
27860 | SX_MACRO_FAST_MEMCPY(pItem, &zbase[pSet->nUsed * pSet->eSize], pSet->eSize); | ||
27861 | pSet->nUsed++; | ||
27862 | return SXRET_OK; | ||
27863 | } | ||
27864 | JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem) | ||
27865 | { | ||
27866 | if( pSet->nSize > 0 ){ | ||
27867 | return SXERR_LOCKED; | ||
27868 | } | ||
27869 | if( nItem < 8 ){ | ||
27870 | nItem = 8; | ||
27871 | } | ||
27872 | pSet->pBase = SyMemBackendAlloc(pSet->pAllocator, pSet->eSize * nItem); | ||
27873 | if( pSet->pBase == 0 ){ | ||
27874 | return SXERR_MEM; | ||
27875 | } | ||
27876 | pSet->nSize = nItem; | ||
27877 | return SXRET_OK; | ||
27878 | } | ||
27879 | JX9_PRIVATE sxi32 SySetReset(SySet *pSet) | ||
27880 | { | ||
27881 | pSet->nUsed = 0; | ||
27882 | pSet->nCursor = 0; | ||
27883 | return SXRET_OK; | ||
27884 | } | ||
27885 | JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet) | ||
27886 | { | ||
27887 | pSet->nCursor = 0; | ||
27888 | return SXRET_OK; | ||
27889 | } | ||
27890 | JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry) | ||
27891 | { | ||
27892 | register unsigned char *zSrc; | ||
27893 | if( pSet->nCursor >= pSet->nUsed ){ | ||
27894 | /* Reset cursor */ | ||
27895 | pSet->nCursor = 0; | ||
27896 | return SXERR_EOF; | ||
27897 | } | ||
27898 | zSrc = (unsigned char *)SySetBasePtr(pSet); | ||
27899 | if( ppEntry ){ | ||
27900 | *ppEntry = (void *)&zSrc[pSet->nCursor * pSet->eSize]; | ||
27901 | } | ||
27902 | pSet->nCursor++; | ||
27903 | return SXRET_OK; | ||
27904 | } | ||
27905 | JX9_PRIVATE sxi32 SySetRelease(SySet *pSet) | ||
27906 | { | ||
27907 | sxi32 rc = SXRET_OK; | ||
27908 | if( pSet->pAllocator && pSet->pBase ){ | ||
27909 | rc = SyMemBackendFree(pSet->pAllocator, pSet->pBase); | ||
27910 | } | ||
27911 | pSet->pBase = 0; | ||
27912 | pSet->nUsed = 0; | ||
27913 | pSet->nCursor = 0; | ||
27914 | return rc; | ||
27915 | } | ||
27916 | JX9_PRIVATE void * SySetPeek(SySet *pSet) | ||
27917 | { | ||
27918 | const char *zBase; | ||
27919 | if( pSet->nUsed <= 0 ){ | ||
27920 | return 0; | ||
27921 | } | ||
27922 | zBase = (const char *)pSet->pBase; | ||
27923 | return (void *)&zBase[(pSet->nUsed - 1) * pSet->eSize]; | ||
27924 | } | ||
27925 | JX9_PRIVATE void * SySetPop(SySet *pSet) | ||
27926 | { | ||
27927 | const char *zBase; | ||
27928 | void *pData; | ||
27929 | if( pSet->nUsed <= 0 ){ | ||
27930 | return 0; | ||
27931 | } | ||
27932 | zBase = (const char *)pSet->pBase; | ||
27933 | pSet->nUsed--; | ||
27934 | pData = (void *)&zBase[pSet->nUsed * pSet->eSize]; | ||
27935 | return pData; | ||
27936 | } | ||
27937 | JX9_PRIVATE void * SySetAt(SySet *pSet, sxu32 nIdx) | ||
27938 | { | ||
27939 | const char *zBase; | ||
27940 | if( nIdx >= pSet->nUsed ){ | ||
27941 | /* Out of range */ | ||
27942 | return 0; | ||
27943 | } | ||
27944 | zBase = (const char *)pSet->pBase; | ||
27945 | return (void *)&zBase[nIdx * pSet->eSize]; | ||
27946 | } | ||
27947 | /* Private hash entry */ | ||
27948 | struct SyHashEntry_Pr | ||
27949 | { | ||
27950 | const void *pKey; /* Hash key */ | ||
27951 | sxu32 nKeyLen; /* Key length */ | ||
27952 | void *pUserData; /* User private data */ | ||
27953 | /* Private fields */ | ||
27954 | sxu32 nHash; | ||
27955 | SyHash *pHash; | ||
27956 | SyHashEntry_Pr *pNext, *pPrev; /* Next and previous entry in the list */ | ||
27957 | SyHashEntry_Pr *pNextCollide, *pPrevCollide; /* Collision list */ | ||
27958 | }; | ||
27959 | #define INVALID_HASH(H) ((H)->apBucket == 0) | ||
27960 | JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp) | ||
27961 | { | ||
27962 | SyHashEntry_Pr **apNew; | ||
27963 | #if defined(UNTRUST) | ||
27964 | if( pHash == 0 ){ | ||
27965 | return SXERR_EMPTY; | ||
27966 | } | ||
27967 | #endif | ||
27968 | /* Allocate a new table */ | ||
27969 | apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(&(*pAllocator), sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); | ||
27970 | if( apNew == 0 ){ | ||
27971 | return SXERR_MEM; | ||
27972 | } | ||
27973 | SyZero((void *)apNew, sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); | ||
27974 | pHash->pAllocator = &(*pAllocator); | ||
27975 | pHash->xHash = xHash ? xHash : SyBinHash; | ||
27976 | pHash->xCmp = xCmp ? xCmp : SyMemcmp; | ||
27977 | pHash->pCurrent = pHash->pList = 0; | ||
27978 | pHash->nEntry = 0; | ||
27979 | pHash->apBucket = apNew; | ||
27980 | pHash->nBucketSize = SXHASH_BUCKET_SIZE; | ||
27981 | return SXRET_OK; | ||
27982 | } | ||
27983 | JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash) | ||
27984 | { | ||
27985 | SyHashEntry_Pr *pEntry, *pNext; | ||
27986 | #if defined(UNTRUST) | ||
27987 | if( INVALID_HASH(pHash) ){ | ||
27988 | return SXERR_EMPTY; | ||
27989 | } | ||
27990 | #endif | ||
27991 | pEntry = pHash->pList; | ||
27992 | for(;;){ | ||
27993 | if( pHash->nEntry == 0 ){ | ||
27994 | break; | ||
27995 | } | ||
27996 | pNext = pEntry->pNext; | ||
27997 | SyMemBackendPoolFree(pHash->pAllocator, pEntry); | ||
27998 | pEntry = pNext; | ||
27999 | pHash->nEntry--; | ||
28000 | } | ||
28001 | if( pHash->apBucket ){ | ||
28002 | SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket); | ||
28003 | } | ||
28004 | pHash->apBucket = 0; | ||
28005 | pHash->nBucketSize = 0; | ||
28006 | pHash->pAllocator = 0; | ||
28007 | return SXRET_OK; | ||
28008 | } | ||
28009 | static SyHashEntry_Pr * HashGetEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen) | ||
28010 | { | ||
28011 | SyHashEntry_Pr *pEntry; | ||
28012 | sxu32 nHash; | ||
28013 | |||
28014 | nHash = pHash->xHash(pKey, nKeyLen); | ||
28015 | pEntry = pHash->apBucket[nHash & (pHash->nBucketSize - 1)]; | ||
28016 | for(;;){ | ||
28017 | if( pEntry == 0 ){ | ||
28018 | break; | ||
28019 | } | ||
28020 | if( pEntry->nHash == nHash && pEntry->nKeyLen == nKeyLen && | ||
28021 | pHash->xCmp(pEntry->pKey, pKey, nKeyLen) == 0 ){ | ||
28022 | return pEntry; | ||
28023 | } | ||
28024 | pEntry = pEntry->pNextCollide; | ||
28025 | } | ||
28026 | /* Entry not found */ | ||
28027 | return 0; | ||
28028 | } | ||
28029 | JX9_PRIVATE SyHashEntry * SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen) | ||
28030 | { | ||
28031 | SyHashEntry_Pr *pEntry; | ||
28032 | #if defined(UNTRUST) | ||
28033 | if( INVALID_HASH(pHash) ){ | ||
28034 | return 0; | ||
28035 | } | ||
28036 | #endif | ||
28037 | if( pHash->nEntry < 1 || nKeyLen < 1 ){ | ||
28038 | /* Don't bother hashing, return immediately */ | ||
28039 | return 0; | ||
28040 | } | ||
28041 | pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen); | ||
28042 | if( pEntry == 0 ){ | ||
28043 | return 0; | ||
28044 | } | ||
28045 | return (SyHashEntry *)pEntry; | ||
28046 | } | ||
28047 | static sxi32 HashDeleteEntry(SyHash *pHash, SyHashEntry_Pr *pEntry, void **ppUserData) | ||
28048 | { | ||
28049 | sxi32 rc; | ||
28050 | if( pEntry->pPrevCollide == 0 ){ | ||
28051 | pHash->apBucket[pEntry->nHash & (pHash->nBucketSize - 1)] = pEntry->pNextCollide; | ||
28052 | }else{ | ||
28053 | pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; | ||
28054 | } | ||
28055 | if( pEntry->pNextCollide ){ | ||
28056 | pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; | ||
28057 | } | ||
28058 | MACRO_LD_REMOVE(pHash->pList, pEntry); | ||
28059 | pHash->nEntry--; | ||
28060 | if( ppUserData ){ | ||
28061 | /* Write a pointer to the user data */ | ||
28062 | *ppUserData = pEntry->pUserData; | ||
28063 | } | ||
28064 | /* Release the entry */ | ||
28065 | rc = SyMemBackendPoolFree(pHash->pAllocator, pEntry); | ||
28066 | return rc; | ||
28067 | } | ||
28068 | JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData) | ||
28069 | { | ||
28070 | SyHashEntry_Pr *pEntry; | ||
28071 | sxi32 rc; | ||
28072 | #if defined(UNTRUST) | ||
28073 | if( INVALID_HASH(pHash) ){ | ||
28074 | return SXERR_CORRUPT; | ||
28075 | } | ||
28076 | #endif | ||
28077 | pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen); | ||
28078 | if( pEntry == 0 ){ | ||
28079 | return SXERR_NOTFOUND; | ||
28080 | } | ||
28081 | rc = HashDeleteEntry(&(*pHash), pEntry, ppUserData); | ||
28082 | return rc; | ||
28083 | } | ||
28084 | JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32 (*xStep)(SyHashEntry *, void *), void *pUserData) | ||
28085 | { | ||
28086 | SyHashEntry_Pr *pEntry; | ||
28087 | sxi32 rc; | ||
28088 | sxu32 n; | ||
28089 | #if defined(UNTRUST) | ||
28090 | if( INVALID_HASH(pHash) || xStep == 0){ | ||
28091 | return 0; | ||
28092 | } | ||
28093 | #endif | ||
28094 | pEntry = pHash->pList; | ||
28095 | for( n = 0 ; n < pHash->nEntry ; n++ ){ | ||
28096 | /* Invoke the callback */ | ||
28097 | rc = xStep((SyHashEntry *)pEntry, pUserData); | ||
28098 | if( rc != SXRET_OK ){ | ||
28099 | return rc; | ||
28100 | } | ||
28101 | /* Point to the next entry */ | ||
28102 | pEntry = pEntry->pNext; | ||
28103 | } | ||
28104 | return SXRET_OK; | ||
28105 | } | ||
28106 | static sxi32 HashGrowTable(SyHash *pHash) | ||
28107 | { | ||
28108 | sxu32 nNewSize = pHash->nBucketSize * 2; | ||
28109 | SyHashEntry_Pr *pEntry; | ||
28110 | SyHashEntry_Pr **apNew; | ||
28111 | sxu32 n, iBucket; | ||
28112 | |||
28113 | /* Allocate a new larger table */ | ||
28114 | apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(pHash->pAllocator, nNewSize * sizeof(SyHashEntry_Pr *)); | ||
28115 | if( apNew == 0 ){ | ||
28116 | /* Not so fatal, simply a performance hit */ | ||
28117 | return SXRET_OK; | ||
28118 | } | ||
28119 | /* Zero the new table */ | ||
28120 | SyZero((void *)apNew, nNewSize * sizeof(SyHashEntry_Pr *)); | ||
28121 | /* Rehash all entries */ | ||
28122 | for( n = 0, pEntry = pHash->pList; n < pHash->nEntry ; n++ ){ | ||
28123 | pEntry->pNextCollide = pEntry->pPrevCollide = 0; | ||
28124 | /* Install in the new bucket */ | ||
28125 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
28126 | pEntry->pNextCollide = apNew[iBucket]; | ||
28127 | if( apNew[iBucket] != 0 ){ | ||
28128 | apNew[iBucket]->pPrevCollide = pEntry; | ||
28129 | } | ||
28130 | apNew[iBucket] = pEntry; | ||
28131 | /* Point to the next entry */ | ||
28132 | pEntry = pEntry->pNext; | ||
28133 | } | ||
28134 | /* Release the old table and reflect the change */ | ||
28135 | SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket); | ||
28136 | pHash->apBucket = apNew; | ||
28137 | pHash->nBucketSize = nNewSize; | ||
28138 | return SXRET_OK; | ||
28139 | } | ||
28140 | static sxi32 HashInsert(SyHash *pHash, SyHashEntry_Pr *pEntry) | ||
28141 | { | ||
28142 | sxu32 iBucket = pEntry->nHash & (pHash->nBucketSize - 1); | ||
28143 | /* Insert the entry in its corresponding bcuket */ | ||
28144 | pEntry->pNextCollide = pHash->apBucket[iBucket]; | ||
28145 | if( pHash->apBucket[iBucket] != 0 ){ | ||
28146 | pHash->apBucket[iBucket]->pPrevCollide = pEntry; | ||
28147 | } | ||
28148 | pHash->apBucket[iBucket] = pEntry; | ||
28149 | /* Link to the entry list */ | ||
28150 | MACRO_LD_PUSH(pHash->pList, pEntry); | ||
28151 | if( pHash->nEntry == 0 ){ | ||
28152 | pHash->pCurrent = pHash->pList; | ||
28153 | } | ||
28154 | pHash->nEntry++; | ||
28155 | return SXRET_OK; | ||
28156 | } | ||
28157 | JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData) | ||
28158 | { | ||
28159 | SyHashEntry_Pr *pEntry; | ||
28160 | sxi32 rc; | ||
28161 | #if defined(UNTRUST) | ||
28162 | if( INVALID_HASH(pHash) || pKey == 0 ){ | ||
28163 | return SXERR_CORRUPT; | ||
28164 | } | ||
28165 | #endif | ||
28166 | if( pHash->nEntry >= pHash->nBucketSize * SXHASH_FILL_FACTOR ){ | ||
28167 | rc = HashGrowTable(&(*pHash)); | ||
28168 | if( rc != SXRET_OK ){ | ||
28169 | return rc; | ||
28170 | } | ||
28171 | } | ||
28172 | /* Allocate a new hash entry */ | ||
28173 | pEntry = (SyHashEntry_Pr *)SyMemBackendPoolAlloc(pHash->pAllocator, sizeof(SyHashEntry_Pr)); | ||
28174 | if( pEntry == 0 ){ | ||
28175 | return SXERR_MEM; | ||
28176 | } | ||
28177 | /* Zero the entry */ | ||
28178 | SyZero(pEntry, sizeof(SyHashEntry_Pr)); | ||
28179 | pEntry->pHash = pHash; | ||
28180 | pEntry->pKey = pKey; | ||
28181 | pEntry->nKeyLen = nKeyLen; | ||
28182 | pEntry->pUserData = pUserData; | ||
28183 | pEntry->nHash = pHash->xHash(pEntry->pKey, pEntry->nKeyLen); | ||
28184 | /* Finally insert the entry in its corresponding bucket */ | ||
28185 | rc = HashInsert(&(*pHash), pEntry); | ||
28186 | return rc; | ||
28187 | } | ||
28188 | /* SyRunTimeApi:sxutils.c */ | ||
28189 | JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail) | ||
28190 | { | ||
28191 | const char *zCur, *zEnd; | ||
28192 | #ifdef UNTRUST | ||
28193 | if( SX_EMPTY_STR(zSrc) ){ | ||
28194 | return SXERR_EMPTY; | ||
28195 | } | ||
28196 | #endif | ||
28197 | zEnd = &zSrc[nLen]; | ||
28198 | /* Jump leading white spaces */ | ||
28199 | while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisSpace(zSrc[0]) ){ | ||
28200 | zSrc++; | ||
28201 | } | ||
28202 | if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ | ||
28203 | zSrc++; | ||
28204 | } | ||
28205 | zCur = zSrc; | ||
28206 | if( pReal ){ | ||
28207 | *pReal = FALSE; | ||
28208 | } | ||
28209 | for(;;){ | ||
28210 | if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; | ||
28211 | if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; | ||
28212 | if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; | ||
28213 | if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; | ||
28214 | }; | ||
28215 | if( zSrc < zEnd && zSrc > zCur ){ | ||
28216 | int c = zSrc[0]; | ||
28217 | if( c == '.' ){ | ||
28218 | zSrc++; | ||
28219 | if( pReal ){ | ||
28220 | *pReal = TRUE; | ||
28221 | } | ||
28222 | if( pzTail ){ | ||
28223 | while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ | ||
28224 | zSrc++; | ||
28225 | } | ||
28226 | if( zSrc < zEnd && (zSrc[0] == 'e' || zSrc[0] == 'E') ){ | ||
28227 | zSrc++; | ||
28228 | if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ | ||
28229 | zSrc++; | ||
28230 | } | ||
28231 | while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ | ||
28232 | zSrc++; | ||
28233 | } | ||
28234 | } | ||
28235 | } | ||
28236 | }else if( c == 'e' || c == 'E' ){ | ||
28237 | zSrc++; | ||
28238 | if( pReal ){ | ||
28239 | *pReal = TRUE; | ||
28240 | } | ||
28241 | if( pzTail ){ | ||
28242 | if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ | ||
28243 | zSrc++; | ||
28244 | } | ||
28245 | while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ | ||
28246 | zSrc++; | ||
28247 | } | ||
28248 | } | ||
28249 | } | ||
28250 | } | ||
28251 | if( pzTail ){ | ||
28252 | /* Point to the non numeric part */ | ||
28253 | *pzTail = zSrc; | ||
28254 | } | ||
28255 | return zSrc > zCur ? SXRET_OK /* String prefix is numeric */ : SXERR_INVALID /* Not a digit stream */; | ||
28256 | } | ||
28257 | #define SXINT32_MIN_STR "2147483648" | ||
28258 | #define SXINT32_MAX_STR "2147483647" | ||
28259 | #define SXINT64_MIN_STR "9223372036854775808" | ||
28260 | #define SXINT64_MAX_STR "9223372036854775807" | ||
28261 | JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) | ||
28262 | { | ||
28263 | int isNeg = FALSE; | ||
28264 | const char *zEnd; | ||
28265 | sxi32 nVal = 0; | ||
28266 | sxi16 i; | ||
28267 | #if defined(UNTRUST) | ||
28268 | if( SX_EMPTY_STR(zSrc) ){ | ||
28269 | if( pOutVal ){ | ||
28270 | *(sxi32 *)pOutVal = 0; | ||
28271 | } | ||
28272 | return SXERR_EMPTY; | ||
28273 | } | ||
28274 | #endif | ||
28275 | zEnd = &zSrc[nLen]; | ||
28276 | while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28277 | zSrc++; | ||
28278 | } | ||
28279 | if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ | ||
28280 | isNeg = (zSrc[0] == '-') ? TRUE :FALSE; | ||
28281 | zSrc++; | ||
28282 | } | ||
28283 | /* Skip leading zero */ | ||
28284 | while(zSrc < zEnd && zSrc[0] == '0' ){ | ||
28285 | zSrc++; | ||
28286 | } | ||
28287 | i = 10; | ||
28288 | if( (sxu32)(zEnd-zSrc) >= 10 ){ | ||
28289 | /* Handle overflow */ | ||
28290 | i = SyMemcmp(zSrc, (isNeg == TRUE) ? SXINT32_MIN_STR : SXINT32_MAX_STR, nLen) <= 0 ? 10 : 9; | ||
28291 | } | ||
28292 | for(;;){ | ||
28293 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28294 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28295 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28296 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28297 | } | ||
28298 | /* Skip trailing spaces */ | ||
28299 | while(zSrc < zEnd && SyisSpace(zSrc[0])){ | ||
28300 | zSrc++; | ||
28301 | } | ||
28302 | if( zRest ){ | ||
28303 | *zRest = (char *)zSrc; | ||
28304 | } | ||
28305 | if( pOutVal ){ | ||
28306 | if( isNeg == TRUE && nVal != 0 ){ | ||
28307 | nVal = -nVal; | ||
28308 | } | ||
28309 | *(sxi32 *)pOutVal = nVal; | ||
28310 | } | ||
28311 | return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; | ||
28312 | } | ||
28313 | JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) | ||
28314 | { | ||
28315 | int isNeg = FALSE; | ||
28316 | const char *zEnd; | ||
28317 | sxi64 nVal; | ||
28318 | sxi16 i; | ||
28319 | #if defined(UNTRUST) | ||
28320 | if( SX_EMPTY_STR(zSrc) ){ | ||
28321 | if( pOutVal ){ | ||
28322 | *(sxi32 *)pOutVal = 0; | ||
28323 | } | ||
28324 | return SXERR_EMPTY; | ||
28325 | } | ||
28326 | #endif | ||
28327 | zEnd = &zSrc[nLen]; | ||
28328 | while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28329 | zSrc++; | ||
28330 | } | ||
28331 | if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ | ||
28332 | isNeg = (zSrc[0] == '-') ? TRUE :FALSE; | ||
28333 | zSrc++; | ||
28334 | } | ||
28335 | /* Skip leading zero */ | ||
28336 | while(zSrc < zEnd && zSrc[0] == '0' ){ | ||
28337 | zSrc++; | ||
28338 | } | ||
28339 | i = 19; | ||
28340 | if( (sxu32)(zEnd-zSrc) >= 19 ){ | ||
28341 | i = SyMemcmp(zSrc, isNeg ? SXINT64_MIN_STR : SXINT64_MAX_STR, 19) <= 0 ? 19 : 18 ; | ||
28342 | } | ||
28343 | nVal = 0; | ||
28344 | for(;;){ | ||
28345 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28346 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28347 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28348 | if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; | ||
28349 | } | ||
28350 | /* Skip trailing spaces */ | ||
28351 | while(zSrc < zEnd && SyisSpace(zSrc[0])){ | ||
28352 | zSrc++; | ||
28353 | } | ||
28354 | if( zRest ){ | ||
28355 | *zRest = (char *)zSrc; | ||
28356 | } | ||
28357 | if( pOutVal ){ | ||
28358 | if( isNeg == TRUE && nVal != 0 ){ | ||
28359 | nVal = -nVal; | ||
28360 | } | ||
28361 | *(sxi64 *)pOutVal = nVal; | ||
28362 | } | ||
28363 | return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; | ||
28364 | } | ||
28365 | JX9_PRIVATE sxi32 SyHexToint(sxi32 c) | ||
28366 | { | ||
28367 | switch(c){ | ||
28368 | case '0': return 0; | ||
28369 | case '1': return 1; | ||
28370 | case '2': return 2; | ||
28371 | case '3': return 3; | ||
28372 | case '4': return 4; | ||
28373 | case '5': return 5; | ||
28374 | case '6': return 6; | ||
28375 | case '7': return 7; | ||
28376 | case '8': return 8; | ||
28377 | case '9': return 9; | ||
28378 | case 'A': case 'a': return 10; | ||
28379 | case 'B': case 'b': return 11; | ||
28380 | case 'C': case 'c': return 12; | ||
28381 | case 'D': case 'd': return 13; | ||
28382 | case 'E': case 'e': return 14; | ||
28383 | case 'F': case 'f': return 15; | ||
28384 | } | ||
28385 | return -1; | ||
28386 | } | ||
28387 | JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) | ||
28388 | { | ||
28389 | const char *zIn, *zEnd; | ||
28390 | int isNeg = FALSE; | ||
28391 | sxi64 nVal = 0; | ||
28392 | #if defined(UNTRUST) | ||
28393 | if( SX_EMPTY_STR(zSrc) ){ | ||
28394 | if( pOutVal ){ | ||
28395 | *(sxi32 *)pOutVal = 0; | ||
28396 | } | ||
28397 | return SXERR_EMPTY; | ||
28398 | } | ||
28399 | #endif | ||
28400 | zEnd = &zSrc[nLen]; | ||
28401 | while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28402 | zSrc++; | ||
28403 | } | ||
28404 | if( zSrc < zEnd && ( *zSrc == '-' || *zSrc == '+' ) ){ | ||
28405 | isNeg = (zSrc[0] == '-') ? TRUE :FALSE; | ||
28406 | zSrc++; | ||
28407 | } | ||
28408 | if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'x' || zSrc[1] == 'X') ){ | ||
28409 | /* Bypass hex prefix */ | ||
28410 | zSrc += sizeof(char) * 2; | ||
28411 | } | ||
28412 | /* Skip leading zero */ | ||
28413 | while(zSrc < zEnd && zSrc[0] == '0' ){ | ||
28414 | zSrc++; | ||
28415 | } | ||
28416 | zIn = zSrc; | ||
28417 | for(;;){ | ||
28418 | if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; | ||
28419 | if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; | ||
28420 | if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; | ||
28421 | if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; | ||
28422 | } | ||
28423 | while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28424 | zSrc++; | ||
28425 | } | ||
28426 | if( zRest ){ | ||
28427 | *zRest = zSrc; | ||
28428 | } | ||
28429 | if( pOutVal ){ | ||
28430 | if( isNeg == TRUE && nVal != 0 ){ | ||
28431 | nVal = -nVal; | ||
28432 | } | ||
28433 | *(sxi64 *)pOutVal = nVal; | ||
28434 | } | ||
28435 | return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; | ||
28436 | } | ||
28437 | JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) | ||
28438 | { | ||
28439 | const char *zIn, *zEnd; | ||
28440 | int isNeg = FALSE; | ||
28441 | sxi64 nVal = 0; | ||
28442 | int c; | ||
28443 | #if defined(UNTRUST) | ||
28444 | if( SX_EMPTY_STR(zSrc) ){ | ||
28445 | if( pOutVal ){ | ||
28446 | *(sxi32 *)pOutVal = 0; | ||
28447 | } | ||
28448 | return SXERR_EMPTY; | ||
28449 | } | ||
28450 | #endif | ||
28451 | zEnd = &zSrc[nLen]; | ||
28452 | while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28453 | zSrc++; | ||
28454 | } | ||
28455 | if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ | ||
28456 | isNeg = (zSrc[0] == '-') ? TRUE :FALSE; | ||
28457 | zSrc++; | ||
28458 | } | ||
28459 | /* Skip leading zero */ | ||
28460 | while(zSrc < zEnd && zSrc[0] == '0' ){ | ||
28461 | zSrc++; | ||
28462 | } | ||
28463 | zIn = zSrc; | ||
28464 | for(;;){ | ||
28465 | if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; | ||
28466 | if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; | ||
28467 | if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; | ||
28468 | if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; | ||
28469 | } | ||
28470 | /* Skip trailing spaces */ | ||
28471 | while(zSrc < zEnd && SyisSpace(zSrc[0])){ | ||
28472 | zSrc++; | ||
28473 | } | ||
28474 | if( zRest ){ | ||
28475 | *zRest = zSrc; | ||
28476 | } | ||
28477 | if( pOutVal ){ | ||
28478 | if( isNeg == TRUE && nVal != 0 ){ | ||
28479 | nVal = -nVal; | ||
28480 | } | ||
28481 | *(sxi64 *)pOutVal = nVal; | ||
28482 | } | ||
28483 | return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; | ||
28484 | } | ||
28485 | JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) | ||
28486 | { | ||
28487 | const char *zIn, *zEnd; | ||
28488 | int isNeg = FALSE; | ||
28489 | sxi64 nVal = 0; | ||
28490 | int c; | ||
28491 | #if defined(UNTRUST) | ||
28492 | if( SX_EMPTY_STR(zSrc) ){ | ||
28493 | if( pOutVal ){ | ||
28494 | *(sxi32 *)pOutVal = 0; | ||
28495 | } | ||
28496 | return SXERR_EMPTY; | ||
28497 | } | ||
28498 | #endif | ||
28499 | zEnd = &zSrc[nLen]; | ||
28500 | while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28501 | zSrc++; | ||
28502 | } | ||
28503 | if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ | ||
28504 | isNeg = (zSrc[0] == '-') ? TRUE :FALSE; | ||
28505 | zSrc++; | ||
28506 | } | ||
28507 | if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'b' || zSrc[1] == 'B') ){ | ||
28508 | /* Bypass binary prefix */ | ||
28509 | zSrc += sizeof(char) * 2; | ||
28510 | } | ||
28511 | /* Skip leading zero */ | ||
28512 | while(zSrc < zEnd && zSrc[0] == '0' ){ | ||
28513 | zSrc++; | ||
28514 | } | ||
28515 | zIn = zSrc; | ||
28516 | for(;;){ | ||
28517 | if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; | ||
28518 | if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; | ||
28519 | if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; | ||
28520 | if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; | ||
28521 | } | ||
28522 | /* Skip trailing spaces */ | ||
28523 | while(zSrc < zEnd && SyisSpace(zSrc[0])){ | ||
28524 | zSrc++; | ||
28525 | } | ||
28526 | if( zRest ){ | ||
28527 | *zRest = zSrc; | ||
28528 | } | ||
28529 | if( pOutVal ){ | ||
28530 | if( isNeg == TRUE && nVal != 0 ){ | ||
28531 | nVal = -nVal; | ||
28532 | } | ||
28533 | *(sxi64 *)pOutVal = nVal; | ||
28534 | } | ||
28535 | return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; | ||
28536 | } | ||
28537 | JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) | ||
28538 | { | ||
28539 | #define SXDBL_DIG 15 | ||
28540 | #define SXDBL_MAX_EXP 308 | ||
28541 | #define SXDBL_MIN_EXP_PLUS 307 | ||
28542 | static const sxreal aTab[] = { | ||
28543 | 10, | ||
28544 | 1.0e2, | ||
28545 | 1.0e4, | ||
28546 | 1.0e8, | ||
28547 | 1.0e16, | ||
28548 | 1.0e32, | ||
28549 | 1.0e64, | ||
28550 | 1.0e128, | ||
28551 | 1.0e256 | ||
28552 | }; | ||
28553 | sxu8 neg = FALSE; | ||
28554 | sxreal Val = 0.0; | ||
28555 | const char *zEnd; | ||
28556 | sxi32 Lim, exp; | ||
28557 | sxreal *p = 0; | ||
28558 | #ifdef UNTRUST | ||
28559 | if( SX_EMPTY_STR(zSrc) ){ | ||
28560 | if( pOutVal ){ | ||
28561 | *(sxreal *)pOutVal = 0.0; | ||
28562 | } | ||
28563 | return SXERR_EMPTY; | ||
28564 | } | ||
28565 | #endif | ||
28566 | zEnd = &zSrc[nLen]; | ||
28567 | while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28568 | zSrc++; | ||
28569 | } | ||
28570 | if( zSrc < zEnd && (zSrc[0] == '-' || zSrc[0] == '+' ) ){ | ||
28571 | neg = zSrc[0] == '-' ? TRUE : FALSE ; | ||
28572 | zSrc++; | ||
28573 | } | ||
28574 | Lim = SXDBL_DIG ; | ||
28575 | for(;;){ | ||
28576 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; | ||
28577 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; | ||
28578 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; | ||
28579 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; | ||
28580 | } | ||
28581 | if( zSrc < zEnd && ( zSrc[0] == '.' || zSrc[0] == ',' ) ){ | ||
28582 | sxreal dec = 1.0; | ||
28583 | zSrc++; | ||
28584 | for(;;){ | ||
28585 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; | ||
28586 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; | ||
28587 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; | ||
28588 | if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; | ||
28589 | } | ||
28590 | Val /= dec; | ||
28591 | } | ||
28592 | if( neg == TRUE && Val != 0.0 ) { | ||
28593 | Val = -Val ; | ||
28594 | } | ||
28595 | if( Lim <= 0 ){ | ||
28596 | /* jump overflow digit */ | ||
28597 | while( zSrc < zEnd ){ | ||
28598 | if( zSrc[0] == 'e' || zSrc[0] == 'E' ){ | ||
28599 | break; | ||
28600 | } | ||
28601 | zSrc++; | ||
28602 | } | ||
28603 | } | ||
28604 | neg = FALSE; | ||
28605 | if( zSrc < zEnd && ( zSrc[0] == 'e' || zSrc[0] == 'E' ) ){ | ||
28606 | zSrc++; | ||
28607 | if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+') ){ | ||
28608 | neg = zSrc[0] == '-' ? TRUE : FALSE ; | ||
28609 | zSrc++; | ||
28610 | } | ||
28611 | exp = 0; | ||
28612 | while( zSrc < zEnd && SyisDigit(zSrc[0]) && exp < SXDBL_MAX_EXP ){ | ||
28613 | exp = exp * 10 + (zSrc[0] - '0'); | ||
28614 | zSrc++; | ||
28615 | } | ||
28616 | if( neg ){ | ||
28617 | if( exp > SXDBL_MIN_EXP_PLUS ) exp = SXDBL_MIN_EXP_PLUS ; | ||
28618 | }else if ( exp > SXDBL_MAX_EXP ){ | ||
28619 | exp = SXDBL_MAX_EXP; | ||
28620 | } | ||
28621 | for( p = (sxreal *)aTab ; exp ; exp >>= 1 , p++ ){ | ||
28622 | if( exp & 01 ){ | ||
28623 | if( neg ){ | ||
28624 | Val /= *p ; | ||
28625 | }else{ | ||
28626 | Val *= *p; | ||
28627 | } | ||
28628 | } | ||
28629 | } | ||
28630 | } | ||
28631 | while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ | ||
28632 | zSrc++; | ||
28633 | } | ||
28634 | if( zRest ){ | ||
28635 | *zRest = zSrc; | ||
28636 | } | ||
28637 | if( pOutVal ){ | ||
28638 | *(sxreal *)pOutVal = Val; | ||
28639 | } | ||
28640 | return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; | ||
28641 | } | ||
28642 | /* SyRunTimeApi:sxlib.c */ | ||
28643 | JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen) | ||
28644 | { | ||
28645 | register unsigned char *zIn = (unsigned char *)pSrc; | ||
28646 | unsigned char *zEnd; | ||
28647 | sxu32 nH = 5381; | ||
28648 | zEnd = &zIn[nLen]; | ||
28649 | for(;;){ | ||
28650 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
28651 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
28652 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
28653 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
28654 | } | ||
28655 | return nH; | ||
28656 | } | ||
28657 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
28658 | JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) | ||
28659 | { | ||
28660 | static const unsigned char zBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||
28661 | unsigned char *zIn = (unsigned char *)zSrc; | ||
28662 | unsigned char z64[4]; | ||
28663 | sxu32 i; | ||
28664 | sxi32 rc; | ||
28665 | #if defined(UNTRUST) | ||
28666 | if( SX_EMPTY_STR(zSrc) || xConsumer == 0){ | ||
28667 | return SXERR_EMPTY; | ||
28668 | } | ||
28669 | #endif | ||
28670 | for(i = 0; i + 2 < nLen; i += 3){ | ||
28671 | z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; | ||
28672 | z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; | ||
28673 | z64[2] = zBase64[( ((zIn[i+1] & 0x0F) << 2) | (zIn[i + 2] >> 6) ) & 0x3F]; | ||
28674 | z64[3] = zBase64[ zIn[i + 2] & 0x3F]; | ||
28675 | |||
28676 | rc = xConsumer((const void *)z64, sizeof(z64), pUserData); | ||
28677 | if( rc != SXRET_OK ){return SXERR_ABORT;} | ||
28678 | |||
28679 | } | ||
28680 | if ( i+1 < nLen ){ | ||
28681 | z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; | ||
28682 | z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; | ||
28683 | z64[2] = zBase64[(zIn[i+1] & 0x0F) << 2 ]; | ||
28684 | z64[3] = '='; | ||
28685 | |||
28686 | rc = xConsumer((const void *)z64, sizeof(z64), pUserData); | ||
28687 | if( rc != SXRET_OK ){return SXERR_ABORT;} | ||
28688 | |||
28689 | }else if( i < nLen ){ | ||
28690 | z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; | ||
28691 | z64[1] = zBase64[(zIn[i] & 0x03) << 4]; | ||
28692 | z64[2] = '='; | ||
28693 | z64[3] = '='; | ||
28694 | |||
28695 | rc = xConsumer((const void *)z64, sizeof(z64), pUserData); | ||
28696 | if( rc != SXRET_OK ){return SXERR_ABORT;} | ||
28697 | } | ||
28698 | |||
28699 | return SXRET_OK; | ||
28700 | } | ||
28701 | JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) | ||
28702 | { | ||
28703 | static const sxu32 aBase64Trans[] = { | ||
28704 | 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, | ||
28705 | 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, | ||
28706 | 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, | ||
28707 | 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, | ||
28708 | 0, 0, 0 | ||
28709 | }; | ||
28710 | sxu32 n, w, x, y, z; | ||
28711 | sxi32 rc; | ||
28712 | unsigned char zOut[10]; | ||
28713 | #if defined(UNTRUST) | ||
28714 | if( SX_EMPTY_STR(zB64) || xConsumer == 0 ){ | ||
28715 | return SXERR_EMPTY; | ||
28716 | } | ||
28717 | #endif | ||
28718 | while(nLen > 0 && zB64[nLen - 1] == '=' ){ | ||
28719 | nLen--; | ||
28720 | } | ||
28721 | for( n = 0 ; n+3<nLen ; n += 4){ | ||
28722 | w = aBase64Trans[zB64[n] & 0x7F]; | ||
28723 | x = aBase64Trans[zB64[n+1] & 0x7F]; | ||
28724 | y = aBase64Trans[zB64[n+2] & 0x7F]; | ||
28725 | z = aBase64Trans[zB64[n+3] & 0x7F]; | ||
28726 | zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); | ||
28727 | zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); | ||
28728 | zOut[2] = ((y<<6) & 0xC0) | (z & 0x3F); | ||
28729 | |||
28730 | rc = xConsumer((const void *)zOut, sizeof(unsigned char)*3, pUserData); | ||
28731 | if( rc != SXRET_OK ){ return SXERR_ABORT;} | ||
28732 | } | ||
28733 | if( n+2 < nLen ){ | ||
28734 | w = aBase64Trans[zB64[n] & 0x7F]; | ||
28735 | x = aBase64Trans[zB64[n+1] & 0x7F]; | ||
28736 | y = aBase64Trans[zB64[n+2] & 0x7F]; | ||
28737 | |||
28738 | zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); | ||
28739 | zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); | ||
28740 | |||
28741 | rc = xConsumer((const void *)zOut, sizeof(unsigned char)*2, pUserData); | ||
28742 | if( rc != SXRET_OK ){ return SXERR_ABORT;} | ||
28743 | }else if( n+1 < nLen ){ | ||
28744 | w = aBase64Trans[zB64[n] & 0x7F]; | ||
28745 | x = aBase64Trans[zB64[n+1] & 0x7F]; | ||
28746 | |||
28747 | zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); | ||
28748 | |||
28749 | rc = xConsumer((const void *)zOut, sizeof(unsigned char)*1, pUserData); | ||
28750 | if( rc != SXRET_OK ){ return SXERR_ABORT;} | ||
28751 | } | ||
28752 | return SXRET_OK; | ||
28753 | } | ||
28754 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
28755 | #define INVALID_LEXER(LEX) ( LEX == 0 || LEX->xTokenizer == 0 ) | ||
28756 | JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData) | ||
28757 | { | ||
28758 | SyStream *pStream; | ||
28759 | #if defined (UNTRUST) | ||
28760 | if ( pLex == 0 || xTokenizer == 0 ){ | ||
28761 | return SXERR_CORRUPT; | ||
28762 | } | ||
28763 | #endif | ||
28764 | pLex->pTokenSet = 0; | ||
28765 | /* Initialize lexer fields */ | ||
28766 | if( pSet ){ | ||
28767 | if ( SySetElemSize(pSet) != sizeof(SyToken) ){ | ||
28768 | return SXERR_INVALID; | ||
28769 | } | ||
28770 | pLex->pTokenSet = pSet; | ||
28771 | } | ||
28772 | pStream = &pLex->sStream; | ||
28773 | pLex->xTokenizer = xTokenizer; | ||
28774 | pLex->pUserData = pUserData; | ||
28775 | |||
28776 | pStream->nLine = 1; | ||
28777 | pStream->nIgn = 0; | ||
28778 | pStream->zText = pStream->zEnd = 0; | ||
28779 | pStream->pSet = pSet; | ||
28780 | return SXRET_OK; | ||
28781 | } | ||
28782 | JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp) | ||
28783 | { | ||
28784 | const unsigned char *zCur; | ||
28785 | SyStream *pStream; | ||
28786 | SyToken sToken; | ||
28787 | sxi32 rc; | ||
28788 | #if defined (UNTRUST) | ||
28789 | if ( INVALID_LEXER(pLex) || zInput == 0 ){ | ||
28790 | return SXERR_CORRUPT; | ||
28791 | } | ||
28792 | #endif | ||
28793 | pStream = &pLex->sStream; | ||
28794 | /* Point to the head of the input */ | ||
28795 | pStream->zText = pStream->zInput = (const unsigned char *)zInput; | ||
28796 | /* Point to the end of the input */ | ||
28797 | pStream->zEnd = &pStream->zInput[nLen]; | ||
28798 | for(;;){ | ||
28799 | if( pStream->zText >= pStream->zEnd ){ | ||
28800 | /* End of the input reached */ | ||
28801 | break; | ||
28802 | } | ||
28803 | zCur = pStream->zText; | ||
28804 | /* Call the tokenizer callback */ | ||
28805 | rc = pLex->xTokenizer(pStream, &sToken, pLex->pUserData, pCtxData); | ||
28806 | if( rc != SXRET_OK && rc != SXERR_CONTINUE ){ | ||
28807 | /* Tokenizer callback request an operation abort */ | ||
28808 | if( rc == SXERR_ABORT ){ | ||
28809 | return SXERR_ABORT; | ||
28810 | } | ||
28811 | break; | ||
28812 | } | ||
28813 | if( rc == SXERR_CONTINUE ){ | ||
28814 | /* Request to ignore this token */ | ||
28815 | pStream->nIgn++; | ||
28816 | }else if( pLex->pTokenSet ){ | ||
28817 | /* Put the token in the set */ | ||
28818 | rc = SySetPut(pLex->pTokenSet, (const void *)&sToken); | ||
28819 | if( rc != SXRET_OK ){ | ||
28820 | break; | ||
28821 | } | ||
28822 | } | ||
28823 | if( zCur >= pStream->zText ){ | ||
28824 | /* Automatic advance of the stream cursor */ | ||
28825 | pStream->zText = &zCur[1]; | ||
28826 | } | ||
28827 | } | ||
28828 | if( xSort && pLex->pTokenSet ){ | ||
28829 | SyToken *aToken = (SyToken *)SySetBasePtr(pLex->pTokenSet); | ||
28830 | /* Sort the extrated tokens */ | ||
28831 | if( xCmp == 0 ){ | ||
28832 | /* Use a default comparison function */ | ||
28833 | xCmp = SyMemcmp; | ||
28834 | } | ||
28835 | xSort(aToken, SySetUsed(pLex->pTokenSet), sizeof(SyToken), xCmp); | ||
28836 | } | ||
28837 | return SXRET_OK; | ||
28838 | } | ||
28839 | JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex) | ||
28840 | { | ||
28841 | sxi32 rc = SXRET_OK; | ||
28842 | #if defined (UNTRUST) | ||
28843 | if ( INVALID_LEXER(pLex) ){ | ||
28844 | return SXERR_CORRUPT; | ||
28845 | } | ||
28846 | #else | ||
28847 | SXUNUSED(pLex); /* Prevent compiler warning */ | ||
28848 | #endif | ||
28849 | return rc; | ||
28850 | } | ||
28851 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
28852 | #define SAFE_HTTP(C) (SyisAlphaNum(c) || c == '_' || c == '-' || c == '$' || c == '.' ) | ||
28853 | JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) | ||
28854 | { | ||
28855 | unsigned char *zIn = (unsigned char *)zSrc; | ||
28856 | unsigned char zHex[3] = { '%', 0, 0 }; | ||
28857 | unsigned char zOut[2]; | ||
28858 | unsigned char *zCur, *zEnd; | ||
28859 | sxi32 c; | ||
28860 | sxi32 rc; | ||
28861 | #ifdef UNTRUST | ||
28862 | if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ | ||
28863 | return SXERR_EMPTY; | ||
28864 | } | ||
28865 | #endif | ||
28866 | rc = SXRET_OK; | ||
28867 | zEnd = &zIn[nLen]; zCur = zIn; | ||
28868 | for(;;){ | ||
28869 | if( zCur >= zEnd ){ | ||
28870 | if( zCur != zIn ){ | ||
28871 | rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData); | ||
28872 | } | ||
28873 | break; | ||
28874 | } | ||
28875 | c = zCur[0]; | ||
28876 | if( SAFE_HTTP(c) ){ | ||
28877 | zCur++; continue; | ||
28878 | } | ||
28879 | if( zCur != zIn && SXRET_OK != (rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData))){ | ||
28880 | break; | ||
28881 | } | ||
28882 | if( c == ' ' ){ | ||
28883 | zOut[0] = '+'; | ||
28884 | rc = xConsumer((const void *)zOut, sizeof(unsigned char), pUserData); | ||
28885 | }else{ | ||
28886 | zHex[1] = "0123456789ABCDEF"[(c >> 4) & 0x0F]; | ||
28887 | zHex[2] = "0123456789ABCDEF"[c & 0x0F]; | ||
28888 | rc = xConsumer(zHex, sizeof(zHex), pUserData); | ||
28889 | } | ||
28890 | if( SXRET_OK != rc ){ | ||
28891 | break; | ||
28892 | } | ||
28893 | zIn = &zCur[1]; zCur = zIn ; | ||
28894 | } | ||
28895 | return rc == SXRET_OK ? SXRET_OK : SXERR_ABORT; | ||
28896 | } | ||
28897 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
28898 | static sxi32 SyAsciiToHex(sxi32 c) | ||
28899 | { | ||
28900 | if( c >= 'a' && c <= 'f' ){ | ||
28901 | c += 10 - 'a'; | ||
28902 | return c; | ||
28903 | } | ||
28904 | if( c >= '0' && c <= '9' ){ | ||
28905 | c -= '0'; | ||
28906 | return c; | ||
28907 | } | ||
28908 | if( c >= 'A' && c <= 'F') { | ||
28909 | c += 10 - 'A'; | ||
28910 | return c; | ||
28911 | } | ||
28912 | return 0; | ||
28913 | } | ||
28914 | JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8) | ||
28915 | { | ||
28916 | static const sxu8 Utf8Trans[] = { | ||
28917 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, | ||
28918 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, | ||
28919 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, | ||
28920 | 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, | ||
28921 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, | ||
28922 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, | ||
28923 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, | ||
28924 | 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00 | ||
28925 | }; | ||
28926 | const char *zIn = zSrc; | ||
28927 | const char *zEnd; | ||
28928 | const char *zCur; | ||
28929 | sxu8 *zOutPtr; | ||
28930 | sxu8 zOut[10]; | ||
28931 | sxi32 c, d; | ||
28932 | sxi32 rc; | ||
28933 | #if defined(UNTRUST) | ||
28934 | if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ | ||
28935 | return SXERR_EMPTY; | ||
28936 | } | ||
28937 | #endif | ||
28938 | rc = SXRET_OK; | ||
28939 | zEnd = &zSrc[nLen]; | ||
28940 | zCur = zIn; | ||
28941 | for(;;){ | ||
28942 | while(zCur < zEnd && zCur[0] != '%' && zCur[0] != '+' ){ | ||
28943 | zCur++; | ||
28944 | } | ||
28945 | if( zCur != zIn ){ | ||
28946 | /* Consume input */ | ||
28947 | rc = xConsumer(zIn, (unsigned int)(zCur-zIn), pUserData); | ||
28948 | if( rc != SXRET_OK ){ | ||
28949 | /* User consumer routine request an operation abort */ | ||
28950 | break; | ||
28951 | } | ||
28952 | } | ||
28953 | if( zCur >= zEnd ){ | ||
28954 | rc = SXRET_OK; | ||
28955 | break; | ||
28956 | } | ||
28957 | /* Decode unsafe HTTP characters */ | ||
28958 | zOutPtr = zOut; | ||
28959 | if( zCur[0] == '+' ){ | ||
28960 | *zOutPtr++ = ' '; | ||
28961 | zCur++; | ||
28962 | }else{ | ||
28963 | if( &zCur[2] >= zEnd ){ | ||
28964 | rc = SXERR_OVERFLOW; | ||
28965 | break; | ||
28966 | } | ||
28967 | c = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); | ||
28968 | zCur += 3; | ||
28969 | if( c < 0x000C0 ){ | ||
28970 | *zOutPtr++ = (sxu8)c; | ||
28971 | }else{ | ||
28972 | c = Utf8Trans[c-0xC0]; | ||
28973 | while( zCur[0] == '%' ){ | ||
28974 | d = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); | ||
28975 | if( (d&0xC0) != 0x80 ){ | ||
28976 | break; | ||
28977 | } | ||
28978 | c = (c<<6) + (0x3f & d); | ||
28979 | zCur += 3; | ||
28980 | } | ||
28981 | if( bUTF8 == FALSE ){ | ||
28982 | *zOutPtr++ = (sxu8)c; | ||
28983 | }else{ | ||
28984 | SX_WRITE_UTF8(zOutPtr, c); | ||
28985 | } | ||
28986 | } | ||
28987 | |||
28988 | } | ||
28989 | /* Consume the decoded characters */ | ||
28990 | rc = xConsumer((const void *)zOut, (unsigned int)(zOutPtr-zOut), pUserData); | ||
28991 | if( rc != SXRET_OK ){ | ||
28992 | break; | ||
28993 | } | ||
28994 | /* Synchronize pointers */ | ||
28995 | zIn = zCur; | ||
28996 | } | ||
28997 | return rc; | ||
28998 | } | ||
28999 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
29000 | static const char *zEngDay[] = { | ||
29001 | "Sunday", "Monday", "Tuesday", "Wednesday", | ||
29002 | "Thursday", "Friday", "Saturday" | ||
29003 | }; | ||
29004 | static const char *zEngMonth[] = { | ||
29005 | "January", "February", "March", "April", | ||
29006 | "May", "June", "July", "August", | ||
29007 | "September", "October", "November", "December" | ||
29008 | }; | ||
29009 | static const char * GetDay(sxi32 i) | ||
29010 | { | ||
29011 | return zEngDay[ i % 7 ]; | ||
29012 | } | ||
29013 | static const char * GetMonth(sxi32 i) | ||
29014 | { | ||
29015 | return zEngMonth[ i % 12 ]; | ||
29016 | } | ||
29017 | JX9_PRIVATE const char * SyTimeGetDay(sxi32 iDay) | ||
29018 | { | ||
29019 | return GetDay(iDay); | ||
29020 | } | ||
29021 | JX9_PRIVATE const char * SyTimeGetMonth(sxi32 iMonth) | ||
29022 | { | ||
29023 | return GetMonth(iMonth); | ||
29024 | } | ||
29025 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
29026 | /* SyRunTimeApi: sxfmt.c */ | ||
29027 | #define SXFMT_BUFSIZ 1024 /* Conversion buffer size */ | ||
29028 | /* | ||
29029 | ** Conversion types fall into various categories as defined by the | ||
29030 | ** following enumeration. | ||
29031 | */ | ||
29032 | #define SXFMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ | ||
29033 | #define SXFMT_FLOAT 2 /* Floating point.%f */ | ||
29034 | #define SXFMT_EXP 3 /* Exponentional notation.%e and %E */ | ||
29035 | #define SXFMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ | ||
29036 | #define SXFMT_SIZE 5 /* Total number of characters processed so far.%n */ | ||
29037 | #define SXFMT_STRING 6 /* Strings.%s */ | ||
29038 | #define SXFMT_PERCENT 7 /* Percent symbol.%% */ | ||
29039 | #define SXFMT_CHARX 8 /* Characters.%c */ | ||
29040 | #define SXFMT_ERROR 9 /* Used to indicate no such conversion type */ | ||
29041 | /* Extension by Symisc Systems */ | ||
29042 | #define SXFMT_RAWSTR 13 /* %z Pointer to raw string (SyString *) */ | ||
29043 | #define SXFMT_UNUSED 15 | ||
29044 | /* | ||
29045 | ** Allowed values for SyFmtInfo.flags | ||
29046 | */ | ||
29047 | #define SXFLAG_SIGNED 0x01 | ||
29048 | #define SXFLAG_UNSIGNED 0x02 | ||
29049 | /* Allowed values for SyFmtConsumer.nType */ | ||
29050 | #define SXFMT_CONS_PROC 1 /* Consumer is a procedure */ | ||
29051 | #define SXFMT_CONS_STR 2 /* Consumer is a managed string */ | ||
29052 | #define SXFMT_CONS_FILE 5 /* Consumer is an open File */ | ||
29053 | #define SXFMT_CONS_BLOB 6 /* Consumer is a BLOB */ | ||
29054 | /* | ||
29055 | ** Each builtin conversion character (ex: the 'd' in "%d") is described | ||
29056 | ** by an instance of the following structure | ||
29057 | */ | ||
29058 | typedef struct SyFmtInfo SyFmtInfo; | ||
29059 | struct SyFmtInfo | ||
29060 | { | ||
29061 | char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */ | ||
29062 | sxu8 base; /* The base for radix conversion */ | ||
29063 | int flags; /* One or more of SXFLAG_ constants below */ | ||
29064 | sxu8 type; /* Conversion paradigm */ | ||
29065 | char *charset; /* The character set for conversion */ | ||
29066 | char *prefix; /* Prefix on non-zero values in alt format */ | ||
29067 | }; | ||
29068 | typedef struct SyFmtConsumer SyFmtConsumer; | ||
29069 | struct SyFmtConsumer | ||
29070 | { | ||
29071 | sxu32 nLen; /* Total output length */ | ||
29072 | sxi32 nType; /* Type of the consumer see below */ | ||
29073 | sxi32 rc; /* Consumer return value;Abort processing if rc != SXRET_OK */ | ||
29074 | union{ | ||
29075 | struct{ | ||
29076 | ProcConsumer xUserConsumer; | ||
29077 | void *pUserData; | ||
29078 | }sFunc; | ||
29079 | SyBlob *pBlob; | ||
29080 | }uConsumer; | ||
29081 | }; | ||
29082 | #ifndef SX_OMIT_FLOATINGPOINT | ||
29083 | static int getdigit(sxlongreal *val, int *cnt) | ||
29084 | { | ||
29085 | sxlongreal d; | ||
29086 | int digit; | ||
29087 | |||
29088 | if( (*cnt)++ >= 16 ){ | ||
29089 | return '0'; | ||
29090 | } | ||
29091 | digit = (int)*val; | ||
29092 | d = digit; | ||
29093 | *val = (*val - d)*10.0; | ||
29094 | return digit + '0' ; | ||
29095 | } | ||
29096 | #endif /* SX_OMIT_FLOATINGPOINT */ | ||
29097 | /* | ||
29098 | * The following routine was taken from the SQLITE2 source tree and was | ||
29099 | * extended by Symisc Systems to fit its need. | ||
29100 | * Status: Public Domain | ||
29101 | */ | ||
29102 | static sxi32 InternFormat(ProcConsumer xConsumer, void *pUserData, const char *zFormat, va_list ap) | ||
29103 | { | ||
29104 | /* | ||
29105 | * The following table is searched linearly, so it is good to put the most frequently | ||
29106 | * used conversion types first. | ||
29107 | */ | ||
29108 | static const SyFmtInfo aFmt[] = { | ||
29109 | { 'd', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, | ||
29110 | { 's', 0, 0, SXFMT_STRING, 0, 0 }, | ||
29111 | { 'c', 0, 0, SXFMT_CHARX, 0, 0 }, | ||
29112 | { 'x', 16, 0, SXFMT_RADIX, "0123456789abcdef", "x0" }, | ||
29113 | { 'X', 16, 0, SXFMT_RADIX, "0123456789ABCDEF", "X0" }, | ||
29114 | /* -- Extensions by Symisc Systems -- */ | ||
29115 | { 'z', 0, 0, SXFMT_RAWSTR, 0, 0 }, /* Pointer to a raw string (SyString *) */ | ||
29116 | { 'B', 2, 0, SXFMT_RADIX, "01", "b0"}, | ||
29117 | /* -- End of Extensions -- */ | ||
29118 | { 'o', 8, 0, SXFMT_RADIX, "01234567", "0" }, | ||
29119 | { 'u', 10, 0, SXFMT_RADIX, "0123456789", 0 }, | ||
29120 | #ifndef SX_OMIT_FLOATINGPOINT | ||
29121 | { 'f', 0, SXFLAG_SIGNED, SXFMT_FLOAT, 0, 0 }, | ||
29122 | { 'e', 0, SXFLAG_SIGNED, SXFMT_EXP, "e", 0 }, | ||
29123 | { 'E', 0, SXFLAG_SIGNED, SXFMT_EXP, "E", 0 }, | ||
29124 | { 'g', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "e", 0 }, | ||
29125 | { 'G', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "E", 0 }, | ||
29126 | #endif | ||
29127 | { 'i', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, | ||
29128 | { 'n', 0, 0, SXFMT_SIZE, 0, 0 }, | ||
29129 | { '%', 0, 0, SXFMT_PERCENT, 0, 0 }, | ||
29130 | { 'p', 10, 0, SXFMT_RADIX, "0123456789", 0 } | ||
29131 | }; | ||
29132 | int c; /* Next character in the format string */ | ||
29133 | char *bufpt; /* Pointer to the conversion buffer */ | ||
29134 | int precision; /* Precision of the current field */ | ||
29135 | int length; /* Length of the field */ | ||
29136 | int idx; /* A general purpose loop counter */ | ||
29137 | int width; /* Width of the current field */ | ||
29138 | sxu8 flag_leftjustify; /* True if "-" flag is present */ | ||
29139 | sxu8 flag_plussign; /* True if "+" flag is present */ | ||
29140 | sxu8 flag_blanksign; /* True if " " flag is present */ | ||
29141 | sxu8 flag_alternateform; /* True if "#" flag is present */ | ||
29142 | sxu8 flag_zeropad; /* True if field width constant starts with zero */ | ||
29143 | sxu8 flag_long; /* True if "l" flag is present */ | ||
29144 | sxi64 longvalue; /* Value for integer types */ | ||
29145 | const SyFmtInfo *infop; /* Pointer to the appropriate info structure */ | ||
29146 | char buf[SXFMT_BUFSIZ]; /* Conversion buffer */ | ||
29147 | char prefix; /* Prefix character."+" or "-" or " " or '\0'.*/ | ||
29148 | sxu8 errorflag = 0; /* True if an error is encountered */ | ||
29149 | sxu8 xtype; /* Conversion paradigm */ | ||
29150 | char *zExtra; | ||
29151 | static char spaces[] = " "; | ||
29152 | #define etSPACESIZE ((int)sizeof(spaces)-1) | ||
29153 | #ifndef SX_OMIT_FLOATINGPOINT | ||
29154 | sxlongreal realvalue; /* Value for real types */ | ||
29155 | int exp; /* exponent of real numbers */ | ||
29156 | double rounder; /* Used for rounding floating point values */ | ||
29157 | sxu8 flag_dp; /* True if decimal point should be shown */ | ||
29158 | sxu8 flag_rtz; /* True if trailing zeros should be removed */ | ||
29159 | sxu8 flag_exp; /* True to force display of the exponent */ | ||
29160 | int nsd; /* Number of significant digits returned */ | ||
29161 | #endif | ||
29162 | int rc; | ||
29163 | |||
29164 | length = 0; | ||
29165 | bufpt = 0; | ||
29166 | for(; (c=(*zFormat))!=0; ++zFormat){ | ||
29167 | if( c!='%' ){ | ||
29168 | unsigned int amt; | ||
29169 | bufpt = (char *)zFormat; | ||
29170 | amt = 1; | ||
29171 | while( (c=(*++zFormat))!='%' && c!=0 ) amt++; | ||
29172 | rc = xConsumer((const void *)bufpt, amt, pUserData); | ||
29173 | if( rc != SXRET_OK ){ | ||
29174 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
29175 | } | ||
29176 | if( c==0 ){ | ||
29177 | return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; | ||
29178 | } | ||
29179 | } | ||
29180 | if( (c=(*++zFormat))==0 ){ | ||
29181 | errorflag = 1; | ||
29182 | rc = xConsumer("%", sizeof("%")-1, pUserData); | ||
29183 | if( rc != SXRET_OK ){ | ||
29184 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
29185 | } | ||
29186 | return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; | ||
29187 | } | ||
29188 | /* Find out what flags are present */ | ||
29189 | flag_leftjustify = flag_plussign = flag_blanksign = | ||
29190 | flag_alternateform = flag_zeropad = 0; | ||
29191 | do{ | ||
29192 | switch( c ){ | ||
29193 | case '-': flag_leftjustify = 1; c = 0; break; | ||
29194 | case '+': flag_plussign = 1; c = 0; break; | ||
29195 | case ' ': flag_blanksign = 1; c = 0; break; | ||
29196 | case '#': flag_alternateform = 1; c = 0; break; | ||
29197 | case '0': flag_zeropad = 1; c = 0; break; | ||
29198 | default: break; | ||
29199 | } | ||
29200 | }while( c==0 && (c=(*++zFormat))!=0 ); | ||
29201 | /* Get the field width */ | ||
29202 | width = 0; | ||
29203 | if( c=='*' ){ | ||
29204 | width = va_arg(ap, int); | ||
29205 | if( width<0 ){ | ||
29206 | flag_leftjustify = 1; | ||
29207 | width = -width; | ||
29208 | } | ||
29209 | c = *++zFormat; | ||
29210 | }else{ | ||
29211 | while( c>='0' && c<='9' ){ | ||
29212 | width = width*10 + c - '0'; | ||
29213 | c = *++zFormat; | ||
29214 | } | ||
29215 | } | ||
29216 | if( width > SXFMT_BUFSIZ-10 ){ | ||
29217 | width = SXFMT_BUFSIZ-10; | ||
29218 | } | ||
29219 | /* Get the precision */ | ||
29220 | precision = -1; | ||
29221 | if( c=='.' ){ | ||
29222 | precision = 0; | ||
29223 | c = *++zFormat; | ||
29224 | if( c=='*' ){ | ||
29225 | precision = va_arg(ap, int); | ||
29226 | if( precision<0 ) precision = -precision; | ||
29227 | c = *++zFormat; | ||
29228 | }else{ | ||
29229 | while( c>='0' && c<='9' ){ | ||
29230 | precision = precision*10 + c - '0'; | ||
29231 | c = *++zFormat; | ||
29232 | } | ||
29233 | } | ||
29234 | } | ||
29235 | /* Get the conversion type modifier */ | ||
29236 | flag_long = 0; | ||
29237 | if( c=='l' || c == 'q' /* BSD quad (expect a 64-bit integer) */ ){ | ||
29238 | flag_long = (c == 'q') ? 2 : 1; | ||
29239 | c = *++zFormat; | ||
29240 | if( c == 'l' ){ | ||
29241 | /* Standard printf emulation 'lld' (expect a 64bit integer) */ | ||
29242 | flag_long = 2; | ||
29243 | } | ||
29244 | } | ||
29245 | /* Fetch the info entry for the field */ | ||
29246 | infop = 0; | ||
29247 | xtype = SXFMT_ERROR; | ||
29248 | for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ | ||
29249 | if( c==aFmt[idx].fmttype ){ | ||
29250 | infop = &aFmt[idx]; | ||
29251 | xtype = infop->type; | ||
29252 | break; | ||
29253 | } | ||
29254 | } | ||
29255 | zExtra = 0; | ||
29256 | |||
29257 | /* | ||
29258 | ** At this point, variables are initialized as follows: | ||
29259 | ** | ||
29260 | ** flag_alternateform TRUE if a '#' is present. | ||
29261 | ** flag_plussign TRUE if a '+' is present. | ||
29262 | ** flag_leftjustify TRUE if a '-' is present or if the | ||
29263 | ** field width was negative. | ||
29264 | ** flag_zeropad TRUE if the width began with 0. | ||
29265 | ** flag_long TRUE if the letter 'l' (ell) or 'q'(BSD quad) prefixed | ||
29266 | ** the conversion character. | ||
29267 | ** flag_blanksign TRUE if a ' ' is present. | ||
29268 | ** width The specified field width.This is | ||
29269 | ** always non-negative.Zero is the default. | ||
29270 | ** precision The specified precision.The default | ||
29271 | ** is -1. | ||
29272 | ** xtype The object of the conversion. | ||
29273 | ** infop Pointer to the appropriate info struct. | ||
29274 | */ | ||
29275 | switch( xtype ){ | ||
29276 | case SXFMT_RADIX: | ||
29277 | if( flag_long > 0 ){ | ||
29278 | if( flag_long > 1 ){ | ||
29279 | /* BSD quad: expect a 64-bit integer */ | ||
29280 | longvalue = va_arg(ap, sxi64); | ||
29281 | }else{ | ||
29282 | longvalue = va_arg(ap, sxlong); | ||
29283 | } | ||
29284 | }else{ | ||
29285 | if( infop->flags & SXFLAG_SIGNED ){ | ||
29286 | longvalue = va_arg(ap, sxi32); | ||
29287 | }else{ | ||
29288 | longvalue = va_arg(ap, sxu32); | ||
29289 | } | ||
29290 | } | ||
29291 | /* Limit the precision to prevent overflowing buf[] during conversion */ | ||
29292 | if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; | ||
29293 | #if 1 | ||
29294 | /* For the format %#x, the value zero is printed "0" not "0x0". | ||
29295 | ** I think this is stupid.*/ | ||
29296 | if( longvalue==0 ) flag_alternateform = 0; | ||
29297 | #else | ||
29298 | /* More sensible: turn off the prefix for octal (to prevent "00"), | ||
29299 | ** but leave the prefix for hex.*/ | ||
29300 | if( longvalue==0 && infop->base==8 ) flag_alternateform = 0; | ||
29301 | #endif | ||
29302 | if( infop->flags & SXFLAG_SIGNED ){ | ||
29303 | if( longvalue<0 ){ | ||
29304 | longvalue = -longvalue; | ||
29305 | /* Ticket 1433-003 */ | ||
29306 | if( longvalue < 0 ){ | ||
29307 | /* Overflow */ | ||
29308 | longvalue= 0x7FFFFFFFFFFFFFFF; | ||
29309 | } | ||
29310 | prefix = '-'; | ||
29311 | }else if( flag_plussign ) prefix = '+'; | ||
29312 | else if( flag_blanksign ) prefix = ' '; | ||
29313 | else prefix = 0; | ||
29314 | }else{ | ||
29315 | if( longvalue<0 ){ | ||
29316 | longvalue = -longvalue; | ||
29317 | /* Ticket 1433-003 */ | ||
29318 | if( longvalue < 0 ){ | ||
29319 | /* Overflow */ | ||
29320 | longvalue= 0x7FFFFFFFFFFFFFFF; | ||
29321 | } | ||
29322 | } | ||
29323 | prefix = 0; | ||
29324 | } | ||
29325 | if( flag_zeropad && precision<width-(prefix!=0) ){ | ||
29326 | precision = width-(prefix!=0); | ||
29327 | } | ||
29328 | bufpt = &buf[SXFMT_BUFSIZ-1]; | ||
29329 | { | ||
29330 | register char *cset; /* Use registers for speed */ | ||
29331 | register int base; | ||
29332 | cset = infop->charset; | ||
29333 | base = infop->base; | ||
29334 | do{ /* Convert to ascii */ | ||
29335 | *(--bufpt) = cset[longvalue%base]; | ||
29336 | longvalue = longvalue/base; | ||
29337 | }while( longvalue>0 ); | ||
29338 | } | ||
29339 | length = &buf[SXFMT_BUFSIZ-1]-bufpt; | ||
29340 | for(idx=precision-length; idx>0; idx--){ | ||
29341 | *(--bufpt) = '0'; /* Zero pad */ | ||
29342 | } | ||
29343 | if( prefix ) *(--bufpt) = prefix; /* Add sign */ | ||
29344 | if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ | ||
29345 | char *pre, x; | ||
29346 | pre = infop->prefix; | ||
29347 | if( *bufpt!=pre[0] ){ | ||
29348 | for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x; | ||
29349 | } | ||
29350 | } | ||
29351 | length = &buf[SXFMT_BUFSIZ-1]-bufpt; | ||
29352 | break; | ||
29353 | case SXFMT_FLOAT: | ||
29354 | case SXFMT_EXP: | ||
29355 | case SXFMT_GENERIC: | ||
29356 | #ifndef SX_OMIT_FLOATINGPOINT | ||
29357 | realvalue = va_arg(ap, double); | ||
29358 | if( precision<0 ) precision = 6; /* Set default precision */ | ||
29359 | if( precision>SXFMT_BUFSIZ-40) precision = SXFMT_BUFSIZ-40; | ||
29360 | if( realvalue<0.0 ){ | ||
29361 | realvalue = -realvalue; | ||
29362 | prefix = '-'; | ||
29363 | }else{ | ||
29364 | if( flag_plussign ) prefix = '+'; | ||
29365 | else if( flag_blanksign ) prefix = ' '; | ||
29366 | else prefix = 0; | ||
29367 | } | ||
29368 | if( infop->type==SXFMT_GENERIC && precision>0 ) precision--; | ||
29369 | rounder = 0.0; | ||
29370 | #if 0 | ||
29371 | /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ | ||
29372 | for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); | ||
29373 | #else | ||
29374 | /* It makes more sense to use 0.5 */ | ||
29375 | for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); | ||
29376 | #endif | ||
29377 | if( infop->type==SXFMT_FLOAT ) realvalue += rounder; | ||
29378 | /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ | ||
29379 | exp = 0; | ||
29380 | if( realvalue>0.0 ){ | ||
29381 | while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } | ||
29382 | while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } | ||
29383 | while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } | ||
29384 | while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } | ||
29385 | if( exp>350 || exp<-350 ){ | ||
29386 | bufpt = "NaN"; | ||
29387 | length = 3; | ||
29388 | break; | ||
29389 | } | ||
29390 | } | ||
29391 | bufpt = buf; | ||
29392 | /* | ||
29393 | ** If the field type is etGENERIC, then convert to either etEXP | ||
29394 | ** or etFLOAT, as appropriate. | ||
29395 | */ | ||
29396 | flag_exp = xtype==SXFMT_EXP; | ||
29397 | if( xtype!=SXFMT_FLOAT ){ | ||
29398 | realvalue += rounder; | ||
29399 | if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } | ||
29400 | } | ||
29401 | if( xtype==SXFMT_GENERIC ){ | ||
29402 | flag_rtz = !flag_alternateform; | ||
29403 | if( exp<-4 || exp>precision ){ | ||
29404 | xtype = SXFMT_EXP; | ||
29405 | }else{ | ||
29406 | precision = precision - exp; | ||
29407 | xtype = SXFMT_FLOAT; | ||
29408 | } | ||
29409 | }else{ | ||
29410 | flag_rtz = 0; | ||
29411 | } | ||
29412 | /* | ||
29413 | ** The "exp+precision" test causes output to be of type etEXP if | ||
29414 | ** the precision is too large to fit in buf[]. | ||
29415 | */ | ||
29416 | nsd = 0; | ||
29417 | if( xtype==SXFMT_FLOAT && exp+precision<SXFMT_BUFSIZ-30 ){ | ||
29418 | flag_dp = (precision>0 || flag_alternateform); | ||
29419 | if( prefix ) *(bufpt++) = prefix; /* Sign */ | ||
29420 | if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */ | ||
29421 | else for(; exp>=0; exp--) *(bufpt++) = (char)getdigit(&realvalue, &nsd); | ||
29422 | if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */ | ||
29423 | for(exp++; exp<0 && precision>0; precision--, exp++){ | ||
29424 | *(bufpt++) = '0'; | ||
29425 | } | ||
29426 | while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd); | ||
29427 | *(bufpt--) = 0; /* Null terminate */ | ||
29428 | if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ | ||
29429 | while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; | ||
29430 | if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; | ||
29431 | } | ||
29432 | bufpt++; /* point to next free slot */ | ||
29433 | }else{ /* etEXP or etGENERIC */ | ||
29434 | flag_dp = (precision>0 || flag_alternateform); | ||
29435 | if( prefix ) *(bufpt++) = prefix; /* Sign */ | ||
29436 | *(bufpt++) = (char)getdigit(&realvalue, &nsd); /* First digit */ | ||
29437 | if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */ | ||
29438 | while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd); | ||
29439 | bufpt--; /* point to last digit */ | ||
29440 | if( flag_rtz && flag_dp ){ /* Remove tail zeros */ | ||
29441 | while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; | ||
29442 | if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; | ||
29443 | } | ||
29444 | bufpt++; /* point to next free slot */ | ||
29445 | if( exp || flag_exp ){ | ||
29446 | *(bufpt++) = infop->charset[0]; | ||
29447 | if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */ | ||
29448 | else { *(bufpt++) = '+'; } | ||
29449 | if( exp>=100 ){ | ||
29450 | *(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */ | ||
29451 | exp %= 100; | ||
29452 | } | ||
29453 | *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */ | ||
29454 | *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */ | ||
29455 | } | ||
29456 | } | ||
29457 | /* The converted number is in buf[] and zero terminated.Output it. | ||
29458 | ** Note that the number is in the usual order, not reversed as with | ||
29459 | ** integer conversions.*/ | ||
29460 | length = bufpt-buf; | ||
29461 | bufpt = buf; | ||
29462 | |||
29463 | /* Special case: Add leading zeros if the flag_zeropad flag is | ||
29464 | ** set and we are not left justified */ | ||
29465 | if( flag_zeropad && !flag_leftjustify && length < width){ | ||
29466 | int i; | ||
29467 | int nPad = width - length; | ||
29468 | for(i=width; i>=nPad; i--){ | ||
29469 | bufpt[i] = bufpt[i-nPad]; | ||
29470 | } | ||
29471 | i = prefix!=0; | ||
29472 | while( nPad-- ) bufpt[i++] = '0'; | ||
29473 | length = width; | ||
29474 | } | ||
29475 | #else | ||
29476 | bufpt = " "; | ||
29477 | length = (int)sizeof(" ") - 1; | ||
29478 | #endif /* SX_OMIT_FLOATINGPOINT */ | ||
29479 | break; | ||
29480 | case SXFMT_SIZE:{ | ||
29481 | int *pSize = va_arg(ap, int *); | ||
29482 | *pSize = ((SyFmtConsumer *)pUserData)->nLen; | ||
29483 | length = width = 0; | ||
29484 | } | ||
29485 | break; | ||
29486 | case SXFMT_PERCENT: | ||
29487 | buf[0] = '%'; | ||
29488 | bufpt = buf; | ||
29489 | length = 1; | ||
29490 | break; | ||
29491 | case SXFMT_CHARX: | ||
29492 | c = va_arg(ap, int); | ||
29493 | buf[0] = (char)c; | ||
29494 | /* Limit the precision to prevent overflowing buf[] during conversion */ | ||
29495 | if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; | ||
29496 | if( precision>=0 ){ | ||
29497 | for(idx=1; idx<precision; idx++) buf[idx] = (char)c; | ||
29498 | length = precision; | ||
29499 | }else{ | ||
29500 | length =1; | ||
29501 | } | ||
29502 | bufpt = buf; | ||
29503 | break; | ||
29504 | case SXFMT_STRING: | ||
29505 | bufpt = va_arg(ap, char*); | ||
29506 | if( bufpt==0 ){ | ||
29507 | bufpt = " "; | ||
29508 | length = (int)sizeof(" ")-1; | ||
29509 | break; | ||
29510 | } | ||
29511 | length = precision; | ||
29512 | if( precision < 0 ){ | ||
29513 | /* Symisc extension */ | ||
29514 | length = (int)SyStrlen(bufpt); | ||
29515 | } | ||
29516 | if( precision>=0 && precision<length ) length = precision; | ||
29517 | break; | ||
29518 | case SXFMT_RAWSTR:{ | ||
29519 | /* Symisc extension */ | ||
29520 | SyString *pStr = va_arg(ap, SyString *); | ||
29521 | if( pStr == 0 || pStr->zString == 0 ){ | ||
29522 | bufpt = " "; | ||
29523 | length = (int)sizeof(char); | ||
29524 | break; | ||
29525 | } | ||
29526 | bufpt = (char *)pStr->zString; | ||
29527 | length = (int)pStr->nByte; | ||
29528 | break; | ||
29529 | } | ||
29530 | case SXFMT_ERROR: | ||
29531 | buf[0] = '?'; | ||
29532 | bufpt = buf; | ||
29533 | length = (int)sizeof(char); | ||
29534 | if( c==0 ) zFormat--; | ||
29535 | break; | ||
29536 | }/* End switch over the format type */ | ||
29537 | /* | ||
29538 | ** The text of the conversion is pointed to by "bufpt" and is | ||
29539 | ** "length" characters long.The field width is "width".Do | ||
29540 | ** the output. | ||
29541 | */ | ||
29542 | if( !flag_leftjustify ){ | ||
29543 | register int nspace; | ||
29544 | nspace = width-length; | ||
29545 | if( nspace>0 ){ | ||
29546 | while( nspace>=etSPACESIZE ){ | ||
29547 | rc = xConsumer(spaces, etSPACESIZE, pUserData); | ||
29548 | if( rc != SXRET_OK ){ | ||
29549 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
29550 | } | ||
29551 | nspace -= etSPACESIZE; | ||
29552 | } | ||
29553 | if( nspace>0 ){ | ||
29554 | rc = xConsumer(spaces, (unsigned int)nspace, pUserData); | ||
29555 | if( rc != SXRET_OK ){ | ||
29556 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
29557 | } | ||
29558 | } | ||
29559 | } | ||
29560 | } | ||
29561 | if( length>0 ){ | ||
29562 | rc = xConsumer(bufpt, (unsigned int)length, pUserData); | ||
29563 | if( rc != SXRET_OK ){ | ||
29564 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
29565 | } | ||
29566 | } | ||
29567 | if( flag_leftjustify ){ | ||
29568 | register int nspace; | ||
29569 | nspace = width-length; | ||
29570 | if( nspace>0 ){ | ||
29571 | while( nspace>=etSPACESIZE ){ | ||
29572 | rc = xConsumer(spaces, etSPACESIZE, pUserData); | ||
29573 | if( rc != SXRET_OK ){ | ||
29574 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
29575 | } | ||
29576 | nspace -= etSPACESIZE; | ||
29577 | } | ||
29578 | if( nspace>0 ){ | ||
29579 | rc = xConsumer(spaces, (unsigned int)nspace, pUserData); | ||
29580 | if( rc != SXRET_OK ){ | ||
29581 | return SXERR_ABORT; /* Consumer routine request an operation abort */ | ||
29582 | } | ||
29583 | } | ||
29584 | } | ||
29585 | } | ||
29586 | }/* End for loop over the format string */ | ||
29587 | return errorflag ? SXERR_FORMAT : SXRET_OK; | ||
29588 | } | ||
29589 | static sxi32 FormatConsumer(const void *pSrc, unsigned int nLen, void *pData) | ||
29590 | { | ||
29591 | SyFmtConsumer *pConsumer = (SyFmtConsumer *)pData; | ||
29592 | sxi32 rc = SXERR_ABORT; | ||
29593 | switch(pConsumer->nType){ | ||
29594 | case SXFMT_CONS_PROC: | ||
29595 | /* User callback */ | ||
29596 | rc = pConsumer->uConsumer.sFunc.xUserConsumer(pSrc, nLen, pConsumer->uConsumer.sFunc.pUserData); | ||
29597 | break; | ||
29598 | case SXFMT_CONS_BLOB: | ||
29599 | /* Blob consumer */ | ||
29600 | rc = SyBlobAppend(pConsumer->uConsumer.pBlob, pSrc, (sxu32)nLen); | ||
29601 | break; | ||
29602 | default: | ||
29603 | /* Unknown consumer */ | ||
29604 | break; | ||
29605 | } | ||
29606 | /* Update total number of bytes consumed so far */ | ||
29607 | pConsumer->nLen += nLen; | ||
29608 | pConsumer->rc = rc; | ||
29609 | return rc; | ||
29610 | } | ||
29611 | static sxi32 FormatMount(sxi32 nType, void *pConsumer, ProcConsumer xUserCons, void *pUserData, sxu32 *pOutLen, const char *zFormat, va_list ap) | ||
29612 | { | ||
29613 | SyFmtConsumer sCons; | ||
29614 | sCons.nType = nType; | ||
29615 | sCons.rc = SXRET_OK; | ||
29616 | sCons.nLen = 0; | ||
29617 | if( pOutLen ){ | ||
29618 | *pOutLen = 0; | ||
29619 | } | ||
29620 | switch(nType){ | ||
29621 | case SXFMT_CONS_PROC: | ||
29622 | #if defined(UNTRUST) | ||
29623 | if( xUserCons == 0 ){ | ||
29624 | return SXERR_EMPTY; | ||
29625 | } | ||
29626 | #endif | ||
29627 | sCons.uConsumer.sFunc.xUserConsumer = xUserCons; | ||
29628 | sCons.uConsumer.sFunc.pUserData = pUserData; | ||
29629 | break; | ||
29630 | case SXFMT_CONS_BLOB: | ||
29631 | sCons.uConsumer.pBlob = (SyBlob *)pConsumer; | ||
29632 | break; | ||
29633 | default: | ||
29634 | return SXERR_UNKNOWN; | ||
29635 | } | ||
29636 | InternFormat(FormatConsumer, &sCons, zFormat, ap); | ||
29637 | if( pOutLen ){ | ||
29638 | *pOutLen = sCons.nLen; | ||
29639 | } | ||
29640 | return sCons.rc; | ||
29641 | } | ||
29642 | JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...) | ||
29643 | { | ||
29644 | va_list ap; | ||
29645 | sxi32 rc; | ||
29646 | #if defined(UNTRUST) | ||
29647 | if( SX_EMPTY_STR(zFormat) ){ | ||
29648 | return SXERR_EMPTY; | ||
29649 | } | ||
29650 | #endif | ||
29651 | va_start(ap, zFormat); | ||
29652 | rc = FormatMount(SXFMT_CONS_PROC, 0, xConsumer, pData, 0, zFormat, ap); | ||
29653 | va_end(ap); | ||
29654 | return rc; | ||
29655 | } | ||
29656 | JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...) | ||
29657 | { | ||
29658 | va_list ap; | ||
29659 | sxu32 n; | ||
29660 | #if defined(UNTRUST) | ||
29661 | if( SX_EMPTY_STR(zFormat) ){ | ||
29662 | return 0; | ||
29663 | } | ||
29664 | #endif | ||
29665 | va_start(ap, zFormat); | ||
29666 | FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); | ||
29667 | va_end(ap); | ||
29668 | return n; | ||
29669 | } | ||
29670 | JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap) | ||
29671 | { | ||
29672 | sxu32 n = 0; /* cc warning */ | ||
29673 | #if defined(UNTRUST) | ||
29674 | if( SX_EMPTY_STR(zFormat) ){ | ||
29675 | return 0; | ||
29676 | } | ||
29677 | #endif | ||
29678 | FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); | ||
29679 | return n; | ||
29680 | } | ||
29681 | JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...) | ||
29682 | { | ||
29683 | SyBlob sBlob; | ||
29684 | va_list ap; | ||
29685 | sxu32 n; | ||
29686 | #if defined(UNTRUST) | ||
29687 | if( SX_EMPTY_STR(zFormat) ){ | ||
29688 | return 0; | ||
29689 | } | ||
29690 | #endif | ||
29691 | if( SXRET_OK != SyBlobInitFromBuf(&sBlob, zBuf, nLen - 1) ){ | ||
29692 | return 0; | ||
29693 | } | ||
29694 | va_start(ap, zFormat); | ||
29695 | FormatMount(SXFMT_CONS_BLOB, &sBlob, 0, 0, 0, zFormat, ap); | ||
29696 | va_end(ap); | ||
29697 | n = SyBlobLength(&sBlob); | ||
29698 | /* Append the null terminator */ | ||
29699 | sBlob.mByte++; | ||
29700 | SyBlobAppend(&sBlob, "\0", sizeof(char)); | ||
29701 | return n; | ||
29702 | } | ||
29703 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
29704 | /* | ||
29705 | * Zip File Format: | ||
29706 | * | ||
29707 | * Byte order: Little-endian | ||
29708 | * | ||
29709 | * [Local file header + Compressed data [+ Extended local header]?]* | ||
29710 | * [Central directory]* | ||
29711 | * [End of central directory record] | ||
29712 | * | ||
29713 | * Local file header:* | ||
29714 | * Offset Length Contents | ||
29715 | * 0 4 bytes Local file header signature (0x04034b50) | ||
29716 | * 4 2 bytes Version needed to extract | ||
29717 | * 6 2 bytes General purpose bit flag | ||
29718 | * 8 2 bytes Compression method | ||
29719 | * 10 2 bytes Last mod file time | ||
29720 | * 12 2 bytes Last mod file date | ||
29721 | * 14 4 bytes CRC-32 | ||
29722 | * 18 4 bytes Compressed size (n) | ||
29723 | * 22 4 bytes Uncompressed size | ||
29724 | * 26 2 bytes Filename length (f) | ||
29725 | * 28 2 bytes Extra field length (e) | ||
29726 | * 30 (f)bytes Filename | ||
29727 | * (e)bytes Extra field | ||
29728 | * (n)bytes Compressed data | ||
29729 | * | ||
29730 | * Extended local header:* | ||
29731 | * Offset Length Contents | ||
29732 | * 0 4 bytes Extended Local file header signature (0x08074b50) | ||
29733 | * 4 4 bytes CRC-32 | ||
29734 | * 8 4 bytes Compressed size | ||
29735 | * 12 4 bytes Uncompressed size | ||
29736 | * | ||
29737 | * Extra field:?(if any) | ||
29738 | * Offset Length Contents | ||
29739 | * 0 2 bytes Header ID (0x001 until 0xfb4a) see extended appnote from Info-zip | ||
29740 | * 2 2 bytes Data size (g) | ||
29741 | * (g) bytes (g) bytes of extra field | ||
29742 | * | ||
29743 | * Central directory:* | ||
29744 | * Offset Length Contents | ||
29745 | * 0 4 bytes Central file header signature (0x02014b50) | ||
29746 | * 4 2 bytes Version made by | ||
29747 | * 6 2 bytes Version needed to extract | ||
29748 | * 8 2 bytes General purpose bit flag | ||
29749 | * 10 2 bytes Compression method | ||
29750 | * 12 2 bytes Last mod file time | ||
29751 | * 14 2 bytes Last mod file date | ||
29752 | * 16 4 bytes CRC-32 | ||
29753 | * 20 4 bytes Compressed size | ||
29754 | * 24 4 bytes Uncompressed size | ||
29755 | * 28 2 bytes Filename length (f) | ||
29756 | * 30 2 bytes Extra field length (e) | ||
29757 | * 32 2 bytes File comment length (c) | ||
29758 | * 34 2 bytes Disk number start | ||
29759 | * 36 2 bytes Internal file attributes | ||
29760 | * 38 4 bytes External file attributes | ||
29761 | * 42 4 bytes Relative offset of local header | ||
29762 | * 46 (f)bytes Filename | ||
29763 | * (e)bytes Extra field | ||
29764 | * (c)bytes File comment | ||
29765 | * | ||
29766 | * End of central directory record: | ||
29767 | * Offset Length Contents | ||
29768 | * 0 4 bytes End of central dir signature (0x06054b50) | ||
29769 | * 4 2 bytes Number of this disk | ||
29770 | * 6 2 bytes Number of the disk with the start of the central directory | ||
29771 | * 8 2 bytes Total number of entries in the central dir on this disk | ||
29772 | * 10 2 bytes Total number of entries in the central dir | ||
29773 | * 12 4 bytes Size of the central directory | ||
29774 | * 16 4 bytes Offset of start of central directory with respect to the starting disk number | ||
29775 | * 20 2 bytes zipfile comment length (c) | ||
29776 | * 22 (c)bytes zipfile comment | ||
29777 | * | ||
29778 | * compression method: (2 bytes) | ||
29779 | * 0 - The file is stored (no compression) | ||
29780 | * 1 - The file is Shrunk | ||
29781 | * 2 - The file is Reduced with compression factor 1 | ||
29782 | * 3 - The file is Reduced with compression factor 2 | ||
29783 | * 4 - The file is Reduced with compression factor 3 | ||
29784 | * 5 - The file is Reduced with compression factor 4 | ||
29785 | * 6 - The file is Imploded | ||
29786 | * 7 - Reserved for Tokenizing compression algorithm | ||
29787 | * 8 - The file is Deflated | ||
29788 | */ | ||
29789 | |||
29790 | #define SXMAKE_ZIP_WORKBUF (SXU16_HIGH/2) /* 32KB Initial working buffer size */ | ||
29791 | #define SXMAKE_ZIP_EXTRACT_VER 0x000a /* Version needed to extract */ | ||
29792 | #define SXMAKE_ZIP_VER 0x003 /* Version made by */ | ||
29793 | |||
29794 | #define SXZIP_CENTRAL_MAGIC 0x02014b50 | ||
29795 | #define SXZIP_END_CENTRAL_MAGIC 0x06054b50 | ||
29796 | #define SXZIP_LOCAL_MAGIC 0x04034b50 | ||
29797 | /*#define SXZIP_CRC32_START 0xdebb20e3*/ | ||
29798 | |||
29799 | #define SXZIP_LOCAL_HDRSZ 30 /* Local header size */ | ||
29800 | #define SXZIP_LOCAL_EXT_HDRZ 16 /* Extended local header(footer) size */ | ||
29801 | #define SXZIP_CENTRAL_HDRSZ 46 /* Central directory header size */ | ||
29802 | #define SXZIP_END_CENTRAL_HDRSZ 22 /* End of central directory header size */ | ||
29803 | |||
29804 | #define SXARCHIVE_HASH_SIZE 64 /* Starting hash table size(MUST BE POWER OF 2)*/ | ||
29805 | static sxi32 SyLittleEndianUnpack32(sxu32 *uNB, const unsigned char *buf, sxu32 Len) | ||
29806 | { | ||
29807 | if( Len < sizeof(sxu32) ){ | ||
29808 | return SXERR_SHORT; | ||
29809 | } | ||
29810 | *uNB = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); | ||
29811 | return SXRET_OK; | ||
29812 | } | ||
29813 | static sxi32 SyLittleEndianUnpack16(sxu16 *pOut, const unsigned char *zBuf, sxu32 nLen) | ||
29814 | { | ||
29815 | if( nLen < sizeof(sxu16) ){ | ||
29816 | return SXERR_SHORT; | ||
29817 | } | ||
29818 | *pOut = zBuf[0] + (zBuf[1] <<8); | ||
29819 | |||
29820 | return SXRET_OK; | ||
29821 | } | ||
29822 | /* | ||
29823 | * Archive hashtable manager | ||
29824 | */ | ||
29825 | static sxi32 ArchiveHashGetEntry(SyArchive *pArch, const char *zName, sxu32 nLen, SyArchiveEntry **ppEntry) | ||
29826 | { | ||
29827 | SyArchiveEntry *pBucketEntry; | ||
29828 | SyString sEntry; | ||
29829 | sxu32 nHash; | ||
29830 | |||
29831 | nHash = pArch->xHash(zName, nLen); | ||
29832 | pBucketEntry = pArch->apHash[nHash & (pArch->nSize - 1)]; | ||
29833 | |||
29834 | SyStringInitFromBuf(&sEntry, zName, nLen); | ||
29835 | |||
29836 | for(;;){ | ||
29837 | if( pBucketEntry == 0 ){ | ||
29838 | break; | ||
29839 | } | ||
29840 | if( nHash == pBucketEntry->nHash && pArch->xCmp(&sEntry, &pBucketEntry->sFileName) == 0 ){ | ||
29841 | if( ppEntry ){ | ||
29842 | *ppEntry = pBucketEntry; | ||
29843 | } | ||
29844 | return SXRET_OK; | ||
29845 | } | ||
29846 | pBucketEntry = pBucketEntry->pNextHash; | ||
29847 | } | ||
29848 | return SXERR_NOTFOUND; | ||
29849 | } | ||
29850 | static void ArchiveHashBucketInstall(SyArchiveEntry **apTable, sxu32 nBucket, SyArchiveEntry *pEntry) | ||
29851 | { | ||
29852 | pEntry->pNextHash = apTable[nBucket]; | ||
29853 | if( apTable[nBucket] != 0 ){ | ||
29854 | apTable[nBucket]->pPrevHash = pEntry; | ||
29855 | } | ||
29856 | apTable[nBucket] = pEntry; | ||
29857 | } | ||
29858 | static sxi32 ArchiveHashGrowTable(SyArchive *pArch) | ||
29859 | { | ||
29860 | sxu32 nNewSize = pArch->nSize * 2; | ||
29861 | SyArchiveEntry **apNew; | ||
29862 | SyArchiveEntry *pEntry; | ||
29863 | sxu32 n; | ||
29864 | |||
29865 | /* Allocate a new table */ | ||
29866 | apNew = (SyArchiveEntry **)SyMemBackendAlloc(pArch->pAllocator, nNewSize * sizeof(SyArchiveEntry *)); | ||
29867 | if( apNew == 0 ){ | ||
29868 | return SXRET_OK; /* Not so fatal, simply a performance hit */ | ||
29869 | } | ||
29870 | SyZero(apNew, nNewSize * sizeof(SyArchiveEntry *)); | ||
29871 | /* Rehash old entries */ | ||
29872 | for( n = 0 , pEntry = pArch->pList ; n < pArch->nLoaded ; n++ , pEntry = pEntry->pNext ){ | ||
29873 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
29874 | ArchiveHashBucketInstall(apNew, pEntry->nHash & (nNewSize - 1), pEntry); | ||
29875 | } | ||
29876 | /* Release the old table */ | ||
29877 | SyMemBackendFree(pArch->pAllocator, pArch->apHash); | ||
29878 | pArch->apHash = apNew; | ||
29879 | pArch->nSize = nNewSize; | ||
29880 | |||
29881 | return SXRET_OK; | ||
29882 | } | ||
29883 | static sxi32 ArchiveHashInstallEntry(SyArchive *pArch, SyArchiveEntry *pEntry) | ||
29884 | { | ||
29885 | if( pArch->nLoaded > pArch->nSize * 3 ){ | ||
29886 | ArchiveHashGrowTable(&(*pArch)); | ||
29887 | } | ||
29888 | pEntry->nHash = pArch->xHash(SyStringData(&pEntry->sFileName), SyStringLength(&pEntry->sFileName)); | ||
29889 | /* Install the entry in its bucket */ | ||
29890 | ArchiveHashBucketInstall(pArch->apHash, pEntry->nHash & (pArch->nSize - 1), pEntry); | ||
29891 | MACRO_LD_PUSH(pArch->pList, pEntry); | ||
29892 | pArch->nLoaded++; | ||
29893 | |||
29894 | return SXRET_OK; | ||
29895 | } | ||
29896 | /* | ||
29897 | * Parse the End of central directory and report status | ||
29898 | */ | ||
29899 | static sxi32 ParseEndOfCentralDirectory(SyArchive *pArch, const unsigned char *zBuf) | ||
29900 | { | ||
29901 | sxu32 nMagic = 0; /* cc -O6 warning */ | ||
29902 | sxi32 rc; | ||
29903 | |||
29904 | /* Sanity check */ | ||
29905 | rc = SyLittleEndianUnpack32(&nMagic, zBuf, sizeof(sxu32)); | ||
29906 | if( /* rc != SXRET_OK || */nMagic != SXZIP_END_CENTRAL_MAGIC ){ | ||
29907 | return SXERR_CORRUPT; | ||
29908 | } | ||
29909 | /* # of entries */ | ||
29910 | rc = SyLittleEndianUnpack16((sxu16 *)&pArch->nEntry, &zBuf[8], sizeof(sxu16)); | ||
29911 | if( /* rc != SXRET_OK || */ pArch->nEntry > SXI16_HIGH /* SXU16_HIGH */ ){ | ||
29912 | return SXERR_CORRUPT; | ||
29913 | } | ||
29914 | /* Size of central directory */ | ||
29915 | rc = SyLittleEndianUnpack32(&pArch->nCentralSize, &zBuf[12], sizeof(sxu32)); | ||
29916 | if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ | ||
29917 | return SXERR_CORRUPT; | ||
29918 | } | ||
29919 | /* Starting offset of central directory */ | ||
29920 | rc = SyLittleEndianUnpack32(&pArch->nCentralOfft, &zBuf[16], sizeof(sxu32)); | ||
29921 | if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ | ||
29922 | return SXERR_CORRUPT; | ||
29923 | } | ||
29924 | |||
29925 | return SXRET_OK; | ||
29926 | } | ||
29927 | /* | ||
29928 | * Fill the zip entry with the appropriate information from the central directory | ||
29929 | */ | ||
29930 | static sxi32 GetCentralDirectoryEntry(SyArchive *pArch, SyArchiveEntry *pEntry, const unsigned char *zCentral, sxu32 *pNextOffset) | ||
29931 | { | ||
29932 | SyString *pName = &pEntry->sFileName; /* File name */ | ||
29933 | sxu16 nDosDate, nDosTime; | ||
29934 | sxu16 nComment = 0 ; | ||
29935 | sxu32 nMagic = 0; /* cc -O6 warning */ | ||
29936 | sxi32 rc; | ||
29937 | nDosDate = nDosTime = 0; /* cc -O6 warning */ | ||
29938 | SXUNUSED(pArch); | ||
29939 | /* Sanity check */ | ||
29940 | rc = SyLittleEndianUnpack32(&nMagic, zCentral, sizeof(sxu32)); | ||
29941 | if( /* rc != SXRET_OK || */ nMagic != SXZIP_CENTRAL_MAGIC ){ | ||
29942 | rc = SXERR_CORRUPT; | ||
29943 | /* | ||
29944 | * Try to recover by examing the next central directory record. | ||
29945 | * Dont worry here, there is no risk of an infinite loop since | ||
29946 | * the buffer size is delimited. | ||
29947 | */ | ||
29948 | |||
29949 | /* pName->nByte = 0; nComment = 0; pName->nExtra = 0 */ | ||
29950 | goto update; | ||
29951 | } | ||
29952 | /* | ||
29953 | * entry name length | ||
29954 | */ | ||
29955 | SyLittleEndianUnpack16((sxu16 *)&pName->nByte, &zCentral[28], sizeof(sxu16)); | ||
29956 | if( pName->nByte > SXI16_HIGH /* SXU16_HIGH */){ | ||
29957 | rc = SXERR_BIG; | ||
29958 | goto update; | ||
29959 | } | ||
29960 | /* Extra information */ | ||
29961 | SyLittleEndianUnpack16(&pEntry->nExtra, &zCentral[30], sizeof(sxu16)); | ||
29962 | /* Comment length */ | ||
29963 | SyLittleEndianUnpack16(&nComment, &zCentral[32], sizeof(sxu16)); | ||
29964 | /* Compression method 0 == stored / 8 == deflated */ | ||
29965 | rc = SyLittleEndianUnpack16(&pEntry->nComprMeth, &zCentral[10], sizeof(sxu16)); | ||
29966 | /* DOS Timestamp */ | ||
29967 | SyLittleEndianUnpack16(&nDosTime, &zCentral[12], sizeof(sxu16)); | ||
29968 | SyLittleEndianUnpack16(&nDosDate, &zCentral[14], sizeof(sxu16)); | ||
29969 | SyDosTimeFormat((nDosDate << 16 | nDosTime), &pEntry->sFmt); | ||
29970 | /* Little hack to fix month index */ | ||
29971 | pEntry->sFmt.tm_mon--; | ||
29972 | /* CRC32 */ | ||
29973 | rc = SyLittleEndianUnpack32(&pEntry->nCrc, &zCentral[16], sizeof(sxu32)); | ||
29974 | /* Content size before compression */ | ||
29975 | rc = SyLittleEndianUnpack32(&pEntry->nByte, &zCentral[24], sizeof(sxu32)); | ||
29976 | if( pEntry->nByte > SXI32_HIGH ){ | ||
29977 | rc = SXERR_BIG; | ||
29978 | goto update; | ||
29979 | } | ||
29980 | /* | ||
29981 | * Content size after compression. | ||
29982 | * Note that if the file is stored pEntry->nByte should be equal to pEntry->nByteCompr | ||
29983 | */ | ||
29984 | rc = SyLittleEndianUnpack32(&pEntry->nByteCompr, &zCentral[20], sizeof(sxu32)); | ||
29985 | if( pEntry->nByteCompr > SXI32_HIGH ){ | ||
29986 | rc = SXERR_BIG; | ||
29987 | goto update; | ||
29988 | } | ||
29989 | /* Finally grab the contents offset */ | ||
29990 | SyLittleEndianUnpack32(&pEntry->nOfft, &zCentral[42], sizeof(sxu32)); | ||
29991 | if( pEntry->nOfft > SXI32_HIGH ){ | ||
29992 | rc = SXERR_BIG; | ||
29993 | goto update; | ||
29994 | } | ||
29995 | rc = SXRET_OK; | ||
29996 | update: | ||
29997 | /* Update the offset to point to the next central directory record */ | ||
29998 | *pNextOffset = SXZIP_CENTRAL_HDRSZ + pName->nByte + pEntry->nExtra + nComment; | ||
29999 | return rc; /* Report failure or success */ | ||
30000 | } | ||
30001 | static sxi32 ZipFixOffset(SyArchiveEntry *pEntry, void *pSrc) | ||
30002 | { | ||
30003 | sxu16 nExtra, nNameLen; | ||
30004 | unsigned char *zHdr; | ||
30005 | nExtra = nNameLen = 0; | ||
30006 | zHdr = (unsigned char *)pSrc; | ||
30007 | zHdr = &zHdr[pEntry->nOfft]; | ||
30008 | if( SyMemcmp(zHdr, "PK\003\004", sizeof(sxu32)) != 0 ){ | ||
30009 | return SXERR_CORRUPT; | ||
30010 | } | ||
30011 | SyLittleEndianUnpack16(&nNameLen, &zHdr[26], sizeof(sxu16)); | ||
30012 | SyLittleEndianUnpack16(&nExtra, &zHdr[28], sizeof(sxu16)); | ||
30013 | /* Fix contents offset */ | ||
30014 | pEntry->nOfft += SXZIP_LOCAL_HDRSZ + nExtra + nNameLen; | ||
30015 | return SXRET_OK; | ||
30016 | } | ||
30017 | /* | ||
30018 | * Extract all valid entries from the central directory | ||
30019 | */ | ||
30020 | static sxi32 ZipExtract(SyArchive *pArch, const unsigned char *zCentral, sxu32 nLen, void *pSrc) | ||
30021 | { | ||
30022 | SyArchiveEntry *pEntry, *pDup; | ||
30023 | const unsigned char *zEnd ; /* End of central directory */ | ||
30024 | sxu32 nIncr, nOfft; /* Central Offset */ | ||
30025 | SyString *pName; /* Entry name */ | ||
30026 | char *zName; | ||
30027 | sxi32 rc; | ||
30028 | |||
30029 | nOfft = nIncr = 0; | ||
30030 | zEnd = &zCentral[nLen]; | ||
30031 | |||
30032 | for(;;){ | ||
30033 | if( &zCentral[nOfft] >= zEnd ){ | ||
30034 | break; | ||
30035 | } | ||
30036 | /* Add a new entry */ | ||
30037 | pEntry = (SyArchiveEntry *)SyMemBackendPoolAlloc(pArch->pAllocator, sizeof(SyArchiveEntry)); | ||
30038 | if( pEntry == 0 ){ | ||
30039 | break; | ||
30040 | } | ||
30041 | SyZero(pEntry, sizeof(SyArchiveEntry)); | ||
30042 | pEntry->nMagic = SXARCH_MAGIC; | ||
30043 | nIncr = 0; | ||
30044 | rc = GetCentralDirectoryEntry(&(*pArch), pEntry, &zCentral[nOfft], &nIncr); | ||
30045 | if( rc == SXRET_OK ){ | ||
30046 | /* Fix the starting record offset so we can access entry contents correctly */ | ||
30047 | rc = ZipFixOffset(pEntry, pSrc); | ||
30048 | } | ||
30049 | if(rc != SXRET_OK ){ | ||
30050 | sxu32 nJmp = 0; | ||
30051 | SyMemBackendPoolFree(pArch->pAllocator, pEntry); | ||
30052 | /* Try to recover by brute-forcing for a valid central directory record */ | ||
30053 | if( SXRET_OK == SyBlobSearch((const void *)&zCentral[nOfft + nIncr], (sxu32)(zEnd - &zCentral[nOfft + nIncr]), | ||
30054 | (const void *)"PK\001\002", sizeof(sxu32), &nJmp)){ | ||
30055 | nOfft += nIncr + nJmp; /* Check next entry */ | ||
30056 | continue; | ||
30057 | } | ||
30058 | break; /* Giving up, archive is hopelessly corrupted */ | ||
30059 | } | ||
30060 | pName = &pEntry->sFileName; | ||
30061 | pName->zString = (const char *)&zCentral[nOfft + SXZIP_CENTRAL_HDRSZ]; | ||
30062 | if( pName->nByte <= 0 || ( pEntry->nByte <= 0 && pName->zString[pName->nByte - 1] != '/') ){ | ||
30063 | /* Ignore zero length records (except folders) and records without names */ | ||
30064 | SyMemBackendPoolFree(pArch->pAllocator, pEntry); | ||
30065 | nOfft += nIncr; /* Check next entry */ | ||
30066 | continue; | ||
30067 | } | ||
30068 | zName = SyMemBackendStrDup(pArch->pAllocator, pName->zString, pName->nByte); | ||
30069 | if( zName == 0 ){ | ||
30070 | SyMemBackendPoolFree(pArch->pAllocator, pEntry); | ||
30071 | nOfft += nIncr; /* Check next entry */ | ||
30072 | continue; | ||
30073 | } | ||
30074 | pName->zString = (const char *)zName; | ||
30075 | /* Check for duplicates */ | ||
30076 | rc = ArchiveHashGetEntry(&(*pArch), pName->zString, pName->nByte, &pDup); | ||
30077 | if( rc == SXRET_OK ){ | ||
30078 | /* Another entry with the same name exists ; link them together */ | ||
30079 | pEntry->pNextName = pDup->pNextName; | ||
30080 | pDup->pNextName = pEntry; | ||
30081 | pDup->nDup++; | ||
30082 | }else{ | ||
30083 | /* Insert in hashtable */ | ||
30084 | ArchiveHashInstallEntry(pArch, pEntry); | ||
30085 | } | ||
30086 | nOfft += nIncr; /* Check next record */ | ||
30087 | } | ||
30088 | pArch->pCursor = pArch->pList; | ||
30089 | |||
30090 | return pArch->nLoaded > 0 ? SXRET_OK : SXERR_EMPTY; | ||
30091 | } | ||
30092 | JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen) | ||
30093 | { | ||
30094 | const unsigned char *zCentral, *zEnd; | ||
30095 | sxi32 rc; | ||
30096 | #if defined(UNTRUST) | ||
30097 | if( SXARCH_INVALID(pArch) || zBuf == 0 ){ | ||
30098 | return SXERR_INVALID; | ||
30099 | } | ||
30100 | #endif | ||
30101 | /* The miminal size of a zip archive: | ||
30102 | * LOCAL_HDR_SZ + CENTRAL_HDR_SZ + END_OF_CENTRAL_HDR_SZ | ||
30103 | * 30 46 22 | ||
30104 | */ | ||
30105 | if( nLen < SXZIP_LOCAL_HDRSZ + SXZIP_CENTRAL_HDRSZ + SXZIP_END_CENTRAL_HDRSZ ){ | ||
30106 | return SXERR_CORRUPT; /* Don't bother processing return immediately */ | ||
30107 | } | ||
30108 | |||
30109 | zEnd = (unsigned char *)&zBuf[nLen - SXZIP_END_CENTRAL_HDRSZ]; | ||
30110 | /* Find the end of central directory */ | ||
30111 | while( ((sxu32)((unsigned char *)&zBuf[nLen] - zEnd) < (SXZIP_END_CENTRAL_HDRSZ + SXI16_HIGH)) && | ||
30112 | zEnd > (unsigned char *)zBuf && SyMemcmp(zEnd, "PK\005\006", sizeof(sxu32)) != 0 ){ | ||
30113 | zEnd--; | ||
30114 | } | ||
30115 | /* Parse the end of central directory */ | ||
30116 | rc = ParseEndOfCentralDirectory(&(*pArch), zEnd); | ||
30117 | if( rc != SXRET_OK ){ | ||
30118 | return rc; | ||
30119 | } | ||
30120 | |||
30121 | /* Find the starting offset of the central directory */ | ||
30122 | zCentral = &zEnd[-(sxi32)pArch->nCentralSize]; | ||
30123 | if( zCentral <= (unsigned char *)zBuf || SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){ | ||
30124 | if( pArch->nCentralOfft >= nLen ){ | ||
30125 | /* Corrupted central directory offset */ | ||
30126 | return SXERR_CORRUPT; | ||
30127 | } | ||
30128 | zCentral = (unsigned char *)&zBuf[pArch->nCentralOfft]; | ||
30129 | if( SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){ | ||
30130 | /* Corrupted zip archive */ | ||
30131 | return SXERR_CORRUPT; | ||
30132 | } | ||
30133 | /* Fall thru and extract all valid entries from the central directory */ | ||
30134 | } | ||
30135 | rc = ZipExtract(&(*pArch), zCentral, (sxu32)(zEnd - zCentral), (void *)zBuf); | ||
30136 | return rc; | ||
30137 | } | ||
30138 | /* | ||
30139 | * Default comparison function. | ||
30140 | */ | ||
30141 | static sxi32 ArchiveHashCmp(const SyString *pStr1, const SyString *pStr2) | ||
30142 | { | ||
30143 | sxi32 rc; | ||
30144 | rc = SyStringCmp(pStr1, pStr2, SyMemcmp); | ||
30145 | return rc; | ||
30146 | } | ||
30147 | JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp) | ||
30148 | { | ||
30149 | SyArchiveEntry **apHash; | ||
30150 | #if defined(UNTRUST) | ||
30151 | if( pArch == 0 ){ | ||
30152 | return SXERR_EMPTY; | ||
30153 | } | ||
30154 | #endif | ||
30155 | SyZero(pArch, sizeof(SyArchive)); | ||
30156 | /* Allocate a new hashtable */ | ||
30157 | apHash = (SyArchiveEntry **)SyMemBackendAlloc(&(*pAllocator), SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); | ||
30158 | if( apHash == 0){ | ||
30159 | return SXERR_MEM; | ||
30160 | } | ||
30161 | SyZero(apHash, SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); | ||
30162 | pArch->apHash = apHash; | ||
30163 | pArch->xHash = xHash ? xHash : SyBinHash; | ||
30164 | pArch->xCmp = xCmp ? xCmp : ArchiveHashCmp; | ||
30165 | pArch->nSize = SXARCHIVE_HASH_SIZE; | ||
30166 | pArch->pAllocator = &(*pAllocator); | ||
30167 | pArch->nMagic = SXARCH_MAGIC; | ||
30168 | return SXRET_OK; | ||
30169 | } | ||
30170 | static sxi32 ArchiveReleaseEntry(SyMemBackend *pAllocator, SyArchiveEntry *pEntry) | ||
30171 | { | ||
30172 | SyArchiveEntry *pDup = pEntry->pNextName; | ||
30173 | SyArchiveEntry *pNextDup; | ||
30174 | |||
30175 | /* Release duplicates first since there are not stored in the hashtable */ | ||
30176 | for(;;){ | ||
30177 | if( pEntry->nDup == 0 ){ | ||
30178 | break; | ||
30179 | } | ||
30180 | pNextDup = pDup->pNextName; | ||
30181 | pDup->nMagic = 0x2661; | ||
30182 | SyMemBackendFree(pAllocator, (void *)SyStringData(&pDup->sFileName)); | ||
30183 | SyMemBackendPoolFree(pAllocator, pDup); | ||
30184 | pDup = pNextDup; | ||
30185 | pEntry->nDup--; | ||
30186 | } | ||
30187 | pEntry->nMagic = 0x2661; | ||
30188 | SyMemBackendFree(pAllocator, (void *)SyStringData(&pEntry->sFileName)); | ||
30189 | SyMemBackendPoolFree(pAllocator, pEntry); | ||
30190 | return SXRET_OK; | ||
30191 | } | ||
30192 | JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch) | ||
30193 | { | ||
30194 | SyArchiveEntry *pEntry, *pNext; | ||
30195 | pEntry = pArch->pList; | ||
30196 | for(;;){ | ||
30197 | if( pArch->nLoaded < 1 ){ | ||
30198 | break; | ||
30199 | } | ||
30200 | pNext = pEntry->pNext; | ||
30201 | MACRO_LD_REMOVE(pArch->pList, pEntry); | ||
30202 | ArchiveReleaseEntry(pArch->pAllocator, pEntry); | ||
30203 | pEntry = pNext; | ||
30204 | pArch->nLoaded--; | ||
30205 | } | ||
30206 | SyMemBackendFree(pArch->pAllocator, pArch->apHash); | ||
30207 | pArch->pCursor = 0; | ||
30208 | pArch->nMagic = 0x2626; | ||
30209 | return SXRET_OK; | ||
30210 | } | ||
30211 | JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch) | ||
30212 | { | ||
30213 | pArch->pCursor = pArch->pList; | ||
30214 | return SXRET_OK; | ||
30215 | } | ||
30216 | JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry) | ||
30217 | { | ||
30218 | SyArchiveEntry *pNext; | ||
30219 | if( pArch->pCursor == 0 ){ | ||
30220 | /* Rewind the cursor */ | ||
30221 | pArch->pCursor = pArch->pList; | ||
30222 | return SXERR_EOF; | ||
30223 | } | ||
30224 | *ppEntry = pArch->pCursor; | ||
30225 | pNext = pArch->pCursor->pNext; | ||
30226 | /* Advance the cursor to the next entry */ | ||
30227 | pArch->pCursor = pNext; | ||
30228 | return SXRET_OK; | ||
30229 | } | ||
30230 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
30231 | /* | ||
30232 | * Psuedo Random Number Generator (PRNG) | ||
30233 | * @authors: SQLite authors <http://www.sqlite.org/> | ||
30234 | * @status: Public Domain | ||
30235 | * NOTE: | ||
30236 | * Nothing in this file or anywhere else in the library does any kind of | ||
30237 | * encryption.The RC4 algorithm is being used as a PRNG (pseudo-random | ||
30238 | * number generator) not as an encryption device. | ||
30239 | */ | ||
30240 | #define SXPRNG_MAGIC 0x13C4 | ||
30241 | #ifdef __UNIXES__ | ||
30242 | #include <sys/types.h> | ||
30243 | #include <sys/stat.h> | ||
30244 | #include <fcntl.h> | ||
30245 | #include <unistd.h> | ||
30246 | #include <errno.h> | ||
30247 | #include <time.h> | ||
30248 | #include <sys/time.h> | ||
30249 | #endif | ||
30250 | static sxi32 SyOSUtilRandomSeed(void *pBuf, sxu32 nLen, void *pUnused) | ||
30251 | { | ||
30252 | char *zBuf = (char *)pBuf; | ||
30253 | #ifdef __WINNT__ | ||
30254 | DWORD nProcessID; /* Yes, keep it uninitialized when compiling using the MinGW32 builds tools */ | ||
30255 | #elif defined(__UNIXES__) | ||
30256 | pid_t pid; | ||
30257 | int fd; | ||
30258 | #else | ||
30259 | char zGarbage[128]; /* Yes, keep this buffer uninitialized */ | ||
30260 | #endif | ||
30261 | SXUNUSED(pUnused); | ||
30262 | #ifdef __WINNT__ | ||
30263 | #ifndef __MINGW32__ | ||
30264 | nProcessID = GetProcessId(GetCurrentProcess()); | ||
30265 | #endif | ||
30266 | SyMemcpy((const void *)&nProcessID, zBuf, SXMIN(nLen, sizeof(DWORD))); | ||
30267 | if( (sxu32)(&zBuf[nLen] - &zBuf[sizeof(DWORD)]) >= sizeof(SYSTEMTIME) ){ | ||
30268 | GetSystemTime((LPSYSTEMTIME)&zBuf[sizeof(DWORD)]); | ||
30269 | } | ||
30270 | #elif defined(__UNIXES__) | ||
30271 | fd = open("/dev/urandom", O_RDONLY); | ||
30272 | if (fd >= 0 ){ | ||
30273 | if( read(fd, zBuf, nLen) > 0 ){ | ||
30274 | return SXRET_OK; | ||
30275 | } | ||
30276 | /* FALL THRU */ | ||
30277 | } | ||
30278 | pid = getpid(); | ||
30279 | SyMemcpy((const void *)&pid, zBuf, SXMIN(nLen, sizeof(pid_t))); | ||
30280 | if( &zBuf[nLen] - &zBuf[sizeof(pid_t)] >= (int)sizeof(struct timeval) ){ | ||
30281 | gettimeofday((struct timeval *)&zBuf[sizeof(pid_t)], 0); | ||
30282 | } | ||
30283 | #else | ||
30284 | /* Fill with uninitialized data */ | ||
30285 | SyMemcpy(zGarbage, zBuf, SXMIN(nLen, sizeof(zGarbage))); | ||
30286 | #endif | ||
30287 | return SXRET_OK; | ||
30288 | } | ||
30289 | JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void * pUserData) | ||
30290 | { | ||
30291 | char zSeed[256]; | ||
30292 | sxu8 t; | ||
30293 | sxi32 rc; | ||
30294 | sxu32 i; | ||
30295 | if( pCtx->nMagic == SXPRNG_MAGIC ){ | ||
30296 | return SXRET_OK; /* Already initialized */ | ||
30297 | } | ||
30298 | /* Initialize the state of the random number generator once, | ||
30299 | ** the first time this routine is called.The seed value does | ||
30300 | ** not need to contain a lot of randomness since we are not | ||
30301 | ** trying to do secure encryption or anything like that... | ||
30302 | */ | ||
30303 | if( xSeed == 0 ){ | ||
30304 | xSeed = SyOSUtilRandomSeed; | ||
30305 | } | ||
30306 | rc = xSeed(zSeed, sizeof(zSeed), pUserData); | ||
30307 | if( rc != SXRET_OK ){ | ||
30308 | return rc; | ||
30309 | } | ||
30310 | pCtx->i = pCtx->j = 0; | ||
30311 | for(i=0; i < SX_ARRAYSIZE(pCtx->s) ; i++){ | ||
30312 | pCtx->s[i] = (unsigned char)i; | ||
30313 | } | ||
30314 | for(i=0; i < sizeof(zSeed) ; i++){ | ||
30315 | pCtx->j += pCtx->s[i] + zSeed[i]; | ||
30316 | t = pCtx->s[pCtx->j]; | ||
30317 | pCtx->s[pCtx->j] = pCtx->s[i]; | ||
30318 | pCtx->s[i] = t; | ||
30319 | } | ||
30320 | pCtx->nMagic = SXPRNG_MAGIC; | ||
30321 | |||
30322 | return SXRET_OK; | ||
30323 | } | ||
30324 | /* | ||
30325 | * Get a single 8-bit random value using the RC4 PRNG. | ||
30326 | */ | ||
30327 | static sxu8 randomByte(SyPRNGCtx *pCtx) | ||
30328 | { | ||
30329 | sxu8 t; | ||
30330 | |||
30331 | /* Generate and return single random byte */ | ||
30332 | pCtx->i++; | ||
30333 | t = pCtx->s[pCtx->i]; | ||
30334 | pCtx->j += t; | ||
30335 | pCtx->s[pCtx->i] = pCtx->s[pCtx->j]; | ||
30336 | pCtx->s[pCtx->j] = t; | ||
30337 | t += pCtx->s[pCtx->i]; | ||
30338 | return pCtx->s[t]; | ||
30339 | } | ||
30340 | JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen) | ||
30341 | { | ||
30342 | unsigned char *zBuf = (unsigned char *)pBuf; | ||
30343 | unsigned char *zEnd = &zBuf[nLen]; | ||
30344 | #if defined(UNTRUST) | ||
30345 | if( pCtx == 0 || pBuf == 0 || nLen <= 0 ){ | ||
30346 | return SXERR_EMPTY; | ||
30347 | } | ||
30348 | #endif | ||
30349 | if(pCtx->nMagic != SXPRNG_MAGIC ){ | ||
30350 | return SXERR_CORRUPT; | ||
30351 | } | ||
30352 | for(;;){ | ||
30353 | if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; | ||
30354 | if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; | ||
30355 | if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; | ||
30356 | if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; | ||
30357 | } | ||
30358 | return SXRET_OK; | ||
30359 | } | ||
30360 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
30361 | #ifndef JX9_DISABLE_HASH_FUNC | ||
30362 | /* SyRunTimeApi: sxhash.c */ | ||
30363 | /* | ||
30364 | * This code implements the MD5 message-digest algorithm. | ||
30365 | * The algorithm is due to Ron Rivest.This code was | ||
30366 | * written by Colin Plumb in 1993, no copyright is claimed. | ||
30367 | * This code is in the public domain; do with it what you wish. | ||
30368 | * | ||
30369 | * Equivalent code is available from RSA Data Security, Inc. | ||
30370 | * This code has been tested against that, and is equivalent, | ||
30371 | * except that you don't need to include two pages of legalese | ||
30372 | * with every copy. | ||
30373 | * | ||
30374 | * To compute the message digest of a chunk of bytes, declare an | ||
30375 | * MD5Context structure, pass it to MD5Init, call MD5Update as | ||
30376 | * needed on buffers full of bytes, and then call MD5Final, which | ||
30377 | * will fill a supplied 16-byte array with the digest. | ||
30378 | */ | ||
30379 | #define SX_MD5_BINSZ 16 | ||
30380 | #define SX_MD5_HEXSZ 32 | ||
30381 | /* | ||
30382 | * Note: this code is harmless on little-endian machines. | ||
30383 | */ | ||
30384 | static void byteReverse (unsigned char *buf, unsigned longs) | ||
30385 | { | ||
30386 | sxu32 t; | ||
30387 | do { | ||
30388 | t = (sxu32)((unsigned)buf[3]<<8 | buf[2]) << 16 | | ||
30389 | ((unsigned)buf[1]<<8 | buf[0]); | ||
30390 | *(sxu32*)buf = t; | ||
30391 | buf += 4; | ||
30392 | } while (--longs); | ||
30393 | } | ||
30394 | /* The four core functions - F1 is optimized somewhat */ | ||
30395 | |||
30396 | /* #define F1(x, y, z) (x & y | ~x & z) */ | ||
30397 | #ifdef F1 | ||
30398 | #undef F1 | ||
30399 | #endif | ||
30400 | #ifdef F2 | ||
30401 | #undef F2 | ||
30402 | #endif | ||
30403 | #ifdef F3 | ||
30404 | #undef F3 | ||
30405 | #endif | ||
30406 | #ifdef F4 | ||
30407 | #undef F4 | ||
30408 | #endif | ||
30409 | |||
30410 | #define F1(x, y, z) (z ^ (x & (y ^ z))) | ||
30411 | #define F2(x, y, z) F1(z, x, y) | ||
30412 | #define F3(x, y, z) (x ^ y ^ z) | ||
30413 | #define F4(x, y, z) (y ^ (x | ~z)) | ||
30414 | |||
30415 | /* This is the central step in the MD5 algorithm.*/ | ||
30416 | #define SX_MD5STEP(f, w, x, y, z, data, s) \ | ||
30417 | ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) | ||
30418 | |||
30419 | /* | ||
30420 | * The core of the MD5 algorithm, this alters an existing MD5 hash to | ||
30421 | * reflect the addition of 16 longwords of new data.MD5Update blocks | ||
30422 | * the data and converts bytes into longwords for this routine. | ||
30423 | */ | ||
30424 | static void MD5Transform(sxu32 buf[4], const sxu32 in[16]) | ||
30425 | { | ||
30426 | register sxu32 a, b, c, d; | ||
30427 | |||
30428 | a = buf[0]; | ||
30429 | b = buf[1]; | ||
30430 | c = buf[2]; | ||
30431 | d = buf[3]; | ||
30432 | |||
30433 | SX_MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); | ||
30434 | SX_MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); | ||
30435 | SX_MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); | ||
30436 | SX_MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); | ||
30437 | SX_MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); | ||
30438 | SX_MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); | ||
30439 | SX_MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); | ||
30440 | SX_MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); | ||
30441 | SX_MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); | ||
30442 | SX_MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); | ||
30443 | SX_MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); | ||
30444 | SX_MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); | ||
30445 | SX_MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); | ||
30446 | SX_MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); | ||
30447 | SX_MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); | ||
30448 | SX_MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); | ||
30449 | |||
30450 | SX_MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); | ||
30451 | SX_MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); | ||
30452 | SX_MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); | ||
30453 | SX_MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); | ||
30454 | SX_MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); | ||
30455 | SX_MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); | ||
30456 | SX_MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); | ||
30457 | SX_MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); | ||
30458 | SX_MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); | ||
30459 | SX_MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); | ||
30460 | SX_MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); | ||
30461 | SX_MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); | ||
30462 | SX_MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); | ||
30463 | SX_MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); | ||
30464 | SX_MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); | ||
30465 | SX_MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); | ||
30466 | |||
30467 | SX_MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); | ||
30468 | SX_MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); | ||
30469 | SX_MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); | ||
30470 | SX_MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); | ||
30471 | SX_MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); | ||
30472 | SX_MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); | ||
30473 | SX_MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); | ||
30474 | SX_MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); | ||
30475 | SX_MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); | ||
30476 | SX_MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); | ||
30477 | SX_MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); | ||
30478 | SX_MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); | ||
30479 | SX_MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); | ||
30480 | SX_MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); | ||
30481 | SX_MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); | ||
30482 | SX_MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); | ||
30483 | |||
30484 | SX_MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); | ||
30485 | SX_MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); | ||
30486 | SX_MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); | ||
30487 | SX_MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); | ||
30488 | SX_MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); | ||
30489 | SX_MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); | ||
30490 | SX_MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); | ||
30491 | SX_MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); | ||
30492 | SX_MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); | ||
30493 | SX_MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); | ||
30494 | SX_MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); | ||
30495 | SX_MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); | ||
30496 | SX_MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); | ||
30497 | SX_MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); | ||
30498 | SX_MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); | ||
30499 | SX_MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); | ||
30500 | |||
30501 | buf[0] += a; | ||
30502 | buf[1] += b; | ||
30503 | buf[2] += c; | ||
30504 | buf[3] += d; | ||
30505 | } | ||
30506 | /* | ||
30507 | * Update context to reflect the concatenation of another buffer full | ||
30508 | * of bytes. | ||
30509 | */ | ||
30510 | JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len) | ||
30511 | { | ||
30512 | sxu32 t; | ||
30513 | |||
30514 | /* Update bitcount */ | ||
30515 | t = ctx->bits[0]; | ||
30516 | if ((ctx->bits[0] = t + ((sxu32)len << 3)) < t) | ||
30517 | ctx->bits[1]++; /* Carry from low to high */ | ||
30518 | ctx->bits[1] += len >> 29; | ||
30519 | t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ | ||
30520 | /* Handle any leading odd-sized chunks */ | ||
30521 | if ( t ) { | ||
30522 | unsigned char *p = (unsigned char *)ctx->in + t; | ||
30523 | |||
30524 | t = 64-t; | ||
30525 | if (len < t) { | ||
30526 | SyMemcpy(buf, p, len); | ||
30527 | return; | ||
30528 | } | ||
30529 | SyMemcpy(buf, p, t); | ||
30530 | byteReverse(ctx->in, 16); | ||
30531 | MD5Transform(ctx->buf, (sxu32*)ctx->in); | ||
30532 | buf += t; | ||
30533 | len -= t; | ||
30534 | } | ||
30535 | /* Process data in 64-byte chunks */ | ||
30536 | while (len >= 64) { | ||
30537 | SyMemcpy(buf, ctx->in, 64); | ||
30538 | byteReverse(ctx->in, 16); | ||
30539 | MD5Transform(ctx->buf, (sxu32*)ctx->in); | ||
30540 | buf += 64; | ||
30541 | len -= 64; | ||
30542 | } | ||
30543 | /* Handle any remaining bytes of data.*/ | ||
30544 | SyMemcpy(buf, ctx->in, len); | ||
30545 | } | ||
30546 | /* | ||
30547 | * Final wrapup - pad to 64-byte boundary with the bit pattern | ||
30548 | * 1 0* (64-bit count of bits processed, MSB-first) | ||
30549 | */ | ||
30550 | JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx){ | ||
30551 | unsigned count; | ||
30552 | unsigned char *p; | ||
30553 | |||
30554 | /* Compute number of bytes mod 64 */ | ||
30555 | count = (ctx->bits[0] >> 3) & 0x3F; | ||
30556 | |||
30557 | /* Set the first char of padding to 0x80.This is safe since there is | ||
30558 | always at least one byte free */ | ||
30559 | p = ctx->in + count; | ||
30560 | *p++ = 0x80; | ||
30561 | |||
30562 | /* Bytes of padding needed to make 64 bytes */ | ||
30563 | count = 64 - 1 - count; | ||
30564 | |||
30565 | /* Pad out to 56 mod 64 */ | ||
30566 | if (count < 8) { | ||
30567 | /* Two lots of padding: Pad the first block to 64 bytes */ | ||
30568 | SyZero(p, count); | ||
30569 | byteReverse(ctx->in, 16); | ||
30570 | MD5Transform(ctx->buf, (sxu32*)ctx->in); | ||
30571 | |||
30572 | /* Now fill the next block with 56 bytes */ | ||
30573 | SyZero(ctx->in, 56); | ||
30574 | } else { | ||
30575 | /* Pad block to 56 bytes */ | ||
30576 | SyZero(p, count-8); | ||
30577 | } | ||
30578 | byteReverse(ctx->in, 14); | ||
30579 | |||
30580 | /* Append length in bits and transform */ | ||
30581 | ((sxu32*)ctx->in)[ 14 ] = ctx->bits[0]; | ||
30582 | ((sxu32*)ctx->in)[ 15 ] = ctx->bits[1]; | ||
30583 | |||
30584 | MD5Transform(ctx->buf, (sxu32*)ctx->in); | ||
30585 | byteReverse((unsigned char *)ctx->buf, 4); | ||
30586 | SyMemcpy(ctx->buf, digest, 0x10); | ||
30587 | SyZero(ctx, sizeof(ctx)); /* In case it's sensitive */ | ||
30588 | } | ||
30589 | #undef F1 | ||
30590 | #undef F2 | ||
30591 | #undef F3 | ||
30592 | #undef F4 | ||
30593 | JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx) | ||
30594 | { | ||
30595 | pCtx->buf[0] = 0x67452301; | ||
30596 | pCtx->buf[1] = 0xefcdab89; | ||
30597 | pCtx->buf[2] = 0x98badcfe; | ||
30598 | pCtx->buf[3] = 0x10325476; | ||
30599 | pCtx->bits[0] = 0; | ||
30600 | pCtx->bits[1] = 0; | ||
30601 | |||
30602 | return SXRET_OK; | ||
30603 | } | ||
30604 | JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]) | ||
30605 | { | ||
30606 | MD5Context sCtx; | ||
30607 | MD5Init(&sCtx); | ||
30608 | MD5Update(&sCtx, (const unsigned char *)pIn, nLen); | ||
30609 | MD5Final(zDigest, &sCtx); | ||
30610 | return SXRET_OK; | ||
30611 | } | ||
30612 | /* | ||
30613 | * SHA-1 in C | ||
30614 | * By Steve Reid <steve@edmweb.com> | ||
30615 | * Status: Public Domain | ||
30616 | */ | ||
30617 | /* | ||
30618 | * blk0() and blk() perform the initial expand. | ||
30619 | * I got the idea of expanding during the round function from SSLeay | ||
30620 | * | ||
30621 | * blk0le() for little-endian and blk0be() for big-endian. | ||
30622 | */ | ||
30623 | #if __GNUC__ && (defined(__i386__) || defined(__x86_64__)) | ||
30624 | /* | ||
30625 | * GCC by itself only generates left rotates. Use right rotates if | ||
30626 | * possible to be kinder to dinky implementations with iterative rotate | ||
30627 | * instructions. | ||
30628 | */ | ||
30629 | #define SHA_ROT(op, x, k) \ | ||
30630 | ({ unsigned int y; asm(op " %1, %0" : "=r" (y) : "I" (k), "0" (x)); y; }) | ||
30631 | #define rol(x, k) SHA_ROT("roll", x, k) | ||
30632 | #define ror(x, k) SHA_ROT("rorl", x, k) | ||
30633 | |||
30634 | #else | ||
30635 | /* Generic C equivalent */ | ||
30636 | #define SHA_ROT(x, l, r) ((x) << (l) | (x) >> (r)) | ||
30637 | #define rol(x, k) SHA_ROT(x, k, 32-(k)) | ||
30638 | #define ror(x, k) SHA_ROT(x, 32-(k), k) | ||
30639 | #endif | ||
30640 | |||
30641 | #define blk0le(i) (block[i] = (ror(block[i], 8)&0xFF00FF00) \ | ||
30642 | |(rol(block[i], 8)&0x00FF00FF)) | ||
30643 | #define blk0be(i) block[i] | ||
30644 | #define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \ | ||
30645 | ^block[(i+2)&15]^block[i&15], 1)) | ||
30646 | |||
30647 | /* | ||
30648 | * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 | ||
30649 | * | ||
30650 | * Rl0() for little-endian and Rb0() for big-endian. Endianness is | ||
30651 | * determined at run-time. | ||
30652 | */ | ||
30653 | #define Rl0(v, w, x, y, z, i) \ | ||
30654 | z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v, 5);w=ror(w, 2); | ||
30655 | #define Rb0(v, w, x, y, z, i) \ | ||
30656 | z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v, 5);w=ror(w, 2); | ||
30657 | #define R1(v, w, x, y, z, i) \ | ||
30658 | z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v, 5);w=ror(w, 2); | ||
30659 | #define R2(v, w, x, y, z, i) \ | ||
30660 | z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v, 5);w=ror(w, 2); | ||
30661 | #define R3(v, w, x, y, z, i) \ | ||
30662 | z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v, 5);w=ror(w, 2); | ||
30663 | #define R4(v, w, x, y, z, i) \ | ||
30664 | z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v, 5);w=ror(w, 2); | ||
30665 | |||
30666 | /* | ||
30667 | * Hash a single 512-bit block. This is the core of the algorithm. | ||
30668 | */ | ||
30669 | #define a qq[0] | ||
30670 | #define b qq[1] | ||
30671 | #define c qq[2] | ||
30672 | #define d qq[3] | ||
30673 | #define e qq[4] | ||
30674 | |||
30675 | static void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) | ||
30676 | { | ||
30677 | unsigned int qq[5]; /* a, b, c, d, e; */ | ||
30678 | static int one = 1; | ||
30679 | unsigned int block[16]; | ||
30680 | SyMemcpy(buffer, (void *)block, 64); | ||
30681 | SyMemcpy(state, qq, 5*sizeof(unsigned int)); | ||
30682 | |||
30683 | /* Copy context->state[] to working vars */ | ||
30684 | /* | ||
30685 | a = state[0]; | ||
30686 | b = state[1]; | ||
30687 | c = state[2]; | ||
30688 | d = state[3]; | ||
30689 | e = state[4]; | ||
30690 | */ | ||
30691 | |||
30692 | /* 4 rounds of 20 operations each. Loop unrolled. */ | ||
30693 | if( 1 == *(unsigned char*)&one ){ | ||
30694 | 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); | ||
30695 | 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); | ||
30696 | 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); | ||
30697 | 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); | ||
30698 | }else{ | ||
30699 | 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); | ||
30700 | 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); | ||
30701 | 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); | ||
30702 | 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); | ||
30703 | } | ||
30704 | 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); | ||
30705 | 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); | ||
30706 | 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); | ||
30707 | 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); | ||
30708 | 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); | ||
30709 | 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); | ||
30710 | 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); | ||
30711 | 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); | ||
30712 | 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); | ||
30713 | 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); | ||
30714 | 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); | ||
30715 | 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); | ||
30716 | 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); | ||
30717 | 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); | ||
30718 | 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); | ||
30719 | 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); | ||
30720 | |||
30721 | /* Add the working vars back into context.state[] */ | ||
30722 | state[0] += a; | ||
30723 | state[1] += b; | ||
30724 | state[2] += c; | ||
30725 | state[3] += d; | ||
30726 | state[4] += e; | ||
30727 | } | ||
30728 | #undef a | ||
30729 | #undef b | ||
30730 | #undef c | ||
30731 | #undef d | ||
30732 | #undef e | ||
30733 | /* | ||
30734 | * SHA1Init - Initialize new context | ||
30735 | */ | ||
30736 | JX9_PRIVATE void SHA1Init(SHA1Context *context){ | ||
30737 | /* SHA1 initialization constants */ | ||
30738 | context->state[0] = 0x67452301; | ||
30739 | context->state[1] = 0xEFCDAB89; | ||
30740 | context->state[2] = 0x98BADCFE; | ||
30741 | context->state[3] = 0x10325476; | ||
30742 | context->state[4] = 0xC3D2E1F0; | ||
30743 | context->count[0] = context->count[1] = 0; | ||
30744 | } | ||
30745 | /* | ||
30746 | * Run your data through this. | ||
30747 | */ | ||
30748 | JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len){ | ||
30749 | unsigned int i, j; | ||
30750 | |||
30751 | j = context->count[0]; | ||
30752 | if ((context->count[0] += len << 3) < j) | ||
30753 | context->count[1] += (len>>29)+1; | ||
30754 | j = (j >> 3) & 63; | ||
30755 | if ((j + len) > 63) { | ||
30756 | (void)SyMemcpy(data, &context->buffer[j], (i = 64-j)); | ||
30757 | SHA1Transform(context->state, context->buffer); | ||
30758 | for ( ; i + 63 < len; i += 64) | ||
30759 | SHA1Transform(context->state, &data[i]); | ||
30760 | j = 0; | ||
30761 | } else { | ||
30762 | i = 0; | ||
30763 | } | ||
30764 | (void)SyMemcpy(&data[i], &context->buffer[j], len - i); | ||
30765 | } | ||
30766 | /* | ||
30767 | * Add padding and return the message digest. | ||
30768 | */ | ||
30769 | JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]){ | ||
30770 | unsigned int i; | ||
30771 | unsigned char finalcount[8]; | ||
30772 | |||
30773 | for (i = 0; i < 8; i++) { | ||
30774 | finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] | ||
30775 | >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ | ||
30776 | } | ||
30777 | SHA1Update(context, (const unsigned char *)"\200", 1); | ||
30778 | while ((context->count[0] & 504) != 448) | ||
30779 | SHA1Update(context, (const unsigned char *)"\0", 1); | ||
30780 | SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ | ||
30781 | |||
30782 | if (digest) { | ||
30783 | for (i = 0; i < 20; i++) | ||
30784 | digest[i] = (unsigned char) | ||
30785 | ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); | ||
30786 | } | ||
30787 | } | ||
30788 | #undef Rl0 | ||
30789 | #undef Rb0 | ||
30790 | #undef R1 | ||
30791 | #undef R2 | ||
30792 | #undef R3 | ||
30793 | #undef R4 | ||
30794 | |||
30795 | JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]) | ||
30796 | { | ||
30797 | SHA1Context sCtx; | ||
30798 | SHA1Init(&sCtx); | ||
30799 | SHA1Update(&sCtx, (const unsigned char *)pIn, nLen); | ||
30800 | SHA1Final(&sCtx, zDigest); | ||
30801 | return SXRET_OK; | ||
30802 | } | ||
30803 | static const sxu32 crc32_table[] = { | ||
30804 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, | ||
30805 | 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, | ||
30806 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, | ||
30807 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, | ||
30808 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, | ||
30809 | 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, | ||
30810 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, | ||
30811 | 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, | ||
30812 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, | ||
30813 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, | ||
30814 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, | ||
30815 | 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, | ||
30816 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, | ||
30817 | 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, | ||
30818 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, | ||
30819 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, | ||
30820 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, | ||
30821 | 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, | ||
30822 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, | ||
30823 | 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, | ||
30824 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, | ||
30825 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, | ||
30826 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, | ||
30827 | 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, | ||
30828 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, | ||
30829 | 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, | ||
30830 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, | ||
30831 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, | ||
30832 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, | ||
30833 | 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, | ||
30834 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, | ||
30835 | 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, | ||
30836 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, | ||
30837 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, | ||
30838 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, | ||
30839 | 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, | ||
30840 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, | ||
30841 | 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, | ||
30842 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, | ||
30843 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, | ||
30844 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, | ||
30845 | 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, | ||
30846 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, | ||
30847 | 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, | ||
30848 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, | ||
30849 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, | ||
30850 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, | ||
30851 | 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, | ||
30852 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, | ||
30853 | 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, | ||
30854 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, | ||
30855 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, | ||
30856 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, | ||
30857 | 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, | ||
30858 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, | ||
30859 | 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, | ||
30860 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, | ||
30861 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, | ||
30862 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, | ||
30863 | 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, | ||
30864 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, | ||
30865 | 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, | ||
30866 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, | ||
30867 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, | ||
30868 | }; | ||
30869 | #define CRC32C(c, d) (c = ( crc32_table[(c ^ (d)) & 0xFF] ^ (c>>8) ) ) | ||
30870 | static sxu32 SyCrc32Update(sxu32 crc32, const void *pSrc, sxu32 nLen) | ||
30871 | { | ||
30872 | register unsigned char *zIn = (unsigned char *)pSrc; | ||
30873 | unsigned char *zEnd; | ||
30874 | if( zIn == 0 ){ | ||
30875 | return crc32; | ||
30876 | } | ||
30877 | zEnd = &zIn[nLen]; | ||
30878 | for(;;){ | ||
30879 | if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; | ||
30880 | if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; | ||
30881 | if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; | ||
30882 | if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; | ||
30883 | } | ||
30884 | |||
30885 | return crc32; | ||
30886 | } | ||
30887 | JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen) | ||
30888 | { | ||
30889 | return SyCrc32Update(SXU32_HIGH, pSrc, nLen); | ||
30890 | } | ||
30891 | #endif /* JX9_DISABLE_HASH_FUNC */ | ||
30892 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
30893 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
30894 | JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData) | ||
30895 | { | ||
30896 | static const unsigned char zHexTab[] = "0123456789abcdef"; | ||
30897 | const unsigned char *zIn, *zEnd; | ||
30898 | unsigned char zOut[3]; | ||
30899 | sxi32 rc; | ||
30900 | #if defined(UNTRUST) | ||
30901 | if( pIn == 0 || xConsumer == 0 ){ | ||
30902 | return SXERR_EMPTY; | ||
30903 | } | ||
30904 | #endif | ||
30905 | zIn = (const unsigned char *)pIn; | ||
30906 | zEnd = &zIn[nLen]; | ||
30907 | for(;;){ | ||
30908 | if( zIn >= zEnd ){ | ||
30909 | break; | ||
30910 | } | ||
30911 | zOut[0] = zHexTab[zIn[0] >> 4]; zOut[1] = zHexTab[zIn[0] & 0x0F]; | ||
30912 | rc = xConsumer((const void *)zOut, sizeof(char)*2, pConsumerData); | ||
30913 | if( rc != SXRET_OK ){ | ||
30914 | return rc; | ||
30915 | } | ||
30916 | zIn++; | ||
30917 | } | ||
30918 | return SXRET_OK; | ||
30919 | } | ||
30920 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
30921 | JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb) | ||
30922 | { | ||
30923 | buf[3] = nb & 0xFF ; nb >>=8; | ||
30924 | buf[2] = nb & 0xFF ; nb >>=8; | ||
30925 | buf[1] = nb & 0xFF ; nb >>=8; | ||
30926 | buf[0] = (unsigned char)nb ; | ||
30927 | } | ||
30928 | JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB) | ||
30929 | { | ||
30930 | *uNB = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); | ||
30931 | } | ||
30932 | JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb) | ||
30933 | { | ||
30934 | buf[1] = nb & 0xFF ; nb >>=8; | ||
30935 | buf[0] = (unsigned char)nb ; | ||
30936 | } | ||
30937 | JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB) | ||
30938 | { | ||
30939 | *uNB = buf[1] + (buf[0] << 8); | ||
30940 | } | ||
30941 | JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64) | ||
30942 | { | ||
30943 | buf[7] = n64 & 0xFF; n64 >>=8; | ||
30944 | buf[6] = n64 & 0xFF; n64 >>=8; | ||
30945 | buf[5] = n64 & 0xFF; n64 >>=8; | ||
30946 | buf[4] = n64 & 0xFF; n64 >>=8; | ||
30947 | buf[3] = n64 & 0xFF; n64 >>=8; | ||
30948 | buf[2] = n64 & 0xFF; n64 >>=8; | ||
30949 | buf[1] = n64 & 0xFF; n64 >>=8; | ||
30950 | buf[0] = (sxu8)n64 ; | ||
30951 | } | ||
30952 | JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64) | ||
30953 | { | ||
30954 | sxu32 u1,u2; | ||
30955 | u1 = buf[7] + (buf[6] << 8) + (buf[5] << 16) + (buf[4] << 24); | ||
30956 | u2 = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); | ||
30957 | *n64 = (((sxu64)u2) << 32) | u1; | ||
30958 | } | ||
30959 | JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64) | ||
30960 | { | ||
30961 | unsigned char zBuf[8]; | ||
30962 | sxi32 rc; | ||
30963 | SyBigEndianPack64(zBuf,n64); | ||
30964 | rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); | ||
30965 | return rc; | ||
30966 | } | ||
30967 | JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32) | ||
30968 | { | ||
30969 | unsigned char zBuf[4]; | ||
30970 | sxi32 rc; | ||
30971 | SyBigEndianPack32(zBuf,n32); | ||
30972 | rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); | ||
30973 | return rc; | ||
30974 | } | ||
30975 | JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16) | ||
30976 | { | ||
30977 | unsigned char zBuf[2]; | ||
30978 | sxi32 rc; | ||
30979 | SyBigEndianPack16(zBuf,n16); | ||
30980 | rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); | ||
30981 | return rc; | ||
30982 | } | ||
30983 | JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut) | ||
30984 | { | ||
30985 | sxi32 nDate,nTime; | ||
30986 | nDate = ((pFmt->tm_year - 1980) << 9) + (pFmt->tm_mon << 5) + pFmt->tm_mday; | ||
30987 | nTime = (pFmt->tm_hour << 11) + (pFmt->tm_min << 5)+ (pFmt->tm_sec >> 1); | ||
30988 | *pOut = (nDate << 16) | nTime; | ||
30989 | } | ||
30990 | JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut) | ||
30991 | { | ||
30992 | sxu16 nDate; | ||
30993 | sxu16 nTime; | ||
30994 | nDate = nDosDate >> 16; | ||
30995 | nTime = nDosDate & 0xFFFF; | ||
30996 | pOut->tm_isdst = 0; | ||
30997 | pOut->tm_year = 1980 + (nDate >> 9); | ||
30998 | pOut->tm_mon = (nDate % (1<<9))>>5; | ||
30999 | pOut->tm_mday = (nDate % (1<<9))&0x1F; | ||
31000 | pOut->tm_hour = nTime >> 11; | ||
31001 | pOut->tm_min = (nTime % (1<<11)) >> 5; | ||
31002 | pOut->tm_sec = ((nTime % (1<<11))& 0x1F )<<1; | ||
31003 | } | ||
31004 | /* | ||
31005 | * ---------------------------------------------------------- | ||
31006 | * File: jx9_memobj.c | ||
31007 | * MD5: 8692d7f4cb297c0946066b4a9034c637 | ||
31008 | * ---------------------------------------------------------- | ||
31009 | */ | ||
31010 | /* | ||
31011 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
31012 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
31013 | * Version 1.7.2 | ||
31014 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
31015 | * please contact Symisc Systems via: | ||
31016 | * legal@symisc.net | ||
31017 | * licensing@symisc.net | ||
31018 | * contact@symisc.net | ||
31019 | * or visit: | ||
31020 | * http://jx9.symisc.net/ | ||
31021 | */ | ||
31022 | /* $SymiscID: memobj.c v2.7 FreeBSD 2012-08-09 03:40 stable <chm@symisc.net> $ */ | ||
31023 | #ifndef JX9_AMALGAMATION | ||
31024 | #include "jx9Int.h" | ||
31025 | #endif | ||
31026 | /* This file manage low-level stuff related to indexed memory objects [i.e: jx9_value] */ | ||
31027 | /* | ||
31028 | * Notes on memory objects [i.e: jx9_value]. | ||
31029 | * Internally, the JX9 virtual machine manipulates nearly all JX9 values | ||
31030 | * [i.e: string, int, float, resource, object, bool, null..] as jx9_values structures. | ||
31031 | * Each jx9_values struct may cache multiple representations (string, | ||
31032 | * integer etc.) of the same value. | ||
31033 | */ | ||
31034 | /* | ||
31035 | * Convert a 64-bit IEEE double into a 64-bit signed integer. | ||
31036 | * If the double is too large, return 0x8000000000000000. | ||
31037 | * | ||
31038 | * Most systems appear to do this simply by assigning ariables and without | ||
31039 | * the extra range tests. | ||
31040 | * But there are reports that windows throws an expection if the floating | ||
31041 | * point value is out of range. | ||
31042 | */ | ||
31043 | static sxi64 MemObjRealToInt(jx9_value *pObj) | ||
31044 | { | ||
31045 | #ifdef JX9_OMIT_FLOATING_POINT | ||
31046 | /* Real and 64bit integer are the same when floating point arithmetic | ||
31047 | * is omitted from the build. | ||
31048 | */ | ||
31049 | return pObj->x.rVal; | ||
31050 | #else | ||
31051 | /* | ||
31052 | ** Many compilers we encounter do not define constants for the | ||
31053 | ** minimum and maximum 64-bit integers, or they define them | ||
31054 | ** inconsistently. And many do not understand the "LL" notation. | ||
31055 | ** So we define our own static constants here using nothing | ||
31056 | ** larger than a 32-bit integer constant. | ||
31057 | */ | ||
31058 | static const sxi64 maxInt = LARGEST_INT64; | ||
31059 | static const sxi64 minInt = SMALLEST_INT64; | ||
31060 | jx9_real r = pObj->x.rVal; | ||
31061 | if( r<(jx9_real)minInt ){ | ||
31062 | return minInt; | ||
31063 | }else if( r>(jx9_real)maxInt ){ | ||
31064 | /* minInt is correct here - not maxInt. It turns out that assigning | ||
31065 | ** a very large positive number to an integer results in a very large | ||
31066 | ** negative integer. This makes no sense, but it is what x86 hardware | ||
31067 | ** does so for compatibility we will do the same in software. */ | ||
31068 | return minInt; | ||
31069 | }else{ | ||
31070 | return (sxi64)r; | ||
31071 | } | ||
31072 | #endif | ||
31073 | } | ||
31074 | /* | ||
31075 | * Convert a raw token value typically a stream of digit [i.e: hex, octal, binary or decimal] | ||
31076 | * to a 64-bit integer. | ||
31077 | */ | ||
31078 | JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pVal) | ||
31079 | { | ||
31080 | sxi64 iVal = 0; | ||
31081 | if( pVal->nByte <= 0 ){ | ||
31082 | return 0; | ||
31083 | } | ||
31084 | if( pVal->zString[0] == '0' ){ | ||
31085 | sxi32 c; | ||
31086 | if( pVal->nByte == sizeof(char) ){ | ||
31087 | return 0; | ||
31088 | } | ||
31089 | c = pVal->zString[1]; | ||
31090 | if( c == 'x' || c == 'X' ){ | ||
31091 | /* Hex digit stream */ | ||
31092 | SyHexStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); | ||
31093 | }else if( c == 'b' || c == 'B' ){ | ||
31094 | /* Binary digit stream */ | ||
31095 | SyBinaryStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); | ||
31096 | }else{ | ||
31097 | /* Octal digit stream */ | ||
31098 | SyOctalStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); | ||
31099 | } | ||
31100 | }else{ | ||
31101 | /* Decimal digit stream */ | ||
31102 | SyStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); | ||
31103 | } | ||
31104 | return iVal; | ||
31105 | } | ||
31106 | /* | ||
31107 | * Return some kind of 64-bit integer value which is the best we can | ||
31108 | * do at representing the value that pObj describes as a string | ||
31109 | * representation. | ||
31110 | */ | ||
31111 | static sxi64 MemObjStringToInt(jx9_value *pObj) | ||
31112 | { | ||
31113 | SyString sVal; | ||
31114 | SyStringInitFromBuf(&sVal, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); | ||
31115 | return jx9TokenValueToInt64(&sVal); | ||
31116 | } | ||
31117 | /* | ||
31118 | * Return some kind of integer value which is the best we can | ||
31119 | * do at representing the value that pObj describes as an integer. | ||
31120 | * If pObj is an integer, then the value is exact. If pObj is | ||
31121 | * a floating-point then the value returned is the integer part. | ||
31122 | * If pObj is a string, then we make an attempt to convert it into | ||
31123 | * a integer and return that. | ||
31124 | * If pObj represents a NULL value, return 0. | ||
31125 | */ | ||
31126 | static sxi64 MemObjIntValue(jx9_value *pObj) | ||
31127 | { | ||
31128 | sxi32 iFlags; | ||
31129 | iFlags = pObj->iFlags; | ||
31130 | if (iFlags & MEMOBJ_REAL ){ | ||
31131 | return MemObjRealToInt(&(*pObj)); | ||
31132 | }else if( iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ | ||
31133 | return pObj->x.iVal; | ||
31134 | }else if (iFlags & MEMOBJ_STRING) { | ||
31135 | return MemObjStringToInt(&(*pObj)); | ||
31136 | }else if( iFlags & MEMOBJ_NULL ){ | ||
31137 | return 0; | ||
31138 | }else if( iFlags & MEMOBJ_HASHMAP ){ | ||
31139 | jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; | ||
31140 | sxu32 n = pMap->nEntry; | ||
31141 | jx9HashmapUnref(pMap); | ||
31142 | /* Return total number of entries in the hashmap */ | ||
31143 | return n; | ||
31144 | }else if(iFlags & MEMOBJ_RES ){ | ||
31145 | return pObj->x.pOther != 0; | ||
31146 | } | ||
31147 | /* CANT HAPPEN */ | ||
31148 | return 0; | ||
31149 | } | ||
31150 | /* | ||
31151 | * Return some kind of real value which is the best we can | ||
31152 | * do at representing the value that pObj describes as a real. | ||
31153 | * If pObj is a real, then the value is exact.If pObj is an | ||
31154 | * integer then the integer is promoted to real and that value | ||
31155 | * is returned. | ||
31156 | * If pObj is a string, then we make an attempt to convert it | ||
31157 | * into a real and return that. | ||
31158 | * If pObj represents a NULL value, return 0.0 | ||
31159 | */ | ||
31160 | static jx9_real MemObjRealValue(jx9_value *pObj) | ||
31161 | { | ||
31162 | sxi32 iFlags; | ||
31163 | iFlags = pObj->iFlags; | ||
31164 | if( iFlags & MEMOBJ_REAL ){ | ||
31165 | return pObj->x.rVal; | ||
31166 | }else if (iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ | ||
31167 | return (jx9_real)pObj->x.iVal; | ||
31168 | }else if (iFlags & MEMOBJ_STRING){ | ||
31169 | SyString sString; | ||
31170 | #ifdef JX9_OMIT_FLOATING_POINT | ||
31171 | jx9_real rVal = 0; | ||
31172 | #else | ||
31173 | jx9_real rVal = 0.0; | ||
31174 | #endif | ||
31175 | SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); | ||
31176 | if( SyBlobLength(&pObj->sBlob) > 0 ){ | ||
31177 | /* Convert as much as we can */ | ||
31178 | #ifdef JX9_OMIT_FLOATING_POINT | ||
31179 | rVal = MemObjStringToInt(&(*pObj)); | ||
31180 | #else | ||
31181 | SyStrToReal(sString.zString, sString.nByte, (void *)&rVal, 0); | ||
31182 | #endif | ||
31183 | } | ||
31184 | return rVal; | ||
31185 | }else if( iFlags & MEMOBJ_NULL ){ | ||
31186 | #ifdef JX9_OMIT_FLOATING_POINT | ||
31187 | return 0; | ||
31188 | #else | ||
31189 | return 0.0; | ||
31190 | #endif | ||
31191 | }else if( iFlags & MEMOBJ_HASHMAP ){ | ||
31192 | /* Return the total number of entries in the hashmap */ | ||
31193 | jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; | ||
31194 | jx9_real n = (jx9_real)pMap->nEntry; | ||
31195 | jx9HashmapUnref(pMap); | ||
31196 | return n; | ||
31197 | }else if(iFlags & MEMOBJ_RES ){ | ||
31198 | return (jx9_real)(pObj->x.pOther != 0); | ||
31199 | } | ||
31200 | /* NOT REACHED */ | ||
31201 | return 0; | ||
31202 | } | ||
31203 | /* | ||
31204 | * Return the string representation of a given jx9_value. | ||
31205 | * This function never fail and always return SXRET_OK. | ||
31206 | */ | ||
31207 | static sxi32 MemObjStringValue(SyBlob *pOut,jx9_value *pObj) | ||
31208 | { | ||
31209 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
31210 | SyBlobFormat(&(*pOut), "%.15g", pObj->x.rVal); | ||
31211 | }else if( pObj->iFlags & MEMOBJ_INT ){ | ||
31212 | SyBlobFormat(&(*pOut), "%qd", pObj->x.iVal); | ||
31213 | /* %qd (BSD quad) is equivalent to %lld in the libc printf */ | ||
31214 | }else if( pObj->iFlags & MEMOBJ_BOOL ){ | ||
31215 | if( pObj->x.iVal ){ | ||
31216 | SyBlobAppend(&(*pOut),"true", sizeof("true")-1); | ||
31217 | }else{ | ||
31218 | SyBlobAppend(&(*pOut),"false", sizeof("false")-1); | ||
31219 | } | ||
31220 | }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ | ||
31221 | /* Serialize JSON object or array */ | ||
31222 | jx9JsonSerialize(pObj,pOut); | ||
31223 | jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther); | ||
31224 | }else if(pObj->iFlags & MEMOBJ_RES ){ | ||
31225 | SyBlobFormat(&(*pOut), "ResourceID_%#x", pObj->x.pOther); | ||
31226 | } | ||
31227 | return SXRET_OK; | ||
31228 | } | ||
31229 | /* | ||
31230 | * Return some kind of boolean value which is the best we can do | ||
31231 | * at representing the value that pObj describes as a boolean. | ||
31232 | * When converting to boolean, the following values are considered FALSE: | ||
31233 | * NULL | ||
31234 | * the boolean FALSE itself. | ||
31235 | * the integer 0 (zero). | ||
31236 | * the real 0.0 (zero). | ||
31237 | * the empty string, a stream of zero [i.e: "0", "00", "000", ...] and the string | ||
31238 | * "false". | ||
31239 | * an array with zero elements. | ||
31240 | */ | ||
31241 | static sxi32 MemObjBooleanValue(jx9_value *pObj) | ||
31242 | { | ||
31243 | sxi32 iFlags; | ||
31244 | iFlags = pObj->iFlags; | ||
31245 | if (iFlags & MEMOBJ_REAL ){ | ||
31246 | #ifdef JX9_OMIT_FLOATING_POINT | ||
31247 | return pObj->x.rVal ? 1 : 0; | ||
31248 | #else | ||
31249 | return pObj->x.rVal != 0.0 ? 1 : 0; | ||
31250 | #endif | ||
31251 | }else if( iFlags & MEMOBJ_INT ){ | ||
31252 | return pObj->x.iVal ? 1 : 0; | ||
31253 | }else if (iFlags & MEMOBJ_STRING) { | ||
31254 | SyString sString; | ||
31255 | SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); | ||
31256 | if( sString.nByte == 0 ){ | ||
31257 | /* Empty string */ | ||
31258 | return 0; | ||
31259 | }else if( (sString.nByte == sizeof("true") - 1 && SyStrnicmp(sString.zString, "true", sizeof("true")-1) == 0) || | ||
31260 | (sString.nByte == sizeof("on") - 1 && SyStrnicmp(sString.zString, "on", sizeof("on")-1) == 0) || | ||
31261 | (sString.nByte == sizeof("yes") - 1 && SyStrnicmp(sString.zString, "yes", sizeof("yes")-1) == 0) ){ | ||
31262 | return 1; | ||
31263 | }else if( sString.nByte == sizeof("false") - 1 && SyStrnicmp(sString.zString, "false", sizeof("false")-1) == 0 ){ | ||
31264 | return 0; | ||
31265 | }else{ | ||
31266 | const char *zIn, *zEnd; | ||
31267 | zIn = sString.zString; | ||
31268 | zEnd = &zIn[sString.nByte]; | ||
31269 | while( zIn < zEnd && zIn[0] == '0' ){ | ||
31270 | zIn++; | ||
31271 | } | ||
31272 | return zIn >= zEnd ? 0 : 1; | ||
31273 | } | ||
31274 | }else if( iFlags & MEMOBJ_NULL ){ | ||
31275 | return 0; | ||
31276 | }else if( iFlags & MEMOBJ_HASHMAP ){ | ||
31277 | jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; | ||
31278 | sxu32 n = pMap->nEntry; | ||
31279 | jx9HashmapUnref(pMap); | ||
31280 | return n > 0 ? TRUE : FALSE; | ||
31281 | }else if(iFlags & MEMOBJ_RES ){ | ||
31282 | return pObj->x.pOther != 0; | ||
31283 | } | ||
31284 | /* NOT REACHED */ | ||
31285 | return 0; | ||
31286 | } | ||
31287 | /* | ||
31288 | * If the jx9_value is of type real, try to make it an integer also. | ||
31289 | */ | ||
31290 | static sxi32 MemObjTryIntger(jx9_value *pObj) | ||
31291 | { | ||
31292 | sxi64 iVal = MemObjRealToInt(&(*pObj)); | ||
31293 | /* Only mark the value as an integer if | ||
31294 | ** | ||
31295 | ** (1) the round-trip conversion real->int->real is a no-op, and | ||
31296 | ** (2) The integer is neither the largest nor the smallest | ||
31297 | ** possible integer | ||
31298 | ** | ||
31299 | ** The second and third terms in the following conditional enforces | ||
31300 | ** the second condition under the assumption that addition overflow causes | ||
31301 | ** values to wrap around. On x86 hardware, the third term is always | ||
31302 | ** true and could be omitted. But we leave it in because other | ||
31303 | ** architectures might behave differently. | ||
31304 | */ | ||
31305 | if( pObj->x.rVal ==(jx9_real)iVal && iVal>SMALLEST_INT64 && iVal<LARGEST_INT64 ){ | ||
31306 | pObj->x.iVal = iVal; | ||
31307 | pObj->iFlags = MEMOBJ_INT; | ||
31308 | } | ||
31309 | return SXRET_OK; | ||
31310 | } | ||
31311 | /* | ||
31312 | * Convert a jx9_value to type integer.Invalidate any prior representations. | ||
31313 | */ | ||
31314 | JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj) | ||
31315 | { | ||
31316 | if( (pObj->iFlags & MEMOBJ_INT) == 0 ){ | ||
31317 | /* Preform the conversion */ | ||
31318 | pObj->x.iVal = MemObjIntValue(&(*pObj)); | ||
31319 | /* Invalidate any prior representations */ | ||
31320 | SyBlobRelease(&pObj->sBlob); | ||
31321 | MemObjSetType(pObj, MEMOBJ_INT); | ||
31322 | } | ||
31323 | return SXRET_OK; | ||
31324 | } | ||
31325 | /* | ||
31326 | * Convert a jx9_value to type real (Try to get an integer representation also). | ||
31327 | * Invalidate any prior representations | ||
31328 | */ | ||
31329 | JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj) | ||
31330 | { | ||
31331 | if((pObj->iFlags & MEMOBJ_REAL) == 0 ){ | ||
31332 | /* Preform the conversion */ | ||
31333 | pObj->x.rVal = MemObjRealValue(&(*pObj)); | ||
31334 | /* Invalidate any prior representations */ | ||
31335 | SyBlobRelease(&pObj->sBlob); | ||
31336 | MemObjSetType(pObj, MEMOBJ_REAL); | ||
31337 | } | ||
31338 | return SXRET_OK; | ||
31339 | } | ||
31340 | /* | ||
31341 | * Convert a jx9_value to type boolean.Invalidate any prior representations. | ||
31342 | */ | ||
31343 | JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj) | ||
31344 | { | ||
31345 | if( (pObj->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
31346 | /* Preform the conversion */ | ||
31347 | pObj->x.iVal = MemObjBooleanValue(&(*pObj)); | ||
31348 | /* Invalidate any prior representations */ | ||
31349 | SyBlobRelease(&pObj->sBlob); | ||
31350 | MemObjSetType(pObj, MEMOBJ_BOOL); | ||
31351 | } | ||
31352 | return SXRET_OK; | ||
31353 | } | ||
31354 | /* | ||
31355 | * Convert a jx9_value to type string.Prior representations are NOT invalidated. | ||
31356 | */ | ||
31357 | JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj) | ||
31358 | { | ||
31359 | sxi32 rc = SXRET_OK; | ||
31360 | if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ | ||
31361 | /* Perform the conversion */ | ||
31362 | SyBlobReset(&pObj->sBlob); /* Reset the internal buffer */ | ||
31363 | rc = MemObjStringValue(&pObj->sBlob, &(*pObj)); | ||
31364 | MemObjSetType(pObj, MEMOBJ_STRING); | ||
31365 | } | ||
31366 | return rc; | ||
31367 | } | ||
31368 | /* | ||
31369 | * Nullify a jx9_value.In other words invalidate any prior | ||
31370 | * representation. | ||
31371 | */ | ||
31372 | JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj) | ||
31373 | { | ||
31374 | return jx9MemObjRelease(pObj); | ||
31375 | } | ||
31376 | /* | ||
31377 | * Convert a jx9_value to type array.Invalidate any prior representations. | ||
31378 | * According to the JX9 language reference manual. | ||
31379 | * For any of the types: integer, float, string, boolean converting a value | ||
31380 | * to an array results in an array with a single element with index zero | ||
31381 | * and the value of the scalar which was converted. | ||
31382 | */ | ||
31383 | JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj) | ||
31384 | { | ||
31385 | if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
31386 | jx9_hashmap *pMap; | ||
31387 | /* Allocate a new hashmap instance */ | ||
31388 | pMap = jx9NewHashmap(pObj->pVm, 0, 0); | ||
31389 | if( pMap == 0 ){ | ||
31390 | return SXERR_MEM; | ||
31391 | } | ||
31392 | if( (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_RES)) == 0 ){ | ||
31393 | /* | ||
31394 | * According to the JX9 language reference manual. | ||
31395 | * For any of the types: integer, float, string, boolean converting a value | ||
31396 | * to an array results in an array with a single element with index zero | ||
31397 | * and the value of the scalar which was converted. | ||
31398 | */ | ||
31399 | /* Insert a single element */ | ||
31400 | jx9HashmapInsert(pMap, 0/* Automatic index assign */, &(*pObj)); | ||
31401 | SyBlobRelease(&pObj->sBlob); | ||
31402 | } | ||
31403 | /* Invalidate any prior representation */ | ||
31404 | MemObjSetType(pObj, MEMOBJ_HASHMAP); | ||
31405 | pObj->x.pOther = pMap; | ||
31406 | } | ||
31407 | return SXRET_OK; | ||
31408 | } | ||
31409 | /* | ||
31410 | * Return a pointer to the appropriate convertion method associated | ||
31411 | * with the given type. | ||
31412 | * Note on type juggling. | ||
31413 | * Accoding to the JX9 language reference manual | ||
31414 | * JX9 does not require (or support) explicit type definition in variable | ||
31415 | * declaration; a variable's type is determined by the context in which | ||
31416 | * the variable is used. That is to say, if a string value is assigned | ||
31417 | * to variable $var, $var becomes a string. If an integer value is then | ||
31418 | * assigned to $var, it becomes an integer. | ||
31419 | */ | ||
31420 | JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags) | ||
31421 | { | ||
31422 | if( iFlags & MEMOBJ_STRING ){ | ||
31423 | return jx9MemObjToString; | ||
31424 | }else if( iFlags & MEMOBJ_INT ){ | ||
31425 | return jx9MemObjToInteger; | ||
31426 | }else if( iFlags & MEMOBJ_REAL ){ | ||
31427 | return jx9MemObjToReal; | ||
31428 | }else if( iFlags & MEMOBJ_BOOL ){ | ||
31429 | return jx9MemObjToBool; | ||
31430 | }else if( iFlags & MEMOBJ_HASHMAP ){ | ||
31431 | return jx9MemObjToHashmap; | ||
31432 | } | ||
31433 | /* NULL cast */ | ||
31434 | return jx9MemObjToNull; | ||
31435 | } | ||
31436 | /* | ||
31437 | * Check whether the jx9_value is numeric [i.e: int/float/bool] or looks | ||
31438 | * like a numeric number [i.e: if the jx9_value is of type string.]. | ||
31439 | * Return TRUE if numeric.FALSE otherwise. | ||
31440 | */ | ||
31441 | JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj) | ||
31442 | { | ||
31443 | if( pObj->iFlags & ( MEMOBJ_BOOL|MEMOBJ_INT|MEMOBJ_REAL) ){ | ||
31444 | return TRUE; | ||
31445 | }else if( pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES) ){ | ||
31446 | return FALSE; | ||
31447 | }else if( pObj->iFlags & MEMOBJ_STRING ){ | ||
31448 | SyString sStr; | ||
31449 | sxi32 rc; | ||
31450 | SyStringInitFromBuf(&sStr, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); | ||
31451 | if( sStr.nByte <= 0 ){ | ||
31452 | /* Empty string */ | ||
31453 | return FALSE; | ||
31454 | } | ||
31455 | /* Check if the string representation looks like a numeric number */ | ||
31456 | rc = SyStrIsNumeric(sStr.zString, sStr.nByte, 0, 0); | ||
31457 | return rc == SXRET_OK ? TRUE : FALSE; | ||
31458 | } | ||
31459 | /* NOT REACHED */ | ||
31460 | return FALSE; | ||
31461 | } | ||
31462 | /* | ||
31463 | * Check whether the jx9_value is empty.Return TRUE if empty. | ||
31464 | * FALSE otherwise. | ||
31465 | * An jx9_value is considered empty if the following are true: | ||
31466 | * NULL value. | ||
31467 | * Boolean FALSE. | ||
31468 | * Integer/Float with a 0 (zero) value. | ||
31469 | * An empty string or a stream of 0 (zero) [i.e: "0", "00", "000", ...]. | ||
31470 | * An empty array. | ||
31471 | * NOTE | ||
31472 | * OBJECT VALUE MUST NOT BE MODIFIED. | ||
31473 | */ | ||
31474 | JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj) | ||
31475 | { | ||
31476 | if( pObj->iFlags & MEMOBJ_NULL ){ | ||
31477 | return TRUE; | ||
31478 | }else if( pObj->iFlags & MEMOBJ_INT ){ | ||
31479 | return pObj->x.iVal == 0 ? TRUE : FALSE; | ||
31480 | }else if( pObj->iFlags & MEMOBJ_REAL ){ | ||
31481 | return pObj->x.rVal == (jx9_real)0 ? TRUE : FALSE; | ||
31482 | }else if( pObj->iFlags & MEMOBJ_BOOL ){ | ||
31483 | return !pObj->x.iVal; | ||
31484 | }else if( pObj->iFlags & MEMOBJ_STRING ){ | ||
31485 | if( SyBlobLength(&pObj->sBlob) <= 0 ){ | ||
31486 | return TRUE; | ||
31487 | }else{ | ||
31488 | const char *zIn, *zEnd; | ||
31489 | zIn = (const char *)SyBlobData(&pObj->sBlob); | ||
31490 | zEnd = &zIn[SyBlobLength(&pObj->sBlob)]; | ||
31491 | while( zIn < zEnd ){ | ||
31492 | if( zIn[0] != '0' ){ | ||
31493 | break; | ||
31494 | } | ||
31495 | zIn++; | ||
31496 | } | ||
31497 | return zIn >= zEnd ? TRUE : FALSE; | ||
31498 | } | ||
31499 | }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ | ||
31500 | jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; | ||
31501 | return pMap->nEntry == 0 ? TRUE : FALSE; | ||
31502 | }else if ( pObj->iFlags & (MEMOBJ_RES) ){ | ||
31503 | return FALSE; | ||
31504 | } | ||
31505 | /* Assume empty by default */ | ||
31506 | return TRUE; | ||
31507 | } | ||
31508 | /* | ||
31509 | * Convert a jx9_value so that it has types MEMOBJ_REAL or MEMOBJ_INT | ||
31510 | * or both. | ||
31511 | * Invalidate any prior representations. Every effort is made to force | ||
31512 | * the conversion, even if the input is a string that does not look | ||
31513 | * completely like a number.Convert as much of the string as we can | ||
31514 | * and ignore the rest. | ||
31515 | */ | ||
31516 | JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj) | ||
31517 | { | ||
31518 | if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) ){ | ||
31519 | if( pObj->iFlags & (MEMOBJ_BOOL|MEMOBJ_NULL) ){ | ||
31520 | if( pObj->iFlags & MEMOBJ_NULL ){ | ||
31521 | pObj->x.iVal = 0; | ||
31522 | } | ||
31523 | MemObjSetType(pObj, MEMOBJ_INT); | ||
31524 | } | ||
31525 | /* Already numeric */ | ||
31526 | return SXRET_OK; | ||
31527 | } | ||
31528 | if( pObj->iFlags & MEMOBJ_STRING ){ | ||
31529 | sxi32 rc = SXERR_INVALID; | ||
31530 | sxu8 bReal = FALSE; | ||
31531 | SyString sString; | ||
31532 | SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); | ||
31533 | /* Check if the given string looks like a numeric number */ | ||
31534 | if( sString.nByte > 0 ){ | ||
31535 | rc = SyStrIsNumeric(sString.zString, sString.nByte, &bReal, 0); | ||
31536 | } | ||
31537 | if( bReal ){ | ||
31538 | jx9MemObjToReal(&(*pObj)); | ||
31539 | }else{ | ||
31540 | if( rc != SXRET_OK ){ | ||
31541 | /* The input does not look at all like a number, set the value to 0 */ | ||
31542 | pObj->x.iVal = 0; | ||
31543 | }else{ | ||
31544 | /* Convert as much as we can */ | ||
31545 | pObj->x.iVal = MemObjStringToInt(&(*pObj)); | ||
31546 | } | ||
31547 | MemObjSetType(pObj, MEMOBJ_INT); | ||
31548 | SyBlobRelease(&pObj->sBlob); | ||
31549 | } | ||
31550 | }else if(pObj->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)){ | ||
31551 | jx9MemObjToInteger(pObj); | ||
31552 | }else{ | ||
31553 | /* Perform a blind cast */ | ||
31554 | jx9MemObjToReal(&(*pObj)); | ||
31555 | } | ||
31556 | return SXRET_OK; | ||
31557 | } | ||
31558 | /* | ||
31559 | * Try a get an integer representation of the given jx9_value. | ||
31560 | * If the jx9_value is not of type real, this function is a no-op. | ||
31561 | */ | ||
31562 | JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj) | ||
31563 | { | ||
31564 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
31565 | /* Work only with reals */ | ||
31566 | MemObjTryIntger(&(*pObj)); | ||
31567 | } | ||
31568 | return SXRET_OK; | ||
31569 | } | ||
31570 | /* | ||
31571 | * Initialize a jx9_value to the null type. | ||
31572 | */ | ||
31573 | JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj) | ||
31574 | { | ||
31575 | /* Zero the structure */ | ||
31576 | SyZero(pObj, sizeof(jx9_value)); | ||
31577 | /* Initialize fields */ | ||
31578 | pObj->pVm = pVm; | ||
31579 | SyBlobInit(&pObj->sBlob, &pVm->sAllocator); | ||
31580 | /* Set the NULL type */ | ||
31581 | pObj->iFlags = MEMOBJ_NULL; | ||
31582 | return SXRET_OK; | ||
31583 | } | ||
31584 | /* | ||
31585 | * Initialize a jx9_value to the integer type. | ||
31586 | */ | ||
31587 | JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal) | ||
31588 | { | ||
31589 | /* Zero the structure */ | ||
31590 | SyZero(pObj, sizeof(jx9_value)); | ||
31591 | /* Initialize fields */ | ||
31592 | pObj->pVm = pVm; | ||
31593 | SyBlobInit(&pObj->sBlob, &pVm->sAllocator); | ||
31594 | /* Set the desired type */ | ||
31595 | pObj->x.iVal = iVal; | ||
31596 | pObj->iFlags = MEMOBJ_INT; | ||
31597 | return SXRET_OK; | ||
31598 | } | ||
31599 | /* | ||
31600 | * Initialize a jx9_value to the boolean type. | ||
31601 | */ | ||
31602 | JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal) | ||
31603 | { | ||
31604 | /* Zero the structure */ | ||
31605 | SyZero(pObj, sizeof(jx9_value)); | ||
31606 | /* Initialize fields */ | ||
31607 | pObj->pVm = pVm; | ||
31608 | SyBlobInit(&pObj->sBlob, &pVm->sAllocator); | ||
31609 | /* Set the desired type */ | ||
31610 | pObj->x.iVal = iVal ? 1 : 0; | ||
31611 | pObj->iFlags = MEMOBJ_BOOL; | ||
31612 | return SXRET_OK; | ||
31613 | } | ||
31614 | #if 0 | ||
31615 | /* | ||
31616 | * Initialize a jx9_value to the real type. | ||
31617 | */ | ||
31618 | JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal) | ||
31619 | { | ||
31620 | /* Zero the structure */ | ||
31621 | SyZero(pObj, sizeof(jx9_value)); | ||
31622 | /* Initialize fields */ | ||
31623 | pObj->pVm = pVm; | ||
31624 | SyBlobInit(&pObj->sBlob, &pVm->sAllocator); | ||
31625 | /* Set the desired type */ | ||
31626 | pObj->x.rVal = rVal; | ||
31627 | pObj->iFlags = MEMOBJ_REAL; | ||
31628 | return SXRET_OK; | ||
31629 | } | ||
31630 | #endif | ||
31631 | /* | ||
31632 | * Initialize a jx9_value to the array type. | ||
31633 | */ | ||
31634 | JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray) | ||
31635 | { | ||
31636 | /* Zero the structure */ | ||
31637 | SyZero(pObj, sizeof(jx9_value)); | ||
31638 | /* Initialize fields */ | ||
31639 | pObj->pVm = pVm; | ||
31640 | SyBlobInit(&pObj->sBlob, &pVm->sAllocator); | ||
31641 | /* Set the desired type */ | ||
31642 | pObj->iFlags = MEMOBJ_HASHMAP; | ||
31643 | pObj->x.pOther = pArray; | ||
31644 | return SXRET_OK; | ||
31645 | } | ||
31646 | /* | ||
31647 | * Initialize a jx9_value to the string type. | ||
31648 | */ | ||
31649 | JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal) | ||
31650 | { | ||
31651 | /* Zero the structure */ | ||
31652 | SyZero(pObj, sizeof(jx9_value)); | ||
31653 | /* Initialize fields */ | ||
31654 | pObj->pVm = pVm; | ||
31655 | SyBlobInit(&pObj->sBlob, &pVm->sAllocator); | ||
31656 | if( pVal ){ | ||
31657 | /* Append contents */ | ||
31658 | SyBlobAppend(&pObj->sBlob, (const void *)pVal->zString, pVal->nByte); | ||
31659 | } | ||
31660 | /* Set the desired type */ | ||
31661 | pObj->iFlags = MEMOBJ_STRING; | ||
31662 | return SXRET_OK; | ||
31663 | } | ||
31664 | /* | ||
31665 | * Append some contents to the internal buffer of a given jx9_value. | ||
31666 | * If the given jx9_value is not of type string, this function | ||
31667 | * invalidate any prior representation and set the string type. | ||
31668 | * Then a simple append operation is performed. | ||
31669 | */ | ||
31670 | JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen) | ||
31671 | { | ||
31672 | sxi32 rc; | ||
31673 | if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ | ||
31674 | /* Invalidate any prior representation */ | ||
31675 | jx9MemObjRelease(pObj); | ||
31676 | MemObjSetType(pObj, MEMOBJ_STRING); | ||
31677 | } | ||
31678 | /* Append contents */ | ||
31679 | rc = SyBlobAppend(&pObj->sBlob, zData, nLen); | ||
31680 | return rc; | ||
31681 | } | ||
31682 | #if 0 | ||
31683 | /* | ||
31684 | * Format and append some contents to the internal buffer of a given jx9_value. | ||
31685 | * If the given jx9_value is not of type string, this function invalidate | ||
31686 | * any prior representation and set the string type. | ||
31687 | * Then a simple format and append operation is performed. | ||
31688 | */ | ||
31689 | JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap) | ||
31690 | { | ||
31691 | sxi32 rc; | ||
31692 | if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ | ||
31693 | /* Invalidate any prior representation */ | ||
31694 | jx9MemObjRelease(pObj); | ||
31695 | MemObjSetType(pObj, MEMOBJ_STRING); | ||
31696 | } | ||
31697 | /* Format and append contents */ | ||
31698 | rc = SyBlobFormatAp(&pObj->sBlob, zFormat, ap); | ||
31699 | return rc; | ||
31700 | } | ||
31701 | #endif | ||
31702 | /* | ||
31703 | * Duplicate the contents of a jx9_value. | ||
31704 | */ | ||
31705 | JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest) | ||
31706 | { | ||
31707 | jx9_hashmap *pMap = 0; | ||
31708 | sxi32 rc; | ||
31709 | if( pSrc->iFlags & MEMOBJ_HASHMAP ){ | ||
31710 | /* Increment reference count */ | ||
31711 | ((jx9_hashmap *)pSrc->x.pOther)->iRef++; | ||
31712 | } | ||
31713 | if( pDest->iFlags & MEMOBJ_HASHMAP ){ | ||
31714 | pMap = (jx9_hashmap *)pDest->x.pOther; | ||
31715 | } | ||
31716 | SyMemcpy((const void *)&(*pSrc), &(*pDest), sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32))); | ||
31717 | rc = SXRET_OK; | ||
31718 | if( SyBlobLength(&pSrc->sBlob) > 0 ){ | ||
31719 | SyBlobReset(&pDest->sBlob); | ||
31720 | rc = SyBlobDup(&pSrc->sBlob, &pDest->sBlob); | ||
31721 | }else{ | ||
31722 | if( SyBlobLength(&pDest->sBlob) > 0 ){ | ||
31723 | SyBlobRelease(&pDest->sBlob); | ||
31724 | } | ||
31725 | } | ||
31726 | if( pMap ){ | ||
31727 | jx9HashmapUnref(pMap); | ||
31728 | } | ||
31729 | return rc; | ||
31730 | } | ||
31731 | /* | ||
31732 | * Duplicate the contents of a jx9_value but do not copy internal | ||
31733 | * buffer contents, simply point to it. | ||
31734 | */ | ||
31735 | JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest) | ||
31736 | { | ||
31737 | SyMemcpy((const void *)&(*pSrc), &(*pDest), | ||
31738 | sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32))); | ||
31739 | if( pSrc->iFlags & MEMOBJ_HASHMAP ){ | ||
31740 | /* Increment reference count */ | ||
31741 | ((jx9_hashmap *)pSrc->x.pOther)->iRef++; | ||
31742 | } | ||
31743 | if( SyBlobLength(&pDest->sBlob) > 0 ){ | ||
31744 | SyBlobRelease(&pDest->sBlob); | ||
31745 | } | ||
31746 | if( SyBlobLength(&pSrc->sBlob) > 0 ){ | ||
31747 | SyBlobReadOnly(&pDest->sBlob, SyBlobData(&pSrc->sBlob), SyBlobLength(&pSrc->sBlob)); | ||
31748 | } | ||
31749 | return SXRET_OK; | ||
31750 | } | ||
31751 | /* | ||
31752 | * Invalidate any prior representation of a given jx9_value. | ||
31753 | */ | ||
31754 | JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj) | ||
31755 | { | ||
31756 | if( (pObj->iFlags & MEMOBJ_NULL) == 0 ){ | ||
31757 | if( pObj->iFlags & MEMOBJ_HASHMAP ){ | ||
31758 | jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther); | ||
31759 | } | ||
31760 | /* Release the internal buffer */ | ||
31761 | SyBlobRelease(&pObj->sBlob); | ||
31762 | /* Invalidate any prior representation */ | ||
31763 | pObj->iFlags = MEMOBJ_NULL; | ||
31764 | } | ||
31765 | return SXRET_OK; | ||
31766 | } | ||
31767 | /* | ||
31768 | * Compare two jx9_values. | ||
31769 | * Return 0 if the values are equals, > 0 if pObj1 is greater than pObj2 | ||
31770 | * or < 0 if pObj2 is greater than pObj1. | ||
31771 | * Type comparison table taken from the JX9 language reference manual. | ||
31772 | * Comparisons of $x with JX9 functions Expression | ||
31773 | * gettype() empty() is_null() isset() boolean : if($x) | ||
31774 | * $x = ""; string TRUE FALSE TRUE FALSE | ||
31775 | * $x = null NULL TRUE TRUE FALSE FALSE | ||
31776 | * var $x; NULL TRUE TRUE FALSE FALSE | ||
31777 | * $x is undefined NULL TRUE TRUE FALSE FALSE | ||
31778 | * $x = array(); array TRUE FALSE TRUE FALSE | ||
31779 | * $x = false; boolean TRUE FALSE TRUE FALSE | ||
31780 | * $x = true; boolean FALSE FALSE TRUE TRUE | ||
31781 | * $x = 1; integer FALSE FALSE TRUE TRUE | ||
31782 | * $x = 42; integer FALSE FALSE TRUE TRUE | ||
31783 | * $x = 0; integer TRUE FALSE TRUE FALSE | ||
31784 | * $x = -1; integer FALSE FALSE TRUE TRUE | ||
31785 | * $x = "1"; string FALSE FALSE TRUE TRUE | ||
31786 | * $x = "0"; string TRUE FALSE TRUE FALSE | ||
31787 | * $x = "-1"; string FALSE FALSE TRUE TRUE | ||
31788 | * $x = "jx9"; string FALSE FALSE TRUE TRUE | ||
31789 | * $x = "true"; string FALSE FALSE TRUE TRUE | ||
31790 | * $x = "false"; string FALSE FALSE TRUE TRUE | ||
31791 | * Loose comparisons with == | ||
31792 | * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" "" | ||
31793 | * TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE | ||
31794 | * FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE TRUE FALSE TRUE | ||
31795 | * 1 TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31796 | * 0 FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE TRUE | ||
31797 | * -1 TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE | ||
31798 | * "1" TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31799 | * "0" FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE | ||
31800 | * "-1" TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE | ||
31801 | * NULL FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE | ||
31802 | * array() FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE | ||
31803 | * "jx9" TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE | ||
31804 | * "" FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE | ||
31805 | * Strict comparisons with === | ||
31806 | * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" "" | ||
31807 | * TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31808 | * FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31809 | * 1 FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31810 | * 0 FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31811 | * -1 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31812 | * "1" FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE | ||
31813 | * "0" FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE | ||
31814 | * "-1" FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE | ||
31815 | * NULL FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE | ||
31816 | * array() FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE | ||
31817 | * "jx9" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE | ||
31818 | * "" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE | ||
31819 | */ | ||
31820 | JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest) | ||
31821 | { | ||
31822 | sxi32 iComb; | ||
31823 | sxi32 rc; | ||
31824 | if( bStrict ){ | ||
31825 | sxi32 iF1, iF2; | ||
31826 | /* Strict comparisons with === */ | ||
31827 | iF1 = pObj1->iFlags; | ||
31828 | iF2 = pObj2->iFlags; | ||
31829 | if( iF1 != iF2 ){ | ||
31830 | /* Not of the same type */ | ||
31831 | return 1; | ||
31832 | } | ||
31833 | } | ||
31834 | /* Combine flag together */ | ||
31835 | iComb = pObj1->iFlags|pObj2->iFlags; | ||
31836 | if( iComb & (MEMOBJ_NULL|MEMOBJ_RES|MEMOBJ_BOOL) ){ | ||
31837 | /* Convert to boolean: Keep in mind FALSE < TRUE */ | ||
31838 | if( (pObj1->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
31839 | jx9MemObjToBool(pObj1); | ||
31840 | } | ||
31841 | if( (pObj2->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
31842 | jx9MemObjToBool(pObj2); | ||
31843 | } | ||
31844 | return (sxi32)((pObj1->x.iVal != 0) - (pObj2->x.iVal != 0)); | ||
31845 | }else if ( iComb & MEMOBJ_HASHMAP ){ | ||
31846 | /* Hashmap aka 'array' comparison */ | ||
31847 | if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
31848 | /* Array is always greater */ | ||
31849 | return -1; | ||
31850 | } | ||
31851 | if( (pObj2->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
31852 | /* Array is always greater */ | ||
31853 | return 1; | ||
31854 | } | ||
31855 | /* Perform the comparison */ | ||
31856 | rc = jx9HashmapCmp((jx9_hashmap *)pObj1->x.pOther, (jx9_hashmap *)pObj2->x.pOther, bStrict); | ||
31857 | return rc; | ||
31858 | }else if ( iComb & MEMOBJ_STRING ){ | ||
31859 | SyString s1, s2; | ||
31860 | /* Perform a strict string comparison.*/ | ||
31861 | if( (pObj1->iFlags&MEMOBJ_STRING) == 0 ){ | ||
31862 | jx9MemObjToString(pObj1); | ||
31863 | } | ||
31864 | if( (pObj2->iFlags&MEMOBJ_STRING) == 0 ){ | ||
31865 | jx9MemObjToString(pObj2); | ||
31866 | } | ||
31867 | SyStringInitFromBuf(&s1, SyBlobData(&pObj1->sBlob), SyBlobLength(&pObj1->sBlob)); | ||
31868 | SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob)); | ||
31869 | /* | ||
31870 | * Strings are compared using memcmp(). If one value is an exact prefix of the | ||
31871 | * other, then the shorter value is less than the longer value. | ||
31872 | */ | ||
31873 | rc = SyMemcmp((const void *)s1.zString, (const void *)s2.zString, SXMIN(s1.nByte, s2.nByte)); | ||
31874 | if( rc == 0 ){ | ||
31875 | if( s1.nByte != s2.nByte ){ | ||
31876 | rc = s1.nByte < s2.nByte ? -1 : 1; | ||
31877 | } | ||
31878 | } | ||
31879 | return rc; | ||
31880 | }else if( iComb & (MEMOBJ_INT|MEMOBJ_REAL) ){ | ||
31881 | /* Perform a numeric comparison if one of the operand is numeric(integer or real) */ | ||
31882 | if( (pObj1->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ | ||
31883 | jx9MemObjToNumeric(pObj1); | ||
31884 | } | ||
31885 | if( (pObj2->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ | ||
31886 | jx9MemObjToNumeric(pObj2); | ||
31887 | } | ||
31888 | if( (pObj1->iFlags & pObj2->iFlags & MEMOBJ_INT) == 0) { | ||
31889 | jx9_real r1, r2; | ||
31890 | /* Compare as reals */ | ||
31891 | if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ | ||
31892 | jx9MemObjToReal(pObj1); | ||
31893 | } | ||
31894 | r1 = pObj1->x.rVal; | ||
31895 | if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ | ||
31896 | jx9MemObjToReal(pObj2); | ||
31897 | } | ||
31898 | r2 = pObj2->x.rVal; | ||
31899 | if( r1 > r2 ){ | ||
31900 | return 1; | ||
31901 | }else if( r1 < r2 ){ | ||
31902 | return -1; | ||
31903 | } | ||
31904 | return 0; | ||
31905 | }else{ | ||
31906 | /* Integer comparison */ | ||
31907 | if( pObj1->x.iVal > pObj2->x.iVal ){ | ||
31908 | return 1; | ||
31909 | }else if( pObj1->x.iVal < pObj2->x.iVal ){ | ||
31910 | return -1; | ||
31911 | } | ||
31912 | return 0; | ||
31913 | } | ||
31914 | } | ||
31915 | /* NOT REACHED */ | ||
31916 | SXUNUSED(iNest); | ||
31917 | return 0; | ||
31918 | } | ||
31919 | /* | ||
31920 | * Perform an addition operation of two jx9_values. | ||
31921 | * The reason this function is implemented here rather than 'vm.c' | ||
31922 | * is that the '+' operator is overloaded. | ||
31923 | * That is, the '+' operator is used for arithmetic operation and also | ||
31924 | * used for operation on arrays [i.e: union]. When used with an array | ||
31925 | * The + operator returns the right-hand array appended to the left-hand array. | ||
31926 | * For keys that exist in both arrays, the elements from the left-hand array | ||
31927 | * will be used, and the matching elements from the right-hand array will | ||
31928 | * be ignored. | ||
31929 | * This function take care of handling all the scenarios. | ||
31930 | */ | ||
31931 | JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore) | ||
31932 | { | ||
31933 | if( ((pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP) == 0 ){ | ||
31934 | /* Arithemtic operation */ | ||
31935 | jx9MemObjToNumeric(pObj1); | ||
31936 | jx9MemObjToNumeric(pObj2); | ||
31937 | if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_REAL ){ | ||
31938 | /* Floating point arithmetic */ | ||
31939 | jx9_real a, b; | ||
31940 | if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ | ||
31941 | jx9MemObjToReal(pObj1); | ||
31942 | } | ||
31943 | if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ | ||
31944 | jx9MemObjToReal(pObj2); | ||
31945 | } | ||
31946 | a = pObj1->x.rVal; | ||
31947 | b = pObj2->x.rVal; | ||
31948 | pObj1->x.rVal = a+b; | ||
31949 | MemObjSetType(pObj1, MEMOBJ_REAL); | ||
31950 | /* Try to get an integer representation also */ | ||
31951 | MemObjTryIntger(&(*pObj1)); | ||
31952 | }else{ | ||
31953 | /* Integer arithmetic */ | ||
31954 | sxi64 a, b; | ||
31955 | a = pObj1->x.iVal; | ||
31956 | b = pObj2->x.iVal; | ||
31957 | pObj1->x.iVal = a+b; | ||
31958 | MemObjSetType(pObj1, MEMOBJ_INT); | ||
31959 | } | ||
31960 | }else{ | ||
31961 | if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP ){ | ||
31962 | jx9_hashmap *pMap; | ||
31963 | sxi32 rc; | ||
31964 | if( bAddStore ){ | ||
31965 | /* Do not duplicate the hashmap, use the left one since its an add&store operation. | ||
31966 | */ | ||
31967 | if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
31968 | /* Force a hashmap cast */ | ||
31969 | rc = jx9MemObjToHashmap(pObj1); | ||
31970 | if( rc != SXRET_OK ){ | ||
31971 | jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array"); | ||
31972 | return rc; | ||
31973 | } | ||
31974 | } | ||
31975 | /* Point to the structure that describe the hashmap */ | ||
31976 | pMap = (jx9_hashmap *)pObj1->x.pOther; | ||
31977 | }else{ | ||
31978 | /* Create a new hashmap */ | ||
31979 | pMap = jx9NewHashmap(pObj1->pVm, 0, 0); | ||
31980 | if( pMap == 0){ | ||
31981 | jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array"); | ||
31982 | return SXERR_MEM; | ||
31983 | } | ||
31984 | } | ||
31985 | if( !bAddStore ){ | ||
31986 | if(pObj1->iFlags & MEMOBJ_HASHMAP ){ | ||
31987 | /* Perform a hashmap duplication */ | ||
31988 | jx9HashmapDup((jx9_hashmap *)pObj1->x.pOther, pMap); | ||
31989 | }else{ | ||
31990 | if((pObj1->iFlags & MEMOBJ_NULL) == 0 ){ | ||
31991 | /* Simple insertion */ | ||
31992 | jx9HashmapInsert(pMap, 0, pObj1); | ||
31993 | } | ||
31994 | } | ||
31995 | } | ||
31996 | /* Perform the union */ | ||
31997 | if(pObj2->iFlags & MEMOBJ_HASHMAP ){ | ||
31998 | jx9HashmapUnion(pMap, (jx9_hashmap *)pObj2->x.pOther); | ||
31999 | }else{ | ||
32000 | if((pObj2->iFlags & MEMOBJ_NULL) == 0 ){ | ||
32001 | /* Simple insertion */ | ||
32002 | jx9HashmapInsert(pMap, 0, pObj2); | ||
32003 | } | ||
32004 | } | ||
32005 | /* Reflect the change */ | ||
32006 | if( pObj1->iFlags & MEMOBJ_STRING ){ | ||
32007 | SyBlobRelease(&pObj1->sBlob); | ||
32008 | } | ||
32009 | pObj1->x.pOther = pMap; | ||
32010 | MemObjSetType(pObj1, MEMOBJ_HASHMAP); | ||
32011 | } | ||
32012 | } | ||
32013 | return SXRET_OK; | ||
32014 | } | ||
32015 | /* | ||
32016 | * Return a printable representation of the type of a given | ||
32017 | * jx9_value. | ||
32018 | */ | ||
32019 | JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal) | ||
32020 | { | ||
32021 | const char *zType = ""; | ||
32022 | if( pVal->iFlags & MEMOBJ_NULL ){ | ||
32023 | zType = "null"; | ||
32024 | }else if( pVal->iFlags & MEMOBJ_INT ){ | ||
32025 | zType = "int"; | ||
32026 | }else if( pVal->iFlags & MEMOBJ_REAL ){ | ||
32027 | zType = "float"; | ||
32028 | }else if( pVal->iFlags & MEMOBJ_STRING ){ | ||
32029 | zType = "string"; | ||
32030 | }else if( pVal->iFlags & MEMOBJ_BOOL ){ | ||
32031 | zType = "bool"; | ||
32032 | }else if( pVal->iFlags & MEMOBJ_HASHMAP ){ | ||
32033 | jx9_hashmap *pMap = (jx9_hashmap *)pVal->x.pOther; | ||
32034 | if( pMap->iFlags & HASHMAP_JSON_OBJECT ){ | ||
32035 | zType = "JSON Object"; | ||
32036 | }else{ | ||
32037 | zType = "JSON Array"; | ||
32038 | } | ||
32039 | }else if( pVal->iFlags & MEMOBJ_RES ){ | ||
32040 | zType = "resource"; | ||
32041 | } | ||
32042 | return zType; | ||
32043 | } | ||
32044 | /* | ||
32045 | * Dump a jx9_value [i.e: get a printable representation of it's type and contents.]. | ||
32046 | * Store the dump in the given blob. | ||
32047 | */ | ||
32048 | JX9_PRIVATE sxi32 jx9MemObjDump( | ||
32049 | SyBlob *pOut, /* Store the dump here */ | ||
32050 | jx9_value *pObj /* Dump this */ | ||
32051 | ) | ||
32052 | { | ||
32053 | sxi32 rc = SXRET_OK; | ||
32054 | const char *zType; | ||
32055 | /* Get value type first */ | ||
32056 | zType = jx9MemObjTypeDump(pObj); | ||
32057 | SyBlobAppend(&(*pOut), zType, SyStrlen(zType)); | ||
32058 | if((pObj->iFlags & MEMOBJ_NULL) == 0 ){ | ||
32059 | SyBlobAppend(&(*pOut), "(", sizeof(char)); | ||
32060 | if( pObj->iFlags & MEMOBJ_HASHMAP ){ | ||
32061 | jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; | ||
32062 | SyBlobFormat(pOut,"%u ",pMap->nEntry); | ||
32063 | /* Dump hashmap entries */ | ||
32064 | rc = jx9JsonSerialize(pObj,pOut); | ||
32065 | }else{ | ||
32066 | SyBlob *pContents = &pObj->sBlob; | ||
32067 | /* Get a printable representation of the contents */ | ||
32068 | if((pObj->iFlags & MEMOBJ_STRING) == 0 ){ | ||
32069 | MemObjStringValue(&(*pOut), &(*pObj)); | ||
32070 | }else{ | ||
32071 | /* Append length first */ | ||
32072 | SyBlobFormat(&(*pOut), "%u '", SyBlobLength(&pObj->sBlob)); | ||
32073 | if( SyBlobLength(pContents) > 0 ){ | ||
32074 | SyBlobAppend(&(*pOut), SyBlobData(pContents), SyBlobLength(pContents)); | ||
32075 | } | ||
32076 | SyBlobAppend(&(*pOut), "'", sizeof(char)); | ||
32077 | } | ||
32078 | } | ||
32079 | SyBlobAppend(&(*pOut), ")", sizeof(char)); | ||
32080 | } | ||
32081 | #ifdef __WINNT__ | ||
32082 | SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n")-1); | ||
32083 | #else | ||
32084 | SyBlobAppend(&(*pOut), "\n", sizeof(char)); | ||
32085 | #endif | ||
32086 | return rc; | ||
32087 | } | ||
32088 | /* | ||
32089 | * ---------------------------------------------------------- | ||
32090 | * File: jx9_parse.c | ||
32091 | * MD5: d8fcac4c6cd7672f0103c0bf4a4b61fc | ||
32092 | * ---------------------------------------------------------- | ||
32093 | */ | ||
32094 | /* | ||
32095 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
32096 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
32097 | * Version 1.7.2 | ||
32098 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
32099 | * please contact Symisc Systems via: | ||
32100 | * legal@symisc.net | ||
32101 | * licensing@symisc.net | ||
32102 | * contact@symisc.net | ||
32103 | * or visit: | ||
32104 | * http://jx9.symisc.net/ | ||
32105 | */ | ||
32106 | /* $SymiscID: parse.c v1.2 FreeBSD 2012-12-11 00:46 stable <chm@symisc.net> $ */ | ||
32107 | #ifndef JX9_AMALGAMATION | ||
32108 | #include "jx9Int.h" | ||
32109 | #endif | ||
32110 | /* Expression parser for the Jx9 programming language */ | ||
32111 | /* Operators associativity */ | ||
32112 | #define EXPR_OP_ASSOC_LEFT 0x01 /* Left associative operator */ | ||
32113 | #define EXPR_OP_ASSOC_RIGHT 0x02 /* Right associative operator */ | ||
32114 | #define EXPR_OP_NON_ASSOC 0x04 /* Non-associative operator */ | ||
32115 | /* | ||
32116 | * Operators table | ||
32117 | * This table is sorted by operators priority (highest to lowest) according | ||
32118 | * the JX9 language reference manual. | ||
32119 | * JX9 implements all the 60 JX9 operators and have introduced the eq and ne operators. | ||
32120 | * The operators precedence table have been improved dramatically so that you can do same | ||
32121 | * amazing things now such as array dereferencing, on the fly function call, anonymous function | ||
32122 | * as array values, object member access on instantiation and so on. | ||
32123 | * Refer to the following page for a full discussion on these improvements: | ||
32124 | * http://jx9.symisc.net/features.html | ||
32125 | */ | ||
32126 | static const jx9_expr_op aOpTable[] = { | ||
32127 | /* Postfix operators */ | ||
32128 | /* Precedence 2(Highest), left-associative */ | ||
32129 | { {".", sizeof(char)}, EXPR_OP_DOT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_MEMBER }, | ||
32130 | { {"[", sizeof(char)}, EXPR_OP_SUBSCRIPT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_LOAD_IDX}, | ||
32131 | /* Precedence 3, non-associative */ | ||
32132 | { {"++", sizeof(char)*2}, EXPR_OP_INCR, 3, EXPR_OP_NON_ASSOC , JX9_OP_INCR}, | ||
32133 | { {"--", sizeof(char)*2}, EXPR_OP_DECR, 3, EXPR_OP_NON_ASSOC , JX9_OP_DECR}, | ||
32134 | /* Unary operators */ | ||
32135 | /* Precedence 4, right-associative */ | ||
32136 | { {"-", sizeof(char)}, EXPR_OP_UMINUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UMINUS }, | ||
32137 | { {"+", sizeof(char)}, EXPR_OP_UPLUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UPLUS }, | ||
32138 | { {"~", sizeof(char)}, EXPR_OP_BITNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_BITNOT }, | ||
32139 | { {"!", sizeof(char)}, EXPR_OP_LOGNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_LNOT }, | ||
32140 | /* Cast operators */ | ||
32141 | { {"(int)", sizeof("(int)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_INT }, | ||
32142 | { {"(bool)", sizeof("(bool)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_BOOL }, | ||
32143 | { {"(string)", sizeof("(string)")-1}, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_STR }, | ||
32144 | { {"(float)", sizeof("(float)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_REAL }, | ||
32145 | { {"(array)", sizeof("(array)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */ | ||
32146 | { {"(object)", sizeof("(object)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */ | ||
32147 | /* Binary operators */ | ||
32148 | /* Precedence 7, left-associative */ | ||
32149 | { {"*", sizeof(char)}, EXPR_OP_MUL, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MUL}, | ||
32150 | { {"/", sizeof(char)}, EXPR_OP_DIV, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_DIV}, | ||
32151 | { {"%", sizeof(char)}, EXPR_OP_MOD, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MOD}, | ||
32152 | /* Precedence 8, left-associative */ | ||
32153 | { {"+", sizeof(char)}, EXPR_OP_ADD, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_ADD}, | ||
32154 | { {"-", sizeof(char)}, EXPR_OP_SUB, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_SUB}, | ||
32155 | { {"..", sizeof(char)*2},EXPR_OP_DDOT, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_CAT}, | ||
32156 | /* Precedence 9, left-associative */ | ||
32157 | { {"<<", sizeof(char)*2}, EXPR_OP_SHL, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHL}, | ||
32158 | { {">>", sizeof(char)*2}, EXPR_OP_SHR, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHR}, | ||
32159 | /* Precedence 10, non-associative */ | ||
32160 | { {"<", sizeof(char)}, EXPR_OP_LT, 10, EXPR_OP_NON_ASSOC, JX9_OP_LT}, | ||
32161 | { {">", sizeof(char)}, EXPR_OP_GT, 10, EXPR_OP_NON_ASSOC, JX9_OP_GT}, | ||
32162 | { {"<=", sizeof(char)*2}, EXPR_OP_LE, 10, EXPR_OP_NON_ASSOC, JX9_OP_LE}, | ||
32163 | { {">=", sizeof(char)*2}, EXPR_OP_GE, 10, EXPR_OP_NON_ASSOC, JX9_OP_GE}, | ||
32164 | { {"<>", sizeof(char)*2}, EXPR_OP_NE, 10, EXPR_OP_NON_ASSOC, JX9_OP_NEQ}, | ||
32165 | /* Precedence 11, non-associative */ | ||
32166 | { {"==", sizeof(char)*2}, EXPR_OP_EQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_EQ}, | ||
32167 | { {"!=", sizeof(char)*2}, EXPR_OP_NE, 11, EXPR_OP_NON_ASSOC, JX9_OP_NEQ}, | ||
32168 | { {"===", sizeof(char)*3}, EXPR_OP_TEQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_TEQ}, | ||
32169 | { {"!==", sizeof(char)*3}, EXPR_OP_TNE, 11, EXPR_OP_NON_ASSOC, JX9_OP_TNE}, | ||
32170 | /* Precedence 12, left-associative */ | ||
32171 | { {"&", sizeof(char)}, EXPR_OP_BAND, 12, EXPR_OP_ASSOC_LEFT, JX9_OP_BAND}, | ||
32172 | /* Binary operators */ | ||
32173 | /* Precedence 13, left-associative */ | ||
32174 | { {"^", sizeof(char)}, EXPR_OP_XOR, 13, EXPR_OP_ASSOC_LEFT, JX9_OP_BXOR}, | ||
32175 | /* Precedence 14, left-associative */ | ||
32176 | { {"|", sizeof(char)}, EXPR_OP_BOR, 14, EXPR_OP_ASSOC_LEFT, JX9_OP_BOR}, | ||
32177 | /* Precedence 15, left-associative */ | ||
32178 | { {"&&", sizeof(char)*2}, EXPR_OP_LAND, 15, EXPR_OP_ASSOC_LEFT, JX9_OP_LAND}, | ||
32179 | /* Precedence 16, left-associative */ | ||
32180 | { {"||", sizeof(char)*2}, EXPR_OP_LOR, 16, EXPR_OP_ASSOC_LEFT, JX9_OP_LOR}, | ||
32181 | /* Ternary operator */ | ||
32182 | /* Precedence 17, left-associative */ | ||
32183 | { {"?", sizeof(char)}, EXPR_OP_QUESTY, 17, EXPR_OP_ASSOC_LEFT, 0}, | ||
32184 | /* Combined binary operators */ | ||
32185 | /* Precedence 18, right-associative */ | ||
32186 | { {"=", sizeof(char)}, EXPR_OP_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_STORE}, | ||
32187 | { {"+=", sizeof(char)*2}, EXPR_OP_ADD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_ADD_STORE }, | ||
32188 | { {"-=", sizeof(char)*2}, EXPR_OP_SUB_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SUB_STORE }, | ||
32189 | { {".=", sizeof(char)*2}, EXPR_OP_DOT_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_CAT_STORE }, | ||
32190 | { {"*=", sizeof(char)*2}, EXPR_OP_MUL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MUL_STORE }, | ||
32191 | { {"/=", sizeof(char)*2}, EXPR_OP_DIV_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_DIV_STORE }, | ||
32192 | { {"%=", sizeof(char)*2}, EXPR_OP_MOD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MOD_STORE }, | ||
32193 | { {"&=", sizeof(char)*2}, EXPR_OP_AND_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BAND_STORE }, | ||
32194 | { {"|=", sizeof(char)*2}, EXPR_OP_OR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BOR_STORE }, | ||
32195 | { {"^=", sizeof(char)*2}, EXPR_OP_XOR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BXOR_STORE }, | ||
32196 | { {"<<=", sizeof(char)*3}, EXPR_OP_SHL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHL_STORE }, | ||
32197 | { {">>=", sizeof(char)*3}, EXPR_OP_SHR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHR_STORE }, | ||
32198 | /* Precedence 22, left-associative [Lowest operator] */ | ||
32199 | { {",", sizeof(char)}, EXPR_OP_COMMA, 22, EXPR_OP_ASSOC_LEFT, 0}, /* IMP-0139-COMMA: Symisc eXtension */ | ||
32200 | }; | ||
32201 | /* Function call operator need special handling */ | ||
32202 | static const jx9_expr_op sFCallOp = {{"(", sizeof(char)}, EXPR_OP_FUNC_CALL, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_CALL}; | ||
32203 | /* | ||
32204 | * Check if the given token is a potential operator or not. | ||
32205 | * This function is called by the lexer each time it extract a token that may | ||
32206 | * look like an operator. | ||
32207 | * Return a structure [i.e: jx9_expr_op instnace ] that describe the operator on success. | ||
32208 | * Otherwise NULL. | ||
32209 | * Note that the function take care of handling ambiguity [i.e: whether we are dealing with | ||
32210 | * a binary minus or unary minus.] | ||
32211 | */ | ||
32212 | JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast) | ||
32213 | { | ||
32214 | sxu32 n = 0; | ||
32215 | sxi32 rc; | ||
32216 | /* Do a linear lookup on the operators table */ | ||
32217 | for(;;){ | ||
32218 | if( n >= SX_ARRAYSIZE(aOpTable) ){ | ||
32219 | break; | ||
32220 | } | ||
32221 | rc = SyStringCmp(pStr, &aOpTable[n].sOp, SyMemcmp); | ||
32222 | if( rc == 0 ){ | ||
32223 | if( aOpTable[n].sOp.nByte != sizeof(char) || (aOpTable[n].iOp != EXPR_OP_UMINUS && aOpTable[n].iOp != EXPR_OP_UPLUS) || pLast == 0 ){ | ||
32224 | if( aOpTable[n].iOp == EXPR_OP_SUBSCRIPT && (pLast == 0 || (pLast->nType & (JX9_TK_ID|JX9_TK_CSB/*]*/|JX9_TK_RPAREN/*)*/)) == 0) ){ | ||
32225 | /* JSON Array not subscripting, return NULL */ | ||
32226 | return 0; | ||
32227 | } | ||
32228 | /* There is no ambiguity here, simply return the first operator seen */ | ||
32229 | return &aOpTable[n]; | ||
32230 | } | ||
32231 | /* Handle ambiguity */ | ||
32232 | if( pLast->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_COLON/*:*/|JX9_TK_COMMA/*, '*/) ){ | ||
32233 | /* Unary opertors have prcedence here over binary operators */ | ||
32234 | return &aOpTable[n]; | ||
32235 | } | ||
32236 | if( pLast->nType & JX9_TK_OP ){ | ||
32237 | const jx9_expr_op *pOp = (const jx9_expr_op *)pLast->pUserData; | ||
32238 | /* Ticket 1433-31: Handle the '++', '--' operators case */ | ||
32239 | if( pOp->iOp != EXPR_OP_INCR && pOp->iOp != EXPR_OP_DECR ){ | ||
32240 | /* Unary opertors have prcedence here over binary operators */ | ||
32241 | return &aOpTable[n]; | ||
32242 | } | ||
32243 | |||
32244 | } | ||
32245 | } | ||
32246 | ++n; /* Next operator in the table */ | ||
32247 | } | ||
32248 | /* No such operator */ | ||
32249 | return 0; | ||
32250 | } | ||
32251 | /* | ||
32252 | * Delimit a set of token stream. | ||
32253 | * This function take care of handling the nesting level and stops when it hit | ||
32254 | * the end of the input or the ending token is found and the nesting level is zero. | ||
32255 | */ | ||
32256 | JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn,SyToken *pEnd,sxu32 nTokStart,sxu32 nTokEnd,SyToken **ppEnd) | ||
32257 | { | ||
32258 | SyToken *pCur = pIn; | ||
32259 | sxi32 iNest = 1; | ||
32260 | for(;;){ | ||
32261 | if( pCur >= pEnd ){ | ||
32262 | break; | ||
32263 | } | ||
32264 | if( pCur->nType & nTokStart ){ | ||
32265 | /* Increment nesting level */ | ||
32266 | iNest++; | ||
32267 | }else if( pCur->nType & nTokEnd ){ | ||
32268 | /* Decrement nesting level */ | ||
32269 | iNest--; | ||
32270 | if( iNest <= 0 ){ | ||
32271 | break; | ||
32272 | } | ||
32273 | } | ||
32274 | /* Advance cursor */ | ||
32275 | pCur++; | ||
32276 | } | ||
32277 | /* Point to the end of the chunk */ | ||
32278 | *ppEnd = pCur; | ||
32279 | } | ||
32280 | /* | ||
32281 | * Retrun TRUE if the given ID represent a language construct [i.e: print, print..]. FALSE otherwise. | ||
32282 | * Note on reserved keywords. | ||
32283 | * According to the JX9 language reference manual: | ||
32284 | * These words have special meaning in JX9. Some of them represent things which look like | ||
32285 | * functions, some look like constants, and so on--but they're not, really: they are language | ||
32286 | * constructs. You cannot use any of the following words as constants, object names, function | ||
32287 | * or method names. Using them as variable names is generally OK, but could lead to confusion. | ||
32288 | */ | ||
32289 | JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID) | ||
32290 | { | ||
32291 | if( nKeyID == JX9_TKWRD_PRINT || nKeyID == JX9_TKWRD_EXIT || nKeyID == JX9_TKWRD_DIE | ||
32292 | || nKeyID == JX9_TKWRD_INCLUDE|| nKeyID == JX9_TKWRD_IMPORT ){ | ||
32293 | return TRUE; | ||
32294 | } | ||
32295 | /* Not a language construct */ | ||
32296 | return FALSE; | ||
32297 | } | ||
32298 | /* | ||
32299 | * Point to the next expression that should be evaluated shortly. | ||
32300 | * The cursor stops when it hit a comma ', ' or a semi-colon and the nesting | ||
32301 | * level is zero. | ||
32302 | */ | ||
32303 | JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart,SyToken *pEnd,SyToken **ppNext) | ||
32304 | { | ||
32305 | SyToken *pCur = pStart; | ||
32306 | sxi32 iNest = 0; | ||
32307 | if( pCur >= pEnd || (pCur->nType & JX9_TK_SEMI/*';'*/) ){ | ||
32308 | /* Last expression */ | ||
32309 | return SXERR_EOF; | ||
32310 | } | ||
32311 | while( pCur < pEnd ){ | ||
32312 | if( (pCur->nType & (JX9_TK_COMMA/*','*/|JX9_TK_SEMI/*';'*/)) && iNest <= 0){ | ||
32313 | break; | ||
32314 | } | ||
32315 | if( pCur->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OSB/*'['*/|JX9_TK_OCB/*'{'*/) ){ | ||
32316 | iNest++; | ||
32317 | }else if( pCur->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*']'*/|JX9_TK_CCB/*'}*/) ){ | ||
32318 | iNest--; | ||
32319 | } | ||
32320 | pCur++; | ||
32321 | } | ||
32322 | *ppNext = pCur; | ||
32323 | return SXRET_OK; | ||
32324 | } | ||
32325 | /* | ||
32326 | * Collect and assemble tokens holding annonymous functions/closure body. | ||
32327 | * When errors, JX9 take care of generating the appropriate error message. | ||
32328 | * Note on annonymous functions. | ||
32329 | * According to the JX9 language reference manual: | ||
32330 | * Anonymous functions, also known as closures, allow the creation of functions | ||
32331 | * which have no specified name. They are most useful as the value of callback | ||
32332 | * parameters, but they have many other uses. | ||
32333 | * Closures may also inherit variables from the parent scope. Any such variables | ||
32334 | * must be declared in the function header. Inheriting variables from the parent | ||
32335 | * scope is not the same as using global variables. Global variables exist in the global scope | ||
32336 | * which is the same no matter what function is executing. The parent scope of a closure is the | ||
32337 | * function in which the closure was declared (not necessarily the function it was called from). | ||
32338 | * | ||
32339 | * Some example: | ||
32340 | * $greet = function($name) | ||
32341 | * { | ||
32342 | * printf("Hello %s\r\n", $name); | ||
32343 | * }; | ||
32344 | * $greet('World'); | ||
32345 | * $greet('JX9'); | ||
32346 | * | ||
32347 | * $double = function($a) { | ||
32348 | * return $a * 2; | ||
32349 | * }; | ||
32350 | * // This is our range of numbers | ||
32351 | * $numbers = range(1, 5); | ||
32352 | * // Use the Annonymous function as a callback here to | ||
32353 | * // double the size of each element in our | ||
32354 | * // range | ||
32355 | * $new_numbers = array_map($double, $numbers); | ||
32356 | * print implode(' ', $new_numbers); | ||
32357 | */ | ||
32358 | static sxi32 ExprAssembleAnnon(jx9_gen_state *pGen,SyToken **ppCur, SyToken *pEnd) | ||
32359 | { | ||
32360 | SyToken *pIn = *ppCur; | ||
32361 | sxu32 nLine; | ||
32362 | sxi32 rc; | ||
32363 | /* Jump the 'function' keyword */ | ||
32364 | nLine = pIn->nLine; | ||
32365 | pIn++; | ||
32366 | if( pIn < pEnd && (pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) ){ | ||
32367 | pIn++; | ||
32368 | } | ||
32369 | if( pIn >= pEnd || (pIn->nType & JX9_TK_LPAREN) == 0 ){ | ||
32370 | /* Syntax error */ | ||
32371 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing opening parenthesis '(' while declaring annonymous function"); | ||
32372 | if( rc != SXERR_ABORT ){ | ||
32373 | rc = SXERR_SYNTAX; | ||
32374 | } | ||
32375 | goto Synchronize; | ||
32376 | } | ||
32377 | pIn++; /* Jump the leading parenthesis '(' */ | ||
32378 | jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_LPAREN/*'('*/, JX9_TK_RPAREN/*')'*/, &pIn); | ||
32379 | if( pIn >= pEnd || &pIn[1] >= pEnd ){ | ||
32380 | /* Syntax error */ | ||
32381 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function"); | ||
32382 | if( rc != SXERR_ABORT ){ | ||
32383 | rc = SXERR_SYNTAX; | ||
32384 | } | ||
32385 | goto Synchronize; | ||
32386 | } | ||
32387 | pIn++; /* Jump the trailing parenthesis */ | ||
32388 | if( pIn->nType & JX9_TK_OCB /*'{'*/ ){ | ||
32389 | pIn++; /* Jump the leading curly '{' */ | ||
32390 | jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_OCB/*'{'*/, JX9_TK_CCB/*'}'*/, &pIn); | ||
32391 | if( pIn < pEnd ){ | ||
32392 | pIn++; | ||
32393 | } | ||
32394 | }else{ | ||
32395 | /* Syntax error */ | ||
32396 | rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function, missing '{'"); | ||
32397 | if( rc == SXERR_ABORT ){ | ||
32398 | return SXERR_ABORT; | ||
32399 | } | ||
32400 | } | ||
32401 | rc = SXRET_OK; | ||
32402 | Synchronize: | ||
32403 | /* Synchronize pointers */ | ||
32404 | *ppCur = pIn; | ||
32405 | return rc; | ||
32406 | } | ||
32407 | /* | ||
32408 | * Make sure we are dealing with a valid expression tree. | ||
32409 | * This function check for balanced parenthesis, braces, brackets and so on. | ||
32410 | * When errors, JX9 take care of generating the appropriate error message. | ||
32411 | * Return SXRET_OK on success. Any other return value indicates syntax error. | ||
32412 | */ | ||
32413 | static sxi32 ExprVerifyNodes(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nNode) | ||
32414 | { | ||
32415 | sxi32 iParen, iSquare, iBraces; | ||
32416 | sxi32 i, rc; | ||
32417 | |||
32418 | if( nNode > 0 && apNode[0]->pOp && (apNode[0]->pOp->iOp == EXPR_OP_ADD || apNode[0]->pOp->iOp == EXPR_OP_SUB) ){ | ||
32419 | /* Fix and mark as an unary not binary plus/minus operator */ | ||
32420 | apNode[0]->pOp = jx9ExprExtractOperator(&apNode[0]->pStart->sData, 0); | ||
32421 | apNode[0]->pStart->pUserData = (void *)apNode[0]->pOp; | ||
32422 | } | ||
32423 | iParen = iSquare = iBraces = 0; | ||
32424 | for( i = 0 ; i < nNode ; ++i ){ | ||
32425 | if( apNode[i]->pStart->nType & JX9_TK_LPAREN /*'('*/){ | ||
32426 | if( i > 0 && ( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral || | ||
32427 | (apNode[i - 1]->pStart->nType & (JX9_TK_ID|JX9_TK_KEYWORD|JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*]*/))) ){ | ||
32428 | /* Ticket 1433-033: Take care to ignore alpha-stream [i.e: or, xor] operators followed by an opening parenthesis */ | ||
32429 | if( (apNode[i - 1]->pStart->nType & JX9_TK_OP) == 0 ){ | ||
32430 | /* We are dealing with a postfix [i.e: function call] operator | ||
32431 | * not a simple left parenthesis. Mark the node. | ||
32432 | */ | ||
32433 | apNode[i]->pStart->nType |= JX9_TK_OP; | ||
32434 | apNode[i]->pStart->pUserData = (void *)&sFCallOp; /* Function call operator */ | ||
32435 | apNode[i]->pOp = &sFCallOp; | ||
32436 | } | ||
32437 | } | ||
32438 | iParen++; | ||
32439 | }else if( apNode[i]->pStart->nType & JX9_TK_RPAREN/*')*/){ | ||
32440 | if( iParen <= 0 ){ | ||
32441 | rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ')'"); | ||
32442 | if( rc != SXERR_ABORT ){ | ||
32443 | rc = SXERR_SYNTAX; | ||
32444 | } | ||
32445 | return rc; | ||
32446 | } | ||
32447 | iParen--; | ||
32448 | }else if( apNode[i]->pStart->nType & JX9_TK_OSB /*'['*/ && apNode[i]->xCode == 0 ){ | ||
32449 | iSquare++; | ||
32450 | }else if (apNode[i]->pStart->nType & JX9_TK_CSB /*']'*/){ | ||
32451 | if( iSquare <= 0 ){ | ||
32452 | rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ']'"); | ||
32453 | if( rc != SXERR_ABORT ){ | ||
32454 | rc = SXERR_SYNTAX; | ||
32455 | } | ||
32456 | return rc; | ||
32457 | } | ||
32458 | iSquare--; | ||
32459 | }else if( apNode[i]->pStart->nType & JX9_TK_OCB /*'{'*/ && apNode[i]->xCode == 0 ){ | ||
32460 | iBraces++; | ||
32461 | }else if (apNode[i]->pStart->nType & JX9_TK_CCB /*'}'*/){ | ||
32462 | if( iBraces <= 0 ){ | ||
32463 | rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token '}'"); | ||
32464 | if( rc != SXERR_ABORT ){ | ||
32465 | rc = SXERR_SYNTAX; | ||
32466 | } | ||
32467 | return rc; | ||
32468 | } | ||
32469 | iBraces--; | ||
32470 | }else if( apNode[i]->pStart->nType & JX9_TK_OP ){ | ||
32471 | const jx9_expr_op *pOp = (const jx9_expr_op *)apNode[i]->pOp; | ||
32472 | if( i > 0 && (pOp->iOp == EXPR_OP_UMINUS || pOp->iOp == EXPR_OP_UPLUS)){ | ||
32473 | if( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral ){ | ||
32474 | sxi32 iExprOp = EXPR_OP_SUB; /* Binary minus */ | ||
32475 | sxu32 n = 0; | ||
32476 | if( pOp->iOp == EXPR_OP_UPLUS ){ | ||
32477 | iExprOp = EXPR_OP_ADD; /* Binary plus */ | ||
32478 | } | ||
32479 | /* | ||
32480 | * TICKET 1433-013: This is a fix around an obscure bug when the user uses | ||
32481 | * a variable name which is an alpha-stream operator [i.e: $and, $xor, $eq..]. | ||
32482 | */ | ||
32483 | while( n < SX_ARRAYSIZE(aOpTable) && aOpTable[n].iOp != iExprOp ){ | ||
32484 | ++n; | ||
32485 | } | ||
32486 | pOp = &aOpTable[n]; | ||
32487 | /* Mark as binary '+' or '-', not an unary */ | ||
32488 | apNode[i]->pOp = pOp; | ||
32489 | apNode[i]->pStart->pUserData = (void *)pOp; | ||
32490 | } | ||
32491 | } | ||
32492 | } | ||
32493 | } | ||
32494 | if( iParen != 0 || iSquare != 0 || iBraces != 0){ | ||
32495 | rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[0]->pStart->nLine, "Syntax error, mismatched '(', '[' or '{'"); | ||
32496 | if( rc != SXERR_ABORT ){ | ||
32497 | rc = SXERR_SYNTAX; | ||
32498 | } | ||
32499 | return rc; | ||
32500 | } | ||
32501 | return SXRET_OK; | ||
32502 | } | ||
32503 | /* | ||
32504 | * Extract a single expression node from the input. | ||
32505 | * On success store the freshly extractd node in ppNode. | ||
32506 | * When errors, JX9 take care of generating the appropriate error message. | ||
32507 | * An expression node can be a variable [i.e: $var], an operator [i.e: ++] | ||
32508 | * an annonymous function [i.e: function(){ return "Hello"; }, a double/single | ||
32509 | * quoted string, a heredoc/nowdoc, a literal [i.e: JX9_EOL], a namespace path | ||
32510 | * [i.e: namespaces\path\to..], a array/list [i.e: array(4, 5, 6)] and so on. | ||
32511 | */ | ||
32512 | static sxi32 ExprExtractNode(jx9_gen_state *pGen, jx9_expr_node **ppNode) | ||
32513 | { | ||
32514 | jx9_expr_node *pNode; | ||
32515 | SyToken *pCur; | ||
32516 | sxi32 rc; | ||
32517 | /* Allocate a new node */ | ||
32518 | pNode = (jx9_expr_node *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_expr_node)); | ||
32519 | if( pNode == 0 ){ | ||
32520 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
32521 | * a tiny chunk of memory, there is no much we can do here. | ||
32522 | */ | ||
32523 | return SXERR_MEM; | ||
32524 | } | ||
32525 | /* Zero the structure */ | ||
32526 | SyZero(pNode, sizeof(jx9_expr_node)); | ||
32527 | SySetInit(&pNode->aNodeArgs, &pGen->pVm->sAllocator, sizeof(jx9_expr_node **)); | ||
32528 | /* Point to the head of the token stream */ | ||
32529 | pCur = pNode->pStart = pGen->pIn; | ||
32530 | /* Start collecting tokens */ | ||
32531 | if( pCur->nType & JX9_TK_OP ){ | ||
32532 | /* Point to the instance that describe this operator */ | ||
32533 | pNode->pOp = (const jx9_expr_op *)pCur->pUserData; | ||
32534 | /* Advance the stream cursor */ | ||
32535 | pCur++; | ||
32536 | }else if( pCur->nType & JX9_TK_DOLLAR ){ | ||
32537 | /* Isolate variable */ | ||
32538 | pCur++; /* Jump the dollar sign */ | ||
32539 | if( pCur >= pGen->pEnd ){ | ||
32540 | /* Syntax error */ | ||
32541 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"Invalid variable name"); | ||
32542 | if( rc != SXERR_ABORT ){ | ||
32543 | rc = SXERR_SYNTAX; | ||
32544 | } | ||
32545 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); | ||
32546 | return rc; | ||
32547 | } | ||
32548 | pCur++; /* Jump the variable name */ | ||
32549 | pNode->xCode = jx9CompileVariable; | ||
32550 | }else if( pCur->nType & JX9_TK_OCB /* '{' */ ){ | ||
32551 | /* JSON Object, assemble tokens */ | ||
32552 | pCur++; | ||
32553 | jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OCB /* '[' */, JX9_TK_CCB /* ']' */, &pCur); | ||
32554 | if( pCur < pGen->pEnd ){ | ||
32555 | pCur++; | ||
32556 | }else{ | ||
32557 | /* Syntax error */ | ||
32558 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Object: Missing closing braces '}'"); | ||
32559 | if( rc != SXERR_ABORT ){ | ||
32560 | rc = SXERR_SYNTAX; | ||
32561 | } | ||
32562 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); | ||
32563 | return rc; | ||
32564 | } | ||
32565 | pNode->xCode = jx9CompileJsonObject; | ||
32566 | }else if( pCur->nType & JX9_TK_OSB /* '[' */ && !(pCur->nType & JX9_TK_OP) ){ | ||
32567 | /* JSON Array, assemble tokens */ | ||
32568 | pCur++; | ||
32569 | jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OSB /* '[' */, JX9_TK_CSB /* ']' */, &pCur); | ||
32570 | if( pCur < pGen->pEnd ){ | ||
32571 | pCur++; | ||
32572 | }else{ | ||
32573 | /* Syntax error */ | ||
32574 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Array: Missing closing square bracket ']'"); | ||
32575 | if( rc != SXERR_ABORT ){ | ||
32576 | rc = SXERR_SYNTAX; | ||
32577 | } | ||
32578 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); | ||
32579 | return rc; | ||
32580 | } | ||
32581 | pNode->xCode = jx9CompileJsonArray; | ||
32582 | }else if( pCur->nType & JX9_TK_KEYWORD ){ | ||
32583 | int nKeyword = SX_PTR_TO_INT(pCur->pUserData); | ||
32584 | if( nKeyword == JX9_TKWRD_FUNCTION ){ | ||
32585 | /* Annonymous function */ | ||
32586 | if( &pCur[1] >= pGen->pEnd ){ | ||
32587 | /* Assume a literal */ | ||
32588 | pCur++; | ||
32589 | pNode->xCode = jx9CompileLiteral; | ||
32590 | }else{ | ||
32591 | /* Assemble annonymous functions body */ | ||
32592 | rc = ExprAssembleAnnon(&(*pGen), &pCur, pGen->pEnd); | ||
32593 | if( rc != SXRET_OK ){ | ||
32594 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); | ||
32595 | return rc; | ||
32596 | } | ||
32597 | pNode->xCode = jx9CompileAnnonFunc; | ||
32598 | } | ||
32599 | }else if( jx9IsLangConstruct(nKeyword) && &pCur[1] < pGen->pEnd ){ | ||
32600 | /* Language constructs [i.e: print,die...] require special handling */ | ||
32601 | jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_LPAREN|JX9_TK_OCB|JX9_TK_OSB, JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB, &pCur); | ||
32602 | pNode->xCode = jx9CompileLangConstruct; | ||
32603 | }else{ | ||
32604 | /* Assume a literal */ | ||
32605 | pCur++; | ||
32606 | pNode->xCode = jx9CompileLiteral; | ||
32607 | } | ||
32608 | }else if( pCur->nType & (JX9_TK_ID) ){ | ||
32609 | /* Constants, function name, namespace path, object name... */ | ||
32610 | pCur++; | ||
32611 | pNode->xCode = jx9CompileLiteral; | ||
32612 | }else{ | ||
32613 | 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 ){ | ||
32614 | /* Point to the code generator routine */ | ||
32615 | pNode->xCode = jx9GetNodeHandler(pCur->nType); | ||
32616 | if( pNode->xCode == 0 ){ | ||
32617 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Syntax error: Unexpected token '%z'", &pNode->pStart->sData); | ||
32618 | if( rc != SXERR_ABORT ){ | ||
32619 | rc = SXERR_SYNTAX; | ||
32620 | } | ||
32621 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); | ||
32622 | return rc; | ||
32623 | } | ||
32624 | } | ||
32625 | /* Advance the stream cursor */ | ||
32626 | pCur++; | ||
32627 | } | ||
32628 | /* Point to the end of the token stream */ | ||
32629 | pNode->pEnd = pCur; | ||
32630 | /* Save the node for later processing */ | ||
32631 | *ppNode = pNode; | ||
32632 | /* Synchronize cursors */ | ||
32633 | pGen->pIn = pCur; | ||
32634 | return SXRET_OK; | ||
32635 | } | ||
32636 | /* | ||
32637 | * Free an expression tree. | ||
32638 | */ | ||
32639 | static void ExprFreeTree(jx9_gen_state *pGen, jx9_expr_node *pNode) | ||
32640 | { | ||
32641 | if( pNode->pLeft ){ | ||
32642 | /* Release the left tree */ | ||
32643 | ExprFreeTree(&(*pGen), pNode->pLeft); | ||
32644 | } | ||
32645 | if( pNode->pRight ){ | ||
32646 | /* Release the right tree */ | ||
32647 | ExprFreeTree(&(*pGen), pNode->pRight); | ||
32648 | } | ||
32649 | if( pNode->pCond ){ | ||
32650 | /* Release the conditional tree used by the ternary operator */ | ||
32651 | ExprFreeTree(&(*pGen), pNode->pCond); | ||
32652 | } | ||
32653 | if( SySetUsed(&pNode->aNodeArgs) > 0 ){ | ||
32654 | jx9_expr_node **apArg; | ||
32655 | sxu32 n; | ||
32656 | /* Release node arguments */ | ||
32657 | apArg = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); | ||
32658 | for( n = 0 ; n < SySetUsed(&pNode->aNodeArgs) ; ++n ){ | ||
32659 | ExprFreeTree(&(*pGen), apArg[n]); | ||
32660 | } | ||
32661 | SySetRelease(&pNode->aNodeArgs); | ||
32662 | } | ||
32663 | /* Finally, release this node */ | ||
32664 | SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); | ||
32665 | } | ||
32666 | /* | ||
32667 | * Free an expression tree. | ||
32668 | * This function is a wrapper around ExprFreeTree() defined above. | ||
32669 | */ | ||
32670 | JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet) | ||
32671 | { | ||
32672 | jx9_expr_node **apNode; | ||
32673 | sxu32 n; | ||
32674 | apNode = (jx9_expr_node **)SySetBasePtr(pNodeSet); | ||
32675 | for( n = 0 ; n < SySetUsed(pNodeSet) ; ++n ){ | ||
32676 | if( apNode[n] ){ | ||
32677 | ExprFreeTree(&(*pGen), apNode[n]); | ||
32678 | } | ||
32679 | } | ||
32680 | return SXRET_OK; | ||
32681 | } | ||
32682 | /* | ||
32683 | * Check if the given node is a modifialbe l/r-value. | ||
32684 | * Return TRUE if modifiable.FALSE otherwise. | ||
32685 | */ | ||
32686 | static int ExprIsModifiableValue(jx9_expr_node *pNode) | ||
32687 | { | ||
32688 | sxi32 iExprOp; | ||
32689 | if( pNode->pOp == 0 ){ | ||
32690 | return pNode->xCode == jx9CompileVariable ? TRUE : FALSE; | ||
32691 | } | ||
32692 | iExprOp = pNode->pOp->iOp; | ||
32693 | if( iExprOp == EXPR_OP_DOT /*'.' */ ){ | ||
32694 | return TRUE; | ||
32695 | } | ||
32696 | if( iExprOp == EXPR_OP_SUBSCRIPT/*'[]'*/ ){ | ||
32697 | if( pNode->pLeft->pOp ) { | ||
32698 | if( pNode->pLeft->pOp->iOp != EXPR_OP_SUBSCRIPT /*'['*/ && pNode->pLeft->pOp->iOp != EXPR_OP_DOT /*'.'*/){ | ||
32699 | return FALSE; | ||
32700 | } | ||
32701 | }else if( pNode->pLeft->xCode != jx9CompileVariable ){ | ||
32702 | return FALSE; | ||
32703 | } | ||
32704 | return TRUE; | ||
32705 | } | ||
32706 | /* Not a modifiable l or r-value */ | ||
32707 | return FALSE; | ||
32708 | } | ||
32709 | /* Forward declaration */ | ||
32710 | static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken); | ||
32711 | /* Macro to check if the given node is a terminal */ | ||
32712 | #define NODE_ISTERM(NODE) (apNode[NODE] && (!apNode[NODE]->pOp || apNode[NODE]->pLeft )) | ||
32713 | /* | ||
32714 | * Buid an expression tree for each given function argument. | ||
32715 | * When errors, JX9 take care of generating the appropriate error message. | ||
32716 | */ | ||
32717 | static sxi32 ExprProcessFuncArguments(jx9_gen_state *pGen, jx9_expr_node *pOp, jx9_expr_node **apNode, sxi32 nToken) | ||
32718 | { | ||
32719 | sxi32 iNest, iCur, iNode; | ||
32720 | sxi32 rc; | ||
32721 | /* Process function arguments from left to right */ | ||
32722 | iCur = 0; | ||
32723 | for(;;){ | ||
32724 | if( iCur >= nToken ){ | ||
32725 | /* No more arguments to process */ | ||
32726 | break; | ||
32727 | } | ||
32728 | iNode = iCur; | ||
32729 | iNest = 0; | ||
32730 | while( iCur < nToken ){ | ||
32731 | if( apNode[iCur] ){ | ||
32732 | if( (apNode[iCur]->pStart->nType & JX9_TK_COMMA) && apNode[iCur]->pLeft == 0 && iNest <= 0 ){ | ||
32733 | break; | ||
32734 | }else if( apNode[iCur]->pStart->nType & (JX9_TK_LPAREN|JX9_TK_OSB|JX9_TK_OCB) ){ | ||
32735 | iNest++; | ||
32736 | }else if( apNode[iCur]->pStart->nType & (JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB) ){ | ||
32737 | iNest--; | ||
32738 | } | ||
32739 | } | ||
32740 | iCur++; | ||
32741 | } | ||
32742 | if( iCur > iNode ){ | ||
32743 | ExprMakeTree(&(*pGen), &apNode[iNode], iCur-iNode); | ||
32744 | if( apNode[iNode] ){ | ||
32745 | /* Put a pointer to the root of the tree in the arguments set */ | ||
32746 | SySetPut(&pOp->aNodeArgs, (const void *)&apNode[iNode]); | ||
32747 | }else{ | ||
32748 | /* Empty function argument */ | ||
32749 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Empty function argument"); | ||
32750 | if( rc != SXERR_ABORT ){ | ||
32751 | rc = SXERR_SYNTAX; | ||
32752 | } | ||
32753 | return rc; | ||
32754 | } | ||
32755 | }else{ | ||
32756 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument"); | ||
32757 | if( rc != SXERR_ABORT ){ | ||
32758 | rc = SXERR_SYNTAX; | ||
32759 | } | ||
32760 | return rc; | ||
32761 | } | ||
32762 | /* Jump trailing comma */ | ||
32763 | if( iCur < nToken && apNode[iCur] && (apNode[iCur]->pStart->nType & JX9_TK_COMMA) ){ | ||
32764 | iCur++; | ||
32765 | if( iCur >= nToken ){ | ||
32766 | /* missing function argument */ | ||
32767 | rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument"); | ||
32768 | if( rc != SXERR_ABORT ){ | ||
32769 | rc = SXERR_SYNTAX; | ||
32770 | } | ||
32771 | return rc; | ||
32772 | } | ||
32773 | } | ||
32774 | } | ||
32775 | return SXRET_OK; | ||
32776 | } | ||
32777 | /* | ||
32778 | * Create an expression tree from an array of tokens. | ||
32779 | * If successful, the root of the tree is stored in apNode[0]. | ||
32780 | * When errors, JX9 take care of generating the appropriate error message. | ||
32781 | */ | ||
32782 | static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken) | ||
32783 | { | ||
32784 | sxi32 i, iLeft, iRight; | ||
32785 | jx9_expr_node *pNode; | ||
32786 | sxi32 iCur; | ||
32787 | sxi32 rc; | ||
32788 | if( nToken <= 0 || (nToken == 1 && apNode[0]->xCode) ){ | ||
32789 | /* TICKET 1433-17: self evaluating node */ | ||
32790 | return SXRET_OK; | ||
32791 | } | ||
32792 | /* Process expressions enclosed in parenthesis first */ | ||
32793 | for( iCur = 0 ; iCur < nToken ; ++iCur ){ | ||
32794 | sxi32 iNest; | ||
32795 | /* Note that, we use strict comparison here '!=' instead of the bitwise and '&' operator | ||
32796 | * since the LPAREN token can also be an operator [i.e: Function call]. | ||
32797 | */ | ||
32798 | if( apNode[iCur] == 0 || apNode[iCur]->pStart->nType != JX9_TK_LPAREN ){ | ||
32799 | continue; | ||
32800 | } | ||
32801 | iNest = 1; | ||
32802 | iLeft = iCur; | ||
32803 | /* Find the closing parenthesis */ | ||
32804 | iCur++; | ||
32805 | while( iCur < nToken ){ | ||
32806 | if( apNode[iCur] ){ | ||
32807 | if( apNode[iCur]->pStart->nType & JX9_TK_RPAREN /* ')' */){ | ||
32808 | /* Decrement nesting level */ | ||
32809 | iNest--; | ||
32810 | if( iNest <= 0 ){ | ||
32811 | break; | ||
32812 | } | ||
32813 | }else if( apNode[iCur]->pStart->nType & JX9_TK_LPAREN /* '(' */ ){ | ||
32814 | /* Increment nesting level */ | ||
32815 | iNest++; | ||
32816 | } | ||
32817 | } | ||
32818 | iCur++; | ||
32819 | } | ||
32820 | if( iCur - iLeft > 1 ){ | ||
32821 | /* Recurse and process this expression */ | ||
32822 | rc = ExprMakeTree(&(*pGen), &apNode[iLeft + 1], iCur - iLeft - 1); | ||
32823 | if( rc != SXRET_OK ){ | ||
32824 | return rc; | ||
32825 | } | ||
32826 | } | ||
32827 | /* Free the left and right nodes */ | ||
32828 | ExprFreeTree(&(*pGen), apNode[iLeft]); | ||
32829 | ExprFreeTree(&(*pGen), apNode[iCur]); | ||
32830 | apNode[iLeft] = 0; | ||
32831 | apNode[iCur] = 0; | ||
32832 | } | ||
32833 | /* Handle postfix [i.e: function call, member access] operators with precedence 2 */ | ||
32834 | iLeft = -1; | ||
32835 | for( iCur = 0 ; iCur < nToken ; ++iCur ){ | ||
32836 | if( apNode[iCur] == 0 ){ | ||
32837 | continue; | ||
32838 | } | ||
32839 | pNode = apNode[iCur]; | ||
32840 | if( pNode->pOp && pNode->pOp->iPrec == 2 && pNode->pLeft == 0 ){ | ||
32841 | if( pNode->pOp->iOp == EXPR_OP_FUNC_CALL ){ | ||
32842 | /* Collect function arguments */ | ||
32843 | sxi32 iPtr = 0; | ||
32844 | sxi32 nFuncTok = 0; | ||
32845 | while( nFuncTok + iCur < nToken ){ | ||
32846 | if( apNode[nFuncTok+iCur] ){ | ||
32847 | if( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_LPAREN /*'('*/ ){ | ||
32848 | iPtr++; | ||
32849 | }else if ( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_RPAREN /*')'*/){ | ||
32850 | iPtr--; | ||
32851 | if( iPtr <= 0 ){ | ||
32852 | break; | ||
32853 | } | ||
32854 | } | ||
32855 | } | ||
32856 | nFuncTok++; | ||
32857 | } | ||
32858 | if( nFuncTok + iCur >= nToken ){ | ||
32859 | /* Syntax error */ | ||
32860 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Missing right parenthesis ')'"); | ||
32861 | if( rc != SXERR_ABORT ){ | ||
32862 | rc = SXERR_SYNTAX; | ||
32863 | } | ||
32864 | return rc; | ||
32865 | } | ||
32866 | if( iLeft < 0 || !NODE_ISTERM(iLeft) /*|| ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2)*/ ){ | ||
32867 | /* Syntax error */ | ||
32868 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Invalid function name"); | ||
32869 | if( rc != SXERR_ABORT ){ | ||
32870 | rc = SXERR_SYNTAX; | ||
32871 | } | ||
32872 | return rc; | ||
32873 | } | ||
32874 | if( nFuncTok > 1 ){ | ||
32875 | /* Process function arguments */ | ||
32876 | rc = ExprProcessFuncArguments(&(*pGen), pNode, &apNode[iCur+1], nFuncTok-1); | ||
32877 | if( rc != SXRET_OK ){ | ||
32878 | return rc; | ||
32879 | } | ||
32880 | } | ||
32881 | /* Link the node to the tree */ | ||
32882 | pNode->pLeft = apNode[iLeft]; | ||
32883 | apNode[iLeft] = 0; | ||
32884 | for( iPtr = 1; iPtr <= nFuncTok ; iPtr++ ){ | ||
32885 | apNode[iCur+iPtr] = 0; | ||
32886 | } | ||
32887 | }else if (pNode->pOp->iOp == EXPR_OP_SUBSCRIPT ){ | ||
32888 | /* Subscripting */ | ||
32889 | sxi32 iArrTok = iCur + 1; | ||
32890 | sxi32 iNest = 1; | ||
32891 | if( iLeft >= 0 && (apNode[iLeft]->xCode == jx9CompileVariable || (apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* postfix */) ) ){ | ||
32892 | /* Collect index tokens */ | ||
32893 | while( iArrTok < nToken ){ | ||
32894 | if( apNode[iArrTok] ){ | ||
32895 | if( apNode[iArrTok]->pStart->nType & JX9_TK_OSB /*'['*/){ | ||
32896 | /* Increment nesting level */ | ||
32897 | iNest++; | ||
32898 | }else if( apNode[iArrTok]->pStart->nType & JX9_TK_CSB /*']'*/){ | ||
32899 | /* Decrement nesting level */ | ||
32900 | iNest--; | ||
32901 | if( iNest <= 0 ){ | ||
32902 | break; | ||
32903 | } | ||
32904 | } | ||
32905 | } | ||
32906 | ++iArrTok; | ||
32907 | } | ||
32908 | if( iArrTok > iCur + 1 ){ | ||
32909 | /* Recurse and process this expression */ | ||
32910 | rc = ExprMakeTree(&(*pGen), &apNode[iCur+1], iArrTok - iCur - 1); | ||
32911 | if( rc != SXRET_OK ){ | ||
32912 | return rc; | ||
32913 | } | ||
32914 | /* Link the node to it's index */ | ||
32915 | SySetPut(&pNode->aNodeArgs, (const void *)&apNode[iCur+1]); | ||
32916 | } | ||
32917 | /* Link the node to the tree */ | ||
32918 | pNode->pLeft = apNode[iLeft]; | ||
32919 | pNode->pRight = 0; | ||
32920 | apNode[iLeft] = 0; | ||
32921 | for( iNest = iCur + 1 ; iNest <= iArrTok ; ++iNest ){ | ||
32922 | apNode[iNest] = 0; | ||
32923 | } | ||
32924 | } | ||
32925 | }else{ | ||
32926 | /* Member access operators [i.e: '.' ] */ | ||
32927 | iRight = iCur + 1; | ||
32928 | while( iRight < nToken && apNode[iRight] == 0 ){ | ||
32929 | iRight++; | ||
32930 | } | ||
32931 | if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ | ||
32932 | /* Syntax error */ | ||
32933 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid member name", &pNode->pOp->sOp); | ||
32934 | if( rc != SXERR_ABORT ){ | ||
32935 | rc = SXERR_SYNTAX; | ||
32936 | } | ||
32937 | return rc; | ||
32938 | } | ||
32939 | /* Link the node to the tree */ | ||
32940 | pNode->pLeft = apNode[iLeft]; | ||
32941 | if( pNode->pLeft->pOp == 0 && pNode->pLeft->xCode != jx9CompileVariable ){ | ||
32942 | /* Syntax error */ | ||
32943 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, | ||
32944 | "'%z': Expecting a variable as left operand", &pNode->pOp->sOp); | ||
32945 | if( rc != SXERR_ABORT ){ | ||
32946 | rc = SXERR_SYNTAX; | ||
32947 | } | ||
32948 | return rc; | ||
32949 | } | ||
32950 | pNode->pRight = apNode[iRight]; | ||
32951 | apNode[iLeft] = apNode[iRight] = 0; | ||
32952 | } | ||
32953 | } | ||
32954 | iLeft = iCur; | ||
32955 | } | ||
32956 | /* Handle post/pre icrement/decrement [i.e: ++/--] operators with precedence 3 */ | ||
32957 | iLeft = -1; | ||
32958 | for( iCur = 0 ; iCur < nToken ; ++iCur ){ | ||
32959 | if( apNode[iCur] == 0 ){ | ||
32960 | continue; | ||
32961 | } | ||
32962 | pNode = apNode[iCur]; | ||
32963 | if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ | ||
32964 | if( iLeft >= 0 && ((apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* Postfix */) | ||
32965 | || apNode[iLeft]->xCode == jx9CompileVariable) ){ | ||
32966 | /* Link the node to the tree */ | ||
32967 | pNode->pLeft = apNode[iLeft]; | ||
32968 | apNode[iLeft] = 0; | ||
32969 | } | ||
32970 | } | ||
32971 | iLeft = iCur; | ||
32972 | } | ||
32973 | iLeft = -1; | ||
32974 | for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ | ||
32975 | if( apNode[iCur] == 0 ){ | ||
32976 | continue; | ||
32977 | } | ||
32978 | pNode = apNode[iCur]; | ||
32979 | if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ | ||
32980 | if( iLeft < 0 || (apNode[iLeft]->pOp == 0 && apNode[iLeft]->xCode != jx9CompileVariable) | ||
32981 | || ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2 /* Postfix */) ){ | ||
32982 | /* Syntax error */ | ||
32983 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z' operator needs l-value", &pNode->pOp->sOp); | ||
32984 | if( rc != SXERR_ABORT ){ | ||
32985 | rc = SXERR_SYNTAX; | ||
32986 | } | ||
32987 | return rc; | ||
32988 | } | ||
32989 | /* Link the node to the tree */ | ||
32990 | pNode->pLeft = apNode[iLeft]; | ||
32991 | apNode[iLeft] = 0; | ||
32992 | /* Mark as pre-increment/decrement node */ | ||
32993 | pNode->iFlags |= EXPR_NODE_PRE_INCR; | ||
32994 | } | ||
32995 | iLeft = iCur; | ||
32996 | } | ||
32997 | /* Handle right associative unary and cast operators [i.e: !, (string), ~...] with precedence 4 */ | ||
32998 | iLeft = 0; | ||
32999 | for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ | ||
33000 | if( apNode[iCur] ){ | ||
33001 | pNode = apNode[iCur]; | ||
33002 | if( pNode->pOp && pNode->pOp->iPrec == 4 && pNode->pLeft == 0){ | ||
33003 | if( iLeft > 0 ){ | ||
33004 | /* Link the node to the tree */ | ||
33005 | pNode->pLeft = apNode[iLeft]; | ||
33006 | apNode[iLeft] = 0; | ||
33007 | if( pNode->pLeft && pNode->pLeft->pOp && pNode->pLeft->pOp->iPrec > 4 ){ | ||
33008 | if( pNode->pLeft->pLeft == 0 || pNode->pLeft->pRight == 0 ){ | ||
33009 | /* Syntax error */ | ||
33010 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pLeft->pStart->nLine, "'%z': Missing operand", &pNode->pLeft->pOp->sOp); | ||
33011 | if( rc != SXERR_ABORT ){ | ||
33012 | rc = SXERR_SYNTAX; | ||
33013 | } | ||
33014 | return rc; | ||
33015 | } | ||
33016 | } | ||
33017 | }else{ | ||
33018 | /* Syntax error */ | ||
33019 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing operand", &pNode->pOp->sOp); | ||
33020 | if( rc != SXERR_ABORT ){ | ||
33021 | rc = SXERR_SYNTAX; | ||
33022 | } | ||
33023 | return rc; | ||
33024 | } | ||
33025 | } | ||
33026 | /* Save terminal position */ | ||
33027 | iLeft = iCur; | ||
33028 | } | ||
33029 | } | ||
33030 | /* Process left and non-associative binary operators [i.e: *, /, &&, ||...]*/ | ||
33031 | for( i = 7 ; i < 17 ; i++ ){ | ||
33032 | iLeft = -1; | ||
33033 | for( iCur = 0 ; iCur < nToken ; ++iCur ){ | ||
33034 | if( apNode[iCur] == 0 ){ | ||
33035 | continue; | ||
33036 | } | ||
33037 | pNode = apNode[iCur]; | ||
33038 | if( pNode->pOp && pNode->pOp->iPrec == i && pNode->pLeft == 0 ){ | ||
33039 | /* Get the right node */ | ||
33040 | iRight = iCur + 1; | ||
33041 | while( iRight < nToken && apNode[iRight] == 0 ){ | ||
33042 | iRight++; | ||
33043 | } | ||
33044 | if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ | ||
33045 | /* Syntax error */ | ||
33046 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); | ||
33047 | if( rc != SXERR_ABORT ){ | ||
33048 | rc = SXERR_SYNTAX; | ||
33049 | } | ||
33050 | return rc; | ||
33051 | } | ||
33052 | /* Link the node to the tree */ | ||
33053 | pNode->pLeft = apNode[iLeft]; | ||
33054 | pNode->pRight = apNode[iRight]; | ||
33055 | apNode[iLeft] = apNode[iRight] = 0; | ||
33056 | } | ||
33057 | iLeft = iCur; | ||
33058 | } | ||
33059 | } | ||
33060 | /* Handle the ternary operator. (expr1) ? (expr2) : (expr3) | ||
33061 | * Note that we do not need a precedence loop here since | ||
33062 | * we are dealing with a single operator. | ||
33063 | */ | ||
33064 | iLeft = -1; | ||
33065 | for( iCur = 0 ; iCur < nToken ; ++iCur ){ | ||
33066 | if( apNode[iCur] == 0 ){ | ||
33067 | continue; | ||
33068 | } | ||
33069 | pNode = apNode[iCur]; | ||
33070 | if( pNode->pOp && pNode->pOp->iOp == EXPR_OP_QUESTY && pNode->pLeft == 0 ){ | ||
33071 | sxi32 iNest = 1; | ||
33072 | if( iLeft < 0 || !NODE_ISTERM(iLeft) ){ | ||
33073 | /* Missing condition */ | ||
33074 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Syntax error", &pNode->pOp->sOp); | ||
33075 | if( rc != SXERR_ABORT ){ | ||
33076 | rc = SXERR_SYNTAX; | ||
33077 | } | ||
33078 | return rc; | ||
33079 | } | ||
33080 | /* Get the right node */ | ||
33081 | iRight = iCur + 1; | ||
33082 | while( iRight < nToken ){ | ||
33083 | if( apNode[iRight] ){ | ||
33084 | if( apNode[iRight]->pOp && apNode[iRight]->pOp->iOp == EXPR_OP_QUESTY && apNode[iRight]->pCond == 0){ | ||
33085 | /* Increment nesting level */ | ||
33086 | ++iNest; | ||
33087 | }else if( apNode[iRight]->pStart->nType & JX9_TK_COLON /*:*/ ){ | ||
33088 | /* Decrement nesting level */ | ||
33089 | --iNest; | ||
33090 | if( iNest <= 0 ){ | ||
33091 | break; | ||
33092 | } | ||
33093 | } | ||
33094 | } | ||
33095 | iRight++; | ||
33096 | } | ||
33097 | if( iRight > iCur + 1 ){ | ||
33098 | /* Recurse and process the then expression */ | ||
33099 | rc = ExprMakeTree(&(*pGen), &apNode[iCur + 1], iRight - iCur - 1); | ||
33100 | if( rc != SXRET_OK ){ | ||
33101 | return rc; | ||
33102 | } | ||
33103 | /* Link the node to the tree */ | ||
33104 | pNode->pLeft = apNode[iCur + 1]; | ||
33105 | }else{ | ||
33106 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'then' expression", &pNode->pOp->sOp); | ||
33107 | if( rc != SXERR_ABORT ){ | ||
33108 | rc = SXERR_SYNTAX; | ||
33109 | } | ||
33110 | return rc; | ||
33111 | } | ||
33112 | apNode[iCur + 1] = 0; | ||
33113 | if( iRight + 1 < nToken ){ | ||
33114 | /* Recurse and process the else expression */ | ||
33115 | rc = ExprMakeTree(&(*pGen), &apNode[iRight + 1], nToken - iRight - 1); | ||
33116 | if( rc != SXRET_OK ){ | ||
33117 | return rc; | ||
33118 | } | ||
33119 | /* Link the node to the tree */ | ||
33120 | pNode->pRight = apNode[iRight + 1]; | ||
33121 | apNode[iRight + 1] = apNode[iRight] = 0; | ||
33122 | }else{ | ||
33123 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'else' expression", &pNode->pOp->sOp); | ||
33124 | if( rc != SXERR_ABORT ){ | ||
33125 | rc = SXERR_SYNTAX; | ||
33126 | } | ||
33127 | return rc; | ||
33128 | } | ||
33129 | /* Point to the condition */ | ||
33130 | pNode->pCond = apNode[iLeft]; | ||
33131 | apNode[iLeft] = 0; | ||
33132 | break; | ||
33133 | } | ||
33134 | iLeft = iCur; | ||
33135 | } | ||
33136 | /* Process right associative binary operators [i.e: '=', '+=', '/='] | ||
33137 | * Note: All right associative binary operators have precedence 18 | ||
33138 | * so there is no need for a precedence loop here. | ||
33139 | */ | ||
33140 | iRight = -1; | ||
33141 | for( iCur = nToken - 1 ; iCur >= 0 ; iCur--){ | ||
33142 | if( apNode[iCur] == 0 ){ | ||
33143 | continue; | ||
33144 | } | ||
33145 | pNode = apNode[iCur]; | ||
33146 | if( pNode->pOp && pNode->pOp->iPrec == 18 && pNode->pLeft == 0 ){ | ||
33147 | /* Get the left node */ | ||
33148 | iLeft = iCur - 1; | ||
33149 | while( iLeft >= 0 && apNode[iLeft] == 0 ){ | ||
33150 | iLeft--; | ||
33151 | } | ||
33152 | if( iLeft < 0 || iRight < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ | ||
33153 | /* Syntax error */ | ||
33154 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); | ||
33155 | if( rc != SXERR_ABORT ){ | ||
33156 | rc = SXERR_SYNTAX; | ||
33157 | } | ||
33158 | return rc; | ||
33159 | } | ||
33160 | if( ExprIsModifiableValue(apNode[iLeft]) == FALSE ){ | ||
33161 | if( pNode->pOp->iVmOp != JX9_OP_STORE ){ | ||
33162 | /* Left operand must be a modifiable l-value */ | ||
33163 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, | ||
33164 | "'%z': Left operand must be a modifiable l-value", &pNode->pOp->sOp); | ||
33165 | if( rc != SXERR_ABORT ){ | ||
33166 | rc = SXERR_SYNTAX; | ||
33167 | } | ||
33168 | return rc; | ||
33169 | } | ||
33170 | } | ||
33171 | /* Link the node to the tree (Reverse) */ | ||
33172 | pNode->pLeft = apNode[iRight]; | ||
33173 | pNode->pRight = apNode[iLeft]; | ||
33174 | apNode[iLeft] = apNode[iRight] = 0; | ||
33175 | } | ||
33176 | iRight = iCur; | ||
33177 | } | ||
33178 | /* Process the lowest precedence operator (22, comma) */ | ||
33179 | iLeft = -1; | ||
33180 | for( iCur = 0 ; iCur < nToken ; ++iCur ){ | ||
33181 | if( apNode[iCur] == 0 ){ | ||
33182 | continue; | ||
33183 | } | ||
33184 | pNode = apNode[iCur]; | ||
33185 | if( pNode->pOp && pNode->pOp->iPrec == 22 /* ',' */ && pNode->pLeft == 0 ){ | ||
33186 | /* Get the right node */ | ||
33187 | iRight = iCur + 1; | ||
33188 | while( iRight < nToken && apNode[iRight] == 0 ){ | ||
33189 | iRight++; | ||
33190 | } | ||
33191 | if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ | ||
33192 | /* Syntax error */ | ||
33193 | rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); | ||
33194 | if( rc != SXERR_ABORT ){ | ||
33195 | rc = SXERR_SYNTAX; | ||
33196 | } | ||
33197 | return rc; | ||
33198 | } | ||
33199 | /* Link the node to the tree */ | ||
33200 | pNode->pLeft = apNode[iLeft]; | ||
33201 | pNode->pRight = apNode[iRight]; | ||
33202 | apNode[iLeft] = apNode[iRight] = 0; | ||
33203 | } | ||
33204 | iLeft = iCur; | ||
33205 | } | ||
33206 | /* Point to the root of the expression tree */ | ||
33207 | for( iCur = 1 ; iCur < nToken ; ++iCur ){ | ||
33208 | if( apNode[iCur] ){ | ||
33209 | if( (apNode[iCur]->pOp || apNode[iCur]->xCode ) && apNode[0] != 0){ | ||
33210 | rc = jx9GenCompileError(pGen, E_ERROR, apNode[iCur]->pStart->nLine, "Unexpected token '%z'", &apNode[iCur]->pStart->sData); | ||
33211 | if( rc != SXERR_ABORT ){ | ||
33212 | rc = SXERR_SYNTAX; | ||
33213 | } | ||
33214 | return rc; | ||
33215 | } | ||
33216 | apNode[0] = apNode[iCur]; | ||
33217 | apNode[iCur] = 0; | ||
33218 | } | ||
33219 | } | ||
33220 | return SXRET_OK; | ||
33221 | } | ||
33222 | /* | ||
33223 | * Build an expression tree from the freshly extracted raw tokens. | ||
33224 | * If successful, the root of the tree is stored in ppRoot. | ||
33225 | * When errors, JX9 take care of generating the appropriate error message. | ||
33226 | * This is the public interface used by the most code generator routines. | ||
33227 | */ | ||
33228 | JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot) | ||
33229 | { | ||
33230 | jx9_expr_node **apNode; | ||
33231 | jx9_expr_node *pNode; | ||
33232 | sxi32 rc; | ||
33233 | /* Reset node container */ | ||
33234 | SySetReset(pExprNode); | ||
33235 | pNode = 0; /* Prevent compiler warning */ | ||
33236 | /* Extract nodes one after one until we hit the end of the input */ | ||
33237 | while( pGen->pIn < pGen->pEnd ){ | ||
33238 | rc = ExprExtractNode(&(*pGen), &pNode); | ||
33239 | if( rc != SXRET_OK ){ | ||
33240 | return rc; | ||
33241 | } | ||
33242 | /* Save the extracted node */ | ||
33243 | SySetPut(pExprNode, (const void *)&pNode); | ||
33244 | } | ||
33245 | if( SySetUsed(pExprNode) < 1 ){ | ||
33246 | /* Empty expression [i.e: A semi-colon;] */ | ||
33247 | *ppRoot = 0; | ||
33248 | return SXRET_OK; | ||
33249 | } | ||
33250 | apNode = (jx9_expr_node **)SySetBasePtr(pExprNode); | ||
33251 | /* Make sure we are dealing with valid nodes */ | ||
33252 | rc = ExprVerifyNodes(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode)); | ||
33253 | if( rc != SXRET_OK ){ | ||
33254 | /* Don't worry about freeing memory, upper layer will | ||
33255 | * cleanup the mess left behind. | ||
33256 | */ | ||
33257 | *ppRoot = 0; | ||
33258 | return rc; | ||
33259 | } | ||
33260 | /* Build the tree */ | ||
33261 | rc = ExprMakeTree(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode)); | ||
33262 | if( rc != SXRET_OK ){ | ||
33263 | /* Something goes wrong [i.e: Syntax error] */ | ||
33264 | *ppRoot = 0; | ||
33265 | return rc; | ||
33266 | } | ||
33267 | /* Point to the root of the tree */ | ||
33268 | *ppRoot = apNode[0]; | ||
33269 | return SXRET_OK; | ||
33270 | } | ||
33271 | /* | ||
33272 | * ---------------------------------------------------------- | ||
33273 | * File: jx9_vfs.c | ||
33274 | * MD5: 8b73046a366acaf6aa7227c2133e16c0 | ||
33275 | * ---------------------------------------------------------- | ||
33276 | */ | ||
33277 | /* | ||
33278 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
33279 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
33280 | * Version 1.7.2 | ||
33281 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
33282 | * please contact Symisc Systems via: | ||
33283 | * legal@symisc.net | ||
33284 | * licensing@symisc.net | ||
33285 | * contact@symisc.net | ||
33286 | * or visit: | ||
33287 | * http://jx9.symisc.net/ | ||
33288 | */ | ||
33289 | /* $SymiscID: vfs.c v2.1 Ubuntu 2012-12-13 00:013 stable <chm@symisc.net> $ */ | ||
33290 | #ifndef JX9_AMALGAMATION | ||
33291 | #include "jx9Int.h" | ||
33292 | #endif | ||
33293 | /* | ||
33294 | * This file implement a virtual file systems (VFS) for the JX9 engine. | ||
33295 | */ | ||
33296 | /* | ||
33297 | * Given a string containing the path of a file or directory, this function | ||
33298 | * return the parent directory's path. | ||
33299 | */ | ||
33300 | JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen) | ||
33301 | { | ||
33302 | const char *zEnd = &zPath[nByte - 1]; | ||
33303 | int c, d; | ||
33304 | c = d = '/'; | ||
33305 | #ifdef __WINNT__ | ||
33306 | d = '\\'; | ||
33307 | #endif | ||
33308 | while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ | ||
33309 | zEnd--; | ||
33310 | } | ||
33311 | *pLen = (int)(zEnd-zPath); | ||
33312 | #ifdef __WINNT__ | ||
33313 | if( (*pLen) == (int)sizeof(char) && zPath[0] == '/' ){ | ||
33314 | /* Normalize path on windows */ | ||
33315 | return "\\"; | ||
33316 | } | ||
33317 | #endif | ||
33318 | if( zEnd == zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d) ){ | ||
33319 | /* No separator, return "." as the current directory */ | ||
33320 | *pLen = sizeof(char); | ||
33321 | return "."; | ||
33322 | } | ||
33323 | if( (*pLen) == 0 ){ | ||
33324 | *pLen = sizeof(char); | ||
33325 | #ifdef __WINNT__ | ||
33326 | return "\\"; | ||
33327 | #else | ||
33328 | return "/"; | ||
33329 | #endif | ||
33330 | } | ||
33331 | return zPath; | ||
33332 | } | ||
33333 | /* | ||
33334 | * Omit the vfs layer implementation from the built if the JX9_DISABLE_BUILTIN_FUNC directive is defined. | ||
33335 | */ | ||
33336 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
33337 | /* | ||
33338 | * bool chdir(string $directory) | ||
33339 | * Change the current directory. | ||
33340 | * Parameters | ||
33341 | * $directory | ||
33342 | * The new current directory | ||
33343 | * Return | ||
33344 | * TRUE on success or FALSE on failure. | ||
33345 | */ | ||
33346 | static int jx9Vfs_chdir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33347 | { | ||
33348 | const char *zPath; | ||
33349 | jx9_vfs *pVfs; | ||
33350 | int rc; | ||
33351 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33352 | /* Missing/Invalid argument, return FALSE */ | ||
33353 | jx9_result_bool(pCtx, 0); | ||
33354 | return JX9_OK; | ||
33355 | } | ||
33356 | /* Point to the underlying vfs */ | ||
33357 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33358 | if( pVfs == 0 || pVfs->xChdir == 0 ){ | ||
33359 | /* IO routine not implemented, return NULL */ | ||
33360 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33361 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33362 | jx9_function_name(pCtx) | ||
33363 | ); | ||
33364 | jx9_result_bool(pCtx, 0); | ||
33365 | return JX9_OK; | ||
33366 | } | ||
33367 | /* Point to the desired directory */ | ||
33368 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33369 | /* Perform the requested operation */ | ||
33370 | rc = pVfs->xChdir(zPath); | ||
33371 | /* IO return value */ | ||
33372 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33373 | return JX9_OK; | ||
33374 | } | ||
33375 | /* | ||
33376 | * bool chroot(string $directory) | ||
33377 | * Change the root directory. | ||
33378 | * Parameters | ||
33379 | * $directory | ||
33380 | * The path to change the root directory to | ||
33381 | * Return | ||
33382 | * TRUE on success or FALSE on failure. | ||
33383 | */ | ||
33384 | static int jx9Vfs_chroot(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33385 | { | ||
33386 | const char *zPath; | ||
33387 | jx9_vfs *pVfs; | ||
33388 | int rc; | ||
33389 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33390 | /* Missing/Invalid argument, return FALSE */ | ||
33391 | jx9_result_bool(pCtx, 0); | ||
33392 | return JX9_OK; | ||
33393 | } | ||
33394 | /* Point to the underlying vfs */ | ||
33395 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33396 | if( pVfs == 0 || pVfs->xChroot == 0 ){ | ||
33397 | /* IO routine not implemented, return NULL */ | ||
33398 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33399 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33400 | jx9_function_name(pCtx) | ||
33401 | ); | ||
33402 | jx9_result_bool(pCtx, 0); | ||
33403 | return JX9_OK; | ||
33404 | } | ||
33405 | /* Point to the desired directory */ | ||
33406 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33407 | /* Perform the requested operation */ | ||
33408 | rc = pVfs->xChroot(zPath); | ||
33409 | /* IO return value */ | ||
33410 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33411 | return JX9_OK; | ||
33412 | } | ||
33413 | /* | ||
33414 | * string getcwd(void) | ||
33415 | * Gets the current working directory. | ||
33416 | * Parameters | ||
33417 | * None | ||
33418 | * Return | ||
33419 | * Returns the current working directory on success, or FALSE on failure. | ||
33420 | */ | ||
33421 | static int jx9Vfs_getcwd(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33422 | { | ||
33423 | jx9_vfs *pVfs; | ||
33424 | int rc; | ||
33425 | /* Point to the underlying vfs */ | ||
33426 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33427 | if( pVfs == 0 || pVfs->xGetcwd == 0 ){ | ||
33428 | SXUNUSED(nArg); /* cc warning */ | ||
33429 | SXUNUSED(apArg); | ||
33430 | /* IO routine not implemented, return NULL */ | ||
33431 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33432 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33433 | jx9_function_name(pCtx) | ||
33434 | ); | ||
33435 | jx9_result_bool(pCtx, 0); | ||
33436 | return JX9_OK; | ||
33437 | } | ||
33438 | jx9_result_string(pCtx, "", 0); | ||
33439 | /* Perform the requested operation */ | ||
33440 | rc = pVfs->xGetcwd(pCtx); | ||
33441 | if( rc != JX9_OK ){ | ||
33442 | /* Error, return FALSE */ | ||
33443 | jx9_result_bool(pCtx, 0); | ||
33444 | } | ||
33445 | return JX9_OK; | ||
33446 | } | ||
33447 | /* | ||
33448 | * bool rmdir(string $directory) | ||
33449 | * Removes directory. | ||
33450 | * Parameters | ||
33451 | * $directory | ||
33452 | * The path to the directory | ||
33453 | * Return | ||
33454 | * TRUE on success or FALSE on failure. | ||
33455 | */ | ||
33456 | static int jx9Vfs_rmdir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33457 | { | ||
33458 | const char *zPath; | ||
33459 | jx9_vfs *pVfs; | ||
33460 | int rc; | ||
33461 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33462 | /* Missing/Invalid argument, return FALSE */ | ||
33463 | jx9_result_bool(pCtx, 0); | ||
33464 | return JX9_OK; | ||
33465 | } | ||
33466 | /* Point to the underlying vfs */ | ||
33467 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33468 | if( pVfs == 0 || pVfs->xRmdir == 0 ){ | ||
33469 | /* IO routine not implemented, return NULL */ | ||
33470 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33471 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33472 | jx9_function_name(pCtx) | ||
33473 | ); | ||
33474 | jx9_result_bool(pCtx, 0); | ||
33475 | return JX9_OK; | ||
33476 | } | ||
33477 | /* Point to the desired directory */ | ||
33478 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33479 | /* Perform the requested operation */ | ||
33480 | rc = pVfs->xRmdir(zPath); | ||
33481 | /* IO return value */ | ||
33482 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33483 | return JX9_OK; | ||
33484 | } | ||
33485 | /* | ||
33486 | * bool is_dir(string $filename) | ||
33487 | * Tells whether the given filename is a directory. | ||
33488 | * Parameters | ||
33489 | * $filename | ||
33490 | * Path to the file. | ||
33491 | * Return | ||
33492 | * TRUE on success or FALSE on failure. | ||
33493 | */ | ||
33494 | static int jx9Vfs_is_dir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33495 | { | ||
33496 | const char *zPath; | ||
33497 | jx9_vfs *pVfs; | ||
33498 | int rc; | ||
33499 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33500 | /* Missing/Invalid argument, return FALSE */ | ||
33501 | jx9_result_bool(pCtx, 0); | ||
33502 | return JX9_OK; | ||
33503 | } | ||
33504 | /* Point to the underlying vfs */ | ||
33505 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33506 | if( pVfs == 0 || pVfs->xIsdir == 0 ){ | ||
33507 | /* IO routine not implemented, return NULL */ | ||
33508 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33509 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33510 | jx9_function_name(pCtx) | ||
33511 | ); | ||
33512 | jx9_result_bool(pCtx, 0); | ||
33513 | return JX9_OK; | ||
33514 | } | ||
33515 | /* Point to the desired directory */ | ||
33516 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33517 | /* Perform the requested operation */ | ||
33518 | rc = pVfs->xIsdir(zPath); | ||
33519 | /* IO return value */ | ||
33520 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33521 | return JX9_OK; | ||
33522 | } | ||
33523 | /* | ||
33524 | * bool mkdir(string $pathname[, int $mode = 0777]) | ||
33525 | * Make a directory. | ||
33526 | * Parameters | ||
33527 | * $pathname | ||
33528 | * The directory path. | ||
33529 | * $mode | ||
33530 | * The mode is 0777 by default, which means the widest possible access. | ||
33531 | * Note: | ||
33532 | * mode is ignored on Windows. | ||
33533 | * Note that you probably want to specify the mode as an octal number, which means | ||
33534 | * it should have a leading zero. The mode is also modified by the current umask | ||
33535 | * which you can change using umask(). | ||
33536 | * Return | ||
33537 | * TRUE on success or FALSE on failure. | ||
33538 | */ | ||
33539 | static int jx9Vfs_mkdir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33540 | { | ||
33541 | int iRecursive = 0; | ||
33542 | const char *zPath; | ||
33543 | jx9_vfs *pVfs; | ||
33544 | int iMode, rc; | ||
33545 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33546 | /* Missing/Invalid argument, return FALSE */ | ||
33547 | jx9_result_bool(pCtx, 0); | ||
33548 | return JX9_OK; | ||
33549 | } | ||
33550 | /* Point to the underlying vfs */ | ||
33551 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33552 | if( pVfs == 0 || pVfs->xMkdir == 0 ){ | ||
33553 | /* IO routine not implemented, return NULL */ | ||
33554 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33555 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33556 | jx9_function_name(pCtx) | ||
33557 | ); | ||
33558 | jx9_result_bool(pCtx, 0); | ||
33559 | return JX9_OK; | ||
33560 | } | ||
33561 | /* Point to the desired directory */ | ||
33562 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33563 | #ifdef __WINNT__ | ||
33564 | iMode = 0; | ||
33565 | #else | ||
33566 | /* Assume UNIX */ | ||
33567 | iMode = 0777; | ||
33568 | #endif | ||
33569 | if( nArg > 1 ){ | ||
33570 | iMode = jx9_value_to_int(apArg[1]); | ||
33571 | if( nArg > 2 ){ | ||
33572 | iRecursive = jx9_value_to_bool(apArg[2]); | ||
33573 | } | ||
33574 | } | ||
33575 | /* Perform the requested operation */ | ||
33576 | rc = pVfs->xMkdir(zPath, iMode, iRecursive); | ||
33577 | /* IO return value */ | ||
33578 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33579 | return JX9_OK; | ||
33580 | } | ||
33581 | /* | ||
33582 | * bool rename(string $oldname, string $newname) | ||
33583 | * Attempts to rename oldname to newname. | ||
33584 | * Parameters | ||
33585 | * $oldname | ||
33586 | * Old name. | ||
33587 | * $newname | ||
33588 | * New name. | ||
33589 | * Return | ||
33590 | * TRUE on success or FALSE on failure. | ||
33591 | */ | ||
33592 | static int jx9Vfs_rename(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33593 | { | ||
33594 | const char *zOld, *zNew; | ||
33595 | jx9_vfs *pVfs; | ||
33596 | int rc; | ||
33597 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ | ||
33598 | /* Missing/Invalid arguments, return FALSE */ | ||
33599 | jx9_result_bool(pCtx, 0); | ||
33600 | return JX9_OK; | ||
33601 | } | ||
33602 | /* Point to the underlying vfs */ | ||
33603 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33604 | if( pVfs == 0 || pVfs->xRename == 0 ){ | ||
33605 | /* IO routine not implemented, return NULL */ | ||
33606 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33607 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33608 | jx9_function_name(pCtx) | ||
33609 | ); | ||
33610 | jx9_result_bool(pCtx, 0); | ||
33611 | return JX9_OK; | ||
33612 | } | ||
33613 | /* Perform the requested operation */ | ||
33614 | zOld = jx9_value_to_string(apArg[0], 0); | ||
33615 | zNew = jx9_value_to_string(apArg[1], 0); | ||
33616 | rc = pVfs->xRename(zOld, zNew); | ||
33617 | /* IO result */ | ||
33618 | jx9_result_bool(pCtx, rc == JX9_OK ); | ||
33619 | return JX9_OK; | ||
33620 | } | ||
33621 | /* | ||
33622 | * string realpath(string $path) | ||
33623 | * Returns canonicalized absolute pathname. | ||
33624 | * Parameters | ||
33625 | * $path | ||
33626 | * Target path. | ||
33627 | * Return | ||
33628 | * Canonicalized absolute pathname on success. or FALSE on failure. | ||
33629 | */ | ||
33630 | static int jx9Vfs_realpath(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33631 | { | ||
33632 | const char *zPath; | ||
33633 | jx9_vfs *pVfs; | ||
33634 | int rc; | ||
33635 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33636 | /* Missing/Invalid argument, return FALSE */ | ||
33637 | jx9_result_bool(pCtx, 0); | ||
33638 | return JX9_OK; | ||
33639 | } | ||
33640 | /* Point to the underlying vfs */ | ||
33641 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33642 | if( pVfs == 0 || pVfs->xRealpath == 0 ){ | ||
33643 | /* IO routine not implemented, return NULL */ | ||
33644 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33645 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33646 | jx9_function_name(pCtx) | ||
33647 | ); | ||
33648 | jx9_result_bool(pCtx, 0); | ||
33649 | return JX9_OK; | ||
33650 | } | ||
33651 | /* Set an empty string untnil the underlying OS interface change that */ | ||
33652 | jx9_result_string(pCtx, "", 0); | ||
33653 | /* Perform the requested operation */ | ||
33654 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33655 | rc = pVfs->xRealpath(zPath, pCtx); | ||
33656 | if( rc != JX9_OK ){ | ||
33657 | jx9_result_bool(pCtx, 0); | ||
33658 | } | ||
33659 | return JX9_OK; | ||
33660 | } | ||
33661 | /* | ||
33662 | * int sleep(int $seconds) | ||
33663 | * Delays the program execution for the given number of seconds. | ||
33664 | * Parameters | ||
33665 | * $seconds | ||
33666 | * Halt time in seconds. | ||
33667 | * Return | ||
33668 | * Zero on success or FALSE on failure. | ||
33669 | */ | ||
33670 | static int jx9Vfs_sleep(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33671 | { | ||
33672 | jx9_vfs *pVfs; | ||
33673 | int rc, nSleep; | ||
33674 | if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){ | ||
33675 | /* Missing/Invalid argument, return FALSE */ | ||
33676 | jx9_result_bool(pCtx, 0); | ||
33677 | return JX9_OK; | ||
33678 | } | ||
33679 | /* Point to the underlying vfs */ | ||
33680 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33681 | if( pVfs == 0 || pVfs->xSleep == 0 ){ | ||
33682 | /* IO routine not implemented, return NULL */ | ||
33683 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33684 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33685 | jx9_function_name(pCtx) | ||
33686 | ); | ||
33687 | jx9_result_bool(pCtx, 0); | ||
33688 | return JX9_OK; | ||
33689 | } | ||
33690 | /* Amount to sleep */ | ||
33691 | nSleep = jx9_value_to_int(apArg[0]); | ||
33692 | if( nSleep < 0 ){ | ||
33693 | /* Invalid value, return FALSE */ | ||
33694 | jx9_result_bool(pCtx, 0); | ||
33695 | return JX9_OK; | ||
33696 | } | ||
33697 | /* Perform the requested operation (Microseconds) */ | ||
33698 | rc = pVfs->xSleep((unsigned int)(nSleep * SX_USEC_PER_SEC)); | ||
33699 | if( rc != JX9_OK ){ | ||
33700 | /* Return FALSE */ | ||
33701 | jx9_result_bool(pCtx, 0); | ||
33702 | }else{ | ||
33703 | /* Return zero */ | ||
33704 | jx9_result_int(pCtx, 0); | ||
33705 | } | ||
33706 | return JX9_OK; | ||
33707 | } | ||
33708 | /* | ||
33709 | * void usleep(int $micro_seconds) | ||
33710 | * Delays program execution for the given number of micro seconds. | ||
33711 | * Parameters | ||
33712 | * $micro_seconds | ||
33713 | * Halt time in micro seconds. A micro second is one millionth of a second. | ||
33714 | * Return | ||
33715 | * None. | ||
33716 | */ | ||
33717 | static int jx9Vfs_usleep(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33718 | { | ||
33719 | jx9_vfs *pVfs; | ||
33720 | int nSleep; | ||
33721 | if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){ | ||
33722 | /* Missing/Invalid argument, return immediately */ | ||
33723 | return JX9_OK; | ||
33724 | } | ||
33725 | /* Point to the underlying vfs */ | ||
33726 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33727 | if( pVfs == 0 || pVfs->xSleep == 0 ){ | ||
33728 | /* IO routine not implemented, return NULL */ | ||
33729 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33730 | "IO routine(%s) not implemented in the underlying VFS", | ||
33731 | jx9_function_name(pCtx) | ||
33732 | ); | ||
33733 | return JX9_OK; | ||
33734 | } | ||
33735 | /* Amount to sleep */ | ||
33736 | nSleep = jx9_value_to_int(apArg[0]); | ||
33737 | if( nSleep < 0 ){ | ||
33738 | /* Invalid value, return immediately */ | ||
33739 | return JX9_OK; | ||
33740 | } | ||
33741 | /* Perform the requested operation (Microseconds) */ | ||
33742 | pVfs->xSleep((unsigned int)nSleep); | ||
33743 | return JX9_OK; | ||
33744 | } | ||
33745 | /* | ||
33746 | * bool unlink (string $filename) | ||
33747 | * Delete a file. | ||
33748 | * Parameters | ||
33749 | * $filename | ||
33750 | * Path to the file. | ||
33751 | * Return | ||
33752 | * TRUE on success or FALSE on failure. | ||
33753 | */ | ||
33754 | static int jx9Vfs_unlink(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33755 | { | ||
33756 | const char *zPath; | ||
33757 | jx9_vfs *pVfs; | ||
33758 | int rc; | ||
33759 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33760 | /* Missing/Invalid argument, return FALSE */ | ||
33761 | jx9_result_bool(pCtx, 0); | ||
33762 | return JX9_OK; | ||
33763 | } | ||
33764 | /* Point to the underlying vfs */ | ||
33765 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33766 | if( pVfs == 0 || pVfs->xUnlink == 0 ){ | ||
33767 | /* IO routine not implemented, return NULL */ | ||
33768 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33769 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33770 | jx9_function_name(pCtx) | ||
33771 | ); | ||
33772 | jx9_result_bool(pCtx, 0); | ||
33773 | return JX9_OK; | ||
33774 | } | ||
33775 | /* Point to the desired directory */ | ||
33776 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33777 | /* Perform the requested operation */ | ||
33778 | rc = pVfs->xUnlink(zPath); | ||
33779 | /* IO return value */ | ||
33780 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33781 | return JX9_OK; | ||
33782 | } | ||
33783 | /* | ||
33784 | * bool chmod(string $filename, int $mode) | ||
33785 | * Attempts to change the mode of the specified file to that given in mode. | ||
33786 | * Parameters | ||
33787 | * $filename | ||
33788 | * Path to the file. | ||
33789 | * $mode | ||
33790 | * Mode (Must be an integer) | ||
33791 | * Return | ||
33792 | * TRUE on success or FALSE on failure. | ||
33793 | */ | ||
33794 | static int jx9Vfs_chmod(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33795 | { | ||
33796 | const char *zPath; | ||
33797 | jx9_vfs *pVfs; | ||
33798 | int iMode; | ||
33799 | int rc; | ||
33800 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ | ||
33801 | /* Missing/Invalid argument, return FALSE */ | ||
33802 | jx9_result_bool(pCtx, 0); | ||
33803 | return JX9_OK; | ||
33804 | } | ||
33805 | /* Point to the underlying vfs */ | ||
33806 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33807 | if( pVfs == 0 || pVfs->xChmod == 0 ){ | ||
33808 | /* IO routine not implemented, return NULL */ | ||
33809 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33810 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33811 | jx9_function_name(pCtx) | ||
33812 | ); | ||
33813 | jx9_result_bool(pCtx, 0); | ||
33814 | return JX9_OK; | ||
33815 | } | ||
33816 | /* Point to the desired directory */ | ||
33817 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33818 | /* Extract the mode */ | ||
33819 | iMode = jx9_value_to_int(apArg[1]); | ||
33820 | /* Perform the requested operation */ | ||
33821 | rc = pVfs->xChmod(zPath, iMode); | ||
33822 | /* IO return value */ | ||
33823 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33824 | return JX9_OK; | ||
33825 | } | ||
33826 | /* | ||
33827 | * bool chown(string $filename, string $user) | ||
33828 | * Attempts to change the owner of the file filename to user user. | ||
33829 | * Parameters | ||
33830 | * $filename | ||
33831 | * Path to the file. | ||
33832 | * $user | ||
33833 | * Username. | ||
33834 | * Return | ||
33835 | * TRUE on success or FALSE on failure. | ||
33836 | */ | ||
33837 | static int jx9Vfs_chown(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33838 | { | ||
33839 | const char *zPath, *zUser; | ||
33840 | jx9_vfs *pVfs; | ||
33841 | int rc; | ||
33842 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ | ||
33843 | /* Missing/Invalid arguments, return FALSE */ | ||
33844 | jx9_result_bool(pCtx, 0); | ||
33845 | return JX9_OK; | ||
33846 | } | ||
33847 | /* Point to the underlying vfs */ | ||
33848 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33849 | if( pVfs == 0 || pVfs->xChown == 0 ){ | ||
33850 | /* IO routine not implemented, return NULL */ | ||
33851 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33852 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33853 | jx9_function_name(pCtx) | ||
33854 | ); | ||
33855 | jx9_result_bool(pCtx, 0); | ||
33856 | return JX9_OK; | ||
33857 | } | ||
33858 | /* Point to the desired directory */ | ||
33859 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33860 | /* Extract the user */ | ||
33861 | zUser = jx9_value_to_string(apArg[1], 0); | ||
33862 | /* Perform the requested operation */ | ||
33863 | rc = pVfs->xChown(zPath, zUser); | ||
33864 | /* IO return value */ | ||
33865 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33866 | return JX9_OK; | ||
33867 | } | ||
33868 | /* | ||
33869 | * bool chgrp(string $filename, string $group) | ||
33870 | * Attempts to change the group of the file filename to group. | ||
33871 | * Parameters | ||
33872 | * $filename | ||
33873 | * Path to the file. | ||
33874 | * $group | ||
33875 | * groupname. | ||
33876 | * Return | ||
33877 | * TRUE on success or FALSE on failure. | ||
33878 | */ | ||
33879 | static int jx9Vfs_chgrp(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33880 | { | ||
33881 | const char *zPath, *zGroup; | ||
33882 | jx9_vfs *pVfs; | ||
33883 | int rc; | ||
33884 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ | ||
33885 | /* Missing/Invalid arguments, return FALSE */ | ||
33886 | jx9_result_bool(pCtx, 0); | ||
33887 | return JX9_OK; | ||
33888 | } | ||
33889 | /* Point to the underlying vfs */ | ||
33890 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33891 | if( pVfs == 0 || pVfs->xChgrp == 0 ){ | ||
33892 | /* IO routine not implemented, return NULL */ | ||
33893 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33894 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33895 | jx9_function_name(pCtx) | ||
33896 | ); | ||
33897 | jx9_result_bool(pCtx, 0); | ||
33898 | return JX9_OK; | ||
33899 | } | ||
33900 | /* Point to the desired directory */ | ||
33901 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33902 | /* Extract the user */ | ||
33903 | zGroup = jx9_value_to_string(apArg[1], 0); | ||
33904 | /* Perform the requested operation */ | ||
33905 | rc = pVfs->xChgrp(zPath, zGroup); | ||
33906 | /* IO return value */ | ||
33907 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
33908 | return JX9_OK; | ||
33909 | } | ||
33910 | /* | ||
33911 | * int64 disk_free_space(string $directory) | ||
33912 | * Returns available space on filesystem or disk partition. | ||
33913 | * Parameters | ||
33914 | * $directory | ||
33915 | * A directory of the filesystem or disk partition. | ||
33916 | * Return | ||
33917 | * Returns the number of available bytes as a 64-bit integer or FALSE on failure. | ||
33918 | */ | ||
33919 | static int jx9Vfs_disk_free_space(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33920 | { | ||
33921 | const char *zPath; | ||
33922 | jx9_int64 iSize; | ||
33923 | jx9_vfs *pVfs; | ||
33924 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33925 | /* Missing/Invalid argument, return FALSE */ | ||
33926 | jx9_result_bool(pCtx, 0); | ||
33927 | return JX9_OK; | ||
33928 | } | ||
33929 | /* Point to the underlying vfs */ | ||
33930 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33931 | if( pVfs == 0 || pVfs->xFreeSpace == 0 ){ | ||
33932 | /* IO routine not implemented, return NULL */ | ||
33933 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33934 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33935 | jx9_function_name(pCtx) | ||
33936 | ); | ||
33937 | jx9_result_bool(pCtx, 0); | ||
33938 | return JX9_OK; | ||
33939 | } | ||
33940 | /* Point to the desired directory */ | ||
33941 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33942 | /* Perform the requested operation */ | ||
33943 | iSize = pVfs->xFreeSpace(zPath); | ||
33944 | /* IO return value */ | ||
33945 | jx9_result_int64(pCtx, iSize); | ||
33946 | return JX9_OK; | ||
33947 | } | ||
33948 | /* | ||
33949 | * int64 disk_total_space(string $directory) | ||
33950 | * Returns the total size of a filesystem or disk partition. | ||
33951 | * Parameters | ||
33952 | * $directory | ||
33953 | * A directory of the filesystem or disk partition. | ||
33954 | * Return | ||
33955 | * Returns the number of available bytes as a 64-bit integer or FALSE on failure. | ||
33956 | */ | ||
33957 | static int jx9Vfs_disk_total_space(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33958 | { | ||
33959 | const char *zPath; | ||
33960 | jx9_int64 iSize; | ||
33961 | jx9_vfs *pVfs; | ||
33962 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
33963 | /* Missing/Invalid argument, return FALSE */ | ||
33964 | jx9_result_bool(pCtx, 0); | ||
33965 | return JX9_OK; | ||
33966 | } | ||
33967 | /* Point to the underlying vfs */ | ||
33968 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
33969 | if( pVfs == 0 || pVfs->xTotalSpace == 0 ){ | ||
33970 | /* IO routine not implemented, return NULL */ | ||
33971 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
33972 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
33973 | jx9_function_name(pCtx) | ||
33974 | ); | ||
33975 | jx9_result_bool(pCtx, 0); | ||
33976 | return JX9_OK; | ||
33977 | } | ||
33978 | /* Point to the desired directory */ | ||
33979 | zPath = jx9_value_to_string(apArg[0], 0); | ||
33980 | /* Perform the requested operation */ | ||
33981 | iSize = pVfs->xTotalSpace(zPath); | ||
33982 | /* IO return value */ | ||
33983 | jx9_result_int64(pCtx, iSize); | ||
33984 | return JX9_OK; | ||
33985 | } | ||
33986 | /* | ||
33987 | * bool file_exists(string $filename) | ||
33988 | * Checks whether a file or directory exists. | ||
33989 | * Parameters | ||
33990 | * $filename | ||
33991 | * Path to the file. | ||
33992 | * Return | ||
33993 | * TRUE on success or FALSE on failure. | ||
33994 | */ | ||
33995 | static int jx9Vfs_file_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
33996 | { | ||
33997 | const char *zPath; | ||
33998 | jx9_vfs *pVfs; | ||
33999 | int rc; | ||
34000 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34001 | /* Missing/Invalid argument, return FALSE */ | ||
34002 | jx9_result_bool(pCtx, 0); | ||
34003 | return JX9_OK; | ||
34004 | } | ||
34005 | /* Point to the underlying vfs */ | ||
34006 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34007 | if( pVfs == 0 || pVfs->xFileExists == 0 ){ | ||
34008 | /* IO routine not implemented, return NULL */ | ||
34009 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34010 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34011 | jx9_function_name(pCtx) | ||
34012 | ); | ||
34013 | jx9_result_bool(pCtx, 0); | ||
34014 | return JX9_OK; | ||
34015 | } | ||
34016 | /* Point to the desired directory */ | ||
34017 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34018 | /* Perform the requested operation */ | ||
34019 | rc = pVfs->xFileExists(zPath); | ||
34020 | /* IO return value */ | ||
34021 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
34022 | return JX9_OK; | ||
34023 | } | ||
34024 | /* | ||
34025 | * int64 file_size(string $filename) | ||
34026 | * Gets the size for the given file. | ||
34027 | * Parameters | ||
34028 | * $filename | ||
34029 | * Path to the file. | ||
34030 | * Return | ||
34031 | * File size on success or FALSE on failure. | ||
34032 | */ | ||
34033 | static int jx9Vfs_file_size(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34034 | { | ||
34035 | const char *zPath; | ||
34036 | jx9_int64 iSize; | ||
34037 | jx9_vfs *pVfs; | ||
34038 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34039 | /* Missing/Invalid argument, return FALSE */ | ||
34040 | jx9_result_bool(pCtx, 0); | ||
34041 | return JX9_OK; | ||
34042 | } | ||
34043 | /* Point to the underlying vfs */ | ||
34044 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34045 | if( pVfs == 0 || pVfs->xFileSize == 0 ){ | ||
34046 | /* IO routine not implemented, return NULL */ | ||
34047 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34048 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34049 | jx9_function_name(pCtx) | ||
34050 | ); | ||
34051 | jx9_result_bool(pCtx, 0); | ||
34052 | return JX9_OK; | ||
34053 | } | ||
34054 | /* Point to the desired directory */ | ||
34055 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34056 | /* Perform the requested operation */ | ||
34057 | iSize = pVfs->xFileSize(zPath); | ||
34058 | /* IO return value */ | ||
34059 | jx9_result_int64(pCtx, iSize); | ||
34060 | return JX9_OK; | ||
34061 | } | ||
34062 | /* | ||
34063 | * int64 fileatime(string $filename) | ||
34064 | * Gets the last access time of the given file. | ||
34065 | * Parameters | ||
34066 | * $filename | ||
34067 | * Path to the file. | ||
34068 | * Return | ||
34069 | * File atime on success or FALSE on failure. | ||
34070 | */ | ||
34071 | static int jx9Vfs_file_atime(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34072 | { | ||
34073 | const char *zPath; | ||
34074 | jx9_int64 iTime; | ||
34075 | jx9_vfs *pVfs; | ||
34076 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34077 | /* Missing/Invalid argument, return FALSE */ | ||
34078 | jx9_result_bool(pCtx, 0); | ||
34079 | return JX9_OK; | ||
34080 | } | ||
34081 | /* Point to the underlying vfs */ | ||
34082 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34083 | if( pVfs == 0 || pVfs->xFileAtime == 0 ){ | ||
34084 | /* IO routine not implemented, return NULL */ | ||
34085 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34086 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34087 | jx9_function_name(pCtx) | ||
34088 | ); | ||
34089 | jx9_result_bool(pCtx, 0); | ||
34090 | return JX9_OK; | ||
34091 | } | ||
34092 | /* Point to the desired directory */ | ||
34093 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34094 | /* Perform the requested operation */ | ||
34095 | iTime = pVfs->xFileAtime(zPath); | ||
34096 | /* IO return value */ | ||
34097 | jx9_result_int64(pCtx, iTime); | ||
34098 | return JX9_OK; | ||
34099 | } | ||
34100 | /* | ||
34101 | * int64 filemtime(string $filename) | ||
34102 | * Gets file modification time. | ||
34103 | * Parameters | ||
34104 | * $filename | ||
34105 | * Path to the file. | ||
34106 | * Return | ||
34107 | * File mtime on success or FALSE on failure. | ||
34108 | */ | ||
34109 | static int jx9Vfs_file_mtime(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34110 | { | ||
34111 | const char *zPath; | ||
34112 | jx9_int64 iTime; | ||
34113 | jx9_vfs *pVfs; | ||
34114 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34115 | /* Missing/Invalid argument, return FALSE */ | ||
34116 | jx9_result_bool(pCtx, 0); | ||
34117 | return JX9_OK; | ||
34118 | } | ||
34119 | /* Point to the underlying vfs */ | ||
34120 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34121 | if( pVfs == 0 || pVfs->xFileMtime == 0 ){ | ||
34122 | /* IO routine not implemented, return NULL */ | ||
34123 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34124 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34125 | jx9_function_name(pCtx) | ||
34126 | ); | ||
34127 | jx9_result_bool(pCtx, 0); | ||
34128 | return JX9_OK; | ||
34129 | } | ||
34130 | /* Point to the desired directory */ | ||
34131 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34132 | /* Perform the requested operation */ | ||
34133 | iTime = pVfs->xFileMtime(zPath); | ||
34134 | /* IO return value */ | ||
34135 | jx9_result_int64(pCtx, iTime); | ||
34136 | return JX9_OK; | ||
34137 | } | ||
34138 | /* | ||
34139 | * int64 filectime(string $filename) | ||
34140 | * Gets inode change time of file. | ||
34141 | * Parameters | ||
34142 | * $filename | ||
34143 | * Path to the file. | ||
34144 | * Return | ||
34145 | * File ctime on success or FALSE on failure. | ||
34146 | */ | ||
34147 | static int jx9Vfs_file_ctime(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34148 | { | ||
34149 | const char *zPath; | ||
34150 | jx9_int64 iTime; | ||
34151 | jx9_vfs *pVfs; | ||
34152 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34153 | /* Missing/Invalid argument, return FALSE */ | ||
34154 | jx9_result_bool(pCtx, 0); | ||
34155 | return JX9_OK; | ||
34156 | } | ||
34157 | /* Point to the underlying vfs */ | ||
34158 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34159 | if( pVfs == 0 || pVfs->xFileCtime == 0 ){ | ||
34160 | /* IO routine not implemented, return NULL */ | ||
34161 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34162 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34163 | jx9_function_name(pCtx) | ||
34164 | ); | ||
34165 | jx9_result_bool(pCtx, 0); | ||
34166 | return JX9_OK; | ||
34167 | } | ||
34168 | /* Point to the desired directory */ | ||
34169 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34170 | /* Perform the requested operation */ | ||
34171 | iTime = pVfs->xFileCtime(zPath); | ||
34172 | /* IO return value */ | ||
34173 | jx9_result_int64(pCtx, iTime); | ||
34174 | return JX9_OK; | ||
34175 | } | ||
34176 | /* | ||
34177 | * bool is_file(string $filename) | ||
34178 | * Tells whether the filename is a regular file. | ||
34179 | * Parameters | ||
34180 | * $filename | ||
34181 | * Path to the file. | ||
34182 | * Return | ||
34183 | * TRUE on success or FALSE on failure. | ||
34184 | */ | ||
34185 | static int jx9Vfs_is_file(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34186 | { | ||
34187 | const char *zPath; | ||
34188 | jx9_vfs *pVfs; | ||
34189 | int rc; | ||
34190 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34191 | /* Missing/Invalid argument, return FALSE */ | ||
34192 | jx9_result_bool(pCtx, 0); | ||
34193 | return JX9_OK; | ||
34194 | } | ||
34195 | /* Point to the underlying vfs */ | ||
34196 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34197 | if( pVfs == 0 || pVfs->xIsfile == 0 ){ | ||
34198 | /* IO routine not implemented, return NULL */ | ||
34199 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34200 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34201 | jx9_function_name(pCtx) | ||
34202 | ); | ||
34203 | jx9_result_bool(pCtx, 0); | ||
34204 | return JX9_OK; | ||
34205 | } | ||
34206 | /* Point to the desired directory */ | ||
34207 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34208 | /* Perform the requested operation */ | ||
34209 | rc = pVfs->xIsfile(zPath); | ||
34210 | /* IO return value */ | ||
34211 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
34212 | return JX9_OK; | ||
34213 | } | ||
34214 | /* | ||
34215 | * bool is_link(string $filename) | ||
34216 | * Tells whether the filename is a symbolic link. | ||
34217 | * Parameters | ||
34218 | * $filename | ||
34219 | * Path to the file. | ||
34220 | * Return | ||
34221 | * TRUE on success or FALSE on failure. | ||
34222 | */ | ||
34223 | static int jx9Vfs_is_link(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34224 | { | ||
34225 | const char *zPath; | ||
34226 | jx9_vfs *pVfs; | ||
34227 | int rc; | ||
34228 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34229 | /* Missing/Invalid argument, return FALSE */ | ||
34230 | jx9_result_bool(pCtx, 0); | ||
34231 | return JX9_OK; | ||
34232 | } | ||
34233 | /* Point to the underlying vfs */ | ||
34234 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34235 | if( pVfs == 0 || pVfs->xIslink == 0 ){ | ||
34236 | /* IO routine not implemented, return NULL */ | ||
34237 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34238 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34239 | jx9_function_name(pCtx) | ||
34240 | ); | ||
34241 | jx9_result_bool(pCtx, 0); | ||
34242 | return JX9_OK; | ||
34243 | } | ||
34244 | /* Point to the desired directory */ | ||
34245 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34246 | /* Perform the requested operation */ | ||
34247 | rc = pVfs->xIslink(zPath); | ||
34248 | /* IO return value */ | ||
34249 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
34250 | return JX9_OK; | ||
34251 | } | ||
34252 | /* | ||
34253 | * bool is_readable(string $filename) | ||
34254 | * Tells whether a file exists and is readable. | ||
34255 | * Parameters | ||
34256 | * $filename | ||
34257 | * Path to the file. | ||
34258 | * Return | ||
34259 | * TRUE on success or FALSE on failure. | ||
34260 | */ | ||
34261 | static int jx9Vfs_is_readable(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34262 | { | ||
34263 | const char *zPath; | ||
34264 | jx9_vfs *pVfs; | ||
34265 | int rc; | ||
34266 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34267 | /* Missing/Invalid argument, return FALSE */ | ||
34268 | jx9_result_bool(pCtx, 0); | ||
34269 | return JX9_OK; | ||
34270 | } | ||
34271 | /* Point to the underlying vfs */ | ||
34272 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34273 | if( pVfs == 0 || pVfs->xReadable == 0 ){ | ||
34274 | /* IO routine not implemented, return NULL */ | ||
34275 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34276 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34277 | jx9_function_name(pCtx) | ||
34278 | ); | ||
34279 | jx9_result_bool(pCtx, 0); | ||
34280 | return JX9_OK; | ||
34281 | } | ||
34282 | /* Point to the desired directory */ | ||
34283 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34284 | /* Perform the requested operation */ | ||
34285 | rc = pVfs->xReadable(zPath); | ||
34286 | /* IO return value */ | ||
34287 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
34288 | return JX9_OK; | ||
34289 | } | ||
34290 | /* | ||
34291 | * bool is_writable(string $filename) | ||
34292 | * Tells whether the filename is writable. | ||
34293 | * Parameters | ||
34294 | * $filename | ||
34295 | * Path to the file. | ||
34296 | * Return | ||
34297 | * TRUE on success or FALSE on failure. | ||
34298 | */ | ||
34299 | static int jx9Vfs_is_writable(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34300 | { | ||
34301 | const char *zPath; | ||
34302 | jx9_vfs *pVfs; | ||
34303 | int rc; | ||
34304 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34305 | /* Missing/Invalid argument, return FALSE */ | ||
34306 | jx9_result_bool(pCtx, 0); | ||
34307 | return JX9_OK; | ||
34308 | } | ||
34309 | /* Point to the underlying vfs */ | ||
34310 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34311 | if( pVfs == 0 || pVfs->xWritable == 0 ){ | ||
34312 | /* IO routine not implemented, return NULL */ | ||
34313 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34314 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34315 | jx9_function_name(pCtx) | ||
34316 | ); | ||
34317 | jx9_result_bool(pCtx, 0); | ||
34318 | return JX9_OK; | ||
34319 | } | ||
34320 | /* Point to the desired directory */ | ||
34321 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34322 | /* Perform the requested operation */ | ||
34323 | rc = pVfs->xWritable(zPath); | ||
34324 | /* IO return value */ | ||
34325 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
34326 | return JX9_OK; | ||
34327 | } | ||
34328 | /* | ||
34329 | * bool is_executable(string $filename) | ||
34330 | * Tells whether the filename is executable. | ||
34331 | * Parameters | ||
34332 | * $filename | ||
34333 | * Path to the file. | ||
34334 | * Return | ||
34335 | * TRUE on success or FALSE on failure. | ||
34336 | */ | ||
34337 | static int jx9Vfs_is_executable(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34338 | { | ||
34339 | const char *zPath; | ||
34340 | jx9_vfs *pVfs; | ||
34341 | int rc; | ||
34342 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34343 | /* Missing/Invalid argument, return FALSE */ | ||
34344 | jx9_result_bool(pCtx, 0); | ||
34345 | return JX9_OK; | ||
34346 | } | ||
34347 | /* Point to the underlying vfs */ | ||
34348 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34349 | if( pVfs == 0 || pVfs->xExecutable == 0 ){ | ||
34350 | /* IO routine not implemented, return NULL */ | ||
34351 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34352 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34353 | jx9_function_name(pCtx) | ||
34354 | ); | ||
34355 | jx9_result_bool(pCtx, 0); | ||
34356 | return JX9_OK; | ||
34357 | } | ||
34358 | /* Point to the desired directory */ | ||
34359 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34360 | /* Perform the requested operation */ | ||
34361 | rc = pVfs->xExecutable(zPath); | ||
34362 | /* IO return value */ | ||
34363 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
34364 | return JX9_OK; | ||
34365 | } | ||
34366 | /* | ||
34367 | * string filetype(string $filename) | ||
34368 | * Gets file type. | ||
34369 | * Parameters | ||
34370 | * $filename | ||
34371 | * Path to the file. | ||
34372 | * Return | ||
34373 | * The type of the file. Possible values are fifo, char, dir, block, link | ||
34374 | * file, socket and unknown. | ||
34375 | */ | ||
34376 | static int jx9Vfs_filetype(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34377 | { | ||
34378 | const char *zPath; | ||
34379 | jx9_vfs *pVfs; | ||
34380 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34381 | /* Missing/Invalid argument, return 'unknown' */ | ||
34382 | jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); | ||
34383 | return JX9_OK; | ||
34384 | } | ||
34385 | /* Point to the underlying vfs */ | ||
34386 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34387 | if( pVfs == 0 || pVfs->xFiletype == 0 ){ | ||
34388 | /* IO routine not implemented, return NULL */ | ||
34389 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34390 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34391 | jx9_function_name(pCtx) | ||
34392 | ); | ||
34393 | jx9_result_bool(pCtx, 0); | ||
34394 | return JX9_OK; | ||
34395 | } | ||
34396 | /* Point to the desired directory */ | ||
34397 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34398 | /* Set the empty string as the default return value */ | ||
34399 | jx9_result_string(pCtx, "", 0); | ||
34400 | /* Perform the requested operation */ | ||
34401 | pVfs->xFiletype(zPath, pCtx); | ||
34402 | return JX9_OK; | ||
34403 | } | ||
34404 | /* | ||
34405 | * array stat(string $filename) | ||
34406 | * Gives information about a file. | ||
34407 | * Parameters | ||
34408 | * $filename | ||
34409 | * Path to the file. | ||
34410 | * Return | ||
34411 | * An associative array on success holding the following entries on success | ||
34412 | * 0 dev device number | ||
34413 | * 1 ino inode number (zero on windows) | ||
34414 | * 2 mode inode protection mode | ||
34415 | * 3 nlink number of links | ||
34416 | * 4 uid userid of owner (zero on windows) | ||
34417 | * 5 gid groupid of owner (zero on windows) | ||
34418 | * 6 rdev device type, if inode device | ||
34419 | * 7 size size in bytes | ||
34420 | * 8 atime time of last access (Unix timestamp) | ||
34421 | * 9 mtime time of last modification (Unix timestamp) | ||
34422 | * 10 ctime time of last inode change (Unix timestamp) | ||
34423 | * 11 blksize blocksize of filesystem IO (zero on windows) | ||
34424 | * 12 blocks number of 512-byte blocks allocated. | ||
34425 | * Note: | ||
34426 | * FALSE is returned on failure. | ||
34427 | */ | ||
34428 | static int jx9Vfs_stat(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34429 | { | ||
34430 | jx9_value *pArray, *pValue; | ||
34431 | const char *zPath; | ||
34432 | jx9_vfs *pVfs; | ||
34433 | int rc; | ||
34434 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34435 | /* Missing/Invalid argument, return FALSE */ | ||
34436 | jx9_result_bool(pCtx, 0); | ||
34437 | return JX9_OK; | ||
34438 | } | ||
34439 | /* Point to the underlying vfs */ | ||
34440 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34441 | if( pVfs == 0 || pVfs->xStat == 0 ){ | ||
34442 | /* IO routine not implemented, return NULL */ | ||
34443 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34444 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34445 | jx9_function_name(pCtx) | ||
34446 | ); | ||
34447 | jx9_result_bool(pCtx, 0); | ||
34448 | return JX9_OK; | ||
34449 | } | ||
34450 | /* Create the array and the working value */ | ||
34451 | pArray = jx9_context_new_array(pCtx); | ||
34452 | pValue = jx9_context_new_scalar(pCtx); | ||
34453 | if( pArray == 0 || pValue == 0 ){ | ||
34454 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
34455 | jx9_result_bool(pCtx, 0); | ||
34456 | return JX9_OK; | ||
34457 | } | ||
34458 | /* Extract the file path */ | ||
34459 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34460 | /* Perform the requested operation */ | ||
34461 | rc = pVfs->xStat(zPath, pArray, pValue); | ||
34462 | if( rc != JX9_OK ){ | ||
34463 | /* IO error, return FALSE */ | ||
34464 | jx9_result_bool(pCtx, 0); | ||
34465 | }else{ | ||
34466 | /* Return the associative array */ | ||
34467 | jx9_result_value(pCtx, pArray); | ||
34468 | } | ||
34469 | /* Don't worry about freeing memory here, everything will be released | ||
34470 | * automatically as soon we return from this function. */ | ||
34471 | return JX9_OK; | ||
34472 | } | ||
34473 | /* | ||
34474 | * array lstat(string $filename) | ||
34475 | * Gives information about a file or symbolic link. | ||
34476 | * Parameters | ||
34477 | * $filename | ||
34478 | * Path to the file. | ||
34479 | * Return | ||
34480 | * An associative array on success holding the following entries on success | ||
34481 | * 0 dev device number | ||
34482 | * 1 ino inode number (zero on windows) | ||
34483 | * 2 mode inode protection mode | ||
34484 | * 3 nlink number of links | ||
34485 | * 4 uid userid of owner (zero on windows) | ||
34486 | * 5 gid groupid of owner (zero on windows) | ||
34487 | * 6 rdev device type, if inode device | ||
34488 | * 7 size size in bytes | ||
34489 | * 8 atime time of last access (Unix timestamp) | ||
34490 | * 9 mtime time of last modification (Unix timestamp) | ||
34491 | * 10 ctime time of last inode change (Unix timestamp) | ||
34492 | * 11 blksize blocksize of filesystem IO (zero on windows) | ||
34493 | * 12 blocks number of 512-byte blocks allocated. | ||
34494 | * Note: | ||
34495 | * FALSE is returned on failure. | ||
34496 | */ | ||
34497 | static int jx9Vfs_lstat(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34498 | { | ||
34499 | jx9_value *pArray, *pValue; | ||
34500 | const char *zPath; | ||
34501 | jx9_vfs *pVfs; | ||
34502 | int rc; | ||
34503 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34504 | /* Missing/Invalid argument, return FALSE */ | ||
34505 | jx9_result_bool(pCtx, 0); | ||
34506 | return JX9_OK; | ||
34507 | } | ||
34508 | /* Point to the underlying vfs */ | ||
34509 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34510 | if( pVfs == 0 || pVfs->xlStat == 0 ){ | ||
34511 | /* IO routine not implemented, return NULL */ | ||
34512 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34513 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34514 | jx9_function_name(pCtx) | ||
34515 | ); | ||
34516 | jx9_result_bool(pCtx, 0); | ||
34517 | return JX9_OK; | ||
34518 | } | ||
34519 | /* Create the array and the working value */ | ||
34520 | pArray = jx9_context_new_array(pCtx); | ||
34521 | pValue = jx9_context_new_scalar(pCtx); | ||
34522 | if( pArray == 0 || pValue == 0 ){ | ||
34523 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
34524 | jx9_result_bool(pCtx, 0); | ||
34525 | return JX9_OK; | ||
34526 | } | ||
34527 | /* Extract the file path */ | ||
34528 | zPath = jx9_value_to_string(apArg[0], 0); | ||
34529 | /* Perform the requested operation */ | ||
34530 | rc = pVfs->xlStat(zPath, pArray, pValue); | ||
34531 | if( rc != JX9_OK ){ | ||
34532 | /* IO error, return FALSE */ | ||
34533 | jx9_result_bool(pCtx, 0); | ||
34534 | }else{ | ||
34535 | /* Return the associative array */ | ||
34536 | jx9_result_value(pCtx, pArray); | ||
34537 | } | ||
34538 | /* Don't worry about freeing memory here, everything will be released | ||
34539 | * automatically as soon we return from this function. */ | ||
34540 | return JX9_OK; | ||
34541 | } | ||
34542 | /* | ||
34543 | * string getenv(string $varname) | ||
34544 | * Gets the value of an environment variable. | ||
34545 | * Parameters | ||
34546 | * $varname | ||
34547 | * The variable name. | ||
34548 | * Return | ||
34549 | * Returns the value of the environment variable varname, or FALSE if the environment | ||
34550 | * variable varname does not exist. | ||
34551 | */ | ||
34552 | static int jx9Vfs_getenv(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34553 | { | ||
34554 | const char *zEnv; | ||
34555 | jx9_vfs *pVfs; | ||
34556 | int iLen; | ||
34557 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34558 | /* Missing/Invalid argument, return FALSE */ | ||
34559 | jx9_result_bool(pCtx, 0); | ||
34560 | return JX9_OK; | ||
34561 | } | ||
34562 | /* Point to the underlying vfs */ | ||
34563 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34564 | if( pVfs == 0 || pVfs->xGetenv == 0 ){ | ||
34565 | /* IO routine not implemented, return NULL */ | ||
34566 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34567 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34568 | jx9_function_name(pCtx) | ||
34569 | ); | ||
34570 | jx9_result_bool(pCtx, 0); | ||
34571 | return JX9_OK; | ||
34572 | } | ||
34573 | /* Extract the environment variable */ | ||
34574 | zEnv = jx9_value_to_string(apArg[0], &iLen); | ||
34575 | /* Set a boolean FALSE as the default return value */ | ||
34576 | jx9_result_bool(pCtx, 0); | ||
34577 | if( iLen < 1 ){ | ||
34578 | /* Empty string */ | ||
34579 | return JX9_OK; | ||
34580 | } | ||
34581 | /* Perform the requested operation */ | ||
34582 | pVfs->xGetenv(zEnv, pCtx); | ||
34583 | return JX9_OK; | ||
34584 | } | ||
34585 | /* | ||
34586 | * bool putenv(string $settings) | ||
34587 | * Set the value of an environment variable. | ||
34588 | * Parameters | ||
34589 | * $setting | ||
34590 | * The setting, like "FOO=BAR" | ||
34591 | * Return | ||
34592 | * TRUE on success or FALSE on failure. | ||
34593 | */ | ||
34594 | static int jx9Vfs_putenv(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34595 | { | ||
34596 | const char *zName, *zValue; | ||
34597 | char *zSettings, *zEnd; | ||
34598 | jx9_vfs *pVfs; | ||
34599 | int iLen, rc; | ||
34600 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34601 | /* Missing/Invalid argument, return FALSE */ | ||
34602 | jx9_result_bool(pCtx, 0); | ||
34603 | return JX9_OK; | ||
34604 | } | ||
34605 | /* Extract the setting variable */ | ||
34606 | zSettings = (char *)jx9_value_to_string(apArg[0], &iLen); | ||
34607 | if( iLen < 1 ){ | ||
34608 | /* Empty string, return FALSE */ | ||
34609 | jx9_result_bool(pCtx, 0); | ||
34610 | return JX9_OK; | ||
34611 | } | ||
34612 | /* Parse the setting */ | ||
34613 | zEnd = &zSettings[iLen]; | ||
34614 | zValue = 0; | ||
34615 | zName = zSettings; | ||
34616 | while( zSettings < zEnd ){ | ||
34617 | if( zSettings[0] == '=' ){ | ||
34618 | /* Null terminate the name */ | ||
34619 | zSettings[0] = 0; | ||
34620 | zValue = &zSettings[1]; | ||
34621 | break; | ||
34622 | } | ||
34623 | zSettings++; | ||
34624 | } | ||
34625 | /* Install the environment variable in the $_Env array */ | ||
34626 | if( zValue == 0 || zName[0] == 0 || zValue >= zEnd || zName >= zValue ){ | ||
34627 | /* Invalid settings, retun FALSE */ | ||
34628 | jx9_result_bool(pCtx, 0); | ||
34629 | if( zSettings < zEnd ){ | ||
34630 | zSettings[0] = '='; | ||
34631 | } | ||
34632 | return JX9_OK; | ||
34633 | } | ||
34634 | jx9_vm_config(pCtx->pVm, JX9_VM_CONFIG_ENV_ATTR, zName, zValue, (int)(zEnd-zValue)); | ||
34635 | /* Point to the underlying vfs */ | ||
34636 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34637 | if( pVfs == 0 || pVfs->xSetenv == 0 ){ | ||
34638 | /* IO routine not implemented, return NULL */ | ||
34639 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34640 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34641 | jx9_function_name(pCtx) | ||
34642 | ); | ||
34643 | jx9_result_bool(pCtx, 0); | ||
34644 | zSettings[0] = '='; | ||
34645 | return JX9_OK; | ||
34646 | } | ||
34647 | /* Perform the requested operation */ | ||
34648 | rc = pVfs->xSetenv(zName, zValue); | ||
34649 | jx9_result_bool(pCtx, rc == JX9_OK ); | ||
34650 | zSettings[0] = '='; | ||
34651 | return JX9_OK; | ||
34652 | } | ||
34653 | /* | ||
34654 | * bool touch(string $filename[, int64 $time = time()[, int64 $atime]]) | ||
34655 | * Sets access and modification time of file. | ||
34656 | * Note: On windows | ||
34657 | * If the file does not exists, it will not be created. | ||
34658 | * Parameters | ||
34659 | * $filename | ||
34660 | * The name of the file being touched. | ||
34661 | * $time | ||
34662 | * The touch time. If time is not supplied, the current system time is used. | ||
34663 | * $atime | ||
34664 | * If present, the access time of the given filename is set to the value of atime. | ||
34665 | * Otherwise, it is set to the value passed to the time parameter. If neither are | ||
34666 | * present, the current system time is used. | ||
34667 | * Return | ||
34668 | * TRUE on success or FALSE on failure. | ||
34669 | */ | ||
34670 | static int jx9Vfs_touch(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34671 | { | ||
34672 | jx9_int64 nTime, nAccess; | ||
34673 | const char *zFile; | ||
34674 | jx9_vfs *pVfs; | ||
34675 | int rc; | ||
34676 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34677 | /* Missing/Invalid argument, return FALSE */ | ||
34678 | jx9_result_bool(pCtx, 0); | ||
34679 | return JX9_OK; | ||
34680 | } | ||
34681 | /* Point to the underlying vfs */ | ||
34682 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
34683 | if( pVfs == 0 || pVfs->xTouch == 0 ){ | ||
34684 | /* IO routine not implemented, return NULL */ | ||
34685 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
34686 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
34687 | jx9_function_name(pCtx) | ||
34688 | ); | ||
34689 | jx9_result_bool(pCtx, 0); | ||
34690 | return JX9_OK; | ||
34691 | } | ||
34692 | /* Perform the requested operation */ | ||
34693 | nTime = nAccess = -1; | ||
34694 | zFile = jx9_value_to_string(apArg[0], 0); | ||
34695 | if( nArg > 1 ){ | ||
34696 | nTime = jx9_value_to_int64(apArg[1]); | ||
34697 | if( nArg > 2 ){ | ||
34698 | nAccess = jx9_value_to_int64(apArg[1]); | ||
34699 | }else{ | ||
34700 | nAccess = nTime; | ||
34701 | } | ||
34702 | } | ||
34703 | rc = pVfs->xTouch(zFile, nTime, nAccess); | ||
34704 | /* IO result */ | ||
34705 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
34706 | return JX9_OK; | ||
34707 | } | ||
34708 | /* | ||
34709 | * Path processing functions that do not need access to the VFS layer | ||
34710 | * Authors: | ||
34711 | * Symisc Systems, devel@symisc.net. | ||
34712 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
34713 | * Status: | ||
34714 | * Stable. | ||
34715 | */ | ||
34716 | /* | ||
34717 | * string dirname(string $path) | ||
34718 | * Returns parent directory's path. | ||
34719 | * Parameters | ||
34720 | * $path | ||
34721 | * Target path. | ||
34722 | * On Windows, both slash (/) and backslash (\) are used as directory separator character. | ||
34723 | * In other environments, it is the forward slash (/). | ||
34724 | * Return | ||
34725 | * The path of the parent directory. If there are no slashes in path, a dot ('.') | ||
34726 | * is returned, indicating the current directory. | ||
34727 | */ | ||
34728 | static int jx9Builtin_dirname(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34729 | { | ||
34730 | const char *zPath, *zDir; | ||
34731 | int iLen, iDirlen; | ||
34732 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34733 | /* Missing/Invalid arguments, return the empty string */ | ||
34734 | jx9_result_string(pCtx, "", 0); | ||
34735 | return JX9_OK; | ||
34736 | } | ||
34737 | /* Point to the target path */ | ||
34738 | zPath = jx9_value_to_string(apArg[0], &iLen); | ||
34739 | if( iLen < 1 ){ | ||
34740 | /* Reuturn "." */ | ||
34741 | jx9_result_string(pCtx, ".", sizeof(char)); | ||
34742 | return JX9_OK; | ||
34743 | } | ||
34744 | /* Perform the requested operation */ | ||
34745 | zDir = jx9ExtractDirName(zPath, iLen, &iDirlen); | ||
34746 | /* Return directory name */ | ||
34747 | jx9_result_string(pCtx, zDir, iDirlen); | ||
34748 | return JX9_OK; | ||
34749 | } | ||
34750 | /* | ||
34751 | * string basename(string $path[, string $suffix ]) | ||
34752 | * Returns trailing name component of path. | ||
34753 | * Parameters | ||
34754 | * $path | ||
34755 | * Target path. | ||
34756 | * On Windows, both slash (/) and backslash (\) are used as directory separator character. | ||
34757 | * In other environments, it is the forward slash (/). | ||
34758 | * $suffix | ||
34759 | * If the name component ends in suffix this will also be cut off. | ||
34760 | * Return | ||
34761 | * The base name of the given path. | ||
34762 | */ | ||
34763 | static int jx9Builtin_basename(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34764 | { | ||
34765 | const char *zPath, *zBase, *zEnd; | ||
34766 | int c, d, iLen; | ||
34767 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34768 | /* Missing/Invalid argument, return the empty string */ | ||
34769 | jx9_result_string(pCtx, "", 0); | ||
34770 | return JX9_OK; | ||
34771 | } | ||
34772 | c = d = '/'; | ||
34773 | #ifdef __WINNT__ | ||
34774 | d = '\\'; | ||
34775 | #endif | ||
34776 | /* Point to the target path */ | ||
34777 | zPath = jx9_value_to_string(apArg[0], &iLen); | ||
34778 | if( iLen < 1 ){ | ||
34779 | /* Empty string */ | ||
34780 | jx9_result_string(pCtx, "", 0); | ||
34781 | return JX9_OK; | ||
34782 | } | ||
34783 | /* Perform the requested operation */ | ||
34784 | zEnd = &zPath[iLen - 1]; | ||
34785 | /* Ignore trailing '/' */ | ||
34786 | while( zEnd > zPath && ( (int)zEnd[0] == c || (int)zEnd[0] == d ) ){ | ||
34787 | zEnd--; | ||
34788 | } | ||
34789 | iLen = (int)(&zEnd[1]-zPath); | ||
34790 | while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ | ||
34791 | zEnd--; | ||
34792 | } | ||
34793 | zBase = (zEnd > zPath) ? &zEnd[1] : zPath; | ||
34794 | zEnd = &zPath[iLen]; | ||
34795 | if( nArg > 1 && jx9_value_is_string(apArg[1]) ){ | ||
34796 | const char *zSuffix; | ||
34797 | int nSuffix; | ||
34798 | /* Strip suffix */ | ||
34799 | zSuffix = jx9_value_to_string(apArg[1], &nSuffix); | ||
34800 | if( nSuffix > 0 && nSuffix < iLen && SyMemcmp(&zEnd[-nSuffix], zSuffix, nSuffix) == 0 ){ | ||
34801 | zEnd -= nSuffix; | ||
34802 | } | ||
34803 | } | ||
34804 | /* Store the basename */ | ||
34805 | jx9_result_string(pCtx, zBase, (int)(zEnd-zBase)); | ||
34806 | return JX9_OK; | ||
34807 | } | ||
34808 | /* | ||
34809 | * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) | ||
34810 | * Returns information about a file path. | ||
34811 | * Parameter | ||
34812 | * $path | ||
34813 | * The path to be parsed. | ||
34814 | * $options | ||
34815 | * If present, specifies a specific element to be returned; one of | ||
34816 | * PATHINFO_DIRNAME, PATHINFO_BASENAME, PATHINFO_EXTENSION or PATHINFO_FILENAME. | ||
34817 | * Return | ||
34818 | * If the options parameter is not passed, an associative array containing the following | ||
34819 | * elements is returned: dirname, basename, extension (if any), and filename. | ||
34820 | * If options is present, returns a string containing the requested element. | ||
34821 | */ | ||
34822 | typedef struct path_info path_info; | ||
34823 | struct path_info | ||
34824 | { | ||
34825 | SyString sDir; /* Directory [i.e: /var/www] */ | ||
34826 | SyString sBasename; /* Basename [i.e httpd.conf] */ | ||
34827 | SyString sExtension; /* File extension [i.e xml, pdf..] */ | ||
34828 | SyString sFilename; /* Filename */ | ||
34829 | }; | ||
34830 | /* | ||
34831 | * Extract path fields. | ||
34832 | */ | ||
34833 | static sxi32 ExtractPathInfo(const char *zPath, int nByte, path_info *pOut) | ||
34834 | { | ||
34835 | const char *zPtr, *zEnd = &zPath[nByte - 1]; | ||
34836 | SyString *pCur; | ||
34837 | int c, d; | ||
34838 | c = d = '/'; | ||
34839 | #ifdef __WINNT__ | ||
34840 | d = '\\'; | ||
34841 | #endif | ||
34842 | /* Zero the structure */ | ||
34843 | SyZero(pOut, sizeof(path_info)); | ||
34844 | /* Handle special case */ | ||
34845 | if( nByte == sizeof(char) && ( (int)zPath[0] == c || (int)zPath[0] == d ) ){ | ||
34846 | #ifdef __WINNT__ | ||
34847 | SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char)); | ||
34848 | #else | ||
34849 | SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char)); | ||
34850 | #endif | ||
34851 | return SXRET_OK; | ||
34852 | } | ||
34853 | /* Extract the basename */ | ||
34854 | while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ | ||
34855 | zEnd--; | ||
34856 | } | ||
34857 | zPtr = (zEnd > zPath) ? &zEnd[1] : zPath; | ||
34858 | zEnd = &zPath[nByte]; | ||
34859 | /* dirname */ | ||
34860 | pCur = &pOut->sDir; | ||
34861 | SyStringInitFromBuf(pCur, zPath, zPtr-zPath); | ||
34862 | if( pCur->nByte > 1 ){ | ||
34863 | SyStringTrimTrailingChar(pCur, '/'); | ||
34864 | #ifdef __WINNT__ | ||
34865 | SyStringTrimTrailingChar(pCur, '\\'); | ||
34866 | #endif | ||
34867 | }else if( (int)zPath[0] == c || (int)zPath[0] == d ){ | ||
34868 | #ifdef __WINNT__ | ||
34869 | SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char)); | ||
34870 | #else | ||
34871 | SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char)); | ||
34872 | #endif | ||
34873 | } | ||
34874 | /* basename/filename */ | ||
34875 | pCur = &pOut->sBasename; | ||
34876 | SyStringInitFromBuf(pCur, zPtr, zEnd-zPtr); | ||
34877 | SyStringTrimLeadingChar(pCur, '/'); | ||
34878 | #ifdef __WINNT__ | ||
34879 | SyStringTrimLeadingChar(pCur, '\\'); | ||
34880 | #endif | ||
34881 | SyStringDupPtr(&pOut->sFilename, pCur); | ||
34882 | if( pCur->nByte > 0 ){ | ||
34883 | /* extension */ | ||
34884 | zEnd--; | ||
34885 | while( zEnd > pCur->zString /*basename*/ && zEnd[0] != '.' ){ | ||
34886 | zEnd--; | ||
34887 | } | ||
34888 | if( zEnd > pCur->zString ){ | ||
34889 | zEnd++; /* Jump leading dot */ | ||
34890 | SyStringInitFromBuf(&pOut->sExtension, zEnd, &zPath[nByte]-zEnd); | ||
34891 | /* Fix filename */ | ||
34892 | pCur = &pOut->sFilename; | ||
34893 | if( pCur->nByte > SyStringLength(&pOut->sExtension) ){ | ||
34894 | pCur->nByte -= 1 + SyStringLength(&pOut->sExtension); | ||
34895 | } | ||
34896 | } | ||
34897 | } | ||
34898 | return SXRET_OK; | ||
34899 | } | ||
34900 | /* | ||
34901 | * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) | ||
34902 | * See block comment above. | ||
34903 | */ | ||
34904 | static int jx9Builtin_pathinfo(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
34905 | { | ||
34906 | const char *zPath; | ||
34907 | path_info sInfo; | ||
34908 | SyString *pComp; | ||
34909 | int iLen; | ||
34910 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
34911 | /* Missing/Invalid argument, return the empty string */ | ||
34912 | jx9_result_string(pCtx, "", 0); | ||
34913 | return JX9_OK; | ||
34914 | } | ||
34915 | /* Point to the target path */ | ||
34916 | zPath = jx9_value_to_string(apArg[0], &iLen); | ||
34917 | if( iLen < 1 ){ | ||
34918 | /* Empty string */ | ||
34919 | jx9_result_string(pCtx, "", 0); | ||
34920 | return JX9_OK; | ||
34921 | } | ||
34922 | /* Extract path info */ | ||
34923 | ExtractPathInfo(zPath, iLen, &sInfo); | ||
34924 | if( nArg > 1 && jx9_value_is_int(apArg[1]) ){ | ||
34925 | /* Return path component */ | ||
34926 | int nComp = jx9_value_to_int(apArg[1]); | ||
34927 | switch(nComp){ | ||
34928 | case 1: /* PATHINFO_DIRNAME */ | ||
34929 | pComp = &sInfo.sDir; | ||
34930 | if( pComp->nByte > 0 ){ | ||
34931 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
34932 | }else{ | ||
34933 | /* Expand the empty string */ | ||
34934 | jx9_result_string(pCtx, "", 0); | ||
34935 | } | ||
34936 | break; | ||
34937 | case 2: /*PATHINFO_BASENAME*/ | ||
34938 | pComp = &sInfo.sBasename; | ||
34939 | if( pComp->nByte > 0 ){ | ||
34940 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
34941 | }else{ | ||
34942 | /* Expand the empty string */ | ||
34943 | jx9_result_string(pCtx, "", 0); | ||
34944 | } | ||
34945 | break; | ||
34946 | case 3: /*PATHINFO_EXTENSION*/ | ||
34947 | pComp = &sInfo.sExtension; | ||
34948 | if( pComp->nByte > 0 ){ | ||
34949 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
34950 | }else{ | ||
34951 | /* Expand the empty string */ | ||
34952 | jx9_result_string(pCtx, "", 0); | ||
34953 | } | ||
34954 | break; | ||
34955 | case 4: /*PATHINFO_FILENAME*/ | ||
34956 | pComp = &sInfo.sFilename; | ||
34957 | if( pComp->nByte > 0 ){ | ||
34958 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
34959 | }else{ | ||
34960 | /* Expand the empty string */ | ||
34961 | jx9_result_string(pCtx, "", 0); | ||
34962 | } | ||
34963 | break; | ||
34964 | default: | ||
34965 | /* Expand the empty string */ | ||
34966 | jx9_result_string(pCtx, "", 0); | ||
34967 | break; | ||
34968 | } | ||
34969 | }else{ | ||
34970 | /* Return an associative array */ | ||
34971 | jx9_value *pArray, *pValue; | ||
34972 | pArray = jx9_context_new_array(pCtx); | ||
34973 | pValue = jx9_context_new_scalar(pCtx); | ||
34974 | if( pArray == 0 || pValue == 0 ){ | ||
34975 | /* Out of mem, return NULL */ | ||
34976 | jx9_result_bool(pCtx, 0); | ||
34977 | return JX9_OK; | ||
34978 | } | ||
34979 | /* dirname */ | ||
34980 | pComp = &sInfo.sDir; | ||
34981 | if( pComp->nByte > 0 ){ | ||
34982 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
34983 | /* Perform the insertion */ | ||
34984 | jx9_array_add_strkey_elem(pArray, "dirname", pValue); /* Will make it's own copy */ | ||
34985 | } | ||
34986 | /* Reset the string cursor */ | ||
34987 | jx9_value_reset_string_cursor(pValue); | ||
34988 | /* basername */ | ||
34989 | pComp = &sInfo.sBasename; | ||
34990 | if( pComp->nByte > 0 ){ | ||
34991 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
34992 | /* Perform the insertion */ | ||
34993 | jx9_array_add_strkey_elem(pArray, "basename", pValue); /* Will make it's own copy */ | ||
34994 | } | ||
34995 | /* Reset the string cursor */ | ||
34996 | jx9_value_reset_string_cursor(pValue); | ||
34997 | /* extension */ | ||
34998 | pComp = &sInfo.sExtension; | ||
34999 | if( pComp->nByte > 0 ){ | ||
35000 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
35001 | /* Perform the insertion */ | ||
35002 | jx9_array_add_strkey_elem(pArray, "extension", pValue); /* Will make it's own copy */ | ||
35003 | } | ||
35004 | /* Reset the string cursor */ | ||
35005 | jx9_value_reset_string_cursor(pValue); | ||
35006 | /* filename */ | ||
35007 | pComp = &sInfo.sFilename; | ||
35008 | if( pComp->nByte > 0 ){ | ||
35009 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
35010 | /* Perform the insertion */ | ||
35011 | jx9_array_add_strkey_elem(pArray, "filename", pValue); /* Will make it's own copy */ | ||
35012 | } | ||
35013 | /* Return the created array */ | ||
35014 | jx9_result_value(pCtx, pArray); | ||
35015 | /* Don't worry about freeing memory, everything will be released | ||
35016 | * automatically as soon we return from this foreign function. | ||
35017 | */ | ||
35018 | } | ||
35019 | return JX9_OK; | ||
35020 | } | ||
35021 | /* | ||
35022 | * Globbing implementation extracted from the sqlite3 source tree. | ||
35023 | * Original author: D. Richard Hipp (http://www.sqlite.org) | ||
35024 | * Status: Public Domain | ||
35025 | */ | ||
35026 | typedef unsigned char u8; | ||
35027 | /* An array to map all upper-case characters into their corresponding | ||
35028 | ** lower-case character. | ||
35029 | ** | ||
35030 | ** SQLite only considers US-ASCII (or EBCDIC) characters. We do not | ||
35031 | ** handle case conversions for the UTF character set since the tables | ||
35032 | ** involved are nearly as big or bigger than SQLite itself. | ||
35033 | */ | ||
35034 | static const unsigned char sqlite3UpperToLower[] = { | ||
35035 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, | ||
35036 | 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, | ||
35037 | 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, | ||
35038 | 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, | ||
35039 | 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, | ||
35040 | 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, | ||
35041 | 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, | ||
35042 | 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, | ||
35043 | 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, | ||
35044 | 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, | ||
35045 | 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, | ||
35046 | 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, | ||
35047 | 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, | ||
35048 | 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, | ||
35049 | 252, 253, 254, 255 | ||
35050 | }; | ||
35051 | #define GlogUpperToLower(A) if( A<0x80 ){ A = sqlite3UpperToLower[A]; } | ||
35052 | /* | ||
35053 | ** Assuming zIn points to the first byte of a UTF-8 character, | ||
35054 | ** advance zIn to point to the first byte of the next UTF-8 character. | ||
35055 | */ | ||
35056 | #define SQLITE_SKIP_UTF8(zIn) { \ | ||
35057 | if( (*(zIn++))>=0xc0 ){ \ | ||
35058 | while( (*zIn & 0xc0)==0x80 ){ zIn++; } \ | ||
35059 | } \ | ||
35060 | } | ||
35061 | /* | ||
35062 | ** Compare two UTF-8 strings for equality where the first string can | ||
35063 | ** potentially be a "glob" expression. Return true (1) if they | ||
35064 | ** are the same and false (0) if they are different. | ||
35065 | ** | ||
35066 | ** Globbing rules: | ||
35067 | ** | ||
35068 | ** '*' Matches any sequence of zero or more characters. | ||
35069 | ** | ||
35070 | ** '?' Matches exactly one character. | ||
35071 | ** | ||
35072 | ** [...] Matches one character from the enclosed list of | ||
35073 | ** characters. | ||
35074 | ** | ||
35075 | ** [^...] Matches one character not in the enclosed list. | ||
35076 | ** | ||
35077 | ** With the [...] and [^...] matching, a ']' character can be included | ||
35078 | ** in the list by making it the first character after '[' or '^'. A | ||
35079 | ** range of characters can be specified using '-'. Example: | ||
35080 | ** "[a-z]" matches any single lower-case letter. To match a '-', make | ||
35081 | ** it the last character in the list. | ||
35082 | ** | ||
35083 | ** This routine is usually quick, but can be N**2 in the worst case. | ||
35084 | ** | ||
35085 | ** Hints: to match '*' or '?', put them in "[]". Like this: | ||
35086 | ** | ||
35087 | ** abc[*]xyz Matches "abc*xyz" only | ||
35088 | */ | ||
35089 | static int patternCompare( | ||
35090 | const u8 *zPattern, /* The glob pattern */ | ||
35091 | const u8 *zString, /* The string to compare against the glob */ | ||
35092 | const int esc, /* The escape character */ | ||
35093 | int noCase | ||
35094 | ){ | ||
35095 | int c, c2; | ||
35096 | int invert; | ||
35097 | int seen; | ||
35098 | u8 matchOne = '?'; | ||
35099 | u8 matchAll = '*'; | ||
35100 | u8 matchSet = '['; | ||
35101 | int prevEscape = 0; /* True if the previous character was 'escape' */ | ||
35102 | |||
35103 | if( !zPattern || !zString ) return 0; | ||
35104 | while( (c = jx9Utf8Read(zPattern, 0, &zPattern))!=0 ){ | ||
35105 | if( !prevEscape && c==matchAll ){ | ||
35106 | while( (c= jx9Utf8Read(zPattern, 0, &zPattern)) == matchAll | ||
35107 | || c == matchOne ){ | ||
35108 | if( c==matchOne && jx9Utf8Read(zString, 0, &zString)==0 ){ | ||
35109 | return 0; | ||
35110 | } | ||
35111 | } | ||
35112 | if( c==0 ){ | ||
35113 | return 1; | ||
35114 | }else if( c==esc ){ | ||
35115 | c = jx9Utf8Read(zPattern, 0, &zPattern); | ||
35116 | if( c==0 ){ | ||
35117 | return 0; | ||
35118 | } | ||
35119 | }else if( c==matchSet ){ | ||
35120 | if( (esc==0) || (matchSet<0x80) ) return 0; | ||
35121 | while( *zString && patternCompare(&zPattern[-1], zString, esc, noCase)==0 ){ | ||
35122 | SQLITE_SKIP_UTF8(zString); | ||
35123 | } | ||
35124 | return *zString!=0; | ||
35125 | } | ||
35126 | while( (c2 = jx9Utf8Read(zString, 0, &zString))!=0 ){ | ||
35127 | if( noCase ){ | ||
35128 | GlogUpperToLower(c2); | ||
35129 | GlogUpperToLower(c); | ||
35130 | while( c2 != 0 && c2 != c ){ | ||
35131 | c2 = jx9Utf8Read(zString, 0, &zString); | ||
35132 | GlogUpperToLower(c2); | ||
35133 | } | ||
35134 | }else{ | ||
35135 | while( c2 != 0 && c2 != c ){ | ||
35136 | c2 = jx9Utf8Read(zString, 0, &zString); | ||
35137 | } | ||
35138 | } | ||
35139 | if( c2==0 ) return 0; | ||
35140 | if( patternCompare(zPattern, zString, esc, noCase) ) return 1; | ||
35141 | } | ||
35142 | return 0; | ||
35143 | }else if( !prevEscape && c==matchOne ){ | ||
35144 | if( jx9Utf8Read(zString, 0, &zString)==0 ){ | ||
35145 | return 0; | ||
35146 | } | ||
35147 | }else if( c==matchSet ){ | ||
35148 | int prior_c = 0; | ||
35149 | if( esc == 0 ) return 0; | ||
35150 | seen = 0; | ||
35151 | invert = 0; | ||
35152 | c = jx9Utf8Read(zString, 0, &zString); | ||
35153 | if( c==0 ) return 0; | ||
35154 | c2 = jx9Utf8Read(zPattern, 0, &zPattern); | ||
35155 | if( c2=='^' ){ | ||
35156 | invert = 1; | ||
35157 | c2 = jx9Utf8Read(zPattern, 0, &zPattern); | ||
35158 | } | ||
35159 | if( c2==']' ){ | ||
35160 | if( c==']' ) seen = 1; | ||
35161 | c2 = jx9Utf8Read(zPattern, 0, &zPattern); | ||
35162 | } | ||
35163 | while( c2 && c2!=']' ){ | ||
35164 | if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ | ||
35165 | c2 = jx9Utf8Read(zPattern, 0, &zPattern); | ||
35166 | if( c>=prior_c && c<=c2 ) seen = 1; | ||
35167 | prior_c = 0; | ||
35168 | }else{ | ||
35169 | if( c==c2 ){ | ||
35170 | seen = 1; | ||
35171 | } | ||
35172 | prior_c = c2; | ||
35173 | } | ||
35174 | c2 = jx9Utf8Read(zPattern, 0, &zPattern); | ||
35175 | } | ||
35176 | if( c2==0 || (seen ^ invert)==0 ){ | ||
35177 | return 0; | ||
35178 | } | ||
35179 | }else if( esc==c && !prevEscape ){ | ||
35180 | prevEscape = 1; | ||
35181 | }else{ | ||
35182 | c2 = jx9Utf8Read(zString, 0, &zString); | ||
35183 | if( noCase ){ | ||
35184 | GlogUpperToLower(c); | ||
35185 | GlogUpperToLower(c2); | ||
35186 | } | ||
35187 | if( c!=c2 ){ | ||
35188 | return 0; | ||
35189 | } | ||
35190 | prevEscape = 0; | ||
35191 | } | ||
35192 | } | ||
35193 | return *zString==0; | ||
35194 | } | ||
35195 | /* | ||
35196 | * Wrapper around patternCompare() defined above. | ||
35197 | * See block comment above for more information. | ||
35198 | */ | ||
35199 | static int Glob(const unsigned char *zPattern, const unsigned char *zString, int iEsc, int CaseCompare) | ||
35200 | { | ||
35201 | int rc; | ||
35202 | if( iEsc < 0 ){ | ||
35203 | iEsc = '\\'; | ||
35204 | } | ||
35205 | rc = patternCompare(zPattern, zString, iEsc, CaseCompare); | ||
35206 | return rc; | ||
35207 | } | ||
35208 | /* | ||
35209 | * bool fnmatch(string $pattern, string $string[, int $flags = 0 ]) | ||
35210 | * Match filename against a pattern. | ||
35211 | * Parameters | ||
35212 | * $pattern | ||
35213 | * The shell wildcard pattern. | ||
35214 | * $string | ||
35215 | * The tested string. | ||
35216 | * $flags | ||
35217 | * A list of possible flags: | ||
35218 | * FNM_NOESCAPE Disable backslash escaping. | ||
35219 | * FNM_PATHNAME Slash in string only matches slash in the given pattern. | ||
35220 | * FNM_PERIOD Leading period in string must be exactly matched by period in the given pattern. | ||
35221 | * FNM_CASEFOLD Caseless match. | ||
35222 | * Return | ||
35223 | * TRUE if there is a match, FALSE otherwise. | ||
35224 | */ | ||
35225 | static int jx9Builtin_fnmatch(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35226 | { | ||
35227 | const char *zString, *zPattern; | ||
35228 | int iEsc = '\\'; | ||
35229 | int noCase = 0; | ||
35230 | int rc; | ||
35231 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ | ||
35232 | /* Missing/Invalid arguments, return FALSE */ | ||
35233 | jx9_result_bool(pCtx, 0); | ||
35234 | return JX9_OK; | ||
35235 | } | ||
35236 | /* Extract the pattern and the string */ | ||
35237 | zPattern = jx9_value_to_string(apArg[0], 0); | ||
35238 | zString = jx9_value_to_string(apArg[1], 0); | ||
35239 | /* Extract the flags if avaialble */ | ||
35240 | if( nArg > 2 && jx9_value_is_int(apArg[2]) ){ | ||
35241 | rc = jx9_value_to_int(apArg[2]); | ||
35242 | if( rc & 0x01 /*FNM_NOESCAPE*/){ | ||
35243 | iEsc = 0; | ||
35244 | } | ||
35245 | if( rc & 0x08 /*FNM_CASEFOLD*/){ | ||
35246 | noCase = 1; | ||
35247 | } | ||
35248 | } | ||
35249 | /* Go globbing */ | ||
35250 | rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, noCase); | ||
35251 | /* Globbing result */ | ||
35252 | jx9_result_bool(pCtx, rc); | ||
35253 | return JX9_OK; | ||
35254 | } | ||
35255 | /* | ||
35256 | * bool strglob(string $pattern, string $string) | ||
35257 | * Match string against a pattern. | ||
35258 | * Parameters | ||
35259 | * $pattern | ||
35260 | * The shell wildcard pattern. | ||
35261 | * $string | ||
35262 | * The tested string. | ||
35263 | * Return | ||
35264 | * TRUE if there is a match, FALSE otherwise. | ||
35265 | */ | ||
35266 | static int jx9Builtin_strglob(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35267 | { | ||
35268 | const char *zString, *zPattern; | ||
35269 | int iEsc = '\\'; | ||
35270 | int rc; | ||
35271 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ | ||
35272 | /* Missing/Invalid arguments, return FALSE */ | ||
35273 | jx9_result_bool(pCtx, 0); | ||
35274 | return JX9_OK; | ||
35275 | } | ||
35276 | /* Extract the pattern and the string */ | ||
35277 | zPattern = jx9_value_to_string(apArg[0], 0); | ||
35278 | zString = jx9_value_to_string(apArg[1], 0); | ||
35279 | /* Go globbing */ | ||
35280 | rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, 0); | ||
35281 | /* Globbing result */ | ||
35282 | jx9_result_bool(pCtx, rc); | ||
35283 | return JX9_OK; | ||
35284 | } | ||
35285 | /* | ||
35286 | * bool link(string $target, string $link) | ||
35287 | * Create a hard link. | ||
35288 | * Parameters | ||
35289 | * $target | ||
35290 | * Target of the link. | ||
35291 | * $link | ||
35292 | * The link name. | ||
35293 | * Return | ||
35294 | * TRUE on success or FALSE on failure. | ||
35295 | */ | ||
35296 | static int jx9Vfs_link(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35297 | { | ||
35298 | const char *zTarget, *zLink; | ||
35299 | jx9_vfs *pVfs; | ||
35300 | int rc; | ||
35301 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ | ||
35302 | /* Missing/Invalid arguments, return FALSE */ | ||
35303 | jx9_result_bool(pCtx, 0); | ||
35304 | return JX9_OK; | ||
35305 | } | ||
35306 | /* Point to the underlying vfs */ | ||
35307 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35308 | if( pVfs == 0 || pVfs->xLink == 0 ){ | ||
35309 | /* IO routine not implemented, return NULL */ | ||
35310 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35311 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
35312 | jx9_function_name(pCtx) | ||
35313 | ); | ||
35314 | jx9_result_bool(pCtx, 0); | ||
35315 | return JX9_OK; | ||
35316 | } | ||
35317 | /* Extract the given arguments */ | ||
35318 | zTarget = jx9_value_to_string(apArg[0], 0); | ||
35319 | zLink = jx9_value_to_string(apArg[1], 0); | ||
35320 | /* Perform the requested operation */ | ||
35321 | rc = pVfs->xLink(zTarget, zLink, 0/*Not a symbolic link */); | ||
35322 | /* IO result */ | ||
35323 | jx9_result_bool(pCtx, rc == JX9_OK ); | ||
35324 | return JX9_OK; | ||
35325 | } | ||
35326 | /* | ||
35327 | * bool symlink(string $target, string $link) | ||
35328 | * Creates a symbolic link. | ||
35329 | * Parameters | ||
35330 | * $target | ||
35331 | * Target of the link. | ||
35332 | * $link | ||
35333 | * The link name. | ||
35334 | * Return | ||
35335 | * TRUE on success or FALSE on failure. | ||
35336 | */ | ||
35337 | static int jx9Vfs_symlink(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35338 | { | ||
35339 | const char *zTarget, *zLink; | ||
35340 | jx9_vfs *pVfs; | ||
35341 | int rc; | ||
35342 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ | ||
35343 | /* Missing/Invalid arguments, return FALSE */ | ||
35344 | jx9_result_bool(pCtx, 0); | ||
35345 | return JX9_OK; | ||
35346 | } | ||
35347 | /* Point to the underlying vfs */ | ||
35348 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35349 | if( pVfs == 0 || pVfs->xLink == 0 ){ | ||
35350 | /* IO routine not implemented, return NULL */ | ||
35351 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35352 | "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", | ||
35353 | jx9_function_name(pCtx) | ||
35354 | ); | ||
35355 | jx9_result_bool(pCtx, 0); | ||
35356 | return JX9_OK; | ||
35357 | } | ||
35358 | /* Extract the given arguments */ | ||
35359 | zTarget = jx9_value_to_string(apArg[0], 0); | ||
35360 | zLink = jx9_value_to_string(apArg[1], 0); | ||
35361 | /* Perform the requested operation */ | ||
35362 | rc = pVfs->xLink(zTarget, zLink, 1/*A symbolic link */); | ||
35363 | /* IO result */ | ||
35364 | jx9_result_bool(pCtx, rc == JX9_OK ); | ||
35365 | return JX9_OK; | ||
35366 | } | ||
35367 | /* | ||
35368 | * int umask([ int $mask ]) | ||
35369 | * Changes the current umask. | ||
35370 | * Parameters | ||
35371 | * $mask | ||
35372 | * The new umask. | ||
35373 | * Return | ||
35374 | * umask() without arguments simply returns the current umask. | ||
35375 | * Otherwise the old umask is returned. | ||
35376 | */ | ||
35377 | static int jx9Vfs_umask(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35378 | { | ||
35379 | int iOld, iNew; | ||
35380 | jx9_vfs *pVfs; | ||
35381 | /* Point to the underlying vfs */ | ||
35382 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35383 | if( pVfs == 0 || pVfs->xUmask == 0 ){ | ||
35384 | /* IO routine not implemented, return -1 */ | ||
35385 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35386 | "IO routine(%s) not implemented in the underlying VFS", | ||
35387 | jx9_function_name(pCtx) | ||
35388 | ); | ||
35389 | jx9_result_int(pCtx, 0); | ||
35390 | return JX9_OK; | ||
35391 | } | ||
35392 | iNew = 0; | ||
35393 | if( nArg > 0 ){ | ||
35394 | iNew = jx9_value_to_int(apArg[0]); | ||
35395 | } | ||
35396 | /* Perform the requested operation */ | ||
35397 | iOld = pVfs->xUmask(iNew); | ||
35398 | /* Old mask */ | ||
35399 | jx9_result_int(pCtx, iOld); | ||
35400 | return JX9_OK; | ||
35401 | } | ||
35402 | /* | ||
35403 | * string sys_get_temp_dir() | ||
35404 | * Returns directory path used for temporary files. | ||
35405 | * Parameters | ||
35406 | * None | ||
35407 | * Return | ||
35408 | * Returns the path of the temporary directory. | ||
35409 | */ | ||
35410 | static int jx9Vfs_sys_get_temp_dir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35411 | { | ||
35412 | jx9_vfs *pVfs; | ||
35413 | /* Set the empty string as the default return value */ | ||
35414 | jx9_result_string(pCtx, "", 0); | ||
35415 | /* Point to the underlying vfs */ | ||
35416 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35417 | if( pVfs == 0 || pVfs->xTempDir == 0 ){ | ||
35418 | SXUNUSED(nArg); /* cc warning */ | ||
35419 | SXUNUSED(apArg); | ||
35420 | /* IO routine not implemented, return "" */ | ||
35421 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35422 | "IO routine(%s) not implemented in the underlying VFS", | ||
35423 | jx9_function_name(pCtx) | ||
35424 | ); | ||
35425 | return JX9_OK; | ||
35426 | } | ||
35427 | /* Perform the requested operation */ | ||
35428 | pVfs->xTempDir(pCtx); | ||
35429 | return JX9_OK; | ||
35430 | } | ||
35431 | /* | ||
35432 | * string get_current_user() | ||
35433 | * Returns the name of the current working user. | ||
35434 | * Parameters | ||
35435 | * None | ||
35436 | * Return | ||
35437 | * Returns the name of the current working user. | ||
35438 | */ | ||
35439 | static int jx9Vfs_get_current_user(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35440 | { | ||
35441 | jx9_vfs *pVfs; | ||
35442 | /* Point to the underlying vfs */ | ||
35443 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35444 | if( pVfs == 0 || pVfs->xUsername == 0 ){ | ||
35445 | SXUNUSED(nArg); /* cc warning */ | ||
35446 | SXUNUSED(apArg); | ||
35447 | /* IO routine not implemented */ | ||
35448 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35449 | "IO routine(%s) not implemented in the underlying VFS", | ||
35450 | jx9_function_name(pCtx) | ||
35451 | ); | ||
35452 | /* Set a dummy username */ | ||
35453 | jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); | ||
35454 | return JX9_OK; | ||
35455 | } | ||
35456 | /* Perform the requested operation */ | ||
35457 | pVfs->xUsername(pCtx); | ||
35458 | return JX9_OK; | ||
35459 | } | ||
35460 | /* | ||
35461 | * int64 getmypid() | ||
35462 | * Gets process ID. | ||
35463 | * Parameters | ||
35464 | * None | ||
35465 | * Return | ||
35466 | * Returns the process ID. | ||
35467 | */ | ||
35468 | static int jx9Vfs_getmypid(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35469 | { | ||
35470 | jx9_int64 nProcessId; | ||
35471 | jx9_vfs *pVfs; | ||
35472 | /* Point to the underlying vfs */ | ||
35473 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35474 | if( pVfs == 0 || pVfs->xProcessId == 0 ){ | ||
35475 | SXUNUSED(nArg); /* cc warning */ | ||
35476 | SXUNUSED(apArg); | ||
35477 | /* IO routine not implemented, return -1 */ | ||
35478 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35479 | "IO routine(%s) not implemented in the underlying VFS", | ||
35480 | jx9_function_name(pCtx) | ||
35481 | ); | ||
35482 | jx9_result_int(pCtx, -1); | ||
35483 | return JX9_OK; | ||
35484 | } | ||
35485 | /* Perform the requested operation */ | ||
35486 | nProcessId = (jx9_int64)pVfs->xProcessId(); | ||
35487 | /* Set the result */ | ||
35488 | jx9_result_int64(pCtx, nProcessId); | ||
35489 | return JX9_OK; | ||
35490 | } | ||
35491 | /* | ||
35492 | * int getmyuid() | ||
35493 | * Get user ID. | ||
35494 | * Parameters | ||
35495 | * None | ||
35496 | * Return | ||
35497 | * Returns the user ID. | ||
35498 | */ | ||
35499 | static int jx9Vfs_getmyuid(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35500 | { | ||
35501 | jx9_vfs *pVfs; | ||
35502 | int nUid; | ||
35503 | /* Point to the underlying vfs */ | ||
35504 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35505 | if( pVfs == 0 || pVfs->xUid == 0 ){ | ||
35506 | SXUNUSED(nArg); /* cc warning */ | ||
35507 | SXUNUSED(apArg); | ||
35508 | /* IO routine not implemented, return -1 */ | ||
35509 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35510 | "IO routine(%s) not implemented in the underlying VFS", | ||
35511 | jx9_function_name(pCtx) | ||
35512 | ); | ||
35513 | jx9_result_int(pCtx, -1); | ||
35514 | return JX9_OK; | ||
35515 | } | ||
35516 | /* Perform the requested operation */ | ||
35517 | nUid = pVfs->xUid(); | ||
35518 | /* Set the result */ | ||
35519 | jx9_result_int(pCtx, nUid); | ||
35520 | return JX9_OK; | ||
35521 | } | ||
35522 | /* | ||
35523 | * int getmygid() | ||
35524 | * Get group ID. | ||
35525 | * Parameters | ||
35526 | * None | ||
35527 | * Return | ||
35528 | * Returns the group ID. | ||
35529 | */ | ||
35530 | static int jx9Vfs_getmygid(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35531 | { | ||
35532 | jx9_vfs *pVfs; | ||
35533 | int nGid; | ||
35534 | /* Point to the underlying vfs */ | ||
35535 | pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); | ||
35536 | if( pVfs == 0 || pVfs->xGid == 0 ){ | ||
35537 | SXUNUSED(nArg); /* cc warning */ | ||
35538 | SXUNUSED(apArg); | ||
35539 | /* IO routine not implemented, return -1 */ | ||
35540 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35541 | "IO routine(%s) not implemented in the underlying VFS", | ||
35542 | jx9_function_name(pCtx) | ||
35543 | ); | ||
35544 | jx9_result_int(pCtx, -1); | ||
35545 | return JX9_OK; | ||
35546 | } | ||
35547 | /* Perform the requested operation */ | ||
35548 | nGid = pVfs->xGid(); | ||
35549 | /* Set the result */ | ||
35550 | jx9_result_int(pCtx, nGid); | ||
35551 | return JX9_OK; | ||
35552 | } | ||
35553 | #ifdef __WINNT__ | ||
35554 | #include <Windows.h> | ||
35555 | #elif defined(__UNIXES__) | ||
35556 | #include <sys/utsname.h> | ||
35557 | #endif | ||
35558 | /* | ||
35559 | * string uname([ string $mode = "a" ]) | ||
35560 | * Returns information about the host operating system. | ||
35561 | * Parameters | ||
35562 | * $mode | ||
35563 | * mode is a single character that defines what information is returned: | ||
35564 | * 'a': This is the default. Contains all modes in the sequence "s n r v m". | ||
35565 | * 's': Operating system name. eg. FreeBSD. | ||
35566 | * 'n': Host name. eg. localhost.example.com. | ||
35567 | * 'r': Release name. eg. 5.1.2-RELEASE. | ||
35568 | * 'v': Version information. Varies a lot between operating systems. | ||
35569 | * 'm': Machine type. eg. i386. | ||
35570 | * Return | ||
35571 | * OS description as a string. | ||
35572 | */ | ||
35573 | static int jx9Vfs_uname(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35574 | { | ||
35575 | #if defined(__WINNT__) | ||
35576 | const char *zName = "Microsoft Windows"; | ||
35577 | OSVERSIONINFOW sVer; | ||
35578 | #elif defined(__UNIXES__) | ||
35579 | struct utsname sName; | ||
35580 | #endif | ||
35581 | const char *zMode = "a"; | ||
35582 | if( nArg > 0 && jx9_value_is_string(apArg[0]) ){ | ||
35583 | /* Extract the desired mode */ | ||
35584 | zMode = jx9_value_to_string(apArg[0], 0); | ||
35585 | } | ||
35586 | #if defined(__WINNT__) | ||
35587 | sVer.dwOSVersionInfoSize = sizeof(sVer); | ||
35588 | if( TRUE != GetVersionExW(&sVer)){ | ||
35589 | jx9_result_string(pCtx, zName, -1); | ||
35590 | return JX9_OK; | ||
35591 | } | ||
35592 | if( sVer.dwPlatformId == VER_PLATFORM_WIN32_NT ){ | ||
35593 | if( sVer.dwMajorVersion <= 4 ){ | ||
35594 | zName = "Microsoft Windows NT"; | ||
35595 | }else if( sVer.dwMajorVersion == 5 ){ | ||
35596 | switch(sVer.dwMinorVersion){ | ||
35597 | case 0: zName = "Microsoft Windows 2000"; break; | ||
35598 | case 1: zName = "Microsoft Windows XP"; break; | ||
35599 | case 2: zName = "Microsoft Windows Server 2003"; break; | ||
35600 | } | ||
35601 | }else if( sVer.dwMajorVersion == 6){ | ||
35602 | switch(sVer.dwMinorVersion){ | ||
35603 | case 0: zName = "Microsoft Windows Vista"; break; | ||
35604 | case 1: zName = "Microsoft Windows 7"; break; | ||
35605 | case 2: zName = "Microsoft Windows 8"; break; | ||
35606 | default: break; | ||
35607 | } | ||
35608 | } | ||
35609 | } | ||
35610 | switch(zMode[0]){ | ||
35611 | case 's': | ||
35612 | /* Operating system name */ | ||
35613 | jx9_result_string(pCtx, zName, -1/* Compute length automatically*/); | ||
35614 | break; | ||
35615 | case 'n': | ||
35616 | /* Host name */ | ||
35617 | jx9_result_string(pCtx, "localhost", (int)sizeof("localhost")-1); | ||
35618 | break; | ||
35619 | case 'r': | ||
35620 | case 'v': | ||
35621 | /* Version information. */ | ||
35622 | jx9_result_string_format(pCtx, "%u.%u build %u", | ||
35623 | sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber | ||
35624 | ); | ||
35625 | break; | ||
35626 | case 'm': | ||
35627 | /* Machine name */ | ||
35628 | jx9_result_string(pCtx, "x86", (int)sizeof("x86")-1); | ||
35629 | break; | ||
35630 | default: | ||
35631 | jx9_result_string_format(pCtx, "%s localhost %u.%u build %u x86", | ||
35632 | zName, | ||
35633 | sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber | ||
35634 | ); | ||
35635 | break; | ||
35636 | } | ||
35637 | #elif defined(__UNIXES__) | ||
35638 | if( uname(&sName) != 0 ){ | ||
35639 | jx9_result_string(pCtx, "Unix", (int)sizeof("Unix")-1); | ||
35640 | return JX9_OK; | ||
35641 | } | ||
35642 | switch(zMode[0]){ | ||
35643 | case 's': | ||
35644 | /* Operating system name */ | ||
35645 | jx9_result_string(pCtx, sName.sysname, -1/* Compute length automatically*/); | ||
35646 | break; | ||
35647 | case 'n': | ||
35648 | /* Host name */ | ||
35649 | jx9_result_string(pCtx, sName.nodename, -1/* Compute length automatically*/); | ||
35650 | break; | ||
35651 | case 'r': | ||
35652 | /* Release information */ | ||
35653 | jx9_result_string(pCtx, sName.release, -1/* Compute length automatically*/); | ||
35654 | break; | ||
35655 | case 'v': | ||
35656 | /* Version information. */ | ||
35657 | jx9_result_string(pCtx, sName.version, -1/* Compute length automatically*/); | ||
35658 | break; | ||
35659 | case 'm': | ||
35660 | /* Machine name */ | ||
35661 | jx9_result_string(pCtx, sName.machine, -1/* Compute length automatically*/); | ||
35662 | break; | ||
35663 | default: | ||
35664 | jx9_result_string_format(pCtx, | ||
35665 | "%s %s %s %s %s", | ||
35666 | sName.sysname, | ||
35667 | sName.release, | ||
35668 | sName.version, | ||
35669 | sName.nodename, | ||
35670 | sName.machine | ||
35671 | ); | ||
35672 | break; | ||
35673 | } | ||
35674 | #else | ||
35675 | jx9_result_string(pCtx, "Host Operating System/localhost", (int)sizeof("Host Operating System/localhost")-1); | ||
35676 | #endif | ||
35677 | return JX9_OK; | ||
35678 | } | ||
35679 | /* | ||
35680 | * Section: | ||
35681 | * IO stream implementation. | ||
35682 | * Authors: | ||
35683 | * Symisc Systems, devel@symisc.net. | ||
35684 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
35685 | * Status: | ||
35686 | * Stable. | ||
35687 | */ | ||
35688 | typedef struct io_private io_private; | ||
35689 | struct io_private | ||
35690 | { | ||
35691 | const jx9_io_stream *pStream; /* Underlying IO device */ | ||
35692 | void *pHandle; /* IO handle */ | ||
35693 | /* Unbuffered IO */ | ||
35694 | SyBlob sBuffer; /* Working buffer */ | ||
35695 | sxu32 nOfft; /* Current read offset */ | ||
35696 | sxu32 iMagic; /* Sanity check to avoid misuse */ | ||
35697 | }; | ||
35698 | #define IO_PRIVATE_MAGIC 0xFEAC14 | ||
35699 | /* Make sure we are dealing with a valid io_private instance */ | ||
35700 | #define IO_PRIVATE_INVALID(IO) ( IO == 0 || IO->iMagic != IO_PRIVATE_MAGIC ) | ||
35701 | /* Forward declaration */ | ||
35702 | static void ResetIOPrivate(io_private *pDev); | ||
35703 | /* | ||
35704 | * bool ftruncate(resource $handle, int64 $size) | ||
35705 | * Truncates a file to a given length. | ||
35706 | * Parameters | ||
35707 | * $handle | ||
35708 | * The file pointer. | ||
35709 | * Note: | ||
35710 | * The handle must be open for writing. | ||
35711 | * $size | ||
35712 | * The size to truncate to. | ||
35713 | * Return | ||
35714 | * TRUE on success or FALSE on failure. | ||
35715 | */ | ||
35716 | static int jx9Builtin_ftruncate(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35717 | { | ||
35718 | const jx9_io_stream *pStream; | ||
35719 | io_private *pDev; | ||
35720 | int rc; | ||
35721 | if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ | ||
35722 | /* Missing/Invalid arguments, return FALSE */ | ||
35723 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35724 | jx9_result_bool(pCtx, 0); | ||
35725 | return JX9_OK; | ||
35726 | } | ||
35727 | /* Extract our private data */ | ||
35728 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
35729 | /* Make sure we are dealing with a valid io_private instance */ | ||
35730 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
35731 | /*Expecting an IO handle */ | ||
35732 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35733 | jx9_result_bool(pCtx, 0); | ||
35734 | return JX9_OK; | ||
35735 | } | ||
35736 | /* Point to the target IO stream device */ | ||
35737 | pStream = pDev->pStream; | ||
35738 | if( pStream == 0 || pStream->xTrunc == 0){ | ||
35739 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35740 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
35741 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
35742 | ); | ||
35743 | jx9_result_bool(pCtx, 0); | ||
35744 | return JX9_OK; | ||
35745 | } | ||
35746 | /* Perform the requested operation */ | ||
35747 | rc = pStream->xTrunc(pDev->pHandle, jx9_value_to_int64(apArg[1])); | ||
35748 | if( rc == JX9_OK ){ | ||
35749 | /* Discard buffered data */ | ||
35750 | ResetIOPrivate(pDev); | ||
35751 | } | ||
35752 | /* IO result */ | ||
35753 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
35754 | return JX9_OK; | ||
35755 | } | ||
35756 | /* | ||
35757 | * int fseek(resource $handle, int $offset[, int $whence = SEEK_SET ]) | ||
35758 | * Seeks on a file pointer. | ||
35759 | * Parameters | ||
35760 | * $handle | ||
35761 | * A file system pointer resource that is typically created using fopen(). | ||
35762 | * $offset | ||
35763 | * The offset. | ||
35764 | * To move to a position before the end-of-file, you need to pass a negative | ||
35765 | * value in offset and set whence to SEEK_END. | ||
35766 | * whence | ||
35767 | * whence values are: | ||
35768 | * SEEK_SET - Set position equal to offset bytes. | ||
35769 | * SEEK_CUR - Set position to current location plus offset. | ||
35770 | * SEEK_END - Set position to end-of-file plus offset. | ||
35771 | * Return | ||
35772 | * 0 on success, -1 on failure | ||
35773 | */ | ||
35774 | static int jx9Builtin_fseek(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35775 | { | ||
35776 | const jx9_io_stream *pStream; | ||
35777 | io_private *pDev; | ||
35778 | jx9_int64 iOfft; | ||
35779 | int whence; | ||
35780 | int rc; | ||
35781 | if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ | ||
35782 | /* Missing/Invalid arguments, return FALSE */ | ||
35783 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35784 | jx9_result_int(pCtx, -1); | ||
35785 | return JX9_OK; | ||
35786 | } | ||
35787 | /* Extract our private data */ | ||
35788 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
35789 | /* Make sure we are dealing with a valid io_private instance */ | ||
35790 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
35791 | /*Expecting an IO handle */ | ||
35792 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35793 | jx9_result_int(pCtx, -1); | ||
35794 | return JX9_OK; | ||
35795 | } | ||
35796 | /* Point to the target IO stream device */ | ||
35797 | pStream = pDev->pStream; | ||
35798 | if( pStream == 0 || pStream->xSeek == 0){ | ||
35799 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35800 | "IO routine(%s) not implemented in the underlying stream(%s) device", | ||
35801 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
35802 | ); | ||
35803 | jx9_result_int(pCtx, -1); | ||
35804 | return JX9_OK; | ||
35805 | } | ||
35806 | /* Extract the offset */ | ||
35807 | iOfft = jx9_value_to_int64(apArg[1]); | ||
35808 | whence = 0;/* SEEK_SET */ | ||
35809 | if( nArg > 2 && jx9_value_is_int(apArg[2]) ){ | ||
35810 | whence = jx9_value_to_int(apArg[2]); | ||
35811 | } | ||
35812 | /* Perform the requested operation */ | ||
35813 | rc = pStream->xSeek(pDev->pHandle, iOfft, whence); | ||
35814 | if( rc == JX9_OK ){ | ||
35815 | /* Ignore buffered data */ | ||
35816 | ResetIOPrivate(pDev); | ||
35817 | } | ||
35818 | /* IO result */ | ||
35819 | jx9_result_int(pCtx, rc == JX9_OK ? 0 : - 1); | ||
35820 | return JX9_OK; | ||
35821 | } | ||
35822 | /* | ||
35823 | * int64 ftell(resource $handle) | ||
35824 | * Returns the current position of the file read/write pointer. | ||
35825 | * Parameters | ||
35826 | * $handle | ||
35827 | * The file pointer. | ||
35828 | * Return | ||
35829 | * Returns the position of the file pointer referenced by handle | ||
35830 | * as an integer; i.e., its offset into the file stream. | ||
35831 | * FALSE is returned on failure. | ||
35832 | */ | ||
35833 | static int jx9Builtin_ftell(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35834 | { | ||
35835 | const jx9_io_stream *pStream; | ||
35836 | io_private *pDev; | ||
35837 | jx9_int64 iOfft; | ||
35838 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
35839 | /* Missing/Invalid arguments, return FALSE */ | ||
35840 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35841 | jx9_result_bool(pCtx, 0); | ||
35842 | return JX9_OK; | ||
35843 | } | ||
35844 | /* Extract our private data */ | ||
35845 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
35846 | /* Make sure we are dealing with a valid io_private instance */ | ||
35847 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
35848 | /*Expecting an IO handle */ | ||
35849 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35850 | jx9_result_bool(pCtx, 0); | ||
35851 | return JX9_OK; | ||
35852 | } | ||
35853 | /* Point to the target IO stream device */ | ||
35854 | pStream = pDev->pStream; | ||
35855 | if( pStream == 0 || pStream->xTell == 0){ | ||
35856 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35857 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
35858 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
35859 | ); | ||
35860 | jx9_result_bool(pCtx, 0); | ||
35861 | return JX9_OK; | ||
35862 | } | ||
35863 | /* Perform the requested operation */ | ||
35864 | iOfft = pStream->xTell(pDev->pHandle); | ||
35865 | /* IO result */ | ||
35866 | jx9_result_int64(pCtx, iOfft); | ||
35867 | return JX9_OK; | ||
35868 | } | ||
35869 | /* | ||
35870 | * bool rewind(resource $handle) | ||
35871 | * Rewind the position of a file pointer. | ||
35872 | * Parameters | ||
35873 | * $handle | ||
35874 | * The file pointer. | ||
35875 | * Return | ||
35876 | * TRUE on success or FALSE on failure. | ||
35877 | */ | ||
35878 | static int jx9Builtin_rewind(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35879 | { | ||
35880 | const jx9_io_stream *pStream; | ||
35881 | io_private *pDev; | ||
35882 | int rc; | ||
35883 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
35884 | /* Missing/Invalid arguments, return FALSE */ | ||
35885 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35886 | jx9_result_bool(pCtx, 0); | ||
35887 | return JX9_OK; | ||
35888 | } | ||
35889 | /* Extract our private data */ | ||
35890 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
35891 | /* Make sure we are dealing with a valid io_private instance */ | ||
35892 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
35893 | /*Expecting an IO handle */ | ||
35894 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35895 | jx9_result_bool(pCtx, 0); | ||
35896 | return JX9_OK; | ||
35897 | } | ||
35898 | /* Point to the target IO stream device */ | ||
35899 | pStream = pDev->pStream; | ||
35900 | if( pStream == 0 || pStream->xSeek == 0){ | ||
35901 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35902 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
35903 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
35904 | ); | ||
35905 | jx9_result_bool(pCtx, 0); | ||
35906 | return JX9_OK; | ||
35907 | } | ||
35908 | /* Perform the requested operation */ | ||
35909 | rc = pStream->xSeek(pDev->pHandle, 0, 0/*SEEK_SET*/); | ||
35910 | if( rc == JX9_OK ){ | ||
35911 | /* Ignore buffered data */ | ||
35912 | ResetIOPrivate(pDev); | ||
35913 | } | ||
35914 | /* IO result */ | ||
35915 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
35916 | return JX9_OK; | ||
35917 | } | ||
35918 | /* | ||
35919 | * bool fflush(resource $handle) | ||
35920 | * Flushes the output to a file. | ||
35921 | * Parameters | ||
35922 | * $handle | ||
35923 | * The file pointer. | ||
35924 | * Return | ||
35925 | * TRUE on success or FALSE on failure. | ||
35926 | */ | ||
35927 | static int jx9Builtin_fflush(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35928 | { | ||
35929 | const jx9_io_stream *pStream; | ||
35930 | io_private *pDev; | ||
35931 | int rc; | ||
35932 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
35933 | /* Missing/Invalid arguments, return FALSE */ | ||
35934 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35935 | jx9_result_bool(pCtx, 0); | ||
35936 | return JX9_OK; | ||
35937 | } | ||
35938 | /* Extract our private data */ | ||
35939 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
35940 | /* Make sure we are dealing with a valid io_private instance */ | ||
35941 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
35942 | /*Expecting an IO handle */ | ||
35943 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35944 | jx9_result_bool(pCtx, 0); | ||
35945 | return JX9_OK; | ||
35946 | } | ||
35947 | /* Point to the target IO stream device */ | ||
35948 | pStream = pDev->pStream; | ||
35949 | if( pStream == 0 || pStream->xSync == 0){ | ||
35950 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35951 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
35952 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
35953 | ); | ||
35954 | jx9_result_bool(pCtx, 0); | ||
35955 | return JX9_OK; | ||
35956 | } | ||
35957 | /* Perform the requested operation */ | ||
35958 | rc = pStream->xSync(pDev->pHandle); | ||
35959 | /* IO result */ | ||
35960 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
35961 | return JX9_OK; | ||
35962 | } | ||
35963 | /* | ||
35964 | * bool feof(resource $handle) | ||
35965 | * Tests for end-of-file on a file pointer. | ||
35966 | * Parameters | ||
35967 | * $handle | ||
35968 | * The file pointer. | ||
35969 | * Return | ||
35970 | * Returns TRUE if the file pointer is at EOF.FALSE otherwise | ||
35971 | */ | ||
35972 | static int jx9Builtin_feof(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
35973 | { | ||
35974 | const jx9_io_stream *pStream; | ||
35975 | io_private *pDev; | ||
35976 | int rc; | ||
35977 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
35978 | /* Missing/Invalid arguments */ | ||
35979 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35980 | jx9_result_bool(pCtx, 1); | ||
35981 | return JX9_OK; | ||
35982 | } | ||
35983 | /* Extract our private data */ | ||
35984 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
35985 | /* Make sure we are dealing with a valid io_private instance */ | ||
35986 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
35987 | /*Expecting an IO handle */ | ||
35988 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
35989 | jx9_result_bool(pCtx, 1); | ||
35990 | return JX9_OK; | ||
35991 | } | ||
35992 | /* Point to the target IO stream device */ | ||
35993 | pStream = pDev->pStream; | ||
35994 | if( pStream == 0 ){ | ||
35995 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
35996 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
35997 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
35998 | ); | ||
35999 | jx9_result_bool(pCtx, 1); | ||
36000 | return JX9_OK; | ||
36001 | } | ||
36002 | rc = SXERR_EOF; | ||
36003 | /* Perform the requested operation */ | ||
36004 | if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ | ||
36005 | /* Data is available */ | ||
36006 | rc = JX9_OK; | ||
36007 | }else{ | ||
36008 | char zBuf[4096]; | ||
36009 | jx9_int64 n; | ||
36010 | /* Perform a buffered read */ | ||
36011 | n = pStream->xRead(pDev->pHandle, zBuf, sizeof(zBuf)); | ||
36012 | if( n > 0 ){ | ||
36013 | /* Copy buffered data */ | ||
36014 | SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n); | ||
36015 | rc = JX9_OK; | ||
36016 | } | ||
36017 | } | ||
36018 | /* EOF or not */ | ||
36019 | jx9_result_bool(pCtx, rc == SXERR_EOF); | ||
36020 | return JX9_OK; | ||
36021 | } | ||
36022 | /* | ||
36023 | * Read n bytes from the underlying IO stream device. | ||
36024 | * Return total numbers of bytes readen on success. A number < 1 on failure | ||
36025 | * [i.e: IO error ] or EOF. | ||
36026 | */ | ||
36027 | static jx9_int64 StreamRead(io_private *pDev, void *pBuf, jx9_int64 nLen) | ||
36028 | { | ||
36029 | const jx9_io_stream *pStream = pDev->pStream; | ||
36030 | char *zBuf = (char *)pBuf; | ||
36031 | jx9_int64 n, nRead; | ||
36032 | n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; | ||
36033 | if( n > 0 ){ | ||
36034 | if( n > nLen ){ | ||
36035 | n = nLen; | ||
36036 | } | ||
36037 | /* Copy the buffered data */ | ||
36038 | SyMemcpy(SyBlobDataAt(&pDev->sBuffer, pDev->nOfft), pBuf, (sxu32)n); | ||
36039 | /* Update the read offset */ | ||
36040 | pDev->nOfft += (sxu32)n; | ||
36041 | if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ | ||
36042 | /* Reset the working buffer so that we avoid excessive memory allocation */ | ||
36043 | SyBlobReset(&pDev->sBuffer); | ||
36044 | pDev->nOfft = 0; | ||
36045 | } | ||
36046 | nLen -= n; | ||
36047 | if( nLen < 1 ){ | ||
36048 | /* All done */ | ||
36049 | return n; | ||
36050 | } | ||
36051 | /* Advance the cursor */ | ||
36052 | zBuf += n; | ||
36053 | } | ||
36054 | /* Read without buffering */ | ||
36055 | nRead = pStream->xRead(pDev->pHandle, zBuf, nLen); | ||
36056 | if( nRead > 0 ){ | ||
36057 | n += nRead; | ||
36058 | }else if( n < 1 ){ | ||
36059 | /* EOF or IO error */ | ||
36060 | return nRead; | ||
36061 | } | ||
36062 | return n; | ||
36063 | } | ||
36064 | /* | ||
36065 | * Extract a single line from the buffered input. | ||
36066 | */ | ||
36067 | static sxi32 GetLine(io_private *pDev, jx9_int64 *pLen, const char **pzLine) | ||
36068 | { | ||
36069 | const char *zIn, *zEnd, *zPtr; | ||
36070 | zIn = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); | ||
36071 | zEnd = &zIn[SyBlobLength(&pDev->sBuffer)-pDev->nOfft]; | ||
36072 | zPtr = zIn; | ||
36073 | while( zIn < zEnd ){ | ||
36074 | if( zIn[0] == '\n' ){ | ||
36075 | /* Line found */ | ||
36076 | zIn++; /* Include the line ending as requested by the JX9 specification */ | ||
36077 | *pLen = (jx9_int64)(zIn-zPtr); | ||
36078 | *pzLine = zPtr; | ||
36079 | return SXRET_OK; | ||
36080 | } | ||
36081 | zIn++; | ||
36082 | } | ||
36083 | /* No line were found */ | ||
36084 | return SXERR_NOTFOUND; | ||
36085 | } | ||
36086 | /* | ||
36087 | * Read a single line from the underlying IO stream device. | ||
36088 | */ | ||
36089 | static jx9_int64 StreamReadLine(io_private *pDev, const char **pzData, jx9_int64 nMaxLen) | ||
36090 | { | ||
36091 | const jx9_io_stream *pStream = pDev->pStream; | ||
36092 | char zBuf[8192]; | ||
36093 | jx9_int64 n; | ||
36094 | sxi32 rc; | ||
36095 | n = 0; | ||
36096 | if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ | ||
36097 | /* Reset the working buffer so that we avoid excessive memory allocation */ | ||
36098 | SyBlobReset(&pDev->sBuffer); | ||
36099 | pDev->nOfft = 0; | ||
36100 | } | ||
36101 | if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ | ||
36102 | /* Check if there is a line */ | ||
36103 | rc = GetLine(pDev, &n, pzData); | ||
36104 | if( rc == SXRET_OK ){ | ||
36105 | /* Got line, update the cursor */ | ||
36106 | pDev->nOfft += (sxu32)n; | ||
36107 | return n; | ||
36108 | } | ||
36109 | } | ||
36110 | /* Perform the read operation until a new line is extracted or length | ||
36111 | * limit is reached. | ||
36112 | */ | ||
36113 | for(;;){ | ||
36114 | n = pStream->xRead(pDev->pHandle, zBuf, (nMaxLen > 0 && nMaxLen < sizeof(zBuf)) ? nMaxLen : sizeof(zBuf)); | ||
36115 | if( n < 1 ){ | ||
36116 | /* EOF or IO error */ | ||
36117 | break; | ||
36118 | } | ||
36119 | /* Append the data just read */ | ||
36120 | SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n); | ||
36121 | /* Try to extract a line */ | ||
36122 | rc = GetLine(pDev, &n, pzData); | ||
36123 | if( rc == SXRET_OK ){ | ||
36124 | /* Got one, return immediately */ | ||
36125 | pDev->nOfft += (sxu32)n; | ||
36126 | return n; | ||
36127 | } | ||
36128 | if( nMaxLen > 0 && (SyBlobLength(&pDev->sBuffer) - pDev->nOfft >= nMaxLen) ){ | ||
36129 | /* Read limit reached, return the available data */ | ||
36130 | *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); | ||
36131 | n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; | ||
36132 | /* Reset the working buffer */ | ||
36133 | SyBlobReset(&pDev->sBuffer); | ||
36134 | pDev->nOfft = 0; | ||
36135 | return n; | ||
36136 | } | ||
36137 | } | ||
36138 | if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ | ||
36139 | /* Read limit reached, return the available data */ | ||
36140 | *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); | ||
36141 | n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; | ||
36142 | /* Reset the working buffer */ | ||
36143 | SyBlobReset(&pDev->sBuffer); | ||
36144 | pDev->nOfft = 0; | ||
36145 | } | ||
36146 | return n; | ||
36147 | } | ||
36148 | /* | ||
36149 | * Open an IO stream handle. | ||
36150 | * Notes on stream: | ||
36151 | * According to the JX9 reference manual. | ||
36152 | * In its simplest definition, a stream is a resource object which exhibits streamable behavior. | ||
36153 | * That is, it can be read from or written to in a linear fashion, and may be able to fseek() | ||
36154 | * to an arbitrary locations within the stream. | ||
36155 | * A wrapper is additional code which tells the stream how to handle specific protocols/encodings. | ||
36156 | * For example, the http wrapper knows how to translate a URL into an HTTP/1.0 request for a file | ||
36157 | * on a remote server. | ||
36158 | * A stream is referenced as: scheme://target | ||
36159 | * scheme(string) - The name of the wrapper to be used. Examples include: file, http... | ||
36160 | * If no wrapper is specified, the function default is used (typically file://). | ||
36161 | * target - Depends on the wrapper used. For filesystem related streams this is typically a path | ||
36162 | * and filename of the desired file. For network related streams this is typically a hostname, often | ||
36163 | * with a path appended. | ||
36164 | * | ||
36165 | * Note that JX9 IO streams looks like JX9 streams but their implementation differ greately. | ||
36166 | * Please refer to the official documentation for a full discussion. | ||
36167 | * This function return a handle on success. Otherwise null. | ||
36168 | */ | ||
36169 | JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile, | ||
36170 | int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew) | ||
36171 | { | ||
36172 | void *pHandle = 0; /* cc warning */ | ||
36173 | SyString sFile; | ||
36174 | int rc; | ||
36175 | if( pStream == 0 ){ | ||
36176 | /* No such stream device */ | ||
36177 | return 0; | ||
36178 | } | ||
36179 | SyStringInitFromBuf(&sFile, zFile, SyStrlen(zFile)); | ||
36180 | if( use_include ){ | ||
36181 | if( sFile.zString[0] == '/' || | ||
36182 | #ifdef __WINNT__ | ||
36183 | (sFile.nByte > 2 && sFile.zString[1] == ':' && (sFile.zString[2] == '\\' || sFile.zString[2] == '/') ) || | ||
36184 | #endif | ||
36185 | (sFile.nByte > 1 && sFile.zString[0] == '.' && sFile.zString[1] == '/') || | ||
36186 | (sFile.nByte > 2 && sFile.zString[0] == '.' && sFile.zString[1] == '.' && sFile.zString[2] == '/') ){ | ||
36187 | /* Open the file directly */ | ||
36188 | rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle); | ||
36189 | }else{ | ||
36190 | SyString *pPath; | ||
36191 | SyBlob sWorker; | ||
36192 | #ifdef __WINNT__ | ||
36193 | static const int c = '\\'; | ||
36194 | #else | ||
36195 | static const int c = '/'; | ||
36196 | #endif | ||
36197 | /* Init the path builder working buffer */ | ||
36198 | SyBlobInit(&sWorker, &pVm->sAllocator); | ||
36199 | /* Build a path from the set of include path */ | ||
36200 | SySetResetCursor(&pVm->aPaths); | ||
36201 | rc = SXERR_IO; | ||
36202 | while( SXRET_OK == SySetGetNextEntry(&pVm->aPaths, (void **)&pPath) ){ | ||
36203 | /* Build full path */ | ||
36204 | SyBlobFormat(&sWorker, "%z%c%z", pPath, c, &sFile); | ||
36205 | /* Append null terminator */ | ||
36206 | if( SXRET_OK != SyBlobNullAppend(&sWorker) ){ | ||
36207 | continue; | ||
36208 | } | ||
36209 | /* Try to open the file */ | ||
36210 | rc = pStream->xOpen((const char *)SyBlobData(&sWorker), iFlags, pResource, &pHandle); | ||
36211 | if( rc == JX9_OK ){ | ||
36212 | if( bPushInclude ){ | ||
36213 | /* Mark as included */ | ||
36214 | jx9VmPushFilePath(pVm, (const char *)SyBlobData(&sWorker), SyBlobLength(&sWorker), FALSE, pNew); | ||
36215 | } | ||
36216 | break; | ||
36217 | } | ||
36218 | /* Reset the working buffer */ | ||
36219 | SyBlobReset(&sWorker); | ||
36220 | /* Check the next path */ | ||
36221 | } | ||
36222 | SyBlobRelease(&sWorker); | ||
36223 | } | ||
36224 | if( rc == JX9_OK ){ | ||
36225 | if( bPushInclude ){ | ||
36226 | /* Mark as included */ | ||
36227 | jx9VmPushFilePath(pVm, sFile.zString, sFile.nByte, FALSE, pNew); | ||
36228 | } | ||
36229 | } | ||
36230 | }else{ | ||
36231 | /* Open the URI direcly */ | ||
36232 | rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle); | ||
36233 | } | ||
36234 | if( rc != JX9_OK ){ | ||
36235 | /* IO error */ | ||
36236 | return 0; | ||
36237 | } | ||
36238 | /* Return the file handle */ | ||
36239 | return pHandle; | ||
36240 | } | ||
36241 | /* | ||
36242 | * Read the whole contents of an open IO stream handle [i.e local file/URL..] | ||
36243 | * Store the read data in the given BLOB (last argument). | ||
36244 | * The read operation is stopped when he hit the EOF or an IO error occurs. | ||
36245 | */ | ||
36246 | JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut) | ||
36247 | { | ||
36248 | jx9_int64 nRead; | ||
36249 | char zBuf[8192]; /* 8K */ | ||
36250 | int rc; | ||
36251 | /* Perform the requested operation */ | ||
36252 | for(;;){ | ||
36253 | nRead = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); | ||
36254 | if( nRead < 1 ){ | ||
36255 | /* EOF or IO error */ | ||
36256 | break; | ||
36257 | } | ||
36258 | /* Append contents */ | ||
36259 | rc = SyBlobAppend(pOut, zBuf, (sxu32)nRead); | ||
36260 | if( rc != SXRET_OK ){ | ||
36261 | break; | ||
36262 | } | ||
36263 | } | ||
36264 | return SyBlobLength(pOut) > 0 ? SXRET_OK : -1; | ||
36265 | } | ||
36266 | /* | ||
36267 | * Close an open IO stream handle [i.e local file/URI..]. | ||
36268 | */ | ||
36269 | JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle) | ||
36270 | { | ||
36271 | if( pStream->xClose ){ | ||
36272 | pStream->xClose(pHandle); | ||
36273 | } | ||
36274 | } | ||
36275 | /* | ||
36276 | * string fgetc(resource $handle) | ||
36277 | * Gets a character from the given file pointer. | ||
36278 | * Parameters | ||
36279 | * $handle | ||
36280 | * The file pointer. | ||
36281 | * Return | ||
36282 | * Returns a string containing a single character read from the file | ||
36283 | * pointed to by handle. Returns FALSE on EOF. | ||
36284 | * WARNING | ||
36285 | * This operation is extremely slow.Avoid using it. | ||
36286 | */ | ||
36287 | static int jx9Builtin_fgetc(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36288 | { | ||
36289 | const jx9_io_stream *pStream; | ||
36290 | io_private *pDev; | ||
36291 | int c, n; | ||
36292 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36293 | /* Missing/Invalid arguments, return FALSE */ | ||
36294 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36295 | jx9_result_bool(pCtx, 0); | ||
36296 | return JX9_OK; | ||
36297 | } | ||
36298 | /* Extract our private data */ | ||
36299 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36300 | /* Make sure we are dealing with a valid io_private instance */ | ||
36301 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36302 | /*Expecting an IO handle */ | ||
36303 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36304 | jx9_result_bool(pCtx, 0); | ||
36305 | return JX9_OK; | ||
36306 | } | ||
36307 | /* Point to the target IO stream device */ | ||
36308 | pStream = pDev->pStream; | ||
36309 | if( pStream == 0 ){ | ||
36310 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36311 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36312 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36313 | ); | ||
36314 | jx9_result_bool(pCtx, 0); | ||
36315 | return JX9_OK; | ||
36316 | } | ||
36317 | /* Perform the requested operation */ | ||
36318 | n = (int)StreamRead(pDev, (void *)&c, sizeof(char)); | ||
36319 | /* IO result */ | ||
36320 | if( n < 1 ){ | ||
36321 | /* EOF or error, return FALSE */ | ||
36322 | jx9_result_bool(pCtx, 0); | ||
36323 | }else{ | ||
36324 | /* Return the string holding the character */ | ||
36325 | jx9_result_string(pCtx, (const char *)&c, sizeof(char)); | ||
36326 | } | ||
36327 | return JX9_OK; | ||
36328 | } | ||
36329 | /* | ||
36330 | * string fgets(resource $handle[, int64 $length ]) | ||
36331 | * Gets line from file pointer. | ||
36332 | * Parameters | ||
36333 | * $handle | ||
36334 | * The file pointer. | ||
36335 | * $length | ||
36336 | * Reading ends when length - 1 bytes have been read, on a newline | ||
36337 | * (which is included in the return value), or on EOF (whichever comes first). | ||
36338 | * If no length is specified, it will keep reading from the stream until it reaches | ||
36339 | * the end of the line. | ||
36340 | * Return | ||
36341 | * Returns a string of up to length - 1 bytes read from the file pointed to by handle. | ||
36342 | * If there is no more data to read in the file pointer, then FALSE is returned. | ||
36343 | * If an error occurs, FALSE is returned. | ||
36344 | */ | ||
36345 | static int jx9Builtin_fgets(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36346 | { | ||
36347 | const jx9_io_stream *pStream; | ||
36348 | const char *zLine; | ||
36349 | io_private *pDev; | ||
36350 | jx9_int64 n, nLen; | ||
36351 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36352 | /* Missing/Invalid arguments, return FALSE */ | ||
36353 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36354 | jx9_result_bool(pCtx, 0); | ||
36355 | return JX9_OK; | ||
36356 | } | ||
36357 | /* Extract our private data */ | ||
36358 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36359 | /* Make sure we are dealing with a valid io_private instance */ | ||
36360 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36361 | /*Expecting an IO handle */ | ||
36362 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36363 | jx9_result_bool(pCtx, 0); | ||
36364 | return JX9_OK; | ||
36365 | } | ||
36366 | /* Point to the target IO stream device */ | ||
36367 | pStream = pDev->pStream; | ||
36368 | if( pStream == 0 ){ | ||
36369 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36370 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36371 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36372 | ); | ||
36373 | jx9_result_bool(pCtx, 0); | ||
36374 | return JX9_OK; | ||
36375 | } | ||
36376 | nLen = -1; | ||
36377 | if( nArg > 1 ){ | ||
36378 | /* Maximum data to read */ | ||
36379 | nLen = jx9_value_to_int64(apArg[1]); | ||
36380 | } | ||
36381 | /* Perform the requested operation */ | ||
36382 | n = StreamReadLine(pDev, &zLine, nLen); | ||
36383 | if( n < 1 ){ | ||
36384 | /* EOF or IO error, return FALSE */ | ||
36385 | jx9_result_bool(pCtx, 0); | ||
36386 | }else{ | ||
36387 | /* Return the freshly extracted line */ | ||
36388 | jx9_result_string(pCtx, zLine, (int)n); | ||
36389 | } | ||
36390 | return JX9_OK; | ||
36391 | } | ||
36392 | /* | ||
36393 | * string fread(resource $handle, int64 $length) | ||
36394 | * Binary-safe file read. | ||
36395 | * Parameters | ||
36396 | * $handle | ||
36397 | * The file pointer. | ||
36398 | * $length | ||
36399 | * Up to length number of bytes read. | ||
36400 | * Return | ||
36401 | * The data readen on success or FALSE on failure. | ||
36402 | */ | ||
36403 | static int jx9Builtin_fread(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36404 | { | ||
36405 | const jx9_io_stream *pStream; | ||
36406 | io_private *pDev; | ||
36407 | jx9_int64 nRead; | ||
36408 | void *pBuf; | ||
36409 | int nLen; | ||
36410 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36411 | /* Missing/Invalid arguments, return FALSE */ | ||
36412 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36413 | jx9_result_bool(pCtx, 0); | ||
36414 | return JX9_OK; | ||
36415 | } | ||
36416 | /* Extract our private data */ | ||
36417 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36418 | /* Make sure we are dealing with a valid io_private instance */ | ||
36419 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36420 | /*Expecting an IO handle */ | ||
36421 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36422 | jx9_result_bool(pCtx, 0); | ||
36423 | return JX9_OK; | ||
36424 | } | ||
36425 | /* Point to the target IO stream device */ | ||
36426 | pStream = pDev->pStream; | ||
36427 | if( pStream == 0 ){ | ||
36428 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36429 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36430 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36431 | ); | ||
36432 | jx9_result_bool(pCtx, 0); | ||
36433 | return JX9_OK; | ||
36434 | } | ||
36435 | nLen = 4096; | ||
36436 | if( nArg > 1 ){ | ||
36437 | nLen = jx9_value_to_int(apArg[1]); | ||
36438 | if( nLen < 1 ){ | ||
36439 | /* Invalid length, set a default length */ | ||
36440 | nLen = 4096; | ||
36441 | } | ||
36442 | } | ||
36443 | /* Allocate enough buffer */ | ||
36444 | pBuf = jx9_context_alloc_chunk(pCtx, (unsigned int)nLen, FALSE, FALSE); | ||
36445 | if( pBuf == 0 ){ | ||
36446 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
36447 | jx9_result_bool(pCtx, 0); | ||
36448 | return JX9_OK; | ||
36449 | } | ||
36450 | /* Perform the requested operation */ | ||
36451 | nRead = StreamRead(pDev, pBuf, (jx9_int64)nLen); | ||
36452 | if( nRead < 1 ){ | ||
36453 | /* Nothing read, return FALSE */ | ||
36454 | jx9_result_bool(pCtx, 0); | ||
36455 | }else{ | ||
36456 | /* Make a copy of the data just read */ | ||
36457 | jx9_result_string(pCtx, (const char *)pBuf, (int)nRead); | ||
36458 | } | ||
36459 | /* Release the buffer */ | ||
36460 | jx9_context_free_chunk(pCtx, pBuf); | ||
36461 | return JX9_OK; | ||
36462 | } | ||
36463 | /* | ||
36464 | * array fgetcsv(resource $handle [, int $length = 0 | ||
36465 | * [, string $delimiter = ', '[, string $enclosure = '"'[, string $escape='\\']]]]) | ||
36466 | * Gets line from file pointer and parse for CSV fields. | ||
36467 | * Parameters | ||
36468 | * $handle | ||
36469 | * The file pointer. | ||
36470 | * $length | ||
36471 | * Reading ends when length - 1 bytes have been read, on a newline | ||
36472 | * (which is included in the return value), or on EOF (whichever comes first). | ||
36473 | * If no length is specified, it will keep reading from the stream until it reaches | ||
36474 | * the end of the line. | ||
36475 | * $delimiter | ||
36476 | * Set the field delimiter (one character only). | ||
36477 | * $enclosure | ||
36478 | * Set the field enclosure character (one character only). | ||
36479 | * $escape | ||
36480 | * Set the escape character (one character only). Defaults as a backslash (\) | ||
36481 | * Return | ||
36482 | * Returns a string of up to length - 1 bytes read from the file pointed to by handle. | ||
36483 | * If there is no more data to read in the file pointer, then FALSE is returned. | ||
36484 | * If an error occurs, FALSE is returned. | ||
36485 | */ | ||
36486 | static int jx9Builtin_fgetcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36487 | { | ||
36488 | const jx9_io_stream *pStream; | ||
36489 | const char *zLine; | ||
36490 | io_private *pDev; | ||
36491 | jx9_int64 n, nLen; | ||
36492 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36493 | /* Missing/Invalid arguments, return FALSE */ | ||
36494 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36495 | jx9_result_bool(pCtx, 0); | ||
36496 | return JX9_OK; | ||
36497 | } | ||
36498 | /* Extract our private data */ | ||
36499 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36500 | /* Make sure we are dealing with a valid io_private instance */ | ||
36501 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36502 | /*Expecting an IO handle */ | ||
36503 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36504 | jx9_result_bool(pCtx, 0); | ||
36505 | return JX9_OK; | ||
36506 | } | ||
36507 | /* Point to the target IO stream device */ | ||
36508 | pStream = pDev->pStream; | ||
36509 | if( pStream == 0 ){ | ||
36510 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36511 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36512 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36513 | ); | ||
36514 | jx9_result_bool(pCtx, 0); | ||
36515 | return JX9_OK; | ||
36516 | } | ||
36517 | nLen = -1; | ||
36518 | if( nArg > 1 ){ | ||
36519 | /* Maximum data to read */ | ||
36520 | nLen = jx9_value_to_int64(apArg[1]); | ||
36521 | } | ||
36522 | /* Perform the requested operation */ | ||
36523 | n = StreamReadLine(pDev, &zLine, nLen); | ||
36524 | if( n < 1 ){ | ||
36525 | /* EOF or IO error, return FALSE */ | ||
36526 | jx9_result_bool(pCtx, 0); | ||
36527 | }else{ | ||
36528 | jx9_value *pArray; | ||
36529 | int delim = ','; /* Delimiter */ | ||
36530 | int encl = '"' ; /* Enclosure */ | ||
36531 | int escape = '\\'; /* Escape character */ | ||
36532 | if( nArg > 2 ){ | ||
36533 | const char *zPtr; | ||
36534 | int i; | ||
36535 | if( jx9_value_is_string(apArg[2]) ){ | ||
36536 | /* Extract the delimiter */ | ||
36537 | zPtr = jx9_value_to_string(apArg[2], &i); | ||
36538 | if( i > 0 ){ | ||
36539 | delim = zPtr[0]; | ||
36540 | } | ||
36541 | } | ||
36542 | if( nArg > 3 ){ | ||
36543 | if( jx9_value_is_string(apArg[3]) ){ | ||
36544 | /* Extract the enclosure */ | ||
36545 | zPtr = jx9_value_to_string(apArg[3], &i); | ||
36546 | if( i > 0 ){ | ||
36547 | encl = zPtr[0]; | ||
36548 | } | ||
36549 | } | ||
36550 | if( nArg > 4 ){ | ||
36551 | if( jx9_value_is_string(apArg[4]) ){ | ||
36552 | /* Extract the escape character */ | ||
36553 | zPtr = jx9_value_to_string(apArg[4], &i); | ||
36554 | if( i > 0 ){ | ||
36555 | escape = zPtr[0]; | ||
36556 | } | ||
36557 | } | ||
36558 | } | ||
36559 | } | ||
36560 | } | ||
36561 | /* Create our array */ | ||
36562 | pArray = jx9_context_new_array(pCtx); | ||
36563 | if( pArray == 0 ){ | ||
36564 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
36565 | jx9_result_null(pCtx); | ||
36566 | return JX9_OK; | ||
36567 | } | ||
36568 | /* Parse the raw input */ | ||
36569 | jx9ProcessCsv(zLine, (int)n, delim, encl, escape, jx9CsvConsumer, pArray); | ||
36570 | /* Return the freshly created array */ | ||
36571 | jx9_result_value(pCtx, pArray); | ||
36572 | } | ||
36573 | return JX9_OK; | ||
36574 | } | ||
36575 | /* | ||
36576 | * string fgetss(resource $handle [, int $length [, string $allowable_tags ]]) | ||
36577 | * Gets line from file pointer and strip HTML tags. | ||
36578 | * Parameters | ||
36579 | * $handle | ||
36580 | * The file pointer. | ||
36581 | * $length | ||
36582 | * Reading ends when length - 1 bytes have been read, on a newline | ||
36583 | * (which is included in the return value), or on EOF (whichever comes first). | ||
36584 | * If no length is specified, it will keep reading from the stream until it reaches | ||
36585 | * the end of the line. | ||
36586 | * $allowable_tags | ||
36587 | * You can use the optional second parameter to specify tags which should not be stripped. | ||
36588 | * Return | ||
36589 | * Returns a string of up to length - 1 bytes read from the file pointed to by | ||
36590 | * handle, with all HTML and JX9 code stripped. If an error occurs, returns FALSE. | ||
36591 | */ | ||
36592 | static int jx9Builtin_fgetss(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36593 | { | ||
36594 | const jx9_io_stream *pStream; | ||
36595 | const char *zLine; | ||
36596 | io_private *pDev; | ||
36597 | jx9_int64 n, nLen; | ||
36598 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36599 | /* Missing/Invalid arguments, return FALSE */ | ||
36600 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36601 | jx9_result_bool(pCtx, 0); | ||
36602 | return JX9_OK; | ||
36603 | } | ||
36604 | /* Extract our private data */ | ||
36605 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36606 | /* Make sure we are dealing with a valid io_private instance */ | ||
36607 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36608 | /*Expecting an IO handle */ | ||
36609 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36610 | jx9_result_bool(pCtx, 0); | ||
36611 | return JX9_OK; | ||
36612 | } | ||
36613 | /* Point to the target IO stream device */ | ||
36614 | pStream = pDev->pStream; | ||
36615 | if( pStream == 0 ){ | ||
36616 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36617 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36618 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36619 | ); | ||
36620 | jx9_result_bool(pCtx, 0); | ||
36621 | return JX9_OK; | ||
36622 | } | ||
36623 | nLen = -1; | ||
36624 | if( nArg > 1 ){ | ||
36625 | /* Maximum data to read */ | ||
36626 | nLen = jx9_value_to_int64(apArg[1]); | ||
36627 | } | ||
36628 | /* Perform the requested operation */ | ||
36629 | n = StreamReadLine(pDev, &zLine, nLen); | ||
36630 | if( n < 1 ){ | ||
36631 | /* EOF or IO error, return FALSE */ | ||
36632 | jx9_result_bool(pCtx, 0); | ||
36633 | }else{ | ||
36634 | const char *zTaglist = 0; | ||
36635 | int nTaglen = 0; | ||
36636 | if( nArg > 2 && jx9_value_is_string(apArg[2]) ){ | ||
36637 | /* Allowed tag */ | ||
36638 | zTaglist = jx9_value_to_string(apArg[2], &nTaglen); | ||
36639 | } | ||
36640 | /* Process data just read */ | ||
36641 | jx9StripTagsFromString(pCtx, zLine, (int)n, zTaglist, nTaglen); | ||
36642 | } | ||
36643 | return JX9_OK; | ||
36644 | } | ||
36645 | /* | ||
36646 | * string readdir(resource $dir_handle) | ||
36647 | * Read entry from directory handle. | ||
36648 | * Parameter | ||
36649 | * $dir_handle | ||
36650 | * The directory handle resource previously opened with opendir(). | ||
36651 | * Return | ||
36652 | * Returns the filename on success or FALSE on failure. | ||
36653 | */ | ||
36654 | static int jx9Builtin_readdir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36655 | { | ||
36656 | const jx9_io_stream *pStream; | ||
36657 | io_private *pDev; | ||
36658 | int rc; | ||
36659 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36660 | /* Missing/Invalid arguments, return FALSE */ | ||
36661 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36662 | jx9_result_bool(pCtx, 0); | ||
36663 | return JX9_OK; | ||
36664 | } | ||
36665 | /* Extract our private data */ | ||
36666 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36667 | /* Make sure we are dealing with a valid io_private instance */ | ||
36668 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36669 | /*Expecting an IO handle */ | ||
36670 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36671 | jx9_result_bool(pCtx, 0); | ||
36672 | return JX9_OK; | ||
36673 | } | ||
36674 | /* Point to the target IO stream device */ | ||
36675 | pStream = pDev->pStream; | ||
36676 | if( pStream == 0 || pStream->xReadDir == 0 ){ | ||
36677 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36678 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36679 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36680 | ); | ||
36681 | jx9_result_bool(pCtx, 0); | ||
36682 | return JX9_OK; | ||
36683 | } | ||
36684 | jx9_result_bool(pCtx, 0); | ||
36685 | /* Perform the requested operation */ | ||
36686 | rc = pStream->xReadDir(pDev->pHandle, pCtx); | ||
36687 | if( rc != JX9_OK ){ | ||
36688 | /* Return FALSE */ | ||
36689 | jx9_result_bool(pCtx, 0); | ||
36690 | } | ||
36691 | return JX9_OK; | ||
36692 | } | ||
36693 | /* | ||
36694 | * void rewinddir(resource $dir_handle) | ||
36695 | * Rewind directory handle. | ||
36696 | * Parameter | ||
36697 | * $dir_handle | ||
36698 | * The directory handle resource previously opened with opendir(). | ||
36699 | * Return | ||
36700 | * FALSE on failure. | ||
36701 | */ | ||
36702 | static int jx9Builtin_rewinddir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36703 | { | ||
36704 | const jx9_io_stream *pStream; | ||
36705 | io_private *pDev; | ||
36706 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36707 | /* Missing/Invalid arguments, return FALSE */ | ||
36708 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36709 | jx9_result_bool(pCtx, 0); | ||
36710 | return JX9_OK; | ||
36711 | } | ||
36712 | /* Extract our private data */ | ||
36713 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36714 | /* Make sure we are dealing with a valid io_private instance */ | ||
36715 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36716 | /*Expecting an IO handle */ | ||
36717 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36718 | jx9_result_bool(pCtx, 0); | ||
36719 | return JX9_OK; | ||
36720 | } | ||
36721 | /* Point to the target IO stream device */ | ||
36722 | pStream = pDev->pStream; | ||
36723 | if( pStream == 0 || pStream->xRewindDir == 0 ){ | ||
36724 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36725 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36726 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36727 | ); | ||
36728 | jx9_result_bool(pCtx, 0); | ||
36729 | return JX9_OK; | ||
36730 | } | ||
36731 | /* Perform the requested operation */ | ||
36732 | pStream->xRewindDir(pDev->pHandle); | ||
36733 | return JX9_OK; | ||
36734 | } | ||
36735 | /* Forward declaration */ | ||
36736 | static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut); | ||
36737 | static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev); | ||
36738 | /* | ||
36739 | * void closedir(resource $dir_handle) | ||
36740 | * Close directory handle. | ||
36741 | * Parameter | ||
36742 | * $dir_handle | ||
36743 | * The directory handle resource previously opened with opendir(). | ||
36744 | * Return | ||
36745 | * FALSE on failure. | ||
36746 | */ | ||
36747 | static int jx9Builtin_closedir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36748 | { | ||
36749 | const jx9_io_stream *pStream; | ||
36750 | io_private *pDev; | ||
36751 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
36752 | /* Missing/Invalid arguments, return FALSE */ | ||
36753 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36754 | jx9_result_bool(pCtx, 0); | ||
36755 | return JX9_OK; | ||
36756 | } | ||
36757 | /* Extract our private data */ | ||
36758 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
36759 | /* Make sure we are dealing with a valid io_private instance */ | ||
36760 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
36761 | /*Expecting an IO handle */ | ||
36762 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
36763 | jx9_result_bool(pCtx, 0); | ||
36764 | return JX9_OK; | ||
36765 | } | ||
36766 | /* Point to the target IO stream device */ | ||
36767 | pStream = pDev->pStream; | ||
36768 | if( pStream == 0 || pStream->xCloseDir == 0 ){ | ||
36769 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36770 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
36771 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
36772 | ); | ||
36773 | jx9_result_bool(pCtx, 0); | ||
36774 | return JX9_OK; | ||
36775 | } | ||
36776 | /* Perform the requested operation */ | ||
36777 | pStream->xCloseDir(pDev->pHandle); | ||
36778 | /* Release the private stucture */ | ||
36779 | ReleaseIOPrivate(pCtx, pDev); | ||
36780 | jx9MemObjRelease(apArg[0]); | ||
36781 | return JX9_OK; | ||
36782 | } | ||
36783 | /* | ||
36784 | * resource opendir(string $path[, resource $context]) | ||
36785 | * Open directory handle. | ||
36786 | * Parameters | ||
36787 | * $path | ||
36788 | * The directory path that is to be opened. | ||
36789 | * $context | ||
36790 | * A context stream resource. | ||
36791 | * Return | ||
36792 | * A directory handle resource on success, or FALSE on failure. | ||
36793 | */ | ||
36794 | static int jx9Builtin_opendir(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36795 | { | ||
36796 | const jx9_io_stream *pStream; | ||
36797 | const char *zPath; | ||
36798 | io_private *pDev; | ||
36799 | int iLen, rc; | ||
36800 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
36801 | /* Missing/Invalid arguments, return FALSE */ | ||
36802 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a directory path"); | ||
36803 | jx9_result_bool(pCtx, 0); | ||
36804 | return JX9_OK; | ||
36805 | } | ||
36806 | /* Extract the target path */ | ||
36807 | zPath = jx9_value_to_string(apArg[0], &iLen); | ||
36808 | /* Try to extract a stream */ | ||
36809 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zPath, iLen); | ||
36810 | if( pStream == 0 ){ | ||
36811 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36812 | "No stream device is associated with the given path(%s)", zPath); | ||
36813 | jx9_result_bool(pCtx, 0); | ||
36814 | return JX9_OK; | ||
36815 | } | ||
36816 | if( pStream->xOpenDir == 0 ){ | ||
36817 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
36818 | "IO routine(%s) not implemented in the underlying stream(%s) device", | ||
36819 | jx9_function_name(pCtx), pStream->zName | ||
36820 | ); | ||
36821 | jx9_result_bool(pCtx, 0); | ||
36822 | return JX9_OK; | ||
36823 | } | ||
36824 | /* Allocate a new IO private instance */ | ||
36825 | pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); | ||
36826 | if( pDev == 0 ){ | ||
36827 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
36828 | jx9_result_bool(pCtx, 0); | ||
36829 | return JX9_OK; | ||
36830 | } | ||
36831 | /* Initialize the structure */ | ||
36832 | InitIOPrivate(pCtx->pVm, pStream, pDev); | ||
36833 | /* Open the target directory */ | ||
36834 | rc = pStream->xOpenDir(zPath, nArg > 1 ? apArg[1] : 0, &pDev->pHandle); | ||
36835 | if( rc != JX9_OK ){ | ||
36836 | /* IO error, return FALSE */ | ||
36837 | ReleaseIOPrivate(pCtx, pDev); | ||
36838 | jx9_result_bool(pCtx, 0); | ||
36839 | }else{ | ||
36840 | /* Return the handle as a resource */ | ||
36841 | jx9_result_resource(pCtx, pDev); | ||
36842 | } | ||
36843 | return JX9_OK; | ||
36844 | } | ||
36845 | /* | ||
36846 | * int readfile(string $filename[, bool $use_include_path = false [, resource $context ]]) | ||
36847 | * Reads a file and writes it to the output buffer. | ||
36848 | * Parameters | ||
36849 | * $filename | ||
36850 | * The filename being read. | ||
36851 | * $use_include_path | ||
36852 | * You can use the optional second parameter and set it to | ||
36853 | * TRUE, if you want to search for the file in the include_path, too. | ||
36854 | * $context | ||
36855 | * A context stream resource. | ||
36856 | * Return | ||
36857 | * The number of bytes read from the file on success or FALSE on failure. | ||
36858 | */ | ||
36859 | static int jx9Builtin_readfile(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36860 | { | ||
36861 | int use_include = FALSE; | ||
36862 | const jx9_io_stream *pStream; | ||
36863 | jx9_int64 n, nRead; | ||
36864 | const char *zFile; | ||
36865 | char zBuf[8192]; | ||
36866 | void *pHandle; | ||
36867 | int rc, nLen; | ||
36868 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
36869 | /* Missing/Invalid arguments, return FALSE */ | ||
36870 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
36871 | jx9_result_bool(pCtx, 0); | ||
36872 | return JX9_OK; | ||
36873 | } | ||
36874 | /* Extract the file path */ | ||
36875 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
36876 | /* Point to the target IO stream device */ | ||
36877 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
36878 | if( pStream == 0 ){ | ||
36879 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
36880 | jx9_result_bool(pCtx, 0); | ||
36881 | return JX9_OK; | ||
36882 | } | ||
36883 | if( nArg > 1 ){ | ||
36884 | use_include = jx9_value_to_bool(apArg[1]); | ||
36885 | } | ||
36886 | /* Try to open the file in read-only mode */ | ||
36887 | pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, | ||
36888 | use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); | ||
36889 | if( pHandle == 0 ){ | ||
36890 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
36891 | jx9_result_bool(pCtx, 0); | ||
36892 | return JX9_OK; | ||
36893 | } | ||
36894 | /* Perform the requested operation */ | ||
36895 | nRead = 0; | ||
36896 | for(;;){ | ||
36897 | n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); | ||
36898 | if( n < 1 ){ | ||
36899 | /* EOF or IO error, break immediately */ | ||
36900 | break; | ||
36901 | } | ||
36902 | /* Output data */ | ||
36903 | rc = jx9_context_output(pCtx, zBuf, (int)n); | ||
36904 | if( rc == JX9_ABORT ){ | ||
36905 | break; | ||
36906 | } | ||
36907 | /* Increment counter */ | ||
36908 | nRead += n; | ||
36909 | } | ||
36910 | /* Close the stream */ | ||
36911 | jx9StreamCloseHandle(pStream, pHandle); | ||
36912 | /* Total number of bytes readen */ | ||
36913 | jx9_result_int64(pCtx, nRead); | ||
36914 | return JX9_OK; | ||
36915 | } | ||
36916 | /* | ||
36917 | * string file_get_contents(string $filename[, bool $use_include_path = false | ||
36918 | * [, resource $context [, int $offset = -1 [, int $maxlen ]]]]) | ||
36919 | * Reads entire file into a string. | ||
36920 | * Parameters | ||
36921 | * $filename | ||
36922 | * The filename being read. | ||
36923 | * $use_include_path | ||
36924 | * You can use the optional second parameter and set it to | ||
36925 | * TRUE, if you want to search for the file in the include_path, too. | ||
36926 | * $context | ||
36927 | * A context stream resource. | ||
36928 | * $offset | ||
36929 | * The offset where the reading starts on the original stream. | ||
36930 | * $maxlen | ||
36931 | * Maximum length of data read. The default is to read until end of file | ||
36932 | * is reached. Note that this parameter is applied to the stream processed by the filters. | ||
36933 | * Return | ||
36934 | * The function returns the read data or FALSE on failure. | ||
36935 | */ | ||
36936 | static int jx9Builtin_file_get_contents(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
36937 | { | ||
36938 | const jx9_io_stream *pStream; | ||
36939 | jx9_int64 n, nRead, nMaxlen; | ||
36940 | int use_include = FALSE; | ||
36941 | const char *zFile; | ||
36942 | char zBuf[8192]; | ||
36943 | void *pHandle; | ||
36944 | int nLen; | ||
36945 | |||
36946 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
36947 | /* Missing/Invalid arguments, return FALSE */ | ||
36948 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
36949 | jx9_result_bool(pCtx, 0); | ||
36950 | return JX9_OK; | ||
36951 | } | ||
36952 | /* Extract the file path */ | ||
36953 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
36954 | /* Point to the target IO stream device */ | ||
36955 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
36956 | if( pStream == 0 ){ | ||
36957 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
36958 | jx9_result_bool(pCtx, 0); | ||
36959 | return JX9_OK; | ||
36960 | } | ||
36961 | nMaxlen = -1; | ||
36962 | if( nArg > 1 ){ | ||
36963 | use_include = jx9_value_to_bool(apArg[1]); | ||
36964 | } | ||
36965 | /* Try to open the file in read-only mode */ | ||
36966 | pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); | ||
36967 | if( pHandle == 0 ){ | ||
36968 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
36969 | jx9_result_bool(pCtx, 0); | ||
36970 | return JX9_OK; | ||
36971 | } | ||
36972 | if( nArg > 3 ){ | ||
36973 | /* Extract the offset */ | ||
36974 | n = jx9_value_to_int64(apArg[3]); | ||
36975 | if( n > 0 ){ | ||
36976 | if( pStream->xSeek ){ | ||
36977 | /* Seek to the desired offset */ | ||
36978 | pStream->xSeek(pHandle, n, 0/*SEEK_SET*/); | ||
36979 | } | ||
36980 | } | ||
36981 | if( nArg > 4 ){ | ||
36982 | /* Maximum data to read */ | ||
36983 | nMaxlen = jx9_value_to_int64(apArg[4]); | ||
36984 | } | ||
36985 | } | ||
36986 | /* Perform the requested operation */ | ||
36987 | nRead = 0; | ||
36988 | for(;;){ | ||
36989 | n = pStream->xRead(pHandle, zBuf, | ||
36990 | (nMaxlen > 0 && (nMaxlen < sizeof(zBuf))) ? nMaxlen : sizeof(zBuf)); | ||
36991 | if( n < 1 ){ | ||
36992 | /* EOF or IO error, break immediately */ | ||
36993 | break; | ||
36994 | } | ||
36995 | /* Append data */ | ||
36996 | jx9_result_string(pCtx, zBuf, (int)n); | ||
36997 | /* Increment read counter */ | ||
36998 | nRead += n; | ||
36999 | if( nMaxlen > 0 && nRead >= nMaxlen ){ | ||
37000 | /* Read limit reached */ | ||
37001 | break; | ||
37002 | } | ||
37003 | } | ||
37004 | /* Close the stream */ | ||
37005 | jx9StreamCloseHandle(pStream, pHandle); | ||
37006 | /* Check if we have read something */ | ||
37007 | if( jx9_context_result_buf_length(pCtx) < 1 ){ | ||
37008 | /* Nothing read, return FALSE */ | ||
37009 | jx9_result_bool(pCtx, 0); | ||
37010 | } | ||
37011 | return JX9_OK; | ||
37012 | } | ||
37013 | /* | ||
37014 | * int file_put_contents(string $filename, mixed $data[, int $flags = 0[, resource $context]]) | ||
37015 | * Write a string to a file. | ||
37016 | * Parameters | ||
37017 | * $filename | ||
37018 | * Path to the file where to write the data. | ||
37019 | * $data | ||
37020 | * The data to write(Must be a string). | ||
37021 | * $flags | ||
37022 | * The value of flags can be any combination of the following | ||
37023 | * flags, joined with the binary OR (|) operator. | ||
37024 | * FILE_USE_INCLUDE_PATH Search for filename in the include directory. See include_path for more information. | ||
37025 | * FILE_APPEND If file filename already exists, append the data to the file instead of overwriting it. | ||
37026 | * LOCK_EX Acquire an exclusive lock on the file while proceeding to the writing. | ||
37027 | * context | ||
37028 | * A context stream resource. | ||
37029 | * Return | ||
37030 | * The function returns the number of bytes that were written to the file, or FALSE on failure. | ||
37031 | */ | ||
37032 | static int jx9Builtin_file_put_contents(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37033 | { | ||
37034 | int use_include = FALSE; | ||
37035 | const jx9_io_stream *pStream; | ||
37036 | const char *zFile; | ||
37037 | const char *zData; | ||
37038 | int iOpenFlags; | ||
37039 | void *pHandle; | ||
37040 | int iFlags; | ||
37041 | int nLen; | ||
37042 | |||
37043 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ | ||
37044 | /* Missing/Invalid arguments, return FALSE */ | ||
37045 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
37046 | jx9_result_bool(pCtx, 0); | ||
37047 | return JX9_OK; | ||
37048 | } | ||
37049 | /* Extract the file path */ | ||
37050 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
37051 | /* Point to the target IO stream device */ | ||
37052 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
37053 | if( pStream == 0 ){ | ||
37054 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
37055 | jx9_result_bool(pCtx, 0); | ||
37056 | return JX9_OK; | ||
37057 | } | ||
37058 | /* Data to write */ | ||
37059 | zData = jx9_value_to_string(apArg[1], &nLen); | ||
37060 | if( nLen < 1 ){ | ||
37061 | /* Nothing to write, return immediately */ | ||
37062 | jx9_result_bool(pCtx, 0); | ||
37063 | return JX9_OK; | ||
37064 | } | ||
37065 | /* Try to open the file in read-write mode */ | ||
37066 | iOpenFlags = JX9_IO_OPEN_CREATE|JX9_IO_OPEN_RDWR|JX9_IO_OPEN_TRUNC; | ||
37067 | /* Extract the flags */ | ||
37068 | iFlags = 0; | ||
37069 | if( nArg > 2 ){ | ||
37070 | iFlags = jx9_value_to_int(apArg[2]); | ||
37071 | if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/){ | ||
37072 | use_include = TRUE; | ||
37073 | } | ||
37074 | if( iFlags & 0x08 /* FILE_APPEND */){ | ||
37075 | /* If the file already exists, append the data to the file | ||
37076 | * instead of overwriting it. | ||
37077 | */ | ||
37078 | iOpenFlags &= ~JX9_IO_OPEN_TRUNC; | ||
37079 | /* Append mode */ | ||
37080 | iOpenFlags |= JX9_IO_OPEN_APPEND; | ||
37081 | } | ||
37082 | } | ||
37083 | pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, iOpenFlags, use_include, | ||
37084 | nArg > 3 ? apArg[3] : 0, FALSE, FALSE); | ||
37085 | if( pHandle == 0 ){ | ||
37086 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
37087 | jx9_result_bool(pCtx, 0); | ||
37088 | return JX9_OK; | ||
37089 | } | ||
37090 | if( pStream->xWrite ){ | ||
37091 | jx9_int64 n; | ||
37092 | if( (iFlags & 0x01/* LOCK_EX */) && pStream->xLock ){ | ||
37093 | /* Try to acquire an exclusive lock */ | ||
37094 | pStream->xLock(pHandle, 1/* LOCK_EX */); | ||
37095 | } | ||
37096 | /* Perform the write operation */ | ||
37097 | n = pStream->xWrite(pHandle, (const void *)zData, nLen); | ||
37098 | if( n < 1 ){ | ||
37099 | /* IO error, return FALSE */ | ||
37100 | jx9_result_bool(pCtx, 0); | ||
37101 | }else{ | ||
37102 | /* Total number of bytes written */ | ||
37103 | jx9_result_int64(pCtx, n); | ||
37104 | } | ||
37105 | }else{ | ||
37106 | /* Read-only stream */ | ||
37107 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, | ||
37108 | "Read-only stream(%s): Cannot perform write operation", | ||
37109 | pStream ? pStream->zName : "null_stream" | ||
37110 | ); | ||
37111 | jx9_result_bool(pCtx, 0); | ||
37112 | } | ||
37113 | /* Close the handle */ | ||
37114 | jx9StreamCloseHandle(pStream, pHandle); | ||
37115 | return JX9_OK; | ||
37116 | } | ||
37117 | /* | ||
37118 | * array file(string $filename[, int $flags = 0[, resource $context]]) | ||
37119 | * Reads entire file into an array. | ||
37120 | * Parameters | ||
37121 | * $filename | ||
37122 | * The filename being read. | ||
37123 | * $flags | ||
37124 | * The optional parameter flags can be one, or more, of the following constants: | ||
37125 | * FILE_USE_INCLUDE_PATH | ||
37126 | * Search for the file in the include_path. | ||
37127 | * FILE_IGNORE_NEW_LINES | ||
37128 | * Do not add newline at the end of each array element | ||
37129 | * FILE_SKIP_EMPTY_LINES | ||
37130 | * Skip empty lines | ||
37131 | * $context | ||
37132 | * A context stream resource. | ||
37133 | * Return | ||
37134 | * The function returns the read data or FALSE on failure. | ||
37135 | */ | ||
37136 | static int jx9Builtin_file(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37137 | { | ||
37138 | const char *zFile, *zPtr, *zEnd, *zBuf; | ||
37139 | jx9_value *pArray, *pLine; | ||
37140 | const jx9_io_stream *pStream; | ||
37141 | int use_include = 0; | ||
37142 | io_private *pDev; | ||
37143 | jx9_int64 n; | ||
37144 | int iFlags; | ||
37145 | int nLen; | ||
37146 | |||
37147 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
37148 | /* Missing/Invalid arguments, return FALSE */ | ||
37149 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
37150 | jx9_result_bool(pCtx, 0); | ||
37151 | return JX9_OK; | ||
37152 | } | ||
37153 | /* Extract the file path */ | ||
37154 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
37155 | /* Point to the target IO stream device */ | ||
37156 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
37157 | if( pStream == 0 ){ | ||
37158 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
37159 | jx9_result_bool(pCtx, 0); | ||
37160 | return JX9_OK; | ||
37161 | } | ||
37162 | /* Allocate a new IO private instance */ | ||
37163 | pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); | ||
37164 | if( pDev == 0 ){ | ||
37165 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
37166 | jx9_result_bool(pCtx, 0); | ||
37167 | return JX9_OK; | ||
37168 | } | ||
37169 | /* Initialize the structure */ | ||
37170 | InitIOPrivate(pCtx->pVm, pStream, pDev); | ||
37171 | iFlags = 0; | ||
37172 | if( nArg > 1 ){ | ||
37173 | iFlags = jx9_value_to_int(apArg[1]); | ||
37174 | } | ||
37175 | if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/ ){ | ||
37176 | use_include = TRUE; | ||
37177 | } | ||
37178 | /* Create the array and the working value */ | ||
37179 | pArray = jx9_context_new_array(pCtx); | ||
37180 | pLine = jx9_context_new_scalar(pCtx); | ||
37181 | if( pArray == 0 || pLine == 0 ){ | ||
37182 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
37183 | jx9_result_bool(pCtx, 0); | ||
37184 | return JX9_OK; | ||
37185 | } | ||
37186 | /* Try to open the file in read-only mode */ | ||
37187 | pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); | ||
37188 | if( pDev->pHandle == 0 ){ | ||
37189 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
37190 | jx9_result_bool(pCtx, 0); | ||
37191 | /* Don't worry about freeing memory, everything will be released automatically | ||
37192 | * as soon we return from this function. | ||
37193 | */ | ||
37194 | return JX9_OK; | ||
37195 | } | ||
37196 | /* Perform the requested operation */ | ||
37197 | for(;;){ | ||
37198 | /* Try to extract a line */ | ||
37199 | n = StreamReadLine(pDev, &zBuf, -1); | ||
37200 | if( n < 1 ){ | ||
37201 | /* EOF or IO error */ | ||
37202 | break; | ||
37203 | } | ||
37204 | /* Reset the cursor */ | ||
37205 | jx9_value_reset_string_cursor(pLine); | ||
37206 | /* Remove line ending if requested by the caller */ | ||
37207 | zPtr = zBuf; | ||
37208 | zEnd = &zBuf[n]; | ||
37209 | if( iFlags & 0x02 /* FILE_IGNORE_NEW_LINES */ ){ | ||
37210 | /* Ignore trailig lines */ | ||
37211 | while( zPtr < zEnd && (zEnd[-1] == '\n' | ||
37212 | #ifdef __WINNT__ | ||
37213 | || zEnd[-1] == '\r' | ||
37214 | #endif | ||
37215 | )){ | ||
37216 | n--; | ||
37217 | zEnd--; | ||
37218 | } | ||
37219 | } | ||
37220 | if( iFlags & 0x04 /* FILE_SKIP_EMPTY_LINES */ ){ | ||
37221 | /* Ignore empty lines */ | ||
37222 | while( zPtr < zEnd && (unsigned char)zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) ){ | ||
37223 | zPtr++; | ||
37224 | } | ||
37225 | if( zPtr >= zEnd ){ | ||
37226 | /* Empty line */ | ||
37227 | continue; | ||
37228 | } | ||
37229 | } | ||
37230 | jx9_value_string(pLine, zBuf, (int)(zEnd-zBuf)); | ||
37231 | /* Insert line */ | ||
37232 | jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pLine); | ||
37233 | } | ||
37234 | /* Close the stream */ | ||
37235 | jx9StreamCloseHandle(pStream, pDev->pHandle); | ||
37236 | /* Release the io_private instance */ | ||
37237 | ReleaseIOPrivate(pCtx, pDev); | ||
37238 | /* Return the created array */ | ||
37239 | jx9_result_value(pCtx, pArray); | ||
37240 | return JX9_OK; | ||
37241 | } | ||
37242 | /* | ||
37243 | * bool copy(string $source, string $dest[, resource $context ] ) | ||
37244 | * Makes a copy of the file source to dest. | ||
37245 | * Parameters | ||
37246 | * $source | ||
37247 | * Path to the source file. | ||
37248 | * $dest | ||
37249 | * The destination path. If dest is a URL, the copy operation | ||
37250 | * may fail if the wrapper does not support overwriting of existing files. | ||
37251 | * $context | ||
37252 | * A context stream resource. | ||
37253 | * Return | ||
37254 | * TRUE on success or FALSE on failure. | ||
37255 | */ | ||
37256 | static int jx9Builtin_copy(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37257 | { | ||
37258 | const jx9_io_stream *pSin, *pSout; | ||
37259 | const char *zFile; | ||
37260 | char zBuf[8192]; | ||
37261 | void *pIn, *pOut; | ||
37262 | jx9_int64 n; | ||
37263 | int nLen; | ||
37264 | if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1])){ | ||
37265 | /* Missing/Invalid arguments, return FALSE */ | ||
37266 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a source and a destination path"); | ||
37267 | jx9_result_bool(pCtx, 0); | ||
37268 | return JX9_OK; | ||
37269 | } | ||
37270 | /* Extract the source name */ | ||
37271 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
37272 | /* Point to the target IO stream device */ | ||
37273 | pSin = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
37274 | if( pSin == 0 ){ | ||
37275 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
37276 | jx9_result_bool(pCtx, 0); | ||
37277 | return JX9_OK; | ||
37278 | } | ||
37279 | /* Try to open the source file in a read-only mode */ | ||
37280 | pIn = jx9StreamOpenHandle(pCtx->pVm, pSin, zFile, JX9_IO_OPEN_RDONLY, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0); | ||
37281 | if( pIn == 0 ){ | ||
37282 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening source: '%s'", zFile); | ||
37283 | jx9_result_bool(pCtx, 0); | ||
37284 | return JX9_OK; | ||
37285 | } | ||
37286 | /* Extract the destination name */ | ||
37287 | zFile = jx9_value_to_string(apArg[1], &nLen); | ||
37288 | /* Point to the target IO stream device */ | ||
37289 | pSout = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
37290 | if( pSout == 0 ){ | ||
37291 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
37292 | jx9_result_bool(pCtx, 0); | ||
37293 | jx9StreamCloseHandle(pSin, pIn); | ||
37294 | return JX9_OK; | ||
37295 | } | ||
37296 | if( pSout->xWrite == 0 ){ | ||
37297 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37298 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
37299 | jx9_function_name(pCtx), pSin->zName | ||
37300 | ); | ||
37301 | jx9_result_bool(pCtx, 0); | ||
37302 | jx9StreamCloseHandle(pSin, pIn); | ||
37303 | return JX9_OK; | ||
37304 | } | ||
37305 | /* Try to open the destination file in a read-write mode */ | ||
37306 | pOut = jx9StreamOpenHandle(pCtx->pVm, pSout, zFile, | ||
37307 | JX9_IO_OPEN_CREATE|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_RDWR, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0); | ||
37308 | if( pOut == 0 ){ | ||
37309 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening destination: '%s'", zFile); | ||
37310 | jx9_result_bool(pCtx, 0); | ||
37311 | jx9StreamCloseHandle(pSin, pIn); | ||
37312 | return JX9_OK; | ||
37313 | } | ||
37314 | /* Perform the requested operation */ | ||
37315 | for(;;){ | ||
37316 | /* Read from source */ | ||
37317 | n = pSin->xRead(pIn, zBuf, sizeof(zBuf)); | ||
37318 | if( n < 1 ){ | ||
37319 | /* EOF or IO error, break immediately */ | ||
37320 | break; | ||
37321 | } | ||
37322 | /* Write to dest */ | ||
37323 | n = pSout->xWrite(pOut, zBuf, n); | ||
37324 | if( n < 1 ){ | ||
37325 | /* IO error, break immediately */ | ||
37326 | break; | ||
37327 | } | ||
37328 | } | ||
37329 | /* Close the streams */ | ||
37330 | jx9StreamCloseHandle(pSin, pIn); | ||
37331 | jx9StreamCloseHandle(pSout, pOut); | ||
37332 | /* Return TRUE */ | ||
37333 | jx9_result_bool(pCtx, 1); | ||
37334 | return JX9_OK; | ||
37335 | } | ||
37336 | /* | ||
37337 | * array fstat(resource $handle) | ||
37338 | * Gets information about a file using an open file pointer. | ||
37339 | * Parameters | ||
37340 | * $handle | ||
37341 | * The file pointer. | ||
37342 | * Return | ||
37343 | * Returns an array with the statistics of the file or FALSE on failure. | ||
37344 | */ | ||
37345 | static int jx9Builtin_fstat(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37346 | { | ||
37347 | jx9_value *pArray, *pValue; | ||
37348 | const jx9_io_stream *pStream; | ||
37349 | io_private *pDev; | ||
37350 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
37351 | /* Missing/Invalid arguments, return FALSE */ | ||
37352 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37353 | jx9_result_bool(pCtx, 0); | ||
37354 | return JX9_OK; | ||
37355 | } | ||
37356 | /* Extract our private data */ | ||
37357 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
37358 | /* Make sure we are dealing with a valid io_private instance */ | ||
37359 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
37360 | /* Expecting an IO handle */ | ||
37361 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37362 | jx9_result_bool(pCtx, 0); | ||
37363 | return JX9_OK; | ||
37364 | } | ||
37365 | /* Point to the target IO stream device */ | ||
37366 | pStream = pDev->pStream; | ||
37367 | if( pStream == 0 || pStream->xStat == 0){ | ||
37368 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37369 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
37370 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
37371 | ); | ||
37372 | jx9_result_bool(pCtx, 0); | ||
37373 | return JX9_OK; | ||
37374 | } | ||
37375 | /* Create the array and the working value */ | ||
37376 | pArray = jx9_context_new_array(pCtx); | ||
37377 | pValue = jx9_context_new_scalar(pCtx); | ||
37378 | if( pArray == 0 || pValue == 0 ){ | ||
37379 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
37380 | jx9_result_bool(pCtx, 0); | ||
37381 | return JX9_OK; | ||
37382 | } | ||
37383 | /* Perform the requested operation */ | ||
37384 | pStream->xStat(pDev->pHandle, pArray, pValue); | ||
37385 | /* Return the freshly created array */ | ||
37386 | jx9_result_value(pCtx, pArray); | ||
37387 | /* Don't worry about freeing memory here, everything will be | ||
37388 | * released automatically as soon we return from this function. | ||
37389 | */ | ||
37390 | return JX9_OK; | ||
37391 | } | ||
37392 | /* | ||
37393 | * int fwrite(resource $handle, string $string[, int $length]) | ||
37394 | * Writes the contents of string to the file stream pointed to by handle. | ||
37395 | * Parameters | ||
37396 | * $handle | ||
37397 | * The file pointer. | ||
37398 | * $string | ||
37399 | * The string that is to be written. | ||
37400 | * $length | ||
37401 | * If the length argument is given, writing will stop after length bytes have been written | ||
37402 | * or the end of string is reached, whichever comes first. | ||
37403 | * Return | ||
37404 | * Returns the number of bytes written, or FALSE on error. | ||
37405 | */ | ||
37406 | static int jx9Builtin_fwrite(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37407 | { | ||
37408 | const jx9_io_stream *pStream; | ||
37409 | const char *zString; | ||
37410 | io_private *pDev; | ||
37411 | int nLen, n; | ||
37412 | if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ | ||
37413 | /* Missing/Invalid arguments, return FALSE */ | ||
37414 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37415 | jx9_result_bool(pCtx, 0); | ||
37416 | return JX9_OK; | ||
37417 | } | ||
37418 | /* Extract our private data */ | ||
37419 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
37420 | /* Make sure we are dealing with a valid io_private instance */ | ||
37421 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
37422 | /* Expecting an IO handle */ | ||
37423 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37424 | jx9_result_bool(pCtx, 0); | ||
37425 | return JX9_OK; | ||
37426 | } | ||
37427 | /* Point to the target IO stream device */ | ||
37428 | pStream = pDev->pStream; | ||
37429 | if( pStream == 0 || pStream->xWrite == 0){ | ||
37430 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37431 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
37432 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
37433 | ); | ||
37434 | jx9_result_bool(pCtx, 0); | ||
37435 | return JX9_OK; | ||
37436 | } | ||
37437 | /* Extract the data to write */ | ||
37438 | zString = jx9_value_to_string(apArg[1], &nLen); | ||
37439 | if( nArg > 2 ){ | ||
37440 | /* Maximum data length to write */ | ||
37441 | n = jx9_value_to_int(apArg[2]); | ||
37442 | if( n >= 0 && n < nLen ){ | ||
37443 | nLen = n; | ||
37444 | } | ||
37445 | } | ||
37446 | if( nLen < 1 ){ | ||
37447 | /* Nothing to write */ | ||
37448 | jx9_result_int(pCtx, 0); | ||
37449 | return JX9_OK; | ||
37450 | } | ||
37451 | /* Perform the requested operation */ | ||
37452 | n = (int)pStream->xWrite(pDev->pHandle, (const void *)zString, nLen); | ||
37453 | if( n < 0 ){ | ||
37454 | /* IO error, return FALSE */ | ||
37455 | jx9_result_bool(pCtx, 0); | ||
37456 | }else{ | ||
37457 | /* #Bytes written */ | ||
37458 | jx9_result_int(pCtx, n); | ||
37459 | } | ||
37460 | return JX9_OK; | ||
37461 | } | ||
37462 | /* | ||
37463 | * bool flock(resource $handle, int $operation) | ||
37464 | * Portable advisory file locking. | ||
37465 | * Parameters | ||
37466 | * $handle | ||
37467 | * The file pointer. | ||
37468 | * $operation | ||
37469 | * operation is one of the following: | ||
37470 | * LOCK_SH to acquire a shared lock (reader). | ||
37471 | * LOCK_EX to acquire an exclusive lock (writer). | ||
37472 | * LOCK_UN to release a lock (shared or exclusive). | ||
37473 | * Return | ||
37474 | * Returns TRUE on success or FALSE on failure. | ||
37475 | */ | ||
37476 | static int jx9Builtin_flock(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37477 | { | ||
37478 | const jx9_io_stream *pStream; | ||
37479 | io_private *pDev; | ||
37480 | int nLock; | ||
37481 | int rc; | ||
37482 | if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ | ||
37483 | /* Missing/Invalid arguments, return FALSE */ | ||
37484 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37485 | jx9_result_bool(pCtx, 0); | ||
37486 | return JX9_OK; | ||
37487 | } | ||
37488 | /* Extract our private data */ | ||
37489 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
37490 | /* Make sure we are dealing with a valid io_private instance */ | ||
37491 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
37492 | /*Expecting an IO handle */ | ||
37493 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37494 | jx9_result_bool(pCtx, 0); | ||
37495 | return JX9_OK; | ||
37496 | } | ||
37497 | /* Point to the target IO stream device */ | ||
37498 | pStream = pDev->pStream; | ||
37499 | if( pStream == 0 || pStream->xLock == 0){ | ||
37500 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37501 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
37502 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
37503 | ); | ||
37504 | jx9_result_bool(pCtx, 0); | ||
37505 | return JX9_OK; | ||
37506 | } | ||
37507 | /* Requested lock operation */ | ||
37508 | nLock = jx9_value_to_int(apArg[1]); | ||
37509 | /* Lock operation */ | ||
37510 | rc = pStream->xLock(pDev->pHandle, nLock); | ||
37511 | /* IO result */ | ||
37512 | jx9_result_bool(pCtx, rc == JX9_OK); | ||
37513 | return JX9_OK; | ||
37514 | } | ||
37515 | /* | ||
37516 | * int fpassthru(resource $handle) | ||
37517 | * Output all remaining data on a file pointer. | ||
37518 | * Parameters | ||
37519 | * $handle | ||
37520 | * The file pointer. | ||
37521 | * Return | ||
37522 | * Total number of characters read from handle and passed through | ||
37523 | * to the output on success or FALSE on failure. | ||
37524 | */ | ||
37525 | static int jx9Builtin_fpassthru(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37526 | { | ||
37527 | const jx9_io_stream *pStream; | ||
37528 | io_private *pDev; | ||
37529 | jx9_int64 n, nRead; | ||
37530 | char zBuf[8192]; | ||
37531 | int rc; | ||
37532 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
37533 | /* Missing/Invalid arguments, return FALSE */ | ||
37534 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37535 | jx9_result_bool(pCtx, 0); | ||
37536 | return JX9_OK; | ||
37537 | } | ||
37538 | /* Extract our private data */ | ||
37539 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
37540 | /* Make sure we are dealing with a valid io_private instance */ | ||
37541 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
37542 | /*Expecting an IO handle */ | ||
37543 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37544 | jx9_result_bool(pCtx, 0); | ||
37545 | return JX9_OK; | ||
37546 | } | ||
37547 | /* Point to the target IO stream device */ | ||
37548 | pStream = pDev->pStream; | ||
37549 | if( pStream == 0 ){ | ||
37550 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37551 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
37552 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
37553 | ); | ||
37554 | jx9_result_bool(pCtx, 0); | ||
37555 | return JX9_OK; | ||
37556 | } | ||
37557 | /* Perform the requested operation */ | ||
37558 | nRead = 0; | ||
37559 | for(;;){ | ||
37560 | n = StreamRead(pDev, zBuf, sizeof(zBuf)); | ||
37561 | if( n < 1 ){ | ||
37562 | /* Error or EOF */ | ||
37563 | break; | ||
37564 | } | ||
37565 | /* Increment the read counter */ | ||
37566 | nRead += n; | ||
37567 | /* Output data */ | ||
37568 | rc = jx9_context_output(pCtx, zBuf, (int)nRead /* FIXME: 64-bit issues */); | ||
37569 | if( rc == JX9_ABORT ){ | ||
37570 | /* Consumer callback request an operation abort */ | ||
37571 | break; | ||
37572 | } | ||
37573 | } | ||
37574 | /* Total number of bytes readen */ | ||
37575 | jx9_result_int64(pCtx, nRead); | ||
37576 | return JX9_OK; | ||
37577 | } | ||
37578 | /* CSV reader/writer private data */ | ||
37579 | struct csv_data | ||
37580 | { | ||
37581 | int delimiter; /* Delimiter. Default ', ' */ | ||
37582 | int enclosure; /* Enclosure. Default '"'*/ | ||
37583 | io_private *pDev; /* Open stream handle */ | ||
37584 | int iCount; /* Counter */ | ||
37585 | }; | ||
37586 | /* | ||
37587 | * The following callback is used by the fputcsv() function inorder to iterate | ||
37588 | * throw array entries and output CSV data based on the current key and it's | ||
37589 | * associated data. | ||
37590 | */ | ||
37591 | static int csv_write_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData) | ||
37592 | { | ||
37593 | struct csv_data *pData = (struct csv_data *)pUserData; | ||
37594 | const char *zData; | ||
37595 | int nLen, c2; | ||
37596 | sxu32 n; | ||
37597 | /* Point to the raw data */ | ||
37598 | zData = jx9_value_to_string(pValue, &nLen); | ||
37599 | if( nLen < 1 ){ | ||
37600 | /* Nothing to write */ | ||
37601 | return JX9_OK; | ||
37602 | } | ||
37603 | if( pData->iCount > 0 ){ | ||
37604 | /* Write the delimiter */ | ||
37605 | pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->delimiter, sizeof(char)); | ||
37606 | } | ||
37607 | n = 1; | ||
37608 | c2 = 0; | ||
37609 | if( SyByteFind(zData, (sxu32)nLen, pData->delimiter, 0) == SXRET_OK || | ||
37610 | SyByteFind(zData, (sxu32)nLen, pData->enclosure, &n) == SXRET_OK ){ | ||
37611 | c2 = 1; | ||
37612 | if( n == 0 ){ | ||
37613 | c2 = 2; | ||
37614 | } | ||
37615 | /* Write the enclosure */ | ||
37616 | pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); | ||
37617 | if( c2 > 1 ){ | ||
37618 | pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); | ||
37619 | } | ||
37620 | } | ||
37621 | /* Write the data */ | ||
37622 | if( pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)zData, (jx9_int64)nLen) < 1 ){ | ||
37623 | SXUNUSED(pKey); /* cc warning */ | ||
37624 | return JX9_ABORT; | ||
37625 | } | ||
37626 | if( c2 > 0 ){ | ||
37627 | /* Write the enclosure */ | ||
37628 | pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); | ||
37629 | if( c2 > 1 ){ | ||
37630 | pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); | ||
37631 | } | ||
37632 | } | ||
37633 | pData->iCount++; | ||
37634 | return JX9_OK; | ||
37635 | } | ||
37636 | /* | ||
37637 | * int fputcsv(resource $handle, array $fields[, string $delimiter = ', '[, string $enclosure = '"' ]]) | ||
37638 | * Format line as CSV and write to file pointer. | ||
37639 | * Parameters | ||
37640 | * $handle | ||
37641 | * Open file handle. | ||
37642 | * $fields | ||
37643 | * An array of values. | ||
37644 | * $delimiter | ||
37645 | * The optional delimiter parameter sets the field delimiter (one character only). | ||
37646 | * $enclosure | ||
37647 | * The optional enclosure parameter sets the field enclosure (one character only). | ||
37648 | */ | ||
37649 | static int jx9Builtin_fputcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37650 | { | ||
37651 | const jx9_io_stream *pStream; | ||
37652 | struct csv_data sCsv; | ||
37653 | io_private *pDev; | ||
37654 | char *zEol; | ||
37655 | int eolen; | ||
37656 | if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ | ||
37657 | /* Missing/Invalid arguments, return FALSE */ | ||
37658 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Missing/Invalid arguments"); | ||
37659 | jx9_result_bool(pCtx, 0); | ||
37660 | return JX9_OK; | ||
37661 | } | ||
37662 | /* Extract our private data */ | ||
37663 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
37664 | /* Make sure we are dealing with a valid io_private instance */ | ||
37665 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
37666 | /*Expecting an IO handle */ | ||
37667 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37668 | jx9_result_bool(pCtx, 0); | ||
37669 | return JX9_OK; | ||
37670 | } | ||
37671 | /* Point to the target IO stream device */ | ||
37672 | pStream = pDev->pStream; | ||
37673 | if( pStream == 0 || pStream->xWrite == 0){ | ||
37674 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37675 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
37676 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
37677 | ); | ||
37678 | jx9_result_bool(pCtx, 0); | ||
37679 | return JX9_OK; | ||
37680 | } | ||
37681 | /* Set default csv separator */ | ||
37682 | sCsv.delimiter = ','; | ||
37683 | sCsv.enclosure = '"'; | ||
37684 | sCsv.pDev = pDev; | ||
37685 | sCsv.iCount = 0; | ||
37686 | if( nArg > 2 ){ | ||
37687 | /* User delimiter */ | ||
37688 | const char *z; | ||
37689 | int n; | ||
37690 | z = jx9_value_to_string(apArg[2], &n); | ||
37691 | if( n > 0 ){ | ||
37692 | sCsv.delimiter = z[0]; | ||
37693 | } | ||
37694 | if( nArg > 3 ){ | ||
37695 | z = jx9_value_to_string(apArg[3], &n); | ||
37696 | if( n > 0 ){ | ||
37697 | sCsv.enclosure = z[0]; | ||
37698 | } | ||
37699 | } | ||
37700 | } | ||
37701 | /* Iterate throw array entries and write csv data */ | ||
37702 | jx9_array_walk(apArg[1], csv_write_callback, &sCsv); | ||
37703 | /* Write a line ending */ | ||
37704 | #ifdef __WINNT__ | ||
37705 | zEol = "\r\n"; | ||
37706 | eolen = (int)sizeof("\r\n")-1; | ||
37707 | #else | ||
37708 | /* Assume UNIX LF */ | ||
37709 | zEol = "\n"; | ||
37710 | eolen = (int)sizeof(char); | ||
37711 | #endif | ||
37712 | pDev->pStream->xWrite(pDev->pHandle, (const void *)zEol, eolen); | ||
37713 | return JX9_OK; | ||
37714 | } | ||
37715 | /* | ||
37716 | * fprintf, vfprintf private data. | ||
37717 | * An instance of the following structure is passed to the formatted | ||
37718 | * input consumer callback defined below. | ||
37719 | */ | ||
37720 | typedef struct fprintf_data fprintf_data; | ||
37721 | struct fprintf_data | ||
37722 | { | ||
37723 | io_private *pIO; /* IO stream */ | ||
37724 | jx9_int64 nCount; /* Total number of bytes written */ | ||
37725 | }; | ||
37726 | /* | ||
37727 | * Callback [i.e: Formatted input consumer] for the fprintf function. | ||
37728 | */ | ||
37729 | static int fprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) | ||
37730 | { | ||
37731 | fprintf_data *pFdata = (fprintf_data *)pUserData; | ||
37732 | jx9_int64 n; | ||
37733 | /* Write the formatted data */ | ||
37734 | n = pFdata->pIO->pStream->xWrite(pFdata->pIO->pHandle, (const void *)zInput, nLen); | ||
37735 | if( n < 1 ){ | ||
37736 | SXUNUSED(pCtx); /* cc warning */ | ||
37737 | /* IO error, abort immediately */ | ||
37738 | return SXERR_ABORT; | ||
37739 | } | ||
37740 | /* Increment counter */ | ||
37741 | pFdata->nCount += n; | ||
37742 | return JX9_OK; | ||
37743 | } | ||
37744 | /* | ||
37745 | * int fprintf(resource $handle, string $format[, mixed $args [, mixed $... ]]) | ||
37746 | * Write a formatted string to a stream. | ||
37747 | * Parameters | ||
37748 | * $handle | ||
37749 | * The file pointer. | ||
37750 | * $format | ||
37751 | * String format (see sprintf()). | ||
37752 | * Return | ||
37753 | * The length of the written string. | ||
37754 | */ | ||
37755 | static int jx9Builtin_fprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37756 | { | ||
37757 | fprintf_data sFdata; | ||
37758 | const char *zFormat; | ||
37759 | io_private *pDev; | ||
37760 | int nLen; | ||
37761 | if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ | ||
37762 | /* Missing/Invalid arguments, return zero */ | ||
37763 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments"); | ||
37764 | jx9_result_int(pCtx, 0); | ||
37765 | return JX9_OK; | ||
37766 | } | ||
37767 | /* Extract our private data */ | ||
37768 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
37769 | /* Make sure we are dealing with a valid io_private instance */ | ||
37770 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
37771 | /*Expecting an IO handle */ | ||
37772 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37773 | jx9_result_int(pCtx, 0); | ||
37774 | return JX9_OK; | ||
37775 | } | ||
37776 | /* Point to the target IO stream device */ | ||
37777 | if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ | ||
37778 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37779 | "IO routine(%s) not implemented in the underlying stream(%s) device", | ||
37780 | jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream" | ||
37781 | ); | ||
37782 | jx9_result_int(pCtx, 0); | ||
37783 | return JX9_OK; | ||
37784 | } | ||
37785 | /* Extract the string format */ | ||
37786 | zFormat = jx9_value_to_string(apArg[1], &nLen); | ||
37787 | if( nLen < 1 ){ | ||
37788 | /* Empty string, return zero */ | ||
37789 | jx9_result_int(pCtx, 0); | ||
37790 | return JX9_OK; | ||
37791 | } | ||
37792 | /* Prepare our private data */ | ||
37793 | sFdata.nCount = 0; | ||
37794 | sFdata.pIO = pDev; | ||
37795 | /* Format the string */ | ||
37796 | jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, nArg - 1, &apArg[1], (void *)&sFdata, FALSE); | ||
37797 | /* Return total number of bytes written */ | ||
37798 | jx9_result_int64(pCtx, sFdata.nCount); | ||
37799 | return JX9_OK; | ||
37800 | } | ||
37801 | /* | ||
37802 | * int vfprintf(resource $handle, string $format, array $args) | ||
37803 | * Write a formatted string to a stream. | ||
37804 | * Parameters | ||
37805 | * $handle | ||
37806 | * The file pointer. | ||
37807 | * $format | ||
37808 | * String format (see sprintf()). | ||
37809 | * $args | ||
37810 | * User arguments. | ||
37811 | * Return | ||
37812 | * The length of the written string. | ||
37813 | */ | ||
37814 | static int jx9Builtin_vfprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
37815 | { | ||
37816 | fprintf_data sFdata; | ||
37817 | const char *zFormat; | ||
37818 | jx9_hashmap *pMap; | ||
37819 | io_private *pDev; | ||
37820 | SySet sArg; | ||
37821 | int n, nLen; | ||
37822 | if( nArg < 3 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) || !jx9_value_is_json_array(apArg[2]) ){ | ||
37823 | /* Missing/Invalid arguments, return zero */ | ||
37824 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments"); | ||
37825 | jx9_result_int(pCtx, 0); | ||
37826 | return JX9_OK; | ||
37827 | } | ||
37828 | /* Extract our private data */ | ||
37829 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
37830 | /* Make sure we are dealing with a valid io_private instance */ | ||
37831 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
37832 | /*Expecting an IO handle */ | ||
37833 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
37834 | jx9_result_int(pCtx, 0); | ||
37835 | return JX9_OK; | ||
37836 | } | ||
37837 | /* Point to the target IO stream device */ | ||
37838 | if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ | ||
37839 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
37840 | "IO routine(%s) not implemented in the underlying stream(%s) device", | ||
37841 | jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream" | ||
37842 | ); | ||
37843 | jx9_result_int(pCtx, 0); | ||
37844 | return JX9_OK; | ||
37845 | } | ||
37846 | /* Extract the string format */ | ||
37847 | zFormat = jx9_value_to_string(apArg[1], &nLen); | ||
37848 | if( nLen < 1 ){ | ||
37849 | /* Empty string, return zero */ | ||
37850 | jx9_result_int(pCtx, 0); | ||
37851 | return JX9_OK; | ||
37852 | } | ||
37853 | /* Point to hashmap */ | ||
37854 | pMap = (jx9_hashmap *)apArg[2]->x.pOther; | ||
37855 | /* Extract arguments from the hashmap */ | ||
37856 | n = jx9HashmapValuesToSet(pMap, &sArg); | ||
37857 | /* Prepare our private data */ | ||
37858 | sFdata.nCount = 0; | ||
37859 | sFdata.pIO = pDev; | ||
37860 | /* Format the string */ | ||
37861 | jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&sFdata, TRUE); | ||
37862 | /* Return total number of bytes written*/ | ||
37863 | jx9_result_int64(pCtx, sFdata.nCount); | ||
37864 | SySetRelease(&sArg); | ||
37865 | return JX9_OK; | ||
37866 | } | ||
37867 | /* | ||
37868 | * Convert open modes (string passed to the fopen() function) [i.e: 'r', 'w+', 'a', ...] into JX9 flags. | ||
37869 | * According to the JX9 reference manual: | ||
37870 | * The mode parameter specifies the type of access you require to the stream. It may be any of the following | ||
37871 | * 'r' Open for reading only; place the file pointer at the beginning of the file. | ||
37872 | * 'r+' Open for reading and writing; place the file pointer at the beginning of the file. | ||
37873 | * 'w' Open for writing only; place the file pointer at the beginning of the file and truncate the file | ||
37874 | * to zero length. If the file does not exist, attempt to create it. | ||
37875 | * 'w+' Open for reading and writing; place the file pointer at the beginning of the file and truncate | ||
37876 | * the file to zero length. If the file does not exist, attempt to create it. | ||
37877 | * 'a' Open for writing only; place the file pointer at the end of the file. If the file does not | ||
37878 | * exist, attempt to create it. | ||
37879 | * 'a+' Open for reading and writing; place the file pointer at the end of the file. If the file does | ||
37880 | * not exist, attempt to create it. | ||
37881 | * 'x' Create and open for writing only; place the file pointer at the beginning of the file. If the file | ||
37882 | * already exists, | ||
37883 | * the fopen() call will fail by returning FALSE and generating an error of level E_WARNING. If the file | ||
37884 | * does not exist attempt to create it. This is equivalent to specifying O_EXCL|O_CREAT flags for | ||
37885 | * the underlying open(2) system call. | ||
37886 | * 'x+' Create and open for reading and writing; otherwise it has the same behavior as 'x'. | ||
37887 | * 'c' Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated | ||
37888 | * (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer | ||
37889 | * is positioned on the beginning of the file. | ||
37890 | * This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file | ||
37891 | * as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can | ||
37892 | * be used after the lock is requested). | ||
37893 | * 'c+' Open the file for reading and writing; otherwise it has the same behavior as 'c'. | ||
37894 | */ | ||
37895 | static int StrModeToFlags(jx9_context *pCtx, const char *zMode, int nLen) | ||
37896 | { | ||
37897 | const char *zEnd = &zMode[nLen]; | ||
37898 | int iFlag = 0; | ||
37899 | int c; | ||
37900 | if( nLen < 1 ){ | ||
37901 | /* Open in a read-only mode */ | ||
37902 | return JX9_IO_OPEN_RDONLY; | ||
37903 | } | ||
37904 | c = zMode[0]; | ||
37905 | if( c == 'r' || c == 'R' ){ | ||
37906 | /* Read-only access */ | ||
37907 | iFlag = JX9_IO_OPEN_RDONLY; | ||
37908 | zMode++; /* Advance */ | ||
37909 | if( zMode < zEnd ){ | ||
37910 | c = zMode[0]; | ||
37911 | if( c == '+' || c == 'w' || c == 'W' ){ | ||
37912 | /* Read+Write access */ | ||
37913 | iFlag = JX9_IO_OPEN_RDWR; | ||
37914 | } | ||
37915 | } | ||
37916 | }else if( c == 'w' || c == 'W' ){ | ||
37917 | /* Overwrite mode. | ||
37918 | * If the file does not exists, try to create it | ||
37919 | */ | ||
37920 | iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_CREATE; | ||
37921 | zMode++; /* Advance */ | ||
37922 | if( zMode < zEnd ){ | ||
37923 | c = zMode[0]; | ||
37924 | if( c == '+' || c == 'r' || c == 'R' ){ | ||
37925 | /* Read+Write access */ | ||
37926 | iFlag &= ~JX9_IO_OPEN_WRONLY; | ||
37927 | iFlag |= JX9_IO_OPEN_RDWR; | ||
37928 | } | ||
37929 | } | ||
37930 | }else if( c == 'a' || c == 'A' ){ | ||
37931 | /* Append mode (place the file pointer at the end of the file). | ||
37932 | * Create the file if it does not exists. | ||
37933 | */ | ||
37934 | iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_APPEND|JX9_IO_OPEN_CREATE; | ||
37935 | zMode++; /* Advance */ | ||
37936 | if( zMode < zEnd ){ | ||
37937 | c = zMode[0]; | ||
37938 | if( c == '+' ){ | ||
37939 | /* Read-Write access */ | ||
37940 | iFlag &= ~JX9_IO_OPEN_WRONLY; | ||
37941 | iFlag |= JX9_IO_OPEN_RDWR; | ||
37942 | } | ||
37943 | } | ||
37944 | }else if( c == 'x' || c == 'X' ){ | ||
37945 | /* Exclusive access. | ||
37946 | * If the file already exists, return immediately with a failure code. | ||
37947 | * Otherwise create a new file. | ||
37948 | */ | ||
37949 | iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_EXCL; | ||
37950 | zMode++; /* Advance */ | ||
37951 | if( zMode < zEnd ){ | ||
37952 | c = zMode[0]; | ||
37953 | if( c == '+' || c == 'r' || c == 'R' ){ | ||
37954 | /* Read-Write access */ | ||
37955 | iFlag &= ~JX9_IO_OPEN_WRONLY; | ||
37956 | iFlag |= JX9_IO_OPEN_RDWR; | ||
37957 | } | ||
37958 | } | ||
37959 | }else if( c == 'c' || c == 'C' ){ | ||
37960 | /* Overwrite mode.Create the file if it does not exists.*/ | ||
37961 | iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_CREATE; | ||
37962 | zMode++; /* Advance */ | ||
37963 | if( zMode < zEnd ){ | ||
37964 | c = zMode[0]; | ||
37965 | if( c == '+' ){ | ||
37966 | /* Read-Write access */ | ||
37967 | iFlag &= ~JX9_IO_OPEN_WRONLY; | ||
37968 | iFlag |= JX9_IO_OPEN_RDWR; | ||
37969 | } | ||
37970 | } | ||
37971 | }else{ | ||
37972 | /* Invalid mode. Assume a read only open */ | ||
37973 | jx9_context_throw_error(pCtx, JX9_CTX_NOTICE, "Invalid open mode, JX9 is assuming a Read-Only open"); | ||
37974 | iFlag = JX9_IO_OPEN_RDONLY; | ||
37975 | } | ||
37976 | while( zMode < zEnd ){ | ||
37977 | c = zMode[0]; | ||
37978 | if( c == 'b' || c == 'B' ){ | ||
37979 | iFlag &= ~JX9_IO_OPEN_TEXT; | ||
37980 | iFlag |= JX9_IO_OPEN_BINARY; | ||
37981 | }else if( c == 't' || c == 'T' ){ | ||
37982 | iFlag &= ~JX9_IO_OPEN_BINARY; | ||
37983 | iFlag |= JX9_IO_OPEN_TEXT; | ||
37984 | } | ||
37985 | zMode++; | ||
37986 | } | ||
37987 | return iFlag; | ||
37988 | } | ||
37989 | /* | ||
37990 | * Initialize the IO private structure. | ||
37991 | */ | ||
37992 | static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut) | ||
37993 | { | ||
37994 | pOut->pStream = pStream; | ||
37995 | SyBlobInit(&pOut->sBuffer, &pVm->sAllocator); | ||
37996 | pOut->nOfft = 0; | ||
37997 | /* Set the magic number */ | ||
37998 | pOut->iMagic = IO_PRIVATE_MAGIC; | ||
37999 | } | ||
38000 | /* | ||
38001 | * Release the IO private structure. | ||
38002 | */ | ||
38003 | static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev) | ||
38004 | { | ||
38005 | SyBlobRelease(&pDev->sBuffer); | ||
38006 | pDev->iMagic = 0x2126; /* Invalid magic number so we can detetct misuse */ | ||
38007 | /* Release the whole structure */ | ||
38008 | jx9_context_free_chunk(pCtx, pDev); | ||
38009 | } | ||
38010 | /* | ||
38011 | * Reset the IO private structure. | ||
38012 | */ | ||
38013 | static void ResetIOPrivate(io_private *pDev) | ||
38014 | { | ||
38015 | SyBlobReset(&pDev->sBuffer); | ||
38016 | pDev->nOfft = 0; | ||
38017 | } | ||
38018 | /* Forward declaration */ | ||
38019 | static int is_jx9_stream(const jx9_io_stream *pStream); | ||
38020 | /* | ||
38021 | * resource fopen(string $filename, string $mode [, bool $use_include_path = false[, resource $context ]]) | ||
38022 | * Open a file, a URL or any other IO stream. | ||
38023 | * Parameters | ||
38024 | * $filename | ||
38025 | * If filename is of the form "scheme://...", it is assumed to be a URL and JX9 will search | ||
38026 | * for a protocol handler (also known as a wrapper) for that scheme. If no scheme is given | ||
38027 | * then a regular file is assumed. | ||
38028 | * $mode | ||
38029 | * The mode parameter specifies the type of access you require to the stream | ||
38030 | * See the block comment associated with the StrModeToFlags() for the supported | ||
38031 | * modes. | ||
38032 | * $use_include_path | ||
38033 | * You can use the optional second parameter and set it to | ||
38034 | * TRUE, if you want to search for the file in the include_path, too. | ||
38035 | * $context | ||
38036 | * A context stream resource. | ||
38037 | * Return | ||
38038 | * File handle on success or FALSE on failure. | ||
38039 | */ | ||
38040 | static int jx9Builtin_fopen(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38041 | { | ||
38042 | const jx9_io_stream *pStream; | ||
38043 | const char *zUri, *zMode; | ||
38044 | jx9_value *pResource; | ||
38045 | io_private *pDev; | ||
38046 | int iLen, imLen; | ||
38047 | int iOpenFlags; | ||
38048 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
38049 | /* Missing/Invalid arguments, return FALSE */ | ||
38050 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path or URL"); | ||
38051 | jx9_result_bool(pCtx, 0); | ||
38052 | return JX9_OK; | ||
38053 | } | ||
38054 | /* Extract the URI and the desired access mode */ | ||
38055 | zUri = jx9_value_to_string(apArg[0], &iLen); | ||
38056 | if( nArg > 1 ){ | ||
38057 | zMode = jx9_value_to_string(apArg[1], &imLen); | ||
38058 | }else{ | ||
38059 | /* Set a default read-only mode */ | ||
38060 | zMode = "r"; | ||
38061 | imLen = (int)sizeof(char); | ||
38062 | } | ||
38063 | /* Try to extract a stream */ | ||
38064 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zUri, iLen); | ||
38065 | if( pStream == 0 ){ | ||
38066 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
38067 | "No stream device is associated with the given URI(%s)", zUri); | ||
38068 | jx9_result_bool(pCtx, 0); | ||
38069 | return JX9_OK; | ||
38070 | } | ||
38071 | /* Allocate a new IO private instance */ | ||
38072 | pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); | ||
38073 | if( pDev == 0 ){ | ||
38074 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
38075 | jx9_result_bool(pCtx, 0); | ||
38076 | return JX9_OK; | ||
38077 | } | ||
38078 | pResource = 0; | ||
38079 | if( nArg > 3 ){ | ||
38080 | pResource = apArg[3]; | ||
38081 | }else if( is_jx9_stream(pStream) ){ | ||
38082 | /* TICKET 1433-80: The jx9:// stream need a jx9_value to access the underlying | ||
38083 | * virtual machine. | ||
38084 | */ | ||
38085 | pResource = apArg[0]; | ||
38086 | } | ||
38087 | /* Initialize the structure */ | ||
38088 | InitIOPrivate(pCtx->pVm, pStream, pDev); | ||
38089 | /* Convert open mode to JX9 flags */ | ||
38090 | iOpenFlags = StrModeToFlags(pCtx, zMode, imLen); | ||
38091 | /* Try to get a handle */ | ||
38092 | pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zUri, iOpenFlags, | ||
38093 | nArg > 2 ? jx9_value_to_bool(apArg[2]) : FALSE, pResource, FALSE, 0); | ||
38094 | if( pDev->pHandle == 0 ){ | ||
38095 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zUri); | ||
38096 | jx9_result_bool(pCtx, 0); | ||
38097 | jx9_context_free_chunk(pCtx, pDev); | ||
38098 | return JX9_OK; | ||
38099 | } | ||
38100 | /* All done, return the io_private instance as a resource */ | ||
38101 | jx9_result_resource(pCtx, pDev); | ||
38102 | return JX9_OK; | ||
38103 | } | ||
38104 | /* | ||
38105 | * bool fclose(resource $handle) | ||
38106 | * Closes an open file pointer | ||
38107 | * Parameters | ||
38108 | * $handle | ||
38109 | * The file pointer. | ||
38110 | * Return | ||
38111 | * TRUE on success or FALSE on failure. | ||
38112 | */ | ||
38113 | static int jx9Builtin_fclose(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38114 | { | ||
38115 | const jx9_io_stream *pStream; | ||
38116 | io_private *pDev; | ||
38117 | jx9_vm *pVm; | ||
38118 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38119 | /* Missing/Invalid arguments, return FALSE */ | ||
38120 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
38121 | jx9_result_bool(pCtx, 0); | ||
38122 | return JX9_OK; | ||
38123 | } | ||
38124 | /* Extract our private data */ | ||
38125 | pDev = (io_private *)jx9_value_to_resource(apArg[0]); | ||
38126 | /* Make sure we are dealing with a valid io_private instance */ | ||
38127 | if( IO_PRIVATE_INVALID(pDev) ){ | ||
38128 | /*Expecting an IO handle */ | ||
38129 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); | ||
38130 | jx9_result_bool(pCtx, 0); | ||
38131 | return JX9_OK; | ||
38132 | } | ||
38133 | /* Point to the target IO stream device */ | ||
38134 | pStream = pDev->pStream; | ||
38135 | if( pStream == 0 ){ | ||
38136 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, | ||
38137 | "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", | ||
38138 | jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" | ||
38139 | ); | ||
38140 | jx9_result_bool(pCtx, 0); | ||
38141 | return JX9_OK; | ||
38142 | } | ||
38143 | /* Point to the VM that own this context */ | ||
38144 | pVm = pCtx->pVm; | ||
38145 | /* TICKET 1433-62: Keep the STDIN/STDOUT/STDERR handles open */ | ||
38146 | if( pDev != pVm->pStdin && pDev != pVm->pStdout && pDev != pVm->pStderr ){ | ||
38147 | /* Perform the requested operation */ | ||
38148 | jx9StreamCloseHandle(pStream, pDev->pHandle); | ||
38149 | /* Release the IO private structure */ | ||
38150 | ReleaseIOPrivate(pCtx, pDev); | ||
38151 | /* Invalidate the resource handle */ | ||
38152 | jx9_value_release(apArg[0]); | ||
38153 | } | ||
38154 | /* Return TRUE */ | ||
38155 | jx9_result_bool(pCtx, 1); | ||
38156 | return JX9_OK; | ||
38157 | } | ||
38158 | #if !defined(JX9_DISABLE_HASH_FUNC) | ||
38159 | /* | ||
38160 | * MD5/SHA1 digest consumer. | ||
38161 | */ | ||
38162 | static int vfsHashConsumer(const void *pData, unsigned int nLen, void *pUserData) | ||
38163 | { | ||
38164 | /* Append hex chunk verbatim */ | ||
38165 | jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); | ||
38166 | return SXRET_OK; | ||
38167 | } | ||
38168 | /* | ||
38169 | * string md5_file(string $uri[, bool $raw_output = false ]) | ||
38170 | * Calculates the md5 hash of a given file. | ||
38171 | * Parameters | ||
38172 | * $uri | ||
38173 | * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) | ||
38174 | * $raw_output | ||
38175 | * When TRUE, returns the digest in raw binary format with a length of 16. | ||
38176 | * Return | ||
38177 | * Return the MD5 digest on success or FALSE on failure. | ||
38178 | */ | ||
38179 | static int jx9Builtin_md5_file(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38180 | { | ||
38181 | const jx9_io_stream *pStream; | ||
38182 | unsigned char zDigest[16]; | ||
38183 | int raw_output = FALSE; | ||
38184 | const char *zFile; | ||
38185 | MD5Context sCtx; | ||
38186 | char zBuf[8192]; | ||
38187 | void *pHandle; | ||
38188 | jx9_int64 n; | ||
38189 | int nLen; | ||
38190 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
38191 | /* Missing/Invalid arguments, return FALSE */ | ||
38192 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
38193 | jx9_result_bool(pCtx, 0); | ||
38194 | return JX9_OK; | ||
38195 | } | ||
38196 | /* Extract the file path */ | ||
38197 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
38198 | /* Point to the target IO stream device */ | ||
38199 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
38200 | if( pStream == 0 ){ | ||
38201 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
38202 | jx9_result_bool(pCtx, 0); | ||
38203 | return JX9_OK; | ||
38204 | } | ||
38205 | if( nArg > 1 ){ | ||
38206 | raw_output = jx9_value_to_bool(apArg[1]); | ||
38207 | } | ||
38208 | /* Try to open the file in read-only mode */ | ||
38209 | pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); | ||
38210 | if( pHandle == 0 ){ | ||
38211 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
38212 | jx9_result_bool(pCtx, 0); | ||
38213 | return JX9_OK; | ||
38214 | } | ||
38215 | /* Init the MD5 context */ | ||
38216 | MD5Init(&sCtx); | ||
38217 | /* Perform the requested operation */ | ||
38218 | for(;;){ | ||
38219 | n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); | ||
38220 | if( n < 1 ){ | ||
38221 | /* EOF or IO error, break immediately */ | ||
38222 | break; | ||
38223 | } | ||
38224 | MD5Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n); | ||
38225 | } | ||
38226 | /* Close the stream */ | ||
38227 | jx9StreamCloseHandle(pStream, pHandle); | ||
38228 | /* Extract the digest */ | ||
38229 | MD5Final(zDigest, &sCtx); | ||
38230 | if( raw_output ){ | ||
38231 | /* Output raw digest */ | ||
38232 | jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest)); | ||
38233 | }else{ | ||
38234 | /* Perform a binary to hex conversion */ | ||
38235 | SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx); | ||
38236 | } | ||
38237 | return JX9_OK; | ||
38238 | } | ||
38239 | /* | ||
38240 | * string sha1_file(string $uri[, bool $raw_output = false ]) | ||
38241 | * Calculates the SHA1 hash of a given file. | ||
38242 | * Parameters | ||
38243 | * $uri | ||
38244 | * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) | ||
38245 | * $raw_output | ||
38246 | * When TRUE, returns the digest in raw binary format with a length of 20. | ||
38247 | * Return | ||
38248 | * Return the SHA1 digest on success or FALSE on failure. | ||
38249 | */ | ||
38250 | static int jx9Builtin_sha1_file(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38251 | { | ||
38252 | const jx9_io_stream *pStream; | ||
38253 | unsigned char zDigest[20]; | ||
38254 | int raw_output = FALSE; | ||
38255 | const char *zFile; | ||
38256 | SHA1Context sCtx; | ||
38257 | char zBuf[8192]; | ||
38258 | void *pHandle; | ||
38259 | jx9_int64 n; | ||
38260 | int nLen; | ||
38261 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
38262 | /* Missing/Invalid arguments, return FALSE */ | ||
38263 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
38264 | jx9_result_bool(pCtx, 0); | ||
38265 | return JX9_OK; | ||
38266 | } | ||
38267 | /* Extract the file path */ | ||
38268 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
38269 | /* Point to the target IO stream device */ | ||
38270 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
38271 | if( pStream == 0 ){ | ||
38272 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
38273 | jx9_result_bool(pCtx, 0); | ||
38274 | return JX9_OK; | ||
38275 | } | ||
38276 | if( nArg > 1 ){ | ||
38277 | raw_output = jx9_value_to_bool(apArg[1]); | ||
38278 | } | ||
38279 | /* Try to open the file in read-only mode */ | ||
38280 | pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); | ||
38281 | if( pHandle == 0 ){ | ||
38282 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
38283 | jx9_result_bool(pCtx, 0); | ||
38284 | return JX9_OK; | ||
38285 | } | ||
38286 | /* Init the SHA1 context */ | ||
38287 | SHA1Init(&sCtx); | ||
38288 | /* Perform the requested operation */ | ||
38289 | for(;;){ | ||
38290 | n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); | ||
38291 | if( n < 1 ){ | ||
38292 | /* EOF or IO error, break immediately */ | ||
38293 | break; | ||
38294 | } | ||
38295 | SHA1Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n); | ||
38296 | } | ||
38297 | /* Close the stream */ | ||
38298 | jx9StreamCloseHandle(pStream, pHandle); | ||
38299 | /* Extract the digest */ | ||
38300 | SHA1Final(&sCtx, zDigest); | ||
38301 | if( raw_output ){ | ||
38302 | /* Output raw digest */ | ||
38303 | jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest)); | ||
38304 | }else{ | ||
38305 | /* Perform a binary to hex conversion */ | ||
38306 | SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx); | ||
38307 | } | ||
38308 | return JX9_OK; | ||
38309 | } | ||
38310 | #endif /* JX9_DISABLE_HASH_FUNC */ | ||
38311 | /* | ||
38312 | * array parse_ini_file(string $filename[, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]] ) | ||
38313 | * Parse a configuration file. | ||
38314 | * Parameters | ||
38315 | * $filename | ||
38316 | * The filename of the ini file being parsed. | ||
38317 | * $process_sections | ||
38318 | * By setting the process_sections parameter to TRUE, you get a multidimensional array | ||
38319 | * with the section names and settings included. | ||
38320 | * The default for process_sections is FALSE. | ||
38321 | * $scanner_mode | ||
38322 | * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. | ||
38323 | * If INI_SCANNER_RAW is supplied, then option values will not be parsed. | ||
38324 | * Return | ||
38325 | * The settings are returned as an associative array on success. | ||
38326 | * Otherwise is returned. | ||
38327 | */ | ||
38328 | static int jx9Builtin_parse_ini_file(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38329 | { | ||
38330 | const jx9_io_stream *pStream; | ||
38331 | const char *zFile; | ||
38332 | SyBlob sContents; | ||
38333 | void *pHandle; | ||
38334 | int nLen; | ||
38335 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
38336 | /* Missing/Invalid arguments, return FALSE */ | ||
38337 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
38338 | jx9_result_bool(pCtx, 0); | ||
38339 | return JX9_OK; | ||
38340 | } | ||
38341 | /* Extract the file path */ | ||
38342 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
38343 | /* Point to the target IO stream device */ | ||
38344 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
38345 | if( pStream == 0 ){ | ||
38346 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
38347 | jx9_result_bool(pCtx, 0); | ||
38348 | return JX9_OK; | ||
38349 | } | ||
38350 | /* Try to open the file in read-only mode */ | ||
38351 | pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); | ||
38352 | if( pHandle == 0 ){ | ||
38353 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
38354 | jx9_result_bool(pCtx, 0); | ||
38355 | return JX9_OK; | ||
38356 | } | ||
38357 | SyBlobInit(&sContents, &pCtx->pVm->sAllocator); | ||
38358 | /* Read the whole file */ | ||
38359 | jx9StreamReadWholeFile(pHandle, pStream, &sContents); | ||
38360 | if( SyBlobLength(&sContents) < 1 ){ | ||
38361 | /* Empty buffer, return FALSE */ | ||
38362 | jx9_result_bool(pCtx, 0); | ||
38363 | }else{ | ||
38364 | /* Process the raw INI buffer */ | ||
38365 | jx9ParseIniString(pCtx, (const char *)SyBlobData(&sContents), SyBlobLength(&sContents), | ||
38366 | nArg > 1 ? jx9_value_to_bool(apArg[1]) : 0); | ||
38367 | } | ||
38368 | /* Close the stream */ | ||
38369 | jx9StreamCloseHandle(pStream, pHandle); | ||
38370 | /* Release the working buffer */ | ||
38371 | SyBlobRelease(&sContents); | ||
38372 | return JX9_OK; | ||
38373 | } | ||
38374 | /* | ||
38375 | * Section: | ||
38376 | * ZIP archive processing. | ||
38377 | * Authors: | ||
38378 | * Symisc Systems, devel@symisc.net. | ||
38379 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
38380 | * Status: | ||
38381 | * Stable. | ||
38382 | */ | ||
38383 | typedef struct zip_raw_data zip_raw_data; | ||
38384 | struct zip_raw_data | ||
38385 | { | ||
38386 | int iType; /* Where the raw data is stored */ | ||
38387 | union raw_data{ | ||
38388 | struct mmap_data{ | ||
38389 | void *pMap; /* Memory mapped data */ | ||
38390 | jx9_int64 nSize; /* Map size */ | ||
38391 | const jx9_vfs *pVfs; /* Underlying vfs */ | ||
38392 | }mmap; | ||
38393 | SyBlob sBlob; /* Memory buffer */ | ||
38394 | }raw; | ||
38395 | }; | ||
38396 | #define ZIP_RAW_DATA_MMAPED 1 /* Memory mapped ZIP raw data */ | ||
38397 | #define ZIP_RAW_DATA_MEMBUF 2 /* ZIP raw data stored in a dynamically | ||
38398 | * allocated memory chunk. | ||
38399 | */ | ||
38400 | /* | ||
38401 | * mixed zip_open(string $filename) | ||
38402 | * Opens a new zip archive for reading. | ||
38403 | * Parameters | ||
38404 | * $filename | ||
38405 | * The file name of the ZIP archive to open. | ||
38406 | * Return | ||
38407 | * A resource handle for later use with zip_read() and zip_close() or FALSE on failure. | ||
38408 | */ | ||
38409 | static int jx9Builtin_zip_open(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38410 | { | ||
38411 | const jx9_io_stream *pStream; | ||
38412 | SyArchive *pArchive; | ||
38413 | zip_raw_data *pRaw; | ||
38414 | const char *zFile; | ||
38415 | SyBlob *pContents; | ||
38416 | void *pHandle; | ||
38417 | int nLen; | ||
38418 | sxi32 rc; | ||
38419 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
38420 | /* Missing/Invalid arguments, return FALSE */ | ||
38421 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); | ||
38422 | jx9_result_bool(pCtx, 0); | ||
38423 | return JX9_OK; | ||
38424 | } | ||
38425 | /* Extract the file path */ | ||
38426 | zFile = jx9_value_to_string(apArg[0], &nLen); | ||
38427 | /* Point to the target IO stream device */ | ||
38428 | pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); | ||
38429 | if( pStream == 0 ){ | ||
38430 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); | ||
38431 | jx9_result_bool(pCtx, 0); | ||
38432 | return JX9_OK; | ||
38433 | } | ||
38434 | /* Create an in-memory archive */ | ||
38435 | pArchive = (SyArchive *)jx9_context_alloc_chunk(pCtx, sizeof(SyArchive)+sizeof(zip_raw_data), TRUE, FALSE); | ||
38436 | if( pArchive == 0 ){ | ||
38437 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "JX9 is running out of memory"); | ||
38438 | jx9_result_bool(pCtx, 0); | ||
38439 | return JX9_OK; | ||
38440 | } | ||
38441 | pRaw = (zip_raw_data *)&pArchive[1]; | ||
38442 | /* Initialize the archive */ | ||
38443 | SyArchiveInit(pArchive, &pCtx->pVm->sAllocator, 0, 0); | ||
38444 | /* Extract the default stream */ | ||
38445 | if( pStream == pCtx->pVm->pDefStream /* file:// stream*/){ | ||
38446 | const jx9_vfs *pVfs; | ||
38447 | /* Try to get a memory view of the whole file since ZIP files | ||
38448 | * tends to be very big this days, this is a huge performance win. | ||
38449 | */ | ||
38450 | pVfs = jx9ExportBuiltinVfs(); | ||
38451 | if( pVfs && pVfs->xMmap ){ | ||
38452 | rc = pVfs->xMmap(zFile, &pRaw->raw.mmap.pMap, &pRaw->raw.mmap.nSize); | ||
38453 | if( rc == JX9_OK ){ | ||
38454 | /* Nice, Extract the whole archive */ | ||
38455 | rc = SyZipExtractFromBuf(pArchive, (const char *)pRaw->raw.mmap.pMap, (sxu32)pRaw->raw.mmap.nSize); | ||
38456 | if( rc != SXRET_OK ){ | ||
38457 | if( pVfs->xUnmap ){ | ||
38458 | pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize); | ||
38459 | } | ||
38460 | /* Release the allocated chunk */ | ||
38461 | jx9_context_free_chunk(pCtx, pArchive); | ||
38462 | /* Something goes wrong with this ZIP archive, return FALSE */ | ||
38463 | jx9_result_bool(pCtx, 0); | ||
38464 | return JX9_OK; | ||
38465 | } | ||
38466 | /* Archive successfully opened */ | ||
38467 | pRaw->iType = ZIP_RAW_DATA_MMAPED; | ||
38468 | pRaw->raw.mmap.pVfs = pVfs; | ||
38469 | goto success; | ||
38470 | } | ||
38471 | } | ||
38472 | /* FALL THROUGH */ | ||
38473 | } | ||
38474 | /* Try to open the file in read-only mode */ | ||
38475 | pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); | ||
38476 | if( pHandle == 0 ){ | ||
38477 | jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); | ||
38478 | jx9_result_bool(pCtx, 0); | ||
38479 | return JX9_OK; | ||
38480 | } | ||
38481 | pContents = &pRaw->raw.sBlob; | ||
38482 | SyBlobInit(pContents, &pCtx->pVm->sAllocator); | ||
38483 | /* Read the whole file */ | ||
38484 | jx9StreamReadWholeFile(pHandle, pStream, pContents); | ||
38485 | /* Assume an invalid ZIP file */ | ||
38486 | rc = SXERR_INVALID; | ||
38487 | if( SyBlobLength(pContents) > 0 ){ | ||
38488 | /* Extract archive entries */ | ||
38489 | rc = SyZipExtractFromBuf(pArchive, (const char *)SyBlobData(pContents), SyBlobLength(pContents)); | ||
38490 | } | ||
38491 | pRaw->iType = ZIP_RAW_DATA_MEMBUF; | ||
38492 | /* Close the stream */ | ||
38493 | jx9StreamCloseHandle(pStream, pHandle); | ||
38494 | if( rc != SXRET_OK ){ | ||
38495 | /* Release the working buffer */ | ||
38496 | SyBlobRelease(pContents); | ||
38497 | /* Release the allocated chunk */ | ||
38498 | jx9_context_free_chunk(pCtx, pArchive); | ||
38499 | /* Something goes wrong with this ZIP archive, return FALSE */ | ||
38500 | jx9_result_bool(pCtx, 0); | ||
38501 | return JX9_OK; | ||
38502 | } | ||
38503 | success: | ||
38504 | /* Reset the loop cursor */ | ||
38505 | SyArchiveResetLoopCursor(pArchive); | ||
38506 | /* Return the in-memory archive as a resource handle */ | ||
38507 | jx9_result_resource(pCtx, pArchive); | ||
38508 | return JX9_OK; | ||
38509 | } | ||
38510 | /* | ||
38511 | * void zip_close(resource $zip) | ||
38512 | * Close an in-memory ZIP archive. | ||
38513 | * Parameters | ||
38514 | * $zip | ||
38515 | * A ZIP file previously opened with zip_open(). | ||
38516 | * Return | ||
38517 | * null. | ||
38518 | */ | ||
38519 | static int jx9Builtin_zip_close(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38520 | { | ||
38521 | SyArchive *pArchive; | ||
38522 | zip_raw_data *pRaw; | ||
38523 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38524 | /* Missing/Invalid arguments */ | ||
38525 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); | ||
38526 | return JX9_OK; | ||
38527 | } | ||
38528 | /* Point to the in-memory archive */ | ||
38529 | pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); | ||
38530 | /* Make sure we are dealing with a valid ZIP archive */ | ||
38531 | if( SXARCH_INVALID(pArchive) ){ | ||
38532 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); | ||
38533 | return JX9_OK; | ||
38534 | } | ||
38535 | /* Release the archive */ | ||
38536 | SyArchiveRelease(pArchive); | ||
38537 | pRaw = (zip_raw_data *)&pArchive[1]; | ||
38538 | if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ | ||
38539 | SyBlobRelease(&pRaw->raw.sBlob); | ||
38540 | }else{ | ||
38541 | const jx9_vfs *pVfs = pRaw->raw.mmap.pVfs; | ||
38542 | if( pVfs->xUnmap ){ | ||
38543 | /* Unmap the memory view */ | ||
38544 | pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize); | ||
38545 | } | ||
38546 | } | ||
38547 | /* Release the memory chunk */ | ||
38548 | jx9_context_free_chunk(pCtx, pArchive); | ||
38549 | return JX9_OK; | ||
38550 | } | ||
38551 | /* | ||
38552 | * mixed zip_read(resource $zip) | ||
38553 | * Reads the next entry from an in-memory ZIP archive. | ||
38554 | * Parameters | ||
38555 | * $zip | ||
38556 | * A ZIP file previously opened with zip_open(). | ||
38557 | * Return | ||
38558 | * A directory entry resource for later use with the zip_entry_... functions | ||
38559 | * or FALSE if there are no more entries to read, or an error code if an error occurred. | ||
38560 | */ | ||
38561 | static int jx9Builtin_zip_read(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38562 | { | ||
38563 | SyArchiveEntry *pNext = 0; /* cc warning */ | ||
38564 | SyArchive *pArchive; | ||
38565 | sxi32 rc; | ||
38566 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38567 | /* Missing/Invalid arguments */ | ||
38568 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); | ||
38569 | /* return FALSE */ | ||
38570 | jx9_result_bool(pCtx, 0); | ||
38571 | return JX9_OK; | ||
38572 | } | ||
38573 | /* Point to the in-memory archive */ | ||
38574 | pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); | ||
38575 | /* Make sure we are dealing with a valid ZIP archive */ | ||
38576 | if( SXARCH_INVALID(pArchive) ){ | ||
38577 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); | ||
38578 | /* return FALSE */ | ||
38579 | jx9_result_bool(pCtx, 0); | ||
38580 | return JX9_OK; | ||
38581 | } | ||
38582 | /* Extract the next entry */ | ||
38583 | rc = SyArchiveGetNextEntry(pArchive, &pNext); | ||
38584 | if( rc != SXRET_OK ){ | ||
38585 | /* No more entries in the central directory, return FALSE */ | ||
38586 | jx9_result_bool(pCtx, 0); | ||
38587 | }else{ | ||
38588 | /* Return as a resource handle */ | ||
38589 | jx9_result_resource(pCtx, pNext); | ||
38590 | /* Point to the ZIP raw data */ | ||
38591 | pNext->pUserData = (void *)&pArchive[1]; | ||
38592 | } | ||
38593 | return JX9_OK; | ||
38594 | } | ||
38595 | /* | ||
38596 | * bool zip_entry_open(resource $zip, resource $zip_entry[, string $mode ]) | ||
38597 | * Open a directory entry for reading | ||
38598 | * Parameters | ||
38599 | * $zip | ||
38600 | * A ZIP file previously opened with zip_open(). | ||
38601 | * $zip_entry | ||
38602 | * A directory entry returned by zip_read(). | ||
38603 | * $mode | ||
38604 | * Not used | ||
38605 | * Return | ||
38606 | * A directory entry resource for later use with the zip_entry_... functions | ||
38607 | * or FALSE if there are no more entries to read, or an error code if an error occurred. | ||
38608 | */ | ||
38609 | static int jx9Builtin_zip_entry_open(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38610 | { | ||
38611 | SyArchiveEntry *pEntry; | ||
38612 | SyArchive *pArchive; | ||
38613 | if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_resource(apArg[1]) ){ | ||
38614 | /* Missing/Invalid arguments */ | ||
38615 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); | ||
38616 | /* return FALSE */ | ||
38617 | jx9_result_bool(pCtx, 0); | ||
38618 | return JX9_OK; | ||
38619 | } | ||
38620 | /* Point to the in-memory archive */ | ||
38621 | pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); | ||
38622 | /* Make sure we are dealing with a valid ZIP archive */ | ||
38623 | if( SXARCH_INVALID(pArchive) ){ | ||
38624 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); | ||
38625 | /* return FALSE */ | ||
38626 | jx9_result_bool(pCtx, 0); | ||
38627 | return JX9_OK; | ||
38628 | } | ||
38629 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38630 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[1]); | ||
38631 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38632 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38633 | /* return FALSE */ | ||
38634 | jx9_result_bool(pCtx, 0); | ||
38635 | return JX9_OK; | ||
38636 | } | ||
38637 | /* All done. Actually this function is a no-op, return TRUE */ | ||
38638 | jx9_result_bool(pCtx, 1); | ||
38639 | return JX9_OK; | ||
38640 | } | ||
38641 | /* | ||
38642 | * bool zip_entry_close(resource $zip_entry) | ||
38643 | * Close a directory entry. | ||
38644 | * Parameters | ||
38645 | * $zip_entry | ||
38646 | * A directory entry returned by zip_read(). | ||
38647 | * Return | ||
38648 | * Returns TRUE on success or FALSE on failure. | ||
38649 | */ | ||
38650 | static int jx9Builtin_zip_entry_close(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38651 | { | ||
38652 | SyArchiveEntry *pEntry; | ||
38653 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38654 | /* Missing/Invalid arguments */ | ||
38655 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38656 | /* return FALSE */ | ||
38657 | jx9_result_bool(pCtx, 0); | ||
38658 | return JX9_OK; | ||
38659 | } | ||
38660 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38661 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); | ||
38662 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38663 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38664 | /* return FALSE */ | ||
38665 | jx9_result_bool(pCtx, 0); | ||
38666 | return JX9_OK; | ||
38667 | } | ||
38668 | /* Reset the read cursor */ | ||
38669 | pEntry->nReadCount = 0; | ||
38670 | /*All done. Actually this function is a no-op, return TRUE */ | ||
38671 | jx9_result_bool(pCtx, 1); | ||
38672 | return JX9_OK; | ||
38673 | } | ||
38674 | /* | ||
38675 | * string zip_entry_name(resource $zip_entry) | ||
38676 | * Retrieve the name of a directory entry. | ||
38677 | * Parameters | ||
38678 | * $zip_entry | ||
38679 | * A directory entry returned by zip_read(). | ||
38680 | * Return | ||
38681 | * The name of the directory entry. | ||
38682 | */ | ||
38683 | static int jx9Builtin_zip_entry_name(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38684 | { | ||
38685 | SyArchiveEntry *pEntry; | ||
38686 | SyString *pName; | ||
38687 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38688 | /* Missing/Invalid arguments */ | ||
38689 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38690 | /* return FALSE */ | ||
38691 | jx9_result_bool(pCtx, 0); | ||
38692 | return JX9_OK; | ||
38693 | } | ||
38694 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38695 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); | ||
38696 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38697 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38698 | /* return FALSE */ | ||
38699 | jx9_result_bool(pCtx, 0); | ||
38700 | return JX9_OK; | ||
38701 | } | ||
38702 | /* Return entry name */ | ||
38703 | pName = &pEntry->sFileName; | ||
38704 | jx9_result_string(pCtx, pName->zString, (int)pName->nByte); | ||
38705 | return JX9_OK; | ||
38706 | } | ||
38707 | /* | ||
38708 | * int64 zip_entry_filesize(resource $zip_entry) | ||
38709 | * Retrieve the actual file size of a directory entry. | ||
38710 | * Parameters | ||
38711 | * $zip_entry | ||
38712 | * A directory entry returned by zip_read(). | ||
38713 | * Return | ||
38714 | * The size of the directory entry. | ||
38715 | */ | ||
38716 | static int jx9Builtin_zip_entry_filesize(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38717 | { | ||
38718 | SyArchiveEntry *pEntry; | ||
38719 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38720 | /* Missing/Invalid arguments */ | ||
38721 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38722 | /* return FALSE */ | ||
38723 | jx9_result_bool(pCtx, 0); | ||
38724 | return JX9_OK; | ||
38725 | } | ||
38726 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38727 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); | ||
38728 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38729 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38730 | /* return FALSE */ | ||
38731 | jx9_result_bool(pCtx, 0); | ||
38732 | return JX9_OK; | ||
38733 | } | ||
38734 | /* Return entry size */ | ||
38735 | jx9_result_int64(pCtx, (jx9_int64)pEntry->nByte); | ||
38736 | return JX9_OK; | ||
38737 | } | ||
38738 | /* | ||
38739 | * int64 zip_entry_compressedsize(resource $zip_entry) | ||
38740 | * Retrieve the compressed size of a directory entry. | ||
38741 | * Parameters | ||
38742 | * $zip_entry | ||
38743 | * A directory entry returned by zip_read(). | ||
38744 | * Return | ||
38745 | * The compressed size. | ||
38746 | */ | ||
38747 | static int jx9Builtin_zip_entry_compressedsize(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38748 | { | ||
38749 | SyArchiveEntry *pEntry; | ||
38750 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38751 | /* Missing/Invalid arguments */ | ||
38752 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38753 | /* return FALSE */ | ||
38754 | jx9_result_bool(pCtx, 0); | ||
38755 | return JX9_OK; | ||
38756 | } | ||
38757 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38758 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); | ||
38759 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38760 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38761 | /* return FALSE */ | ||
38762 | jx9_result_bool(pCtx, 0); | ||
38763 | return JX9_OK; | ||
38764 | } | ||
38765 | /* Return entry compressed size */ | ||
38766 | jx9_result_int64(pCtx, (jx9_int64)pEntry->nByteCompr); | ||
38767 | return JX9_OK; | ||
38768 | } | ||
38769 | /* | ||
38770 | * string zip_entry_read(resource $zip_entry[, int $length]) | ||
38771 | * Reads from an open directory entry. | ||
38772 | * Parameters | ||
38773 | * $zip_entry | ||
38774 | * A directory entry returned by zip_read(). | ||
38775 | * $length | ||
38776 | * The number of bytes to return. If not specified, this function | ||
38777 | * will attempt to read 1024 bytes. | ||
38778 | * Return | ||
38779 | * Returns the data read, or FALSE if the end of the file is reached. | ||
38780 | */ | ||
38781 | static int jx9Builtin_zip_entry_read(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38782 | { | ||
38783 | SyArchiveEntry *pEntry; | ||
38784 | zip_raw_data *pRaw; | ||
38785 | const char *zData; | ||
38786 | int iLength; | ||
38787 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38788 | /* Missing/Invalid arguments */ | ||
38789 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38790 | /* return FALSE */ | ||
38791 | jx9_result_bool(pCtx, 0); | ||
38792 | return JX9_OK; | ||
38793 | } | ||
38794 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38795 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); | ||
38796 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38797 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38798 | /* return FALSE */ | ||
38799 | jx9_result_bool(pCtx, 0); | ||
38800 | return JX9_OK; | ||
38801 | } | ||
38802 | zData = 0; | ||
38803 | if( pEntry->nReadCount >= pEntry->nByteCompr ){ | ||
38804 | /* No more data to read, return FALSE */ | ||
38805 | jx9_result_bool(pCtx, 0); | ||
38806 | return JX9_OK; | ||
38807 | } | ||
38808 | /* Set a default read length */ | ||
38809 | iLength = 1024; | ||
38810 | if( nArg > 1 ){ | ||
38811 | iLength = jx9_value_to_int(apArg[1]); | ||
38812 | if( iLength < 1 ){ | ||
38813 | iLength = 1024; | ||
38814 | } | ||
38815 | } | ||
38816 | if( (sxu32)iLength > pEntry->nByteCompr - pEntry->nReadCount ){ | ||
38817 | iLength = (int)(pEntry->nByteCompr - pEntry->nReadCount); | ||
38818 | } | ||
38819 | /* Return the entry contents */ | ||
38820 | pRaw = (zip_raw_data *)pEntry->pUserData; | ||
38821 | if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ | ||
38822 | zData = (const char *)SyBlobDataAt(&pRaw->raw.sBlob, (pEntry->nOfft+pEntry->nReadCount)); | ||
38823 | }else{ | ||
38824 | const char *zMap = (const char *)pRaw->raw.mmap.pMap; | ||
38825 | /* Memory mmaped chunk */ | ||
38826 | zData = &zMap[pEntry->nOfft+pEntry->nReadCount]; | ||
38827 | } | ||
38828 | /* Increment the read counter */ | ||
38829 | pEntry->nReadCount += iLength; | ||
38830 | /* Return the raw data */ | ||
38831 | jx9_result_string(pCtx, zData, iLength); | ||
38832 | return JX9_OK; | ||
38833 | } | ||
38834 | /* | ||
38835 | * bool zip_entry_reset_cursor(resource $zip_entry) | ||
38836 | * Reset the read cursor of an open directory entry. | ||
38837 | * Parameters | ||
38838 | * $zip_entry | ||
38839 | * A directory entry returned by zip_read(). | ||
38840 | * Return | ||
38841 | * TRUE on success, FALSE on failure. | ||
38842 | */ | ||
38843 | static int jx9Builtin_zip_entry_reset_cursor(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38844 | { | ||
38845 | SyArchiveEntry *pEntry; | ||
38846 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38847 | /* Missing/Invalid arguments */ | ||
38848 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38849 | /* return FALSE */ | ||
38850 | jx9_result_bool(pCtx, 0); | ||
38851 | return JX9_OK; | ||
38852 | } | ||
38853 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38854 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); | ||
38855 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38856 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38857 | /* return FALSE */ | ||
38858 | jx9_result_bool(pCtx, 0); | ||
38859 | return JX9_OK; | ||
38860 | } | ||
38861 | /* Reset the cursor */ | ||
38862 | pEntry->nReadCount = 0; | ||
38863 | /* Return TRUE */ | ||
38864 | jx9_result_bool(pCtx, 1); | ||
38865 | return JX9_OK; | ||
38866 | } | ||
38867 | /* | ||
38868 | * string zip_entry_compressionmethod(resource $zip_entry) | ||
38869 | * Retrieve the compression method of a directory entry. | ||
38870 | * Parameters | ||
38871 | * $zip_entry | ||
38872 | * A directory entry returned by zip_read(). | ||
38873 | * Return | ||
38874 | * The compression method on success or FALSE on failure. | ||
38875 | */ | ||
38876 | static int jx9Builtin_zip_entry_compressionmethod(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
38877 | { | ||
38878 | SyArchiveEntry *pEntry; | ||
38879 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
38880 | /* Missing/Invalid arguments */ | ||
38881 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38882 | /* return FALSE */ | ||
38883 | jx9_result_bool(pCtx, 0); | ||
38884 | return JX9_OK; | ||
38885 | } | ||
38886 | /* Make sure we are dealing with a valid ZIP archive entry */ | ||
38887 | pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); | ||
38888 | if( SXARCH_ENTRY_INVALID(pEntry) ){ | ||
38889 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); | ||
38890 | /* return FALSE */ | ||
38891 | jx9_result_bool(pCtx, 0); | ||
38892 | return JX9_OK; | ||
38893 | } | ||
38894 | switch(pEntry->nComprMeth){ | ||
38895 | case 0: | ||
38896 | /* No compression;entry is stored */ | ||
38897 | jx9_result_string(pCtx, "stored", (int)sizeof("stored")-1); | ||
38898 | break; | ||
38899 | case 8: | ||
38900 | /* Entry is deflated (Default compression algorithm) */ | ||
38901 | jx9_result_string(pCtx, "deflate", (int)sizeof("deflate")-1); | ||
38902 | break; | ||
38903 | /* Exotic compression algorithms */ | ||
38904 | case 1: | ||
38905 | jx9_result_string(pCtx, "shrunk", (int)sizeof("shrunk")-1); | ||
38906 | break; | ||
38907 | case 2: | ||
38908 | case 3: | ||
38909 | case 4: | ||
38910 | case 5: | ||
38911 | /* Entry is reduced */ | ||
38912 | jx9_result_string(pCtx, "reduced", (int)sizeof("reduced")-1); | ||
38913 | break; | ||
38914 | case 6: | ||
38915 | /* Entry is imploded */ | ||
38916 | jx9_result_string(pCtx, "implode", (int)sizeof("implode")-1); | ||
38917 | break; | ||
38918 | default: | ||
38919 | jx9_result_string(pCtx, "unknown", (int)sizeof("unknown")-1); | ||
38920 | break; | ||
38921 | } | ||
38922 | return JX9_OK; | ||
38923 | } | ||
38924 | #endif /* #ifndef JX9_DISABLE_BUILTIN_FUNC*/ | ||
38925 | /* NULL VFS [i.e: a no-op VFS]*/ | ||
38926 | static const jx9_vfs null_vfs = { | ||
38927 | "null_vfs", | ||
38928 | JX9_VFS_VERSION, | ||
38929 | 0, /* int (*xChdir)(const char *) */ | ||
38930 | 0, /* int (*xChroot)(const char *); */ | ||
38931 | 0, /* int (*xGetcwd)(jx9_context *) */ | ||
38932 | 0, /* int (*xMkdir)(const char *, int, int) */ | ||
38933 | 0, /* int (*xRmdir)(const char *) */ | ||
38934 | 0, /* int (*xIsdir)(const char *) */ | ||
38935 | 0, /* int (*xRename)(const char *, const char *) */ | ||
38936 | 0, /*int (*xRealpath)(const char *, jx9_context *)*/ | ||
38937 | 0, /* int (*xSleep)(unsigned int) */ | ||
38938 | 0, /* int (*xUnlink)(const char *) */ | ||
38939 | 0, /* int (*xFileExists)(const char *) */ | ||
38940 | 0, /*int (*xChmod)(const char *, int)*/ | ||
38941 | 0, /*int (*xChown)(const char *, const char *)*/ | ||
38942 | 0, /*int (*xChgrp)(const char *, const char *)*/ | ||
38943 | 0, /* jx9_int64 (*xFreeSpace)(const char *) */ | ||
38944 | 0, /* jx9_int64 (*xTotalSpace)(const char *) */ | ||
38945 | 0, /* jx9_int64 (*xFileSize)(const char *) */ | ||
38946 | 0, /* jx9_int64 (*xFileAtime)(const char *) */ | ||
38947 | 0, /* jx9_int64 (*xFileMtime)(const char *) */ | ||
38948 | 0, /* jx9_int64 (*xFileCtime)(const char *) */ | ||
38949 | 0, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ | ||
38950 | 0, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ | ||
38951 | 0, /* int (*xIsfile)(const char *) */ | ||
38952 | 0, /* int (*xIslink)(const char *) */ | ||
38953 | 0, /* int (*xReadable)(const char *) */ | ||
38954 | 0, /* int (*xWritable)(const char *) */ | ||
38955 | 0, /* int (*xExecutable)(const char *) */ | ||
38956 | 0, /* int (*xFiletype)(const char *, jx9_context *) */ | ||
38957 | 0, /* int (*xGetenv)(const char *, jx9_context *) */ | ||
38958 | 0, /* int (*xSetenv)(const char *, const char *) */ | ||
38959 | 0, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ | ||
38960 | 0, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ | ||
38961 | 0, /* void (*xUnmap)(void *, jx9_int64); */ | ||
38962 | 0, /* int (*xLink)(const char *, const char *, int) */ | ||
38963 | 0, /* int (*xUmask)(int) */ | ||
38964 | 0, /* void (*xTempDir)(jx9_context *) */ | ||
38965 | 0, /* unsigned int (*xProcessId)(void) */ | ||
38966 | 0, /* int (*xUid)(void) */ | ||
38967 | 0, /* int (*xGid)(void) */ | ||
38968 | 0, /* void (*xUsername)(jx9_context *) */ | ||
38969 | 0 /* int (*xExec)(const char *, jx9_context *) */ | ||
38970 | }; | ||
38971 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
38972 | #ifndef JX9_DISABLE_DISK_IO | ||
38973 | #ifdef __WINNT__ | ||
38974 | /* | ||
38975 | * Windows VFS implementation for the JX9 engine. | ||
38976 | * Authors: | ||
38977 | * Symisc Systems, devel@symisc.net. | ||
38978 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
38979 | * Status: | ||
38980 | * Stable. | ||
38981 | */ | ||
38982 | /* What follows here is code that is specific to windows systems. */ | ||
38983 | #include <Windows.h> | ||
38984 | /* | ||
38985 | ** Convert a UTF-8 string to microsoft unicode (UTF-16?). | ||
38986 | ** | ||
38987 | ** Space to hold the returned string is obtained from HeapAlloc(). | ||
38988 | ** Taken from the sqlite3 source tree | ||
38989 | ** status: Public Domain | ||
38990 | */ | ||
38991 | static WCHAR *jx9utf8ToUnicode(const char *zFilename){ | ||
38992 | int nChar; | ||
38993 | WCHAR *zWideFilename; | ||
38994 | |||
38995 | nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0); | ||
38996 | zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, nChar*sizeof(zWideFilename[0])); | ||
38997 | if( zWideFilename == 0 ){ | ||
38998 | return 0; | ||
38999 | } | ||
39000 | nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar); | ||
39001 | if( nChar==0 ){ | ||
39002 | HeapFree(GetProcessHeap(), 0, zWideFilename); | ||
39003 | return 0; | ||
39004 | } | ||
39005 | return zWideFilename; | ||
39006 | } | ||
39007 | /* | ||
39008 | ** Convert a UTF-8 filename into whatever form the underlying | ||
39009 | ** operating system wants filenames in.Space to hold the result | ||
39010 | ** is obtained from HeapAlloc() and must be freed by the calling | ||
39011 | ** function. | ||
39012 | ** Taken from the sqlite3 source tree | ||
39013 | ** status: Public Domain | ||
39014 | */ | ||
39015 | static void *jx9convertUtf8Filename(const char *zFilename){ | ||
39016 | void *zConverted; | ||
39017 | zConverted = jx9utf8ToUnicode(zFilename); | ||
39018 | return zConverted; | ||
39019 | } | ||
39020 | /* | ||
39021 | ** Convert microsoft unicode to UTF-8. Space to hold the returned string is | ||
39022 | ** obtained from HeapAlloc(). | ||
39023 | ** Taken from the sqlite3 source tree | ||
39024 | ** status: Public Domain | ||
39025 | */ | ||
39026 | static char *jx9unicodeToUtf8(const WCHAR *zWideFilename){ | ||
39027 | char *zFilename; | ||
39028 | int nByte; | ||
39029 | |||
39030 | nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); | ||
39031 | zFilename = (char *)HeapAlloc(GetProcessHeap(), 0, nByte); | ||
39032 | if( zFilename == 0 ){ | ||
39033 | return 0; | ||
39034 | } | ||
39035 | nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, 0, 0); | ||
39036 | if( nByte == 0 ){ | ||
39037 | HeapFree(GetProcessHeap(), 0, zFilename); | ||
39038 | return 0; | ||
39039 | } | ||
39040 | return zFilename; | ||
39041 | } | ||
39042 | /* int (*xchdir)(const char *) */ | ||
39043 | static int WinVfs_chdir(const char *zPath) | ||
39044 | { | ||
39045 | void * pConverted; | ||
39046 | BOOL rc; | ||
39047 | pConverted = jx9convertUtf8Filename(zPath); | ||
39048 | if( pConverted == 0 ){ | ||
39049 | return -1; | ||
39050 | } | ||
39051 | rc = SetCurrentDirectoryW((LPCWSTR)pConverted); | ||
39052 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39053 | return rc ? JX9_OK : -1; | ||
39054 | } | ||
39055 | /* int (*xGetcwd)(jx9_context *) */ | ||
39056 | static int WinVfs_getcwd(jx9_context *pCtx) | ||
39057 | { | ||
39058 | WCHAR zDir[2048]; | ||
39059 | char *zConverted; | ||
39060 | DWORD rc; | ||
39061 | /* Get the current directory */ | ||
39062 | rc = GetCurrentDirectoryW(sizeof(zDir), zDir); | ||
39063 | if( rc < 1 ){ | ||
39064 | return -1; | ||
39065 | } | ||
39066 | zConverted = jx9unicodeToUtf8(zDir); | ||
39067 | if( zConverted == 0 ){ | ||
39068 | return -1; | ||
39069 | } | ||
39070 | jx9_result_string(pCtx, zConverted, -1/*Compute length automatically*/); /* Will make it's own copy */ | ||
39071 | HeapFree(GetProcessHeap(), 0, zConverted); | ||
39072 | return JX9_OK; | ||
39073 | } | ||
39074 | /* int (*xMkdir)(const char *, int, int) */ | ||
39075 | static int WinVfs_mkdir(const char *zPath, int mode, int recursive) | ||
39076 | { | ||
39077 | void * pConverted; | ||
39078 | BOOL rc; | ||
39079 | pConverted = jx9convertUtf8Filename(zPath); | ||
39080 | if( pConverted == 0 ){ | ||
39081 | return -1; | ||
39082 | } | ||
39083 | mode= 0; /* MSVC warning */ | ||
39084 | recursive = 0; | ||
39085 | rc = CreateDirectoryW((LPCWSTR)pConverted, 0); | ||
39086 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39087 | return rc ? JX9_OK : -1; | ||
39088 | } | ||
39089 | /* int (*xRmdir)(const char *) */ | ||
39090 | static int WinVfs_rmdir(const char *zPath) | ||
39091 | { | ||
39092 | void * pConverted; | ||
39093 | BOOL rc; | ||
39094 | pConverted = jx9convertUtf8Filename(zPath); | ||
39095 | if( pConverted == 0 ){ | ||
39096 | return -1; | ||
39097 | } | ||
39098 | rc = RemoveDirectoryW((LPCWSTR)pConverted); | ||
39099 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39100 | return rc ? JX9_OK : -1; | ||
39101 | } | ||
39102 | /* int (*xIsdir)(const char *) */ | ||
39103 | static int WinVfs_isdir(const char *zPath) | ||
39104 | { | ||
39105 | void * pConverted; | ||
39106 | DWORD dwAttr; | ||
39107 | pConverted = jx9convertUtf8Filename(zPath); | ||
39108 | if( pConverted == 0 ){ | ||
39109 | return -1; | ||
39110 | } | ||
39111 | dwAttr = GetFileAttributesW((LPCWSTR)pConverted); | ||
39112 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39113 | if( dwAttr == INVALID_FILE_ATTRIBUTES ){ | ||
39114 | return -1; | ||
39115 | } | ||
39116 | return (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? JX9_OK : -1; | ||
39117 | } | ||
39118 | /* int (*xRename)(const char *, const char *) */ | ||
39119 | static int WinVfs_Rename(const char *zOld, const char *zNew) | ||
39120 | { | ||
39121 | void *pOld, *pNew; | ||
39122 | BOOL rc = 0; | ||
39123 | pOld = jx9convertUtf8Filename(zOld); | ||
39124 | if( pOld == 0 ){ | ||
39125 | return -1; | ||
39126 | } | ||
39127 | pNew = jx9convertUtf8Filename(zNew); | ||
39128 | if( pNew ){ | ||
39129 | rc = MoveFileW((LPCWSTR)pOld, (LPCWSTR)pNew); | ||
39130 | } | ||
39131 | HeapFree(GetProcessHeap(), 0, pOld); | ||
39132 | if( pNew ){ | ||
39133 | HeapFree(GetProcessHeap(), 0, pNew); | ||
39134 | } | ||
39135 | return rc ? JX9_OK : - 1; | ||
39136 | } | ||
39137 | /* int (*xRealpath)(const char *, jx9_context *) */ | ||
39138 | static int WinVfs_Realpath(const char *zPath, jx9_context *pCtx) | ||
39139 | { | ||
39140 | WCHAR zTemp[2048]; | ||
39141 | void *pPath; | ||
39142 | char *zReal; | ||
39143 | DWORD n; | ||
39144 | pPath = jx9convertUtf8Filename(zPath); | ||
39145 | if( pPath == 0 ){ | ||
39146 | return -1; | ||
39147 | } | ||
39148 | n = GetFullPathNameW((LPCWSTR)pPath, 0, 0, 0); | ||
39149 | if( n > 0 ){ | ||
39150 | if( n >= sizeof(zTemp) ){ | ||
39151 | n = sizeof(zTemp) - 1; | ||
39152 | } | ||
39153 | GetFullPathNameW((LPCWSTR)pPath, n, zTemp, 0); | ||
39154 | } | ||
39155 | HeapFree(GetProcessHeap(), 0, pPath); | ||
39156 | if( !n ){ | ||
39157 | return -1; | ||
39158 | } | ||
39159 | zReal = jx9unicodeToUtf8(zTemp); | ||
39160 | if( zReal == 0 ){ | ||
39161 | return -1; | ||
39162 | } | ||
39163 | jx9_result_string(pCtx, zReal, -1); /* Will make it's own copy */ | ||
39164 | HeapFree(GetProcessHeap(), 0, zReal); | ||
39165 | return JX9_OK; | ||
39166 | } | ||
39167 | /* int (*xSleep)(unsigned int) */ | ||
39168 | static int WinVfs_Sleep(unsigned int uSec) | ||
39169 | { | ||
39170 | Sleep(uSec/1000/*uSec per Millisec */); | ||
39171 | return JX9_OK; | ||
39172 | } | ||
39173 | /* int (*xUnlink)(const char *) */ | ||
39174 | static int WinVfs_unlink(const char *zPath) | ||
39175 | { | ||
39176 | void * pConverted; | ||
39177 | BOOL rc; | ||
39178 | pConverted = jx9convertUtf8Filename(zPath); | ||
39179 | if( pConverted == 0 ){ | ||
39180 | return -1; | ||
39181 | } | ||
39182 | rc = DeleteFileW((LPCWSTR)pConverted); | ||
39183 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39184 | return rc ? JX9_OK : - 1; | ||
39185 | } | ||
39186 | /* jx9_int64 (*xFreeSpace)(const char *) */ | ||
39187 | static jx9_int64 WinVfs_DiskFreeSpace(const char *zPath) | ||
39188 | { | ||
39189 | #ifdef _WIN32_WCE | ||
39190 | /* GetDiskFreeSpace is not supported under WINCE */ | ||
39191 | SXUNUSED(zPath); | ||
39192 | return 0; | ||
39193 | #else | ||
39194 | DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters; | ||
39195 | void * pConverted; | ||
39196 | WCHAR *p; | ||
39197 | BOOL rc; | ||
39198 | pConverted = jx9convertUtf8Filename(zPath); | ||
39199 | if( pConverted == 0 ){ | ||
39200 | return 0; | ||
39201 | } | ||
39202 | p = (WCHAR *)pConverted; | ||
39203 | for(;*p;p++){ | ||
39204 | if( *p == '\\' || *p == '/'){ | ||
39205 | *p = '\0'; | ||
39206 | break; | ||
39207 | } | ||
39208 | } | ||
39209 | rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters); | ||
39210 | if( !rc ){ | ||
39211 | return 0; | ||
39212 | } | ||
39213 | return (jx9_int64)dwFreeClusters * dwSectPerClust * dwBytesPerSect; | ||
39214 | #endif | ||
39215 | } | ||
39216 | /* jx9_int64 (*xTotalSpace)(const char *) */ | ||
39217 | static jx9_int64 WinVfs_DiskTotalSpace(const char *zPath) | ||
39218 | { | ||
39219 | #ifdef _WIN32_WCE | ||
39220 | /* GetDiskFreeSpace is not supported under WINCE */ | ||
39221 | SXUNUSED(zPath); | ||
39222 | return 0; | ||
39223 | #else | ||
39224 | DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters; | ||
39225 | void * pConverted; | ||
39226 | WCHAR *p; | ||
39227 | BOOL rc; | ||
39228 | pConverted = jx9convertUtf8Filename(zPath); | ||
39229 | if( pConverted == 0 ){ | ||
39230 | return 0; | ||
39231 | } | ||
39232 | p = (WCHAR *)pConverted; | ||
39233 | for(;*p;p++){ | ||
39234 | if( *p == '\\' || *p == '/'){ | ||
39235 | *p = '\0'; | ||
39236 | break; | ||
39237 | } | ||
39238 | } | ||
39239 | rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters); | ||
39240 | if( !rc ){ | ||
39241 | return 0; | ||
39242 | } | ||
39243 | return (jx9_int64)dwTotalClusters * dwSectPerClust * dwBytesPerSect; | ||
39244 | #endif | ||
39245 | } | ||
39246 | /* int (*xFileExists)(const char *) */ | ||
39247 | static int WinVfs_FileExists(const char *zPath) | ||
39248 | { | ||
39249 | void * pConverted; | ||
39250 | DWORD dwAttr; | ||
39251 | pConverted = jx9convertUtf8Filename(zPath); | ||
39252 | if( pConverted == 0 ){ | ||
39253 | return -1; | ||
39254 | } | ||
39255 | dwAttr = GetFileAttributesW((LPCWSTR)pConverted); | ||
39256 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39257 | if( dwAttr == INVALID_FILE_ATTRIBUTES ){ | ||
39258 | return -1; | ||
39259 | } | ||
39260 | return JX9_OK; | ||
39261 | } | ||
39262 | /* Open a file in a read-only mode */ | ||
39263 | static HANDLE OpenReadOnly(LPCWSTR pPath) | ||
39264 | { | ||
39265 | DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; | ||
39266 | DWORD dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; | ||
39267 | DWORD dwAccess = GENERIC_READ; | ||
39268 | DWORD dwCreate = OPEN_EXISTING; | ||
39269 | HANDLE pHandle; | ||
39270 | pHandle = CreateFileW(pPath, dwAccess, dwShare, 0, dwCreate, dwType, 0); | ||
39271 | if( pHandle == INVALID_HANDLE_VALUE){ | ||
39272 | return 0; | ||
39273 | } | ||
39274 | return pHandle; | ||
39275 | } | ||
39276 | /* jx9_int64 (*xFileSize)(const char *) */ | ||
39277 | static jx9_int64 WinVfs_FileSize(const char *zPath) | ||
39278 | { | ||
39279 | DWORD dwLow, dwHigh; | ||
39280 | void * pConverted; | ||
39281 | jx9_int64 nSize; | ||
39282 | HANDLE pHandle; | ||
39283 | |||
39284 | pConverted = jx9convertUtf8Filename(zPath); | ||
39285 | if( pConverted == 0 ){ | ||
39286 | return -1; | ||
39287 | } | ||
39288 | /* Open the file in read-only mode */ | ||
39289 | pHandle = OpenReadOnly((LPCWSTR)pConverted); | ||
39290 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39291 | if( pHandle ){ | ||
39292 | dwLow = GetFileSize(pHandle, &dwHigh); | ||
39293 | nSize = dwHigh; | ||
39294 | nSize <<= 32; | ||
39295 | nSize += dwLow; | ||
39296 | CloseHandle(pHandle); | ||
39297 | }else{ | ||
39298 | nSize = -1; | ||
39299 | } | ||
39300 | return nSize; | ||
39301 | } | ||
39302 | #define TICKS_PER_SECOND 10000000 | ||
39303 | #define EPOCH_DIFFERENCE 11644473600LL | ||
39304 | /* Convert Windows timestamp to UNIX timestamp */ | ||
39305 | static jx9_int64 convertWindowsTimeToUnixTime(LPFILETIME pTime) | ||
39306 | { | ||
39307 | jx9_int64 input, temp; | ||
39308 | input = pTime->dwHighDateTime; | ||
39309 | input <<= 32; | ||
39310 | input += pTime->dwLowDateTime; | ||
39311 | temp = input / TICKS_PER_SECOND; /*convert from 100ns intervals to seconds*/ | ||
39312 | temp = temp - EPOCH_DIFFERENCE; /*subtract number of seconds between epochs*/ | ||
39313 | return temp; | ||
39314 | } | ||
39315 | /* Convert UNIX timestamp to Windows timestamp */ | ||
39316 | static void convertUnixTimeToWindowsTime(jx9_int64 nUnixtime, LPFILETIME pOut) | ||
39317 | { | ||
39318 | jx9_int64 result = EPOCH_DIFFERENCE; | ||
39319 | result += nUnixtime; | ||
39320 | result *= 10000000LL; | ||
39321 | pOut->dwHighDateTime = (DWORD)(nUnixtime>>32); | ||
39322 | pOut->dwLowDateTime = (DWORD)nUnixtime; | ||
39323 | } | ||
39324 | /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ | ||
39325 | static int WinVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time) | ||
39326 | { | ||
39327 | FILETIME sTouch, sAccess; | ||
39328 | void *pConverted; | ||
39329 | void *pHandle; | ||
39330 | BOOL rc = 0; | ||
39331 | pConverted = jx9convertUtf8Filename(zPath); | ||
39332 | if( pConverted == 0 ){ | ||
39333 | return -1; | ||
39334 | } | ||
39335 | pHandle = OpenReadOnly((LPCWSTR)pConverted); | ||
39336 | if( pHandle ){ | ||
39337 | if( touch_time < 0 ){ | ||
39338 | GetSystemTimeAsFileTime(&sTouch); | ||
39339 | }else{ | ||
39340 | convertUnixTimeToWindowsTime(touch_time, &sTouch); | ||
39341 | } | ||
39342 | if( access_time < 0 ){ | ||
39343 | /* Use the touch time */ | ||
39344 | sAccess = sTouch; /* Structure assignment */ | ||
39345 | }else{ | ||
39346 | convertUnixTimeToWindowsTime(access_time, &sAccess); | ||
39347 | } | ||
39348 | rc = SetFileTime(pHandle, &sTouch, &sAccess, 0); | ||
39349 | /* Close the handle */ | ||
39350 | CloseHandle(pHandle); | ||
39351 | } | ||
39352 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39353 | return rc ? JX9_OK : -1; | ||
39354 | } | ||
39355 | /* jx9_int64 (*xFileAtime)(const char *) */ | ||
39356 | static jx9_int64 WinVfs_FileAtime(const char *zPath) | ||
39357 | { | ||
39358 | BY_HANDLE_FILE_INFORMATION sInfo; | ||
39359 | void * pConverted; | ||
39360 | jx9_int64 atime; | ||
39361 | HANDLE pHandle; | ||
39362 | pConverted = jx9convertUtf8Filename(zPath); | ||
39363 | if( pConverted == 0 ){ | ||
39364 | return -1; | ||
39365 | } | ||
39366 | /* Open the file in read-only mode */ | ||
39367 | pHandle = OpenReadOnly((LPCWSTR)pConverted); | ||
39368 | if( pHandle ){ | ||
39369 | BOOL rc; | ||
39370 | rc = GetFileInformationByHandle(pHandle, &sInfo); | ||
39371 | if( rc ){ | ||
39372 | atime = convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime); | ||
39373 | }else{ | ||
39374 | atime = -1; | ||
39375 | } | ||
39376 | CloseHandle(pHandle); | ||
39377 | }else{ | ||
39378 | atime = -1; | ||
39379 | } | ||
39380 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39381 | return atime; | ||
39382 | } | ||
39383 | /* jx9_int64 (*xFileMtime)(const char *) */ | ||
39384 | static jx9_int64 WinVfs_FileMtime(const char *zPath) | ||
39385 | { | ||
39386 | BY_HANDLE_FILE_INFORMATION sInfo; | ||
39387 | void * pConverted; | ||
39388 | jx9_int64 mtime; | ||
39389 | HANDLE pHandle; | ||
39390 | pConverted = jx9convertUtf8Filename(zPath); | ||
39391 | if( pConverted == 0 ){ | ||
39392 | return -1; | ||
39393 | } | ||
39394 | /* Open the file in read-only mode */ | ||
39395 | pHandle = OpenReadOnly((LPCWSTR)pConverted); | ||
39396 | if( pHandle ){ | ||
39397 | BOOL rc; | ||
39398 | rc = GetFileInformationByHandle(pHandle, &sInfo); | ||
39399 | if( rc ){ | ||
39400 | mtime = convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime); | ||
39401 | }else{ | ||
39402 | mtime = -1; | ||
39403 | } | ||
39404 | CloseHandle(pHandle); | ||
39405 | }else{ | ||
39406 | mtime = -1; | ||
39407 | } | ||
39408 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39409 | return mtime; | ||
39410 | } | ||
39411 | /* jx9_int64 (*xFileCtime)(const char *) */ | ||
39412 | static jx9_int64 WinVfs_FileCtime(const char *zPath) | ||
39413 | { | ||
39414 | BY_HANDLE_FILE_INFORMATION sInfo; | ||
39415 | void * pConverted; | ||
39416 | jx9_int64 ctime; | ||
39417 | HANDLE pHandle; | ||
39418 | pConverted = jx9convertUtf8Filename(zPath); | ||
39419 | if( pConverted == 0 ){ | ||
39420 | return -1; | ||
39421 | } | ||
39422 | /* Open the file in read-only mode */ | ||
39423 | pHandle = OpenReadOnly((LPCWSTR)pConverted); | ||
39424 | if( pHandle ){ | ||
39425 | BOOL rc; | ||
39426 | rc = GetFileInformationByHandle(pHandle, &sInfo); | ||
39427 | if( rc ){ | ||
39428 | ctime = convertWindowsTimeToUnixTime(&sInfo.ftCreationTime); | ||
39429 | }else{ | ||
39430 | ctime = -1; | ||
39431 | } | ||
39432 | CloseHandle(pHandle); | ||
39433 | }else{ | ||
39434 | ctime = -1; | ||
39435 | } | ||
39436 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39437 | return ctime; | ||
39438 | } | ||
39439 | /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ | ||
39440 | /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ | ||
39441 | static int WinVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) | ||
39442 | { | ||
39443 | BY_HANDLE_FILE_INFORMATION sInfo; | ||
39444 | void *pConverted; | ||
39445 | HANDLE pHandle; | ||
39446 | BOOL rc; | ||
39447 | pConverted = jx9convertUtf8Filename(zPath); | ||
39448 | if( pConverted == 0 ){ | ||
39449 | return -1; | ||
39450 | } | ||
39451 | /* Open the file in read-only mode */ | ||
39452 | pHandle = OpenReadOnly((LPCWSTR)pConverted); | ||
39453 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39454 | if( pHandle == 0 ){ | ||
39455 | return -1; | ||
39456 | } | ||
39457 | rc = GetFileInformationByHandle(pHandle, &sInfo); | ||
39458 | CloseHandle(pHandle); | ||
39459 | if( !rc ){ | ||
39460 | return -1; | ||
39461 | } | ||
39462 | /* dev */ | ||
39463 | jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber); | ||
39464 | jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ | ||
39465 | /* ino */ | ||
39466 | jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); | ||
39467 | jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ | ||
39468 | /* mode */ | ||
39469 | jx9_value_int(pWorker, 0); | ||
39470 | jx9_array_add_strkey_elem(pArray, "mode", pWorker); | ||
39471 | /* nlink */ | ||
39472 | jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks); | ||
39473 | jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ | ||
39474 | /* uid, gid, rdev */ | ||
39475 | jx9_value_int(pWorker, 0); | ||
39476 | jx9_array_add_strkey_elem(pArray, "uid", pWorker); | ||
39477 | jx9_array_add_strkey_elem(pArray, "gid", pWorker); | ||
39478 | jx9_array_add_strkey_elem(pArray, "rdev", pWorker); | ||
39479 | /* size */ | ||
39480 | jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); | ||
39481 | jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ | ||
39482 | /* atime */ | ||
39483 | jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); | ||
39484 | jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ | ||
39485 | /* mtime */ | ||
39486 | jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); | ||
39487 | jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ | ||
39488 | /* ctime */ | ||
39489 | jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); | ||
39490 | jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ | ||
39491 | /* blksize, blocks */ | ||
39492 | jx9_value_int(pWorker, 0); | ||
39493 | jx9_array_add_strkey_elem(pArray, "blksize", pWorker); | ||
39494 | jx9_array_add_strkey_elem(pArray, "blocks", pWorker); | ||
39495 | return JX9_OK; | ||
39496 | } | ||
39497 | /* int (*xIsfile)(const char *) */ | ||
39498 | static int WinVfs_isfile(const char *zPath) | ||
39499 | { | ||
39500 | void * pConverted; | ||
39501 | DWORD dwAttr; | ||
39502 | pConverted = jx9convertUtf8Filename(zPath); | ||
39503 | if( pConverted == 0 ){ | ||
39504 | return -1; | ||
39505 | } | ||
39506 | dwAttr = GetFileAttributesW((LPCWSTR)pConverted); | ||
39507 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39508 | if( dwAttr == INVALID_FILE_ATTRIBUTES ){ | ||
39509 | return -1; | ||
39510 | } | ||
39511 | return (dwAttr & (FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE)) ? JX9_OK : -1; | ||
39512 | } | ||
39513 | /* int (*xIslink)(const char *) */ | ||
39514 | static int WinVfs_islink(const char *zPath) | ||
39515 | { | ||
39516 | void * pConverted; | ||
39517 | DWORD dwAttr; | ||
39518 | pConverted = jx9convertUtf8Filename(zPath); | ||
39519 | if( pConverted == 0 ){ | ||
39520 | return -1; | ||
39521 | } | ||
39522 | dwAttr = GetFileAttributesW((LPCWSTR)pConverted); | ||
39523 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39524 | if( dwAttr == INVALID_FILE_ATTRIBUTES ){ | ||
39525 | return -1; | ||
39526 | } | ||
39527 | return (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) ? JX9_OK : -1; | ||
39528 | } | ||
39529 | /* int (*xWritable)(const char *) */ | ||
39530 | static int WinVfs_iswritable(const char *zPath) | ||
39531 | { | ||
39532 | void * pConverted; | ||
39533 | DWORD dwAttr; | ||
39534 | pConverted = jx9convertUtf8Filename(zPath); | ||
39535 | if( pConverted == 0 ){ | ||
39536 | return -1; | ||
39537 | } | ||
39538 | dwAttr = GetFileAttributesW((LPCWSTR)pConverted); | ||
39539 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39540 | if( dwAttr == INVALID_FILE_ATTRIBUTES ){ | ||
39541 | return -1; | ||
39542 | } | ||
39543 | if( (dwAttr & (FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL)) == 0 ){ | ||
39544 | /* Not a regular file */ | ||
39545 | return -1; | ||
39546 | } | ||
39547 | if( dwAttr & FILE_ATTRIBUTE_READONLY ){ | ||
39548 | /* Read-only file */ | ||
39549 | return -1; | ||
39550 | } | ||
39551 | /* File is writable */ | ||
39552 | return JX9_OK; | ||
39553 | } | ||
39554 | /* int (*xExecutable)(const char *) */ | ||
39555 | static int WinVfs_isexecutable(const char *zPath) | ||
39556 | { | ||
39557 | void * pConverted; | ||
39558 | DWORD dwAttr; | ||
39559 | pConverted = jx9convertUtf8Filename(zPath); | ||
39560 | if( pConverted == 0 ){ | ||
39561 | return -1; | ||
39562 | } | ||
39563 | dwAttr = GetFileAttributesW((LPCWSTR)pConverted); | ||
39564 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39565 | if( dwAttr == INVALID_FILE_ATTRIBUTES ){ | ||
39566 | return -1; | ||
39567 | } | ||
39568 | if( (dwAttr & FILE_ATTRIBUTE_NORMAL) == 0 ){ | ||
39569 | /* Not a regular file */ | ||
39570 | return -1; | ||
39571 | } | ||
39572 | /* File is executable */ | ||
39573 | return JX9_OK; | ||
39574 | } | ||
39575 | /* int (*xFiletype)(const char *, jx9_context *) */ | ||
39576 | static int WinVfs_Filetype(const char *zPath, jx9_context *pCtx) | ||
39577 | { | ||
39578 | void * pConverted; | ||
39579 | DWORD dwAttr; | ||
39580 | pConverted = jx9convertUtf8Filename(zPath); | ||
39581 | if( pConverted == 0 ){ | ||
39582 | /* Expand 'unknown' */ | ||
39583 | jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); | ||
39584 | return -1; | ||
39585 | } | ||
39586 | dwAttr = GetFileAttributesW((LPCWSTR)pConverted); | ||
39587 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39588 | if( dwAttr == INVALID_FILE_ATTRIBUTES ){ | ||
39589 | /* Expand 'unknown' */ | ||
39590 | jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); | ||
39591 | return -1; | ||
39592 | } | ||
39593 | if(dwAttr & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE) ){ | ||
39594 | jx9_result_string(pCtx, "file", sizeof("file")-1); | ||
39595 | }else if(dwAttr & FILE_ATTRIBUTE_DIRECTORY){ | ||
39596 | jx9_result_string(pCtx, "dir", sizeof("dir")-1); | ||
39597 | }else if(dwAttr & FILE_ATTRIBUTE_REPARSE_POINT){ | ||
39598 | jx9_result_string(pCtx, "link", sizeof("link")-1); | ||
39599 | }else if(dwAttr & (FILE_ATTRIBUTE_DEVICE)){ | ||
39600 | jx9_result_string(pCtx, "block", sizeof("block")-1); | ||
39601 | }else{ | ||
39602 | jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); | ||
39603 | } | ||
39604 | return JX9_OK; | ||
39605 | } | ||
39606 | /* int (*xGetenv)(const char *, jx9_context *) */ | ||
39607 | static int WinVfs_Getenv(const char *zVar, jx9_context *pCtx) | ||
39608 | { | ||
39609 | char zValue[1024]; | ||
39610 | DWORD n; | ||
39611 | /* | ||
39612 | * According to MSDN | ||
39613 | * If lpBuffer is not large enough to hold the data, the return | ||
39614 | * value is the buffer size, in characters, required to hold the | ||
39615 | * string and its terminating null character and the contents | ||
39616 | * of lpBuffer are undefined. | ||
39617 | */ | ||
39618 | n = sizeof(zValue); | ||
39619 | SyMemcpy("Undefined", zValue, sizeof("Undefined")-1); | ||
39620 | /* Extract the environment value */ | ||
39621 | n = GetEnvironmentVariableA(zVar, zValue, sizeof(zValue)); | ||
39622 | if( !n ){ | ||
39623 | /* No such variable*/ | ||
39624 | return -1; | ||
39625 | } | ||
39626 | jx9_result_string(pCtx, zValue, (int)n); | ||
39627 | return JX9_OK; | ||
39628 | } | ||
39629 | /* int (*xSetenv)(const char *, const char *) */ | ||
39630 | static int WinVfs_Setenv(const char *zName, const char *zValue) | ||
39631 | { | ||
39632 | BOOL rc; | ||
39633 | rc = SetEnvironmentVariableA(zName, zValue); | ||
39634 | return rc ? JX9_OK : -1; | ||
39635 | } | ||
39636 | /* int (*xMmap)(const char *, void **, jx9_int64 *) */ | ||
39637 | static int WinVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize) | ||
39638 | { | ||
39639 | DWORD dwSizeLow, dwSizeHigh; | ||
39640 | HANDLE pHandle, pMapHandle; | ||
39641 | void *pConverted, *pView; | ||
39642 | |||
39643 | pConverted = jx9convertUtf8Filename(zPath); | ||
39644 | if( pConverted == 0 ){ | ||
39645 | return -1; | ||
39646 | } | ||
39647 | pHandle = OpenReadOnly((LPCWSTR)pConverted); | ||
39648 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39649 | if( pHandle == 0 ){ | ||
39650 | return -1; | ||
39651 | } | ||
39652 | /* Get the file size */ | ||
39653 | dwSizeLow = GetFileSize(pHandle, &dwSizeHigh); | ||
39654 | /* Create the mapping */ | ||
39655 | pMapHandle = CreateFileMappingW(pHandle, 0, PAGE_READONLY, dwSizeHigh, dwSizeLow, 0); | ||
39656 | if( pMapHandle == 0 ){ | ||
39657 | CloseHandle(pHandle); | ||
39658 | return -1; | ||
39659 | } | ||
39660 | *pSize = ((jx9_int64)dwSizeHigh << 32) | dwSizeLow; | ||
39661 | /* Obtain the view */ | ||
39662 | pView = MapViewOfFile(pMapHandle, FILE_MAP_READ, 0, 0, (SIZE_T)(*pSize)); | ||
39663 | if( pView ){ | ||
39664 | /* Let the upper layer point to the view */ | ||
39665 | *ppMap = pView; | ||
39666 | } | ||
39667 | /* Close the handle | ||
39668 | * According to MSDN it's OK the close the HANDLES. | ||
39669 | */ | ||
39670 | CloseHandle(pMapHandle); | ||
39671 | CloseHandle(pHandle); | ||
39672 | return pView ? JX9_OK : -1; | ||
39673 | } | ||
39674 | /* void (*xUnmap)(void *, jx9_int64) */ | ||
39675 | static void WinVfs_Unmap(void *pView, jx9_int64 nSize) | ||
39676 | { | ||
39677 | nSize = 0; /* Compiler warning */ | ||
39678 | UnmapViewOfFile(pView); | ||
39679 | } | ||
39680 | /* void (*xTempDir)(jx9_context *) */ | ||
39681 | static void WinVfs_TempDir(jx9_context *pCtx) | ||
39682 | { | ||
39683 | CHAR zTemp[1024]; | ||
39684 | DWORD n; | ||
39685 | n = GetTempPathA(sizeof(zTemp), zTemp); | ||
39686 | if( n < 1 ){ | ||
39687 | /* Assume the default windows temp directory */ | ||
39688 | jx9_result_string(pCtx, "C:\\Windows\\Temp", -1/*Compute length automatically*/); | ||
39689 | }else{ | ||
39690 | jx9_result_string(pCtx, zTemp, (int)n); | ||
39691 | } | ||
39692 | } | ||
39693 | /* unsigned int (*xProcessId)(void) */ | ||
39694 | static unsigned int WinVfs_ProcessId(void) | ||
39695 | { | ||
39696 | DWORD nID = 0; | ||
39697 | #ifndef __MINGW32__ | ||
39698 | nID = GetProcessId(GetCurrentProcess()); | ||
39699 | #endif /* __MINGW32__ */ | ||
39700 | return (unsigned int)nID; | ||
39701 | } | ||
39702 | |||
39703 | /* Export the windows vfs */ | ||
39704 | static const jx9_vfs sWinVfs = { | ||
39705 | "Windows_vfs", | ||
39706 | JX9_VFS_VERSION, | ||
39707 | WinVfs_chdir, /* int (*xChdir)(const char *) */ | ||
39708 | 0, /* int (*xChroot)(const char *); */ | ||
39709 | WinVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */ | ||
39710 | WinVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */ | ||
39711 | WinVfs_rmdir, /* int (*xRmdir)(const char *) */ | ||
39712 | WinVfs_isdir, /* int (*xIsdir)(const char *) */ | ||
39713 | WinVfs_Rename, /* int (*xRename)(const char *, const char *) */ | ||
39714 | WinVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/ | ||
39715 | WinVfs_Sleep, /* int (*xSleep)(unsigned int) */ | ||
39716 | WinVfs_unlink, /* int (*xUnlink)(const char *) */ | ||
39717 | WinVfs_FileExists, /* int (*xFileExists)(const char *) */ | ||
39718 | 0, /*int (*xChmod)(const char *, int)*/ | ||
39719 | 0, /*int (*xChown)(const char *, const char *)*/ | ||
39720 | 0, /*int (*xChgrp)(const char *, const char *)*/ | ||
39721 | WinVfs_DiskFreeSpace, /* jx9_int64 (*xFreeSpace)(const char *) */ | ||
39722 | WinVfs_DiskTotalSpace, /* jx9_int64 (*xTotalSpace)(const char *) */ | ||
39723 | WinVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */ | ||
39724 | WinVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */ | ||
39725 | WinVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */ | ||
39726 | WinVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */ | ||
39727 | WinVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ | ||
39728 | WinVfs_Stat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ | ||
39729 | WinVfs_isfile, /* int (*xIsfile)(const char *) */ | ||
39730 | WinVfs_islink, /* int (*xIslink)(const char *) */ | ||
39731 | WinVfs_isfile, /* int (*xReadable)(const char *) */ | ||
39732 | WinVfs_iswritable, /* int (*xWritable)(const char *) */ | ||
39733 | WinVfs_isexecutable, /* int (*xExecutable)(const char *) */ | ||
39734 | WinVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */ | ||
39735 | WinVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */ | ||
39736 | WinVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */ | ||
39737 | WinVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ | ||
39738 | WinVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ | ||
39739 | WinVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */ | ||
39740 | 0, /* int (*xLink)(const char *, const char *, int) */ | ||
39741 | 0, /* int (*xUmask)(int) */ | ||
39742 | WinVfs_TempDir, /* void (*xTempDir)(jx9_context *) */ | ||
39743 | WinVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ | ||
39744 | 0, /* int (*xUid)(void) */ | ||
39745 | 0, /* int (*xGid)(void) */ | ||
39746 | 0, /* void (*xUsername)(jx9_context *) */ | ||
39747 | 0 /* int (*xExec)(const char *, jx9_context *) */ | ||
39748 | }; | ||
39749 | /* Windows file IO */ | ||
39750 | #ifndef INVALID_SET_FILE_POINTER | ||
39751 | # define INVALID_SET_FILE_POINTER ((DWORD)-1) | ||
39752 | #endif | ||
39753 | /* int (*xOpen)(const char *, int, jx9_value *, void **) */ | ||
39754 | static int WinFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle) | ||
39755 | { | ||
39756 | DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; | ||
39757 | DWORD dwAccess = GENERIC_READ; | ||
39758 | DWORD dwShare, dwCreate; | ||
39759 | void *pConverted; | ||
39760 | HANDLE pHandle; | ||
39761 | |||
39762 | pConverted = jx9convertUtf8Filename(zPath); | ||
39763 | if( pConverted == 0 ){ | ||
39764 | return -1; | ||
39765 | } | ||
39766 | /* Set the desired flags according to the open mode */ | ||
39767 | if( iOpenMode & JX9_IO_OPEN_CREATE ){ | ||
39768 | /* Open existing file, or create if it doesn't exist */ | ||
39769 | dwCreate = OPEN_ALWAYS; | ||
39770 | if( iOpenMode & JX9_IO_OPEN_TRUNC ){ | ||
39771 | /* If the specified file exists and is writable, the function overwrites the file */ | ||
39772 | dwCreate = CREATE_ALWAYS; | ||
39773 | } | ||
39774 | }else if( iOpenMode & JX9_IO_OPEN_EXCL ){ | ||
39775 | /* Creates a new file, only if it does not already exist. | ||
39776 | * If the file exists, it fails. | ||
39777 | */ | ||
39778 | dwCreate = CREATE_NEW; | ||
39779 | }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){ | ||
39780 | /* Opens a file and truncates it so that its size is zero bytes | ||
39781 | * The file must exist. | ||
39782 | */ | ||
39783 | dwCreate = TRUNCATE_EXISTING; | ||
39784 | }else{ | ||
39785 | /* Opens a file, only if it exists. */ | ||
39786 | dwCreate = OPEN_EXISTING; | ||
39787 | } | ||
39788 | if( iOpenMode & JX9_IO_OPEN_RDWR ){ | ||
39789 | /* Read+Write access */ | ||
39790 | dwAccess |= GENERIC_WRITE; | ||
39791 | }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){ | ||
39792 | /* Write only access */ | ||
39793 | dwAccess = GENERIC_WRITE; | ||
39794 | } | ||
39795 | if( iOpenMode & JX9_IO_OPEN_APPEND ){ | ||
39796 | /* Append mode */ | ||
39797 | dwAccess = FILE_APPEND_DATA; | ||
39798 | } | ||
39799 | if( iOpenMode & JX9_IO_OPEN_TEMP ){ | ||
39800 | /* File is temporary */ | ||
39801 | dwType = FILE_ATTRIBUTE_TEMPORARY; | ||
39802 | } | ||
39803 | dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; | ||
39804 | pHandle = CreateFileW((LPCWSTR)pConverted, dwAccess, dwShare, 0, dwCreate, dwType, 0); | ||
39805 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39806 | if( pHandle == INVALID_HANDLE_VALUE){ | ||
39807 | SXUNUSED(pResource); /* MSVC warning */ | ||
39808 | return -1; | ||
39809 | } | ||
39810 | /* Make the handle accessible to the upper layer */ | ||
39811 | *ppHandle = (void *)pHandle; | ||
39812 | return JX9_OK; | ||
39813 | } | ||
39814 | /* An instance of the following structure is used to record state information | ||
39815 | * while iterating throw directory entries. | ||
39816 | */ | ||
39817 | typedef struct WinDir_Info WinDir_Info; | ||
39818 | struct WinDir_Info | ||
39819 | { | ||
39820 | HANDLE pDirHandle; | ||
39821 | void *pPath; | ||
39822 | WIN32_FIND_DATAW sInfo; | ||
39823 | int rc; | ||
39824 | }; | ||
39825 | /* int (*xOpenDir)(const char *, jx9_value *, void **) */ | ||
39826 | static int WinDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle) | ||
39827 | { | ||
39828 | WinDir_Info *pDirInfo; | ||
39829 | void *pConverted; | ||
39830 | char *zPrep; | ||
39831 | sxu32 n; | ||
39832 | /* Prepare the path */ | ||
39833 | n = SyStrlen(zPath); | ||
39834 | zPrep = (char *)HeapAlloc(GetProcessHeap(), 0, n+sizeof("\\*")+4); | ||
39835 | if( zPrep == 0 ){ | ||
39836 | return -1; | ||
39837 | } | ||
39838 | SyMemcpy((const void *)zPath, zPrep, n); | ||
39839 | zPrep[n] = '\\'; | ||
39840 | zPrep[n+1] = '*'; | ||
39841 | zPrep[n+2] = 0; | ||
39842 | pConverted = jx9convertUtf8Filename(zPrep); | ||
39843 | HeapFree(GetProcessHeap(), 0, zPrep); | ||
39844 | if( pConverted == 0 ){ | ||
39845 | return -1; | ||
39846 | } | ||
39847 | /* Allocate a new instance */ | ||
39848 | pDirInfo = (WinDir_Info *)HeapAlloc(GetProcessHeap(), 0, sizeof(WinDir_Info)); | ||
39849 | if( pDirInfo == 0 ){ | ||
39850 | pResource = 0; /* Compiler warning */ | ||
39851 | return -1; | ||
39852 | } | ||
39853 | pDirInfo->rc = SXRET_OK; | ||
39854 | pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pConverted, &pDirInfo->sInfo); | ||
39855 | if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ | ||
39856 | /* Cannot open directory */ | ||
39857 | HeapFree(GetProcessHeap(), 0, pConverted); | ||
39858 | HeapFree(GetProcessHeap(), 0, pDirInfo); | ||
39859 | return -1; | ||
39860 | } | ||
39861 | /* Save the path */ | ||
39862 | pDirInfo->pPath = pConverted; | ||
39863 | /* Save our structure */ | ||
39864 | *ppHandle = pDirInfo; | ||
39865 | return JX9_OK; | ||
39866 | } | ||
39867 | /* void (*xCloseDir)(void *) */ | ||
39868 | static void WinDir_Close(void *pUserData) | ||
39869 | { | ||
39870 | WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; | ||
39871 | if( pDirInfo->pDirHandle != INVALID_HANDLE_VALUE ){ | ||
39872 | FindClose(pDirInfo->pDirHandle); | ||
39873 | } | ||
39874 | HeapFree(GetProcessHeap(), 0, pDirInfo->pPath); | ||
39875 | HeapFree(GetProcessHeap(), 0, pDirInfo); | ||
39876 | } | ||
39877 | /* void (*xClose)(void *); */ | ||
39878 | static void WinFile_Close(void *pUserData) | ||
39879 | { | ||
39880 | HANDLE pHandle = (HANDLE)pUserData; | ||
39881 | CloseHandle(pHandle); | ||
39882 | } | ||
39883 | /* int (*xReadDir)(void *, jx9_context *) */ | ||
39884 | static int WinDir_Read(void *pUserData, jx9_context *pCtx) | ||
39885 | { | ||
39886 | WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; | ||
39887 | LPWIN32_FIND_DATAW pData; | ||
39888 | char *zName; | ||
39889 | BOOL rc; | ||
39890 | sxu32 n; | ||
39891 | if( pDirInfo->rc != SXRET_OK ){ | ||
39892 | /* No more entry to process */ | ||
39893 | return -1; | ||
39894 | } | ||
39895 | pData = &pDirInfo->sInfo; | ||
39896 | for(;;){ | ||
39897 | zName = jx9unicodeToUtf8(pData->cFileName); | ||
39898 | if( zName == 0 ){ | ||
39899 | /* Out of memory */ | ||
39900 | return -1; | ||
39901 | } | ||
39902 | n = SyStrlen(zName); | ||
39903 | /* Ignore '.' && '..' */ | ||
39904 | if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ | ||
39905 | break; | ||
39906 | } | ||
39907 | HeapFree(GetProcessHeap(), 0, zName); | ||
39908 | rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo); | ||
39909 | if( !rc ){ | ||
39910 | return -1; | ||
39911 | } | ||
39912 | } | ||
39913 | /* Return the current file name */ | ||
39914 | jx9_result_string(pCtx, zName, -1); | ||
39915 | HeapFree(GetProcessHeap(), 0, zName); | ||
39916 | /* Point to the next entry */ | ||
39917 | rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo); | ||
39918 | if( !rc ){ | ||
39919 | pDirInfo->rc = SXERR_EOF; | ||
39920 | } | ||
39921 | return JX9_OK; | ||
39922 | } | ||
39923 | /* void (*xRewindDir)(void *) */ | ||
39924 | static void WinDir_RewindDir(void *pUserData) | ||
39925 | { | ||
39926 | WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; | ||
39927 | FindClose(pDirInfo->pDirHandle); | ||
39928 | pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pDirInfo->pPath, &pDirInfo->sInfo); | ||
39929 | if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ | ||
39930 | pDirInfo->rc = SXERR_EOF; | ||
39931 | }else{ | ||
39932 | pDirInfo->rc = SXRET_OK; | ||
39933 | } | ||
39934 | } | ||
39935 | /* jx9_int64 (*xRead)(void *, void *, jx9_int64); */ | ||
39936 | static jx9_int64 WinFile_Read(void *pOS, void *pBuffer, jx9_int64 nDatatoRead) | ||
39937 | { | ||
39938 | HANDLE pHandle = (HANDLE)pOS; | ||
39939 | DWORD nRd; | ||
39940 | BOOL rc; | ||
39941 | rc = ReadFile(pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0); | ||
39942 | if( !rc ){ | ||
39943 | /* EOF or IO error */ | ||
39944 | return -1; | ||
39945 | } | ||
39946 | return (jx9_int64)nRd; | ||
39947 | } | ||
39948 | /* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */ | ||
39949 | static jx9_int64 WinFile_Write(void *pOS, const void *pBuffer, jx9_int64 nWrite) | ||
39950 | { | ||
39951 | const char *zData = (const char *)pBuffer; | ||
39952 | HANDLE pHandle = (HANDLE)pOS; | ||
39953 | jx9_int64 nCount; | ||
39954 | DWORD nWr; | ||
39955 | BOOL rc; | ||
39956 | nWr = 0; | ||
39957 | nCount = 0; | ||
39958 | for(;;){ | ||
39959 | if( nWrite < 1 ){ | ||
39960 | break; | ||
39961 | } | ||
39962 | rc = WriteFile(pHandle, zData, (DWORD)nWrite, &nWr, 0); | ||
39963 | if( !rc ){ | ||
39964 | /* IO error */ | ||
39965 | break; | ||
39966 | } | ||
39967 | nWrite -= nWr; | ||
39968 | nCount += nWr; | ||
39969 | zData += nWr; | ||
39970 | } | ||
39971 | if( nWrite > 0 ){ | ||
39972 | return -1; | ||
39973 | } | ||
39974 | return nCount; | ||
39975 | } | ||
39976 | /* int (*xSeek)(void *, jx9_int64, int) */ | ||
39977 | static int WinFile_Seek(void *pUserData, jx9_int64 iOfft, int whence) | ||
39978 | { | ||
39979 | HANDLE pHandle = (HANDLE)pUserData; | ||
39980 | DWORD dwMove, dwNew; | ||
39981 | LONG nHighOfft; | ||
39982 | switch(whence){ | ||
39983 | case 1:/*SEEK_CUR*/ | ||
39984 | dwMove = FILE_CURRENT; | ||
39985 | break; | ||
39986 | case 2: /* SEEK_END */ | ||
39987 | dwMove = FILE_END; | ||
39988 | break; | ||
39989 | case 0: /* SEEK_SET */ | ||
39990 | default: | ||
39991 | dwMove = FILE_BEGIN; | ||
39992 | break; | ||
39993 | } | ||
39994 | nHighOfft = (LONG)(iOfft >> 32); | ||
39995 | dwNew = SetFilePointer(pHandle, (LONG)iOfft, &nHighOfft, dwMove); | ||
39996 | if( dwNew == INVALID_SET_FILE_POINTER ){ | ||
39997 | return -1; | ||
39998 | } | ||
39999 | return JX9_OK; | ||
40000 | } | ||
40001 | /* int (*xLock)(void *, int) */ | ||
40002 | static int WinFile_Lock(void *pUserData, int lock_type) | ||
40003 | { | ||
40004 | HANDLE pHandle = (HANDLE)pUserData; | ||
40005 | static DWORD dwLo = 0, dwHi = 0; /* xx: MT-SAFE */ | ||
40006 | OVERLAPPED sDummy; | ||
40007 | BOOL rc; | ||
40008 | SyZero(&sDummy, sizeof(sDummy)); | ||
40009 | /* Get the file size */ | ||
40010 | if( lock_type < 1 ){ | ||
40011 | /* Unlock the file */ | ||
40012 | rc = UnlockFileEx(pHandle, 0, dwLo, dwHi, &sDummy); | ||
40013 | }else{ | ||
40014 | DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; /* Shared non-blocking lock by default*/ | ||
40015 | /* Lock the file */ | ||
40016 | if( lock_type == 1 /* LOCK_EXCL */ ){ | ||
40017 | dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; | ||
40018 | } | ||
40019 | dwLo = GetFileSize(pHandle, &dwHi); | ||
40020 | rc = LockFileEx(pHandle, dwFlags, 0, dwLo, dwHi, &sDummy); | ||
40021 | } | ||
40022 | return rc ? JX9_OK : -1 /* Lock error */; | ||
40023 | } | ||
40024 | /* jx9_int64 (*xTell)(void *) */ | ||
40025 | static jx9_int64 WinFile_Tell(void *pUserData) | ||
40026 | { | ||
40027 | HANDLE pHandle = (HANDLE)pUserData; | ||
40028 | DWORD dwNew; | ||
40029 | dwNew = SetFilePointer(pHandle, 0, 0, FILE_CURRENT/* SEEK_CUR */); | ||
40030 | if( dwNew == INVALID_SET_FILE_POINTER ){ | ||
40031 | return -1; | ||
40032 | } | ||
40033 | return (jx9_int64)dwNew; | ||
40034 | } | ||
40035 | /* int (*xTrunc)(void *, jx9_int64) */ | ||
40036 | static int WinFile_Trunc(void *pUserData, jx9_int64 nOfft) | ||
40037 | { | ||
40038 | HANDLE pHandle = (HANDLE)pUserData; | ||
40039 | LONG HighOfft; | ||
40040 | DWORD dwNew; | ||
40041 | BOOL rc; | ||
40042 | HighOfft = (LONG)(nOfft >> 32); | ||
40043 | dwNew = SetFilePointer(pHandle, (LONG)nOfft, &HighOfft, FILE_BEGIN); | ||
40044 | if( dwNew == INVALID_SET_FILE_POINTER ){ | ||
40045 | return -1; | ||
40046 | } | ||
40047 | rc = SetEndOfFile(pHandle); | ||
40048 | return rc ? JX9_OK : -1; | ||
40049 | } | ||
40050 | /* int (*xSync)(void *); */ | ||
40051 | static int WinFile_Sync(void *pUserData) | ||
40052 | { | ||
40053 | HANDLE pHandle = (HANDLE)pUserData; | ||
40054 | BOOL rc; | ||
40055 | rc = FlushFileBuffers(pHandle); | ||
40056 | return rc ? JX9_OK : - 1; | ||
40057 | } | ||
40058 | /* int (*xStat)(void *, jx9_value *, jx9_value *) */ | ||
40059 | static int WinFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker) | ||
40060 | { | ||
40061 | BY_HANDLE_FILE_INFORMATION sInfo; | ||
40062 | HANDLE pHandle = (HANDLE)pUserData; | ||
40063 | BOOL rc; | ||
40064 | rc = GetFileInformationByHandle(pHandle, &sInfo); | ||
40065 | if( !rc ){ | ||
40066 | return -1; | ||
40067 | } | ||
40068 | /* dev */ | ||
40069 | jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber); | ||
40070 | jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ | ||
40071 | /* ino */ | ||
40072 | jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); | ||
40073 | jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ | ||
40074 | /* mode */ | ||
40075 | jx9_value_int(pWorker, 0); | ||
40076 | jx9_array_add_strkey_elem(pArray, "mode", pWorker); | ||
40077 | /* nlink */ | ||
40078 | jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks); | ||
40079 | jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ | ||
40080 | /* uid, gid, rdev */ | ||
40081 | jx9_value_int(pWorker, 0); | ||
40082 | jx9_array_add_strkey_elem(pArray, "uid", pWorker); | ||
40083 | jx9_array_add_strkey_elem(pArray, "gid", pWorker); | ||
40084 | jx9_array_add_strkey_elem(pArray, "rdev", pWorker); | ||
40085 | /* size */ | ||
40086 | jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); | ||
40087 | jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ | ||
40088 | /* atime */ | ||
40089 | jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); | ||
40090 | jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ | ||
40091 | /* mtime */ | ||
40092 | jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); | ||
40093 | jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ | ||
40094 | /* ctime */ | ||
40095 | jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); | ||
40096 | jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ | ||
40097 | /* blksize, blocks */ | ||
40098 | jx9_value_int(pWorker, 0); | ||
40099 | jx9_array_add_strkey_elem(pArray, "blksize", pWorker); | ||
40100 | jx9_array_add_strkey_elem(pArray, "blocks", pWorker); | ||
40101 | return JX9_OK; | ||
40102 | } | ||
40103 | /* Export the file:// stream */ | ||
40104 | static const jx9_io_stream sWinFileStream = { | ||
40105 | "file", /* Stream name */ | ||
40106 | JX9_IO_STREAM_VERSION, | ||
40107 | WinFile_Open, /* xOpen */ | ||
40108 | WinDir_Open, /* xOpenDir */ | ||
40109 | WinFile_Close, /* xClose */ | ||
40110 | WinDir_Close, /* xCloseDir */ | ||
40111 | WinFile_Read, /* xRead */ | ||
40112 | WinDir_Read, /* xReadDir */ | ||
40113 | WinFile_Write, /* xWrite */ | ||
40114 | WinFile_Seek, /* xSeek */ | ||
40115 | WinFile_Lock, /* xLock */ | ||
40116 | WinDir_RewindDir, /* xRewindDir */ | ||
40117 | WinFile_Tell, /* xTell */ | ||
40118 | WinFile_Trunc, /* xTrunc */ | ||
40119 | WinFile_Sync, /* xSeek */ | ||
40120 | WinFile_Stat /* xStat */ | ||
40121 | }; | ||
40122 | #elif defined(__UNIXES__) | ||
40123 | /* | ||
40124 | * UNIX VFS implementation for the JX9 engine. | ||
40125 | * Authors: | ||
40126 | * Symisc Systems, devel@symisc.net. | ||
40127 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
40128 | * Status: | ||
40129 | * Stable. | ||
40130 | */ | ||
40131 | #include <sys/types.h> | ||
40132 | #include <limits.h> | ||
40133 | #include <fcntl.h> | ||
40134 | #include <unistd.h> | ||
40135 | #include <sys/uio.h> | ||
40136 | #include <sys/stat.h> | ||
40137 | #include <sys/mman.h> | ||
40138 | #include <sys/file.h> | ||
40139 | #include <pwd.h> | ||
40140 | #include <grp.h> | ||
40141 | #include <dirent.h> | ||
40142 | #include <utime.h> | ||
40143 | #include <stdio.h> | ||
40144 | #include <stdlib.h> | ||
40145 | /* int (*xchdir)(const char *) */ | ||
40146 | static int UnixVfs_chdir(const char *zPath) | ||
40147 | { | ||
40148 | int rc; | ||
40149 | rc = chdir(zPath); | ||
40150 | return rc == 0 ? JX9_OK : -1; | ||
40151 | } | ||
40152 | /* int (*xGetcwd)(jx9_context *) */ | ||
40153 | static int UnixVfs_getcwd(jx9_context *pCtx) | ||
40154 | { | ||
40155 | char zBuf[4096]; | ||
40156 | char *zDir; | ||
40157 | /* Get the current directory */ | ||
40158 | zDir = getcwd(zBuf, sizeof(zBuf)); | ||
40159 | if( zDir == 0 ){ | ||
40160 | return -1; | ||
40161 | } | ||
40162 | jx9_result_string(pCtx, zDir, -1/*Compute length automatically*/); | ||
40163 | return JX9_OK; | ||
40164 | } | ||
40165 | /* int (*xMkdir)(const char *, int, int) */ | ||
40166 | static int UnixVfs_mkdir(const char *zPath, int mode, int recursive) | ||
40167 | { | ||
40168 | int rc; | ||
40169 | rc = mkdir(zPath, mode); | ||
40170 | recursive = 0; /* cc warning */ | ||
40171 | return rc == 0 ? JX9_OK : -1; | ||
40172 | } | ||
40173 | /* int (*xRmdir)(const char *) */ | ||
40174 | static int UnixVfs_rmdir(const char *zPath) | ||
40175 | { | ||
40176 | int rc; | ||
40177 | rc = rmdir(zPath); | ||
40178 | return rc == 0 ? JX9_OK : -1; | ||
40179 | } | ||
40180 | /* int (*xIsdir)(const char *) */ | ||
40181 | static int UnixVfs_isdir(const char *zPath) | ||
40182 | { | ||
40183 | struct stat st; | ||
40184 | int rc; | ||
40185 | rc = stat(zPath, &st); | ||
40186 | if( rc != 0 ){ | ||
40187 | return -1; | ||
40188 | } | ||
40189 | rc = S_ISDIR(st.st_mode); | ||
40190 | return rc ? JX9_OK : -1 ; | ||
40191 | } | ||
40192 | /* int (*xRename)(const char *, const char *) */ | ||
40193 | static int UnixVfs_Rename(const char *zOld, const char *zNew) | ||
40194 | { | ||
40195 | int rc; | ||
40196 | rc = rename(zOld, zNew); | ||
40197 | return rc == 0 ? JX9_OK : -1; | ||
40198 | } | ||
40199 | /* int (*xRealpath)(const char *, jx9_context *) */ | ||
40200 | static int UnixVfs_Realpath(const char *zPath, jx9_context *pCtx) | ||
40201 | { | ||
40202 | #ifndef JX9_UNIX_OLD_LIBC | ||
40203 | char *zReal; | ||
40204 | zReal = realpath(zPath, 0); | ||
40205 | if( zReal == 0 ){ | ||
40206 | return -1; | ||
40207 | } | ||
40208 | jx9_result_string(pCtx, zReal, -1/*Compute length automatically*/); | ||
40209 | /* Release the allocated buffer */ | ||
40210 | free(zReal); | ||
40211 | return JX9_OK; | ||
40212 | #else | ||
40213 | zPath = 0; /* cc warning */ | ||
40214 | pCtx = 0; | ||
40215 | return -1; | ||
40216 | #endif | ||
40217 | } | ||
40218 | /* int (*xSleep)(unsigned int) */ | ||
40219 | static int UnixVfs_Sleep(unsigned int uSec) | ||
40220 | { | ||
40221 | usleep(uSec); | ||
40222 | return JX9_OK; | ||
40223 | } | ||
40224 | /* int (*xUnlink)(const char *) */ | ||
40225 | static int UnixVfs_unlink(const char *zPath) | ||
40226 | { | ||
40227 | int rc; | ||
40228 | rc = unlink(zPath); | ||
40229 | return rc == 0 ? JX9_OK : -1 ; | ||
40230 | } | ||
40231 | /* int (*xFileExists)(const char *) */ | ||
40232 | static int UnixVfs_FileExists(const char *zPath) | ||
40233 | { | ||
40234 | int rc; | ||
40235 | rc = access(zPath, F_OK); | ||
40236 | return rc == 0 ? JX9_OK : -1; | ||
40237 | } | ||
40238 | /* jx9_int64 (*xFileSize)(const char *) */ | ||
40239 | static jx9_int64 UnixVfs_FileSize(const char *zPath) | ||
40240 | { | ||
40241 | struct stat st; | ||
40242 | int rc; | ||
40243 | rc = stat(zPath, &st); | ||
40244 | if( rc != 0 ){ | ||
40245 | return -1; | ||
40246 | } | ||
40247 | return (jx9_int64)st.st_size; | ||
40248 | } | ||
40249 | /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ | ||
40250 | static int UnixVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time) | ||
40251 | { | ||
40252 | struct utimbuf ut; | ||
40253 | int rc; | ||
40254 | ut.actime = (time_t)access_time; | ||
40255 | ut.modtime = (time_t)touch_time; | ||
40256 | rc = utime(zPath, &ut); | ||
40257 | if( rc != 0 ){ | ||
40258 | return -1; | ||
40259 | } | ||
40260 | return JX9_OK; | ||
40261 | } | ||
40262 | /* jx9_int64 (*xFileAtime)(const char *) */ | ||
40263 | static jx9_int64 UnixVfs_FileAtime(const char *zPath) | ||
40264 | { | ||
40265 | struct stat st; | ||
40266 | int rc; | ||
40267 | rc = stat(zPath, &st); | ||
40268 | if( rc != 0 ){ | ||
40269 | return -1; | ||
40270 | } | ||
40271 | return (jx9_int64)st.st_atime; | ||
40272 | } | ||
40273 | /* jx9_int64 (*xFileMtime)(const char *) */ | ||
40274 | static jx9_int64 UnixVfs_FileMtime(const char *zPath) | ||
40275 | { | ||
40276 | struct stat st; | ||
40277 | int rc; | ||
40278 | rc = stat(zPath, &st); | ||
40279 | if( rc != 0 ){ | ||
40280 | return -1; | ||
40281 | } | ||
40282 | return (jx9_int64)st.st_mtime; | ||
40283 | } | ||
40284 | /* jx9_int64 (*xFileCtime)(const char *) */ | ||
40285 | static jx9_int64 UnixVfs_FileCtime(const char *zPath) | ||
40286 | { | ||
40287 | struct stat st; | ||
40288 | int rc; | ||
40289 | rc = stat(zPath, &st); | ||
40290 | if( rc != 0 ){ | ||
40291 | return -1; | ||
40292 | } | ||
40293 | return (jx9_int64)st.st_ctime; | ||
40294 | } | ||
40295 | /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ | ||
40296 | static int UnixVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) | ||
40297 | { | ||
40298 | struct stat st; | ||
40299 | int rc; | ||
40300 | rc = stat(zPath, &st); | ||
40301 | if( rc != 0 ){ | ||
40302 | return -1; | ||
40303 | } | ||
40304 | /* dev */ | ||
40305 | jx9_value_int64(pWorker, (jx9_int64)st.st_dev); | ||
40306 | jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ | ||
40307 | /* ino */ | ||
40308 | jx9_value_int64(pWorker, (jx9_int64)st.st_ino); | ||
40309 | jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ | ||
40310 | /* mode */ | ||
40311 | jx9_value_int(pWorker, (int)st.st_mode); | ||
40312 | jx9_array_add_strkey_elem(pArray, "mode", pWorker); | ||
40313 | /* nlink */ | ||
40314 | jx9_value_int(pWorker, (int)st.st_nlink); | ||
40315 | jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ | ||
40316 | /* uid, gid, rdev */ | ||
40317 | jx9_value_int(pWorker, (int)st.st_uid); | ||
40318 | jx9_array_add_strkey_elem(pArray, "uid", pWorker); | ||
40319 | jx9_value_int(pWorker, (int)st.st_gid); | ||
40320 | jx9_array_add_strkey_elem(pArray, "gid", pWorker); | ||
40321 | jx9_value_int(pWorker, (int)st.st_rdev); | ||
40322 | jx9_array_add_strkey_elem(pArray, "rdev", pWorker); | ||
40323 | /* size */ | ||
40324 | jx9_value_int64(pWorker, (jx9_int64)st.st_size); | ||
40325 | jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ | ||
40326 | /* atime */ | ||
40327 | jx9_value_int64(pWorker, (jx9_int64)st.st_atime); | ||
40328 | jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ | ||
40329 | /* mtime */ | ||
40330 | jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); | ||
40331 | jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ | ||
40332 | /* ctime */ | ||
40333 | jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); | ||
40334 | jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ | ||
40335 | /* blksize, blocks */ | ||
40336 | jx9_value_int(pWorker, (int)st.st_blksize); | ||
40337 | jx9_array_add_strkey_elem(pArray, "blksize", pWorker); | ||
40338 | jx9_value_int(pWorker, (int)st.st_blocks); | ||
40339 | jx9_array_add_strkey_elem(pArray, "blocks", pWorker); | ||
40340 | return JX9_OK; | ||
40341 | } | ||
40342 | /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ | ||
40343 | static int UnixVfs_lStat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) | ||
40344 | { | ||
40345 | struct stat st; | ||
40346 | int rc; | ||
40347 | rc = lstat(zPath, &st); | ||
40348 | if( rc != 0 ){ | ||
40349 | return -1; | ||
40350 | } | ||
40351 | /* dev */ | ||
40352 | jx9_value_int64(pWorker, (jx9_int64)st.st_dev); | ||
40353 | jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ | ||
40354 | /* ino */ | ||
40355 | jx9_value_int64(pWorker, (jx9_int64)st.st_ino); | ||
40356 | jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ | ||
40357 | /* mode */ | ||
40358 | jx9_value_int(pWorker, (int)st.st_mode); | ||
40359 | jx9_array_add_strkey_elem(pArray, "mode", pWorker); | ||
40360 | /* nlink */ | ||
40361 | jx9_value_int(pWorker, (int)st.st_nlink); | ||
40362 | jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ | ||
40363 | /* uid, gid, rdev */ | ||
40364 | jx9_value_int(pWorker, (int)st.st_uid); | ||
40365 | jx9_array_add_strkey_elem(pArray, "uid", pWorker); | ||
40366 | jx9_value_int(pWorker, (int)st.st_gid); | ||
40367 | jx9_array_add_strkey_elem(pArray, "gid", pWorker); | ||
40368 | jx9_value_int(pWorker, (int)st.st_rdev); | ||
40369 | jx9_array_add_strkey_elem(pArray, "rdev", pWorker); | ||
40370 | /* size */ | ||
40371 | jx9_value_int64(pWorker, (jx9_int64)st.st_size); | ||
40372 | jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ | ||
40373 | /* atime */ | ||
40374 | jx9_value_int64(pWorker, (jx9_int64)st.st_atime); | ||
40375 | jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ | ||
40376 | /* mtime */ | ||
40377 | jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); | ||
40378 | jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ | ||
40379 | /* ctime */ | ||
40380 | jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); | ||
40381 | jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ | ||
40382 | /* blksize, blocks */ | ||
40383 | jx9_value_int(pWorker, (int)st.st_blksize); | ||
40384 | jx9_array_add_strkey_elem(pArray, "blksize", pWorker); | ||
40385 | jx9_value_int(pWorker, (int)st.st_blocks); | ||
40386 | jx9_array_add_strkey_elem(pArray, "blocks", pWorker); | ||
40387 | return JX9_OK; | ||
40388 | } | ||
40389 | /* int (*xChmod)(const char *, int) */ | ||
40390 | static int UnixVfs_Chmod(const char *zPath, int mode) | ||
40391 | { | ||
40392 | int rc; | ||
40393 | rc = chmod(zPath, (mode_t)mode); | ||
40394 | return rc == 0 ? JX9_OK : - 1; | ||
40395 | } | ||
40396 | /* int (*xChown)(const char *, const char *) */ | ||
40397 | static int UnixVfs_Chown(const char *zPath, const char *zUser) | ||
40398 | { | ||
40399 | #ifndef JX9_UNIX_STATIC_BUILD | ||
40400 | struct passwd *pwd; | ||
40401 | uid_t uid; | ||
40402 | int rc; | ||
40403 | pwd = getpwnam(zUser); /* Try getting UID for username */ | ||
40404 | if (pwd == 0) { | ||
40405 | return -1; | ||
40406 | } | ||
40407 | uid = pwd->pw_uid; | ||
40408 | rc = chown(zPath, uid, -1); | ||
40409 | return rc == 0 ? JX9_OK : -1; | ||
40410 | #else | ||
40411 | SXUNUSED(zPath); | ||
40412 | SXUNUSED(zUser); | ||
40413 | return -1; | ||
40414 | #endif /* JX9_UNIX_STATIC_BUILD */ | ||
40415 | } | ||
40416 | /* int (*xChgrp)(const char *, const char *) */ | ||
40417 | static int UnixVfs_Chgrp(const char *zPath, const char *zGroup) | ||
40418 | { | ||
40419 | #ifndef JX9_UNIX_STATIC_BUILD | ||
40420 | struct group *group; | ||
40421 | gid_t gid; | ||
40422 | int rc; | ||
40423 | group = getgrnam(zGroup); | ||
40424 | if (group == 0) { | ||
40425 | return -1; | ||
40426 | } | ||
40427 | gid = group->gr_gid; | ||
40428 | rc = chown(zPath, -1, gid); | ||
40429 | return rc == 0 ? JX9_OK : -1; | ||
40430 | #else | ||
40431 | SXUNUSED(zPath); | ||
40432 | SXUNUSED(zGroup); | ||
40433 | return -1; | ||
40434 | #endif /* JX9_UNIX_STATIC_BUILD */ | ||
40435 | } | ||
40436 | /* int (*xIsfile)(const char *) */ | ||
40437 | static int UnixVfs_isfile(const char *zPath) | ||
40438 | { | ||
40439 | struct stat st; | ||
40440 | int rc; | ||
40441 | rc = stat(zPath, &st); | ||
40442 | if( rc != 0 ){ | ||
40443 | return -1; | ||
40444 | } | ||
40445 | rc = S_ISREG(st.st_mode); | ||
40446 | return rc ? JX9_OK : -1 ; | ||
40447 | } | ||
40448 | /* int (*xIslink)(const char *) */ | ||
40449 | static int UnixVfs_islink(const char *zPath) | ||
40450 | { | ||
40451 | struct stat st; | ||
40452 | int rc; | ||
40453 | rc = stat(zPath, &st); | ||
40454 | if( rc != 0 ){ | ||
40455 | return -1; | ||
40456 | } | ||
40457 | rc = S_ISLNK(st.st_mode); | ||
40458 | return rc ? JX9_OK : -1 ; | ||
40459 | } | ||
40460 | /* int (*xReadable)(const char *) */ | ||
40461 | static int UnixVfs_isreadable(const char *zPath) | ||
40462 | { | ||
40463 | int rc; | ||
40464 | rc = access(zPath, R_OK); | ||
40465 | return rc == 0 ? JX9_OK : -1; | ||
40466 | } | ||
40467 | /* int (*xWritable)(const char *) */ | ||
40468 | static int UnixVfs_iswritable(const char *zPath) | ||
40469 | { | ||
40470 | int rc; | ||
40471 | rc = access(zPath, W_OK); | ||
40472 | return rc == 0 ? JX9_OK : -1; | ||
40473 | } | ||
40474 | /* int (*xExecutable)(const char *) */ | ||
40475 | static int UnixVfs_isexecutable(const char *zPath) | ||
40476 | { | ||
40477 | int rc; | ||
40478 | rc = access(zPath, X_OK); | ||
40479 | return rc == 0 ? JX9_OK : -1; | ||
40480 | } | ||
40481 | /* int (*xFiletype)(const char *, jx9_context *) */ | ||
40482 | static int UnixVfs_Filetype(const char *zPath, jx9_context *pCtx) | ||
40483 | { | ||
40484 | struct stat st; | ||
40485 | int rc; | ||
40486 | rc = stat(zPath, &st); | ||
40487 | if( rc != 0 ){ | ||
40488 | /* Expand 'unknown' */ | ||
40489 | jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); | ||
40490 | return -1; | ||
40491 | } | ||
40492 | if(S_ISREG(st.st_mode) ){ | ||
40493 | jx9_result_string(pCtx, "file", sizeof("file")-1); | ||
40494 | }else if(S_ISDIR(st.st_mode)){ | ||
40495 | jx9_result_string(pCtx, "dir", sizeof("dir")-1); | ||
40496 | }else if(S_ISLNK(st.st_mode)){ | ||
40497 | jx9_result_string(pCtx, "link", sizeof("link")-1); | ||
40498 | }else if(S_ISBLK(st.st_mode)){ | ||
40499 | jx9_result_string(pCtx, "block", sizeof("block")-1); | ||
40500 | }else if(S_ISSOCK(st.st_mode)){ | ||
40501 | jx9_result_string(pCtx, "socket", sizeof("socket")-1); | ||
40502 | }else if(S_ISFIFO(st.st_mode)){ | ||
40503 | jx9_result_string(pCtx, "fifo", sizeof("fifo")-1); | ||
40504 | }else{ | ||
40505 | jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); | ||
40506 | } | ||
40507 | return JX9_OK; | ||
40508 | } | ||
40509 | /* int (*xGetenv)(const char *, jx9_context *) */ | ||
40510 | static int UnixVfs_Getenv(const char *zVar, jx9_context *pCtx) | ||
40511 | { | ||
40512 | char *zEnv; | ||
40513 | zEnv = getenv(zVar); | ||
40514 | if( zEnv == 0 ){ | ||
40515 | return -1; | ||
40516 | } | ||
40517 | jx9_result_string(pCtx, zEnv, -1/*Compute length automatically*/); | ||
40518 | return JX9_OK; | ||
40519 | } | ||
40520 | /* int (*xSetenv)(const char *, const char *) */ | ||
40521 | static int UnixVfs_Setenv(const char *zName, const char *zValue) | ||
40522 | { | ||
40523 | int rc; | ||
40524 | rc = setenv(zName, zValue, 1); | ||
40525 | return rc == 0 ? JX9_OK : -1; | ||
40526 | } | ||
40527 | /* int (*xMmap)(const char *, void **, jx9_int64 *) */ | ||
40528 | static int UnixVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize) | ||
40529 | { | ||
40530 | struct stat st; | ||
40531 | void *pMap; | ||
40532 | int fd; | ||
40533 | int rc; | ||
40534 | /* Open the file in a read-only mode */ | ||
40535 | fd = open(zPath, O_RDONLY); | ||
40536 | if( fd < 0 ){ | ||
40537 | return -1; | ||
40538 | } | ||
40539 | /* stat the handle */ | ||
40540 | fstat(fd, &st); | ||
40541 | /* Obtain a memory view of the whole file */ | ||
40542 | pMap = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE|MAP_FILE, fd, 0); | ||
40543 | rc = JX9_OK; | ||
40544 | if( pMap == MAP_FAILED ){ | ||
40545 | rc = -1; | ||
40546 | }else{ | ||
40547 | /* Point to the memory view */ | ||
40548 | *ppMap = pMap; | ||
40549 | *pSize = (jx9_int64)st.st_size; | ||
40550 | } | ||
40551 | close(fd); | ||
40552 | return rc; | ||
40553 | } | ||
40554 | /* void (*xUnmap)(void *, jx9_int64) */ | ||
40555 | static void UnixVfs_Unmap(void *pView, jx9_int64 nSize) | ||
40556 | { | ||
40557 | munmap(pView, (size_t)nSize); | ||
40558 | } | ||
40559 | /* void (*xTempDir)(jx9_context *) */ | ||
40560 | static void UnixVfs_TempDir(jx9_context *pCtx) | ||
40561 | { | ||
40562 | static const char *azDirs[] = { | ||
40563 | "/var/tmp", | ||
40564 | "/usr/tmp", | ||
40565 | "/usr/local/tmp" | ||
40566 | }; | ||
40567 | unsigned int i; | ||
40568 | struct stat buf; | ||
40569 | const char *zDir; | ||
40570 | zDir = getenv("TMPDIR"); | ||
40571 | if( zDir && zDir[0] != 0 && !access(zDir, 07) ){ | ||
40572 | jx9_result_string(pCtx, zDir, -1); | ||
40573 | return; | ||
40574 | } | ||
40575 | for(i=0; i<sizeof(azDirs)/sizeof(azDirs[0]); i++){ | ||
40576 | zDir=azDirs[i]; | ||
40577 | if( zDir==0 ) continue; | ||
40578 | if( stat(zDir, &buf) ) continue; | ||
40579 | if( !S_ISDIR(buf.st_mode) ) continue; | ||
40580 | if( access(zDir, 07) ) continue; | ||
40581 | /* Got one */ | ||
40582 | jx9_result_string(pCtx, zDir, -1); | ||
40583 | return; | ||
40584 | } | ||
40585 | /* Default temp dir */ | ||
40586 | jx9_result_string(pCtx, "/tmp", (int)sizeof("/tmp")-1); | ||
40587 | } | ||
40588 | /* unsigned int (*xProcessId)(void) */ | ||
40589 | static unsigned int UnixVfs_ProcessId(void) | ||
40590 | { | ||
40591 | return (unsigned int)getpid(); | ||
40592 | } | ||
40593 | /* int (*xUid)(void) */ | ||
40594 | static int UnixVfs_uid(void) | ||
40595 | { | ||
40596 | return (int)getuid(); | ||
40597 | } | ||
40598 | /* int (*xGid)(void) */ | ||
40599 | static int UnixVfs_gid(void) | ||
40600 | { | ||
40601 | return (int)getgid(); | ||
40602 | } | ||
40603 | /* int (*xUmask)(int) */ | ||
40604 | static int UnixVfs_Umask(int new_mask) | ||
40605 | { | ||
40606 | int old_mask; | ||
40607 | old_mask = umask(new_mask); | ||
40608 | return old_mask; | ||
40609 | } | ||
40610 | /* void (*xUsername)(jx9_context *) */ | ||
40611 | static void UnixVfs_Username(jx9_context *pCtx) | ||
40612 | { | ||
40613 | #ifndef JX9_UNIX_STATIC_BUILD | ||
40614 | struct passwd *pwd; | ||
40615 | uid_t uid; | ||
40616 | uid = getuid(); | ||
40617 | pwd = getpwuid(uid); /* Try getting UID for username */ | ||
40618 | if (pwd == 0) { | ||
40619 | return; | ||
40620 | } | ||
40621 | /* Return the username */ | ||
40622 | jx9_result_string(pCtx, pwd->pw_name, -1); | ||
40623 | #else | ||
40624 | jx9_result_string(pCtx, "Unknown", -1); | ||
40625 | #endif /* JX9_UNIX_STATIC_BUILD */ | ||
40626 | return; | ||
40627 | } | ||
40628 | /* int (*xLink)(const char *, const char *, int) */ | ||
40629 | static int UnixVfs_link(const char *zSrc, const char *zTarget, int is_sym) | ||
40630 | { | ||
40631 | int rc; | ||
40632 | if( is_sym ){ | ||
40633 | /* Symbolic link */ | ||
40634 | rc = symlink(zSrc, zTarget); | ||
40635 | }else{ | ||
40636 | /* Hard link */ | ||
40637 | rc = link(zSrc, zTarget); | ||
40638 | } | ||
40639 | return rc == 0 ? JX9_OK : -1; | ||
40640 | } | ||
40641 | /* int (*xChroot)(const char *) */ | ||
40642 | static int UnixVfs_chroot(const char *zRootDir) | ||
40643 | { | ||
40644 | int rc; | ||
40645 | rc = chroot(zRootDir); | ||
40646 | return rc == 0 ? JX9_OK : -1; | ||
40647 | } | ||
40648 | /* Export the UNIX vfs */ | ||
40649 | static const jx9_vfs sUnixVfs = { | ||
40650 | "Unix_vfs", | ||
40651 | JX9_VFS_VERSION, | ||
40652 | UnixVfs_chdir, /* int (*xChdir)(const char *) */ | ||
40653 | UnixVfs_chroot, /* int (*xChroot)(const char *); */ | ||
40654 | UnixVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */ | ||
40655 | UnixVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */ | ||
40656 | UnixVfs_rmdir, /* int (*xRmdir)(const char *) */ | ||
40657 | UnixVfs_isdir, /* int (*xIsdir)(const char *) */ | ||
40658 | UnixVfs_Rename, /* int (*xRename)(const char *, const char *) */ | ||
40659 | UnixVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/ | ||
40660 | UnixVfs_Sleep, /* int (*xSleep)(unsigned int) */ | ||
40661 | UnixVfs_unlink, /* int (*xUnlink)(const char *) */ | ||
40662 | UnixVfs_FileExists, /* int (*xFileExists)(const char *) */ | ||
40663 | UnixVfs_Chmod, /*int (*xChmod)(const char *, int)*/ | ||
40664 | UnixVfs_Chown, /*int (*xChown)(const char *, const char *)*/ | ||
40665 | UnixVfs_Chgrp, /*int (*xChgrp)(const char *, const char *)*/ | ||
40666 | 0, /* jx9_int64 (*xFreeSpace)(const char *) */ | ||
40667 | 0, /* jx9_int64 (*xTotalSpace)(const char *) */ | ||
40668 | UnixVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */ | ||
40669 | UnixVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */ | ||
40670 | UnixVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */ | ||
40671 | UnixVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */ | ||
40672 | UnixVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ | ||
40673 | UnixVfs_lStat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ | ||
40674 | UnixVfs_isfile, /* int (*xIsfile)(const char *) */ | ||
40675 | UnixVfs_islink, /* int (*xIslink)(const char *) */ | ||
40676 | UnixVfs_isreadable, /* int (*xReadable)(const char *) */ | ||
40677 | UnixVfs_iswritable, /* int (*xWritable)(const char *) */ | ||
40678 | UnixVfs_isexecutable, /* int (*xExecutable)(const char *) */ | ||
40679 | UnixVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */ | ||
40680 | UnixVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */ | ||
40681 | UnixVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */ | ||
40682 | UnixVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ | ||
40683 | UnixVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ | ||
40684 | UnixVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */ | ||
40685 | UnixVfs_link, /* int (*xLink)(const char *, const char *, int) */ | ||
40686 | UnixVfs_Umask, /* int (*xUmask)(int) */ | ||
40687 | UnixVfs_TempDir, /* void (*xTempDir)(jx9_context *) */ | ||
40688 | UnixVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ | ||
40689 | UnixVfs_uid, /* int (*xUid)(void) */ | ||
40690 | UnixVfs_gid, /* int (*xGid)(void) */ | ||
40691 | UnixVfs_Username, /* void (*xUsername)(jx9_context *) */ | ||
40692 | 0 /* int (*xExec)(const char *, jx9_context *) */ | ||
40693 | }; | ||
40694 | /* UNIX File IO */ | ||
40695 | #define JX9_UNIX_OPEN_MODE 0640 /* Default open mode */ | ||
40696 | /* int (*xOpen)(const char *, int, jx9_value *, void **) */ | ||
40697 | static int UnixFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle) | ||
40698 | { | ||
40699 | int iOpen = O_RDONLY; | ||
40700 | int fd; | ||
40701 | /* Set the desired flags according to the open mode */ | ||
40702 | if( iOpenMode & JX9_IO_OPEN_CREATE ){ | ||
40703 | /* Open existing file, or create if it doesn't exist */ | ||
40704 | iOpen = O_CREAT; | ||
40705 | if( iOpenMode & JX9_IO_OPEN_TRUNC ){ | ||
40706 | /* If the specified file exists and is writable, the function overwrites the file */ | ||
40707 | iOpen |= O_TRUNC; | ||
40708 | SXUNUSED(pResource); /* cc warning */ | ||
40709 | } | ||
40710 | }else if( iOpenMode & JX9_IO_OPEN_EXCL ){ | ||
40711 | /* Creates a new file, only if it does not already exist. | ||
40712 | * If the file exists, it fails. | ||
40713 | */ | ||
40714 | iOpen = O_CREAT|O_EXCL; | ||
40715 | }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){ | ||
40716 | /* Opens a file and truncates it so that its size is zero bytes | ||
40717 | * The file must exist. | ||
40718 | */ | ||
40719 | iOpen = O_RDWR|O_TRUNC; | ||
40720 | } | ||
40721 | if( iOpenMode & JX9_IO_OPEN_RDWR ){ | ||
40722 | /* Read+Write access */ | ||
40723 | iOpen &= ~O_RDONLY; | ||
40724 | iOpen |= O_RDWR; | ||
40725 | }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){ | ||
40726 | /* Write only access */ | ||
40727 | iOpen &= ~O_RDONLY; | ||
40728 | iOpen |= O_WRONLY; | ||
40729 | } | ||
40730 | if( iOpenMode & JX9_IO_OPEN_APPEND ){ | ||
40731 | /* Append mode */ | ||
40732 | iOpen |= O_APPEND; | ||
40733 | } | ||
40734 | #ifdef O_TEMP | ||
40735 | if( iOpenMode & JX9_IO_OPEN_TEMP ){ | ||
40736 | /* File is temporary */ | ||
40737 | iOpen |= O_TEMP; | ||
40738 | } | ||
40739 | #endif | ||
40740 | /* Open the file now */ | ||
40741 | fd = open(zPath, iOpen, JX9_UNIX_OPEN_MODE); | ||
40742 | if( fd < 0 ){ | ||
40743 | /* IO error */ | ||
40744 | return -1; | ||
40745 | } | ||
40746 | /* Save the handle */ | ||
40747 | *ppHandle = SX_INT_TO_PTR(fd); | ||
40748 | return JX9_OK; | ||
40749 | } | ||
40750 | /* int (*xOpenDir)(const char *, jx9_value *, void **) */ | ||
40751 | static int UnixDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle) | ||
40752 | { | ||
40753 | DIR *pDir; | ||
40754 | /* Open the target directory */ | ||
40755 | pDir = opendir(zPath); | ||
40756 | if( pDir == 0 ){ | ||
40757 | pResource = 0; /* Compiler warning */ | ||
40758 | return -1; | ||
40759 | } | ||
40760 | /* Save our structure */ | ||
40761 | *ppHandle = pDir; | ||
40762 | return JX9_OK; | ||
40763 | } | ||
40764 | /* void (*xCloseDir)(void *) */ | ||
40765 | static void UnixDir_Close(void *pUserData) | ||
40766 | { | ||
40767 | closedir((DIR *)pUserData); | ||
40768 | } | ||
40769 | /* void (*xClose)(void *); */ | ||
40770 | static void UnixFile_Close(void *pUserData) | ||
40771 | { | ||
40772 | close(SX_PTR_TO_INT(pUserData)); | ||
40773 | } | ||
40774 | /* int (*xReadDir)(void *, jx9_context *) */ | ||
40775 | static int UnixDir_Read(void *pUserData, jx9_context *pCtx) | ||
40776 | { | ||
40777 | DIR *pDir = (DIR *)pUserData; | ||
40778 | struct dirent *pEntry; | ||
40779 | char *zName = 0; /* cc warning */ | ||
40780 | sxu32 n = 0; | ||
40781 | for(;;){ | ||
40782 | pEntry = readdir(pDir); | ||
40783 | if( pEntry == 0 ){ | ||
40784 | /* No more entries to process */ | ||
40785 | return -1; | ||
40786 | } | ||
40787 | zName = pEntry->d_name; | ||
40788 | n = SyStrlen(zName); | ||
40789 | /* Ignore '.' && '..' */ | ||
40790 | if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ | ||
40791 | break; | ||
40792 | } | ||
40793 | /* Next entry */ | ||
40794 | } | ||
40795 | /* Return the current file name */ | ||
40796 | jx9_result_string(pCtx, zName, (int)n); | ||
40797 | return JX9_OK; | ||
40798 | } | ||
40799 | /* void (*xRewindDir)(void *) */ | ||
40800 | static void UnixDir_Rewind(void *pUserData) | ||
40801 | { | ||
40802 | rewinddir((DIR *)pUserData); | ||
40803 | } | ||
40804 | /* jx9_int64 (*xRead)(void *, void *, jx9_int64); */ | ||
40805 | static jx9_int64 UnixFile_Read(void *pUserData, void *pBuffer, jx9_int64 nDatatoRead) | ||
40806 | { | ||
40807 | ssize_t nRd; | ||
40808 | nRd = read(SX_PTR_TO_INT(pUserData), pBuffer, (size_t)nDatatoRead); | ||
40809 | if( nRd < 1 ){ | ||
40810 | /* EOF or IO error */ | ||
40811 | return -1; | ||
40812 | } | ||
40813 | return (jx9_int64)nRd; | ||
40814 | } | ||
40815 | /* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */ | ||
40816 | static jx9_int64 UnixFile_Write(void *pUserData, const void *pBuffer, jx9_int64 nWrite) | ||
40817 | { | ||
40818 | const char *zData = (const char *)pBuffer; | ||
40819 | int fd = SX_PTR_TO_INT(pUserData); | ||
40820 | jx9_int64 nCount; | ||
40821 | ssize_t nWr; | ||
40822 | nCount = 0; | ||
40823 | for(;;){ | ||
40824 | if( nWrite < 1 ){ | ||
40825 | break; | ||
40826 | } | ||
40827 | nWr = write(fd, zData, (size_t)nWrite); | ||
40828 | if( nWr < 1 ){ | ||
40829 | /* IO error */ | ||
40830 | break; | ||
40831 | } | ||
40832 | nWrite -= nWr; | ||
40833 | nCount += nWr; | ||
40834 | zData += nWr; | ||
40835 | } | ||
40836 | if( nWrite > 0 ){ | ||
40837 | return -1; | ||
40838 | } | ||
40839 | return nCount; | ||
40840 | } | ||
40841 | /* int (*xSeek)(void *, jx9_int64, int) */ | ||
40842 | static int UnixFile_Seek(void *pUserData, jx9_int64 iOfft, int whence) | ||
40843 | { | ||
40844 | off_t iNew; | ||
40845 | switch(whence){ | ||
40846 | case 1:/*SEEK_CUR*/ | ||
40847 | whence = SEEK_CUR; | ||
40848 | break; | ||
40849 | case 2: /* SEEK_END */ | ||
40850 | whence = SEEK_END; | ||
40851 | break; | ||
40852 | case 0: /* SEEK_SET */ | ||
40853 | default: | ||
40854 | whence = SEEK_SET; | ||
40855 | break; | ||
40856 | } | ||
40857 | iNew = lseek(SX_PTR_TO_INT(pUserData), (off_t)iOfft, whence); | ||
40858 | if( iNew < 0 ){ | ||
40859 | return -1; | ||
40860 | } | ||
40861 | return JX9_OK; | ||
40862 | } | ||
40863 | /* int (*xLock)(void *, int) */ | ||
40864 | static int UnixFile_Lock(void *pUserData, int lock_type) | ||
40865 | { | ||
40866 | int fd = SX_PTR_TO_INT(pUserData); | ||
40867 | int rc = JX9_OK; /* cc warning */ | ||
40868 | if( lock_type < 0 ){ | ||
40869 | /* Unlock the file */ | ||
40870 | rc = flock(fd, LOCK_UN); | ||
40871 | }else{ | ||
40872 | if( lock_type == 1 ){ | ||
40873 | /* Exculsive lock */ | ||
40874 | rc = flock(fd, LOCK_EX); | ||
40875 | }else{ | ||
40876 | /* Shared lock */ | ||
40877 | rc = flock(fd, LOCK_SH); | ||
40878 | } | ||
40879 | } | ||
40880 | return !rc ? JX9_OK : -1; | ||
40881 | } | ||
40882 | /* jx9_int64 (*xTell)(void *) */ | ||
40883 | static jx9_int64 UnixFile_Tell(void *pUserData) | ||
40884 | { | ||
40885 | off_t iNew; | ||
40886 | iNew = lseek(SX_PTR_TO_INT(pUserData), 0, SEEK_CUR); | ||
40887 | return (jx9_int64)iNew; | ||
40888 | } | ||
40889 | /* int (*xTrunc)(void *, jx9_int64) */ | ||
40890 | static int UnixFile_Trunc(void *pUserData, jx9_int64 nOfft) | ||
40891 | { | ||
40892 | int rc; | ||
40893 | rc = ftruncate(SX_PTR_TO_INT(pUserData), (off_t)nOfft); | ||
40894 | if( rc != 0 ){ | ||
40895 | return -1; | ||
40896 | } | ||
40897 | return JX9_OK; | ||
40898 | } | ||
40899 | /* int (*xSync)(void *); */ | ||
40900 | static int UnixFile_Sync(void *pUserData) | ||
40901 | { | ||
40902 | int rc; | ||
40903 | rc = fsync(SX_PTR_TO_INT(pUserData)); | ||
40904 | return rc == 0 ? JX9_OK : - 1; | ||
40905 | } | ||
40906 | /* int (*xStat)(void *, jx9_value *, jx9_value *) */ | ||
40907 | static int UnixFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker) | ||
40908 | { | ||
40909 | struct stat st; | ||
40910 | int rc; | ||
40911 | rc = fstat(SX_PTR_TO_INT(pUserData), &st); | ||
40912 | if( rc != 0 ){ | ||
40913 | return -1; | ||
40914 | } | ||
40915 | /* dev */ | ||
40916 | jx9_value_int64(pWorker, (jx9_int64)st.st_dev); | ||
40917 | jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ | ||
40918 | /* ino */ | ||
40919 | jx9_value_int64(pWorker, (jx9_int64)st.st_ino); | ||
40920 | jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ | ||
40921 | /* mode */ | ||
40922 | jx9_value_int(pWorker, (int)st.st_mode); | ||
40923 | jx9_array_add_strkey_elem(pArray, "mode", pWorker); | ||
40924 | /* nlink */ | ||
40925 | jx9_value_int(pWorker, (int)st.st_nlink); | ||
40926 | jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ | ||
40927 | /* uid, gid, rdev */ | ||
40928 | jx9_value_int(pWorker, (int)st.st_uid); | ||
40929 | jx9_array_add_strkey_elem(pArray, "uid", pWorker); | ||
40930 | jx9_value_int(pWorker, (int)st.st_gid); | ||
40931 | jx9_array_add_strkey_elem(pArray, "gid", pWorker); | ||
40932 | jx9_value_int(pWorker, (int)st.st_rdev); | ||
40933 | jx9_array_add_strkey_elem(pArray, "rdev", pWorker); | ||
40934 | /* size */ | ||
40935 | jx9_value_int64(pWorker, (jx9_int64)st.st_size); | ||
40936 | jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ | ||
40937 | /* atime */ | ||
40938 | jx9_value_int64(pWorker, (jx9_int64)st.st_atime); | ||
40939 | jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ | ||
40940 | /* mtime */ | ||
40941 | jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); | ||
40942 | jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ | ||
40943 | /* ctime */ | ||
40944 | jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); | ||
40945 | jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ | ||
40946 | /* blksize, blocks */ | ||
40947 | jx9_value_int(pWorker, (int)st.st_blksize); | ||
40948 | jx9_array_add_strkey_elem(pArray, "blksize", pWorker); | ||
40949 | jx9_value_int(pWorker, (int)st.st_blocks); | ||
40950 | jx9_array_add_strkey_elem(pArray, "blocks", pWorker); | ||
40951 | return JX9_OK; | ||
40952 | } | ||
40953 | /* Export the file:// stream */ | ||
40954 | static const jx9_io_stream sUnixFileStream = { | ||
40955 | "file", /* Stream name */ | ||
40956 | JX9_IO_STREAM_VERSION, | ||
40957 | UnixFile_Open, /* xOpen */ | ||
40958 | UnixDir_Open, /* xOpenDir */ | ||
40959 | UnixFile_Close, /* xClose */ | ||
40960 | UnixDir_Close, /* xCloseDir */ | ||
40961 | UnixFile_Read, /* xRead */ | ||
40962 | UnixDir_Read, /* xReadDir */ | ||
40963 | UnixFile_Write, /* xWrite */ | ||
40964 | UnixFile_Seek, /* xSeek */ | ||
40965 | UnixFile_Lock, /* xLock */ | ||
40966 | UnixDir_Rewind, /* xRewindDir */ | ||
40967 | UnixFile_Tell, /* xTell */ | ||
40968 | UnixFile_Trunc, /* xTrunc */ | ||
40969 | UnixFile_Sync, /* xSeek */ | ||
40970 | UnixFile_Stat /* xStat */ | ||
40971 | }; | ||
40972 | #endif /* __WINNT__/__UNIXES__ */ | ||
40973 | #endif /* JX9_DISABLE_DISK_IO */ | ||
40974 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
40975 | /* | ||
40976 | * Export the builtin vfs. | ||
40977 | * Return a pointer to the builtin vfs if available. | ||
40978 | * Otherwise return the null_vfs [i.e: a no-op vfs] instead. | ||
40979 | * Note: | ||
40980 | * The built-in vfs is always available for Windows/UNIX systems. | ||
40981 | * Note: | ||
40982 | * If the engine is compiled with the JX9_DISABLE_DISK_IO/JX9_DISABLE_BUILTIN_FUNC | ||
40983 | * directives defined then this function return the null_vfs instead. | ||
40984 | */ | ||
40985 | JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void) | ||
40986 | { | ||
40987 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
40988 | #ifdef JX9_DISABLE_DISK_IO | ||
40989 | return &null_vfs; | ||
40990 | #else | ||
40991 | #ifdef __WINNT__ | ||
40992 | return &sWinVfs; | ||
40993 | #elif defined(__UNIXES__) | ||
40994 | return &sUnixVfs; | ||
40995 | #else | ||
40996 | return &null_vfs; | ||
40997 | #endif /* __WINNT__/__UNIXES__ */ | ||
40998 | #endif /*JX9_DISABLE_DISK_IO*/ | ||
40999 | #else | ||
41000 | return &null_vfs; | ||
41001 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
41002 | } | ||
41003 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
41004 | #ifndef JX9_DISABLE_DISK_IO | ||
41005 | /* | ||
41006 | * The following defines are mostly used by the UNIX built and have | ||
41007 | * no particular meaning on windows. | ||
41008 | */ | ||
41009 | #ifndef STDIN_FILENO | ||
41010 | #define STDIN_FILENO 0 | ||
41011 | #endif | ||
41012 | #ifndef STDOUT_FILENO | ||
41013 | #define STDOUT_FILENO 1 | ||
41014 | #endif | ||
41015 | #ifndef STDERR_FILENO | ||
41016 | #define STDERR_FILENO 2 | ||
41017 | #endif | ||
41018 | /* | ||
41019 | * jx9:// Accessing various I/O streams | ||
41020 | * According to the JX9 langage reference manual | ||
41021 | * JX9 provides a number of miscellaneous I/O streams that allow access to JX9's own input | ||
41022 | * and output streams, the standard input, output and error file descriptors. | ||
41023 | * jx9://stdin, jx9://stdout and jx9://stderr: | ||
41024 | * Allow direct access to the corresponding input or output stream of the JX9 process. | ||
41025 | * The stream references a duplicate file descriptor, so if you open jx9://stdin and later | ||
41026 | * close it, you close only your copy of the descriptor-the actual stream referenced by STDIN is unaffected. | ||
41027 | * jx9://stdin is read-only, whereas jx9://stdout and jx9://stderr are write-only. | ||
41028 | * jx9://output | ||
41029 | * jx9://output is a write-only stream that allows you to write to the output buffer | ||
41030 | * mechanism in the same way as print and print. | ||
41031 | */ | ||
41032 | typedef struct jx9_stream_data jx9_stream_data; | ||
41033 | /* Supported IO streams */ | ||
41034 | #define JX9_IO_STREAM_STDIN 1 /* jx9://stdin */ | ||
41035 | #define JX9_IO_STREAM_STDOUT 2 /* jx9://stdout */ | ||
41036 | #define JX9_IO_STREAM_STDERR 3 /* jx9://stderr */ | ||
41037 | #define JX9_IO_STREAM_OUTPUT 4 /* jx9://output */ | ||
41038 | /* The following structure is the private data associated with the jx9:// stream */ | ||
41039 | struct jx9_stream_data | ||
41040 | { | ||
41041 | jx9_vm *pVm; /* VM that own this instance */ | ||
41042 | int iType; /* Stream type */ | ||
41043 | union{ | ||
41044 | void *pHandle; /* Stream handle */ | ||
41045 | jx9_output_consumer sConsumer; /* VM output consumer */ | ||
41046 | }x; | ||
41047 | }; | ||
41048 | /* | ||
41049 | * Allocate a new instance of the jx9_stream_data structure. | ||
41050 | */ | ||
41051 | static jx9_stream_data * JX9StreamDataInit(jx9_vm *pVm, int iType) | ||
41052 | { | ||
41053 | jx9_stream_data *pData; | ||
41054 | if( pVm == 0 ){ | ||
41055 | return 0; | ||
41056 | } | ||
41057 | /* Allocate a new instance */ | ||
41058 | pData = (jx9_stream_data *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(jx9_stream_data)); | ||
41059 | if( pData == 0 ){ | ||
41060 | return 0; | ||
41061 | } | ||
41062 | /* Zero the structure */ | ||
41063 | SyZero(pData, sizeof(jx9_stream_data)); | ||
41064 | /* Initialize fields */ | ||
41065 | pData->iType = iType; | ||
41066 | if( iType == JX9_IO_STREAM_OUTPUT ){ | ||
41067 | /* Point to the default VM consumer routine. */ | ||
41068 | pData->x.sConsumer = pVm->sVmConsumer; | ||
41069 | }else{ | ||
41070 | #ifdef __WINNT__ | ||
41071 | DWORD nChannel; | ||
41072 | switch(iType){ | ||
41073 | case JX9_IO_STREAM_STDOUT: nChannel = STD_OUTPUT_HANDLE; break; | ||
41074 | case JX9_IO_STREAM_STDERR: nChannel = STD_ERROR_HANDLE; break; | ||
41075 | default: | ||
41076 | nChannel = STD_INPUT_HANDLE; | ||
41077 | break; | ||
41078 | } | ||
41079 | pData->x.pHandle = GetStdHandle(nChannel); | ||
41080 | #else | ||
41081 | /* Assume an UNIX system */ | ||
41082 | int ifd = STDIN_FILENO; | ||
41083 | switch(iType){ | ||
41084 | case JX9_IO_STREAM_STDOUT: ifd = STDOUT_FILENO; break; | ||
41085 | case JX9_IO_STREAM_STDERR: ifd = STDERR_FILENO; break; | ||
41086 | default: | ||
41087 | break; | ||
41088 | } | ||
41089 | pData->x.pHandle = SX_INT_TO_PTR(ifd); | ||
41090 | #endif | ||
41091 | } | ||
41092 | pData->pVm = pVm; | ||
41093 | return pData; | ||
41094 | } | ||
41095 | /* | ||
41096 | * Implementation of the jx9:// IO streams routines | ||
41097 | * Authors: | ||
41098 | * Symisc Systems, devel@symisc.net. | ||
41099 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
41100 | * Status: | ||
41101 | * Stable. | ||
41102 | */ | ||
41103 | /* int (*xOpen)(const char *, int, jx9_value *, void **) */ | ||
41104 | static int JX9StreamData_Open(const char *zName, int iMode, jx9_value *pResource, void ** ppHandle) | ||
41105 | { | ||
41106 | jx9_stream_data *pData; | ||
41107 | SyString sStream; | ||
41108 | SyStringInitFromBuf(&sStream, zName, SyStrlen(zName)); | ||
41109 | /* Trim leading and trailing white spaces */ | ||
41110 | SyStringFullTrim(&sStream); | ||
41111 | /* Stream to open */ | ||
41112 | if( SyStrnicmp(sStream.zString, "stdin", sizeof("stdin")-1) == 0 ){ | ||
41113 | iMode = JX9_IO_STREAM_STDIN; | ||
41114 | }else if( SyStrnicmp(sStream.zString, "output", sizeof("output")-1) == 0 ){ | ||
41115 | iMode = JX9_IO_STREAM_OUTPUT; | ||
41116 | }else if( SyStrnicmp(sStream.zString, "stdout", sizeof("stdout")-1) == 0 ){ | ||
41117 | iMode = JX9_IO_STREAM_STDOUT; | ||
41118 | }else if( SyStrnicmp(sStream.zString, "stderr", sizeof("stderr")-1) == 0 ){ | ||
41119 | iMode = JX9_IO_STREAM_STDERR; | ||
41120 | }else{ | ||
41121 | /* unknown stream name */ | ||
41122 | return -1; | ||
41123 | } | ||
41124 | /* Create our handle */ | ||
41125 | pData = JX9StreamDataInit(pResource?pResource->pVm:0, iMode); | ||
41126 | if( pData == 0 ){ | ||
41127 | return -1; | ||
41128 | } | ||
41129 | /* Make the handle public */ | ||
41130 | *ppHandle = (void *)pData; | ||
41131 | return JX9_OK; | ||
41132 | } | ||
41133 | /* jx9_int64 (*xRead)(void *, void *, jx9_int64) */ | ||
41134 | static jx9_int64 JX9StreamData_Read(void *pHandle, void *pBuffer, jx9_int64 nDatatoRead) | ||
41135 | { | ||
41136 | jx9_stream_data *pData = (jx9_stream_data *)pHandle; | ||
41137 | if( pData == 0 ){ | ||
41138 | return -1; | ||
41139 | } | ||
41140 | if( pData->iType != JX9_IO_STREAM_STDIN ){ | ||
41141 | /* Forbidden */ | ||
41142 | return -1; | ||
41143 | } | ||
41144 | #ifdef __WINNT__ | ||
41145 | { | ||
41146 | DWORD nRd; | ||
41147 | BOOL rc; | ||
41148 | rc = ReadFile(pData->x.pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0); | ||
41149 | if( !rc ){ | ||
41150 | /* IO error */ | ||
41151 | return -1; | ||
41152 | } | ||
41153 | return (jx9_int64)nRd; | ||
41154 | } | ||
41155 | #elif defined(__UNIXES__) | ||
41156 | { | ||
41157 | ssize_t nRd; | ||
41158 | int fd; | ||
41159 | fd = SX_PTR_TO_INT(pData->x.pHandle); | ||
41160 | nRd = read(fd, pBuffer, (size_t)nDatatoRead); | ||
41161 | if( nRd < 1 ){ | ||
41162 | return -1; | ||
41163 | } | ||
41164 | return (jx9_int64)nRd; | ||
41165 | } | ||
41166 | #else | ||
41167 | return -1; | ||
41168 | #endif | ||
41169 | } | ||
41170 | /* jx9_int64 (*xWrite)(void *, const void *, jx9_int64) */ | ||
41171 | static jx9_int64 JX9StreamData_Write(void *pHandle, const void *pBuf, jx9_int64 nWrite) | ||
41172 | { | ||
41173 | jx9_stream_data *pData = (jx9_stream_data *)pHandle; | ||
41174 | if( pData == 0 ){ | ||
41175 | return -1; | ||
41176 | } | ||
41177 | if( pData->iType == JX9_IO_STREAM_STDIN ){ | ||
41178 | /* Forbidden */ | ||
41179 | return -1; | ||
41180 | }else if( pData->iType == JX9_IO_STREAM_OUTPUT ){ | ||
41181 | jx9_output_consumer *pCons = &pData->x.sConsumer; | ||
41182 | int rc; | ||
41183 | /* Call the vm output consumer */ | ||
41184 | rc = pCons->xConsumer(pBuf, (unsigned int)nWrite, pCons->pUserData); | ||
41185 | if( rc == JX9_ABORT ){ | ||
41186 | return -1; | ||
41187 | } | ||
41188 | return nWrite; | ||
41189 | } | ||
41190 | #ifdef __WINNT__ | ||
41191 | { | ||
41192 | DWORD nWr; | ||
41193 | BOOL rc; | ||
41194 | rc = WriteFile(pData->x.pHandle, pBuf, (DWORD)nWrite, &nWr, 0); | ||
41195 | if( !rc ){ | ||
41196 | /* IO error */ | ||
41197 | return -1; | ||
41198 | } | ||
41199 | return (jx9_int64)nWr; | ||
41200 | } | ||
41201 | #elif defined(__UNIXES__) | ||
41202 | { | ||
41203 | ssize_t nWr; | ||
41204 | int fd; | ||
41205 | fd = SX_PTR_TO_INT(pData->x.pHandle); | ||
41206 | nWr = write(fd, pBuf, (size_t)nWrite); | ||
41207 | if( nWr < 1 ){ | ||
41208 | return -1; | ||
41209 | } | ||
41210 | return (jx9_int64)nWr; | ||
41211 | } | ||
41212 | #else | ||
41213 | return -1; | ||
41214 | #endif | ||
41215 | } | ||
41216 | /* void (*xClose)(void *) */ | ||
41217 | static void JX9StreamData_Close(void *pHandle) | ||
41218 | { | ||
41219 | jx9_stream_data *pData = (jx9_stream_data *)pHandle; | ||
41220 | jx9_vm *pVm; | ||
41221 | if( pData == 0 ){ | ||
41222 | return; | ||
41223 | } | ||
41224 | pVm = pData->pVm; | ||
41225 | /* Free the instance */ | ||
41226 | SyMemBackendFree(&pVm->sAllocator, pData); | ||
41227 | } | ||
41228 | /* Export the jx9:// stream */ | ||
41229 | static const jx9_io_stream sjx9Stream = { | ||
41230 | "jx9", | ||
41231 | JX9_IO_STREAM_VERSION, | ||
41232 | JX9StreamData_Open, /* xOpen */ | ||
41233 | 0, /* xOpenDir */ | ||
41234 | JX9StreamData_Close, /* xClose */ | ||
41235 | 0, /* xCloseDir */ | ||
41236 | JX9StreamData_Read, /* xRead */ | ||
41237 | 0, /* xReadDir */ | ||
41238 | JX9StreamData_Write, /* xWrite */ | ||
41239 | 0, /* xSeek */ | ||
41240 | 0, /* xLock */ | ||
41241 | 0, /* xRewindDir */ | ||
41242 | 0, /* xTell */ | ||
41243 | 0, /* xTrunc */ | ||
41244 | 0, /* xSeek */ | ||
41245 | 0 /* xStat */ | ||
41246 | }; | ||
41247 | #endif /* JX9_DISABLE_DISK_IO */ | ||
41248 | /* | ||
41249 | * Return TRUE if we are dealing with the jx9:// stream. | ||
41250 | * FALSE otherwise. | ||
41251 | */ | ||
41252 | static int is_jx9_stream(const jx9_io_stream *pStream) | ||
41253 | { | ||
41254 | #ifndef JX9_DISABLE_DISK_IO | ||
41255 | return pStream == &sjx9Stream; | ||
41256 | #else | ||
41257 | SXUNUSED(pStream); /* cc warning */ | ||
41258 | return 0; | ||
41259 | #endif /* JX9_DISABLE_DISK_IO */ | ||
41260 | } | ||
41261 | |||
41262 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
41263 | /* | ||
41264 | * Export the IO routines defined above and the built-in IO streams | ||
41265 | * [i.e: file://, jx9://]. | ||
41266 | * Note: | ||
41267 | * If the engine is compiled with the JX9_DISABLE_BUILTIN_FUNC directive | ||
41268 | * defined then this function is a no-op. | ||
41269 | */ | ||
41270 | JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm) | ||
41271 | { | ||
41272 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
41273 | /* VFS functions */ | ||
41274 | static const jx9_builtin_func aVfsFunc[] = { | ||
41275 | {"chdir", jx9Vfs_chdir }, | ||
41276 | {"chroot", jx9Vfs_chroot }, | ||
41277 | {"getcwd", jx9Vfs_getcwd }, | ||
41278 | {"rmdir", jx9Vfs_rmdir }, | ||
41279 | {"is_dir", jx9Vfs_is_dir }, | ||
41280 | {"mkdir", jx9Vfs_mkdir }, | ||
41281 | {"rename", jx9Vfs_rename }, | ||
41282 | {"realpath", jx9Vfs_realpath}, | ||
41283 | {"sleep", jx9Vfs_sleep }, | ||
41284 | {"usleep", jx9Vfs_usleep }, | ||
41285 | {"unlink", jx9Vfs_unlink }, | ||
41286 | {"delete", jx9Vfs_unlink }, | ||
41287 | {"chmod", jx9Vfs_chmod }, | ||
41288 | {"chown", jx9Vfs_chown }, | ||
41289 | {"chgrp", jx9Vfs_chgrp }, | ||
41290 | {"disk_free_space", jx9Vfs_disk_free_space }, | ||
41291 | {"disk_total_space", jx9Vfs_disk_total_space}, | ||
41292 | {"file_exists", jx9Vfs_file_exists }, | ||
41293 | {"filesize", jx9Vfs_file_size }, | ||
41294 | {"fileatime", jx9Vfs_file_atime }, | ||
41295 | {"filemtime", jx9Vfs_file_mtime }, | ||
41296 | {"filectime", jx9Vfs_file_ctime }, | ||
41297 | {"is_file", jx9Vfs_is_file }, | ||
41298 | {"is_link", jx9Vfs_is_link }, | ||
41299 | {"is_readable", jx9Vfs_is_readable }, | ||
41300 | {"is_writable", jx9Vfs_is_writable }, | ||
41301 | {"is_executable", jx9Vfs_is_executable}, | ||
41302 | {"filetype", jx9Vfs_filetype }, | ||
41303 | {"stat", jx9Vfs_stat }, | ||
41304 | {"lstat", jx9Vfs_lstat }, | ||
41305 | {"getenv", jx9Vfs_getenv }, | ||
41306 | {"setenv", jx9Vfs_putenv }, | ||
41307 | {"putenv", jx9Vfs_putenv }, | ||
41308 | {"touch", jx9Vfs_touch }, | ||
41309 | {"link", jx9Vfs_link }, | ||
41310 | {"symlink", jx9Vfs_symlink }, | ||
41311 | {"umask", jx9Vfs_umask }, | ||
41312 | {"sys_get_temp_dir", jx9Vfs_sys_get_temp_dir }, | ||
41313 | {"get_current_user", jx9Vfs_get_current_user }, | ||
41314 | {"getpid", jx9Vfs_getmypid }, | ||
41315 | {"getuid", jx9Vfs_getmyuid }, | ||
41316 | {"getgid", jx9Vfs_getmygid }, | ||
41317 | {"uname", jx9Vfs_uname}, | ||
41318 | /* Path processing */ | ||
41319 | {"dirname", jx9Builtin_dirname }, | ||
41320 | {"basename", jx9Builtin_basename }, | ||
41321 | {"pathinfo", jx9Builtin_pathinfo }, | ||
41322 | {"strglob", jx9Builtin_strglob }, | ||
41323 | {"fnmatch", jx9Builtin_fnmatch }, | ||
41324 | /* ZIP processing */ | ||
41325 | {"zip_open", jx9Builtin_zip_open }, | ||
41326 | {"zip_close", jx9Builtin_zip_close}, | ||
41327 | {"zip_read", jx9Builtin_zip_read }, | ||
41328 | {"zip_entry_open", jx9Builtin_zip_entry_open }, | ||
41329 | {"zip_entry_close", jx9Builtin_zip_entry_close}, | ||
41330 | {"zip_entry_name", jx9Builtin_zip_entry_name }, | ||
41331 | {"zip_entry_filesize", jx9Builtin_zip_entry_filesize }, | ||
41332 | {"zip_entry_compressedsize", jx9Builtin_zip_entry_compressedsize }, | ||
41333 | {"zip_entry_read", jx9Builtin_zip_entry_read }, | ||
41334 | {"zip_entry_reset_cursor", jx9Builtin_zip_entry_reset_cursor}, | ||
41335 | {"zip_entry_compressionmethod", jx9Builtin_zip_entry_compressionmethod} | ||
41336 | }; | ||
41337 | /* IO stream functions */ | ||
41338 | static const jx9_builtin_func aIOFunc[] = { | ||
41339 | {"ftruncate", jx9Builtin_ftruncate }, | ||
41340 | {"fseek", jx9Builtin_fseek }, | ||
41341 | {"ftell", jx9Builtin_ftell }, | ||
41342 | {"rewind", jx9Builtin_rewind }, | ||
41343 | {"fflush", jx9Builtin_fflush }, | ||
41344 | {"feof", jx9Builtin_feof }, | ||
41345 | {"fgetc", jx9Builtin_fgetc }, | ||
41346 | {"fgets", jx9Builtin_fgets }, | ||
41347 | {"fread", jx9Builtin_fread }, | ||
41348 | {"fgetcsv", jx9Builtin_fgetcsv}, | ||
41349 | {"fgetss", jx9Builtin_fgetss }, | ||
41350 | {"readdir", jx9Builtin_readdir}, | ||
41351 | {"rewinddir", jx9Builtin_rewinddir }, | ||
41352 | {"closedir", jx9Builtin_closedir}, | ||
41353 | {"opendir", jx9Builtin_opendir }, | ||
41354 | {"readfile", jx9Builtin_readfile}, | ||
41355 | {"file_get_contents", jx9Builtin_file_get_contents}, | ||
41356 | {"file_put_contents", jx9Builtin_file_put_contents}, | ||
41357 | {"file", jx9Builtin_file }, | ||
41358 | {"copy", jx9Builtin_copy }, | ||
41359 | {"fstat", jx9Builtin_fstat }, | ||
41360 | {"fwrite", jx9Builtin_fwrite }, | ||
41361 | {"fputs", jx9Builtin_fwrite }, | ||
41362 | {"flock", jx9Builtin_flock }, | ||
41363 | {"fclose", jx9Builtin_fclose }, | ||
41364 | {"fopen", jx9Builtin_fopen }, | ||
41365 | {"fpassthru", jx9Builtin_fpassthru }, | ||
41366 | {"fputcsv", jx9Builtin_fputcsv }, | ||
41367 | {"fprintf", jx9Builtin_fprintf }, | ||
41368 | #if !defined(JX9_DISABLE_HASH_FUNC) | ||
41369 | {"md5_file", jx9Builtin_md5_file}, | ||
41370 | {"sha1_file", jx9Builtin_sha1_file}, | ||
41371 | #endif /* JX9_DISABLE_HASH_FUNC */ | ||
41372 | {"parse_ini_file", jx9Builtin_parse_ini_file}, | ||
41373 | {"vfprintf", jx9Builtin_vfprintf} | ||
41374 | }; | ||
41375 | const jx9_io_stream *pFileStream = 0; | ||
41376 | sxu32 n = 0; | ||
41377 | /* Register the functions defined above */ | ||
41378 | for( n = 0 ; n < SX_ARRAYSIZE(aVfsFunc) ; ++n ){ | ||
41379 | jx9_create_function(&(*pVm), aVfsFunc[n].zName, aVfsFunc[n].xFunc, (void *)pVm->pEngine->pVfs); | ||
41380 | } | ||
41381 | for( n = 0 ; n < SX_ARRAYSIZE(aIOFunc) ; ++n ){ | ||
41382 | jx9_create_function(&(*pVm), aIOFunc[n].zName, aIOFunc[n].xFunc, pVm); | ||
41383 | } | ||
41384 | #ifndef JX9_DISABLE_DISK_IO | ||
41385 | /* Register the file stream if available */ | ||
41386 | #ifdef __WINNT__ | ||
41387 | pFileStream = &sWinFileStream; | ||
41388 | #elif defined(__UNIXES__) | ||
41389 | pFileStream = &sUnixFileStream; | ||
41390 | #endif | ||
41391 | /* Install the jx9:// stream */ | ||
41392 | jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, &sjx9Stream); | ||
41393 | #endif /* JX9_DISABLE_DISK_IO */ | ||
41394 | if( pFileStream ){ | ||
41395 | /* Install the file:// stream */ | ||
41396 | jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, pFileStream); | ||
41397 | } | ||
41398 | #else | ||
41399 | SXUNUSED(pVm); /* cc warning */ | ||
41400 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
41401 | return SXRET_OK; | ||
41402 | } | ||
41403 | /* | ||
41404 | * Export the STDIN handle. | ||
41405 | */ | ||
41406 | JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm) | ||
41407 | { | ||
41408 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
41409 | #ifndef JX9_DISABLE_DISK_IO | ||
41410 | if( pVm->pStdin == 0 ){ | ||
41411 | io_private *pIn; | ||
41412 | /* Allocate an IO private instance */ | ||
41413 | pIn = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); | ||
41414 | if( pIn == 0 ){ | ||
41415 | return 0; | ||
41416 | } | ||
41417 | InitIOPrivate(pVm, &sjx9Stream, pIn); | ||
41418 | /* Initialize the handle */ | ||
41419 | pIn->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDIN); | ||
41420 | /* Install the STDIN stream */ | ||
41421 | pVm->pStdin = pIn; | ||
41422 | return pIn; | ||
41423 | }else{ | ||
41424 | /* NULL or STDIN */ | ||
41425 | return pVm->pStdin; | ||
41426 | } | ||
41427 | #else | ||
41428 | return 0; | ||
41429 | #endif | ||
41430 | #else | ||
41431 | SXUNUSED(pVm); /* cc warning */ | ||
41432 | return 0; | ||
41433 | #endif | ||
41434 | } | ||
41435 | /* | ||
41436 | * Export the STDOUT handle. | ||
41437 | */ | ||
41438 | JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm) | ||
41439 | { | ||
41440 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
41441 | #ifndef JX9_DISABLE_DISK_IO | ||
41442 | if( pVm->pStdout == 0 ){ | ||
41443 | io_private *pOut; | ||
41444 | /* Allocate an IO private instance */ | ||
41445 | pOut = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); | ||
41446 | if( pOut == 0 ){ | ||
41447 | return 0; | ||
41448 | } | ||
41449 | InitIOPrivate(pVm, &sjx9Stream, pOut); | ||
41450 | /* Initialize the handle */ | ||
41451 | pOut->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDOUT); | ||
41452 | /* Install the STDOUT stream */ | ||
41453 | pVm->pStdout = pOut; | ||
41454 | return pOut; | ||
41455 | }else{ | ||
41456 | /* NULL or STDOUT */ | ||
41457 | return pVm->pStdout; | ||
41458 | } | ||
41459 | #else | ||
41460 | return 0; | ||
41461 | #endif | ||
41462 | #else | ||
41463 | SXUNUSED(pVm); /* cc warning */ | ||
41464 | return 0; | ||
41465 | #endif | ||
41466 | } | ||
41467 | /* | ||
41468 | * Export the STDERR handle. | ||
41469 | */ | ||
41470 | JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm) | ||
41471 | { | ||
41472 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
41473 | #ifndef JX9_DISABLE_DISK_IO | ||
41474 | if( pVm->pStderr == 0 ){ | ||
41475 | io_private *pErr; | ||
41476 | /* Allocate an IO private instance */ | ||
41477 | pErr = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); | ||
41478 | if( pErr == 0 ){ | ||
41479 | return 0; | ||
41480 | } | ||
41481 | InitIOPrivate(pVm, &sjx9Stream, pErr); | ||
41482 | /* Initialize the handle */ | ||
41483 | pErr->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDERR); | ||
41484 | /* Install the STDERR stream */ | ||
41485 | pVm->pStderr = pErr; | ||
41486 | return pErr; | ||
41487 | }else{ | ||
41488 | /* NULL or STDERR */ | ||
41489 | return pVm->pStderr; | ||
41490 | } | ||
41491 | #else | ||
41492 | return 0; | ||
41493 | #endif | ||
41494 | #else | ||
41495 | SXUNUSED(pVm); /* cc warning */ | ||
41496 | return 0; | ||
41497 | #endif | ||
41498 | } | ||
41499 | |||
41500 | /* | ||
41501 | * ---------------------------------------------------------- | ||
41502 | * File: jx9_vm.c | ||
41503 | * MD5: beca4be65a9a49c932c356d7680034c9 | ||
41504 | * ---------------------------------------------------------- | ||
41505 | */ | ||
41506 | /* | ||
41507 | * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. | ||
41508 | * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ | ||
41509 | * Version 1.7.2 | ||
41510 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
41511 | * please contact Symisc Systems via: | ||
41512 | * legal@symisc.net | ||
41513 | * licensing@symisc.net | ||
41514 | * contact@symisc.net | ||
41515 | * or visit: | ||
41516 | * http://jx9.symisc.net/ | ||
41517 | */ | ||
41518 | /* $SymiscID: jx9_vm.c v1.0 FreeBSD 2012-12-09 00:19 stable <chm@symisc.net> $ */ | ||
41519 | #ifndef JX9_AMALGAMATION | ||
41520 | #include "jx9Int.h" | ||
41521 | #endif | ||
41522 | /* | ||
41523 | * The code in this file implements execution method of the JX9 Virtual Machine. | ||
41524 | * The JX9 compiler (implemented in 'compiler.c' and 'parse.c') generates a bytecode program | ||
41525 | * which is then executed by the virtual machine implemented here to do the work of the JX9 | ||
41526 | * statements. | ||
41527 | * JX9 bytecode programs are similar in form to assembly language. The program consists | ||
41528 | * of a linear sequence of operations .Each operation has an opcode and 3 operands. | ||
41529 | * Operands P1 and P2 are integers where the first is signed while the second is unsigned. | ||
41530 | * Operand P3 is an arbitrary pointer specific to each instruction. The P2 operand is usually | ||
41531 | * the jump destination used by the OP_JMP, OP_JZ, OP_JNZ, ... instructions. | ||
41532 | * Opcodes will typically ignore one or more operands. Many opcodes ignore all three operands. | ||
41533 | * Computation results are stored on a stack. Each entry on the stack is of type jx9_value. | ||
41534 | * JX9 uses the jx9_value object to represent all values that can be stored in a JX9 variable. | ||
41535 | * Since JX9 uses dynamic typing for the values it stores. Values stored in jx9_value objects | ||
41536 | * can be integers, floating point values, strings, arrays, object instances (object in the JX9 jargon) | ||
41537 | * and so on. | ||
41538 | * Internally, the JX9 virtual machine manipulates nearly all values as jx9_values structures. | ||
41539 | * Each jx9_value may cache multiple representations(string, integer etc.) of the same value. | ||
41540 | * An implicit conversion from one type to the other occurs as necessary. | ||
41541 | * Most of the code in this file is taken up by the [VmByteCodeExec()] function which does | ||
41542 | * the work of interpreting a JX9 bytecode program. But other routines are also provided | ||
41543 | * to help in building up a program instruction by instruction. | ||
41544 | */ | ||
41545 | /* | ||
41546 | * Each active virtual machine frame is represented by an instance | ||
41547 | * of the following structure. | ||
41548 | * VM Frame hold local variables and other stuff related to function call. | ||
41549 | */ | ||
41550 | struct VmFrame | ||
41551 | { | ||
41552 | VmFrame *pParent; /* Parent frame or NULL if global scope */ | ||
41553 | void *pUserData; /* Upper layer private data associated with this frame */ | ||
41554 | SySet sLocal; /* Local variables container (VmSlot instance) */ | ||
41555 | jx9_vm *pVm; /* VM that own this frame */ | ||
41556 | SyHash hVar; /* Variable hashtable for fast lookup */ | ||
41557 | SySet sArg; /* Function arguments container */ | ||
41558 | sxi32 iFlags; /* Frame configuration flags (See below)*/ | ||
41559 | sxu32 iExceptionJump; /* Exception jump destination */ | ||
41560 | }; | ||
41561 | /* | ||
41562 | * When a user defined variable is garbage collected, memory object index | ||
41563 | * is stored in an instance of the following structure and put in the free object | ||
41564 | * table so that it can be reused again without allocating a new memory object. | ||
41565 | */ | ||
41566 | typedef struct VmSlot VmSlot; | ||
41567 | struct VmSlot | ||
41568 | { | ||
41569 | sxu32 nIdx; /* Index in pVm->aMemObj[] */ | ||
41570 | void *pUserData; /* Upper-layer private data */ | ||
41571 | }; | ||
41572 | /* | ||
41573 | * Each parsed URI is recorded and stored in an instance of the following structure. | ||
41574 | * This structure and it's related routines are taken verbatim from the xHT project | ||
41575 | * [A modern embeddable HTTP engine implementing all the RFC2616 methods] | ||
41576 | * the xHT project is developed internally by Symisc Systems. | ||
41577 | */ | ||
41578 | typedef struct SyhttpUri SyhttpUri; | ||
41579 | struct SyhttpUri | ||
41580 | { | ||
41581 | SyString sHost; /* Hostname or IP address */ | ||
41582 | SyString sPort; /* Port number */ | ||
41583 | SyString sPath; /* Mandatory resource path passed verbatim (Not decoded) */ | ||
41584 | SyString sQuery; /* Query part */ | ||
41585 | SyString sFragment; /* Fragment part */ | ||
41586 | SyString sScheme; /* Scheme */ | ||
41587 | SyString sUser; /* Username */ | ||
41588 | SyString sPass; /* Password */ | ||
41589 | SyString sRaw; /* Raw URI */ | ||
41590 | }; | ||
41591 | /* | ||
41592 | * An instance of the following structure is used to record all MIME headers seen | ||
41593 | * during a HTTP interaction. | ||
41594 | * This structure and it's related routines are taken verbatim from the xHT project | ||
41595 | * [A modern embeddable HTTP engine implementing all the RFC2616 methods] | ||
41596 | * the xHT project is developed internally by Symisc Systems. | ||
41597 | */ | ||
41598 | typedef struct SyhttpHeader SyhttpHeader; | ||
41599 | struct SyhttpHeader | ||
41600 | { | ||
41601 | SyString sName; /* Header name [i.e:"Content-Type", "Host", "User-Agent"]. NOT NUL TERMINATED */ | ||
41602 | SyString sValue; /* Header values [i.e: "text/html"]. NOT NUL TERMINATED */ | ||
41603 | }; | ||
41604 | /* | ||
41605 | * Supported HTTP methods. | ||
41606 | */ | ||
41607 | #define HTTP_METHOD_GET 1 /* GET */ | ||
41608 | #define HTTP_METHOD_HEAD 2 /* HEAD */ | ||
41609 | #define HTTP_METHOD_POST 3 /* POST */ | ||
41610 | #define HTTP_METHOD_PUT 4 /* PUT */ | ||
41611 | #define HTTP_METHOD_OTHR 5 /* Other HTTP methods [i.e: DELETE, TRACE, OPTIONS...]*/ | ||
41612 | /* | ||
41613 | * Supported HTTP protocol version. | ||
41614 | */ | ||
41615 | #define HTTP_PROTO_10 1 /* HTTP/1.0 */ | ||
41616 | #define HTTP_PROTO_11 2 /* HTTP/1.1 */ | ||
41617 | /* | ||
41618 | * Register a constant and it's associated expansion callback so that | ||
41619 | * it can be expanded from the target JX9 program. | ||
41620 | * The constant expansion mechanism under JX9 is extremely powerful yet | ||
41621 | * simple and work as follows: | ||
41622 | * Each registered constant have a C procedure associated with it. | ||
41623 | * This procedure known as the constant expansion callback is responsible | ||
41624 | * of expanding the invoked constant to the desired value, for example: | ||
41625 | * The C procedure associated with the "__PI__" constant expands to 3.14 (the value of PI). | ||
41626 | * The "__OS__" constant procedure expands to the name of the host Operating Systems | ||
41627 | * (Windows, Linux, ...) and so on. | ||
41628 | * Please refer to the official documentation for additional information. | ||
41629 | */ | ||
41630 | JX9_PRIVATE sxi32 jx9VmRegisterConstant( | ||
41631 | jx9_vm *pVm, /* Target VM */ | ||
41632 | const SyString *pName, /* Constant name */ | ||
41633 | ProcConstant xExpand, /* Constant expansion callback */ | ||
41634 | void *pUserData /* Last argument to xExpand() */ | ||
41635 | ) | ||
41636 | { | ||
41637 | jx9_constant *pCons; | ||
41638 | SyHashEntry *pEntry; | ||
41639 | char *zDupName; | ||
41640 | sxi32 rc; | ||
41641 | pEntry = SyHashGet(&pVm->hConstant, (const void *)pName->zString, pName->nByte); | ||
41642 | if( pEntry ){ | ||
41643 | /* Overwrite the old definition and return immediately */ | ||
41644 | pCons = (jx9_constant *)pEntry->pUserData; | ||
41645 | pCons->xExpand = xExpand; | ||
41646 | pCons->pUserData = pUserData; | ||
41647 | return SXRET_OK; | ||
41648 | } | ||
41649 | /* Allocate a new constant instance */ | ||
41650 | pCons = (jx9_constant *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_constant)); | ||
41651 | if( pCons == 0 ){ | ||
41652 | return 0; | ||
41653 | } | ||
41654 | /* Duplicate constant name */ | ||
41655 | zDupName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); | ||
41656 | if( zDupName == 0 ){ | ||
41657 | SyMemBackendPoolFree(&pVm->sAllocator, pCons); | ||
41658 | return 0; | ||
41659 | } | ||
41660 | /* Install the constant */ | ||
41661 | SyStringInitFromBuf(&pCons->sName, zDupName, pName->nByte); | ||
41662 | pCons->xExpand = xExpand; | ||
41663 | pCons->pUserData = pUserData; | ||
41664 | rc = SyHashInsert(&pVm->hConstant, (const void *)zDupName, SyStringLength(&pCons->sName), pCons); | ||
41665 | if( rc != SXRET_OK ){ | ||
41666 | SyMemBackendFree(&pVm->sAllocator, zDupName); | ||
41667 | SyMemBackendPoolFree(&pVm->sAllocator, pCons); | ||
41668 | return rc; | ||
41669 | } | ||
41670 | /* All done, constant can be invoked from JX9 code */ | ||
41671 | return SXRET_OK; | ||
41672 | } | ||
41673 | /* | ||
41674 | * Allocate a new foreign function instance. | ||
41675 | * This function return SXRET_OK on success. Any other | ||
41676 | * return value indicates failure. | ||
41677 | * Please refer to the official documentation for an introduction to | ||
41678 | * the foreign function mechanism. | ||
41679 | */ | ||
41680 | static sxi32 jx9NewForeignFunction( | ||
41681 | jx9_vm *pVm, /* Target VM */ | ||
41682 | const SyString *pName, /* Foreign function name */ | ||
41683 | ProcHostFunction xFunc, /* Foreign function implementation */ | ||
41684 | void *pUserData, /* Foreign function private data */ | ||
41685 | jx9_user_func **ppOut /* OUT: VM image of the foreign function */ | ||
41686 | ) | ||
41687 | { | ||
41688 | jx9_user_func *pFunc; | ||
41689 | char *zDup; | ||
41690 | /* Allocate a new user function */ | ||
41691 | pFunc = (jx9_user_func *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_user_func)); | ||
41692 | if( pFunc == 0 ){ | ||
41693 | return SXERR_MEM; | ||
41694 | } | ||
41695 | /* Duplicate function name */ | ||
41696 | zDup = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); | ||
41697 | if( zDup == 0 ){ | ||
41698 | SyMemBackendPoolFree(&pVm->sAllocator, pFunc); | ||
41699 | return SXERR_MEM; | ||
41700 | } | ||
41701 | /* Zero the structure */ | ||
41702 | SyZero(pFunc, sizeof(jx9_user_func)); | ||
41703 | /* Initialize structure fields */ | ||
41704 | SyStringInitFromBuf(&pFunc->sName, zDup, pName->nByte); | ||
41705 | pFunc->pVm = pVm; | ||
41706 | pFunc->xFunc = xFunc; | ||
41707 | pFunc->pUserData = pUserData; | ||
41708 | SySetInit(&pFunc->aAux, &pVm->sAllocator, sizeof(jx9_aux_data)); | ||
41709 | /* Write a pointer to the new function */ | ||
41710 | *ppOut = pFunc; | ||
41711 | return SXRET_OK; | ||
41712 | } | ||
41713 | /* | ||
41714 | * Install a foreign function and it's associated callback so that | ||
41715 | * it can be invoked from the target JX9 code. | ||
41716 | * This function return SXRET_OK on successful registration. Any other | ||
41717 | * return value indicates failure. | ||
41718 | * Please refer to the official documentation for an introduction to | ||
41719 | * the foreign function mechanism. | ||
41720 | */ | ||
41721 | JX9_PRIVATE sxi32 jx9VmInstallForeignFunction( | ||
41722 | jx9_vm *pVm, /* Target VM */ | ||
41723 | const SyString *pName, /* Foreign function name */ | ||
41724 | ProcHostFunction xFunc, /* Foreign function implementation */ | ||
41725 | void *pUserData /* Foreign function private data */ | ||
41726 | ) | ||
41727 | { | ||
41728 | jx9_user_func *pFunc; | ||
41729 | SyHashEntry *pEntry; | ||
41730 | sxi32 rc; | ||
41731 | /* Overwrite any previously registered function with the same name */ | ||
41732 | pEntry = SyHashGet(&pVm->hHostFunction, pName->zString, pName->nByte); | ||
41733 | if( pEntry ){ | ||
41734 | pFunc = (jx9_user_func *)pEntry->pUserData; | ||
41735 | pFunc->pUserData = pUserData; | ||
41736 | pFunc->xFunc = xFunc; | ||
41737 | SySetReset(&pFunc->aAux); | ||
41738 | return SXRET_OK; | ||
41739 | } | ||
41740 | /* Create a new user function */ | ||
41741 | rc = jx9NewForeignFunction(&(*pVm), &(*pName), xFunc, pUserData, &pFunc); | ||
41742 | if( rc != SXRET_OK ){ | ||
41743 | return rc; | ||
41744 | } | ||
41745 | /* Install the function in the corresponding hashtable */ | ||
41746 | rc = SyHashInsert(&pVm->hHostFunction, SyStringData(&pFunc->sName), pName->nByte, pFunc); | ||
41747 | if( rc != SXRET_OK ){ | ||
41748 | SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName)); | ||
41749 | SyMemBackendPoolFree(&pVm->sAllocator, pFunc); | ||
41750 | return rc; | ||
41751 | } | ||
41752 | /* User function successfully installed */ | ||
41753 | return SXRET_OK; | ||
41754 | } | ||
41755 | /* | ||
41756 | * Initialize a VM function. | ||
41757 | */ | ||
41758 | JX9_PRIVATE sxi32 jx9VmInitFuncState( | ||
41759 | jx9_vm *pVm, /* Target VM */ | ||
41760 | jx9_vm_func *pFunc, /* Target Fucntion */ | ||
41761 | const char *zName, /* Function name */ | ||
41762 | sxu32 nByte, /* zName length */ | ||
41763 | sxi32 iFlags, /* Configuration flags */ | ||
41764 | void *pUserData /* Function private data */ | ||
41765 | ) | ||
41766 | { | ||
41767 | /* Zero the structure */ | ||
41768 | SyZero(pFunc, sizeof(jx9_vm_func)); | ||
41769 | /* Initialize structure fields */ | ||
41770 | /* Arguments container */ | ||
41771 | SySetInit(&pFunc->aArgs, &pVm->sAllocator, sizeof(jx9_vm_func_arg)); | ||
41772 | /* Static variable container */ | ||
41773 | SySetInit(&pFunc->aStatic, &pVm->sAllocator, sizeof(jx9_vm_func_static_var)); | ||
41774 | /* Bytecode container */ | ||
41775 | SySetInit(&pFunc->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); | ||
41776 | /* Preallocate some instruction slots */ | ||
41777 | SySetAlloc(&pFunc->aByteCode, 0x10); | ||
41778 | pFunc->iFlags = iFlags; | ||
41779 | pFunc->pUserData = pUserData; | ||
41780 | SyStringInitFromBuf(&pFunc->sName, zName, nByte); | ||
41781 | return SXRET_OK; | ||
41782 | } | ||
41783 | /* | ||
41784 | * Install a user defined function in the corresponding VM container. | ||
41785 | */ | ||
41786 | JX9_PRIVATE sxi32 jx9VmInstallUserFunction( | ||
41787 | jx9_vm *pVm, /* Target VM */ | ||
41788 | jx9_vm_func *pFunc, /* Target function */ | ||
41789 | SyString *pName /* Function name */ | ||
41790 | ) | ||
41791 | { | ||
41792 | SyHashEntry *pEntry; | ||
41793 | sxi32 rc; | ||
41794 | if( pName == 0 ){ | ||
41795 | /* Use the built-in name */ | ||
41796 | pName = &pFunc->sName; | ||
41797 | } | ||
41798 | /* Check for duplicates (functions with the same name) first */ | ||
41799 | pEntry = SyHashGet(&pVm->hFunction, pName->zString, pName->nByte); | ||
41800 | if( pEntry ){ | ||
41801 | jx9_vm_func *pLink = (jx9_vm_func *)pEntry->pUserData; | ||
41802 | if( pLink != pFunc ){ | ||
41803 | /* Link */ | ||
41804 | pFunc->pNextName = pLink; | ||
41805 | pEntry->pUserData = pFunc; | ||
41806 | } | ||
41807 | return SXRET_OK; | ||
41808 | } | ||
41809 | /* First time seen */ | ||
41810 | pFunc->pNextName = 0; | ||
41811 | rc = SyHashInsert(&pVm->hFunction, pName->zString, pName->nByte, pFunc); | ||
41812 | return rc; | ||
41813 | } | ||
41814 | /* | ||
41815 | * Instruction builder interface. | ||
41816 | */ | ||
41817 | JX9_PRIVATE sxi32 jx9VmEmitInstr( | ||
41818 | jx9_vm *pVm, /* Target VM */ | ||
41819 | sxi32 iOp, /* Operation to perform */ | ||
41820 | sxi32 iP1, /* First operand */ | ||
41821 | sxu32 iP2, /* Second operand */ | ||
41822 | void *p3, /* Third operand */ | ||
41823 | sxu32 *pIndex /* Instruction index. NULL otherwise */ | ||
41824 | ) | ||
41825 | { | ||
41826 | VmInstr sInstr; | ||
41827 | sxi32 rc; | ||
41828 | /* Fill the VM instruction */ | ||
41829 | sInstr.iOp = (sxu8)iOp; | ||
41830 | sInstr.iP1 = iP1; | ||
41831 | sInstr.iP2 = iP2; | ||
41832 | sInstr.p3 = p3; | ||
41833 | if( pIndex ){ | ||
41834 | /* Instruction index in the bytecode array */ | ||
41835 | *pIndex = SySetUsed(pVm->pByteContainer); | ||
41836 | } | ||
41837 | /* Finally, record the instruction */ | ||
41838 | rc = SySetPut(pVm->pByteContainer, (const void *)&sInstr); | ||
41839 | if( rc != SXRET_OK ){ | ||
41840 | jx9GenCompileError(&pVm->sCodeGen, E_ERROR, 1, "Fatal, Cannot emit instruction due to a memory failure"); | ||
41841 | /* Fall throw */ | ||
41842 | } | ||
41843 | return rc; | ||
41844 | } | ||
41845 | /* | ||
41846 | * Swap the current bytecode container with the given one. | ||
41847 | */ | ||
41848 | JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer) | ||
41849 | { | ||
41850 | if( pContainer == 0 ){ | ||
41851 | /* Point to the default container */ | ||
41852 | pVm->pByteContainer = &pVm->aByteCode; | ||
41853 | }else{ | ||
41854 | /* Change container */ | ||
41855 | pVm->pByteContainer = &(*pContainer); | ||
41856 | } | ||
41857 | return SXRET_OK; | ||
41858 | } | ||
41859 | /* | ||
41860 | * Return the current bytecode container. | ||
41861 | */ | ||
41862 | JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm) | ||
41863 | { | ||
41864 | return pVm->pByteContainer; | ||
41865 | } | ||
41866 | /* | ||
41867 | * Extract the VM instruction rooted at nIndex. | ||
41868 | */ | ||
41869 | JX9_PRIVATE VmInstr * jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex) | ||
41870 | { | ||
41871 | VmInstr *pInstr; | ||
41872 | pInstr = (VmInstr *)SySetAt(pVm->pByteContainer, nIndex); | ||
41873 | return pInstr; | ||
41874 | } | ||
41875 | /* | ||
41876 | * Return the total number of VM instructions recorded so far. | ||
41877 | */ | ||
41878 | JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm) | ||
41879 | { | ||
41880 | return SySetUsed(pVm->pByteContainer); | ||
41881 | } | ||
41882 | /* | ||
41883 | * Pop the last VM instruction. | ||
41884 | */ | ||
41885 | JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm) | ||
41886 | { | ||
41887 | return (VmInstr *)SySetPop(pVm->pByteContainer); | ||
41888 | } | ||
41889 | /* | ||
41890 | * Peek the last VM instruction. | ||
41891 | */ | ||
41892 | JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm) | ||
41893 | { | ||
41894 | return (VmInstr *)SySetPeek(pVm->pByteContainer); | ||
41895 | } | ||
41896 | /* | ||
41897 | * Allocate a new virtual machine frame. | ||
41898 | */ | ||
41899 | static VmFrame * VmNewFrame( | ||
41900 | jx9_vm *pVm, /* Target VM */ | ||
41901 | void *pUserData /* Upper-layer private data */ | ||
41902 | ) | ||
41903 | { | ||
41904 | VmFrame *pFrame; | ||
41905 | /* Allocate a new vm frame */ | ||
41906 | pFrame = (VmFrame *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmFrame)); | ||
41907 | if( pFrame == 0 ){ | ||
41908 | return 0; | ||
41909 | } | ||
41910 | /* Zero the structure */ | ||
41911 | SyZero(pFrame, sizeof(VmFrame)); | ||
41912 | /* Initialize frame fields */ | ||
41913 | pFrame->pUserData = pUserData; | ||
41914 | pFrame->pVm = pVm; | ||
41915 | SyHashInit(&pFrame->hVar, &pVm->sAllocator, 0, 0); | ||
41916 | SySetInit(&pFrame->sArg, &pVm->sAllocator, sizeof(VmSlot)); | ||
41917 | SySetInit(&pFrame->sLocal, &pVm->sAllocator, sizeof(VmSlot)); | ||
41918 | return pFrame; | ||
41919 | } | ||
41920 | /* | ||
41921 | * Enter a VM frame. | ||
41922 | */ | ||
41923 | static sxi32 VmEnterFrame( | ||
41924 | jx9_vm *pVm, /* Target VM */ | ||
41925 | void *pUserData, /* Upper-layer private data */ | ||
41926 | VmFrame **ppFrame /* OUT: Top most active frame */ | ||
41927 | ) | ||
41928 | { | ||
41929 | VmFrame *pFrame; | ||
41930 | /* Allocate a new frame */ | ||
41931 | pFrame = VmNewFrame(&(*pVm), pUserData); | ||
41932 | if( pFrame == 0 ){ | ||
41933 | return SXERR_MEM; | ||
41934 | } | ||
41935 | /* Link to the list of active VM frame */ | ||
41936 | pFrame->pParent = pVm->pFrame; | ||
41937 | pVm->pFrame = pFrame; | ||
41938 | if( ppFrame ){ | ||
41939 | /* Write a pointer to the new VM frame */ | ||
41940 | *ppFrame = pFrame; | ||
41941 | } | ||
41942 | return SXRET_OK; | ||
41943 | } | ||
41944 | /* | ||
41945 | * Link a foreign variable with the TOP most active frame. | ||
41946 | * Refer to the JX9_OP_UPLINK instruction implementation for more | ||
41947 | * information. | ||
41948 | */ | ||
41949 | static sxi32 VmFrameLink(jx9_vm *pVm,SyString *pName) | ||
41950 | { | ||
41951 | VmFrame *pTarget, *pFrame; | ||
41952 | SyHashEntry *pEntry = 0; | ||
41953 | sxi32 rc; | ||
41954 | /* Point to the upper frame */ | ||
41955 | pFrame = pVm->pFrame; | ||
41956 | pTarget = pFrame; | ||
41957 | pFrame = pTarget->pParent; | ||
41958 | while( pFrame ){ | ||
41959 | /* Query the current frame */ | ||
41960 | pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); | ||
41961 | if( pEntry ){ | ||
41962 | /* Variable found */ | ||
41963 | break; | ||
41964 | } | ||
41965 | /* Point to the upper frame */ | ||
41966 | pFrame = pFrame->pParent; | ||
41967 | } | ||
41968 | if( pEntry == 0 ){ | ||
41969 | /* Inexistant variable */ | ||
41970 | return SXERR_NOTFOUND; | ||
41971 | } | ||
41972 | /* Link to the current frame */ | ||
41973 | rc = SyHashInsert(&pTarget->hVar, pEntry->pKey, pEntry->nKeyLen, pEntry->pUserData); | ||
41974 | return rc; | ||
41975 | } | ||
41976 | /* | ||
41977 | * Leave the top-most active frame. | ||
41978 | */ | ||
41979 | static void VmLeaveFrame(jx9_vm *pVm) | ||
41980 | { | ||
41981 | VmFrame *pFrame = pVm->pFrame; | ||
41982 | if( pFrame ){ | ||
41983 | /* Unlink from the list of active VM frame */ | ||
41984 | pVm->pFrame = pFrame->pParent; | ||
41985 | if( pFrame->pParent ){ | ||
41986 | VmSlot *aSlot; | ||
41987 | sxu32 n; | ||
41988 | /* Restore local variable to the free pool so that they can be reused again */ | ||
41989 | aSlot = (VmSlot *)SySetBasePtr(&pFrame->sLocal); | ||
41990 | for(n = 0 ; n < SySetUsed(&pFrame->sLocal) ; ++n ){ | ||
41991 | /* Unset the local variable */ | ||
41992 | jx9VmUnsetMemObj(&(*pVm), aSlot[n].nIdx); | ||
41993 | } | ||
41994 | } | ||
41995 | /* Release internal containers */ | ||
41996 | SyHashRelease(&pFrame->hVar); | ||
41997 | SySetRelease(&pFrame->sArg); | ||
41998 | SySetRelease(&pFrame->sLocal); | ||
41999 | /* Release the whole structure */ | ||
42000 | SyMemBackendPoolFree(&pVm->sAllocator, pFrame); | ||
42001 | } | ||
42002 | } | ||
42003 | /* | ||
42004 | * Compare two functions signature and return the comparison result. | ||
42005 | */ | ||
42006 | static int VmOverloadCompare(SyString *pFirst, SyString *pSecond) | ||
42007 | { | ||
42008 | const char *zSend = &pSecond->zString[pSecond->nByte]; | ||
42009 | const char *zFend = &pFirst->zString[pFirst->nByte]; | ||
42010 | const char *zSin = pSecond->zString; | ||
42011 | const char *zFin = pFirst->zString; | ||
42012 | const char *zPtr = zFin; | ||
42013 | for(;;){ | ||
42014 | if( zFin >= zFend || zSin >= zSend ){ | ||
42015 | break; | ||
42016 | } | ||
42017 | if( zFin[0] != zSin[0] ){ | ||
42018 | /* mismatch */ | ||
42019 | break; | ||
42020 | } | ||
42021 | zFin++; | ||
42022 | zSin++; | ||
42023 | } | ||
42024 | return (int)(zFin-zPtr); | ||
42025 | } | ||
42026 | /* | ||
42027 | * Select the appropriate VM function for the current call context. | ||
42028 | * This is the implementation of the powerful 'function overloading' feature | ||
42029 | * introduced by the version 2 of the JX9 engine. | ||
42030 | * Refer to the official documentation for more information. | ||
42031 | */ | ||
42032 | static jx9_vm_func * VmOverload( | ||
42033 | jx9_vm *pVm, /* Target VM */ | ||
42034 | jx9_vm_func *pList, /* Linked list of candidates for overloading */ | ||
42035 | jx9_value *aArg, /* Array of passed arguments */ | ||
42036 | int nArg /* Total number of passed arguments */ | ||
42037 | ) | ||
42038 | { | ||
42039 | int iTarget, i, j, iCur, iMax; | ||
42040 | jx9_vm_func *apSet[10]; /* Maximum number of candidates */ | ||
42041 | jx9_vm_func *pLink; | ||
42042 | SyString sArgSig; | ||
42043 | SyBlob sSig; | ||
42044 | |||
42045 | pLink = pList; | ||
42046 | i = 0; | ||
42047 | /* Put functions expecting the same number of passed arguments */ | ||
42048 | while( i < (int)SX_ARRAYSIZE(apSet) ){ | ||
42049 | if( pLink == 0 ){ | ||
42050 | break; | ||
42051 | } | ||
42052 | if( (int)SySetUsed(&pLink->aArgs) == nArg ){ | ||
42053 | /* Candidate for overloading */ | ||
42054 | apSet[i++] = pLink; | ||
42055 | } | ||
42056 | /* Point to the next entry */ | ||
42057 | pLink = pLink->pNextName; | ||
42058 | } | ||
42059 | if( i < 1 ){ | ||
42060 | /* No candidates, return the head of the list */ | ||
42061 | return pList; | ||
42062 | } | ||
42063 | if( nArg < 1 || i < 2 ){ | ||
42064 | /* Return the only candidate */ | ||
42065 | return apSet[0]; | ||
42066 | } | ||
42067 | /* Calculate function signature */ | ||
42068 | SyBlobInit(&sSig, &pVm->sAllocator); | ||
42069 | for( j = 0 ; j < nArg ; j++ ){ | ||
42070 | int c = 'n'; /* null */ | ||
42071 | if( aArg[j].iFlags & MEMOBJ_HASHMAP ){ | ||
42072 | /* Hashmap */ | ||
42073 | c = 'h'; | ||
42074 | }else if( aArg[j].iFlags & MEMOBJ_BOOL ){ | ||
42075 | /* bool */ | ||
42076 | c = 'b'; | ||
42077 | }else if( aArg[j].iFlags & MEMOBJ_INT ){ | ||
42078 | /* int */ | ||
42079 | c = 'i'; | ||
42080 | }else if( aArg[j].iFlags & MEMOBJ_STRING ){ | ||
42081 | /* String */ | ||
42082 | c = 's'; | ||
42083 | }else if( aArg[j].iFlags & MEMOBJ_REAL ){ | ||
42084 | /* Float */ | ||
42085 | c = 'f'; | ||
42086 | } | ||
42087 | if( c > 0 ){ | ||
42088 | SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); | ||
42089 | } | ||
42090 | } | ||
42091 | SyStringInitFromBuf(&sArgSig, SyBlobData(&sSig), SyBlobLength(&sSig)); | ||
42092 | iTarget = 0; | ||
42093 | iMax = -1; | ||
42094 | /* Select the appropriate function */ | ||
42095 | for( j = 0 ; j < i ; j++ ){ | ||
42096 | /* Compare the two signatures */ | ||
42097 | iCur = VmOverloadCompare(&sArgSig, &apSet[j]->sSignature); | ||
42098 | if( iCur > iMax ){ | ||
42099 | iMax = iCur; | ||
42100 | iTarget = j; | ||
42101 | } | ||
42102 | } | ||
42103 | SyBlobRelease(&sSig); | ||
42104 | /* Appropriate function for the current call context */ | ||
42105 | return apSet[iTarget]; | ||
42106 | } | ||
42107 | /* | ||
42108 | * Dummy read-only buffer used for slot reservation. | ||
42109 | */ | ||
42110 | static const char zDummy[sizeof(jx9_value)] = { 0 }; /* Must be >= sizeof(jx9_value) */ | ||
42111 | /* | ||
42112 | * Reserve a constant memory object. | ||
42113 | * Return a pointer to the raw jx9_value on success. NULL on failure. | ||
42114 | */ | ||
42115 | JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex) | ||
42116 | { | ||
42117 | jx9_value *pObj; | ||
42118 | sxi32 rc; | ||
42119 | if( pIndex ){ | ||
42120 | /* Object index in the object table */ | ||
42121 | *pIndex = SySetUsed(&pVm->aLitObj); | ||
42122 | } | ||
42123 | /* Reserve a slot for the new object */ | ||
42124 | rc = SySetPut(&pVm->aLitObj, (const void *)zDummy); | ||
42125 | if( rc != SXRET_OK ){ | ||
42126 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
42127 | * a tiny chunk of memory, there is no much we can do here. | ||
42128 | */ | ||
42129 | return 0; | ||
42130 | } | ||
42131 | pObj = (jx9_value *)SySetPeek(&pVm->aLitObj); | ||
42132 | return pObj; | ||
42133 | } | ||
42134 | /* | ||
42135 | * Reserve a memory object. | ||
42136 | * Return a pointer to the raw jx9_value on success. NULL on failure. | ||
42137 | */ | ||
42138 | static jx9_value * VmReserveMemObj(jx9_vm *pVm, sxu32 *pIndex) | ||
42139 | { | ||
42140 | jx9_value *pObj; | ||
42141 | sxi32 rc; | ||
42142 | if( pIndex ){ | ||
42143 | /* Object index in the object table */ | ||
42144 | *pIndex = SySetUsed(&pVm->aMemObj); | ||
42145 | } | ||
42146 | /* Reserve a slot for the new object */ | ||
42147 | rc = SySetPut(&pVm->aMemObj, (const void *)zDummy); | ||
42148 | if( rc != SXRET_OK ){ | ||
42149 | /* If the supplied memory subsystem is so sick that we are unable to allocate | ||
42150 | * a tiny chunk of memory, there is no much we can do here. | ||
42151 | */ | ||
42152 | return 0; | ||
42153 | } | ||
42154 | pObj = (jx9_value *)SySetPeek(&pVm->aMemObj); | ||
42155 | return pObj; | ||
42156 | } | ||
42157 | /* Forward declaration */ | ||
42158 | static sxi32 VmEvalChunk(jx9_vm *pVm, jx9_context *pCtx, SyString *pChunk, int iFlags, int bTrueReturn); | ||
42159 | /* | ||
42160 | * Built-in functions that cannot be implemented directly as foreign functions. | ||
42161 | */ | ||
42162 | #define JX9_BUILTIN_LIB \ | ||
42163 | "function scandir(string $directory, int $sort_order = SCANDIR_SORT_ASCENDING)"\ | ||
42164 | "{"\ | ||
42165 | " if( func_num_args() < 1 ){ return FALSE; }"\ | ||
42166 | " $aDir = [];"\ | ||
42167 | " $pHandle = opendir($directory);"\ | ||
42168 | " if( $pHandle == FALSE ){ return FALSE; }"\ | ||
42169 | " while(FALSE !== ($pEntry = readdir($pHandle)) ){"\ | ||
42170 | " $aDir[] = $pEntry;"\ | ||
42171 | " }"\ | ||
42172 | " closedir($pHandle);"\ | ||
42173 | " if( $sort_order == SCANDIR_SORT_DESCENDING ){"\ | ||
42174 | " rsort($aDir);"\ | ||
42175 | " }else if( $sort_order == SCANDIR_SORT_ASCENDING ){"\ | ||
42176 | " sort($aDir);"\ | ||
42177 | " }"\ | ||
42178 | " return $aDir;"\ | ||
42179 | "}"\ | ||
42180 | "function glob(string $pattern, int $iFlags = 0){"\ | ||
42181 | "/* Open the target directory */"\ | ||
42182 | "$zDir = dirname($pattern);"\ | ||
42183 | "if(!is_string($zDir) ){ $zDir = './'; }"\ | ||
42184 | "$pHandle = opendir($zDir);"\ | ||
42185 | "if( $pHandle == FALSE ){"\ | ||
42186 | " /* IO error while opening the current directory, return FALSE */"\ | ||
42187 | " return FALSE;"\ | ||
42188 | "}"\ | ||
42189 | "$pattern = basename($pattern);"\ | ||
42190 | "$pArray = []; /* Empty array */"\ | ||
42191 | "/* Loop throw available entries */"\ | ||
42192 | "while( FALSE !== ($pEntry = readdir($pHandle)) ){"\ | ||
42193 | " /* Use the built-in strglob function which is a Symisc eXtension for wildcard comparison*/"\ | ||
42194 | " $rc = strglob($pattern, $pEntry);"\ | ||
42195 | " if( $rc ){"\ | ||
42196 | " if( is_dir($pEntry) ){"\ | ||
42197 | " if( $iFlags & GLOB_MARK ){"\ | ||
42198 | " /* Adds a slash to each directory returned */"\ | ||
42199 | " $pEntry .= DIRECTORY_SEPARATOR;"\ | ||
42200 | " }"\ | ||
42201 | " }else if( $iFlags & GLOB_ONLYDIR ){"\ | ||
42202 | " /* Not a directory, ignore */"\ | ||
42203 | " continue;"\ | ||
42204 | " }"\ | ||
42205 | " /* Add the entry */"\ | ||
42206 | " $pArray[] = $pEntry;"\ | ||
42207 | " }"\ | ||
42208 | " }"\ | ||
42209 | "/* Close the handle */"\ | ||
42210 | "closedir($pHandle);"\ | ||
42211 | "if( ($iFlags & GLOB_NOSORT) == 0 ){"\ | ||
42212 | " /* Sort the array */"\ | ||
42213 | " sort($pArray);"\ | ||
42214 | "}"\ | ||
42215 | "if( ($iFlags & GLOB_NOCHECK) && sizeof($pArray) < 1 ){"\ | ||
42216 | " /* Return the search pattern if no files matching were found */"\ | ||
42217 | " $pArray[] = $pattern;"\ | ||
42218 | "}"\ | ||
42219 | "/* Return the created array */"\ | ||
42220 | "return $pArray;"\ | ||
42221 | "}"\ | ||
42222 | "/* Creates a temporary file */"\ | ||
42223 | "function tmpfile(){"\ | ||
42224 | " /* Extract the temp directory */"\ | ||
42225 | " $zTempDir = sys_get_temp_dir();"\ | ||
42226 | " if( strlen($zTempDir) < 1 ){"\ | ||
42227 | " /* Use the current dir */"\ | ||
42228 | " $zTempDir = '.';"\ | ||
42229 | " }"\ | ||
42230 | " /* Create the file */"\ | ||
42231 | " $pHandle = fopen($zTempDir.DIRECTORY_SEPARATOR.'JX9'.rand_str(12), 'w+');"\ | ||
42232 | " return $pHandle;"\ | ||
42233 | "}"\ | ||
42234 | "/* Creates a temporary filename */"\ | ||
42235 | "function tempnam(string $zDir = sys_get_temp_dir() /* Symisc eXtension */, string $zPrefix = 'JX9')"\ | ||
42236 | "{"\ | ||
42237 | " return $zDir.DIRECTORY_SEPARATOR.$zPrefix.rand_str(12);"\ | ||
42238 | "}"\ | ||
42239 | "function max(){"\ | ||
42240 | " $pArgs = func_get_args();"\ | ||
42241 | " if( sizeof($pArgs) < 1 ){"\ | ||
42242 | " return null;"\ | ||
42243 | " }"\ | ||
42244 | " if( sizeof($pArgs) < 2 ){"\ | ||
42245 | " $pArg = $pArgs[0];"\ | ||
42246 | " if( !is_array($pArg) ){"\ | ||
42247 | " return $pArg; "\ | ||
42248 | " }"\ | ||
42249 | " if( sizeof($pArg) < 1 ){"\ | ||
42250 | " return null;"\ | ||
42251 | " }"\ | ||
42252 | " $pArg = array_copy($pArgs[0]);"\ | ||
42253 | " reset($pArg);"\ | ||
42254 | " $max = current($pArg);"\ | ||
42255 | " while( FALSE !== ($val = next($pArg)) ){"\ | ||
42256 | " if( $val > $max ){"\ | ||
42257 | " $max = $val;"\ | ||
42258 | " }"\ | ||
42259 | " }"\ | ||
42260 | " return $max;"\ | ||
42261 | " }"\ | ||
42262 | " $max = $pArgs[0];"\ | ||
42263 | " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ | ||
42264 | " $val = $pArgs[$i];"\ | ||
42265 | "if( $val > $max ){"\ | ||
42266 | " $max = $val;"\ | ||
42267 | "}"\ | ||
42268 | " }"\ | ||
42269 | " return $max;"\ | ||
42270 | "}"\ | ||
42271 | "function min(){"\ | ||
42272 | " $pArgs = func_get_args();"\ | ||
42273 | " if( sizeof($pArgs) < 1 ){"\ | ||
42274 | " return null;"\ | ||
42275 | " }"\ | ||
42276 | " if( sizeof($pArgs) < 2 ){"\ | ||
42277 | " $pArg = $pArgs[0];"\ | ||
42278 | " if( !is_array($pArg) ){"\ | ||
42279 | " return $pArg; "\ | ||
42280 | " }"\ | ||
42281 | " if( sizeof($pArg) < 1 ){"\ | ||
42282 | " return null;"\ | ||
42283 | " }"\ | ||
42284 | " $pArg = array_copy($pArgs[0]);"\ | ||
42285 | " reset($pArg);"\ | ||
42286 | " $min = current($pArg);"\ | ||
42287 | " while( FALSE !== ($val = next($pArg)) ){"\ | ||
42288 | " if( $val < $min ){"\ | ||
42289 | " $min = $val;"\ | ||
42290 | " }"\ | ||
42291 | " }"\ | ||
42292 | " return $min;"\ | ||
42293 | " }"\ | ||
42294 | " $min = $pArgs[0];"\ | ||
42295 | " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ | ||
42296 | " $val = $pArgs[$i];"\ | ||
42297 | "if( $val < $min ){"\ | ||
42298 | " $min = $val;"\ | ||
42299 | " }"\ | ||
42300 | " }"\ | ||
42301 | " return $min;"\ | ||
42302 | "}" | ||
42303 | /* | ||
42304 | * Initialize a freshly allocated JX9 Virtual Machine so that we can | ||
42305 | * start compiling the target JX9 program. | ||
42306 | */ | ||
42307 | JX9_PRIVATE sxi32 jx9VmInit( | ||
42308 | jx9_vm *pVm, /* Initialize this */ | ||
42309 | jx9 *pEngine /* Master engine */ | ||
42310 | ) | ||
42311 | { | ||
42312 | SyString sBuiltin; | ||
42313 | jx9_value *pObj; | ||
42314 | sxi32 rc; | ||
42315 | /* Zero the structure */ | ||
42316 | SyZero(pVm, sizeof(jx9_vm)); | ||
42317 | /* Initialize VM fields */ | ||
42318 | pVm->pEngine = &(*pEngine); | ||
42319 | SyMemBackendInitFromParent(&pVm->sAllocator, &pEngine->sAllocator); | ||
42320 | /* Instructions containers */ | ||
42321 | SySetInit(&pVm->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); | ||
42322 | SySetAlloc(&pVm->aByteCode, 0xFF); | ||
42323 | pVm->pByteContainer = &pVm->aByteCode; | ||
42324 | /* Object containers */ | ||
42325 | SySetInit(&pVm->aMemObj, &pVm->sAllocator, sizeof(jx9_value)); | ||
42326 | SySetAlloc(&pVm->aMemObj, 0xFF); | ||
42327 | /* Virtual machine internal containers */ | ||
42328 | SyBlobInit(&pVm->sConsumer, &pVm->sAllocator); | ||
42329 | SyBlobInit(&pVm->sWorker, &pVm->sAllocator); | ||
42330 | SyBlobInit(&pVm->sArgv, &pVm->sAllocator); | ||
42331 | SySetInit(&pVm->aLitObj, &pVm->sAllocator, sizeof(jx9_value)); | ||
42332 | SySetAlloc(&pVm->aLitObj, 0xFF); | ||
42333 | SyHashInit(&pVm->hHostFunction, &pVm->sAllocator, 0, 0); | ||
42334 | SyHashInit(&pVm->hFunction, &pVm->sAllocator, 0, 0); | ||
42335 | SyHashInit(&pVm->hConstant, &pVm->sAllocator, 0, 0); | ||
42336 | SyHashInit(&pVm->hSuper, &pVm->sAllocator, 0, 0); | ||
42337 | SySetInit(&pVm->aFreeObj, &pVm->sAllocator, sizeof(VmSlot)); | ||
42338 | /* Configuration containers */ | ||
42339 | SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString)); | ||
42340 | SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString)); | ||
42341 | SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString)); | ||
42342 | SySetInit(&pVm->aIOstream, &pVm->sAllocator, sizeof(jx9_io_stream *)); | ||
42343 | /* Error callbacks containers */ | ||
42344 | jx9MemObjInit(&(*pVm), &pVm->sAssertCallback); | ||
42345 | /* Set a default recursion limit */ | ||
42346 | #if defined(__WINNT__) || defined(__UNIXES__) | ||
42347 | pVm->nMaxDepth = 32; | ||
42348 | #else | ||
42349 | pVm->nMaxDepth = 16; | ||
42350 | #endif | ||
42351 | /* Default assertion flags */ | ||
42352 | pVm->iAssertFlags = JX9_ASSERT_WARNING; /* Issue a warning for each failed assertion */ | ||
42353 | /* PRNG context */ | ||
42354 | SyRandomnessInit(&pVm->sPrng, 0, 0); | ||
42355 | /* Install the null constant */ | ||
42356 | pObj = jx9VmReserveConstObj(&(*pVm), 0); | ||
42357 | if( pObj == 0 ){ | ||
42358 | rc = SXERR_MEM; | ||
42359 | goto Err; | ||
42360 | } | ||
42361 | jx9MemObjInit(pVm, pObj); | ||
42362 | /* Install the boolean TRUE constant */ | ||
42363 | pObj = jx9VmReserveConstObj(&(*pVm), 0); | ||
42364 | if( pObj == 0 ){ | ||
42365 | rc = SXERR_MEM; | ||
42366 | goto Err; | ||
42367 | } | ||
42368 | jx9MemObjInitFromBool(pVm, pObj, 1); | ||
42369 | /* Install the boolean FALSE constant */ | ||
42370 | pObj = jx9VmReserveConstObj(&(*pVm), 0); | ||
42371 | if( pObj == 0 ){ | ||
42372 | rc = SXERR_MEM; | ||
42373 | goto Err; | ||
42374 | } | ||
42375 | jx9MemObjInitFromBool(pVm, pObj, 0); | ||
42376 | /* Create the global frame */ | ||
42377 | rc = VmEnterFrame(&(*pVm), 0, 0); | ||
42378 | if( rc != SXRET_OK ){ | ||
42379 | goto Err; | ||
42380 | } | ||
42381 | /* Initialize the code generator */ | ||
42382 | rc = jx9InitCodeGenerator(pVm, pEngine->xConf.xErr, pEngine->xConf.pErrData); | ||
42383 | if( rc != SXRET_OK ){ | ||
42384 | goto Err; | ||
42385 | } | ||
42386 | /* VM correctly initialized, set the magic number */ | ||
42387 | pVm->nMagic = JX9_VM_INIT; | ||
42388 | SyStringInitFromBuf(&sBuiltin,JX9_BUILTIN_LIB, sizeof(JX9_BUILTIN_LIB)-1); | ||
42389 | /* Compile the built-in library */ | ||
42390 | VmEvalChunk(&(*pVm), 0, &sBuiltin, 0, FALSE); | ||
42391 | /* Reset the code generator */ | ||
42392 | jx9ResetCodeGenerator(&(*pVm), pEngine->xConf.xErr, pEngine->xConf.pErrData); | ||
42393 | return SXRET_OK; | ||
42394 | Err: | ||
42395 | SyMemBackendRelease(&pVm->sAllocator); | ||
42396 | return rc; | ||
42397 | } | ||
42398 | /* | ||
42399 | * Default VM output consumer callback.That is, all VM output is redirected to this | ||
42400 | * routine which store the output in an internal blob. | ||
42401 | * The output can be extracted later after program execution [jx9_vm_exec()] via | ||
42402 | * the [jx9_vm_config()] interface with a configuration verb set to | ||
42403 | * jx9VM_CONFIG_EXTRACT_OUTPUT. | ||
42404 | * Refer to the official docurmentation for additional information. | ||
42405 | * Note that for performance reason it's preferable to install a VM output | ||
42406 | * consumer callback via (jx9VM_CONFIG_OUTPUT) rather than waiting for the VM | ||
42407 | * to finish executing and extracting the output. | ||
42408 | */ | ||
42409 | JX9_PRIVATE sxi32 jx9VmBlobConsumer( | ||
42410 | const void *pOut, /* VM Generated output*/ | ||
42411 | unsigned int nLen, /* Generated output length */ | ||
42412 | void *pUserData /* User private data */ | ||
42413 | ) | ||
42414 | { | ||
42415 | sxi32 rc; | ||
42416 | /* Store the output in an internal BLOB */ | ||
42417 | rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen); | ||
42418 | return rc; | ||
42419 | } | ||
42420 | #define VM_STACK_GUARD 16 | ||
42421 | /* | ||
42422 | * Allocate a new operand stack so that we can start executing | ||
42423 | * our compiled JX9 program. | ||
42424 | * Return a pointer to the operand stack (array of jx9_values) | ||
42425 | * on success. NULL (Fatal error) on failure. | ||
42426 | */ | ||
42427 | static jx9_value * VmNewOperandStack( | ||
42428 | jx9_vm *pVm, /* Target VM */ | ||
42429 | sxu32 nInstr /* Total numer of generated bytecode instructions */ | ||
42430 | ) | ||
42431 | { | ||
42432 | jx9_value *pStack; | ||
42433 | /* No instruction ever pushes more than a single element onto the | ||
42434 | ** stack and the stack never grows on successive executions of the | ||
42435 | ** same loop. So the total number of instructions is an upper bound | ||
42436 | ** on the maximum stack depth required. | ||
42437 | ** | ||
42438 | ** Allocation all the stack space we will ever need. | ||
42439 | */ | ||
42440 | nInstr += VM_STACK_GUARD; | ||
42441 | pStack = (jx9_value *)SyMemBackendAlloc(&pVm->sAllocator, nInstr * sizeof(jx9_value)); | ||
42442 | if( pStack == 0 ){ | ||
42443 | return 0; | ||
42444 | } | ||
42445 | /* Initialize the operand stack */ | ||
42446 | while( nInstr > 0 ){ | ||
42447 | jx9MemObjInit(&(*pVm), &pStack[nInstr - 1]); | ||
42448 | --nInstr; | ||
42449 | } | ||
42450 | /* Ready for bytecode execution */ | ||
42451 | return pStack; | ||
42452 | } | ||
42453 | /* Forward declaration */ | ||
42454 | static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm); | ||
42455 | /* | ||
42456 | * Prepare the Virtual Machine for bytecode execution. | ||
42457 | * This routine gets called by the JX9 engine after | ||
42458 | * successful compilation of the target JX9 program. | ||
42459 | */ | ||
42460 | JX9_PRIVATE sxi32 jx9VmMakeReady( | ||
42461 | jx9_vm *pVm /* Target VM */ | ||
42462 | ) | ||
42463 | { | ||
42464 | sxi32 rc; | ||
42465 | if( pVm->nMagic != JX9_VM_INIT ){ | ||
42466 | /* Initialize your VM first */ | ||
42467 | return SXERR_CORRUPT; | ||
42468 | } | ||
42469 | /* Mark the VM ready for bytecode execution */ | ||
42470 | pVm->nMagic = JX9_VM_RUN; | ||
42471 | /* Release the code generator now we have compiled our program */ | ||
42472 | jx9ResetCodeGenerator(pVm, 0, 0); | ||
42473 | /* Emit the DONE instruction */ | ||
42474 | rc = jx9VmEmitInstr(&(*pVm), JX9_OP_DONE, 0, 0, 0, 0); | ||
42475 | if( rc != SXRET_OK ){ | ||
42476 | return SXERR_MEM; | ||
42477 | } | ||
42478 | /* Script return value */ | ||
42479 | jx9MemObjInit(&(*pVm), &pVm->sExec); /* Assume a NULL return value */ | ||
42480 | /* Allocate a new operand stack */ | ||
42481 | pVm->aOps = VmNewOperandStack(&(*pVm), SySetUsed(pVm->pByteContainer)); | ||
42482 | if( pVm->aOps == 0 ){ | ||
42483 | return SXERR_MEM; | ||
42484 | } | ||
42485 | /* Set the default VM output consumer callback and it's | ||
42486 | * private data. */ | ||
42487 | pVm->sVmConsumer.xConsumer = jx9VmBlobConsumer; | ||
42488 | pVm->sVmConsumer.pUserData = &pVm->sConsumer; | ||
42489 | /* Register special functions first [i.e: print, func_get_args(), die, etc.] */ | ||
42490 | rc = VmRegisterSpecialFunction(&(*pVm)); | ||
42491 | if( rc != SXRET_OK ){ | ||
42492 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
42493 | return rc; | ||
42494 | } | ||
42495 | /* Create superglobals [i.e: $GLOBALS, $_GET, $_POST...] */ | ||
42496 | rc = jx9HashmapLoadBuiltin(&(*pVm)); | ||
42497 | if( rc != SXRET_OK ){ | ||
42498 | /* Don't worry about freeing memory, everything will be released shortly */ | ||
42499 | return rc; | ||
42500 | } | ||
42501 | /* Register built-in constants [i.e: JX9_EOL, JX9_OS...] */ | ||
42502 | jx9RegisterBuiltInConstant(&(*pVm)); | ||
42503 | /* Register built-in functions [i.e: is_null(), array_diff(), strlen(), etc.] */ | ||
42504 | jx9RegisterBuiltInFunction(&(*pVm)); | ||
42505 | /* VM is ready for bytecode execution */ | ||
42506 | return SXRET_OK; | ||
42507 | } | ||
42508 | /* | ||
42509 | * Reset a Virtual Machine to it's initial state. | ||
42510 | */ | ||
42511 | JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm) | ||
42512 | { | ||
42513 | if( pVm->nMagic != JX9_VM_RUN && pVm->nMagic != JX9_VM_EXEC ){ | ||
42514 | return SXERR_CORRUPT; | ||
42515 | } | ||
42516 | /* TICKET 1433-003: As of this version, the VM is automatically reset */ | ||
42517 | SyBlobReset(&pVm->sConsumer); | ||
42518 | jx9MemObjRelease(&pVm->sExec); | ||
42519 | /* Set the ready flag */ | ||
42520 | pVm->nMagic = JX9_VM_RUN; | ||
42521 | return SXRET_OK; | ||
42522 | } | ||
42523 | /* | ||
42524 | * Release a Virtual Machine. | ||
42525 | * Every virtual machine must be destroyed in order to avoid memory leaks. | ||
42526 | */ | ||
42527 | JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm) | ||
42528 | { | ||
42529 | /* Set the stale magic number */ | ||
42530 | pVm->nMagic = JX9_VM_STALE; | ||
42531 | /* Release the private memory subsystem */ | ||
42532 | SyMemBackendRelease(&pVm->sAllocator); | ||
42533 | return SXRET_OK; | ||
42534 | } | ||
42535 | /* | ||
42536 | * Initialize a foreign function call context. | ||
42537 | * The context in which a foreign function executes is stored in a jx9_context object. | ||
42538 | * A pointer to a jx9_context object is always first parameter to application-defined foreign | ||
42539 | * functions. | ||
42540 | * The application-defined foreign function implementation will pass this pointer through into | ||
42541 | * calls to dozens of interfaces, these includes jx9_result_int(), jx9_result_string(), jx9_result_value(), | ||
42542 | * jx9_context_new_scalar(), jx9_context_alloc_chunk(), jx9_context_output(), jx9_context_throw_error() | ||
42543 | * and many more. Refer to the C/C++ Interfaces documentation for additional information. | ||
42544 | */ | ||
42545 | static sxi32 VmInitCallContext( | ||
42546 | jx9_context *pOut, /* Call Context */ | ||
42547 | jx9_vm *pVm, /* Target VM */ | ||
42548 | jx9_user_func *pFunc, /* Foreign function to execute shortly */ | ||
42549 | jx9_value *pRet, /* Store return value here*/ | ||
42550 | sxi32 iFlags /* Control flags */ | ||
42551 | ) | ||
42552 | { | ||
42553 | pOut->pFunc = pFunc; | ||
42554 | pOut->pVm = pVm; | ||
42555 | SySetInit(&pOut->sVar, &pVm->sAllocator, sizeof(jx9_value *)); | ||
42556 | SySetInit(&pOut->sChunk, &pVm->sAllocator, sizeof(jx9_aux_data)); | ||
42557 | /* Assume a null return value */ | ||
42558 | MemObjSetType(pRet, MEMOBJ_NULL); | ||
42559 | pOut->pRet = pRet; | ||
42560 | pOut->iFlags = iFlags; | ||
42561 | return SXRET_OK; | ||
42562 | } | ||
42563 | /* | ||
42564 | * Release a foreign function call context and cleanup the mess | ||
42565 | * left behind. | ||
42566 | */ | ||
42567 | static void VmReleaseCallContext(jx9_context *pCtx) | ||
42568 | { | ||
42569 | sxu32 n; | ||
42570 | if( SySetUsed(&pCtx->sVar) > 0 ){ | ||
42571 | jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar); | ||
42572 | for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ | ||
42573 | if( apObj[n] == 0 ){ | ||
42574 | /* Already released */ | ||
42575 | continue; | ||
42576 | } | ||
42577 | jx9MemObjRelease(apObj[n]); | ||
42578 | SyMemBackendPoolFree(&pCtx->pVm->sAllocator, apObj[n]); | ||
42579 | } | ||
42580 | SySetRelease(&pCtx->sVar); | ||
42581 | } | ||
42582 | if( SySetUsed(&pCtx->sChunk) > 0 ){ | ||
42583 | jx9_aux_data *aAux; | ||
42584 | void *pChunk; | ||
42585 | /* Automatic release of dynamically allocated chunk | ||
42586 | * using [jx9_context_alloc_chunk()]. | ||
42587 | */ | ||
42588 | aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk); | ||
42589 | for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ | ||
42590 | pChunk = aAux[n].pAuxData; | ||
42591 | /* Release the chunk */ | ||
42592 | if( pChunk ){ | ||
42593 | SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk); | ||
42594 | } | ||
42595 | } | ||
42596 | SySetRelease(&pCtx->sChunk); | ||
42597 | } | ||
42598 | } | ||
42599 | /* | ||
42600 | * Release a jx9_value allocated from the body of a foreign function. | ||
42601 | * Refer to [jx9_context_release_value()] for additional information. | ||
42602 | */ | ||
42603 | JX9_PRIVATE void jx9VmReleaseContextValue( | ||
42604 | jx9_context *pCtx, /* Call context */ | ||
42605 | jx9_value *pValue /* Release this value */ | ||
42606 | ) | ||
42607 | { | ||
42608 | if( pValue == 0 ){ | ||
42609 | /* NULL value is a harmless operation */ | ||
42610 | return; | ||
42611 | } | ||
42612 | if( SySetUsed(&pCtx->sVar) > 0 ){ | ||
42613 | jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar); | ||
42614 | sxu32 n; | ||
42615 | for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ | ||
42616 | if( apObj[n] == pValue ){ | ||
42617 | jx9MemObjRelease(pValue); | ||
42618 | SyMemBackendPoolFree(&pCtx->pVm->sAllocator, pValue); | ||
42619 | /* Mark as released */ | ||
42620 | apObj[n] = 0; | ||
42621 | break; | ||
42622 | } | ||
42623 | } | ||
42624 | } | ||
42625 | } | ||
42626 | /* | ||
42627 | * Pop and release as many memory object from the operand stack. | ||
42628 | */ | ||
42629 | static void VmPopOperand( | ||
42630 | jx9_value **ppTos, /* Operand stack */ | ||
42631 | sxi32 nPop /* Total number of memory objects to pop */ | ||
42632 | ) | ||
42633 | { | ||
42634 | jx9_value *pTos = *ppTos; | ||
42635 | while( nPop > 0 ){ | ||
42636 | jx9MemObjRelease(pTos); | ||
42637 | pTos--; | ||
42638 | nPop--; | ||
42639 | } | ||
42640 | /* Top of the stack */ | ||
42641 | *ppTos = pTos; | ||
42642 | } | ||
42643 | /* | ||
42644 | * Reserve a memory object. | ||
42645 | * Return a pointer to the raw jx9_value on success. NULL on failure. | ||
42646 | */ | ||
42647 | JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIdx) | ||
42648 | { | ||
42649 | jx9_value *pObj = 0; | ||
42650 | VmSlot *pSlot; | ||
42651 | sxu32 nIdx; | ||
42652 | /* Check for a free slot */ | ||
42653 | nIdx = SXU32_HIGH; /* cc warning */ | ||
42654 | pSlot = (VmSlot *)SySetPop(&pVm->aFreeObj); | ||
42655 | if( pSlot ){ | ||
42656 | pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx); | ||
42657 | nIdx = pSlot->nIdx; | ||
42658 | } | ||
42659 | if( pObj == 0 ){ | ||
42660 | /* Reserve a new memory object */ | ||
42661 | pObj = VmReserveMemObj(&(*pVm), &nIdx); | ||
42662 | if( pObj == 0 ){ | ||
42663 | return 0; | ||
42664 | } | ||
42665 | } | ||
42666 | /* Set a null default value */ | ||
42667 | jx9MemObjInit(&(*pVm), pObj); | ||
42668 | if( pIdx ){ | ||
42669 | *pIdx = nIdx; | ||
42670 | } | ||
42671 | pObj->nIdx = nIdx; | ||
42672 | return pObj; | ||
42673 | } | ||
42674 | /* | ||
42675 | * Extract a variable value from the top active VM frame. | ||
42676 | * Return a pointer to the variable value on success. | ||
42677 | * NULL otherwise (non-existent variable/Out-of-memory, ...). | ||
42678 | */ | ||
42679 | static jx9_value * VmExtractMemObj( | ||
42680 | jx9_vm *pVm, /* Target VM */ | ||
42681 | const SyString *pName, /* Variable name */ | ||
42682 | int bDup, /* True to duplicate variable name */ | ||
42683 | int bCreate /* True to create the variable if non-existent */ | ||
42684 | ) | ||
42685 | { | ||
42686 | int bNullify = FALSE; | ||
42687 | SyHashEntry *pEntry; | ||
42688 | VmFrame *pFrame; | ||
42689 | jx9_value *pObj; | ||
42690 | sxu32 nIdx; | ||
42691 | sxi32 rc; | ||
42692 | /* Point to the top active frame */ | ||
42693 | pFrame = pVm->pFrame; | ||
42694 | /* Perform the lookup */ | ||
42695 | if( pName == 0 || pName->nByte < 1 ){ | ||
42696 | static const SyString sAnnon = { " " , sizeof(char) }; | ||
42697 | pName = &sAnnon; | ||
42698 | /* Always nullify the object */ | ||
42699 | bNullify = TRUE; | ||
42700 | bDup = FALSE; | ||
42701 | } | ||
42702 | /* Check the superglobals table first */ | ||
42703 | pEntry = SyHashGet(&pVm->hSuper, (const void *)pName->zString, pName->nByte); | ||
42704 | if( pEntry == 0 ){ | ||
42705 | /* Query the top active frame */ | ||
42706 | pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); | ||
42707 | if( pEntry == 0 ){ | ||
42708 | char *zName = (char *)pName->zString; | ||
42709 | VmSlot sLocal; | ||
42710 | if( !bCreate ){ | ||
42711 | /* Do not create the variable, return NULL */ | ||
42712 | return 0; | ||
42713 | } | ||
42714 | /* No such variable, automatically create a new one and install | ||
42715 | * it in the current frame. | ||
42716 | */ | ||
42717 | pObj = jx9VmReserveMemObj(&(*pVm),&nIdx); | ||
42718 | if( pObj == 0 ){ | ||
42719 | return 0; | ||
42720 | } | ||
42721 | if( bDup ){ | ||
42722 | /* Duplicate name */ | ||
42723 | zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); | ||
42724 | if( zName == 0 ){ | ||
42725 | return 0; | ||
42726 | } | ||
42727 | } | ||
42728 | /* Link to the top active VM frame */ | ||
42729 | rc = SyHashInsert(&pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx)); | ||
42730 | if( rc != SXRET_OK ){ | ||
42731 | /* Return the slot to the free pool */ | ||
42732 | sLocal.nIdx = nIdx; | ||
42733 | sLocal.pUserData = 0; | ||
42734 | SySetPut(&pVm->aFreeObj, (const void *)&sLocal); | ||
42735 | return 0; | ||
42736 | } | ||
42737 | if( pFrame->pParent != 0 ){ | ||
42738 | /* Local variable */ | ||
42739 | sLocal.nIdx = nIdx; | ||
42740 | SySetPut(&pFrame->sLocal, (const void *)&sLocal); | ||
42741 | } | ||
42742 | }else{ | ||
42743 | /* Extract variable contents */ | ||
42744 | nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); | ||
42745 | pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); | ||
42746 | if( bNullify && pObj ){ | ||
42747 | jx9MemObjRelease(pObj); | ||
42748 | } | ||
42749 | } | ||
42750 | }else{ | ||
42751 | /* Superglobal */ | ||
42752 | nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); | ||
42753 | pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); | ||
42754 | } | ||
42755 | return pObj; | ||
42756 | } | ||
42757 | /* | ||
42758 | * Extract a superglobal variable such as $_GET, $_POST, $_HEADERS, .... | ||
42759 | * Return a pointer to the variable value on success.NULL otherwise. | ||
42760 | */ | ||
42761 | static jx9_value * VmExtractSuper( | ||
42762 | jx9_vm *pVm, /* Target VM */ | ||
42763 | const char *zName, /* Superglobal name: NOT NULL TERMINATED */ | ||
42764 | sxu32 nByte /* zName length */ | ||
42765 | ) | ||
42766 | { | ||
42767 | SyHashEntry *pEntry; | ||
42768 | jx9_value *pValue; | ||
42769 | sxu32 nIdx; | ||
42770 | /* Query the superglobal table */ | ||
42771 | pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); | ||
42772 | if( pEntry == 0 ){ | ||
42773 | /* No such entry */ | ||
42774 | return 0; | ||
42775 | } | ||
42776 | /* Extract the superglobal index in the global object pool */ | ||
42777 | nIdx = SX_PTR_TO_INT(pEntry->pUserData); | ||
42778 | /* Extract the variable value */ | ||
42779 | pValue = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); | ||
42780 | return pValue; | ||
42781 | } | ||
42782 | /* | ||
42783 | * Perform a raw hashmap insertion. | ||
42784 | * Refer to the [jx9VmConfigure()] implementation for additional information. | ||
42785 | */ | ||
42786 | static sxi32 VmHashmapInsert( | ||
42787 | jx9_hashmap *pMap, /* Target hashmap */ | ||
42788 | const char *zKey, /* Entry key */ | ||
42789 | int nKeylen, /* zKey length*/ | ||
42790 | const char *zData, /* Entry data */ | ||
42791 | int nLen /* zData length */ | ||
42792 | ) | ||
42793 | { | ||
42794 | jx9_value sKey,sValue; | ||
42795 | jx9_value *pKey; | ||
42796 | sxi32 rc; | ||
42797 | pKey = 0; | ||
42798 | jx9MemObjInit(pMap->pVm, &sKey); | ||
42799 | jx9MemObjInitFromString(pMap->pVm, &sValue, 0); | ||
42800 | if( zKey ){ | ||
42801 | if( nKeylen < 0 ){ | ||
42802 | nKeylen = (int)SyStrlen(zKey); | ||
42803 | } | ||
42804 | jx9MemObjStringAppend(&sKey, zKey, (sxu32)nKeylen); | ||
42805 | pKey = &sKey; | ||
42806 | } | ||
42807 | if( zData ){ | ||
42808 | if( nLen < 0 ){ | ||
42809 | /* Compute length automatically */ | ||
42810 | nLen = (int)SyStrlen(zData); | ||
42811 | } | ||
42812 | jx9MemObjStringAppend(&sValue, zData, (sxu32)nLen); | ||
42813 | } | ||
42814 | /* Perform the insertion */ | ||
42815 | rc = jx9HashmapInsert(&(*pMap),pKey,&sValue); | ||
42816 | jx9MemObjRelease(&sKey); | ||
42817 | jx9MemObjRelease(&sValue); | ||
42818 | return rc; | ||
42819 | } | ||
42820 | /* Forward declaration */ | ||
42821 | static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte); | ||
42822 | /* | ||
42823 | * Configure a working virtual machine instance. | ||
42824 | * | ||
42825 | * This routine is used to configure a JX9 virtual machine obtained by a prior | ||
42826 | * successful call to one of the compile interface such as jx9_compile() | ||
42827 | * jx9_compile_v2() or jx9_compile_file(). | ||
42828 | * The second argument to this function is an integer configuration option | ||
42829 | * that determines what property of the JX9 virtual machine is to be configured. | ||
42830 | * Subsequent arguments vary depending on the configuration option in the second | ||
42831 | * argument. There are many verbs but the most important are JX9_VM_CONFIG_OUTPUT, | ||
42832 | * JX9_VM_CONFIG_HTTP_REQUEST and JX9_VM_CONFIG_ARGV_ENTRY. | ||
42833 | * Refer to the official documentation for the list of allowed verbs. | ||
42834 | */ | ||
42835 | JX9_PRIVATE sxi32 jx9VmConfigure( | ||
42836 | jx9_vm *pVm, /* Target VM */ | ||
42837 | sxi32 nOp, /* Configuration verb */ | ||
42838 | va_list ap /* Subsequent option arguments */ | ||
42839 | ) | ||
42840 | { | ||
42841 | sxi32 rc = SXRET_OK; | ||
42842 | switch(nOp){ | ||
42843 | case JX9_VM_CONFIG_OUTPUT: { | ||
42844 | ProcConsumer xConsumer = va_arg(ap, ProcConsumer); | ||
42845 | void *pUserData = va_arg(ap, void *); | ||
42846 | /* VM output consumer callback */ | ||
42847 | #ifdef UNTRUST | ||
42848 | if( xConsumer == 0 ){ | ||
42849 | rc = SXERR_CORRUPT; | ||
42850 | break; | ||
42851 | } | ||
42852 | #endif | ||
42853 | /* Install the output consumer */ | ||
42854 | pVm->sVmConsumer.xConsumer = xConsumer; | ||
42855 | pVm->sVmConsumer.pUserData = pUserData; | ||
42856 | break; | ||
42857 | } | ||
42858 | case JX9_VM_CONFIG_IMPORT_PATH: { | ||
42859 | /* Import path */ | ||
42860 | const char *zPath; | ||
42861 | SyString sPath; | ||
42862 | zPath = va_arg(ap, const char *); | ||
42863 | #if defined(UNTRUST) | ||
42864 | if( zPath == 0 ){ | ||
42865 | rc = SXERR_EMPTY; | ||
42866 | break; | ||
42867 | } | ||
42868 | #endif | ||
42869 | SyStringInitFromBuf(&sPath, zPath, SyStrlen(zPath)); | ||
42870 | /* Remove trailing slashes and backslashes */ | ||
42871 | #ifdef __WINNT__ | ||
42872 | SyStringTrimTrailingChar(&sPath, '\\'); | ||
42873 | #endif | ||
42874 | SyStringTrimTrailingChar(&sPath, '/'); | ||
42875 | /* Remove leading and trailing white spaces */ | ||
42876 | SyStringFullTrim(&sPath); | ||
42877 | if( sPath.nByte > 0 ){ | ||
42878 | /* Store the path in the corresponding conatiner */ | ||
42879 | rc = SySetPut(&pVm->aPaths, (const void *)&sPath); | ||
42880 | } | ||
42881 | break; | ||
42882 | } | ||
42883 | case JX9_VM_CONFIG_ERR_REPORT: | ||
42884 | /* Run-Time Error report */ | ||
42885 | pVm->bErrReport = 1; | ||
42886 | break; | ||
42887 | case JX9_VM_CONFIG_RECURSION_DEPTH:{ | ||
42888 | /* Recursion depth */ | ||
42889 | int nDepth = va_arg(ap, int); | ||
42890 | if( nDepth > 2 && nDepth < 1024 ){ | ||
42891 | pVm->nMaxDepth = nDepth; | ||
42892 | } | ||
42893 | break; | ||
42894 | } | ||
42895 | case JX9_VM_OUTPUT_LENGTH: { | ||
42896 | /* VM output length in bytes */ | ||
42897 | sxu32 *pOut = va_arg(ap, sxu32 *); | ||
42898 | #ifdef UNTRUST | ||
42899 | if( pOut == 0 ){ | ||
42900 | rc = SXERR_CORRUPT; | ||
42901 | break; | ||
42902 | } | ||
42903 | #endif | ||
42904 | *pOut = pVm->nOutputLen; | ||
42905 | break; | ||
42906 | } | ||
42907 | case JX9_VM_CONFIG_CREATE_VAR: { | ||
42908 | /* Create a new superglobal/global variable */ | ||
42909 | const char *zName = va_arg(ap, const char *); | ||
42910 | jx9_value *pValue = va_arg(ap, jx9_value *); | ||
42911 | SyHashEntry *pEntry; | ||
42912 | jx9_value *pObj; | ||
42913 | sxu32 nByte; | ||
42914 | sxu32 nIdx; | ||
42915 | #ifdef UNTRUST | ||
42916 | if( SX_EMPTY_STR(zName) || pValue == 0 ){ | ||
42917 | rc = SXERR_CORRUPT; | ||
42918 | break; | ||
42919 | } | ||
42920 | #endif | ||
42921 | nByte = SyStrlen(zName); | ||
42922 | /* Check if the superglobal is already installed */ | ||
42923 | pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); | ||
42924 | if( pEntry ){ | ||
42925 | /* Variable already installed */ | ||
42926 | nIdx = SX_PTR_TO_INT(pEntry->pUserData); | ||
42927 | /* Extract contents */ | ||
42928 | pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); | ||
42929 | if( pObj ){ | ||
42930 | /* Overwrite old contents */ | ||
42931 | jx9MemObjStore(pValue, pObj); | ||
42932 | } | ||
42933 | }else{ | ||
42934 | /* Install a new variable */ | ||
42935 | pObj = jx9VmReserveMemObj(&(*pVm),&nIdx); | ||
42936 | if( pObj == 0 ){ | ||
42937 | rc = SXERR_MEM; | ||
42938 | break; | ||
42939 | } | ||
42940 | /* Copy value */ | ||
42941 | jx9MemObjStore(pValue, pObj); | ||
42942 | /* Install the superglobal */ | ||
42943 | rc = SyHashInsert(&pVm->hSuper, (const void *)zName, nByte, SX_INT_TO_PTR(nIdx)); | ||
42944 | } | ||
42945 | break; | ||
42946 | } | ||
42947 | case JX9_VM_CONFIG_SERVER_ATTR: | ||
42948 | case JX9_VM_CONFIG_ENV_ATTR: { | ||
42949 | const char *zKey = va_arg(ap, const char *); | ||
42950 | const char *zValue = va_arg(ap, const char *); | ||
42951 | int nLen = va_arg(ap, int); | ||
42952 | jx9_hashmap *pMap; | ||
42953 | jx9_value *pValue; | ||
42954 | if( nOp == JX9_VM_CONFIG_ENV_ATTR ){ | ||
42955 | /* Extract the $_ENV superglobal */ | ||
42956 | pValue = VmExtractSuper(&(*pVm), "_ENV", sizeof("_ENV")-1); | ||
42957 | }else{ | ||
42958 | /* Extract the $_SERVER superglobal */ | ||
42959 | pValue = VmExtractSuper(&(*pVm), "_SERVER", sizeof("_SERVER")-1); | ||
42960 | } | ||
42961 | if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
42962 | /* No such entry */ | ||
42963 | rc = SXERR_NOTFOUND; | ||
42964 | break; | ||
42965 | } | ||
42966 | /* Point to the hashmap */ | ||
42967 | pMap = (jx9_hashmap *)pValue->x.pOther; | ||
42968 | /* Perform the insertion */ | ||
42969 | rc = VmHashmapInsert(pMap, zKey, -1, zValue, nLen); | ||
42970 | break; | ||
42971 | } | ||
42972 | case JX9_VM_CONFIG_ARGV_ENTRY:{ | ||
42973 | /* Script arguments */ | ||
42974 | const char *zValue = va_arg(ap, const char *); | ||
42975 | jx9_hashmap *pMap; | ||
42976 | jx9_value *pValue; | ||
42977 | /* Extract the $argv array */ | ||
42978 | pValue = VmExtractSuper(&(*pVm), "argv", sizeof("argv")-1); | ||
42979 | if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
42980 | /* No such entry */ | ||
42981 | rc = SXERR_NOTFOUND; | ||
42982 | break; | ||
42983 | } | ||
42984 | /* Point to the hashmap */ | ||
42985 | pMap = (jx9_hashmap *)pValue->x.pOther; | ||
42986 | /* Perform the insertion */ | ||
42987 | rc = VmHashmapInsert(pMap, 0, 0, zValue,-1); | ||
42988 | if( rc == SXRET_OK && zValue && zValue[0] != 0 ){ | ||
42989 | if( pMap->nEntry > 1 ){ | ||
42990 | /* Append space separator first */ | ||
42991 | SyBlobAppend(&pVm->sArgv, (const void *)" ", sizeof(char)); | ||
42992 | } | ||
42993 | SyBlobAppend(&pVm->sArgv, (const void *)zValue,SyStrlen(zValue)); | ||
42994 | } | ||
42995 | break; | ||
42996 | } | ||
42997 | case JX9_VM_CONFIG_EXEC_VALUE: { | ||
42998 | /* Script return value */ | ||
42999 | jx9_value **ppValue = va_arg(ap, jx9_value **); | ||
43000 | #ifdef UNTRUST | ||
43001 | if( ppValue == 0 ){ | ||
43002 | rc = SXERR_CORRUPT; | ||
43003 | break; | ||
43004 | } | ||
43005 | #endif | ||
43006 | *ppValue = &pVm->sExec; | ||
43007 | break; | ||
43008 | } | ||
43009 | case JX9_VM_CONFIG_IO_STREAM: { | ||
43010 | /* Register an IO stream device */ | ||
43011 | const jx9_io_stream *pStream = va_arg(ap, const jx9_io_stream *); | ||
43012 | /* Make sure we are dealing with a valid IO stream */ | ||
43013 | if( pStream == 0 || pStream->zName == 0 || pStream->zName[0] == 0 || | ||
43014 | pStream->xOpen == 0 || pStream->xRead == 0 ){ | ||
43015 | /* Invalid stream */ | ||
43016 | rc = SXERR_INVALID; | ||
43017 | break; | ||
43018 | } | ||
43019 | if( pVm->pDefStream == 0 && SyStrnicmp(pStream->zName, "file", sizeof("file")-1) == 0 ){ | ||
43020 | /* Make the 'file://' stream the defaut stream device */ | ||
43021 | pVm->pDefStream = pStream; | ||
43022 | } | ||
43023 | /* Insert in the appropriate container */ | ||
43024 | rc = SySetPut(&pVm->aIOstream, (const void *)&pStream); | ||
43025 | break; | ||
43026 | } | ||
43027 | case JX9_VM_CONFIG_EXTRACT_OUTPUT: { | ||
43028 | /* Point to the VM internal output consumer buffer */ | ||
43029 | const void **ppOut = va_arg(ap, const void **); | ||
43030 | unsigned int *pLen = va_arg(ap, unsigned int *); | ||
43031 | #ifdef UNTRUST | ||
43032 | if( ppOut == 0 || pLen == 0 ){ | ||
43033 | rc = SXERR_CORRUPT; | ||
43034 | break; | ||
43035 | } | ||
43036 | #endif | ||
43037 | *ppOut = SyBlobData(&pVm->sConsumer); | ||
43038 | *pLen = SyBlobLength(&pVm->sConsumer); | ||
43039 | break; | ||
43040 | } | ||
43041 | case JX9_VM_CONFIG_HTTP_REQUEST:{ | ||
43042 | /* Raw HTTP request*/ | ||
43043 | const char *zRequest = va_arg(ap, const char *); | ||
43044 | int nByte = va_arg(ap, int); | ||
43045 | if( SX_EMPTY_STR(zRequest) ){ | ||
43046 | rc = SXERR_EMPTY; | ||
43047 | break; | ||
43048 | } | ||
43049 | if( nByte < 0 ){ | ||
43050 | /* Compute length automatically */ | ||
43051 | nByte = (int)SyStrlen(zRequest); | ||
43052 | } | ||
43053 | /* Process the request */ | ||
43054 | rc = VmHttpProcessRequest(&(*pVm), zRequest, nByte); | ||
43055 | break; | ||
43056 | } | ||
43057 | default: | ||
43058 | /* Unknown configuration option */ | ||
43059 | rc = SXERR_UNKNOWN; | ||
43060 | break; | ||
43061 | } | ||
43062 | return rc; | ||
43063 | } | ||
43064 | /* Forward declaration */ | ||
43065 | static const char * VmInstrToString(sxi32 nOp); | ||
43066 | /* | ||
43067 | * This routine is used to dump JX9 bytecode instructions to a human readable | ||
43068 | * format. | ||
43069 | * The dump is redirected to the given consumer callback which is responsible | ||
43070 | * of consuming the generated dump perhaps redirecting it to its standard output | ||
43071 | * (STDOUT). | ||
43072 | */ | ||
43073 | static sxi32 VmByteCodeDump( | ||
43074 | SySet *pByteCode, /* Bytecode container */ | ||
43075 | ProcConsumer xConsumer, /* Dump consumer callback */ | ||
43076 | void *pUserData /* Last argument to xConsumer() */ | ||
43077 | ) | ||
43078 | { | ||
43079 | static const char zDump[] = { | ||
43080 | "====================================================\n" | ||
43081 | "JX9 VM Dump Copyright (C) 2012-2013 Symisc Systems\n" | ||
43082 | " http://jx9.symisc.net/\n" | ||
43083 | "====================================================\n" | ||
43084 | }; | ||
43085 | VmInstr *pInstr, *pEnd; | ||
43086 | sxi32 rc = SXRET_OK; | ||
43087 | sxu32 n; | ||
43088 | /* Point to the JX9 instructions */ | ||
43089 | pInstr = (VmInstr *)SySetBasePtr(pByteCode); | ||
43090 | pEnd = &pInstr[SySetUsed(pByteCode)]; | ||
43091 | n = 0; | ||
43092 | xConsumer((const void *)zDump, sizeof(zDump)-1, pUserData); | ||
43093 | /* Dump instructions */ | ||
43094 | for(;;){ | ||
43095 | if( pInstr >= pEnd ){ | ||
43096 | /* No more instructions */ | ||
43097 | break; | ||
43098 | } | ||
43099 | /* Format and call the consumer callback */ | ||
43100 | rc = SyProcFormat(xConsumer, pUserData, "%s %8d %8u %#8x [%u]\n", | ||
43101 | VmInstrToString(pInstr->iOp), pInstr->iP1, pInstr->iP2, | ||
43102 | SX_PTR_TO_INT(pInstr->p3), n); | ||
43103 | if( rc != SXRET_OK ){ | ||
43104 | /* Consumer routine request an operation abort */ | ||
43105 | return rc; | ||
43106 | } | ||
43107 | ++n; | ||
43108 | pInstr++; /* Next instruction in the stream */ | ||
43109 | } | ||
43110 | return rc; | ||
43111 | } | ||
43112 | /* | ||
43113 | * Consume a generated run-time error message by invoking the VM output | ||
43114 | * consumer callback. | ||
43115 | */ | ||
43116 | static sxi32 VmCallErrorHandler(jx9_vm *pVm, SyBlob *pMsg) | ||
43117 | { | ||
43118 | jx9_output_consumer *pCons = &pVm->sVmConsumer; | ||
43119 | sxi32 rc = SXRET_OK; | ||
43120 | /* Append a new line */ | ||
43121 | #ifdef __WINNT__ | ||
43122 | SyBlobAppend(pMsg, "\r\n", sizeof("\r\n")-1); | ||
43123 | #else | ||
43124 | SyBlobAppend(pMsg, "\n", sizeof(char)); | ||
43125 | #endif | ||
43126 | /* Invoke the output consumer callback */ | ||
43127 | rc = pCons->xConsumer(SyBlobData(pMsg), SyBlobLength(pMsg), pCons->pUserData); | ||
43128 | /* Increment output length */ | ||
43129 | pVm->nOutputLen += SyBlobLength(pMsg); | ||
43130 | |||
43131 | return rc; | ||
43132 | } | ||
43133 | /* | ||
43134 | * Throw a run-time error and invoke the supplied VM output consumer callback. | ||
43135 | * Refer to the implementation of [jx9_context_throw_error()] for additional | ||
43136 | * information. | ||
43137 | */ | ||
43138 | JX9_PRIVATE sxi32 jx9VmThrowError( | ||
43139 | jx9_vm *pVm, /* Target VM */ | ||
43140 | SyString *pFuncName, /* Function name. NULL otherwise */ | ||
43141 | sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice]*/ | ||
43142 | const char *zMessage /* Null terminated error message */ | ||
43143 | ) | ||
43144 | { | ||
43145 | SyBlob *pWorker = &pVm->sWorker; | ||
43146 | SyString *pFile; | ||
43147 | char *zErr; | ||
43148 | sxi32 rc; | ||
43149 | if( !pVm->bErrReport ){ | ||
43150 | /* Don't bother reporting errors */ | ||
43151 | return SXRET_OK; | ||
43152 | } | ||
43153 | /* Reset the working buffer */ | ||
43154 | SyBlobReset(pWorker); | ||
43155 | /* Peek the processed file if available */ | ||
43156 | pFile = (SyString *)SySetPeek(&pVm->aFiles); | ||
43157 | if( pFile ){ | ||
43158 | /* Append file name */ | ||
43159 | SyBlobAppend(pWorker, pFile->zString, pFile->nByte); | ||
43160 | SyBlobAppend(pWorker, (const void *)" ", sizeof(char)); | ||
43161 | } | ||
43162 | zErr = "Error: "; | ||
43163 | switch(iErr){ | ||
43164 | case JX9_CTX_WARNING: zErr = "Warning: "; break; | ||
43165 | case JX9_CTX_NOTICE: zErr = "Notice: "; break; | ||
43166 | default: | ||
43167 | iErr = JX9_CTX_ERR; | ||
43168 | break; | ||
43169 | } | ||
43170 | SyBlobAppend(pWorker, zErr, SyStrlen(zErr)); | ||
43171 | if( pFuncName ){ | ||
43172 | /* Append function name first */ | ||
43173 | SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte); | ||
43174 | SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1); | ||
43175 | } | ||
43176 | SyBlobAppend(pWorker, zMessage, SyStrlen(zMessage)); | ||
43177 | /* Consume the error message */ | ||
43178 | rc = VmCallErrorHandler(&(*pVm), pWorker); | ||
43179 | return rc; | ||
43180 | } | ||
43181 | /* | ||
43182 | * Format and throw a run-time error and invoke the supplied VM output consumer callback. | ||
43183 | * Refer to the implementation of [jx9_context_throw_error_format()] for additional | ||
43184 | * information. | ||
43185 | */ | ||
43186 | static sxi32 VmThrowErrorAp( | ||
43187 | jx9_vm *pVm, /* Target VM */ | ||
43188 | SyString *pFuncName, /* Function name. NULL otherwise */ | ||
43189 | sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice] */ | ||
43190 | const char *zFormat, /* Format message */ | ||
43191 | va_list ap /* Variable list of arguments */ | ||
43192 | ) | ||
43193 | { | ||
43194 | SyBlob *pWorker = &pVm->sWorker; | ||
43195 | SyString *pFile; | ||
43196 | char *zErr; | ||
43197 | sxi32 rc; | ||
43198 | if( !pVm->bErrReport ){ | ||
43199 | /* Don't bother reporting errors */ | ||
43200 | return SXRET_OK; | ||
43201 | } | ||
43202 | /* Reset the working buffer */ | ||
43203 | SyBlobReset(pWorker); | ||
43204 | /* Peek the processed file if available */ | ||
43205 | pFile = (SyString *)SySetPeek(&pVm->aFiles); | ||
43206 | if( pFile ){ | ||
43207 | /* Append file name */ | ||
43208 | SyBlobAppend(pWorker, pFile->zString, pFile->nByte); | ||
43209 | SyBlobAppend(pWorker, (const void *)" ", sizeof(char)); | ||
43210 | } | ||
43211 | zErr = "Error: "; | ||
43212 | switch(iErr){ | ||
43213 | case JX9_CTX_WARNING: zErr = "Warning: "; break; | ||
43214 | case JX9_CTX_NOTICE: zErr = "Notice: "; break; | ||
43215 | default: | ||
43216 | iErr = JX9_CTX_ERR; | ||
43217 | break; | ||
43218 | } | ||
43219 | SyBlobAppend(pWorker, zErr, SyStrlen(zErr)); | ||
43220 | if( pFuncName ){ | ||
43221 | /* Append function name first */ | ||
43222 | SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte); | ||
43223 | SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1); | ||
43224 | } | ||
43225 | SyBlobFormatAp(pWorker, zFormat, ap); | ||
43226 | /* Consume the error message */ | ||
43227 | rc = VmCallErrorHandler(&(*pVm), pWorker); | ||
43228 | return rc; | ||
43229 | } | ||
43230 | /* | ||
43231 | * Format and throw a run-time error and invoke the supplied VM output consumer callback. | ||
43232 | * Refer to the implementation of [jx9_context_throw_error_format()] for additional | ||
43233 | * information. | ||
43234 | * ------------------------------------ | ||
43235 | * Simple boring wrapper function. | ||
43236 | * ------------------------------------ | ||
43237 | */ | ||
43238 | static sxi32 VmErrorFormat(jx9_vm *pVm, sxi32 iErr, const char *zFormat, ...) | ||
43239 | { | ||
43240 | va_list ap; | ||
43241 | sxi32 rc; | ||
43242 | va_start(ap, zFormat); | ||
43243 | rc = VmThrowErrorAp(&(*pVm), 0, iErr, zFormat, ap); | ||
43244 | va_end(ap); | ||
43245 | return rc; | ||
43246 | } | ||
43247 | /* | ||
43248 | * Format and throw a run-time error and invoke the supplied VM output consumer callback. | ||
43249 | * Refer to the implementation of [jx9_context_throw_error_format()] for additional | ||
43250 | * information. | ||
43251 | * ------------------------------------ | ||
43252 | * Simple boring wrapper function. | ||
43253 | * ------------------------------------ | ||
43254 | */ | ||
43255 | JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap) | ||
43256 | { | ||
43257 | sxi32 rc; | ||
43258 | rc = VmThrowErrorAp(&(*pVm), &(*pFuncName), iErr, zFormat, ap); | ||
43259 | return rc; | ||
43260 | } | ||
43261 | /* Forward declaration */ | ||
43262 | static sxi32 VmLocalExec(jx9_vm *pVm,SySet *pByteCode,jx9_value *pResult); | ||
43263 | /* | ||
43264 | * Execute as much of a JX9 bytecode program as we can then return. | ||
43265 | * | ||
43266 | * [jx9VmMakeReady()] must be called before this routine in order to | ||
43267 | * close the program with a final OP_DONE and to set up the default | ||
43268 | * consumer routines and other stuff. Refer to the implementation | ||
43269 | * of [jx9VmMakeReady()] for additional information. | ||
43270 | * If the installed VM output consumer callback ever returns JX9_ABORT | ||
43271 | * then the program execution is halted. | ||
43272 | * After this routine has finished, [jx9VmRelease()] or [jx9VmReset()] | ||
43273 | * should be used respectively to clean up the mess that was left behind | ||
43274 | * or to reset the VM to it's initial state. | ||
43275 | */ | ||
43276 | static sxi32 VmByteCodeExec( | ||
43277 | jx9_vm *pVm, /* Target VM */ | ||
43278 | VmInstr *aInstr, /* JX9 bytecode program */ | ||
43279 | jx9_value *pStack, /* Operand stack */ | ||
43280 | int nTos, /* Top entry in the operand stack (usually -1) */ | ||
43281 | jx9_value *pResult /* Store program return value here. NULL otherwise */ | ||
43282 | ) | ||
43283 | { | ||
43284 | VmInstr *pInstr; | ||
43285 | jx9_value *pTos; | ||
43286 | SySet aArg; | ||
43287 | sxi32 pc; | ||
43288 | sxi32 rc; | ||
43289 | /* Argument container */ | ||
43290 | SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *)); | ||
43291 | if( nTos < 0 ){ | ||
43292 | pTos = &pStack[-1]; | ||
43293 | }else{ | ||
43294 | pTos = &pStack[nTos]; | ||
43295 | } | ||
43296 | pc = 0; | ||
43297 | /* Execute as much as we can */ | ||
43298 | for(;;){ | ||
43299 | /* Fetch the instruction to execute */ | ||
43300 | pInstr = &aInstr[pc]; | ||
43301 | rc = SXRET_OK; | ||
43302 | /* | ||
43303 | * What follows here is a massive switch statement where each case implements a | ||
43304 | * separate instruction in the virtual machine. If we follow the usual | ||
43305 | * indentation convention each case should be indented by 6 spaces. But | ||
43306 | * that is a lot of wasted space on the left margin. So the code within | ||
43307 | * the switch statement will break with convention and be flush-left. | ||
43308 | */ | ||
43309 | switch(pInstr->iOp){ | ||
43310 | /* | ||
43311 | * DONE: P1 * * | ||
43312 | * | ||
43313 | * Program execution completed: Clean up the mess left behind | ||
43314 | * and return immediately. | ||
43315 | */ | ||
43316 | case JX9_OP_DONE: | ||
43317 | if( pInstr->iP1 ){ | ||
43318 | #ifdef UNTRUST | ||
43319 | if( pTos < pStack ){ | ||
43320 | goto Abort; | ||
43321 | } | ||
43322 | #endif | ||
43323 | if( pResult ){ | ||
43324 | /* Execution result */ | ||
43325 | jx9MemObjStore(pTos, pResult); | ||
43326 | } | ||
43327 | VmPopOperand(&pTos, 1); | ||
43328 | } | ||
43329 | goto Done; | ||
43330 | /* | ||
43331 | * HALT: P1 * * | ||
43332 | * | ||
43333 | * Program execution aborted: Clean up the mess left behind | ||
43334 | * and abort immediately. | ||
43335 | */ | ||
43336 | case JX9_OP_HALT: | ||
43337 | if( pInstr->iP1 ){ | ||
43338 | #ifdef UNTRUST | ||
43339 | if( pTos < pStack ){ | ||
43340 | goto Abort; | ||
43341 | } | ||
43342 | #endif | ||
43343 | if( pTos->iFlags & MEMOBJ_STRING ){ | ||
43344 | if( SyBlobLength(&pTos->sBlob) > 0 ){ | ||
43345 | /* Output the exit message */ | ||
43346 | pVm->sVmConsumer.xConsumer(SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob), | ||
43347 | pVm->sVmConsumer.pUserData); | ||
43348 | /* Increment output length */ | ||
43349 | pVm->nOutputLen += SyBlobLength(&pTos->sBlob); | ||
43350 | } | ||
43351 | }else if(pTos->iFlags & MEMOBJ_INT ){ | ||
43352 | /* Record exit status */ | ||
43353 | pVm->iExitStatus = (sxi32)pTos->x.iVal; | ||
43354 | } | ||
43355 | VmPopOperand(&pTos, 1); | ||
43356 | } | ||
43357 | goto Abort; | ||
43358 | /* | ||
43359 | * JMP: * P2 * | ||
43360 | * | ||
43361 | * Unconditional jump: The next instruction executed will be | ||
43362 | * the one at index P2 from the beginning of the program. | ||
43363 | */ | ||
43364 | case JX9_OP_JMP: | ||
43365 | pc = pInstr->iP2 - 1; | ||
43366 | break; | ||
43367 | /* | ||
43368 | * JZ: P1 P2 * | ||
43369 | * | ||
43370 | * Take the jump if the top value is zero (FALSE jump).Pop the top most | ||
43371 | * entry in the stack if P1 is zero. | ||
43372 | */ | ||
43373 | case JX9_OP_JZ: | ||
43374 | #ifdef UNTRUST | ||
43375 | if( pTos < pStack ){ | ||
43376 | goto Abort; | ||
43377 | } | ||
43378 | #endif | ||
43379 | /* Get a boolean value */ | ||
43380 | if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
43381 | jx9MemObjToBool(pTos); | ||
43382 | } | ||
43383 | if( !pTos->x.iVal ){ | ||
43384 | /* Take the jump */ | ||
43385 | pc = pInstr->iP2 - 1; | ||
43386 | } | ||
43387 | if( !pInstr->iP1 ){ | ||
43388 | VmPopOperand(&pTos, 1); | ||
43389 | } | ||
43390 | break; | ||
43391 | /* | ||
43392 | * JNZ: P1 P2 * | ||
43393 | * | ||
43394 | * Take the jump if the top value is not zero (TRUE jump).Pop the top most | ||
43395 | * entry in the stack if P1 is zero. | ||
43396 | */ | ||
43397 | case JX9_OP_JNZ: | ||
43398 | #ifdef UNTRUST | ||
43399 | if( pTos < pStack ){ | ||
43400 | goto Abort; | ||
43401 | } | ||
43402 | #endif | ||
43403 | /* Get a boolean value */ | ||
43404 | if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
43405 | jx9MemObjToBool(pTos); | ||
43406 | } | ||
43407 | if( pTos->x.iVal ){ | ||
43408 | /* Take the jump */ | ||
43409 | pc = pInstr->iP2 - 1; | ||
43410 | } | ||
43411 | if( !pInstr->iP1 ){ | ||
43412 | VmPopOperand(&pTos, 1); | ||
43413 | } | ||
43414 | break; | ||
43415 | /* | ||
43416 | * NOOP: * * * | ||
43417 | * | ||
43418 | * Do nothing. This instruction is often useful as a jump | ||
43419 | * destination. | ||
43420 | */ | ||
43421 | case JX9_OP_NOOP: | ||
43422 | break; | ||
43423 | /* | ||
43424 | * POP: P1 * * | ||
43425 | * | ||
43426 | * Pop P1 elements from the operand stack. | ||
43427 | */ | ||
43428 | case JX9_OP_POP: { | ||
43429 | sxi32 n = pInstr->iP1; | ||
43430 | if( &pTos[-n+1] < pStack ){ | ||
43431 | /* TICKET 1433-51 Stack underflow must be handled at run-time */ | ||
43432 | n = (sxi32)(pTos - pStack); | ||
43433 | } | ||
43434 | VmPopOperand(&pTos, n); | ||
43435 | break; | ||
43436 | } | ||
43437 | /* | ||
43438 | * CVT_INT: * * * | ||
43439 | * | ||
43440 | * Force the top of the stack to be an integer. | ||
43441 | */ | ||
43442 | case JX9_OP_CVT_INT: | ||
43443 | #ifdef UNTRUST | ||
43444 | if( pTos < pStack ){ | ||
43445 | goto Abort; | ||
43446 | } | ||
43447 | #endif | ||
43448 | if((pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
43449 | jx9MemObjToInteger(pTos); | ||
43450 | } | ||
43451 | /* Invalidate any prior representation */ | ||
43452 | MemObjSetType(pTos, MEMOBJ_INT); | ||
43453 | break; | ||
43454 | /* | ||
43455 | * CVT_REAL: * * * | ||
43456 | * | ||
43457 | * Force the top of the stack to be a real. | ||
43458 | */ | ||
43459 | case JX9_OP_CVT_REAL: | ||
43460 | #ifdef UNTRUST | ||
43461 | if( pTos < pStack ){ | ||
43462 | goto Abort; | ||
43463 | } | ||
43464 | #endif | ||
43465 | if((pTos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
43466 | jx9MemObjToReal(pTos); | ||
43467 | } | ||
43468 | /* Invalidate any prior representation */ | ||
43469 | MemObjSetType(pTos, MEMOBJ_REAL); | ||
43470 | break; | ||
43471 | /* | ||
43472 | * CVT_STR: * * * | ||
43473 | * | ||
43474 | * Force the top of the stack to be a string. | ||
43475 | */ | ||
43476 | case JX9_OP_CVT_STR: | ||
43477 | #ifdef UNTRUST | ||
43478 | if( pTos < pStack ){ | ||
43479 | goto Abort; | ||
43480 | } | ||
43481 | #endif | ||
43482 | if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
43483 | jx9MemObjToString(pTos); | ||
43484 | } | ||
43485 | break; | ||
43486 | /* | ||
43487 | * CVT_BOOL: * * * | ||
43488 | * | ||
43489 | * Force the top of the stack to be a boolean. | ||
43490 | */ | ||
43491 | case JX9_OP_CVT_BOOL: | ||
43492 | #ifdef UNTRUST | ||
43493 | if( pTos < pStack ){ | ||
43494 | goto Abort; | ||
43495 | } | ||
43496 | #endif | ||
43497 | if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
43498 | jx9MemObjToBool(pTos); | ||
43499 | } | ||
43500 | break; | ||
43501 | /* | ||
43502 | * CVT_NULL: * * * | ||
43503 | * | ||
43504 | * Nullify the top of the stack. | ||
43505 | */ | ||
43506 | case JX9_OP_CVT_NULL: | ||
43507 | #ifdef UNTRUST | ||
43508 | if( pTos < pStack ){ | ||
43509 | goto Abort; | ||
43510 | } | ||
43511 | #endif | ||
43512 | jx9MemObjRelease(pTos); | ||
43513 | break; | ||
43514 | /* | ||
43515 | * CVT_NUMC: * * * | ||
43516 | * | ||
43517 | * Force the top of the stack to be a numeric type (integer, real or both). | ||
43518 | */ | ||
43519 | case JX9_OP_CVT_NUMC: | ||
43520 | #ifdef UNTRUST | ||
43521 | if( pTos < pStack ){ | ||
43522 | goto Abort; | ||
43523 | } | ||
43524 | #endif | ||
43525 | /* Force a numeric cast */ | ||
43526 | jx9MemObjToNumeric(pTos); | ||
43527 | break; | ||
43528 | /* | ||
43529 | * CVT_ARRAY: * * * | ||
43530 | * | ||
43531 | * Force the top of the stack to be a hashmap aka 'array'. | ||
43532 | */ | ||
43533 | case JX9_OP_CVT_ARRAY: | ||
43534 | #ifdef UNTRUST | ||
43535 | if( pTos < pStack ){ | ||
43536 | goto Abort; | ||
43537 | } | ||
43538 | #endif | ||
43539 | /* Force a hashmap cast */ | ||
43540 | rc = jx9MemObjToHashmap(pTos); | ||
43541 | if( rc != SXRET_OK ){ | ||
43542 | /* Not so fatal, emit a simple warning */ | ||
43543 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING, | ||
43544 | "JX9 engine is running out of memory while performing an array cast"); | ||
43545 | } | ||
43546 | break; | ||
43547 | /* | ||
43548 | * LOADC P1 P2 * | ||
43549 | * | ||
43550 | * Load a constant [i.e: JX9_EOL, JX9_OS, __TIME__, ...] indexed at P2 in the constant pool. | ||
43551 | * If P1 is set, then this constant is candidate for expansion via user installable callbacks. | ||
43552 | */ | ||
43553 | case JX9_OP_LOADC: { | ||
43554 | jx9_value *pObj; | ||
43555 | /* Reserve a room */ | ||
43556 | pTos++; | ||
43557 | if( (pObj = (jx9_value *)SySetAt(&pVm->aLitObj, pInstr->iP2)) != 0 ){ | ||
43558 | if( pInstr->iP1 == 1 && SyBlobLength(&pObj->sBlob) <= 64 ){ | ||
43559 | SyHashEntry *pEntry; | ||
43560 | /* Candidate for expansion via user defined callbacks */ | ||
43561 | pEntry = SyHashGet(&pVm->hConstant, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); | ||
43562 | if( pEntry ){ | ||
43563 | jx9_constant *pCons = (jx9_constant *)pEntry->pUserData; | ||
43564 | /* Set a NULL default value */ | ||
43565 | MemObjSetType(pTos, MEMOBJ_NULL); | ||
43566 | SyBlobReset(&pTos->sBlob); | ||
43567 | /* Invoke the callback and deal with the expanded value */ | ||
43568 | pCons->xExpand(pTos, pCons->pUserData); | ||
43569 | /* Mark as constant */ | ||
43570 | pTos->nIdx = SXU32_HIGH; | ||
43571 | break; | ||
43572 | } | ||
43573 | } | ||
43574 | jx9MemObjLoad(pObj, pTos); | ||
43575 | }else{ | ||
43576 | /* Set a NULL value */ | ||
43577 | MemObjSetType(pTos, MEMOBJ_NULL); | ||
43578 | } | ||
43579 | /* Mark as constant */ | ||
43580 | pTos->nIdx = SXU32_HIGH; | ||
43581 | break; | ||
43582 | } | ||
43583 | /* | ||
43584 | * LOAD: P1 * P3 | ||
43585 | * | ||
43586 | * Load a variable where it's name is taken from the top of the stack or | ||
43587 | * from the P3 operand. | ||
43588 | * If P1 is set, then perform a lookup only.In other words do not create | ||
43589 | * the variable if non existent and push the NULL constant instead. | ||
43590 | */ | ||
43591 | case JX9_OP_LOAD:{ | ||
43592 | jx9_value *pObj; | ||
43593 | SyString sName; | ||
43594 | if( pInstr->p3 == 0 ){ | ||
43595 | /* Take the variable name from the top of the stack */ | ||
43596 | #ifdef UNTRUST | ||
43597 | if( pTos < pStack ){ | ||
43598 | goto Abort; | ||
43599 | } | ||
43600 | #endif | ||
43601 | /* Force a string cast */ | ||
43602 | if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
43603 | jx9MemObjToString(pTos); | ||
43604 | } | ||
43605 | SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); | ||
43606 | }else{ | ||
43607 | SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3)); | ||
43608 | /* Reserve a room for the target object */ | ||
43609 | pTos++; | ||
43610 | } | ||
43611 | /* Extract the requested memory object */ | ||
43612 | pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, pInstr->iP1 != 1); | ||
43613 | if( pObj == 0 ){ | ||
43614 | if( pInstr->iP1 ){ | ||
43615 | /* Variable not found, load NULL */ | ||
43616 | if( !pInstr->p3 ){ | ||
43617 | jx9MemObjRelease(pTos); | ||
43618 | }else{ | ||
43619 | MemObjSetType(pTos, MEMOBJ_NULL); | ||
43620 | } | ||
43621 | pTos->nIdx = SXU32_HIGH; /* Mark as constant */ | ||
43622 | break; | ||
43623 | }else{ | ||
43624 | /* Fatal error */ | ||
43625 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName); | ||
43626 | goto Abort; | ||
43627 | } | ||
43628 | } | ||
43629 | /* Load variable contents */ | ||
43630 | jx9MemObjLoad(pObj, pTos); | ||
43631 | pTos->nIdx = pObj->nIdx; | ||
43632 | break; | ||
43633 | } | ||
43634 | /* | ||
43635 | * LOAD_MAP P1 * * | ||
43636 | * | ||
43637 | * Allocate a new empty hashmap (array in the JX9 jargon) and push it on the stack. | ||
43638 | * If the P1 operand is greater than zero then pop P1 elements from the | ||
43639 | * stack and insert them (key => value pair) in the new hashmap. | ||
43640 | */ | ||
43641 | case JX9_OP_LOAD_MAP: { | ||
43642 | jx9_hashmap *pMap; | ||
43643 | int is_json_object; /* TRUE if we are dealing with a JSON object */ | ||
43644 | int iIncr = 1; | ||
43645 | /* Allocate a new hashmap instance */ | ||
43646 | pMap = jx9NewHashmap(&(*pVm), 0, 0); | ||
43647 | if( pMap == 0 ){ | ||
43648 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, | ||
43649 | "Fatal, JX9 engine is running out of memory while loading JSON array/object at instruction #:%d", pc); | ||
43650 | goto Abort; | ||
43651 | } | ||
43652 | is_json_object = 0; | ||
43653 | if( pInstr->iP2 ){ | ||
43654 | /* JSON object, record that */ | ||
43655 | pMap->iFlags |= HASHMAP_JSON_OBJECT; | ||
43656 | is_json_object = 1; | ||
43657 | iIncr = 2; | ||
43658 | } | ||
43659 | if( pInstr->iP1 > 0 ){ | ||
43660 | jx9_value *pEntry = &pTos[-pInstr->iP1+1]; /* Point to the first entry */ | ||
43661 | /* Perform the insertion */ | ||
43662 | while( pEntry <= pTos ){ | ||
43663 | /* Standard insertion */ | ||
43664 | jx9HashmapInsert(pMap, | ||
43665 | is_json_object ? pEntry : 0 /* Automatic index assign */, | ||
43666 | is_json_object ? &pEntry[1] : pEntry | ||
43667 | ); | ||
43668 | /* Next pair on the stack */ | ||
43669 | pEntry += iIncr; | ||
43670 | } | ||
43671 | /* Pop P1 elements */ | ||
43672 | VmPopOperand(&pTos, pInstr->iP1); | ||
43673 | } | ||
43674 | /* Push the hashmap */ | ||
43675 | pTos++; | ||
43676 | pTos->x.pOther = pMap; | ||
43677 | MemObjSetType(pTos, MEMOBJ_HASHMAP); | ||
43678 | break; | ||
43679 | } | ||
43680 | /* | ||
43681 | * LOAD_IDX: P1 P2 * | ||
43682 | * | ||
43683 | * Load a hasmap entry where it's index (either numeric or string) is taken | ||
43684 | * from the stack. | ||
43685 | * If the index does not refer to a valid element, then push the NULL constant | ||
43686 | * instead. | ||
43687 | */ | ||
43688 | case JX9_OP_LOAD_IDX: { | ||
43689 | jx9_hashmap_node *pNode = 0; /* cc warning */ | ||
43690 | jx9_hashmap *pMap = 0; | ||
43691 | jx9_value *pIdx; | ||
43692 | pIdx = 0; | ||
43693 | if( pInstr->iP1 == 0 ){ | ||
43694 | if( !pInstr->iP2){ | ||
43695 | /* No available index, load NULL */ | ||
43696 | if( pTos >= pStack ){ | ||
43697 | jx9MemObjRelease(pTos); | ||
43698 | }else{ | ||
43699 | /* TICKET 1433-020: Empty stack */ | ||
43700 | pTos++; | ||
43701 | MemObjSetType(pTos, MEMOBJ_NULL); | ||
43702 | pTos->nIdx = SXU32_HIGH; | ||
43703 | } | ||
43704 | /* Emit a notice */ | ||
43705 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_NOTICE, | ||
43706 | "JSON Array/Object: Attempt to access an undefined member, JX9 is loading NULL"); | ||
43707 | break; | ||
43708 | } | ||
43709 | }else{ | ||
43710 | pIdx = pTos; | ||
43711 | pTos--; | ||
43712 | } | ||
43713 | if( pTos->iFlags & MEMOBJ_STRING ){ | ||
43714 | /* String access */ | ||
43715 | if( pIdx ){ | ||
43716 | sxu32 nOfft; | ||
43717 | if( (pIdx->iFlags & MEMOBJ_INT) == 0 ){ | ||
43718 | /* Force an int cast */ | ||
43719 | jx9MemObjToInteger(pIdx); | ||
43720 | } | ||
43721 | nOfft = (sxu32)pIdx->x.iVal; | ||
43722 | if( nOfft >= SyBlobLength(&pTos->sBlob) ){ | ||
43723 | /* Invalid offset, load null */ | ||
43724 | jx9MemObjRelease(pTos); | ||
43725 | }else{ | ||
43726 | const char *zData = (const char *)SyBlobData(&pTos->sBlob); | ||
43727 | int c = zData[nOfft]; | ||
43728 | jx9MemObjRelease(pTos); | ||
43729 | MemObjSetType(pTos, MEMOBJ_STRING); | ||
43730 | SyBlobAppend(&pTos->sBlob, (const void *)&c, sizeof(char)); | ||
43731 | } | ||
43732 | }else{ | ||
43733 | /* No available index, load NULL */ | ||
43734 | MemObjSetType(pTos, MEMOBJ_NULL); | ||
43735 | } | ||
43736 | break; | ||
43737 | } | ||
43738 | if( pInstr->iP2 && (pTos->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
43739 | if( pTos->nIdx != SXU32_HIGH ){ | ||
43740 | jx9_value *pObj; | ||
43741 | if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
43742 | jx9MemObjToHashmap(pObj); | ||
43743 | jx9MemObjLoad(pObj, pTos); | ||
43744 | } | ||
43745 | } | ||
43746 | } | ||
43747 | rc = SXERR_NOTFOUND; /* Assume the index is invalid */ | ||
43748 | if( pTos->iFlags & MEMOBJ_HASHMAP ){ | ||
43749 | /* Point to the hashmap */ | ||
43750 | pMap = (jx9_hashmap *)pTos->x.pOther; | ||
43751 | if( pIdx ){ | ||
43752 | /* Load the desired entry */ | ||
43753 | rc = jx9HashmapLookup(pMap, pIdx, &pNode); | ||
43754 | } | ||
43755 | if( rc != SXRET_OK && pInstr->iP2 ){ | ||
43756 | /* Create a new empty entry */ | ||
43757 | rc = jx9HashmapInsert(pMap, pIdx, 0); | ||
43758 | if( rc == SXRET_OK ){ | ||
43759 | /* Point to the last inserted entry */ | ||
43760 | pNode = pMap->pLast; | ||
43761 | } | ||
43762 | } | ||
43763 | } | ||
43764 | if( pIdx ){ | ||
43765 | jx9MemObjRelease(pIdx); | ||
43766 | } | ||
43767 | if( rc == SXRET_OK ){ | ||
43768 | /* Load entry contents */ | ||
43769 | if( pMap->iRef < 2 ){ | ||
43770 | /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy | ||
43771 | * of the entry value, rather than pointing to it. | ||
43772 | */ | ||
43773 | pTos->nIdx = SXU32_HIGH; | ||
43774 | jx9HashmapExtractNodeValue(pNode, pTos, TRUE); | ||
43775 | }else{ | ||
43776 | pTos->nIdx = pNode->nValIdx; | ||
43777 | jx9HashmapExtractNodeValue(pNode, pTos, FALSE); | ||
43778 | jx9HashmapUnref(pMap); | ||
43779 | } | ||
43780 | }else{ | ||
43781 | /* No such entry, load NULL */ | ||
43782 | jx9MemObjRelease(pTos); | ||
43783 | pTos->nIdx = SXU32_HIGH; | ||
43784 | } | ||
43785 | break; | ||
43786 | } | ||
43787 | /* | ||
43788 | * STORE * P2 P3 | ||
43789 | * | ||
43790 | * Perform a store (Assignment) operation. | ||
43791 | */ | ||
43792 | case JX9_OP_STORE: { | ||
43793 | jx9_value *pObj; | ||
43794 | SyString sName; | ||
43795 | #ifdef UNTRUST | ||
43796 | if( pTos < pStack ){ | ||
43797 | goto Abort; | ||
43798 | } | ||
43799 | #endif | ||
43800 | if( pInstr->iP2 ){ | ||
43801 | sxu32 nIdx; | ||
43802 | /* Member store operation */ | ||
43803 | nIdx = pTos->nIdx; | ||
43804 | VmPopOperand(&pTos, 1); | ||
43805 | if( nIdx == SXU32_HIGH ){ | ||
43806 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, | ||
43807 | "Cannot perform assignment on a constant object attribute, JX9 is loading NULL"); | ||
43808 | pTos->nIdx = SXU32_HIGH; | ||
43809 | }else{ | ||
43810 | /* Point to the desired memory object */ | ||
43811 | pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); | ||
43812 | if( pObj ){ | ||
43813 | /* Perform the store operation */ | ||
43814 | jx9MemObjStore(pTos, pObj); | ||
43815 | } | ||
43816 | } | ||
43817 | break; | ||
43818 | }else if( pInstr->p3 == 0 ){ | ||
43819 | /* Take the variable name from the next on the stack */ | ||
43820 | if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
43821 | /* Force a string cast */ | ||
43822 | jx9MemObjToString(pTos); | ||
43823 | } | ||
43824 | SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); | ||
43825 | pTos--; | ||
43826 | #ifdef UNTRUST | ||
43827 | if( pTos < pStack ){ | ||
43828 | goto Abort; | ||
43829 | } | ||
43830 | #endif | ||
43831 | }else{ | ||
43832 | SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3)); | ||
43833 | } | ||
43834 | /* Extract the desired variable and if not available dynamically create it */ | ||
43835 | pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, TRUE); | ||
43836 | if( pObj == 0 ){ | ||
43837 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, | ||
43838 | "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName); | ||
43839 | goto Abort; | ||
43840 | } | ||
43841 | if( !pInstr->p3 ){ | ||
43842 | jx9MemObjRelease(&pTos[1]); | ||
43843 | } | ||
43844 | /* Perform the store operation */ | ||
43845 | jx9MemObjStore(pTos, pObj); | ||
43846 | break; | ||
43847 | } | ||
43848 | /* | ||
43849 | * STORE_IDX: P1 * P3 | ||
43850 | * | ||
43851 | * Perfrom a store operation an a hashmap entry. | ||
43852 | */ | ||
43853 | case JX9_OP_STORE_IDX: { | ||
43854 | jx9_hashmap *pMap = 0; /* cc warning */ | ||
43855 | jx9_value *pKey; | ||
43856 | sxu32 nIdx; | ||
43857 | if( pInstr->iP1 ){ | ||
43858 | /* Key is next on stack */ | ||
43859 | pKey = pTos; | ||
43860 | pTos--; | ||
43861 | }else{ | ||
43862 | pKey = 0; | ||
43863 | } | ||
43864 | nIdx = pTos->nIdx; | ||
43865 | if( pTos->iFlags & MEMOBJ_HASHMAP ){ | ||
43866 | /* Hashmap already loaded */ | ||
43867 | pMap = (jx9_hashmap *)pTos->x.pOther; | ||
43868 | if( pMap->iRef < 2 ){ | ||
43869 | /* TICKET 1433-48: Prevent garbage collection */ | ||
43870 | pMap->iRef = 2; | ||
43871 | } | ||
43872 | }else{ | ||
43873 | jx9_value *pObj; | ||
43874 | pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); | ||
43875 | if( pObj == 0 ){ | ||
43876 | if( pKey ){ | ||
43877 | jx9MemObjRelease(pKey); | ||
43878 | } | ||
43879 | VmPopOperand(&pTos, 1); | ||
43880 | break; | ||
43881 | } | ||
43882 | /* Phase#1: Load the array */ | ||
43883 | if( (pObj->iFlags & MEMOBJ_STRING) ){ | ||
43884 | VmPopOperand(&pTos, 1); | ||
43885 | if( (pTos->iFlags&MEMOBJ_STRING) == 0 ){ | ||
43886 | /* Force a string cast */ | ||
43887 | jx9MemObjToString(pTos); | ||
43888 | } | ||
43889 | if( pKey == 0 ){ | ||
43890 | /* Append string */ | ||
43891 | if( SyBlobLength(&pTos->sBlob) > 0 ){ | ||
43892 | SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); | ||
43893 | } | ||
43894 | }else{ | ||
43895 | sxu32 nOfft; | ||
43896 | if((pKey->iFlags & MEMOBJ_INT)){ | ||
43897 | /* Force an int cast */ | ||
43898 | jx9MemObjToInteger(pKey); | ||
43899 | } | ||
43900 | nOfft = (sxu32)pKey->x.iVal; | ||
43901 | if( nOfft < SyBlobLength(&pObj->sBlob) && SyBlobLength(&pTos->sBlob) > 0 ){ | ||
43902 | const char *zBlob = (const char *)SyBlobData(&pTos->sBlob); | ||
43903 | char *zData = (char *)SyBlobData(&pObj->sBlob); | ||
43904 | zData[nOfft] = zBlob[0]; | ||
43905 | }else{ | ||
43906 | if( SyBlobLength(&pTos->sBlob) >= sizeof(char) ){ | ||
43907 | /* Perform an append operation */ | ||
43908 | SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), sizeof(char)); | ||
43909 | } | ||
43910 | } | ||
43911 | } | ||
43912 | if( pKey ){ | ||
43913 | jx9MemObjRelease(pKey); | ||
43914 | } | ||
43915 | break; | ||
43916 | }else if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
43917 | /* Force a hashmap cast */ | ||
43918 | rc = jx9MemObjToHashmap(pObj); | ||
43919 | if( rc != SXRET_OK ){ | ||
43920 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while creating a new array"); | ||
43921 | goto Abort; | ||
43922 | } | ||
43923 | } | ||
43924 | pMap = (jx9_hashmap *)pObj->x.pOther; | ||
43925 | } | ||
43926 | VmPopOperand(&pTos, 1); | ||
43927 | /* Phase#2: Perform the insertion */ | ||
43928 | jx9HashmapInsert(pMap, pKey, pTos); | ||
43929 | if( pKey ){ | ||
43930 | jx9MemObjRelease(pKey); | ||
43931 | } | ||
43932 | break; | ||
43933 | } | ||
43934 | /* | ||
43935 | * INCR: P1 * * | ||
43936 | * | ||
43937 | * Force a numeric cast and increment the top of the stack by 1. | ||
43938 | * If the P1 operand is set then perform a duplication of the top of | ||
43939 | * the stack and increment after that. | ||
43940 | */ | ||
43941 | case JX9_OP_INCR: | ||
43942 | #ifdef UNTRUST | ||
43943 | if( pTos < pStack ){ | ||
43944 | goto Abort; | ||
43945 | } | ||
43946 | #endif | ||
43947 | if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0 ){ | ||
43948 | if( pTos->nIdx != SXU32_HIGH ){ | ||
43949 | jx9_value *pObj; | ||
43950 | if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
43951 | /* Force a numeric cast */ | ||
43952 | jx9MemObjToNumeric(pObj); | ||
43953 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
43954 | pObj->x.rVal++; | ||
43955 | /* Try to get an integer representation */ | ||
43956 | jx9MemObjTryInteger(pTos); | ||
43957 | }else{ | ||
43958 | pObj->x.iVal++; | ||
43959 | MemObjSetType(pTos, MEMOBJ_INT); | ||
43960 | } | ||
43961 | if( pInstr->iP1 ){ | ||
43962 | /* Pre-icrement */ | ||
43963 | jx9MemObjStore(pObj, pTos); | ||
43964 | } | ||
43965 | } | ||
43966 | }else{ | ||
43967 | if( pInstr->iP1 ){ | ||
43968 | /* Force a numeric cast */ | ||
43969 | jx9MemObjToNumeric(pTos); | ||
43970 | /* Pre-increment */ | ||
43971 | if( pTos->iFlags & MEMOBJ_REAL ){ | ||
43972 | pTos->x.rVal++; | ||
43973 | /* Try to get an integer representation */ | ||
43974 | jx9MemObjTryInteger(pTos); | ||
43975 | }else{ | ||
43976 | pTos->x.iVal++; | ||
43977 | MemObjSetType(pTos, MEMOBJ_INT); | ||
43978 | } | ||
43979 | } | ||
43980 | } | ||
43981 | } | ||
43982 | break; | ||
43983 | /* | ||
43984 | * DECR: P1 * * | ||
43985 | * | ||
43986 | * Force a numeric cast and decrement the top of the stack by 1. | ||
43987 | * If the P1 operand is set then perform a duplication of the top of the stack | ||
43988 | * and decrement after that. | ||
43989 | */ | ||
43990 | case JX9_OP_DECR: | ||
43991 | #ifdef UNTRUST | ||
43992 | if( pTos < pStack ){ | ||
43993 | goto Abort; | ||
43994 | } | ||
43995 | #endif | ||
43996 | if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES|MEMOBJ_NULL)) == 0 ){ | ||
43997 | /* Force a numeric cast */ | ||
43998 | jx9MemObjToNumeric(pTos); | ||
43999 | if( pTos->nIdx != SXU32_HIGH ){ | ||
44000 | jx9_value *pObj; | ||
44001 | if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44002 | /* Force a numeric cast */ | ||
44003 | jx9MemObjToNumeric(pObj); | ||
44004 | if( pObj->iFlags & MEMOBJ_REAL ){ | ||
44005 | pObj->x.rVal--; | ||
44006 | /* Try to get an integer representation */ | ||
44007 | jx9MemObjTryInteger(pTos); | ||
44008 | }else{ | ||
44009 | pObj->x.iVal--; | ||
44010 | MemObjSetType(pTos, MEMOBJ_INT); | ||
44011 | } | ||
44012 | if( pInstr->iP1 ){ | ||
44013 | /* Pre-icrement */ | ||
44014 | jx9MemObjStore(pObj, pTos); | ||
44015 | } | ||
44016 | } | ||
44017 | }else{ | ||
44018 | if( pInstr->iP1 ){ | ||
44019 | /* Pre-increment */ | ||
44020 | if( pTos->iFlags & MEMOBJ_REAL ){ | ||
44021 | pTos->x.rVal--; | ||
44022 | /* Try to get an integer representation */ | ||
44023 | jx9MemObjTryInteger(pTos); | ||
44024 | }else{ | ||
44025 | pTos->x.iVal--; | ||
44026 | MemObjSetType(pTos, MEMOBJ_INT); | ||
44027 | } | ||
44028 | } | ||
44029 | } | ||
44030 | } | ||
44031 | break; | ||
44032 | /* | ||
44033 | * UMINUS: * * * | ||
44034 | * | ||
44035 | * Perform a unary minus operation. | ||
44036 | */ | ||
44037 | case JX9_OP_UMINUS: | ||
44038 | #ifdef UNTRUST | ||
44039 | if( pTos < pStack ){ | ||
44040 | goto Abort; | ||
44041 | } | ||
44042 | #endif | ||
44043 | /* Force a numeric (integer, real or both) cast */ | ||
44044 | jx9MemObjToNumeric(pTos); | ||
44045 | if( pTos->iFlags & MEMOBJ_REAL ){ | ||
44046 | pTos->x.rVal = -pTos->x.rVal; | ||
44047 | } | ||
44048 | if( pTos->iFlags & MEMOBJ_INT ){ | ||
44049 | pTos->x.iVal = -pTos->x.iVal; | ||
44050 | } | ||
44051 | break; | ||
44052 | /* | ||
44053 | * UPLUS: * * * | ||
44054 | * | ||
44055 | * Perform a unary plus operation. | ||
44056 | */ | ||
44057 | case JX9_OP_UPLUS: | ||
44058 | #ifdef UNTRUST | ||
44059 | if( pTos < pStack ){ | ||
44060 | goto Abort; | ||
44061 | } | ||
44062 | #endif | ||
44063 | /* Force a numeric (integer, real or both) cast */ | ||
44064 | jx9MemObjToNumeric(pTos); | ||
44065 | if( pTos->iFlags & MEMOBJ_REAL ){ | ||
44066 | pTos->x.rVal = +pTos->x.rVal; | ||
44067 | } | ||
44068 | if( pTos->iFlags & MEMOBJ_INT ){ | ||
44069 | pTos->x.iVal = +pTos->x.iVal; | ||
44070 | } | ||
44071 | break; | ||
44072 | /* | ||
44073 | * OP_LNOT: * * * | ||
44074 | * | ||
44075 | * Interpret the top of the stack as a boolean value. Replace it | ||
44076 | * with its complement. | ||
44077 | */ | ||
44078 | case JX9_OP_LNOT: | ||
44079 | #ifdef UNTRUST | ||
44080 | if( pTos < pStack ){ | ||
44081 | goto Abort; | ||
44082 | } | ||
44083 | #endif | ||
44084 | /* Force a boolean cast */ | ||
44085 | if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
44086 | jx9MemObjToBool(pTos); | ||
44087 | } | ||
44088 | pTos->x.iVal = !pTos->x.iVal; | ||
44089 | break; | ||
44090 | /* | ||
44091 | * OP_BITNOT: * * * | ||
44092 | * | ||
44093 | * Interpret the top of the stack as an value.Replace it | ||
44094 | * with its ones-complement. | ||
44095 | */ | ||
44096 | case JX9_OP_BITNOT: | ||
44097 | #ifdef UNTRUST | ||
44098 | if( pTos < pStack ){ | ||
44099 | goto Abort; | ||
44100 | } | ||
44101 | #endif | ||
44102 | /* Force an integer cast */ | ||
44103 | if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44104 | jx9MemObjToInteger(pTos); | ||
44105 | } | ||
44106 | pTos->x.iVal = ~pTos->x.iVal; | ||
44107 | break; | ||
44108 | /* OP_MUL * * * | ||
44109 | * OP_MUL_STORE * * * | ||
44110 | * | ||
44111 | * Pop the top two elements from the stack, multiply them together, | ||
44112 | * and push the result back onto the stack. | ||
44113 | */ | ||
44114 | case JX9_OP_MUL: | ||
44115 | case JX9_OP_MUL_STORE: { | ||
44116 | jx9_value *pNos = &pTos[-1]; | ||
44117 | /* Force the operand to be numeric */ | ||
44118 | #ifdef UNTRUST | ||
44119 | if( pNos < pStack ){ | ||
44120 | goto Abort; | ||
44121 | } | ||
44122 | #endif | ||
44123 | jx9MemObjToNumeric(pTos); | ||
44124 | jx9MemObjToNumeric(pNos); | ||
44125 | /* Perform the requested operation */ | ||
44126 | if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ | ||
44127 | /* Floating point arithemic */ | ||
44128 | jx9_real a, b, r; | ||
44129 | if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44130 | jx9MemObjToReal(pTos); | ||
44131 | } | ||
44132 | if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44133 | jx9MemObjToReal(pNos); | ||
44134 | } | ||
44135 | a = pNos->x.rVal; | ||
44136 | b = pTos->x.rVal; | ||
44137 | r = a * b; | ||
44138 | /* Push the result */ | ||
44139 | pNos->x.rVal = r; | ||
44140 | MemObjSetType(pNos, MEMOBJ_REAL); | ||
44141 | /* Try to get an integer representation */ | ||
44142 | jx9MemObjTryInteger(pNos); | ||
44143 | }else{ | ||
44144 | /* Integer arithmetic */ | ||
44145 | sxi64 a, b, r; | ||
44146 | a = pNos->x.iVal; | ||
44147 | b = pTos->x.iVal; | ||
44148 | r = a * b; | ||
44149 | /* Push the result */ | ||
44150 | pNos->x.iVal = r; | ||
44151 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44152 | } | ||
44153 | if( pInstr->iOp == JX9_OP_MUL_STORE ){ | ||
44154 | jx9_value *pObj; | ||
44155 | if( pTos->nIdx == SXU32_HIGH ){ | ||
44156 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44157 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44158 | jx9MemObjStore(pNos, pObj); | ||
44159 | } | ||
44160 | } | ||
44161 | VmPopOperand(&pTos, 1); | ||
44162 | break; | ||
44163 | } | ||
44164 | /* OP_ADD * * * | ||
44165 | * | ||
44166 | * Pop the top two elements from the stack, add them together, | ||
44167 | * and push the result back onto the stack. | ||
44168 | */ | ||
44169 | case JX9_OP_ADD:{ | ||
44170 | jx9_value *pNos = &pTos[-1]; | ||
44171 | #ifdef UNTRUST | ||
44172 | if( pNos < pStack ){ | ||
44173 | goto Abort; | ||
44174 | } | ||
44175 | #endif | ||
44176 | /* Perform the addition */ | ||
44177 | jx9MemObjAdd(pNos, pTos, FALSE); | ||
44178 | VmPopOperand(&pTos, 1); | ||
44179 | break; | ||
44180 | } | ||
44181 | /* | ||
44182 | * OP_ADD_STORE * * * | ||
44183 | * | ||
44184 | * Pop the top two elements from the stack, add them together, | ||
44185 | * and push the result back onto the stack. | ||
44186 | */ | ||
44187 | case JX9_OP_ADD_STORE:{ | ||
44188 | jx9_value *pNos = &pTos[-1]; | ||
44189 | jx9_value *pObj; | ||
44190 | sxu32 nIdx; | ||
44191 | #ifdef UNTRUST | ||
44192 | if( pNos < pStack ){ | ||
44193 | goto Abort; | ||
44194 | } | ||
44195 | #endif | ||
44196 | /* Perform the addition */ | ||
44197 | nIdx = pTos->nIdx; | ||
44198 | jx9MemObjAdd(pTos, pNos, TRUE); | ||
44199 | /* Peform the store operation */ | ||
44200 | if( nIdx == SXU32_HIGH ){ | ||
44201 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44202 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx)) != 0 ){ | ||
44203 | jx9MemObjStore(pTos, pObj); | ||
44204 | } | ||
44205 | /* Ticket 1433-35: Perform a stack dup */ | ||
44206 | jx9MemObjStore(pTos, pNos); | ||
44207 | VmPopOperand(&pTos, 1); | ||
44208 | break; | ||
44209 | } | ||
44210 | /* OP_SUB * * * | ||
44211 | * | ||
44212 | * Pop the top two elements from the stack, subtract the | ||
44213 | * first (what was next on the stack) from the second (the | ||
44214 | * top of the stack) and push the result back onto the stack. | ||
44215 | */ | ||
44216 | case JX9_OP_SUB: { | ||
44217 | jx9_value *pNos = &pTos[-1]; | ||
44218 | #ifdef UNTRUST | ||
44219 | if( pNos < pStack ){ | ||
44220 | goto Abort; | ||
44221 | } | ||
44222 | #endif | ||
44223 | if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ | ||
44224 | /* Floating point arithemic */ | ||
44225 | jx9_real a, b, r; | ||
44226 | if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44227 | jx9MemObjToReal(pTos); | ||
44228 | } | ||
44229 | if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44230 | jx9MemObjToReal(pNos); | ||
44231 | } | ||
44232 | a = pNos->x.rVal; | ||
44233 | b = pTos->x.rVal; | ||
44234 | r = a - b; | ||
44235 | /* Push the result */ | ||
44236 | pNos->x.rVal = r; | ||
44237 | MemObjSetType(pNos, MEMOBJ_REAL); | ||
44238 | /* Try to get an integer representation */ | ||
44239 | jx9MemObjTryInteger(pNos); | ||
44240 | }else{ | ||
44241 | /* Integer arithmetic */ | ||
44242 | sxi64 a, b, r; | ||
44243 | a = pNos->x.iVal; | ||
44244 | b = pTos->x.iVal; | ||
44245 | r = a - b; | ||
44246 | /* Push the result */ | ||
44247 | pNos->x.iVal = r; | ||
44248 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44249 | } | ||
44250 | VmPopOperand(&pTos, 1); | ||
44251 | break; | ||
44252 | } | ||
44253 | /* OP_SUB_STORE * * * | ||
44254 | * | ||
44255 | * Pop the top two elements from the stack, subtract the | ||
44256 | * first (what was next on the stack) from the second (the | ||
44257 | * top of the stack) and push the result back onto the stack. | ||
44258 | */ | ||
44259 | case JX9_OP_SUB_STORE: { | ||
44260 | jx9_value *pNos = &pTos[-1]; | ||
44261 | jx9_value *pObj; | ||
44262 | #ifdef UNTRUST | ||
44263 | if( pNos < pStack ){ | ||
44264 | goto Abort; | ||
44265 | } | ||
44266 | #endif | ||
44267 | if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ | ||
44268 | /* Floating point arithemic */ | ||
44269 | jx9_real a, b, r; | ||
44270 | if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44271 | jx9MemObjToReal(pTos); | ||
44272 | } | ||
44273 | if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44274 | jx9MemObjToReal(pNos); | ||
44275 | } | ||
44276 | a = pTos->x.rVal; | ||
44277 | b = pNos->x.rVal; | ||
44278 | r = a - b; | ||
44279 | /* Push the result */ | ||
44280 | pNos->x.rVal = r; | ||
44281 | MemObjSetType(pNos, MEMOBJ_REAL); | ||
44282 | /* Try to get an integer representation */ | ||
44283 | jx9MemObjTryInteger(pNos); | ||
44284 | }else{ | ||
44285 | /* Integer arithmetic */ | ||
44286 | sxi64 a, b, r; | ||
44287 | a = pTos->x.iVal; | ||
44288 | b = pNos->x.iVal; | ||
44289 | r = a - b; | ||
44290 | /* Push the result */ | ||
44291 | pNos->x.iVal = r; | ||
44292 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44293 | } | ||
44294 | if( pTos->nIdx == SXU32_HIGH ){ | ||
44295 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44296 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44297 | jx9MemObjStore(pNos, pObj); | ||
44298 | } | ||
44299 | VmPopOperand(&pTos, 1); | ||
44300 | break; | ||
44301 | } | ||
44302 | |||
44303 | /* | ||
44304 | * OP_MOD * * * | ||
44305 | * | ||
44306 | * Pop the top two elements from the stack, divide the | ||
44307 | * first (what was next on the stack) from the second (the | ||
44308 | * top of the stack) and push the remainder after division | ||
44309 | * onto the stack. | ||
44310 | * Note: Only integer arithemtic is allowed. | ||
44311 | */ | ||
44312 | case JX9_OP_MOD:{ | ||
44313 | jx9_value *pNos = &pTos[-1]; | ||
44314 | sxi64 a, b, r; | ||
44315 | #ifdef UNTRUST | ||
44316 | if( pNos < pStack ){ | ||
44317 | goto Abort; | ||
44318 | } | ||
44319 | #endif | ||
44320 | /* Force the operands to be integer */ | ||
44321 | if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44322 | jx9MemObjToInteger(pTos); | ||
44323 | } | ||
44324 | if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44325 | jx9MemObjToInteger(pNos); | ||
44326 | } | ||
44327 | /* Perform the requested operation */ | ||
44328 | a = pNos->x.iVal; | ||
44329 | b = pTos->x.iVal; | ||
44330 | if( b == 0 ){ | ||
44331 | r = 0; | ||
44332 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a); | ||
44333 | /* goto Abort; */ | ||
44334 | }else{ | ||
44335 | r = a%b; | ||
44336 | } | ||
44337 | /* Push the result */ | ||
44338 | pNos->x.iVal = r; | ||
44339 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44340 | VmPopOperand(&pTos, 1); | ||
44341 | break; | ||
44342 | } | ||
44343 | /* | ||
44344 | * OP_MOD_STORE * * * | ||
44345 | * | ||
44346 | * Pop the top two elements from the stack, divide the | ||
44347 | * first (what was next on the stack) from the second (the | ||
44348 | * top of the stack) and push the remainder after division | ||
44349 | * onto the stack. | ||
44350 | * Note: Only integer arithemtic is allowed. | ||
44351 | */ | ||
44352 | case JX9_OP_MOD_STORE: { | ||
44353 | jx9_value *pNos = &pTos[-1]; | ||
44354 | jx9_value *pObj; | ||
44355 | sxi64 a, b, r; | ||
44356 | #ifdef UNTRUST | ||
44357 | if( pNos < pStack ){ | ||
44358 | goto Abort; | ||
44359 | } | ||
44360 | #endif | ||
44361 | /* Force the operands to be integer */ | ||
44362 | if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44363 | jx9MemObjToInteger(pTos); | ||
44364 | } | ||
44365 | if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44366 | jx9MemObjToInteger(pNos); | ||
44367 | } | ||
44368 | /* Perform the requested operation */ | ||
44369 | a = pTos->x.iVal; | ||
44370 | b = pNos->x.iVal; | ||
44371 | if( b == 0 ){ | ||
44372 | r = 0; | ||
44373 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a); | ||
44374 | /* goto Abort; */ | ||
44375 | }else{ | ||
44376 | r = a%b; | ||
44377 | } | ||
44378 | /* Push the result */ | ||
44379 | pNos->x.iVal = r; | ||
44380 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44381 | if( pTos->nIdx == SXU32_HIGH ){ | ||
44382 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44383 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44384 | jx9MemObjStore(pNos, pObj); | ||
44385 | } | ||
44386 | VmPopOperand(&pTos, 1); | ||
44387 | break; | ||
44388 | } | ||
44389 | /* | ||
44390 | * OP_DIV * * * | ||
44391 | * | ||
44392 | * Pop the top two elements from the stack, divide the | ||
44393 | * first (what was next on the stack) from the second (the | ||
44394 | * top of the stack) and push the result onto the stack. | ||
44395 | * Note: Only floating point arithemtic is allowed. | ||
44396 | */ | ||
44397 | case JX9_OP_DIV:{ | ||
44398 | jx9_value *pNos = &pTos[-1]; | ||
44399 | jx9_real a, b, r; | ||
44400 | #ifdef UNTRUST | ||
44401 | if( pNos < pStack ){ | ||
44402 | goto Abort; | ||
44403 | } | ||
44404 | #endif | ||
44405 | /* Force the operands to be real */ | ||
44406 | if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44407 | jx9MemObjToReal(pTos); | ||
44408 | } | ||
44409 | if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44410 | jx9MemObjToReal(pNos); | ||
44411 | } | ||
44412 | /* Perform the requested operation */ | ||
44413 | a = pNos->x.rVal; | ||
44414 | b = pTos->x.rVal; | ||
44415 | if( b == 0 ){ | ||
44416 | /* Division by zero */ | ||
44417 | r = 0; | ||
44418 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Division by zero"); | ||
44419 | /* goto Abort; */ | ||
44420 | }else{ | ||
44421 | r = a/b; | ||
44422 | /* Push the result */ | ||
44423 | pNos->x.rVal = r; | ||
44424 | MemObjSetType(pNos, MEMOBJ_REAL); | ||
44425 | /* Try to get an integer representation */ | ||
44426 | jx9MemObjTryInteger(pNos); | ||
44427 | } | ||
44428 | VmPopOperand(&pTos, 1); | ||
44429 | break; | ||
44430 | } | ||
44431 | /* | ||
44432 | * OP_DIV_STORE * * * | ||
44433 | * | ||
44434 | * Pop the top two elements from the stack, divide the | ||
44435 | * first (what was next on the stack) from the second (the | ||
44436 | * top of the stack) and push the result onto the stack. | ||
44437 | * Note: Only floating point arithemtic is allowed. | ||
44438 | */ | ||
44439 | case JX9_OP_DIV_STORE:{ | ||
44440 | jx9_value *pNos = &pTos[-1]; | ||
44441 | jx9_value *pObj; | ||
44442 | jx9_real a, b, r; | ||
44443 | #ifdef UNTRUST | ||
44444 | if( pNos < pStack ){ | ||
44445 | goto Abort; | ||
44446 | } | ||
44447 | #endif | ||
44448 | /* Force the operands to be real */ | ||
44449 | if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44450 | jx9MemObjToReal(pTos); | ||
44451 | } | ||
44452 | if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ | ||
44453 | jx9MemObjToReal(pNos); | ||
44454 | } | ||
44455 | /* Perform the requested operation */ | ||
44456 | a = pTos->x.rVal; | ||
44457 | b = pNos->x.rVal; | ||
44458 | if( b == 0 ){ | ||
44459 | /* Division by zero */ | ||
44460 | r = 0; | ||
44461 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd/0", a); | ||
44462 | /* goto Abort; */ | ||
44463 | }else{ | ||
44464 | r = a/b; | ||
44465 | /* Push the result */ | ||
44466 | pNos->x.rVal = r; | ||
44467 | MemObjSetType(pNos, MEMOBJ_REAL); | ||
44468 | /* Try to get an integer representation */ | ||
44469 | jx9MemObjTryInteger(pNos); | ||
44470 | } | ||
44471 | if( pTos->nIdx == SXU32_HIGH ){ | ||
44472 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44473 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44474 | jx9MemObjStore(pNos, pObj); | ||
44475 | } | ||
44476 | VmPopOperand(&pTos, 1); | ||
44477 | break; | ||
44478 | } | ||
44479 | /* OP_BAND * * * | ||
44480 | * | ||
44481 | * Pop the top two elements from the stack. Convert both elements | ||
44482 | * to integers. Push back onto the stack the bit-wise AND of the | ||
44483 | * two elements. | ||
44484 | */ | ||
44485 | /* OP_BOR * * * | ||
44486 | * | ||
44487 | * Pop the top two elements from the stack. Convert both elements | ||
44488 | * to integers. Push back onto the stack the bit-wise OR of the | ||
44489 | * two elements. | ||
44490 | */ | ||
44491 | /* OP_BXOR * * * | ||
44492 | * | ||
44493 | * Pop the top two elements from the stack. Convert both elements | ||
44494 | * to integers. Push back onto the stack the bit-wise XOR of the | ||
44495 | * two elements. | ||
44496 | */ | ||
44497 | case JX9_OP_BAND: | ||
44498 | case JX9_OP_BOR: | ||
44499 | case JX9_OP_BXOR:{ | ||
44500 | jx9_value *pNos = &pTos[-1]; | ||
44501 | sxi64 a, b, r; | ||
44502 | #ifdef UNTRUST | ||
44503 | if( pNos < pStack ){ | ||
44504 | goto Abort; | ||
44505 | } | ||
44506 | #endif | ||
44507 | /* Force the operands to be integer */ | ||
44508 | if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44509 | jx9MemObjToInteger(pTos); | ||
44510 | } | ||
44511 | if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44512 | jx9MemObjToInteger(pNos); | ||
44513 | } | ||
44514 | /* Perform the requested operation */ | ||
44515 | a = pNos->x.iVal; | ||
44516 | b = pTos->x.iVal; | ||
44517 | switch(pInstr->iOp){ | ||
44518 | case JX9_OP_BOR_STORE: | ||
44519 | case JX9_OP_BOR: r = a|b; break; | ||
44520 | case JX9_OP_BXOR_STORE: | ||
44521 | case JX9_OP_BXOR: r = a^b; break; | ||
44522 | case JX9_OP_BAND_STORE: | ||
44523 | case JX9_OP_BAND: | ||
44524 | default: r = a&b; break; | ||
44525 | } | ||
44526 | /* Push the result */ | ||
44527 | pNos->x.iVal = r; | ||
44528 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44529 | VmPopOperand(&pTos, 1); | ||
44530 | break; | ||
44531 | } | ||
44532 | /* OP_BAND_STORE * * * | ||
44533 | * | ||
44534 | * Pop the top two elements from the stack. Convert both elements | ||
44535 | * to integers. Push back onto the stack the bit-wise AND of the | ||
44536 | * two elements. | ||
44537 | */ | ||
44538 | /* OP_BOR_STORE * * * | ||
44539 | * | ||
44540 | * Pop the top two elements from the stack. Convert both elements | ||
44541 | * to integers. Push back onto the stack the bit-wise OR of the | ||
44542 | * two elements. | ||
44543 | */ | ||
44544 | /* OP_BXOR_STORE * * * | ||
44545 | * | ||
44546 | * Pop the top two elements from the stack. Convert both elements | ||
44547 | * to integers. Push back onto the stack the bit-wise XOR of the | ||
44548 | * two elements. | ||
44549 | */ | ||
44550 | case JX9_OP_BAND_STORE: | ||
44551 | case JX9_OP_BOR_STORE: | ||
44552 | case JX9_OP_BXOR_STORE:{ | ||
44553 | jx9_value *pNos = &pTos[-1]; | ||
44554 | jx9_value *pObj; | ||
44555 | sxi64 a, b, r; | ||
44556 | #ifdef UNTRUST | ||
44557 | if( pNos < pStack ){ | ||
44558 | goto Abort; | ||
44559 | } | ||
44560 | #endif | ||
44561 | /* Force the operands to be integer */ | ||
44562 | if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44563 | jx9MemObjToInteger(pTos); | ||
44564 | } | ||
44565 | if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44566 | jx9MemObjToInteger(pNos); | ||
44567 | } | ||
44568 | /* Perform the requested operation */ | ||
44569 | a = pTos->x.iVal; | ||
44570 | b = pNos->x.iVal; | ||
44571 | switch(pInstr->iOp){ | ||
44572 | case JX9_OP_BOR_STORE: | ||
44573 | case JX9_OP_BOR: r = a|b; break; | ||
44574 | case JX9_OP_BXOR_STORE: | ||
44575 | case JX9_OP_BXOR: r = a^b; break; | ||
44576 | case JX9_OP_BAND_STORE: | ||
44577 | case JX9_OP_BAND: | ||
44578 | default: r = a&b; break; | ||
44579 | } | ||
44580 | /* Push the result */ | ||
44581 | pNos->x.iVal = r; | ||
44582 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44583 | if( pTos->nIdx == SXU32_HIGH ){ | ||
44584 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44585 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44586 | jx9MemObjStore(pNos, pObj); | ||
44587 | } | ||
44588 | VmPopOperand(&pTos, 1); | ||
44589 | break; | ||
44590 | } | ||
44591 | /* OP_SHL * * * | ||
44592 | * | ||
44593 | * Pop the top two elements from the stack. Convert both elements | ||
44594 | * to integers. Push back onto the stack the second element shifted | ||
44595 | * left by N bits where N is the top element on the stack. | ||
44596 | * Note: Only integer arithmetic is allowed. | ||
44597 | */ | ||
44598 | /* OP_SHR * * * | ||
44599 | * | ||
44600 | * Pop the top two elements from the stack. Convert both elements | ||
44601 | * to integers. Push back onto the stack the second element shifted | ||
44602 | * right by N bits where N is the top element on the stack. | ||
44603 | * Note: Only integer arithmetic is allowed. | ||
44604 | */ | ||
44605 | case JX9_OP_SHL: | ||
44606 | case JX9_OP_SHR: { | ||
44607 | jx9_value *pNos = &pTos[-1]; | ||
44608 | sxi64 a, r; | ||
44609 | sxi32 b; | ||
44610 | #ifdef UNTRUST | ||
44611 | if( pNos < pStack ){ | ||
44612 | goto Abort; | ||
44613 | } | ||
44614 | #endif | ||
44615 | /* Force the operands to be integer */ | ||
44616 | if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44617 | jx9MemObjToInteger(pTos); | ||
44618 | } | ||
44619 | if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44620 | jx9MemObjToInteger(pNos); | ||
44621 | } | ||
44622 | /* Perform the requested operation */ | ||
44623 | a = pNos->x.iVal; | ||
44624 | b = (sxi32)pTos->x.iVal; | ||
44625 | if( pInstr->iOp == JX9_OP_SHL ){ | ||
44626 | r = a << b; | ||
44627 | }else{ | ||
44628 | r = a >> b; | ||
44629 | } | ||
44630 | /* Push the result */ | ||
44631 | pNos->x.iVal = r; | ||
44632 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44633 | VmPopOperand(&pTos, 1); | ||
44634 | break; | ||
44635 | } | ||
44636 | /* OP_SHL_STORE * * * | ||
44637 | * | ||
44638 | * Pop the top two elements from the stack. Convert both elements | ||
44639 | * to integers. Push back onto the stack the second element shifted | ||
44640 | * left by N bits where N is the top element on the stack. | ||
44641 | * Note: Only integer arithmetic is allowed. | ||
44642 | */ | ||
44643 | /* OP_SHR_STORE * * * | ||
44644 | * | ||
44645 | * Pop the top two elements from the stack. Convert both elements | ||
44646 | * to integers. Push back onto the stack the second element shifted | ||
44647 | * right by N bits where N is the top element on the stack. | ||
44648 | * Note: Only integer arithmetic is allowed. | ||
44649 | */ | ||
44650 | case JX9_OP_SHL_STORE: | ||
44651 | case JX9_OP_SHR_STORE: { | ||
44652 | jx9_value *pNos = &pTos[-1]; | ||
44653 | jx9_value *pObj; | ||
44654 | sxi64 a, r; | ||
44655 | sxi32 b; | ||
44656 | #ifdef UNTRUST | ||
44657 | if( pNos < pStack ){ | ||
44658 | goto Abort; | ||
44659 | } | ||
44660 | #endif | ||
44661 | /* Force the operands to be integer */ | ||
44662 | if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44663 | jx9MemObjToInteger(pTos); | ||
44664 | } | ||
44665 | if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ | ||
44666 | jx9MemObjToInteger(pNos); | ||
44667 | } | ||
44668 | /* Perform the requested operation */ | ||
44669 | a = pTos->x.iVal; | ||
44670 | b = (sxi32)pNos->x.iVal; | ||
44671 | if( pInstr->iOp == JX9_OP_SHL_STORE ){ | ||
44672 | r = a << b; | ||
44673 | }else{ | ||
44674 | r = a >> b; | ||
44675 | } | ||
44676 | /* Push the result */ | ||
44677 | pNos->x.iVal = r; | ||
44678 | MemObjSetType(pNos, MEMOBJ_INT); | ||
44679 | if( pTos->nIdx == SXU32_HIGH ){ | ||
44680 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44681 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44682 | jx9MemObjStore(pNos, pObj); | ||
44683 | } | ||
44684 | VmPopOperand(&pTos, 1); | ||
44685 | break; | ||
44686 | } | ||
44687 | /* CAT: P1 * * | ||
44688 | * | ||
44689 | * Pop P1 elements from the stack. Concatenate them togeher and push the result | ||
44690 | * back. | ||
44691 | */ | ||
44692 | case JX9_OP_CAT:{ | ||
44693 | jx9_value *pNos, *pCur; | ||
44694 | if( pInstr->iP1 < 1 ){ | ||
44695 | pNos = &pTos[-1]; | ||
44696 | }else{ | ||
44697 | pNos = &pTos[-pInstr->iP1+1]; | ||
44698 | } | ||
44699 | #ifdef UNTRUST | ||
44700 | if( pNos < pStack ){ | ||
44701 | goto Abort; | ||
44702 | } | ||
44703 | #endif | ||
44704 | /* Force a string cast */ | ||
44705 | if( (pNos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
44706 | jx9MemObjToString(pNos); | ||
44707 | } | ||
44708 | pCur = &pNos[1]; | ||
44709 | while( pCur <= pTos ){ | ||
44710 | if( (pCur->iFlags & MEMOBJ_STRING) == 0 ){ | ||
44711 | jx9MemObjToString(pCur); | ||
44712 | } | ||
44713 | /* Perform the concatenation */ | ||
44714 | if( SyBlobLength(&pCur->sBlob) > 0 ){ | ||
44715 | jx9MemObjStringAppend(pNos, (const char *)SyBlobData(&pCur->sBlob), SyBlobLength(&pCur->sBlob)); | ||
44716 | } | ||
44717 | SyBlobRelease(&pCur->sBlob); | ||
44718 | pCur++; | ||
44719 | } | ||
44720 | pTos = pNos; | ||
44721 | break; | ||
44722 | } | ||
44723 | /* CAT_STORE: * * * | ||
44724 | * | ||
44725 | * Pop two elements from the stack. Concatenate them togeher and push the result | ||
44726 | * back. | ||
44727 | */ | ||
44728 | case JX9_OP_CAT_STORE:{ | ||
44729 | jx9_value *pNos = &pTos[-1]; | ||
44730 | jx9_value *pObj; | ||
44731 | #ifdef UNTRUST | ||
44732 | if( pNos < pStack ){ | ||
44733 | goto Abort; | ||
44734 | } | ||
44735 | #endif | ||
44736 | if((pTos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
44737 | /* Force a string cast */ | ||
44738 | jx9MemObjToString(pTos); | ||
44739 | } | ||
44740 | if((pNos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
44741 | /* Force a string cast */ | ||
44742 | jx9MemObjToString(pNos); | ||
44743 | } | ||
44744 | /* Perform the concatenation (Reverse order) */ | ||
44745 | if( SyBlobLength(&pNos->sBlob) > 0 ){ | ||
44746 | jx9MemObjStringAppend(pTos, (const char *)SyBlobData(&pNos->sBlob), SyBlobLength(&pNos->sBlob)); | ||
44747 | } | ||
44748 | /* Perform the store operation */ | ||
44749 | if( pTos->nIdx == SXU32_HIGH ){ | ||
44750 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); | ||
44751 | }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ | ||
44752 | jx9MemObjStore(pTos, pObj); | ||
44753 | } | ||
44754 | jx9MemObjStore(pTos, pNos); | ||
44755 | VmPopOperand(&pTos, 1); | ||
44756 | break; | ||
44757 | } | ||
44758 | /* OP_AND: * * * | ||
44759 | * | ||
44760 | * Pop two values off the stack. Take the logical AND of the | ||
44761 | * two values and push the resulting boolean value back onto the | ||
44762 | * stack. | ||
44763 | */ | ||
44764 | /* OP_OR: * * * | ||
44765 | * | ||
44766 | * Pop two values off the stack. Take the logical OR of the | ||
44767 | * two values and push the resulting boolean value back onto the | ||
44768 | * stack. | ||
44769 | */ | ||
44770 | case JX9_OP_LAND: | ||
44771 | case JX9_OP_LOR: { | ||
44772 | jx9_value *pNos = &pTos[-1]; | ||
44773 | sxi32 v1, v2; /* 0==TRUE, 1==FALSE, 2==UNKNOWN or NULL */ | ||
44774 | #ifdef UNTRUST | ||
44775 | if( pNos < pStack ){ | ||
44776 | goto Abort; | ||
44777 | } | ||
44778 | #endif | ||
44779 | /* Force a boolean cast */ | ||
44780 | if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
44781 | jx9MemObjToBool(pTos); | ||
44782 | } | ||
44783 | if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
44784 | jx9MemObjToBool(pNos); | ||
44785 | } | ||
44786 | v1 = pNos->x.iVal == 0 ? 1 : 0; | ||
44787 | v2 = pTos->x.iVal == 0 ? 1 : 0; | ||
44788 | if( pInstr->iOp == JX9_OP_LAND ){ | ||
44789 | static const unsigned char and_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 }; | ||
44790 | v1 = and_logic[v1*3+v2]; | ||
44791 | }else{ | ||
44792 | static const unsigned char or_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 }; | ||
44793 | v1 = or_logic[v1*3+v2]; | ||
44794 | } | ||
44795 | if( v1 == 2 ){ | ||
44796 | v1 = 1; | ||
44797 | } | ||
44798 | VmPopOperand(&pTos, 1); | ||
44799 | pTos->x.iVal = v1 == 0 ? 1 : 0; | ||
44800 | MemObjSetType(pTos, MEMOBJ_BOOL); | ||
44801 | break; | ||
44802 | } | ||
44803 | /* OP_LXOR: * * * | ||
44804 | * | ||
44805 | * Pop two values off the stack. Take the logical XOR of the | ||
44806 | * two values and push the resulting boolean value back onto the | ||
44807 | * stack. | ||
44808 | * According to the JX9 language reference manual: | ||
44809 | * $a xor $b is evaluated to TRUE if either $a or $b is | ||
44810 | * TRUE, but not both. | ||
44811 | */ | ||
44812 | case JX9_OP_LXOR:{ | ||
44813 | jx9_value *pNos = &pTos[-1]; | ||
44814 | sxi32 v = 0; | ||
44815 | #ifdef UNTRUST | ||
44816 | if( pNos < pStack ){ | ||
44817 | goto Abort; | ||
44818 | } | ||
44819 | #endif | ||
44820 | /* Force a boolean cast */ | ||
44821 | if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
44822 | jx9MemObjToBool(pTos); | ||
44823 | } | ||
44824 | if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ | ||
44825 | jx9MemObjToBool(pNos); | ||
44826 | } | ||
44827 | if( (pNos->x.iVal && !pTos->x.iVal) || (pTos->x.iVal && !pNos->x.iVal) ){ | ||
44828 | v = 1; | ||
44829 | } | ||
44830 | VmPopOperand(&pTos, 1); | ||
44831 | pTos->x.iVal = v; | ||
44832 | MemObjSetType(pTos, MEMOBJ_BOOL); | ||
44833 | break; | ||
44834 | } | ||
44835 | /* OP_EQ P1 P2 P3 | ||
44836 | * | ||
44837 | * Pop the top two elements from the stack. If they are equal, then | ||
44838 | * jump to instruction P2. Otherwise, continue to the next instruction. | ||
44839 | * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the | ||
44840 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
44841 | */ | ||
44842 | /* OP_NEQ P1 P2 P3 | ||
44843 | * | ||
44844 | * Pop the top two elements from the stack. If they are not equal, then | ||
44845 | * jump to instruction P2. Otherwise, continue to the next instruction. | ||
44846 | * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the | ||
44847 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
44848 | */ | ||
44849 | case JX9_OP_EQ: | ||
44850 | case JX9_OP_NEQ: { | ||
44851 | jx9_value *pNos = &pTos[-1]; | ||
44852 | /* Perform the comparison and act accordingly */ | ||
44853 | #ifdef UNTRUST | ||
44854 | if( pNos < pStack ){ | ||
44855 | goto Abort; | ||
44856 | } | ||
44857 | #endif | ||
44858 | rc = jx9MemObjCmp(pNos, pTos, FALSE, 0); | ||
44859 | if( pInstr->iOp == JX9_OP_EQ ){ | ||
44860 | rc = rc == 0; | ||
44861 | }else{ | ||
44862 | rc = rc != 0; | ||
44863 | } | ||
44864 | VmPopOperand(&pTos, 1); | ||
44865 | if( !pInstr->iP2 ){ | ||
44866 | /* Push comparison result without taking the jump */ | ||
44867 | jx9MemObjRelease(pTos); | ||
44868 | pTos->x.iVal = rc; | ||
44869 | /* Invalidate any prior representation */ | ||
44870 | MemObjSetType(pTos, MEMOBJ_BOOL); | ||
44871 | }else{ | ||
44872 | if( rc ){ | ||
44873 | /* Jump to the desired location */ | ||
44874 | pc = pInstr->iP2 - 1; | ||
44875 | VmPopOperand(&pTos, 1); | ||
44876 | } | ||
44877 | } | ||
44878 | break; | ||
44879 | } | ||
44880 | /* OP_TEQ P1 P2 * | ||
44881 | * | ||
44882 | * Pop the top two elements from the stack. If they have the same type and are equal | ||
44883 | * then jump to instruction P2. Otherwise, continue to the next instruction. | ||
44884 | * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the | ||
44885 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
44886 | */ | ||
44887 | case JX9_OP_TEQ: { | ||
44888 | jx9_value *pNos = &pTos[-1]; | ||
44889 | /* Perform the comparison and act accordingly */ | ||
44890 | #ifdef UNTRUST | ||
44891 | if( pNos < pStack ){ | ||
44892 | goto Abort; | ||
44893 | } | ||
44894 | #endif | ||
44895 | rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) == 0; | ||
44896 | VmPopOperand(&pTos, 1); | ||
44897 | if( !pInstr->iP2 ){ | ||
44898 | /* Push comparison result without taking the jump */ | ||
44899 | jx9MemObjRelease(pTos); | ||
44900 | pTos->x.iVal = rc; | ||
44901 | /* Invalidate any prior representation */ | ||
44902 | MemObjSetType(pTos, MEMOBJ_BOOL); | ||
44903 | }else{ | ||
44904 | if( rc ){ | ||
44905 | /* Jump to the desired location */ | ||
44906 | pc = pInstr->iP2 - 1; | ||
44907 | VmPopOperand(&pTos, 1); | ||
44908 | } | ||
44909 | } | ||
44910 | break; | ||
44911 | } | ||
44912 | /* OP_TNE P1 P2 * | ||
44913 | * | ||
44914 | * Pop the top two elements from the stack.If they are not equal an they are not | ||
44915 | * of the same type, then jump to instruction P2. Otherwise, continue to the next | ||
44916 | * instruction. | ||
44917 | * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the | ||
44918 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
44919 | * | ||
44920 | */ | ||
44921 | case JX9_OP_TNE: { | ||
44922 | jx9_value *pNos = &pTos[-1]; | ||
44923 | /* Perform the comparison and act accordingly */ | ||
44924 | #ifdef UNTRUST | ||
44925 | if( pNos < pStack ){ | ||
44926 | goto Abort; | ||
44927 | } | ||
44928 | #endif | ||
44929 | rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) != 0; | ||
44930 | VmPopOperand(&pTos, 1); | ||
44931 | if( !pInstr->iP2 ){ | ||
44932 | /* Push comparison result without taking the jump */ | ||
44933 | jx9MemObjRelease(pTos); | ||
44934 | pTos->x.iVal = rc; | ||
44935 | /* Invalidate any prior representation */ | ||
44936 | MemObjSetType(pTos, MEMOBJ_BOOL); | ||
44937 | }else{ | ||
44938 | if( rc ){ | ||
44939 | /* Jump to the desired location */ | ||
44940 | pc = pInstr->iP2 - 1; | ||
44941 | VmPopOperand(&pTos, 1); | ||
44942 | } | ||
44943 | } | ||
44944 | break; | ||
44945 | } | ||
44946 | /* OP_LT P1 P2 P3 | ||
44947 | * | ||
44948 | * Pop the top two elements from the stack. If the second element (the top of stack) | ||
44949 | * is less than the first (next on stack), then jump to instruction P2.Otherwise | ||
44950 | * continue to the next instruction. In other words, jump if pNos<pTos. | ||
44951 | * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the | ||
44952 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
44953 | * | ||
44954 | */ | ||
44955 | /* OP_LE P1 P2 P3 | ||
44956 | * | ||
44957 | * Pop the top two elements from the stack. If the second element (the top of stack) | ||
44958 | * is less than or equal to the first (next on stack), then jump to instruction P2. | ||
44959 | * Otherwise continue to the next instruction. In other words, jump if pNos<pTos. | ||
44960 | * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the | ||
44961 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
44962 | * | ||
44963 | */ | ||
44964 | case JX9_OP_LT: | ||
44965 | case JX9_OP_LE: { | ||
44966 | jx9_value *pNos = &pTos[-1]; | ||
44967 | /* Perform the comparison and act accordingly */ | ||
44968 | #ifdef UNTRUST | ||
44969 | if( pNos < pStack ){ | ||
44970 | goto Abort; | ||
44971 | } | ||
44972 | #endif | ||
44973 | rc = jx9MemObjCmp(pNos, pTos, FALSE, 0); | ||
44974 | if( pInstr->iOp == JX9_OP_LE ){ | ||
44975 | rc = rc < 1; | ||
44976 | }else{ | ||
44977 | rc = rc < 0; | ||
44978 | } | ||
44979 | VmPopOperand(&pTos, 1); | ||
44980 | if( !pInstr->iP2 ){ | ||
44981 | /* Push comparison result without taking the jump */ | ||
44982 | jx9MemObjRelease(pTos); | ||
44983 | pTos->x.iVal = rc; | ||
44984 | /* Invalidate any prior representation */ | ||
44985 | MemObjSetType(pTos, MEMOBJ_BOOL); | ||
44986 | }else{ | ||
44987 | if( rc ){ | ||
44988 | /* Jump to the desired location */ | ||
44989 | pc = pInstr->iP2 - 1; | ||
44990 | VmPopOperand(&pTos, 1); | ||
44991 | } | ||
44992 | } | ||
44993 | break; | ||
44994 | } | ||
44995 | /* OP_GT P1 P2 P3 | ||
44996 | * | ||
44997 | * Pop the top two elements from the stack. If the second element (the top of stack) | ||
44998 | * is greater than the first (next on stack), then jump to instruction P2.Otherwise | ||
44999 | * continue to the next instruction. In other words, jump if pNos<pTos. | ||
45000 | * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the | ||
45001 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
45002 | * | ||
45003 | */ | ||
45004 | /* OP_GE P1 P2 P3 | ||
45005 | * | ||
45006 | * Pop the top two elements from the stack. If the second element (the top of stack) | ||
45007 | * is greater than or equal to the first (next on stack), then jump to instruction P2. | ||
45008 | * Otherwise continue to the next instruction. In other words, jump if pNos<pTos. | ||
45009 | * If P2 is zero, do not jump.Instead, push a boolean 1 (TRUE) onto the | ||
45010 | * stack if the jump would have been taken, or a 0 (FALSE) if not. | ||
45011 | * | ||
45012 | */ | ||
45013 | case JX9_OP_GT: | ||
45014 | case JX9_OP_GE: { | ||
45015 | jx9_value *pNos = &pTos[-1]; | ||
45016 | /* Perform the comparison and act accordingly */ | ||
45017 | #ifdef UNTRUST | ||
45018 | if( pNos < pStack ){ | ||
45019 | goto Abort; | ||
45020 | } | ||
45021 | #endif | ||
45022 | rc = jx9MemObjCmp(pNos, pTos, FALSE, 0); | ||
45023 | if( pInstr->iOp == JX9_OP_GE ){ | ||
45024 | rc = rc >= 0; | ||
45025 | }else{ | ||
45026 | rc = rc > 0; | ||
45027 | } | ||
45028 | VmPopOperand(&pTos, 1); | ||
45029 | if( !pInstr->iP2 ){ | ||
45030 | /* Push comparison result without taking the jump */ | ||
45031 | jx9MemObjRelease(pTos); | ||
45032 | pTos->x.iVal = rc; | ||
45033 | /* Invalidate any prior representation */ | ||
45034 | MemObjSetType(pTos, MEMOBJ_BOOL); | ||
45035 | }else{ | ||
45036 | if( rc ){ | ||
45037 | /* Jump to the desired location */ | ||
45038 | pc = pInstr->iP2 - 1; | ||
45039 | VmPopOperand(&pTos, 1); | ||
45040 | } | ||
45041 | } | ||
45042 | break; | ||
45043 | } | ||
45044 | /* | ||
45045 | * OP_FOREACH_INIT * P2 P3 | ||
45046 | * Prepare a foreach step. | ||
45047 | */ | ||
45048 | case JX9_OP_FOREACH_INIT: { | ||
45049 | jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3; | ||
45050 | void *pName; | ||
45051 | #ifdef UNTRUST | ||
45052 | if( pTos < pStack ){ | ||
45053 | goto Abort; | ||
45054 | } | ||
45055 | #endif | ||
45056 | if( SyStringLength(&pInfo->sValue) < 1 ){ | ||
45057 | /* Take the variable name from the top of the stack */ | ||
45058 | if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
45059 | /* Force a string cast */ | ||
45060 | jx9MemObjToString(pTos); | ||
45061 | } | ||
45062 | /* Duplicate name */ | ||
45063 | if( SyBlobLength(&pTos->sBlob) > 0 ){ | ||
45064 | pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); | ||
45065 | SyStringInitFromBuf(&pInfo->sValue, pName, SyBlobLength(&pTos->sBlob)); | ||
45066 | } | ||
45067 | VmPopOperand(&pTos, 1); | ||
45068 | } | ||
45069 | if( (pInfo->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) < 1 ){ | ||
45070 | if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
45071 | /* Force a string cast */ | ||
45072 | jx9MemObjToString(pTos); | ||
45073 | } | ||
45074 | /* Duplicate name */ | ||
45075 | if( SyBlobLength(&pTos->sBlob) > 0 ){ | ||
45076 | pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); | ||
45077 | SyStringInitFromBuf(&pInfo->sKey, pName, SyBlobLength(&pTos->sBlob)); | ||
45078 | } | ||
45079 | VmPopOperand(&pTos, 1); | ||
45080 | } | ||
45081 | /* Make sure we are dealing with a hashmap [i.e. JSON array or object ]*/ | ||
45082 | if( (pTos->iFlags & (MEMOBJ_HASHMAP)) == 0 || SyStringLength(&pInfo->sValue) < 1 ){ | ||
45083 | /* Jump out of the loop */ | ||
45084 | if( (pTos->iFlags & MEMOBJ_NULL) == 0 ){ | ||
45085 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING, | ||
45086 | "Invalid argument supplied for the foreach statement, expecting JSON array or object instance"); | ||
45087 | } | ||
45088 | pc = pInstr->iP2 - 1; | ||
45089 | }else{ | ||
45090 | jx9_foreach_step *pStep; | ||
45091 | pStep = (jx9_foreach_step *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_foreach_step)); | ||
45092 | if( pStep == 0 ){ | ||
45093 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step"); | ||
45094 | /* Jump out of the loop */ | ||
45095 | pc = pInstr->iP2 - 1; | ||
45096 | }else{ | ||
45097 | /* Zero the structure */ | ||
45098 | SyZero(pStep, sizeof(jx9_foreach_step)); | ||
45099 | /* Prepare the step */ | ||
45100 | pStep->iFlags = pInfo->iFlags; | ||
45101 | if( pTos->iFlags & MEMOBJ_HASHMAP ){ | ||
45102 | jx9_hashmap *pMap = (jx9_hashmap *)pTos->x.pOther; | ||
45103 | /* Reset the internal loop cursor */ | ||
45104 | jx9HashmapResetLoopCursor(pMap); | ||
45105 | /* Mark the step */ | ||
45106 | pStep->pMap = pMap; | ||
45107 | pMap->iRef++; | ||
45108 | } | ||
45109 | } | ||
45110 | if( SXRET_OK != SySetPut(&pInfo->aStep, (const void *)&pStep) ){ | ||
45111 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step"); | ||
45112 | SyMemBackendPoolFree(&pVm->sAllocator, pStep); | ||
45113 | /* Jump out of the loop */ | ||
45114 | pc = pInstr->iP2 - 1; | ||
45115 | } | ||
45116 | } | ||
45117 | VmPopOperand(&pTos, 1); | ||
45118 | break; | ||
45119 | } | ||
45120 | /* | ||
45121 | * OP_FOREACH_STEP * P2 P3 | ||
45122 | * Perform a foreach step. Jump to P2 at the end of the step. | ||
45123 | */ | ||
45124 | case JX9_OP_FOREACH_STEP: { | ||
45125 | jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3; | ||
45126 | jx9_foreach_step **apStep, *pStep; | ||
45127 | jx9_hashmap_node *pNode; | ||
45128 | jx9_hashmap *pMap; | ||
45129 | jx9_value *pValue; | ||
45130 | /* Peek the last step */ | ||
45131 | apStep = (jx9_foreach_step **)SySetBasePtr(&pInfo->aStep); | ||
45132 | pStep = apStep[SySetUsed(&pInfo->aStep) - 1]; | ||
45133 | pMap = pStep->pMap; | ||
45134 | /* Extract the current node value */ | ||
45135 | pNode = jx9HashmapGetNextEntry(pMap); | ||
45136 | if( pNode == 0 ){ | ||
45137 | /* No more entry to process */ | ||
45138 | pc = pInstr->iP2 - 1; /* Jump to this destination */ | ||
45139 | /* Automatically reset the loop cursor */ | ||
45140 | jx9HashmapResetLoopCursor(pMap); | ||
45141 | /* Cleanup the mess left behind */ | ||
45142 | SyMemBackendPoolFree(&pVm->sAllocator, pStep); | ||
45143 | SySetPop(&pInfo->aStep); | ||
45144 | jx9HashmapUnref(pMap); | ||
45145 | }else{ | ||
45146 | if( (pStep->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0 ){ | ||
45147 | jx9_value *pKey = VmExtractMemObj(&(*pVm), &pInfo->sKey, FALSE, TRUE); | ||
45148 | if( pKey ){ | ||
45149 | jx9HashmapExtractNodeKey(pNode, pKey); | ||
45150 | } | ||
45151 | } | ||
45152 | /* Make a copy of the entry value */ | ||
45153 | pValue = VmExtractMemObj(&(*pVm), &pInfo->sValue, FALSE, TRUE); | ||
45154 | if( pValue ){ | ||
45155 | jx9HashmapExtractNodeValue(pNode, pValue, TRUE); | ||
45156 | } | ||
45157 | } | ||
45158 | break; | ||
45159 | } | ||
45160 | /* | ||
45161 | * OP_MEMBER P1 P2 | ||
45162 | * Load JSON object entry on the stack. | ||
45163 | */ | ||
45164 | case JX9_OP_MEMBER: { | ||
45165 | jx9_hashmap_node *pNode = 0; /* cc warning */ | ||
45166 | jx9_hashmap *pMap = 0; | ||
45167 | jx9_value *pIdx; | ||
45168 | pIdx = pTos; | ||
45169 | pTos--; | ||
45170 | rc = SXERR_NOTFOUND; /* Assume the index is invalid */ | ||
45171 | if( pTos->iFlags & MEMOBJ_HASHMAP ){ | ||
45172 | /* Point to the hashmap */ | ||
45173 | pMap = (jx9_hashmap *)pTos->x.pOther; | ||
45174 | /* Load the desired entry */ | ||
45175 | rc = jx9HashmapLookup(pMap, pIdx, &pNode); | ||
45176 | } | ||
45177 | jx9MemObjRelease(pIdx); | ||
45178 | if( rc == SXRET_OK ){ | ||
45179 | /* Load entry contents */ | ||
45180 | if( pMap->iRef < 2 ){ | ||
45181 | /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy | ||
45182 | * of the entry value, rather than pointing to it. | ||
45183 | */ | ||
45184 | pTos->nIdx = SXU32_HIGH; | ||
45185 | jx9HashmapExtractNodeValue(pNode, pTos, TRUE); | ||
45186 | }else{ | ||
45187 | pTos->nIdx = pNode->nValIdx; | ||
45188 | jx9HashmapExtractNodeValue(pNode, pTos, FALSE); | ||
45189 | jx9HashmapUnref(pMap); | ||
45190 | } | ||
45191 | }else{ | ||
45192 | /* No such entry, load NULL */ | ||
45193 | jx9MemObjRelease(pTos); | ||
45194 | pTos->nIdx = SXU32_HIGH; | ||
45195 | } | ||
45196 | break; | ||
45197 | } | ||
45198 | /* | ||
45199 | * OP_SWITCH * * P3 | ||
45200 | * This is the bytecode implementation of the complex switch() JX9 construct. | ||
45201 | */ | ||
45202 | case JX9_OP_SWITCH: { | ||
45203 | jx9_switch *pSwitch = (jx9_switch *)pInstr->p3; | ||
45204 | jx9_case_expr *aCase, *pCase; | ||
45205 | jx9_value sValue, sCaseValue; | ||
45206 | sxu32 n, nEntry; | ||
45207 | #ifdef UNTRUST | ||
45208 | if( pSwitch == 0 || pTos < pStack ){ | ||
45209 | goto Abort; | ||
45210 | } | ||
45211 | #endif | ||
45212 | /* Point to the case table */ | ||
45213 | aCase = (jx9_case_expr *)SySetBasePtr(&pSwitch->aCaseExpr); | ||
45214 | nEntry = SySetUsed(&pSwitch->aCaseExpr); | ||
45215 | /* Select the appropriate case block to execute */ | ||
45216 | jx9MemObjInit(pVm, &sValue); | ||
45217 | jx9MemObjInit(pVm, &sCaseValue); | ||
45218 | for( n = 0 ; n < nEntry ; ++n ){ | ||
45219 | pCase = &aCase[n]; | ||
45220 | jx9MemObjLoad(pTos, &sValue); | ||
45221 | /* Execute the case expression first */ | ||
45222 | VmLocalExec(pVm,&pCase->aByteCode, &sCaseValue); | ||
45223 | /* Compare the two expression */ | ||
45224 | rc = jx9MemObjCmp(&sValue, &sCaseValue, FALSE, 0); | ||
45225 | jx9MemObjRelease(&sValue); | ||
45226 | jx9MemObjRelease(&sCaseValue); | ||
45227 | if( rc == 0 ){ | ||
45228 | /* Value match, jump to this block */ | ||
45229 | pc = pCase->nStart - 1; | ||
45230 | break; | ||
45231 | } | ||
45232 | } | ||
45233 | VmPopOperand(&pTos, 1); | ||
45234 | if( n >= nEntry ){ | ||
45235 | /* No approprite case to execute, jump to the default case */ | ||
45236 | if( pSwitch->nDefault > 0 ){ | ||
45237 | pc = pSwitch->nDefault - 1; | ||
45238 | }else{ | ||
45239 | /* No default case, jump out of this switch */ | ||
45240 | pc = pSwitch->nOut - 1; | ||
45241 | } | ||
45242 | } | ||
45243 | break; | ||
45244 | } | ||
45245 | /* | ||
45246 | * OP_UPLINK P1 * * | ||
45247 | * Link a variable to the top active VM frame. | ||
45248 | * This is used to implement the 'uplink' JX9 construct. | ||
45249 | */ | ||
45250 | case JX9_OP_UPLINK: { | ||
45251 | if( pVm->pFrame->pParent ){ | ||
45252 | jx9_value *pLink = &pTos[-pInstr->iP1+1]; | ||
45253 | SyString sName; | ||
45254 | /* Perform the link */ | ||
45255 | while( pLink <= pTos ){ | ||
45256 | if((pLink->iFlags & MEMOBJ_STRING) == 0 ){ | ||
45257 | /* Force a string cast */ | ||
45258 | jx9MemObjToString(pLink); | ||
45259 | } | ||
45260 | SyStringInitFromBuf(&sName, SyBlobData(&pLink->sBlob), SyBlobLength(&pLink->sBlob)); | ||
45261 | if( sName.nByte > 0 ){ | ||
45262 | VmFrameLink(&(*pVm), &sName); | ||
45263 | } | ||
45264 | pLink++; | ||
45265 | } | ||
45266 | } | ||
45267 | VmPopOperand(&pTos, pInstr->iP1); | ||
45268 | break; | ||
45269 | } | ||
45270 | /* | ||
45271 | * OP_CALL P1 * * | ||
45272 | * Call a JX9 or a foreign function and push the return value of the called | ||
45273 | * function on the stack. | ||
45274 | */ | ||
45275 | case JX9_OP_CALL: { | ||
45276 | jx9_value *pArg = &pTos[-pInstr->iP1]; | ||
45277 | SyHashEntry *pEntry; | ||
45278 | SyString sName; | ||
45279 | /* Extract function name */ | ||
45280 | if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ | ||
45281 | /* Raise exception: Invalid function name */ | ||
45282 | VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Invalid function name, JX9 is returning NULL."); | ||
45283 | /* Pop given arguments */ | ||
45284 | if( pInstr->iP1 > 0 ){ | ||
45285 | VmPopOperand(&pTos, pInstr->iP1); | ||
45286 | } | ||
45287 | /* Assume a null return value so that the program continue it's execution normally */ | ||
45288 | jx9MemObjRelease(pTos); | ||
45289 | break; | ||
45290 | } | ||
45291 | SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); | ||
45292 | /* Check for a compiled function first */ | ||
45293 | pEntry = SyHashGet(&pVm->hFunction, (const void *)sName.zString, sName.nByte); | ||
45294 | if( pEntry ){ | ||
45295 | jx9_vm_func_arg *aFormalArg; | ||
45296 | jx9_value *pFrameStack; | ||
45297 | jx9_vm_func *pVmFunc; | ||
45298 | VmFrame *pFrame; | ||
45299 | jx9_value *pObj; | ||
45300 | VmSlot sArg; | ||
45301 | sxu32 n; | ||
45302 | pVmFunc = (jx9_vm_func *)pEntry->pUserData; | ||
45303 | /* Check The recursion limit */ | ||
45304 | if( pVm->nRecursionDepth > pVm->nMaxDepth ){ | ||
45305 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, | ||
45306 | "Recursion limit reached while invoking user function '%z', JX9 will set a NULL return value", | ||
45307 | &pVmFunc->sName); | ||
45308 | /* Pop given arguments */ | ||
45309 | if( pInstr->iP1 > 0 ){ | ||
45310 | VmPopOperand(&pTos, pInstr->iP1); | ||
45311 | } | ||
45312 | /* Assume a null return value so that the program continue it's execution normally */ | ||
45313 | jx9MemObjRelease(pTos); | ||
45314 | break; | ||
45315 | } | ||
45316 | if( pVmFunc->pNextName ){ | ||
45317 | /* Function is candidate for overloading, select the appropriate function to call */ | ||
45318 | pVmFunc = VmOverload(&(*pVm), pVmFunc, pArg, (int)(pTos-pArg)); | ||
45319 | } | ||
45320 | /* Extract the formal argument set */ | ||
45321 | aFormalArg = (jx9_vm_func_arg *)SySetBasePtr(&pVmFunc->aArgs); | ||
45322 | /* Create a new VM frame */ | ||
45323 | rc = VmEnterFrame(&(*pVm),pVmFunc,&pFrame); | ||
45324 | if( rc != SXRET_OK ){ | ||
45325 | /* Raise exception: Out of memory */ | ||
45326 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, | ||
45327 | "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.", | ||
45328 | &pVmFunc->sName); | ||
45329 | /* Pop given arguments */ | ||
45330 | if( pInstr->iP1 > 0 ){ | ||
45331 | VmPopOperand(&pTos, pInstr->iP1); | ||
45332 | } | ||
45333 | /* Assume a null return value so that the program continue it's execution normally */ | ||
45334 | jx9MemObjRelease(pTos); | ||
45335 | break; | ||
45336 | } | ||
45337 | if( SySetUsed(&pVmFunc->aStatic) > 0 ){ | ||
45338 | jx9_vm_func_static_var *pStatic, *aStatic; | ||
45339 | /* Install static variables */ | ||
45340 | aStatic = (jx9_vm_func_static_var *)SySetBasePtr(&pVmFunc->aStatic); | ||
45341 | for( n = 0 ; n < SySetUsed(&pVmFunc->aStatic) ; ++n ){ | ||
45342 | pStatic = &aStatic[n]; | ||
45343 | if( pStatic->nIdx == SXU32_HIGH ){ | ||
45344 | /* Initialize the static variables */ | ||
45345 | pObj = VmReserveMemObj(&(*pVm), &pStatic->nIdx); | ||
45346 | if( pObj ){ | ||
45347 | /* Assume a NULL initialization value */ | ||
45348 | jx9MemObjInit(&(*pVm), pObj); | ||
45349 | if( SySetUsed(&pStatic->aByteCode) > 0 ){ | ||
45350 | /* Evaluate initialization expression (Any complex expression) */ | ||
45351 | VmLocalExec(&(*pVm), &pStatic->aByteCode, pObj); | ||
45352 | } | ||
45353 | pObj->nIdx = pStatic->nIdx; | ||
45354 | }else{ | ||
45355 | continue; | ||
45356 | } | ||
45357 | } | ||
45358 | /* Install in the current frame */ | ||
45359 | SyHashInsert(&pFrame->hVar, SyStringData(&pStatic->sName), SyStringLength(&pStatic->sName), | ||
45360 | SX_INT_TO_PTR(pStatic->nIdx)); | ||
45361 | } | ||
45362 | } | ||
45363 | /* Push arguments in the local frame */ | ||
45364 | n = 0; | ||
45365 | while( pArg < pTos ){ | ||
45366 | if( n < SySetUsed(&pVmFunc->aArgs) ){ | ||
45367 | if( (pArg->iFlags & MEMOBJ_NULL) && SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ | ||
45368 | /* NULL values are redirected to default arguments */ | ||
45369 | rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pArg); | ||
45370 | if( rc == JX9_ABORT ){ | ||
45371 | goto Abort; | ||
45372 | } | ||
45373 | } | ||
45374 | /* Make sure the given arguments are of the correct type */ | ||
45375 | if( aFormalArg[n].nType > 0 ){ | ||
45376 | if( ((pArg->iFlags & aFormalArg[n].nType) == 0) ){ | ||
45377 | ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType); | ||
45378 | /* Cast to the desired type */ | ||
45379 | if( xCast ){ | ||
45380 | xCast(pArg); | ||
45381 | } | ||
45382 | } | ||
45383 | } | ||
45384 | /* Pass by value, make a copy of the given argument */ | ||
45385 | pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE); | ||
45386 | }else{ | ||
45387 | char zName[32]; | ||
45388 | SyString sName; | ||
45389 | /* Set a dummy name */ | ||
45390 | sName.nByte = SyBufferFormat(zName, sizeof(zName), "[%u]apArg", n); | ||
45391 | sName.zString = zName; | ||
45392 | /* Annonymous argument */ | ||
45393 | pObj = VmExtractMemObj(&(*pVm), &sName, TRUE, TRUE); | ||
45394 | } | ||
45395 | if( pObj ){ | ||
45396 | jx9MemObjStore(pArg, pObj); | ||
45397 | /* Insert argument index */ | ||
45398 | sArg.nIdx = pObj->nIdx; | ||
45399 | sArg.pUserData = 0; | ||
45400 | SySetPut(&pFrame->sArg, (const void *)&sArg); | ||
45401 | } | ||
45402 | jx9MemObjRelease(pArg); | ||
45403 | pArg++; | ||
45404 | ++n; | ||
45405 | } | ||
45406 | /* Process default values */ | ||
45407 | while( n < SySetUsed(&pVmFunc->aArgs) ){ | ||
45408 | if( SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ | ||
45409 | pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE); | ||
45410 | if( pObj ){ | ||
45411 | /* Evaluate the default value and extract it's result */ | ||
45412 | rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pObj); | ||
45413 | if( rc == JX9_ABORT ){ | ||
45414 | goto Abort; | ||
45415 | } | ||
45416 | /* Insert argument index */ | ||
45417 | sArg.nIdx = pObj->nIdx; | ||
45418 | sArg.pUserData = 0; | ||
45419 | SySetPut(&pFrame->sArg, (const void *)&sArg); | ||
45420 | /* Make sure the default argument is of the correct type */ | ||
45421 | if( aFormalArg[n].nType > 0 && ((pObj->iFlags & aFormalArg[n].nType) == 0) ){ | ||
45422 | ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType); | ||
45423 | /* Cast to the desired type */ | ||
45424 | xCast(pObj); | ||
45425 | } | ||
45426 | } | ||
45427 | } | ||
45428 | ++n; | ||
45429 | } | ||
45430 | /* Pop arguments, function name from the operand stack and assume the function | ||
45431 | * does not return anything. | ||
45432 | */ | ||
45433 | jx9MemObjRelease(pTos); | ||
45434 | pTos = &pTos[-pInstr->iP1]; | ||
45435 | /* Allocate a new operand stack and evaluate the function body */ | ||
45436 | pFrameStack = VmNewOperandStack(&(*pVm), SySetUsed(&pVmFunc->aByteCode)); | ||
45437 | if( pFrameStack == 0 ){ | ||
45438 | /* Raise exception: Out of memory */ | ||
45439 | VmErrorFormat(&(*pVm), JX9_CTX_ERR, "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.", | ||
45440 | &pVmFunc->sName); | ||
45441 | if( pInstr->iP1 > 0 ){ | ||
45442 | VmPopOperand(&pTos, pInstr->iP1); | ||
45443 | } | ||
45444 | break; | ||
45445 | } | ||
45446 | /* Increment nesting level */ | ||
45447 | pVm->nRecursionDepth++; | ||
45448 | /* Execute function body */ | ||
45449 | rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(&pVmFunc->aByteCode), pFrameStack, -1, pTos); | ||
45450 | /* Decrement nesting level */ | ||
45451 | pVm->nRecursionDepth--; | ||
45452 | /* Free the operand stack */ | ||
45453 | SyMemBackendFree(&pVm->sAllocator, pFrameStack); | ||
45454 | /* Leave the frame */ | ||
45455 | VmLeaveFrame(&(*pVm)); | ||
45456 | if( rc == JX9_ABORT ){ | ||
45457 | /* Abort processing immeditaley */ | ||
45458 | goto Abort; | ||
45459 | } | ||
45460 | }else{ | ||
45461 | jx9_user_func *pFunc; | ||
45462 | jx9_context sCtx; | ||
45463 | jx9_value sRet; | ||
45464 | /* Look for an installed foreign function */ | ||
45465 | pEntry = SyHashGet(&pVm->hHostFunction, (const void *)sName.zString, sName.nByte); | ||
45466 | if( pEntry == 0 ){ | ||
45467 | /* Call to undefined function */ | ||
45468 | VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Call to undefined function '%z', JX9 is returning NULL.", &sName); | ||
45469 | /* Pop given arguments */ | ||
45470 | if( pInstr->iP1 > 0 ){ | ||
45471 | VmPopOperand(&pTos, pInstr->iP1); | ||
45472 | } | ||
45473 | /* Assume a null return value so that the program continue it's execution normally */ | ||
45474 | jx9MemObjRelease(pTos); | ||
45475 | break; | ||
45476 | } | ||
45477 | pFunc = (jx9_user_func *)pEntry->pUserData; | ||
45478 | /* Start collecting function arguments */ | ||
45479 | SySetReset(&aArg); | ||
45480 | while( pArg < pTos ){ | ||
45481 | SySetPut(&aArg, (const void *)&pArg); | ||
45482 | pArg++; | ||
45483 | } | ||
45484 | /* Assume a null return value */ | ||
45485 | jx9MemObjInit(&(*pVm), &sRet); | ||
45486 | /* Init the call context */ | ||
45487 | VmInitCallContext(&sCtx, &(*pVm), pFunc, &sRet, 0); | ||
45488 | /* Call the foreign function */ | ||
45489 | rc = pFunc->xFunc(&sCtx, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg)); | ||
45490 | /* Release the call context */ | ||
45491 | VmReleaseCallContext(&sCtx); | ||
45492 | if( rc == JX9_ABORT ){ | ||
45493 | goto Abort; | ||
45494 | } | ||
45495 | if( pInstr->iP1 > 0 ){ | ||
45496 | /* Pop function name and arguments */ | ||
45497 | VmPopOperand(&pTos, pInstr->iP1); | ||
45498 | } | ||
45499 | /* Save foreign function return value */ | ||
45500 | jx9MemObjStore(&sRet, pTos); | ||
45501 | jx9MemObjRelease(&sRet); | ||
45502 | } | ||
45503 | break; | ||
45504 | } | ||
45505 | /* | ||
45506 | * OP_CONSUME: P1 * * | ||
45507 | * Consume (Invoke the installed VM output consumer callback) and POP P1 elements from the stack. | ||
45508 | */ | ||
45509 | case JX9_OP_CONSUME: { | ||
45510 | jx9_output_consumer *pCons = &pVm->sVmConsumer; | ||
45511 | jx9_value *pCur, *pOut = pTos; | ||
45512 | |||
45513 | pOut = &pTos[-pInstr->iP1 + 1]; | ||
45514 | pCur = pOut; | ||
45515 | /* Start the consume process */ | ||
45516 | while( pOut <= pTos ){ | ||
45517 | /* Force a string cast */ | ||
45518 | if( (pOut->iFlags & MEMOBJ_STRING) == 0 ){ | ||
45519 | jx9MemObjToString(pOut); | ||
45520 | } | ||
45521 | if( SyBlobLength(&pOut->sBlob) > 0 ){ | ||
45522 | /*SyBlobNullAppend(&pOut->sBlob);*/ | ||
45523 | /* Invoke the output consumer callback */ | ||
45524 | rc = pCons->xConsumer(SyBlobData(&pOut->sBlob), SyBlobLength(&pOut->sBlob), pCons->pUserData); | ||
45525 | /* Increment output length */ | ||
45526 | pVm->nOutputLen += SyBlobLength(&pOut->sBlob); | ||
45527 | SyBlobRelease(&pOut->sBlob); | ||
45528 | if( rc == SXERR_ABORT ){ | ||
45529 | /* Output consumer callback request an operation abort. */ | ||
45530 | goto Abort; | ||
45531 | } | ||
45532 | } | ||
45533 | pOut++; | ||
45534 | } | ||
45535 | pTos = &pCur[-1]; | ||
45536 | break; | ||
45537 | } | ||
45538 | |||
45539 | } /* Switch() */ | ||
45540 | pc++; /* Next instruction in the stream */ | ||
45541 | } /* For(;;) */ | ||
45542 | Done: | ||
45543 | SySetRelease(&aArg); | ||
45544 | return SXRET_OK; | ||
45545 | Abort: | ||
45546 | SySetRelease(&aArg); | ||
45547 | while( pTos >= pStack ){ | ||
45548 | jx9MemObjRelease(pTos); | ||
45549 | pTos--; | ||
45550 | } | ||
45551 | return JX9_ABORT; | ||
45552 | } | ||
45553 | /* | ||
45554 | * Execute as much of a local JX9 bytecode program as we can then return. | ||
45555 | * This function is a wrapper around [VmByteCodeExec()]. | ||
45556 | * See block-comment on that function for additional information. | ||
45557 | */ | ||
45558 | static sxi32 VmLocalExec(jx9_vm *pVm, SySet *pByteCode,jx9_value *pResult) | ||
45559 | { | ||
45560 | jx9_value *pStack; | ||
45561 | sxi32 rc; | ||
45562 | /* Allocate a new operand stack */ | ||
45563 | pStack = VmNewOperandStack(&(*pVm), SySetUsed(pByteCode)); | ||
45564 | if( pStack == 0 ){ | ||
45565 | return SXERR_MEM; | ||
45566 | } | ||
45567 | /* Execute the program */ | ||
45568 | rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pByteCode), pStack, -1, &(*pResult)); | ||
45569 | /* Free the operand stack */ | ||
45570 | SyMemBackendFree(&pVm->sAllocator, pStack); | ||
45571 | /* Execution result */ | ||
45572 | return rc; | ||
45573 | } | ||
45574 | /* | ||
45575 | * Execute as much of a JX9 bytecode program as we can then return. | ||
45576 | * This function is a wrapper around [VmByteCodeExec()]. | ||
45577 | * See block-comment on that function for additional information. | ||
45578 | */ | ||
45579 | JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm) | ||
45580 | { | ||
45581 | /* Make sure we are ready to execute this program */ | ||
45582 | if( pVm->nMagic != JX9_VM_RUN ){ | ||
45583 | return pVm->nMagic == JX9_VM_EXEC ? SXERR_LOCKED /* Locked VM */ : SXERR_CORRUPT; /* Stale VM */ | ||
45584 | } | ||
45585 | /* Set the execution magic number */ | ||
45586 | pVm->nMagic = JX9_VM_EXEC; | ||
45587 | /* Execute the program */ | ||
45588 | VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pVm->pByteContainer), pVm->aOps, -1, &pVm->sExec); | ||
45589 | /* | ||
45590 | * TICKET 1433-100: Do not remove the JX9_VM_EXEC magic number | ||
45591 | * so that any following call to [jx9_vm_exec()] without calling | ||
45592 | * [jx9_vm_reset()] first would fail. | ||
45593 | */ | ||
45594 | return SXRET_OK; | ||
45595 | } | ||
45596 | /* | ||
45597 | * Extract a memory object (i.e. a variable) from the running script. | ||
45598 | * This function must be called after calling jx9_vm_exec(). Otherwise | ||
45599 | * NULL is returned. | ||
45600 | */ | ||
45601 | JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar) | ||
45602 | { | ||
45603 | jx9_value *pValue; | ||
45604 | if( pVm->nMagic != JX9_VM_EXEC ){ | ||
45605 | /* call jx9_vm_exec() first */ | ||
45606 | return 0; | ||
45607 | } | ||
45608 | /* Perform the lookup */ | ||
45609 | pValue = VmExtractMemObj(pVm,pVar,FALSE,FALSE); | ||
45610 | /* Lookup result */ | ||
45611 | return pValue; | ||
45612 | } | ||
45613 | /* | ||
45614 | * Invoke the installed VM output consumer callback to consume | ||
45615 | * the desired message. | ||
45616 | * Refer to the implementation of [jx9_context_output()] defined | ||
45617 | * in 'api.c' for additional information. | ||
45618 | */ | ||
45619 | JX9_PRIVATE sxi32 jx9VmOutputConsume( | ||
45620 | jx9_vm *pVm, /* Target VM */ | ||
45621 | SyString *pString /* Message to output */ | ||
45622 | ) | ||
45623 | { | ||
45624 | jx9_output_consumer *pCons = &pVm->sVmConsumer; | ||
45625 | sxi32 rc = SXRET_OK; | ||
45626 | /* Call the output consumer */ | ||
45627 | if( pString->nByte > 0 ){ | ||
45628 | rc = pCons->xConsumer((const void *)pString->zString, pString->nByte, pCons->pUserData); | ||
45629 | /* Increment output length */ | ||
45630 | pVm->nOutputLen += pString->nByte; | ||
45631 | } | ||
45632 | return rc; | ||
45633 | } | ||
45634 | /* | ||
45635 | * Format a message and invoke the installed VM output consumer | ||
45636 | * callback to consume the formatted message. | ||
45637 | * Refer to the implementation of [jx9_context_output_format()] defined | ||
45638 | * in 'api.c' for additional information. | ||
45639 | */ | ||
45640 | JX9_PRIVATE sxi32 jx9VmOutputConsumeAp( | ||
45641 | jx9_vm *pVm, /* Target VM */ | ||
45642 | const char *zFormat, /* Formatted message to output */ | ||
45643 | va_list ap /* Variable list of arguments */ | ||
45644 | ) | ||
45645 | { | ||
45646 | jx9_output_consumer *pCons = &pVm->sVmConsumer; | ||
45647 | sxi32 rc = SXRET_OK; | ||
45648 | SyBlob sWorker; | ||
45649 | /* Format the message and call the output consumer */ | ||
45650 | SyBlobInit(&sWorker, &pVm->sAllocator); | ||
45651 | SyBlobFormatAp(&sWorker, zFormat, ap); | ||
45652 | if( SyBlobLength(&sWorker) > 0 ){ | ||
45653 | /* Consume the formatted message */ | ||
45654 | rc = pCons->xConsumer(SyBlobData(&sWorker), SyBlobLength(&sWorker), pCons->pUserData); | ||
45655 | } | ||
45656 | /* Increment output length */ | ||
45657 | pVm->nOutputLen += SyBlobLength(&sWorker); | ||
45658 | /* Release the working buffer */ | ||
45659 | SyBlobRelease(&sWorker); | ||
45660 | return rc; | ||
45661 | } | ||
45662 | /* | ||
45663 | * Return a string representation of the given JX9 OP code. | ||
45664 | * This function never fail and always return a pointer | ||
45665 | * to a null terminated string. | ||
45666 | */ | ||
45667 | static const char * VmInstrToString(sxi32 nOp) | ||
45668 | { | ||
45669 | const char *zOp = "Unknown "; | ||
45670 | switch(nOp){ | ||
45671 | case JX9_OP_DONE: zOp = "DONE "; break; | ||
45672 | case JX9_OP_HALT: zOp = "HALT "; break; | ||
45673 | case JX9_OP_LOAD: zOp = "LOAD "; break; | ||
45674 | case JX9_OP_LOADC: zOp = "LOADC "; break; | ||
45675 | case JX9_OP_LOAD_MAP: zOp = "LOAD_MAP "; break; | ||
45676 | case JX9_OP_LOAD_IDX: zOp = "LOAD_IDX "; break; | ||
45677 | case JX9_OP_NOOP: zOp = "NOOP "; break; | ||
45678 | case JX9_OP_JMP: zOp = "JMP "; break; | ||
45679 | case JX9_OP_JZ: zOp = "JZ "; break; | ||
45680 | case JX9_OP_JNZ: zOp = "JNZ "; break; | ||
45681 | case JX9_OP_POP: zOp = "POP "; break; | ||
45682 | case JX9_OP_CAT: zOp = "CAT "; break; | ||
45683 | case JX9_OP_CVT_INT: zOp = "CVT_INT "; break; | ||
45684 | case JX9_OP_CVT_STR: zOp = "CVT_STR "; break; | ||
45685 | case JX9_OP_CVT_REAL: zOp = "CVT_REAL "; break; | ||
45686 | case JX9_OP_CALL: zOp = "CALL "; break; | ||
45687 | case JX9_OP_UMINUS: zOp = "UMINUS "; break; | ||
45688 | case JX9_OP_UPLUS: zOp = "UPLUS "; break; | ||
45689 | case JX9_OP_BITNOT: zOp = "BITNOT "; break; | ||
45690 | case JX9_OP_LNOT: zOp = "LOGNOT "; break; | ||
45691 | case JX9_OP_MUL: zOp = "MUL "; break; | ||
45692 | case JX9_OP_DIV: zOp = "DIV "; break; | ||
45693 | case JX9_OP_MOD: zOp = "MOD "; break; | ||
45694 | case JX9_OP_ADD: zOp = "ADD "; break; | ||
45695 | case JX9_OP_SUB: zOp = "SUB "; break; | ||
45696 | case JX9_OP_SHL: zOp = "SHL "; break; | ||
45697 | case JX9_OP_SHR: zOp = "SHR "; break; | ||
45698 | case JX9_OP_LT: zOp = "LT "; break; | ||
45699 | case JX9_OP_LE: zOp = "LE "; break; | ||
45700 | case JX9_OP_GT: zOp = "GT "; break; | ||
45701 | case JX9_OP_GE: zOp = "GE "; break; | ||
45702 | case JX9_OP_EQ: zOp = "EQ "; break; | ||
45703 | case JX9_OP_NEQ: zOp = "NEQ "; break; | ||
45704 | case JX9_OP_TEQ: zOp = "TEQ "; break; | ||
45705 | case JX9_OP_TNE: zOp = "TNE "; break; | ||
45706 | case JX9_OP_BAND: zOp = "BITAND "; break; | ||
45707 | case JX9_OP_BXOR: zOp = "BITXOR "; break; | ||
45708 | case JX9_OP_BOR: zOp = "BITOR "; break; | ||
45709 | case JX9_OP_LAND: zOp = "LOGAND "; break; | ||
45710 | case JX9_OP_LOR: zOp = "LOGOR "; break; | ||
45711 | case JX9_OP_LXOR: zOp = "LOGXOR "; break; | ||
45712 | case JX9_OP_STORE: zOp = "STORE "; break; | ||
45713 | case JX9_OP_STORE_IDX: zOp = "STORE_IDX "; break; | ||
45714 | case JX9_OP_PULL: zOp = "PULL "; break; | ||
45715 | case JX9_OP_SWAP: zOp = "SWAP "; break; | ||
45716 | case JX9_OP_YIELD: zOp = "YIELD "; break; | ||
45717 | case JX9_OP_CVT_BOOL: zOp = "CVT_BOOL "; break; | ||
45718 | case JX9_OP_CVT_NULL: zOp = "CVT_NULL "; break; | ||
45719 | case JX9_OP_CVT_ARRAY: zOp = "CVT_JSON "; break; | ||
45720 | case JX9_OP_CVT_NUMC: zOp = "CVT_NUMC "; break; | ||
45721 | case JX9_OP_INCR: zOp = "INCR "; break; | ||
45722 | case JX9_OP_DECR: zOp = "DECR "; break; | ||
45723 | case JX9_OP_ADD_STORE: zOp = "ADD_STORE "; break; | ||
45724 | case JX9_OP_SUB_STORE: zOp = "SUB_STORE "; break; | ||
45725 | case JX9_OP_MUL_STORE: zOp = "MUL_STORE "; break; | ||
45726 | case JX9_OP_DIV_STORE: zOp = "DIV_STORE "; break; | ||
45727 | case JX9_OP_MOD_STORE: zOp = "MOD_STORE "; break; | ||
45728 | case JX9_OP_CAT_STORE: zOp = "CAT_STORE "; break; | ||
45729 | case JX9_OP_SHL_STORE: zOp = "SHL_STORE "; break; | ||
45730 | case JX9_OP_SHR_STORE: zOp = "SHR_STORE "; break; | ||
45731 | case JX9_OP_BAND_STORE: zOp = "BAND_STORE "; break; | ||
45732 | case JX9_OP_BOR_STORE: zOp = "BOR_STORE "; break; | ||
45733 | case JX9_OP_BXOR_STORE: zOp = "BXOR_STORE "; break; | ||
45734 | case JX9_OP_CONSUME: zOp = "CONSUME "; break; | ||
45735 | case JX9_OP_MEMBER: zOp = "MEMBER "; break; | ||
45736 | case JX9_OP_UPLINK: zOp = "UPLINK "; break; | ||
45737 | case JX9_OP_SWITCH: zOp = "SWITCH "; break; | ||
45738 | case JX9_OP_FOREACH_INIT: | ||
45739 | zOp = "4EACH_INIT "; break; | ||
45740 | case JX9_OP_FOREACH_STEP: | ||
45741 | zOp = "4EACH_STEP "; break; | ||
45742 | default: | ||
45743 | break; | ||
45744 | } | ||
45745 | return zOp; | ||
45746 | } | ||
45747 | /* | ||
45748 | * Dump JX9 bytecodes instructions to a human readable format. | ||
45749 | * The xConsumer() callback which is an used defined function | ||
45750 | * is responsible of consuming the generated dump. | ||
45751 | */ | ||
45752 | JX9_PRIVATE sxi32 jx9VmDump( | ||
45753 | jx9_vm *pVm, /* Target VM */ | ||
45754 | ProcConsumer xConsumer, /* Output [i.e: dump] consumer callback */ | ||
45755 | void *pUserData /* Last argument to xConsumer() */ | ||
45756 | ) | ||
45757 | { | ||
45758 | sxi32 rc; | ||
45759 | rc = VmByteCodeDump(pVm->pByteContainer, xConsumer, pUserData); | ||
45760 | return rc; | ||
45761 | } | ||
45762 | /* | ||
45763 | * Default constant expansion callback used by the 'const' statement if used | ||
45764 | * outside a object body [i.e: global or function scope]. | ||
45765 | * Refer to the implementation of [JX9_CompileConstant()] defined | ||
45766 | * in 'compile.c' for additional information. | ||
45767 | */ | ||
45768 | JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData) | ||
45769 | { | ||
45770 | SySet *pByteCode = (SySet *)pUserData; | ||
45771 | /* Evaluate and expand constant value */ | ||
45772 | VmLocalExec((jx9_vm *)SySetGetUserData(pByteCode), pByteCode, (jx9_value *)pVal); | ||
45773 | } | ||
45774 | /* | ||
45775 | * Section: | ||
45776 | * Function handling functions. | ||
45777 | * Authors: | ||
45778 | * Symisc Systems, devel@symisc.net. | ||
45779 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
45780 | * Status: | ||
45781 | * Stable. | ||
45782 | */ | ||
45783 | /* | ||
45784 | * int func_num_args(void) | ||
45785 | * Returns the number of arguments passed to the function. | ||
45786 | * Parameters | ||
45787 | * None. | ||
45788 | * Return | ||
45789 | * Total number of arguments passed into the current user-defined function | ||
45790 | * or -1 if called from the globe scope. | ||
45791 | */ | ||
45792 | static int vm_builtin_func_num_args(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
45793 | { | ||
45794 | VmFrame *pFrame; | ||
45795 | jx9_vm *pVm; | ||
45796 | /* Point to the target VM */ | ||
45797 | pVm = pCtx->pVm; | ||
45798 | /* Current frame */ | ||
45799 | pFrame = pVm->pFrame; | ||
45800 | if( pFrame->pParent == 0 ){ | ||
45801 | SXUNUSED(nArg); | ||
45802 | SXUNUSED(apArg); | ||
45803 | /* Global frame, return -1 */ | ||
45804 | jx9_result_int(pCtx, -1); | ||
45805 | return SXRET_OK; | ||
45806 | } | ||
45807 | /* Total number of arguments passed to the enclosing function */ | ||
45808 | nArg = (int)SySetUsed(&pFrame->sArg); | ||
45809 | jx9_result_int(pCtx, nArg); | ||
45810 | return SXRET_OK; | ||
45811 | } | ||
45812 | /* | ||
45813 | * value func_get_arg(int $arg_num) | ||
45814 | * Return an item from the argument list. | ||
45815 | * Parameters | ||
45816 | * Argument number(index start from zero). | ||
45817 | * Return | ||
45818 | * Returns the specified argument or FALSE on error. | ||
45819 | */ | ||
45820 | static int vm_builtin_func_get_arg(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
45821 | { | ||
45822 | jx9_value *pObj = 0; | ||
45823 | VmSlot *pSlot = 0; | ||
45824 | VmFrame *pFrame; | ||
45825 | jx9_vm *pVm; | ||
45826 | /* Point to the target VM */ | ||
45827 | pVm = pCtx->pVm; | ||
45828 | /* Current frame */ | ||
45829 | pFrame = pVm->pFrame; | ||
45830 | if( nArg < 1 || pFrame->pParent == 0 ){ | ||
45831 | /* Global frame or Missing arguments, return FALSE */ | ||
45832 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope"); | ||
45833 | jx9_result_bool(pCtx, 0); | ||
45834 | return SXRET_OK; | ||
45835 | } | ||
45836 | /* Extract the desired index */ | ||
45837 | nArg = jx9_value_to_int(apArg[0]); | ||
45838 | if( nArg < 0 || nArg >= (int)SySetUsed(&pFrame->sArg) ){ | ||
45839 | /* Invalid index, return FALSE */ | ||
45840 | jx9_result_bool(pCtx, 0); | ||
45841 | return SXRET_OK; | ||
45842 | } | ||
45843 | /* Extract the desired argument */ | ||
45844 | if( (pSlot = (VmSlot *)SySetAt(&pFrame->sArg, (sxu32)nArg)) != 0 ){ | ||
45845 | if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx)) != 0 ){ | ||
45846 | /* Return the desired argument */ | ||
45847 | jx9_result_value(pCtx, (jx9_value *)pObj); | ||
45848 | }else{ | ||
45849 | /* No such argument, return false */ | ||
45850 | jx9_result_bool(pCtx, 0); | ||
45851 | } | ||
45852 | }else{ | ||
45853 | /* CAN'T HAPPEN */ | ||
45854 | jx9_result_bool(pCtx, 0); | ||
45855 | } | ||
45856 | return SXRET_OK; | ||
45857 | } | ||
45858 | /* | ||
45859 | * array func_get_args(void) | ||
45860 | * Returns an array comprising a copy of function's argument list. | ||
45861 | * Parameters | ||
45862 | * None. | ||
45863 | * Return | ||
45864 | * Returns an array in which each element is a copy of the corresponding | ||
45865 | * member of the current user-defined function's argument list. | ||
45866 | * Otherwise FALSE is returned on failure. | ||
45867 | */ | ||
45868 | static int vm_builtin_func_get_args(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
45869 | { | ||
45870 | jx9_value *pObj = 0; | ||
45871 | jx9_value *pArray; | ||
45872 | VmFrame *pFrame; | ||
45873 | VmSlot *aSlot; | ||
45874 | sxu32 n; | ||
45875 | /* Point to the current frame */ | ||
45876 | pFrame = pCtx->pVm->pFrame; | ||
45877 | if( pFrame->pParent == 0 ){ | ||
45878 | /* Global frame, return FALSE */ | ||
45879 | jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope"); | ||
45880 | jx9_result_bool(pCtx, 0); | ||
45881 | return SXRET_OK; | ||
45882 | } | ||
45883 | /* Create a new array */ | ||
45884 | pArray = jx9_context_new_array(pCtx); | ||
45885 | if( pArray == 0 ){ | ||
45886 | SXUNUSED(nArg); /* cc warning */ | ||
45887 | SXUNUSED(apArg); | ||
45888 | jx9_result_bool(pCtx, 0); | ||
45889 | return SXRET_OK; | ||
45890 | } | ||
45891 | /* Start filling the array with the given arguments */ | ||
45892 | aSlot = (VmSlot *)SySetBasePtr(&pFrame->sArg); | ||
45893 | for( n = 0; n < SySetUsed(&pFrame->sArg) ; n++ ){ | ||
45894 | pObj = (jx9_value *)SySetAt(&pCtx->pVm->aMemObj, aSlot[n].nIdx); | ||
45895 | if( pObj ){ | ||
45896 | jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pObj); | ||
45897 | } | ||
45898 | } | ||
45899 | /* Return the freshly created array */ | ||
45900 | jx9_result_value(pCtx, pArray); | ||
45901 | return SXRET_OK; | ||
45902 | } | ||
45903 | /* | ||
45904 | * bool function_exists(string $name) | ||
45905 | * Return TRUE if the given function has been defined. | ||
45906 | * Parameters | ||
45907 | * The name of the desired function. | ||
45908 | * Return | ||
45909 | * Return TRUE if the given function has been defined.False otherwise | ||
45910 | */ | ||
45911 | static int vm_builtin_func_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
45912 | { | ||
45913 | const char *zName; | ||
45914 | jx9_vm *pVm; | ||
45915 | int nLen; | ||
45916 | int res; | ||
45917 | if( nArg < 1 ){ | ||
45918 | /* Missing argument, return FALSE */ | ||
45919 | jx9_result_bool(pCtx, 0); | ||
45920 | return SXRET_OK; | ||
45921 | } | ||
45922 | /* Point to the target VM */ | ||
45923 | pVm = pCtx->pVm; | ||
45924 | /* Extract the function name */ | ||
45925 | zName = jx9_value_to_string(apArg[0], &nLen); | ||
45926 | /* Assume the function is not defined */ | ||
45927 | res = 0; | ||
45928 | /* Perform the lookup */ | ||
45929 | if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 || | ||
45930 | SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){ | ||
45931 | /* Function is defined */ | ||
45932 | res = 1; | ||
45933 | } | ||
45934 | jx9_result_bool(pCtx, res); | ||
45935 | return SXRET_OK; | ||
45936 | } | ||
45937 | /* | ||
45938 | * Verify that the contents of a variable can be called as a function. | ||
45939 | * [i.e: Whether it is callable or not]. | ||
45940 | * Return TRUE if callable.FALSE otherwise. | ||
45941 | */ | ||
45942 | JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue) | ||
45943 | { | ||
45944 | int res = 0; | ||
45945 | if( pValue->iFlags & MEMOBJ_STRING ){ | ||
45946 | const char *zName; | ||
45947 | int nLen; | ||
45948 | /* Extract the name */ | ||
45949 | zName = jx9_value_to_string(pValue, &nLen); | ||
45950 | /* Perform the lookup */ | ||
45951 | if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 || | ||
45952 | SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){ | ||
45953 | /* Function is callable */ | ||
45954 | res = 1; | ||
45955 | } | ||
45956 | } | ||
45957 | return res; | ||
45958 | } | ||
45959 | /* | ||
45960 | * bool is_callable(callable $name[, bool $syntax_only = false]) | ||
45961 | * Verify that the contents of a variable can be called as a function. | ||
45962 | * Parameters | ||
45963 | * $name | ||
45964 | * The callback function to check | ||
45965 | * $syntax_only | ||
45966 | * If set to TRUE the function only verifies that name might be a function or method. | ||
45967 | * It will only reject simple variables that are not strings, or an array that does | ||
45968 | * not have a valid structure to be used as a callback. The valid ones are supposed | ||
45969 | * to have only 2 entries, the first of which is an object or a string, and the second | ||
45970 | * a string. | ||
45971 | * Return | ||
45972 | * TRUE if name is callable, FALSE otherwise. | ||
45973 | */ | ||
45974 | static int vm_builtin_is_callable(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
45975 | { | ||
45976 | jx9_vm *pVm; | ||
45977 | int res; | ||
45978 | if( nArg < 1 ){ | ||
45979 | /* Missing arguments, return FALSE */ | ||
45980 | jx9_result_bool(pCtx, 0); | ||
45981 | return SXRET_OK; | ||
45982 | } | ||
45983 | /* Point to the target VM */ | ||
45984 | pVm = pCtx->pVm; | ||
45985 | /* Perform the requested operation */ | ||
45986 | res = jx9VmIsCallable(pVm, apArg[0]); | ||
45987 | jx9_result_bool(pCtx, res); | ||
45988 | return SXRET_OK; | ||
45989 | } | ||
45990 | /* | ||
45991 | * Hash walker callback used by the [get_defined_functions()] function | ||
45992 | * defined below. | ||
45993 | */ | ||
45994 | static int VmHashFuncStep(SyHashEntry *pEntry, void *pUserData) | ||
45995 | { | ||
45996 | jx9_value *pArray = (jx9_value *)pUserData; | ||
45997 | jx9_value sName; | ||
45998 | sxi32 rc; | ||
45999 | /* Prepare the function name for insertion */ | ||
46000 | jx9MemObjInitFromString(pArray->pVm, &sName, 0); | ||
46001 | jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen); | ||
46002 | /* Perform the insertion */ | ||
46003 | rc = jx9_array_add_elem(pArray, 0/* Automatic index assign */, &sName); /* Will make it's own copy */ | ||
46004 | jx9MemObjRelease(&sName); | ||
46005 | return rc; | ||
46006 | } | ||
46007 | /* | ||
46008 | * array get_defined_functions(void) | ||
46009 | * Returns an array of all defined functions. | ||
46010 | * Parameter | ||
46011 | * None. | ||
46012 | * Return | ||
46013 | * Returns an multidimensional array containing a list of all defined functions | ||
46014 | * both built-in (internal) and user-defined. | ||
46015 | * The internal functions will be accessible via $arr["internal"], and the user | ||
46016 | * defined ones using $arr["user"]. | ||
46017 | * Note: | ||
46018 | * NULL is returned on failure. | ||
46019 | */ | ||
46020 | static int vm_builtin_get_defined_func(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46021 | { | ||
46022 | jx9_value *pArray; | ||
46023 | /* NOTE: | ||
46024 | * Don't worry about freeing memory here, every allocated resource will be released | ||
46025 | * automatically by the engine as soon we return from this foreign function. | ||
46026 | */ | ||
46027 | pArray = jx9_context_new_array(pCtx); | ||
46028 | if( pArray == 0 ){ | ||
46029 | SXUNUSED(nArg); /* cc warning */ | ||
46030 | SXUNUSED(apArg); | ||
46031 | /* Return NULL */ | ||
46032 | jx9_result_null(pCtx); | ||
46033 | return SXRET_OK; | ||
46034 | } | ||
46035 | /* Fill with the appropriate information */ | ||
46036 | SyHashForEach(&pCtx->pVm->hHostFunction,VmHashFuncStep,pArray); | ||
46037 | /* Fill with the appropriate information */ | ||
46038 | SyHashForEach(&pCtx->pVm->hFunction, VmHashFuncStep,pArray); | ||
46039 | /* Return a copy of the array array */ | ||
46040 | jx9_result_value(pCtx, pArray); | ||
46041 | return SXRET_OK; | ||
46042 | } | ||
46043 | /* | ||
46044 | * Call a user defined or foreign function where the name of the function | ||
46045 | * is stored in the pFunc parameter and the given arguments are stored | ||
46046 | * in the apArg[] array. | ||
46047 | * Return SXRET_OK if the function was successfuly called.Any other | ||
46048 | * return value indicates failure. | ||
46049 | */ | ||
46050 | JX9_PRIVATE sxi32 jx9VmCallUserFunction( | ||
46051 | jx9_vm *pVm, /* Target VM */ | ||
46052 | jx9_value *pFunc, /* Callback name */ | ||
46053 | int nArg, /* Total number of given arguments */ | ||
46054 | jx9_value **apArg, /* Callback arguments */ | ||
46055 | jx9_value *pResult /* Store callback return value here. NULL otherwise */ | ||
46056 | ) | ||
46057 | { | ||
46058 | jx9_value *aStack; | ||
46059 | VmInstr aInstr[2]; | ||
46060 | int i; | ||
46061 | if((pFunc->iFlags & (MEMOBJ_STRING)) == 0 ){ | ||
46062 | /* Don't bother processing, it's invalid anyway */ | ||
46063 | if( pResult ){ | ||
46064 | /* Assume a null return value */ | ||
46065 | jx9MemObjRelease(pResult); | ||
46066 | } | ||
46067 | return SXERR_INVALID; | ||
46068 | } | ||
46069 | /* Create a new operand stack */ | ||
46070 | aStack = VmNewOperandStack(&(*pVm), 1+nArg); | ||
46071 | if( aStack == 0 ){ | ||
46072 | jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, | ||
46073 | "JX9 is running out of memory while invoking user callback"); | ||
46074 | if( pResult ){ | ||
46075 | /* Assume a null return value */ | ||
46076 | jx9MemObjRelease(pResult); | ||
46077 | } | ||
46078 | return SXERR_MEM; | ||
46079 | } | ||
46080 | /* Fill the operand stack with the given arguments */ | ||
46081 | for( i = 0 ; i < nArg ; i++ ){ | ||
46082 | jx9MemObjLoad(apArg[i], &aStack[i]); | ||
46083 | aStack[i].nIdx = apArg[i]->nIdx; | ||
46084 | } | ||
46085 | /* Push the function name */ | ||
46086 | jx9MemObjLoad(pFunc, &aStack[i]); | ||
46087 | aStack[i].nIdx = SXU32_HIGH; /* Mark as constant */ | ||
46088 | /* Emit the CALL istruction */ | ||
46089 | aInstr[0].iOp = JX9_OP_CALL; | ||
46090 | aInstr[0].iP1 = nArg; /* Total number of given arguments */ | ||
46091 | aInstr[0].iP2 = 0; | ||
46092 | aInstr[0].p3 = 0; | ||
46093 | /* Emit the DONE instruction */ | ||
46094 | aInstr[1].iOp = JX9_OP_DONE; | ||
46095 | aInstr[1].iP1 = 1; /* Extract function return value if available */ | ||
46096 | aInstr[1].iP2 = 0; | ||
46097 | aInstr[1].p3 = 0; | ||
46098 | /* Execute the function body (if available) */ | ||
46099 | VmByteCodeExec(&(*pVm), aInstr, aStack, nArg, pResult); | ||
46100 | /* Clean up the mess left behind */ | ||
46101 | SyMemBackendFree(&pVm->sAllocator, aStack); | ||
46102 | return JX9_OK; | ||
46103 | } | ||
46104 | /* | ||
46105 | * Call a user defined or foreign function whith a varibale number | ||
46106 | * of arguments where the name of the function is stored in the pFunc | ||
46107 | * parameter. | ||
46108 | * Return SXRET_OK if the function was successfuly called.Any other | ||
46109 | * return value indicates failure. | ||
46110 | */ | ||
46111 | JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp( | ||
46112 | jx9_vm *pVm, /* Target VM */ | ||
46113 | jx9_value *pFunc, /* Callback name */ | ||
46114 | jx9_value *pResult, /* Store callback return value here. NULL otherwise */ | ||
46115 | ... /* 0 (Zero) or more Callback arguments */ | ||
46116 | ) | ||
46117 | { | ||
46118 | jx9_value *pArg; | ||
46119 | SySet aArg; | ||
46120 | va_list ap; | ||
46121 | sxi32 rc; | ||
46122 | SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *)); | ||
46123 | /* Copy arguments one after one */ | ||
46124 | va_start(ap, pResult); | ||
46125 | for(;;){ | ||
46126 | pArg = va_arg(ap, jx9_value *); | ||
46127 | if( pArg == 0 ){ | ||
46128 | break; | ||
46129 | } | ||
46130 | SySetPut(&aArg, (const void *)&pArg); | ||
46131 | } | ||
46132 | /* Call the core routine */ | ||
46133 | rc = jx9VmCallUserFunction(&(*pVm), pFunc, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg), pResult); | ||
46134 | /* Cleanup */ | ||
46135 | SySetRelease(&aArg); | ||
46136 | return rc; | ||
46137 | } | ||
46138 | /* | ||
46139 | * bool defined(string $name) | ||
46140 | * Checks whether a given named constant exists. | ||
46141 | * Parameter: | ||
46142 | * Name of the desired constant. | ||
46143 | * Return | ||
46144 | * TRUE if the given constant exists.FALSE otherwise. | ||
46145 | */ | ||
46146 | static int vm_builtin_defined(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46147 | { | ||
46148 | const char *zName; | ||
46149 | int nLen = 0; | ||
46150 | int res = 0; | ||
46151 | if( nArg < 1 ){ | ||
46152 | /* Missing constant name, return FALSE */ | ||
46153 | jx9_context_throw_error(pCtx,JX9_CTX_NOTICE,"Missing constant name"); | ||
46154 | jx9_result_bool(pCtx, 0); | ||
46155 | return SXRET_OK; | ||
46156 | } | ||
46157 | /* Extract constant name */ | ||
46158 | zName = jx9_value_to_string(apArg[0], &nLen); | ||
46159 | /* Perform the lookup */ | ||
46160 | if( nLen > 0 && SyHashGet(&pCtx->pVm->hConstant, (const void *)zName, (sxu32)nLen) != 0 ){ | ||
46161 | /* Already defined */ | ||
46162 | res = 1; | ||
46163 | } | ||
46164 | jx9_result_bool(pCtx, res); | ||
46165 | return SXRET_OK; | ||
46166 | } | ||
46167 | /* | ||
46168 | * Hash walker callback used by the [get_defined_constants()] function | ||
46169 | * defined below. | ||
46170 | */ | ||
46171 | static int VmHashConstStep(SyHashEntry *pEntry, void *pUserData) | ||
46172 | { | ||
46173 | jx9_value *pArray = (jx9_value *)pUserData; | ||
46174 | jx9_value sName; | ||
46175 | sxi32 rc; | ||
46176 | /* Prepare the constant name for insertion */ | ||
46177 | jx9MemObjInitFromString(pArray->pVm, &sName, 0); | ||
46178 | jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen); | ||
46179 | /* Perform the insertion */ | ||
46180 | rc = jx9_array_add_elem(pArray, 0, &sName); /* Will make it's own copy */ | ||
46181 | jx9MemObjRelease(&sName); | ||
46182 | return rc; | ||
46183 | } | ||
46184 | /* | ||
46185 | * array get_defined_constants(void) | ||
46186 | * Returns an associative array with the names of all defined | ||
46187 | * constants. | ||
46188 | * Parameters | ||
46189 | * NONE. | ||
46190 | * Returns | ||
46191 | * Returns the names of all the constants currently defined. | ||
46192 | */ | ||
46193 | static int vm_builtin_get_defined_constants(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46194 | { | ||
46195 | jx9_value *pArray; | ||
46196 | /* Create the array first*/ | ||
46197 | pArray = jx9_context_new_array(pCtx); | ||
46198 | if( pArray == 0 ){ | ||
46199 | SXUNUSED(nArg); /* cc warning */ | ||
46200 | SXUNUSED(apArg); | ||
46201 | /* Return NULL */ | ||
46202 | jx9_result_null(pCtx); | ||
46203 | return SXRET_OK; | ||
46204 | } | ||
46205 | /* Fill the array with the defined constants */ | ||
46206 | SyHashForEach(&pCtx->pVm->hConstant, VmHashConstStep, pArray); | ||
46207 | /* Return the created array */ | ||
46208 | jx9_result_value(pCtx, pArray); | ||
46209 | return SXRET_OK; | ||
46210 | } | ||
46211 | /* | ||
46212 | * Section: | ||
46213 | * Random numbers/string generators. | ||
46214 | * Authors: | ||
46215 | * Symisc Systems, devel@symisc.net. | ||
46216 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
46217 | * Status: | ||
46218 | * Stable. | ||
46219 | */ | ||
46220 | /* | ||
46221 | * Generate a random 32-bit unsigned integer. | ||
46222 | * JX9 use it's own private PRNG which is based on the one | ||
46223 | * used by te SQLite3 library. | ||
46224 | */ | ||
46225 | JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm) | ||
46226 | { | ||
46227 | sxu32 iNum; | ||
46228 | SyRandomness(&pVm->sPrng, (void *)&iNum, sizeof(sxu32)); | ||
46229 | return iNum; | ||
46230 | } | ||
46231 | /* | ||
46232 | * Generate a random string (English Alphabet) of length nLen. | ||
46233 | * Note that the generated string is NOT null terminated. | ||
46234 | * JX9 use it's own private PRNG which is based on the one used | ||
46235 | * by te SQLite3 library. | ||
46236 | */ | ||
46237 | JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen) | ||
46238 | { | ||
46239 | static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */ | ||
46240 | int i; | ||
46241 | /* Generate a binary string first */ | ||
46242 | SyRandomness(&pVm->sPrng, zBuf, (sxu32)nLen); | ||
46243 | /* Turn the binary string into english based alphabet */ | ||
46244 | for( i = 0 ; i < nLen ; ++i ){ | ||
46245 | zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)]; | ||
46246 | } | ||
46247 | } | ||
46248 | /* | ||
46249 | * int rand() | ||
46250 | * Generate a random (unsigned 32-bit) integer. | ||
46251 | * Parameter | ||
46252 | * $min | ||
46253 | * The lowest value to return (default: 0) | ||
46254 | * $max | ||
46255 | * The highest value to return (default: getrandmax()) | ||
46256 | * Return | ||
46257 | * A pseudo random value between min (or 0) and max (or getrandmax(), inclusive). | ||
46258 | * Note: | ||
46259 | * JX9 use it's own private PRNG which is based on the one used | ||
46260 | * by te SQLite3 library. | ||
46261 | */ | ||
46262 | static int vm_builtin_rand(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46263 | { | ||
46264 | sxu32 iNum; | ||
46265 | /* Generate the random number */ | ||
46266 | iNum = jx9VmRandomNum(pCtx->pVm); | ||
46267 | if( nArg > 1 ){ | ||
46268 | sxu32 iMin, iMax; | ||
46269 | iMin = (sxu32)jx9_value_to_int(apArg[0]); | ||
46270 | iMax = (sxu32)jx9_value_to_int(apArg[1]); | ||
46271 | if( iMin < iMax ){ | ||
46272 | sxu32 iDiv = iMax+1-iMin; | ||
46273 | if( iDiv > 0 ){ | ||
46274 | iNum = (iNum % iDiv)+iMin; | ||
46275 | } | ||
46276 | }else if(iMax > 0 ){ | ||
46277 | iNum %= iMax; | ||
46278 | } | ||
46279 | } | ||
46280 | /* Return the number */ | ||
46281 | jx9_result_int64(pCtx, (jx9_int64)iNum); | ||
46282 | return SXRET_OK; | ||
46283 | } | ||
46284 | /* | ||
46285 | * int getrandmax(void) | ||
46286 | * Show largest possible random value | ||
46287 | * Return | ||
46288 | * The largest possible random value returned by rand() which is in | ||
46289 | * this implementation 0xFFFFFFFF. | ||
46290 | * Note: | ||
46291 | * JX9 use it's own private PRNG which is based on the one used | ||
46292 | * by te SQLite3 library. | ||
46293 | */ | ||
46294 | static int vm_builtin_getrandmax(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46295 | { | ||
46296 | SXUNUSED(nArg); /* cc warning */ | ||
46297 | SXUNUSED(apArg); | ||
46298 | jx9_result_int64(pCtx, SXU32_HIGH); | ||
46299 | return SXRET_OK; | ||
46300 | } | ||
46301 | /* | ||
46302 | * string rand_str() | ||
46303 | * string rand_str(int $len) | ||
46304 | * Generate a random string (English alphabet). | ||
46305 | * Parameter | ||
46306 | * $len | ||
46307 | * Length of the desired string (default: 16, Min: 1, Max: 1024) | ||
46308 | * Return | ||
46309 | * A pseudo random string. | ||
46310 | * Note: | ||
46311 | * JX9 use it's own private PRNG which is based on the one used | ||
46312 | * by te SQLite3 library. | ||
46313 | */ | ||
46314 | static int vm_builtin_rand_str(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46315 | { | ||
46316 | char zString[1024]; | ||
46317 | int iLen = 0x10; | ||
46318 | if( nArg > 0 ){ | ||
46319 | /* Get the desired length */ | ||
46320 | iLen = jx9_value_to_int(apArg[0]); | ||
46321 | if( iLen < 1 || iLen > 1024 ){ | ||
46322 | /* Default length */ | ||
46323 | iLen = 0x10; | ||
46324 | } | ||
46325 | } | ||
46326 | /* Generate the random string */ | ||
46327 | jx9VmRandomString(pCtx->pVm, zString, iLen); | ||
46328 | /* Return the generated string */ | ||
46329 | jx9_result_string(pCtx, zString, iLen); /* Will make it's own copy */ | ||
46330 | return SXRET_OK; | ||
46331 | } | ||
46332 | /* | ||
46333 | * Section: | ||
46334 | * Language construct implementation as foreign functions. | ||
46335 | * Authors: | ||
46336 | * Symisc Systems, devel@symisc.net. | ||
46337 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
46338 | * Status: | ||
46339 | * Stable. | ||
46340 | */ | ||
46341 | /* | ||
46342 | * void print($string...) | ||
46343 | * Output one or more messages. | ||
46344 | * Parameters | ||
46345 | * $string | ||
46346 | * Message to output. | ||
46347 | * Return | ||
46348 | * NULL. | ||
46349 | */ | ||
46350 | static int vm_builtin_print(jx9_context *pCtx, int nArg,jx9_value **apArg) | ||
46351 | { | ||
46352 | const char *zData; | ||
46353 | int nDataLen = 0; | ||
46354 | jx9_vm *pVm; | ||
46355 | int i, rc; | ||
46356 | /* Point to the target VM */ | ||
46357 | pVm = pCtx->pVm; | ||
46358 | /* Output */ | ||
46359 | for( i = 0 ; i < nArg ; ++i ){ | ||
46360 | zData = jx9_value_to_string(apArg[i], &nDataLen); | ||
46361 | if( nDataLen > 0 ){ | ||
46362 | rc = pVm->sVmConsumer.xConsumer((const void *)zData, (unsigned int)nDataLen, pVm->sVmConsumer.pUserData); | ||
46363 | /* Increment output length */ | ||
46364 | pVm->nOutputLen += nDataLen; | ||
46365 | if( rc == SXERR_ABORT ){ | ||
46366 | /* Output consumer callback request an operation abort */ | ||
46367 | return JX9_ABORT; | ||
46368 | } | ||
46369 | } | ||
46370 | } | ||
46371 | return SXRET_OK; | ||
46372 | } | ||
46373 | /* | ||
46374 | * void exit(string $msg) | ||
46375 | * void exit(int $status) | ||
46376 | * void die(string $ms) | ||
46377 | * void die(int $status) | ||
46378 | * Output a message and terminate program execution. | ||
46379 | * Parameter | ||
46380 | * If status is a string, this function prints the status just before exiting. | ||
46381 | * If status is an integer, that value will be used as the exit status | ||
46382 | * and not printed | ||
46383 | * Return | ||
46384 | * NULL | ||
46385 | */ | ||
46386 | static int vm_builtin_exit(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46387 | { | ||
46388 | if( nArg > 0 ){ | ||
46389 | if( jx9_value_is_string(apArg[0]) ){ | ||
46390 | const char *zData; | ||
46391 | int iLen = 0; | ||
46392 | /* Print exit message */ | ||
46393 | zData = jx9_value_to_string(apArg[0], &iLen); | ||
46394 | jx9_context_output(pCtx, zData, iLen); | ||
46395 | }else if(jx9_value_is_int(apArg[0]) ){ | ||
46396 | sxi32 iExitStatus; | ||
46397 | /* Record exit status code */ | ||
46398 | iExitStatus = jx9_value_to_int(apArg[0]); | ||
46399 | pCtx->pVm->iExitStatus = iExitStatus; | ||
46400 | } | ||
46401 | } | ||
46402 | /* Abort processing immediately */ | ||
46403 | return JX9_ABORT; | ||
46404 | } | ||
46405 | /* | ||
46406 | * Unset a memory object [i.e: a jx9_value]. | ||
46407 | */ | ||
46408 | JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm,sxu32 nObjIdx) | ||
46409 | { | ||
46410 | jx9_value *pObj; | ||
46411 | pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nObjIdx); | ||
46412 | if( pObj ){ | ||
46413 | VmSlot sFree; | ||
46414 | /* Release the object */ | ||
46415 | jx9MemObjRelease(pObj); | ||
46416 | /* Restore to the free list */ | ||
46417 | sFree.nIdx = nObjIdx; | ||
46418 | sFree.pUserData = 0; | ||
46419 | SySetPut(&pVm->aFreeObj, (const void *)&sFree); | ||
46420 | } | ||
46421 | return SXRET_OK; | ||
46422 | } | ||
46423 | /* | ||
46424 | * string gettype($var) | ||
46425 | * Get the type of a variable | ||
46426 | * Parameters | ||
46427 | * $var | ||
46428 | * The variable being type checked. | ||
46429 | * Return | ||
46430 | * String representation of the given variable type. | ||
46431 | */ | ||
46432 | static int vm_builtin_gettype(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46433 | { | ||
46434 | const char *zType = "null"; | ||
46435 | if( nArg > 0 ){ | ||
46436 | zType = jx9MemObjTypeDump(apArg[0]); | ||
46437 | } | ||
46438 | /* Return the variable type */ | ||
46439 | jx9_result_string(pCtx, zType, -1/*Compute length automatically*/); | ||
46440 | return SXRET_OK; | ||
46441 | } | ||
46442 | /* | ||
46443 | * string get_resource_type(resource $handle) | ||
46444 | * This function gets the type of the given resource. | ||
46445 | * Parameters | ||
46446 | * $handle | ||
46447 | * The evaluated resource handle. | ||
46448 | * Return | ||
46449 | * If the given handle is a resource, this function will return a string | ||
46450 | * representing its type. If the type is not identified by this function | ||
46451 | * the return value will be the string Unknown. | ||
46452 | * This function will return FALSE and generate an error if handle | ||
46453 | * is not a resource. | ||
46454 | */ | ||
46455 | static int vm_builtin_get_resource_type(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46456 | { | ||
46457 | if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ | ||
46458 | /* Missing/Invalid arguments, return FALSE*/ | ||
46459 | jx9_result_bool(pCtx, 0); | ||
46460 | return SXRET_OK; | ||
46461 | } | ||
46462 | jx9_result_string_format(pCtx, "resID_%#x", apArg[0]->x.pOther); | ||
46463 | return SXRET_OK; | ||
46464 | } | ||
46465 | /* | ||
46466 | * void dump(expression, ....) | ||
46467 | * dump — Dumps information about a variable | ||
46468 | * Parameters | ||
46469 | * One or more expression to dump. | ||
46470 | * Returns | ||
46471 | * Nothing. | ||
46472 | */ | ||
46473 | static int vm_builtin_dump(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46474 | { | ||
46475 | SyBlob sDump; /* Generated dump is stored here */ | ||
46476 | int i; | ||
46477 | SyBlobInit(&sDump,&pCtx->pVm->sAllocator); | ||
46478 | /* Dump one or more expressions */ | ||
46479 | for( i = 0 ; i < nArg ; i++ ){ | ||
46480 | jx9_value *pObj = apArg[i]; | ||
46481 | /* Reset the working buffer */ | ||
46482 | SyBlobReset(&sDump); | ||
46483 | /* Dump the given expression */ | ||
46484 | jx9MemObjDump(&sDump,pObj); | ||
46485 | /* Output */ | ||
46486 | if( SyBlobLength(&sDump) > 0 ){ | ||
46487 | jx9_context_output(pCtx, (const char *)SyBlobData(&sDump), (int)SyBlobLength(&sDump)); | ||
46488 | } | ||
46489 | } | ||
46490 | /* Release the working buffer */ | ||
46491 | SyBlobRelease(&sDump); | ||
46492 | return SXRET_OK; | ||
46493 | } | ||
46494 | /* | ||
46495 | * Section: | ||
46496 | * Version, Credits and Copyright related functions. | ||
46497 | * Authors: | ||
46498 | * Symisc Systems, devel@symisc.net. | ||
46499 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
46500 | * Stable. | ||
46501 | */ | ||
46502 | /* | ||
46503 | * string jx9_version(void) | ||
46504 | * string jx9_credits(void) | ||
46505 | * Returns the running version of the jx9 version. | ||
46506 | * Parameters | ||
46507 | * None | ||
46508 | * Return | ||
46509 | * Current jx9 version. | ||
46510 | */ | ||
46511 | static int vm_builtin_jx9_version(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46512 | { | ||
46513 | SXUNUSED(nArg); | ||
46514 | SXUNUSED(apArg); /* cc warning */ | ||
46515 | /* Current engine version, signature and cipyright notice */ | ||
46516 | jx9_result_string_format(pCtx,"%s %s, %s",JX9_VERSION,JX9_SIG,JX9_COPYRIGHT); | ||
46517 | return JX9_OK; | ||
46518 | } | ||
46519 | /* | ||
46520 | * Section: | ||
46521 | * URL related routines. | ||
46522 | * Authors: | ||
46523 | * Symisc Systems, devel@symisc.net. | ||
46524 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
46525 | * Status: | ||
46526 | * Stable. | ||
46527 | */ | ||
46528 | /* Forward declaration */ | ||
46529 | static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen); | ||
46530 | /* | ||
46531 | * value parse_url(string $url [, int $component = -1 ]) | ||
46532 | * Parse a URL and return its fields. | ||
46533 | * Parameters | ||
46534 | * $url | ||
46535 | * The URL to parse. | ||
46536 | * $component | ||
46537 | * Specify one of JX9_URL_SCHEME, JX9_URL_HOST, JX9_URL_PORT, JX9_URL_USER | ||
46538 | * JX9_URL_PASS, JX9_URL_PATH, JX9_URL_QUERY or JX9_URL_FRAGMENT to retrieve | ||
46539 | * just a specific URL component as a string (except when JX9_URL_PORT is given | ||
46540 | * in which case the return value will be an integer). | ||
46541 | * Return | ||
46542 | * If the component parameter is omitted, an associative array is returned. | ||
46543 | * At least one element will be present within the array. Potential keys within | ||
46544 | * this array are: | ||
46545 | * scheme - e.g. http | ||
46546 | * host | ||
46547 | * port | ||
46548 | * user | ||
46549 | * pass | ||
46550 | * path | ||
46551 | * query - after the question mark ? | ||
46552 | * fragment - after the hashmark # | ||
46553 | * Note: | ||
46554 | * FALSE is returned on failure. | ||
46555 | * This function work with relative URL unlike the one shipped | ||
46556 | * with the standard JX9 engine. | ||
46557 | */ | ||
46558 | static int vm_builtin_parse_url(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46559 | { | ||
46560 | const char *zStr; /* Input string */ | ||
46561 | SyString *pComp; /* Pointer to the URI component */ | ||
46562 | SyhttpUri sURI; /* Parse of the given URI */ | ||
46563 | int nLen; | ||
46564 | sxi32 rc; | ||
46565 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
46566 | /* Missing/Invalid arguments, return FALSE */ | ||
46567 | jx9_result_bool(pCtx, 0); | ||
46568 | return JX9_OK; | ||
46569 | } | ||
46570 | /* Extract the given URI */ | ||
46571 | zStr = jx9_value_to_string(apArg[0], &nLen); | ||
46572 | if( nLen < 1 ){ | ||
46573 | /* Nothing to process, return FALSE */ | ||
46574 | jx9_result_bool(pCtx, 0); | ||
46575 | return JX9_OK; | ||
46576 | } | ||
46577 | /* Get a parse */ | ||
46578 | rc = VmHttpSplitURI(&sURI, zStr, (sxu32)nLen); | ||
46579 | if( rc != SXRET_OK ){ | ||
46580 | /* Malformed input, return FALSE */ | ||
46581 | jx9_result_bool(pCtx, 0); | ||
46582 | return JX9_OK; | ||
46583 | } | ||
46584 | if( nArg > 1 ){ | ||
46585 | int nComponent = jx9_value_to_int(apArg[1]); | ||
46586 | /* Refer to constant.c for constants values */ | ||
46587 | switch(nComponent){ | ||
46588 | case 1: /* JX9_URL_SCHEME */ | ||
46589 | pComp = &sURI.sScheme; | ||
46590 | if( pComp->nByte < 1 ){ | ||
46591 | /* No available value, return NULL */ | ||
46592 | jx9_result_null(pCtx); | ||
46593 | }else{ | ||
46594 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
46595 | } | ||
46596 | break; | ||
46597 | case 2: /* JX9_URL_HOST */ | ||
46598 | pComp = &sURI.sHost; | ||
46599 | if( pComp->nByte < 1 ){ | ||
46600 | /* No available value, return NULL */ | ||
46601 | jx9_result_null(pCtx); | ||
46602 | }else{ | ||
46603 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
46604 | } | ||
46605 | break; | ||
46606 | case 3: /* JX9_URL_PORT */ | ||
46607 | pComp = &sURI.sPort; | ||
46608 | if( pComp->nByte < 1 ){ | ||
46609 | /* No available value, return NULL */ | ||
46610 | jx9_result_null(pCtx); | ||
46611 | }else{ | ||
46612 | int iPort = 0; | ||
46613 | /* Cast the value to integer */ | ||
46614 | SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0); | ||
46615 | jx9_result_int(pCtx, iPort); | ||
46616 | } | ||
46617 | break; | ||
46618 | case 4: /* JX9_URL_USER */ | ||
46619 | pComp = &sURI.sUser; | ||
46620 | if( pComp->nByte < 1 ){ | ||
46621 | /* No available value, return NULL */ | ||
46622 | jx9_result_null(pCtx); | ||
46623 | }else{ | ||
46624 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
46625 | } | ||
46626 | break; | ||
46627 | case 5: /* JX9_URL_PASS */ | ||
46628 | pComp = &sURI.sPass; | ||
46629 | if( pComp->nByte < 1 ){ | ||
46630 | /* No available value, return NULL */ | ||
46631 | jx9_result_null(pCtx); | ||
46632 | }else{ | ||
46633 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
46634 | } | ||
46635 | break; | ||
46636 | case 7: /* JX9_URL_QUERY */ | ||
46637 | pComp = &sURI.sQuery; | ||
46638 | if( pComp->nByte < 1 ){ | ||
46639 | /* No available value, return NULL */ | ||
46640 | jx9_result_null(pCtx); | ||
46641 | }else{ | ||
46642 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
46643 | } | ||
46644 | break; | ||
46645 | case 8: /* JX9_URL_FRAGMENT */ | ||
46646 | pComp = &sURI.sFragment; | ||
46647 | if( pComp->nByte < 1 ){ | ||
46648 | /* No available value, return NULL */ | ||
46649 | jx9_result_null(pCtx); | ||
46650 | }else{ | ||
46651 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
46652 | } | ||
46653 | break; | ||
46654 | case 6: /* JX9_URL_PATH */ | ||
46655 | pComp = &sURI.sPath; | ||
46656 | if( pComp->nByte < 1 ){ | ||
46657 | /* No available value, return NULL */ | ||
46658 | jx9_result_null(pCtx); | ||
46659 | }else{ | ||
46660 | jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); | ||
46661 | } | ||
46662 | break; | ||
46663 | default: | ||
46664 | /* No such entry, return NULL */ | ||
46665 | jx9_result_null(pCtx); | ||
46666 | break; | ||
46667 | } | ||
46668 | }else{ | ||
46669 | jx9_value *pArray, *pValue; | ||
46670 | /* Return an associative array */ | ||
46671 | pArray = jx9_context_new_array(pCtx); /* Empty array */ | ||
46672 | pValue = jx9_context_new_scalar(pCtx); /* Array value */ | ||
46673 | if( pArray == 0 || pValue == 0 ){ | ||
46674 | /* Out of memory */ | ||
46675 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "jx9 engine is running out of memory"); | ||
46676 | /* Return false */ | ||
46677 | jx9_result_bool(pCtx, 0); | ||
46678 | return JX9_OK; | ||
46679 | } | ||
46680 | /* Fill the array */ | ||
46681 | pComp = &sURI.sScheme; | ||
46682 | if( pComp->nByte > 0 ){ | ||
46683 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
46684 | jx9_array_add_strkey_elem(pArray, "scheme", pValue); /* Will make it's own copy */ | ||
46685 | } | ||
46686 | /* Reset the string cursor */ | ||
46687 | jx9_value_reset_string_cursor(pValue); | ||
46688 | pComp = &sURI.sHost; | ||
46689 | if( pComp->nByte > 0 ){ | ||
46690 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
46691 | jx9_array_add_strkey_elem(pArray, "host", pValue); /* Will make it's own copy */ | ||
46692 | } | ||
46693 | /* Reset the string cursor */ | ||
46694 | jx9_value_reset_string_cursor(pValue); | ||
46695 | pComp = &sURI.sPort; | ||
46696 | if( pComp->nByte > 0 ){ | ||
46697 | int iPort = 0;/* cc warning */ | ||
46698 | /* Convert to integer */ | ||
46699 | SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0); | ||
46700 | jx9_value_int(pValue, iPort); | ||
46701 | jx9_array_add_strkey_elem(pArray, "port", pValue); /* Will make it's own copy */ | ||
46702 | } | ||
46703 | /* Reset the string cursor */ | ||
46704 | jx9_value_reset_string_cursor(pValue); | ||
46705 | pComp = &sURI.sUser; | ||
46706 | if( pComp->nByte > 0 ){ | ||
46707 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
46708 | jx9_array_add_strkey_elem(pArray, "user", pValue); /* Will make it's own copy */ | ||
46709 | } | ||
46710 | /* Reset the string cursor */ | ||
46711 | jx9_value_reset_string_cursor(pValue); | ||
46712 | pComp = &sURI.sPass; | ||
46713 | if( pComp->nByte > 0 ){ | ||
46714 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
46715 | jx9_array_add_strkey_elem(pArray, "pass", pValue); /* Will make it's own copy */ | ||
46716 | } | ||
46717 | /* Reset the string cursor */ | ||
46718 | jx9_value_reset_string_cursor(pValue); | ||
46719 | pComp = &sURI.sPath; | ||
46720 | if( pComp->nByte > 0 ){ | ||
46721 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
46722 | jx9_array_add_strkey_elem(pArray, "path", pValue); /* Will make it's own copy */ | ||
46723 | } | ||
46724 | /* Reset the string cursor */ | ||
46725 | jx9_value_reset_string_cursor(pValue); | ||
46726 | pComp = &sURI.sQuery; | ||
46727 | if( pComp->nByte > 0 ){ | ||
46728 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
46729 | jx9_array_add_strkey_elem(pArray, "query", pValue); /* Will make it's own copy */ | ||
46730 | } | ||
46731 | /* Reset the string cursor */ | ||
46732 | jx9_value_reset_string_cursor(pValue); | ||
46733 | pComp = &sURI.sFragment; | ||
46734 | if( pComp->nByte > 0 ){ | ||
46735 | jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); | ||
46736 | jx9_array_add_strkey_elem(pArray, "fragment", pValue); /* Will make it's own copy */ | ||
46737 | } | ||
46738 | /* Return the created array */ | ||
46739 | jx9_result_value(pCtx, pArray); | ||
46740 | /* NOTE: | ||
46741 | * Don't worry about freeing 'pValue', everything will be released | ||
46742 | * automatically as soon we return from this function. | ||
46743 | */ | ||
46744 | } | ||
46745 | /* All done */ | ||
46746 | return JX9_OK; | ||
46747 | } | ||
46748 | /* | ||
46749 | * Section: | ||
46750 | * Array related routines. | ||
46751 | * Authors: | ||
46752 | * Symisc Systems, devel@symisc.net. | ||
46753 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
46754 | * Status: | ||
46755 | * Stable. | ||
46756 | * Note 2012-5-21 01:04:15: | ||
46757 | * Array related functions that need access to the underlying | ||
46758 | * virtual machine are implemented here rather than 'hashmap.c' | ||
46759 | */ | ||
46760 | /* | ||
46761 | * The [extract()] function store it's state information in an instance | ||
46762 | * of the following structure. | ||
46763 | */ | ||
46764 | typedef struct extract_aux_data extract_aux_data; | ||
46765 | struct extract_aux_data | ||
46766 | { | ||
46767 | jx9_vm *pVm; /* VM that own this instance */ | ||
46768 | int iCount; /* Number of variables successfully imported */ | ||
46769 | const char *zPrefix; /* Prefix name */ | ||
46770 | int Prefixlen; /* Prefix length */ | ||
46771 | int iFlags; /* Control flags */ | ||
46772 | char zWorker[1024]; /* Working buffer */ | ||
46773 | }; | ||
46774 | /* Forward declaration */ | ||
46775 | static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData); | ||
46776 | /* | ||
46777 | * int extract(array $var_array[, int $extract_type = EXTR_OVERWRITE[, string $prefix = NULL ]]) | ||
46778 | * Import variables into the current symbol table from an array. | ||
46779 | * Parameters | ||
46780 | * $var_array | ||
46781 | * An associative array. This function treats keys as variable names and values | ||
46782 | * as variable values. For each key/value pair it will create a variable in the current symbol | ||
46783 | * table, subject to extract_type and prefix parameters. | ||
46784 | * You must use an associative array; a numerically indexed array will not produce results | ||
46785 | * unless you use EXTR_PREFIX_ALL or EXTR_PREFIX_INVALID. | ||
46786 | * $extract_type | ||
46787 | * The way invalid/numeric keys and collisions are treated is determined by the extract_type. | ||
46788 | * It can be one of the following values: | ||
46789 | * EXTR_OVERWRITE | ||
46790 | * If there is a collision, overwrite the existing variable. | ||
46791 | * EXTR_SKIP | ||
46792 | * If there is a collision, don't overwrite the existing variable. | ||
46793 | * EXTR_PREFIX_SAME | ||
46794 | * If there is a collision, prefix the variable name with prefix. | ||
46795 | * EXTR_PREFIX_ALL | ||
46796 | * Prefix all variable names with prefix. | ||
46797 | * EXTR_PREFIX_INVALID | ||
46798 | * Only prefix invalid/numeric variable names with prefix. | ||
46799 | * EXTR_IF_EXISTS | ||
46800 | * Only overwrite the variable if it already exists in the current symbol table | ||
46801 | * otherwise do nothing. | ||
46802 | * This is useful for defining a list of valid variables and then extracting only those | ||
46803 | * variables you have defined out of $_REQUEST, for example. | ||
46804 | * EXTR_PREFIX_IF_EXISTS | ||
46805 | * Only create prefixed variable names if the non-prefixed version of the same variable exists in | ||
46806 | * the current symbol table. | ||
46807 | * $prefix | ||
46808 | * Note that prefix is only required if extract_type is EXTR_PREFIX_SAME, EXTR_PREFIX_ALL | ||
46809 | * EXTR_PREFIX_INVALID or EXTR_PREFIX_IF_EXISTS. If the prefixed result is not a valid variable name | ||
46810 | * it is not imported into the symbol table. Prefixes are automatically separated from the array key by an | ||
46811 | * underscore character. | ||
46812 | * Return | ||
46813 | * Returns the number of variables successfully imported into the symbol table. | ||
46814 | */ | ||
46815 | static int vm_builtin_extract(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
46816 | { | ||
46817 | extract_aux_data sAux; | ||
46818 | jx9_hashmap *pMap; | ||
46819 | if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ | ||
46820 | /* Missing/Invalid arguments, return 0 */ | ||
46821 | jx9_result_int(pCtx, 0); | ||
46822 | return JX9_OK; | ||
46823 | } | ||
46824 | /* Point to the target hashmap */ | ||
46825 | pMap = (jx9_hashmap *)apArg[0]->x.pOther; | ||
46826 | if( pMap->nEntry < 1 ){ | ||
46827 | /* Empty map, return 0 */ | ||
46828 | jx9_result_int(pCtx, 0); | ||
46829 | return JX9_OK; | ||
46830 | } | ||
46831 | /* Prepare the aux data */ | ||
46832 | SyZero(&sAux, sizeof(extract_aux_data)-sizeof(sAux.zWorker)); | ||
46833 | if( nArg > 1 ){ | ||
46834 | sAux.iFlags = jx9_value_to_int(apArg[1]); | ||
46835 | if( nArg > 2 ){ | ||
46836 | sAux.zPrefix = jx9_value_to_string(apArg[2], &sAux.Prefixlen); | ||
46837 | } | ||
46838 | } | ||
46839 | sAux.pVm = pCtx->pVm; | ||
46840 | /* Invoke the worker callback */ | ||
46841 | jx9HashmapWalk(pMap, VmExtractCallback, &sAux); | ||
46842 | /* Number of variables successfully imported */ | ||
46843 | jx9_result_int(pCtx, sAux.iCount); | ||
46844 | return JX9_OK; | ||
46845 | } | ||
46846 | /* | ||
46847 | * Worker callback for the [extract()] function defined | ||
46848 | * below. | ||
46849 | */ | ||
46850 | static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData) | ||
46851 | { | ||
46852 | extract_aux_data *pAux = (extract_aux_data *)pUserData; | ||
46853 | int iFlags = pAux->iFlags; | ||
46854 | jx9_vm *pVm = pAux->pVm; | ||
46855 | jx9_value *pObj; | ||
46856 | SyString sVar; | ||
46857 | if( (iFlags & 0x10/* EXTR_PREFIX_INVALID */) && (pKey->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL|MEMOBJ_REAL))){ | ||
46858 | iFlags |= 0x08; /*EXTR_PREFIX_ALL*/ | ||
46859 | } | ||
46860 | /* Perform a string cast */ | ||
46861 | jx9MemObjToString(pKey); | ||
46862 | if( SyBlobLength(&pKey->sBlob) < 1 ){ | ||
46863 | /* Unavailable variable name */ | ||
46864 | return SXRET_OK; | ||
46865 | } | ||
46866 | sVar.nByte = 0; /* cc warning */ | ||
46867 | if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/ ) && pAux->Prefixlen > 0 ){ | ||
46868 | sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker, sizeof(pAux->zWorker), "%.*s_%.*s", | ||
46869 | pAux->Prefixlen, pAux->zPrefix, | ||
46870 | SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob) | ||
46871 | ); | ||
46872 | }else{ | ||
46873 | sVar.nByte = (sxu32) SyMemcpy(SyBlobData(&pKey->sBlob), pAux->zWorker, | ||
46874 | SXMIN(SyBlobLength(&pKey->sBlob), sizeof(pAux->zWorker))); | ||
46875 | } | ||
46876 | sVar.zString = pAux->zWorker; | ||
46877 | /* Try to extract the variable */ | ||
46878 | pObj = VmExtractMemObj(pVm, &sVar, TRUE, FALSE); | ||
46879 | if( pObj ){ | ||
46880 | /* Collision */ | ||
46881 | if( iFlags & 0x02 /* EXTR_SKIP */ ){ | ||
46882 | return SXRET_OK; | ||
46883 | } | ||
46884 | if( iFlags & 0x04 /* EXTR_PREFIX_SAME */ ){ | ||
46885 | if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/) || pAux->Prefixlen < 1){ | ||
46886 | /* Already prefixed */ | ||
46887 | return SXRET_OK; | ||
46888 | } | ||
46889 | sVar.nByte = SyBufferFormat( | ||
46890 | pAux->zWorker, sizeof(pAux->zWorker), | ||
46891 | "%.*s_%.*s", | ||
46892 | pAux->Prefixlen, pAux->zPrefix, | ||
46893 | SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob) | ||
46894 | ); | ||
46895 | pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE); | ||
46896 | } | ||
46897 | }else{ | ||
46898 | /* Create the variable */ | ||
46899 | pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE); | ||
46900 | } | ||
46901 | if( pObj ){ | ||
46902 | /* Overwrite the old value */ | ||
46903 | jx9MemObjStore(pValue, pObj); | ||
46904 | /* Increment counter */ | ||
46905 | pAux->iCount++; | ||
46906 | } | ||
46907 | return SXRET_OK; | ||
46908 | } | ||
46909 | /* | ||
46910 | * Compile and evaluate a JX9 chunk at run-time. | ||
46911 | * Refer to the include language construct implementation for more | ||
46912 | * information. | ||
46913 | */ | ||
46914 | static sxi32 VmEvalChunk( | ||
46915 | jx9_vm *pVm, /* Underlying Virtual Machine */ | ||
46916 | jx9_context *pCtx, /* Call Context */ | ||
46917 | SyString *pChunk, /* JX9 chunk to evaluate */ | ||
46918 | int iFlags, /* Compile flag */ | ||
46919 | int bTrueReturn /* TRUE to return execution result */ | ||
46920 | ) | ||
46921 | { | ||
46922 | SySet *pByteCode, aByteCode; | ||
46923 | ProcConsumer xErr = 0; | ||
46924 | void *pErrData = 0; | ||
46925 | /* Initialize bytecode container */ | ||
46926 | SySetInit(&aByteCode, &pVm->sAllocator, sizeof(VmInstr)); | ||
46927 | SySetAlloc(&aByteCode, 0x20); | ||
46928 | /* Reset the code generator */ | ||
46929 | if( bTrueReturn ){ | ||
46930 | /* Included file, log compile-time errors */ | ||
46931 | xErr = pVm->pEngine->xConf.xErr; | ||
46932 | pErrData = pVm->pEngine->xConf.pErrData; | ||
46933 | } | ||
46934 | jx9ResetCodeGenerator(pVm, xErr, pErrData); | ||
46935 | /* Swap bytecode container */ | ||
46936 | pByteCode = pVm->pByteContainer; | ||
46937 | pVm->pByteContainer = &aByteCode; | ||
46938 | /* Compile the chunk */ | ||
46939 | jx9CompileScript(pVm, pChunk, iFlags); | ||
46940 | if( pVm->sCodeGen.nErr > 0 ){ | ||
46941 | /* Compilation error, return false */ | ||
46942 | if( pCtx ){ | ||
46943 | jx9_result_bool(pCtx, 0); | ||
46944 | } | ||
46945 | }else{ | ||
46946 | jx9_value sResult; /* Return value */ | ||
46947 | if( SXRET_OK != jx9VmEmitInstr(pVm, JX9_OP_DONE, 0, 0, 0, 0) ){ | ||
46948 | /* Out of memory */ | ||
46949 | if( pCtx ){ | ||
46950 | jx9_result_bool(pCtx, 0); | ||
46951 | } | ||
46952 | goto Cleanup; | ||
46953 | } | ||
46954 | if( bTrueReturn ){ | ||
46955 | /* Assume a boolean true return value */ | ||
46956 | jx9MemObjInitFromBool(pVm, &sResult, 1); | ||
46957 | }else{ | ||
46958 | /* Assume a null return value */ | ||
46959 | jx9MemObjInit(pVm, &sResult); | ||
46960 | } | ||
46961 | /* Execute the compiled chunk */ | ||
46962 | VmLocalExec(pVm, &aByteCode, &sResult); | ||
46963 | if( pCtx ){ | ||
46964 | /* Set the execution result */ | ||
46965 | jx9_result_value(pCtx, &sResult); | ||
46966 | } | ||
46967 | jx9MemObjRelease(&sResult); | ||
46968 | } | ||
46969 | Cleanup: | ||
46970 | /* Cleanup the mess left behind */ | ||
46971 | pVm->pByteContainer = pByteCode; | ||
46972 | SySetRelease(&aByteCode); | ||
46973 | return SXRET_OK; | ||
46974 | } | ||
46975 | /* | ||
46976 | * Check if a file path is already included. | ||
46977 | */ | ||
46978 | static int VmIsIncludedFile(jx9_vm *pVm, SyString *pFile) | ||
46979 | { | ||
46980 | SyString *aEntries; | ||
46981 | sxu32 n; | ||
46982 | aEntries = (SyString *)SySetBasePtr(&pVm->aIncluded); | ||
46983 | /* Perform a linear search */ | ||
46984 | for( n = 0 ; n < SySetUsed(&pVm->aIncluded) ; ++n ){ | ||
46985 | if( SyStringCmp(pFile, &aEntries[n], SyMemcmp) == 0 ){ | ||
46986 | /* Already included */ | ||
46987 | return TRUE; | ||
46988 | } | ||
46989 | } | ||
46990 | return FALSE; | ||
46991 | } | ||
46992 | /* | ||
46993 | * Push a file path in the appropriate VM container. | ||
46994 | */ | ||
46995 | JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew) | ||
46996 | { | ||
46997 | SyString sPath; | ||
46998 | char *zDup; | ||
46999 | #ifdef __WINNT__ | ||
47000 | char *zCur; | ||
47001 | #endif | ||
47002 | sxi32 rc; | ||
47003 | if( nLen < 0 ){ | ||
47004 | nLen = SyStrlen(zPath); | ||
47005 | } | ||
47006 | /* Duplicate the file path first */ | ||
47007 | zDup = SyMemBackendStrDup(&pVm->sAllocator, zPath, nLen); | ||
47008 | if( zDup == 0 ){ | ||
47009 | return SXERR_MEM; | ||
47010 | } | ||
47011 | #ifdef __WINNT__ | ||
47012 | /* Normalize path on windows | ||
47013 | * Example: | ||
47014 | * Path/To/File.jx9 | ||
47015 | * becomes | ||
47016 | * path\to\file.jx9 | ||
47017 | */ | ||
47018 | zCur = zDup; | ||
47019 | while( zCur[0] != 0 ){ | ||
47020 | if( zCur[0] == '/' ){ | ||
47021 | zCur[0] = '\\'; | ||
47022 | }else if( (unsigned char)zCur[0] < 0xc0 && SyisUpper(zCur[0]) ){ | ||
47023 | int c = SyToLower(zCur[0]); | ||
47024 | zCur[0] = (char)c; /* MSVC stupidity */ | ||
47025 | } | ||
47026 | zCur++; | ||
47027 | } | ||
47028 | #endif | ||
47029 | /* Install the file path */ | ||
47030 | SyStringInitFromBuf(&sPath, zDup, nLen); | ||
47031 | if( !bMain ){ | ||
47032 | if( VmIsIncludedFile(&(*pVm), &sPath) ){ | ||
47033 | /* Already included */ | ||
47034 | *pNew = 0; | ||
47035 | }else{ | ||
47036 | /* Insert in the corresponding container */ | ||
47037 | rc = SySetPut(&pVm->aIncluded, (const void *)&sPath); | ||
47038 | if( rc != SXRET_OK ){ | ||
47039 | SyMemBackendFree(&pVm->sAllocator, zDup); | ||
47040 | return rc; | ||
47041 | } | ||
47042 | *pNew = 1; | ||
47043 | } | ||
47044 | } | ||
47045 | SySetPut(&pVm->aFiles, (const void *)&sPath); | ||
47046 | return SXRET_OK; | ||
47047 | } | ||
47048 | /* | ||
47049 | * Compile and Execute a JX9 script at run-time. | ||
47050 | * SXRET_OK is returned on sucessful evaluation.Any other return values | ||
47051 | * indicates failure. | ||
47052 | * Note that the JX9 script to evaluate can be a local or remote file.In | ||
47053 | * either cases the [jx9StreamReadWholeFile()] function handle all the underlying | ||
47054 | * operations. | ||
47055 | * If the [jJX9_DISABLE_BUILTIN_FUNC] compile-time directive is defined, then | ||
47056 | * this function is a no-op. | ||
47057 | * Refer to the implementation of the include(), import() language | ||
47058 | * constructs for more information. | ||
47059 | */ | ||
47060 | static sxi32 VmExecIncludedFile( | ||
47061 | jx9_context *pCtx, /* Call Context */ | ||
47062 | SyString *pPath, /* Script path or URL*/ | ||
47063 | int IncludeOnce /* TRUE if called from import() or require_once() */ | ||
47064 | ) | ||
47065 | { | ||
47066 | sxi32 rc; | ||
47067 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
47068 | const jx9_io_stream *pStream; | ||
47069 | SyBlob sContents; | ||
47070 | void *pHandle; | ||
47071 | jx9_vm *pVm; | ||
47072 | int isNew; | ||
47073 | /* Initialize fields */ | ||
47074 | pVm = pCtx->pVm; | ||
47075 | SyBlobInit(&sContents, &pVm->sAllocator); | ||
47076 | isNew = 0; | ||
47077 | /* Extract the associated stream */ | ||
47078 | pStream = jx9VmGetStreamDevice(pVm, &pPath->zString, pPath->nByte); | ||
47079 | /* | ||
47080 | * Open the file or the URL [i.e: http://jx9.symisc.net/example/hello.jx9.txt"] | ||
47081 | * in a read-only mode. | ||
47082 | */ | ||
47083 | pHandle = jx9StreamOpenHandle(pVm, pStream,pPath->zString, JX9_IO_OPEN_RDONLY, TRUE, 0, TRUE, &isNew); | ||
47084 | if( pHandle == 0 ){ | ||
47085 | return SXERR_IO; | ||
47086 | } | ||
47087 | rc = SXRET_OK; /* Stupid cc warning */ | ||
47088 | if( IncludeOnce && !isNew ){ | ||
47089 | /* Already included */ | ||
47090 | rc = SXERR_EXISTS; | ||
47091 | }else{ | ||
47092 | /* Read the whole file contents */ | ||
47093 | rc = jx9StreamReadWholeFile(pHandle, pStream, &sContents); | ||
47094 | if( rc == SXRET_OK ){ | ||
47095 | SyString sScript; | ||
47096 | /* Compile and execute the script */ | ||
47097 | SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents)); | ||
47098 | VmEvalChunk(pCtx->pVm, &(*pCtx), &sScript, 0, TRUE); | ||
47099 | } | ||
47100 | } | ||
47101 | /* Pop from the set of included file */ | ||
47102 | (void)SySetPop(&pVm->aFiles); | ||
47103 | /* Close the handle */ | ||
47104 | jx9StreamCloseHandle(pStream, pHandle); | ||
47105 | /* Release the working buffer */ | ||
47106 | SyBlobRelease(&sContents); | ||
47107 | #else | ||
47108 | pCtx = 0; /* cc warning */ | ||
47109 | pPath = 0; | ||
47110 | IncludeOnce = 0; | ||
47111 | rc = SXERR_IO; | ||
47112 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
47113 | return rc; | ||
47114 | } | ||
47115 | /* * include: | ||
47116 | * According to the JX9 reference manual. | ||
47117 | * The include() function includes and evaluates the specified file. | ||
47118 | * Files are included based on the file path given or, if none is given | ||
47119 | * the include_path specified.If the file isn't found in the include_path | ||
47120 | * include() will finally check in the calling script's own directory | ||
47121 | * and the current working directory before failing. The include() | ||
47122 | * construct will emit a warning if it cannot find a file; this is different | ||
47123 | * behavior from require(), which will emit a fatal error. | ||
47124 | * If a path is defined — whether absolute (starting with a drive letter | ||
47125 | * or \ on Windows, or / on Unix/Linux systems) or relative to the current | ||
47126 | * directory (starting with . or ..) — the include_path will be ignored altogether. | ||
47127 | * For example, if a filename begins with ../, the parser will look in the parent | ||
47128 | * directory to find the requested file. | ||
47129 | * When a file is included, the code it contains inherits the variable scope | ||
47130 | * of the line on which the include occurs. Any variables available at that line | ||
47131 | * in the calling file will be available within the called file, from that point forward. | ||
47132 | * However, all functions and objectes defined in the included file have the global scope. | ||
47133 | */ | ||
47134 | static int vm_builtin_include(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
47135 | { | ||
47136 | SyString sFile; | ||
47137 | sxi32 rc; | ||
47138 | if( nArg < 1 ){ | ||
47139 | /* Nothing to evaluate, return NULL */ | ||
47140 | jx9_result_null(pCtx); | ||
47141 | return SXRET_OK; | ||
47142 | } | ||
47143 | /* File to include */ | ||
47144 | sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte); | ||
47145 | if( sFile.nByte < 1 ){ | ||
47146 | /* Empty string, return NULL */ | ||
47147 | jx9_result_null(pCtx); | ||
47148 | return SXRET_OK; | ||
47149 | } | ||
47150 | /* Open, compile and execute the desired script */ | ||
47151 | rc = VmExecIncludedFile(&(*pCtx), &sFile, FALSE); | ||
47152 | if( rc != SXRET_OK ){ | ||
47153 | /* Emit a warning and return false */ | ||
47154 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile); | ||
47155 | jx9_result_bool(pCtx, 0); | ||
47156 | } | ||
47157 | return SXRET_OK; | ||
47158 | } | ||
47159 | /* | ||
47160 | * import: | ||
47161 | * According to the JX9 reference manual. | ||
47162 | * The import() statement includes and evaluates the specified file during | ||
47163 | * the execution of the script. This is a behavior similar to the include() | ||
47164 | * statement, with the only difference being that if the code from a file has already | ||
47165 | * been included, it will not be included again. As the name suggests, it will be included | ||
47166 | * just once. | ||
47167 | */ | ||
47168 | static int vm_builtin_import(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
47169 | { | ||
47170 | SyString sFile; | ||
47171 | sxi32 rc; | ||
47172 | if( nArg < 1 ){ | ||
47173 | /* Nothing to evaluate, return NULL */ | ||
47174 | jx9_result_null(pCtx); | ||
47175 | return SXRET_OK; | ||
47176 | } | ||
47177 | /* File to include */ | ||
47178 | sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte); | ||
47179 | if( sFile.nByte < 1 ){ | ||
47180 | /* Empty string, return NULL */ | ||
47181 | jx9_result_null(pCtx); | ||
47182 | return SXRET_OK; | ||
47183 | } | ||
47184 | /* Open, compile and execute the desired script */ | ||
47185 | rc = VmExecIncludedFile(&(*pCtx), &sFile, TRUE); | ||
47186 | if( rc == SXERR_EXISTS ){ | ||
47187 | /* File already included, return TRUE */ | ||
47188 | jx9_result_bool(pCtx, 1); | ||
47189 | return SXRET_OK; | ||
47190 | } | ||
47191 | if( rc != SXRET_OK ){ | ||
47192 | /* Emit a warning and return false */ | ||
47193 | jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile); | ||
47194 | jx9_result_bool(pCtx, 0); | ||
47195 | } | ||
47196 | return SXRET_OK; | ||
47197 | } | ||
47198 | /* | ||
47199 | * Section: | ||
47200 | * Command line arguments processing. | ||
47201 | * Authors: | ||
47202 | * Symisc Systems, devel@symisc.net. | ||
47203 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
47204 | * Status: | ||
47205 | * Stable. | ||
47206 | */ | ||
47207 | /* | ||
47208 | * Check if a short option argument [i.e: -c] is available in the command | ||
47209 | * line string. Return a pointer to the start of the stream on success. | ||
47210 | * NULL otherwise. | ||
47211 | */ | ||
47212 | static const char * VmFindShortOpt(int c, const char *zIn, const char *zEnd) | ||
47213 | { | ||
47214 | while( zIn < zEnd ){ | ||
47215 | if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == c ){ | ||
47216 | /* Got one */ | ||
47217 | return &zIn[1]; | ||
47218 | } | ||
47219 | /* Advance the cursor */ | ||
47220 | zIn++; | ||
47221 | } | ||
47222 | /* No such option */ | ||
47223 | return 0; | ||
47224 | } | ||
47225 | /* | ||
47226 | * Check if a long option argument [i.e: --opt] is available in the command | ||
47227 | * line string. Return a pointer to the start of the stream on success. | ||
47228 | * NULL otherwise. | ||
47229 | */ | ||
47230 | static const char * VmFindLongOpt(const char *zLong, int nByte, const char *zIn, const char *zEnd) | ||
47231 | { | ||
47232 | const char *zOpt; | ||
47233 | while( zIn < zEnd ){ | ||
47234 | if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == '-' ){ | ||
47235 | zIn += 2; | ||
47236 | zOpt = zIn; | ||
47237 | while( zIn < zEnd && !SyisSpace(zIn[0]) ){ | ||
47238 | if( zIn[0] == '=' /* --opt=val */){ | ||
47239 | break; | ||
47240 | } | ||
47241 | zIn++; | ||
47242 | } | ||
47243 | /* Test */ | ||
47244 | if( (int)(zIn-zOpt) == nByte && SyMemcmp(zOpt, zLong, nByte) == 0 ){ | ||
47245 | /* Got one, return it's value */ | ||
47246 | return zIn; | ||
47247 | } | ||
47248 | |||
47249 | }else{ | ||
47250 | zIn++; | ||
47251 | } | ||
47252 | } | ||
47253 | /* No such option */ | ||
47254 | return 0; | ||
47255 | } | ||
47256 | /* | ||
47257 | * Long option [i.e: --opt] arguments private data structure. | ||
47258 | */ | ||
47259 | struct getopt_long_opt | ||
47260 | { | ||
47261 | const char *zArgIn, *zArgEnd; /* Command line arguments */ | ||
47262 | jx9_value *pWorker; /* Worker variable*/ | ||
47263 | jx9_value *pArray; /* getopt() return value */ | ||
47264 | jx9_context *pCtx; /* Call Context */ | ||
47265 | }; | ||
47266 | /* Forward declaration */ | ||
47267 | static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData); | ||
47268 | /* | ||
47269 | * Extract short or long argument option values. | ||
47270 | */ | ||
47271 | static void VmExtractOptArgValue( | ||
47272 | jx9_value *pArray, /* getopt() return value */ | ||
47273 | jx9_value *pWorker, /* Worker variable */ | ||
47274 | const char *zArg, /* Argument stream */ | ||
47275 | const char *zArgEnd, /* End of the argument stream */ | ||
47276 | int need_val, /* TRUE to fetch option argument */ | ||
47277 | jx9_context *pCtx, /* Call Context */ | ||
47278 | const char *zName /* Option name */) | ||
47279 | { | ||
47280 | jx9_value_bool(pWorker, 0); | ||
47281 | if( !need_val ){ | ||
47282 | /* | ||
47283 | * Option does not need arguments. | ||
47284 | * Insert the option name and a boolean FALSE. | ||
47285 | */ | ||
47286 | jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ | ||
47287 | }else{ | ||
47288 | const char *zCur; | ||
47289 | /* Extract option argument */ | ||
47290 | zArg++; | ||
47291 | if( zArg < zArgEnd && zArg[0] == '=' ){ | ||
47292 | zArg++; | ||
47293 | } | ||
47294 | while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ | ||
47295 | zArg++; | ||
47296 | } | ||
47297 | if( zArg >= zArgEnd || zArg[0] == '-' ){ | ||
47298 | /* | ||
47299 | * Argument not found. | ||
47300 | * Insert the option name and a boolean FALSE. | ||
47301 | */ | ||
47302 | jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ | ||
47303 | return; | ||
47304 | } | ||
47305 | /* Delimit the value */ | ||
47306 | zCur = zArg; | ||
47307 | if( zArg[0] == '\'' || zArg[0] == '"' ){ | ||
47308 | int d = zArg[0]; | ||
47309 | /* Delimt the argument */ | ||
47310 | zArg++; | ||
47311 | zCur = zArg; | ||
47312 | while( zArg < zArgEnd ){ | ||
47313 | if( zArg[0] == d && zArg[-1] != '\\' ){ | ||
47314 | /* Delimiter found, exit the loop */ | ||
47315 | break; | ||
47316 | } | ||
47317 | zArg++; | ||
47318 | } | ||
47319 | /* Save the value */ | ||
47320 | jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); | ||
47321 | if( zArg < zArgEnd ){ zArg++; } | ||
47322 | }else{ | ||
47323 | while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ | ||
47324 | zArg++; | ||
47325 | } | ||
47326 | /* Save the value */ | ||
47327 | jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); | ||
47328 | } | ||
47329 | /* | ||
47330 | * Check if we are dealing with multiple values. | ||
47331 | * If so, create an array to hold them, rather than a scalar variable. | ||
47332 | */ | ||
47333 | while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ | ||
47334 | zArg++; | ||
47335 | } | ||
47336 | if( zArg < zArgEnd && zArg[0] != '-' ){ | ||
47337 | jx9_value *pOptArg; /* Array of option arguments */ | ||
47338 | pOptArg = jx9_context_new_array(pCtx); | ||
47339 | if( pOptArg == 0 ){ | ||
47340 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); | ||
47341 | }else{ | ||
47342 | /* Insert the first value */ | ||
47343 | jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */ | ||
47344 | for(;;){ | ||
47345 | if( zArg >= zArgEnd || zArg[0] == '-' ){ | ||
47346 | /* No more value */ | ||
47347 | break; | ||
47348 | } | ||
47349 | /* Delimit the value */ | ||
47350 | zCur = zArg; | ||
47351 | if( zArg < zArgEnd && zArg[0] == '\\' ){ | ||
47352 | zArg++; | ||
47353 | zCur = zArg; | ||
47354 | } | ||
47355 | while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ | ||
47356 | zArg++; | ||
47357 | } | ||
47358 | /* Reset the string cursor */ | ||
47359 | jx9_value_reset_string_cursor(pWorker); | ||
47360 | /* Save the value */ | ||
47361 | jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); | ||
47362 | /* Insert */ | ||
47363 | jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */ | ||
47364 | /* Jump trailing white spaces */ | ||
47365 | while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ | ||
47366 | zArg++; | ||
47367 | } | ||
47368 | } | ||
47369 | /* Insert the option arg array */ | ||
47370 | jx9_array_add_strkey_elem(pArray, (const char *)zName, pOptArg); /* Will make it's own copy */ | ||
47371 | /* Safely release */ | ||
47372 | jx9_context_release_value(pCtx, pOptArg); | ||
47373 | } | ||
47374 | }else{ | ||
47375 | /* Single value */ | ||
47376 | jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ | ||
47377 | } | ||
47378 | } | ||
47379 | } | ||
47380 | /* | ||
47381 | * array getopt(string $options[, array $longopts ]) | ||
47382 | * Gets options from the command line argument list. | ||
47383 | * Parameters | ||
47384 | * $options | ||
47385 | * Each character in this string will be used as option characters | ||
47386 | * and matched against options passed to the script starting with | ||
47387 | * a single hyphen (-). For example, an option string "x" recognizes | ||
47388 | * an option -x. Only a-z, A-Z and 0-9 are allowed. | ||
47389 | * $longopts | ||
47390 | * An array of options. Each element in this array will be used as option | ||
47391 | * strings and matched against options passed to the script starting with | ||
47392 | * two hyphens (--). For example, an longopts element "opt" recognizes an | ||
47393 | * option --opt. | ||
47394 | * Return | ||
47395 | * This function will return an array of option / argument pairs or FALSE | ||
47396 | * on failure. | ||
47397 | */ | ||
47398 | static int vm_builtin_getopt(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
47399 | { | ||
47400 | const char *zIn, *zEnd, *zArg, *zArgIn, *zArgEnd; | ||
47401 | struct getopt_long_opt sLong; | ||
47402 | jx9_value *pArray, *pWorker; | ||
47403 | SyBlob *pArg; | ||
47404 | int nByte; | ||
47405 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
47406 | /* Missing/Invalid arguments, return FALSE */ | ||
47407 | jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Missing/Invalid option arguments"); | ||
47408 | jx9_result_bool(pCtx, 0); | ||
47409 | return JX9_OK; | ||
47410 | } | ||
47411 | /* Extract option arguments */ | ||
47412 | zIn = jx9_value_to_string(apArg[0], &nByte); | ||
47413 | zEnd = &zIn[nByte]; | ||
47414 | /* Point to the string representation of the $argv[] array */ | ||
47415 | pArg = &pCtx->pVm->sArgv; | ||
47416 | /* Create a new empty array and a worker variable */ | ||
47417 | pArray = jx9_context_new_array(pCtx); | ||
47418 | pWorker = jx9_context_new_scalar(pCtx); | ||
47419 | if( pArray == 0 || pWorker == 0 ){ | ||
47420 | jx9_context_throw_error(pCtx,JX9_CTX_ERR, "JX9 is running out of memory"); | ||
47421 | jx9_result_bool(pCtx, 0); | ||
47422 | return JX9_OK; | ||
47423 | } | ||
47424 | if( SyBlobLength(pArg) < 1 ){ | ||
47425 | /* Empty command line, return the empty array*/ | ||
47426 | jx9_result_value(pCtx, pArray); | ||
47427 | /* Everything will be released automatically when we return | ||
47428 | * from this function. | ||
47429 | */ | ||
47430 | return JX9_OK; | ||
47431 | } | ||
47432 | zArgIn = (const char *)SyBlobData(pArg); | ||
47433 | zArgEnd = &zArgIn[SyBlobLength(pArg)]; | ||
47434 | /* Fill the long option structure */ | ||
47435 | sLong.pArray = pArray; | ||
47436 | sLong.pWorker = pWorker; | ||
47437 | sLong.zArgIn = zArgIn; | ||
47438 | sLong.zArgEnd = zArgEnd; | ||
47439 | sLong.pCtx = pCtx; | ||
47440 | /* Start processing */ | ||
47441 | while( zIn < zEnd ){ | ||
47442 | int c = zIn[0]; | ||
47443 | int need_val = 0; | ||
47444 | /* Advance the stream cursor */ | ||
47445 | zIn++; | ||
47446 | /* Ignore non-alphanum characters */ | ||
47447 | if( !SyisAlphaNum(c) ){ | ||
47448 | continue; | ||
47449 | } | ||
47450 | if( zIn < zEnd && zIn[0] == ':' ){ | ||
47451 | zIn++; | ||
47452 | need_val = 1; | ||
47453 | if( zIn < zEnd && zIn[0] == ':' ){ | ||
47454 | zIn++; | ||
47455 | } | ||
47456 | } | ||
47457 | /* Find option */ | ||
47458 | zArg = VmFindShortOpt(c, zArgIn, zArgEnd); | ||
47459 | if( zArg == 0 ){ | ||
47460 | /* No such option */ | ||
47461 | continue; | ||
47462 | } | ||
47463 | /* Extract option argument value */ | ||
47464 | VmExtractOptArgValue(pArray, pWorker, zArg, zArgEnd, need_val, pCtx, (const char *)&c); | ||
47465 | } | ||
47466 | if( nArg > 1 && jx9_value_is_json_array(apArg[1]) && jx9_array_count(apArg[1]) > 0 ){ | ||
47467 | /* Process long options */ | ||
47468 | jx9_array_walk(apArg[1], VmProcessLongOpt, &sLong); | ||
47469 | } | ||
47470 | /* Return the option array */ | ||
47471 | jx9_result_value(pCtx, pArray); | ||
47472 | /* | ||
47473 | * Don't worry about freeing memory, everything will be released | ||
47474 | * automatically as soon we return from this foreign function. | ||
47475 | */ | ||
47476 | return JX9_OK; | ||
47477 | } | ||
47478 | /* | ||
47479 | * Array walker callback used for processing long options values. | ||
47480 | */ | ||
47481 | static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData) | ||
47482 | { | ||
47483 | struct getopt_long_opt *pOpt = (struct getopt_long_opt *)pUserData; | ||
47484 | const char *zArg, *zOpt, *zEnd; | ||
47485 | int need_value = 0; | ||
47486 | int nByte; | ||
47487 | /* Value must be of type string */ | ||
47488 | if( !jx9_value_is_string(pValue) ){ | ||
47489 | /* Simply ignore */ | ||
47490 | return JX9_OK; | ||
47491 | } | ||
47492 | zOpt = jx9_value_to_string(pValue, &nByte); | ||
47493 | if( nByte < 1 ){ | ||
47494 | /* Empty string, ignore */ | ||
47495 | return JX9_OK; | ||
47496 | } | ||
47497 | zEnd = &zOpt[nByte - 1]; | ||
47498 | if( zEnd[0] == ':' ){ | ||
47499 | char *zTerm; | ||
47500 | /* Try to extract a value */ | ||
47501 | need_value = 1; | ||
47502 | while( zEnd >= zOpt && zEnd[0] == ':' ){ | ||
47503 | zEnd--; | ||
47504 | } | ||
47505 | if( zOpt >= zEnd ){ | ||
47506 | /* Empty string, ignore */ | ||
47507 | SXUNUSED(pKey); | ||
47508 | return JX9_OK; | ||
47509 | } | ||
47510 | zEnd++; | ||
47511 | zTerm = (char *)zEnd; | ||
47512 | zTerm[0] = 0; | ||
47513 | }else{ | ||
47514 | zEnd = &zOpt[nByte]; | ||
47515 | } | ||
47516 | /* Find the option */ | ||
47517 | zArg = VmFindLongOpt(zOpt, (int)(zEnd-zOpt), pOpt->zArgIn, pOpt->zArgEnd); | ||
47518 | if( zArg == 0 ){ | ||
47519 | /* No such option, return immediately */ | ||
47520 | return JX9_OK; | ||
47521 | } | ||
47522 | /* Try to extract a value */ | ||
47523 | VmExtractOptArgValue(pOpt->pArray, pOpt->pWorker, zArg, pOpt->zArgEnd, need_value, pOpt->pCtx, zOpt); | ||
47524 | return JX9_OK; | ||
47525 | } | ||
47526 | /* | ||
47527 | * int utf8_encode(string $input) | ||
47528 | * UTF-8 encoding. | ||
47529 | * This function encodes the string data to UTF-8, and returns the encoded version. | ||
47530 | * UTF-8 is a standard mechanism used by Unicode for encoding wide character values | ||
47531 | * into a byte stream. UTF-8 is transparent to plain ASCII characters, is self-synchronized | ||
47532 | * (meaning it is possible for a program to figure out where in the bytestream characters start) | ||
47533 | * and can be used with normal string comparison functions for sorting and such. | ||
47534 | * Notes on UTF-8 (According to SQLite3 authors): | ||
47535 | * Byte-0 Byte-1 Byte-2 Byte-3 Value | ||
47536 | * 0xxxxxxx 00000000 00000000 0xxxxxxx | ||
47537 | * 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx | ||
47538 | * 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx | ||
47539 | * 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx | ||
47540 | * Parameters | ||
47541 | * $input | ||
47542 | * String to encode or NULL on failure. | ||
47543 | * Return | ||
47544 | * An UTF-8 encoded string. | ||
47545 | */ | ||
47546 | static int vm_builtin_utf8_encode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
47547 | { | ||
47548 | const unsigned char *zIn, *zEnd; | ||
47549 | int nByte, c, e; | ||
47550 | if( nArg < 1 ){ | ||
47551 | /* Missing arguments, return null */ | ||
47552 | jx9_result_null(pCtx); | ||
47553 | return JX9_OK; | ||
47554 | } | ||
47555 | /* Extract the target string */ | ||
47556 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte); | ||
47557 | if( nByte < 1 ){ | ||
47558 | /* Empty string, return null */ | ||
47559 | jx9_result_null(pCtx); | ||
47560 | return JX9_OK; | ||
47561 | } | ||
47562 | zEnd = &zIn[nByte]; | ||
47563 | /* Start the encoding process */ | ||
47564 | for(;;){ | ||
47565 | if( zIn >= zEnd ){ | ||
47566 | /* End of input */ | ||
47567 | break; | ||
47568 | } | ||
47569 | c = zIn[0]; | ||
47570 | /* Advance the stream cursor */ | ||
47571 | zIn++; | ||
47572 | /* Encode */ | ||
47573 | if( c<0x00080 ){ | ||
47574 | e = (c&0xFF); | ||
47575 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47576 | }else if( c<0x00800 ){ | ||
47577 | e = 0xC0 + ((c>>6)&0x1F); | ||
47578 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47579 | e = 0x80 + (c & 0x3F); | ||
47580 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47581 | }else if( c<0x10000 ){ | ||
47582 | e = 0xE0 + ((c>>12)&0x0F); | ||
47583 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47584 | e = 0x80 + ((c>>6) & 0x3F); | ||
47585 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47586 | e = 0x80 + (c & 0x3F); | ||
47587 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47588 | }else{ | ||
47589 | e = 0xF0 + ((c>>18) & 0x07); | ||
47590 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47591 | e = 0x80 + ((c>>12) & 0x3F); | ||
47592 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47593 | e = 0x80 + ((c>>6) & 0x3F); | ||
47594 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47595 | e = 0x80 + (c & 0x3F); | ||
47596 | jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); | ||
47597 | } | ||
47598 | } | ||
47599 | /* All done */ | ||
47600 | return JX9_OK; | ||
47601 | } | ||
47602 | /* | ||
47603 | * UTF-8 decoding routine extracted from the sqlite3 source tree. | ||
47604 | * Original author: D. Richard Hipp (http://www.sqlite.org) | ||
47605 | * Status: Public Domain | ||
47606 | */ | ||
47607 | /* | ||
47608 | ** This lookup table is used to help decode the first byte of | ||
47609 | ** a multi-byte UTF8 character. | ||
47610 | */ | ||
47611 | static const unsigned char UtfTrans1[] = { | ||
47612 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, | ||
47613 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, | ||
47614 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, | ||
47615 | 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, | ||
47616 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, | ||
47617 | 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, | ||
47618 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, | ||
47619 | 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, | ||
47620 | }; | ||
47621 | /* | ||
47622 | ** Translate a single UTF-8 character. Return the unicode value. | ||
47623 | ** | ||
47624 | ** During translation, assume that the byte that zTerm points | ||
47625 | ** is a 0x00. | ||
47626 | ** | ||
47627 | ** Write a pointer to the next unread byte back into *pzNext. | ||
47628 | ** | ||
47629 | ** Notes On Invalid UTF-8: | ||
47630 | ** | ||
47631 | ** * This routine never allows a 7-bit character (0x00 through 0x7f) to | ||
47632 | ** be encoded as a multi-byte character. Any multi-byte character that | ||
47633 | ** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd. | ||
47634 | ** | ||
47635 | ** * This routine never allows a UTF16 surrogate value to be encoded. | ||
47636 | ** If a multi-byte character attempts to encode a value between | ||
47637 | ** 0xd800 and 0xe000 then it is rendered as 0xfffd. | ||
47638 | ** | ||
47639 | ** * Bytes in the range of 0x80 through 0xbf which occur as the first | ||
47640 | ** byte of a character are interpreted as single-byte characters | ||
47641 | ** and rendered as themselves even though they are technically | ||
47642 | ** invalid characters. | ||
47643 | ** | ||
47644 | ** * This routine accepts an infinite number of different UTF8 encodings | ||
47645 | ** for unicode values 0x80 and greater. It do not change over-length | ||
47646 | ** encodings to 0xfffd as some systems recommend. | ||
47647 | */ | ||
47648 | #define READ_UTF8(zIn, zTerm, c) \ | ||
47649 | c = *(zIn++); \ | ||
47650 | if( c>=0xc0 ){ \ | ||
47651 | c = UtfTrans1[c-0xc0]; \ | ||
47652 | while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ | ||
47653 | c = (c<<6) + (0x3f & *(zIn++)); \ | ||
47654 | } \ | ||
47655 | if( c<0x80 \ | ||
47656 | || (c&0xFFFFF800)==0xD800 \ | ||
47657 | || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ | ||
47658 | } | ||
47659 | JX9_PRIVATE int jx9Utf8Read( | ||
47660 | const unsigned char *z, /* First byte of UTF-8 character */ | ||
47661 | const unsigned char *zTerm, /* Pretend this byte is 0x00 */ | ||
47662 | const unsigned char **pzNext /* Write first byte past UTF-8 char here */ | ||
47663 | ){ | ||
47664 | int c; | ||
47665 | READ_UTF8(z, zTerm, c); | ||
47666 | *pzNext = z; | ||
47667 | return c; | ||
47668 | } | ||
47669 | /* | ||
47670 | * string utf8_decode(string $data) | ||
47671 | * This function decodes data, assumed to be UTF-8 encoded, to unicode. | ||
47672 | * Parameters | ||
47673 | * data | ||
47674 | * An UTF-8 encoded string. | ||
47675 | * Return | ||
47676 | * Unicode decoded string or NULL on failure. | ||
47677 | */ | ||
47678 | static int vm_builtin_utf8_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
47679 | { | ||
47680 | const unsigned char *zIn, *zEnd; | ||
47681 | int nByte, c; | ||
47682 | if( nArg < 1 ){ | ||
47683 | /* Missing arguments, return null */ | ||
47684 | jx9_result_null(pCtx); | ||
47685 | return JX9_OK; | ||
47686 | } | ||
47687 | /* Extract the target string */ | ||
47688 | zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte); | ||
47689 | if( nByte < 1 ){ | ||
47690 | /* Empty string, return null */ | ||
47691 | jx9_result_null(pCtx); | ||
47692 | return JX9_OK; | ||
47693 | } | ||
47694 | zEnd = &zIn[nByte]; | ||
47695 | /* Start the decoding process */ | ||
47696 | while( zIn < zEnd ){ | ||
47697 | c = jx9Utf8Read(zIn, zEnd, &zIn); | ||
47698 | if( c == 0x0 ){ | ||
47699 | break; | ||
47700 | } | ||
47701 | jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); | ||
47702 | } | ||
47703 | return JX9_OK; | ||
47704 | } | ||
47705 | /* | ||
47706 | * string json_encode(mixed $value) | ||
47707 | * Returns a string containing the JSON representation of value. | ||
47708 | * Parameters | ||
47709 | * $value | ||
47710 | * The value being encoded. Can be any type except a resource. | ||
47711 | * Return | ||
47712 | * Returns a JSON encoded string on success. FALSE otherwise | ||
47713 | */ | ||
47714 | static int vm_builtin_json_encode(jx9_context *pCtx,int nArg,jx9_value **apArg) | ||
47715 | { | ||
47716 | SyBlob sBlob; | ||
47717 | if( nArg < 1 ){ | ||
47718 | /* Missing arguments, return FALSE */ | ||
47719 | jx9_result_bool(pCtx, 0); | ||
47720 | return JX9_OK; | ||
47721 | } | ||
47722 | /* Init the working buffer */ | ||
47723 | SyBlobInit(&sBlob,&pCtx->pVm->sAllocator); | ||
47724 | /* Perform the encoding operation */ | ||
47725 | jx9JsonSerialize(apArg[0],&sBlob); | ||
47726 | /* Return the serialized value */ | ||
47727 | jx9_result_string(pCtx,(const char *)SyBlobData(&sBlob),(int)SyBlobLength(&sBlob)); | ||
47728 | /* Cleanup */ | ||
47729 | SyBlobRelease(&sBlob); | ||
47730 | /* All done */ | ||
47731 | return JX9_OK; | ||
47732 | } | ||
47733 | /* | ||
47734 | * mixed json_decode(string $json) | ||
47735 | * Takes a JSON encoded string and converts it into a JX9 variable. | ||
47736 | * Parameters | ||
47737 | * $json | ||
47738 | * The json string being decoded. | ||
47739 | * Return | ||
47740 | * The value encoded in json in appropriate JX9 type. Values true, false and null (case-insensitive) | ||
47741 | * are returned as TRUE, FALSE and NULL respectively. NULL is returned if the json cannot be decoded | ||
47742 | * or if the encoded data is deeper than the recursion limit. | ||
47743 | */ | ||
47744 | static int vm_builtin_json_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) | ||
47745 | { | ||
47746 | const char *zJSON; | ||
47747 | int nByte; | ||
47748 | if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ | ||
47749 | /* Missing/Invalid arguments, return NULL */ | ||
47750 | jx9_result_null(pCtx); | ||
47751 | return JX9_OK; | ||
47752 | } | ||
47753 | /* Extract the JSON string */ | ||
47754 | zJSON = jx9_value_to_string(apArg[0], &nByte); | ||
47755 | if( nByte < 1 ){ | ||
47756 | /* Empty string, return NULL */ | ||
47757 | jx9_result_null(pCtx); | ||
47758 | return JX9_OK; | ||
47759 | } | ||
47760 | /* Decode the raw JSON */ | ||
47761 | jx9JsonDecode(pCtx,zJSON,nByte); | ||
47762 | return JX9_OK; | ||
47763 | } | ||
47764 | /* Table of built-in VM functions. */ | ||
47765 | static const jx9_builtin_func aVmFunc[] = { | ||
47766 | /* JSON Encoding/Decoding */ | ||
47767 | { "json_encode", vm_builtin_json_encode }, | ||
47768 | { "json_decode", vm_builtin_json_decode }, | ||
47769 | /* Functions calls */ | ||
47770 | { "func_num_args" , vm_builtin_func_num_args }, | ||
47771 | { "func_get_arg" , vm_builtin_func_get_arg }, | ||
47772 | { "func_get_args" , vm_builtin_func_get_args }, | ||
47773 | { "function_exists", vm_builtin_func_exists }, | ||
47774 | { "is_callable" , vm_builtin_is_callable }, | ||
47775 | { "get_defined_functions", vm_builtin_get_defined_func }, | ||
47776 | /* Constants management */ | ||
47777 | { "defined", vm_builtin_defined }, | ||
47778 | { "get_defined_constants", vm_builtin_get_defined_constants }, | ||
47779 | /* Random numbers/strings generators */ | ||
47780 | { "rand", vm_builtin_rand }, | ||
47781 | { "rand_str", vm_builtin_rand_str }, | ||
47782 | { "getrandmax", vm_builtin_getrandmax }, | ||
47783 | /* Language constructs functions */ | ||
47784 | { "print", vm_builtin_print }, | ||
47785 | { "exit", vm_builtin_exit }, | ||
47786 | { "die", vm_builtin_exit }, | ||
47787 | /* Variable handling functions */ | ||
47788 | { "gettype", vm_builtin_gettype }, | ||
47789 | { "get_resource_type", vm_builtin_get_resource_type}, | ||
47790 | /* Variable dumping */ | ||
47791 | { "dump", vm_builtin_dump }, | ||
47792 | /* Release info */ | ||
47793 | {"jx9_version", vm_builtin_jx9_version }, | ||
47794 | {"jx9_credits", vm_builtin_jx9_version }, | ||
47795 | {"jx9_info", vm_builtin_jx9_version }, | ||
47796 | {"jx9_copyright", vm_builtin_jx9_version }, | ||
47797 | /* hashmap */ | ||
47798 | {"extract", vm_builtin_extract }, | ||
47799 | /* URL related function */ | ||
47800 | {"parse_url", vm_builtin_parse_url }, | ||
47801 | /* UTF-8 encoding/decoding */ | ||
47802 | {"utf8_encode", vm_builtin_utf8_encode}, | ||
47803 | {"utf8_decode", vm_builtin_utf8_decode}, | ||
47804 | /* Command line processing */ | ||
47805 | {"getopt", vm_builtin_getopt }, | ||
47806 | /* Files/URI inclusion facility */ | ||
47807 | { "include", vm_builtin_include }, | ||
47808 | { "import", vm_builtin_import } | ||
47809 | }; | ||
47810 | /* | ||
47811 | * Register the built-in VM functions defined above. | ||
47812 | */ | ||
47813 | static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm) | ||
47814 | { | ||
47815 | sxi32 rc; | ||
47816 | sxu32 n; | ||
47817 | for( n = 0 ; n < SX_ARRAYSIZE(aVmFunc) ; ++n ){ | ||
47818 | /* Note that these special functions have access | ||
47819 | * to the underlying virtual machine as their | ||
47820 | * private data. | ||
47821 | */ | ||
47822 | rc = jx9_create_function(&(*pVm), aVmFunc[n].zName, aVmFunc[n].xFunc, &(*pVm)); | ||
47823 | if( rc != SXRET_OK ){ | ||
47824 | return rc; | ||
47825 | } | ||
47826 | } | ||
47827 | return SXRET_OK; | ||
47828 | } | ||
47829 | #ifndef JX9_DISABLE_BUILTIN_FUNC | ||
47830 | /* | ||
47831 | * Extract the IO stream device associated with a given scheme. | ||
47832 | * Return a pointer to an instance of jx9_io_stream when the scheme | ||
47833 | * have an associated IO stream registered with it. NULL otherwise. | ||
47834 | * If no scheme:// is avalilable then the file:// scheme is assumed. | ||
47835 | * For more information on how to register IO stream devices, please | ||
47836 | * refer to the official documentation. | ||
47837 | */ | ||
47838 | JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice( | ||
47839 | jx9_vm *pVm, /* Target VM */ | ||
47840 | const char **pzDevice, /* Full path, URI, ... */ | ||
47841 | int nByte /* *pzDevice length*/ | ||
47842 | ) | ||
47843 | { | ||
47844 | const char *zIn, *zEnd, *zCur, *zNext; | ||
47845 | jx9_io_stream **apStream, *pStream; | ||
47846 | SyString sDev, sCur; | ||
47847 | sxu32 n, nEntry; | ||
47848 | int rc; | ||
47849 | /* Check if a scheme [i.e: file://, http://, zip://...] is available */ | ||
47850 | zNext = zCur = zIn = *pzDevice; | ||
47851 | zEnd = &zIn[nByte]; | ||
47852 | while( zIn < zEnd ){ | ||
47853 | if( zIn < &zEnd[-3]/*://*/ && zIn[0] == ':' && zIn[1] == '/' && zIn[2] == '/' ){ | ||
47854 | /* Got one */ | ||
47855 | zNext = &zIn[sizeof("://")-1]; | ||
47856 | break; | ||
47857 | } | ||
47858 | /* Advance the cursor */ | ||
47859 | zIn++; | ||
47860 | } | ||
47861 | if( zIn >= zEnd ){ | ||
47862 | /* No such scheme, return the default stream */ | ||
47863 | return pVm->pDefStream; | ||
47864 | } | ||
47865 | SyStringInitFromBuf(&sDev, zCur, zIn-zCur); | ||
47866 | /* Remove leading and trailing white spaces */ | ||
47867 | SyStringFullTrim(&sDev); | ||
47868 | /* Perform a linear lookup on the installed stream devices */ | ||
47869 | apStream = (jx9_io_stream **)SySetBasePtr(&pVm->aIOstream); | ||
47870 | nEntry = SySetUsed(&pVm->aIOstream); | ||
47871 | for( n = 0 ; n < nEntry ; n++ ){ | ||
47872 | pStream = apStream[n]; | ||
47873 | SyStringInitFromBuf(&sCur, pStream->zName, SyStrlen(pStream->zName)); | ||
47874 | /* Perfrom a case-insensitive comparison */ | ||
47875 | rc = SyStringCmp(&sDev, &sCur, SyStrnicmp); | ||
47876 | if( rc == 0 ){ | ||
47877 | /* Stream device found */ | ||
47878 | *pzDevice = zNext; | ||
47879 | return pStream; | ||
47880 | } | ||
47881 | } | ||
47882 | /* No such stream, return NULL */ | ||
47883 | return 0; | ||
47884 | } | ||
47885 | #endif /* JX9_DISABLE_BUILTIN_FUNC */ | ||
47886 | /* | ||
47887 | * Section: | ||
47888 | * HTTP/URI related routines. | ||
47889 | * Authors: | ||
47890 | * Symisc Systems, devel@symisc.net. | ||
47891 | * Copyright (C) Symisc Systems, http://jx9.symisc.net | ||
47892 | * Status: | ||
47893 | * Stable. | ||
47894 | */ | ||
47895 | /* | ||
47896 | * URI Parser: Split an URI into components [i.e: Host, Path, Query, ...]. | ||
47897 | * URI syntax: [method:/][/[user[:pwd]@]host[:port]/][document] | ||
47898 | * This almost, but not quite, RFC1738 URI syntax. | ||
47899 | * This routine is not a validator, it does not check for validity | ||
47900 | * nor decode URI parts, the only thing this routine does is splitting | ||
47901 | * the input to its fields. | ||
47902 | * Upper layer are responsible of decoding and validating URI parts. | ||
47903 | * On success, this function populate the "SyhttpUri" structure passed | ||
47904 | * as the first argument. Otherwise SXERR_* is returned when a malformed | ||
47905 | * input is encountered. | ||
47906 | */ | ||
47907 | static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen) | ||
47908 | { | ||
47909 | const char *zEnd = &zUri[nLen]; | ||
47910 | sxu8 bHostOnly = FALSE; | ||
47911 | sxu8 bIPv6 = FALSE ; | ||
47912 | const char *zCur; | ||
47913 | SyString *pComp; | ||
47914 | sxu32 nPos = 0; | ||
47915 | sxi32 rc; | ||
47916 | /* Zero the structure first */ | ||
47917 | SyZero(pOut, sizeof(SyhttpUri)); | ||
47918 | /* Remove leading and trailing white spaces */ | ||
47919 | SyStringInitFromBuf(&pOut->sRaw, zUri, nLen); | ||
47920 | SyStringFullTrim(&pOut->sRaw); | ||
47921 | /* Find the first '/' separator */ | ||
47922 | rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos); | ||
47923 | if( rc != SXRET_OK ){ | ||
47924 | /* Assume a host name only */ | ||
47925 | zCur = zEnd; | ||
47926 | bHostOnly = TRUE; | ||
47927 | goto ProcessHost; | ||
47928 | } | ||
47929 | zCur = &zUri[nPos]; | ||
47930 | if( zUri != zCur && zCur[-1] == ':' ){ | ||
47931 | /* Extract a scheme: | ||
47932 | * Not that we can get an invalid scheme here. | ||
47933 | * Fortunately the caller can discard any URI by comparing this scheme with its | ||
47934 | * registered schemes and will report the error as soon as his comparison function | ||
47935 | * fail. | ||
47936 | */ | ||
47937 | pComp = &pOut->sScheme; | ||
47938 | SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri - 1)); | ||
47939 | SyStringLeftTrim(pComp); | ||
47940 | } | ||
47941 | if( zCur[1] != '/' ){ | ||
47942 | if( zCur == zUri || zCur[-1] == ':' ){ | ||
47943 | /* No authority */ | ||
47944 | goto PathSplit; | ||
47945 | } | ||
47946 | /* There is something here , we will assume its an authority | ||
47947 | * and someone has forgot the two prefix slashes "//", | ||
47948 | * sooner or later we will detect if we are dealing with a malicious | ||
47949 | * user or not, but now assume we are dealing with an authority | ||
47950 | * and let the caller handle all the validation process. | ||
47951 | */ | ||
47952 | goto ProcessHost; | ||
47953 | } | ||
47954 | zUri = &zCur[2]; | ||
47955 | zCur = zEnd; | ||
47956 | rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos); | ||
47957 | if( rc == SXRET_OK ){ | ||
47958 | zCur = &zUri[nPos]; | ||
47959 | } | ||
47960 | ProcessHost: | ||
47961 | /* Extract user information if present */ | ||
47962 | rc = SyByteFind(zUri, (sxu32)(zCur - zUri), '@', &nPos); | ||
47963 | if( rc == SXRET_OK ){ | ||
47964 | if( nPos > 0 ){ | ||
47965 | sxu32 nPassOfft; /* Password offset */ | ||
47966 | pComp = &pOut->sUser; | ||
47967 | SyStringInitFromBuf(pComp, zUri, nPos); | ||
47968 | /* Extract the password if available */ | ||
47969 | rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPassOfft); | ||
47970 | if( rc == SXRET_OK && nPassOfft < nPos){ | ||
47971 | pComp->nByte = nPassOfft; | ||
47972 | pComp = &pOut->sPass; | ||
47973 | pComp->zString = &zUri[nPassOfft+sizeof(char)]; | ||
47974 | pComp->nByte = nPos - nPassOfft - 1; | ||
47975 | } | ||
47976 | /* Update the cursor */ | ||
47977 | zUri = &zUri[nPos+1]; | ||
47978 | }else{ | ||
47979 | zUri++; | ||
47980 | } | ||
47981 | } | ||
47982 | pComp = &pOut->sHost; | ||
47983 | while( zUri < zCur && SyisSpace(zUri[0])){ | ||
47984 | zUri++; | ||
47985 | } | ||
47986 | SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri)); | ||
47987 | if( pComp->zString[0] == '[' ){ | ||
47988 | /* An IPv6 Address: Make a simple naive test | ||
47989 | */ | ||
47990 | zUri++; pComp->zString++; pComp->nByte = 0; | ||
47991 | while( ((unsigned char)zUri[0] < 0xc0 && SyisHex(zUri[0])) || zUri[0] == ':' ){ | ||
47992 | zUri++; pComp->nByte++; | ||
47993 | } | ||
47994 | if( zUri[0] != ']' ){ | ||
47995 | return SXERR_CORRUPT; /* Malformed IPv6 address */ | ||
47996 | } | ||
47997 | zUri++; | ||
47998 | bIPv6 = TRUE; | ||
47999 | } | ||
48000 | /* Extract a port number if available */ | ||
48001 | rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPos); | ||
48002 | if( rc == SXRET_OK ){ | ||
48003 | if( bIPv6 == FALSE ){ | ||
48004 | pComp->nByte = (sxu32)(&zUri[nPos] - zUri); | ||
48005 | } | ||
48006 | pComp = &pOut->sPort; | ||
48007 | SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zCur - &zUri[nPos+1])); | ||
48008 | } | ||
48009 | if( bHostOnly == TRUE ){ | ||
48010 | return SXRET_OK; | ||
48011 | } | ||
48012 | PathSplit: | ||
48013 | zUri = zCur; | ||
48014 | pComp = &pOut->sPath; | ||
48015 | SyStringInitFromBuf(pComp, zUri, (sxu32)(zEnd-zUri)); | ||
48016 | if( pComp->nByte == 0 ){ | ||
48017 | return SXRET_OK; /* Empty path */ | ||
48018 | } | ||
48019 | if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '?', &nPos) ){ | ||
48020 | pComp->nByte = nPos; /* Update path length */ | ||
48021 | pComp = &pOut->sQuery; | ||
48022 | SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1])); | ||
48023 | } | ||
48024 | if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '#', &nPos) ){ | ||
48025 | /* Update path or query length */ | ||
48026 | if( pComp == &pOut->sPath ){ | ||
48027 | pComp->nByte = nPos; | ||
48028 | }else{ | ||
48029 | if( &zUri[nPos] < (char *)SyStringData(pComp) ){ | ||
48030 | /* Malformed syntax : Query must be present before fragment */ | ||
48031 | return SXERR_SYNTAX; | ||
48032 | } | ||
48033 | pComp->nByte -= (sxu32)(zEnd - &zUri[nPos]); | ||
48034 | } | ||
48035 | pComp = &pOut->sFragment; | ||
48036 | SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1])) | ||
48037 | } | ||
48038 | return SXRET_OK; | ||
48039 | } | ||
48040 | /* | ||
48041 | * Extract a single line from a raw HTTP request. | ||
48042 | * Return SXRET_OK on success, SXERR_EOF when end of input | ||
48043 | * and SXERR_MORE when more input is needed. | ||
48044 | */ | ||
48045 | static sxi32 VmGetNextLine(SyString *pCursor, SyString *pCurrent) | ||
48046 | { | ||
48047 | const char *zIn; | ||
48048 | sxu32 nPos; | ||
48049 | /* Jump leading white spaces */ | ||
48050 | SyStringLeftTrim(pCursor); | ||
48051 | if( pCursor->nByte < 1 ){ | ||
48052 | SyStringInitFromBuf(pCurrent, 0, 0); | ||
48053 | return SXERR_EOF; /* End of input */ | ||
48054 | } | ||
48055 | zIn = SyStringData(pCursor); | ||
48056 | if( SXRET_OK != SyByteListFind(pCursor->zString, pCursor->nByte, "\r\n", &nPos) ){ | ||
48057 | /* Line not found, tell the caller to read more input from source */ | ||
48058 | SyStringDupPtr(pCurrent, pCursor); | ||
48059 | return SXERR_MORE; | ||
48060 | } | ||
48061 | pCurrent->zString = zIn; | ||
48062 | pCurrent->nByte = nPos; | ||
48063 | /* advance the cursor so we can call this routine again */ | ||
48064 | pCursor->zString = &zIn[nPos]; | ||
48065 | pCursor->nByte -= nPos; | ||
48066 | return SXRET_OK; | ||
48067 | } | ||
48068 | /* | ||
48069 | * Split a single MIME header into a name value pair. | ||
48070 | * This function return SXRET_OK, SXERR_CONTINUE on success. | ||
48071 | * Otherwise SXERR_NEXT is returned when a malformed header | ||
48072 | * is encountered. | ||
48073 | * Note: This function handle also mult-line headers. | ||
48074 | */ | ||
48075 | static sxi32 VmHttpProcessOneHeader(SyhttpHeader *pHdr, SyhttpHeader *pLast, const char *zLine, sxu32 nLen) | ||
48076 | { | ||
48077 | SyString *pName; | ||
48078 | sxu32 nPos; | ||
48079 | sxi32 rc; | ||
48080 | if( nLen < 1 ){ | ||
48081 | return SXERR_NEXT; | ||
48082 | } | ||
48083 | /* Check for multi-line header */ | ||
48084 | if( pLast && (zLine[-1] == ' ' || zLine[-1] == '\t') ){ | ||
48085 | SyString *pTmp = &pLast->sValue; | ||
48086 | SyStringFullTrim(pTmp); | ||
48087 | if( pTmp->nByte == 0 ){ | ||
48088 | SyStringInitFromBuf(pTmp, zLine, nLen); | ||
48089 | }else{ | ||
48090 | /* Update header value length */ | ||
48091 | pTmp->nByte = (sxu32)(&zLine[nLen] - pTmp->zString); | ||
48092 | } | ||
48093 | /* Simply tell the caller to reset its states and get another line */ | ||
48094 | return SXERR_CONTINUE; | ||
48095 | } | ||
48096 | /* Split the header */ | ||
48097 | pName = &pHdr->sName; | ||
48098 | rc = SyByteFind(zLine, nLen, ':', &nPos); | ||
48099 | if(rc != SXRET_OK ){ | ||
48100 | return SXERR_NEXT; /* Malformed header;Check the next entry */ | ||
48101 | } | ||
48102 | SyStringInitFromBuf(pName, zLine, nPos); | ||
48103 | SyStringFullTrim(pName); | ||
48104 | /* Extract a header value */ | ||
48105 | SyStringInitFromBuf(&pHdr->sValue, &zLine[nPos + 1], nLen - nPos - 1); | ||
48106 | /* Remove leading and trailing whitespaces */ | ||
48107 | SyStringFullTrim(&pHdr->sValue); | ||
48108 | return SXRET_OK; | ||
48109 | } | ||
48110 | /* | ||
48111 | * Extract all MIME headers associated with a HTTP request. | ||
48112 | * After processing the first line of a HTTP request, the following | ||
48113 | * routine is called in order to extract MIME headers. | ||
48114 | * This function return SXRET_OK on success, SXERR_MORE when it needs | ||
48115 | * more inputs. | ||
48116 | * Note: Any malformed header is simply discarded. | ||
48117 | */ | ||
48118 | static sxi32 VmHttpExtractHeaders(SyString *pRequest, SySet *pOut) | ||
48119 | { | ||
48120 | SyhttpHeader *pLast = 0; | ||
48121 | SyString sCurrent; | ||
48122 | SyhttpHeader sHdr; | ||
48123 | sxu8 bEol; | ||
48124 | sxi32 rc; | ||
48125 | if( SySetUsed(pOut) > 0 ){ | ||
48126 | pLast = (SyhttpHeader *)SySetAt(pOut, SySetUsed(pOut)-1); | ||
48127 | } | ||
48128 | bEol = FALSE; | ||
48129 | for(;;){ | ||
48130 | SyZero(&sHdr, sizeof(SyhttpHeader)); | ||
48131 | /* Extract a single line from the raw HTTP request */ | ||
48132 | rc = VmGetNextLine(pRequest, &sCurrent); | ||
48133 | if(rc != SXRET_OK ){ | ||
48134 | if( sCurrent.nByte < 1 ){ | ||
48135 | break; | ||
48136 | } | ||
48137 | bEol = TRUE; | ||
48138 | } | ||
48139 | /* Process the header */ | ||
48140 | if( SXRET_OK == VmHttpProcessOneHeader(&sHdr, pLast, sCurrent.zString, sCurrent.nByte)){ | ||
48141 | if( SXRET_OK != SySetPut(pOut, (const void *)&sHdr) ){ | ||
48142 | break; | ||
48143 | } | ||
48144 | /* Retrieve the last parsed header so we can handle multi-line header | ||
48145 | * in case we face one of them. | ||
48146 | */ | ||
48147 | pLast = (SyhttpHeader *)SySetPeek(pOut); | ||
48148 | } | ||
48149 | if( bEol ){ | ||
48150 | break; | ||
48151 | } | ||
48152 | } /* for(;;) */ | ||
48153 | return SXRET_OK; | ||
48154 | } | ||
48155 | /* | ||
48156 | * Process the first line of a HTTP request. | ||
48157 | * This routine perform the following operations | ||
48158 | * 1) Extract the HTTP method. | ||
48159 | * 2) Split the request URI to it's fields [ie: host, path, query, ...]. | ||
48160 | * 3) Extract the HTTP protocol version. | ||
48161 | */ | ||
48162 | static sxi32 VmHttpProcessFirstLine( | ||
48163 | SyString *pRequest, /* Raw HTTP request */ | ||
48164 | sxi32 *pMethod, /* OUT: HTTP method */ | ||
48165 | SyhttpUri *pUri, /* OUT: Parse of the URI */ | ||
48166 | sxi32 *pProto /* OUT: HTTP protocol */ | ||
48167 | ) | ||
48168 | { | ||
48169 | static const char *azMethods[] = { "get", "post", "head", "put"}; | ||
48170 | static const sxi32 aMethods[] = { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PUT}; | ||
48171 | const char *zIn, *zEnd, *zPtr; | ||
48172 | SyString sLine; | ||
48173 | sxu32 nLen; | ||
48174 | sxi32 rc; | ||
48175 | /* Extract the first line and update the pointer */ | ||
48176 | rc = VmGetNextLine(pRequest, &sLine); | ||
48177 | if( rc != SXRET_OK ){ | ||
48178 | return rc; | ||
48179 | } | ||
48180 | if ( sLine.nByte < 1 ){ | ||
48181 | /* Empty HTTP request */ | ||
48182 | return SXERR_EMPTY; | ||
48183 | } | ||
48184 | /* Delimit the line and ignore trailing and leading white spaces */ | ||
48185 | zIn = sLine.zString; | ||
48186 | zEnd = &zIn[sLine.nByte]; | ||
48187 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ | ||
48188 | zIn++; | ||
48189 | } | ||
48190 | /* Extract the HTTP method */ | ||
48191 | zPtr = zIn; | ||
48192 | while( zIn < zEnd && !SyisSpace(zIn[0]) ){ | ||
48193 | zIn++; | ||
48194 | } | ||
48195 | *pMethod = HTTP_METHOD_OTHR; | ||
48196 | if( zIn > zPtr ){ | ||
48197 | sxu32 i; | ||
48198 | nLen = (sxu32)(zIn-zPtr); | ||
48199 | for( i = 0 ; i < SX_ARRAYSIZE(azMethods) ; ++i ){ | ||
48200 | if( SyStrnicmp(azMethods[i], zPtr, nLen) == 0 ){ | ||
48201 | *pMethod = aMethods[i]; | ||
48202 | break; | ||
48203 | } | ||
48204 | } | ||
48205 | } | ||
48206 | /* Jump trailing white spaces */ | ||
48207 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ | ||
48208 | zIn++; | ||
48209 | } | ||
48210 | /* Extract the request URI */ | ||
48211 | zPtr = zIn; | ||
48212 | while( zIn < zEnd && !SyisSpace(zIn[0]) ){ | ||
48213 | zIn++; | ||
48214 | } | ||
48215 | if( zIn > zPtr ){ | ||
48216 | nLen = (sxu32)(zIn-zPtr); | ||
48217 | /* Split raw URI to it's fields */ | ||
48218 | VmHttpSplitURI(pUri, zPtr, nLen); | ||
48219 | } | ||
48220 | /* Jump trailing white spaces */ | ||
48221 | while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ | ||
48222 | zIn++; | ||
48223 | } | ||
48224 | /* Extract the HTTP version */ | ||
48225 | zPtr = zIn; | ||
48226 | while( zIn < zEnd && !SyisSpace(zIn[0]) ){ | ||
48227 | zIn++; | ||
48228 | } | ||
48229 | *pProto = HTTP_PROTO_11; /* HTTP/1.1 */ | ||
48230 | rc = 1; | ||
48231 | if( zIn > zPtr ){ | ||
48232 | rc = SyStrnicmp(zPtr, "http/1.0", (sxu32)(zIn-zPtr)); | ||
48233 | } | ||
48234 | if( !rc ){ | ||
48235 | *pProto = HTTP_PROTO_10; /* HTTP/1.0 */ | ||
48236 | } | ||
48237 | return SXRET_OK; | ||
48238 | } | ||
48239 | /* | ||
48240 | * Tokenize, decode and split a raw query encoded as: "x-www-form-urlencoded" | ||
48241 | * into a name value pair. | ||
48242 | * Note that this encoding is implicit in GET based requests. | ||
48243 | * After the tokenization process, register the decoded queries | ||
48244 | * in the $_GET/$_POST/$_REQUEST superglobals arrays. | ||
48245 | */ | ||
48246 | static sxi32 VmHttpSplitEncodedQuery( | ||
48247 | jx9_vm *pVm, /* Target VM */ | ||
48248 | SyString *pQuery, /* Raw query to decode */ | ||
48249 | SyBlob *pWorker, /* Working buffer */ | ||
48250 | int is_post /* TRUE if we are dealing with a POST request */ | ||
48251 | ) | ||
48252 | { | ||
48253 | const char *zEnd = &pQuery->zString[pQuery->nByte]; | ||
48254 | const char *zIn = pQuery->zString; | ||
48255 | jx9_value *pGet, *pRequest; | ||
48256 | SyString sName, sValue; | ||
48257 | const char *zPtr; | ||
48258 | sxu32 nBlobOfft; | ||
48259 | /* Extract superglobals */ | ||
48260 | if( is_post ){ | ||
48261 | /* $_POST superglobal */ | ||
48262 | pGet = VmExtractSuper(&(*pVm), "_POST", sizeof("_POST")-1); | ||
48263 | }else{ | ||
48264 | /* $_GET superglobal */ | ||
48265 | pGet = VmExtractSuper(&(*pVm), "_GET", sizeof("_GET")-1); | ||
48266 | } | ||
48267 | pRequest = VmExtractSuper(&(*pVm), "_REQUEST", sizeof("_REQUEST")-1); | ||
48268 | /* Split up the raw query */ | ||
48269 | for(;;){ | ||
48270 | /* Jump leading white spaces */ | ||
48271 | while(zIn < zEnd && SyisSpace(zIn[0]) ){ | ||
48272 | zIn++; | ||
48273 | } | ||
48274 | if( zIn >= zEnd ){ | ||
48275 | break; | ||
48276 | } | ||
48277 | zPtr = zIn; | ||
48278 | while( zPtr < zEnd && zPtr[0] != '=' && zPtr[0] != '&' && zPtr[0] != ';' ){ | ||
48279 | zPtr++; | ||
48280 | } | ||
48281 | /* Reset the working buffer */ | ||
48282 | SyBlobReset(pWorker); | ||
48283 | /* Decode the entry */ | ||
48284 | SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); | ||
48285 | /* Save the entry */ | ||
48286 | sName.nByte = SyBlobLength(pWorker); | ||
48287 | sValue.zString = 0; | ||
48288 | sValue.nByte = 0; | ||
48289 | if( zPtr < zEnd && zPtr[0] == '=' ){ | ||
48290 | zPtr++; | ||
48291 | zIn = zPtr; | ||
48292 | /* Store field value */ | ||
48293 | while( zPtr < zEnd && zPtr[0] != '&' && zPtr[0] != ';' ){ | ||
48294 | zPtr++; | ||
48295 | } | ||
48296 | if( zPtr > zIn ){ | ||
48297 | /* Decode the value */ | ||
48298 | nBlobOfft = SyBlobLength(pWorker); | ||
48299 | SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); | ||
48300 | sValue.zString = (const char *)SyBlobDataAt(pWorker, nBlobOfft); | ||
48301 | sValue.nByte = SyBlobLength(pWorker) - nBlobOfft; | ||
48302 | |||
48303 | } | ||
48304 | /* Synchronize pointers */ | ||
48305 | zIn = zPtr; | ||
48306 | } | ||
48307 | sName.zString = (const char *)SyBlobData(pWorker); | ||
48308 | /* Install the decoded query in the $_GET/$_REQUEST array */ | ||
48309 | if( pGet && (pGet->iFlags & MEMOBJ_HASHMAP) ){ | ||
48310 | VmHashmapInsert((jx9_hashmap *)pGet->x.pOther, | ||
48311 | sName.zString, (int)sName.nByte, | ||
48312 | sValue.zString, (int)sValue.nByte | ||
48313 | ); | ||
48314 | } | ||
48315 | if( pRequest && (pRequest->iFlags & MEMOBJ_HASHMAP) ){ | ||
48316 | VmHashmapInsert((jx9_hashmap *)pRequest->x.pOther, | ||
48317 | sName.zString, (int)sName.nByte, | ||
48318 | sValue.zString, (int)sValue.nByte | ||
48319 | ); | ||
48320 | } | ||
48321 | /* Advance the pointer */ | ||
48322 | zIn = &zPtr[1]; | ||
48323 | } | ||
48324 | /* All done*/ | ||
48325 | return SXRET_OK; | ||
48326 | } | ||
48327 | /* | ||
48328 | * Extract MIME header value from the given set. | ||
48329 | * Return header value on success. NULL otherwise. | ||
48330 | */ | ||
48331 | static SyString * VmHttpExtractHeaderValue(SySet *pSet, const char *zMime, sxu32 nByte) | ||
48332 | { | ||
48333 | SyhttpHeader *aMime, *pMime; | ||
48334 | SyString sMime; | ||
48335 | sxu32 n; | ||
48336 | SyStringInitFromBuf(&sMime, zMime, nByte); | ||
48337 | /* Point to the MIME entries */ | ||
48338 | aMime = (SyhttpHeader *)SySetBasePtr(pSet); | ||
48339 | /* Perform the lookup */ | ||
48340 | for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ | ||
48341 | pMime = &aMime[n]; | ||
48342 | if( SyStringCmp(&sMime, &pMime->sName, SyStrnicmp) == 0 ){ | ||
48343 | /* Header found, return it's associated value */ | ||
48344 | return &pMime->sValue; | ||
48345 | } | ||
48346 | } | ||
48347 | /* No such MIME header */ | ||
48348 | return 0; | ||
48349 | } | ||
48350 | /* | ||
48351 | * Tokenize and decode a raw "Cookie:" MIME header into a name value pair | ||
48352 | * and insert it's fields [i.e name, value] in the $_COOKIE superglobal. | ||
48353 | */ | ||
48354 | static sxi32 VmHttpPorcessCookie(jx9_vm *pVm, SyBlob *pWorker, const char *zIn, sxu32 nByte) | ||
48355 | { | ||
48356 | const char *zPtr, *zDelimiter, *zEnd = &zIn[nByte]; | ||
48357 | SyString sName, sValue; | ||
48358 | jx9_value *pCookie; | ||
48359 | sxu32 nOfft; | ||
48360 | /* Make sure the $_COOKIE superglobal is available */ | ||
48361 | pCookie = VmExtractSuper(&(*pVm), "_COOKIE", sizeof("_COOKIE")-1); | ||
48362 | if( pCookie == 0 || (pCookie->iFlags & MEMOBJ_HASHMAP) == 0 ){ | ||
48363 | /* $_COOKIE superglobal not available */ | ||
48364 | return SXERR_NOTFOUND; | ||
48365 | } | ||
48366 | for(;;){ | ||
48367 | /* Jump leading white spaces */ | ||
48368 | while( zIn < zEnd && SyisSpace(zIn[0]) ){ | ||
48369 | zIn++; | ||
48370 | } | ||
48371 | if( zIn >= zEnd ){ | ||
48372 | break; | ||
48373 | } | ||
48374 | /* Reset the working buffer */ | ||
48375 | SyBlobReset(pWorker); | ||
48376 | zDelimiter = zIn; | ||
48377 | /* Delimit the name[=value]; pair */ | ||
48378 | while( zDelimiter < zEnd && zDelimiter[0] != ';' ){ | ||
48379 | zDelimiter++; | ||
48380 | } | ||
48381 | zPtr = zIn; | ||
48382 | while( zPtr < zDelimiter && zPtr[0] != '=' ){ | ||
48383 | zPtr++; | ||
48384 | } | ||
48385 | /* Decode the cookie */ | ||
48386 | SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); | ||
48387 | sName.nByte = SyBlobLength(pWorker); | ||
48388 | zPtr++; | ||
48389 | sValue.zString = 0; | ||
48390 | sValue.nByte = 0; | ||
48391 | if( zPtr < zDelimiter ){ | ||
48392 | /* Got a Cookie value */ | ||
48393 | nOfft = SyBlobLength(pWorker); | ||
48394 | SyUriDecode(zPtr, (sxu32)(zDelimiter-zPtr), jx9VmBlobConsumer, pWorker, TRUE); | ||
48395 | SyStringInitFromBuf(&sValue, SyBlobDataAt(pWorker, nOfft), SyBlobLength(pWorker)-nOfft); | ||
48396 | } | ||
48397 | /* Synchronize pointers */ | ||
48398 | zIn = &zDelimiter[1]; | ||
48399 | /* Perform the insertion */ | ||
48400 | sName.zString = (const char *)SyBlobData(pWorker); | ||
48401 | VmHashmapInsert((jx9_hashmap *)pCookie->x.pOther, | ||
48402 | sName.zString, (int)sName.nByte, | ||
48403 | sValue.zString, (int)sValue.nByte | ||
48404 | ); | ||
48405 | } | ||
48406 | return SXRET_OK; | ||
48407 | } | ||
48408 | /* | ||
48409 | * Process a full HTTP request and populate the appropriate arrays | ||
48410 | * such as $_SERVER, $_GET, $_POST, $_COOKIE, $_REQUEST, ... with the information | ||
48411 | * extracted from the raw HTTP request. As an extension Symisc introduced | ||
48412 | * the $_HEADER array which hold a copy of the processed HTTP MIME headers | ||
48413 | * and their associated values. [i.e: $_HEADER['Server'], $_HEADER['User-Agent'], ...]. | ||
48414 | * This function return SXRET_OK on success. Any other return value indicates | ||
48415 | * a malformed HTTP request. | ||
48416 | */ | ||
48417 | static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte) | ||
48418 | { | ||
48419 | SyString *pName, *pValue, sRequest; /* Raw HTTP request */ | ||
48420 | jx9_value *pHeaderArray; /* $_HEADER superglobal (Symisc eXtension to the JX9 specification)*/ | ||
48421 | SyhttpHeader *pHeader; /* MIME header */ | ||
48422 | SyhttpUri sUri; /* Parse of the raw URI*/ | ||
48423 | SyBlob sWorker; /* General purpose working buffer */ | ||
48424 | SySet sHeader; /* MIME headers set */ | ||
48425 | sxi32 iMethod; /* HTTP method [i.e: GET, POST, HEAD...]*/ | ||
48426 | sxi32 iVer; /* HTTP protocol version */ | ||
48427 | sxi32 rc; | ||
48428 | SyStringInitFromBuf(&sRequest, zRequest, nByte); | ||
48429 | SySetInit(&sHeader, &pVm->sAllocator, sizeof(SyhttpHeader)); | ||
48430 | SyBlobInit(&sWorker, &pVm->sAllocator); | ||
48431 | /* Ignore leading and trailing white spaces*/ | ||
48432 | SyStringFullTrim(&sRequest); | ||
48433 | /* Process the first line */ | ||
48434 | rc = VmHttpProcessFirstLine(&sRequest, &iMethod, &sUri, &iVer); | ||
48435 | if( rc != SXRET_OK ){ | ||
48436 | return rc; | ||
48437 | } | ||
48438 | /* Process MIME headers */ | ||
48439 | VmHttpExtractHeaders(&sRequest, &sHeader); | ||
48440 | /* | ||
48441 | * Setup $_SERVER environments | ||
48442 | */ | ||
48443 | /* 'SERVER_PROTOCOL': Name and revision of the information protocol via which the page was requested */ | ||
48444 | jx9_vm_config(pVm, | ||
48445 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48446 | "SERVER_PROTOCOL", | ||
48447 | iVer == HTTP_PROTO_10 ? "HTTP/1.0" : "HTTP/1.1", | ||
48448 | sizeof("HTTP/1.1")-1 | ||
48449 | ); | ||
48450 | /* 'REQUEST_METHOD': Which request method was used to access the page */ | ||
48451 | jx9_vm_config(pVm, | ||
48452 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48453 | "REQUEST_METHOD", | ||
48454 | iMethod == HTTP_METHOD_GET ? "GET" : | ||
48455 | (iMethod == HTTP_METHOD_POST ? "POST": | ||
48456 | (iMethod == HTTP_METHOD_PUT ? "PUT" : | ||
48457 | (iMethod == HTTP_METHOD_HEAD ? "HEAD" : "OTHER"))), | ||
48458 | -1 /* Compute attribute length automatically */ | ||
48459 | ); | ||
48460 | if( SyStringLength(&sUri.sQuery) > 0 && iMethod == HTTP_METHOD_GET ){ | ||
48461 | pValue = &sUri.sQuery; | ||
48462 | /* 'QUERY_STRING': The query string, if any, via which the page was accessed */ | ||
48463 | jx9_vm_config(pVm, | ||
48464 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48465 | "QUERY_STRING", | ||
48466 | pValue->zString, | ||
48467 | pValue->nByte | ||
48468 | ); | ||
48469 | /* Decoded the raw query */ | ||
48470 | VmHttpSplitEncodedQuery(&(*pVm), pValue, &sWorker, FALSE); | ||
48471 | } | ||
48472 | /* REQUEST_URI: The URI which was given in order to access this page; for instance, '/index.html' */ | ||
48473 | pValue = &sUri.sRaw; | ||
48474 | jx9_vm_config(pVm, | ||
48475 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48476 | "REQUEST_URI", | ||
48477 | pValue->zString, | ||
48478 | pValue->nByte | ||
48479 | ); | ||
48480 | /* | ||
48481 | * 'PATH_INFO' | ||
48482 | * 'ORIG_PATH_INFO' | ||
48483 | * Contains any client-provided pathname information trailing the actual script filename but preceding | ||
48484 | * the query string, if available. For instance, if the current script was accessed via the URL | ||
48485 | * http://www.example.com/jx9/path_info.jx9/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain | ||
48486 | * /some/stuff. | ||
48487 | */ | ||
48488 | pValue = &sUri.sPath; | ||
48489 | jx9_vm_config(pVm, | ||
48490 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48491 | "PATH_INFO", | ||
48492 | pValue->zString, | ||
48493 | pValue->nByte | ||
48494 | ); | ||
48495 | jx9_vm_config(pVm, | ||
48496 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48497 | "ORIG_PATH_INFO", | ||
48498 | pValue->zString, | ||
48499 | pValue->nByte | ||
48500 | ); | ||
48501 | /* 'HTTP_ACCEPT': Contents of the Accept: header from the current request, if there is one */ | ||
48502 | pValue = VmHttpExtractHeaderValue(&sHeader, "Accept", sizeof("Accept")-1); | ||
48503 | if( pValue ){ | ||
48504 | jx9_vm_config(pVm, | ||
48505 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48506 | "HTTP_ACCEPT", | ||
48507 | pValue->zString, | ||
48508 | pValue->nByte | ||
48509 | ); | ||
48510 | } | ||
48511 | /* 'HTTP_ACCEPT_CHARSET': Contents of the Accept-Charset: header from the current request, if there is one. */ | ||
48512 | pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Charset", sizeof("Accept-Charset")-1); | ||
48513 | if( pValue ){ | ||
48514 | jx9_vm_config(pVm, | ||
48515 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48516 | "HTTP_ACCEPT_CHARSET", | ||
48517 | pValue->zString, | ||
48518 | pValue->nByte | ||
48519 | ); | ||
48520 | } | ||
48521 | /* 'HTTP_ACCEPT_ENCODING': Contents of the Accept-Encoding: header from the current request, if there is one. */ | ||
48522 | pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Encoding", sizeof("Accept-Encoding")-1); | ||
48523 | if( pValue ){ | ||
48524 | jx9_vm_config(pVm, | ||
48525 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48526 | "HTTP_ACCEPT_ENCODING", | ||
48527 | pValue->zString, | ||
48528 | pValue->nByte | ||
48529 | ); | ||
48530 | } | ||
48531 | /* 'HTTP_ACCEPT_LANGUAGE': Contents of the Accept-Language: header from the current request, if there is one */ | ||
48532 | pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Language", sizeof("Accept-Language")-1); | ||
48533 | if( pValue ){ | ||
48534 | jx9_vm_config(pVm, | ||
48535 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48536 | "HTTP_ACCEPT_LANGUAGE", | ||
48537 | pValue->zString, | ||
48538 | pValue->nByte | ||
48539 | ); | ||
48540 | } | ||
48541 | /* 'HTTP_CONNECTION': Contents of the Connection: header from the current request, if there is one. */ | ||
48542 | pValue = VmHttpExtractHeaderValue(&sHeader, "Connection", sizeof("Connection")-1); | ||
48543 | if( pValue ){ | ||
48544 | jx9_vm_config(pVm, | ||
48545 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48546 | "HTTP_CONNECTION", | ||
48547 | pValue->zString, | ||
48548 | pValue->nByte | ||
48549 | ); | ||
48550 | } | ||
48551 | /* 'HTTP_HOST': Contents of the Host: header from the current request, if there is one. */ | ||
48552 | pValue = VmHttpExtractHeaderValue(&sHeader, "Host", sizeof("Host")-1); | ||
48553 | if( pValue ){ | ||
48554 | jx9_vm_config(pVm, | ||
48555 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48556 | "HTTP_HOST", | ||
48557 | pValue->zString, | ||
48558 | pValue->nByte | ||
48559 | ); | ||
48560 | } | ||
48561 | /* 'HTTP_REFERER': Contents of the Referer: header from the current request, if there is one. */ | ||
48562 | pValue = VmHttpExtractHeaderValue(&sHeader, "Referer", sizeof("Referer")-1); | ||
48563 | if( pValue ){ | ||
48564 | jx9_vm_config(pVm, | ||
48565 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48566 | "HTTP_REFERER", | ||
48567 | pValue->zString, | ||
48568 | pValue->nByte | ||
48569 | ); | ||
48570 | } | ||
48571 | /* 'HTTP_USER_AGENT': Contents of the Referer: header from the current request, if there is one. */ | ||
48572 | pValue = VmHttpExtractHeaderValue(&sHeader, "User-Agent", sizeof("User-Agent")-1); | ||
48573 | if( pValue ){ | ||
48574 | jx9_vm_config(pVm, | ||
48575 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48576 | "HTTP_USER_AGENT", | ||
48577 | pValue->zString, | ||
48578 | pValue->nByte | ||
48579 | ); | ||
48580 | } | ||
48581 | /* 'JX9_AUTH_DIGEST': When doing Digest HTTP authentication this variable is set to the 'Authorization' | ||
48582 | * header sent by the client (which you should then use to make the appropriate validation). | ||
48583 | */ | ||
48584 | pValue = VmHttpExtractHeaderValue(&sHeader, "Authorization", sizeof("Authorization")-1); | ||
48585 | if( pValue ){ | ||
48586 | jx9_vm_config(pVm, | ||
48587 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48588 | "JX9_AUTH_DIGEST", | ||
48589 | pValue->zString, | ||
48590 | pValue->nByte | ||
48591 | ); | ||
48592 | jx9_vm_config(pVm, | ||
48593 | JX9_VM_CONFIG_SERVER_ATTR, | ||
48594 | "JX9_AUTH", | ||
48595 | pValue->zString, | ||
48596 | pValue->nByte | ||
48597 | ); | ||
48598 | } | ||
48599 | /* Install all clients HTTP headers in the $_HEADER superglobal */ | ||
48600 | pHeaderArray = VmExtractSuper(&(*pVm), "_HEADER", sizeof("_HEADER")-1); | ||
48601 | /* Iterate throw the available MIME headers*/ | ||
48602 | SySetResetCursor(&sHeader); | ||
48603 | pHeader = 0; /* stupid cc warning */ | ||
48604 | while( SXRET_OK == SySetGetNextEntry(&sHeader, (void **)&pHeader) ){ | ||
48605 | pName = &pHeader->sName; | ||
48606 | pValue = &pHeader->sValue; | ||
48607 | if( pHeaderArray && (pHeaderArray->iFlags & MEMOBJ_HASHMAP)){ | ||
48608 | /* Insert the MIME header and it's associated value */ | ||
48609 | VmHashmapInsert((jx9_hashmap *)pHeaderArray->x.pOther, | ||
48610 | pName->zString, (int)pName->nByte, | ||
48611 | pValue->zString, (int)pValue->nByte | ||
48612 | ); | ||
48613 | } | ||
48614 | if( pName->nByte == sizeof("Cookie")-1 && SyStrnicmp(pName->zString, "Cookie", sizeof("Cookie")-1) == 0 | ||
48615 | && pValue->nByte > 0){ | ||
48616 | /* Process the name=value pair and insert them in the $_COOKIE superglobal array */ | ||
48617 | VmHttpPorcessCookie(&(*pVm), &sWorker, pValue->zString, pValue->nByte); | ||
48618 | } | ||
48619 | } | ||
48620 | if( iMethod == HTTP_METHOD_POST ){ | ||
48621 | /* Extract raw POST data */ | ||
48622 | pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Type", sizeof("Content-Type") - 1); | ||
48623 | if( pValue && pValue->nByte >= sizeof("application/x-www-form-urlencoded") - 1 && | ||
48624 | SyMemcmp("application/x-www-form-urlencoded", pValue->zString, pValue->nByte) == 0 ){ | ||
48625 | /* Extract POST data length */ | ||
48626 | pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Length", sizeof("Content-Length") - 1); | ||
48627 | if( pValue ){ | ||
48628 | sxi32 iLen = 0; /* POST data length */ | ||
48629 | SyStrToInt32(pValue->zString, pValue->nByte, (void *)&iLen, 0); | ||
48630 | if( iLen > 0 ){ | ||
48631 | /* Remove leading and trailing white spaces */ | ||
48632 | SyStringFullTrim(&sRequest); | ||
48633 | if( (int)sRequest.nByte > iLen ){ | ||
48634 | sRequest.nByte = (sxu32)iLen; | ||
48635 | } | ||
48636 | /* Decode POST data now */ | ||
48637 | VmHttpSplitEncodedQuery(&(*pVm), &sRequest, &sWorker, TRUE); | ||
48638 | } | ||
48639 | } | ||
48640 | } | ||
48641 | } | ||
48642 | /* All done, clean-up the mess left behind */ | ||
48643 | SySetRelease(&sHeader); | ||
48644 | SyBlobRelease(&sWorker); | ||
48645 | return SXRET_OK; | ||
48646 | } | ||
48647 | |||
48648 | /* | ||
48649 | * ---------------------------------------------------------- | ||
48650 | * File: lhash_kv.c | ||
48651 | * MD5: 581b07ce2984fd95740677285d8a11d3 | ||
48652 | * ---------------------------------------------------------- | ||
48653 | */ | ||
48654 | /* | ||
48655 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
48656 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
48657 | * Version 1.1.6 | ||
48658 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
48659 | * please contact Symisc Systems via: | ||
48660 | * legal@symisc.net | ||
48661 | * licensing@symisc.net | ||
48662 | * contact@symisc.net | ||
48663 | * or visit: | ||
48664 | * http://unqlite.org/licensing.html | ||
48665 | */ | ||
48666 | /* $SymiscID: lhash_kv.c v1.7 Solaris 2013-01-14 12:56 stable <chm@symisc.net> $ */ | ||
48667 | #ifndef UNQLITE_AMALGAMATION | ||
48668 | #include "unqliteInt.h" | ||
48669 | #endif | ||
48670 | /* | ||
48671 | * This file implements disk based hashtable using the linear hashing algorithm. | ||
48672 | * This implementation is the one decribed in the paper: | ||
48673 | * LINEAR HASHING : A NEW TOOL FOR FILE AND TABLE ADDRESSING. Witold Litwin. I. N. Ft. I. A.. 78 150 Le Chesnay, France. | ||
48674 | * Plus a smart extension called Virtual Bucket Table. (contact devel@symisc.net for additional information). | ||
48675 | */ | ||
48676 | /* Magic number identifying a valid storage image */ | ||
48677 | #define L_HASH_MAGIC 0xFA782DCB | ||
48678 | /* | ||
48679 | * Magic word to hash to identify a valid hash function. | ||
48680 | */ | ||
48681 | #define L_HASH_WORD "chm@symisc" | ||
48682 | /* | ||
48683 | * Cell size on disk. | ||
48684 | */ | ||
48685 | #define L_HASH_CELL_SZ (4/*Hash*/+4/*Key*/+8/*Data*/+2/* Offset of the next cell */+8/*Overflow*/) | ||
48686 | /* | ||
48687 | * Primary page (not overflow pages) header size on disk. | ||
48688 | */ | ||
48689 | #define L_HASH_PAGE_HDR_SZ (2/* Cell offset*/+2/* Free block offset*/+8/*Slave page number*/) | ||
48690 | /* | ||
48691 | * The maximum amount of payload (in bytes) that can be stored locally for | ||
48692 | * a database entry. If the entry contains more data than this, the | ||
48693 | * extra goes onto overflow pages. | ||
48694 | */ | ||
48695 | #define L_HASH_MX_PAYLOAD(PageSize) (PageSize-(L_HASH_PAGE_HDR_SZ+L_HASH_CELL_SZ)) | ||
48696 | /* | ||
48697 | * Maxium free space on a single page. | ||
48698 | */ | ||
48699 | #define L_HASH_MX_FREE_SPACE(PageSize) (PageSize - (L_HASH_PAGE_HDR_SZ)) | ||
48700 | /* | ||
48701 | ** The maximum number of bytes of payload allowed on a single overflow page. | ||
48702 | */ | ||
48703 | #define L_HASH_OVERFLOW_SIZE(PageSize) (PageSize-8) | ||
48704 | /* Forward declaration */ | ||
48705 | typedef struct lhash_kv_engine lhash_kv_engine; | ||
48706 | typedef struct lhpage lhpage; | ||
48707 | /* | ||
48708 | * Each record in the database is identified either in-memory or in | ||
48709 | * disk by an instance of the following structure. | ||
48710 | */ | ||
48711 | typedef struct lhcell lhcell; | ||
48712 | struct lhcell | ||
48713 | { | ||
48714 | /* Disk-data (Big-Endian) */ | ||
48715 | sxu32 nHash; /* Hash of the key: 4 bytes */ | ||
48716 | sxu32 nKey; /* Key length: 4 bytes */ | ||
48717 | sxu64 nData; /* Data length: 8 bytes */ | ||
48718 | sxu16 iNext; /* Offset of the next cell: 2 bytes */ | ||
48719 | pgno iOvfl; /* Overflow page number if any: 8 bytes */ | ||
48720 | /* In-memory data only */ | ||
48721 | lhpage *pPage; /* Page this cell belongs */ | ||
48722 | sxu16 iStart; /* Offset of this cell */ | ||
48723 | pgno iDataPage; /* Data page number when overflow */ | ||
48724 | sxu16 iDataOfft; /* Offset of the data in iDataPage */ | ||
48725 | SyBlob sKey; /* Record key for fast lookup (Kept in-memory if < 256KB ) */ | ||
48726 | lhcell *pNext,*pPrev; /* Linked list of the loaded memory cells */ | ||
48727 | lhcell *pNextCol,*pPrevCol; /* Collison chain */ | ||
48728 | }; | ||
48729 | /* | ||
48730 | ** Each database page has a header that is an instance of this | ||
48731 | ** structure. | ||
48732 | */ | ||
48733 | typedef struct lhphdr lhphdr; | ||
48734 | struct lhphdr | ||
48735 | { | ||
48736 | sxu16 iOfft; /* Offset of the first cell */ | ||
48737 | sxu16 iFree; /* Offset of the first free block*/ | ||
48738 | pgno iSlave; /* Slave page number */ | ||
48739 | }; | ||
48740 | /* | ||
48741 | * Each loaded primary disk page is represented in-memory using | ||
48742 | * an instance of the following structure. | ||
48743 | */ | ||
48744 | struct lhpage | ||
48745 | { | ||
48746 | lhash_kv_engine *pHash; /* KV Storage engine that own this page */ | ||
48747 | unqlite_page *pRaw; /* Raw page contents */ | ||
48748 | lhphdr sHdr; /* Processed page header */ | ||
48749 | lhcell **apCell; /* Cell buckets */ | ||
48750 | lhcell *pList,*pFirst; /* Linked list of cells */ | ||
48751 | sxu32 nCell; /* Total number of cells */ | ||
48752 | sxu32 nCellSize; /* apCell[] size */ | ||
48753 | lhpage *pMaster; /* Master page in case we are dealing with a slave page */ | ||
48754 | lhpage *pSlave; /* List of slave pages */ | ||
48755 | lhpage *pNextSlave; /* Next slave page on the list */ | ||
48756 | sxi32 iSlave; /* Total number of slave pages */ | ||
48757 | sxu16 nFree; /* Amount of free space available in the page */ | ||
48758 | }; | ||
48759 | /* | ||
48760 | * A Bucket map record which is used to map logical bucket number to real | ||
48761 | * bucket number is represented by an instance of the following structure. | ||
48762 | */ | ||
48763 | typedef struct lhash_bmap_rec lhash_bmap_rec; | ||
48764 | struct lhash_bmap_rec | ||
48765 | { | ||
48766 | pgno iLogic; /* Logical bucket number */ | ||
48767 | pgno iReal; /* Real bucket number */ | ||
48768 | lhash_bmap_rec *pNext,*pPrev; /* Link to other bucket map */ | ||
48769 | lhash_bmap_rec *pNextCol,*pPrevCol; /* Collision links */ | ||
48770 | }; | ||
48771 | typedef struct lhash_bmap_page lhash_bmap_page; | ||
48772 | struct lhash_bmap_page | ||
48773 | { | ||
48774 | pgno iNum; /* Page number where this entry is stored */ | ||
48775 | sxu16 iPtr; /* Offset to start reading/writing from */ | ||
48776 | sxu32 nRec; /* Total number of records in this page */ | ||
48777 | pgno iNext; /* Next map page */ | ||
48778 | }; | ||
48779 | /* | ||
48780 | * An in memory linear hash implemenation is represented by in an isntance | ||
48781 | * of the following structure. | ||
48782 | */ | ||
48783 | struct lhash_kv_engine | ||
48784 | { | ||
48785 | const unqlite_kv_io *pIo; /* IO methods: Must be first */ | ||
48786 | /* Private fields */ | ||
48787 | SyMemBackend sAllocator; /* Private memory backend */ | ||
48788 | ProcHash xHash; /* Default hash function */ | ||
48789 | ProcCmp xCmp; /* Default comparison function */ | ||
48790 | unqlite_page *pHeader; /* Page one to identify a valid implementation */ | ||
48791 | lhash_bmap_rec **apMap; /* Buckets map records */ | ||
48792 | sxu32 nBuckRec; /* Total number of bucket map records */ | ||
48793 | sxu32 nBuckSize; /* apMap[] size */ | ||
48794 | lhash_bmap_rec *pList; /* List of bucket map records */ | ||
48795 | lhash_bmap_rec *pFirst; /* First record*/ | ||
48796 | lhash_bmap_page sPageMap; /* Primary bucket map */ | ||
48797 | int iPageSize; /* Page size */ | ||
48798 | pgno nFreeList; /* List of free pages */ | ||
48799 | pgno split_bucket; /* Current split bucket: MUST BE A POWER OF TWO */ | ||
48800 | pgno max_split_bucket; /* Maximum split bucket: MUST BE A POWER OF TWO */ | ||
48801 | pgno nmax_split_nucket; /* Next maximum split bucket (1 << nMsb): In-memory only */ | ||
48802 | sxu32 nMagic; /* Magic number to identify a valid linear hash disk database */ | ||
48803 | }; | ||
48804 | /* | ||
48805 | * Given a logical bucket number, return the record associated with it. | ||
48806 | */ | ||
48807 | static lhash_bmap_rec * lhMapFindBucket(lhash_kv_engine *pEngine,pgno iLogic) | ||
48808 | { | ||
48809 | lhash_bmap_rec *pRec; | ||
48810 | if( pEngine->nBuckRec < 1 ){ | ||
48811 | /* Don't bother */ | ||
48812 | return 0; | ||
48813 | } | ||
48814 | pRec = pEngine->apMap[iLogic & (pEngine->nBuckSize - 1)]; | ||
48815 | for(;;){ | ||
48816 | if( pRec == 0 ){ | ||
48817 | break; | ||
48818 | } | ||
48819 | if( pRec->iLogic == iLogic ){ | ||
48820 | return pRec; | ||
48821 | } | ||
48822 | /* Point to the next entry */ | ||
48823 | pRec = pRec->pNextCol; | ||
48824 | } | ||
48825 | /* No such record */ | ||
48826 | return 0; | ||
48827 | } | ||
48828 | /* | ||
48829 | * Install a new bucket map record. | ||
48830 | */ | ||
48831 | static int lhMapInstallBucket(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal) | ||
48832 | { | ||
48833 | lhash_bmap_rec *pRec; | ||
48834 | sxu32 iBucket; | ||
48835 | /* Allocate a new instance */ | ||
48836 | pRec = (lhash_bmap_rec *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhash_bmap_rec)); | ||
48837 | if( pRec == 0 ){ | ||
48838 | return UNQLITE_NOMEM; | ||
48839 | } | ||
48840 | /* Zero the structure */ | ||
48841 | SyZero(pRec,sizeof(lhash_bmap_rec)); | ||
48842 | /* Fill in the structure */ | ||
48843 | pRec->iLogic = iLogic; | ||
48844 | pRec->iReal = iReal; | ||
48845 | iBucket = iLogic & (pEngine->nBuckSize - 1); | ||
48846 | pRec->pNextCol = pEngine->apMap[iBucket]; | ||
48847 | if( pEngine->apMap[iBucket] ){ | ||
48848 | pEngine->apMap[iBucket]->pPrevCol = pRec; | ||
48849 | } | ||
48850 | pEngine->apMap[iBucket] = pRec; | ||
48851 | /* Link */ | ||
48852 | if( pEngine->pFirst == 0 ){ | ||
48853 | pEngine->pFirst = pEngine->pList = pRec; | ||
48854 | }else{ | ||
48855 | MACRO_LD_PUSH(pEngine->pList,pRec); | ||
48856 | } | ||
48857 | pEngine->nBuckRec++; | ||
48858 | if( (pEngine->nBuckRec >= pEngine->nBuckSize * 3) && pEngine->nBuckRec < 100000 ){ | ||
48859 | /* Allocate a new larger table */ | ||
48860 | sxu32 nNewSize = pEngine->nBuckSize << 1; | ||
48861 | lhash_bmap_rec *pEntry; | ||
48862 | lhash_bmap_rec **apNew; | ||
48863 | sxu32 n; | ||
48864 | |||
48865 | apNew = (lhash_bmap_rec **)SyMemBackendAlloc(&pEngine->sAllocator, nNewSize * sizeof(lhash_bmap_rec *)); | ||
48866 | if( apNew ){ | ||
48867 | /* Zero the new table */ | ||
48868 | SyZero((void *)apNew, nNewSize * sizeof(lhash_bmap_rec *)); | ||
48869 | /* Rehash all entries */ | ||
48870 | n = 0; | ||
48871 | pEntry = pEngine->pList; | ||
48872 | for(;;){ | ||
48873 | /* Loop one */ | ||
48874 | if( n >= pEngine->nBuckRec ){ | ||
48875 | break; | ||
48876 | } | ||
48877 | pEntry->pNextCol = pEntry->pPrevCol = 0; | ||
48878 | /* Install in the new bucket */ | ||
48879 | iBucket = pEntry->iLogic & (nNewSize - 1); | ||
48880 | pEntry->pNextCol = apNew[iBucket]; | ||
48881 | if( apNew[iBucket] ){ | ||
48882 | apNew[iBucket]->pPrevCol = pEntry; | ||
48883 | } | ||
48884 | apNew[iBucket] = pEntry; | ||
48885 | /* Point to the next entry */ | ||
48886 | pEntry = pEntry->pNext; | ||
48887 | n++; | ||
48888 | } | ||
48889 | /* Release the old table and reflect the change */ | ||
48890 | SyMemBackendFree(&pEngine->sAllocator,(void *)pEngine->apMap); | ||
48891 | pEngine->apMap = apNew; | ||
48892 | pEngine->nBuckSize = nNewSize; | ||
48893 | } | ||
48894 | } | ||
48895 | return UNQLITE_OK; | ||
48896 | } | ||
48897 | /* | ||
48898 | * Process a raw bucket map record. | ||
48899 | */ | ||
48900 | static int lhMapLoadPage(lhash_kv_engine *pEngine,lhash_bmap_page *pMap,const unsigned char *zRaw) | ||
48901 | { | ||
48902 | const unsigned char *zEnd = &zRaw[pEngine->iPageSize]; | ||
48903 | const unsigned char *zPtr = zRaw; | ||
48904 | pgno iLogic,iReal; | ||
48905 | sxu32 n; | ||
48906 | int rc; | ||
48907 | if( pMap->iPtr == 0 ){ | ||
48908 | /* Read the map header */ | ||
48909 | SyBigEndianUnpack64(zRaw,&pMap->iNext); | ||
48910 | zRaw += 8; | ||
48911 | SyBigEndianUnpack32(zRaw,&pMap->nRec); | ||
48912 | zRaw += 4; | ||
48913 | }else{ | ||
48914 | /* Mostly page one of the database */ | ||
48915 | zRaw += pMap->iPtr; | ||
48916 | } | ||
48917 | /* Start processing */ | ||
48918 | for( n = 0; n < pMap->nRec ; ++n ){ | ||
48919 | if( zRaw >= zEnd ){ | ||
48920 | break; | ||
48921 | } | ||
48922 | /* Extract the logical and real bucket number */ | ||
48923 | SyBigEndianUnpack64(zRaw,&iLogic); | ||
48924 | zRaw += 8; | ||
48925 | SyBigEndianUnpack64(zRaw,&iReal); | ||
48926 | zRaw += 8; | ||
48927 | /* Install the record in the map */ | ||
48928 | rc = lhMapInstallBucket(pEngine,iLogic,iReal); | ||
48929 | if( rc != UNQLITE_OK ){ | ||
48930 | return rc; | ||
48931 | } | ||
48932 | } | ||
48933 | pMap->iPtr = (sxu16)(zRaw-zPtr); | ||
48934 | /* All done */ | ||
48935 | return UNQLITE_OK; | ||
48936 | } | ||
48937 | /* | ||
48938 | * Allocate a new cell instance. | ||
48939 | */ | ||
48940 | static lhcell * lhNewCell(lhash_kv_engine *pEngine,lhpage *pPage) | ||
48941 | { | ||
48942 | lhcell *pCell; | ||
48943 | pCell = (lhcell *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhcell)); | ||
48944 | if( pCell == 0 ){ | ||
48945 | return 0; | ||
48946 | } | ||
48947 | /* Zero the structure */ | ||
48948 | SyZero(pCell,sizeof(lhcell)); | ||
48949 | /* Fill in the structure */ | ||
48950 | SyBlobInit(&pCell->sKey,&pEngine->sAllocator); | ||
48951 | pCell->pPage = pPage; | ||
48952 | return pCell; | ||
48953 | } | ||
48954 | /* | ||
48955 | * Discard a cell from the page table. | ||
48956 | */ | ||
48957 | static void lhCellDiscard(lhcell *pCell) | ||
48958 | { | ||
48959 | lhpage *pPage = pCell->pPage->pMaster; | ||
48960 | |||
48961 | if( pCell->pPrevCol ){ | ||
48962 | pCell->pPrevCol->pNextCol = pCell->pNextCol; | ||
48963 | }else{ | ||
48964 | pPage->apCell[pCell->nHash & (pPage->nCellSize - 1)] = pCell->pNextCol; | ||
48965 | } | ||
48966 | if( pCell->pNextCol ){ | ||
48967 | pCell->pNextCol->pPrevCol = pCell->pPrevCol; | ||
48968 | } | ||
48969 | MACRO_LD_REMOVE(pPage->pList,pCell); | ||
48970 | if( pCell == pPage->pFirst ){ | ||
48971 | pPage->pFirst = pCell->pPrev; | ||
48972 | } | ||
48973 | pPage->nCell--; | ||
48974 | /* Release the cell */ | ||
48975 | SyBlobRelease(&pCell->sKey); | ||
48976 | SyMemBackendPoolFree(&pPage->pHash->sAllocator,pCell); | ||
48977 | } | ||
48978 | /* | ||
48979 | * Install a cell in the page table. | ||
48980 | */ | ||
48981 | static int lhInstallCell(lhcell *pCell) | ||
48982 | { | ||
48983 | lhpage *pPage = pCell->pPage->pMaster; | ||
48984 | sxu32 iBucket; | ||
48985 | if( pPage->nCell < 1 ){ | ||
48986 | sxu32 nTableSize = 32; /* Must be a power of two */ | ||
48987 | lhcell **apTable; | ||
48988 | /* Allocate a new cell table */ | ||
48989 | apTable = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nTableSize * sizeof(lhcell *)); | ||
48990 | if( apTable == 0 ){ | ||
48991 | return UNQLITE_NOMEM; | ||
48992 | } | ||
48993 | /* Zero the new table */ | ||
48994 | SyZero((void *)apTable, nTableSize * sizeof(lhcell *)); | ||
48995 | /* Install it */ | ||
48996 | pPage->apCell = apTable; | ||
48997 | pPage->nCellSize = nTableSize; | ||
48998 | } | ||
48999 | iBucket = pCell->nHash & (pPage->nCellSize - 1); | ||
49000 | pCell->pNextCol = pPage->apCell[iBucket]; | ||
49001 | if( pPage->apCell[iBucket] ){ | ||
49002 | pPage->apCell[iBucket]->pPrevCol = pCell; | ||
49003 | } | ||
49004 | pPage->apCell[iBucket] = pCell; | ||
49005 | if( pPage->pFirst == 0 ){ | ||
49006 | pPage->pFirst = pPage->pList = pCell; | ||
49007 | }else{ | ||
49008 | MACRO_LD_PUSH(pPage->pList,pCell); | ||
49009 | } | ||
49010 | pPage->nCell++; | ||
49011 | if( (pPage->nCell >= pPage->nCellSize * 3) && pPage->nCell < 100000 ){ | ||
49012 | /* Allocate a new larger table */ | ||
49013 | sxu32 nNewSize = pPage->nCellSize << 1; | ||
49014 | lhcell *pEntry; | ||
49015 | lhcell **apNew; | ||
49016 | sxu32 n; | ||
49017 | |||
49018 | apNew = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nNewSize * sizeof(lhcell *)); | ||
49019 | if( apNew ){ | ||
49020 | /* Zero the new table */ | ||
49021 | SyZero((void *)apNew, nNewSize * sizeof(lhcell *)); | ||
49022 | /* Rehash all entries */ | ||
49023 | n = 0; | ||
49024 | pEntry = pPage->pList; | ||
49025 | for(;;){ | ||
49026 | /* Loop one */ | ||
49027 | if( n >= pPage->nCell ){ | ||
49028 | break; | ||
49029 | } | ||
49030 | pEntry->pNextCol = pEntry->pPrevCol = 0; | ||
49031 | /* Install in the new bucket */ | ||
49032 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
49033 | pEntry->pNextCol = apNew[iBucket]; | ||
49034 | if( apNew[iBucket] ){ | ||
49035 | apNew[iBucket]->pPrevCol = pEntry; | ||
49036 | } | ||
49037 | apNew[iBucket] = pEntry; | ||
49038 | /* Point to the next entry */ | ||
49039 | pEntry = pEntry->pNext; | ||
49040 | n++; | ||
49041 | } | ||
49042 | /* Release the old table and reflect the change */ | ||
49043 | SyMemBackendFree(&pPage->pHash->sAllocator,(void *)pPage->apCell); | ||
49044 | pPage->apCell = apNew; | ||
49045 | pPage->nCellSize = nNewSize; | ||
49046 | } | ||
49047 | } | ||
49048 | return UNQLITE_OK; | ||
49049 | } | ||
49050 | /* | ||
49051 | * Private data of lhKeyCmp(). | ||
49052 | */ | ||
49053 | struct lhash_key_cmp | ||
49054 | { | ||
49055 | const char *zIn; /* Start of the stream */ | ||
49056 | const char *zEnd; /* End of the stream */ | ||
49057 | ProcCmp xCmp; /* Comparison function */ | ||
49058 | }; | ||
49059 | /* | ||
49060 | * Comparsion callback for large key > 256 KB | ||
49061 | */ | ||
49062 | static int lhKeyCmp(const void *pData,sxu32 nLen,void *pUserData) | ||
49063 | { | ||
49064 | struct lhash_key_cmp *pCmp = (struct lhash_key_cmp *)pUserData; | ||
49065 | int rc; | ||
49066 | if( pCmp->zIn >= pCmp->zEnd ){ | ||
49067 | if( nLen > 0 ){ | ||
49068 | return UNQLITE_ABORT; | ||
49069 | } | ||
49070 | return UNQLITE_OK; | ||
49071 | } | ||
49072 | /* Perform the comparison */ | ||
49073 | rc = pCmp->xCmp((const void *)pCmp->zIn,pData,nLen); | ||
49074 | if( rc != 0 ){ | ||
49075 | /* Abort comparison */ | ||
49076 | return UNQLITE_ABORT; | ||
49077 | } | ||
49078 | /* Advance the cursor */ | ||
49079 | pCmp->zIn += nLen; | ||
49080 | return UNQLITE_OK; | ||
49081 | } | ||
49082 | /* Forward declaration */ | ||
49083 | static int lhConsumeCellkey(lhcell *pCell,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData,int offt_only); | ||
49084 | /* | ||
49085 | * given a key, return the cell associated with it on success. NULL otherwise. | ||
49086 | */ | ||
49087 | static lhcell * lhFindCell( | ||
49088 | lhpage *pPage, /* Target page */ | ||
49089 | const void *pKey, /* Lookup key */ | ||
49090 | sxu32 nByte, /* Key length */ | ||
49091 | sxu32 nHash /* Hash of the key */ | ||
49092 | ) | ||
49093 | { | ||
49094 | lhcell *pEntry; | ||
49095 | if( pPage->nCell < 1 ){ | ||
49096 | /* Don't bother hashing */ | ||
49097 | return 0; | ||
49098 | } | ||
49099 | /* Point to the corresponding bucket */ | ||
49100 | pEntry = pPage->apCell[nHash & (pPage->nCellSize - 1)]; | ||
49101 | for(;;){ | ||
49102 | if( pEntry == 0 ){ | ||
49103 | break; | ||
49104 | } | ||
49105 | if( pEntry->nHash == nHash && pEntry->nKey == nByte ){ | ||
49106 | if( SyBlobLength(&pEntry->sKey) < 1 ){ | ||
49107 | /* Large key (> 256 KB) are not kept in-memory */ | ||
49108 | struct lhash_key_cmp sCmp; | ||
49109 | int rc; | ||
49110 | /* Fill-in the structure */ | ||
49111 | sCmp.zIn = (const char *)pKey; | ||
49112 | sCmp.zEnd = &sCmp.zIn[nByte]; | ||
49113 | sCmp.xCmp = pPage->pHash->xCmp; | ||
49114 | /* Fetch the key from disk and perform the comparison */ | ||
49115 | rc = lhConsumeCellkey(pEntry,lhKeyCmp,&sCmp,0); | ||
49116 | if( rc == UNQLITE_OK ){ | ||
49117 | /* Cell found */ | ||
49118 | return pEntry; | ||
49119 | } | ||
49120 | }else if ( pPage->pHash->xCmp(pKey,SyBlobData(&pEntry->sKey),nByte) == 0 ){ | ||
49121 | /* Cell found */ | ||
49122 | return pEntry; | ||
49123 | } | ||
49124 | } | ||
49125 | /* Point to the next entry */ | ||
49126 | pEntry = pEntry->pNextCol; | ||
49127 | } | ||
49128 | /* No such entry */ | ||
49129 | return 0; | ||
49130 | } | ||
49131 | /* | ||
49132 | * Parse a raw cell fetched from disk. | ||
49133 | */ | ||
49134 | static int lhParseOneCell(lhpage *pPage,const unsigned char *zRaw,const unsigned char *zEnd,lhcell **ppOut) | ||
49135 | { | ||
49136 | sxu16 iNext,iOfft; | ||
49137 | sxu32 iHash,nKey; | ||
49138 | lhcell *pCell; | ||
49139 | sxu64 nData; | ||
49140 | int rc; | ||
49141 | /* Offset this cell is stored */ | ||
49142 | iOfft = (sxu16)(zRaw - (const unsigned char *)pPage->pRaw->zData); | ||
49143 | /* 4 byte hash number */ | ||
49144 | SyBigEndianUnpack32(zRaw,&iHash); | ||
49145 | zRaw += 4; | ||
49146 | /* 4 byte key length */ | ||
49147 | SyBigEndianUnpack32(zRaw,&nKey); | ||
49148 | zRaw += 4; | ||
49149 | /* 8 byte data length */ | ||
49150 | SyBigEndianUnpack64(zRaw,&nData); | ||
49151 | zRaw += 8; | ||
49152 | /* 2 byte offset of the next cell */ | ||
49153 | SyBigEndianUnpack16(zRaw,&iNext); | ||
49154 | /* Perform a sanity check */ | ||
49155 | if( iNext > 0 && &pPage->pRaw->zData[iNext] >= zEnd ){ | ||
49156 | return UNQLITE_CORRUPT; | ||
49157 | } | ||
49158 | zRaw += 2; | ||
49159 | pCell = lhNewCell(pPage->pHash,pPage); | ||
49160 | if( pCell == 0 ){ | ||
49161 | return UNQLITE_NOMEM; | ||
49162 | } | ||
49163 | /* Fill in the structure */ | ||
49164 | pCell->iNext = iNext; | ||
49165 | pCell->nKey = nKey; | ||
49166 | pCell->nData = nData; | ||
49167 | pCell->nHash = iHash; | ||
49168 | /* Overflow page if any */ | ||
49169 | SyBigEndianUnpack64(zRaw,&pCell->iOvfl); | ||
49170 | zRaw += 8; | ||
49171 | /* Cell offset */ | ||
49172 | pCell->iStart = iOfft; | ||
49173 | /* Consume the key */ | ||
49174 | rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,pCell->nKey > 262144 /* 256 KB */? 1 : 0); | ||
49175 | if( rc != UNQLITE_OK ){ | ||
49176 | /* TICKET: 14-32-chm@symisc.net: Key too large for memory */ | ||
49177 | SyBlobRelease(&pCell->sKey); | ||
49178 | } | ||
49179 | /* Finally install the cell */ | ||
49180 | rc = lhInstallCell(pCell); | ||
49181 | if( rc != UNQLITE_OK ){ | ||
49182 | return rc; | ||
49183 | } | ||
49184 | if( ppOut ){ | ||
49185 | *ppOut = pCell; | ||
49186 | } | ||
49187 | return UNQLITE_OK; | ||
49188 | } | ||
49189 | /* | ||
49190 | * Compute the total number of free space on a given page. | ||
49191 | */ | ||
49192 | static int lhPageFreeSpace(lhpage *pPage) | ||
49193 | { | ||
49194 | const unsigned char *zEnd,*zRaw = pPage->pRaw->zData; | ||
49195 | lhphdr *pHdr = &pPage->sHdr; | ||
49196 | sxu16 iNext,iAmount; | ||
49197 | sxu16 nFree = 0; | ||
49198 | if( pHdr->iFree < 1 ){ | ||
49199 | /* Don't bother processing, the page is full */ | ||
49200 | pPage->nFree = 0; | ||
49201 | return UNQLITE_OK; | ||
49202 | } | ||
49203 | /* Point to first free block */ | ||
49204 | zEnd = &zRaw[pPage->pHash->iPageSize]; | ||
49205 | zRaw += pHdr->iFree; | ||
49206 | for(;;){ | ||
49207 | /* Offset of the next free block */ | ||
49208 | SyBigEndianUnpack16(zRaw,&iNext); | ||
49209 | zRaw += 2; | ||
49210 | /* Available space on this block */ | ||
49211 | SyBigEndianUnpack16(zRaw,&iAmount); | ||
49212 | nFree += iAmount; | ||
49213 | if( iNext < 1 ){ | ||
49214 | /* No more free blocks */ | ||
49215 | break; | ||
49216 | } | ||
49217 | /* Point to the next free block*/ | ||
49218 | zRaw = &pPage->pRaw->zData[iNext]; | ||
49219 | if( zRaw >= zEnd ){ | ||
49220 | /* Corrupt page */ | ||
49221 | return UNQLITE_CORRUPT; | ||
49222 | } | ||
49223 | } | ||
49224 | /* Save the amount of free space */ | ||
49225 | pPage->nFree = nFree; | ||
49226 | return UNQLITE_OK; | ||
49227 | } | ||
49228 | /* | ||
49229 | * Given a primary page, load all its cell. | ||
49230 | */ | ||
49231 | static int lhLoadCells(lhpage *pPage) | ||
49232 | { | ||
49233 | const unsigned char *zEnd,*zRaw = pPage->pRaw->zData; | ||
49234 | lhphdr *pHdr = &pPage->sHdr; | ||
49235 | lhcell *pCell = 0; /* cc warning */ | ||
49236 | int rc; | ||
49237 | /* Calculate the amount of free space available first */ | ||
49238 | rc = lhPageFreeSpace(pPage); | ||
49239 | if( rc != UNQLITE_OK ){ | ||
49240 | return rc; | ||
49241 | } | ||
49242 | if( pHdr->iOfft < 1 ){ | ||
49243 | /* Don't bother processing, the page is empty */ | ||
49244 | return UNQLITE_OK; | ||
49245 | } | ||
49246 | /* Point to first cell */ | ||
49247 | zRaw += pHdr->iOfft; | ||
49248 | zEnd = &zRaw[pPage->pHash->iPageSize]; | ||
49249 | for(;;){ | ||
49250 | /* Parse a single cell */ | ||
49251 | rc = lhParseOneCell(pPage,zRaw,zEnd,&pCell); | ||
49252 | if( rc != UNQLITE_OK ){ | ||
49253 | return rc; | ||
49254 | } | ||
49255 | if( pCell->iNext < 1 ){ | ||
49256 | /* No more cells */ | ||
49257 | break; | ||
49258 | } | ||
49259 | /* Point to the next cell */ | ||
49260 | zRaw = &pPage->pRaw->zData[pCell->iNext]; | ||
49261 | if( zRaw >= zEnd ){ | ||
49262 | /* Corrupt page */ | ||
49263 | return UNQLITE_CORRUPT; | ||
49264 | } | ||
49265 | } | ||
49266 | /* All done */ | ||
49267 | return UNQLITE_OK; | ||
49268 | } | ||
49269 | /* | ||
49270 | * Given a page, parse its raw headers. | ||
49271 | */ | ||
49272 | static int lhParsePageHeader(lhpage *pPage) | ||
49273 | { | ||
49274 | const unsigned char *zRaw = pPage->pRaw->zData; | ||
49275 | lhphdr *pHdr = &pPage->sHdr; | ||
49276 | /* Offset of the first cell */ | ||
49277 | SyBigEndianUnpack16(zRaw,&pHdr->iOfft); | ||
49278 | zRaw += 2; | ||
49279 | /* Offset of the first free block */ | ||
49280 | SyBigEndianUnpack16(zRaw,&pHdr->iFree); | ||
49281 | zRaw += 2; | ||
49282 | /* Slave page number */ | ||
49283 | SyBigEndianUnpack64(zRaw,&pHdr->iSlave); | ||
49284 | /* All done */ | ||
49285 | return UNQLITE_OK; | ||
49286 | } | ||
49287 | /* | ||
49288 | * Allocate a new page instance. | ||
49289 | */ | ||
49290 | static lhpage * lhNewPage( | ||
49291 | lhash_kv_engine *pEngine, /* KV store which own this instance */ | ||
49292 | unqlite_page *pRaw, /* Raw page contents */ | ||
49293 | lhpage *pMaster /* Master page in case we are dealing with a slave page */ | ||
49294 | ) | ||
49295 | { | ||
49296 | lhpage *pPage; | ||
49297 | /* Allocate a new instance */ | ||
49298 | pPage = (lhpage *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhpage)); | ||
49299 | if( pPage == 0 ){ | ||
49300 | return 0; | ||
49301 | } | ||
49302 | /* Zero the structure */ | ||
49303 | SyZero(pPage,sizeof(lhpage)); | ||
49304 | /* Fill-in the structure */ | ||
49305 | pPage->pHash = pEngine; | ||
49306 | pPage->pRaw = pRaw; | ||
49307 | pPage->pMaster = pMaster ? pMaster /* Slave page */ : pPage /* Master page */ ; | ||
49308 | if( pPage->pMaster != pPage ){ | ||
49309 | /* Slave page, attach it to its master */ | ||
49310 | pPage->pNextSlave = pMaster->pSlave; | ||
49311 | pMaster->pSlave = pPage; | ||
49312 | pMaster->iSlave++; | ||
49313 | } | ||
49314 | /* Save this instance for future fast lookup */ | ||
49315 | pRaw->pUserData = pPage; | ||
49316 | /* All done */ | ||
49317 | return pPage; | ||
49318 | } | ||
49319 | /* | ||
49320 | * Load a primary and its associated slave pages from disk. | ||
49321 | */ | ||
49322 | static int lhLoadPage(lhash_kv_engine *pEngine,pgno pnum,lhpage *pMaster,lhpage **ppOut,int iNest) | ||
49323 | { | ||
49324 | unqlite_page *pRaw; | ||
49325 | lhpage *pPage = 0; /* cc warning */ | ||
49326 | int rc; | ||
49327 | /* Aquire the page from the pager first */ | ||
49328 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pnum,&pRaw); | ||
49329 | if( rc != UNQLITE_OK ){ | ||
49330 | return rc; | ||
49331 | } | ||
49332 | if( pRaw->pUserData ){ | ||
49333 | /* The page is already parsed and loaded in memory. Point to it */ | ||
49334 | pPage = (lhpage *)pRaw->pUserData; | ||
49335 | }else{ | ||
49336 | /* Allocate a new page */ | ||
49337 | pPage = lhNewPage(pEngine,pRaw,pMaster); | ||
49338 | if( pPage == 0 ){ | ||
49339 | return UNQLITE_NOMEM; | ||
49340 | } | ||
49341 | /* Process the page */ | ||
49342 | rc = lhParsePageHeader(pPage); | ||
49343 | if( rc == UNQLITE_OK ){ | ||
49344 | /* Load cells */ | ||
49345 | rc = lhLoadCells(pPage); | ||
49346 | } | ||
49347 | if( rc != UNQLITE_OK ){ | ||
49348 | pEngine->pIo->xPageUnref(pPage->pRaw); /* pPage will be released inside this call */ | ||
49349 | return rc; | ||
49350 | } | ||
49351 | if( pPage->sHdr.iSlave > 0 && iNest < 128 ){ | ||
49352 | if( pMaster == 0 ){ | ||
49353 | pMaster = pPage; | ||
49354 | } | ||
49355 | /* Slave page. Not a fatal error if something goes wrong here */ | ||
49356 | lhLoadPage(pEngine,pPage->sHdr.iSlave,pMaster,0,iNest++); | ||
49357 | } | ||
49358 | } | ||
49359 | if( ppOut ){ | ||
49360 | *ppOut = pPage; | ||
49361 | } | ||
49362 | return UNQLITE_OK; | ||
49363 | } | ||
49364 | /* | ||
49365 | * Given a cell, Consume its key by invoking the given callback for each extracted chunk. | ||
49366 | */ | ||
49367 | static int lhConsumeCellkey( | ||
49368 | lhcell *pCell, /* Target cell */ | ||
49369 | int (*xConsumer)(const void *,unsigned int,void *), /* Consumer callback */ | ||
49370 | void *pUserData, /* Last argument to xConsumer() */ | ||
49371 | int offt_only | ||
49372 | ) | ||
49373 | { | ||
49374 | lhpage *pPage = pCell->pPage; | ||
49375 | const unsigned char *zRaw = pPage->pRaw->zData; | ||
49376 | const unsigned char *zPayload; | ||
49377 | int rc; | ||
49378 | /* Point to the payload area */ | ||
49379 | zPayload = &zRaw[pCell->iStart]; | ||
49380 | if( pCell->iOvfl == 0 ){ | ||
49381 | /* Best scenario, consume the key directly without any overflow page */ | ||
49382 | zPayload += L_HASH_CELL_SZ; | ||
49383 | rc = xConsumer((const void *)zPayload,pCell->nKey,pUserData); | ||
49384 | if( rc != UNQLITE_OK ){ | ||
49385 | rc = UNQLITE_ABORT; | ||
49386 | } | ||
49387 | }else{ | ||
49388 | lhash_kv_engine *pEngine = pPage->pHash; | ||
49389 | sxu32 nByte,nData = pCell->nKey; | ||
49390 | unqlite_page *pOvfl; | ||
49391 | int data_offset = 0; | ||
49392 | pgno iOvfl; | ||
49393 | /* Overflow page */ | ||
49394 | iOvfl = pCell->iOvfl; | ||
49395 | /* Total usable bytes in an overflow page */ | ||
49396 | nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize); | ||
49397 | for(;;){ | ||
49398 | if( iOvfl == 0 || nData < 1 ){ | ||
49399 | /* no more overflow page */ | ||
49400 | break; | ||
49401 | } | ||
49402 | /* Point to the overflow page */ | ||
49403 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl); | ||
49404 | if( rc != UNQLITE_OK ){ | ||
49405 | return rc; | ||
49406 | } | ||
49407 | zPayload = &pOvfl->zData[8]; | ||
49408 | /* Point to the raw content */ | ||
49409 | if( !data_offset ){ | ||
49410 | /* Get the data page and offset */ | ||
49411 | SyBigEndianUnpack64(zPayload,&pCell->iDataPage); | ||
49412 | zPayload += 8; | ||
49413 | SyBigEndianUnpack16(zPayload,&pCell->iDataOfft); | ||
49414 | zPayload += 2; | ||
49415 | if( offt_only ){ | ||
49416 | /* Key too large, grab the data offset and return */ | ||
49417 | pEngine->pIo->xPageUnref(pOvfl); | ||
49418 | return UNQLITE_OK; | ||
49419 | } | ||
49420 | data_offset = 1; | ||
49421 | } | ||
49422 | /* Consume the key */ | ||
49423 | if( nData <= nByte ){ | ||
49424 | rc = xConsumer((const void *)zPayload,nData,pUserData); | ||
49425 | if( rc != UNQLITE_OK ){ | ||
49426 | pEngine->pIo->xPageUnref(pOvfl); | ||
49427 | return UNQLITE_ABORT; | ||
49428 | } | ||
49429 | nData = 0; | ||
49430 | }else{ | ||
49431 | rc = xConsumer((const void *)zPayload,nByte,pUserData); | ||
49432 | if( rc != UNQLITE_OK ){ | ||
49433 | pEngine->pIo->xPageUnref(pOvfl); | ||
49434 | return UNQLITE_ABORT; | ||
49435 | } | ||
49436 | nData -= nByte; | ||
49437 | } | ||
49438 | /* Next overflow page in the chain */ | ||
49439 | SyBigEndianUnpack64(pOvfl->zData,&iOvfl); | ||
49440 | /* Unref the page */ | ||
49441 | pEngine->pIo->xPageUnref(pOvfl); | ||
49442 | } | ||
49443 | rc = UNQLITE_OK; | ||
49444 | } | ||
49445 | return rc; | ||
49446 | } | ||
49447 | /* | ||
49448 | * Given a cell, Consume its data by invoking the given callback for each extracted chunk. | ||
49449 | */ | ||
49450 | static int lhConsumeCellData( | ||
49451 | lhcell *pCell, /* Target cell */ | ||
49452 | int (*xConsumer)(const void *,unsigned int,void *), /* Data consumer callback */ | ||
49453 | void *pUserData /* Last argument to xConsumer() */ | ||
49454 | ) | ||
49455 | { | ||
49456 | lhpage *pPage = pCell->pPage; | ||
49457 | const unsigned char *zRaw = pPage->pRaw->zData; | ||
49458 | const unsigned char *zPayload; | ||
49459 | int rc; | ||
49460 | /* Point to the payload area */ | ||
49461 | zPayload = &zRaw[pCell->iStart]; | ||
49462 | if( pCell->iOvfl == 0 ){ | ||
49463 | /* Best scenario, consume the data directly without any overflow page */ | ||
49464 | zPayload += L_HASH_CELL_SZ + pCell->nKey; | ||
49465 | rc = xConsumer((const void *)zPayload,(sxu32)pCell->nData,pUserData); | ||
49466 | if( rc != UNQLITE_OK ){ | ||
49467 | rc = UNQLITE_ABORT; | ||
49468 | } | ||
49469 | }else{ | ||
49470 | lhash_kv_engine *pEngine = pPage->pHash; | ||
49471 | sxu64 nData = pCell->nData; | ||
49472 | unqlite_page *pOvfl; | ||
49473 | int fix_offset = 0; | ||
49474 | sxu32 nByte; | ||
49475 | pgno iOvfl; | ||
49476 | /* Overflow page where data is stored */ | ||
49477 | iOvfl = pCell->iDataPage; | ||
49478 | for(;;){ | ||
49479 | if( iOvfl == 0 || nData < 1 ){ | ||
49480 | /* no more overflow page */ | ||
49481 | break; | ||
49482 | } | ||
49483 | /* Point to the overflow page */ | ||
49484 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl); | ||
49485 | if( rc != UNQLITE_OK ){ | ||
49486 | return rc; | ||
49487 | } | ||
49488 | /* Point to the raw content */ | ||
49489 | zPayload = pOvfl->zData; | ||
49490 | if( !fix_offset ){ | ||
49491 | /* Point to the data */ | ||
49492 | zPayload += pCell->iDataOfft; | ||
49493 | nByte = pEngine->iPageSize - pCell->iDataOfft; | ||
49494 | fix_offset = 1; | ||
49495 | }else{ | ||
49496 | zPayload += 8; | ||
49497 | /* Total usable bytes in an overflow page */ | ||
49498 | nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize); | ||
49499 | } | ||
49500 | /* Consume the data */ | ||
49501 | if( nData <= (sxu64)nByte ){ | ||
49502 | rc = xConsumer((const void *)zPayload,(unsigned int)nData,pUserData); | ||
49503 | if( rc != UNQLITE_OK ){ | ||
49504 | pEngine->pIo->xPageUnref(pOvfl); | ||
49505 | return UNQLITE_ABORT; | ||
49506 | } | ||
49507 | nData = 0; | ||
49508 | }else{ | ||
49509 | if( nByte > 0 ){ | ||
49510 | rc = xConsumer((const void *)zPayload,nByte,pUserData); | ||
49511 | if( rc != UNQLITE_OK ){ | ||
49512 | pEngine->pIo->xPageUnref(pOvfl); | ||
49513 | return UNQLITE_ABORT; | ||
49514 | } | ||
49515 | nData -= nByte; | ||
49516 | } | ||
49517 | } | ||
49518 | /* Next overflow page in the chain */ | ||
49519 | SyBigEndianUnpack64(pOvfl->zData,&iOvfl); | ||
49520 | /* Unref the page */ | ||
49521 | pEngine->pIo->xPageUnref(pOvfl); | ||
49522 | } | ||
49523 | rc = UNQLITE_OK; | ||
49524 | } | ||
49525 | return rc; | ||
49526 | } | ||
49527 | /* | ||
49528 | * Read the linear hash header (Page one of the database). | ||
49529 | */ | ||
49530 | static int lhash_read_header(lhash_kv_engine *pEngine,unqlite_page *pHeader) | ||
49531 | { | ||
49532 | const unsigned char *zRaw = pHeader->zData; | ||
49533 | lhash_bmap_page *pMap; | ||
49534 | sxu32 nHash; | ||
49535 | int rc; | ||
49536 | pEngine->pHeader = pHeader; | ||
49537 | /* 4 byte magic number */ | ||
49538 | SyBigEndianUnpack32(zRaw,&pEngine->nMagic); | ||
49539 | zRaw += 4; | ||
49540 | if( pEngine->nMagic != L_HASH_MAGIC ){ | ||
49541 | /* Corrupt implementation */ | ||
49542 | return UNQLITE_CORRUPT; | ||
49543 | } | ||
49544 | /* 4 byte hash value to identify a valid hash function */ | ||
49545 | SyBigEndianUnpack32(zRaw,&nHash); | ||
49546 | zRaw += 4; | ||
49547 | /* Sanity check */ | ||
49548 | if( pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1) != nHash ){ | ||
49549 | /* Different hash function */ | ||
49550 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Invalid hash function"); | ||
49551 | return UNQLITE_INVALID; | ||
49552 | } | ||
49553 | /* List of free pages */ | ||
49554 | SyBigEndianUnpack64(zRaw,&pEngine->nFreeList); | ||
49555 | zRaw += 8; | ||
49556 | /* Current split bucket */ | ||
49557 | SyBigEndianUnpack64(zRaw,&pEngine->split_bucket); | ||
49558 | zRaw += 8; | ||
49559 | /* Maximum split bucket */ | ||
49560 | SyBigEndianUnpack64(zRaw,&pEngine->max_split_bucket); | ||
49561 | zRaw += 8; | ||
49562 | /* Next generation */ | ||
49563 | pEngine->nmax_split_nucket = pEngine->max_split_bucket << 1; | ||
49564 | /* Initialiaze the bucket map */ | ||
49565 | pMap = &pEngine->sPageMap; | ||
49566 | /* Fill in the structure */ | ||
49567 | pMap->iNum = pHeader->pgno; | ||
49568 | /* Next page in the bucket map */ | ||
49569 | SyBigEndianUnpack64(zRaw,&pMap->iNext); | ||
49570 | zRaw += 8; | ||
49571 | /* Total number of records in the bucket map (This page only) */ | ||
49572 | SyBigEndianUnpack32(zRaw,&pMap->nRec); | ||
49573 | zRaw += 4; | ||
49574 | pMap->iPtr = (sxu16)(zRaw - pHeader->zData); | ||
49575 | /* Load the map in memory */ | ||
49576 | rc = lhMapLoadPage(pEngine,pMap,pHeader->zData); | ||
49577 | if( rc != UNQLITE_OK ){ | ||
49578 | return rc; | ||
49579 | } | ||
49580 | /* Load the bucket map chain if any */ | ||
49581 | for(;;){ | ||
49582 | pgno iNext = pMap->iNext; | ||
49583 | unqlite_page *pPage; | ||
49584 | if( iNext == 0 ){ | ||
49585 | /* No more map pages */ | ||
49586 | break; | ||
49587 | } | ||
49588 | /* Point to the target page */ | ||
49589 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pPage); | ||
49590 | if( rc != UNQLITE_OK ){ | ||
49591 | return rc; | ||
49592 | } | ||
49593 | /* Fill in the structure */ | ||
49594 | pMap->iNum = iNext; | ||
49595 | pMap->iPtr = 0; | ||
49596 | /* Load the map in memory */ | ||
49597 | rc = lhMapLoadPage(pEngine,pMap,pPage->zData); | ||
49598 | if( rc != UNQLITE_OK ){ | ||
49599 | return rc; | ||
49600 | } | ||
49601 | } | ||
49602 | /* All done */ | ||
49603 | return UNQLITE_OK; | ||
49604 | } | ||
49605 | /* | ||
49606 | * Perform a record lookup. | ||
49607 | */ | ||
49608 | static int lhRecordLookup( | ||
49609 | lhash_kv_engine *pEngine, /* KV storage engine */ | ||
49610 | const void *pKey, /* Lookup key */ | ||
49611 | sxu32 nByte, /* Key length */ | ||
49612 | lhcell **ppCell /* OUT: Target cell on success */ | ||
49613 | ) | ||
49614 | { | ||
49615 | lhash_bmap_rec *pRec; | ||
49616 | lhpage *pPage; | ||
49617 | lhcell *pCell; | ||
49618 | pgno iBucket; | ||
49619 | sxu32 nHash; | ||
49620 | int rc; | ||
49621 | /* Acquire the first page (hash Header) so that everything gets loaded autmatically */ | ||
49622 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); | ||
49623 | if( rc != UNQLITE_OK ){ | ||
49624 | return rc; | ||
49625 | } | ||
49626 | /* Compute the hash of the key first */ | ||
49627 | nHash = pEngine->xHash(pKey,nByte); | ||
49628 | /* Extract the logical (i.e. not real) page number */ | ||
49629 | iBucket = nHash & (pEngine->nmax_split_nucket - 1); | ||
49630 | if( iBucket >= (pEngine->split_bucket + pEngine->max_split_bucket) ){ | ||
49631 | /* Low mask */ | ||
49632 | iBucket = nHash & (pEngine->max_split_bucket - 1); | ||
49633 | } | ||
49634 | /* Map the logical bucket number to real page number */ | ||
49635 | pRec = lhMapFindBucket(pEngine,iBucket); | ||
49636 | if( pRec == 0 ){ | ||
49637 | /* No such entry */ | ||
49638 | return UNQLITE_NOTFOUND; | ||
49639 | } | ||
49640 | /* Load the master page and it's slave page in-memory */ | ||
49641 | rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0); | ||
49642 | if( rc != UNQLITE_OK ){ | ||
49643 | /* IO error, unlikely scenario */ | ||
49644 | return rc; | ||
49645 | } | ||
49646 | /* Lookup for the cell */ | ||
49647 | pCell = lhFindCell(pPage,pKey,nByte,nHash); | ||
49648 | if( pCell == 0 ){ | ||
49649 | /* No such entry */ | ||
49650 | return UNQLITE_NOTFOUND; | ||
49651 | } | ||
49652 | if( ppCell ){ | ||
49653 | *ppCell = pCell; | ||
49654 | } | ||
49655 | return UNQLITE_OK; | ||
49656 | } | ||
49657 | /* | ||
49658 | * Acquire a new page either from the free list or ask the pager | ||
49659 | * for a new one. | ||
49660 | */ | ||
49661 | static int lhAcquirePage(lhash_kv_engine *pEngine,unqlite_page **ppOut) | ||
49662 | { | ||
49663 | unqlite_page *pPage; | ||
49664 | int rc; | ||
49665 | if( pEngine->nFreeList != 0 ){ | ||
49666 | /* Acquire one from the free list */ | ||
49667 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pEngine->nFreeList,&pPage); | ||
49668 | if( rc == UNQLITE_OK ){ | ||
49669 | /* Point to the next free page */ | ||
49670 | SyBigEndianUnpack64(pPage->zData,&pEngine->nFreeList); | ||
49671 | /* Update the database header */ | ||
49672 | rc = pEngine->pIo->xWrite(pEngine->pHeader); | ||
49673 | if( rc != UNQLITE_OK ){ | ||
49674 | return rc; | ||
49675 | } | ||
49676 | SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList); | ||
49677 | /* Tell the pager do not journal this page */ | ||
49678 | pEngine->pIo->xDontJournal(pPage); | ||
49679 | /* Return to the caller */ | ||
49680 | *ppOut = pPage; | ||
49681 | /* All done */ | ||
49682 | return UNQLITE_OK; | ||
49683 | } | ||
49684 | } | ||
49685 | /* Acquire a new page */ | ||
49686 | rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pPage); | ||
49687 | if( rc != UNQLITE_OK ){ | ||
49688 | return rc; | ||
49689 | } | ||
49690 | /* Point to the target page */ | ||
49691 | *ppOut = pPage; | ||
49692 | return UNQLITE_OK; | ||
49693 | } | ||
49694 | /* | ||
49695 | * Write a bucket map record to disk. | ||
49696 | */ | ||
49697 | static int lhMapWriteRecord(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal) | ||
49698 | { | ||
49699 | lhash_bmap_page *pMap = &pEngine->sPageMap; | ||
49700 | unqlite_page *pPage = 0; | ||
49701 | int rc; | ||
49702 | if( pMap->iPtr > (pEngine->iPageSize - 16) /* 8 byte logical bucket number + 8 byte real bucket number */ ){ | ||
49703 | unqlite_page *pOld; | ||
49704 | /* Point to the old page */ | ||
49705 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pOld); | ||
49706 | if( rc != UNQLITE_OK ){ | ||
49707 | return rc; | ||
49708 | } | ||
49709 | /* Acquire a new page */ | ||
49710 | rc = lhAcquirePage(pEngine,&pPage); | ||
49711 | if( rc != UNQLITE_OK ){ | ||
49712 | return rc; | ||
49713 | } | ||
49714 | /* Reflect the change */ | ||
49715 | pMap->iNext = 0; | ||
49716 | pMap->iNum = pPage->pgno; | ||
49717 | pMap->nRec = 0; | ||
49718 | pMap->iPtr = 8/* Next page number */+4/* Total records in the map*/; | ||
49719 | /* Link this page */ | ||
49720 | rc = pEngine->pIo->xWrite(pOld); | ||
49721 | if( rc != UNQLITE_OK ){ | ||
49722 | return rc; | ||
49723 | } | ||
49724 | if( pOld->pgno == pEngine->pHeader->pgno ){ | ||
49725 | /* First page (Hash header) */ | ||
49726 | SyBigEndianPack64(&pOld->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/],pPage->pgno); | ||
49727 | }else{ | ||
49728 | /* Link the new page */ | ||
49729 | SyBigEndianPack64(pOld->zData,pPage->pgno); | ||
49730 | /* Unref */ | ||
49731 | pEngine->pIo->xPageUnref(pOld); | ||
49732 | } | ||
49733 | /* Assume the last bucket map page */ | ||
49734 | rc = pEngine->pIo->xWrite(pPage); | ||
49735 | if( rc != UNQLITE_OK ){ | ||
49736 | return rc; | ||
49737 | } | ||
49738 | SyBigEndianPack64(pPage->zData,0); /* Next bucket map page on the list */ | ||
49739 | } | ||
49740 | if( pPage == 0){ | ||
49741 | /* Point to the current map page */ | ||
49742 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pPage); | ||
49743 | if( rc != UNQLITE_OK ){ | ||
49744 | return rc; | ||
49745 | } | ||
49746 | } | ||
49747 | /* Make page writable */ | ||
49748 | rc = pEngine->pIo->xWrite(pPage); | ||
49749 | if( rc != UNQLITE_OK ){ | ||
49750 | return rc; | ||
49751 | } | ||
49752 | /* Write the data */ | ||
49753 | SyBigEndianPack64(&pPage->zData[pMap->iPtr],iLogic); | ||
49754 | pMap->iPtr += 8; | ||
49755 | SyBigEndianPack64(&pPage->zData[pMap->iPtr],iReal); | ||
49756 | pMap->iPtr += 8; | ||
49757 | /* Install the bucket map */ | ||
49758 | rc = lhMapInstallBucket(pEngine,iLogic,iReal); | ||
49759 | if( rc == UNQLITE_OK ){ | ||
49760 | /* Total number of records */ | ||
49761 | pMap->nRec++; | ||
49762 | if( pPage->pgno == pEngine->pHeader->pgno ){ | ||
49763 | /* Page one: Always writable */ | ||
49764 | SyBigEndianPack32( | ||
49765 | &pPage->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/+8/*Next map page*/], | ||
49766 | pMap->nRec); | ||
49767 | }else{ | ||
49768 | /* Make page writable */ | ||
49769 | rc = pEngine->pIo->xWrite(pPage); | ||
49770 | if( rc != UNQLITE_OK ){ | ||
49771 | return rc; | ||
49772 | } | ||
49773 | SyBigEndianPack32(&pPage->zData[8],pMap->nRec); | ||
49774 | } | ||
49775 | } | ||
49776 | return rc; | ||
49777 | } | ||
49778 | /* | ||
49779 | * Defragment a page. | ||
49780 | */ | ||
49781 | static int lhPageDefragment(lhpage *pPage) | ||
49782 | { | ||
49783 | lhash_kv_engine *pEngine = pPage->pHash; | ||
49784 | unsigned char *zTmp,*zPtr,*zEnd,*zPayload; | ||
49785 | lhcell *pCell; | ||
49786 | /* Get a temporary page from the pager. This opertaion never fail */ | ||
49787 | zTmp = pEngine->pIo->xTmpPage(pEngine->pIo->pHandle); | ||
49788 | /* Move the target cells to the begining */ | ||
49789 | pCell = pPage->pList; | ||
49790 | /* Write the slave page number */ | ||
49791 | SyBigEndianPack64(&zTmp[2/*Offset of the first cell */+2/*Offset of the first free block */],pPage->sHdr.iSlave); | ||
49792 | zPtr = &zTmp[L_HASH_PAGE_HDR_SZ]; /* Offset to start writing from */ | ||
49793 | zEnd = &zTmp[pEngine->iPageSize]; | ||
49794 | pPage->sHdr.iOfft = 0; /* Offset of the first cell */ | ||
49795 | for(;;){ | ||
49796 | if( pCell == 0 ){ | ||
49797 | /* No more cells */ | ||
49798 | break; | ||
49799 | } | ||
49800 | if( pCell->pPage->pRaw->pgno == pPage->pRaw->pgno ){ | ||
49801 | /* Cell payload if locally stored */ | ||
49802 | zPayload = 0; | ||
49803 | if( pCell->iOvfl == 0 ){ | ||
49804 | zPayload = &pCell->pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ]; | ||
49805 | } | ||
49806 | /* Move the cell */ | ||
49807 | pCell->iNext = pPage->sHdr.iOfft; | ||
49808 | pCell->iStart = (sxu16)(zPtr - zTmp); /* Offset where this cell start */ | ||
49809 | pPage->sHdr.iOfft = pCell->iStart; | ||
49810 | /* Write the cell header */ | ||
49811 | /* 4 byte hash number */ | ||
49812 | SyBigEndianPack32(zPtr,pCell->nHash); | ||
49813 | zPtr += 4; | ||
49814 | /* 4 byte ley length */ | ||
49815 | SyBigEndianPack32(zPtr,pCell->nKey); | ||
49816 | zPtr += 4; | ||
49817 | /* 8 byte data length */ | ||
49818 | SyBigEndianPack64(zPtr,pCell->nData); | ||
49819 | zPtr += 8; | ||
49820 | /* 2 byte offset of the next cell */ | ||
49821 | SyBigEndianPack16(zPtr,pCell->iNext); | ||
49822 | zPtr += 2; | ||
49823 | /* 8 byte overflow page number */ | ||
49824 | SyBigEndianPack64(zPtr,pCell->iOvfl); | ||
49825 | zPtr += 8; | ||
49826 | if( zPayload ){ | ||
49827 | /* Local payload */ | ||
49828 | SyMemcpy((const void *)zPayload,zPtr,(sxu32)(pCell->nKey + pCell->nData)); | ||
49829 | zPtr += pCell->nKey + pCell->nData; | ||
49830 | } | ||
49831 | if( zPtr >= zEnd ){ | ||
49832 | /* Can't happen */ | ||
49833 | break; | ||
49834 | } | ||
49835 | } | ||
49836 | /* Point to the next page */ | ||
49837 | pCell = pCell->pNext; | ||
49838 | } | ||
49839 | /* Mark the free block */ | ||
49840 | pPage->nFree = (sxu16)(zEnd - zPtr); /* Block length */ | ||
49841 | if( pPage->nFree > 3 ){ | ||
49842 | pPage->sHdr.iFree = (sxu16)(zPtr - zTmp); /* Offset of the free block */ | ||
49843 | /* Mark the block */ | ||
49844 | SyBigEndianPack16(zPtr,0); /* Offset of the next free block */ | ||
49845 | SyBigEndianPack16(&zPtr[2],pPage->nFree); /* Block length */ | ||
49846 | }else{ | ||
49847 | /* Block of length less than 4 bytes are simply discarded */ | ||
49848 | pPage->nFree = 0; | ||
49849 | pPage->sHdr.iFree = 0; | ||
49850 | } | ||
49851 | /* Reflect the change */ | ||
49852 | SyBigEndianPack16(zTmp,pPage->sHdr.iOfft); /* Offset of the first cell */ | ||
49853 | SyBigEndianPack16(&zTmp[2],pPage->sHdr.iFree); /* Offset of the first free block */ | ||
49854 | SyMemcpy((const void *)zTmp,pPage->pRaw->zData,pEngine->iPageSize); | ||
49855 | /* All done */ | ||
49856 | return UNQLITE_OK; | ||
49857 | } | ||
49858 | /* | ||
49859 | ** Allocate nByte bytes of space on a page. | ||
49860 | ** | ||
49861 | ** Return the index into pPage->pRaw->zData[] of the first byte of | ||
49862 | ** the new allocation. Or return 0 if there is not enough free | ||
49863 | ** space on the page to satisfy the allocation request. | ||
49864 | ** | ||
49865 | ** If the page contains nBytes of free space but does not contain | ||
49866 | ** nBytes of contiguous free space, then this routine automatically | ||
49867 | ** calls defragementPage() to consolidate all free space before | ||
49868 | ** allocating the new chunk. | ||
49869 | */ | ||
49870 | static int lhAllocateSpace(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft) | ||
49871 | { | ||
49872 | const unsigned char *zEnd,*zPtr; | ||
49873 | sxu16 iNext,iBlksz,nByte; | ||
49874 | unsigned char *zPrev; | ||
49875 | int rc; | ||
49876 | if( (sxu64)pPage->nFree < nAmount ){ | ||
49877 | /* Don't bother looking for a free chunk */ | ||
49878 | return UNQLITE_FULL; | ||
49879 | } | ||
49880 | if( pPage->nCell < 10 && ((int)nAmount >= (pPage->pHash->iPageSize / 2)) ){ | ||
49881 | /* Big chunk need an overflow page for its data */ | ||
49882 | return UNQLITE_FULL; | ||
49883 | } | ||
49884 | zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree]; | ||
49885 | zEnd = &pPage->pRaw->zData[pPage->pHash->iPageSize]; | ||
49886 | nByte = (sxu16)nAmount; | ||
49887 | zPrev = 0; | ||
49888 | iBlksz = 0; /* cc warning */ | ||
49889 | /* Perform the lookup */ | ||
49890 | for(;;){ | ||
49891 | if( zPtr >= zEnd ){ | ||
49892 | return UNQLITE_FULL; | ||
49893 | } | ||
49894 | /* Offset of the next free block */ | ||
49895 | SyBigEndianUnpack16(zPtr,&iNext); | ||
49896 | /* Block size */ | ||
49897 | SyBigEndianUnpack16(&zPtr[2],&iBlksz); | ||
49898 | if( iBlksz >= nByte ){ | ||
49899 | /* Got one */ | ||
49900 | break; | ||
49901 | } | ||
49902 | zPrev = (unsigned char *)zPtr; | ||
49903 | if( iNext == 0 ){ | ||
49904 | /* No more free blocks, defragment the page */ | ||
49905 | rc = lhPageDefragment(pPage); | ||
49906 | if( rc == UNQLITE_OK && pPage->nFree >= nByte) { | ||
49907 | /* Free blocks are merged together */ | ||
49908 | iNext = 0; | ||
49909 | zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree]; | ||
49910 | iBlksz = pPage->nFree; | ||
49911 | zPrev = 0; | ||
49912 | break; | ||
49913 | }else{ | ||
49914 | return UNQLITE_FULL; | ||
49915 | } | ||
49916 | } | ||
49917 | /* Point to the next free block */ | ||
49918 | zPtr = &pPage->pRaw->zData[iNext]; | ||
49919 | } | ||
49920 | /* Acquire writer lock on this page */ | ||
49921 | rc = pPage->pHash->pIo->xWrite(pPage->pRaw); | ||
49922 | if( rc != UNQLITE_OK ){ | ||
49923 | return rc; | ||
49924 | } | ||
49925 | /* Save block offset */ | ||
49926 | *pOfft = (sxu16)(zPtr - pPage->pRaw->zData); | ||
49927 | /* Fix pointers */ | ||
49928 | if( iBlksz >= nByte && (iBlksz - nByte) > 3 ){ | ||
49929 | unsigned char *zBlock = &pPage->pRaw->zData[(*pOfft) + nByte]; | ||
49930 | /* Create a new block */ | ||
49931 | zPtr = zBlock; | ||
49932 | SyBigEndianPack16(zBlock,iNext); /* Offset of the next block */ | ||
49933 | SyBigEndianPack16(&zBlock[2],iBlksz-nByte); /* Block size*/ | ||
49934 | /* Offset of the new block */ | ||
49935 | iNext = (sxu16)(zPtr - pPage->pRaw->zData); | ||
49936 | } | ||
49937 | /* Fix offsets */ | ||
49938 | if( zPrev ){ | ||
49939 | SyBigEndianPack16(zPrev,iNext); | ||
49940 | }else{ | ||
49941 | /* First block */ | ||
49942 | pPage->sHdr.iFree = iNext; | ||
49943 | /* Reflect on the page header */ | ||
49944 | SyBigEndianPack16(&pPage->pRaw->zData[2/* Offset of the first cell1*/],iNext); | ||
49945 | } | ||
49946 | /* All done */ | ||
49947 | pPage->nFree -= nByte; | ||
49948 | return UNQLITE_OK; | ||
49949 | } | ||
49950 | /* | ||
49951 | * Write the cell header into the corresponding offset. | ||
49952 | */ | ||
49953 | static int lhCellWriteHeader(lhcell *pCell) | ||
49954 | { | ||
49955 | lhpage *pPage = pCell->pPage; | ||
49956 | unsigned char *zRaw = pPage->pRaw->zData; | ||
49957 | /* Seek to the desired location */ | ||
49958 | zRaw += pCell->iStart; | ||
49959 | /* 4 byte hash number */ | ||
49960 | SyBigEndianPack32(zRaw,pCell->nHash); | ||
49961 | zRaw += 4; | ||
49962 | /* 4 byte key length */ | ||
49963 | SyBigEndianPack32(zRaw,pCell->nKey); | ||
49964 | zRaw += 4; | ||
49965 | /* 8 byte data length */ | ||
49966 | SyBigEndianPack64(zRaw,pCell->nData); | ||
49967 | zRaw += 8; | ||
49968 | /* 2 byte offset of the next cell */ | ||
49969 | pCell->iNext = pPage->sHdr.iOfft; | ||
49970 | SyBigEndianPack16(zRaw,pCell->iNext); | ||
49971 | zRaw += 2; | ||
49972 | /* 8 byte overflow page number */ | ||
49973 | SyBigEndianPack64(zRaw,pCell->iOvfl); | ||
49974 | /* Update the page header */ | ||
49975 | pPage->sHdr.iOfft = pCell->iStart; | ||
49976 | /* pEngine->pIo->xWrite() has been successfully called on this page */ | ||
49977 | SyBigEndianPack16(pPage->pRaw->zData,pCell->iStart); | ||
49978 | /* All done */ | ||
49979 | return UNQLITE_OK; | ||
49980 | } | ||
49981 | /* | ||
49982 | * Write local payload. | ||
49983 | */ | ||
49984 | static int lhCellWriteLocalPayload(lhcell *pCell, | ||
49985 | const void *pKey,sxu32 nKeylen, | ||
49986 | const void *pData,unqlite_int64 nDatalen | ||
49987 | ) | ||
49988 | { | ||
49989 | /* A writer lock have been acquired on this page */ | ||
49990 | lhpage *pPage = pCell->pPage; | ||
49991 | unsigned char *zRaw = pPage->pRaw->zData; | ||
49992 | /* Seek to the desired location */ | ||
49993 | zRaw += pCell->iStart + L_HASH_CELL_SZ; | ||
49994 | /* Write the key */ | ||
49995 | SyMemcpy(pKey,(void *)zRaw,nKeylen); | ||
49996 | zRaw += nKeylen; | ||
49997 | if( nDatalen > 0 ){ | ||
49998 | /* Write the Data */ | ||
49999 | SyMemcpy(pData,(void *)zRaw,(sxu32)nDatalen); | ||
50000 | } | ||
50001 | return UNQLITE_OK; | ||
50002 | } | ||
50003 | /* | ||
50004 | * Allocate as much overflow page we need to store the cell payload. | ||
50005 | */ | ||
50006 | static int lhCellWriteOvflPayload(lhcell *pCell,const void *pKey,sxu32 nKeylen,...) | ||
50007 | { | ||
50008 | lhpage *pPage = pCell->pPage; | ||
50009 | lhash_kv_engine *pEngine = pPage->pHash; | ||
50010 | unqlite_page *pOvfl,*pFirst,*pNew; | ||
50011 | const unsigned char *zPtr,*zEnd; | ||
50012 | unsigned char *zRaw,*zRawEnd; | ||
50013 | sxu32 nAvail; | ||
50014 | va_list ap; | ||
50015 | int rc; | ||
50016 | /* Acquire a new overflow page */ | ||
50017 | rc = lhAcquirePage(pEngine,&pOvfl); | ||
50018 | if( rc != UNQLITE_OK ){ | ||
50019 | return rc; | ||
50020 | } | ||
50021 | /* Acquire a writer lock */ | ||
50022 | rc = pEngine->pIo->xWrite(pOvfl); | ||
50023 | if( rc != UNQLITE_OK ){ | ||
50024 | return rc; | ||
50025 | } | ||
50026 | pFirst = pOvfl; | ||
50027 | /* Link */ | ||
50028 | pCell->iOvfl = pOvfl->pgno; | ||
50029 | /* Update the cell header */ | ||
50030 | SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4/*Hash*/ + 4/*Key*/ + 8/*Data*/ + 2 /*Next cell*/],pCell->iOvfl); | ||
50031 | /* Start the write process */ | ||
50032 | zPtr = (const unsigned char *)pKey; | ||
50033 | zEnd = &zPtr[nKeylen]; | ||
50034 | SyBigEndianPack64(pOvfl->zData,0); /* Next overflow page on the chain */ | ||
50035 | zRaw = &pOvfl->zData[8/* Next ovfl page*/ + 8 /* Data page */ + 2 /* Data offset*/]; | ||
50036 | zRawEnd = &pOvfl->zData[pEngine->iPageSize]; | ||
50037 | pNew = pOvfl; | ||
50038 | /* Write the key */ | ||
50039 | for(;;){ | ||
50040 | if( zPtr >= zEnd ){ | ||
50041 | break; | ||
50042 | } | ||
50043 | if( zRaw >= zRawEnd ){ | ||
50044 | /* Acquire a new page */ | ||
50045 | rc = lhAcquirePage(pEngine,&pNew); | ||
50046 | if( rc != UNQLITE_OK ){ | ||
50047 | return rc; | ||
50048 | } | ||
50049 | rc = pEngine->pIo->xWrite(pNew); | ||
50050 | if( rc != UNQLITE_OK ){ | ||
50051 | return rc; | ||
50052 | } | ||
50053 | /* Link */ | ||
50054 | SyBigEndianPack64(pOvfl->zData,pNew->pgno); | ||
50055 | pEngine->pIo->xPageUnref(pOvfl); | ||
50056 | SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ | ||
50057 | pOvfl = pNew; | ||
50058 | zRaw = &pNew->zData[8]; | ||
50059 | zRawEnd = &pNew->zData[pEngine->iPageSize]; | ||
50060 | } | ||
50061 | nAvail = (sxu32)(zRawEnd-zRaw); | ||
50062 | nKeylen = (sxu32)(zEnd-zPtr); | ||
50063 | if( nKeylen > nAvail ){ | ||
50064 | nKeylen = nAvail; | ||
50065 | } | ||
50066 | SyMemcpy((const void *)zPtr,(void *)zRaw,nKeylen); | ||
50067 | /* Synchronize pointers */ | ||
50068 | zPtr += nKeylen; | ||
50069 | zRaw += nKeylen; | ||
50070 | } | ||
50071 | rc = UNQLITE_OK; | ||
50072 | va_start(ap,nKeylen); | ||
50073 | pCell->iDataPage = pNew->pgno; | ||
50074 | pCell->iDataOfft = (sxu16)(zRaw-pNew->zData); | ||
50075 | /* Write the data page and its offset */ | ||
50076 | SyBigEndianPack64(&pFirst->zData[8/*Next ovfl*/],pCell->iDataPage); | ||
50077 | SyBigEndianPack16(&pFirst->zData[8/*Next ovfl*/+8/*Data page*/],pCell->iDataOfft); | ||
50078 | /* Write data */ | ||
50079 | for(;;){ | ||
50080 | const void *pData; | ||
50081 | sxu32 nDatalen; | ||
50082 | sxu64 nData; | ||
50083 | pData = va_arg(ap,const void *); | ||
50084 | nData = va_arg(ap,sxu64); | ||
50085 | if( pData == 0 ){ | ||
50086 | /* No more chunks */ | ||
50087 | break; | ||
50088 | } | ||
50089 | /* Write this chunk */ | ||
50090 | zPtr = (const unsigned char *)pData; | ||
50091 | zEnd = &zPtr[nData]; | ||
50092 | for(;;){ | ||
50093 | if( zPtr >= zEnd ){ | ||
50094 | break; | ||
50095 | } | ||
50096 | if( zRaw >= zRawEnd ){ | ||
50097 | /* Acquire a new page */ | ||
50098 | rc = lhAcquirePage(pEngine,&pNew); | ||
50099 | if( rc != UNQLITE_OK ){ | ||
50100 | va_end(ap); | ||
50101 | return rc; | ||
50102 | } | ||
50103 | rc = pEngine->pIo->xWrite(pNew); | ||
50104 | if( rc != UNQLITE_OK ){ | ||
50105 | va_end(ap); | ||
50106 | return rc; | ||
50107 | } | ||
50108 | /* Link */ | ||
50109 | SyBigEndianPack64(pOvfl->zData,pNew->pgno); | ||
50110 | pEngine->pIo->xPageUnref(pOvfl); | ||
50111 | SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ | ||
50112 | pOvfl = pNew; | ||
50113 | zRaw = &pNew->zData[8]; | ||
50114 | zRawEnd = &pNew->zData[pEngine->iPageSize]; | ||
50115 | } | ||
50116 | nAvail = (sxu32)(zRawEnd-zRaw); | ||
50117 | nDatalen = (sxu32)(zEnd-zPtr); | ||
50118 | if( nDatalen > nAvail ){ | ||
50119 | nDatalen = nAvail; | ||
50120 | } | ||
50121 | SyMemcpy((const void *)zPtr,(void *)zRaw,nDatalen); | ||
50122 | /* Synchronize pointers */ | ||
50123 | zPtr += nDatalen; | ||
50124 | zRaw += nDatalen; | ||
50125 | } | ||
50126 | } | ||
50127 | /* Unref the overflow page */ | ||
50128 | pEngine->pIo->xPageUnref(pOvfl); | ||
50129 | va_end(ap); | ||
50130 | return UNQLITE_OK; | ||
50131 | } | ||
50132 | /* | ||
50133 | * Restore a page to the free list. | ||
50134 | */ | ||
50135 | static int lhRestorePage(lhash_kv_engine *pEngine,unqlite_page *pPage) | ||
50136 | { | ||
50137 | int rc; | ||
50138 | rc = pEngine->pIo->xWrite(pEngine->pHeader); | ||
50139 | if( rc != UNQLITE_OK ){ | ||
50140 | return rc; | ||
50141 | } | ||
50142 | rc = pEngine->pIo->xWrite(pPage); | ||
50143 | if( rc != UNQLITE_OK ){ | ||
50144 | return rc; | ||
50145 | } | ||
50146 | /* Link to the list of free page */ | ||
50147 | SyBigEndianPack64(pPage->zData,pEngine->nFreeList); | ||
50148 | pEngine->nFreeList = pPage->pgno; | ||
50149 | SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList); | ||
50150 | /* All done */ | ||
50151 | return UNQLITE_OK; | ||
50152 | } | ||
50153 | /* | ||
50154 | * Restore cell space and mark it as a free block. | ||
50155 | */ | ||
50156 | static int lhRestoreSpace(lhpage *pPage,sxu16 iOfft,sxu16 nByte) | ||
50157 | { | ||
50158 | unsigned char *zRaw; | ||
50159 | if( nByte < 4 ){ | ||
50160 | /* At least 4 bytes of freespace must form a valid block */ | ||
50161 | return UNQLITE_OK; | ||
50162 | } | ||
50163 | /* pEngine->pIo->xWrite() has been successfully called on this page */ | ||
50164 | zRaw = &pPage->pRaw->zData[iOfft]; | ||
50165 | /* Mark as a free block */ | ||
50166 | SyBigEndianPack16(zRaw,pPage->sHdr.iFree); /* Offset of the next free block */ | ||
50167 | zRaw += 2; | ||
50168 | SyBigEndianPack16(zRaw,nByte); | ||
50169 | /* Link */ | ||
50170 | SyBigEndianPack16(&pPage->pRaw->zData[2/* offset of the first cell */],iOfft); | ||
50171 | pPage->sHdr.iFree = iOfft; | ||
50172 | pPage->nFree += nByte; | ||
50173 | return UNQLITE_OK; | ||
50174 | } | ||
50175 | /* Forward declaration */ | ||
50176 | static lhcell * lhFindSibeling(lhcell *pCell); | ||
50177 | /* | ||
50178 | * Unlink a cell. | ||
50179 | */ | ||
50180 | static int lhUnlinkCell(lhcell *pCell) | ||
50181 | { | ||
50182 | lhash_kv_engine *pEngine = pCell->pPage->pHash; | ||
50183 | lhpage *pPage = pCell->pPage; | ||
50184 | sxu16 nByte = L_HASH_CELL_SZ; | ||
50185 | lhcell *pPrev; | ||
50186 | int rc; | ||
50187 | rc = pEngine->pIo->xWrite(pPage->pRaw); | ||
50188 | if( rc != UNQLITE_OK ){ | ||
50189 | return rc; | ||
50190 | } | ||
50191 | /* Bring the link */ | ||
50192 | pPrev = lhFindSibeling(pCell); | ||
50193 | if( pPrev ){ | ||
50194 | pPrev->iNext = pCell->iNext; | ||
50195 | /* Fix offsets in the page header */ | ||
50196 | SyBigEndianPack16(&pPage->pRaw->zData[pPrev->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext); | ||
50197 | }else{ | ||
50198 | /* First entry on this page (either master or slave) */ | ||
50199 | pPage->sHdr.iOfft = pCell->iNext; | ||
50200 | /* Update the page header */ | ||
50201 | SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext); | ||
50202 | } | ||
50203 | /* Restore cell space */ | ||
50204 | if( pCell->iOvfl == 0 ){ | ||
50205 | nByte += (sxu16)(pCell->nData + pCell->nKey); | ||
50206 | } | ||
50207 | lhRestoreSpace(pPage,pCell->iStart,nByte); | ||
50208 | /* Discard the cell from the in-memory hashtable */ | ||
50209 | lhCellDiscard(pCell); | ||
50210 | return UNQLITE_OK; | ||
50211 | } | ||
50212 | /* | ||
50213 | * Remove a cell and its paylod (key + data). | ||
50214 | */ | ||
50215 | static int lhRecordRemove(lhcell *pCell) | ||
50216 | { | ||
50217 | lhash_kv_engine *pEngine = pCell->pPage->pHash; | ||
50218 | int rc; | ||
50219 | if( pCell->iOvfl > 0){ | ||
50220 | /* Discard overflow pages */ | ||
50221 | unqlite_page *pOvfl; | ||
50222 | pgno iNext = pCell->iOvfl; | ||
50223 | for(;;){ | ||
50224 | /* Point to the overflow page */ | ||
50225 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pOvfl); | ||
50226 | if( rc != UNQLITE_OK ){ | ||
50227 | return rc; | ||
50228 | } | ||
50229 | /* Next page on the chain */ | ||
50230 | SyBigEndianUnpack64(pOvfl->zData,&iNext); | ||
50231 | /* Restore the page to the free list */ | ||
50232 | rc = lhRestorePage(pEngine,pOvfl); | ||
50233 | if( rc != UNQLITE_OK ){ | ||
50234 | return rc; | ||
50235 | } | ||
50236 | /* Unref */ | ||
50237 | pEngine->pIo->xPageUnref(pOvfl); | ||
50238 | if( iNext == 0 ){ | ||
50239 | break; | ||
50240 | } | ||
50241 | } | ||
50242 | } | ||
50243 | /* Unlink the cell */ | ||
50244 | rc = lhUnlinkCell(pCell); | ||
50245 | return rc; | ||
50246 | } | ||
50247 | /* | ||
50248 | * Find cell sibeling. | ||
50249 | */ | ||
50250 | static lhcell * lhFindSibeling(lhcell *pCell) | ||
50251 | { | ||
50252 | lhpage *pPage = pCell->pPage->pMaster; | ||
50253 | lhcell *pEntry; | ||
50254 | pEntry = pPage->pFirst; | ||
50255 | while( pEntry ){ | ||
50256 | if( pEntry->pPage == pCell->pPage && pEntry->iNext == pCell->iStart ){ | ||
50257 | /* Sibeling found */ | ||
50258 | return pEntry; | ||
50259 | } | ||
50260 | /* Point to the previous entry */ | ||
50261 | pEntry = pEntry->pPrev; | ||
50262 | } | ||
50263 | /* Last inserted cell */ | ||
50264 | return 0; | ||
50265 | } | ||
50266 | /* | ||
50267 | * Move a cell to a new location with its new data. | ||
50268 | */ | ||
50269 | static int lhMoveLocalCell( | ||
50270 | lhcell *pCell, | ||
50271 | sxu16 iOfft, | ||
50272 | const void *pData, | ||
50273 | unqlite_int64 nData | ||
50274 | ) | ||
50275 | { | ||
50276 | sxu16 iKeyOfft = pCell->iStart + L_HASH_CELL_SZ; | ||
50277 | lhpage *pPage = pCell->pPage; | ||
50278 | lhcell *pSibeling; | ||
50279 | pSibeling = lhFindSibeling(pCell); | ||
50280 | if( pSibeling ){ | ||
50281 | /* Fix link */ | ||
50282 | SyBigEndianPack16(&pPage->pRaw->zData[pSibeling->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext); | ||
50283 | pSibeling->iNext = pCell->iNext; | ||
50284 | }else{ | ||
50285 | /* First cell, update page header only */ | ||
50286 | SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext); | ||
50287 | pPage->sHdr.iOfft = pCell->iNext; | ||
50288 | } | ||
50289 | /* Set the new offset */ | ||
50290 | pCell->iStart = iOfft; | ||
50291 | pCell->nData = (sxu64)nData; | ||
50292 | /* Write the cell payload */ | ||
50293 | lhCellWriteLocalPayload(pCell,(const void *)&pPage->pRaw->zData[iKeyOfft],pCell->nKey,pData,nData); | ||
50294 | /* Finally write the cell header */ | ||
50295 | lhCellWriteHeader(pCell); | ||
50296 | /* All done */ | ||
50297 | return UNQLITE_OK; | ||
50298 | } | ||
50299 | /* | ||
50300 | * Overwrite an existing record. | ||
50301 | */ | ||
50302 | static int lhRecordOverwrite( | ||
50303 | lhcell *pCell, | ||
50304 | const void *pData,unqlite_int64 nByte | ||
50305 | ) | ||
50306 | { | ||
50307 | lhash_kv_engine *pEngine = pCell->pPage->pHash; | ||
50308 | unsigned char *zRaw,*zRawEnd,*zPayload; | ||
50309 | const unsigned char *zPtr,*zEnd; | ||
50310 | unqlite_page *pOvfl,*pOld,*pNew; | ||
50311 | lhpage *pPage = pCell->pPage; | ||
50312 | sxu32 nAvail; | ||
50313 | pgno iOvfl; | ||
50314 | int rc; | ||
50315 | /* Acquire a writer lock on this page */ | ||
50316 | rc = pEngine->pIo->xWrite(pPage->pRaw); | ||
50317 | if( rc != UNQLITE_OK ){ | ||
50318 | return rc; | ||
50319 | } | ||
50320 | if( pCell->iOvfl == 0 ){ | ||
50321 | /* Local payload, try to deal with the free space issues */ | ||
50322 | zPayload = &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey]; | ||
50323 | if( pCell->nData == (sxu64)nByte ){ | ||
50324 | /* Best scenario, simply a memcpy operation */ | ||
50325 | SyMemcpy(pData,(void *)zPayload,(sxu32)nByte); | ||
50326 | }else if( (sxu64)nByte < pCell->nData ){ | ||
50327 | /* Shorter data, not so ugly */ | ||
50328 | SyMemcpy(pData,(void *)zPayload,(sxu32)nByte); | ||
50329 | /* Update the cell header */ | ||
50330 | SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],nByte); | ||
50331 | /* Restore freespace */ | ||
50332 | lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ + pCell->nKey + nByte),(sxu16)(pCell->nData - nByte)); | ||
50333 | /* New data size */ | ||
50334 | pCell->nData = (sxu64)nByte; | ||
50335 | }else{ | ||
50336 | sxu16 iOfft = 0; /* cc warning */ | ||
50337 | /* Check if another chunk is available for this cell */ | ||
50338 | rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + nByte,&iOfft); | ||
50339 | if( rc != UNQLITE_OK ){ | ||
50340 | /* Transfer the payload to an overflow page */ | ||
50341 | rc = lhCellWriteOvflPayload(pCell,&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey,pData,nByte,(const void *)0); | ||
50342 | if( rc != UNQLITE_OK ){ | ||
50343 | return rc; | ||
50344 | } | ||
50345 | /* Update the cell header */ | ||
50346 | SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],(sxu64)nByte); | ||
50347 | /* Restore freespace */ | ||
50348 | lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData)); | ||
50349 | /* New data size */ | ||
50350 | pCell->nData = (sxu64)nByte; | ||
50351 | }else{ | ||
50352 | sxu16 iOldOfft = pCell->iStart; | ||
50353 | sxu32 iOld = (sxu32)pCell->nData; | ||
50354 | /* Space is available, transfer the cell */ | ||
50355 | lhMoveLocalCell(pCell,iOfft,pData,nByte); | ||
50356 | /* Restore cell space */ | ||
50357 | lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld)); | ||
50358 | } | ||
50359 | } | ||
50360 | return UNQLITE_OK; | ||
50361 | } | ||
50362 | /* Point to the overflow page */ | ||
50363 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl); | ||
50364 | if( rc != UNQLITE_OK ){ | ||
50365 | return rc; | ||
50366 | } | ||
50367 | /* Relase all old overflow pages first */ | ||
50368 | SyBigEndianUnpack64(pOvfl->zData,&iOvfl); | ||
50369 | pOld = pOvfl; | ||
50370 | for(;;){ | ||
50371 | if( iOvfl == 0 ){ | ||
50372 | /* No more overflow pages on the chain */ | ||
50373 | break; | ||
50374 | } | ||
50375 | /* Point to the target page */ | ||
50376 | if( UNQLITE_OK != pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOld) ){ | ||
50377 | /* Not so fatal if something goes wrong here */ | ||
50378 | break; | ||
50379 | } | ||
50380 | /* Next overflow page to be released */ | ||
50381 | SyBigEndianUnpack64(pOld->zData,&iOvfl); | ||
50382 | if( pOld != pOvfl ){ /* xx: chm is maniac */ | ||
50383 | /* Restore the page to the free list */ | ||
50384 | lhRestorePage(pEngine,pOld); | ||
50385 | /* Unref */ | ||
50386 | pEngine->pIo->xPageUnref(pOld); | ||
50387 | } | ||
50388 | } | ||
50389 | /* Point to the data offset */ | ||
50390 | zRaw = &pOvfl->zData[pCell->iDataOfft]; | ||
50391 | zRawEnd = &pOvfl->zData[pEngine->iPageSize]; | ||
50392 | /* The data to be stored */ | ||
50393 | zPtr = (const unsigned char *)pData; | ||
50394 | zEnd = &zPtr[nByte]; | ||
50395 | /* Start the overwrite process */ | ||
50396 | /* Acquire a writer lock */ | ||
50397 | rc = pEngine->pIo->xWrite(pOvfl); | ||
50398 | if( rc != UNQLITE_OK ){ | ||
50399 | return rc; | ||
50400 | } | ||
50401 | SyBigEndianPack64(pOvfl->zData,0); | ||
50402 | for(;;){ | ||
50403 | sxu32 nLen; | ||
50404 | if( zPtr >= zEnd ){ | ||
50405 | break; | ||
50406 | } | ||
50407 | if( zRaw >= zRawEnd ){ | ||
50408 | /* Acquire a new page */ | ||
50409 | rc = lhAcquirePage(pEngine,&pNew); | ||
50410 | if( rc != UNQLITE_OK ){ | ||
50411 | return rc; | ||
50412 | } | ||
50413 | rc = pEngine->pIo->xWrite(pNew); | ||
50414 | if( rc != UNQLITE_OK ){ | ||
50415 | return rc; | ||
50416 | } | ||
50417 | /* Link */ | ||
50418 | SyBigEndianPack64(pOvfl->zData,pNew->pgno); | ||
50419 | pEngine->pIo->xPageUnref(pOvfl); | ||
50420 | SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ | ||
50421 | pOvfl = pNew; | ||
50422 | zRaw = &pNew->zData[8]; | ||
50423 | zRawEnd = &pNew->zData[pEngine->iPageSize]; | ||
50424 | } | ||
50425 | nAvail = (sxu32)(zRawEnd-zRaw); | ||
50426 | nLen = (sxu32)(zEnd-zPtr); | ||
50427 | if( nLen > nAvail ){ | ||
50428 | nLen = nAvail; | ||
50429 | } | ||
50430 | SyMemcpy((const void *)zPtr,(void *)zRaw,nLen); | ||
50431 | /* Synchronize pointers */ | ||
50432 | zPtr += nLen; | ||
50433 | zRaw += nLen; | ||
50434 | } | ||
50435 | /* Unref the last overflow page */ | ||
50436 | pEngine->pIo->xPageUnref(pOvfl); | ||
50437 | /* Finally, update the cell header */ | ||
50438 | pCell->nData = (sxu64)nByte; | ||
50439 | SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData); | ||
50440 | /* All done */ | ||
50441 | return UNQLITE_OK; | ||
50442 | } | ||
50443 | /* | ||
50444 | * Append data to an existing record. | ||
50445 | */ | ||
50446 | static int lhRecordAppend( | ||
50447 | lhcell *pCell, | ||
50448 | const void *pData,unqlite_int64 nByte | ||
50449 | ) | ||
50450 | { | ||
50451 | lhash_kv_engine *pEngine = pCell->pPage->pHash; | ||
50452 | const unsigned char *zPtr,*zEnd; | ||
50453 | lhpage *pPage = pCell->pPage; | ||
50454 | unsigned char *zRaw,*zRawEnd; | ||
50455 | unqlite_page *pOvfl,*pNew; | ||
50456 | sxu64 nDatalen; | ||
50457 | sxu32 nAvail; | ||
50458 | pgno iOvfl; | ||
50459 | int rc; | ||
50460 | if( pCell->nData + nByte < pCell->nData ){ | ||
50461 | /* Overflow */ | ||
50462 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); | ||
50463 | return UNQLITE_LIMIT; | ||
50464 | } | ||
50465 | /* Acquire a writer lock on this page */ | ||
50466 | rc = pEngine->pIo->xWrite(pPage->pRaw); | ||
50467 | if( rc != UNQLITE_OK ){ | ||
50468 | return rc; | ||
50469 | } | ||
50470 | if( pCell->iOvfl == 0 ){ | ||
50471 | sxu16 iOfft = 0; /* cc warning */ | ||
50472 | /* Local payload, check for a bigger place */ | ||
50473 | rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + pCell->nData + nByte,&iOfft); | ||
50474 | if( rc != UNQLITE_OK ){ | ||
50475 | /* Transfer the payload to an overflow page */ | ||
50476 | rc = lhCellWriteOvflPayload(pCell, | ||
50477 | &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey, | ||
50478 | (const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],pCell->nData, | ||
50479 | pData,nByte, | ||
50480 | (const void *)0); | ||
50481 | if( rc != UNQLITE_OK ){ | ||
50482 | return rc; | ||
50483 | } | ||
50484 | /* Update the cell header */ | ||
50485 | SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData + nByte); | ||
50486 | /* Restore freespace */ | ||
50487 | lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData)); | ||
50488 | /* New data size */ | ||
50489 | pCell->nData += nByte; | ||
50490 | }else{ | ||
50491 | sxu16 iOldOfft = pCell->iStart; | ||
50492 | sxu32 iOld = (sxu32)pCell->nData; | ||
50493 | SyBlob sWorker; | ||
50494 | SyBlobInit(&sWorker,&pEngine->sAllocator); | ||
50495 | /* Copy the old data */ | ||
50496 | rc = SyBlobAppend(&sWorker,(const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],(sxu32)pCell->nData); | ||
50497 | if( rc == SXRET_OK ){ | ||
50498 | /* Append the new data */ | ||
50499 | rc = SyBlobAppend(&sWorker,pData,(sxu32)nByte); | ||
50500 | } | ||
50501 | if( rc != UNQLITE_OK ){ | ||
50502 | SyBlobRelease(&sWorker); | ||
50503 | return rc; | ||
50504 | } | ||
50505 | /* Space is available, transfer the cell */ | ||
50506 | lhMoveLocalCell(pCell,iOfft,SyBlobData(&sWorker),(unqlite_int64)SyBlobLength(&sWorker)); | ||
50507 | /* Restore cell space */ | ||
50508 | lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld)); | ||
50509 | /* All done */ | ||
50510 | SyBlobRelease(&sWorker); | ||
50511 | } | ||
50512 | return UNQLITE_OK; | ||
50513 | } | ||
50514 | /* Point to the overflow page which hold the data */ | ||
50515 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl); | ||
50516 | if( rc != UNQLITE_OK ){ | ||
50517 | return rc; | ||
50518 | } | ||
50519 | /* Next overflow page in the chain */ | ||
50520 | SyBigEndianUnpack64(pOvfl->zData,&iOvfl); | ||
50521 | /* Point to the end of the chunk */ | ||
50522 | zRaw = &pOvfl->zData[pCell->iDataOfft]; | ||
50523 | zRawEnd = &pOvfl->zData[pEngine->iPageSize]; | ||
50524 | nDatalen = pCell->nData; | ||
50525 | nAvail = (sxu32)(zRawEnd - zRaw); | ||
50526 | for(;;){ | ||
50527 | if( zRaw >= zRawEnd ){ | ||
50528 | if( iOvfl == 0 ){ | ||
50529 | /* Cant happen */ | ||
50530 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Corrupt overflow page"); | ||
50531 | return UNQLITE_CORRUPT; | ||
50532 | } | ||
50533 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pNew); | ||
50534 | if( rc != UNQLITE_OK ){ | ||
50535 | return rc; | ||
50536 | } | ||
50537 | /* Next overflow page on the chain */ | ||
50538 | SyBigEndianUnpack64(pNew->zData,&iOvfl); | ||
50539 | /* Unref the previous overflow page */ | ||
50540 | pEngine->pIo->xPageUnref(pOvfl); | ||
50541 | /* Point to the new chunk */ | ||
50542 | zRaw = &pNew->zData[8]; | ||
50543 | zRawEnd = &pNew->zData[pCell->pPage->pHash->iPageSize]; | ||
50544 | nAvail = L_HASH_OVERFLOW_SIZE(pCell->pPage->pHash->iPageSize); | ||
50545 | pOvfl = pNew; | ||
50546 | } | ||
50547 | if( (sxu64)nAvail > nDatalen ){ | ||
50548 | zRaw += nDatalen; | ||
50549 | break; | ||
50550 | }else{ | ||
50551 | nDatalen -= nAvail; | ||
50552 | } | ||
50553 | zRaw += nAvail; | ||
50554 | } | ||
50555 | /* Start the append process */ | ||
50556 | zPtr = (const unsigned char *)pData; | ||
50557 | zEnd = &zPtr[nByte]; | ||
50558 | /* Acquire a writer lock */ | ||
50559 | rc = pEngine->pIo->xWrite(pOvfl); | ||
50560 | if( rc != UNQLITE_OK ){ | ||
50561 | return rc; | ||
50562 | } | ||
50563 | for(;;){ | ||
50564 | sxu32 nLen; | ||
50565 | if( zPtr >= zEnd ){ | ||
50566 | break; | ||
50567 | } | ||
50568 | if( zRaw >= zRawEnd ){ | ||
50569 | /* Acquire a new page */ | ||
50570 | rc = lhAcquirePage(pEngine,&pNew); | ||
50571 | if( rc != UNQLITE_OK ){ | ||
50572 | return rc; | ||
50573 | } | ||
50574 | rc = pEngine->pIo->xWrite(pNew); | ||
50575 | if( rc != UNQLITE_OK ){ | ||
50576 | return rc; | ||
50577 | } | ||
50578 | /* Link */ | ||
50579 | SyBigEndianPack64(pOvfl->zData,pNew->pgno); | ||
50580 | pEngine->pIo->xPageUnref(pOvfl); | ||
50581 | SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ | ||
50582 | pOvfl = pNew; | ||
50583 | zRaw = &pNew->zData[8]; | ||
50584 | zRawEnd = &pNew->zData[pEngine->iPageSize]; | ||
50585 | } | ||
50586 | nAvail = (sxu32)(zRawEnd-zRaw); | ||
50587 | nLen = (sxu32)(zEnd-zPtr); | ||
50588 | if( nLen > nAvail ){ | ||
50589 | nLen = nAvail; | ||
50590 | } | ||
50591 | SyMemcpy((const void *)zPtr,(void *)zRaw,nLen); | ||
50592 | /* Synchronize pointers */ | ||
50593 | zPtr += nLen; | ||
50594 | zRaw += nLen; | ||
50595 | } | ||
50596 | /* Unref the last overflow page */ | ||
50597 | pEngine->pIo->xPageUnref(pOvfl); | ||
50598 | /* Finally, update the cell header */ | ||
50599 | pCell->nData += nByte; | ||
50600 | SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData); | ||
50601 | /* All done */ | ||
50602 | return UNQLITE_OK; | ||
50603 | } | ||
50604 | /* | ||
50605 | * A write privilege have been acquired on this page. | ||
50606 | * Mark it as an empty page (No cells). | ||
50607 | */ | ||
50608 | static int lhSetEmptyPage(lhpage *pPage) | ||
50609 | { | ||
50610 | unsigned char *zRaw = pPage->pRaw->zData; | ||
50611 | lhphdr *pHeader = &pPage->sHdr; | ||
50612 | sxu16 nByte; | ||
50613 | int rc; | ||
50614 | /* Acquire a writer lock */ | ||
50615 | rc = pPage->pHash->pIo->xWrite(pPage->pRaw); | ||
50616 | if( rc != UNQLITE_OK ){ | ||
50617 | return rc; | ||
50618 | } | ||
50619 | /* Offset of the first cell */ | ||
50620 | SyBigEndianPack16(zRaw,0); | ||
50621 | zRaw += 2; | ||
50622 | /* Offset of the first free block */ | ||
50623 | pHeader->iFree = L_HASH_PAGE_HDR_SZ; | ||
50624 | SyBigEndianPack16(zRaw,L_HASH_PAGE_HDR_SZ); | ||
50625 | zRaw += 2; | ||
50626 | /* Slave page number */ | ||
50627 | SyBigEndianPack64(zRaw,0); | ||
50628 | zRaw += 8; | ||
50629 | /* Fill the free block */ | ||
50630 | SyBigEndianPack16(zRaw,0); /* Offset of the next free block */ | ||
50631 | zRaw += 2; | ||
50632 | nByte = (sxu16)L_HASH_MX_FREE_SPACE(pPage->pHash->iPageSize); | ||
50633 | SyBigEndianPack16(zRaw,nByte); | ||
50634 | pPage->nFree = nByte; | ||
50635 | /* Do not add this page to the hot dirty list */ | ||
50636 | pPage->pHash->pIo->xDontMkHot(pPage->pRaw); | ||
50637 | return UNQLITE_OK; | ||
50638 | } | ||
50639 | /* Forward declaration */ | ||
50640 | static int lhSlaveStore( | ||
50641 | lhpage *pPage, | ||
50642 | const void *pKey,sxu32 nKeyLen, | ||
50643 | const void *pData,unqlite_int64 nDataLen, | ||
50644 | sxu32 nHash | ||
50645 | ); | ||
50646 | /* | ||
50647 | * Store a cell and its payload in a given page. | ||
50648 | */ | ||
50649 | static int lhStoreCell( | ||
50650 | lhpage *pPage, /* Target page */ | ||
50651 | const void *pKey,sxu32 nKeyLen, /* Payload: Key */ | ||
50652 | const void *pData,unqlite_int64 nDataLen, /* Payload: Data */ | ||
50653 | sxu32 nHash, /* Hash of the key */ | ||
50654 | int auto_append /* Auto append a slave page if full */ | ||
50655 | ) | ||
50656 | { | ||
50657 | lhash_kv_engine *pEngine = pPage->pHash; | ||
50658 | int iNeedOvfl = 0; /* Need overflow page for this cell and its payload*/ | ||
50659 | lhcell *pCell; | ||
50660 | sxu16 nOfft; | ||
50661 | int rc; | ||
50662 | /* Acquire a writer lock on this page first */ | ||
50663 | rc = pEngine->pIo->xWrite(pPage->pRaw); | ||
50664 | if( rc != UNQLITE_OK ){ | ||
50665 | return rc; | ||
50666 | } | ||
50667 | /* Check for a free block */ | ||
50668 | rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ+nKeyLen+nDataLen,&nOfft); | ||
50669 | if( rc != UNQLITE_OK ){ | ||
50670 | /* Check for a free block to hold a single cell only (without payload) */ | ||
50671 | rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft); | ||
50672 | if( rc != UNQLITE_OK ){ | ||
50673 | if( !auto_append ){ | ||
50674 | /* A split must be done */ | ||
50675 | return UNQLITE_FULL; | ||
50676 | }else{ | ||
50677 | /* Store this record in a slave page */ | ||
50678 | rc = lhSlaveStore(pPage,pKey,nKeyLen,pData,nDataLen,nHash); | ||
50679 | return rc; | ||
50680 | } | ||
50681 | } | ||
50682 | iNeedOvfl = 1; | ||
50683 | } | ||
50684 | /* Allocate a new cell instance */ | ||
50685 | pCell = lhNewCell(pEngine,pPage); | ||
50686 | if( pCell == 0 ){ | ||
50687 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"KV store is running out of memory"); | ||
50688 | return UNQLITE_NOMEM; | ||
50689 | } | ||
50690 | /* Fill-in the structure */ | ||
50691 | pCell->iStart = nOfft; | ||
50692 | pCell->nKey = nKeyLen; | ||
50693 | pCell->nData = (sxu64)nDataLen; | ||
50694 | pCell->nHash = nHash; | ||
50695 | if( nKeyLen < 262144 /* 256 KB */ ){ | ||
50696 | /* Keep the key in-memory for fast lookup */ | ||
50697 | SyBlobAppend(&pCell->sKey,pKey,nKeyLen); | ||
50698 | } | ||
50699 | /* Link the cell */ | ||
50700 | rc = lhInstallCell(pCell); | ||
50701 | if( rc != UNQLITE_OK ){ | ||
50702 | return rc; | ||
50703 | } | ||
50704 | /* Write the payload */ | ||
50705 | if( iNeedOvfl ){ | ||
50706 | rc = lhCellWriteOvflPayload(pCell,pKey,nKeyLen,pData,nDataLen,(const void *)0); | ||
50707 | if( rc != UNQLITE_OK ){ | ||
50708 | lhCellDiscard(pCell); | ||
50709 | return rc; | ||
50710 | } | ||
50711 | }else{ | ||
50712 | lhCellWriteLocalPayload(pCell,pKey,nKeyLen,pData,nDataLen); | ||
50713 | } | ||
50714 | /* Finally, Write the cell header */ | ||
50715 | lhCellWriteHeader(pCell); | ||
50716 | /* All done */ | ||
50717 | return UNQLITE_OK; | ||
50718 | } | ||
50719 | /* | ||
50720 | * Find a slave page capable of hosting the given amount. | ||
50721 | */ | ||
50722 | static int lhFindSlavePage(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft,lhpage **ppSlave) | ||
50723 | { | ||
50724 | lhash_kv_engine *pEngine = pPage->pHash; | ||
50725 | lhpage *pMaster = pPage->pMaster; | ||
50726 | lhpage *pSlave = pMaster->pSlave; | ||
50727 | unqlite_page *pRaw; | ||
50728 | lhpage *pNew; | ||
50729 | sxu16 iOfft; | ||
50730 | sxi32 i; | ||
50731 | int rc; | ||
50732 | /* Look for an already attached slave page */ | ||
50733 | for( i = 0 ; i < pMaster->iSlave ; ++i ){ | ||
50734 | /* Find a free chunk big enough */ | ||
50735 | rc = lhAllocateSpace(pSlave,L_HASH_CELL_SZ+nAmount,&iOfft); | ||
50736 | if( rc != UNQLITE_OK ){ | ||
50737 | /* A space for cell header only */ | ||
50738 | rc = lhAllocateSpace(pSlave,L_HASH_CELL_SZ,&iOfft); | ||
50739 | } | ||
50740 | if( rc == UNQLITE_OK ){ | ||
50741 | /* All done */ | ||
50742 | if( pOfft ){ | ||
50743 | *pOfft = iOfft; | ||
50744 | } | ||
50745 | *ppSlave = pSlave; | ||
50746 | return UNQLITE_OK; | ||
50747 | } | ||
50748 | /* Point to the next slave page */ | ||
50749 | pSlave = pSlave->pNextSlave; | ||
50750 | } | ||
50751 | /* Acquire a new slave page */ | ||
50752 | rc = lhAcquirePage(pEngine,&pRaw); | ||
50753 | if( rc != UNQLITE_OK ){ | ||
50754 | return rc; | ||
50755 | } | ||
50756 | /* Last slave page */ | ||
50757 | pSlave = pMaster->pSlave; | ||
50758 | if( pSlave == 0 ){ | ||
50759 | /* First slave page */ | ||
50760 | pSlave = pMaster; | ||
50761 | } | ||
50762 | /* Initialize the page */ | ||
50763 | pNew = lhNewPage(pEngine,pRaw,pMaster); | ||
50764 | if( pNew == 0 ){ | ||
50765 | return UNQLITE_NOMEM; | ||
50766 | } | ||
50767 | /* Mark as an empty page */ | ||
50768 | rc = lhSetEmptyPage(pNew); | ||
50769 | if( rc != UNQLITE_OK ){ | ||
50770 | goto fail; | ||
50771 | } | ||
50772 | if( pOfft ){ | ||
50773 | /* Look for a free block */ | ||
50774 | if( UNQLITE_OK != lhAllocateSpace(pNew,L_HASH_CELL_SZ+nAmount,&iOfft) ){ | ||
50775 | /* Cell header only */ | ||
50776 | lhAllocateSpace(pNew,L_HASH_CELL_SZ,&iOfft); /* Never fail */ | ||
50777 | } | ||
50778 | *pOfft = iOfft; | ||
50779 | } | ||
50780 | /* Link this page to the previous slave page */ | ||
50781 | rc = pEngine->pIo->xWrite(pSlave->pRaw); | ||
50782 | if( rc != UNQLITE_OK ){ | ||
50783 | goto fail; | ||
50784 | } | ||
50785 | /* Reflect in the page header */ | ||
50786 | SyBigEndianPack64(&pSlave->pRaw->zData[2/*Cell offset*/+2/*Free block offset*/],pRaw->pgno); | ||
50787 | pSlave->sHdr.iSlave = pRaw->pgno; | ||
50788 | /* All done */ | ||
50789 | *ppSlave = pNew; | ||
50790 | return UNQLITE_OK; | ||
50791 | fail: | ||
50792 | pEngine->pIo->xPageUnref(pNew->pRaw); /* pNew will be released in this call */ | ||
50793 | return rc; | ||
50794 | |||
50795 | } | ||
50796 | /* | ||
50797 | * Perform a store operation in a slave page. | ||
50798 | */ | ||
50799 | static int lhSlaveStore( | ||
50800 | lhpage *pPage, /* Master page */ | ||
50801 | const void *pKey,sxu32 nKeyLen, /* Payload: key */ | ||
50802 | const void *pData,unqlite_int64 nDataLen, /* Payload: data */ | ||
50803 | sxu32 nHash /* Hash of the key */ | ||
50804 | ) | ||
50805 | { | ||
50806 | lhpage *pSlave; | ||
50807 | int rc; | ||
50808 | /* Find a slave page */ | ||
50809 | rc = lhFindSlavePage(pPage,nKeyLen + nDataLen,0,&pSlave); | ||
50810 | if( rc != UNQLITE_OK ){ | ||
50811 | return rc; | ||
50812 | } | ||
50813 | /* Perform the insertion in the slave page */ | ||
50814 | rc = lhStoreCell(pSlave,pKey,nKeyLen,pData,nDataLen,nHash,1); | ||
50815 | return rc; | ||
50816 | } | ||
50817 | /* | ||
50818 | * Transfer a cell to a new page (either a master or slave). | ||
50819 | */ | ||
50820 | static int lhTransferCell(lhcell *pTarget,lhpage *pPage) | ||
50821 | { | ||
50822 | lhcell *pCell; | ||
50823 | sxu16 nOfft; | ||
50824 | int rc; | ||
50825 | /* Check for a free block to hold a single cell only */ | ||
50826 | rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft); | ||
50827 | if( rc != UNQLITE_OK ){ | ||
50828 | /* Store in a slave page */ | ||
50829 | rc = lhFindSlavePage(pPage,L_HASH_CELL_SZ,&nOfft,&pPage); | ||
50830 | if( rc != UNQLITE_OK ){ | ||
50831 | return rc; | ||
50832 | } | ||
50833 | } | ||
50834 | /* Allocate a new cell instance */ | ||
50835 | pCell = lhNewCell(pPage->pHash,pPage); | ||
50836 | if( pCell == 0 ){ | ||
50837 | return UNQLITE_NOMEM; | ||
50838 | } | ||
50839 | /* Fill-in the structure */ | ||
50840 | pCell->iStart = nOfft; | ||
50841 | pCell->nData = pTarget->nData; | ||
50842 | pCell->nKey = pTarget->nKey; | ||
50843 | pCell->iOvfl = pTarget->iOvfl; | ||
50844 | pCell->iDataOfft = pTarget->iDataOfft; | ||
50845 | pCell->iDataPage = pTarget->iDataPage; | ||
50846 | pCell->nHash = pTarget->nHash; | ||
50847 | SyBlobDup(&pTarget->sKey,&pCell->sKey); | ||
50848 | /* Link the cell */ | ||
50849 | rc = lhInstallCell(pCell); | ||
50850 | if( rc != UNQLITE_OK ){ | ||
50851 | return rc; | ||
50852 | } | ||
50853 | /* Finally, Write the cell header */ | ||
50854 | lhCellWriteHeader(pCell); | ||
50855 | /* All done */ | ||
50856 | return UNQLITE_OK; | ||
50857 | } | ||
50858 | /* | ||
50859 | * Perform a page split. | ||
50860 | */ | ||
50861 | static int lhPageSplit( | ||
50862 | lhpage *pOld, /* Page to be split */ | ||
50863 | lhpage *pNew, /* New page */ | ||
50864 | pgno split_bucket, /* Current split bucket */ | ||
50865 | pgno high_mask /* High mask (Max split bucket - 1) */ | ||
50866 | ) | ||
50867 | { | ||
50868 | lhcell *pCell,*pNext; | ||
50869 | SyBlob sWorker; | ||
50870 | pgno iBucket; | ||
50871 | int rc; | ||
50872 | SyBlobInit(&sWorker,&pOld->pHash->sAllocator); | ||
50873 | /* Perform the split */ | ||
50874 | pCell = pOld->pList; | ||
50875 | for( ;; ){ | ||
50876 | if( pCell == 0 ){ | ||
50877 | /* No more cells */ | ||
50878 | break; | ||
50879 | } | ||
50880 | /* Obtain the new logical bucket */ | ||
50881 | iBucket = pCell->nHash & high_mask; | ||
50882 | pNext = pCell->pNext; | ||
50883 | if( iBucket != split_bucket){ | ||
50884 | rc = UNQLITE_OK; | ||
50885 | if( pCell->iOvfl ){ | ||
50886 | /* Transfer the cell only */ | ||
50887 | rc = lhTransferCell(pCell,pNew); | ||
50888 | }else{ | ||
50889 | /* Transfer the cell and its payload */ | ||
50890 | SyBlobReset(&sWorker); | ||
50891 | if( SyBlobLength(&pCell->sKey) < 1 ){ | ||
50892 | /* Consume the key */ | ||
50893 | rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,0); | ||
50894 | if( rc != UNQLITE_OK ){ | ||
50895 | goto fail; | ||
50896 | } | ||
50897 | } | ||
50898 | /* Consume the data (Very small data < 65k) */ | ||
50899 | rc = lhConsumeCellData(pCell,unqliteDataConsumer,&sWorker); | ||
50900 | if( rc != UNQLITE_OK ){ | ||
50901 | goto fail; | ||
50902 | } | ||
50903 | /* Perform the transfer */ | ||
50904 | rc = lhStoreCell( | ||
50905 | pNew, | ||
50906 | SyBlobData(&pCell->sKey),(int)SyBlobLength(&pCell->sKey), | ||
50907 | SyBlobData(&sWorker),SyBlobLength(&sWorker), | ||
50908 | pCell->nHash, | ||
50909 | 1 | ||
50910 | ); | ||
50911 | } | ||
50912 | if( rc != UNQLITE_OK ){ | ||
50913 | goto fail; | ||
50914 | } | ||
50915 | /* Discard the cell from the old page */ | ||
50916 | lhUnlinkCell(pCell); | ||
50917 | } | ||
50918 | /* Point to the next cell */ | ||
50919 | pCell = pNext; | ||
50920 | } | ||
50921 | /* All done */ | ||
50922 | rc = UNQLITE_OK; | ||
50923 | fail: | ||
50924 | SyBlobRelease(&sWorker); | ||
50925 | return rc; | ||
50926 | } | ||
50927 | /* | ||
50928 | * Perform the infamous linear hash split operation. | ||
50929 | */ | ||
50930 | static int lhSplit(lhpage *pTarget,int *pRetry) | ||
50931 | { | ||
50932 | lhash_kv_engine *pEngine = pTarget->pHash; | ||
50933 | lhash_bmap_rec *pRec; | ||
50934 | lhpage *pOld,*pNew; | ||
50935 | unqlite_page *pRaw; | ||
50936 | int rc; | ||
50937 | /* Get the real page number of the bucket to split */ | ||
50938 | pRec = lhMapFindBucket(pEngine,pEngine->split_bucket); | ||
50939 | if( pRec == 0 ){ | ||
50940 | /* Can't happen */ | ||
50941 | return UNQLITE_CORRUPT; | ||
50942 | } | ||
50943 | /* Load the page to be split */ | ||
50944 | rc = lhLoadPage(pEngine,pRec->iReal,0,&pOld,0); | ||
50945 | if( rc != UNQLITE_OK ){ | ||
50946 | return rc; | ||
50947 | } | ||
50948 | /* Request a new page */ | ||
50949 | rc = lhAcquirePage(pEngine,&pRaw); | ||
50950 | if( rc != UNQLITE_OK ){ | ||
50951 | return rc; | ||
50952 | } | ||
50953 | /* Initialize the page */ | ||
50954 | pNew = lhNewPage(pEngine,pRaw,0); | ||
50955 | if( pNew == 0 ){ | ||
50956 | return UNQLITE_NOMEM; | ||
50957 | } | ||
50958 | /* Mark as an empty page */ | ||
50959 | rc = lhSetEmptyPage(pNew); | ||
50960 | if( rc != UNQLITE_OK ){ | ||
50961 | goto fail; | ||
50962 | } | ||
50963 | /* Install and write the logical map record */ | ||
50964 | rc = lhMapWriteRecord(pEngine, | ||
50965 | pEngine->split_bucket + pEngine->max_split_bucket, | ||
50966 | pRaw->pgno | ||
50967 | ); | ||
50968 | if( rc != UNQLITE_OK ){ | ||
50969 | goto fail; | ||
50970 | } | ||
50971 | if( pTarget->pRaw->pgno == pOld->pRaw->pgno ){ | ||
50972 | *pRetry = 1; | ||
50973 | } | ||
50974 | /* Perform the split */ | ||
50975 | rc = lhPageSplit(pOld,pNew,pEngine->split_bucket,pEngine->nmax_split_nucket - 1); | ||
50976 | if( rc != UNQLITE_OK ){ | ||
50977 | goto fail; | ||
50978 | } | ||
50979 | /* Update the database header */ | ||
50980 | pEngine->split_bucket++; | ||
50981 | /* Acquire a writer lock on the first page */ | ||
50982 | rc = pEngine->pIo->xWrite(pEngine->pHeader); | ||
50983 | if( rc != UNQLITE_OK ){ | ||
50984 | return rc; | ||
50985 | } | ||
50986 | if( pEngine->split_bucket >= pEngine->max_split_bucket ){ | ||
50987 | /* Increment the generation number */ | ||
50988 | pEngine->split_bucket = 0; | ||
50989 | pEngine->max_split_bucket = pEngine->nmax_split_nucket; | ||
50990 | pEngine->nmax_split_nucket <<= 1; | ||
50991 | if( !pEngine->nmax_split_nucket ){ | ||
50992 | /* If this happen to your installation, please tell us <chm@symisc.net> */ | ||
50993 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Database page (64-bit integer) limit reached"); | ||
50994 | return UNQLITE_LIMIT; | ||
50995 | } | ||
50996 | /* Reflect in the page header */ | ||
50997 | SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket); | ||
50998 | SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/+8/*Split bucket*/],pEngine->max_split_bucket); | ||
50999 | }else{ | ||
51000 | /* Modify only the split bucket */ | ||
51001 | SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket); | ||
51002 | } | ||
51003 | /* All done */ | ||
51004 | return UNQLITE_OK; | ||
51005 | fail: | ||
51006 | pEngine->pIo->xPageUnref(pNew->pRaw); | ||
51007 | return rc; | ||
51008 | } | ||
51009 | /* | ||
51010 | * Store a record in the target page. | ||
51011 | */ | ||
51012 | static int lhRecordInstall( | ||
51013 | lhpage *pPage, /* Target page */ | ||
51014 | sxu32 nHash, /* Hash of the key */ | ||
51015 | const void *pKey,sxu32 nKeyLen, /* Payload: Key */ | ||
51016 | const void *pData,unqlite_int64 nDataLen /* Payload: Data */ | ||
51017 | ) | ||
51018 | { | ||
51019 | int rc; | ||
51020 | rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,0); | ||
51021 | if( rc == UNQLITE_FULL ){ | ||
51022 | int do_retry = 0; | ||
51023 | /* Split */ | ||
51024 | rc = lhSplit(pPage,&do_retry); | ||
51025 | if( rc == UNQLITE_OK ){ | ||
51026 | if( do_retry ){ | ||
51027 | /* Re-calculate logical bucket number */ | ||
51028 | return SXERR_RETRY; | ||
51029 | } | ||
51030 | /* Perform the store */ | ||
51031 | rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1); | ||
51032 | } | ||
51033 | } | ||
51034 | return rc; | ||
51035 | } | ||
51036 | /* | ||
51037 | * Insert a record (Either overwrite or append operation) in our database. | ||
51038 | */ | ||
51039 | static int lh_record_insert( | ||
51040 | unqlite_kv_engine *pKv, /* KV store */ | ||
51041 | const void *pKey,sxu32 nKeyLen, /* Payload: Key */ | ||
51042 | const void *pData,unqlite_int64 nDataLen, /* Payload: data */ | ||
51043 | int is_append /* True for an append operation */ | ||
51044 | ) | ||
51045 | { | ||
51046 | lhash_kv_engine *pEngine = (lhash_kv_engine *)pKv; | ||
51047 | lhash_bmap_rec *pRec; | ||
51048 | unqlite_page *pRaw; | ||
51049 | lhpage *pPage; | ||
51050 | lhcell *pCell; | ||
51051 | pgno iBucket; | ||
51052 | sxu32 nHash; | ||
51053 | int iCnt; | ||
51054 | int rc; | ||
51055 | |||
51056 | /* Acquire the first page (DB hash Header) so that everything gets loaded autmatically */ | ||
51057 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); | ||
51058 | if( rc != UNQLITE_OK ){ | ||
51059 | return rc; | ||
51060 | } | ||
51061 | iCnt = 0; | ||
51062 | /* Compute the hash of the key first */ | ||
51063 | nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); | ||
51064 | retry: | ||
51065 | /* Extract the logical bucket number */ | ||
51066 | iBucket = nHash & (pEngine->nmax_split_nucket - 1); | ||
51067 | if( iBucket >= pEngine->split_bucket + pEngine->max_split_bucket ){ | ||
51068 | /* Low mask */ | ||
51069 | iBucket = nHash & (pEngine->max_split_bucket - 1); | ||
51070 | } | ||
51071 | /* Map the logical bucket number to real page number */ | ||
51072 | pRec = lhMapFindBucket(pEngine,iBucket); | ||
51073 | if( pRec == 0 ){ | ||
51074 | /* Request a new page */ | ||
51075 | rc = lhAcquirePage(pEngine,&pRaw); | ||
51076 | if( rc != UNQLITE_OK ){ | ||
51077 | return rc; | ||
51078 | } | ||
51079 | /* Initialize the page */ | ||
51080 | pPage = lhNewPage(pEngine,pRaw,0); | ||
51081 | if( pPage == 0 ){ | ||
51082 | return UNQLITE_NOMEM; | ||
51083 | } | ||
51084 | /* Mark as an empty page */ | ||
51085 | rc = lhSetEmptyPage(pPage); | ||
51086 | if( rc != UNQLITE_OK ){ | ||
51087 | pEngine->pIo->xPageUnref(pRaw); /* pPage will be released during this call */ | ||
51088 | return rc; | ||
51089 | } | ||
51090 | /* Store the cell */ | ||
51091 | rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1); | ||
51092 | if( rc == UNQLITE_OK ){ | ||
51093 | /* Install and write the logical map record */ | ||
51094 | rc = lhMapWriteRecord(pEngine,iBucket,pRaw->pgno); | ||
51095 | } | ||
51096 | pEngine->pIo->xPageUnref(pRaw); | ||
51097 | return rc; | ||
51098 | }else{ | ||
51099 | /* Load the page */ | ||
51100 | rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0); | ||
51101 | if( rc != UNQLITE_OK ){ | ||
51102 | /* IO error, unlikely scenario */ | ||
51103 | return rc; | ||
51104 | } | ||
51105 | /* Do not add this page to the hot dirty list */ | ||
51106 | pEngine->pIo->xDontMkHot(pPage->pRaw); | ||
51107 | /* Lookup for the cell */ | ||
51108 | pCell = lhFindCell(pPage,pKey,(sxu32)nKeyLen,nHash); | ||
51109 | if( pCell == 0 ){ | ||
51110 | /* Create the record */ | ||
51111 | rc = lhRecordInstall(pPage,nHash,pKey,nKeyLen,pData,nDataLen); | ||
51112 | if( rc == SXERR_RETRY && iCnt++ < 2 ){ | ||
51113 | rc = UNQLITE_OK; | ||
51114 | goto retry; | ||
51115 | } | ||
51116 | }else{ | ||
51117 | if( is_append ){ | ||
51118 | /* Append operation */ | ||
51119 | rc = lhRecordAppend(pCell,pData,nDataLen); | ||
51120 | }else{ | ||
51121 | /* Overwrite old value */ | ||
51122 | rc = lhRecordOverwrite(pCell,pData,nDataLen); | ||
51123 | } | ||
51124 | } | ||
51125 | pEngine->pIo->xPageUnref(pPage->pRaw); | ||
51126 | } | ||
51127 | return rc; | ||
51128 | } | ||
51129 | /* | ||
51130 | * Replace method. | ||
51131 | */ | ||
51132 | static int lhash_kv_replace( | ||
51133 | unqlite_kv_engine *pKv, | ||
51134 | const void *pKey,int nKeyLen, | ||
51135 | const void *pData,unqlite_int64 nDataLen | ||
51136 | ) | ||
51137 | { | ||
51138 | int rc; | ||
51139 | rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,0); | ||
51140 | return rc; | ||
51141 | } | ||
51142 | /* | ||
51143 | * Append method. | ||
51144 | */ | ||
51145 | static int lhash_kv_append( | ||
51146 | unqlite_kv_engine *pKv, | ||
51147 | const void *pKey,int nKeyLen, | ||
51148 | const void *pData,unqlite_int64 nDataLen | ||
51149 | ) | ||
51150 | { | ||
51151 | int rc; | ||
51152 | rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,1); | ||
51153 | return rc; | ||
51154 | } | ||
51155 | /* | ||
51156 | * Write the hash header (Page one). | ||
51157 | */ | ||
51158 | static int lhash_write_header(lhash_kv_engine *pEngine,unqlite_page *pHeader) | ||
51159 | { | ||
51160 | unsigned char *zRaw = pHeader->zData; | ||
51161 | lhash_bmap_page *pMap; | ||
51162 | |||
51163 | pEngine->pHeader = pHeader; | ||
51164 | /* 4 byte magic number */ | ||
51165 | SyBigEndianPack32(zRaw,pEngine->nMagic); | ||
51166 | zRaw += 4; | ||
51167 | /* 4 byte hash value to identify a valid hash function */ | ||
51168 | SyBigEndianPack32(zRaw,pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1)); | ||
51169 | zRaw += 4; | ||
51170 | /* List of free pages: Empty */ | ||
51171 | SyBigEndianPack64(zRaw,0); | ||
51172 | zRaw += 8; | ||
51173 | /* Current split bucket */ | ||
51174 | SyBigEndianPack64(zRaw,pEngine->split_bucket); | ||
51175 | zRaw += 8; | ||
51176 | /* Maximum split bucket */ | ||
51177 | SyBigEndianPack64(zRaw,pEngine->max_split_bucket); | ||
51178 | zRaw += 8; | ||
51179 | /* Initialiaze the bucket map */ | ||
51180 | pMap = &pEngine->sPageMap; | ||
51181 | /* Fill in the structure */ | ||
51182 | pMap->iNum = pHeader->pgno; | ||
51183 | /* Next page in the bucket map */ | ||
51184 | SyBigEndianPack64(zRaw,0); | ||
51185 | zRaw += 8; | ||
51186 | /* Total number of records in the bucket map */ | ||
51187 | SyBigEndianPack32(zRaw,0); | ||
51188 | zRaw += 4; | ||
51189 | pMap->iPtr = (sxu16)(zRaw - pHeader->zData); | ||
51190 | /* All done */ | ||
51191 | return UNQLITE_OK; | ||
51192 | } | ||
51193 | /* | ||
51194 | * Exported: xOpen() method. | ||
51195 | */ | ||
51196 | static int lhash_kv_open(unqlite_kv_engine *pEngine,pgno dbSize) | ||
51197 | { | ||
51198 | lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; | ||
51199 | unqlite_page *pHeader; | ||
51200 | int rc; | ||
51201 | if( dbSize < 1 ){ | ||
51202 | /* A new database, create the header */ | ||
51203 | rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pHeader); | ||
51204 | if( rc != UNQLITE_OK ){ | ||
51205 | return rc; | ||
51206 | } | ||
51207 | /* Acquire a writer lock */ | ||
51208 | rc = pEngine->pIo->xWrite(pHeader); | ||
51209 | if( rc != UNQLITE_OK ){ | ||
51210 | return rc; | ||
51211 | } | ||
51212 | /* Write the hash header */ | ||
51213 | rc = lhash_write_header(pHash,pHeader); | ||
51214 | if( rc != UNQLITE_OK ){ | ||
51215 | return rc; | ||
51216 | } | ||
51217 | }else{ | ||
51218 | /* Acquire the page one of the database */ | ||
51219 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,&pHeader); | ||
51220 | if( rc != UNQLITE_OK ){ | ||
51221 | return rc; | ||
51222 | } | ||
51223 | /* Read the database header */ | ||
51224 | rc = lhash_read_header(pHash,pHeader); | ||
51225 | if( rc != UNQLITE_OK ){ | ||
51226 | return rc; | ||
51227 | } | ||
51228 | } | ||
51229 | return UNQLITE_OK; | ||
51230 | } | ||
51231 | /* | ||
51232 | * Release a master or slave page. (xUnpin callback). | ||
51233 | */ | ||
51234 | static void lhash_page_release(void *pUserData) | ||
51235 | { | ||
51236 | lhpage *pPage = (lhpage *)pUserData; | ||
51237 | lhash_kv_engine *pEngine = pPage->pHash; | ||
51238 | lhcell *pNext,*pCell = pPage->pList; | ||
51239 | unqlite_page *pRaw = pPage->pRaw; | ||
51240 | sxu32 n; | ||
51241 | /* Drop in-memory cells */ | ||
51242 | for( n = 0 ; n < pPage->nCell ; ++n ){ | ||
51243 | pNext = pCell->pNext; | ||
51244 | SyBlobRelease(&pCell->sKey); | ||
51245 | /* Release the cell instance */ | ||
51246 | SyMemBackendPoolFree(&pEngine->sAllocator,(void *)pCell); | ||
51247 | /* Point to the next entry */ | ||
51248 | pCell = pNext; | ||
51249 | } | ||
51250 | if( pPage->apCell ){ | ||
51251 | /* Release the cell table */ | ||
51252 | SyMemBackendFree(&pEngine->sAllocator,(void *)pPage->apCell); | ||
51253 | } | ||
51254 | /* Finally, release the whole page */ | ||
51255 | SyMemBackendPoolFree(&pEngine->sAllocator,pPage); | ||
51256 | pRaw->pUserData = 0; | ||
51257 | } | ||
51258 | /* | ||
51259 | * Default hash function (DJB). | ||
51260 | */ | ||
51261 | static sxu32 lhash_bin_hash(const void *pSrc,sxu32 nLen) | ||
51262 | { | ||
51263 | register unsigned char *zIn = (unsigned char *)pSrc; | ||
51264 | unsigned char *zEnd; | ||
51265 | sxu32 nH = 5381; | ||
51266 | if( nLen > 2048 /* 2K */ ){ | ||
51267 | nLen = 2048; | ||
51268 | } | ||
51269 | zEnd = &zIn[nLen]; | ||
51270 | for(;;){ | ||
51271 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
51272 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
51273 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
51274 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
51275 | } | ||
51276 | return nH; | ||
51277 | } | ||
51278 | /* | ||
51279 | * Exported: xInit() method. | ||
51280 | * Initialize the Key value storage engine. | ||
51281 | */ | ||
51282 | static int lhash_kv_init(unqlite_kv_engine *pEngine,int iPageSize) | ||
51283 | { | ||
51284 | lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; | ||
51285 | int rc; | ||
51286 | |||
51287 | /* This structure is always zeroed, go to the initialization directly */ | ||
51288 | SyMemBackendInitFromParent(&pHash->sAllocator,unqliteExportMemBackend()); | ||
51289 | #if defined(UNQLITE_ENABLE_THREADS) | ||
51290 | /* Already protected by the upper layers */ | ||
51291 | SyMemBackendDisbaleMutexing(&pHash->sAllocator); | ||
51292 | #endif | ||
51293 | pHash->iPageSize = iPageSize; | ||
51294 | /* Default hash function */ | ||
51295 | pHash->xHash = lhash_bin_hash; | ||
51296 | /* Default comparison function */ | ||
51297 | pHash->xCmp = SyMemcmp; | ||
51298 | /* Allocate a new record map */ | ||
51299 | pHash->nBuckSize = 32; | ||
51300 | pHash->apMap = (lhash_bmap_rec **)SyMemBackendAlloc(&pHash->sAllocator,pHash->nBuckSize *sizeof(lhash_bmap_rec *)); | ||
51301 | if( pHash->apMap == 0 ){ | ||
51302 | rc = UNQLITE_NOMEM; | ||
51303 | goto err; | ||
51304 | } | ||
51305 | /* Zero the table */ | ||
51306 | SyZero(pHash->apMap,pHash->nBuckSize * sizeof(lhash_bmap_rec *)); | ||
51307 | /* Linear hashing components */ | ||
51308 | pHash->split_bucket = 0; /* Logical not real bucket number */ | ||
51309 | pHash->max_split_bucket = 1; | ||
51310 | pHash->nmax_split_nucket = 2; | ||
51311 | pHash->nMagic = L_HASH_MAGIC; | ||
51312 | /* Install the cache unpin and reload callbacks */ | ||
51313 | pHash->pIo->xSetUnpin(pHash->pIo->pHandle,lhash_page_release); | ||
51314 | pHash->pIo->xSetReload(pHash->pIo->pHandle,lhash_page_release); | ||
51315 | return UNQLITE_OK; | ||
51316 | err: | ||
51317 | SyMemBackendRelease(&pHash->sAllocator); | ||
51318 | return rc; | ||
51319 | } | ||
51320 | /* | ||
51321 | * Exported: xRelease() method. | ||
51322 | * Release the Key value storage engine. | ||
51323 | */ | ||
51324 | static void lhash_kv_release(unqlite_kv_engine *pEngine) | ||
51325 | { | ||
51326 | lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; | ||
51327 | /* Release the private memory backend */ | ||
51328 | SyMemBackendRelease(&pHash->sAllocator); | ||
51329 | } | ||
51330 | /* | ||
51331 | * Exported: xConfig() method. | ||
51332 | * Configure the linear hash KV store. | ||
51333 | */ | ||
51334 | static int lhash_kv_config(unqlite_kv_engine *pEngine,int op,va_list ap) | ||
51335 | { | ||
51336 | lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; | ||
51337 | int rc = UNQLITE_OK; | ||
51338 | switch(op){ | ||
51339 | case UNQLITE_KV_CONFIG_HASH_FUNC: { | ||
51340 | /* Default hash function */ | ||
51341 | if( pHash->nBuckRec > 0 ){ | ||
51342 | /* Locked operation */ | ||
51343 | rc = UNQLITE_LOCKED; | ||
51344 | }else{ | ||
51345 | ProcHash xHash = va_arg(ap,ProcHash); | ||
51346 | if( xHash ){ | ||
51347 | pHash->xHash = xHash; | ||
51348 | } | ||
51349 | } | ||
51350 | break; | ||
51351 | } | ||
51352 | case UNQLITE_KV_CONFIG_CMP_FUNC: { | ||
51353 | /* Default comparison function */ | ||
51354 | ProcCmp xCmp = va_arg(ap,ProcCmp); | ||
51355 | if( xCmp ){ | ||
51356 | pHash->xCmp = xCmp; | ||
51357 | } | ||
51358 | break; | ||
51359 | } | ||
51360 | default: | ||
51361 | /* Unknown OP */ | ||
51362 | rc = UNQLITE_UNKNOWN; | ||
51363 | break; | ||
51364 | } | ||
51365 | return rc; | ||
51366 | } | ||
51367 | /* | ||
51368 | * Each public cursor is identified by an instance of this structure. | ||
51369 | */ | ||
51370 | typedef struct lhash_kv_cursor lhash_kv_cursor; | ||
51371 | struct lhash_kv_cursor | ||
51372 | { | ||
51373 | unqlite_kv_engine *pStore; /* Must be first */ | ||
51374 | /* Private fields */ | ||
51375 | int iState; /* Current state of the cursor */ | ||
51376 | int is_first; /* True to read the database header */ | ||
51377 | lhcell *pCell; /* Current cell we are processing */ | ||
51378 | unqlite_page *pRaw; /* Raw disk page */ | ||
51379 | lhash_bmap_rec *pRec; /* Logical to real bucket map */ | ||
51380 | }; | ||
51381 | /* | ||
51382 | * Possible state of the cursor | ||
51383 | */ | ||
51384 | #define L_HASH_CURSOR_STATE_NEXT_PAGE 1 /* Next page in the list */ | ||
51385 | #define L_HASH_CURSOR_STATE_CELL 2 /* Processing Cell */ | ||
51386 | #define L_HASH_CURSOR_STATE_DONE 3 /* Cursor does not point to anything */ | ||
51387 | /* | ||
51388 | * Initialize the cursor. | ||
51389 | */ | ||
51390 | static void lhInitCursor(unqlite_kv_cursor *pPtr) | ||
51391 | { | ||
51392 | lhash_kv_engine *pEngine = (lhash_kv_engine *)pPtr->pStore; | ||
51393 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; | ||
51394 | /* Init */ | ||
51395 | pCur->iState = L_HASH_CURSOR_STATE_NEXT_PAGE; | ||
51396 | pCur->pCell = 0; | ||
51397 | pCur->pRec = pEngine->pFirst; | ||
51398 | pCur->pRaw = 0; | ||
51399 | pCur->is_first = 1; | ||
51400 | } | ||
51401 | /* | ||
51402 | * Point to the next page on the database. | ||
51403 | */ | ||
51404 | static int lhCursorNextPage(lhash_kv_cursor *pPtr) | ||
51405 | { | ||
51406 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; | ||
51407 | lhash_bmap_rec *pRec; | ||
51408 | lhpage *pPage; | ||
51409 | int rc; | ||
51410 | for(;;){ | ||
51411 | pRec = pCur->pRec; | ||
51412 | if( pRec == 0 ){ | ||
51413 | pCur->iState = L_HASH_CURSOR_STATE_DONE; | ||
51414 | return UNQLITE_DONE; | ||
51415 | } | ||
51416 | if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){ | ||
51417 | /* Unref this page */ | ||
51418 | pCur->pStore->pIo->xPageUnref(pPtr->pRaw); | ||
51419 | pPtr->pRaw = 0; | ||
51420 | } | ||
51421 | /* Advance the map cursor */ | ||
51422 | pCur->pRec = pRec->pPrev; /* Not a bug, reverse link */ | ||
51423 | /* Load the next page on the list */ | ||
51424 | rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0); | ||
51425 | if( rc != UNQLITE_OK ){ | ||
51426 | return rc; | ||
51427 | } | ||
51428 | if( pPage->pList ){ | ||
51429 | /* Reflect the change */ | ||
51430 | pCur->pCell = pPage->pList; | ||
51431 | pCur->iState = L_HASH_CURSOR_STATE_CELL; | ||
51432 | pCur->pRaw = pPage->pRaw; | ||
51433 | break; | ||
51434 | } | ||
51435 | /* Empty page, discard this page and continue */ | ||
51436 | pPage->pHash->pIo->xPageUnref(pPage->pRaw); | ||
51437 | } | ||
51438 | return UNQLITE_OK; | ||
51439 | } | ||
51440 | /* | ||
51441 | * Point to the previous page on the database. | ||
51442 | */ | ||
51443 | static int lhCursorPrevPage(lhash_kv_cursor *pPtr) | ||
51444 | { | ||
51445 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; | ||
51446 | lhash_bmap_rec *pRec; | ||
51447 | lhpage *pPage; | ||
51448 | int rc; | ||
51449 | for(;;){ | ||
51450 | pRec = pCur->pRec; | ||
51451 | if( pRec == 0 ){ | ||
51452 | pCur->iState = L_HASH_CURSOR_STATE_DONE; | ||
51453 | return UNQLITE_DONE; | ||
51454 | } | ||
51455 | if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){ | ||
51456 | /* Unref this page */ | ||
51457 | pCur->pStore->pIo->xPageUnref(pPtr->pRaw); | ||
51458 | pPtr->pRaw = 0; | ||
51459 | } | ||
51460 | /* Advance the map cursor */ | ||
51461 | pCur->pRec = pRec->pNext; /* Not a bug, reverse link */ | ||
51462 | /* Load the previous page on the list */ | ||
51463 | rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0); | ||
51464 | if( rc != UNQLITE_OK ){ | ||
51465 | return rc; | ||
51466 | } | ||
51467 | if( pPage->pFirst ){ | ||
51468 | /* Reflect the change */ | ||
51469 | pCur->pCell = pPage->pFirst; | ||
51470 | pCur->iState = L_HASH_CURSOR_STATE_CELL; | ||
51471 | pCur->pRaw = pPage->pRaw; | ||
51472 | break; | ||
51473 | } | ||
51474 | /* Discard this page and continue */ | ||
51475 | pPage->pHash->pIo->xPageUnref(pPage->pRaw); | ||
51476 | } | ||
51477 | return UNQLITE_OK; | ||
51478 | } | ||
51479 | /* | ||
51480 | * Is a valid cursor. | ||
51481 | */ | ||
51482 | static int lhCursorValid(unqlite_kv_cursor *pPtr) | ||
51483 | { | ||
51484 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; | ||
51485 | return (pCur->iState == L_HASH_CURSOR_STATE_CELL) && pCur->pCell; | ||
51486 | } | ||
51487 | /* | ||
51488 | * Point to the first record. | ||
51489 | */ | ||
51490 | static int lhCursorFirst(unqlite_kv_cursor *pCursor) | ||
51491 | { | ||
51492 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51493 | lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore; | ||
51494 | int rc; | ||
51495 | if( pCur->is_first ){ | ||
51496 | /* Read the database header first */ | ||
51497 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); | ||
51498 | if( rc != UNQLITE_OK ){ | ||
51499 | return rc; | ||
51500 | } | ||
51501 | pCur->is_first = 0; | ||
51502 | } | ||
51503 | /* Point to the first map record */ | ||
51504 | pCur->pRec = pEngine->pFirst; | ||
51505 | /* Load the cells */ | ||
51506 | rc = lhCursorNextPage(pCur); | ||
51507 | return rc; | ||
51508 | } | ||
51509 | /* | ||
51510 | * Point to the last record. | ||
51511 | */ | ||
51512 | static int lhCursorLast(unqlite_kv_cursor *pCursor) | ||
51513 | { | ||
51514 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51515 | lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore; | ||
51516 | int rc; | ||
51517 | if( pCur->is_first ){ | ||
51518 | /* Read the database header first */ | ||
51519 | rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); | ||
51520 | if( rc != UNQLITE_OK ){ | ||
51521 | return rc; | ||
51522 | } | ||
51523 | pCur->is_first = 0; | ||
51524 | } | ||
51525 | /* Point to the last map record */ | ||
51526 | pCur->pRec = pEngine->pList; | ||
51527 | /* Load the cells */ | ||
51528 | rc = lhCursorPrevPage(pCur); | ||
51529 | return rc; | ||
51530 | } | ||
51531 | /* | ||
51532 | * Reset the cursor. | ||
51533 | */ | ||
51534 | static void lhCursorReset(unqlite_kv_cursor *pCursor) | ||
51535 | { | ||
51536 | lhCursorFirst(pCursor); | ||
51537 | } | ||
51538 | /* | ||
51539 | * Point to the next record. | ||
51540 | */ | ||
51541 | static int lhCursorNext(unqlite_kv_cursor *pCursor) | ||
51542 | { | ||
51543 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51544 | lhcell *pCell; | ||
51545 | int rc; | ||
51546 | if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ | ||
51547 | /* Load the cells of the next page */ | ||
51548 | rc = lhCursorNextPage(pCur); | ||
51549 | return rc; | ||
51550 | } | ||
51551 | pCell = pCur->pCell; | ||
51552 | pCur->pCell = pCell->pNext; | ||
51553 | if( pCur->pCell == 0 ){ | ||
51554 | /* Load the cells of the next page */ | ||
51555 | rc = lhCursorNextPage(pCur); | ||
51556 | return rc; | ||
51557 | } | ||
51558 | return UNQLITE_OK; | ||
51559 | } | ||
51560 | /* | ||
51561 | * Point to the previous record. | ||
51562 | */ | ||
51563 | static int lhCursorPrev(unqlite_kv_cursor *pCursor) | ||
51564 | { | ||
51565 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51566 | lhcell *pCell; | ||
51567 | int rc; | ||
51568 | if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ | ||
51569 | /* Load the cells of the previous page */ | ||
51570 | rc = lhCursorPrevPage(pCur); | ||
51571 | return rc; | ||
51572 | } | ||
51573 | pCell = pCur->pCell; | ||
51574 | pCur->pCell = pCell->pPrev; | ||
51575 | if( pCur->pCell == 0 ){ | ||
51576 | /* Load the cells of the previous page */ | ||
51577 | rc = lhCursorPrevPage(pCur); | ||
51578 | return rc; | ||
51579 | } | ||
51580 | return UNQLITE_OK; | ||
51581 | } | ||
51582 | /* | ||
51583 | * Return key length. | ||
51584 | */ | ||
51585 | static int lhCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) | ||
51586 | { | ||
51587 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51588 | lhcell *pCell; | ||
51589 | |||
51590 | if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ | ||
51591 | /* Invalid state */ | ||
51592 | return UNQLITE_INVALID; | ||
51593 | } | ||
51594 | /* Point to the target cell */ | ||
51595 | pCell = pCur->pCell; | ||
51596 | /* Return key length */ | ||
51597 | *pLen = (int)pCell->nKey; | ||
51598 | return UNQLITE_OK; | ||
51599 | } | ||
51600 | /* | ||
51601 | * Return data length. | ||
51602 | */ | ||
51603 | static int lhCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) | ||
51604 | { | ||
51605 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51606 | lhcell *pCell; | ||
51607 | |||
51608 | if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ | ||
51609 | /* Invalid state */ | ||
51610 | return UNQLITE_INVALID; | ||
51611 | } | ||
51612 | /* Point to the target cell */ | ||
51613 | pCell = pCur->pCell; | ||
51614 | /* Return data length */ | ||
51615 | *pLen = (unqlite_int64)pCell->nData; | ||
51616 | return UNQLITE_OK; | ||
51617 | } | ||
51618 | /* | ||
51619 | * Consume the key. | ||
51620 | */ | ||
51621 | static int lhCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
51622 | { | ||
51623 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51624 | lhcell *pCell; | ||
51625 | int rc; | ||
51626 | if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ | ||
51627 | /* Invalid state */ | ||
51628 | return UNQLITE_INVALID; | ||
51629 | } | ||
51630 | /* Point to the target cell */ | ||
51631 | pCell = pCur->pCell; | ||
51632 | if( SyBlobLength(&pCell->sKey) > 0 ){ | ||
51633 | /* Consume the key directly */ | ||
51634 | rc = xConsumer(SyBlobData(&pCell->sKey),SyBlobLength(&pCell->sKey),pUserData); | ||
51635 | }else{ | ||
51636 | /* Very large key */ | ||
51637 | rc = lhConsumeCellkey(pCell,xConsumer,pUserData,0); | ||
51638 | } | ||
51639 | return rc; | ||
51640 | } | ||
51641 | /* | ||
51642 | * Consume the data. | ||
51643 | */ | ||
51644 | static int lhCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
51645 | { | ||
51646 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51647 | lhcell *pCell; | ||
51648 | int rc; | ||
51649 | if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ | ||
51650 | /* Invalid state */ | ||
51651 | return UNQLITE_INVALID; | ||
51652 | } | ||
51653 | /* Point to the target cell */ | ||
51654 | pCell = pCur->pCell; | ||
51655 | /* Consume the data */ | ||
51656 | rc = lhConsumeCellData(pCell,xConsumer,pUserData); | ||
51657 | return rc; | ||
51658 | } | ||
51659 | /* | ||
51660 | * Find a partiuclar record. | ||
51661 | */ | ||
51662 | static int lhCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) | ||
51663 | { | ||
51664 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51665 | int rc; | ||
51666 | /* Perform a lookup */ | ||
51667 | rc = lhRecordLookup((lhash_kv_engine *)pCur->pStore,pKey,nByte,&pCur->pCell); | ||
51668 | if( rc != UNQLITE_OK ){ | ||
51669 | SXUNUSED(iPos); | ||
51670 | pCur->pCell = 0; | ||
51671 | pCur->iState = L_HASH_CURSOR_STATE_DONE; | ||
51672 | return rc; | ||
51673 | } | ||
51674 | pCur->iState = L_HASH_CURSOR_STATE_CELL; | ||
51675 | return UNQLITE_OK; | ||
51676 | } | ||
51677 | /* | ||
51678 | * Remove a particular record. | ||
51679 | */ | ||
51680 | static int lhCursorDelete(unqlite_kv_cursor *pCursor) | ||
51681 | { | ||
51682 | lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; | ||
51683 | lhcell *pCell; | ||
51684 | int rc; | ||
51685 | if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ | ||
51686 | /* Invalid state */ | ||
51687 | return UNQLITE_INVALID; | ||
51688 | } | ||
51689 | /* Point to the target cell */ | ||
51690 | pCell = pCur->pCell; | ||
51691 | /* Point to the next entry */ | ||
51692 | pCur->pCell = pCell->pNext; | ||
51693 | /* Perform the deletion */ | ||
51694 | rc = lhRecordRemove(pCell); | ||
51695 | return rc; | ||
51696 | } | ||
51697 | /* | ||
51698 | * Export the linear-hash storage engine. | ||
51699 | */ | ||
51700 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void) | ||
51701 | { | ||
51702 | static const unqlite_kv_methods sDiskStore = { | ||
51703 | "hash", /* zName */ | ||
51704 | sizeof(lhash_kv_engine), /* szKv */ | ||
51705 | sizeof(lhash_kv_cursor), /* szCursor */ | ||
51706 | 1, /* iVersion */ | ||
51707 | lhash_kv_init, /* xInit */ | ||
51708 | lhash_kv_release, /* xRelease */ | ||
51709 | lhash_kv_config, /* xConfig */ | ||
51710 | lhash_kv_open, /* xOpen */ | ||
51711 | lhash_kv_replace, /* xReplace */ | ||
51712 | lhash_kv_append, /* xAppend */ | ||
51713 | lhInitCursor, /* xCursorInit */ | ||
51714 | lhCursorSeek, /* xSeek */ | ||
51715 | lhCursorFirst, /* xFirst */ | ||
51716 | lhCursorLast, /* xLast */ | ||
51717 | lhCursorValid, /* xValid */ | ||
51718 | lhCursorNext, /* xNext */ | ||
51719 | lhCursorPrev, /* xPrev */ | ||
51720 | lhCursorDelete, /* xDelete */ | ||
51721 | lhCursorKeyLength, /* xKeyLength */ | ||
51722 | lhCursorKey, /* xKey */ | ||
51723 | lhCursorDataLength, /* xDataLength */ | ||
51724 | lhCursorData, /* xData */ | ||
51725 | lhCursorReset, /* xReset */ | ||
51726 | 0 /* xRelease */ | ||
51727 | }; | ||
51728 | return &sDiskStore; | ||
51729 | } | ||
51730 | /* | ||
51731 | * ---------------------------------------------------------- | ||
51732 | * File: mem_kv.c | ||
51733 | * MD5: 32e2610c95f53038114d9566f0d0489e | ||
51734 | * ---------------------------------------------------------- | ||
51735 | */ | ||
51736 | /* | ||
51737 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
51738 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
51739 | * Version 1.1.6 | ||
51740 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
51741 | * please contact Symisc Systems via: | ||
51742 | * legal@symisc.net | ||
51743 | * licensing@symisc.net | ||
51744 | * contact@symisc.net | ||
51745 | * or visit: | ||
51746 | * http://unqlite.org/licensing.html | ||
51747 | */ | ||
51748 | /* $SymiscID: mem_kv.c v1.7 Win7 2012-11-28 01:41 stable <chm@symisc.net> $ */ | ||
51749 | #ifndef UNQLITE_AMALGAMATION | ||
51750 | #include "unqliteInt.h" | ||
51751 | #endif | ||
51752 | /* | ||
51753 | * This file implements an in-memory key value storage engine for unQLite. | ||
51754 | * Note that this storage engine does not support transactions. | ||
51755 | * | ||
51756 | * Normaly, I (chm@symisc.net) planned to implement a red-black tree | ||
51757 | * which is suitable for this kind of operation, but due to the lack | ||
51758 | * of time, I decided to implement a tunned hashtable which everybody | ||
51759 | * know works very well for this kind of operation. | ||
51760 | * Again, I insist on a red-black tree implementation for future version | ||
51761 | * of Unqlite. | ||
51762 | */ | ||
51763 | /* Forward declaration */ | ||
51764 | typedef struct mem_hash_kv_engine mem_hash_kv_engine; | ||
51765 | /* | ||
51766 | * Each record is storead in an instance of the following structure. | ||
51767 | */ | ||
51768 | typedef struct mem_hash_record mem_hash_record; | ||
51769 | struct mem_hash_record | ||
51770 | { | ||
51771 | mem_hash_kv_engine *pEngine; /* Storage engine */ | ||
51772 | sxu32 nHash; /* Hash of the key */ | ||
51773 | const void *pKey; /* Key */ | ||
51774 | sxu32 nKeyLen; /* Key size (Max 1GB) */ | ||
51775 | const void *pData; /* Data */ | ||
51776 | sxu32 nDataLen; /* Data length (Max 4GB) */ | ||
51777 | mem_hash_record *pNext,*pPrev; /* Link to other records */ | ||
51778 | mem_hash_record *pNextHash,*pPrevHash; /* Collision link */ | ||
51779 | }; | ||
51780 | /* | ||
51781 | * Each in-memory KV engine is represented by an instance | ||
51782 | * of the following structure. | ||
51783 | */ | ||
51784 | struct mem_hash_kv_engine | ||
51785 | { | ||
51786 | const unqlite_kv_io *pIo; /* IO methods: MUST be first */ | ||
51787 | /* Private data */ | ||
51788 | SyMemBackend sAlloc; /* Private memory allocator */ | ||
51789 | ProcHash xHash; /* Default hash function */ | ||
51790 | ProcCmp xCmp; /* Default comparison function */ | ||
51791 | sxu32 nRecord; /* Total number of records */ | ||
51792 | sxu32 nBucket; /* Bucket size: Must be a power of two */ | ||
51793 | mem_hash_record **apBucket; /* Hash bucket */ | ||
51794 | mem_hash_record *pFirst; /* First inserted entry */ | ||
51795 | mem_hash_record *pLast; /* Last inserted entry */ | ||
51796 | }; | ||
51797 | /* | ||
51798 | * Allocate a new hash record. | ||
51799 | */ | ||
51800 | static mem_hash_record * MemHashNewRecord( | ||
51801 | mem_hash_kv_engine *pEngine, | ||
51802 | const void *pKey,int nKey, | ||
51803 | const void *pData,unqlite_int64 nData, | ||
51804 | sxu32 nHash | ||
51805 | ) | ||
51806 | { | ||
51807 | SyMemBackend *pAlloc = &pEngine->sAlloc; | ||
51808 | mem_hash_record *pRecord; | ||
51809 | void *pDupData; | ||
51810 | sxu32 nByte; | ||
51811 | char *zPtr; | ||
51812 | |||
51813 | /* Total number of bytes to alloc */ | ||
51814 | nByte = sizeof(mem_hash_record) + nKey; | ||
51815 | /* Allocate a new instance */ | ||
51816 | pRecord = (mem_hash_record *)SyMemBackendAlloc(pAlloc,nByte); | ||
51817 | if( pRecord == 0 ){ | ||
51818 | return 0; | ||
51819 | } | ||
51820 | pDupData = (void *)SyMemBackendAlloc(pAlloc,(sxu32)nData); | ||
51821 | if( pDupData == 0 ){ | ||
51822 | SyMemBackendFree(pAlloc,pRecord); | ||
51823 | return 0; | ||
51824 | } | ||
51825 | zPtr = (char *)pRecord; | ||
51826 | zPtr += sizeof(mem_hash_record); | ||
51827 | /* Zero the structure */ | ||
51828 | SyZero(pRecord,sizeof(mem_hash_record)); | ||
51829 | /* Fill in the structure */ | ||
51830 | pRecord->pEngine = pEngine; | ||
51831 | pRecord->nDataLen = (sxu32)nData; | ||
51832 | pRecord->nKeyLen = (sxu32)nKey; | ||
51833 | pRecord->nHash = nHash; | ||
51834 | SyMemcpy(pKey,zPtr,pRecord->nKeyLen); | ||
51835 | pRecord->pKey = (const void *)zPtr; | ||
51836 | SyMemcpy(pData,pDupData,pRecord->nDataLen); | ||
51837 | pRecord->pData = pDupData; | ||
51838 | /* All done */ | ||
51839 | return pRecord; | ||
51840 | } | ||
51841 | /* | ||
51842 | * Install a given record in the hashtable. | ||
51843 | */ | ||
51844 | static void MemHashLinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pRecord) | ||
51845 | { | ||
51846 | sxu32 nBucket = pRecord->nHash & (pEngine->nBucket - 1); | ||
51847 | pRecord->pNextHash = pEngine->apBucket[nBucket]; | ||
51848 | if( pEngine->apBucket[nBucket] ){ | ||
51849 | pEngine->apBucket[nBucket]->pPrevHash = pRecord; | ||
51850 | } | ||
51851 | pEngine->apBucket[nBucket] = pRecord; | ||
51852 | if( pEngine->pFirst == 0 ){ | ||
51853 | pEngine->pFirst = pEngine->pLast = pRecord; | ||
51854 | }else{ | ||
51855 | MACRO_LD_PUSH(pEngine->pLast,pRecord); | ||
51856 | } | ||
51857 | pEngine->nRecord++; | ||
51858 | } | ||
51859 | /* | ||
51860 | * Unlink a given record from the hashtable. | ||
51861 | */ | ||
51862 | static void MemHashUnlinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pEntry) | ||
51863 | { | ||
51864 | sxu32 nBucket = pEntry->nHash & (pEngine->nBucket - 1); | ||
51865 | SyMemBackend *pAlloc = &pEngine->sAlloc; | ||
51866 | if( pEntry->pPrevHash == 0 ){ | ||
51867 | pEngine->apBucket[nBucket] = pEntry->pNextHash; | ||
51868 | }else{ | ||
51869 | pEntry->pPrevHash->pNextHash = pEntry->pNextHash; | ||
51870 | } | ||
51871 | if( pEntry->pNextHash ){ | ||
51872 | pEntry->pNextHash->pPrevHash = pEntry->pPrevHash; | ||
51873 | } | ||
51874 | MACRO_LD_REMOVE(pEngine->pLast,pEntry); | ||
51875 | if( pEntry == pEngine->pFirst ){ | ||
51876 | pEngine->pFirst = pEntry->pPrev; | ||
51877 | } | ||
51878 | pEngine->nRecord--; | ||
51879 | /* Release the entry */ | ||
51880 | SyMemBackendFree(pAlloc,(void *)pEntry->pData); | ||
51881 | SyMemBackendFree(pAlloc,pEntry); /* Key is also stored here */ | ||
51882 | } | ||
51883 | /* | ||
51884 | * Perform a lookup for a given entry. | ||
51885 | */ | ||
51886 | static mem_hash_record * MemHashGetEntry( | ||
51887 | mem_hash_kv_engine *pEngine, | ||
51888 | const void *pKey,int nKeyLen | ||
51889 | ) | ||
51890 | { | ||
51891 | mem_hash_record *pEntry; | ||
51892 | sxu32 nHash,nBucket; | ||
51893 | /* Hash the entry */ | ||
51894 | nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); | ||
51895 | nBucket = nHash & (pEngine->nBucket - 1); | ||
51896 | pEntry = pEngine->apBucket[nBucket]; | ||
51897 | for(;;){ | ||
51898 | if( pEntry == 0 ){ | ||
51899 | break; | ||
51900 | } | ||
51901 | if( pEntry->nHash == nHash && pEntry->nKeyLen == (sxu32)nKeyLen && | ||
51902 | pEngine->xCmp(pEntry->pKey,pKey,pEntry->nKeyLen) == 0 ){ | ||
51903 | return pEntry; | ||
51904 | } | ||
51905 | pEntry = pEntry->pNextHash; | ||
51906 | } | ||
51907 | /* No such entry */ | ||
51908 | return 0; | ||
51909 | } | ||
51910 | /* | ||
51911 | * Rehash all the entries in the given table. | ||
51912 | */ | ||
51913 | static int MemHashGrowTable(mem_hash_kv_engine *pEngine) | ||
51914 | { | ||
51915 | sxu32 nNewSize = pEngine->nBucket << 1; | ||
51916 | mem_hash_record *pEntry; | ||
51917 | mem_hash_record **apNew; | ||
51918 | sxu32 n,iBucket; | ||
51919 | /* Allocate a new larger table */ | ||
51920 | apNew = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc, nNewSize * sizeof(mem_hash_record *)); | ||
51921 | if( apNew == 0 ){ | ||
51922 | /* Not so fatal, simply a performance hit */ | ||
51923 | return UNQLITE_OK; | ||
51924 | } | ||
51925 | /* Zero the new table */ | ||
51926 | SyZero((void *)apNew, nNewSize * sizeof(mem_hash_record *)); | ||
51927 | /* Rehash all entries */ | ||
51928 | n = 0; | ||
51929 | pEntry = pEngine->pLast; | ||
51930 | for(;;){ | ||
51931 | |||
51932 | /* Loop one */ | ||
51933 | if( n >= pEngine->nRecord ){ | ||
51934 | break; | ||
51935 | } | ||
51936 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
51937 | /* Install in the new bucket */ | ||
51938 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
51939 | pEntry->pNextHash = apNew[iBucket]; | ||
51940 | if( apNew[iBucket] ){ | ||
51941 | apNew[iBucket]->pPrevHash = pEntry; | ||
51942 | } | ||
51943 | apNew[iBucket] = pEntry; | ||
51944 | /* Point to the next entry */ | ||
51945 | pEntry = pEntry->pNext; | ||
51946 | n++; | ||
51947 | |||
51948 | /* Loop two */ | ||
51949 | if( n >= pEngine->nRecord ){ | ||
51950 | break; | ||
51951 | } | ||
51952 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
51953 | /* Install in the new bucket */ | ||
51954 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
51955 | pEntry->pNextHash = apNew[iBucket]; | ||
51956 | if( apNew[iBucket] ){ | ||
51957 | apNew[iBucket]->pPrevHash = pEntry; | ||
51958 | } | ||
51959 | apNew[iBucket] = pEntry; | ||
51960 | /* Point to the next entry */ | ||
51961 | pEntry = pEntry->pNext; | ||
51962 | n++; | ||
51963 | |||
51964 | /* Loop three */ | ||
51965 | if( n >= pEngine->nRecord ){ | ||
51966 | break; | ||
51967 | } | ||
51968 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
51969 | /* Install in the new bucket */ | ||
51970 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
51971 | pEntry->pNextHash = apNew[iBucket]; | ||
51972 | if( apNew[iBucket] ){ | ||
51973 | apNew[iBucket]->pPrevHash = pEntry; | ||
51974 | } | ||
51975 | apNew[iBucket] = pEntry; | ||
51976 | /* Point to the next entry */ | ||
51977 | pEntry = pEntry->pNext; | ||
51978 | n++; | ||
51979 | |||
51980 | /* Loop four */ | ||
51981 | if( n >= pEngine->nRecord ){ | ||
51982 | break; | ||
51983 | } | ||
51984 | pEntry->pNextHash = pEntry->pPrevHash = 0; | ||
51985 | /* Install in the new bucket */ | ||
51986 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
51987 | pEntry->pNextHash = apNew[iBucket]; | ||
51988 | if( apNew[iBucket] ){ | ||
51989 | apNew[iBucket]->pPrevHash = pEntry; | ||
51990 | } | ||
51991 | apNew[iBucket] = pEntry; | ||
51992 | /* Point to the next entry */ | ||
51993 | pEntry = pEntry->pNext; | ||
51994 | n++; | ||
51995 | } | ||
51996 | /* Release the old table and reflect the change */ | ||
51997 | SyMemBackendFree(&pEngine->sAlloc,(void *)pEngine->apBucket); | ||
51998 | pEngine->apBucket = apNew; | ||
51999 | pEngine->nBucket = nNewSize; | ||
52000 | return UNQLITE_OK; | ||
52001 | } | ||
52002 | /* | ||
52003 | * Exported Interfaces. | ||
52004 | */ | ||
52005 | /* | ||
52006 | * Each public cursor is identified by an instance of this structure. | ||
52007 | */ | ||
52008 | typedef struct mem_hash_cursor mem_hash_cursor; | ||
52009 | struct mem_hash_cursor | ||
52010 | { | ||
52011 | unqlite_kv_engine *pStore; /* Must be first */ | ||
52012 | /* Private fields */ | ||
52013 | mem_hash_record *pCur; /* Current hash record */ | ||
52014 | }; | ||
52015 | /* | ||
52016 | * Initialize the cursor. | ||
52017 | */ | ||
52018 | static void MemHashInitCursor(unqlite_kv_cursor *pCursor) | ||
52019 | { | ||
52020 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
52021 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52022 | /* Point to the first inserted entry */ | ||
52023 | pMem->pCur = pEngine->pFirst; | ||
52024 | } | ||
52025 | /* | ||
52026 | * Point to the first entry. | ||
52027 | */ | ||
52028 | static int MemHashCursorFirst(unqlite_kv_cursor *pCursor) | ||
52029 | { | ||
52030 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
52031 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52032 | pMem->pCur = pEngine->pFirst; | ||
52033 | return UNQLITE_OK; | ||
52034 | } | ||
52035 | /* | ||
52036 | * Point to the last entry. | ||
52037 | */ | ||
52038 | static int MemHashCursorLast(unqlite_kv_cursor *pCursor) | ||
52039 | { | ||
52040 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
52041 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52042 | pMem->pCur = pEngine->pLast; | ||
52043 | return UNQLITE_OK; | ||
52044 | } | ||
52045 | /* | ||
52046 | * is a Valid Cursor. | ||
52047 | */ | ||
52048 | static int MemHashCursorValid(unqlite_kv_cursor *pCursor) | ||
52049 | { | ||
52050 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52051 | return pMem->pCur != 0 ? 1 : 0; | ||
52052 | } | ||
52053 | /* | ||
52054 | * Point to the next entry. | ||
52055 | */ | ||
52056 | static int MemHashCursorNext(unqlite_kv_cursor *pCursor) | ||
52057 | { | ||
52058 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52059 | if( pMem->pCur == 0){ | ||
52060 | return UNQLITE_EOF; | ||
52061 | } | ||
52062 | pMem->pCur = pMem->pCur->pPrev; /* Reverse link: Not a Bug */ | ||
52063 | return UNQLITE_OK; | ||
52064 | } | ||
52065 | /* | ||
52066 | * Point to the previous entry. | ||
52067 | */ | ||
52068 | static int MemHashCursorPrev(unqlite_kv_cursor *pCursor) | ||
52069 | { | ||
52070 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52071 | if( pMem->pCur == 0){ | ||
52072 | return UNQLITE_EOF; | ||
52073 | } | ||
52074 | pMem->pCur = pMem->pCur->pNext; /* Reverse link: Not a Bug */ | ||
52075 | return UNQLITE_OK; | ||
52076 | } | ||
52077 | /* | ||
52078 | * Return key length. | ||
52079 | */ | ||
52080 | static int MemHashCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) | ||
52081 | { | ||
52082 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52083 | if( pMem->pCur == 0){ | ||
52084 | return UNQLITE_EOF; | ||
52085 | } | ||
52086 | *pLen = (int)pMem->pCur->nKeyLen; | ||
52087 | return UNQLITE_OK; | ||
52088 | } | ||
52089 | /* | ||
52090 | * Return data length. | ||
52091 | */ | ||
52092 | static int MemHashCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) | ||
52093 | { | ||
52094 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52095 | if( pMem->pCur == 0 ){ | ||
52096 | return UNQLITE_EOF; | ||
52097 | } | ||
52098 | *pLen = pMem->pCur->nDataLen; | ||
52099 | return UNQLITE_OK; | ||
52100 | } | ||
52101 | /* | ||
52102 | * Consume the key. | ||
52103 | */ | ||
52104 | static int MemHashCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
52105 | { | ||
52106 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52107 | int rc; | ||
52108 | if( pMem->pCur == 0){ | ||
52109 | return UNQLITE_EOF; | ||
52110 | } | ||
52111 | /* Invoke the callback */ | ||
52112 | rc = xConsumer(pMem->pCur->pKey,pMem->pCur->nKeyLen,pUserData); | ||
52113 | /* Callback result */ | ||
52114 | return rc; | ||
52115 | } | ||
52116 | /* | ||
52117 | * Consume the data. | ||
52118 | */ | ||
52119 | static int MemHashCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) | ||
52120 | { | ||
52121 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52122 | int rc; | ||
52123 | if( pMem->pCur == 0){ | ||
52124 | return UNQLITE_EOF; | ||
52125 | } | ||
52126 | /* Invoke the callback */ | ||
52127 | rc = xConsumer(pMem->pCur->pData,pMem->pCur->nDataLen,pUserData); | ||
52128 | /* Callback result */ | ||
52129 | return rc; | ||
52130 | } | ||
52131 | /* | ||
52132 | * Reset the cursor. | ||
52133 | */ | ||
52134 | static void MemHashCursorReset(unqlite_kv_cursor *pCursor) | ||
52135 | { | ||
52136 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52137 | pMem->pCur = ((mem_hash_kv_engine *)pCursor->pStore)->pFirst; | ||
52138 | } | ||
52139 | /* | ||
52140 | * Remove a particular record. | ||
52141 | */ | ||
52142 | static int MemHashCursorDelete(unqlite_kv_cursor *pCursor) | ||
52143 | { | ||
52144 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52145 | mem_hash_record *pNext; | ||
52146 | if( pMem->pCur == 0 ){ | ||
52147 | /* Cursor does not point to anything */ | ||
52148 | return UNQLITE_NOTFOUND; | ||
52149 | } | ||
52150 | pNext = pMem->pCur->pPrev; | ||
52151 | /* Perform the deletion */ | ||
52152 | MemHashUnlinkRecord(pMem->pCur->pEngine,pMem->pCur); | ||
52153 | /* Point to the next entry */ | ||
52154 | pMem->pCur = pNext; | ||
52155 | return UNQLITE_OK; | ||
52156 | } | ||
52157 | /* | ||
52158 | * Find a particular record. | ||
52159 | */ | ||
52160 | static int MemHashCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) | ||
52161 | { | ||
52162 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; | ||
52163 | mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; | ||
52164 | /* Perform the lookup */ | ||
52165 | pMem->pCur = MemHashGetEntry(pEngine,pKey,nByte); | ||
52166 | if( pMem->pCur == 0 ){ | ||
52167 | if( iPos != UNQLITE_CURSOR_MATCH_EXACT ){ | ||
52168 | /* noop; */ | ||
52169 | } | ||
52170 | /* No such record */ | ||
52171 | return UNQLITE_NOTFOUND; | ||
52172 | } | ||
52173 | return UNQLITE_OK; | ||
52174 | } | ||
52175 | /* | ||
52176 | * Builtin hash function. | ||
52177 | */ | ||
52178 | static sxu32 MemHashFunc(const void *pSrc,sxu32 nLen) | ||
52179 | { | ||
52180 | register unsigned char *zIn = (unsigned char *)pSrc; | ||
52181 | unsigned char *zEnd; | ||
52182 | sxu32 nH = 5381; | ||
52183 | zEnd = &zIn[nLen]; | ||
52184 | for(;;){ | ||
52185 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
52186 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
52187 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
52188 | if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; | ||
52189 | } | ||
52190 | return nH; | ||
52191 | } | ||
52192 | /* Default bucket size */ | ||
52193 | #define MEM_HASH_BUCKET_SIZE 64 | ||
52194 | /* Default fill factor */ | ||
52195 | #define MEM_HASH_FILL_FACTOR 4 /* or 3 */ | ||
52196 | /* | ||
52197 | * Initialize the in-memory storage engine. | ||
52198 | */ | ||
52199 | static int MemHashInit(unqlite_kv_engine *pKvEngine,int iPageSize) | ||
52200 | { | ||
52201 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; | ||
52202 | /* Note that this instance is already zeroed */ | ||
52203 | /* Memory backend */ | ||
52204 | SyMemBackendInitFromParent(&pEngine->sAlloc,unqliteExportMemBackend()); | ||
52205 | #if defined(UNQLITE_ENABLE_THREADS) | ||
52206 | /* Already protected by the upper layers */ | ||
52207 | SyMemBackendDisbaleMutexing(&pEngine->sAlloc); | ||
52208 | #endif | ||
52209 | /* Default hash & comparison function */ | ||
52210 | pEngine->xHash = MemHashFunc; | ||
52211 | pEngine->xCmp = SyMemcmp; | ||
52212 | /* Allocate a new bucket */ | ||
52213 | pEngine->apBucket = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); | ||
52214 | if( pEngine->apBucket == 0 ){ | ||
52215 | SXUNUSED(iPageSize); /* cc warning */ | ||
52216 | return UNQLITE_NOMEM; | ||
52217 | } | ||
52218 | /* Zero the bucket */ | ||
52219 | SyZero(pEngine->apBucket,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); | ||
52220 | pEngine->nRecord = 0; | ||
52221 | pEngine->nBucket = MEM_HASH_BUCKET_SIZE; | ||
52222 | return UNQLITE_OK; | ||
52223 | } | ||
52224 | /* | ||
52225 | * Release the in-memory storage engine. | ||
52226 | */ | ||
52227 | static void MemHashRelease(unqlite_kv_engine *pKvEngine) | ||
52228 | { | ||
52229 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; | ||
52230 | /* Release the private memory backend */ | ||
52231 | SyMemBackendRelease(&pEngine->sAlloc); | ||
52232 | } | ||
52233 | /* | ||
52234 | * Configure the in-memory storage engine. | ||
52235 | */ | ||
52236 | static int MemHashConfigure(unqlite_kv_engine *pKvEngine,int iOp,va_list ap) | ||
52237 | { | ||
52238 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; | ||
52239 | int rc = UNQLITE_OK; | ||
52240 | switch(iOp){ | ||
52241 | case UNQLITE_KV_CONFIG_HASH_FUNC:{ | ||
52242 | /* Use a default hash function */ | ||
52243 | if( pEngine->nRecord > 0 ){ | ||
52244 | rc = UNQLITE_LOCKED; | ||
52245 | }else{ | ||
52246 | ProcHash xHash = va_arg(ap,ProcHash); | ||
52247 | if( xHash ){ | ||
52248 | pEngine->xHash = xHash; | ||
52249 | } | ||
52250 | } | ||
52251 | break; | ||
52252 | } | ||
52253 | case UNQLITE_KV_CONFIG_CMP_FUNC: { | ||
52254 | /* Default comparison function */ | ||
52255 | ProcCmp xCmp = va_arg(ap,ProcCmp); | ||
52256 | if( xCmp ){ | ||
52257 | pEngine->xCmp = xCmp; | ||
52258 | } | ||
52259 | break; | ||
52260 | } | ||
52261 | default: | ||
52262 | /* Unknown configuration option */ | ||
52263 | rc = UNQLITE_UNKNOWN; | ||
52264 | } | ||
52265 | return rc; | ||
52266 | } | ||
52267 | /* | ||
52268 | * Replace method. | ||
52269 | */ | ||
52270 | static int MemHashReplace( | ||
52271 | unqlite_kv_engine *pKv, | ||
52272 | const void *pKey,int nKeyLen, | ||
52273 | const void *pData,unqlite_int64 nDataLen | ||
52274 | ) | ||
52275 | { | ||
52276 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; | ||
52277 | mem_hash_record *pRecord; | ||
52278 | if( nDataLen > SXU32_HIGH ){ | ||
52279 | /* Database limit */ | ||
52280 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); | ||
52281 | return UNQLITE_LIMIT; | ||
52282 | } | ||
52283 | /* Fetch the record first */ | ||
52284 | pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); | ||
52285 | if( pRecord == 0 ){ | ||
52286 | /* Allocate a new record */ | ||
52287 | pRecord = MemHashNewRecord(pEngine, | ||
52288 | pKey,nKeyLen, | ||
52289 | pData,nDataLen, | ||
52290 | pEngine->xHash(pKey,nKeyLen) | ||
52291 | ); | ||
52292 | if( pRecord == 0 ){ | ||
52293 | return UNQLITE_NOMEM; | ||
52294 | } | ||
52295 | /* Link the entry */ | ||
52296 | MemHashLinkRecord(pEngine,pRecord); | ||
52297 | if( (pEngine->nRecord >= pEngine->nBucket * MEM_HASH_FILL_FACTOR) && pEngine->nRecord < 100000 ){ | ||
52298 | /* Rehash the table */ | ||
52299 | MemHashGrowTable(pEngine); | ||
52300 | } | ||
52301 | }else{ | ||
52302 | sxu32 nData = (sxu32)nDataLen; | ||
52303 | void *pNew; | ||
52304 | /* Replace an existing record */ | ||
52305 | if( nData == pRecord->nDataLen ){ | ||
52306 | /* No need to free the old chunk */ | ||
52307 | pNew = (void *)pRecord->pData; | ||
52308 | }else{ | ||
52309 | pNew = SyMemBackendAlloc(&pEngine->sAlloc,nData); | ||
52310 | if( pNew == 0 ){ | ||
52311 | return UNQLITE_NOMEM; | ||
52312 | } | ||
52313 | /* Release the old data */ | ||
52314 | SyMemBackendFree(&pEngine->sAlloc,(void *)pRecord->pData); | ||
52315 | } | ||
52316 | /* Reflect the change */ | ||
52317 | pRecord->nDataLen = nData; | ||
52318 | SyMemcpy(pData,pNew,nData); | ||
52319 | pRecord->pData = pNew; | ||
52320 | } | ||
52321 | return UNQLITE_OK; | ||
52322 | } | ||
52323 | /* | ||
52324 | * Append method. | ||
52325 | */ | ||
52326 | static int MemHashAppend( | ||
52327 | unqlite_kv_engine *pKv, | ||
52328 | const void *pKey,int nKeyLen, | ||
52329 | const void *pData,unqlite_int64 nDataLen | ||
52330 | ) | ||
52331 | { | ||
52332 | mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; | ||
52333 | mem_hash_record *pRecord; | ||
52334 | if( nDataLen > SXU32_HIGH ){ | ||
52335 | /* Database limit */ | ||
52336 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); | ||
52337 | return UNQLITE_LIMIT; | ||
52338 | } | ||
52339 | /* Fetch the record first */ | ||
52340 | pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); | ||
52341 | if( pRecord == 0 ){ | ||
52342 | /* Allocate a new record */ | ||
52343 | pRecord = MemHashNewRecord(pEngine, | ||
52344 | pKey,nKeyLen, | ||
52345 | pData,nDataLen, | ||
52346 | pEngine->xHash(pKey,nKeyLen) | ||
52347 | ); | ||
52348 | if( pRecord == 0 ){ | ||
52349 | return UNQLITE_NOMEM; | ||
52350 | } | ||
52351 | /* Link the entry */ | ||
52352 | MemHashLinkRecord(pEngine,pRecord); | ||
52353 | if( pEngine->nRecord * MEM_HASH_FILL_FACTOR >= pEngine->nBucket && pEngine->nRecord < 100000 ){ | ||
52354 | /* Rehash the table */ | ||
52355 | MemHashGrowTable(pEngine); | ||
52356 | } | ||
52357 | }else{ | ||
52358 | unqlite_int64 nNew = pRecord->nDataLen + nDataLen; | ||
52359 | void *pOld = (void *)pRecord->pData; | ||
52360 | sxu32 nData; | ||
52361 | char *zNew; | ||
52362 | /* Append data to the existing record */ | ||
52363 | if( nNew > SXU32_HIGH ){ | ||
52364 | /* Overflow */ | ||
52365 | pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); | ||
52366 | return UNQLITE_LIMIT; | ||
52367 | } | ||
52368 | nData = (sxu32)nNew; | ||
52369 | /* Allocate bigger chunk */ | ||
52370 | zNew = (char *)SyMemBackendRealloc(&pEngine->sAlloc,pOld,nData); | ||
52371 | if( zNew == 0 ){ | ||
52372 | return UNQLITE_NOMEM; | ||
52373 | } | ||
52374 | /* Reflect the change */ | ||
52375 | SyMemcpy(pData,&zNew[pRecord->nDataLen],(sxu32)nDataLen); | ||
52376 | pRecord->pData = (const void *)zNew; | ||
52377 | pRecord->nDataLen = nData; | ||
52378 | } | ||
52379 | return UNQLITE_OK; | ||
52380 | } | ||
52381 | /* | ||
52382 | * Export the in-memory storage engine. | ||
52383 | */ | ||
52384 | UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void) | ||
52385 | { | ||
52386 | static const unqlite_kv_methods sMemStore = { | ||
52387 | "mem", /* zName */ | ||
52388 | sizeof(mem_hash_kv_engine), /* szKv */ | ||
52389 | sizeof(mem_hash_cursor), /* szCursor */ | ||
52390 | 1, /* iVersion */ | ||
52391 | MemHashInit, /* xInit */ | ||
52392 | MemHashRelease, /* xRelease */ | ||
52393 | MemHashConfigure, /* xConfig */ | ||
52394 | 0, /* xOpen */ | ||
52395 | MemHashReplace, /* xReplace */ | ||
52396 | MemHashAppend, /* xAppend */ | ||
52397 | MemHashInitCursor, /* xCursorInit */ | ||
52398 | MemHashCursorSeek, /* xSeek */ | ||
52399 | MemHashCursorFirst, /* xFirst */ | ||
52400 | MemHashCursorLast, /* xLast */ | ||
52401 | MemHashCursorValid, /* xValid */ | ||
52402 | MemHashCursorNext, /* xNext */ | ||
52403 | MemHashCursorPrev, /* xPrev */ | ||
52404 | MemHashCursorDelete, /* xDelete */ | ||
52405 | MemHashCursorKeyLength, /* xKeyLength */ | ||
52406 | MemHashCursorKey, /* xKey */ | ||
52407 | MemHashCursorDataLength, /* xDataLength */ | ||
52408 | MemHashCursorData, /* xData */ | ||
52409 | MemHashCursorReset, /* xReset */ | ||
52410 | 0 /* xRelease */ | ||
52411 | }; | ||
52412 | return &sMemStore; | ||
52413 | } | ||
52414 | /* | ||
52415 | * ---------------------------------------------------------- | ||
52416 | * File: os.c | ||
52417 | * MD5: e7ad243c3cd9e6aac5fba406eedb7766 | ||
52418 | * ---------------------------------------------------------- | ||
52419 | */ | ||
52420 | /* | ||
52421 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
52422 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
52423 | * Version 1.1.6 | ||
52424 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
52425 | * please contact Symisc Systems via: | ||
52426 | * legal@symisc.net | ||
52427 | * licensing@symisc.net | ||
52428 | * contact@symisc.net | ||
52429 | * or visit: | ||
52430 | * http://unqlite.org/licensing.html | ||
52431 | */ | ||
52432 | /* $SymiscID: os.c v1.0 FreeBSD 2012-11-12 21:27 devel <chm@symisc.net> $ */ | ||
52433 | #ifndef UNQLITE_AMALGAMATION | ||
52434 | #include "unqliteInt.h" | ||
52435 | #endif | ||
52436 | /* OS interfaces abstraction layers: Mostly SQLite3 source tree */ | ||
52437 | /* | ||
52438 | ** The following routines are convenience wrappers around methods | ||
52439 | ** of the unqlite_file object. This is mostly just syntactic sugar. All | ||
52440 | ** of this would be completely automatic if UnQLite were coded using | ||
52441 | ** C++ instead of plain old C. | ||
52442 | */ | ||
52443 | UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset) | ||
52444 | { | ||
52445 | return id->pMethods->xRead(id, pBuf, amt, offset); | ||
52446 | } | ||
52447 | UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset) | ||
52448 | { | ||
52449 | return id->pMethods->xWrite(id, pBuf, amt, offset); | ||
52450 | } | ||
52451 | UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size) | ||
52452 | { | ||
52453 | return id->pMethods->xTruncate(id, size); | ||
52454 | } | ||
52455 | UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags) | ||
52456 | { | ||
52457 | return id->pMethods->xSync(id, flags); | ||
52458 | } | ||
52459 | UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize) | ||
52460 | { | ||
52461 | return id->pMethods->xFileSize(id, pSize); | ||
52462 | } | ||
52463 | UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType) | ||
52464 | { | ||
52465 | return id->pMethods->xLock(id, lockType); | ||
52466 | } | ||
52467 | UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType) | ||
52468 | { | ||
52469 | return id->pMethods->xUnlock(id, lockType); | ||
52470 | } | ||
52471 | UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut) | ||
52472 | { | ||
52473 | return id->pMethods->xCheckReservedLock(id, pResOut); | ||
52474 | } | ||
52475 | UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id) | ||
52476 | { | ||
52477 | if( id->pMethods->xSectorSize ){ | ||
52478 | return id->pMethods->xSectorSize(id); | ||
52479 | } | ||
52480 | return UNQLITE_DEFAULT_SECTOR_SIZE; | ||
52481 | } | ||
52482 | /* | ||
52483 | ** The next group of routines are convenience wrappers around the | ||
52484 | ** VFS methods. | ||
52485 | */ | ||
52486 | UNQLITE_PRIVATE int unqliteOsOpen( | ||
52487 | unqlite_vfs *pVfs, | ||
52488 | SyMemBackend *pAlloc, | ||
52489 | const char *zPath, | ||
52490 | unqlite_file **ppOut, | ||
52491 | unsigned int flags | ||
52492 | ) | ||
52493 | { | ||
52494 | unqlite_file *pFile; | ||
52495 | int rc; | ||
52496 | *ppOut = 0; | ||
52497 | if( zPath == 0 ){ | ||
52498 | /* May happen if dealing with an in-memory database */ | ||
52499 | return SXERR_EMPTY; | ||
52500 | } | ||
52501 | /* Allocate a new instance */ | ||
52502 | pFile = (unqlite_file *)SyMemBackendAlloc(pAlloc,sizeof(unqlite_file)+pVfs->szOsFile); | ||
52503 | if( pFile == 0 ){ | ||
52504 | return UNQLITE_NOMEM; | ||
52505 | } | ||
52506 | /* Zero the structure */ | ||
52507 | SyZero(pFile,sizeof(unqlite_file)+pVfs->szOsFile); | ||
52508 | /* Invoke the xOpen method of the underlying VFS */ | ||
52509 | rc = pVfs->xOpen(pVfs, zPath, pFile, flags); | ||
52510 | if( rc != UNQLITE_OK ){ | ||
52511 | SyMemBackendFree(pAlloc,pFile); | ||
52512 | pFile = 0; | ||
52513 | } | ||
52514 | *ppOut = pFile; | ||
52515 | return rc; | ||
52516 | } | ||
52517 | UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId) | ||
52518 | { | ||
52519 | int rc = UNQLITE_OK; | ||
52520 | if( pId ){ | ||
52521 | rc = pId->pMethods->xClose(pId); | ||
52522 | SyMemBackendFree(pAlloc,pId); | ||
52523 | } | ||
52524 | return rc; | ||
52525 | } | ||
52526 | UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync){ | ||
52527 | return pVfs->xDelete(pVfs, zPath, dirSync); | ||
52528 | } | ||
52529 | UNQLITE_PRIVATE int unqliteOsAccess( | ||
52530 | unqlite_vfs *pVfs, | ||
52531 | const char *zPath, | ||
52532 | int flags, | ||
52533 | int *pResOut | ||
52534 | ){ | ||
52535 | return pVfs->xAccess(pVfs, zPath, flags, pResOut); | ||
52536 | } | ||
52537 | /* | ||
52538 | * ---------------------------------------------------------- | ||
52539 | * File: os_unix.c | ||
52540 | * MD5: 5efd57d03f8fb988d081c5bcf5cc2998 | ||
52541 | * ---------------------------------------------------------- | ||
52542 | */ | ||
52543 | /* | ||
52544 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
52545 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
52546 | * Version 1.1.6 | ||
52547 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
52548 | * please contact Symisc Systems via: | ||
52549 | * legal@symisc.net | ||
52550 | * licensing@symisc.net | ||
52551 | * contact@symisc.net | ||
52552 | * or visit: | ||
52553 | * http://unqlite.org/licensing.html | ||
52554 | */ | ||
52555 | /* $SymiscID: os_unix.c v1.3 FreeBSD 2013-04-05 01:10 devel <chm@symisc.net> $ */ | ||
52556 | #ifndef UNQLITE_AMALGAMATION | ||
52557 | #include "unqliteInt.h" | ||
52558 | #endif | ||
52559 | /* | ||
52560 | * Omit the whole layer from the build if compiling for platforms other than Unix (Linux, BSD, Solaris, OS X, etc.). | ||
52561 | * Note: Mostly SQLite3 source tree. | ||
52562 | */ | ||
52563 | #if defined(__UNIXES__) | ||
52564 | /** This file contains the VFS implementation for unix-like operating systems | ||
52565 | ** include Linux, MacOSX, *BSD, QNX, VxWorks, AIX, HPUX, and others. | ||
52566 | ** | ||
52567 | ** There are actually several different VFS implementations in this file. | ||
52568 | ** The differences are in the way that file locking is done. The default | ||
52569 | ** implementation uses Posix Advisory Locks. Alternative implementations | ||
52570 | ** use flock(), dot-files, various proprietary locking schemas, or simply | ||
52571 | ** skip locking all together. | ||
52572 | ** | ||
52573 | ** This source file is organized into divisions where the logic for various | ||
52574 | ** subfunctions is contained within the appropriate division. PLEASE | ||
52575 | ** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed | ||
52576 | ** in the correct division and should be clearly labeled. | ||
52577 | ** | ||
52578 | */ | ||
52579 | /* | ||
52580 | ** standard include files. | ||
52581 | */ | ||
52582 | #include <sys/types.h> | ||
52583 | #include <sys/stat.h> | ||
52584 | #include <sys/uio.h> | ||
52585 | #include <sys/file.h> | ||
52586 | #include <fcntl.h> | ||
52587 | #include <unistd.h> | ||
52588 | #include <time.h> | ||
52589 | #include <sys/time.h> | ||
52590 | #include <errno.h> | ||
52591 | #if defined(__APPLE__) | ||
52592 | # include <sys/mount.h> | ||
52593 | #endif | ||
52594 | /* | ||
52595 | ** Allowed values of unixFile.fsFlags | ||
52596 | */ | ||
52597 | #define UNQLITE_FSFLAGS_IS_MSDOS 0x1 | ||
52598 | |||
52599 | /* | ||
52600 | ** Default permissions when creating a new file | ||
52601 | */ | ||
52602 | #ifndef UNQLITE_DEFAULT_FILE_PERMISSIONS | ||
52603 | # define UNQLITE_DEFAULT_FILE_PERMISSIONS 0644 | ||
52604 | #endif | ||
52605 | /* | ||
52606 | ** Default permissions when creating auto proxy dir | ||
52607 | */ | ||
52608 | #ifndef UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS | ||
52609 | # define UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS 0755 | ||
52610 | #endif | ||
52611 | /* | ||
52612 | ** Maximum supported path-length. | ||
52613 | */ | ||
52614 | #define MAX_PATHNAME 512 | ||
52615 | /* | ||
52616 | ** Only set the lastErrno if the error code is a real error and not | ||
52617 | ** a normal expected return code of UNQLITE_BUSY or UNQLITE_OK | ||
52618 | */ | ||
52619 | #define IS_LOCK_ERROR(x) ((x != UNQLITE_OK) && (x != UNQLITE_BUSY)) | ||
52620 | /* Forward references */ | ||
52621 | typedef struct unixInodeInfo unixInodeInfo; /* An i-node */ | ||
52622 | typedef struct UnixUnusedFd UnixUnusedFd; /* An unused file descriptor */ | ||
52623 | /* | ||
52624 | ** Sometimes, after a file handle is closed by SQLite, the file descriptor | ||
52625 | ** cannot be closed immediately. In these cases, instances of the following | ||
52626 | ** structure are used to store the file descriptor while waiting for an | ||
52627 | ** opportunity to either close or reuse it. | ||
52628 | */ | ||
52629 | struct UnixUnusedFd { | ||
52630 | int fd; /* File descriptor to close */ | ||
52631 | int flags; /* Flags this file descriptor was opened with */ | ||
52632 | UnixUnusedFd *pNext; /* Next unused file descriptor on same file */ | ||
52633 | }; | ||
52634 | /* | ||
52635 | ** The unixFile structure is subclass of unqlite3_file specific to the unix | ||
52636 | ** VFS implementations. | ||
52637 | */ | ||
52638 | typedef struct unixFile unixFile; | ||
52639 | struct unixFile { | ||
52640 | const unqlite_io_methods *pMethod; /* Always the first entry */ | ||
52641 | unixInodeInfo *pInode; /* Info about locks on this inode */ | ||
52642 | int h; /* The file descriptor */ | ||
52643 | int dirfd; /* File descriptor for the directory */ | ||
52644 | unsigned char eFileLock; /* The type of lock held on this fd */ | ||
52645 | int lastErrno; /* The unix errno from last I/O error */ | ||
52646 | void *lockingContext; /* Locking style specific state */ | ||
52647 | UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */ | ||
52648 | int fileFlags; /* Miscellanous flags */ | ||
52649 | const char *zPath; /* Name of the file */ | ||
52650 | unsigned fsFlags; /* cached details from statfs() */ | ||
52651 | }; | ||
52652 | /* | ||
52653 | ** The following macros define bits in unixFile.fileFlags | ||
52654 | */ | ||
52655 | #define UNQLITE_WHOLE_FILE_LOCKING 0x0001 /* Use whole-file locking */ | ||
52656 | /* | ||
52657 | ** Define various macros that are missing from some systems. | ||
52658 | */ | ||
52659 | #ifndef O_LARGEFILE | ||
52660 | # define O_LARGEFILE 0 | ||
52661 | #endif | ||
52662 | #ifndef O_NOFOLLOW | ||
52663 | # define O_NOFOLLOW 0 | ||
52664 | #endif | ||
52665 | #ifndef O_BINARY | ||
52666 | # define O_BINARY 0 | ||
52667 | #endif | ||
52668 | /* | ||
52669 | ** Helper functions to obtain and relinquish the global mutex. The | ||
52670 | ** global mutex is used to protect the unixInodeInfo and | ||
52671 | ** vxworksFileId objects used by this file, all of which may be | ||
52672 | ** shared by multiple threads. | ||
52673 | ** | ||
52674 | ** Function unixMutexHeld() is used to assert() that the global mutex | ||
52675 | ** is held when required. This function is only used as part of assert() | ||
52676 | ** statements. e.g. | ||
52677 | ** | ||
52678 | ** unixEnterMutex() | ||
52679 | ** assert( unixMutexHeld() ); | ||
52680 | ** unixEnterLeave() | ||
52681 | */ | ||
52682 | static void unixEnterMutex(void){ | ||
52683 | #ifdef UNQLITE_ENABLE_THREADS | ||
52684 | const SyMutexMethods *pMutexMethods = SyMutexExportMethods(); | ||
52685 | if( pMutexMethods ){ | ||
52686 | SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */ | ||
52687 | SyMutexEnter(pMutexMethods,pMutex); | ||
52688 | } | ||
52689 | #endif /* UNQLITE_ENABLE_THREADS */ | ||
52690 | } | ||
52691 | static void unixLeaveMutex(void){ | ||
52692 | #ifdef UNQLITE_ENABLE_THREADS | ||
52693 | const SyMutexMethods *pMutexMethods = SyMutexExportMethods(); | ||
52694 | if( pMutexMethods ){ | ||
52695 | SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */ | ||
52696 | SyMutexLeave(pMutexMethods,pMutex); | ||
52697 | } | ||
52698 | #endif /* UNQLITE_ENABLE_THREADS */ | ||
52699 | } | ||
52700 | /* | ||
52701 | ** This routine translates a standard POSIX errno code into something | ||
52702 | ** useful to the clients of the unqlite3 functions. Specifically, it is | ||
52703 | ** intended to translate a variety of "try again" errors into UNQLITE_BUSY | ||
52704 | ** and a variety of "please close the file descriptor NOW" errors into | ||
52705 | ** UNQLITE_IOERR | ||
52706 | ** | ||
52707 | ** Errors during initialization of locks, or file system support for locks, | ||
52708 | ** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately. | ||
52709 | */ | ||
52710 | static int unqliteErrorFromPosixError(int posixError, int unqliteIOErr) { | ||
52711 | switch (posixError) { | ||
52712 | case 0: | ||
52713 | return UNQLITE_OK; | ||
52714 | |||
52715 | case EAGAIN: | ||
52716 | case ETIMEDOUT: | ||
52717 | case EBUSY: | ||
52718 | case EINTR: | ||
52719 | case ENOLCK: | ||
52720 | /* random NFS retry error, unless during file system support | ||
52721 | * introspection, in which it actually means what it says */ | ||
52722 | return UNQLITE_BUSY; | ||
52723 | |||
52724 | case EACCES: | ||
52725 | /* EACCES is like EAGAIN during locking operations, but not any other time*/ | ||
52726 | return UNQLITE_BUSY; | ||
52727 | |||
52728 | case EPERM: | ||
52729 | return UNQLITE_PERM; | ||
52730 | |||
52731 | case EDEADLK: | ||
52732 | return UNQLITE_IOERR; | ||
52733 | |||
52734 | #if EOPNOTSUPP!=ENOTSUP | ||
52735 | case EOPNOTSUPP: | ||
52736 | /* something went terribly awry, unless during file system support | ||
52737 | * introspection, in which it actually means what it says */ | ||
52738 | #endif | ||
52739 | #ifdef ENOTSUP | ||
52740 | case ENOTSUP: | ||
52741 | /* invalid fd, unless during file system support introspection, in which | ||
52742 | * it actually means what it says */ | ||
52743 | #endif | ||
52744 | case EIO: | ||
52745 | case EBADF: | ||
52746 | case EINVAL: | ||
52747 | case ENOTCONN: | ||
52748 | case ENODEV: | ||
52749 | case ENXIO: | ||
52750 | case ENOENT: | ||
52751 | case ESTALE: | ||
52752 | case ENOSYS: | ||
52753 | /* these should force the client to close the file and reconnect */ | ||
52754 | |||
52755 | default: | ||
52756 | return unqliteIOErr; | ||
52757 | } | ||
52758 | } | ||
52759 | /****************************************************************************** | ||
52760 | *************************** Posix Advisory Locking **************************** | ||
52761 | ** | ||
52762 | ** POSIX advisory locks are broken by design. ANSI STD 1003.1 (1996) | ||
52763 | ** section 6.5.2.2 lines 483 through 490 specify that when a process | ||
52764 | ** sets or clears a lock, that operation overrides any prior locks set | ||
52765 | ** by the same process. It does not explicitly say so, but this implies | ||
52766 | ** that it overrides locks set by the same process using a different | ||
52767 | ** file descriptor. Consider this test case: | ||
52768 | ** | ||
52769 | ** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644); | ||
52770 | ** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644); | ||
52771 | ** | ||
52772 | ** Suppose ./file1 and ./file2 are really the same file (because | ||
52773 | ** one is a hard or symbolic link to the other) then if you set | ||
52774 | ** an exclusive lock on fd1, then try to get an exclusive lock | ||
52775 | ** on fd2, it works. I would have expected the second lock to | ||
52776 | ** fail since there was already a lock on the file due to fd1. | ||
52777 | ** But not so. Since both locks came from the same process, the | ||
52778 | ** second overrides the first, even though they were on different | ||
52779 | ** file descriptors opened on different file names. | ||
52780 | ** | ||
52781 | ** This means that we cannot use POSIX locks to synchronize file access | ||
52782 | ** among competing threads of the same process. POSIX locks will work fine | ||
52783 | ** to synchronize access for threads in separate processes, but not | ||
52784 | ** threads within the same process. | ||
52785 | ** | ||
52786 | ** To work around the problem, SQLite has to manage file locks internally | ||
52787 | ** on its own. Whenever a new database is opened, we have to find the | ||
52788 | ** specific inode of the database file (the inode is determined by the | ||
52789 | ** st_dev and st_ino fields of the stat structure that fstat() fills in) | ||
52790 | ** and check for locks already existing on that inode. When locks are | ||
52791 | ** created or removed, we have to look at our own internal record of the | ||
52792 | ** locks to see if another thread has previously set a lock on that same | ||
52793 | ** inode. | ||
52794 | ** | ||
52795 | ** (Aside: The use of inode numbers as unique IDs does not work on VxWorks. | ||
52796 | ** For VxWorks, we have to use the alternative unique ID system based on | ||
52797 | ** canonical filename and implemented in the previous division.) | ||
52798 | ** | ||
52799 | ** There is one locking structure | ||
52800 | ** per inode, so if the same inode is opened twice, both unixFile structures | ||
52801 | ** point to the same locking structure. The locking structure keeps | ||
52802 | ** a reference count (so we will know when to delete it) and a "cnt" | ||
52803 | ** field that tells us its internal lock status. cnt==0 means the | ||
52804 | ** file is unlocked. cnt==-1 means the file has an exclusive lock. | ||
52805 | ** cnt>0 means there are cnt shared locks on the file. | ||
52806 | ** | ||
52807 | ** Any attempt to lock or unlock a file first checks the locking | ||
52808 | ** structure. The fcntl() system call is only invoked to set a | ||
52809 | ** POSIX lock if the internal lock structure transitions between | ||
52810 | ** a locked and an unlocked state. | ||
52811 | ** | ||
52812 | ** But wait: there are yet more problems with POSIX advisory locks. | ||
52813 | ** | ||
52814 | ** If you close a file descriptor that points to a file that has locks, | ||
52815 | ** all locks on that file that are owned by the current process are | ||
52816 | ** released. To work around this problem, each unixInodeInfo object | ||
52817 | ** maintains a count of the number of pending locks on that inode. | ||
52818 | ** When an attempt is made to close an unixFile, if there are | ||
52819 | ** other unixFile open on the same inode that are holding locks, the call | ||
52820 | ** to close() the file descriptor is deferred until all of the locks clear. | ||
52821 | ** The unixInodeInfo structure keeps a list of file descriptors that need to | ||
52822 | ** be closed and that list is walked (and cleared) when the last lock | ||
52823 | ** clears. | ||
52824 | ** | ||
52825 | ** Yet another problem: LinuxThreads do not play well with posix locks. | ||
52826 | ** | ||
52827 | ** Many older versions of linux use the LinuxThreads library which is | ||
52828 | ** not posix compliant. Under LinuxThreads, a lock created by thread | ||
52829 | ** A cannot be modified or overridden by a different thread B. | ||
52830 | ** Only thread A can modify the lock. Locking behavior is correct | ||
52831 | ** if the appliation uses the newer Native Posix Thread Library (NPTL) | ||
52832 | ** on linux - with NPTL a lock created by thread A can override locks | ||
52833 | ** in thread B. But there is no way to know at compile-time which | ||
52834 | ** threading library is being used. So there is no way to know at | ||
52835 | ** compile-time whether or not thread A can override locks on thread B. | ||
52836 | ** One has to do a run-time check to discover the behavior of the | ||
52837 | ** current process. | ||
52838 | ** | ||
52839 | */ | ||
52840 | |||
52841 | /* | ||
52842 | ** An instance of the following structure serves as the key used | ||
52843 | ** to locate a particular unixInodeInfo object. | ||
52844 | */ | ||
52845 | struct unixFileId { | ||
52846 | dev_t dev; /* Device number */ | ||
52847 | ino_t ino; /* Inode number */ | ||
52848 | }; | ||
52849 | /* | ||
52850 | ** An instance of the following structure is allocated for each open | ||
52851 | ** inode. Or, on LinuxThreads, there is one of these structures for | ||
52852 | ** each inode opened by each thread. | ||
52853 | ** | ||
52854 | ** A single inode can have multiple file descriptors, so each unixFile | ||
52855 | ** structure contains a pointer to an instance of this object and this | ||
52856 | ** object keeps a count of the number of unixFile pointing to it. | ||
52857 | */ | ||
52858 | struct unixInodeInfo { | ||
52859 | struct unixFileId fileId; /* The lookup key */ | ||
52860 | int nShared; /* Number of SHARED locks held */ | ||
52861 | int eFileLock; /* One of SHARED_LOCK, RESERVED_LOCK etc. */ | ||
52862 | int nRef; /* Number of pointers to this structure */ | ||
52863 | int nLock; /* Number of outstanding file locks */ | ||
52864 | UnixUnusedFd *pUnused; /* Unused file descriptors to close */ | ||
52865 | unixInodeInfo *pNext; /* List of all unixInodeInfo objects */ | ||
52866 | unixInodeInfo *pPrev; /* .... doubly linked */ | ||
52867 | }; | ||
52868 | |||
52869 | static unixInodeInfo *inodeList = 0; | ||
52870 | /* | ||
52871 | * Local memory allocation stuff. | ||
52872 | */ | ||
52873 | static void * unqlite_malloc(sxu32 nByte) | ||
52874 | { | ||
52875 | SyMemBackend *pAlloc; | ||
52876 | void *p; | ||
52877 | pAlloc = (SyMemBackend *)unqliteExportMemBackend(); | ||
52878 | p = SyMemBackendAlloc(pAlloc,nByte); | ||
52879 | return p; | ||
52880 | } | ||
52881 | static void unqlite_free(void *p) | ||
52882 | { | ||
52883 | SyMemBackend *pAlloc; | ||
52884 | pAlloc = (SyMemBackend *)unqliteExportMemBackend(); | ||
52885 | SyMemBackendFree(pAlloc,p); | ||
52886 | } | ||
52887 | /* | ||
52888 | ** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. | ||
52889 | ** If all such file descriptors are closed without error, the list is | ||
52890 | ** cleared and UNQLITE_OK returned. | ||
52891 | ** | ||
52892 | ** Otherwise, if an error occurs, then successfully closed file descriptor | ||
52893 | ** entries are removed from the list, and UNQLITE_IOERR_CLOSE returned. | ||
52894 | ** not deleted and UNQLITE_IOERR_CLOSE returned. | ||
52895 | */ | ||
52896 | static int closePendingFds(unixFile *pFile){ | ||
52897 | int rc = UNQLITE_OK; | ||
52898 | unixInodeInfo *pInode = pFile->pInode; | ||
52899 | UnixUnusedFd *pError = 0; | ||
52900 | UnixUnusedFd *p; | ||
52901 | UnixUnusedFd *pNext; | ||
52902 | for(p=pInode->pUnused; p; p=pNext){ | ||
52903 | pNext = p->pNext; | ||
52904 | if( close(p->fd) ){ | ||
52905 | pFile->lastErrno = errno; | ||
52906 | rc = UNQLITE_IOERR; | ||
52907 | p->pNext = pError; | ||
52908 | pError = p; | ||
52909 | }else{ | ||
52910 | unqlite_free(p); | ||
52911 | } | ||
52912 | } | ||
52913 | pInode->pUnused = pError; | ||
52914 | return rc; | ||
52915 | } | ||
52916 | /* | ||
52917 | ** Release a unixInodeInfo structure previously allocated by findInodeInfo(). | ||
52918 | ** | ||
52919 | ** The mutex entered using the unixEnterMutex() function must be held | ||
52920 | ** when this function is called. | ||
52921 | */ | ||
52922 | static void releaseInodeInfo(unixFile *pFile){ | ||
52923 | unixInodeInfo *pInode = pFile->pInode; | ||
52924 | if( pInode ){ | ||
52925 | pInode->nRef--; | ||
52926 | if( pInode->nRef==0 ){ | ||
52927 | closePendingFds(pFile); | ||
52928 | if( pInode->pPrev ){ | ||
52929 | pInode->pPrev->pNext = pInode->pNext; | ||
52930 | }else{ | ||
52931 | inodeList = pInode->pNext; | ||
52932 | } | ||
52933 | if( pInode->pNext ){ | ||
52934 | pInode->pNext->pPrev = pInode->pPrev; | ||
52935 | } | ||
52936 | unqlite_free(pInode); | ||
52937 | } | ||
52938 | } | ||
52939 | } | ||
52940 | /* | ||
52941 | ** Given a file descriptor, locate the unixInodeInfo object that | ||
52942 | ** describes that file descriptor. Create a new one if necessary. The | ||
52943 | ** return value might be uninitialized if an error occurs. | ||
52944 | ** | ||
52945 | ** The mutex entered using the unixEnterMutex() function must be held | ||
52946 | ** when this function is called. | ||
52947 | ** | ||
52948 | ** Return an appropriate error code. | ||
52949 | */ | ||
52950 | static int findInodeInfo( | ||
52951 | unixFile *pFile, /* Unix file with file desc used in the key */ | ||
52952 | unixInodeInfo **ppInode /* Return the unixInodeInfo object here */ | ||
52953 | ){ | ||
52954 | int rc; /* System call return code */ | ||
52955 | int fd; /* The file descriptor for pFile */ | ||
52956 | struct unixFileId fileId; /* Lookup key for the unixInodeInfo */ | ||
52957 | struct stat statbuf; /* Low-level file information */ | ||
52958 | unixInodeInfo *pInode = 0; /* Candidate unixInodeInfo object */ | ||
52959 | |||
52960 | /* Get low-level information about the file that we can used to | ||
52961 | ** create a unique name for the file. | ||
52962 | */ | ||
52963 | fd = pFile->h; | ||
52964 | rc = fstat(fd, &statbuf); | ||
52965 | if( rc!=0 ){ | ||
52966 | pFile->lastErrno = errno; | ||
52967 | #ifdef EOVERFLOW | ||
52968 | if( pFile->lastErrno==EOVERFLOW ) return UNQLITE_NOTIMPLEMENTED; | ||
52969 | #endif | ||
52970 | return UNQLITE_IOERR; | ||
52971 | } | ||
52972 | |||
52973 | #ifdef __APPLE__ | ||
52974 | /* On OS X on an msdos filesystem, the inode number is reported | ||
52975 | ** incorrectly for zero-size files. See ticket #3260. To work | ||
52976 | ** around this problem (we consider it a bug in OS X, not SQLite) | ||
52977 | ** we always increase the file size to 1 by writing a single byte | ||
52978 | ** prior to accessing the inode number. The one byte written is | ||
52979 | ** an ASCII 'S' character which also happens to be the first byte | ||
52980 | ** in the header of every SQLite database. In this way, if there | ||
52981 | ** is a race condition such that another thread has already populated | ||
52982 | ** the first page of the database, no damage is done. | ||
52983 | */ | ||
52984 | if( statbuf.st_size==0 && (pFile->fsFlags & UNQLITE_FSFLAGS_IS_MSDOS)!=0 ){ | ||
52985 | rc = write(fd, "S", 1); | ||
52986 | if( rc!=1 ){ | ||
52987 | pFile->lastErrno = errno; | ||
52988 | return UNQLITE_IOERR; | ||
52989 | } | ||
52990 | rc = fstat(fd, &statbuf); | ||
52991 | if( rc!=0 ){ | ||
52992 | pFile->lastErrno = errno; | ||
52993 | return UNQLITE_IOERR; | ||
52994 | } | ||
52995 | } | ||
52996 | #endif | ||
52997 | SyZero(&fileId,sizeof(fileId)); | ||
52998 | fileId.dev = statbuf.st_dev; | ||
52999 | fileId.ino = statbuf.st_ino; | ||
53000 | pInode = inodeList; | ||
53001 | while( pInode && SyMemcmp((const void *)&fileId,(const void *)&pInode->fileId, sizeof(fileId)) ){ | ||
53002 | pInode = pInode->pNext; | ||
53003 | } | ||
53004 | if( pInode==0 ){ | ||
53005 | pInode = (unixInodeInfo *)unqlite_malloc( sizeof(*pInode) ); | ||
53006 | if( pInode==0 ){ | ||
53007 | return UNQLITE_NOMEM; | ||
53008 | } | ||
53009 | SyZero(pInode,sizeof(*pInode)); | ||
53010 | SyMemcpy((const void *)&fileId,(void *)&pInode->fileId,sizeof(fileId)); | ||
53011 | pInode->nRef = 1; | ||
53012 | pInode->pNext = inodeList; | ||
53013 | pInode->pPrev = 0; | ||
53014 | if( inodeList ) inodeList->pPrev = pInode; | ||
53015 | inodeList = pInode; | ||
53016 | }else{ | ||
53017 | pInode->nRef++; | ||
53018 | } | ||
53019 | *ppInode = pInode; | ||
53020 | return UNQLITE_OK; | ||
53021 | } | ||
53022 | /* | ||
53023 | ** This routine checks if there is a RESERVED lock held on the specified | ||
53024 | ** file by this or any other process. If such a lock is held, set *pResOut | ||
53025 | ** to a non-zero value otherwise *pResOut is set to zero. The return value | ||
53026 | ** is set to UNQLITE_OK unless an I/O error occurs during lock checking. | ||
53027 | */ | ||
53028 | static int unixCheckReservedLock(unqlite_file *id, int *pResOut){ | ||
53029 | int rc = UNQLITE_OK; | ||
53030 | int reserved = 0; | ||
53031 | unixFile *pFile = (unixFile*)id; | ||
53032 | |||
53033 | |||
53034 | unixEnterMutex(); /* Because pFile->pInode is shared across threads */ | ||
53035 | |||
53036 | /* Check if a thread in this process holds such a lock */ | ||
53037 | if( pFile->pInode->eFileLock>SHARED_LOCK ){ | ||
53038 | reserved = 1; | ||
53039 | } | ||
53040 | |||
53041 | /* Otherwise see if some other process holds it. | ||
53042 | */ | ||
53043 | if( !reserved ){ | ||
53044 | struct flock lock; | ||
53045 | lock.l_whence = SEEK_SET; | ||
53046 | lock.l_start = RESERVED_BYTE; | ||
53047 | lock.l_len = 1; | ||
53048 | lock.l_type = F_WRLCK; | ||
53049 | if (-1 == fcntl(pFile->h, F_GETLK, &lock)) { | ||
53050 | int tErrno = errno; | ||
53051 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53052 | pFile->lastErrno = tErrno; | ||
53053 | } else if( lock.l_type!=F_UNLCK ){ | ||
53054 | reserved = 1; | ||
53055 | } | ||
53056 | } | ||
53057 | |||
53058 | unixLeaveMutex(); | ||
53059 | |||
53060 | *pResOut = reserved; | ||
53061 | return rc; | ||
53062 | } | ||
53063 | /* | ||
53064 | ** Lock the file with the lock specified by parameter eFileLock - one | ||
53065 | ** of the following: | ||
53066 | ** | ||
53067 | ** (1) SHARED_LOCK | ||
53068 | ** (2) RESERVED_LOCK | ||
53069 | ** (3) PENDING_LOCK | ||
53070 | ** (4) EXCLUSIVE_LOCK | ||
53071 | ** | ||
53072 | ** Sometimes when requesting one lock state, additional lock states | ||
53073 | ** are inserted in between. The locking might fail on one of the later | ||
53074 | ** transitions leaving the lock state different from what it started but | ||
53075 | ** still short of its goal. The following chart shows the allowed | ||
53076 | ** transitions and the inserted intermediate states: | ||
53077 | ** | ||
53078 | ** UNLOCKED -> SHARED | ||
53079 | ** SHARED -> RESERVED | ||
53080 | ** SHARED -> (PENDING) -> EXCLUSIVE | ||
53081 | ** RESERVED -> (PENDING) -> EXCLUSIVE | ||
53082 | ** PENDING -> EXCLUSIVE | ||
53083 | ** | ||
53084 | ** This routine will only increase a lock. Use the unqliteOsUnlock() | ||
53085 | ** routine to lower a locking level. | ||
53086 | */ | ||
53087 | static int unixLock(unqlite_file *id, int eFileLock){ | ||
53088 | /* The following describes the implementation of the various locks and | ||
53089 | ** lock transitions in terms of the POSIX advisory shared and exclusive | ||
53090 | ** lock primitives (called read-locks and write-locks below, to avoid | ||
53091 | ** confusion with SQLite lock names). The algorithms are complicated | ||
53092 | ** slightly in order to be compatible with unixdows systems simultaneously | ||
53093 | ** accessing the same database file, in case that is ever required. | ||
53094 | ** | ||
53095 | ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved | ||
53096 | ** byte', each single bytes at well known offsets, and the 'shared byte | ||
53097 | ** range', a range of 510 bytes at a well known offset. | ||
53098 | ** | ||
53099 | ** To obtain a SHARED lock, a read-lock is obtained on the 'pending | ||
53100 | ** byte'. If this is successful, a random byte from the 'shared byte | ||
53101 | ** range' is read-locked and the lock on the 'pending byte' released. | ||
53102 | ** | ||
53103 | ** A process may only obtain a RESERVED lock after it has a SHARED lock. | ||
53104 | ** A RESERVED lock is implemented by grabbing a write-lock on the | ||
53105 | ** 'reserved byte'. | ||
53106 | ** | ||
53107 | ** A process may only obtain a PENDING lock after it has obtained a | ||
53108 | ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock | ||
53109 | ** on the 'pending byte'. This ensures that no new SHARED locks can be | ||
53110 | ** obtained, but existing SHARED locks are allowed to persist. A process | ||
53111 | ** does not have to obtain a RESERVED lock on the way to a PENDING lock. | ||
53112 | ** This property is used by the algorithm for rolling back a journal file | ||
53113 | ** after a crash. | ||
53114 | ** | ||
53115 | ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is | ||
53116 | ** implemented by obtaining a write-lock on the entire 'shared byte | ||
53117 | ** range'. Since all other locks require a read-lock on one of the bytes | ||
53118 | ** within this range, this ensures that no other locks are held on the | ||
53119 | ** database. | ||
53120 | ** | ||
53121 | ** The reason a single byte cannot be used instead of the 'shared byte | ||
53122 | ** range' is that some versions of unixdows do not support read-locks. By | ||
53123 | ** locking a random byte from a range, concurrent SHARED locks may exist | ||
53124 | ** even if the locking primitive used is always a write-lock. | ||
53125 | */ | ||
53126 | int rc = UNQLITE_OK; | ||
53127 | unixFile *pFile = (unixFile*)id; | ||
53128 | unixInodeInfo *pInode = pFile->pInode; | ||
53129 | struct flock lock; | ||
53130 | int s = 0; | ||
53131 | int tErrno = 0; | ||
53132 | |||
53133 | /* If there is already a lock of this type or more restrictive on the | ||
53134 | ** unixFile, do nothing. Don't use the end_lock: exit path, as | ||
53135 | ** unixEnterMutex() hasn't been called yet. | ||
53136 | */ | ||
53137 | if( pFile->eFileLock>=eFileLock ){ | ||
53138 | return UNQLITE_OK; | ||
53139 | } | ||
53140 | /* This mutex is needed because pFile->pInode is shared across threads | ||
53141 | */ | ||
53142 | unixEnterMutex(); | ||
53143 | pInode = pFile->pInode; | ||
53144 | |||
53145 | /* If some thread using this PID has a lock via a different unixFile* | ||
53146 | ** handle that precludes the requested lock, return BUSY. | ||
53147 | */ | ||
53148 | if( (pFile->eFileLock!=pInode->eFileLock && | ||
53149 | (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) | ||
53150 | ){ | ||
53151 | rc = UNQLITE_BUSY; | ||
53152 | goto end_lock; | ||
53153 | } | ||
53154 | |||
53155 | /* If a SHARED lock is requested, and some thread using this PID already | ||
53156 | ** has a SHARED or RESERVED lock, then increment reference counts and | ||
53157 | ** return UNQLITE_OK. | ||
53158 | */ | ||
53159 | if( eFileLock==SHARED_LOCK && | ||
53160 | (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ | ||
53161 | pFile->eFileLock = SHARED_LOCK; | ||
53162 | pInode->nShared++; | ||
53163 | pInode->nLock++; | ||
53164 | goto end_lock; | ||
53165 | } | ||
53166 | /* A PENDING lock is needed before acquiring a SHARED lock and before | ||
53167 | ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will | ||
53168 | ** be released. | ||
53169 | */ | ||
53170 | lock.l_len = 1L; | ||
53171 | lock.l_whence = SEEK_SET; | ||
53172 | if( eFileLock==SHARED_LOCK | ||
53173 | || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLock<PENDING_LOCK) | ||
53174 | ){ | ||
53175 | lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK); | ||
53176 | lock.l_start = PENDING_BYTE; | ||
53177 | s = fcntl(pFile->h, F_SETLK, &lock); | ||
53178 | if( s==(-1) ){ | ||
53179 | tErrno = errno; | ||
53180 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53181 | if( IS_LOCK_ERROR(rc) ){ | ||
53182 | pFile->lastErrno = tErrno; | ||
53183 | } | ||
53184 | goto end_lock; | ||
53185 | } | ||
53186 | } | ||
53187 | /* If control gets to this point, then actually go ahead and make | ||
53188 | ** operating system calls for the specified lock. | ||
53189 | */ | ||
53190 | if( eFileLock==SHARED_LOCK ){ | ||
53191 | /* Now get the read-lock */ | ||
53192 | lock.l_start = SHARED_FIRST; | ||
53193 | lock.l_len = SHARED_SIZE; | ||
53194 | if( (s = fcntl(pFile->h, F_SETLK, &lock))==(-1) ){ | ||
53195 | tErrno = errno; | ||
53196 | } | ||
53197 | /* Drop the temporary PENDING lock */ | ||
53198 | lock.l_start = PENDING_BYTE; | ||
53199 | lock.l_len = 1L; | ||
53200 | lock.l_type = F_UNLCK; | ||
53201 | if( fcntl(pFile->h, F_SETLK, &lock)!=0 ){ | ||
53202 | if( s != -1 ){ | ||
53203 | /* This could happen with a network mount */ | ||
53204 | tErrno = errno; | ||
53205 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53206 | if( IS_LOCK_ERROR(rc) ){ | ||
53207 | pFile->lastErrno = tErrno; | ||
53208 | } | ||
53209 | goto end_lock; | ||
53210 | } | ||
53211 | } | ||
53212 | if( s==(-1) ){ | ||
53213 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53214 | if( IS_LOCK_ERROR(rc) ){ | ||
53215 | pFile->lastErrno = tErrno; | ||
53216 | } | ||
53217 | }else{ | ||
53218 | pFile->eFileLock = SHARED_LOCK; | ||
53219 | pInode->nLock++; | ||
53220 | pInode->nShared = 1; | ||
53221 | } | ||
53222 | }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){ | ||
53223 | /* We are trying for an exclusive lock but another thread in this | ||
53224 | ** same process is still holding a shared lock. */ | ||
53225 | rc = UNQLITE_BUSY; | ||
53226 | }else{ | ||
53227 | /* The request was for a RESERVED or EXCLUSIVE lock. It is | ||
53228 | ** assumed that there is a SHARED or greater lock on the file | ||
53229 | ** already. | ||
53230 | */ | ||
53231 | lock.l_type = F_WRLCK; | ||
53232 | switch( eFileLock ){ | ||
53233 | case RESERVED_LOCK: | ||
53234 | lock.l_start = RESERVED_BYTE; | ||
53235 | break; | ||
53236 | case EXCLUSIVE_LOCK: | ||
53237 | lock.l_start = SHARED_FIRST; | ||
53238 | lock.l_len = SHARED_SIZE; | ||
53239 | break; | ||
53240 | default: | ||
53241 | /* Can't happen */ | ||
53242 | break; | ||
53243 | } | ||
53244 | s = fcntl(pFile->h, F_SETLK, &lock); | ||
53245 | if( s==(-1) ){ | ||
53246 | tErrno = errno; | ||
53247 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53248 | if( IS_LOCK_ERROR(rc) ){ | ||
53249 | pFile->lastErrno = tErrno; | ||
53250 | } | ||
53251 | } | ||
53252 | } | ||
53253 | if( rc==UNQLITE_OK ){ | ||
53254 | pFile->eFileLock = eFileLock; | ||
53255 | pInode->eFileLock = eFileLock; | ||
53256 | }else if( eFileLock==EXCLUSIVE_LOCK ){ | ||
53257 | pFile->eFileLock = PENDING_LOCK; | ||
53258 | pInode->eFileLock = PENDING_LOCK; | ||
53259 | } | ||
53260 | end_lock: | ||
53261 | unixLeaveMutex(); | ||
53262 | return rc; | ||
53263 | } | ||
53264 | /* | ||
53265 | ** Add the file descriptor used by file handle pFile to the corresponding | ||
53266 | ** pUnused list. | ||
53267 | */ | ||
53268 | static void setPendingFd(unixFile *pFile){ | ||
53269 | unixInodeInfo *pInode = pFile->pInode; | ||
53270 | UnixUnusedFd *p = pFile->pUnused; | ||
53271 | p->pNext = pInode->pUnused; | ||
53272 | pInode->pUnused = p; | ||
53273 | pFile->h = -1; | ||
53274 | pFile->pUnused = 0; | ||
53275 | } | ||
53276 | /* | ||
53277 | ** Lower the locking level on file descriptor pFile to eFileLock. eFileLock | ||
53278 | ** must be either NO_LOCK or SHARED_LOCK. | ||
53279 | ** | ||
53280 | ** If the locking level of the file descriptor is already at or below | ||
53281 | ** the requested locking level, this routine is a no-op. | ||
53282 | ** | ||
53283 | ** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED | ||
53284 | ** the byte range is divided into 2 parts and the first part is unlocked then | ||
53285 | ** set to a read lock, then the other part is simply unlocked. This works | ||
53286 | ** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to | ||
53287 | ** remove the write lock on a region when a read lock is set. | ||
53288 | */ | ||
53289 | static int _posixUnlock(unqlite_file *id, int eFileLock, int handleNFSUnlock){ | ||
53290 | unixFile *pFile = (unixFile*)id; | ||
53291 | unixInodeInfo *pInode; | ||
53292 | struct flock lock; | ||
53293 | int rc = UNQLITE_OK; | ||
53294 | int h; | ||
53295 | int tErrno; /* Error code from system call errors */ | ||
53296 | |||
53297 | if( pFile->eFileLock<=eFileLock ){ | ||
53298 | return UNQLITE_OK; | ||
53299 | } | ||
53300 | unixEnterMutex(); | ||
53301 | |||
53302 | h = pFile->h; | ||
53303 | pInode = pFile->pInode; | ||
53304 | |||
53305 | if( pFile->eFileLock>SHARED_LOCK ){ | ||
53306 | /* downgrading to a shared lock on NFS involves clearing the write lock | ||
53307 | ** before establishing the readlock - to avoid a race condition we downgrade | ||
53308 | ** the lock in 2 blocks, so that part of the range will be covered by a | ||
53309 | ** write lock until the rest is covered by a read lock: | ||
53310 | ** 1: [WWWWW] | ||
53311 | ** 2: [....W] | ||
53312 | ** 3: [RRRRW] | ||
53313 | ** 4: [RRRR.] | ||
53314 | */ | ||
53315 | if( eFileLock==SHARED_LOCK ){ | ||
53316 | if( handleNFSUnlock ){ | ||
53317 | off_t divSize = SHARED_SIZE - 1; | ||
53318 | |||
53319 | lock.l_type = F_UNLCK; | ||
53320 | lock.l_whence = SEEK_SET; | ||
53321 | lock.l_start = SHARED_FIRST; | ||
53322 | lock.l_len = divSize; | ||
53323 | if( fcntl(h, F_SETLK, &lock)==(-1) ){ | ||
53324 | tErrno = errno; | ||
53325 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53326 | if( IS_LOCK_ERROR(rc) ){ | ||
53327 | pFile->lastErrno = tErrno; | ||
53328 | } | ||
53329 | goto end_unlock; | ||
53330 | } | ||
53331 | lock.l_type = F_RDLCK; | ||
53332 | lock.l_whence = SEEK_SET; | ||
53333 | lock.l_start = SHARED_FIRST; | ||
53334 | lock.l_len = divSize; | ||
53335 | if( fcntl(h, F_SETLK, &lock)==(-1) ){ | ||
53336 | tErrno = errno; | ||
53337 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53338 | if( IS_LOCK_ERROR(rc) ){ | ||
53339 | pFile->lastErrno = tErrno; | ||
53340 | } | ||
53341 | goto end_unlock; | ||
53342 | } | ||
53343 | lock.l_type = F_UNLCK; | ||
53344 | lock.l_whence = SEEK_SET; | ||
53345 | lock.l_start = SHARED_FIRST+divSize; | ||
53346 | lock.l_len = SHARED_SIZE-divSize; | ||
53347 | if( fcntl(h, F_SETLK, &lock)==(-1) ){ | ||
53348 | tErrno = errno; | ||
53349 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53350 | if( IS_LOCK_ERROR(rc) ){ | ||
53351 | pFile->lastErrno = tErrno; | ||
53352 | } | ||
53353 | goto end_unlock; | ||
53354 | } | ||
53355 | }else{ | ||
53356 | lock.l_type = F_RDLCK; | ||
53357 | lock.l_whence = SEEK_SET; | ||
53358 | lock.l_start = SHARED_FIRST; | ||
53359 | lock.l_len = SHARED_SIZE; | ||
53360 | if( fcntl(h, F_SETLK, &lock)==(-1) ){ | ||
53361 | tErrno = errno; | ||
53362 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53363 | if( IS_LOCK_ERROR(rc) ){ | ||
53364 | pFile->lastErrno = tErrno; | ||
53365 | } | ||
53366 | goto end_unlock; | ||
53367 | } | ||
53368 | } | ||
53369 | } | ||
53370 | lock.l_type = F_UNLCK; | ||
53371 | lock.l_whence = SEEK_SET; | ||
53372 | lock.l_start = PENDING_BYTE; | ||
53373 | lock.l_len = 2L; | ||
53374 | if( fcntl(h, F_SETLK, &lock)!=(-1) ){ | ||
53375 | pInode->eFileLock = SHARED_LOCK; | ||
53376 | }else{ | ||
53377 | tErrno = errno; | ||
53378 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53379 | if( IS_LOCK_ERROR(rc) ){ | ||
53380 | pFile->lastErrno = tErrno; | ||
53381 | } | ||
53382 | goto end_unlock; | ||
53383 | } | ||
53384 | } | ||
53385 | if( eFileLock==NO_LOCK ){ | ||
53386 | /* Decrement the shared lock counter. Release the lock using an | ||
53387 | ** OS call only when all threads in this same process have released | ||
53388 | ** the lock. | ||
53389 | */ | ||
53390 | pInode->nShared--; | ||
53391 | if( pInode->nShared==0 ){ | ||
53392 | lock.l_type = F_UNLCK; | ||
53393 | lock.l_whence = SEEK_SET; | ||
53394 | lock.l_start = lock.l_len = 0L; | ||
53395 | |||
53396 | if( fcntl(h, F_SETLK, &lock)!=(-1) ){ | ||
53397 | pInode->eFileLock = NO_LOCK; | ||
53398 | }else{ | ||
53399 | tErrno = errno; | ||
53400 | rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); | ||
53401 | if( IS_LOCK_ERROR(rc) ){ | ||
53402 | pFile->lastErrno = tErrno; | ||
53403 | } | ||
53404 | pInode->eFileLock = NO_LOCK; | ||
53405 | pFile->eFileLock = NO_LOCK; | ||
53406 | } | ||
53407 | } | ||
53408 | |||
53409 | /* Decrement the count of locks against this same file. When the | ||
53410 | ** count reaches zero, close any other file descriptors whose close | ||
53411 | ** was deferred because of outstanding locks. | ||
53412 | */ | ||
53413 | pInode->nLock--; | ||
53414 | |||
53415 | if( pInode->nLock==0 ){ | ||
53416 | int rc2 = closePendingFds(pFile); | ||
53417 | if( rc==UNQLITE_OK ){ | ||
53418 | rc = rc2; | ||
53419 | } | ||
53420 | } | ||
53421 | } | ||
53422 | |||
53423 | end_unlock: | ||
53424 | |||
53425 | unixLeaveMutex(); | ||
53426 | |||
53427 | if( rc==UNQLITE_OK ) pFile->eFileLock = eFileLock; | ||
53428 | return rc; | ||
53429 | } | ||
53430 | /* | ||
53431 | ** Lower the locking level on file descriptor pFile to eFileLock. eFileLock | ||
53432 | ** must be either NO_LOCK or SHARED_LOCK. | ||
53433 | ** | ||
53434 | ** If the locking level of the file descriptor is already at or below | ||
53435 | ** the requested locking level, this routine is a no-op. | ||
53436 | */ | ||
53437 | static int unixUnlock(unqlite_file *id, int eFileLock){ | ||
53438 | return _posixUnlock(id, eFileLock, 0); | ||
53439 | } | ||
53440 | /* | ||
53441 | ** This function performs the parts of the "close file" operation | ||
53442 | ** common to all locking schemes. It closes the directory and file | ||
53443 | ** handles, if they are valid, and sets all fields of the unixFile | ||
53444 | ** structure to 0. | ||
53445 | ** | ||
53446 | */ | ||
53447 | static int closeUnixFile(unqlite_file *id){ | ||
53448 | unixFile *pFile = (unixFile*)id; | ||
53449 | if( pFile ){ | ||
53450 | if( pFile->dirfd>=0 ){ | ||
53451 | int err = close(pFile->dirfd); | ||
53452 | if( err ){ | ||
53453 | pFile->lastErrno = errno; | ||
53454 | return UNQLITE_IOERR; | ||
53455 | }else{ | ||
53456 | pFile->dirfd=-1; | ||
53457 | } | ||
53458 | } | ||
53459 | if( pFile->h>=0 ){ | ||
53460 | int err = close(pFile->h); | ||
53461 | if( err ){ | ||
53462 | pFile->lastErrno = errno; | ||
53463 | return UNQLITE_IOERR; | ||
53464 | } | ||
53465 | } | ||
53466 | unqlite_free(pFile->pUnused); | ||
53467 | SyZero(pFile,sizeof(unixFile)); | ||
53468 | } | ||
53469 | return UNQLITE_OK; | ||
53470 | } | ||
53471 | /* | ||
53472 | ** Close a file. | ||
53473 | */ | ||
53474 | static int unixClose(unqlite_file *id){ | ||
53475 | int rc = UNQLITE_OK; | ||
53476 | if( id ){ | ||
53477 | unixFile *pFile = (unixFile *)id; | ||
53478 | unixUnlock(id, NO_LOCK); | ||
53479 | unixEnterMutex(); | ||
53480 | if( pFile->pInode && pFile->pInode->nLock ){ | ||
53481 | /* If there are outstanding locks, do not actually close the file just | ||
53482 | ** yet because that would clear those locks. Instead, add the file | ||
53483 | ** descriptor to pInode->pUnused list. It will be automatically closed | ||
53484 | ** when the last lock is cleared. | ||
53485 | */ | ||
53486 | setPendingFd(pFile); | ||
53487 | } | ||
53488 | releaseInodeInfo(pFile); | ||
53489 | rc = closeUnixFile(id); | ||
53490 | unixLeaveMutex(); | ||
53491 | } | ||
53492 | return rc; | ||
53493 | } | ||
53494 | /************** End of the posix advisory lock implementation ***************** | ||
53495 | ******************************************************************************/ | ||
53496 | /* | ||
53497 | ** | ||
53498 | ** The next division contains implementations for all methods of the | ||
53499 | ** unqlite_file object other than the locking methods. The locking | ||
53500 | ** methods were defined in divisions above (one locking method per | ||
53501 | ** division). Those methods that are common to all locking modes | ||
53502 | ** are gather together into this division. | ||
53503 | */ | ||
53504 | /* | ||
53505 | ** Seek to the offset passed as the second argument, then read cnt | ||
53506 | ** bytes into pBuf. Return the number of bytes actually read. | ||
53507 | ** | ||
53508 | ** NB: If you define USE_PREAD or USE_PREAD64, then it might also | ||
53509 | ** be necessary to define _XOPEN_SOURCE to be 500. This varies from | ||
53510 | ** one system to another. Since SQLite does not define USE_PREAD | ||
53511 | ** any form by default, we will not attempt to define _XOPEN_SOURCE. | ||
53512 | ** See tickets #2741 and #2681. | ||
53513 | ** | ||
53514 | ** To avoid stomping the errno value on a failed read the lastErrno value | ||
53515 | ** is set before returning. | ||
53516 | */ | ||
53517 | static int seekAndRead(unixFile *id, unqlite_int64 offset, void *pBuf, int cnt){ | ||
53518 | int got; | ||
53519 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) | ||
53520 | unqlite_int64 newOffset; | ||
53521 | #endif | ||
53522 | |||
53523 | #if defined(USE_PREAD) | ||
53524 | got = pread(id->h, pBuf, cnt, offset); | ||
53525 | #elif defined(USE_PREAD64) | ||
53526 | got = pread64(id->h, pBuf, cnt, offset); | ||
53527 | #else | ||
53528 | newOffset = lseek(id->h, offset, SEEK_SET); | ||
53529 | |||
53530 | if( newOffset!=offset ){ | ||
53531 | if( newOffset == -1 ){ | ||
53532 | ((unixFile*)id)->lastErrno = errno; | ||
53533 | }else{ | ||
53534 | ((unixFile*)id)->lastErrno = 0; | ||
53535 | } | ||
53536 | return -1; | ||
53537 | } | ||
53538 | got = read(id->h, pBuf, cnt); | ||
53539 | #endif | ||
53540 | if( got<0 ){ | ||
53541 | ((unixFile*)id)->lastErrno = errno; | ||
53542 | } | ||
53543 | return got; | ||
53544 | } | ||
53545 | /* | ||
53546 | ** Read data from a file into a buffer. Return UNQLITE_OK if all | ||
53547 | ** bytes were read successfully and UNQLITE_IOERR if anything goes | ||
53548 | ** wrong. | ||
53549 | */ | ||
53550 | static int unixRead( | ||
53551 | unqlite_file *id, | ||
53552 | void *pBuf, | ||
53553 | unqlite_int64 amt, | ||
53554 | unqlite_int64 offset | ||
53555 | ){ | ||
53556 | unixFile *pFile = (unixFile *)id; | ||
53557 | int got; | ||
53558 | |||
53559 | got = seekAndRead(pFile, offset, pBuf, (int)amt); | ||
53560 | if( got==(int)amt ){ | ||
53561 | return UNQLITE_OK; | ||
53562 | }else if( got<0 ){ | ||
53563 | /* lastErrno set by seekAndRead */ | ||
53564 | return UNQLITE_IOERR; | ||
53565 | }else{ | ||
53566 | pFile->lastErrno = 0; /* not a system error */ | ||
53567 | /* Unread parts of the buffer must be zero-filled */ | ||
53568 | SyZero(&((char*)pBuf)[got],(sxu32)amt-got); | ||
53569 | return UNQLITE_IOERR; | ||
53570 | } | ||
53571 | } | ||
53572 | /* | ||
53573 | ** Seek to the offset in id->offset then read cnt bytes into pBuf. | ||
53574 | ** Return the number of bytes actually read. Update the offset. | ||
53575 | ** | ||
53576 | ** To avoid stomping the errno value on a failed write the lastErrno value | ||
53577 | ** is set before returning. | ||
53578 | */ | ||
53579 | static int seekAndWrite(unixFile *id, unqlite_int64 offset, const void *pBuf, unqlite_int64 cnt){ | ||
53580 | int got; | ||
53581 | #if (!defined(USE_PREAD) && !defined(USE_PREAD64)) | ||
53582 | unqlite_int64 newOffset; | ||
53583 | #endif | ||
53584 | |||
53585 | #if defined(USE_PREAD) | ||
53586 | got = pwrite(id->h, pBuf, cnt, offset); | ||
53587 | #elif defined(USE_PREAD64) | ||
53588 | got = pwrite64(id->h, pBuf, cnt, offset); | ||
53589 | #else | ||
53590 | newOffset = lseek(id->h, offset, SEEK_SET); | ||
53591 | if( newOffset!=offset ){ | ||
53592 | if( newOffset == -1 ){ | ||
53593 | ((unixFile*)id)->lastErrno = errno; | ||
53594 | }else{ | ||
53595 | ((unixFile*)id)->lastErrno = 0; | ||
53596 | } | ||
53597 | return -1; | ||
53598 | } | ||
53599 | got = write(id->h, pBuf, cnt); | ||
53600 | #endif | ||
53601 | if( got<0 ){ | ||
53602 | ((unixFile*)id)->lastErrno = errno; | ||
53603 | } | ||
53604 | return got; | ||
53605 | } | ||
53606 | /* | ||
53607 | ** Write data from a buffer into a file. Return UNQLITE_OK on success | ||
53608 | ** or some other error code on failure. | ||
53609 | */ | ||
53610 | static int unixWrite( | ||
53611 | unqlite_file *id, | ||
53612 | const void *pBuf, | ||
53613 | unqlite_int64 amt, | ||
53614 | unqlite_int64 offset | ||
53615 | ){ | ||
53616 | unixFile *pFile = (unixFile*)id; | ||
53617 | int wrote = 0; | ||
53618 | |||
53619 | while( amt>0 && (wrote = seekAndWrite(pFile, offset, pBuf, amt))>0 ){ | ||
53620 | amt -= wrote; | ||
53621 | offset += wrote; | ||
53622 | pBuf = &((char*)pBuf)[wrote]; | ||
53623 | } | ||
53624 | |||
53625 | if( amt>0 ){ | ||
53626 | if( wrote<0 ){ | ||
53627 | /* lastErrno set by seekAndWrite */ | ||
53628 | return UNQLITE_IOERR; | ||
53629 | }else{ | ||
53630 | pFile->lastErrno = 0; /* not a system error */ | ||
53631 | return UNQLITE_FULL; | ||
53632 | } | ||
53633 | } | ||
53634 | return UNQLITE_OK; | ||
53635 | } | ||
53636 | /* | ||
53637 | ** We do not trust systems to provide a working fdatasync(). Some do. | ||
53638 | ** Others do no. To be safe, we will stick with the (slower) fsync(). | ||
53639 | ** If you know that your system does support fdatasync() correctly, | ||
53640 | ** then simply compile with -Dfdatasync=fdatasync | ||
53641 | */ | ||
53642 | #if !defined(fdatasync) && !defined(__linux__) | ||
53643 | # define fdatasync fsync | ||
53644 | #endif | ||
53645 | |||
53646 | /* | ||
53647 | ** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not | ||
53648 | ** the F_FULLFSYNC macro is defined. F_FULLFSYNC is currently | ||
53649 | ** only available on Mac OS X. But that could change. | ||
53650 | */ | ||
53651 | #ifdef F_FULLFSYNC | ||
53652 | # define HAVE_FULLFSYNC 1 | ||
53653 | #else | ||
53654 | # define HAVE_FULLFSYNC 0 | ||
53655 | #endif | ||
53656 | /* | ||
53657 | ** The fsync() system call does not work as advertised on many | ||
53658 | ** unix systems. The following procedure is an attempt to make | ||
53659 | ** it work better. | ||
53660 | ** | ||
53661 | ** | ||
53662 | ** SQLite sets the dataOnly flag if the size of the file is unchanged. | ||
53663 | ** The idea behind dataOnly is that it should only write the file content | ||
53664 | ** to disk, not the inode. We only set dataOnly if the file size is | ||
53665 | ** unchanged since the file size is part of the inode. However, | ||
53666 | ** Ted Ts'o tells us that fdatasync() will also write the inode if the | ||
53667 | ** file size has changed. The only real difference between fdatasync() | ||
53668 | ** and fsync(), Ted tells us, is that fdatasync() will not flush the | ||
53669 | ** inode if the mtime or owner or other inode attributes have changed. | ||
53670 | ** We only care about the file size, not the other file attributes, so | ||
53671 | ** as far as SQLite is concerned, an fdatasync() is always adequate. | ||
53672 | ** So, we always use fdatasync() if it is available, regardless of | ||
53673 | ** the value of the dataOnly flag. | ||
53674 | */ | ||
53675 | static int full_fsync(int fd, int fullSync, int dataOnly){ | ||
53676 | int rc; | ||
53677 | #if HAVE_FULLFSYNC | ||
53678 | SXUNUSED(dataOnly); | ||
53679 | #else | ||
53680 | SXUNUSED(fullSync); | ||
53681 | SXUNUSED(dataOnly); | ||
53682 | #endif | ||
53683 | |||
53684 | /* If we compiled with the UNQLITE_NO_SYNC flag, then syncing is a | ||
53685 | ** no-op | ||
53686 | */ | ||
53687 | #if HAVE_FULLFSYNC | ||
53688 | if( fullSync ){ | ||
53689 | rc = fcntl(fd, F_FULLFSYNC, 0); | ||
53690 | }else{ | ||
53691 | rc = 1; | ||
53692 | } | ||
53693 | /* If the FULLFSYNC failed, fall back to attempting an fsync(). | ||
53694 | ** It shouldn't be possible for fullfsync to fail on the local | ||
53695 | ** file system (on OSX), so failure indicates that FULLFSYNC | ||
53696 | ** isn't supported for this file system. So, attempt an fsync | ||
53697 | ** and (for now) ignore the overhead of a superfluous fcntl call. | ||
53698 | ** It'd be better to detect fullfsync support once and avoid | ||
53699 | ** the fcntl call every time sync is called. | ||
53700 | */ | ||
53701 | if( rc ) rc = fsync(fd); | ||
53702 | |||
53703 | #elif defined(__APPLE__) | ||
53704 | /* fdatasync() on HFS+ doesn't yet flush the file size if it changed correctly | ||
53705 | ** so currently we default to the macro that redefines fdatasync to fsync | ||
53706 | */ | ||
53707 | rc = fsync(fd); | ||
53708 | #else | ||
53709 | rc = fdatasync(fd); | ||
53710 | #endif /* ifdef UNQLITE_NO_SYNC elif HAVE_FULLFSYNC */ | ||
53711 | if( rc!= -1 ){ | ||
53712 | rc = 0; | ||
53713 | } | ||
53714 | return rc; | ||
53715 | } | ||
53716 | /* | ||
53717 | ** Make sure all writes to a particular file are committed to disk. | ||
53718 | ** | ||
53719 | ** If dataOnly==0 then both the file itself and its metadata (file | ||
53720 | ** size, access time, etc) are synced. If dataOnly!=0 then only the | ||
53721 | ** file data is synced. | ||
53722 | ** | ||
53723 | ** Under Unix, also make sure that the directory entry for the file | ||
53724 | ** has been created by fsync-ing the directory that contains the file. | ||
53725 | ** If we do not do this and we encounter a power failure, the directory | ||
53726 | ** entry for the journal might not exist after we reboot. The next | ||
53727 | ** SQLite to access the file will not know that the journal exists (because | ||
53728 | ** the directory entry for the journal was never created) and the transaction | ||
53729 | ** will not roll back - possibly leading to database corruption. | ||
53730 | */ | ||
53731 | static int unixSync(unqlite_file *id, int flags){ | ||
53732 | int rc; | ||
53733 | unixFile *pFile = (unixFile*)id; | ||
53734 | |||
53735 | int isDataOnly = (flags&UNQLITE_SYNC_DATAONLY); | ||
53736 | int isFullsync = (flags&0x0F)==UNQLITE_SYNC_FULL; | ||
53737 | |||
53738 | rc = full_fsync(pFile->h, isFullsync, isDataOnly); | ||
53739 | |||
53740 | if( rc ){ | ||
53741 | pFile->lastErrno = errno; | ||
53742 | return UNQLITE_IOERR; | ||
53743 | } | ||
53744 | if( pFile->dirfd>=0 ){ | ||
53745 | int err; | ||
53746 | #ifndef UNQLITE_DISABLE_DIRSYNC | ||
53747 | /* The directory sync is only attempted if full_fsync is | ||
53748 | ** turned off or unavailable. If a full_fsync occurred above, | ||
53749 | ** then the directory sync is superfluous. | ||
53750 | */ | ||
53751 | if( (!HAVE_FULLFSYNC || !isFullsync) && full_fsync(pFile->dirfd,0,0) ){ | ||
53752 | /* | ||
53753 | ** We have received multiple reports of fsync() returning | ||
53754 | ** errors when applied to directories on certain file systems. | ||
53755 | ** A failed directory sync is not a big deal. So it seems | ||
53756 | ** better to ignore the error. Ticket #1657 | ||
53757 | */ | ||
53758 | /* pFile->lastErrno = errno; */ | ||
53759 | /* return UNQLITE_IOERR; */ | ||
53760 | } | ||
53761 | #endif | ||
53762 | err = close(pFile->dirfd); /* Only need to sync once, so close the */ | ||
53763 | if( err==0 ){ /* directory when we are done */ | ||
53764 | pFile->dirfd = -1; | ||
53765 | }else{ | ||
53766 | pFile->lastErrno = errno; | ||
53767 | rc = UNQLITE_IOERR; | ||
53768 | } | ||
53769 | } | ||
53770 | return rc; | ||
53771 | } | ||
53772 | /* | ||
53773 | ** Truncate an open file to a specified size | ||
53774 | */ | ||
53775 | static int unixTruncate(unqlite_file *id, sxi64 nByte){ | ||
53776 | unixFile *pFile = (unixFile *)id; | ||
53777 | int rc; | ||
53778 | |||
53779 | rc = ftruncate(pFile->h, (off_t)nByte); | ||
53780 | if( rc ){ | ||
53781 | pFile->lastErrno = errno; | ||
53782 | return UNQLITE_IOERR; | ||
53783 | }else{ | ||
53784 | return UNQLITE_OK; | ||
53785 | } | ||
53786 | } | ||
53787 | /* | ||
53788 | ** Determine the current size of a file in bytes | ||
53789 | */ | ||
53790 | static int unixFileSize(unqlite_file *id,sxi64 *pSize){ | ||
53791 | int rc; | ||
53792 | struct stat buf; | ||
53793 | |||
53794 | rc = fstat(((unixFile*)id)->h, &buf); | ||
53795 | |||
53796 | if( rc!=0 ){ | ||
53797 | ((unixFile*)id)->lastErrno = errno; | ||
53798 | return UNQLITE_IOERR; | ||
53799 | } | ||
53800 | *pSize = buf.st_size; | ||
53801 | |||
53802 | /* When opening a zero-size database, the findInodeInfo() procedure | ||
53803 | ** writes a single byte into that file in order to work around a bug | ||
53804 | ** in the OS-X msdos filesystem. In order to avoid problems with upper | ||
53805 | ** layers, we need to report this file size as zero even though it is | ||
53806 | ** really 1. Ticket #3260. | ||
53807 | */ | ||
53808 | if( *pSize==1 ) *pSize = 0; | ||
53809 | |||
53810 | return UNQLITE_OK; | ||
53811 | } | ||
53812 | /* | ||
53813 | ** Return the sector size in bytes of the underlying block device for | ||
53814 | ** the specified file. This is almost always 512 bytes, but may be | ||
53815 | ** larger for some devices. | ||
53816 | ** | ||
53817 | ** SQLite code assumes this function cannot fail. It also assumes that | ||
53818 | ** if two files are created in the same file-system directory (i.e. | ||
53819 | ** a database and its journal file) that the sector size will be the | ||
53820 | ** same for both. | ||
53821 | */ | ||
53822 | static int unixSectorSize(unqlite_file *NotUsed){ | ||
53823 | SXUNUSED(NotUsed); | ||
53824 | return UNQLITE_DEFAULT_SECTOR_SIZE; | ||
53825 | } | ||
53826 | /* | ||
53827 | ** This vector defines all the methods that can operate on an | ||
53828 | ** unqlite_file for Windows systems. | ||
53829 | */ | ||
53830 | static const unqlite_io_methods unixIoMethod = { | ||
53831 | 1, /* iVersion */ | ||
53832 | unixClose, /* xClose */ | ||
53833 | unixRead, /* xRead */ | ||
53834 | unixWrite, /* xWrite */ | ||
53835 | unixTruncate, /* xTruncate */ | ||
53836 | unixSync, /* xSync */ | ||
53837 | unixFileSize, /* xFileSize */ | ||
53838 | unixLock, /* xLock */ | ||
53839 | unixUnlock, /* xUnlock */ | ||
53840 | unixCheckReservedLock, /* xCheckReservedLock */ | ||
53841 | unixSectorSize, /* xSectorSize */ | ||
53842 | }; | ||
53843 | /**************************************************************************** | ||
53844 | **************************** unqlite_vfs methods **************************** | ||
53845 | ** | ||
53846 | ** This division contains the implementation of methods on the | ||
53847 | ** unqlite_vfs object. | ||
53848 | */ | ||
53849 | /* | ||
53850 | ** Initialize the contents of the unixFile structure pointed to by pId. | ||
53851 | */ | ||
53852 | static int fillInUnixFile( | ||
53853 | unqlite_vfs *pVfs, /* Pointer to vfs object */ | ||
53854 | int h, /* Open file descriptor of file being opened */ | ||
53855 | int dirfd, /* Directory file descriptor */ | ||
53856 | unqlite_file *pId, /* Write to the unixFile structure here */ | ||
53857 | const char *zFilename, /* Name of the file being opened */ | ||
53858 | int noLock, /* Omit locking if true */ | ||
53859 | int isDelete /* Delete on close if true */ | ||
53860 | ){ | ||
53861 | const unqlite_io_methods *pLockingStyle = &unixIoMethod; | ||
53862 | unixFile *pNew = (unixFile *)pId; | ||
53863 | int rc = UNQLITE_OK; | ||
53864 | |||
53865 | /* Parameter isDelete is only used on vxworks. Express this explicitly | ||
53866 | ** here to prevent compiler warnings about unused parameters. | ||
53867 | */ | ||
53868 | SXUNUSED(isDelete); | ||
53869 | SXUNUSED(noLock); | ||
53870 | SXUNUSED(pVfs); | ||
53871 | |||
53872 | pNew->h = h; | ||
53873 | pNew->dirfd = dirfd; | ||
53874 | pNew->fileFlags = 0; | ||
53875 | pNew->zPath = zFilename; | ||
53876 | |||
53877 | unixEnterMutex(); | ||
53878 | rc = findInodeInfo(pNew, &pNew->pInode); | ||
53879 | if( rc!=UNQLITE_OK ){ | ||
53880 | /* If an error occured in findInodeInfo(), close the file descriptor | ||
53881 | ** immediately, before releasing the mutex. findInodeInfo() may fail | ||
53882 | ** in two scenarios: | ||
53883 | ** | ||
53884 | ** (a) A call to fstat() failed. | ||
53885 | ** (b) A malloc failed. | ||
53886 | ** | ||
53887 | ** Scenario (b) may only occur if the process is holding no other | ||
53888 | ** file descriptors open on the same file. If there were other file | ||
53889 | ** descriptors on this file, then no malloc would be required by | ||
53890 | ** findInodeInfo(). If this is the case, it is quite safe to close | ||
53891 | ** handle h - as it is guaranteed that no posix locks will be released | ||
53892 | ** by doing so. | ||
53893 | ** | ||
53894 | ** If scenario (a) caused the error then things are not so safe. The | ||
53895 | ** implicit assumption here is that if fstat() fails, things are in | ||
53896 | ** such bad shape that dropping a lock or two doesn't matter much. | ||
53897 | */ | ||
53898 | close(h); | ||
53899 | h = -1; | ||
53900 | } | ||
53901 | unixLeaveMutex(); | ||
53902 | |||
53903 | pNew->lastErrno = 0; | ||
53904 | if( rc!=UNQLITE_OK ){ | ||
53905 | if( dirfd>=0 ) close(dirfd); /* silent leak if fail, already in error */ | ||
53906 | if( h>=0 ) close(h); | ||
53907 | }else{ | ||
53908 | pNew->pMethod = pLockingStyle; | ||
53909 | } | ||
53910 | return rc; | ||
53911 | } | ||
53912 | /* | ||
53913 | ** Open a file descriptor to the directory containing file zFilename. | ||
53914 | ** If successful, *pFd is set to the opened file descriptor and | ||
53915 | ** UNQLITE_OK is returned. If an error occurs, either UNQLITE_NOMEM | ||
53916 | ** or UNQLITE_CANTOPEN is returned and *pFd is set to an undefined | ||
53917 | ** value. | ||
53918 | ** | ||
53919 | ** If UNQLITE_OK is returned, the caller is responsible for closing | ||
53920 | ** the file descriptor *pFd using close(). | ||
53921 | */ | ||
53922 | static int openDirectory(const char *zFilename, int *pFd){ | ||
53923 | sxu32 ii; | ||
53924 | int fd = -1; | ||
53925 | char zDirname[MAX_PATHNAME+1]; | ||
53926 | sxu32 n; | ||
53927 | n = Systrcpy(zDirname,sizeof(zDirname),zFilename,0); | ||
53928 | for(ii=n; ii>1 && zDirname[ii]!='/'; ii--); | ||
53929 | if( ii>0 ){ | ||
53930 | zDirname[ii] = '\0'; | ||
53931 | fd = open(zDirname, O_RDONLY|O_BINARY, 0); | ||
53932 | if( fd>=0 ){ | ||
53933 | #ifdef FD_CLOEXEC | ||
53934 | fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); | ||
53935 | #endif | ||
53936 | } | ||
53937 | } | ||
53938 | *pFd = fd; | ||
53939 | return (fd>=0?UNQLITE_OK: UNQLITE_IOERR ); | ||
53940 | } | ||
53941 | /* | ||
53942 | ** Search for an unused file descriptor that was opened on the database | ||
53943 | ** file (not a journal or master-journal file) identified by pathname | ||
53944 | ** zPath with UNQLITE_OPEN_XXX flags matching those passed as the second | ||
53945 | ** argument to this function. | ||
53946 | ** | ||
53947 | ** Such a file descriptor may exist if a database connection was closed | ||
53948 | ** but the associated file descriptor could not be closed because some | ||
53949 | ** other file descriptor open on the same file is holding a file-lock. | ||
53950 | ** Refer to comments in the unixClose() function and the lengthy comment | ||
53951 | ** describing "Posix Advisory Locking" at the start of this file for | ||
53952 | ** further details. Also, ticket #4018. | ||
53953 | ** | ||
53954 | ** If a suitable file descriptor is found, then it is returned. If no | ||
53955 | ** such file descriptor is located, -1 is returned. | ||
53956 | */ | ||
53957 | static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ | ||
53958 | UnixUnusedFd *pUnused = 0; | ||
53959 | struct stat sStat; /* Results of stat() call */ | ||
53960 | /* A stat() call may fail for various reasons. If this happens, it is | ||
53961 | ** almost certain that an open() call on the same path will also fail. | ||
53962 | ** For this reason, if an error occurs in the stat() call here, it is | ||
53963 | ** ignored and -1 is returned. The caller will try to open a new file | ||
53964 | ** descriptor on the same path, fail, and return an error to SQLite. | ||
53965 | ** | ||
53966 | ** Even if a subsequent open() call does succeed, the consequences of | ||
53967 | ** not searching for a resusable file descriptor are not dire. */ | ||
53968 | if( 0==stat(zPath, &sStat) ){ | ||
53969 | unixInodeInfo *pInode; | ||
53970 | |||
53971 | unixEnterMutex(); | ||
53972 | pInode = inodeList; | ||
53973 | while( pInode && (pInode->fileId.dev!=sStat.st_dev | ||
53974 | || pInode->fileId.ino!=sStat.st_ino) ){ | ||
53975 | pInode = pInode->pNext; | ||
53976 | } | ||
53977 | if( pInode ){ | ||
53978 | UnixUnusedFd **pp; | ||
53979 | for(pp=&pInode->pUnused; *pp && (*pp)->flags!=flags; pp=&((*pp)->pNext)); | ||
53980 | pUnused = *pp; | ||
53981 | if( pUnused ){ | ||
53982 | *pp = pUnused->pNext; | ||
53983 | } | ||
53984 | } | ||
53985 | unixLeaveMutex(); | ||
53986 | } | ||
53987 | return pUnused; | ||
53988 | } | ||
53989 | /* | ||
53990 | ** This function is called by unixOpen() to determine the unix permissions | ||
53991 | ** to create new files with. If no error occurs, then UNQLITE_OK is returned | ||
53992 | ** and a value suitable for passing as the third argument to open(2) is | ||
53993 | ** written to *pMode. If an IO error occurs, an SQLite error code is | ||
53994 | ** returned and the value of *pMode is not modified. | ||
53995 | ** | ||
53996 | ** If the file being opened is a temporary file, it is always created with | ||
53997 | ** the octal permissions 0600 (read/writable by owner only). If the file | ||
53998 | ** is a database or master journal file, it is created with the permissions | ||
53999 | ** mask UNQLITE_DEFAULT_FILE_PERMISSIONS. | ||
54000 | ** | ||
54001 | ** Finally, if the file being opened is a WAL or regular journal file, then | ||
54002 | ** this function queries the file-system for the permissions on the | ||
54003 | ** corresponding database file and sets *pMode to this value. Whenever | ||
54004 | ** possible, WAL and journal files are created using the same permissions | ||
54005 | ** as the associated database file. | ||
54006 | */ | ||
54007 | static int findCreateFileMode( | ||
54008 | const char *zPath, /* Path of file (possibly) being created */ | ||
54009 | int flags, /* Flags passed as 4th argument to xOpen() */ | ||
54010 | mode_t *pMode /* OUT: Permissions to open file with */ | ||
54011 | ){ | ||
54012 | int rc = UNQLITE_OK; /* Return Code */ | ||
54013 | if( flags & UNQLITE_OPEN_TEMP_DB ){ | ||
54014 | *pMode = 0600; | ||
54015 | SXUNUSED(zPath); | ||
54016 | }else{ | ||
54017 | *pMode = UNQLITE_DEFAULT_FILE_PERMISSIONS; | ||
54018 | } | ||
54019 | return rc; | ||
54020 | } | ||
54021 | /* | ||
54022 | ** Open the file zPath. | ||
54023 | ** | ||
54024 | ** Previously, the SQLite OS layer used three functions in place of this | ||
54025 | ** one: | ||
54026 | ** | ||
54027 | ** unqliteOsOpenReadWrite(); | ||
54028 | ** unqliteOsOpenReadOnly(); | ||
54029 | ** unqliteOsOpenExclusive(); | ||
54030 | ** | ||
54031 | ** These calls correspond to the following combinations of flags: | ||
54032 | ** | ||
54033 | ** ReadWrite() -> (READWRITE | CREATE) | ||
54034 | ** ReadOnly() -> (READONLY) | ||
54035 | ** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE) | ||
54036 | ** | ||
54037 | ** The old OpenExclusive() accepted a boolean argument - "delFlag". If | ||
54038 | ** true, the file was configured to be automatically deleted when the | ||
54039 | ** file handle closed. To achieve the same effect using this new | ||
54040 | ** interface, add the DELETEONCLOSE flag to those specified above for | ||
54041 | ** OpenExclusive(). | ||
54042 | */ | ||
54043 | static int unixOpen( | ||
54044 | unqlite_vfs *pVfs, /* The VFS for which this is the xOpen method */ | ||
54045 | const char *zPath, /* Pathname of file to be opened */ | ||
54046 | unqlite_file *pFile, /* The file descriptor to be filled in */ | ||
54047 | unsigned int flags /* Input flags to control the opening */ | ||
54048 | ){ | ||
54049 | unixFile *p = (unixFile *)pFile; | ||
54050 | int fd = -1; /* File descriptor returned by open() */ | ||
54051 | int dirfd = -1; /* Directory file descriptor */ | ||
54052 | int openFlags = 0; /* Flags to pass to open() */ | ||
54053 | int noLock; /* True to omit locking primitives */ | ||
54054 | int rc = UNQLITE_OK; /* Function Return Code */ | ||
54055 | UnixUnusedFd *pUnused; | ||
54056 | int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE); | ||
54057 | int isDelete = (flags & UNQLITE_OPEN_TEMP_DB); | ||
54058 | int isCreate = (flags & UNQLITE_OPEN_CREATE); | ||
54059 | int isReadonly = (flags & UNQLITE_OPEN_READONLY); | ||
54060 | int isReadWrite = (flags & UNQLITE_OPEN_READWRITE); | ||
54061 | /* If creating a master or main-file journal, this function will open | ||
54062 | ** a file-descriptor on the directory too. The first time unixSync() | ||
54063 | ** is called the directory file descriptor will be fsync()ed and close()d. | ||
54064 | */ | ||
54065 | int isOpenDirectory = isCreate ; | ||
54066 | const char *zName = zPath; | ||
54067 | |||
54068 | SyZero(p,sizeof(unixFile)); | ||
54069 | |||
54070 | pUnused = findReusableFd(zName, flags); | ||
54071 | if( pUnused ){ | ||
54072 | fd = pUnused->fd; | ||
54073 | }else{ | ||
54074 | pUnused = unqlite_malloc(sizeof(*pUnused)); | ||
54075 | if( !pUnused ){ | ||
54076 | return UNQLITE_NOMEM; | ||
54077 | } | ||
54078 | } | ||
54079 | p->pUnused = pUnused; | ||
54080 | |||
54081 | /* Determine the value of the flags parameter passed to POSIX function | ||
54082 | ** open(). These must be calculated even if open() is not called, as | ||
54083 | ** they may be stored as part of the file handle and used by the | ||
54084 | ** 'conch file' locking functions later on. */ | ||
54085 | if( isReadonly ) openFlags |= O_RDONLY; | ||
54086 | if( isReadWrite ) openFlags |= O_RDWR; | ||
54087 | if( isCreate ) openFlags |= O_CREAT; | ||
54088 | if( isExclusive ) openFlags |= (O_EXCL|O_NOFOLLOW); | ||
54089 | openFlags |= (O_LARGEFILE|O_BINARY); | ||
54090 | |||
54091 | if( fd<0 ){ | ||
54092 | mode_t openMode; /* Permissions to create file with */ | ||
54093 | rc = findCreateFileMode(zName, flags, &openMode); | ||
54094 | if( rc!=UNQLITE_OK ){ | ||
54095 | return rc; | ||
54096 | } | ||
54097 | fd = open(zName, openFlags, openMode); | ||
54098 | if( fd<0 ){ | ||
54099 | rc = UNQLITE_IOERR; | ||
54100 | goto open_finished; | ||
54101 | } | ||
54102 | } | ||
54103 | |||
54104 | if( p->pUnused ){ | ||
54105 | p->pUnused->fd = fd; | ||
54106 | p->pUnused->flags = flags; | ||
54107 | } | ||
54108 | |||
54109 | if( isDelete ){ | ||
54110 | unlink(zName); | ||
54111 | } | ||
54112 | |||
54113 | if( isOpenDirectory ){ | ||
54114 | rc = openDirectory(zPath, &dirfd); | ||
54115 | if( rc!=UNQLITE_OK ){ | ||
54116 | /* It is safe to close fd at this point, because it is guaranteed not | ||
54117 | ** to be open on a database file. If it were open on a database file, | ||
54118 | ** it would not be safe to close as this would release any locks held | ||
54119 | ** on the file by this process. */ | ||
54120 | close(fd); /* silently leak if fail, already in error */ | ||
54121 | goto open_finished; | ||
54122 | } | ||
54123 | } | ||
54124 | |||
54125 | #ifdef FD_CLOEXEC | ||
54126 | fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); | ||
54127 | #endif | ||
54128 | |||
54129 | noLock = 0; | ||
54130 | |||
54131 | #if defined(__APPLE__) | ||
54132 | struct statfs fsInfo; | ||
54133 | if( fstatfs(fd, &fsInfo) == -1 ){ | ||
54134 | ((unixFile*)pFile)->lastErrno = errno; | ||
54135 | if( dirfd>=0 ) close(dirfd); /* silently leak if fail, in error */ | ||
54136 | close(fd); /* silently leak if fail, in error */ | ||
54137 | return UNQLITE_IOERR; | ||
54138 | } | ||
54139 | if (0 == SyStrncmp("msdos", fsInfo.f_fstypename, 5)) { | ||
54140 | ((unixFile*)pFile)->fsFlags |= UNQLITE_FSFLAGS_IS_MSDOS; | ||
54141 | } | ||
54142 | #endif | ||
54143 | |||
54144 | rc = fillInUnixFile(pVfs, fd, dirfd, pFile, zPath, noLock, isDelete); | ||
54145 | open_finished: | ||
54146 | if( rc!=UNQLITE_OK ){ | ||
54147 | unqlite_free(p->pUnused); | ||
54148 | } | ||
54149 | return rc; | ||
54150 | } | ||
54151 | /* | ||
54152 | ** Delete the file at zPath. If the dirSync argument is true, fsync() | ||
54153 | ** the directory after deleting the file. | ||
54154 | */ | ||
54155 | static int unixDelete( | ||
54156 | unqlite_vfs *NotUsed, /* VFS containing this as the xDelete method */ | ||
54157 | const char *zPath, /* Name of file to be deleted */ | ||
54158 | int dirSync /* If true, fsync() directory after deleting file */ | ||
54159 | ){ | ||
54160 | int rc = UNQLITE_OK; | ||
54161 | SXUNUSED(NotUsed); | ||
54162 | |||
54163 | if( unlink(zPath)==(-1) && errno!=ENOENT ){ | ||
54164 | return UNQLITE_IOERR; | ||
54165 | } | ||
54166 | #ifndef UNQLITE_DISABLE_DIRSYNC | ||
54167 | if( dirSync ){ | ||
54168 | int fd; | ||
54169 | rc = openDirectory(zPath, &fd); | ||
54170 | if( rc==UNQLITE_OK ){ | ||
54171 | if( fsync(fd) ) | ||
54172 | { | ||
54173 | rc = UNQLITE_IOERR; | ||
54174 | } | ||
54175 | if( close(fd) && !rc ){ | ||
54176 | rc = UNQLITE_IOERR; | ||
54177 | } | ||
54178 | } | ||
54179 | } | ||
54180 | #endif | ||
54181 | return rc; | ||
54182 | } | ||
54183 | /* | ||
54184 | ** Sleep for a little while. Return the amount of time slept. | ||
54185 | ** The argument is the number of microseconds we want to sleep. | ||
54186 | ** The return value is the number of microseconds of sleep actually | ||
54187 | ** requested from the underlying operating system, a number which | ||
54188 | ** might be greater than or equal to the argument, but not less | ||
54189 | ** than the argument. | ||
54190 | */ | ||
54191 | static int unixSleep(unqlite_vfs *NotUsed, int microseconds) | ||
54192 | { | ||
54193 | #if defined(HAVE_USLEEP) && HAVE_USLEEP | ||
54194 | usleep(microseconds); | ||
54195 | SXUNUSED(NotUsed); | ||
54196 | return microseconds; | ||
54197 | #else | ||
54198 | int seconds = (microseconds+999999)/1000000; | ||
54199 | SXUNUSED(NotUsed); | ||
54200 | sleep(seconds); | ||
54201 | return seconds*1000000; | ||
54202 | #endif | ||
54203 | } | ||
54204 | /* | ||
54205 | * Export the current system time. | ||
54206 | */ | ||
54207 | static int unixCurrentTime(unqlite_vfs *pVfs,Sytm *pOut) | ||
54208 | { | ||
54209 | struct tm *pTm; | ||
54210 | time_t tt; | ||
54211 | SXUNUSED(pVfs); | ||
54212 | time(&tt); | ||
54213 | pTm = gmtime(&tt); | ||
54214 | if( pTm ){ /* Yes, it can fail */ | ||
54215 | STRUCT_TM_TO_SYTM(pTm,pOut); | ||
54216 | } | ||
54217 | return UNQLITE_OK; | ||
54218 | } | ||
54219 | /* | ||
54220 | ** Test the existance of or access permissions of file zPath. The | ||
54221 | ** test performed depends on the value of flags: | ||
54222 | ** | ||
54223 | ** UNQLITE_ACCESS_EXISTS: Return 1 if the file exists | ||
54224 | ** UNQLITE_ACCESS_READWRITE: Return 1 if the file is read and writable. | ||
54225 | ** UNQLITE_ACCESS_READONLY: Return 1 if the file is readable. | ||
54226 | ** | ||
54227 | ** Otherwise return 0. | ||
54228 | */ | ||
54229 | static int unixAccess( | ||
54230 | unqlite_vfs *NotUsed, /* The VFS containing this xAccess method */ | ||
54231 | const char *zPath, /* Path of the file to examine */ | ||
54232 | int flags, /* What do we want to learn about the zPath file? */ | ||
54233 | int *pResOut /* Write result boolean here */ | ||
54234 | ){ | ||
54235 | int amode = 0; | ||
54236 | SXUNUSED(NotUsed); | ||
54237 | switch( flags ){ | ||
54238 | case UNQLITE_ACCESS_EXISTS: | ||
54239 | amode = F_OK; | ||
54240 | break; | ||
54241 | case UNQLITE_ACCESS_READWRITE: | ||
54242 | amode = W_OK|R_OK; | ||
54243 | break; | ||
54244 | case UNQLITE_ACCESS_READ: | ||
54245 | amode = R_OK; | ||
54246 | break; | ||
54247 | default: | ||
54248 | /* Can't happen */ | ||
54249 | break; | ||
54250 | } | ||
54251 | *pResOut = (access(zPath, amode)==0); | ||
54252 | if( flags==UNQLITE_ACCESS_EXISTS && *pResOut ){ | ||
54253 | struct stat buf; | ||
54254 | if( 0==stat(zPath, &buf) && buf.st_size==0 ){ | ||
54255 | *pResOut = 0; | ||
54256 | } | ||
54257 | } | ||
54258 | return UNQLITE_OK; | ||
54259 | } | ||
54260 | /* | ||
54261 | ** Turn a relative pathname into a full pathname. The relative path | ||
54262 | ** is stored as a nul-terminated string in the buffer pointed to by | ||
54263 | ** zPath. | ||
54264 | ** | ||
54265 | ** zOut points to a buffer of at least unqlite_vfs.mxPathname bytes | ||
54266 | ** (in this case, MAX_PATHNAME bytes). The full-path is written to | ||
54267 | ** this buffer before returning. | ||
54268 | */ | ||
54269 | static int unixFullPathname( | ||
54270 | unqlite_vfs *pVfs, /* Pointer to vfs object */ | ||
54271 | const char *zPath, /* Possibly relative input path */ | ||
54272 | int nOut, /* Size of output buffer in bytes */ | ||
54273 | char *zOut /* Output buffer */ | ||
54274 | ){ | ||
54275 | if( zPath[0]=='/' ){ | ||
54276 | Systrcpy(zOut,(sxu32)nOut,zPath,0); | ||
54277 | SXUNUSED(pVfs); | ||
54278 | }else{ | ||
54279 | sxu32 nCwd; | ||
54280 | zOut[nOut-1] = '\0'; | ||
54281 | if( getcwd(zOut, nOut-1)==0 ){ | ||
54282 | return UNQLITE_IOERR; | ||
54283 | } | ||
54284 | nCwd = SyStrlen(zOut); | ||
54285 | SyBufferFormat(&zOut[nCwd],(sxu32)nOut-nCwd,"/%s",zPath); | ||
54286 | } | ||
54287 | return UNQLITE_OK; | ||
54288 | } | ||
54289 | /* | ||
54290 | * Export the Unix Vfs. | ||
54291 | */ | ||
54292 | UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void) | ||
54293 | { | ||
54294 | static const unqlite_vfs sUnixvfs = { | ||
54295 | "Unix", /* Vfs name */ | ||
54296 | 1, /* Vfs structure version */ | ||
54297 | sizeof(unixFile), /* szOsFile */ | ||
54298 | MAX_PATHNAME, /* mxPathName */ | ||
54299 | unixOpen, /* xOpen */ | ||
54300 | unixDelete, /* xDelete */ | ||
54301 | unixAccess, /* xAccess */ | ||
54302 | unixFullPathname, /* xFullPathname */ | ||
54303 | 0, /* xTmp */ | ||
54304 | unixSleep, /* xSleep */ | ||
54305 | unixCurrentTime, /* xCurrentTime */ | ||
54306 | 0, /* xGetLastError */ | ||
54307 | }; | ||
54308 | return &sUnixvfs; | ||
54309 | } | ||
54310 | |||
54311 | #endif /* __UNIXES__ */ | ||
54312 | |||
54313 | /* | ||
54314 | * ---------------------------------------------------------- | ||
54315 | * File: os_win.c | ||
54316 | * MD5: ab70fb386c21b39a08b0eb776a8391ab | ||
54317 | * ---------------------------------------------------------- | ||
54318 | */ | ||
54319 | /* | ||
54320 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
54321 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
54322 | * Version 1.1.6 | ||
54323 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
54324 | * please contact Symisc Systems via: | ||
54325 | * legal@symisc.net | ||
54326 | * licensing@symisc.net | ||
54327 | * contact@symisc.net | ||
54328 | * or visit: | ||
54329 | * http://unqlite.org/licensing.html | ||
54330 | */ | ||
54331 | /* $SymiscID: os_win.c v1.2 Win7 2012-11-10 12:10 devel <chm@symisc.net> $ */ | ||
54332 | #ifndef UNQLITE_AMALGAMATION | ||
54333 | #include "unqliteInt.h" | ||
54334 | #endif | ||
54335 | /* Omit the whole layer from the build if compiling for platforms other than Windows */ | ||
54336 | #ifdef __WINNT__ | ||
54337 | /* This file contains code that is specific to windows. (Mostly SQLite3 source tree) */ | ||
54338 | #include <Windows.h> | ||
54339 | /* | ||
54340 | ** Some microsoft compilers lack this definition. | ||
54341 | */ | ||
54342 | #ifndef INVALID_FILE_ATTRIBUTES | ||
54343 | # define INVALID_FILE_ATTRIBUTES ((DWORD)-1) | ||
54344 | #endif | ||
54345 | /* | ||
54346 | ** WinCE lacks native support for file locking so we have to fake it | ||
54347 | ** with some code of our own. | ||
54348 | */ | ||
54349 | #ifdef __WIN_CE__ | ||
54350 | typedef struct winceLock { | ||
54351 | int nReaders; /* Number of reader locks obtained */ | ||
54352 | BOOL bPending; /* Indicates a pending lock has been obtained */ | ||
54353 | BOOL bReserved; /* Indicates a reserved lock has been obtained */ | ||
54354 | BOOL bExclusive; /* Indicates an exclusive lock has been obtained */ | ||
54355 | } winceLock; | ||
54356 | #define AreFileApisANSI() 1 | ||
54357 | #define FormatMessageW(a,b,c,d,e,f,g) 0 | ||
54358 | #endif | ||
54359 | |||
54360 | /* | ||
54361 | ** The winFile structure is a subclass of unqlite_file* specific to the win32 | ||
54362 | ** portability layer. | ||
54363 | */ | ||
54364 | typedef struct winFile winFile; | ||
54365 | struct winFile { | ||
54366 | const unqlite_io_methods *pMethod; /*** Must be first ***/ | ||
54367 | unqlite_vfs *pVfs; /* The VFS used to open this file */ | ||
54368 | HANDLE h; /* Handle for accessing the file */ | ||
54369 | sxu8 locktype; /* Type of lock currently held on this file */ | ||
54370 | short sharedLockByte; /* Randomly chosen byte used as a shared lock */ | ||
54371 | DWORD lastErrno; /* The Windows errno from the last I/O error */ | ||
54372 | DWORD sectorSize; /* Sector size of the device file is on */ | ||
54373 | int szChunk; /* Chunk size */ | ||
54374 | #ifdef __WIN_CE__ | ||
54375 | WCHAR *zDeleteOnClose; /* Name of file to delete when closing */ | ||
54376 | HANDLE hMutex; /* Mutex used to control access to shared lock */ | ||
54377 | HANDLE hShared; /* Shared memory segment used for locking */ | ||
54378 | winceLock local; /* Locks obtained by this instance of winFile */ | ||
54379 | winceLock *shared; /* Global shared lock memory for the file */ | ||
54380 | #endif | ||
54381 | }; | ||
54382 | /* | ||
54383 | ** Convert a UTF-8 string to microsoft unicode (UTF-16?). | ||
54384 | ** | ||
54385 | ** Space to hold the returned string is obtained from HeapAlloc(). | ||
54386 | */ | ||
54387 | static WCHAR *utf8ToUnicode(const char *zFilename){ | ||
54388 | int nChar; | ||
54389 | WCHAR *zWideFilename; | ||
54390 | |||
54391 | nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0); | ||
54392 | zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nChar*sizeof(zWideFilename[0]) ); | ||
54393 | if( zWideFilename==0 ){ | ||
54394 | return 0; | ||
54395 | } | ||
54396 | nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar); | ||
54397 | if( nChar==0 ){ | ||
54398 | HeapFree(GetProcessHeap(),0,zWideFilename); | ||
54399 | zWideFilename = 0; | ||
54400 | } | ||
54401 | return zWideFilename; | ||
54402 | } | ||
54403 | |||
54404 | /* | ||
54405 | ** Convert microsoft unicode to UTF-8. Space to hold the returned string is | ||
54406 | ** obtained from malloc(). | ||
54407 | */ | ||
54408 | static char *unicodeToUtf8(const WCHAR *zWideFilename){ | ||
54409 | int nByte; | ||
54410 | char *zFilename; | ||
54411 | |||
54412 | nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); | ||
54413 | zFilename = (char *)HeapAlloc(GetProcessHeap(),0,nByte ); | ||
54414 | if( zFilename==0 ){ | ||
54415 | return 0; | ||
54416 | } | ||
54417 | nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, | ||
54418 | 0, 0); | ||
54419 | if( nByte == 0 ){ | ||
54420 | HeapFree(GetProcessHeap(),0,zFilename); | ||
54421 | zFilename = 0; | ||
54422 | } | ||
54423 | return zFilename; | ||
54424 | } | ||
54425 | |||
54426 | /* | ||
54427 | ** Convert an ansi string to microsoft unicode, based on the | ||
54428 | ** current codepage settings for file apis. | ||
54429 | ** | ||
54430 | ** Space to hold the returned string is obtained | ||
54431 | ** from malloc. | ||
54432 | */ | ||
54433 | static WCHAR *mbcsToUnicode(const char *zFilename){ | ||
54434 | int nByte; | ||
54435 | WCHAR *zMbcsFilename; | ||
54436 | int codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; | ||
54437 | |||
54438 | nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, 0,0)*sizeof(WCHAR); | ||
54439 | zMbcsFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zMbcsFilename[0]) ); | ||
54440 | if( zMbcsFilename==0 ){ | ||
54441 | return 0; | ||
54442 | } | ||
54443 | nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte); | ||
54444 | if( nByte==0 ){ | ||
54445 | HeapFree(GetProcessHeap(),0,zMbcsFilename); | ||
54446 | zMbcsFilename = 0; | ||
54447 | } | ||
54448 | return zMbcsFilename; | ||
54449 | } | ||
54450 | /* | ||
54451 | ** Convert multibyte character string to UTF-8. Space to hold the | ||
54452 | ** returned string is obtained from malloc(). | ||
54453 | */ | ||
54454 | char *unqlite_win32_mbcs_to_utf8(const char *zFilename){ | ||
54455 | char *zFilenameUtf8; | ||
54456 | WCHAR *zTmpWide; | ||
54457 | |||
54458 | zTmpWide = mbcsToUnicode(zFilename); | ||
54459 | if( zTmpWide==0 ){ | ||
54460 | return 0; | ||
54461 | } | ||
54462 | zFilenameUtf8 = unicodeToUtf8(zTmpWide); | ||
54463 | HeapFree(GetProcessHeap(),0,zTmpWide); | ||
54464 | return zFilenameUtf8; | ||
54465 | } | ||
54466 | /* | ||
54467 | ** Some microsoft compilers lack this definition. | ||
54468 | */ | ||
54469 | #ifndef INVALID_SET_FILE_POINTER | ||
54470 | # define INVALID_SET_FILE_POINTER ((DWORD)-1) | ||
54471 | #endif | ||
54472 | |||
54473 | /* | ||
54474 | ** Move the current position of the file handle passed as the first | ||
54475 | ** argument to offset iOffset within the file. If successful, return 0. | ||
54476 | ** Otherwise, set pFile->lastErrno and return non-zero. | ||
54477 | */ | ||
54478 | static int seekWinFile(winFile *pFile, unqlite_int64 iOffset){ | ||
54479 | LONG upperBits; /* Most sig. 32 bits of new offset */ | ||
54480 | LONG lowerBits; /* Least sig. 32 bits of new offset */ | ||
54481 | DWORD dwRet; /* Value returned by SetFilePointer() */ | ||
54482 | |||
54483 | upperBits = (LONG)((iOffset>>32) & 0x7fffffff); | ||
54484 | lowerBits = (LONG)(iOffset & 0xffffffff); | ||
54485 | |||
54486 | /* API oddity: If successful, SetFilePointer() returns a dword | ||
54487 | ** containing the lower 32-bits of the new file-offset. Or, if it fails, | ||
54488 | ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, | ||
54489 | ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine | ||
54490 | ** whether an error has actually occured, it is also necessary to call | ||
54491 | ** GetLastError(). | ||
54492 | */ | ||
54493 | dwRet = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN); | ||
54494 | if( (dwRet==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR) ){ | ||
54495 | pFile->lastErrno = GetLastError(); | ||
54496 | return 1; | ||
54497 | } | ||
54498 | return 0; | ||
54499 | } | ||
54500 | /* | ||
54501 | ** Close a file. | ||
54502 | ** | ||
54503 | ** It is reported that an attempt to close a handle might sometimes | ||
54504 | ** fail. This is a very unreasonable result, but windows is notorious | ||
54505 | ** for being unreasonable so I do not doubt that it might happen. If | ||
54506 | ** the close fails, we pause for 100 milliseconds and try again. As | ||
54507 | ** many as MX_CLOSE_ATTEMPT attempts to close the handle are made before | ||
54508 | ** giving up and returning an error. | ||
54509 | */ | ||
54510 | #define MX_CLOSE_ATTEMPT 3 | ||
54511 | static int winClose(unqlite_file *id) | ||
54512 | { | ||
54513 | int rc, cnt = 0; | ||
54514 | winFile *pFile = (winFile*)id; | ||
54515 | do{ | ||
54516 | rc = CloseHandle(pFile->h); | ||
54517 | }while( rc==0 && ++cnt < MX_CLOSE_ATTEMPT && (Sleep(100), 1) ); | ||
54518 | |||
54519 | return rc ? UNQLITE_OK : UNQLITE_IOERR; | ||
54520 | } | ||
54521 | /* | ||
54522 | ** Read data from a file into a buffer. Return UNQLITE_OK if all | ||
54523 | ** bytes were read successfully and UNQLITE_IOERR if anything goes | ||
54524 | ** wrong. | ||
54525 | */ | ||
54526 | static int winRead( | ||
54527 | unqlite_file *id, /* File to read from */ | ||
54528 | void *pBuf, /* Write content into this buffer */ | ||
54529 | unqlite_int64 amt, /* Number of bytes to read */ | ||
54530 | unqlite_int64 offset /* Begin reading at this offset */ | ||
54531 | ){ | ||
54532 | winFile *pFile = (winFile*)id; /* file handle */ | ||
54533 | DWORD nRead; /* Number of bytes actually read from file */ | ||
54534 | |||
54535 | if( seekWinFile(pFile, offset) ){ | ||
54536 | return UNQLITE_FULL; | ||
54537 | } | ||
54538 | if( !ReadFile(pFile->h, pBuf, (DWORD)amt, &nRead, 0) ){ | ||
54539 | pFile->lastErrno = GetLastError(); | ||
54540 | return UNQLITE_IOERR; | ||
54541 | } | ||
54542 | if( nRead<(DWORD)amt ){ | ||
54543 | /* Unread parts of the buffer must be zero-filled */ | ||
54544 | SyZero(&((char*)pBuf)[nRead],(sxu32)(amt-nRead)); | ||
54545 | return UNQLITE_IOERR; | ||
54546 | } | ||
54547 | |||
54548 | return UNQLITE_OK; | ||
54549 | } | ||
54550 | |||
54551 | /* | ||
54552 | ** Write data from a buffer into a file. Return UNQLITE_OK on success | ||
54553 | ** or some other error code on failure. | ||
54554 | */ | ||
54555 | static int winWrite( | ||
54556 | unqlite_file *id, /* File to write into */ | ||
54557 | const void *pBuf, /* The bytes to be written */ | ||
54558 | unqlite_int64 amt, /* Number of bytes to write */ | ||
54559 | unqlite_int64 offset /* Offset into the file to begin writing at */ | ||
54560 | ){ | ||
54561 | int rc; /* True if error has occured, else false */ | ||
54562 | winFile *pFile = (winFile*)id; /* File handle */ | ||
54563 | |||
54564 | rc = seekWinFile(pFile, offset); | ||
54565 | if( rc==0 ){ | ||
54566 | sxu8 *aRem = (sxu8 *)pBuf; /* Data yet to be written */ | ||
54567 | unqlite_int64 nRem = amt; /* Number of bytes yet to be written */ | ||
54568 | DWORD nWrite; /* Bytes written by each WriteFile() call */ | ||
54569 | |||
54570 | while( nRem>0 && WriteFile(pFile->h, aRem, (DWORD)nRem, &nWrite, 0) && nWrite>0 ){ | ||
54571 | aRem += nWrite; | ||
54572 | nRem -= nWrite; | ||
54573 | } | ||
54574 | if( nRem>0 ){ | ||
54575 | pFile->lastErrno = GetLastError(); | ||
54576 | rc = 1; | ||
54577 | } | ||
54578 | } | ||
54579 | if( rc ){ | ||
54580 | if( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ){ | ||
54581 | return UNQLITE_FULL; | ||
54582 | } | ||
54583 | return UNQLITE_IOERR; | ||
54584 | } | ||
54585 | return UNQLITE_OK; | ||
54586 | } | ||
54587 | |||
54588 | /* | ||
54589 | ** Truncate an open file to a specified size | ||
54590 | */ | ||
54591 | static int winTruncate(unqlite_file *id, unqlite_int64 nByte){ | ||
54592 | winFile *pFile = (winFile*)id; /* File handle object */ | ||
54593 | int rc = UNQLITE_OK; /* Return code for this function */ | ||
54594 | |||
54595 | |||
54596 | /* If the user has configured a chunk-size for this file, truncate the | ||
54597 | ** file so that it consists of an integer number of chunks (i.e. the | ||
54598 | ** actual file size after the operation may be larger than the requested | ||
54599 | ** size). | ||
54600 | */ | ||
54601 | if( pFile->szChunk ){ | ||
54602 | nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; | ||
54603 | } | ||
54604 | |||
54605 | /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */ | ||
54606 | if( seekWinFile(pFile, nByte) ){ | ||
54607 | rc = UNQLITE_IOERR; | ||
54608 | }else if( 0==SetEndOfFile(pFile->h) ){ | ||
54609 | pFile->lastErrno = GetLastError(); | ||
54610 | rc = UNQLITE_IOERR; | ||
54611 | } | ||
54612 | return rc; | ||
54613 | } | ||
54614 | /* | ||
54615 | ** Make sure all writes to a particular file are committed to disk. | ||
54616 | */ | ||
54617 | static int winSync(unqlite_file *id, int flags){ | ||
54618 | winFile *pFile = (winFile*)id; | ||
54619 | SXUNUSED(flags); /* MSVC warning */ | ||
54620 | if( FlushFileBuffers(pFile->h) ){ | ||
54621 | return UNQLITE_OK; | ||
54622 | }else{ | ||
54623 | pFile->lastErrno = GetLastError(); | ||
54624 | return UNQLITE_IOERR; | ||
54625 | } | ||
54626 | } | ||
54627 | /* | ||
54628 | ** Determine the current size of a file in bytes | ||
54629 | */ | ||
54630 | static int winFileSize(unqlite_file *id, unqlite_int64 *pSize){ | ||
54631 | DWORD upperBits; | ||
54632 | DWORD lowerBits; | ||
54633 | winFile *pFile = (winFile*)id; | ||
54634 | DWORD error; | ||
54635 | lowerBits = GetFileSize(pFile->h, &upperBits); | ||
54636 | if( (lowerBits == INVALID_FILE_SIZE) | ||
54637 | && ((error = GetLastError()) != NO_ERROR) ) | ||
54638 | { | ||
54639 | pFile->lastErrno = error; | ||
54640 | return UNQLITE_IOERR; | ||
54641 | } | ||
54642 | *pSize = (((unqlite_int64)upperBits)<<32) + lowerBits; | ||
54643 | return UNQLITE_OK; | ||
54644 | } | ||
54645 | /* | ||
54646 | ** LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. | ||
54647 | */ | ||
54648 | #ifndef LOCKFILE_FAIL_IMMEDIATELY | ||
54649 | # define LOCKFILE_FAIL_IMMEDIATELY 1 | ||
54650 | #endif | ||
54651 | |||
54652 | /* | ||
54653 | ** Acquire a reader lock. | ||
54654 | */ | ||
54655 | static int getReadLock(winFile *pFile){ | ||
54656 | int res; | ||
54657 | OVERLAPPED ovlp; | ||
54658 | ovlp.Offset = SHARED_FIRST; | ||
54659 | ovlp.OffsetHigh = 0; | ||
54660 | ovlp.hEvent = 0; | ||
54661 | res = LockFileEx(pFile->h, LOCKFILE_FAIL_IMMEDIATELY,0, SHARED_SIZE, 0, &ovlp); | ||
54662 | if( res == 0 ){ | ||
54663 | pFile->lastErrno = GetLastError(); | ||
54664 | } | ||
54665 | return res; | ||
54666 | } | ||
54667 | /* | ||
54668 | ** Undo a readlock | ||
54669 | */ | ||
54670 | static int unlockReadLock(winFile *pFile){ | ||
54671 | int res; | ||
54672 | res = UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); | ||
54673 | if( res == 0 ){ | ||
54674 | pFile->lastErrno = GetLastError(); | ||
54675 | } | ||
54676 | return res; | ||
54677 | } | ||
54678 | /* | ||
54679 | ** Lock the file with the lock specified by parameter locktype - one | ||
54680 | ** of the following: | ||
54681 | ** | ||
54682 | ** (1) SHARED_LOCK | ||
54683 | ** (2) RESERVED_LOCK | ||
54684 | ** (3) PENDING_LOCK | ||
54685 | ** (4) EXCLUSIVE_LOCK | ||
54686 | ** | ||
54687 | ** Sometimes when requesting one lock state, additional lock states | ||
54688 | ** are inserted in between. The locking might fail on one of the later | ||
54689 | ** transitions leaving the lock state different from what it started but | ||
54690 | ** still short of its goal. The following chart shows the allowed | ||
54691 | ** transitions and the inserted intermediate states: | ||
54692 | ** | ||
54693 | ** UNLOCKED -> SHARED | ||
54694 | ** SHARED -> RESERVED | ||
54695 | ** SHARED -> (PENDING) -> EXCLUSIVE | ||
54696 | ** RESERVED -> (PENDING) -> EXCLUSIVE | ||
54697 | ** PENDING -> EXCLUSIVE | ||
54698 | ** | ||
54699 | ** This routine will only increase a lock. The winUnlock() routine | ||
54700 | ** erases all locks at once and returns us immediately to locking level 0. | ||
54701 | ** It is not possible to lower the locking level one step at a time. You | ||
54702 | ** must go straight to locking level 0. | ||
54703 | */ | ||
54704 | static int winLock(unqlite_file *id, int locktype){ | ||
54705 | int rc = UNQLITE_OK; /* Return code from subroutines */ | ||
54706 | int res = 1; /* Result of a windows lock call */ | ||
54707 | int newLocktype; /* Set pFile->locktype to this value before exiting */ | ||
54708 | int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */ | ||
54709 | winFile *pFile = (winFile*)id; | ||
54710 | DWORD error = NO_ERROR; | ||
54711 | |||
54712 | /* If there is already a lock of this type or more restrictive on the | ||
54713 | ** OsFile, do nothing. | ||
54714 | */ | ||
54715 | if( pFile->locktype>=locktype ){ | ||
54716 | return UNQLITE_OK; | ||
54717 | } | ||
54718 | |||
54719 | /* Make sure the locking sequence is correct | ||
54720 | assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK ); | ||
54721 | assert( locktype!=PENDING_LOCK ); | ||
54722 | assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK ); | ||
54723 | */ | ||
54724 | /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or | ||
54725 | ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of | ||
54726 | ** the PENDING_LOCK byte is temporary. | ||
54727 | */ | ||
54728 | newLocktype = pFile->locktype; | ||
54729 | if( (pFile->locktype==NO_LOCK) | ||
54730 | || ( (locktype==EXCLUSIVE_LOCK) | ||
54731 | && (pFile->locktype==RESERVED_LOCK)) | ||
54732 | ){ | ||
54733 | int cnt = 3; | ||
54734 | while( cnt-->0 && (res = LockFile(pFile->h, PENDING_BYTE, 0, 1, 0))==0 ){ | ||
54735 | /* Try 3 times to get the pending lock. The pending lock might be | ||
54736 | ** held by another reader process who will release it momentarily. | ||
54737 | */ | ||
54738 | Sleep(1); | ||
54739 | } | ||
54740 | gotPendingLock = res; | ||
54741 | if( !res ){ | ||
54742 | error = GetLastError(); | ||
54743 | } | ||
54744 | } | ||
54745 | |||
54746 | /* Acquire a shared lock | ||
54747 | */ | ||
54748 | if( locktype==SHARED_LOCK && res ){ | ||
54749 | /* assert( pFile->locktype==NO_LOCK ); */ | ||
54750 | res = getReadLock(pFile); | ||
54751 | if( res ){ | ||
54752 | newLocktype = SHARED_LOCK; | ||
54753 | }else{ | ||
54754 | error = GetLastError(); | ||
54755 | } | ||
54756 | } | ||
54757 | |||
54758 | /* Acquire a RESERVED lock | ||
54759 | */ | ||
54760 | if( locktype==RESERVED_LOCK && res ){ | ||
54761 | /* assert( pFile->locktype==SHARED_LOCK ); */ | ||
54762 | res = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); | ||
54763 | if( res ){ | ||
54764 | newLocktype = RESERVED_LOCK; | ||
54765 | }else{ | ||
54766 | error = GetLastError(); | ||
54767 | } | ||
54768 | } | ||
54769 | |||
54770 | /* Acquire a PENDING lock | ||
54771 | */ | ||
54772 | if( locktype==EXCLUSIVE_LOCK && res ){ | ||
54773 | newLocktype = PENDING_LOCK; | ||
54774 | gotPendingLock = 0; | ||
54775 | } | ||
54776 | |||
54777 | /* Acquire an EXCLUSIVE lock | ||
54778 | */ | ||
54779 | if( locktype==EXCLUSIVE_LOCK && res ){ | ||
54780 | /* assert( pFile->locktype>=SHARED_LOCK ); */ | ||
54781 | res = unlockReadLock(pFile); | ||
54782 | res = LockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); | ||
54783 | if( res ){ | ||
54784 | newLocktype = EXCLUSIVE_LOCK; | ||
54785 | }else{ | ||
54786 | error = GetLastError(); | ||
54787 | getReadLock(pFile); | ||
54788 | } | ||
54789 | } | ||
54790 | |||
54791 | /* If we are holding a PENDING lock that ought to be released, then | ||
54792 | ** release it now. | ||
54793 | */ | ||
54794 | if( gotPendingLock && locktype==SHARED_LOCK ){ | ||
54795 | UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0); | ||
54796 | } | ||
54797 | |||
54798 | /* Update the state of the lock has held in the file descriptor then | ||
54799 | ** return the appropriate result code. | ||
54800 | */ | ||
54801 | if( res ){ | ||
54802 | rc = UNQLITE_OK; | ||
54803 | }else{ | ||
54804 | pFile->lastErrno = error; | ||
54805 | rc = UNQLITE_BUSY; | ||
54806 | } | ||
54807 | pFile->locktype = (sxu8)newLocktype; | ||
54808 | return rc; | ||
54809 | } | ||
54810 | /* | ||
54811 | ** This routine checks if there is a RESERVED lock held on the specified | ||
54812 | ** file by this or any other process. If such a lock is held, return | ||
54813 | ** non-zero, otherwise zero. | ||
54814 | */ | ||
54815 | static int winCheckReservedLock(unqlite_file *id, int *pResOut){ | ||
54816 | int rc; | ||
54817 | winFile *pFile = (winFile*)id; | ||
54818 | if( pFile->locktype>=RESERVED_LOCK ){ | ||
54819 | rc = 1; | ||
54820 | }else{ | ||
54821 | rc = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); | ||
54822 | if( rc ){ | ||
54823 | UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); | ||
54824 | } | ||
54825 | rc = !rc; | ||
54826 | } | ||
54827 | *pResOut = rc; | ||
54828 | return UNQLITE_OK; | ||
54829 | } | ||
54830 | /* | ||
54831 | ** Lower the locking level on file descriptor id to locktype. locktype | ||
54832 | ** must be either NO_LOCK or SHARED_LOCK. | ||
54833 | ** | ||
54834 | ** If the locking level of the file descriptor is already at or below | ||
54835 | ** the requested locking level, this routine is a no-op. | ||
54836 | ** | ||
54837 | ** It is not possible for this routine to fail if the second argument | ||
54838 | ** is NO_LOCK. If the second argument is SHARED_LOCK then this routine | ||
54839 | ** might return UNQLITE_IOERR; | ||
54840 | */ | ||
54841 | static int winUnlock(unqlite_file *id, int locktype){ | ||
54842 | int type; | ||
54843 | winFile *pFile = (winFile*)id; | ||
54844 | int rc = UNQLITE_OK; | ||
54845 | |||
54846 | type = pFile->locktype; | ||
54847 | if( type>=EXCLUSIVE_LOCK ){ | ||
54848 | UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); | ||
54849 | if( locktype==SHARED_LOCK && !getReadLock(pFile) ){ | ||
54850 | /* This should never happen. We should always be able to | ||
54851 | ** reacquire the read lock */ | ||
54852 | rc = UNQLITE_IOERR; | ||
54853 | } | ||
54854 | } | ||
54855 | if( type>=RESERVED_LOCK ){ | ||
54856 | UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); | ||
54857 | } | ||
54858 | if( locktype==NO_LOCK && type>=SHARED_LOCK ){ | ||
54859 | unlockReadLock(pFile); | ||
54860 | } | ||
54861 | if( type>=PENDING_LOCK ){ | ||
54862 | UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0); | ||
54863 | } | ||
54864 | pFile->locktype = (sxu8)locktype; | ||
54865 | return rc; | ||
54866 | } | ||
54867 | /* | ||
54868 | ** Return the sector size in bytes of the underlying block device for | ||
54869 | ** the specified file. This is almost always 512 bytes, but may be | ||
54870 | ** larger for some devices. | ||
54871 | ** | ||
54872 | */ | ||
54873 | static int winSectorSize(unqlite_file *id){ | ||
54874 | return (int)(((winFile*)id)->sectorSize); | ||
54875 | } | ||
54876 | /* | ||
54877 | ** This vector defines all the methods that can operate on an | ||
54878 | ** unqlite_file for Windows systems. | ||
54879 | */ | ||
54880 | static const unqlite_io_methods winIoMethod = { | ||
54881 | 1, /* iVersion */ | ||
54882 | winClose, /* xClose */ | ||
54883 | winRead, /* xRead */ | ||
54884 | winWrite, /* xWrite */ | ||
54885 | winTruncate, /* xTruncate */ | ||
54886 | winSync, /* xSync */ | ||
54887 | winFileSize, /* xFileSize */ | ||
54888 | winLock, /* xLock */ | ||
54889 | winUnlock, /* xUnlock */ | ||
54890 | winCheckReservedLock, /* xCheckReservedLock */ | ||
54891 | winSectorSize, /* xSectorSize */ | ||
54892 | }; | ||
54893 | /* | ||
54894 | * Windows VFS Methods. | ||
54895 | */ | ||
54896 | /* | ||
54897 | ** Convert a UTF-8 filename into whatever form the underlying | ||
54898 | ** operating system wants filenames in. Space to hold the result | ||
54899 | ** is obtained from malloc and must be freed by the calling | ||
54900 | ** function. | ||
54901 | */ | ||
54902 | static void *convertUtf8Filename(const char *zFilename) | ||
54903 | { | ||
54904 | void *zConverted; | ||
54905 | zConverted = utf8ToUnicode(zFilename); | ||
54906 | /* caller will handle out of memory */ | ||
54907 | return zConverted; | ||
54908 | } | ||
54909 | /* | ||
54910 | ** Delete the named file. | ||
54911 | ** | ||
54912 | ** Note that windows does not allow a file to be deleted if some other | ||
54913 | ** process has it open. Sometimes a virus scanner or indexing program | ||
54914 | ** will open a journal file shortly after it is created in order to do | ||
54915 | ** whatever it does. While this other process is holding the | ||
54916 | ** file open, we will be unable to delete it. To work around this | ||
54917 | ** problem, we delay 100 milliseconds and try to delete again. Up | ||
54918 | ** to MX_DELETION_ATTEMPTs deletion attempts are run before giving | ||
54919 | ** up and returning an error. | ||
54920 | */ | ||
54921 | #define MX_DELETION_ATTEMPTS 5 | ||
54922 | static int winDelete( | ||
54923 | unqlite_vfs *pVfs, /* Not used on win32 */ | ||
54924 | const char *zFilename, /* Name of file to delete */ | ||
54925 | int syncDir /* Not used on win32 */ | ||
54926 | ){ | ||
54927 | int cnt = 0; | ||
54928 | DWORD rc; | ||
54929 | DWORD error = 0; | ||
54930 | void *zConverted; | ||
54931 | zConverted = convertUtf8Filename(zFilename); | ||
54932 | if( zConverted==0 ){ | ||
54933 | SXUNUSED(pVfs); | ||
54934 | SXUNUSED(syncDir); | ||
54935 | return UNQLITE_NOMEM; | ||
54936 | } | ||
54937 | do{ | ||
54938 | DeleteFileW((LPCWSTR)zConverted); | ||
54939 | }while( ( ((rc = GetFileAttributesW((LPCWSTR)zConverted)) != INVALID_FILE_ATTRIBUTES) | ||
54940 | || ((error = GetLastError()) == ERROR_ACCESS_DENIED)) | ||
54941 | && (++cnt < MX_DELETION_ATTEMPTS) | ||
54942 | && (Sleep(100), 1) | ||
54943 | ); | ||
54944 | HeapFree(GetProcessHeap(),0,zConverted); | ||
54945 | |||
54946 | return ( (rc == INVALID_FILE_ATTRIBUTES) | ||
54947 | && (error == ERROR_FILE_NOT_FOUND)) ? UNQLITE_OK : UNQLITE_IOERR; | ||
54948 | } | ||
54949 | /* | ||
54950 | ** Check the existance and status of a file. | ||
54951 | */ | ||
54952 | static int winAccess( | ||
54953 | unqlite_vfs *pVfs, /* Not used */ | ||
54954 | const char *zFilename, /* Name of file to check */ | ||
54955 | int flags, /* Type of test to make on this file */ | ||
54956 | int *pResOut /* OUT: Result */ | ||
54957 | ){ | ||
54958 | WIN32_FILE_ATTRIBUTE_DATA sAttrData; | ||
54959 | DWORD attr; | ||
54960 | int rc = 0; | ||
54961 | void *zConverted; | ||
54962 | SXUNUSED(pVfs); | ||
54963 | |||
54964 | zConverted = convertUtf8Filename(zFilename); | ||
54965 | if( zConverted==0 ){ | ||
54966 | return UNQLITE_NOMEM; | ||
54967 | } | ||
54968 | SyZero(&sAttrData,sizeof(sAttrData)); | ||
54969 | if( GetFileAttributesExW((WCHAR*)zConverted, | ||
54970 | GetFileExInfoStandard, | ||
54971 | &sAttrData) ){ | ||
54972 | /* For an UNQLITE_ACCESS_EXISTS query, treat a zero-length file | ||
54973 | ** as if it does not exist. | ||
54974 | */ | ||
54975 | if( flags==UNQLITE_ACCESS_EXISTS | ||
54976 | && sAttrData.nFileSizeHigh==0 | ||
54977 | && sAttrData.nFileSizeLow==0 ){ | ||
54978 | attr = INVALID_FILE_ATTRIBUTES; | ||
54979 | }else{ | ||
54980 | attr = sAttrData.dwFileAttributes; | ||
54981 | } | ||
54982 | }else{ | ||
54983 | if( GetLastError()!=ERROR_FILE_NOT_FOUND ){ | ||
54984 | HeapFree(GetProcessHeap(),0,zConverted); | ||
54985 | return UNQLITE_IOERR; | ||
54986 | }else{ | ||
54987 | attr = INVALID_FILE_ATTRIBUTES; | ||
54988 | } | ||
54989 | } | ||
54990 | HeapFree(GetProcessHeap(),0,zConverted); | ||
54991 | switch( flags ){ | ||
54992 | case UNQLITE_ACCESS_READWRITE: | ||
54993 | rc = (attr & FILE_ATTRIBUTE_READONLY)==0; | ||
54994 | break; | ||
54995 | case UNQLITE_ACCESS_READ: | ||
54996 | case UNQLITE_ACCESS_EXISTS: | ||
54997 | default: | ||
54998 | rc = attr!=INVALID_FILE_ATTRIBUTES; | ||
54999 | break; | ||
55000 | } | ||
55001 | *pResOut = rc; | ||
55002 | return UNQLITE_OK; | ||
55003 | } | ||
55004 | /* | ||
55005 | ** Turn a relative pathname into a full pathname. Write the full | ||
55006 | ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname | ||
55007 | ** bytes in size. | ||
55008 | */ | ||
55009 | static int winFullPathname( | ||
55010 | unqlite_vfs *pVfs, /* Pointer to vfs object */ | ||
55011 | const char *zRelative, /* Possibly relative input path */ | ||
55012 | int nFull, /* Size of output buffer in bytes */ | ||
55013 | char *zFull /* Output buffer */ | ||
55014 | ){ | ||
55015 | int nByte; | ||
55016 | void *zConverted; | ||
55017 | WCHAR *zTemp; | ||
55018 | char *zOut; | ||
55019 | SXUNUSED(nFull); | ||
55020 | zConverted = convertUtf8Filename(zRelative); | ||
55021 | if( zConverted == 0 ){ | ||
55022 | return UNQLITE_NOMEM; | ||
55023 | } | ||
55024 | nByte = GetFullPathNameW((WCHAR*)zConverted, 0, 0, 0) + 3; | ||
55025 | zTemp = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zTemp[0]) ); | ||
55026 | if( zTemp==0 ){ | ||
55027 | HeapFree(GetProcessHeap(),0,zConverted); | ||
55028 | return UNQLITE_NOMEM; | ||
55029 | } | ||
55030 | GetFullPathNameW((WCHAR*)zConverted, nByte, zTemp, 0); | ||
55031 | HeapFree(GetProcessHeap(),0,zConverted); | ||
55032 | zOut = unicodeToUtf8(zTemp); | ||
55033 | HeapFree(GetProcessHeap(),0,zTemp); | ||
55034 | if( zOut == 0 ){ | ||
55035 | return UNQLITE_NOMEM; | ||
55036 | } | ||
55037 | Systrcpy(zFull,(sxu32)pVfs->mxPathname,zOut,0); | ||
55038 | HeapFree(GetProcessHeap(),0,zOut); | ||
55039 | return UNQLITE_OK; | ||
55040 | } | ||
55041 | /* | ||
55042 | ** Get the sector size of the device used to store | ||
55043 | ** file. | ||
55044 | */ | ||
55045 | static int getSectorSize( | ||
55046 | unqlite_vfs *pVfs, | ||
55047 | const char *zRelative /* UTF-8 file name */ | ||
55048 | ){ | ||
55049 | DWORD bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE; | ||
55050 | char zFullpath[MAX_PATH+1]; | ||
55051 | int rc; | ||
55052 | DWORD dwRet = 0; | ||
55053 | DWORD dwDummy; | ||
55054 | /* | ||
55055 | ** We need to get the full path name of the file | ||
55056 | ** to get the drive letter to look up the sector | ||
55057 | ** size. | ||
55058 | */ | ||
55059 | rc = winFullPathname(pVfs, zRelative, MAX_PATH, zFullpath); | ||
55060 | if( rc == UNQLITE_OK ) | ||
55061 | { | ||
55062 | void *zConverted = convertUtf8Filename(zFullpath); | ||
55063 | if( zConverted ){ | ||
55064 | /* trim path to just drive reference */ | ||
55065 | WCHAR *p = (WCHAR *)zConverted; | ||
55066 | for(;*p;p++){ | ||
55067 | if( *p == '\\' ){ | ||
55068 | *p = '\0'; | ||
55069 | break; | ||
55070 | } | ||
55071 | } | ||
55072 | dwRet = GetDiskFreeSpaceW((WCHAR*)zConverted, | ||
55073 | &dwDummy, | ||
55074 | &bytesPerSector, | ||
55075 | &dwDummy, | ||
55076 | &dwDummy); | ||
55077 | HeapFree(GetProcessHeap(),0,zConverted); | ||
55078 | } | ||
55079 | if( !dwRet ){ | ||
55080 | bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE; | ||
55081 | } | ||
55082 | } | ||
55083 | return (int) bytesPerSector; | ||
55084 | } | ||
55085 | /* | ||
55086 | ** Sleep for a little while. Return the amount of time slept. | ||
55087 | */ | ||
55088 | static int winSleep(unqlite_vfs *pVfs, int microsec){ | ||
55089 | Sleep((microsec+999)/1000); | ||
55090 | SXUNUSED(pVfs); | ||
55091 | return ((microsec+999)/1000)*1000; | ||
55092 | } | ||
55093 | /* | ||
55094 | * Export the current system time. | ||
55095 | */ | ||
55096 | static int winCurrentTime(unqlite_vfs *pVfs,Sytm *pOut) | ||
55097 | { | ||
55098 | SYSTEMTIME sSys; | ||
55099 | SXUNUSED(pVfs); | ||
55100 | GetSystemTime(&sSys); | ||
55101 | SYSTEMTIME_TO_SYTM(&sSys,pOut); | ||
55102 | return UNQLITE_OK; | ||
55103 | } | ||
55104 | /* | ||
55105 | ** The idea is that this function works like a combination of | ||
55106 | ** GetLastError() and FormatMessage() on windows (or errno and | ||
55107 | ** strerror_r() on unix). After an error is returned by an OS | ||
55108 | ** function, UnQLite calls this function with zBuf pointing to | ||
55109 | ** a buffer of nBuf bytes. The OS layer should populate the | ||
55110 | ** buffer with a nul-terminated UTF-8 encoded error message | ||
55111 | ** describing the last IO error to have occurred within the calling | ||
55112 | ** thread. | ||
55113 | ** | ||
55114 | ** If the error message is too large for the supplied buffer, | ||
55115 | ** it should be truncated. The return value of xGetLastError | ||
55116 | ** is zero if the error message fits in the buffer, or non-zero | ||
55117 | ** otherwise (if the message was truncated). If non-zero is returned, | ||
55118 | ** then it is not necessary to include the nul-terminator character | ||
55119 | ** in the output buffer. | ||
55120 | */ | ||
55121 | static int winGetLastError(unqlite_vfs *pVfs, int nBuf, char *zBuf) | ||
55122 | { | ||
55123 | /* FormatMessage returns 0 on failure. Otherwise it | ||
55124 | ** returns the number of TCHARs written to the output | ||
55125 | ** buffer, excluding the terminating null char. | ||
55126 | */ | ||
55127 | DWORD error = GetLastError(); | ||
55128 | WCHAR *zTempWide = 0; | ||
55129 | DWORD dwLen; | ||
55130 | char *zOut = 0; | ||
55131 | |||
55132 | SXUNUSED(pVfs); | ||
55133 | dwLen = FormatMessageW( | ||
55134 | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||
55135 | 0, | ||
55136 | error, | ||
55137 | 0, | ||
55138 | (LPWSTR) &zTempWide, | ||
55139 | 0, | ||
55140 | 0 | ||
55141 | ); | ||
55142 | if( dwLen > 0 ){ | ||
55143 | /* allocate a buffer and convert to UTF8 */ | ||
55144 | zOut = unicodeToUtf8(zTempWide); | ||
55145 | /* free the system buffer allocated by FormatMessage */ | ||
55146 | LocalFree(zTempWide); | ||
55147 | } | ||
55148 | if( 0 == dwLen ){ | ||
55149 | Systrcpy(zBuf,(sxu32)nBuf,"OS Error",sizeof("OS Error")-1); | ||
55150 | }else{ | ||
55151 | /* copy a maximum of nBuf chars to output buffer */ | ||
55152 | Systrcpy(zBuf,(sxu32)nBuf,zOut,0 /* Compute input length automatically */); | ||
55153 | /* free the UTF8 buffer */ | ||
55154 | HeapFree(GetProcessHeap(),0,zOut); | ||
55155 | } | ||
55156 | return 0; | ||
55157 | } | ||
55158 | /* | ||
55159 | ** Open a file. | ||
55160 | */ | ||
55161 | static int winOpen( | ||
55162 | unqlite_vfs *pVfs, /* Not used */ | ||
55163 | const char *zName, /* Name of the file (UTF-8) */ | ||
55164 | unqlite_file *id, /* Write the UnQLite file handle here */ | ||
55165 | unsigned int flags /* Open mode flags */ | ||
55166 | ){ | ||
55167 | HANDLE h; | ||
55168 | DWORD dwDesiredAccess; | ||
55169 | DWORD dwShareMode; | ||
55170 | DWORD dwCreationDisposition; | ||
55171 | DWORD dwFlagsAndAttributes = 0; | ||
55172 | winFile *pFile = (winFile*)id; | ||
55173 | void *zConverted; /* Filename in OS encoding */ | ||
55174 | const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ | ||
55175 | int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE); | ||
55176 | int isDelete = (flags & UNQLITE_OPEN_TEMP_DB); | ||
55177 | int isCreate = (flags & UNQLITE_OPEN_CREATE); | ||
55178 | int isReadWrite = (flags & UNQLITE_OPEN_READWRITE); | ||
55179 | |||
55180 | pFile->h = INVALID_HANDLE_VALUE; | ||
55181 | /* Convert the filename to the system encoding. */ | ||
55182 | zConverted = convertUtf8Filename(zUtf8Name); | ||
55183 | if( zConverted==0 ){ | ||
55184 | return UNQLITE_NOMEM; | ||
55185 | } | ||
55186 | if( isReadWrite ){ | ||
55187 | dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; | ||
55188 | }else{ | ||
55189 | dwDesiredAccess = GENERIC_READ; | ||
55190 | } | ||
55191 | /* UNQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is | ||
55192 | ** created. | ||
55193 | */ | ||
55194 | if( isExclusive ){ | ||
55195 | /* Creates a new file, only if it does not already exist. */ | ||
55196 | /* If the file exists, it fails. */ | ||
55197 | dwCreationDisposition = CREATE_NEW; | ||
55198 | }else if( isCreate ){ | ||
55199 | /* Open existing file, or create if it doesn't exist */ | ||
55200 | dwCreationDisposition = OPEN_ALWAYS; | ||
55201 | }else{ | ||
55202 | /* Opens a file, only if it exists. */ | ||
55203 | dwCreationDisposition = OPEN_EXISTING; | ||
55204 | } | ||
55205 | |||
55206 | dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; | ||
55207 | |||
55208 | if( isDelete ){ | ||
55209 | dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY | ||
55210 | | FILE_ATTRIBUTE_HIDDEN | ||
55211 | | FILE_FLAG_DELETE_ON_CLOSE; | ||
55212 | }else{ | ||
55213 | dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; | ||
55214 | } | ||
55215 | h = CreateFileW((WCHAR*)zConverted, | ||
55216 | dwDesiredAccess, | ||
55217 | dwShareMode, | ||
55218 | NULL, | ||
55219 | dwCreationDisposition, | ||
55220 | dwFlagsAndAttributes, | ||
55221 | NULL | ||
55222 | ); | ||
55223 | if( h==INVALID_HANDLE_VALUE ){ | ||
55224 | pFile->lastErrno = GetLastError(); | ||
55225 | HeapFree(GetProcessHeap(),0,zConverted); | ||
55226 | return UNQLITE_IOERR; | ||
55227 | } | ||
55228 | SyZero(pFile,sizeof(*pFile)); | ||
55229 | pFile->pMethod = &winIoMethod; | ||
55230 | pFile->h = h; | ||
55231 | pFile->lastErrno = NO_ERROR; | ||
55232 | pFile->pVfs = pVfs; | ||
55233 | pFile->sectorSize = getSectorSize(pVfs, zUtf8Name); | ||
55234 | HeapFree(GetProcessHeap(),0,zConverted); | ||
55235 | return UNQLITE_OK; | ||
55236 | } | ||
55237 | /* | ||
55238 | * Export the Windows Vfs. | ||
55239 | */ | ||
55240 | UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void) | ||
55241 | { | ||
55242 | static const unqlite_vfs sWinvfs = { | ||
55243 | "Windows", /* Vfs name */ | ||
55244 | 1, /* Vfs structure version */ | ||
55245 | sizeof(winFile), /* szOsFile */ | ||
55246 | MAX_PATH, /* mxPathName */ | ||
55247 | winOpen, /* xOpen */ | ||
55248 | winDelete, /* xDelete */ | ||
55249 | winAccess, /* xAccess */ | ||
55250 | winFullPathname, /* xFullPathname */ | ||
55251 | 0, /* xTmp */ | ||
55252 | winSleep, /* xSleep */ | ||
55253 | winCurrentTime, /* xCurrentTime */ | ||
55254 | winGetLastError, /* xGetLastError */ | ||
55255 | }; | ||
55256 | return &sWinvfs; | ||
55257 | } | ||
55258 | #endif /* __WINNT__ */ | ||
55259 | /* | ||
55260 | * ---------------------------------------------------------- | ||
55261 | * File: pager.c | ||
55262 | * MD5: 57ff77347402fbf6892af589ff8a5df7 | ||
55263 | * ---------------------------------------------------------- | ||
55264 | */ | ||
55265 | /* | ||
55266 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
55267 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
55268 | * Version 1.1.6 | ||
55269 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
55270 | * please contact Symisc Systems via: | ||
55271 | * legal@symisc.net | ||
55272 | * licensing@symisc.net | ||
55273 | * contact@symisc.net | ||
55274 | * or visit: | ||
55275 | * http://unqlite.org/licensing.html | ||
55276 | */ | ||
55277 | /* $SymiscID: pager.c v1.1 Win7 2012-11-29 03:46 stable <chm@symisc.net> $ */ | ||
55278 | #ifndef UNQLITE_AMALGAMATION | ||
55279 | #include "unqliteInt.h" | ||
55280 | #endif | ||
55281 | /* | ||
55282 | ** This file implements the pager and the transaction manager for UnQLite (Mostly inspired from the SQLite3 Source tree). | ||
55283 | ** | ||
55284 | ** The Pager.eState variable stores the current 'state' of a pager. A | ||
55285 | ** pager may be in any one of the seven states shown in the following | ||
55286 | ** state diagram. | ||
55287 | ** | ||
55288 | ** OPEN <------+------+ | ||
55289 | ** | | | | ||
55290 | ** V | | | ||
55291 | ** +---------> READER-------+ | | ||
55292 | ** | | | | ||
55293 | ** | V | | ||
55294 | ** |<-------WRITER_LOCKED--------->| | ||
55295 | ** | | | | ||
55296 | ** | V | | ||
55297 | ** |<------WRITER_CACHEMOD-------->| | ||
55298 | ** | | | | ||
55299 | ** | V | | ||
55300 | ** |<-------WRITER_DBMOD---------->| | ||
55301 | ** | | | | ||
55302 | ** | V | | ||
55303 | ** +<------WRITER_FINISHED-------->+ | ||
55304 | ** | ||
55305 | ** OPEN: | ||
55306 | ** | ||
55307 | ** The pager starts up in this state. Nothing is guaranteed in this | ||
55308 | ** state - the file may or may not be locked and the database size is | ||
55309 | ** unknown. The database may not be read or written. | ||
55310 | ** | ||
55311 | ** * No read or write transaction is active. | ||
55312 | ** * Any lock, or no lock at all, may be held on the database file. | ||
55313 | ** * The dbSize and dbOrigSize variables may not be trusted. | ||
55314 | ** | ||
55315 | ** READER: | ||
55316 | ** | ||
55317 | ** In this state all the requirements for reading the database in | ||
55318 | ** rollback mode are met. Unless the pager is (or recently | ||
55319 | ** was) in exclusive-locking mode, a user-level read transaction is | ||
55320 | ** open. The database size is known in this state. | ||
55321 | ** | ||
55322 | ** * A read transaction may be active (but a write-transaction cannot). | ||
55323 | ** * A SHARED or greater lock is held on the database file. | ||
55324 | ** * The dbSize variable may be trusted (even if a user-level read | ||
55325 | ** transaction is not active). The dbOrigSize variables | ||
55326 | ** may not be trusted at this point. | ||
55327 | ** * Even if a read-transaction is not open, it is guaranteed that | ||
55328 | ** there is no hot-journal in the file-system. | ||
55329 | ** | ||
55330 | ** WRITER_LOCKED: | ||
55331 | ** | ||
55332 | ** The pager moves to this state from READER when a write-transaction | ||
55333 | ** is first opened on the database. In WRITER_LOCKED state, all locks | ||
55334 | ** required to start a write-transaction are held, but no actual | ||
55335 | ** modifications to the cache or database have taken place. | ||
55336 | ** | ||
55337 | ** In rollback mode, a RESERVED or (if the transaction was opened with | ||
55338 | ** EXCLUSIVE flag) EXCLUSIVE lock is obtained on the database file when | ||
55339 | ** moving to this state, but the journal file is not written to or opened | ||
55340 | ** to in this state. If the transaction is committed or rolled back while | ||
55341 | ** in WRITER_LOCKED state, all that is required is to unlock the database | ||
55342 | ** file. | ||
55343 | ** | ||
55344 | ** * A write transaction is active. | ||
55345 | ** * If the connection is open in rollback-mode, a RESERVED or greater | ||
55346 | ** lock is held on the database file. | ||
55347 | ** * The dbSize and dbOrigSize variables are all valid. | ||
55348 | ** * The contents of the pager cache have not been modified. | ||
55349 | ** * The journal file may or may not be open. | ||
55350 | ** * Nothing (not even the first header) has been written to the journal. | ||
55351 | ** | ||
55352 | ** WRITER_CACHEMOD: | ||
55353 | ** | ||
55354 | ** A pager moves from WRITER_LOCKED state to this state when a page is | ||
55355 | ** first modified by the upper layer. In rollback mode the journal file | ||
55356 | ** is opened (if it is not already open) and a header written to the | ||
55357 | ** start of it. The database file on disk has not been modified. | ||
55358 | ** | ||
55359 | ** * A write transaction is active. | ||
55360 | ** * A RESERVED or greater lock is held on the database file. | ||
55361 | ** * The journal file is open and the first header has been written | ||
55362 | ** to it, but the header has not been synced to disk. | ||
55363 | ** * The contents of the page cache have been modified. | ||
55364 | ** | ||
55365 | ** WRITER_DBMOD: | ||
55366 | ** | ||
55367 | ** The pager transitions from WRITER_CACHEMOD into WRITER_DBMOD state | ||
55368 | ** when it modifies the contents of the database file. | ||
55369 | ** | ||
55370 | ** * A write transaction is active. | ||
55371 | ** * An EXCLUSIVE or greater lock is held on the database file. | ||
55372 | ** * The journal file is open and the first header has been written | ||
55373 | ** and synced to disk. | ||
55374 | ** * The contents of the page cache have been modified (and possibly | ||
55375 | ** written to disk). | ||
55376 | ** | ||
55377 | ** WRITER_FINISHED: | ||
55378 | ** | ||
55379 | ** A rollback-mode pager changes to WRITER_FINISHED state from WRITER_DBMOD | ||
55380 | ** state after the entire transaction has been successfully written into the | ||
55381 | ** database file. In this state the transaction may be committed simply | ||
55382 | ** by finalizing the journal file. Once in WRITER_FINISHED state, it is | ||
55383 | ** not possible to modify the database further. At this point, the upper | ||
55384 | ** layer must either commit or rollback the transaction. | ||
55385 | ** | ||
55386 | ** * A write transaction is active. | ||
55387 | ** * An EXCLUSIVE or greater lock is held on the database file. | ||
55388 | ** * All writing and syncing of journal and database data has finished. | ||
55389 | ** If no error occured, all that remains is to finalize the journal to | ||
55390 | ** commit the transaction. If an error did occur, the caller will need | ||
55391 | ** to rollback the transaction. | ||
55392 | ** | ||
55393 | ** | ||
55394 | */ | ||
55395 | #define PAGER_OPEN 0 | ||
55396 | #define PAGER_READER 1 | ||
55397 | #define PAGER_WRITER_LOCKED 2 | ||
55398 | #define PAGER_WRITER_CACHEMOD 3 | ||
55399 | #define PAGER_WRITER_DBMOD 4 | ||
55400 | #define PAGER_WRITER_FINISHED 5 | ||
55401 | /* | ||
55402 | ** Journal files begin with the following magic string. The data | ||
55403 | ** was obtained from /dev/random. It is used only as a sanity check. | ||
55404 | ** | ||
55405 | ** NOTE: These values must be different from the one used by SQLite3 | ||
55406 | ** to avoid journal file collision. | ||
55407 | ** | ||
55408 | */ | ||
55409 | static const unsigned char aJournalMagic[] = { | ||
55410 | 0xa6, 0xe8, 0xcd, 0x2b, 0x1c, 0x92, 0xdb, 0x9f, | ||
55411 | }; | ||
55412 | /* | ||
55413 | ** The journal header size for this pager. This is usually the same | ||
55414 | ** size as a single disk sector. See also setSectorSize(). | ||
55415 | */ | ||
55416 | #define JOURNAL_HDR_SZ(pPager) (pPager->iSectorSize) | ||
55417 | /* | ||
55418 | * Database page handle. | ||
55419 | * Each raw disk page is represented in memory by an instance | ||
55420 | * of the following structure. | ||
55421 | */ | ||
55422 | typedef struct Page Page; | ||
55423 | struct Page { | ||
55424 | /* Must correspond to unqlite_page */ | ||
55425 | unsigned char *zData; /* Content of this page */ | ||
55426 | void *pUserData; /* Extra content */ | ||
55427 | pgno pgno; /* Page number for this page */ | ||
55428 | /********************************************************************** | ||
55429 | ** Elements above are public. All that follows is private to pcache.c | ||
55430 | ** and should not be accessed by other modules. | ||
55431 | */ | ||
55432 | Pager *pPager; /* The pager this page is part of */ | ||
55433 | int flags; /* Page flags defined below */ | ||
55434 | int nRef; /* Number of users of this page */ | ||
55435 | Page *pNext, *pPrev; /* A list of all pages */ | ||
55436 | Page *pDirtyNext; /* Next element in list of dirty pages */ | ||
55437 | Page *pDirtyPrev; /* Previous element in list of dirty pages */ | ||
55438 | Page *pNextCollide,*pPrevCollide; /* Collission chain */ | ||
55439 | Page *pNextHot,*pPrevHot; /* Hot dirty pages chain */ | ||
55440 | }; | ||
55441 | /* Bit values for Page.flags */ | ||
55442 | #define PAGE_DIRTY 0x002 /* Page has changed */ | ||
55443 | #define PAGE_NEED_SYNC 0x004 /* fsync the rollback journal before | ||
55444 | ** writing this page to the database */ | ||
55445 | #define PAGE_DONT_WRITE 0x008 /* Dont write page content to disk */ | ||
55446 | #define PAGE_NEED_READ 0x010 /* Content is unread */ | ||
55447 | #define PAGE_IN_JOURNAL 0x020 /* Page written to the journal */ | ||
55448 | #define PAGE_HOT_DIRTY 0x040 /* Hot dirty page */ | ||
55449 | #define PAGE_DONT_MAKE_HOT 0x080 /* Dont make this page Hot. In other words, | ||
55450 | * do not link it to the hot dirty list. | ||
55451 | */ | ||
55452 | /* | ||
55453 | * Each active database pager is represented by an instance of | ||
55454 | * the following structure. | ||
55455 | */ | ||
55456 | struct Pager | ||
55457 | { | ||
55458 | SyMemBackend *pAllocator; /* Memory backend */ | ||
55459 | unqlite *pDb; /* DB handle that own this instance */ | ||
55460 | unqlite_kv_engine *pEngine; /* Underlying KV storage engine */ | ||
55461 | char *zFilename; /* Name of the database file */ | ||
55462 | char *zJournal; /* Name of the journal file */ | ||
55463 | unqlite_vfs *pVfs; /* Underlying virtual file system */ | ||
55464 | unqlite_file *pfd,*pjfd; /* File descriptors for database and journal */ | ||
55465 | pgno dbSize; /* Number of pages in the file */ | ||
55466 | pgno dbOrigSize; /* dbSize before the current change */ | ||
55467 | sxi64 dbByteSize; /* Database size in bytes */ | ||
55468 | void *pMmap; /* Read-only Memory view (mmap) of the whole file if requested (UNQLITE_OPEN_MMAP). */ | ||
55469 | sxu32 nRec; /* Number of pages written to the journal */ | ||
55470 | SyPRNGCtx sPrng; /* PRNG Context */ | ||
55471 | sxu32 cksumInit; /* Quasi-random value added to every checksum */ | ||
55472 | sxu32 iOpenFlags; /* Flag passed to unqlite_open() after processing */ | ||
55473 | sxi64 iJournalOfft; /* Journal offset we are reading from */ | ||
55474 | int (*xBusyHandler)(void *); /* Busy handler */ | ||
55475 | void *pBusyHandlerArg; /* First arg to xBusyHandler() */ | ||
55476 | void (*xPageUnpin)(void *); /* Page Unpin callback */ | ||
55477 | void (*xPageReload)(void *); /* Page Reload callback */ | ||
55478 | Bitvec *pVec; /* Bitmap */ | ||
55479 | Page *pHeader; /* Page one of the database (Unqlite header) */ | ||
55480 | Sytm tmCreate; /* Database creation time */ | ||
55481 | SyString sKv; /* Underlying Key/Value storage engine name */ | ||
55482 | int iState; /* Pager state */ | ||
55483 | int iLock; /* Lock state */ | ||
55484 | sxi32 iFlags; /* Control flags (see below) */ | ||
55485 | int is_mem; /* True for an in-memory database */ | ||
55486 | int is_rdonly; /* True for a read-only database */ | ||
55487 | int no_jrnl; /* TRUE to omit journaling */ | ||
55488 | int iPageSize; /* Page size in bytes (default 4K) */ | ||
55489 | int iSectorSize; /* Size of a single sector on disk */ | ||
55490 | unsigned char *zTmpPage; /* Temporary page */ | ||
55491 | Page *pFirstDirty; /* First dirty pages */ | ||
55492 | Page *pDirty; /* Transient list of dirty pages */ | ||
55493 | Page *pAll; /* List of all pages */ | ||
55494 | Page *pHotDirty; /* List of hot dirty pages */ | ||
55495 | Page *pFirstHot; /* First hot dirty page */ | ||
55496 | sxu32 nHot; /* Total number of hot dirty pages */ | ||
55497 | Page **apHash; /* Page table */ | ||
55498 | sxu32 nSize; /* apHash[] size: Must be a power of two */ | ||
55499 | sxu32 nPage; /* Total number of page loaded in memory */ | ||
55500 | sxu32 nCacheMax; /* Maximum page to cache*/ | ||
55501 | }; | ||
55502 | /* Control flags */ | ||
55503 | #define PAGER_CTRL_COMMIT_ERR 0x001 /* Commit error */ | ||
55504 | #define PAGER_CTRL_DIRTY_COMMIT 0x002 /* Dirty commit has been applied */ | ||
55505 | /* | ||
55506 | ** Read a 32-bit integer from the given file descriptor. | ||
55507 | ** All values are stored on disk as big-endian. | ||
55508 | */ | ||
55509 | static int ReadInt32(unqlite_file *pFd,sxu32 *pOut,sxi64 iOfft) | ||
55510 | { | ||
55511 | unsigned char zBuf[4]; | ||
55512 | int rc; | ||
55513 | rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft); | ||
55514 | if( rc != UNQLITE_OK ){ | ||
55515 | return rc; | ||
55516 | } | ||
55517 | SyBigEndianUnpack32(zBuf,pOut); | ||
55518 | return UNQLITE_OK; | ||
55519 | } | ||
55520 | /* | ||
55521 | ** Read a 64-bit integer from the given file descriptor. | ||
55522 | ** All values are stored on disk as big-endian. | ||
55523 | */ | ||
55524 | static int ReadInt64(unqlite_file *pFd,sxu64 *pOut,sxi64 iOfft) | ||
55525 | { | ||
55526 | unsigned char zBuf[8]; | ||
55527 | int rc; | ||
55528 | rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft); | ||
55529 | if( rc != UNQLITE_OK ){ | ||
55530 | return rc; | ||
55531 | } | ||
55532 | SyBigEndianUnpack64(zBuf,pOut); | ||
55533 | return UNQLITE_OK; | ||
55534 | } | ||
55535 | /* | ||
55536 | ** Write a 32-bit integer into the given file descriptor. | ||
55537 | */ | ||
55538 | static int WriteInt32(unqlite_file *pFd,sxu32 iNum,sxi64 iOfft) | ||
55539 | { | ||
55540 | unsigned char zBuf[4]; | ||
55541 | int rc; | ||
55542 | SyBigEndianPack32(zBuf,iNum); | ||
55543 | rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft); | ||
55544 | return rc; | ||
55545 | } | ||
55546 | /* | ||
55547 | ** Write a 64-bit integer into the given file descriptor. | ||
55548 | */ | ||
55549 | static int WriteInt64(unqlite_file *pFd,sxu64 iNum,sxi64 iOfft) | ||
55550 | { | ||
55551 | unsigned char zBuf[8]; | ||
55552 | int rc; | ||
55553 | SyBigEndianPack64(zBuf,iNum); | ||
55554 | rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft); | ||
55555 | return rc; | ||
55556 | } | ||
55557 | /* | ||
55558 | ** The maximum allowed sector size. 64KiB. If the xSectorsize() method | ||
55559 | ** returns a value larger than this, then MAX_SECTOR_SIZE is used instead. | ||
55560 | ** This could conceivably cause corruption following a power failure on | ||
55561 | ** such a system. This is currently an undocumented limit. | ||
55562 | */ | ||
55563 | #define MAX_SECTOR_SIZE 0x10000 | ||
55564 | /* | ||
55565 | ** Get the size of a single sector on disk. | ||
55566 | ** The sector size will be used used to determine the size | ||
55567 | ** and alignment of journal header and within created journal files. | ||
55568 | ** | ||
55569 | ** The default sector size is set to 512. | ||
55570 | */ | ||
55571 | static int GetSectorSize(unqlite_file *pFd) | ||
55572 | { | ||
55573 | int iSectorSize = UNQLITE_DEFAULT_SECTOR_SIZE; | ||
55574 | if( pFd ){ | ||
55575 | iSectorSize = unqliteOsSectorSize(pFd); | ||
55576 | if( iSectorSize < 32 ){ | ||
55577 | iSectorSize = 512; | ||
55578 | } | ||
55579 | if( iSectorSize > MAX_SECTOR_SIZE ){ | ||
55580 | iSectorSize = MAX_SECTOR_SIZE; | ||
55581 | } | ||
55582 | } | ||
55583 | return iSectorSize; | ||
55584 | } | ||
55585 | /* Hash function for page number */ | ||
55586 | #define PAGE_HASH(PNUM) (PNUM) | ||
55587 | /* | ||
55588 | * Fetch a page from the cache. | ||
55589 | */ | ||
55590 | static Page * pager_fetch_page(Pager *pPager,pgno page_num) | ||
55591 | { | ||
55592 | Page *pEntry; | ||
55593 | if( pPager->nPage < 1 ){ | ||
55594 | /* Don't bother hashing */ | ||
55595 | return 0; | ||
55596 | } | ||
55597 | /* Perform the lookup */ | ||
55598 | pEntry = pPager->apHash[PAGE_HASH(page_num) & (pPager->nSize - 1)]; | ||
55599 | for(;;){ | ||
55600 | if( pEntry == 0 ){ | ||
55601 | break; | ||
55602 | } | ||
55603 | if( pEntry->pgno == page_num ){ | ||
55604 | return pEntry; | ||
55605 | } | ||
55606 | /* Point to the next entry in the colission chain */ | ||
55607 | pEntry = pEntry->pNextCollide; | ||
55608 | } | ||
55609 | /* No such page */ | ||
55610 | return 0; | ||
55611 | } | ||
55612 | /* | ||
55613 | * Allocate and initialize a new page. | ||
55614 | */ | ||
55615 | static Page * pager_alloc_page(Pager *pPager,pgno num_page) | ||
55616 | { | ||
55617 | Page *pNew; | ||
55618 | |||
55619 | pNew = (Page *)SyMemBackendPoolAlloc(pPager->pAllocator,sizeof(Page)+pPager->iPageSize); | ||
55620 | if( pNew == 0 ){ | ||
55621 | return 0; | ||
55622 | } | ||
55623 | /* Zero the structure */ | ||
55624 | SyZero(pNew,sizeof(Page)+pPager->iPageSize); | ||
55625 | /* Page data */ | ||
55626 | pNew->zData = (unsigned char *)&pNew[1]; | ||
55627 | /* Fill in the structure */ | ||
55628 | pNew->pPager = pPager; | ||
55629 | pNew->nRef = 1; | ||
55630 | pNew->pgno = num_page; | ||
55631 | return pNew; | ||
55632 | } | ||
55633 | /* | ||
55634 | * Increment the reference count of a given page. | ||
55635 | */ | ||
55636 | static void page_ref(Page *pPage) | ||
55637 | { | ||
55638 | pPage->nRef++; | ||
55639 | } | ||
55640 | /* | ||
55641 | * Release an in-memory page after its reference count reach zero. | ||
55642 | */ | ||
55643 | static int pager_release_page(Pager *pPager,Page *pPage) | ||
55644 | { | ||
55645 | int rc = UNQLITE_OK; | ||
55646 | if( !(pPage->flags & PAGE_DIRTY)){ | ||
55647 | /* Invoke the unpin callback if available */ | ||
55648 | if( pPager->xPageUnpin && pPage->pUserData ){ | ||
55649 | pPager->xPageUnpin(pPage->pUserData); | ||
55650 | } | ||
55651 | pPage->pUserData = 0; | ||
55652 | SyMemBackendPoolFree(pPager->pAllocator,pPage); | ||
55653 | }else{ | ||
55654 | /* Dirty page, it will be released later when a dirty commit | ||
55655 | * or the final commit have been applied. | ||
55656 | */ | ||
55657 | rc = UNQLITE_LOCKED; | ||
55658 | } | ||
55659 | return rc; | ||
55660 | } | ||
55661 | /* Forward declaration */ | ||
55662 | static int pager_unlink_page(Pager *pPager,Page *pPage); | ||
55663 | /* | ||
55664 | * Decrement the reference count of a given page. | ||
55665 | */ | ||
55666 | static void page_unref(Page *pPage) | ||
55667 | { | ||
55668 | pPage->nRef--; | ||
55669 | if( pPage->nRef < 1 ){ | ||
55670 | Pager *pPager = pPage->pPager; | ||
55671 | if( !(pPage->flags & PAGE_DIRTY) ){ | ||
55672 | pager_unlink_page(pPager,pPage); | ||
55673 | /* Release the page */ | ||
55674 | pager_release_page(pPager,pPage); | ||
55675 | }else{ | ||
55676 | if( pPage->flags & PAGE_DONT_MAKE_HOT ){ | ||
55677 | /* Do not add this page to the hot dirty list */ | ||
55678 | return; | ||
55679 | } | ||
55680 | if( !(pPage->flags & PAGE_HOT_DIRTY) ){ | ||
55681 | /* Add to the hot dirty list */ | ||
55682 | pPage->pPrevHot = 0; | ||
55683 | if( pPager->pFirstHot == 0 ){ | ||
55684 | pPager->pFirstHot = pPager->pHotDirty = pPage; | ||
55685 | }else{ | ||
55686 | pPage->pNextHot = pPager->pHotDirty; | ||
55687 | if( pPager->pHotDirty ){ | ||
55688 | pPager->pHotDirty->pPrevHot = pPage; | ||
55689 | } | ||
55690 | pPager->pHotDirty = pPage; | ||
55691 | } | ||
55692 | pPager->nHot++; | ||
55693 | pPage->flags |= PAGE_HOT_DIRTY; | ||
55694 | } | ||
55695 | } | ||
55696 | } | ||
55697 | } | ||
55698 | /* | ||
55699 | * Link a freshly created page to the list of active page. | ||
55700 | */ | ||
55701 | static int pager_link_page(Pager *pPager,Page *pPage) | ||
55702 | { | ||
55703 | sxu32 nBucket; | ||
55704 | /* Install in the corresponding bucket */ | ||
55705 | nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1); | ||
55706 | pPage->pNextCollide = pPager->apHash[nBucket]; | ||
55707 | if( pPager->apHash[nBucket] ){ | ||
55708 | pPager->apHash[nBucket]->pPrevCollide = pPage; | ||
55709 | } | ||
55710 | pPager->apHash[nBucket] = pPage; | ||
55711 | /* Link to the list of active pages */ | ||
55712 | MACRO_LD_PUSH(pPager->pAll,pPage); | ||
55713 | pPager->nPage++; | ||
55714 | if( (pPager->nPage >= pPager->nSize * 4) && pPager->nPage < 100000 ){ | ||
55715 | /* Grow the hashtable */ | ||
55716 | sxu32 nNewSize = pPager->nSize << 1; | ||
55717 | Page *pEntry,**apNew; | ||
55718 | sxu32 n; | ||
55719 | apNew = (Page **)SyMemBackendAlloc(pPager->pAllocator, nNewSize * sizeof(Page *)); | ||
55720 | if( apNew ){ | ||
55721 | sxu32 iBucket; | ||
55722 | /* Zero the new table */ | ||
55723 | SyZero((void *)apNew, nNewSize * sizeof(Page *)); | ||
55724 | /* Rehash all entries */ | ||
55725 | n = 0; | ||
55726 | pEntry = pPager->pAll; | ||
55727 | for(;;){ | ||
55728 | /* Loop one */ | ||
55729 | if( n >= pPager->nPage ){ | ||
55730 | break; | ||
55731 | } | ||
55732 | pEntry->pNextCollide = pEntry->pPrevCollide = 0; | ||
55733 | /* Install in the new bucket */ | ||
55734 | iBucket = PAGE_HASH(pEntry->pgno) & (nNewSize - 1); | ||
55735 | pEntry->pNextCollide = apNew[iBucket]; | ||
55736 | if( apNew[iBucket] ){ | ||
55737 | apNew[iBucket]->pPrevCollide = pEntry; | ||
55738 | } | ||
55739 | apNew[iBucket] = pEntry; | ||
55740 | /* Point to the next entry */ | ||
55741 | pEntry = pEntry->pNext; | ||
55742 | n++; | ||
55743 | } | ||
55744 | /* Release the old table and reflect the change */ | ||
55745 | SyMemBackendFree(pPager->pAllocator,(void *)pPager->apHash); | ||
55746 | pPager->apHash = apNew; | ||
55747 | pPager->nSize = nNewSize; | ||
55748 | } | ||
55749 | } | ||
55750 | return UNQLITE_OK; | ||
55751 | } | ||
55752 | /* | ||
55753 | * Unlink a page from the list of active pages. | ||
55754 | */ | ||
55755 | static int pager_unlink_page(Pager *pPager,Page *pPage) | ||
55756 | { | ||
55757 | if( pPage->pNextCollide ){ | ||
55758 | pPage->pNextCollide->pPrevCollide = pPage->pPrevCollide; | ||
55759 | } | ||
55760 | if( pPage->pPrevCollide ){ | ||
55761 | pPage->pPrevCollide->pNextCollide = pPage->pNextCollide; | ||
55762 | }else{ | ||
55763 | sxu32 nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1); | ||
55764 | pPager->apHash[nBucket] = pPage->pNextCollide; | ||
55765 | } | ||
55766 | MACRO_LD_REMOVE(pPager->pAll,pPage); | ||
55767 | pPager->nPage--; | ||
55768 | return UNQLITE_OK; | ||
55769 | } | ||
55770 | /* | ||
55771 | * Update the content of a cached page. | ||
55772 | */ | ||
55773 | static int pager_fill_page(Pager *pPager,pgno iNum,void *pContents) | ||
55774 | { | ||
55775 | Page *pPage; | ||
55776 | /* Fetch the page from the catch */ | ||
55777 | pPage = pager_fetch_page(pPager,iNum); | ||
55778 | if( pPage == 0 ){ | ||
55779 | return SXERR_NOTFOUND; | ||
55780 | } | ||
55781 | /* Reflect the change */ | ||
55782 | SyMemcpy(pContents,pPage->zData,pPager->iPageSize); | ||
55783 | |||
55784 | return UNQLITE_OK; | ||
55785 | } | ||
55786 | /* | ||
55787 | * Read the content of a page from disk. | ||
55788 | */ | ||
55789 | static int pager_get_page_contents(Pager *pPager,Page *pPage,int noContent) | ||
55790 | { | ||
55791 | int rc = UNQLITE_OK; | ||
55792 | if( pPager->is_mem || noContent || pPage->pgno >= pPager->dbSize ){ | ||
55793 | /* Do not bother reading, zero the page contents only */ | ||
55794 | SyZero(pPage->zData,pPager->iPageSize); | ||
55795 | return UNQLITE_OK; | ||
55796 | } | ||
55797 | if( (pPager->iOpenFlags & UNQLITE_OPEN_MMAP) && (pPager->pMmap /* Paranoid edition */) ){ | ||
55798 | unsigned char *zMap = (unsigned char *)pPager->pMmap; | ||
55799 | pPage->zData = &zMap[pPage->pgno * pPager->iPageSize]; | ||
55800 | }else{ | ||
55801 | /* Read content */ | ||
55802 | rc = unqliteOsRead(pPager->pfd,pPage->zData,pPager->iPageSize,pPage->pgno * pPager->iPageSize); | ||
55803 | } | ||
55804 | return rc; | ||
55805 | } | ||
55806 | /* | ||
55807 | * Add a page to the dirty list. | ||
55808 | */ | ||
55809 | static void pager_page_to_dirty_list(Pager *pPager,Page *pPage) | ||
55810 | { | ||
55811 | if( pPage->flags & PAGE_DIRTY ){ | ||
55812 | /* Already set */ | ||
55813 | return; | ||
55814 | } | ||
55815 | /* Mark the page as dirty */ | ||
55816 | pPage->flags |= PAGE_DIRTY|PAGE_NEED_SYNC|PAGE_IN_JOURNAL; | ||
55817 | /* Link to the list */ | ||
55818 | pPage->pDirtyPrev = 0; | ||
55819 | pPage->pDirtyNext = pPager->pDirty; | ||
55820 | if( pPager->pDirty ){ | ||
55821 | pPager->pDirty->pDirtyPrev = pPage; | ||
55822 | } | ||
55823 | pPager->pDirty = pPage; | ||
55824 | if( pPager->pFirstDirty == 0 ){ | ||
55825 | pPager->pFirstDirty = pPage; | ||
55826 | } | ||
55827 | } | ||
55828 | /* | ||
55829 | * Merge sort. | ||
55830 | * The merge sort implementation is based on the one used by | ||
55831 | * the PH7 Embeddable PHP Engine (http://ph7.symisc.net/). | ||
55832 | */ | ||
55833 | /* | ||
55834 | ** Inputs: | ||
55835 | ** a: A sorted, null-terminated linked list. (May be null). | ||
55836 | ** b: A sorted, null-terminated linked list. (May be null). | ||
55837 | ** cmp: A pointer to the comparison function. | ||
55838 | ** | ||
55839 | ** Return Value: | ||
55840 | ** A pointer to the head of a sorted list containing the elements | ||
55841 | ** of both a and b. | ||
55842 | ** | ||
55843 | ** Side effects: | ||
55844 | ** The "next", "prev" pointers for elements in the lists a and b are | ||
55845 | ** changed. | ||
55846 | */ | ||
55847 | static Page * page_merge_dirty(Page *pA, Page *pB) | ||
55848 | { | ||
55849 | Page result, *pTail; | ||
55850 | /* Prevent compiler warning */ | ||
55851 | result.pDirtyNext = result.pDirtyPrev = 0; | ||
55852 | pTail = &result; | ||
55853 | while( pA && pB ){ | ||
55854 | if( pA->pgno < pB->pgno ){ | ||
55855 | pTail->pDirtyPrev = pA; | ||
55856 | pA->pDirtyNext = pTail; | ||
55857 | pTail = pA; | ||
55858 | pA = pA->pDirtyPrev; | ||
55859 | }else{ | ||
55860 | pTail->pDirtyPrev = pB; | ||
55861 | pB->pDirtyNext = pTail; | ||
55862 | pTail = pB; | ||
55863 | pB = pB->pDirtyPrev; | ||
55864 | } | ||
55865 | } | ||
55866 | if( pA ){ | ||
55867 | pTail->pDirtyPrev = pA; | ||
55868 | pA->pDirtyNext = pTail; | ||
55869 | }else if( pB ){ | ||
55870 | pTail->pDirtyPrev = pB; | ||
55871 | pB->pDirtyNext = pTail; | ||
55872 | }else{ | ||
55873 | pTail->pDirtyPrev = pTail->pDirtyNext = 0; | ||
55874 | } | ||
55875 | return result.pDirtyPrev; | ||
55876 | } | ||
55877 | /* | ||
55878 | ** Inputs: | ||
55879 | ** Map: Input hashmap | ||
55880 | ** cmp: A comparison function. | ||
55881 | ** | ||
55882 | ** Return Value: | ||
55883 | ** Sorted hashmap. | ||
55884 | ** | ||
55885 | ** Side effects: | ||
55886 | ** The "next" pointers for elements in list are changed. | ||
55887 | */ | ||
55888 | #define N_SORT_BUCKET 32 | ||
55889 | static Page * pager_get_dirty_pages(Pager *pPager) | ||
55890 | { | ||
55891 | Page *a[N_SORT_BUCKET], *p, *pIn; | ||
55892 | sxu32 i; | ||
55893 | if( pPager->pFirstDirty == 0 ){ | ||
55894 | /* Don't bother sorting, the list is already empty */ | ||
55895 | return 0; | ||
55896 | } | ||
55897 | SyZero(a, sizeof(a)); | ||
55898 | /* Point to the first inserted entry */ | ||
55899 | pIn = pPager->pFirstDirty; | ||
55900 | while( pIn ){ | ||
55901 | p = pIn; | ||
55902 | pIn = p->pDirtyPrev; | ||
55903 | p->pDirtyPrev = 0; | ||
55904 | for(i=0; i<N_SORT_BUCKET-1; i++){ | ||
55905 | if( a[i]==0 ){ | ||
55906 | a[i] = p; | ||
55907 | break; | ||
55908 | }else{ | ||
55909 | p = page_merge_dirty(a[i], p); | ||
55910 | a[i] = 0; | ||
55911 | } | ||
55912 | } | ||
55913 | if( i==N_SORT_BUCKET-1 ){ | ||
55914 | /* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list. | ||
55915 | * But that is impossible. | ||
55916 | */ | ||
55917 | a[i] = page_merge_dirty(a[i], p); | ||
55918 | } | ||
55919 | } | ||
55920 | p = a[0]; | ||
55921 | for(i=1; i<N_SORT_BUCKET; i++){ | ||
55922 | p = page_merge_dirty(p,a[i]); | ||
55923 | } | ||
55924 | p->pDirtyNext = 0; | ||
55925 | return p; | ||
55926 | } | ||
55927 | /* | ||
55928 | * See block comment above. | ||
55929 | */ | ||
55930 | static Page * page_merge_hot(Page *pA, Page *pB) | ||
55931 | { | ||
55932 | Page result, *pTail; | ||
55933 | /* Prevent compiler warning */ | ||
55934 | result.pNextHot = result.pPrevHot = 0; | ||
55935 | pTail = &result; | ||
55936 | while( pA && pB ){ | ||
55937 | if( pA->pgno < pB->pgno ){ | ||
55938 | pTail->pPrevHot = pA; | ||
55939 | pA->pNextHot = pTail; | ||
55940 | pTail = pA; | ||
55941 | pA = pA->pPrevHot; | ||
55942 | }else{ | ||
55943 | pTail->pPrevHot = pB; | ||
55944 | pB->pNextHot = pTail; | ||
55945 | pTail = pB; | ||
55946 | pB = pB->pPrevHot; | ||
55947 | } | ||
55948 | } | ||
55949 | if( pA ){ | ||
55950 | pTail->pPrevHot = pA; | ||
55951 | pA->pNextHot = pTail; | ||
55952 | }else if( pB ){ | ||
55953 | pTail->pPrevHot = pB; | ||
55954 | pB->pNextHot = pTail; | ||
55955 | }else{ | ||
55956 | pTail->pPrevHot = pTail->pNextHot = 0; | ||
55957 | } | ||
55958 | return result.pPrevHot; | ||
55959 | } | ||
55960 | /* | ||
55961 | ** Inputs: | ||
55962 | ** Map: Input hashmap | ||
55963 | ** cmp: A comparison function. | ||
55964 | ** | ||
55965 | ** Return Value: | ||
55966 | ** Sorted hashmap. | ||
55967 | ** | ||
55968 | ** Side effects: | ||
55969 | ** The "next" pointers for elements in list are changed. | ||
55970 | */ | ||
55971 | #define N_SORT_BUCKET 32 | ||
55972 | static Page * pager_get_hot_pages(Pager *pPager) | ||
55973 | { | ||
55974 | Page *a[N_SORT_BUCKET], *p, *pIn; | ||
55975 | sxu32 i; | ||
55976 | if( pPager->pFirstHot == 0 ){ | ||
55977 | /* Don't bother sorting, the list is already empty */ | ||
55978 | return 0; | ||
55979 | } | ||
55980 | SyZero(a, sizeof(a)); | ||
55981 | /* Point to the first inserted entry */ | ||
55982 | pIn = pPager->pFirstHot; | ||
55983 | while( pIn ){ | ||
55984 | p = pIn; | ||
55985 | pIn = p->pPrevHot; | ||
55986 | p->pPrevHot = 0; | ||
55987 | for(i=0; i<N_SORT_BUCKET-1; i++){ | ||
55988 | if( a[i]==0 ){ | ||
55989 | a[i] = p; | ||
55990 | break; | ||
55991 | }else{ | ||
55992 | p = page_merge_hot(a[i], p); | ||
55993 | a[i] = 0; | ||
55994 | } | ||
55995 | } | ||
55996 | if( i==N_SORT_BUCKET-1 ){ | ||
55997 | /* To get here, there need to be 2^(N_SORT_BUCKET) elements in he input list. | ||
55998 | * But that is impossible. | ||
55999 | */ | ||
56000 | a[i] = page_merge_hot(a[i], p); | ||
56001 | } | ||
56002 | } | ||
56003 | p = a[0]; | ||
56004 | for(i=1; i<N_SORT_BUCKET; i++){ | ||
56005 | p = page_merge_hot(p,a[i]); | ||
56006 | } | ||
56007 | p->pNextHot = 0; | ||
56008 | return p; | ||
56009 | } | ||
56010 | /* | ||
56011 | ** The format for the journal header is as follows: | ||
56012 | ** - 8 bytes: Magic identifying journal format. | ||
56013 | ** - 4 bytes: Number of records in journal. | ||
56014 | ** - 4 bytes: Random number used for page hash. | ||
56015 | ** - 8 bytes: Initial database page count. | ||
56016 | ** - 4 bytes: Sector size used by the process that wrote this journal. | ||
56017 | ** - 4 bytes: Database page size. | ||
56018 | ** | ||
56019 | ** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space. | ||
56020 | */ | ||
56021 | /* | ||
56022 | ** Open the journal file and extract its header information. | ||
56023 | ** | ||
56024 | ** If the header is read successfully, *pNRec is set to the number of | ||
56025 | ** page records following this header and *pDbSize is set to the size of the | ||
56026 | ** database before the transaction began, in pages. Also, pPager->cksumInit | ||
56027 | ** is set to the value read from the journal header. UNQLITE_OK is returned | ||
56028 | ** in this case. | ||
56029 | ** | ||
56030 | ** If the journal header file appears to be corrupted, UNQLITE_DONE is | ||
56031 | ** returned and *pNRec and *PDbSize are undefined. If JOURNAL_HDR_SZ bytes | ||
56032 | ** cannot be read from the journal file an error code is returned. | ||
56033 | */ | ||
56034 | static int pager_read_journal_header( | ||
56035 | Pager *pPager, /* Pager object */ | ||
56036 | sxu32 *pNRec, /* OUT: Value read from the nRec field */ | ||
56037 | pgno *pDbSize /* OUT: Value of original database size field */ | ||
56038 | ) | ||
56039 | { | ||
56040 | sxu32 iPageSize,iSectorSize; | ||
56041 | unsigned char zMagic[8]; | ||
56042 | sxi64 iHdrOfft; | ||
56043 | sxi64 iSize; | ||
56044 | int rc; | ||
56045 | /* Offset to start reading from */ | ||
56046 | iHdrOfft = 0; | ||
56047 | /* Get the size of the journal */ | ||
56048 | rc = unqliteOsFileSize(pPager->pjfd,&iSize); | ||
56049 | if( rc != UNQLITE_OK ){ | ||
56050 | return UNQLITE_DONE; | ||
56051 | } | ||
56052 | /* If the journal file is too small, return UNQLITE_DONE. */ | ||
56053 | if( 32 /* Minimum sector size */> iSize ){ | ||
56054 | return UNQLITE_DONE; | ||
56055 | } | ||
56056 | /* Make sure we are dealing with a valid journal */ | ||
56057 | rc = unqliteOsRead(pPager->pjfd,zMagic,sizeof(zMagic),iHdrOfft); | ||
56058 | if( rc != UNQLITE_OK ){ | ||
56059 | return rc; | ||
56060 | } | ||
56061 | if( SyMemcmp(zMagic,aJournalMagic,sizeof(zMagic)) != 0 ){ | ||
56062 | return UNQLITE_DONE; | ||
56063 | } | ||
56064 | iHdrOfft += sizeof(zMagic); | ||
56065 | /* Read the first three 32-bit fields of the journal header: The nRec | ||
56066 | ** field, the checksum-initializer and the database size at the start | ||
56067 | ** of the transaction. Return an error code if anything goes wrong. | ||
56068 | */ | ||
56069 | rc = ReadInt32(pPager->pjfd,pNRec,iHdrOfft); | ||
56070 | if( rc != UNQLITE_OK ){ | ||
56071 | return rc; | ||
56072 | } | ||
56073 | iHdrOfft += 4; | ||
56074 | rc = ReadInt32(pPager->pjfd,&pPager->cksumInit,iHdrOfft); | ||
56075 | if( rc != UNQLITE_OK ){ | ||
56076 | return rc; | ||
56077 | } | ||
56078 | iHdrOfft += 4; | ||
56079 | rc = ReadInt64(pPager->pjfd,pDbSize,iHdrOfft); | ||
56080 | if( rc != UNQLITE_OK ){ | ||
56081 | return rc; | ||
56082 | } | ||
56083 | iHdrOfft += 8; | ||
56084 | /* Read the page-size and sector-size journal header fields. */ | ||
56085 | rc = ReadInt32(pPager->pjfd,&iSectorSize,iHdrOfft); | ||
56086 | if( rc != UNQLITE_OK ){ | ||
56087 | return rc; | ||
56088 | } | ||
56089 | iHdrOfft += 4; | ||
56090 | rc = ReadInt32(pPager->pjfd,&iPageSize,iHdrOfft); | ||
56091 | if( rc != UNQLITE_OK ){ | ||
56092 | return rc; | ||
56093 | } | ||
56094 | /* Check that the values read from the page-size and sector-size fields | ||
56095 | ** are within range. To be 'in range', both values need to be a power | ||
56096 | ** of two greater than or equal to 512 or 32, and not greater than their | ||
56097 | ** respective compile time maximum limits. | ||
56098 | */ | ||
56099 | if( iPageSize < UNQLITE_MIN_PAGE_SIZE || iSectorSize<32 | ||
56100 | || iPageSize > UNQLITE_MAX_PAGE_SIZE || iSectorSize>MAX_SECTOR_SIZE | ||
56101 | || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 | ||
56102 | ){ | ||
56103 | /* If the either the page-size or sector-size in the journal-header is | ||
56104 | ** invalid, then the process that wrote the journal-header must have | ||
56105 | ** crashed before the header was synced. In this case stop reading | ||
56106 | ** the journal file here. | ||
56107 | */ | ||
56108 | return UNQLITE_DONE; | ||
56109 | } | ||
56110 | /* Update the assumed sector-size to match the value used by | ||
56111 | ** the process that created this journal. If this journal was | ||
56112 | ** created by a process other than this one, then this routine | ||
56113 | ** is being called from within pager_playback(). The local value | ||
56114 | ** of Pager.sectorSize is restored at the end of that routine. | ||
56115 | */ | ||
56116 | pPager->iSectorSize = iSectorSize; | ||
56117 | pPager->iPageSize = iPageSize; | ||
56118 | /* Ready to rollback */ | ||
56119 | pPager->iJournalOfft = JOURNAL_HDR_SZ(pPager); | ||
56120 | /* All done */ | ||
56121 | return UNQLITE_OK; | ||
56122 | } | ||
56123 | /* | ||
56124 | * Write the journal header in the given memory buffer. | ||
56125 | * The given buffer is big enough to hold the whole header. | ||
56126 | */ | ||
56127 | static int pager_write_journal_header(Pager *pPager,unsigned char *zBuf) | ||
56128 | { | ||
56129 | unsigned char *zPtr = zBuf; | ||
56130 | /* 8 bytes magic number */ | ||
56131 | SyMemcpy(aJournalMagic,zPtr,sizeof(aJournalMagic)); | ||
56132 | zPtr += sizeof(aJournalMagic); | ||
56133 | /* 4 bytes: Number of records in journal. */ | ||
56134 | SyBigEndianPack32(zPtr,0); | ||
56135 | zPtr += 4; | ||
56136 | /* 4 bytes: Random number used to compute page checksum. */ | ||
56137 | SyBigEndianPack32(zPtr,pPager->cksumInit); | ||
56138 | zPtr += 4; | ||
56139 | /* 8 bytes: Initial database page count. */ | ||
56140 | SyBigEndianPack64(zPtr,pPager->dbOrigSize); | ||
56141 | zPtr += 8; | ||
56142 | /* 4 bytes: Sector size used by the process that wrote this journal. */ | ||
56143 | SyBigEndianPack32(zPtr,(sxu32)pPager->iSectorSize); | ||
56144 | zPtr += 4; | ||
56145 | /* 4 bytes: Database page size. */ | ||
56146 | SyBigEndianPack32(zPtr,(sxu32)pPager->iPageSize); | ||
56147 | return UNQLITE_OK; | ||
56148 | } | ||
56149 | /* | ||
56150 | ** Parameter aData must point to a buffer of pPager->pageSize bytes | ||
56151 | ** of data. Compute and return a checksum based ont the contents of the | ||
56152 | ** page of data and the current value of pPager->cksumInit. | ||
56153 | ** | ||
56154 | ** This is not a real checksum. It is really just the sum of the | ||
56155 | ** random initial value (pPager->cksumInit) and every 200th byte | ||
56156 | ** of the page data, starting with byte offset (pPager->pageSize%200). | ||
56157 | ** Each byte is interpreted as an 8-bit unsigned integer. | ||
56158 | ** | ||
56159 | ** Changing the formula used to compute this checksum results in an | ||
56160 | ** incompatible journal file format. | ||
56161 | ** | ||
56162 | ** If journal corruption occurs due to a power failure, the most likely | ||
56163 | ** scenario is that one end or the other of the record will be changed. | ||
56164 | ** It is much less likely that the two ends of the journal record will be | ||
56165 | ** correct and the middle be corrupt. Thus, this "checksum" scheme, | ||
56166 | ** though fast and simple, catches the mostly likely kind of corruption. | ||
56167 | */ | ||
56168 | static sxu32 pager_cksum(Pager *pPager,const unsigned char *zData) | ||
56169 | { | ||
56170 | sxu32 cksum = pPager->cksumInit; /* Checksum value to return */ | ||
56171 | int i = pPager->iPageSize-200; /* Loop counter */ | ||
56172 | while( i>0 ){ | ||
56173 | cksum += zData[i]; | ||
56174 | i -= 200; | ||
56175 | } | ||
56176 | return cksum; | ||
56177 | } | ||
56178 | /* | ||
56179 | ** Read a single page from the journal file opened on file descriptor | ||
56180 | ** jfd. Playback this one page. Update the offset to read from. | ||
56181 | */ | ||
56182 | static int pager_play_back_one_page(Pager *pPager,sxi64 *pOfft,unsigned char *zTmp) | ||
56183 | { | ||
56184 | unsigned char *zData = zTmp; | ||
56185 | sxi64 iOfft; /* Offset to read from */ | ||
56186 | pgno iNum; /* Pager number */ | ||
56187 | sxu32 ckSum; /* Sanity check */ | ||
56188 | int rc; | ||
56189 | /* Offset to start reading from */ | ||
56190 | iOfft = *pOfft; | ||
56191 | /* Database page number */ | ||
56192 | rc = ReadInt64(pPager->pjfd,&iNum,iOfft); | ||
56193 | if( rc != UNQLITE_OK ){ return rc; } | ||
56194 | iOfft += 8; | ||
56195 | /* Page data */ | ||
56196 | rc = unqliteOsRead(pPager->pjfd,zData,pPager->iPageSize,iOfft); | ||
56197 | if( rc != UNQLITE_OK ){ return rc; } | ||
56198 | iOfft += pPager->iPageSize; | ||
56199 | /* Page cksum */ | ||
56200 | rc = ReadInt32(pPager->pjfd,&ckSum,iOfft); | ||
56201 | if( rc != UNQLITE_OK ){ return rc; } | ||
56202 | iOfft += 4; | ||
56203 | /* Synchronize pointers */ | ||
56204 | *pOfft = iOfft; | ||
56205 | /* Make sure we are dealing with a valid page */ | ||
56206 | if( ckSum != pager_cksum(pPager,zData) ){ | ||
56207 | /* Ignore that page */ | ||
56208 | return SXERR_IGNORE; | ||
56209 | } | ||
56210 | if( iNum >= pPager->dbSize ){ | ||
56211 | /* Ignore that page */ | ||
56212 | return UNQLITE_OK; | ||
56213 | } | ||
56214 | /* playback */ | ||
56215 | rc = unqliteOsWrite(pPager->pfd,zData,pPager->iPageSize,iNum * pPager->iPageSize); | ||
56216 | if( rc == UNQLITE_OK ){ | ||
56217 | /* Flush the cache */ | ||
56218 | pager_fill_page(pPager,iNum,zData); | ||
56219 | } | ||
56220 | return rc; | ||
56221 | } | ||
56222 | /* | ||
56223 | ** Playback the journal and thus restore the database file to | ||
56224 | ** the state it was in before we started making changes. | ||
56225 | ** | ||
56226 | ** The journal file format is as follows: | ||
56227 | ** | ||
56228 | ** (1) 8 byte prefix. A copy of aJournalMagic[]. | ||
56229 | ** (2) 4 byte big-endian integer which is the number of valid page records | ||
56230 | ** in the journal. | ||
56231 | ** (3) 4 byte big-endian integer which is the initial value for the | ||
56232 | ** sanity checksum. | ||
56233 | ** (4) 8 byte integer which is the number of pages to truncate the | ||
56234 | ** database to during a rollback. | ||
56235 | ** (5) 4 byte big-endian integer which is the sector size. The header | ||
56236 | ** is this many bytes in size. | ||
56237 | ** (6) 4 byte big-endian integer which is the page size. | ||
56238 | ** (7) zero padding out to the next sector size. | ||
56239 | ** (8) Zero or more pages instances, each as follows: | ||
56240 | ** + 4 byte page number. | ||
56241 | ** + pPager->pageSize bytes of data. | ||
56242 | ** + 4 byte checksum | ||
56243 | ** | ||
56244 | ** When we speak of the journal header, we mean the first 7 items above. | ||
56245 | ** Each entry in the journal is an instance of the 8th item. | ||
56246 | ** | ||
56247 | ** Call the value from the second bullet "nRec". nRec is the number of | ||
56248 | ** valid page entries in the journal. In most cases, you can compute the | ||
56249 | ** value of nRec from the size of the journal file. But if a power | ||
56250 | ** failure occurred while the journal was being written, it could be the | ||
56251 | ** case that the size of the journal file had already been increased but | ||
56252 | ** the extra entries had not yet made it safely to disk. In such a case, | ||
56253 | ** the value of nRec computed from the file size would be too large. For | ||
56254 | ** that reason, we always use the nRec value in the header. | ||
56255 | ** | ||
56256 | ** If the file opened as the journal file is not a well-formed | ||
56257 | ** journal file then all pages up to the first corrupted page are rolled | ||
56258 | ** back (or no pages if the journal header is corrupted). The journal file | ||
56259 | ** is then deleted and SQLITE_OK returned, just as if no corruption had | ||
56260 | ** been encountered. | ||
56261 | ** | ||
56262 | ** If an I/O or malloc() error occurs, the journal-file is not deleted | ||
56263 | ** and an error code is returned. | ||
56264 | ** | ||
56265 | */ | ||
56266 | static int pager_playback(Pager *pPager) | ||
56267 | { | ||
56268 | unsigned char *zTmp = 0; /* cc warning */ | ||
56269 | sxu32 n,nRec; | ||
56270 | sxi64 iOfft; | ||
56271 | int rc; | ||
56272 | /* Read the journal header*/ | ||
56273 | rc = pager_read_journal_header(pPager,&nRec,&pPager->dbSize); | ||
56274 | if( rc != UNQLITE_OK ){ | ||
56275 | if( rc == UNQLITE_DONE ){ | ||
56276 | goto end_playback; | ||
56277 | } | ||
56278 | unqliteGenErrorFormat(pPager->pDb,"IO error while reading journal file '%s' header",pPager->zJournal); | ||
56279 | return rc; | ||
56280 | } | ||
56281 | /* Truncate the database back to its original size */ | ||
56282 | rc = unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize); | ||
56283 | if( rc != UNQLITE_OK ){ | ||
56284 | unqliteGenError(pPager->pDb,"IO error while truncating database file"); | ||
56285 | return rc; | ||
56286 | } | ||
56287 | /* Allocate a temporary page */ | ||
56288 | zTmp = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize); | ||
56289 | if( zTmp == 0 ){ | ||
56290 | unqliteGenOutofMem(pPager->pDb); | ||
56291 | return UNQLITE_NOMEM; | ||
56292 | } | ||
56293 | SyZero((void *)zTmp,(sxu32)pPager->iPageSize); | ||
56294 | /* Copy original pages out of the journal and back into the | ||
56295 | ** database file and/or page cache. | ||
56296 | */ | ||
56297 | iOfft = pPager->iJournalOfft; | ||
56298 | for( n = 0 ; n < nRec ; ++n ){ | ||
56299 | rc = pager_play_back_one_page(pPager,&iOfft,zTmp); | ||
56300 | if( rc != UNQLITE_OK ){ | ||
56301 | if( rc != SXERR_IGNORE ){ | ||
56302 | unqliteGenError(pPager->pDb,"Page playback error"); | ||
56303 | goto end_playback; | ||
56304 | } | ||
56305 | } | ||
56306 | } | ||
56307 | end_playback: | ||
56308 | /* Release the temp page */ | ||
56309 | SyMemBackendFree(pPager->pAllocator,(void *)zTmp); | ||
56310 | if( rc == UNQLITE_OK ){ | ||
56311 | /* Sync the database file */ | ||
56312 | unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL); | ||
56313 | } | ||
56314 | if( rc == UNQLITE_DONE ){ | ||
56315 | rc = UNQLITE_OK; | ||
56316 | } | ||
56317 | /* Return to the caller */ | ||
56318 | return rc; | ||
56319 | } | ||
56320 | /* | ||
56321 | ** Unlock the database file to level eLock, which must be either NO_LOCK | ||
56322 | ** or SHARED_LOCK. Regardless of whether or not the call to xUnlock() | ||
56323 | ** succeeds, set the Pager.iLock variable to match the (attempted) new lock. | ||
56324 | ** | ||
56325 | ** Except, if Pager.iLock is set to NO_LOCK when this function is | ||
56326 | ** called, do not modify it. See the comment above the #define of | ||
56327 | ** NO_LOCK for an explanation of this. | ||
56328 | */ | ||
56329 | static int pager_unlock_db(Pager *pPager, int eLock) | ||
56330 | { | ||
56331 | int rc = UNQLITE_OK; | ||
56332 | if( pPager->iLock != NO_LOCK ){ | ||
56333 | rc = unqliteOsUnlock(pPager->pfd,eLock); | ||
56334 | pPager->iLock = eLock; | ||
56335 | } | ||
56336 | return rc; | ||
56337 | } | ||
56338 | /* | ||
56339 | ** Lock the database file to level eLock, which must be either SHARED_LOCK, | ||
56340 | ** RESERVED_LOCK or EXCLUSIVE_LOCK. If the caller is successful, set the | ||
56341 | ** Pager.eLock variable to the new locking state. | ||
56342 | ** | ||
56343 | ** Except, if Pager.eLock is set to NO_LOCK when this function is | ||
56344 | ** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. | ||
56345 | ** See the comment above the #define of NO_LOCK for an explanation | ||
56346 | ** of this. | ||
56347 | */ | ||
56348 | static int pager_lock_db(Pager *pPager, int eLock){ | ||
56349 | int rc = UNQLITE_OK; | ||
56350 | if( pPager->iLock < eLock || pPager->iLock == NO_LOCK ){ | ||
56351 | rc = unqliteOsLock(pPager->pfd, eLock); | ||
56352 | if( rc==UNQLITE_OK ){ | ||
56353 | pPager->iLock = eLock; | ||
56354 | }else{ | ||
56355 | unqliteGenError(pPager->pDb, | ||
56356 | rc == UNQLITE_BUSY ? "Another process or thread hold the requested lock" : "Error while requesting database lock" | ||
56357 | ); | ||
56358 | } | ||
56359 | } | ||
56360 | return rc; | ||
56361 | } | ||
56362 | /* | ||
56363 | ** Try to obtain a lock of type locktype on the database file. If | ||
56364 | ** a similar or greater lock is already held, this function is a no-op | ||
56365 | ** (returning UNQLITE_OK immediately). | ||
56366 | ** | ||
56367 | ** Otherwise, attempt to obtain the lock using unqliteOsLock(). Invoke | ||
56368 | ** the busy callback if the lock is currently not available. Repeat | ||
56369 | ** until the busy callback returns false or until the attempt to | ||
56370 | ** obtain the lock succeeds. | ||
56371 | ** | ||
56372 | ** Return UNQLITE_OK on success and an error code if we cannot obtain | ||
56373 | ** the lock. If the lock is obtained successfully, set the Pager.state | ||
56374 | ** variable to locktype before returning. | ||
56375 | */ | ||
56376 | static int pager_wait_on_lock(Pager *pPager, int locktype){ | ||
56377 | int rc; /* Return code */ | ||
56378 | do { | ||
56379 | rc = pager_lock_db(pPager,locktype); | ||
56380 | }while( rc==UNQLITE_BUSY && pPager->xBusyHandler && pPager->xBusyHandler(pPager->pBusyHandlerArg) ); | ||
56381 | return rc; | ||
56382 | } | ||
56383 | /* | ||
56384 | ** This function is called after transitioning from PAGER_OPEN to | ||
56385 | ** PAGER_SHARED state. It tests if there is a hot journal present in | ||
56386 | ** the file-system for the given pager. A hot journal is one that | ||
56387 | ** needs to be played back. According to this function, a hot-journal | ||
56388 | ** file exists if the following criteria are met: | ||
56389 | ** | ||
56390 | ** * The journal file exists in the file system, and | ||
56391 | ** * No process holds a RESERVED or greater lock on the database file, and | ||
56392 | ** * The database file itself is greater than 0 bytes in size, and | ||
56393 | ** * The first byte of the journal file exists and is not 0x00. | ||
56394 | ** | ||
56395 | ** If the current size of the database file is 0 but a journal file | ||
56396 | ** exists, that is probably an old journal left over from a prior | ||
56397 | ** database with the same name. In this case the journal file is | ||
56398 | ** just deleted using OsDelete, *pExists is set to 0 and UNQLITE_OK | ||
56399 | ** is returned. | ||
56400 | ** | ||
56401 | ** If a hot-journal file is found to exist, *pExists is set to 1 and | ||
56402 | ** UNQLITE_OK returned. If no hot-journal file is present, *pExists is | ||
56403 | ** set to 0 and UNQLITE_OK returned. If an IO error occurs while trying | ||
56404 | ** to determine whether or not a hot-journal file exists, the IO error | ||
56405 | ** code is returned and the value of *pExists is undefined. | ||
56406 | */ | ||
56407 | static int pager_has_hot_journal(Pager *pPager, int *pExists) | ||
56408 | { | ||
56409 | unqlite_vfs *pVfs = pPager->pVfs; | ||
56410 | int rc = UNQLITE_OK; /* Return code */ | ||
56411 | int exists = 1; /* True if a journal file is present */ | ||
56412 | |||
56413 | *pExists = 0; | ||
56414 | rc = unqliteOsAccess(pVfs, pPager->zJournal, UNQLITE_ACCESS_EXISTS, &exists); | ||
56415 | if( rc==UNQLITE_OK && exists ){ | ||
56416 | int locked = 0; /* True if some process holds a RESERVED lock */ | ||
56417 | |||
56418 | /* Race condition here: Another process might have been holding the | ||
56419 | ** the RESERVED lock and have a journal open at the unqliteOsAccess() | ||
56420 | ** call above, but then delete the journal and drop the lock before | ||
56421 | ** we get to the following unqliteOsCheckReservedLock() call. If that | ||
56422 | ** is the case, this routine might think there is a hot journal when | ||
56423 | ** in fact there is none. This results in a false-positive which will | ||
56424 | ** be dealt with by the playback routine. | ||
56425 | */ | ||
56426 | rc = unqliteOsCheckReservedLock(pPager->pfd, &locked); | ||
56427 | if( rc==UNQLITE_OK && !locked ){ | ||
56428 | sxi64 n = 0; /* Size of db file in bytes */ | ||
56429 | |||
56430 | /* Check the size of the database file. If it consists of 0 pages, | ||
56431 | ** then delete the journal file. See the header comment above for | ||
56432 | ** the reasoning here. Delete the obsolete journal file under | ||
56433 | ** a RESERVED lock to avoid race conditions. | ||
56434 | */ | ||
56435 | rc = unqliteOsFileSize(pPager->pfd,&n); | ||
56436 | if( rc==UNQLITE_OK ){ | ||
56437 | if( n < 1 ){ | ||
56438 | if( pager_lock_db(pPager, RESERVED_LOCK)==UNQLITE_OK ){ | ||
56439 | unqliteOsDelete(pVfs, pPager->zJournal, 0); | ||
56440 | pager_unlock_db(pPager, SHARED_LOCK); | ||
56441 | } | ||
56442 | }else{ | ||
56443 | /* The journal file exists and no other connection has a reserved | ||
56444 | ** or greater lock on the database file. */ | ||
56445 | *pExists = 1; | ||
56446 | } | ||
56447 | } | ||
56448 | } | ||
56449 | } | ||
56450 | return rc; | ||
56451 | } | ||
56452 | /* | ||
56453 | * Rollback a journal file. (See block-comment above). | ||
56454 | */ | ||
56455 | static int pager_journal_rollback(Pager *pPager,int check_hot) | ||
56456 | { | ||
56457 | int rc; | ||
56458 | if( check_hot ){ | ||
56459 | int iExists = 0; /* cc warning */ | ||
56460 | /* Check if the journal file exists */ | ||
56461 | rc = pager_has_hot_journal(pPager,&iExists); | ||
56462 | if( rc != UNQLITE_OK ){ | ||
56463 | /* IO error */ | ||
56464 | return rc; | ||
56465 | } | ||
56466 | if( !iExists ){ | ||
56467 | /* Journal file does not exists */ | ||
56468 | return UNQLITE_OK; | ||
56469 | } | ||
56470 | } | ||
56471 | if( pPager->is_rdonly ){ | ||
56472 | unqliteGenErrorFormat(pPager->pDb, | ||
56473 | "Cannot rollback journal file '%s' due to a read-only database handle",pPager->zJournal); | ||
56474 | return UNQLITE_READ_ONLY; | ||
56475 | } | ||
56476 | /* Get an EXCLUSIVE lock on the database file. At this point it is | ||
56477 | ** important that a RESERVED lock is not obtained on the way to the | ||
56478 | ** EXCLUSIVE lock. If it were, another process might open the | ||
56479 | ** database file, detect the RESERVED lock, and conclude that the | ||
56480 | ** database is safe to read while this process is still rolling the | ||
56481 | ** hot-journal back. | ||
56482 | ** | ||
56483 | ** Because the intermediate RESERVED lock is not requested, any | ||
56484 | ** other process attempting to access the database file will get to | ||
56485 | ** this point in the code and fail to obtain its own EXCLUSIVE lock | ||
56486 | ** on the database file. | ||
56487 | ** | ||
56488 | ** Unless the pager is in locking_mode=exclusive mode, the lock is | ||
56489 | ** downgraded to SHARED_LOCK before this function returns. | ||
56490 | */ | ||
56491 | /* Open the journal file */ | ||
56492 | rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal,&pPager->pjfd,UNQLITE_OPEN_READWRITE); | ||
56493 | if( rc != UNQLITE_OK ){ | ||
56494 | unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: '%s'",pPager->zJournal); | ||
56495 | goto fail; | ||
56496 | } | ||
56497 | rc = pager_lock_db(pPager,EXCLUSIVE_LOCK); | ||
56498 | if( rc != UNQLITE_OK ){ | ||
56499 | unqliteGenError(pPager->pDb,"Cannot acquire an exclusive lock on the database while journal rollback"); | ||
56500 | goto fail; | ||
56501 | } | ||
56502 | /* Sync the journal file */ | ||
56503 | unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); | ||
56504 | /* Finally rollback the database */ | ||
56505 | rc = pager_playback(pPager); | ||
56506 | /* Switch back to shared lock */ | ||
56507 | pager_unlock_db(pPager,SHARED_LOCK); | ||
56508 | fail: | ||
56509 | /* Close the journal handle */ | ||
56510 | unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); | ||
56511 | pPager->pjfd = 0; | ||
56512 | if( rc == UNQLITE_OK ){ | ||
56513 | /* Delete the journal file */ | ||
56514 | unqliteOsDelete(pPager->pVfs,pPager->zJournal,TRUE); | ||
56515 | } | ||
56516 | return rc; | ||
56517 | } | ||
56518 | /* | ||
56519 | * Write the unqlite header (First page). (Big-Endian) | ||
56520 | */ | ||
56521 | static int pager_write_db_header(Pager *pPager) | ||
56522 | { | ||
56523 | unsigned char *zRaw = pPager->pHeader->zData; | ||
56524 | unqlite_kv_engine *pEngine = pPager->pEngine; | ||
56525 | sxu32 nDos; | ||
56526 | sxu16 nLen; | ||
56527 | /* Database signature */ | ||
56528 | SyMemcpy(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1); | ||
56529 | zRaw += sizeof(UNQLITE_DB_SIG)-1; | ||
56530 | /* Database magic number */ | ||
56531 | SyBigEndianPack32(zRaw,UNQLITE_DB_MAGIC); | ||
56532 | zRaw += 4; /* 4 byte magic number */ | ||
56533 | /* Database creation time */ | ||
56534 | SyZero(&pPager->tmCreate,sizeof(Sytm)); | ||
56535 | if( pPager->pVfs->xCurrentTime ){ | ||
56536 | pPager->pVfs->xCurrentTime(pPager->pVfs,&pPager->tmCreate); | ||
56537 | } | ||
56538 | /* DOS time format (4 bytes) */ | ||
56539 | SyTimeFormatToDos(&pPager->tmCreate,&nDos); | ||
56540 | SyBigEndianPack32(zRaw,nDos); | ||
56541 | zRaw += 4; /* 4 byte DOS time */ | ||
56542 | /* Sector size */ | ||
56543 | SyBigEndianPack32(zRaw,(sxu32)pPager->iSectorSize); | ||
56544 | zRaw += 4; /* 4 byte sector size */ | ||
56545 | /* Page size */ | ||
56546 | SyBigEndianPack32(zRaw,(sxu32)pPager->iPageSize); | ||
56547 | zRaw += 4; /* 4 byte page size */ | ||
56548 | /* Key value storage engine */ | ||
56549 | nLen = (sxu16)SyStrlen(pEngine->pIo->pMethods->zName); | ||
56550 | SyBigEndianPack16(zRaw,nLen); /* 2 byte storage engine name */ | ||
56551 | zRaw += 2; | ||
56552 | SyMemcpy((const void *)pEngine->pIo->pMethods->zName,(void *)zRaw,nLen); | ||
56553 | zRaw += nLen; | ||
56554 | /* All rest are meta-data available to the host application */ | ||
56555 | return UNQLITE_OK; | ||
56556 | } | ||
56557 | /* | ||
56558 | * Read the unqlite header (first page). (Big-Endian) | ||
56559 | */ | ||
56560 | static int pager_extract_header(Pager *pPager,const unsigned char *zRaw,sxu32 nByte) | ||
56561 | { | ||
56562 | const unsigned char *zEnd = &zRaw[nByte]; | ||
56563 | sxu32 nDos,iMagic; | ||
56564 | sxu16 nLen; | ||
56565 | char *zKv; | ||
56566 | /* Database signature */ | ||
56567 | if( SyMemcmp(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1) != 0 ){ | ||
56568 | /* Corrupt database */ | ||
56569 | return UNQLITE_CORRUPT; | ||
56570 | } | ||
56571 | zRaw += sizeof(UNQLITE_DB_SIG)-1; | ||
56572 | /* Database magic number */ | ||
56573 | SyBigEndianUnpack32(zRaw,&iMagic); | ||
56574 | zRaw += 4; /* 4 byte magic number */ | ||
56575 | if( iMagic != UNQLITE_DB_MAGIC ){ | ||
56576 | /* Corrupt database */ | ||
56577 | return UNQLITE_CORRUPT; | ||
56578 | } | ||
56579 | /* Database creation time */ | ||
56580 | SyBigEndianUnpack32(zRaw,&nDos); | ||
56581 | zRaw += 4; /* 4 byte DOS time format */ | ||
56582 | SyDosTimeFormat(nDos,&pPager->tmCreate); | ||
56583 | /* Sector size */ | ||
56584 | SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iSectorSize); | ||
56585 | zRaw += 4; /* 4 byte sector size */ | ||
56586 | /* Page size */ | ||
56587 | SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iPageSize); | ||
56588 | zRaw += 4; /* 4 byte page size */ | ||
56589 | /* Check that the values read from the page-size and sector-size fields | ||
56590 | ** are within range. To be 'in range', both values need to be a power | ||
56591 | ** of two greater than or equal to 512 or 32, and not greater than their | ||
56592 | ** respective compile time maximum limits. | ||
56593 | */ | ||
56594 | if( pPager->iPageSize<UNQLITE_MIN_PAGE_SIZE || pPager->iSectorSize<32 | ||
56595 | || pPager->iPageSize>UNQLITE_MAX_PAGE_SIZE || pPager->iSectorSize>MAX_SECTOR_SIZE | ||
56596 | || ((pPager->iPageSize<-1)&pPager->iPageSize)!=0 || ((pPager->iSectorSize-1)&pPager->iSectorSize)!=0 | ||
56597 | ){ | ||
56598 | return UNQLITE_CORRUPT; | ||
56599 | } | ||
56600 | /* Key value storage engine */ | ||
56601 | SyBigEndianUnpack16(zRaw,&nLen); /* 2 byte storage engine length */ | ||
56602 | zRaw += 2; | ||
56603 | if( nLen > (sxu16)(zEnd - zRaw) ){ | ||
56604 | nLen = (sxu16)(zEnd - zRaw); | ||
56605 | } | ||
56606 | zKv = (char *)SyMemBackendDup(pPager->pAllocator,(const char *)zRaw,nLen); | ||
56607 | if( zKv == 0 ){ | ||
56608 | return UNQLITE_NOMEM; | ||
56609 | } | ||
56610 | SyStringInitFromBuf(&pPager->sKv,zKv,nLen); | ||
56611 | return UNQLITE_OK; | ||
56612 | } | ||
56613 | /* | ||
56614 | * Read the database header. | ||
56615 | */ | ||
56616 | static int pager_read_db_header(Pager *pPager) | ||
56617 | { | ||
56618 | unsigned char zRaw[UNQLITE_MIN_PAGE_SIZE]; /* Minimum page size */ | ||
56619 | sxi64 n = 0; /* Size of db file in bytes */ | ||
56620 | int rc; | ||
56621 | /* Get the file size first */ | ||
56622 | rc = unqliteOsFileSize(pPager->pfd,&n); | ||
56623 | if( rc != UNQLITE_OK ){ | ||
56624 | return rc; | ||
56625 | } | ||
56626 | pPager->dbByteSize = n; | ||
56627 | if( n > 0 ){ | ||
56628 | unqlite_kv_methods *pMethods; | ||
56629 | SyString *pKv; | ||
56630 | pgno nPage; | ||
56631 | if( n < UNQLITE_MIN_PAGE_SIZE ){ | ||
56632 | /* A valid unqlite database must be at least 512 bytes long */ | ||
56633 | unqliteGenError(pPager->pDb,"Malformed database image"); | ||
56634 | return UNQLITE_CORRUPT; | ||
56635 | } | ||
56636 | /* Read the database header */ | ||
56637 | rc = unqliteOsRead(pPager->pfd,zRaw,sizeof(zRaw),0); | ||
56638 | if( rc != UNQLITE_OK ){ | ||
56639 | unqliteGenError(pPager->pDb,"IO error while reading database header"); | ||
56640 | return rc; | ||
56641 | } | ||
56642 | /* Extract the header */ | ||
56643 | rc = pager_extract_header(pPager,zRaw,sizeof(zRaw)); | ||
56644 | if( rc != UNQLITE_OK ){ | ||
56645 | unqliteGenError(pPager->pDb,rc == UNQLITE_NOMEM ? "Unqlite is running out of memory" : "Malformed database image"); | ||
56646 | return rc; | ||
56647 | } | ||
56648 | /* Update pager state */ | ||
56649 | nPage = (pgno)(n / pPager->iPageSize); | ||
56650 | if( nPage==0 && n>0 ){ | ||
56651 | nPage = 1; | ||
56652 | } | ||
56653 | pPager->dbSize = nPage; | ||
56654 | /* Laod the target Key/Value storage engine */ | ||
56655 | pKv = &pPager->sKv; | ||
56656 | pMethods = unqliteFindKVStore(pKv->zString,pKv->nByte); | ||
56657 | if( pMethods == 0 ){ | ||
56658 | unqliteGenErrorFormat(pPager->pDb,"No such Key/Value storage engine '%z'",pKv); | ||
56659 | return UNQLITE_NOTIMPLEMENTED; | ||
56660 | } | ||
56661 | /* Install the new KV storage engine */ | ||
56662 | rc = unqlitePagerRegisterKvEngine(pPager,pMethods); | ||
56663 | if( rc != UNQLITE_OK ){ | ||
56664 | return rc; | ||
56665 | } | ||
56666 | }else{ | ||
56667 | /* Set a default page and sector size */ | ||
56668 | pPager->iSectorSize = GetSectorSize(pPager->pfd); | ||
56669 | pPager->iPageSize = unqliteGetPageSize(); | ||
56670 | SyStringInitFromBuf(&pPager->sKv,pPager->pEngine->pIo->pMethods->zName,SyStrlen(pPager->pEngine->pIo->pMethods->zName)); | ||
56671 | pPager->dbSize = 0; | ||
56672 | } | ||
56673 | /* Allocate a temporary page size */ | ||
56674 | pPager->zTmpPage = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize); | ||
56675 | if( pPager->zTmpPage == 0 ){ | ||
56676 | unqliteGenOutofMem(pPager->pDb); | ||
56677 | return UNQLITE_NOMEM; | ||
56678 | } | ||
56679 | SyZero(pPager->zTmpPage,(sxu32)pPager->iPageSize); | ||
56680 | return UNQLITE_OK; | ||
56681 | } | ||
56682 | /* | ||
56683 | * Write the database header. | ||
56684 | */ | ||
56685 | static int pager_create_header(Pager *pPager) | ||
56686 | { | ||
56687 | Page *pHeader; | ||
56688 | int rc; | ||
56689 | /* Allocate a new page */ | ||
56690 | pHeader = pager_alloc_page(pPager,0); | ||
56691 | if( pHeader == 0 ){ | ||
56692 | return UNQLITE_NOMEM; | ||
56693 | } | ||
56694 | pPager->pHeader = pHeader; | ||
56695 | /* Link the page */ | ||
56696 | pager_link_page(pPager,pHeader); | ||
56697 | /* Add to the dirty list */ | ||
56698 | pager_page_to_dirty_list(pPager,pHeader); | ||
56699 | /* Write the database header */ | ||
56700 | rc = pager_write_db_header(pPager); | ||
56701 | return rc; | ||
56702 | } | ||
56703 | /* | ||
56704 | ** This function is called to obtain a shared lock on the database file. | ||
56705 | ** It is illegal to call unqlitePagerAcquire() until after this function | ||
56706 | ** has been successfully called. If a shared-lock is already held when | ||
56707 | ** this function is called, it is a no-op. | ||
56708 | ** | ||
56709 | ** The following operations are also performed by this function. | ||
56710 | ** | ||
56711 | ** 1) If the pager is currently in PAGER_OPEN state (no lock held | ||
56712 | ** on the database file), then an attempt is made to obtain a | ||
56713 | ** SHARED lock on the database file. Immediately after obtaining | ||
56714 | ** the SHARED lock, the file-system is checked for a hot-journal, | ||
56715 | ** which is played back if present. | ||
56716 | ** | ||
56717 | ** If everything is successful, UNQLITE_OK is returned. If an IO error | ||
56718 | ** occurs while locking the database, checking for a hot-journal file or | ||
56719 | ** rolling back a journal file, the IO error code is returned. | ||
56720 | */ | ||
56721 | static int pager_shared_lock(Pager *pPager) | ||
56722 | { | ||
56723 | int rc = UNQLITE_OK; | ||
56724 | if( pPager->iState == PAGER_OPEN ){ | ||
56725 | unqlite_kv_methods *pMethods; | ||
56726 | /* Open the target database */ | ||
56727 | rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zFilename,&pPager->pfd,pPager->iOpenFlags); | ||
56728 | if( rc != UNQLITE_OK ){ | ||
56729 | unqliteGenErrorFormat(pPager->pDb, | ||
56730 | "IO error while opening the target database file: %s",pPager->zFilename | ||
56731 | ); | ||
56732 | return rc; | ||
56733 | } | ||
56734 | /* Try to obtain a shared lock */ | ||
56735 | rc = pager_wait_on_lock(pPager,SHARED_LOCK); | ||
56736 | if( rc == UNQLITE_OK ){ | ||
56737 | if( pPager->iLock <= SHARED_LOCK ){ | ||
56738 | /* Rollback any hot journal */ | ||
56739 | rc = pager_journal_rollback(pPager,1); | ||
56740 | if( rc != UNQLITE_OK ){ | ||
56741 | return rc; | ||
56742 | } | ||
56743 | } | ||
56744 | /* Read the database header */ | ||
56745 | rc = pager_read_db_header(pPager); | ||
56746 | if( rc != UNQLITE_OK ){ | ||
56747 | return rc; | ||
56748 | } | ||
56749 | if(pPager->dbSize > 0 ){ | ||
56750 | if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){ | ||
56751 | const jx9_vfs *pVfs = jx9ExportBuiltinVfs(); | ||
56752 | /* Obtain a read-only memory view of the whole file */ | ||
56753 | if( pVfs && pVfs->xMmap ){ | ||
56754 | int vr; | ||
56755 | vr = pVfs->xMmap(pPager->zFilename,&pPager->pMmap,&pPager->dbByteSize); | ||
56756 | if( vr != JX9_OK ){ | ||
56757 | /* Generate a warning */ | ||
56758 | unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database"); | ||
56759 | pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP; | ||
56760 | } | ||
56761 | }else{ | ||
56762 | /* Generate a warning */ | ||
56763 | unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database"); | ||
56764 | pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP; | ||
56765 | } | ||
56766 | } | ||
56767 | } | ||
56768 | /* Update the pager state */ | ||
56769 | pPager->iState = PAGER_READER; | ||
56770 | /* Invoke the xOpen methods if available */ | ||
56771 | pMethods = pPager->pEngine->pIo->pMethods; | ||
56772 | if( pMethods->xOpen ){ | ||
56773 | rc = pMethods->xOpen(pPager->pEngine,pPager->dbSize); | ||
56774 | if( rc != UNQLITE_OK ){ | ||
56775 | unqliteGenErrorFormat(pPager->pDb, | ||
56776 | "xOpen() method of the underlying KV engine '%z' failed", | ||
56777 | &pPager->sKv | ||
56778 | ); | ||
56779 | pager_unlock_db(pPager,NO_LOCK); | ||
56780 | pPager->iState = PAGER_OPEN; | ||
56781 | return rc; | ||
56782 | } | ||
56783 | } | ||
56784 | }else if( rc == UNQLITE_BUSY ){ | ||
56785 | unqliteGenError(pPager->pDb,"Another process or thread have a reserved or exclusive lock on this database"); | ||
56786 | } | ||
56787 | } | ||
56788 | return rc; | ||
56789 | } | ||
56790 | /* | ||
56791 | ** Begin a write-transaction on the specified pager object. If a | ||
56792 | ** write-transaction has already been opened, this function is a no-op. | ||
56793 | */ | ||
56794 | UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager) | ||
56795 | { | ||
56796 | int rc; | ||
56797 | /* Obtain a shared lock on the database first */ | ||
56798 | rc = pager_shared_lock(pPager); | ||
56799 | if( rc != UNQLITE_OK ){ | ||
56800 | return rc; | ||
56801 | } | ||
56802 | if( pPager->iState >= PAGER_WRITER_LOCKED ){ | ||
56803 | return UNQLITE_OK; | ||
56804 | } | ||
56805 | if( pPager->is_rdonly ){ | ||
56806 | unqliteGenError(pPager->pDb,"Read-only database"); | ||
56807 | /* Read only database */ | ||
56808 | return UNQLITE_READ_ONLY; | ||
56809 | } | ||
56810 | /* Obtain a reserved lock on the database */ | ||
56811 | rc = pager_wait_on_lock(pPager,RESERVED_LOCK); | ||
56812 | if( rc == UNQLITE_OK ){ | ||
56813 | /* Create the bitvec */ | ||
56814 | pPager->pVec = unqliteBitvecCreate(pPager->pAllocator,pPager->dbSize); | ||
56815 | if( pPager->pVec == 0 ){ | ||
56816 | unqliteGenOutofMem(pPager->pDb); | ||
56817 | rc = UNQLITE_NOMEM; | ||
56818 | goto fail; | ||
56819 | } | ||
56820 | /* Change to the WRITER_LOCK state */ | ||
56821 | pPager->iState = PAGER_WRITER_LOCKED; | ||
56822 | pPager->dbOrigSize = pPager->dbSize; | ||
56823 | pPager->iJournalOfft = 0; | ||
56824 | pPager->nRec = 0; | ||
56825 | if( pPager->dbSize < 1 ){ | ||
56826 | /* Write the database header */ | ||
56827 | rc = pager_create_header(pPager); | ||
56828 | if( rc != UNQLITE_OK ){ | ||
56829 | goto fail; | ||
56830 | } | ||
56831 | pPager->dbSize = 1; | ||
56832 | } | ||
56833 | }else if( rc == UNQLITE_BUSY ){ | ||
56834 | unqliteGenError(pPager->pDb,"Another process or thread have a reserved lock on this database"); | ||
56835 | } | ||
56836 | return rc; | ||
56837 | fail: | ||
56838 | /* Downgrade to shared lock */ | ||
56839 | pager_unlock_db(pPager,SHARED_LOCK); | ||
56840 | return rc; | ||
56841 | } | ||
56842 | /* | ||
56843 | ** This function is called at the start of every write transaction. | ||
56844 | ** There must already be a RESERVED or EXCLUSIVE lock on the database | ||
56845 | ** file when this routine is called. | ||
56846 | ** | ||
56847 | */ | ||
56848 | static int unqliteOpenJournal(Pager *pPager) | ||
56849 | { | ||
56850 | unsigned char *zHeader; | ||
56851 | int rc = UNQLITE_OK; | ||
56852 | if( pPager->is_mem || pPager->no_jrnl ){ | ||
56853 | /* Journaling is omitted for this database */ | ||
56854 | goto finish; | ||
56855 | } | ||
56856 | if( pPager->iState >= PAGER_WRITER_CACHEMOD ){ | ||
56857 | /* Already opened */ | ||
56858 | return UNQLITE_OK; | ||
56859 | } | ||
56860 | /* Delete any previously journal with the same name */ | ||
56861 | unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); | ||
56862 | /* Open the journal file */ | ||
56863 | rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal, | ||
56864 | &pPager->pjfd,UNQLITE_OPEN_CREATE|UNQLITE_OPEN_READWRITE); | ||
56865 | if( rc != UNQLITE_OK ){ | ||
56866 | unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: %s",pPager->zJournal); | ||
56867 | return rc; | ||
56868 | } | ||
56869 | /* Write the journal header */ | ||
56870 | zHeader = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iSectorSize); | ||
56871 | if( zHeader == 0 ){ | ||
56872 | rc = UNQLITE_NOMEM; | ||
56873 | goto fail; | ||
56874 | } | ||
56875 | pager_write_journal_header(pPager,zHeader); | ||
56876 | /* Perform the disk write */ | ||
56877 | rc = unqliteOsWrite(pPager->pjfd,zHeader,pPager->iSectorSize,0); | ||
56878 | /* Offset to start writing from */ | ||
56879 | pPager->iJournalOfft = pPager->iSectorSize; | ||
56880 | /* All done, journal will be synced later */ | ||
56881 | SyMemBackendFree(pPager->pAllocator,zHeader); | ||
56882 | finish: | ||
56883 | if( rc == UNQLITE_OK ){ | ||
56884 | pPager->iState = PAGER_WRITER_CACHEMOD; | ||
56885 | return UNQLITE_OK; | ||
56886 | } | ||
56887 | fail: | ||
56888 | /* Unlink the journal file if something goes wrong */ | ||
56889 | unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); | ||
56890 | unqliteOsDelete(pPager->pVfs,pPager->zJournal,0); | ||
56891 | pPager->pjfd = 0; | ||
56892 | return rc; | ||
56893 | } | ||
56894 | /* | ||
56895 | ** Sync the journal. In other words, make sure all the pages that have | ||
56896 | ** been written to the journal have actually reached the surface of the | ||
56897 | ** disk and can be restored in the event of a hot-journal rollback. | ||
56898 | * | ||
56899 | * This routine try also to obtain an exlusive lock on the database. | ||
56900 | */ | ||
56901 | static int unqliteFinalizeJournal(Pager *pPager,int *pRetry,int close_jrnl) | ||
56902 | { | ||
56903 | int rc; | ||
56904 | *pRetry = 0; | ||
56905 | /* Grab the exclusive lock first */ | ||
56906 | rc = pager_lock_db(pPager,EXCLUSIVE_LOCK); | ||
56907 | if( rc != UNQLITE_OK ){ | ||
56908 | /* Retry the excusive lock process */ | ||
56909 | *pRetry = 1; | ||
56910 | rc = UNQLITE_OK; | ||
56911 | } | ||
56912 | if( pPager->no_jrnl ){ | ||
56913 | /* Journaling is omitted, return immediately */ | ||
56914 | return UNQLITE_OK; | ||
56915 | } | ||
56916 | /* Write the total number of database records */ | ||
56917 | rc = WriteInt32(pPager->pjfd,pPager->nRec,8 /* sizeof(aJournalRec) */); | ||
56918 | if( rc != UNQLITE_OK ){ | ||
56919 | if( pPager->nRec > 0 ){ | ||
56920 | return rc; | ||
56921 | }else{ | ||
56922 | /* Not so fatal */ | ||
56923 | rc = UNQLITE_OK; | ||
56924 | } | ||
56925 | } | ||
56926 | /* Sync the journal and close it */ | ||
56927 | rc = unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); | ||
56928 | if( close_jrnl ){ | ||
56929 | /* close the journal file */ | ||
56930 | if( UNQLITE_OK != unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd) ){ | ||
56931 | if( rc != UNQLITE_OK /* unqliteOsSync */ ){ | ||
56932 | return rc; | ||
56933 | } | ||
56934 | } | ||
56935 | pPager->pjfd = 0; | ||
56936 | } | ||
56937 | if( (*pRetry) == 1 ){ | ||
56938 | if( pager_lock_db(pPager,EXCLUSIVE_LOCK) == UNQLITE_OK ){ | ||
56939 | /* Got exclusive lock */ | ||
56940 | *pRetry = 0; | ||
56941 | } | ||
56942 | } | ||
56943 | return UNQLITE_OK; | ||
56944 | } | ||
56945 | /* | ||
56946 | * Mark a single data page as writeable. The page is written into the | ||
56947 | * main journal as required. | ||
56948 | */ | ||
56949 | static int page_write(Pager *pPager,Page *pPage) | ||
56950 | { | ||
56951 | int rc; | ||
56952 | if( !pPager->is_mem && !pPager->no_jrnl ){ | ||
56953 | /* Write the page to the transaction journal */ | ||
56954 | if( pPage->pgno < pPager->dbOrigSize && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){ | ||
56955 | sxu32 cksum; | ||
56956 | if( pPager->nRec == SXU32_HIGH ){ | ||
56957 | /* Journal Limit reached */ | ||
56958 | unqliteGenError(pPager->pDb,"Journal record limit reached, commit your changes"); | ||
56959 | return UNQLITE_LIMIT; | ||
56960 | } | ||
56961 | /* Write the page number */ | ||
56962 | rc = WriteInt64(pPager->pjfd,pPage->pgno,pPager->iJournalOfft); | ||
56963 | if( rc != UNQLITE_OK ){ return rc; } | ||
56964 | /* Write the raw page */ | ||
56965 | /** CODEC */ | ||
56966 | rc = unqliteOsWrite(pPager->pjfd,pPage->zData,pPager->iPageSize,pPager->iJournalOfft + 8); | ||
56967 | if( rc != UNQLITE_OK ){ return rc; } | ||
56968 | /* Compute the checksum */ | ||
56969 | cksum = pager_cksum(pPager,pPage->zData); | ||
56970 | rc = WriteInt32(pPager->pjfd,cksum,pPager->iJournalOfft + 8 + pPager->iPageSize); | ||
56971 | if( rc != UNQLITE_OK ){ return rc; } | ||
56972 | /* Update the journal offset */ | ||
56973 | pPager->iJournalOfft += 8 /* page num */ + pPager->iPageSize + 4 /* cksum */; | ||
56974 | pPager->nRec++; | ||
56975 | /* Mark as journalled */ | ||
56976 | unqliteBitvecSet(pPager->pVec,pPage->pgno); | ||
56977 | } | ||
56978 | } | ||
56979 | /* Add the page to the dirty list */ | ||
56980 | pager_page_to_dirty_list(pPager,pPage); | ||
56981 | /* Update the database size and return. */ | ||
56982 | if( (1 + pPage->pgno) > pPager->dbSize ){ | ||
56983 | pPager->dbSize = 1 + pPage->pgno; | ||
56984 | if( pPager->dbSize == SXU64_HIGH ){ | ||
56985 | unqliteGenError(pPager->pDb,"Database maximum page limit (64-bit) reached"); | ||
56986 | return UNQLITE_LIMIT; | ||
56987 | } | ||
56988 | } | ||
56989 | return UNQLITE_OK; | ||
56990 | } | ||
56991 | /* | ||
56992 | ** The argument is the first in a linked list of dirty pages connected | ||
56993 | ** by the PgHdr.pDirty pointer. This function writes each one of the | ||
56994 | ** in-memory pages in the list to the database file. The argument may | ||
56995 | ** be NULL, representing an empty list. In this case this function is | ||
56996 | ** a no-op. | ||
56997 | ** | ||
56998 | ** The pager must hold at least a RESERVED lock when this function | ||
56999 | ** is called. Before writing anything to the database file, this lock | ||
57000 | ** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, | ||
57001 | ** UNQLITE_BUSY is returned and no data is written to the database file. | ||
57002 | */ | ||
57003 | static int pager_write_dirty_pages(Pager *pPager,Page *pDirty) | ||
57004 | { | ||
57005 | int rc = UNQLITE_OK; | ||
57006 | Page *pNext; | ||
57007 | for(;;){ | ||
57008 | if( pDirty == 0 ){ | ||
57009 | break; | ||
57010 | } | ||
57011 | /* Point to the next dirty page */ | ||
57012 | pNext = pDirty->pDirtyPrev; /* Not a bug: Reverse link */ | ||
57013 | if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){ | ||
57014 | rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize); | ||
57015 | if( rc != UNQLITE_OK ){ | ||
57016 | /* A rollback should be done */ | ||
57017 | break; | ||
57018 | } | ||
57019 | } | ||
57020 | /* Remove stale flags */ | ||
57021 | pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); | ||
57022 | if( pDirty->nRef < 1 ){ | ||
57023 | /* Unlink the page now it is unused */ | ||
57024 | pager_unlink_page(pPager,pDirty); | ||
57025 | /* Release the page */ | ||
57026 | pager_release_page(pPager,pDirty); | ||
57027 | } | ||
57028 | /* Point to the next page */ | ||
57029 | pDirty = pNext; | ||
57030 | } | ||
57031 | pPager->pDirty = pPager->pFirstDirty = 0; | ||
57032 | pPager->pHotDirty = pPager->pFirstHot = 0; | ||
57033 | pPager->nHot = 0; | ||
57034 | return rc; | ||
57035 | } | ||
57036 | /* | ||
57037 | ** The argument is the first in a linked list of hot dirty pages connected | ||
57038 | ** by the PgHdr.pHotDirty pointer. This function writes each one of the | ||
57039 | ** in-memory pages in the list to the database file. The argument may | ||
57040 | ** be NULL, representing an empty list. In this case this function is | ||
57041 | ** a no-op. | ||
57042 | ** | ||
57043 | ** The pager must hold at least a RESERVED lock when this function | ||
57044 | ** is called. Before writing anything to the database file, this lock | ||
57045 | ** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, | ||
57046 | ** UNQLITE_BUSY is returned and no data is written to the database file. | ||
57047 | */ | ||
57048 | static int pager_write_hot_dirty_pages(Pager *pPager,Page *pDirty) | ||
57049 | { | ||
57050 | int rc = UNQLITE_OK; | ||
57051 | Page *pNext; | ||
57052 | for(;;){ | ||
57053 | if( pDirty == 0 ){ | ||
57054 | break; | ||
57055 | } | ||
57056 | /* Point to the next page */ | ||
57057 | pNext = pDirty->pPrevHot; /* Not a bug: Reverse link */ | ||
57058 | if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){ | ||
57059 | rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize); | ||
57060 | if( rc != UNQLITE_OK ){ | ||
57061 | break; | ||
57062 | } | ||
57063 | } | ||
57064 | /* Remove stale flags */ | ||
57065 | pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); | ||
57066 | /* Unlink from the list of dirty pages */ | ||
57067 | if( pDirty->pDirtyPrev ){ | ||
57068 | pDirty->pDirtyPrev->pDirtyNext = pDirty->pDirtyNext; | ||
57069 | }else{ | ||
57070 | pPager->pDirty = pDirty->pDirtyNext; | ||
57071 | } | ||
57072 | if( pDirty->pDirtyNext ){ | ||
57073 | pDirty->pDirtyNext->pDirtyPrev = pDirty->pDirtyPrev; | ||
57074 | }else{ | ||
57075 | pPager->pFirstDirty = pDirty->pDirtyPrev; | ||
57076 | } | ||
57077 | /* Discard */ | ||
57078 | pager_unlink_page(pPager,pDirty); | ||
57079 | /* Release the page */ | ||
57080 | pager_release_page(pPager,pDirty); | ||
57081 | /* Next hot page */ | ||
57082 | pDirty = pNext; | ||
57083 | } | ||
57084 | return rc; | ||
57085 | } | ||
57086 | /* | ||
57087 | * Commit a transaction: Phase one. | ||
57088 | */ | ||
57089 | static int pager_commit_phase1(Pager *pPager) | ||
57090 | { | ||
57091 | int get_excl = 0; | ||
57092 | Page *pDirty; | ||
57093 | int rc; | ||
57094 | /* If no database changes have been made, return early. */ | ||
57095 | if( pPager->iState < PAGER_WRITER_CACHEMOD ){ | ||
57096 | return UNQLITE_OK; | ||
57097 | } | ||
57098 | if( pPager->is_mem ){ | ||
57099 | /* An in-memory database */ | ||
57100 | return UNQLITE_OK; | ||
57101 | } | ||
57102 | if( pPager->is_rdonly ){ | ||
57103 | /* Read-Only DB */ | ||
57104 | unqliteGenError(pPager->pDb,"Read-Only database"); | ||
57105 | return UNQLITE_READ_ONLY; | ||
57106 | } | ||
57107 | /* Finalize the journal file */ | ||
57108 | rc = unqliteFinalizeJournal(pPager,&get_excl,1); | ||
57109 | if( rc != UNQLITE_OK ){ | ||
57110 | return rc; | ||
57111 | } | ||
57112 | /* Get the dirty pages */ | ||
57113 | pDirty = pager_get_dirty_pages(pPager); | ||
57114 | if( get_excl ){ | ||
57115 | /* Wait one last time for the exclusive lock */ | ||
57116 | rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK); | ||
57117 | if( rc != UNQLITE_OK ){ | ||
57118 | unqliteGenError(pPager->pDb,"Cannot obtain an Exclusive lock on the target database"); | ||
57119 | return rc; | ||
57120 | } | ||
57121 | } | ||
57122 | if( pPager->iFlags & PAGER_CTRL_DIRTY_COMMIT ){ | ||
57123 | /* Synce the database first if a dirty commit have been applied */ | ||
57124 | unqliteOsSync(pPager->pfd,UNQLITE_SYNC_NORMAL); | ||
57125 | } | ||
57126 | /* Write the dirty pages */ | ||
57127 | rc = pager_write_dirty_pages(pPager,pDirty); | ||
57128 | if( rc != UNQLITE_OK ){ | ||
57129 | /* Rollback your DB */ | ||
57130 | pPager->iFlags |= PAGER_CTRL_COMMIT_ERR; | ||
57131 | pPager->pFirstDirty = pDirty; | ||
57132 | unqliteGenError(pPager->pDb,"IO error while writing dirty pages, rollback your database"); | ||
57133 | return rc; | ||
57134 | } | ||
57135 | /* If the file on disk is not the same size as the database image, | ||
57136 | * then use unqliteOsTruncate to grow or shrink the file here. | ||
57137 | */ | ||
57138 | if( pPager->dbSize != pPager->dbOrigSize ){ | ||
57139 | unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize); | ||
57140 | } | ||
57141 | /* Sync the database file */ | ||
57142 | unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL); | ||
57143 | /* Remove stale flags */ | ||
57144 | pPager->iJournalOfft = 0; | ||
57145 | pPager->nRec = 0; | ||
57146 | return UNQLITE_OK; | ||
57147 | } | ||
57148 | /* | ||
57149 | * Commit a transaction: Phase two. | ||
57150 | */ | ||
57151 | static int pager_commit_phase2(Pager *pPager) | ||
57152 | { | ||
57153 | if( !pPager->is_mem ){ | ||
57154 | if( pPager->iState == PAGER_OPEN ){ | ||
57155 | return UNQLITE_OK; | ||
57156 | } | ||
57157 | if( pPager->iState != PAGER_READER ){ | ||
57158 | if( !pPager->no_jrnl ){ | ||
57159 | /* Finally, unlink the journal file */ | ||
57160 | unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); | ||
57161 | } | ||
57162 | /* Downgrade to shraed lock */ | ||
57163 | pager_unlock_db(pPager,SHARED_LOCK); | ||
57164 | pPager->iState = PAGER_READER; | ||
57165 | if( pPager->pVec ){ | ||
57166 | unqliteBitvecDestroy(pPager->pVec); | ||
57167 | pPager->pVec = 0; | ||
57168 | } | ||
57169 | } | ||
57170 | } | ||
57171 | return UNQLITE_OK; | ||
57172 | } | ||
57173 | /* | ||
57174 | * Perform a dirty commit. | ||
57175 | */ | ||
57176 | static int pager_dirty_commit(Pager *pPager) | ||
57177 | { | ||
57178 | int get_excl = 0; | ||
57179 | Page *pHot; | ||
57180 | int rc; | ||
57181 | /* Finalize the journal file without closing it */ | ||
57182 | rc = unqliteFinalizeJournal(pPager,&get_excl,0); | ||
57183 | if( rc != UNQLITE_OK ){ | ||
57184 | /* It's not a fatal error if something goes wrong here since | ||
57185 | * its not the final commit. | ||
57186 | */ | ||
57187 | return UNQLITE_OK; | ||
57188 | } | ||
57189 | /* Point to the list of hot pages */ | ||
57190 | pHot = pager_get_hot_pages(pPager); | ||
57191 | if( pHot == 0 ){ | ||
57192 | return UNQLITE_OK; | ||
57193 | } | ||
57194 | if( get_excl ){ | ||
57195 | /* Wait one last time for the exclusive lock */ | ||
57196 | rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK); | ||
57197 | if( rc != UNQLITE_OK ){ | ||
57198 | /* Not so fatal, will try another time */ | ||
57199 | return UNQLITE_OK; | ||
57200 | } | ||
57201 | } | ||
57202 | /* Tell that a dirty commit happen */ | ||
57203 | pPager->iFlags |= PAGER_CTRL_DIRTY_COMMIT; | ||
57204 | /* Write the hot pages now */ | ||
57205 | rc = pager_write_hot_dirty_pages(pPager,pHot); | ||
57206 | if( rc != UNQLITE_OK ){ | ||
57207 | pPager->iFlags |= PAGER_CTRL_COMMIT_ERR; | ||
57208 | unqliteGenError(pPager->pDb,"IO error while writing hot dirty pages, rollback your database"); | ||
57209 | return rc; | ||
57210 | } | ||
57211 | pPager->pFirstHot = pPager->pHotDirty = 0; | ||
57212 | pPager->nHot = 0; | ||
57213 | /* No need to sync the database file here, since the journal is already | ||
57214 | * open here and this is not the final commit. | ||
57215 | */ | ||
57216 | return UNQLITE_OK; | ||
57217 | } | ||
57218 | /* | ||
57219 | ** Commit a transaction and sync the database file for the pager pPager. | ||
57220 | ** | ||
57221 | ** This routine ensures that: | ||
57222 | ** | ||
57223 | ** * the journal is synced, | ||
57224 | ** * all dirty pages are written to the database file, | ||
57225 | ** * the database file is truncated (if required), and | ||
57226 | ** * the database file synced. | ||
57227 | ** * the journal file is deleted. | ||
57228 | */ | ||
57229 | UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager) | ||
57230 | { | ||
57231 | int rc; | ||
57232 | /* Commit: Phase One */ | ||
57233 | rc = pager_commit_phase1(pPager); | ||
57234 | if( rc != UNQLITE_OK ){ | ||
57235 | goto fail; | ||
57236 | } | ||
57237 | /* Commit: Phase Two */ | ||
57238 | rc = pager_commit_phase2(pPager); | ||
57239 | if( rc != UNQLITE_OK ){ | ||
57240 | goto fail; | ||
57241 | } | ||
57242 | /* Remove stale flags */ | ||
57243 | pPager->iFlags &= ~PAGER_CTRL_COMMIT_ERR; | ||
57244 | /* All done */ | ||
57245 | return UNQLITE_OK; | ||
57246 | fail: | ||
57247 | /* Disable the auto-commit flag */ | ||
57248 | pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; | ||
57249 | return rc; | ||
57250 | } | ||
57251 | /* | ||
57252 | * Reset the pager to its initial state. This is caused by | ||
57253 | * a rollback operation. | ||
57254 | */ | ||
57255 | static int pager_reset_state(Pager *pPager,int bResetKvEngine) | ||
57256 | { | ||
57257 | unqlite_kv_engine *pEngine = pPager->pEngine; | ||
57258 | Page *pNext,*pPtr = pPager->pAll; | ||
57259 | const unqlite_kv_io *pIo; | ||
57260 | int rc; | ||
57261 | /* Remove stale flags */ | ||
57262 | pPager->iFlags &= ~(PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT); | ||
57263 | pPager->iJournalOfft = 0; | ||
57264 | pPager->nRec = 0; | ||
57265 | /* Database original size */ | ||
57266 | pPager->dbSize = pPager->dbOrigSize; | ||
57267 | /* Discard all in-memory pages */ | ||
57268 | for(;;){ | ||
57269 | if( pPtr == 0 ){ | ||
57270 | break; | ||
57271 | } | ||
57272 | pNext = pPtr->pNext; /* Reverse link */ | ||
57273 | /* Remove stale flags */ | ||
57274 | pPtr->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); | ||
57275 | /* Release the page */ | ||
57276 | pager_release_page(pPager,pPtr); | ||
57277 | /* Point to the next page */ | ||
57278 | pPtr = pNext; | ||
57279 | } | ||
57280 | pPager->pAll = 0; | ||
57281 | pPager->nPage = 0; | ||
57282 | pPager->pDirty = pPager->pFirstDirty = 0; | ||
57283 | pPager->pHotDirty = pPager->pFirstHot = 0; | ||
57284 | pPager->nHot = 0; | ||
57285 | if( pPager->apHash ){ | ||
57286 | /* Zero the table */ | ||
57287 | SyZero((void *)pPager->apHash,sizeof(Page *) * pPager->nSize); | ||
57288 | } | ||
57289 | if( pPager->pVec ){ | ||
57290 | unqliteBitvecDestroy(pPager->pVec); | ||
57291 | pPager->pVec = 0; | ||
57292 | } | ||
57293 | /* Switch back to shared lock */ | ||
57294 | pager_unlock_db(pPager,SHARED_LOCK); | ||
57295 | pPager->iState = PAGER_READER; | ||
57296 | if( bResetKvEngine ){ | ||
57297 | /* Reset the underlying KV engine */ | ||
57298 | pIo = pEngine->pIo; | ||
57299 | if( pIo->pMethods->xRelease ){ | ||
57300 | /* Call the release callback */ | ||
57301 | pIo->pMethods->xRelease(pEngine); | ||
57302 | } | ||
57303 | /* Zero the structure */ | ||
57304 | SyZero(pEngine,(sxu32)pIo->pMethods->szKv); | ||
57305 | /* Fill in */ | ||
57306 | pEngine->pIo = pIo; | ||
57307 | if( pIo->pMethods->xInit ){ | ||
57308 | /* Call the init method */ | ||
57309 | rc = pIo->pMethods->xInit(pEngine,pPager->iPageSize); | ||
57310 | if( rc != UNQLITE_OK ){ | ||
57311 | return rc; | ||
57312 | } | ||
57313 | } | ||
57314 | if( pIo->pMethods->xOpen ){ | ||
57315 | /* Call the xOpen method */ | ||
57316 | rc = pIo->pMethods->xOpen(pEngine,pPager->dbSize); | ||
57317 | if( rc != UNQLITE_OK ){ | ||
57318 | return rc; | ||
57319 | } | ||
57320 | } | ||
57321 | } | ||
57322 | /* All done */ | ||
57323 | return UNQLITE_OK; | ||
57324 | } | ||
57325 | /* | ||
57326 | ** If a write transaction is open, then all changes made within the | ||
57327 | ** transaction are reverted and the current write-transaction is closed. | ||
57328 | ** The pager falls back to PAGER_READER state if successful. | ||
57329 | ** | ||
57330 | ** Otherwise, in rollback mode, this function performs two functions: | ||
57331 | ** | ||
57332 | ** 1) It rolls back the journal file, restoring all database file and | ||
57333 | ** in-memory cache pages to the state they were in when the transaction | ||
57334 | ** was opened, and | ||
57335 | ** | ||
57336 | ** 2) It finalizes the journal file, so that it is not used for hot | ||
57337 | ** rollback at any point in the future (i.e. deletion). | ||
57338 | ** | ||
57339 | ** Finalization of the journal file (task 2) is only performed if the | ||
57340 | ** rollback is successful. | ||
57341 | ** | ||
57342 | */ | ||
57343 | UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine) | ||
57344 | { | ||
57345 | int rc = UNQLITE_OK; | ||
57346 | if( pPager->iState < PAGER_WRITER_LOCKED ){ | ||
57347 | /* A write transaction must be opened */ | ||
57348 | return UNQLITE_OK; | ||
57349 | } | ||
57350 | if( pPager->is_mem ){ | ||
57351 | /* As of this release 1.1.6: Transactions are not supported for in-memory databases */ | ||
57352 | return UNQLITE_OK; | ||
57353 | } | ||
57354 | if( pPager->is_rdonly ){ | ||
57355 | /* Read-Only DB */ | ||
57356 | unqliteGenError(pPager->pDb,"Read-Only database"); | ||
57357 | return UNQLITE_READ_ONLY; | ||
57358 | } | ||
57359 | if( pPager->iState >= PAGER_WRITER_CACHEMOD ){ | ||
57360 | if( !pPager->no_jrnl ){ | ||
57361 | /* Close any outstanding joural file */ | ||
57362 | if( pPager->pjfd ){ | ||
57363 | /* Sync the journal file */ | ||
57364 | unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); | ||
57365 | } | ||
57366 | unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); | ||
57367 | pPager->pjfd = 0; | ||
57368 | if( pPager->iFlags & (PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT) ){ | ||
57369 | /* Perform the rollback */ | ||
57370 | rc = pager_journal_rollback(pPager,0); | ||
57371 | if( rc != UNQLITE_OK ){ | ||
57372 | /* Set the auto-commit flag */ | ||
57373 | pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; | ||
57374 | return rc; | ||
57375 | } | ||
57376 | } | ||
57377 | } | ||
57378 | /* Unlink the journal file */ | ||
57379 | unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); | ||
57380 | /* Reset the pager state */ | ||
57381 | rc = pager_reset_state(pPager,bResetKvEngine); | ||
57382 | if( rc != UNQLITE_OK ){ | ||
57383 | /* Mostly an unlikely scenario */ | ||
57384 | pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; /* Set the auto-commit flag */ | ||
57385 | unqliteGenError(pPager->pDb,"Error while reseting pager to its initial state"); | ||
57386 | return rc; | ||
57387 | } | ||
57388 | }else{ | ||
57389 | /* Downgrade to shared lock */ | ||
57390 | pager_unlock_db(pPager,SHARED_LOCK); | ||
57391 | pPager->iState = PAGER_READER; | ||
57392 | } | ||
57393 | return UNQLITE_OK; | ||
57394 | } | ||
57395 | /* | ||
57396 | * Mark a data page as non writeable. | ||
57397 | */ | ||
57398 | static int unqlitePagerDontWrite(unqlite_page *pMyPage) | ||
57399 | { | ||
57400 | Page *pPage = (Page *)pMyPage; | ||
57401 | if( pPage->pgno > 0 /* Page 0 is always writeable */ ){ | ||
57402 | pPage->flags |= PAGE_DONT_WRITE; | ||
57403 | } | ||
57404 | return UNQLITE_OK; | ||
57405 | } | ||
57406 | /* | ||
57407 | ** Mark a data page as writeable. This routine must be called before | ||
57408 | ** making changes to a page. The caller must check the return value | ||
57409 | ** of this function and be careful not to change any page data unless | ||
57410 | ** this routine returns UNQLITE_OK. | ||
57411 | */ | ||
57412 | static int unqlitePageWrite(unqlite_page *pMyPage) | ||
57413 | { | ||
57414 | Page *pPage = (Page *)pMyPage; | ||
57415 | Pager *pPager = pPage->pPager; | ||
57416 | int rc; | ||
57417 | /* Begin the write transaction */ | ||
57418 | rc = unqlitePagerBegin(pPager); | ||
57419 | if( rc != UNQLITE_OK ){ | ||
57420 | return rc; | ||
57421 | } | ||
57422 | if( pPager->iState == PAGER_WRITER_LOCKED ){ | ||
57423 | /* The journal file needs to be opened. Higher level routines have already | ||
57424 | ** obtained the necessary locks to begin the write-transaction, but the | ||
57425 | ** rollback journal might not yet be open. Open it now if this is the case. | ||
57426 | */ | ||
57427 | rc = unqliteOpenJournal(pPager); | ||
57428 | if( rc != UNQLITE_OK ){ | ||
57429 | return rc; | ||
57430 | } | ||
57431 | } | ||
57432 | if( pPager->nHot > 127 ){ | ||
57433 | /* Write hot dirty pages */ | ||
57434 | rc = pager_dirty_commit(pPager); | ||
57435 | if( rc != UNQLITE_OK ){ | ||
57436 | /* A rollback must be done */ | ||
57437 | unqliteGenError(pPager->pDb,"Please perform a rollback"); | ||
57438 | return rc; | ||
57439 | } | ||
57440 | } | ||
57441 | /* Write the page to the journal file */ | ||
57442 | rc = page_write(pPager,pPage); | ||
57443 | return rc; | ||
57444 | } | ||
57445 | /* | ||
57446 | ** Acquire a reference to page number pgno in pager pPager (a page | ||
57447 | ** reference has type unqlite_page*). If the requested reference is | ||
57448 | ** successfully obtained, it is copied to *ppPage and UNQLITE_OK returned. | ||
57449 | ** | ||
57450 | ** If the requested page is already in the cache, it is returned. | ||
57451 | ** Otherwise, a new page object is allocated and populated with data | ||
57452 | ** read from the database file. | ||
57453 | */ | ||
57454 | static int unqlitePagerAcquire( | ||
57455 | Pager *pPager, /* The pager open on the database file */ | ||
57456 | pgno pgno, /* Page number to fetch */ | ||
57457 | unqlite_page **ppPage, /* OUT: Acquired page */ | ||
57458 | int fetchOnly, /* Cache lookup only */ | ||
57459 | int noContent /* Do not bother reading content from disk if true */ | ||
57460 | ) | ||
57461 | { | ||
57462 | Page *pPage; | ||
57463 | int rc; | ||
57464 | /* Acquire a shared lock (if not yet done) on the database and rollback any hot-journal if present */ | ||
57465 | rc = pager_shared_lock(pPager); | ||
57466 | if( rc != UNQLITE_OK ){ | ||
57467 | return rc; | ||
57468 | } | ||
57469 | /* Fetch the page from the cache */ | ||
57470 | pPage = pager_fetch_page(pPager,pgno); | ||
57471 | if( fetchOnly ){ | ||
57472 | if( ppPage ){ | ||
57473 | *ppPage = (unqlite_page *)pPage; | ||
57474 | } | ||
57475 | return pPage ? UNQLITE_OK : UNQLITE_NOTFOUND; | ||
57476 | } | ||
57477 | if( pPage == 0 ){ | ||
57478 | /* Allocate a new page */ | ||
57479 | pPage = pager_alloc_page(pPager,pgno); | ||
57480 | if( pPage == 0 ){ | ||
57481 | unqliteGenOutofMem(pPager->pDb); | ||
57482 | return UNQLITE_NOMEM; | ||
57483 | } | ||
57484 | /* Read page contents */ | ||
57485 | rc = pager_get_page_contents(pPager,pPage,noContent); | ||
57486 | if( rc != UNQLITE_OK ){ | ||
57487 | SyMemBackendPoolFree(pPager->pAllocator,pPage); | ||
57488 | return rc; | ||
57489 | } | ||
57490 | /* Link the page */ | ||
57491 | pager_link_page(pPager,pPage); | ||
57492 | }else{ | ||
57493 | if( ppPage ){ | ||
57494 | page_ref(pPage); | ||
57495 | } | ||
57496 | } | ||
57497 | /* All done, page is loaded in memeory */ | ||
57498 | if( ppPage ){ | ||
57499 | *ppPage = (unqlite_page *)pPage; | ||
57500 | } | ||
57501 | return UNQLITE_OK; | ||
57502 | } | ||
57503 | /* | ||
57504 | * Return true if we are dealing with an in-memory database. | ||
57505 | */ | ||
57506 | static int unqliteInMemory(const char *zFilename) | ||
57507 | { | ||
57508 | sxu32 n; | ||
57509 | if( SX_EMPTY_STR(zFilename) ){ | ||
57510 | /* NULL or the empty string means an in-memory database */ | ||
57511 | return TRUE; | ||
57512 | } | ||
57513 | n = SyStrlen(zFilename); | ||
57514 | if( n == sizeof(":mem:") - 1 && | ||
57515 | SyStrnicmp(zFilename,":mem:",sizeof(":mem:") - 1) == 0 ){ | ||
57516 | return TRUE; | ||
57517 | } | ||
57518 | if( n == sizeof(":memory:") - 1 && | ||
57519 | SyStrnicmp(zFilename,":memory:",sizeof(":memory:") - 1) == 0 ){ | ||
57520 | return TRUE; | ||
57521 | } | ||
57522 | return FALSE; | ||
57523 | } | ||
57524 | /* | ||
57525 | * Allocate a new KV cursor. | ||
57526 | */ | ||
57527 | UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut) | ||
57528 | { | ||
57529 | unqlite_kv_methods *pMethods; | ||
57530 | unqlite_kv_cursor *pCur; | ||
57531 | sxu32 nByte; | ||
57532 | /* Storage engine methods */ | ||
57533 | pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods; | ||
57534 | if( pMethods->szCursor < 1 ){ | ||
57535 | /* Implementation does not supprt cursors */ | ||
57536 | unqliteGenErrorFormat(pDb,"Storage engine '%s' does not support cursors",pMethods->zName); | ||
57537 | return UNQLITE_NOTIMPLEMENTED; | ||
57538 | } | ||
57539 | nByte = pMethods->szCursor; | ||
57540 | if( nByte < sizeof(unqlite_kv_cursor) ){ | ||
57541 | nByte += sizeof(unqlite_kv_cursor); | ||
57542 | } | ||
57543 | pCur = (unqlite_kv_cursor *)SyMemBackendPoolAlloc(&pDb->sMem,nByte); | ||
57544 | if( pCur == 0 ){ | ||
57545 | unqliteGenOutofMem(pDb); | ||
57546 | return UNQLITE_NOMEM; | ||
57547 | } | ||
57548 | /* Zero the structure */ | ||
57549 | SyZero(pCur,nByte); | ||
57550 | /* Save the cursor */ | ||
57551 | pCur->pStore = pDb->sDB.pPager->pEngine; | ||
57552 | /* Invoke the initialization callback if any */ | ||
57553 | if( pMethods->xCursorInit ){ | ||
57554 | pMethods->xCursorInit(pCur); | ||
57555 | } | ||
57556 | /* All done */ | ||
57557 | *ppOut = pCur; | ||
57558 | return UNQLITE_OK; | ||
57559 | } | ||
57560 | /* | ||
57561 | * Release a cursor. | ||
57562 | */ | ||
57563 | UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur) | ||
57564 | { | ||
57565 | unqlite_kv_methods *pMethods; | ||
57566 | /* Storage engine methods */ | ||
57567 | pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods; | ||
57568 | /* Invoke the release callback if available */ | ||
57569 | if( pMethods->xCursorRelease ){ | ||
57570 | pMethods->xCursorRelease(pCur); | ||
57571 | } | ||
57572 | /* Finally, free the whole instance */ | ||
57573 | SyMemBackendPoolFree(&pDb->sMem,pCur); | ||
57574 | return UNQLITE_OK; | ||
57575 | } | ||
57576 | /* | ||
57577 | * Release the underlying KV storage engine and invoke | ||
57578 | * its associated callbacks if available. | ||
57579 | */ | ||
57580 | static void pager_release_kv_engine(Pager *pPager) | ||
57581 | { | ||
57582 | unqlite_kv_engine *pEngine = pPager->pEngine; | ||
57583 | unqlite_db *pStorage = &pPager->pDb->sDB; | ||
57584 | if( pStorage->pCursor ){ | ||
57585 | /* Release the associated cursor */ | ||
57586 | unqliteReleaseCursor(pPager->pDb,pStorage->pCursor); | ||
57587 | pStorage->pCursor = 0; | ||
57588 | } | ||
57589 | if( pEngine->pIo->pMethods->xRelease ){ | ||
57590 | pEngine->pIo->pMethods->xRelease(pEngine); | ||
57591 | } | ||
57592 | /* Release the whole instance */ | ||
57593 | SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine->pIo); | ||
57594 | SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine); | ||
57595 | pPager->pEngine = 0; | ||
57596 | } | ||
57597 | /* Forward declaration */ | ||
57598 | static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo); | ||
57599 | /* | ||
57600 | * Allocate, initialize and register a new KV storage engine | ||
57601 | * within this database instance. | ||
57602 | */ | ||
57603 | UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods) | ||
57604 | { | ||
57605 | unqlite_db *pStorage = &pPager->pDb->sDB; | ||
57606 | unqlite *pDb = pPager->pDb; | ||
57607 | unqlite_kv_engine *pEngine; | ||
57608 | unqlite_kv_io *pIo; | ||
57609 | sxu32 nByte; | ||
57610 | int rc; | ||
57611 | if( pPager->pEngine ){ | ||
57612 | if( pMethods == pPager->pEngine->pIo->pMethods ){ | ||
57613 | /* Ticket 1432: Same implementation */ | ||
57614 | return UNQLITE_OK; | ||
57615 | } | ||
57616 | /* Release the old KV engine */ | ||
57617 | pager_release_kv_engine(pPager); | ||
57618 | } | ||
57619 | /* Allocate a new KV engine instance */ | ||
57620 | nByte = (sxu32)pMethods->szKv; | ||
57621 | pEngine = (unqlite_kv_engine *)SyMemBackendAlloc(&pDb->sMem,nByte); | ||
57622 | if( pEngine == 0 ){ | ||
57623 | unqliteGenOutofMem(pDb); | ||
57624 | return UNQLITE_NOMEM; | ||
57625 | } | ||
57626 | pIo = (unqlite_kv_io *)SyMemBackendAlloc(&pDb->sMem,sizeof(unqlite_kv_io)); | ||
57627 | if( pIo == 0 ){ | ||
57628 | SyMemBackendFree(&pDb->sMem,pEngine); | ||
57629 | unqliteGenOutofMem(pDb); | ||
57630 | return UNQLITE_NOMEM; | ||
57631 | } | ||
57632 | /* Zero the structure */ | ||
57633 | SyZero(pIo,sizeof(unqlite_io_methods)); | ||
57634 | SyZero(pEngine,nByte); | ||
57635 | /* Populate the IO structure */ | ||
57636 | pager_kv_io_init(pPager,pMethods,pIo); | ||
57637 | pEngine->pIo = pIo; | ||
57638 | /* Invoke the init callback if avaialble */ | ||
57639 | if( pMethods->xInit ){ | ||
57640 | rc = pMethods->xInit(pEngine,unqliteGetPageSize()); | ||
57641 | if( rc != UNQLITE_OK ){ | ||
57642 | unqliteGenErrorFormat(pDb, | ||
57643 | "xInit() method of the underlying KV engine '%z' failed",&pPager->sKv); | ||
57644 | goto fail; | ||
57645 | } | ||
57646 | pEngine->pIo = pIo; | ||
57647 | } | ||
57648 | pPager->pEngine = pEngine; | ||
57649 | /* Allocate a new cursor */ | ||
57650 | rc = unqliteInitCursor(pDb,&pStorage->pCursor); | ||
57651 | if( rc != UNQLITE_OK ){ | ||
57652 | goto fail; | ||
57653 | } | ||
57654 | return UNQLITE_OK; | ||
57655 | fail: | ||
57656 | SyMemBackendFree(&pDb->sMem,pEngine); | ||
57657 | SyMemBackendFree(&pDb->sMem,pIo); | ||
57658 | return rc; | ||
57659 | } | ||
57660 | /* | ||
57661 | * Return the underlying KV storage engine instance. | ||
57662 | */ | ||
57663 | UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb) | ||
57664 | { | ||
57665 | return pDb->sDB.pPager->pEngine; | ||
57666 | } | ||
57667 | /* | ||
57668 | * Allocate and initialize a new Pager object. The pager should | ||
57669 | * eventually be freed by passing it to unqlitePagerClose(). | ||
57670 | * | ||
57671 | * The zFilename argument is the path to the database file to open. | ||
57672 | * If zFilename is NULL or ":memory:" then all information is held | ||
57673 | * in cache. It is never written to disk. This can be used to implement | ||
57674 | * an in-memory database. | ||
57675 | */ | ||
57676 | UNQLITE_PRIVATE int unqlitePagerOpen( | ||
57677 | unqlite_vfs *pVfs, /* The virtual file system to use */ | ||
57678 | unqlite *pDb, /* Database handle */ | ||
57679 | const char *zFilename, /* Name of the database file to open */ | ||
57680 | unsigned int iFlags /* flags controlling this file */ | ||
57681 | ) | ||
57682 | { | ||
57683 | unqlite_kv_methods *pMethods = 0; | ||
57684 | int is_mem,rd_only,no_jrnl; | ||
57685 | Pager *pPager; | ||
57686 | sxu32 nByte; | ||
57687 | sxu32 nLen; | ||
57688 | int rc; | ||
57689 | |||
57690 | /* Select the appropriate KV storage subsytem */ | ||
57691 | if( (iFlags & UNQLITE_OPEN_IN_MEMORY) || unqliteInMemory(zFilename) ){ | ||
57692 | /* An in-memory database, record that */ | ||
57693 | pMethods = unqliteFindKVStore("mem",sizeof("mem") - 1); /* Always available */ | ||
57694 | iFlags |= UNQLITE_OPEN_IN_MEMORY; | ||
57695 | }else{ | ||
57696 | /* Install the default key value storage subsystem [i.e. Linear Hash] */ | ||
57697 | pMethods = unqliteFindKVStore("hash",sizeof("hash")-1); | ||
57698 | if( pMethods == 0 ){ | ||
57699 | /* Use the b+tree storage backend if the linear hash storage is not available */ | ||
57700 | pMethods = unqliteFindKVStore("btree",sizeof("btree")-1); | ||
57701 | } | ||
57702 | } | ||
57703 | if( pMethods == 0 ){ | ||
57704 | /* Can't happen */ | ||
57705 | unqliteGenError(pDb,"Cannot install a default Key/Value storage engine"); | ||
57706 | return UNQLITE_NOTIMPLEMENTED; | ||
57707 | } | ||
57708 | is_mem = (iFlags & UNQLITE_OPEN_IN_MEMORY) != 0; | ||
57709 | rd_only = (iFlags & UNQLITE_OPEN_READONLY) != 0; | ||
57710 | no_jrnl = (iFlags & UNQLITE_OPEN_OMIT_JOURNALING) != 0; | ||
57711 | rc = UNQLITE_OK; | ||
57712 | if( is_mem ){ | ||
57713 | /* Omit journaling for in-memory database */ | ||
57714 | no_jrnl = 1; | ||
57715 | } | ||
57716 | /* Total number of bytes to allocate */ | ||
57717 | nByte = sizeof(Pager); | ||
57718 | nLen = 0; | ||
57719 | if( !is_mem ){ | ||
57720 | nLen = SyStrlen(zFilename); | ||
57721 | nByte += pVfs->mxPathname + nLen + sizeof(char) /* null termniator */; | ||
57722 | } | ||
57723 | /* Allocate */ | ||
57724 | pPager = (Pager *)SyMemBackendAlloc(&pDb->sMem,nByte); | ||
57725 | if( pPager == 0 ){ | ||
57726 | return UNQLITE_NOMEM; | ||
57727 | } | ||
57728 | /* Zero the structure */ | ||
57729 | SyZero(pPager,nByte); | ||
57730 | /* Fill-in the structure */ | ||
57731 | pPager->pAllocator = &pDb->sMem; | ||
57732 | pPager->pDb = pDb; | ||
57733 | pDb->sDB.pPager = pPager; | ||
57734 | /* Allocate page table */ | ||
57735 | pPager->nSize = 128; /* Must be a power of two */ | ||
57736 | nByte = pPager->nSize * sizeof(Page *); | ||
57737 | pPager->apHash = (Page **)SyMemBackendAlloc(pPager->pAllocator,nByte); | ||
57738 | if( pPager->apHash == 0 ){ | ||
57739 | rc = UNQLITE_NOMEM; | ||
57740 | goto fail; | ||
57741 | } | ||
57742 | SyZero(pPager->apHash,nByte); | ||
57743 | pPager->is_mem = is_mem; | ||
57744 | pPager->no_jrnl = no_jrnl; | ||
57745 | pPager->is_rdonly = rd_only; | ||
57746 | pPager->iOpenFlags = iFlags; | ||
57747 | pPager->pVfs = pVfs; | ||
57748 | SyRandomnessInit(&pPager->sPrng,0,0); | ||
57749 | SyRandomness(&pPager->sPrng,(void *)&pPager->cksumInit,sizeof(sxu32)); | ||
57750 | /* Unlimited cache size */ | ||
57751 | pPager->nCacheMax = SXU32_HIGH; | ||
57752 | /* Copy filename and journal name */ | ||
57753 | if( !is_mem ){ | ||
57754 | pPager->zFilename = (char *)&pPager[1]; | ||
57755 | rc = UNQLITE_OK; | ||
57756 | if( pVfs->xFullPathname ){ | ||
57757 | rc = pVfs->xFullPathname(pVfs,zFilename,pVfs->mxPathname + nLen,pPager->zFilename); | ||
57758 | } | ||
57759 | if( rc != UNQLITE_OK ){ | ||
57760 | /* Simple filename copy */ | ||
57761 | SyMemcpy(zFilename,pPager->zFilename,nLen); | ||
57762 | pPager->zFilename[nLen] = 0; | ||
57763 | rc = UNQLITE_OK; | ||
57764 | }else{ | ||
57765 | nLen = SyStrlen(pPager->zFilename); | ||
57766 | } | ||
57767 | pPager->zJournal = (char *) SyMemBackendAlloc(pPager->pAllocator,nLen + sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) + sizeof(char)); | ||
57768 | if( pPager->zJournal == 0 ){ | ||
57769 | rc = UNQLITE_NOMEM; | ||
57770 | goto fail; | ||
57771 | } | ||
57772 | /* Copy filename */ | ||
57773 | SyMemcpy(pPager->zFilename,pPager->zJournal,nLen); | ||
57774 | /* Copy journal suffix */ | ||
57775 | SyMemcpy(UNQLITE_JOURNAL_FILE_SUFFIX,&pPager->zJournal[nLen],sizeof(UNQLITE_JOURNAL_FILE_SUFFIX)-1); | ||
57776 | /* Append the nul terminator to the journal path */ | ||
57777 | pPager->zJournal[nLen + ( sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) - 1)] = 0; | ||
57778 | } | ||
57779 | /* Finally, register the selected KV engine */ | ||
57780 | rc = unqlitePagerRegisterKvEngine(pPager,pMethods); | ||
57781 | if( rc != UNQLITE_OK ){ | ||
57782 | goto fail; | ||
57783 | } | ||
57784 | /* Set the pager state */ | ||
57785 | if( pPager->is_mem ){ | ||
57786 | pPager->iState = PAGER_WRITER_FINISHED; | ||
57787 | pPager->iLock = EXCLUSIVE_LOCK; | ||
57788 | }else{ | ||
57789 | pPager->iState = PAGER_OPEN; | ||
57790 | pPager->iLock = NO_LOCK; | ||
57791 | } | ||
57792 | /* All done, ready for processing */ | ||
57793 | return UNQLITE_OK; | ||
57794 | fail: | ||
57795 | SyMemBackendFree(&pDb->sMem,pPager); | ||
57796 | return rc; | ||
57797 | } | ||
57798 | /* | ||
57799 | * Set a cache limit. Note that, this is a simple hint, the pager is not | ||
57800 | * forced to honor this limit. | ||
57801 | */ | ||
57802 | UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage) | ||
57803 | { | ||
57804 | if( mxPage < 256 ){ | ||
57805 | return UNQLITE_INVALID; | ||
57806 | } | ||
57807 | pPager->nCacheMax = mxPage; | ||
57808 | return UNQLITE_OK; | ||
57809 | } | ||
57810 | /* | ||
57811 | * Shutdown the page cache. Free all memory and close the database file. | ||
57812 | */ | ||
57813 | UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager) | ||
57814 | { | ||
57815 | /* Release the KV engine */ | ||
57816 | pager_release_kv_engine(pPager); | ||
57817 | if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){ | ||
57818 | const jx9_vfs *pVfs = jx9ExportBuiltinVfs(); | ||
57819 | if( pVfs && pVfs->xUnmap && pPager->pMmap ){ | ||
57820 | pVfs->xUnmap(pPager->pMmap,pPager->dbByteSize); | ||
57821 | } | ||
57822 | } | ||
57823 | if( !pPager->is_mem && pPager->iState > PAGER_OPEN ){ | ||
57824 | /* Release all lock on this database handle */ | ||
57825 | pager_unlock_db(pPager,NO_LOCK); | ||
57826 | /* Close the file */ | ||
57827 | unqliteOsCloseFree(pPager->pAllocator,pPager->pfd); | ||
57828 | } | ||
57829 | if( pPager->pVec ){ | ||
57830 | unqliteBitvecDestroy(pPager->pVec); | ||
57831 | pPager->pVec = 0; | ||
57832 | } | ||
57833 | return UNQLITE_OK; | ||
57834 | } | ||
57835 | /* | ||
57836 | * Generate a random string. | ||
57837 | */ | ||
57838 | UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen) | ||
57839 | { | ||
57840 | static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */ | ||
57841 | sxu32 i; | ||
57842 | /* Generate a binary string first */ | ||
57843 | SyRandomness(&pPager->sPrng,zBuf,nLen); | ||
57844 | /* Turn the binary string into english based alphabet */ | ||
57845 | for( i = 0 ; i < nLen ; ++i ){ | ||
57846 | zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)]; | ||
57847 | } | ||
57848 | } | ||
57849 | /* | ||
57850 | * Generate a random number. | ||
57851 | */ | ||
57852 | UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager) | ||
57853 | { | ||
57854 | sxu32 iNum; | ||
57855 | SyRandomness(&pPager->sPrng,(void *)&iNum,sizeof(iNum)); | ||
57856 | return iNum; | ||
57857 | } | ||
57858 | /* Exported KV IO Methods */ | ||
57859 | /* | ||
57860 | * Refer to [unqlitePagerAcquire()] | ||
57861 | */ | ||
57862 | static int unqliteKvIoPageGet(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage) | ||
57863 | { | ||
57864 | int rc; | ||
57865 | rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,0,0); | ||
57866 | return rc; | ||
57867 | } | ||
57868 | /* | ||
57869 | * Refer to [unqlitePagerAcquire()] | ||
57870 | */ | ||
57871 | static int unqliteKvIoPageLookup(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage) | ||
57872 | { | ||
57873 | int rc; | ||
57874 | rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,1,0); | ||
57875 | return rc; | ||
57876 | } | ||
57877 | /* | ||
57878 | * Refer to [unqlitePagerAcquire()] | ||
57879 | */ | ||
57880 | static int unqliteKvIoNewPage(unqlite_kv_handle pHandle,unqlite_page **ppPage) | ||
57881 | { | ||
57882 | Pager *pPager = (Pager *)pHandle; | ||
57883 | int rc; | ||
57884 | /* | ||
57885 | * Acquire a reader-lock first so that pPager->dbSize get initialized. | ||
57886 | */ | ||
57887 | rc = pager_shared_lock(pPager); | ||
57888 | if( rc == UNQLITE_OK ){ | ||
57889 | rc = unqlitePagerAcquire(pPager,pPager->dbSize == 0 ? /* Page 0 is reserved */ 1 : pPager->dbSize ,ppPage,0,0); | ||
57890 | } | ||
57891 | return rc; | ||
57892 | } | ||
57893 | /* | ||
57894 | * Refer to [unqlitePageWrite()] | ||
57895 | */ | ||
57896 | static int unqliteKvIopageWrite(unqlite_page *pPage) | ||
57897 | { | ||
57898 | int rc; | ||
57899 | if( pPage == 0 ){ | ||
57900 | /* TICKET 1433-0348 */ | ||
57901 | return UNQLITE_OK; | ||
57902 | } | ||
57903 | rc = unqlitePageWrite(pPage); | ||
57904 | return rc; | ||
57905 | } | ||
57906 | /* | ||
57907 | * Refer to [unqlitePagerDontWrite()] | ||
57908 | */ | ||
57909 | static int unqliteKvIoPageDontWrite(unqlite_page *pPage) | ||
57910 | { | ||
57911 | int rc; | ||
57912 | if( pPage == 0 ){ | ||
57913 | /* TICKET 1433-0348 */ | ||
57914 | return UNQLITE_OK; | ||
57915 | } | ||
57916 | rc = unqlitePagerDontWrite(pPage); | ||
57917 | return rc; | ||
57918 | } | ||
57919 | /* | ||
57920 | * Refer to [unqliteBitvecSet()] | ||
57921 | */ | ||
57922 | static int unqliteKvIoPageDontJournal(unqlite_page *pRaw) | ||
57923 | { | ||
57924 | Page *pPage = (Page *)pRaw; | ||
57925 | Pager *pPager; | ||
57926 | if( pPage == 0 ){ | ||
57927 | /* TICKET 1433-0348 */ | ||
57928 | return UNQLITE_OK; | ||
57929 | } | ||
57930 | pPager = pPage->pPager; | ||
57931 | if( pPager->iState >= PAGER_WRITER_LOCKED ){ | ||
57932 | if( !pPager->no_jrnl && pPager->pVec && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){ | ||
57933 | unqliteBitvecSet(pPager->pVec,pPage->pgno); | ||
57934 | } | ||
57935 | } | ||
57936 | return UNQLITE_OK; | ||
57937 | } | ||
57938 | /* | ||
57939 | * Do not add a page to the hot dirty list. | ||
57940 | */ | ||
57941 | static int unqliteKvIoPageDontMakeHot(unqlite_page *pRaw) | ||
57942 | { | ||
57943 | Page *pPage = (Page *)pRaw; | ||
57944 | |||
57945 | if( pPage == 0 ){ | ||
57946 | /* TICKET 1433-0348 */ | ||
57947 | return UNQLITE_OK; | ||
57948 | } | ||
57949 | pPage->flags |= PAGE_DONT_MAKE_HOT; | ||
57950 | return UNQLITE_OK; | ||
57951 | } | ||
57952 | /* | ||
57953 | * Refer to [page_ref()] | ||
57954 | */ | ||
57955 | static int unqliteKvIopage_ref(unqlite_page *pPage) | ||
57956 | { | ||
57957 | if( pPage ){ | ||
57958 | page_ref((Page *)pPage); | ||
57959 | } | ||
57960 | return UNQLITE_OK; | ||
57961 | } | ||
57962 | /* | ||
57963 | * Refer to [page_unref()] | ||
57964 | */ | ||
57965 | static int unqliteKvIoPageUnRef(unqlite_page *pPage) | ||
57966 | { | ||
57967 | if( pPage ){ | ||
57968 | page_unref((Page *)pPage); | ||
57969 | } | ||
57970 | return UNQLITE_OK; | ||
57971 | } | ||
57972 | /* | ||
57973 | * Refer to the declaration of the [Pager] structure | ||
57974 | */ | ||
57975 | static int unqliteKvIoReadOnly(unqlite_kv_handle pHandle) | ||
57976 | { | ||
57977 | return ((Pager *)pHandle)->is_rdonly; | ||
57978 | } | ||
57979 | /* | ||
57980 | * Refer to the declaration of the [Pager] structure | ||
57981 | */ | ||
57982 | static int unqliteKvIoPageSize(unqlite_kv_handle pHandle) | ||
57983 | { | ||
57984 | return ((Pager *)pHandle)->iPageSize; | ||
57985 | } | ||
57986 | /* | ||
57987 | * Refer to the declaration of the [Pager] structure | ||
57988 | */ | ||
57989 | static unsigned char * unqliteKvIoTempPage(unqlite_kv_handle pHandle) | ||
57990 | { | ||
57991 | return ((Pager *)pHandle)->zTmpPage; | ||
57992 | } | ||
57993 | /* | ||
57994 | * Set a page unpin callback. | ||
57995 | * Refer to the declaration of the [Pager] structure | ||
57996 | */ | ||
57997 | static void unqliteKvIoPageUnpin(unqlite_kv_handle pHandle,void (*xPageUnpin)(void *)) | ||
57998 | { | ||
57999 | Pager *pPager = (Pager *)pHandle; | ||
58000 | pPager->xPageUnpin = xPageUnpin; | ||
58001 | } | ||
58002 | /* | ||
58003 | * Set a page reload callback. | ||
58004 | * Refer to the declaration of the [Pager] structure | ||
58005 | */ | ||
58006 | static void unqliteKvIoPageReload(unqlite_kv_handle pHandle,void (*xPageReload)(void *)) | ||
58007 | { | ||
58008 | Pager *pPager = (Pager *)pHandle; | ||
58009 | pPager->xPageReload = xPageReload; | ||
58010 | } | ||
58011 | /* | ||
58012 | * Log an error. | ||
58013 | * Refer to the declaration of the [Pager] structure | ||
58014 | */ | ||
58015 | static void unqliteKvIoErr(unqlite_kv_handle pHandle,const char *zErr) | ||
58016 | { | ||
58017 | Pager *pPager = (Pager *)pHandle; | ||
58018 | unqliteGenError(pPager->pDb,zErr); | ||
58019 | } | ||
58020 | /* | ||
58021 | * Init an instance of the [unqlite_kv_io] structure. | ||
58022 | */ | ||
58023 | static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo) | ||
58024 | { | ||
58025 | pIo->pHandle = pPager; | ||
58026 | pIo->pMethods = pMethods; | ||
58027 | |||
58028 | pIo->xGet = unqliteKvIoPageGet; | ||
58029 | pIo->xLookup = unqliteKvIoPageLookup; | ||
58030 | pIo->xNew = unqliteKvIoNewPage; | ||
58031 | |||
58032 | pIo->xWrite = unqliteKvIopageWrite; | ||
58033 | pIo->xDontWrite = unqliteKvIoPageDontWrite; | ||
58034 | pIo->xDontJournal = unqliteKvIoPageDontJournal; | ||
58035 | pIo->xDontMkHot = unqliteKvIoPageDontMakeHot; | ||
58036 | |||
58037 | pIo->xPageRef = unqliteKvIopage_ref; | ||
58038 | pIo->xPageUnref = unqliteKvIoPageUnRef; | ||
58039 | |||
58040 | pIo->xPageSize = unqliteKvIoPageSize; | ||
58041 | pIo->xReadOnly = unqliteKvIoReadOnly; | ||
58042 | |||
58043 | pIo->xTmpPage = unqliteKvIoTempPage; | ||
58044 | |||
58045 | pIo->xSetUnpin = unqliteKvIoPageUnpin; | ||
58046 | pIo->xSetReload = unqliteKvIoPageReload; | ||
58047 | |||
58048 | pIo->xErr = unqliteKvIoErr; | ||
58049 | |||
58050 | return UNQLITE_OK; | ||
58051 | } | ||
58052 | /* | ||
58053 | * ---------------------------------------------------------- | ||
58054 | * File: unqlite_vm.c | ||
58055 | * MD5: 2a0c56efb2ab87d3e52d0d7c3147c53b | ||
58056 | * ---------------------------------------------------------- | ||
58057 | */ | ||
58058 | /* | ||
58059 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
58060 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
58061 | * Version 1.1.6 | ||
58062 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
58063 | * please contact Symisc Systems via: | ||
58064 | * legal@symisc.net | ||
58065 | * licensing@symisc.net | ||
58066 | * contact@symisc.net | ||
58067 | * or visit: | ||
58068 | * http://unqlite.org/licensing.html | ||
58069 | */ | ||
58070 | /* $SymiscID: unqlite_vm.c v1.0 Win7 2013-01-29 23:37 stable <chm@symisc.net> $ */ | ||
58071 | #ifndef UNQLITE_AMALGAMATION | ||
58072 | #include "unqliteInt.h" | ||
58073 | #endif | ||
58074 | /* This file deals with low level stuff related to the unQLite Virtual Machine */ | ||
58075 | |||
58076 | /* Record ID as a hash value */ | ||
58077 | #define COL_RECORD_HASH(RID) (RID) | ||
58078 | /* | ||
58079 | * Fetch a record from a given collection. | ||
58080 | */ | ||
58081 | static unqlite_col_record * CollectionCacheFetchRecord( | ||
58082 | unqlite_col *pCol, /* Target collection */ | ||
58083 | jx9_int64 nId /* Unique record ID */ | ||
58084 | ) | ||
58085 | { | ||
58086 | unqlite_col_record *pEntry; | ||
58087 | if( pCol->nRec < 1 ){ | ||
58088 | /* Don't bother hashing */ | ||
58089 | return 0; | ||
58090 | } | ||
58091 | pEntry = pCol->apRecord[COL_RECORD_HASH(nId) & (pCol->nRecSize - 1)]; | ||
58092 | for(;;){ | ||
58093 | if( pEntry == 0 ){ | ||
58094 | break; | ||
58095 | } | ||
58096 | if( pEntry->nId == nId ){ | ||
58097 | /* Record found */ | ||
58098 | return pEntry; | ||
58099 | } | ||
58100 | /* Point to the next entry */ | ||
58101 | pEntry = pEntry->pNextCol; | ||
58102 | |||
58103 | } | ||
58104 | /* No such record */ | ||
58105 | return 0; | ||
58106 | } | ||
58107 | /* | ||
58108 | * Install a freshly created record in a given collection. | ||
58109 | */ | ||
58110 | static int CollectionCacheInstallRecord( | ||
58111 | unqlite_col *pCol, /* Target collection */ | ||
58112 | jx9_int64 nId, /* Unique record ID */ | ||
58113 | jx9_value *pValue /* JSON value */ | ||
58114 | ) | ||
58115 | { | ||
58116 | unqlite_col_record *pRecord; | ||
58117 | sxu32 iBucket; | ||
58118 | /* Fetch the record first */ | ||
58119 | pRecord = CollectionCacheFetchRecord(pCol,nId); | ||
58120 | if( pRecord ){ | ||
58121 | /* Record already installed, overwrite its old value */ | ||
58122 | jx9MemObjStore(pValue,&pRecord->sValue); | ||
58123 | return UNQLITE_OK; | ||
58124 | } | ||
58125 | /* Allocate a new instance */ | ||
58126 | pRecord = (unqlite_col_record *)SyMemBackendPoolAlloc(&pCol->pVm->sAlloc,sizeof(unqlite_col_record)); | ||
58127 | if( pRecord == 0 ){ | ||
58128 | return UNQLITE_NOMEM; | ||
58129 | } | ||
58130 | /* Zero the structure */ | ||
58131 | SyZero(pRecord,sizeof(unqlite_col_record)); | ||
58132 | /* Fill in the structure */ | ||
58133 | jx9MemObjInit(pCol->pVm->pJx9Vm,&pRecord->sValue); | ||
58134 | jx9MemObjStore(pValue,&pRecord->sValue); | ||
58135 | pRecord->nId = nId; | ||
58136 | pRecord->pCol = pCol; | ||
58137 | /* Install in the corresponding bucket */ | ||
58138 | iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1); | ||
58139 | pRecord->pNextCol = pCol->apRecord[iBucket]; | ||
58140 | if( pCol->apRecord[iBucket] ){ | ||
58141 | pCol->apRecord[iBucket]->pPrevCol = pRecord; | ||
58142 | } | ||
58143 | pCol->apRecord[iBucket] = pRecord; | ||
58144 | /* Link */ | ||
58145 | MACRO_LD_PUSH(pCol->pList,pRecord); | ||
58146 | pCol->nRec++; | ||
58147 | if( (pCol->nRec >= pCol->nRecSize * 3) && pCol->nRec < 100000 ){ | ||
58148 | /* Allocate a new larger table */ | ||
58149 | sxu32 nNewSize = pCol->nRecSize << 1; | ||
58150 | unqlite_col_record *pEntry; | ||
58151 | unqlite_col_record **apNew; | ||
58152 | sxu32 n; | ||
58153 | |||
58154 | apNew = (unqlite_col_record **)SyMemBackendAlloc(&pCol->pVm->sAlloc, nNewSize * sizeof(unqlite_col_record *)); | ||
58155 | if( apNew ){ | ||
58156 | /* Zero the new table */ | ||
58157 | SyZero((void *)apNew, nNewSize * sizeof(unqlite_col_record *)); | ||
58158 | /* Rehash all entries */ | ||
58159 | n = 0; | ||
58160 | pEntry = pCol->pList; | ||
58161 | for(;;){ | ||
58162 | /* Loop one */ | ||
58163 | if( n >= pCol->nRec ){ | ||
58164 | break; | ||
58165 | } | ||
58166 | pEntry->pNextCol = pEntry->pPrevCol = 0; | ||
58167 | /* Install in the new bucket */ | ||
58168 | iBucket = COL_RECORD_HASH(pEntry->nId) & (nNewSize - 1); | ||
58169 | pEntry->pNextCol = apNew[iBucket]; | ||
58170 | if( apNew[iBucket] ){ | ||
58171 | apNew[iBucket]->pPrevCol = pEntry; | ||
58172 | } | ||
58173 | apNew[iBucket] = pEntry; | ||
58174 | /* Point to the next entry */ | ||
58175 | pEntry = pEntry->pNext; | ||
58176 | n++; | ||
58177 | } | ||
58178 | /* Release the old table and reflect the change */ | ||
58179 | SyMemBackendFree(&pCol->pVm->sAlloc,(void *)pCol->apRecord); | ||
58180 | pCol->apRecord = apNew; | ||
58181 | pCol->nRecSize = nNewSize; | ||
58182 | } | ||
58183 | } | ||
58184 | /* All done */ | ||
58185 | return UNQLITE_OK; | ||
58186 | } | ||
58187 | /* | ||
58188 | * Remove a record from the collection table. | ||
58189 | */ | ||
58190 | UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord( | ||
58191 | unqlite_col *pCol, /* Target collection */ | ||
58192 | jx9_int64 nId /* Unique record ID */ | ||
58193 | ) | ||
58194 | { | ||
58195 | unqlite_col_record *pRecord; | ||
58196 | /* Fetch the record first */ | ||
58197 | pRecord = CollectionCacheFetchRecord(pCol,nId); | ||
58198 | if( pRecord == 0 ){ | ||
58199 | /* No such record */ | ||
58200 | return UNQLITE_NOTFOUND; | ||
58201 | } | ||
58202 | if( pRecord->pPrevCol ){ | ||
58203 | pRecord->pPrevCol->pNextCol = pRecord->pNextCol; | ||
58204 | }else{ | ||
58205 | sxu32 iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1); | ||
58206 | pCol->apRecord[iBucket] = pRecord->pNextCol; | ||
58207 | } | ||
58208 | if( pRecord->pNextCol ){ | ||
58209 | pRecord->pNextCol->pPrevCol = pRecord->pPrevCol; | ||
58210 | } | ||
58211 | /* Unlink */ | ||
58212 | MACRO_LD_REMOVE(pCol->pList,pRecord); | ||
58213 | pCol->nRec--; | ||
58214 | return UNQLITE_OK; | ||
58215 | } | ||
58216 | /* | ||
58217 | * Discard a collection and its records. | ||
58218 | */ | ||
58219 | static int CollectionCacheRelease(unqlite_col *pCol) | ||
58220 | { | ||
58221 | unqlite_col_record *pNext,*pRec = pCol->pList; | ||
58222 | unqlite_vm *pVm = pCol->pVm; | ||
58223 | sxu32 n; | ||
58224 | /* Discard all records */ | ||
58225 | for( n = 0 ; n < pCol->nRec ; ++n ){ | ||
58226 | pNext = pRec->pNext; | ||
58227 | jx9MemObjRelease(&pRec->sValue); | ||
58228 | SyMemBackendPoolFree(&pVm->sAlloc,(void *)pRec); | ||
58229 | /* Point to the next record */ | ||
58230 | pRec = pNext; | ||
58231 | } | ||
58232 | SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord); | ||
58233 | pCol->nRec = pCol->nRecSize = 0; | ||
58234 | pCol->pList = 0; | ||
58235 | return UNQLITE_OK; | ||
58236 | } | ||
58237 | /* | ||
58238 | * Install a freshly created collection in the unqlite VM. | ||
58239 | */ | ||
58240 | static int unqliteVmInstallCollection( | ||
58241 | unqlite_vm *pVm, /* Target VM */ | ||
58242 | unqlite_col *pCol /* Collection to install */ | ||
58243 | ) | ||
58244 | { | ||
58245 | SyString *pName = &pCol->sName; | ||
58246 | sxu32 iBucket; | ||
58247 | /* Hash the collection name */ | ||
58248 | pCol->nHash = SyBinHash((const void *)pName->zString,pName->nByte); | ||
58249 | /* Install it in the corresponding bucket */ | ||
58250 | iBucket = pCol->nHash & (pVm->iColSize - 1); | ||
58251 | pCol->pNextCol = pVm->apCol[iBucket]; | ||
58252 | if( pVm->apCol[iBucket] ){ | ||
58253 | pVm->apCol[iBucket]->pPrevCol = pCol; | ||
58254 | } | ||
58255 | pVm->apCol[iBucket] = pCol; | ||
58256 | /* Link to the list of active collections */ | ||
58257 | MACRO_LD_PUSH(pVm->pCol,pCol); | ||
58258 | pVm->iCol++; | ||
58259 | if( (pVm->iCol >= pVm->iColSize * 4) && pVm->iCol < 10000 ){ | ||
58260 | /* Grow the hashtable */ | ||
58261 | sxu32 nNewSize = pVm->iColSize << 1; | ||
58262 | unqlite_col *pEntry; | ||
58263 | unqlite_col **apNew; | ||
58264 | sxu32 n; | ||
58265 | |||
58266 | apNew = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc, nNewSize * sizeof(unqlite_col *)); | ||
58267 | if( apNew ){ | ||
58268 | /* Zero the new table */ | ||
58269 | SyZero((void *)apNew, nNewSize * sizeof(unqlite_col *)); | ||
58270 | /* Rehash all entries */ | ||
58271 | n = 0; | ||
58272 | pEntry = pVm->pCol; | ||
58273 | for(;;){ | ||
58274 | /* Loop one */ | ||
58275 | if( n >= pVm->iCol ){ | ||
58276 | break; | ||
58277 | } | ||
58278 | pEntry->pNextCol = pEntry->pPrevCol = 0; | ||
58279 | /* Install in the new bucket */ | ||
58280 | iBucket = pEntry->nHash & (nNewSize - 1); | ||
58281 | pEntry->pNextCol = apNew[iBucket]; | ||
58282 | if( apNew[iBucket] ){ | ||
58283 | apNew[iBucket]->pPrevCol = pEntry; | ||
58284 | } | ||
58285 | apNew[iBucket] = pEntry; | ||
58286 | /* Point to the next entry */ | ||
58287 | pEntry = pEntry->pNext; | ||
58288 | n++; | ||
58289 | } | ||
58290 | /* Release the old table and reflect the change */ | ||
58291 | SyMemBackendFree(&pVm->sAlloc,(void *)pVm->apCol); | ||
58292 | pVm->apCol = apNew; | ||
58293 | pVm->iColSize = nNewSize; | ||
58294 | } | ||
58295 | } | ||
58296 | return UNQLITE_OK; | ||
58297 | } | ||
58298 | /* | ||
58299 | * Fetch a collection from the target VM. | ||
58300 | */ | ||
58301 | static unqlite_col * unqliteVmFetchCollection( | ||
58302 | unqlite_vm *pVm, /* Target VM */ | ||
58303 | SyString *pName /* Lookup name */ | ||
58304 | ) | ||
58305 | { | ||
58306 | unqlite_col *pCol; | ||
58307 | sxu32 nHash; | ||
58308 | if( pVm->iCol < 1 ){ | ||
58309 | /* Don't bother hashing */ | ||
58310 | return 0; | ||
58311 | } | ||
58312 | nHash = SyBinHash((const void *)pName->zString,pName->nByte); | ||
58313 | /* Perform the lookup */ | ||
58314 | pCol = pVm->apCol[nHash & ( pVm->iColSize - 1)]; | ||
58315 | for(;;){ | ||
58316 | if( pCol == 0 ){ | ||
58317 | break; | ||
58318 | } | ||
58319 | if( nHash == pCol->nHash && SyStringCmp(pName,&pCol->sName,SyMemcmp) == 0 ){ | ||
58320 | /* Collection found */ | ||
58321 | return pCol; | ||
58322 | } | ||
58323 | /* Point to the next entry */ | ||
58324 | pCol = pCol->pNextCol; | ||
58325 | } | ||
58326 | /* No such collection */ | ||
58327 | return 0; | ||
58328 | } | ||
58329 | /* | ||
58330 | * Write and/or alter collection binary header. | ||
58331 | */ | ||
58332 | static int CollectionSetHeader( | ||
58333 | unqlite_kv_engine *pEngine, /* Underlying KV storage engine */ | ||
58334 | unqlite_col *pCol, /* Target collection */ | ||
58335 | jx9_int64 iRec, /* Last record ID */ | ||
58336 | jx9_int64 iTotal, /* Total number of records in this collection */ | ||
58337 | jx9_value *pSchema /* Collection schema */ | ||
58338 | ) | ||
58339 | { | ||
58340 | SyBlob *pHeader = &pCol->sHeader; | ||
58341 | unqlite_kv_methods *pMethods; | ||
58342 | int iWrite = 0; | ||
58343 | int rc; | ||
58344 | if( pEngine == 0 ){ | ||
58345 | /* Default storage engine */ | ||
58346 | pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); | ||
58347 | } | ||
58348 | pMethods = pEngine->pIo->pMethods; | ||
58349 | if( SyBlobLength(pHeader) < 1 ){ | ||
58350 | Sytm *pCreate = &pCol->sCreation; /* Creation time */ | ||
58351 | unqlite_vfs *pVfs; | ||
58352 | sxu32 iDos; | ||
58353 | /* Magic number */ | ||
58354 | rc = SyBlobAppendBig16(pHeader,UNQLITE_COLLECTION_MAGIC); | ||
58355 | if( rc != UNQLITE_OK ){ | ||
58356 | return rc; | ||
58357 | } | ||
58358 | /* Initial record ID */ | ||
58359 | rc = SyBlobAppendBig64(pHeader,0); | ||
58360 | if( rc != UNQLITE_OK ){ | ||
58361 | return rc; | ||
58362 | } | ||
58363 | /* Total records in the collection */ | ||
58364 | rc = SyBlobAppendBig64(pHeader,0); | ||
58365 | if( rc != UNQLITE_OK ){ | ||
58366 | return rc; | ||
58367 | } | ||
58368 | pVfs = (unqlite_vfs *)unqliteExportBuiltinVfs(); | ||
58369 | /* Creation time of the collection */ | ||
58370 | if( pVfs->xCurrentTime ){ | ||
58371 | /* Get the creation time */ | ||
58372 | pVfs->xCurrentTime(pVfs,pCreate); | ||
58373 | }else{ | ||
58374 | /* Zero the structure */ | ||
58375 | SyZero(pCreate,sizeof(Sytm)); | ||
58376 | } | ||
58377 | /* Convert to DOS time */ | ||
58378 | SyTimeFormatToDos(pCreate,&iDos); | ||
58379 | rc = SyBlobAppendBig32(pHeader,iDos); | ||
58380 | if( rc != UNQLITE_OK ){ | ||
58381 | return rc; | ||
58382 | } | ||
58383 | /* Offset to start writing collection schema */ | ||
58384 | pCol->nSchemaOfft = SyBlobLength(pHeader); | ||
58385 | iWrite = 1; | ||
58386 | }else{ | ||
58387 | unsigned char *zBinary = (unsigned char *)SyBlobData(pHeader); | ||
58388 | /* Header update */ | ||
58389 | if( iRec >= 0 ){ | ||
58390 | /* Update record ID */ | ||
58391 | SyBigEndianPack64(&zBinary[2/* Magic number*/],(sxu64)iRec); | ||
58392 | iWrite = 1; | ||
58393 | } | ||
58394 | if( iTotal >= 0 ){ | ||
58395 | /* Total records */ | ||
58396 | SyBigEndianPack64(&zBinary[2/* Magic number*/+8/* Record ID*/],(sxu64)iTotal); | ||
58397 | iWrite = 1; | ||
58398 | } | ||
58399 | if( pSchema ){ | ||
58400 | /* Collection Schema */ | ||
58401 | SyBlobTruncate(pHeader,pCol->nSchemaOfft); | ||
58402 | /* Encode the schema to FastJson */ | ||
58403 | rc = FastJsonEncode(pSchema,pHeader,0); | ||
58404 | if( rc != UNQLITE_OK ){ | ||
58405 | return rc; | ||
58406 | } | ||
58407 | /* Copy the collection schema */ | ||
58408 | jx9MemObjStore(pSchema,&pCol->sSchema); | ||
58409 | iWrite = 1; | ||
58410 | } | ||
58411 | } | ||
58412 | if( iWrite ){ | ||
58413 | SyString *pId = &pCol->sName; | ||
58414 | /* Reflect the disk and/or in-memory image */ | ||
58415 | rc = pMethods->xReplace(pEngine, | ||
58416 | (const void *)pId->zString,pId->nByte, | ||
58417 | SyBlobData(pHeader),SyBlobLength(pHeader) | ||
58418 | ); | ||
58419 | if( rc != UNQLITE_OK ){ | ||
58420 | unqliteGenErrorFormat(pCol->pVm->pDb, | ||
58421 | "Cannot save collection '%z' header in the underlying storage engine", | ||
58422 | pId | ||
58423 | ); | ||
58424 | return rc; | ||
58425 | } | ||
58426 | } | ||
58427 | return UNQLITE_OK; | ||
58428 | } | ||
58429 | /* | ||
58430 | * Load a binary collection from disk. | ||
58431 | */ | ||
58432 | static int CollectionLoadHeader(unqlite_col *pCol) | ||
58433 | { | ||
58434 | SyBlob *pHeader = &pCol->sHeader; | ||
58435 | unsigned char *zRaw,*zEnd; | ||
58436 | sxu16 nMagic; | ||
58437 | sxu32 iDos; | ||
58438 | int rc; | ||
58439 | SyBlobReset(pHeader); | ||
58440 | /* Read the binary header */ | ||
58441 | rc = unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pHeader); | ||
58442 | if( rc != UNQLITE_OK ){ | ||
58443 | return rc; | ||
58444 | } | ||
58445 | /* Perform a sanity check */ | ||
58446 | if( SyBlobLength(pHeader) < (2 /* magic */ + 8 /* record_id */ + 8 /* total_records */+ 4 /* DOS creation time*/) ){ | ||
58447 | return UNQLITE_CORRUPT; | ||
58448 | } | ||
58449 | zRaw = (unsigned char *)SyBlobData(pHeader); | ||
58450 | zEnd = &zRaw[SyBlobLength(pHeader)]; | ||
58451 | /* Extract the magic number */ | ||
58452 | SyBigEndianUnpack16(zRaw,&nMagic); | ||
58453 | if( nMagic != UNQLITE_COLLECTION_MAGIC ){ | ||
58454 | return UNQLITE_CORRUPT; | ||
58455 | } | ||
58456 | zRaw += 2; /* sizeof(sxu16) */ | ||
58457 | /* Extract the record ID */ | ||
58458 | SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nLastid); | ||
58459 | zRaw += 8; /* sizeof(sxu64) */ | ||
58460 | /* Total records in the collection */ | ||
58461 | SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nTotRec); | ||
58462 | /* Extract the collection creation date (DOS) */ | ||
58463 | zRaw += 8; /* sizeof(sxu64) */ | ||
58464 | SyBigEndianUnpack32(zRaw,&iDos); | ||
58465 | SyDosTimeFormat(iDos,&pCol->sCreation); | ||
58466 | zRaw += 4; | ||
58467 | /* Check for a collection schema */ | ||
58468 | pCol->nSchemaOfft = (sxu32)(zRaw - (unsigned char *)SyBlobData(pHeader)); | ||
58469 | if( zRaw < zEnd ){ | ||
58470 | /* Decode the FastJson value */ | ||
58471 | FastJsonDecode((const void *)zRaw,(sxu32)(zEnd-zRaw),&pCol->sSchema,0,0); | ||
58472 | } | ||
58473 | return UNQLITE_OK; | ||
58474 | } | ||
58475 | /* | ||
58476 | * Load or create a binary collection. | ||
58477 | */ | ||
58478 | static int unqliteVmLoadCollection( | ||
58479 | unqlite_vm *pVm, /* Target VM */ | ||
58480 | const char *zName, /* Collection name */ | ||
58481 | sxu32 nByte, /* zName length */ | ||
58482 | int iFlag, /* Control flag */ | ||
58483 | unqlite_col **ppOut /* OUT: in-memory collection */ | ||
58484 | ) | ||
58485 | { | ||
58486 | unqlite_kv_methods *pMethods; | ||
58487 | unqlite_kv_engine *pEngine; | ||
58488 | unqlite_kv_cursor *pCursor; | ||
58489 | unqlite *pDb = pVm->pDb; | ||
58490 | unqlite_col *pCol = 0; /* cc warning */ | ||
58491 | int rc = SXERR_MEM; | ||
58492 | char *zDup = 0; | ||
58493 | /* Point to the underlying KV store */ | ||
58494 | pEngine = unqlitePagerGetKvEngine(pVm->pDb); | ||
58495 | pMethods = pEngine->pIo->pMethods; | ||
58496 | /* Allocate a new cursor */ | ||
58497 | rc = unqliteInitCursor(pDb,&pCursor); | ||
58498 | if( rc != UNQLITE_OK ){ | ||
58499 | return rc; | ||
58500 | } | ||
58501 | if( (iFlag & UNQLITE_VM_COLLECTION_CREATE) == 0 ){ | ||
58502 | /* Seek to the desired location */ | ||
58503 | rc = pMethods->xSeek(pCursor,(const void *)zName,(unqlite_int64)nByte,UNQLITE_CURSOR_MATCH_EXACT); | ||
58504 | if( rc != UNQLITE_OK ){ | ||
58505 | unqliteGenErrorFormat(pDb,"Collection '%.*s' not defined in the underlying database",nByte,zName); | ||
58506 | unqliteReleaseCursor(pDb,pCursor); | ||
58507 | return rc; | ||
58508 | } | ||
58509 | } | ||
58510 | /* Allocate a new instance */ | ||
58511 | pCol = (unqlite_col *)SyMemBackendPoolAlloc(&pVm->sAlloc,sizeof(unqlite_col)); | ||
58512 | if( pCol == 0 ){ | ||
58513 | unqliteGenOutofMem(pDb); | ||
58514 | rc = UNQLITE_NOMEM; | ||
58515 | goto fail; | ||
58516 | } | ||
58517 | SyZero(pCol,sizeof(unqlite_col)); | ||
58518 | /* Fill in the structure */ | ||
58519 | SyBlobInit(&pCol->sWorker,&pVm->sAlloc); | ||
58520 | SyBlobInit(&pCol->sHeader,&pVm->sAlloc); | ||
58521 | pCol->pVm = pVm; | ||
58522 | pCol->pCursor = pCursor; | ||
58523 | /* Duplicate collection name */ | ||
58524 | zDup = SyMemBackendStrDup(&pVm->sAlloc,zName,nByte); | ||
58525 | if( zDup == 0 ){ | ||
58526 | unqliteGenOutofMem(pDb); | ||
58527 | rc = UNQLITE_NOMEM; | ||
58528 | goto fail; | ||
58529 | } | ||
58530 | pCol->nRecSize = 64; /* Must be a power of two */ | ||
58531 | pCol->apRecord = (unqlite_col_record **)SyMemBackendAlloc(&pVm->sAlloc,pCol->nRecSize * sizeof(unqlite_col_record *)); | ||
58532 | if( pCol->apRecord == 0 ){ | ||
58533 | unqliteGenOutofMem(pDb); | ||
58534 | rc = UNQLITE_NOMEM; | ||
58535 | goto fail; | ||
58536 | } | ||
58537 | /* Zero the table */ | ||
58538 | SyZero((void *)pCol->apRecord,pCol->nRecSize * sizeof(unqlite_col_record *)); | ||
58539 | SyStringInitFromBuf(&pCol->sName,zDup,nByte); | ||
58540 | jx9MemObjInit(pVm->pJx9Vm,&pCol->sSchema); | ||
58541 | if( iFlag & UNQLITE_VM_COLLECTION_CREATE ){ | ||
58542 | /* Create a new collection */ | ||
58543 | if( pMethods->xReplace == 0 ){ | ||
58544 | /* Read-only KV engine: Generate an error message and return */ | ||
58545 | unqliteGenErrorFormat(pDb, | ||
58546 | "Cannot create new collection '%z' due to a read-only Key/Value storage engine", | ||
58547 | &pCol->sName | ||
58548 | ); | ||
58549 | rc = UNQLITE_ABORT; /* Abort VM execution */ | ||
58550 | goto fail; | ||
58551 | } | ||
58552 | /* Write the collection header */ | ||
58553 | rc = CollectionSetHeader(pEngine,pCol,0,0,0); | ||
58554 | if( rc != UNQLITE_OK ){ | ||
58555 | rc = UNQLITE_ABORT; /* Abort VM execution */ | ||
58556 | goto fail; | ||
58557 | } | ||
58558 | }else{ | ||
58559 | /* Read the collection header */ | ||
58560 | rc = CollectionLoadHeader(pCol); | ||
58561 | if( rc != UNQLITE_OK ){ | ||
58562 | unqliteGenErrorFormat(pDb,"Corrupt collection '%z' header",&pCol->sName); | ||
58563 | goto fail; | ||
58564 | } | ||
58565 | } | ||
58566 | /* Finally install the collection */ | ||
58567 | unqliteVmInstallCollection(pVm,pCol); | ||
58568 | /* All done */ | ||
58569 | if( ppOut ){ | ||
58570 | *ppOut = pCol; | ||
58571 | } | ||
58572 | return UNQLITE_OK; | ||
58573 | fail: | ||
58574 | unqliteReleaseCursor(pDb,pCursor); | ||
58575 | if( zDup ){ | ||
58576 | SyMemBackendFree(&pVm->sAlloc,zDup); | ||
58577 | } | ||
58578 | if( pCol ){ | ||
58579 | if( pCol->apRecord ){ | ||
58580 | SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord); | ||
58581 | } | ||
58582 | SyBlobRelease(&pCol->sHeader); | ||
58583 | SyBlobRelease(&pCol->sWorker); | ||
58584 | jx9MemObjRelease(&pCol->sSchema); | ||
58585 | SyMemBackendPoolFree(&pVm->sAlloc,pCol); | ||
58586 | } | ||
58587 | return rc; | ||
58588 | } | ||
58589 | /* | ||
58590 | * Fetch a collection. | ||
58591 | */ | ||
58592 | UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch( | ||
58593 | unqlite_vm *pVm, /* Target VM */ | ||
58594 | SyString *pName, /* Lookup key */ | ||
58595 | int iFlag /* Control flag */ | ||
58596 | ) | ||
58597 | { | ||
58598 | unqlite_col *pCol = 0; /* cc warning */ | ||
58599 | int rc; | ||
58600 | /* Check if the collection is already loaded in memory */ | ||
58601 | pCol = unqliteVmFetchCollection(pVm,pName); | ||
58602 | if( pCol ){ | ||
58603 | /* Already loaded in memory*/ | ||
58604 | return pCol; | ||
58605 | } | ||
58606 | if( (iFlag & UNQLITE_VM_AUTO_LOAD) == 0 ){ | ||
58607 | return 0; | ||
58608 | } | ||
58609 | /* Ask the storage engine for the collection */ | ||
58610 | rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,0,&pCol); | ||
58611 | /* Return to the caller */ | ||
58612 | return rc == UNQLITE_OK ? pCol : 0; | ||
58613 | } | ||
58614 | /* | ||
58615 | * Return the unique ID of the last inserted record. | ||
58616 | */ | ||
58617 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol) | ||
58618 | { | ||
58619 | return pCol->nLastid == 0 ? 0 : (pCol->nLastid - 1); | ||
58620 | } | ||
58621 | /* | ||
58622 | * Return the current record ID. | ||
58623 | */ | ||
58624 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol) | ||
58625 | { | ||
58626 | return pCol->nCurid; | ||
58627 | } | ||
58628 | /* | ||
58629 | * Return the total number of records in a given collection. | ||
58630 | */ | ||
58631 | UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol) | ||
58632 | { | ||
58633 | return pCol->nTotRec; | ||
58634 | } | ||
58635 | /* | ||
58636 | * Reset the record cursor. | ||
58637 | */ | ||
58638 | UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol) | ||
58639 | { | ||
58640 | pCol->nCurid = 0; | ||
58641 | } | ||
58642 | /* | ||
58643 | * Fetch a record by its unique ID. | ||
58644 | */ | ||
58645 | UNQLITE_PRIVATE int unqliteCollectionFetchRecordById( | ||
58646 | unqlite_col *pCol, /* Target collection */ | ||
58647 | jx9_int64 nId, /* Unique record ID */ | ||
58648 | jx9_value *pValue /* OUT: record value */ | ||
58649 | ) | ||
58650 | { | ||
58651 | SyBlob *pWorker = &pCol->sWorker; | ||
58652 | unqlite_col_record *pRec; | ||
58653 | int rc; | ||
58654 | jx9_value_null(pValue); | ||
58655 | /* Perform a cache lookup first */ | ||
58656 | pRec = CollectionCacheFetchRecord(pCol,nId); | ||
58657 | if( pRec ){ | ||
58658 | /* Copy record value */ | ||
58659 | jx9MemObjStore(&pRec->sValue,pValue); | ||
58660 | return UNQLITE_OK; | ||
58661 | } | ||
58662 | /* Reset the working buffer */ | ||
58663 | SyBlobReset(pWorker); | ||
58664 | /* Generate the unique ID */ | ||
58665 | SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId); | ||
58666 | /* Reset the cursor */ | ||
58667 | unqlite_kv_cursor_reset(pCol->pCursor); | ||
58668 | /* Seek the cursor to the desired location */ | ||
58669 | rc = unqlite_kv_cursor_seek(pCol->pCursor, | ||
58670 | SyBlobData(pWorker),SyBlobLength(pWorker), | ||
58671 | UNQLITE_CURSOR_MATCH_EXACT | ||
58672 | ); | ||
58673 | if( rc != UNQLITE_OK ){ | ||
58674 | return rc; | ||
58675 | } | ||
58676 | /* Consume the binary JSON */ | ||
58677 | SyBlobReset(pWorker); | ||
58678 | unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pWorker); | ||
58679 | if( SyBlobLength(pWorker) < 1 ){ | ||
58680 | unqliteGenErrorFormat(pCol->pVm->pDb, | ||
58681 | "Empty record '%qd'",nId | ||
58682 | ); | ||
58683 | jx9_value_null(pValue); | ||
58684 | }else{ | ||
58685 | /* Decode the binary JSON */ | ||
58686 | rc = FastJsonDecode(SyBlobData(pWorker),SyBlobLength(pWorker),pValue,0,0); | ||
58687 | if( rc == UNQLITE_OK ){ | ||
58688 | /* Install the record in the cache */ | ||
58689 | CollectionCacheInstallRecord(pCol,nId,pValue); | ||
58690 | } | ||
58691 | } | ||
58692 | return rc; | ||
58693 | } | ||
58694 | /* | ||
58695 | * Fetch the next record from a given collection. | ||
58696 | */ | ||
58697 | UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue) | ||
58698 | { | ||
58699 | int rc; | ||
58700 | for(;;){ | ||
58701 | if( pCol->nCurid >= pCol->nLastid ){ | ||
58702 | /* No more records, reset the record cursor ID */ | ||
58703 | pCol->nCurid = 0; | ||
58704 | /* Return to the caller */ | ||
58705 | return SXERR_EOF; | ||
58706 | } | ||
58707 | rc = unqliteCollectionFetchRecordById(pCol,pCol->nCurid,pValue); | ||
58708 | /* Increment the record ID */ | ||
58709 | pCol->nCurid++; | ||
58710 | /* Lookup result */ | ||
58711 | if( rc == UNQLITE_OK || rc != UNQLITE_NOTFOUND ){ | ||
58712 | break; | ||
58713 | } | ||
58714 | } | ||
58715 | return rc; | ||
58716 | } | ||
58717 | /* | ||
58718 | * Create a new collection. | ||
58719 | */ | ||
58720 | UNQLITE_PRIVATE int unqliteCreateCollection( | ||
58721 | unqlite_vm *pVm, /* Target VM */ | ||
58722 | SyString *pName /* Collection name */ | ||
58723 | ) | ||
58724 | { | ||
58725 | unqlite_col *pCol; | ||
58726 | int rc; | ||
58727 | /* Perform a lookup first */ | ||
58728 | pCol = unqliteCollectionFetch(pVm,pName,UNQLITE_VM_AUTO_LOAD); | ||
58729 | if( pCol ){ | ||
58730 | return UNQLITE_EXISTS; | ||
58731 | } | ||
58732 | /* Now, safely create the collection */ | ||
58733 | rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_CREATE,0); | ||
58734 | return rc; | ||
58735 | } | ||
58736 | /* | ||
58737 | * Set a schema (JSON object) for a given collection. | ||
58738 | */ | ||
58739 | UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue) | ||
58740 | { | ||
58741 | int rc; | ||
58742 | if( !jx9_value_is_json_object(pValue) ){ | ||
58743 | /* Must be a JSON object */ | ||
58744 | return SXERR_INVALID; | ||
58745 | } | ||
58746 | rc = CollectionSetHeader(0,pCol,-1,-1,pValue); | ||
58747 | return rc; | ||
58748 | } | ||
58749 | /* | ||
58750 | * Perform a store operation on a given collection. | ||
58751 | */ | ||
58752 | static int CollectionStore( | ||
58753 | unqlite_col *pCol, /* Target collection */ | ||
58754 | jx9_value *pValue /* JSON value to be stored */ | ||
58755 | ) | ||
58756 | { | ||
58757 | SyBlob *pWorker = &pCol->sWorker; | ||
58758 | unqlite_kv_methods *pMethods; | ||
58759 | unqlite_kv_engine *pEngine; | ||
58760 | sxu32 nKeyLen; | ||
58761 | int rc; | ||
58762 | /* Point to the underlying KV store */ | ||
58763 | pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); | ||
58764 | pMethods = pEngine->pIo->pMethods; | ||
58765 | if( pCol->nTotRec >= SXI64_HIGH ){ | ||
58766 | /* Collection limit reached. No more records */ | ||
58767 | unqliteGenErrorFormat(pCol->pVm->pDb, | ||
58768 | "Collection '%z': Records limit reached", | ||
58769 | &pCol->sName | ||
58770 | ); | ||
58771 | return UNQLITE_LIMIT; | ||
58772 | } | ||
58773 | if( pMethods->xReplace == 0 ){ | ||
58774 | unqliteGenErrorFormat(pCol->pVm->pDb, | ||
58775 | "Cannot store record into collection '%z' due to a read-only Key/Value storage engine", | ||
58776 | &pCol->sName | ||
58777 | ); | ||
58778 | return UNQLITE_READ_ONLY; | ||
58779 | } | ||
58780 | /* Reset the working buffer */ | ||
58781 | SyBlobReset(pWorker); | ||
58782 | if( jx9_value_is_json_object(pValue) ){ | ||
58783 | jx9_value sId; | ||
58784 | /* If the given type is a JSON object, then add the special __id field */ | ||
58785 | jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,pCol->nLastid); | ||
58786 | jx9_array_add_strkey_elem(pValue,"__id",&sId); | ||
58787 | jx9MemObjRelease(&sId); | ||
58788 | } | ||
58789 | /* Prepare the unique ID for this record */ | ||
58790 | SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,pCol->nLastid); | ||
58791 | nKeyLen = SyBlobLength(pWorker); | ||
58792 | if( nKeyLen < 1 ){ | ||
58793 | unqliteGenOutofMem(pCol->pVm->pDb); | ||
58794 | return UNQLITE_NOMEM; | ||
58795 | } | ||
58796 | /* Turn to FastJson */ | ||
58797 | rc = FastJsonEncode(pValue,pWorker,0); | ||
58798 | if( rc != UNQLITE_OK ){ | ||
58799 | return rc; | ||
58800 | } | ||
58801 | /* Finally perform the insertion */ | ||
58802 | rc = pMethods->xReplace( | ||
58803 | pEngine, | ||
58804 | SyBlobData(pWorker),nKeyLen, | ||
58805 | SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen | ||
58806 | ); | ||
58807 | if( rc == UNQLITE_OK ){ | ||
58808 | /* Save the value in the cache */ | ||
58809 | CollectionCacheInstallRecord(pCol,pCol->nLastid,pValue); | ||
58810 | /* Increment the unique __id */ | ||
58811 | pCol->nLastid++; | ||
58812 | pCol->nTotRec++; | ||
58813 | /* Reflect the change */ | ||
58814 | rc = CollectionSetHeader(0,pCol,pCol->nLastid,pCol->nTotRec,0); | ||
58815 | } | ||
58816 | if( rc != UNQLITE_OK ){ | ||
58817 | unqliteGenErrorFormat(pCol->pVm->pDb, | ||
58818 | "IO error while storing record into collection '%z'", | ||
58819 | &pCol->sName | ||
58820 | ); | ||
58821 | return rc; | ||
58822 | } | ||
58823 | return UNQLITE_OK; | ||
58824 | } | ||
58825 | /* | ||
58826 | * Array walker callback (Refer to jx9_array_walk()). | ||
58827 | */ | ||
58828 | static int CollectionRecordArrayWalker(jx9_value *pKey,jx9_value *pData,void *pUserData) | ||
58829 | { | ||
58830 | unqlite_col *pCol = (unqlite_col *)pUserData; | ||
58831 | int rc; | ||
58832 | /* Perform the insertion */ | ||
58833 | rc = CollectionStore(pCol,pData); | ||
58834 | if( rc != UNQLITE_OK ){ | ||
58835 | SXUNUSED(pKey); /* cc warning */ | ||
58836 | } | ||
58837 | return rc; | ||
58838 | } | ||
58839 | /* | ||
58840 | * Perform a store operation on a given collection. | ||
58841 | */ | ||
58842 | UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag) | ||
58843 | { | ||
58844 | int rc; | ||
58845 | if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){ | ||
58846 | /* Iterate over the array and store its members in the collection */ | ||
58847 | rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol); | ||
58848 | SXUNUSED(iFlag); /* cc warning */ | ||
58849 | }else{ | ||
58850 | rc = CollectionStore(pCol,pValue); | ||
58851 | } | ||
58852 | return rc; | ||
58853 | } | ||
58854 | /* | ||
58855 | * Drop a record from a given collection. | ||
58856 | */ | ||
58857 | UNQLITE_PRIVATE int unqliteCollectionDropRecord( | ||
58858 | unqlite_col *pCol, /* Target collection */ | ||
58859 | jx9_int64 nId, /* Unique ID of the record to be droped */ | ||
58860 | int wr_header, /* True to alter collection header */ | ||
58861 | int log_err /* True to log error */ | ||
58862 | ) | ||
58863 | { | ||
58864 | SyBlob *pWorker = &pCol->sWorker; | ||
58865 | int rc; | ||
58866 | /* Reset the working buffer */ | ||
58867 | SyBlobReset(pWorker); | ||
58868 | /* Prepare the unique ID for this record */ | ||
58869 | SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId); | ||
58870 | /* Reset the cursor */ | ||
58871 | unqlite_kv_cursor_reset(pCol->pCursor); | ||
58872 | /* Seek the cursor to the desired location */ | ||
58873 | rc = unqlite_kv_cursor_seek(pCol->pCursor, | ||
58874 | SyBlobData(pWorker),SyBlobLength(pWorker), | ||
58875 | UNQLITE_CURSOR_MATCH_EXACT | ||
58876 | ); | ||
58877 | if( rc != UNQLITE_OK ){ | ||
58878 | return rc; | ||
58879 | } | ||
58880 | /* Remove the record from the storage engine */ | ||
58881 | rc = unqlite_kv_cursor_delete_entry(pCol->pCursor); | ||
58882 | /* Finally, Remove the record from the cache */ | ||
58883 | unqliteCollectionCacheRemoveRecord(pCol,nId); | ||
58884 | if( rc == UNQLITE_OK ){ | ||
58885 | pCol->nTotRec--; | ||
58886 | if( wr_header ){ | ||
58887 | /* Relect in the collection header */ | ||
58888 | rc = CollectionSetHeader(0,pCol,-1,pCol->nTotRec,0); | ||
58889 | } | ||
58890 | }else if( rc == UNQLITE_NOTIMPLEMENTED ){ | ||
58891 | if( log_err ){ | ||
58892 | unqliteGenErrorFormat(pCol->pVm->pDb, | ||
58893 | "Cannot delete record from collection '%z' due to a read-only Key/Value storage engine", | ||
58894 | &pCol->sName | ||
58895 | ); | ||
58896 | } | ||
58897 | } | ||
58898 | return rc; | ||
58899 | } | ||
58900 | /* | ||
58901 | * Drop a collection from the KV storage engine and the underlying | ||
58902 | * unqlite VM. | ||
58903 | */ | ||
58904 | UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol) | ||
58905 | { | ||
58906 | unqlite_vm *pVm = pCol->pVm; | ||
58907 | jx9_int64 nId; | ||
58908 | int rc; | ||
58909 | /* Reset the cursor */ | ||
58910 | unqlite_kv_cursor_reset(pCol->pCursor); | ||
58911 | /* Seek the cursor to the desired location */ | ||
58912 | rc = unqlite_kv_cursor_seek(pCol->pCursor, | ||
58913 | SyStringData(&pCol->sName),SyStringLength(&pCol->sName), | ||
58914 | UNQLITE_CURSOR_MATCH_EXACT | ||
58915 | ); | ||
58916 | if( rc == UNQLITE_OK ){ | ||
58917 | /* Remove the record from the storage engine */ | ||
58918 | rc = unqlite_kv_cursor_delete_entry(pCol->pCursor); | ||
58919 | } | ||
58920 | if( rc != UNQLITE_OK ){ | ||
58921 | unqliteGenErrorFormat(pCol->pVm->pDb, | ||
58922 | "Cannot remove collection '%z' due to a read-only Key/Value storage engine", | ||
58923 | &pCol->sName | ||
58924 | ); | ||
58925 | return rc; | ||
58926 | } | ||
58927 | /* Drop collection records */ | ||
58928 | for( nId = 0 ; nId < pCol->nLastid ; ++nId ){ | ||
58929 | unqliteCollectionDropRecord(pCol,nId,0,0); | ||
58930 | } | ||
58931 | /* Cleanup */ | ||
58932 | CollectionCacheRelease(pCol); | ||
58933 | SyBlobRelease(&pCol->sHeader); | ||
58934 | SyBlobRelease(&pCol->sWorker); | ||
58935 | SyMemBackendFree(&pVm->sAlloc,(void *)SyStringData(&pCol->sName)); | ||
58936 | unqliteReleaseCursor(pVm->pDb,pCol->pCursor); | ||
58937 | /* Unlink */ | ||
58938 | if( pCol->pPrevCol ){ | ||
58939 | pCol->pPrevCol->pNextCol = pCol->pNextCol; | ||
58940 | }else{ | ||
58941 | sxu32 iBucket = pCol->nHash & (pVm->iColSize - 1); | ||
58942 | pVm->apCol[iBucket] = pCol->pNextCol; | ||
58943 | } | ||
58944 | if( pCol->pNextCol ){ | ||
58945 | pCol->pNextCol->pPrevCol = pCol->pPrevCol; | ||
58946 | } | ||
58947 | MACRO_LD_REMOVE(pVm->pCol,pCol); | ||
58948 | pVm->iCol--; | ||
58949 | SyMemBackendPoolFree(&pVm->sAlloc,pCol); | ||
58950 | return UNQLITE_OK; | ||
58951 | } | ||
58952 | /* | ||
58953 | * ---------------------------------------------------------- | ||
58954 | * File: unqlite_jx9.c | ||
58955 | * MD5: 8fddc15b667e85d7b5df5367132518fb | ||
58956 | * ---------------------------------------------------------- | ||
58957 | */ | ||
58958 | /* | ||
58959 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
58960 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
58961 | * Version 1.1.6 | ||
58962 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
58963 | * please contact Symisc Systems via: | ||
58964 | * legal@symisc.net | ||
58965 | * licensing@symisc.net | ||
58966 | * contact@symisc.net | ||
58967 | * or visit: | ||
58968 | * http://unqlite.org/licensing.html | ||
58969 | */ | ||
58970 | /* $SymiscID: unql_jx9.c v1.2 FreeBSD 2013-01-24 22:45 stable <chm@symisc.net> $ */ | ||
58971 | #ifndef UNQLITE_AMALGAMATION | ||
58972 | #include "unqliteInt.h" | ||
58973 | #endif | ||
58974 | /* | ||
58975 | * This file implements UnQLite functions (db_exists(), db_create(), db_put(), db_get(), etc.) for the | ||
58976 | * underlying Jx9 Virtual Machine. | ||
58977 | */ | ||
58978 | /* | ||
58979 | * string db_version(void) | ||
58980 | * Return the current version of the unQLite database engine. | ||
58981 | * Parameter | ||
58982 | * None | ||
58983 | * Return | ||
58984 | * unQLite version number (string). | ||
58985 | */ | ||
58986 | static int unqliteBuiltin_db_version(jx9_context *pCtx,int argc,jx9_value **argv) | ||
58987 | { | ||
58988 | SXUNUSED(argc); /* cc warning */ | ||
58989 | SXUNUSED(argv); | ||
58990 | jx9_result_string(pCtx,UNQLITE_VERSION,(int)sizeof(UNQLITE_VERSION)-1); | ||
58991 | return JX9_OK; | ||
58992 | } | ||
58993 | /* | ||
58994 | * string db_errlog(void) | ||
58995 | * Return the database error log. | ||
58996 | * Parameter | ||
58997 | * None | ||
58998 | * Return | ||
58999 | * Database error log (string). | ||
59000 | */ | ||
59001 | static int unqliteBuiltin_db_errlog(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59002 | { | ||
59003 | unqlite_vm *pVm; | ||
59004 | SyBlob *pErr; | ||
59005 | |||
59006 | SXUNUSED(argc); /* cc warning */ | ||
59007 | SXUNUSED(argv); | ||
59008 | |||
59009 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59010 | /* Point to the error log */ | ||
59011 | pErr = &pVm->pDb->sErr; | ||
59012 | /* Return the log */ | ||
59013 | jx9_result_string(pCtx,(const char *)SyBlobData(pErr),(int)SyBlobLength(pErr)); | ||
59014 | return JX9_OK; | ||
59015 | } | ||
59016 | /* | ||
59017 | * string db_copyright(void) | ||
59018 | * string db_credits(void) | ||
59019 | * Return the unQLite database engine copyright notice. | ||
59020 | * Parameter | ||
59021 | * None | ||
59022 | * Return | ||
59023 | * Copyright notice. | ||
59024 | */ | ||
59025 | static int unqliteBuiltin_db_credits(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59026 | { | ||
59027 | SXUNUSED(argc); /* cc warning */ | ||
59028 | SXUNUSED(argv); | ||
59029 | jx9_result_string(pCtx,UNQLITE_COPYRIGHT,(int)sizeof(UNQLITE_COPYRIGHT)-1); | ||
59030 | return JX9_OK; | ||
59031 | } | ||
59032 | /* | ||
59033 | * string db_sig(void) | ||
59034 | * Return the unQLite database engine unique signature. | ||
59035 | * Parameter | ||
59036 | * None | ||
59037 | * Return | ||
59038 | * unQLite signature. | ||
59039 | */ | ||
59040 | static int unqliteBuiltin_db_sig(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59041 | { | ||
59042 | SXUNUSED(argc); /* cc warning */ | ||
59043 | SXUNUSED(argv); | ||
59044 | jx9_result_string(pCtx,UNQLITE_IDENT,sizeof(UNQLITE_IDENT)-1); | ||
59045 | return JX9_OK; | ||
59046 | } | ||
59047 | /* | ||
59048 | * bool collection_exists(string $name) | ||
59049 | * bool db_exits(string $name) | ||
59050 | * Check if a given collection exists in the underlying database. | ||
59051 | * Parameter | ||
59052 | * name: Lookup name | ||
59053 | * Return | ||
59054 | * TRUE if the collection exits. FALSE otherwise. | ||
59055 | */ | ||
59056 | static int unqliteBuiltin_collection_exists(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59057 | { | ||
59058 | unqlite_col *pCol; | ||
59059 | const char *zName; | ||
59060 | unqlite_vm *pVm; | ||
59061 | SyString sName; | ||
59062 | int nByte; | ||
59063 | /* Extract collection name */ | ||
59064 | if( argc < 1 ){ | ||
59065 | /* Missing arguments */ | ||
59066 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59067 | /* Return false */ | ||
59068 | jx9_result_bool(pCtx,0); | ||
59069 | return JX9_OK; | ||
59070 | } | ||
59071 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59072 | if( nByte < 1){ | ||
59073 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59074 | /* Return false */ | ||
59075 | jx9_result_bool(pCtx,0); | ||
59076 | return JX9_OK; | ||
59077 | } | ||
59078 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59079 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59080 | /* Perform the lookup */ | ||
59081 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59082 | /* Lookup result */ | ||
59083 | jx9_result_bool(pCtx,pCol ? 1 : 0); | ||
59084 | return JX9_OK; | ||
59085 | } | ||
59086 | /* | ||
59087 | * bool collection_create(string $name) | ||
59088 | * bool db_create(string $name) | ||
59089 | * Create a new collection. | ||
59090 | * Parameter | ||
59091 | * name: Collection name | ||
59092 | * Return | ||
59093 | * TRUE if the collection was successfuly created. FALSE otherwise. | ||
59094 | */ | ||
59095 | static int unqliteBuiltin_collection_create(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59096 | { | ||
59097 | const char *zName; | ||
59098 | unqlite_vm *pVm; | ||
59099 | SyString sName; | ||
59100 | int nByte; | ||
59101 | int rc; | ||
59102 | /* Extract collection name */ | ||
59103 | if( argc < 1 ){ | ||
59104 | /* Missing arguments */ | ||
59105 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59106 | /* Return false */ | ||
59107 | jx9_result_bool(pCtx,0); | ||
59108 | return JX9_OK; | ||
59109 | } | ||
59110 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59111 | if( nByte < 1){ | ||
59112 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59113 | /* Return false */ | ||
59114 | jx9_result_bool(pCtx,0); | ||
59115 | return JX9_OK; | ||
59116 | } | ||
59117 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59118 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59119 | /* Try to create the collection */ | ||
59120 | rc = unqliteCreateCollection(pVm,&sName); | ||
59121 | /* Return the result to the caller */ | ||
59122 | jx9_result_bool(pCtx,rc == UNQLITE_OK ? 1 : 0); | ||
59123 | return JX9_OK; | ||
59124 | } | ||
59125 | /* | ||
59126 | * value db_fetch(string $col_name) | ||
59127 | * value db_get(string $col_name) | ||
59128 | * Fetch the current record from a given collection and advance | ||
59129 | * the record cursor. | ||
59130 | * Parameter | ||
59131 | * col_name: Collection name | ||
59132 | * Return | ||
59133 | * Record content success. NULL on failure (No more records to retrieve). | ||
59134 | */ | ||
59135 | static int unqliteBuiltin_db_fetch_next(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59136 | { | ||
59137 | unqlite_col *pCol; | ||
59138 | const char *zName; | ||
59139 | unqlite_vm *pVm; | ||
59140 | SyString sName; | ||
59141 | int nByte; | ||
59142 | int rc; | ||
59143 | /* Extract collection name */ | ||
59144 | if( argc < 1 ){ | ||
59145 | /* Missing arguments */ | ||
59146 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59147 | /* Return null */ | ||
59148 | jx9_result_null(pCtx); | ||
59149 | return JX9_OK; | ||
59150 | } | ||
59151 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59152 | if( nByte < 1){ | ||
59153 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59154 | /* Return null */ | ||
59155 | jx9_result_null(pCtx); | ||
59156 | return JX9_OK; | ||
59157 | } | ||
59158 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59159 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59160 | /* Fetch the collection */ | ||
59161 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59162 | if( pCol ){ | ||
59163 | /* Fetch the current record */ | ||
59164 | jx9_value *pValue; | ||
59165 | pValue = jx9_context_new_scalar(pCtx); | ||
59166 | if( pValue == 0 ){ | ||
59167 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); | ||
59168 | jx9_result_null(pCtx); | ||
59169 | return JX9_OK; | ||
59170 | }else{ | ||
59171 | rc = unqliteCollectionFetchNextRecord(pCol,pValue); | ||
59172 | if( rc == UNQLITE_OK ){ | ||
59173 | jx9_result_value(pCtx,pValue); | ||
59174 | /* pValue will be automatically released as soon we return from this function */ | ||
59175 | }else{ | ||
59176 | /* Return null */ | ||
59177 | jx9_result_null(pCtx); | ||
59178 | } | ||
59179 | } | ||
59180 | }else{ | ||
59181 | /* No such collection, return null */ | ||
59182 | jx9_result_null(pCtx); | ||
59183 | } | ||
59184 | return JX9_OK; | ||
59185 | } | ||
59186 | /* | ||
59187 | * value db_fetch_by_id(string $col_name,int64 $record_id) | ||
59188 | * value db_get_by_id(string $col_name,int64 $record_id) | ||
59189 | * Fetch a record using its unique ID from a given collection. | ||
59190 | * Parameter | ||
59191 | * col_name: Collection name | ||
59192 | * record_id: Record number (__id field of a JSON object) | ||
59193 | * Return | ||
59194 | * Record content success. NULL on failure (No such record). | ||
59195 | */ | ||
59196 | static int unqliteBuiltin_db_fetch_by_id(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59197 | { | ||
59198 | unqlite_col *pCol; | ||
59199 | const char *zName; | ||
59200 | unqlite_vm *pVm; | ||
59201 | SyString sName; | ||
59202 | jx9_int64 nId; | ||
59203 | int nByte; | ||
59204 | int rc; | ||
59205 | /* Extract collection name */ | ||
59206 | if( argc < 2 ){ | ||
59207 | /* Missing arguments */ | ||
59208 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or record ID"); | ||
59209 | /* Return NULL */ | ||
59210 | jx9_result_null(pCtx); | ||
59211 | return JX9_OK; | ||
59212 | } | ||
59213 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59214 | if( nByte < 1){ | ||
59215 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59216 | /* Return NULL */ | ||
59217 | jx9_result_null(pCtx); | ||
59218 | return JX9_OK; | ||
59219 | } | ||
59220 | /* Extract the record ID */ | ||
59221 | nId = jx9_value_to_int(argv[1]); | ||
59222 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59223 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59224 | /* Fetch the collection */ | ||
59225 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59226 | if( pCol ){ | ||
59227 | /* Fetch the desired record */ | ||
59228 | jx9_value *pValue; | ||
59229 | pValue = jx9_context_new_scalar(pCtx); | ||
59230 | if( pValue == 0 ){ | ||
59231 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); | ||
59232 | jx9_result_null(pCtx); | ||
59233 | return JX9_OK; | ||
59234 | }else{ | ||
59235 | rc = unqliteCollectionFetchRecordById(pCol,nId,pValue); | ||
59236 | if( rc == UNQLITE_OK ){ | ||
59237 | jx9_result_value(pCtx,pValue); | ||
59238 | /* pValue will be automatically released as soon we return from this function */ | ||
59239 | }else{ | ||
59240 | /* No such record, return null */ | ||
59241 | jx9_result_null(pCtx); | ||
59242 | } | ||
59243 | } | ||
59244 | }else{ | ||
59245 | /* No such collection, return null */ | ||
59246 | jx9_result_null(pCtx); | ||
59247 | } | ||
59248 | return JX9_OK; | ||
59249 | } | ||
59250 | /* | ||
59251 | * array db_fetch_all(string $col_name,[callback filter_callback]) | ||
59252 | * array db_get_all(string $col_name,[callback filter_callback]) | ||
59253 | * Retrieve all records of a given collection and apply the given | ||
59254 | * callback if available to filter records. | ||
59255 | * Parameter | ||
59256 | * col_name: Collection name | ||
59257 | * Return | ||
59258 | * Contents of the collection (JSON array) on success. NULL on failure. | ||
59259 | */ | ||
59260 | static int unqliteBuiltin_db_fetch_all(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59261 | { | ||
59262 | unqlite_col *pCol; | ||
59263 | const char *zName; | ||
59264 | unqlite_vm *pVm; | ||
59265 | SyString sName; | ||
59266 | int nByte; | ||
59267 | int rc; | ||
59268 | /* Extract collection name */ | ||
59269 | if( argc < 1 ){ | ||
59270 | /* Missing arguments */ | ||
59271 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59272 | /* Return NULL */ | ||
59273 | jx9_result_null(pCtx); | ||
59274 | return JX9_OK; | ||
59275 | } | ||
59276 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59277 | if( nByte < 1){ | ||
59278 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59279 | /* Return NULL */ | ||
59280 | jx9_result_null(pCtx); | ||
59281 | return JX9_OK; | ||
59282 | } | ||
59283 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59284 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59285 | /* Fetch the collection */ | ||
59286 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59287 | if( pCol ){ | ||
59288 | jx9_value *pValue,*pArray,*pCallback = 0; | ||
59289 | jx9_value sResult; /* Callback result */ | ||
59290 | /* Allocate an empty scalar value and an empty JSON array */ | ||
59291 | pArray = jx9_context_new_array(pCtx); | ||
59292 | pValue = jx9_context_new_scalar(pCtx); | ||
59293 | jx9MemObjInit(pCtx->pVm,&sResult); | ||
59294 | if( pValue == 0 || pArray == 0 ){ | ||
59295 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); | ||
59296 | jx9_result_null(pCtx); | ||
59297 | return JX9_OK; | ||
59298 | } | ||
59299 | if( argc > 1 && jx9_value_is_callable(argv[1]) ){ | ||
59300 | pCallback = argv[1]; | ||
59301 | } | ||
59302 | unqliteCollectionResetRecordCursor(pCol); | ||
59303 | /* Fetch collection records one after one */ | ||
59304 | while( UNQLITE_OK == unqliteCollectionFetchNextRecord(pCol,pValue) ){ | ||
59305 | if( pCallback ){ | ||
59306 | jx9_value *apArg[2]; | ||
59307 | /* Invoke the filter callback */ | ||
59308 | apArg[0] = pValue; | ||
59309 | rc = jx9VmCallUserFunction(pCtx->pVm,pCallback,1,apArg,&sResult); | ||
59310 | if( rc == JX9_OK ){ | ||
59311 | int iResult; /* Callback result */ | ||
59312 | /* Extract callback result */ | ||
59313 | iResult = jx9_value_to_bool(&sResult); | ||
59314 | if( !iResult ){ | ||
59315 | /* Discard the result */ | ||
59316 | unqliteCollectionCacheRemoveRecord(pCol,unqliteCollectionCurrentRecordId(pCol) - 1); | ||
59317 | continue; | ||
59318 | } | ||
59319 | } | ||
59320 | } | ||
59321 | /* Put the value in the JSON array */ | ||
59322 | jx9_array_add_elem(pArray,0,pValue); | ||
59323 | /* Release the value */ | ||
59324 | jx9_value_null(pValue); | ||
59325 | } | ||
59326 | jx9MemObjRelease(&sResult); | ||
59327 | /* Finally, return our array */ | ||
59328 | jx9_result_value(pCtx,pArray); | ||
59329 | /* pValue will be automatically released as soon we return from | ||
59330 | * this foreign function. | ||
59331 | */ | ||
59332 | }else{ | ||
59333 | /* No such collection, return null */ | ||
59334 | jx9_result_null(pCtx); | ||
59335 | } | ||
59336 | return JX9_OK; | ||
59337 | } | ||
59338 | /* | ||
59339 | * int64 db_last_record_id(string $col_name) | ||
59340 | * Return the ID of the last inserted record. | ||
59341 | * Parameter | ||
59342 | * col_name: Collection name | ||
59343 | * Return | ||
59344 | * Record ID (64-bit integer) on success. FALSE on failure. | ||
59345 | */ | ||
59346 | static int unqliteBuiltin_db_last_record_id(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59347 | { | ||
59348 | unqlite_col *pCol; | ||
59349 | const char *zName; | ||
59350 | unqlite_vm *pVm; | ||
59351 | SyString sName; | ||
59352 | int nByte; | ||
59353 | /* Extract collection name */ | ||
59354 | if( argc < 1 ){ | ||
59355 | /* Missing arguments */ | ||
59356 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59357 | /* Return false */ | ||
59358 | jx9_result_bool(pCtx,0); | ||
59359 | return JX9_OK; | ||
59360 | } | ||
59361 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59362 | if( nByte < 1){ | ||
59363 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59364 | /* Return false */ | ||
59365 | jx9_result_bool(pCtx,0); | ||
59366 | return JX9_OK; | ||
59367 | } | ||
59368 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59369 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59370 | /* Fetch the collection */ | ||
59371 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59372 | if( pCol ){ | ||
59373 | jx9_result_int64(pCtx,unqliteCollectionLastRecordId(pCol)); | ||
59374 | }else{ | ||
59375 | /* No such collection, return FALSE */ | ||
59376 | jx9_result_bool(pCtx,0); | ||
59377 | } | ||
59378 | return JX9_OK; | ||
59379 | } | ||
59380 | /* | ||
59381 | * inr64 db_current_record_id(string $col_name) | ||
59382 | * Return the current record ID. | ||
59383 | * Parameter | ||
59384 | * col_name: Collection name | ||
59385 | * Return | ||
59386 | * Current record ID (64-bit integer) on success. FALSE on failure. | ||
59387 | */ | ||
59388 | static int unqliteBuiltin_db_current_record_id(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59389 | { | ||
59390 | unqlite_col *pCol; | ||
59391 | const char *zName; | ||
59392 | unqlite_vm *pVm; | ||
59393 | SyString sName; | ||
59394 | int nByte; | ||
59395 | /* Extract collection name */ | ||
59396 | if( argc < 1 ){ | ||
59397 | /* Missing arguments */ | ||
59398 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59399 | /* Return false */ | ||
59400 | jx9_result_bool(pCtx,0); | ||
59401 | return JX9_OK; | ||
59402 | } | ||
59403 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59404 | if( nByte < 1){ | ||
59405 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59406 | /* Return false */ | ||
59407 | jx9_result_bool(pCtx,0); | ||
59408 | return JX9_OK; | ||
59409 | } | ||
59410 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59411 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59412 | /* Fetch the collection */ | ||
59413 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59414 | if( pCol ){ | ||
59415 | jx9_result_int64(pCtx,unqliteCollectionCurrentRecordId(pCol)); | ||
59416 | }else{ | ||
59417 | /* No such collection, return FALSE */ | ||
59418 | jx9_result_bool(pCtx,0); | ||
59419 | } | ||
59420 | return JX9_OK; | ||
59421 | } | ||
59422 | /* | ||
59423 | * bool db_reset_record_cursor(string $col_name) | ||
59424 | * Reset the record ID cursor. | ||
59425 | * Parameter | ||
59426 | * col_name: Collection name | ||
59427 | * Return | ||
59428 | * TRUE on success. FALSE on failure. | ||
59429 | */ | ||
59430 | static int unqliteBuiltin_db_reset_record_cursor(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59431 | { | ||
59432 | unqlite_col *pCol; | ||
59433 | const char *zName; | ||
59434 | unqlite_vm *pVm; | ||
59435 | SyString sName; | ||
59436 | int nByte; | ||
59437 | /* Extract collection name */ | ||
59438 | if( argc < 1 ){ | ||
59439 | /* Missing arguments */ | ||
59440 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59441 | /* Return false */ | ||
59442 | jx9_result_bool(pCtx,0); | ||
59443 | return JX9_OK; | ||
59444 | } | ||
59445 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59446 | if( nByte < 1){ | ||
59447 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59448 | /* Return false */ | ||
59449 | jx9_result_bool(pCtx,0); | ||
59450 | return JX9_OK; | ||
59451 | } | ||
59452 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59453 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59454 | /* Fetch the collection */ | ||
59455 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59456 | if( pCol ){ | ||
59457 | unqliteCollectionResetRecordCursor(pCol); | ||
59458 | jx9_result_bool(pCtx,1); | ||
59459 | }else{ | ||
59460 | /* No such collection */ | ||
59461 | jx9_result_bool(pCtx,0); | ||
59462 | } | ||
59463 | return JX9_OK; | ||
59464 | } | ||
59465 | /* | ||
59466 | * int64 db_total_records(string $col_name) | ||
59467 | * Return the total number of inserted records in the given collection. | ||
59468 | * Parameter | ||
59469 | * col_name: Collection name | ||
59470 | * Return | ||
59471 | * Total number of records on success. FALSE on failure. | ||
59472 | */ | ||
59473 | static int unqliteBuiltin_db_total_records(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59474 | { | ||
59475 | unqlite_col *pCol; | ||
59476 | const char *zName; | ||
59477 | unqlite_vm *pVm; | ||
59478 | SyString sName; | ||
59479 | int nByte; | ||
59480 | /* Extract collection name */ | ||
59481 | if( argc < 1 ){ | ||
59482 | /* Missing arguments */ | ||
59483 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59484 | /* Return false */ | ||
59485 | jx9_result_bool(pCtx,0); | ||
59486 | return JX9_OK; | ||
59487 | } | ||
59488 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59489 | if( nByte < 1){ | ||
59490 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59491 | /* Return false */ | ||
59492 | jx9_result_bool(pCtx,0); | ||
59493 | return JX9_OK; | ||
59494 | } | ||
59495 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59496 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59497 | /* Fetch the collection */ | ||
59498 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59499 | if( pCol ){ | ||
59500 | unqlite_int64 nRec; | ||
59501 | nRec = unqliteCollectionTotalRecords(pCol); | ||
59502 | jx9_result_int64(pCtx,nRec); | ||
59503 | }else{ | ||
59504 | /* No such collection */ | ||
59505 | jx9_result_bool(pCtx,0); | ||
59506 | } | ||
59507 | return JX9_OK; | ||
59508 | } | ||
59509 | /* | ||
59510 | * string db_creation_date(string $col_name) | ||
59511 | * Return the creation date of the given collection. | ||
59512 | * Parameter | ||
59513 | * col_name: Collection name | ||
59514 | * Return | ||
59515 | * Creation date on success. FALSE on failure. | ||
59516 | */ | ||
59517 | static int unqliteBuiltin_db_creation_date(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59518 | { | ||
59519 | unqlite_col *pCol; | ||
59520 | const char *zName; | ||
59521 | unqlite_vm *pVm; | ||
59522 | SyString sName; | ||
59523 | int nByte; | ||
59524 | /* Extract collection name */ | ||
59525 | if( argc < 1 ){ | ||
59526 | /* Missing arguments */ | ||
59527 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59528 | /* Return false */ | ||
59529 | jx9_result_bool(pCtx,0); | ||
59530 | return JX9_OK; | ||
59531 | } | ||
59532 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59533 | if( nByte < 1){ | ||
59534 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59535 | /* Return false */ | ||
59536 | jx9_result_bool(pCtx,0); | ||
59537 | return JX9_OK; | ||
59538 | } | ||
59539 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59540 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59541 | /* Fetch the collection */ | ||
59542 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59543 | if( pCol ){ | ||
59544 | Sytm *pTm = &pCol->sCreation; | ||
59545 | jx9_result_string_format(pCtx,"%d-%d-%d %02d:%02d:%02d", | ||
59546 | pTm->tm_year,pTm->tm_mon,pTm->tm_mday, | ||
59547 | pTm->tm_hour,pTm->tm_min,pTm->tm_sec | ||
59548 | ); | ||
59549 | }else{ | ||
59550 | /* No such collection */ | ||
59551 | jx9_result_bool(pCtx,0); | ||
59552 | } | ||
59553 | return JX9_OK; | ||
59554 | } | ||
59555 | /* | ||
59556 | * bool db_store(string $col_name,...) | ||
59557 | * bool db_put(string $col_name,...) | ||
59558 | * Store one or more JSON values in a given collection. | ||
59559 | * Parameter | ||
59560 | * col_name: Collection name | ||
59561 | * Return | ||
59562 | * TRUE on success. FALSE on failure. | ||
59563 | */ | ||
59564 | static int unqliteBuiltin_db_store(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59565 | { | ||
59566 | unqlite_col *pCol; | ||
59567 | const char *zName; | ||
59568 | unqlite_vm *pVm; | ||
59569 | SyString sName; | ||
59570 | int nByte; | ||
59571 | int rc; | ||
59572 | int i; | ||
59573 | /* Extract collection name */ | ||
59574 | if( argc < 2 ){ | ||
59575 | /* Missing arguments */ | ||
59576 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); | ||
59577 | /* Return false */ | ||
59578 | jx9_result_bool(pCtx,0); | ||
59579 | return JX9_OK; | ||
59580 | } | ||
59581 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59582 | if( nByte < 1){ | ||
59583 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59584 | /* Return false */ | ||
59585 | jx9_result_bool(pCtx,0); | ||
59586 | return JX9_OK; | ||
59587 | } | ||
59588 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59589 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59590 | /* Fetch the collection */ | ||
59591 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59592 | if( pCol == 0 ){ | ||
59593 | jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); | ||
59594 | /* Return false */ | ||
59595 | jx9_result_bool(pCtx,0); | ||
59596 | return JX9_OK; | ||
59597 | } | ||
59598 | /* Store the given values */ | ||
59599 | for( i = 1 ; i < argc ; ++i ){ | ||
59600 | rc = unqliteCollectionPut(pCol,argv[i],0); | ||
59601 | if( rc != UNQLITE_OK){ | ||
59602 | jx9_context_throw_error_format(pCtx,JX9_CTX_ERR, | ||
59603 | "Error while storing record %d in collection '%z'",i,&sName | ||
59604 | ); | ||
59605 | /* Return false */ | ||
59606 | jx9_result_bool(pCtx,0); | ||
59607 | return JX9_OK; | ||
59608 | } | ||
59609 | } | ||
59610 | /* All done, return TRUE */ | ||
59611 | jx9_result_bool(pCtx,1); | ||
59612 | return JX9_OK; | ||
59613 | } | ||
59614 | /* | ||
59615 | * bool db_drop_collection(string $col_name) | ||
59616 | * bool collection_delete(string $col_name) | ||
59617 | * Remove a given collection from the database. | ||
59618 | * Parameter | ||
59619 | * col_name: Collection name | ||
59620 | * Return | ||
59621 | * TRUE on success. FALSE on failure. | ||
59622 | */ | ||
59623 | static int unqliteBuiltin_db_drop_col(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59624 | { | ||
59625 | unqlite_col *pCol; | ||
59626 | const char *zName; | ||
59627 | unqlite_vm *pVm; | ||
59628 | SyString sName; | ||
59629 | int nByte; | ||
59630 | int rc; | ||
59631 | /* Extract collection name */ | ||
59632 | if( argc < 1 ){ | ||
59633 | /* Missing arguments */ | ||
59634 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); | ||
59635 | /* Return false */ | ||
59636 | jx9_result_bool(pCtx,0); | ||
59637 | return JX9_OK; | ||
59638 | } | ||
59639 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59640 | if( nByte < 1){ | ||
59641 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59642 | /* Return false */ | ||
59643 | jx9_result_bool(pCtx,0); | ||
59644 | return JX9_OK; | ||
59645 | } | ||
59646 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59647 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59648 | /* Fetch the collection */ | ||
59649 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59650 | if( pCol == 0 ){ | ||
59651 | jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); | ||
59652 | /* Return false */ | ||
59653 | jx9_result_bool(pCtx,0); | ||
59654 | return JX9_OK; | ||
59655 | } | ||
59656 | /* Drop the collection */ | ||
59657 | rc = unqliteDropCollection(pCol); | ||
59658 | /* Processing result */ | ||
59659 | jx9_result_bool(pCtx,rc == UNQLITE_OK); | ||
59660 | return JX9_OK; | ||
59661 | } | ||
59662 | /* | ||
59663 | * bool db_drop_record(string $col_name,int64 record_id) | ||
59664 | * Remove a given record from a collection. | ||
59665 | * Parameter | ||
59666 | * col_name: Collection name. | ||
59667 | * record_id: ID of the record. | ||
59668 | * Return | ||
59669 | * TRUE on success. FALSE on failure. | ||
59670 | */ | ||
59671 | static int unqliteBuiltin_db_drop_record(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59672 | { | ||
59673 | unqlite_col *pCol; | ||
59674 | const char *zName; | ||
59675 | unqlite_vm *pVm; | ||
59676 | SyString sName; | ||
59677 | jx9_int64 nId; | ||
59678 | int nByte; | ||
59679 | int rc; | ||
59680 | /* Extract collection name */ | ||
59681 | if( argc < 2 ){ | ||
59682 | /* Missing arguments */ | ||
59683 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); | ||
59684 | /* Return false */ | ||
59685 | jx9_result_bool(pCtx,0); | ||
59686 | return JX9_OK; | ||
59687 | } | ||
59688 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59689 | if( nByte < 1){ | ||
59690 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59691 | /* Return false */ | ||
59692 | jx9_result_bool(pCtx,0); | ||
59693 | return JX9_OK; | ||
59694 | } | ||
59695 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59696 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59697 | /* Fetch the collection */ | ||
59698 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59699 | if( pCol == 0 ){ | ||
59700 | jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); | ||
59701 | /* Return false */ | ||
59702 | jx9_result_bool(pCtx,0); | ||
59703 | return JX9_OK; | ||
59704 | } | ||
59705 | /* Extract the record ID */ | ||
59706 | nId = jx9_value_to_int64(argv[1]); | ||
59707 | /* Drop the record */ | ||
59708 | rc = unqliteCollectionDropRecord(pCol,nId,1,1); | ||
59709 | /* Processing result */ | ||
59710 | jx9_result_bool(pCtx,rc == UNQLITE_OK); | ||
59711 | return JX9_OK; | ||
59712 | } | ||
59713 | /* | ||
59714 | * bool db_set_schema(string $col_name, object $json_object) | ||
59715 | * Set a schema for a given collection. | ||
59716 | * Parameter | ||
59717 | * col_name: Collection name. | ||
59718 | * json_object: Collection schema (Must be a JSON object). | ||
59719 | * Return | ||
59720 | * TRUE on success. FALSE on failure. | ||
59721 | */ | ||
59722 | static int unqliteBuiltin_db_set_schema(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59723 | { | ||
59724 | unqlite_col *pCol; | ||
59725 | const char *zName; | ||
59726 | unqlite_vm *pVm; | ||
59727 | SyString sName; | ||
59728 | int nByte; | ||
59729 | int rc; | ||
59730 | /* Extract collection name */ | ||
59731 | if( argc < 2 ){ | ||
59732 | /* Missing arguments */ | ||
59733 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme"); | ||
59734 | /* Return false */ | ||
59735 | jx9_result_bool(pCtx,0); | ||
59736 | return JX9_OK; | ||
59737 | } | ||
59738 | if( !jx9_value_is_json_object(argv[1]) ){ | ||
59739 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection scheme"); | ||
59740 | /* Return false */ | ||
59741 | jx9_result_bool(pCtx,0); | ||
59742 | return JX9_OK; | ||
59743 | } | ||
59744 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59745 | if( nByte < 1){ | ||
59746 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59747 | /* Return false */ | ||
59748 | jx9_result_bool(pCtx,0); | ||
59749 | return JX9_OK; | ||
59750 | } | ||
59751 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59752 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59753 | /* Fetch the collection */ | ||
59754 | rc = UNQLITE_NOOP; | ||
59755 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59756 | if( pCol ){ | ||
59757 | /* Set the collection scheme */ | ||
59758 | rc = unqliteCollectionSetSchema(pCol,argv[1]); | ||
59759 | }else{ | ||
59760 | jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING, | ||
59761 | "No such collection '%z'", | ||
59762 | &sName | ||
59763 | ); | ||
59764 | } | ||
59765 | /* Processing result */ | ||
59766 | jx9_result_bool(pCtx,rc == UNQLITE_OK); | ||
59767 | return JX9_OK; | ||
59768 | } | ||
59769 | /* | ||
59770 | * object db_get_schema(string $col_name) | ||
59771 | * Return the schema associated with a given collection. | ||
59772 | * Parameter | ||
59773 | * col_name: Collection name | ||
59774 | * Return | ||
59775 | * Collection schema on success. null otherwise. | ||
59776 | */ | ||
59777 | static int unqliteBuiltin_db_get_schema(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59778 | { | ||
59779 | unqlite_col *pCol; | ||
59780 | const char *zName; | ||
59781 | unqlite_vm *pVm; | ||
59782 | SyString sName; | ||
59783 | int nByte; | ||
59784 | /* Extract collection name */ | ||
59785 | if( argc < 1 ){ | ||
59786 | /* Missing arguments */ | ||
59787 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme"); | ||
59788 | /* Return false */ | ||
59789 | jx9_result_bool(pCtx,0); | ||
59790 | return JX9_OK; | ||
59791 | } | ||
59792 | zName = jx9_value_to_string(argv[0],&nByte); | ||
59793 | if( nByte < 1){ | ||
59794 | jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); | ||
59795 | /* Return false */ | ||
59796 | jx9_result_bool(pCtx,0); | ||
59797 | return JX9_OK; | ||
59798 | } | ||
59799 | SyStringInitFromBuf(&sName,zName,nByte); | ||
59800 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59801 | /* Fetch the collection */ | ||
59802 | pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); | ||
59803 | if( pCol ){ | ||
59804 | /* Return the collection schema */ | ||
59805 | jx9_result_value(pCtx,&pCol->sSchema); | ||
59806 | }else{ | ||
59807 | jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING, | ||
59808 | "No such collection '%z'", | ||
59809 | &sName | ||
59810 | ); | ||
59811 | jx9_result_null(pCtx); | ||
59812 | } | ||
59813 | return JX9_OK; | ||
59814 | } | ||
59815 | /* | ||
59816 | * bool db_begin(void) | ||
59817 | * Manually begin a write transaction. | ||
59818 | * Parameter | ||
59819 | * None | ||
59820 | * Return | ||
59821 | * TRUE on success. FALSE otherwise. | ||
59822 | */ | ||
59823 | static int unqliteBuiltin_db_begin(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59824 | { | ||
59825 | unqlite_vm *pVm; | ||
59826 | unqlite *pDb; | ||
59827 | int rc; | ||
59828 | SXUNUSED(argc); /* cc warning */ | ||
59829 | SXUNUSED(argv); | ||
59830 | /* Point to the unqlite Vm */ | ||
59831 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59832 | /* Point to the underlying database handle */ | ||
59833 | pDb = pVm->pDb; | ||
59834 | /* Begin the transaction */ | ||
59835 | rc = unqlitePagerBegin(pDb->sDB.pPager); | ||
59836 | /* result */ | ||
59837 | jx9_result_bool(pCtx,rc == UNQLITE_OK ); | ||
59838 | return JX9_OK; | ||
59839 | } | ||
59840 | /* | ||
59841 | * bool db_commit(void) | ||
59842 | * Manually commit a transaction. | ||
59843 | * Parameter | ||
59844 | * None | ||
59845 | * Return | ||
59846 | * TRUE if the transaction was successfuly commited. FALSE otherwise. | ||
59847 | */ | ||
59848 | static int unqliteBuiltin_db_commit(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59849 | { | ||
59850 | unqlite_vm *pVm; | ||
59851 | unqlite *pDb; | ||
59852 | int rc; | ||
59853 | SXUNUSED(argc); /* cc warning */ | ||
59854 | SXUNUSED(argv); | ||
59855 | /* Point to the unqlite Vm */ | ||
59856 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59857 | /* Point to the underlying database handle */ | ||
59858 | pDb = pVm->pDb; | ||
59859 | /* Commit the transaction if any */ | ||
59860 | rc = unqlitePagerCommit(pDb->sDB.pPager); | ||
59861 | /* Commit result */ | ||
59862 | jx9_result_bool(pCtx,rc == UNQLITE_OK ); | ||
59863 | return JX9_OK; | ||
59864 | } | ||
59865 | /* | ||
59866 | * bool db_rollback(void) | ||
59867 | * Manually rollback a transaction. | ||
59868 | * Parameter | ||
59869 | * None | ||
59870 | * Return | ||
59871 | * TRUE if the transaction was successfuly rolled back. FALSE otherwise | ||
59872 | */ | ||
59873 | static int unqliteBuiltin_db_rollback(jx9_context *pCtx,int argc,jx9_value **argv) | ||
59874 | { | ||
59875 | unqlite_vm *pVm; | ||
59876 | unqlite *pDb; | ||
59877 | int rc; | ||
59878 | SXUNUSED(argc); /* cc warning */ | ||
59879 | SXUNUSED(argv); | ||
59880 | /* Point to the unqlite Vm */ | ||
59881 | pVm = (unqlite_vm *)jx9_context_user_data(pCtx); | ||
59882 | /* Point to the underlying database handle */ | ||
59883 | pDb = pVm->pDb; | ||
59884 | /* Rollback the transaction if any */ | ||
59885 | rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE); | ||
59886 | /* Rollback result */ | ||
59887 | jx9_result_bool(pCtx,rc == UNQLITE_OK ); | ||
59888 | return JX9_OK; | ||
59889 | } | ||
59890 | /* | ||
59891 | * Register all the UnQLite foreign functions defined above. | ||
59892 | */ | ||
59893 | UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm) | ||
59894 | { | ||
59895 | static const jx9_builtin_func aBuiltin[] = { | ||
59896 | { "db_version" , unqliteBuiltin_db_version }, | ||
59897 | { "db_copyright", unqliteBuiltin_db_credits }, | ||
59898 | { "db_credits" , unqliteBuiltin_db_credits }, | ||
59899 | { "db_sig" , unqliteBuiltin_db_sig }, | ||
59900 | { "db_errlog", unqliteBuiltin_db_errlog }, | ||
59901 | { "collection_exists", unqliteBuiltin_collection_exists }, | ||
59902 | { "db_exists", unqliteBuiltin_collection_exists }, | ||
59903 | { "collection_create", unqliteBuiltin_collection_create }, | ||
59904 | { "db_create", unqliteBuiltin_collection_create }, | ||
59905 | { "db_fetch", unqliteBuiltin_db_fetch_next }, | ||
59906 | { "db_get", unqliteBuiltin_db_fetch_next }, | ||
59907 | { "db_fetch_by_id", unqliteBuiltin_db_fetch_by_id }, | ||
59908 | { "db_get_by_id", unqliteBuiltin_db_fetch_by_id }, | ||
59909 | { "db_fetch_all", unqliteBuiltin_db_fetch_all }, | ||
59910 | { "db_get_all", unqliteBuiltin_db_fetch_all }, | ||
59911 | { "db_last_record_id", unqliteBuiltin_db_last_record_id }, | ||
59912 | { "db_current_record_id", unqliteBuiltin_db_current_record_id }, | ||
59913 | { "db_reset_record_cursor", unqliteBuiltin_db_reset_record_cursor }, | ||
59914 | { "db_total_records", unqliteBuiltin_db_total_records }, | ||
59915 | { "db_creation_date", unqliteBuiltin_db_creation_date }, | ||
59916 | { "db_store", unqliteBuiltin_db_store }, | ||
59917 | { "db_put", unqliteBuiltin_db_store }, | ||
59918 | { "db_drop_collection", unqliteBuiltin_db_drop_col }, | ||
59919 | { "collection_delete", unqliteBuiltin_db_drop_col }, | ||
59920 | { "db_drop_record", unqliteBuiltin_db_drop_record }, | ||
59921 | { "db_set_schema", unqliteBuiltin_db_set_schema }, | ||
59922 | { "db_get_schema", unqliteBuiltin_db_get_schema }, | ||
59923 | { "db_begin", unqliteBuiltin_db_begin }, | ||
59924 | { "db_commit", unqliteBuiltin_db_commit }, | ||
59925 | { "db_rollback", unqliteBuiltin_db_rollback }, | ||
59926 | }; | ||
59927 | int rc = UNQLITE_OK; | ||
59928 | sxu32 n; | ||
59929 | /* Register the unQLite functions defined above in the Jx9 call table */ | ||
59930 | for( n = 0 ; n < SX_ARRAYSIZE(aBuiltin) ; ++n ){ | ||
59931 | rc = jx9_create_function(pVm->pJx9Vm,aBuiltin[n].zName,aBuiltin[n].xFunc,pVm); | ||
59932 | } | ||
59933 | return rc; | ||
59934 | } | ||
59935 | /* END-OF-IMPLEMENTATION: unqlite@embedded@symisc 34-09-46 */ | ||
59936 | /* | ||
59937 | * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. | ||
59938 | * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ | ||
59939 | * Version 1.1.6 | ||
59940 | * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES | ||
59941 | * please contact Symisc Systems via: | ||
59942 | * legal@symisc.net | ||
59943 | * licensing@symisc.net | ||
59944 | * contact@symisc.net | ||
59945 | * or visit: | ||
59946 | * http://unqlite.org/licensing.html | ||
59947 | */ | ||
59948 | /* | ||
59949 | * Copyright (C) 2012, 2013 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine <chm@symisc.net>]. | ||
59950 | * All rights reserved. | ||
59951 | * | ||
59952 | * Redistribution and use in source and binary forms, with or without | ||
59953 | * modification, are permitted provided that the following conditions | ||
59954 | * are met: | ||
59955 | * 1. Redistributions of source code must retain the above copyright | ||
59956 | * notice, this list of conditions and the following disclaimer. | ||
59957 | * 2. Redistributions in binary form must reproduce the above copyright | ||
59958 | * notice, this list of conditions and the following disclaimer in the | ||
59959 | * documentation and/or other materials provided with the distribution. | ||
59960 | * | ||
59961 | * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS | ||
59962 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
59963 | * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR | ||
59964 | * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS | ||
59965 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||
59966 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||
59967 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | ||
59968 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||
59969 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | ||
59970 | * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | ||
59971 | * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
59972 | */ | ||
diff --git a/common/unqlite/unqlite.h b/common/unqlite/unqlite.h deleted file mode 100644 index bf7ee50..0000000 --- a/common/unqlite/unqlite.h +++ /dev/null | |||
@@ -1,949 +0,0 @@ | |||
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 | |||
90 | /* Forward declaration to public objects */ | ||
91 | typedef struct unqlite_io_methods unqlite_io_methods; | ||
92 | typedef struct unqlite_kv_methods unqlite_kv_methods; | ||
93 | typedef struct unqlite_kv_engine unqlite_kv_engine; | ||
94 | typedef struct jx9_io_stream unqlite_io_stream; | ||
95 | typedef struct jx9_context unqlite_context; | ||
96 | typedef struct jx9_value unqlite_value; | ||
97 | typedef struct unqlite_vfs unqlite_vfs; | ||
98 | typedef struct unqlite_vm unqlite_vm; | ||
99 | typedef struct unqlite unqlite; | ||
100 | /* | ||
101 | * ------------------------------ | ||
102 | * Compile time directives | ||
103 | * ------------------------------ | ||
104 | * For most purposes, UnQLite can be built just fine using the default compilation options. | ||
105 | * However, if required, the compile-time options documented below can be used to omit UnQLite | ||
106 | * features (resulting in a smaller compiled library size) or to change the default values | ||
107 | * of some parameters. | ||
108 | * Every effort has been made to ensure that the various combinations of compilation options | ||
109 | * work harmoniously and produce a working library. | ||
110 | * | ||
111 | * UNQLITE_ENABLE_THREADS | ||
112 | * This option controls whether or not code is included in UnQLite to enable it to operate | ||
113 | * safely in a multithreaded environment. The default is not. All mutexing code is omitted | ||
114 | * and it is unsafe to use UnQLite in a multithreaded program. When compiled with the | ||
115 | * UNQLITE_ENABLE_THREADS directive enabled, UnQLite can be used in a multithreaded program | ||
116 | * and it is safe to share the same virtual machine and engine handle between two or more threads. | ||
117 | * The value of UNQLITE_ENABLE_THREADS can be determined at run-time using the unqlite_lib_is_threadsafe() | ||
118 | * interface. | ||
119 | * When UnQLite has been compiled with threading support then the threading mode can be altered | ||
120 | * at run-time using the unqlite_lib_config() interface together with one of these verbs: | ||
121 | * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE | ||
122 | * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI | ||
123 | * Platforms others than Windows and UNIX systems must install their own mutex subsystem via | ||
124 | * unqlite_lib_config() with a configuration verb set to UNQLITE_LIB_CONFIG_USER_MUTEX. | ||
125 | * Otherwise the library is not threadsafe. | ||
126 | * Note that you must link UnQLite with the POSIX threads library under UNIX systems (i.e: -lpthread). | ||
127 | * | ||
128 | * Options To Omit/Enable Features | ||
129 | * | ||
130 | * The following options can be used to reduce the size of the compiled library by omitting optional | ||
131 | * features. This is probably only useful in embedded systems where space is especially tight, as even | ||
132 | * with all features included the UnQLite library is relatively small. Don't forget to tell your | ||
133 | * compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler | ||
134 | * to optimize for size usually has a much larger impact on library footprint than employing | ||
135 | * any of these compile-time options. | ||
136 | * | ||
137 | * JX9_DISABLE_BUILTIN_FUNC | ||
138 | * Jx9 is shipped with more than 312 built-in functions suitable for most purposes like | ||
139 | * string and INI processing, ZIP extracting, Base64 encoding/decoding, JSON encoding/decoding | ||
140 | * and so forth. | ||
141 | * If this directive is enabled, then all built-in Jx9 functions are omitted from the build. | ||
142 | * Note that special functions such as db_create(), db_store(), db_fetch(), etc. are not omitted | ||
143 | * from the build and are not affected by this directive. | ||
144 | * | ||
145 | * JX9_ENABLE_MATH_FUNC | ||
146 | * If this directive is enabled, built-in math functions such as sqrt(), abs(), log(), ceil(), etc. | ||
147 | * are included in the build. Note that you may need to link UnQLite with the math library in same | ||
148 | * Linux/BSD flavor (i.e: -lm). | ||
149 | * | ||
150 | * JX9_DISABLE_DISK_IO | ||
151 | * If this directive is enabled, built-in VFS functions such as chdir(), mkdir(), chroot(), unlink(), | ||
152 | * sleep(), etc. are omitted from the build. | ||
153 | * | ||
154 | * UNQLITE_ENABLE_JX9_HASH_IO | ||
155 | * If this directive is enabled, built-in hash functions such as md5(), sha1(), md5_file(), crc32(), etc. | ||
156 | * are included in the build. | ||
157 | */ | ||
158 | /* Symisc public definitions */ | ||
159 | #if !defined(SYMISC_STANDARD_DEFS) | ||
160 | #define SYMISC_STANDARD_DEFS | ||
161 | #if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE) | ||
162 | /* Windows Systems */ | ||
163 | #if !defined(__WINNT__) | ||
164 | #define __WINNT__ | ||
165 | #endif | ||
166 | /* | ||
167 | * Determine if we are dealing with WindowsCE - which has a much | ||
168 | * reduced API. | ||
169 | */ | ||
170 | #if defined(_WIN32_WCE) | ||
171 | #ifndef __WIN_CE__ | ||
172 | #define __WIN_CE__ | ||
173 | #endif /* __WIN_CE__ */ | ||
174 | #endif /* _WIN32_WCE */ | ||
175 | #else | ||
176 | /* | ||
177 | * By default we will assume that we are compiling on a UNIX systems. | ||
178 | * Otherwise the OS_OTHER directive must be defined. | ||
179 | */ | ||
180 | #if !defined(OS_OTHER) | ||
181 | #if !defined(__UNIXES__) | ||
182 | #define __UNIXES__ | ||
183 | #endif /* __UNIXES__ */ | ||
184 | #else | ||
185 | #endif /* OS_OTHER */ | ||
186 | #endif /* __WINNT__/__UNIXES__ */ | ||
187 | #if defined(_MSC_VER) || defined(__BORLANDC__) | ||
188 | typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ | ||
189 | typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ | ||
190 | #else | ||
191 | typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ | ||
192 | typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ | ||
193 | #endif /* _MSC_VER */ | ||
194 | /* Signature of the consumer routine */ | ||
195 | typedef int (*ProcConsumer)(const void *, unsigned int, void *); | ||
196 | /* Forward reference */ | ||
197 | typedef struct SyMutexMethods SyMutexMethods; | ||
198 | typedef struct SyMemMethods SyMemMethods; | ||
199 | typedef struct SyString SyString; | ||
200 | typedef struct syiovec syiovec; | ||
201 | typedef struct SyMutex SyMutex; | ||
202 | typedef struct Sytm Sytm; | ||
203 | /* Scatter and gather array. */ | ||
204 | struct syiovec | ||
205 | { | ||
206 | #if defined (__WINNT__) | ||
207 | /* Same fields type and offset as WSABUF structure defined one winsock2 header */ | ||
208 | unsigned long nLen; | ||
209 | char *pBase; | ||
210 | #else | ||
211 | void *pBase; | ||
212 | unsigned long nLen; | ||
213 | #endif | ||
214 | }; | ||
215 | struct SyString | ||
216 | { | ||
217 | const char *zString; /* Raw string (may not be null terminated) */ | ||
218 | unsigned int nByte; /* Raw string length */ | ||
219 | }; | ||
220 | /* Time structure. */ | ||
221 | struct Sytm | ||
222 | { | ||
223 | int tm_sec; /* seconds (0 - 60) */ | ||
224 | int tm_min; /* minutes (0 - 59) */ | ||
225 | int tm_hour; /* hours (0 - 23) */ | ||
226 | int tm_mday; /* day of month (1 - 31) */ | ||
227 | int tm_mon; /* month of year (0 - 11) */ | ||
228 | int tm_year; /* year + 1900 */ | ||
229 | int tm_wday; /* day of week (Sunday = 0) */ | ||
230 | int tm_yday; /* day of year (0 - 365) */ | ||
231 | int tm_isdst; /* is summer time in effect? */ | ||
232 | char *tm_zone; /* abbreviation of timezone name */ | ||
233 | long tm_gmtoff; /* offset from UTC in seconds */ | ||
234 | }; | ||
235 | /* Convert a tm structure (struct tm *) found in <time.h> to a Sytm structure */ | ||
236 | #define STRUCT_TM_TO_SYTM(pTM, pSYTM) \ | ||
237 | (pSYTM)->tm_hour = (pTM)->tm_hour;\ | ||
238 | (pSYTM)->tm_min = (pTM)->tm_min;\ | ||
239 | (pSYTM)->tm_sec = (pTM)->tm_sec;\ | ||
240 | (pSYTM)->tm_mon = (pTM)->tm_mon;\ | ||
241 | (pSYTM)->tm_mday = (pTM)->tm_mday;\ | ||
242 | (pSYTM)->tm_year = (pTM)->tm_year + 1900;\ | ||
243 | (pSYTM)->tm_yday = (pTM)->tm_yday;\ | ||
244 | (pSYTM)->tm_wday = (pTM)->tm_wday;\ | ||
245 | (pSYTM)->tm_isdst = (pTM)->tm_isdst;\ | ||
246 | (pSYTM)->tm_gmtoff = 0;\ | ||
247 | (pSYTM)->tm_zone = 0; | ||
248 | |||
249 | /* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */ | ||
250 | #define SYSTEMTIME_TO_SYTM(pSYSTIME, pSYTM) \ | ||
251 | (pSYTM)->tm_hour = (pSYSTIME)->wHour;\ | ||
252 | (pSYTM)->tm_min = (pSYSTIME)->wMinute;\ | ||
253 | (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\ | ||
254 | (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\ | ||
255 | (pSYTM)->tm_mday = (pSYSTIME)->wDay;\ | ||
256 | (pSYTM)->tm_year = (pSYSTIME)->wYear;\ | ||
257 | (pSYTM)->tm_yday = 0;\ | ||
258 | (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\ | ||
259 | (pSYTM)->tm_gmtoff = 0;\ | ||
260 | (pSYTM)->tm_isdst = -1;\ | ||
261 | (pSYTM)->tm_zone = 0; | ||
262 | |||
263 | /* Dynamic memory allocation methods. */ | ||
264 | struct SyMemMethods | ||
265 | { | ||
266 | void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ | ||
267 | void * (*xRealloc)(void *, unsigned int); /* [Required:] Re-allocate a memory chunk */ | ||
268 | void (*xFree)(void *); /* [Required:] Release a memory chunk */ | ||
269 | unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ | ||
270 | int (*xInit)(void *); /* [Optional:] Initialization callback */ | ||
271 | void (*xRelease)(void *); /* [Optional:] Release callback */ | ||
272 | void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */ | ||
273 | }; | ||
274 | /* Out of memory callback signature. */ | ||
275 | typedef int (*ProcMemError)(void *); | ||
276 | /* Mutex methods. */ | ||
277 | struct SyMutexMethods | ||
278 | { | ||
279 | int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */ | ||
280 | void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */ | ||
281 | SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */ | ||
282 | void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */ | ||
283 | void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */ | ||
284 | int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */ | ||
285 | void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */ | ||
286 | }; | ||
287 | #if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec) | ||
288 | #define SX_APIIMPORT __declspec(dllimport) | ||
289 | #define SX_APIEXPORT __declspec(dllexport) | ||
290 | #else | ||
291 | #define SX_APIIMPORT | ||
292 | #define SX_APIEXPORT | ||
293 | #endif | ||
294 | /* Standard return values from Symisc public interfaces */ | ||
295 | #define SXRET_OK 0 /* Not an error */ | ||
296 | #define SXERR_MEM (-1) /* Out of memory */ | ||
297 | #define SXERR_IO (-2) /* IO error */ | ||
298 | #define SXERR_EMPTY (-3) /* Empty field */ | ||
299 | #define SXERR_LOCKED (-4) /* Locked operation */ | ||
300 | #define SXERR_ORANGE (-5) /* Out of range value */ | ||
301 | #define SXERR_NOTFOUND (-6) /* Item not found */ | ||
302 | #define SXERR_LIMIT (-7) /* Limit reached */ | ||
303 | #define SXERR_MORE (-8) /* Need more input */ | ||
304 | #define SXERR_INVALID (-9) /* Invalid parameter */ | ||
305 | #define SXERR_ABORT (-10) /* User callback request an operation abort */ | ||
306 | #define SXERR_EXISTS (-11) /* Item exists */ | ||
307 | #define SXERR_SYNTAX (-12) /* Syntax error */ | ||
308 | #define SXERR_UNKNOWN (-13) /* Unknown error */ | ||
309 | #define SXERR_BUSY (-14) /* Busy operation */ | ||
310 | #define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */ | ||
311 | #define SXERR_WILLBLOCK (-16) /* Operation will block */ | ||
312 | #define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */ | ||
313 | #define SXERR_EOF (-18) /* End of input */ | ||
314 | #define SXERR_PERM (-19) /* Permission error */ | ||
315 | #define SXERR_NOOP (-20) /* No-op */ | ||
316 | #define SXERR_FORMAT (-21) /* Invalid format */ | ||
317 | #define SXERR_NEXT (-22) /* Not an error */ | ||
318 | #define SXERR_OS (-23) /* System call return an error */ | ||
319 | #define SXERR_CORRUPT (-24) /* Corrupted pointer */ | ||
320 | #define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */ | ||
321 | #define SXERR_NOMATCH (-26) /* No match */ | ||
322 | #define SXERR_RESET (-27) /* Operation reset */ | ||
323 | #define SXERR_DONE (-28) /* Not an error */ | ||
324 | #define SXERR_SHORT (-29) /* Buffer too short */ | ||
325 | #define SXERR_PATH (-30) /* Path error */ | ||
326 | #define SXERR_TIMEOUT (-31) /* Timeout */ | ||
327 | #define SXERR_BIG (-32) /* Too big for processing */ | ||
328 | #define SXERR_RETRY (-33) /* Retry your call */ | ||
329 | #define SXERR_IGNORE (-63) /* Ignore */ | ||
330 | #endif /* SYMISC_PUBLIC_DEFS */ | ||
331 | /* | ||
332 | * Marker for exported interfaces. | ||
333 | */ | ||
334 | #define UNQLITE_APIEXPORT SX_APIEXPORT | ||
335 | /* | ||
336 | * If compiling for a processor that lacks floating point | ||
337 | * support, substitute integer for floating-point. | ||
338 | */ | ||
339 | #ifdef UNQLITE_OMIT_FLOATING_POINT | ||
340 | typedef sxi64 uqlite_real; | ||
341 | #else | ||
342 | typedef double unqlite_real; | ||
343 | #endif | ||
344 | typedef sxi64 unqlite_int64; | ||
345 | /* Standard UnQLite return values */ | ||
346 | #define UNQLITE_OK SXRET_OK /* Successful result */ | ||
347 | /* Beginning of error codes */ | ||
348 | #define UNQLITE_NOMEM SXERR_MEM /* Out of memory */ | ||
349 | #define UNQLITE_ABORT SXERR_ABORT /* Another thread have released this instance */ | ||
350 | #define UNQLITE_IOERR SXERR_IO /* IO error */ | ||
351 | #define UNQLITE_CORRUPT SXERR_CORRUPT /* Corrupt pointer */ | ||
352 | #define UNQLITE_LOCKED SXERR_LOCKED /* Forbidden Operation */ | ||
353 | #define UNQLITE_BUSY SXERR_BUSY /* The database file is locked */ | ||
354 | #define UNQLITE_DONE SXERR_DONE /* Operation done */ | ||
355 | #define UNQLITE_PERM SXERR_PERM /* Permission error */ | ||
356 | #define UNQLITE_NOTIMPLEMENTED SXERR_NOTIMPLEMENTED /* Method not implemented by the underlying Key/Value storage engine */ | ||
357 | #define UNQLITE_NOTFOUND SXERR_NOTFOUND /* No such record */ | ||
358 | #define UNQLITE_NOOP SXERR_NOOP /* No such method */ | ||
359 | #define UNQLITE_INVALID SXERR_INVALID /* Invalid parameter */ | ||
360 | #define UNQLITE_EOF SXERR_EOF /* End Of Input */ | ||
361 | #define UNQLITE_UNKNOWN SXERR_UNKNOWN /* Unknown configuration option */ | ||
362 | #define UNQLITE_LIMIT SXERR_LIMIT /* Database limit reached */ | ||
363 | #define UNQLITE_EXISTS SXERR_EXISTS /* Record exists */ | ||
364 | #define UNQLITE_EMPTY SXERR_EMPTY /* Empty record */ | ||
365 | #define UNQLITE_COMPILE_ERR (-70) /* Compilation error */ | ||
366 | #define UNQLITE_VM_ERR (-71) /* Virtual machine error */ | ||
367 | #define UNQLITE_FULL (-73) /* Full database (unlikely) */ | ||
368 | #define UNQLITE_CANTOPEN (-74) /* Unable to open the database file */ | ||
369 | #define UNQLITE_READ_ONLY (-75) /* Read only Key/Value storage engine */ | ||
370 | #define UNQLITE_LOCKERR (-76) /* Locking protocol error */ | ||
371 | /* end-of-error-codes */ | ||
372 | /* | ||
373 | * Database Handle Configuration Commands. | ||
374 | * | ||
375 | * The following set of constants are the available configuration verbs that can | ||
376 | * be used by the host-application to configure an UnQLite database handle. | ||
377 | * These constants must be passed as the second argument to [unqlite_config()]. | ||
378 | * | ||
379 | * Each options require a variable number of arguments. | ||
380 | * The [unqlite_config()] interface will return UNQLITE_OK on success, any other | ||
381 | * return value indicates failure. | ||
382 | * For a full discussion on the configuration verbs and their expected | ||
383 | * parameters, please refer to this page: | ||
384 | * http://unqlite.org/c_api/unqlite_config.html | ||
385 | */ | ||
386 | #define UNQLITE_CONFIG_JX9_ERR_LOG 1 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ | ||
387 | #define UNQLITE_CONFIG_MAX_PAGE_CACHE 2 /* ONE ARGUMENT: int nMaxPage */ | ||
388 | #define UNQLITE_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ | ||
389 | #define UNQLITE_CONFIG_KV_ENGINE 4 /* ONE ARGUMENT: const char *zKvName */ | ||
390 | #define UNQLITE_CONFIG_DISABLE_AUTO_COMMIT 5 /* NO ARGUMENTS */ | ||
391 | #define UNQLITE_CONFIG_GET_KV_NAME 6 /* ONE ARGUMENT: const char **pzPtr */ | ||
392 | /* | ||
393 | * UnQLite/Jx9 Virtual Machine Configuration Commands. | ||
394 | * | ||
395 | * The following set of constants are the available configuration verbs that can | ||
396 | * be used by the host-application to configure the Jx9 (Via UnQLite) Virtual machine. | ||
397 | * These constants must be passed as the second argument to the [unqlite_vm_config()] | ||
398 | * interface. | ||
399 | * Each options require a variable number of arguments. | ||
400 | * The [unqlite_vm_config()] interface will return UNQLITE_OK on success, any other return | ||
401 | * value indicates failure. | ||
402 | * There are many options but the most importants are: UNQLITE_VM_CONFIG_OUTPUT which install | ||
403 | * a VM output consumer callback, UNQLITE_VM_CONFIG_HTTP_REQUEST which parse and register | ||
404 | * a HTTP request and UNQLITE_VM_CONFIG_ARGV_ENTRY which populate the $argv array. | ||
405 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
406 | * refer to this page: | ||
407 | * http://unqlite.org/c_api/unqlite_vm_config.html | ||
408 | */ | ||
409 | #define UNQLITE_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ | ||
410 | #define UNQLITE_VM_CONFIG_IMPORT_PATH 2 /* ONE ARGUMENT: const char *zIncludePath */ | ||
411 | #define UNQLITE_VM_CONFIG_ERR_REPORT 3 /* NO ARGUMENTS: Report all run-time errors in the VM output */ | ||
412 | #define UNQLITE_VM_CONFIG_RECURSION_DEPTH 4 /* ONE ARGUMENT: int nMaxDepth */ | ||
413 | #define UNQLITE_VM_OUTPUT_LENGTH 5 /* ONE ARGUMENT: unsigned int *pLength */ | ||
414 | #define UNQLITE_VM_CONFIG_CREATE_VAR 6 /* TWO ARGUMENTS: const char *zName, unqlite_value *pValue */ | ||
415 | #define UNQLITE_VM_CONFIG_HTTP_REQUEST 7 /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ | ||
416 | #define UNQLITE_VM_CONFIG_SERVER_ATTR 8 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ | ||
417 | #define UNQLITE_VM_CONFIG_ENV_ATTR 9 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ | ||
418 | #define UNQLITE_VM_CONFIG_EXEC_VALUE 10 /* ONE ARGUMENT: unqlite_value **ppValue */ | ||
419 | #define UNQLITE_VM_CONFIG_IO_STREAM 11 /* ONE ARGUMENT: const unqlite_io_stream *pStream */ | ||
420 | #define UNQLITE_VM_CONFIG_ARGV_ENTRY 12 /* ONE ARGUMENT: const char *zValue */ | ||
421 | #define UNQLITE_VM_CONFIG_EXTRACT_OUTPUT 13 /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ | ||
422 | /* | ||
423 | * Storage engine configuration commands. | ||
424 | * | ||
425 | * The following set of constants are the available configuration verbs that can | ||
426 | * be used by the host-application to configure the underlying storage engine (i.e Hash, B+tree, R+tree). | ||
427 | * These constants must be passed as the first argument to [unqlite_kv_config()]. | ||
428 | * Each options require a variable number of arguments. | ||
429 | * The [unqlite_kv_config()] interface will return UNQLITE_OK on success, any other return | ||
430 | * value indicates failure. | ||
431 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
432 | * refer to this page: | ||
433 | * http://unqlite.org/c_api/unqlite_kv_config.html | ||
434 | */ | ||
435 | #define UNQLITE_KV_CONFIG_HASH_FUNC 1 /* ONE ARGUMENT: unsigned int (*xHash)(const void *,unsigned int) */ | ||
436 | #define UNQLITE_KV_CONFIG_CMP_FUNC 2 /* ONE ARGUMENT: int (*xCmp)(const void *,const void *,unsigned int) */ | ||
437 | /* | ||
438 | * Global Library Configuration Commands. | ||
439 | * | ||
440 | * The following set of constants are the available configuration verbs that can | ||
441 | * be used by the host-application to configure the whole library. | ||
442 | * These constants must be passed as the first argument to [unqlite_lib_config()]. | ||
443 | * | ||
444 | * Each options require a variable number of arguments. | ||
445 | * The [unqlite_lib_config()] interface will return UNQLITE_OK on success, any other return | ||
446 | * value indicates failure. | ||
447 | * Notes: | ||
448 | * The default configuration is recommended for most applications and so the call to | ||
449 | * [unqlite_lib_config()] is usually not necessary. It is provided to support rare | ||
450 | * applications with unusual needs. | ||
451 | * The [unqlite_lib_config()] interface is not threadsafe. The application must insure that | ||
452 | * no other [unqlite_*()] interfaces are invoked by other threads while [unqlite_lib_config()] | ||
453 | * is running. Furthermore, [unqlite_lib_config()] may only be invoked prior to library | ||
454 | * initialization using [unqlite_lib_init()] or [unqlite_init()] or after shutdown | ||
455 | * by [unqlite_lib_shutdown()]. If [unqlite_lib_config()] is called after [unqlite_lib_init()] | ||
456 | * or [unqlite_init()] and before [unqlite_lib_shutdown()] then it will return UNQLITE_LOCKED. | ||
457 | * For a full discussion on the configuration verbs and their expected parameters, please | ||
458 | * refer to this page: | ||
459 | * http://unqlite.org/c_api/unqlite_lib.html | ||
460 | */ | ||
461 | #define UNQLITE_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ | ||
462 | #define UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ | ||
463 | #define UNQLITE_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ | ||
464 | #define UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ | ||
465 | #define UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ | ||
466 | #define UNQLITE_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const unqlite_vfs *pVfs */ | ||
467 | #define UNQLITE_LIB_CONFIG_STORAGE_ENGINE 7 /* ONE ARGUMENT: unqlite_kv_methods *pStorage */ | ||
468 | #define UNQLITE_LIB_CONFIG_PAGE_SIZE 8 /* ONE ARGUMENT: int iPageSize */ | ||
469 | /* | ||
470 | * These bit values are intended for use in the 3rd parameter to the [unqlite_open()] interface | ||
471 | * and in the 4th parameter to the xOpen method of the [unqlite_vfs] object. | ||
472 | */ | ||
473 | #define UNQLITE_OPEN_READONLY 0x00000001 /* Read only mode. Ok for [unqlite_open] */ | ||
474 | #define UNQLITE_OPEN_READWRITE 0x00000002 /* Ok for [unqlite_open] */ | ||
475 | #define UNQLITE_OPEN_CREATE 0x00000004 /* Ok for [unqlite_open] */ | ||
476 | #define UNQLITE_OPEN_EXCLUSIVE 0x00000008 /* VFS only */ | ||
477 | #define UNQLITE_OPEN_TEMP_DB 0x00000010 /* VFS only */ | ||
478 | #define UNQLITE_OPEN_NOMUTEX 0x00000020 /* Ok for [unqlite_open] */ | ||
479 | #define UNQLITE_OPEN_OMIT_JOURNALING 0x00000040 /* Omit journaling for this database. Ok for [unqlite_open] */ | ||
480 | #define UNQLITE_OPEN_IN_MEMORY 0x00000080 /* An in memory database. Ok for [unqlite_open]*/ | ||
481 | #define UNQLITE_OPEN_MMAP 0x00000100 /* Obtain a memory view of the whole file. Ok for [unqlite_open] */ | ||
482 | /* | ||
483 | * Synchronization Type Flags | ||
484 | * | ||
485 | * When UnQLite invokes the xSync() method of an [unqlite_io_methods] object it uses | ||
486 | * a combination of these integer values as the second argument. | ||
487 | * | ||
488 | * When the UNQLITE_SYNC_DATAONLY flag is used, it means that the sync operation only | ||
489 | * needs to flush data to mass storage. Inode information need not be flushed. | ||
490 | * If the lower four bits of the flag equal UNQLITE_SYNC_NORMAL, that means to use normal | ||
491 | * fsync() semantics. If the lower four bits equal UNQLITE_SYNC_FULL, that means to use | ||
492 | * Mac OS X style fullsync instead of fsync(). | ||
493 | */ | ||
494 | #define UNQLITE_SYNC_NORMAL 0x00002 | ||
495 | #define UNQLITE_SYNC_FULL 0x00003 | ||
496 | #define UNQLITE_SYNC_DATAONLY 0x00010 | ||
497 | /* | ||
498 | * File Locking Levels | ||
499 | * | ||
500 | * UnQLite uses one of these integer values as the second | ||
501 | * argument to calls it makes to the xLock() and xUnlock() methods | ||
502 | * of an [unqlite_io_methods] object. | ||
503 | */ | ||
504 | #define UNQLITE_LOCK_NONE 0 | ||
505 | #define UNQLITE_LOCK_SHARED 1 | ||
506 | #define UNQLITE_LOCK_RESERVED 2 | ||
507 | #define UNQLITE_LOCK_PENDING 3 | ||
508 | #define UNQLITE_LOCK_EXCLUSIVE 4 | ||
509 | /* | ||
510 | * CAPIREF: OS Interface: Open File Handle | ||
511 | * | ||
512 | * An [unqlite_file] object represents an open file in the [unqlite_vfs] OS interface | ||
513 | * layer. | ||
514 | * Individual OS interface implementations will want to subclass this object by appending | ||
515 | * additional fields for their own use. The pMethods entry is a pointer to an | ||
516 | * [unqlite_io_methods] object that defines methods for performing | ||
517 | * I/O operations on the open file. | ||
518 | */ | ||
519 | typedef struct unqlite_file unqlite_file; | ||
520 | struct unqlite_file { | ||
521 | const unqlite_io_methods *pMethods; /* Methods for an open file. MUST BE FIRST */ | ||
522 | }; | ||
523 | /* | ||
524 | * CAPIREF: OS Interface: File Methods Object | ||
525 | * | ||
526 | * Every file opened by the [unqlite_vfs] xOpen method populates an | ||
527 | * [unqlite_file] object (or, more commonly, a subclass of the | ||
528 | * [unqlite_file] object) with a pointer to an instance of this object. | ||
529 | * This object defines the methods used to perform various operations | ||
530 | * against the open file represented by the [unqlite_file] object. | ||
531 | * | ||
532 | * If the xOpen method sets the unqlite_file.pMethods element | ||
533 | * to a non-NULL pointer, then the unqlite_io_methods.xClose method | ||
534 | * may be invoked even if the xOpen reported that it failed. The | ||
535 | * only way to prevent a call to xClose following a failed xOpen | ||
536 | * is for the xOpen to set the unqlite_file.pMethods element to NULL. | ||
537 | * | ||
538 | * The flags argument to xSync may be one of [UNQLITE_SYNC_NORMAL] or | ||
539 | * [UNQLITE_SYNC_FULL]. The first choice is the normal fsync(). | ||
540 | * The second choice is a Mac OS X style fullsync. The [UNQLITE_SYNC_DATAONLY] | ||
541 | * flag may be ORed in to indicate that only the data of the file | ||
542 | * and not its inode needs to be synced. | ||
543 | * | ||
544 | * The integer values to xLock() and xUnlock() are one of | ||
545 | * | ||
546 | * UNQLITE_LOCK_NONE | ||
547 | * UNQLITE_LOCK_SHARED | ||
548 | * UNQLITE_LOCK_RESERVED | ||
549 | * UNQLITE_LOCK_PENDING | ||
550 | * UNQLITE_LOCK_EXCLUSIVE | ||
551 | * | ||
552 | * xLock() increases the lock. xUnlock() decreases the lock. | ||
553 | * The xCheckReservedLock() method checks whether any database connection, | ||
554 | * either in this process or in some other process, is holding a RESERVED, | ||
555 | * PENDING, or EXCLUSIVE lock on the file. It returns true if such a lock exists | ||
556 | * and false otherwise. | ||
557 | * | ||
558 | * The xSectorSize() method returns the sector size of the device that underlies | ||
559 | * the file. The sector size is the minimum write that can be performed without | ||
560 | * disturbing other bytes in the file. | ||
561 | * | ||
562 | */ | ||
563 | struct unqlite_io_methods { | ||
564 | int iVersion; /* Structure version number (currently 1) */ | ||
565 | int (*xClose)(unqlite_file*); | ||
566 | int (*xRead)(unqlite_file*, void*, unqlite_int64 iAmt, unqlite_int64 iOfst); | ||
567 | int (*xWrite)(unqlite_file*, const void*, unqlite_int64 iAmt, unqlite_int64 iOfst); | ||
568 | int (*xTruncate)(unqlite_file*, unqlite_int64 size); | ||
569 | int (*xSync)(unqlite_file*, int flags); | ||
570 | int (*xFileSize)(unqlite_file*, unqlite_int64 *pSize); | ||
571 | int (*xLock)(unqlite_file*, int); | ||
572 | int (*xUnlock)(unqlite_file*, int); | ||
573 | int (*xCheckReservedLock)(unqlite_file*, int *pResOut); | ||
574 | int (*xSectorSize)(unqlite_file*); | ||
575 | }; | ||
576 | /* | ||
577 | * CAPIREF: OS Interface Object | ||
578 | * | ||
579 | * An instance of the unqlite_vfs object defines the interface between | ||
580 | * the UnQLite core and the underlying operating system. The "vfs" | ||
581 | * in the name of the object stands for "Virtual File System". | ||
582 | * | ||
583 | * Only a single vfs can be registered within the UnQLite core. | ||
584 | * Vfs registration is done using the [unqlite_lib_config()] interface | ||
585 | * with a configuration verb set to UNQLITE_LIB_CONFIG_VFS. | ||
586 | * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users | ||
587 | * does not have to worry about registering and installing a vfs since UnQLite | ||
588 | * come with a built-in vfs for these platforms that implements most the methods | ||
589 | * defined below. | ||
590 | * | ||
591 | * Clients running on exotic systems (ie: Other than Windows and UNIX systems) | ||
592 | * must register their own vfs in order to be able to use the UnQLite library. | ||
593 | * | ||
594 | * The value of the iVersion field is initially 1 but may be larger in | ||
595 | * future versions of UnQLite. | ||
596 | * | ||
597 | * The szOsFile field is the size of the subclassed [unqlite_file] structure | ||
598 | * used by this VFS. mxPathname is the maximum length of a pathname in this VFS. | ||
599 | * | ||
600 | * At least szOsFile bytes of memory are allocated by UnQLite to hold the [unqlite_file] | ||
601 | * structure passed as the third argument to xOpen. The xOpen method does not have to | ||
602 | * allocate the structure; it should just fill it in. Note that the xOpen method must | ||
603 | * set the unqlite_file.pMethods to either a valid [unqlite_io_methods] object or to NULL. | ||
604 | * xOpen must do this even if the open fails. UnQLite expects that the unqlite_file.pMethods | ||
605 | * element will be valid after xOpen returns regardless of the success or failure of the | ||
606 | * xOpen call. | ||
607 | * | ||
608 | */ | ||
609 | struct unqlite_vfs { | ||
610 | const char *zName; /* Name of this virtual file system [i.e: Windows, UNIX, etc.] */ | ||
611 | int iVersion; /* Structure version number (currently 1) */ | ||
612 | int szOsFile; /* Size of subclassed unqlite_file */ | ||
613 | int mxPathname; /* Maximum file pathname length */ | ||
614 | int (*xOpen)(unqlite_vfs*, const char *zName, unqlite_file*,unsigned int flags); | ||
615 | int (*xDelete)(unqlite_vfs*, const char *zName, int syncDir); | ||
616 | int (*xAccess)(unqlite_vfs*, const char *zName, int flags, int *pResOut); | ||
617 | int (*xFullPathname)(unqlite_vfs*, const char *zName,int buf_len,char *zBuf); | ||
618 | int (*xTmpDir)(unqlite_vfs*,char *zBuf,int buf_len); | ||
619 | int (*xSleep)(unqlite_vfs*, int microseconds); | ||
620 | int (*xCurrentTime)(unqlite_vfs*,Sytm *pOut); | ||
621 | int (*xGetLastError)(unqlite_vfs*, int, char *); | ||
622 | }; | ||
623 | /* | ||
624 | * Flags for the xAccess VFS method | ||
625 | * | ||
626 | * These integer constants can be used as the third parameter to | ||
627 | * the xAccess method of an [unqlite_vfs] object. They determine | ||
628 | * what kind of permissions the xAccess method is looking for. | ||
629 | * With UNQLITE_ACCESS_EXISTS, the xAccess method | ||
630 | * simply checks whether the file exists. | ||
631 | * With UNQLITE_ACCESS_READWRITE, the xAccess method | ||
632 | * checks whether the named directory is both readable and writable | ||
633 | * (in other words, if files can be added, removed, and renamed within | ||
634 | * the directory). | ||
635 | * The UNQLITE_ACCESS_READWRITE constant is currently used only by the | ||
636 | * [temp_store_directory pragma], though this could change in a future | ||
637 | * release of UnQLite. | ||
638 | * With UNQLITE_ACCESS_READ, the xAccess method | ||
639 | * checks whether the file is readable. The UNQLITE_ACCESS_READ constant is | ||
640 | * currently unused, though it might be used in a future release of | ||
641 | * UnQLite. | ||
642 | */ | ||
643 | #define UNQLITE_ACCESS_EXISTS 0 | ||
644 | #define UNQLITE_ACCESS_READWRITE 1 | ||
645 | #define UNQLITE_ACCESS_READ 2 | ||
646 | /* | ||
647 | * The type used to represent a page number. The first page in a file | ||
648 | * is called page 1. 0 is used to represent "not a page". | ||
649 | * A page number is an unsigned 64-bit integer. | ||
650 | */ | ||
651 | typedef sxu64 pgno; | ||
652 | /* | ||
653 | * A database disk page is represented by an instance | ||
654 | * of the follwoing structure. | ||
655 | */ | ||
656 | typedef struct unqlite_page unqlite_page; | ||
657 | struct unqlite_page | ||
658 | { | ||
659 | unsigned char *zData; /* Content of this page */ | ||
660 | void *pUserData; /* Extra content */ | ||
661 | pgno pgno; /* Page number for this page */ | ||
662 | }; | ||
663 | /* | ||
664 | * UnQLite handle to the underlying Key/Value Storage Engine (See below). | ||
665 | */ | ||
666 | typedef void * unqlite_kv_handle; | ||
667 | /* | ||
668 | * UnQLite pager IO methods. | ||
669 | * | ||
670 | * An instance of the following structure define the exported methods of the UnQLite pager | ||
671 | * to the underlying Key/Value storage engine. | ||
672 | */ | ||
673 | typedef struct unqlite_kv_io unqlite_kv_io; | ||
674 | struct unqlite_kv_io | ||
675 | { | ||
676 | unqlite_kv_handle pHandle; /* UnQLite handle passed as the first parameter to the | ||
677 | * method defined below. | ||
678 | */ | ||
679 | unqlite_kv_methods *pMethods; /* Underlying storage engine */ | ||
680 | /* Pager methods */ | ||
681 | int (*xGet)(unqlite_kv_handle,pgno,unqlite_page **); | ||
682 | int (*xLookup)(unqlite_kv_handle,pgno,unqlite_page **); | ||
683 | int (*xNew)(unqlite_kv_handle,unqlite_page **); | ||
684 | int (*xWrite)(unqlite_page *); | ||
685 | int (*xDontWrite)(unqlite_page *); | ||
686 | int (*xDontJournal)(unqlite_page *); | ||
687 | int (*xDontMkHot)(unqlite_page *); | ||
688 | int (*xPageRef)(unqlite_page *); | ||
689 | int (*xPageUnref)(unqlite_page *); | ||
690 | int (*xPageSize)(unqlite_kv_handle); | ||
691 | int (*xReadOnly)(unqlite_kv_handle); | ||
692 | unsigned char * (*xTmpPage)(unqlite_kv_handle); | ||
693 | void (*xSetUnpin)(unqlite_kv_handle,void (*xPageUnpin)(void *)); | ||
694 | void (*xSetReload)(unqlite_kv_handle,void (*xPageReload)(void *)); | ||
695 | void (*xErr)(unqlite_kv_handle,const char *); | ||
696 | }; | ||
697 | /* | ||
698 | * Key/Value Storage Engine Cursor Object | ||
699 | * | ||
700 | * An instance of a subclass of the following object defines a cursor | ||
701 | * used to scan through a key-value storage engine. | ||
702 | */ | ||
703 | typedef struct unqlite_kv_cursor unqlite_kv_cursor; | ||
704 | struct unqlite_kv_cursor | ||
705 | { | ||
706 | unqlite_kv_engine *pStore; /* Must be first */ | ||
707 | /* Subclasses will typically add additional fields */ | ||
708 | }; | ||
709 | /* | ||
710 | * Possible seek positions. | ||
711 | */ | ||
712 | #define UNQLITE_CURSOR_MATCH_EXACT 1 | ||
713 | #define UNQLITE_CURSOR_MATCH_LE 2 | ||
714 | #define UNQLITE_CURSOR_MATCH_GE 3 | ||
715 | /* | ||
716 | * Key/Value Storage Engine. | ||
717 | * | ||
718 | * A Key-Value storage engine is defined by an instance of the following | ||
719 | * object. | ||
720 | * UnQLite works with run-time interchangeable storage engines (i.e. Hash, B+Tree, R+Tree, LSM, etc.). | ||
721 | * The storage engine works with key/value pairs where both the key | ||
722 | * and the value are byte arrays of arbitrary length and with no restrictions on content. | ||
723 | * UnQLite come with two built-in KV storage engine: A Virtual Linear Hash (VLH) storage | ||
724 | * engine is used for persistent on-disk databases with O(1) lookup time and an in-memory | ||
725 | * hash-table or Red-black tree storage engine is used for in-memory databases. | ||
726 | * Future versions of UnQLite might add other built-in storage engines (i.e. LSM). | ||
727 | * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] | ||
728 | * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. | ||
729 | */ | ||
730 | struct unqlite_kv_engine | ||
731 | { | ||
732 | const unqlite_kv_io *pIo; /* IO methods: MUST be first */ | ||
733 | /* Subclasses will typically add additional fields */ | ||
734 | }; | ||
735 | /* | ||
736 | * Key/Value Storage Engine Virtual Method Table. | ||
737 | * | ||
738 | * Key/Value storage engine methods is defined by an instance of the following | ||
739 | * object. | ||
740 | * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] | ||
741 | * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. | ||
742 | */ | ||
743 | struct unqlite_kv_methods | ||
744 | { | ||
745 | const char *zName; /* Storage engine name [i.e. Hash, B+tree, LSM, R-tree, Mem, etc.]*/ | ||
746 | int szKv; /* 'unqlite_kv_engine' subclass size */ | ||
747 | int szCursor; /* 'unqlite_kv_cursor' subclass size */ | ||
748 | int iVersion; /* Structure version, currently 1 */ | ||
749 | /* Storage engine methods */ | ||
750 | int (*xInit)(unqlite_kv_engine *,int iPageSize); | ||
751 | void (*xRelease)(unqlite_kv_engine *); | ||
752 | int (*xConfig)(unqlite_kv_engine *,int op,va_list ap); | ||
753 | int (*xOpen)(unqlite_kv_engine *,pgno); | ||
754 | int (*xReplace)( | ||
755 | unqlite_kv_engine *, | ||
756 | const void *pKey,int nKeyLen, | ||
757 | const void *pData,unqlite_int64 nDataLen | ||
758 | ); | ||
759 | int (*xAppend)( | ||
760 | unqlite_kv_engine *, | ||
761 | const void *pKey,int nKeyLen, | ||
762 | const void *pData,unqlite_int64 nDataLen | ||
763 | ); | ||
764 | void (*xCursorInit)(unqlite_kv_cursor *); | ||
765 | int (*xSeek)(unqlite_kv_cursor *,const void *pKey,int nByte,int iPos); /* Mandatory */ | ||
766 | int (*xFirst)(unqlite_kv_cursor *); | ||
767 | int (*xLast)(unqlite_kv_cursor *); | ||
768 | int (*xValid)(unqlite_kv_cursor *); | ||
769 | int (*xNext)(unqlite_kv_cursor *); | ||
770 | int (*xPrev)(unqlite_kv_cursor *); | ||
771 | int (*xDelete)(unqlite_kv_cursor *); | ||
772 | int (*xKeyLength)(unqlite_kv_cursor *,int *); | ||
773 | int (*xKey)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
774 | int (*xDataLength)(unqlite_kv_cursor *,unqlite_int64 *); | ||
775 | int (*xData)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
776 | void (*xReset)(unqlite_kv_cursor *); | ||
777 | void (*xCursorRelease)(unqlite_kv_cursor *); | ||
778 | }; | ||
779 | /* | ||
780 | * UnQLite journal file suffix. | ||
781 | */ | ||
782 | #ifndef UNQLITE_JOURNAL_FILE_SUFFIX | ||
783 | #define UNQLITE_JOURNAL_FILE_SUFFIX "_unqlite_journal" | ||
784 | #endif | ||
785 | /* | ||
786 | * Call Context - Error Message Serverity Level. | ||
787 | * | ||
788 | * The following constans are the allowed severity level that can | ||
789 | * passed as the second argument to the [unqlite_context_throw_error()] or | ||
790 | * [unqlite_context_throw_error_format()] interfaces. | ||
791 | * Refer to the official documentation for additional information. | ||
792 | */ | ||
793 | #define UNQLITE_CTX_ERR 1 /* Call context error such as unexpected number of arguments, invalid types and so on. */ | ||
794 | #define UNQLITE_CTX_WARNING 2 /* Call context Warning */ | ||
795 | #define UNQLITE_CTX_NOTICE 3 /* Call context Notice */ | ||
796 | /* | ||
797 | * C-API-REF: Please refer to the official documentation for interfaces | ||
798 | * purpose and expected parameters. | ||
799 | */ | ||
800 | |||
801 | /* Database Engine Handle */ | ||
802 | UNQLITE_APIEXPORT int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode); | ||
803 | UNQLITE_APIEXPORT int unqlite_config(unqlite *pDb,int nOp,...); | ||
804 | UNQLITE_APIEXPORT int unqlite_close(unqlite *pDb); | ||
805 | |||
806 | /* Key/Value (KV) Store Interfaces */ | ||
807 | UNQLITE_APIEXPORT int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); | ||
808 | UNQLITE_APIEXPORT int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); | ||
809 | UNQLITE_APIEXPORT int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); | ||
810 | UNQLITE_APIEXPORT int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); | ||
811 | UNQLITE_APIEXPORT int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 /* in|out */*pBufLen); | ||
812 | UNQLITE_APIEXPORT int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey, | ||
813 | int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
814 | UNQLITE_APIEXPORT int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen); | ||
815 | UNQLITE_APIEXPORT int unqlite_kv_config(unqlite *pDb,int iOp,...); | ||
816 | |||
817 | /* Document (JSON) Store Interfaces powered by the Jx9 Scripting Language */ | ||
818 | UNQLITE_APIEXPORT int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut); | ||
819 | UNQLITE_APIEXPORT int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut); | ||
820 | UNQLITE_APIEXPORT int unqlite_vm_config(unqlite_vm *pVm,int iOp,...); | ||
821 | UNQLITE_APIEXPORT int unqlite_vm_exec(unqlite_vm *pVm); | ||
822 | UNQLITE_APIEXPORT int unqlite_vm_reset(unqlite_vm *pVm); | ||
823 | UNQLITE_APIEXPORT int unqlite_vm_release(unqlite_vm *pVm); | ||
824 | UNQLITE_APIEXPORT int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData); | ||
825 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname); | ||
826 | |||
827 | /* Cursor Iterator Interfaces */ | ||
828 | UNQLITE_APIEXPORT int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut); | ||
829 | UNQLITE_APIEXPORT int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur); | ||
830 | UNQLITE_APIEXPORT int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos); | ||
831 | UNQLITE_APIEXPORT int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor); | ||
832 | UNQLITE_APIEXPORT int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor); | ||
833 | UNQLITE_APIEXPORT int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor); | ||
834 | UNQLITE_APIEXPORT int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor); | ||
835 | UNQLITE_APIEXPORT int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor); | ||
836 | UNQLITE_APIEXPORT int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte); | ||
837 | UNQLITE_APIEXPORT int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
838 | UNQLITE_APIEXPORT int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnData); | ||
839 | UNQLITE_APIEXPORT int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); | ||
840 | UNQLITE_APIEXPORT int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor); | ||
841 | UNQLITE_APIEXPORT int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor); | ||
842 | |||
843 | /* Manual Transaction Manager */ | ||
844 | UNQLITE_APIEXPORT int unqlite_begin(unqlite *pDb); | ||
845 | UNQLITE_APIEXPORT int unqlite_commit(unqlite *pDb); | ||
846 | UNQLITE_APIEXPORT int unqlite_rollback(unqlite *pDb); | ||
847 | |||
848 | /* Utility interfaces */ | ||
849 | UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize); | ||
850 | UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize); | ||
851 | UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size); | ||
852 | UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb); | ||
853 | |||
854 | /* In-process extending interfaces */ | ||
855 | UNQLITE_APIEXPORT int unqlite_create_function(unqlite_vm *pVm,const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData); | ||
856 | UNQLITE_APIEXPORT int unqlite_delete_function(unqlite_vm *pVm, const char *zName); | ||
857 | UNQLITE_APIEXPORT int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData); | ||
858 | UNQLITE_APIEXPORT int unqlite_delete_constant(unqlite_vm *pVm, const char *zName); | ||
859 | |||
860 | /* On Demand Object allocation interfaces */ | ||
861 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm); | ||
862 | UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm); | ||
863 | UNQLITE_APIEXPORT int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue); | ||
864 | UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx); | ||
865 | UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_array(unqlite_context *pCtx); | ||
866 | UNQLITE_APIEXPORT void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue); | ||
867 | |||
868 | /* Dynamically Typed Value Object Management Interfaces */ | ||
869 | UNQLITE_APIEXPORT int unqlite_value_int(unqlite_value *pVal, int iValue); | ||
870 | UNQLITE_APIEXPORT int unqlite_value_int64(unqlite_value *pVal, unqlite_int64 iValue); | ||
871 | UNQLITE_APIEXPORT int unqlite_value_bool(unqlite_value *pVal, int iBool); | ||
872 | UNQLITE_APIEXPORT int unqlite_value_null(unqlite_value *pVal); | ||
873 | UNQLITE_APIEXPORT int unqlite_value_double(unqlite_value *pVal, double Value); | ||
874 | UNQLITE_APIEXPORT int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen); | ||
875 | UNQLITE_APIEXPORT int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...); | ||
876 | UNQLITE_APIEXPORT int unqlite_value_reset_string_cursor(unqlite_value *pVal); | ||
877 | UNQLITE_APIEXPORT int unqlite_value_resource(unqlite_value *pVal, void *pUserData); | ||
878 | UNQLITE_APIEXPORT int unqlite_value_release(unqlite_value *pVal); | ||
879 | |||
880 | /* Foreign Function Parameter Values */ | ||
881 | UNQLITE_APIEXPORT int unqlite_value_to_int(unqlite_value *pValue); | ||
882 | UNQLITE_APIEXPORT int unqlite_value_to_bool(unqlite_value *pValue); | ||
883 | UNQLITE_APIEXPORT unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue); | ||
884 | UNQLITE_APIEXPORT double unqlite_value_to_double(unqlite_value *pValue); | ||
885 | UNQLITE_APIEXPORT const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen); | ||
886 | UNQLITE_APIEXPORT void * unqlite_value_to_resource(unqlite_value *pValue); | ||
887 | UNQLITE_APIEXPORT int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict); | ||
888 | |||
889 | /* Setting The Result Of A Foreign Function */ | ||
890 | UNQLITE_APIEXPORT int unqlite_result_int(unqlite_context *pCtx, int iValue); | ||
891 | UNQLITE_APIEXPORT int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue); | ||
892 | UNQLITE_APIEXPORT int unqlite_result_bool(unqlite_context *pCtx, int iBool); | ||
893 | UNQLITE_APIEXPORT int unqlite_result_double(unqlite_context *pCtx, double Value); | ||
894 | UNQLITE_APIEXPORT int unqlite_result_null(unqlite_context *pCtx); | ||
895 | UNQLITE_APIEXPORT int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen); | ||
896 | UNQLITE_APIEXPORT int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...); | ||
897 | UNQLITE_APIEXPORT int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue); | ||
898 | UNQLITE_APIEXPORT int unqlite_result_resource(unqlite_context *pCtx, void *pUserData); | ||
899 | |||
900 | /* Dynamically Typed Value Object Query Interfaces */ | ||
901 | UNQLITE_APIEXPORT int unqlite_value_is_int(unqlite_value *pVal); | ||
902 | UNQLITE_APIEXPORT int unqlite_value_is_float(unqlite_value *pVal); | ||
903 | UNQLITE_APIEXPORT int unqlite_value_is_bool(unqlite_value *pVal); | ||
904 | UNQLITE_APIEXPORT int unqlite_value_is_string(unqlite_value *pVal); | ||
905 | UNQLITE_APIEXPORT int unqlite_value_is_null(unqlite_value *pVal); | ||
906 | UNQLITE_APIEXPORT int unqlite_value_is_numeric(unqlite_value *pVal); | ||
907 | UNQLITE_APIEXPORT int unqlite_value_is_callable(unqlite_value *pVal); | ||
908 | UNQLITE_APIEXPORT int unqlite_value_is_scalar(unqlite_value *pVal); | ||
909 | UNQLITE_APIEXPORT int unqlite_value_is_json_array(unqlite_value *pVal); | ||
910 | UNQLITE_APIEXPORT int unqlite_value_is_json_object(unqlite_value *pVal); | ||
911 | UNQLITE_APIEXPORT int unqlite_value_is_resource(unqlite_value *pVal); | ||
912 | UNQLITE_APIEXPORT int unqlite_value_is_empty(unqlite_value *pVal); | ||
913 | |||
914 | /* JSON Array/Object Management Interfaces */ | ||
915 | UNQLITE_APIEXPORT unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte); | ||
916 | UNQLITE_APIEXPORT int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData); | ||
917 | UNQLITE_APIEXPORT int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue); | ||
918 | UNQLITE_APIEXPORT int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue); | ||
919 | UNQLITE_APIEXPORT int unqlite_array_count(unqlite_value *pArray); | ||
920 | |||
921 | /* Call Context Handling Interfaces */ | ||
922 | UNQLITE_APIEXPORT int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen); | ||
923 | UNQLITE_APIEXPORT int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...); | ||
924 | UNQLITE_APIEXPORT int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr); | ||
925 | UNQLITE_APIEXPORT int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...); | ||
926 | UNQLITE_APIEXPORT unsigned int unqlite_context_random_num(unqlite_context *pCtx); | ||
927 | UNQLITE_APIEXPORT int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen); | ||
928 | UNQLITE_APIEXPORT void * unqlite_context_user_data(unqlite_context *pCtx); | ||
929 | UNQLITE_APIEXPORT int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData); | ||
930 | UNQLITE_APIEXPORT void * unqlite_context_peek_aux_data(unqlite_context *pCtx); | ||
931 | UNQLITE_APIEXPORT unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx); | ||
932 | UNQLITE_APIEXPORT const char * unqlite_function_name(unqlite_context *pCtx); | ||
933 | |||
934 | /* Call Context Memory Management Interfaces */ | ||
935 | UNQLITE_APIEXPORT void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); | ||
936 | UNQLITE_APIEXPORT void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte); | ||
937 | UNQLITE_APIEXPORT void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk); | ||
938 | |||
939 | /* Global Library Management Interfaces */ | ||
940 | UNQLITE_APIEXPORT int unqlite_lib_config(int nConfigOp,...); | ||
941 | UNQLITE_APIEXPORT int unqlite_lib_init(void); | ||
942 | UNQLITE_APIEXPORT int unqlite_lib_shutdown(void); | ||
943 | UNQLITE_APIEXPORT int unqlite_lib_is_threadsafe(void); | ||
944 | UNQLITE_APIEXPORT const char * unqlite_lib_version(void); | ||
945 | UNQLITE_APIEXPORT const char * unqlite_lib_signature(void); | ||
946 | UNQLITE_APIEXPORT const char * unqlite_lib_ident(void); | ||
947 | UNQLITE_APIEXPORT const char * unqlite_lib_copyright(void); | ||
948 | |||
949 | #endif /* _UNQLITE_H_ */ | ||