diff --git a/.appveyor.yml b/.appveyor.yml index 0b3a0b643..d74337b36 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -16,7 +16,7 @@ matrix: fast_finish: true # Immediately finish build if one of the jobs fails. before_build: - - cmake -DCMAKE_INSTALL_PREFIX=install -DCMAKE_GENERATOR_PLATFORM=x64 . + - cmake -DCI=ON -DCMAKE_INSTALL_PREFIX=install -DCMAKE_GENERATOR_PLATFORM=x64 . build: project: cquery.sln diff --git a/.travis.yml b/.travis.yml index e50c9bd0b..47bb46123 100644 --- a/.travis.yml +++ b/.travis.yml @@ -70,7 +70,7 @@ before_install: - eval "${INSTALL}" script: - - cmake -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Debug . + - cmake -DCI=ON -DCMAKE_INSTALL_PREFIX=install -DCMAKE_BUILD_TYPE=Debug . - cmake --build . -- -j 3 - cmake --build . --target install - ./install/bin/cquery --ci --log-all-to-stderr --test-unit diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a2db0cb4..2c2d864ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.1) -project(cquery LANGUAGES CXX) +project(cquery LANGUAGES CXX C) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) include(DefaultCMakeBuildType) @@ -11,6 +11,8 @@ set(CLANG_DOWNLOAD_LOCATION ${CMAKE_BINARY_DIR} option(SYSTEM_CLANG "Use system installation of Clang instead of \ downloading Clang" OFF) option(ASAN "Compile with address sanitizers" OFF) +option(ASSERTS "Compile with asserts enabled" OFF) +option(CI "Add -Werror or equivalent" OFF) # Sources for the executable are specified at end of CMakeLists.txt add_executable(cquery "") @@ -39,6 +41,7 @@ if(MSVC) target_compile_options(cquery PRIVATE /nologo /EHsc + /D_CRT_SECURE_NO_WARNINGS # don't try to use MSVC std replacements /W3 # roughly -Wall /wd4996 # disable loguru unsafe warnings /wd4722 # ignores warning C4722 @@ -47,13 +50,17 @@ if(MSVC) # (conversion from 'size_t' to 'type'), # roughly -Wno-sign-compare /wd4800 + /wd4068 # Disable unknown pragma warning $<$:/FS> + $<$:/WX> ) else() # Common GCC/Clang(Linux) options target_compile_options(cquery PRIVATE -Wall -Wno-sign-compare + -Wno-unknown-pragmas + $<$:-Werror> ) if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU) @@ -72,6 +79,14 @@ else() endif() endif() + +# Enable asserts +if(ASSERTS) + string(REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + string(REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") +endif() + + ### Download Clang if required if(NOT SYSTEM_CLANG) @@ -87,6 +102,10 @@ endif() ### Libraries +# unqlite +add_subdirectory(third_party/unqlite) +target_link_libraries(cquery PRIVATE unqlite) + # See cmake/FindClang.cmake find_package(Clang ${CLANG_VERSION} REQUIRED) target_link_libraries(cquery PRIVATE Clang::Clang) @@ -136,11 +155,11 @@ target_include_directories(cquery PRIVATE install(TARGETS cquery RUNTIME DESTINATION bin) -# If downloaded Clang is used we have to bundle the required files from +# If downloaded Clang is used we have to bundle the required files from # the downloaded Clang along with the cquery executable if(NOT SYSTEM_CLANG) - # On Linux/FreeBSD/Darwin we set the rpath so cquery can find + # On Linux/FreeBSD/Darwin we set the rpath so cquery can find # libclang.[so,dylib]. On Windows we install libclang.dll to the bin directory # to do the same. @@ -151,7 +170,7 @@ if(NOT SYSTEM_CLANG) set_property(TARGET cquery APPEND PROPERTY INSTALL_RPATH @loader_path/../lib) elseif(${CMAKE_SYSTEM_NAME} STREQUAL Windows) - install(FILES ${DOWNLOADED_CLANG_DIR}/bin/libclang.dll DESTINATION bin) + install(FILES ${DOWNLOADED_CLANG_DIR}/bin/libclang.dll DESTINATION bin) endif() # Install libclang.[so,lib,dylib] to lib directory @@ -194,15 +213,15 @@ if(NOT SYSTEM_CLANG) # prefix. file(GLOB CLANG_INCLUDE_DIR ${DOWNLOADED_CLANG_DIR}/lib/clang/*/include) - string(REPLACE ${DOWNLOADED_CLANG_DIR}/ "" + string(REPLACE ${DOWNLOADED_CLANG_DIR}/ "" CLANG_INCLUDE_DIR ${CLANG_INCLUDE_DIR}) # Add trailing slash to overwrite destination directory instead of putting the # directory inside the destination directory install(DIRECTORY ${DOWNLOADED_CLANG_DIR}/${CLANG_INCLUDE_DIR}/ DESTINATION ${CLANG_INCLUDE_DIR}) - - # include/c++/v1 is not included in every Clang download (Windows) so we check + + # include/c++/v1 is not included in every Clang download (Windows) so we check # if it exists first if(IS_DIRECTORY ${DOWNLOADED_CLANG_DIR}/include/c++/v1) install(DIRECTORY ${DOWNLOADED_CLANG_DIR}/include/c++/v1/ @@ -245,6 +264,7 @@ target_sources(cquery PRIVATE third_party/pugixml/src/pugixml.cpp) target_sources(cquery PRIVATE + src/c_cpp_properties.cc src/cache_manager.cc src/clang_complete.cc src/clang_cursor.cc @@ -256,6 +276,7 @@ target_sources(cquery PRIVATE src/clang_utils.cc src/code_complete_cache.cc src/command_line.cc + src/compiler.cc src/diagnostics_engine.cc src/file_consumer.cc src/file_contents.cc @@ -275,7 +296,6 @@ target_sources(cquery PRIVATE src/platform_posix.cc src/platform_win.cc src/platform.cc - src/port.cc src/position.cc src/project.cc src/query_utils.cc @@ -288,6 +308,7 @@ target_sources(cquery PRIVATE src/task.cc src/test.cc src/third_party_impl.cc + src/threaded_queue.cc src/timer.cc src/timestamp_manager.cc src/type_printer.cc @@ -299,14 +320,12 @@ target_sources(cquery PRIVATE src/messages/cquery_base.cc src/messages/cquery_call_hierarchy.cc src/messages/cquery_callers.cc - src/messages/cquery_derived.cc src/messages/cquery_did_view.cc src/messages/cquery_file_info.cc src/messages/cquery_freshen_index.cc src/messages/cquery_index_file.cc src/messages/cquery_inheritance_hierarchy.cc src/messages/cquery_member_hierarchy.cc - src/messages/cquery_random.cc src/messages/cquery_vars.cc src/messages/cquery_wait.cc src/messages/exit.cc @@ -325,6 +344,7 @@ target_sources(cquery PRIVATE src/messages/text_document_document_symbol.cc src/messages/text_document_formatting.cc src/messages/text_document_hover.cc + src/messages/text_document_implementation.cc src/messages/text_document_range_formatting.cc src/messages/text_document_references.cc src/messages/text_document_rename.cc diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..dfec96eba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017-2018 Jacob Dufault + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/c_cpp_properties.cc b/src/c_cpp_properties.cc new file mode 100644 index 000000000..38c7e2083 --- /dev/null +++ b/src/c_cpp_properties.cc @@ -0,0 +1,141 @@ +#include "c_cpp_properties.h" + +#include +#include +#include + +#include + +#include +#include + +#ifdef _WIN32 + #define CURRENT_PLATFORM "Win32" +#elif defined(__APPLE__) + #define CURRENT_PLATFORM "Mac" +#else + #define CURRENT_PLATFORM "Linux" +#endif + +namespace { + +std::string_view kCurrentPlatform = CURRENT_PLATFORM; + +optional LoadCCppPropertiesFromStr( + const std::string_view filecontent, + const std::string& project_dir) { + CCppProperties res; + res.args.push_back("%clang"); + rapidjson::Document document; + document.Parse(filecontent.data()); + if (!document.IsObject()) + return {}; + auto conf_it = document.FindMember("configurations"); + if (conf_it == document.MemberEnd()) + return {}; + if (!conf_it->value.IsArray()) + return {}; + for (auto& conf : conf_it->value.GetArray()) { + if (!conf.HasMember("name") || conf["name"].GetString() != kCurrentPlatform) + continue; + auto def_it = conf.FindMember("defines"); + if (def_it != conf.MemberEnd() && def_it->value.IsArray()) { + for (auto& def : def_it->value.GetArray()) { + res.args.push_back(std::string("-D") + def.GetString()); + } + } + + auto inc_it = conf.FindMember("includePath"); + if (inc_it != conf.MemberEnd() && inc_it->value.IsArray()) { + for (auto& inc : inc_it->value.GetArray()) { + // TODO maybe handle "path/**" ? + auto incpath = std::regex_replace( + std::string(inc.GetString()), + std::regex("\\$\\{workspaceFolder\\}"), + project_dir); + res.args.push_back("-I" + incpath); + } + } + + if (res.cStandard.empty() && conf.HasMember("cStandard")) { + res.cStandard = conf["cStandard"].GetString(); + res.args.push_back("%c -std=" + res.cStandard); + } + if (res.cppStandard.empty() && conf.HasMember("cppStandard")) { + res.cppStandard = conf["cppStandard"].GetString(); + res.args.push_back("%cpp -std=" + res.cppStandard); + } + } + + return res; +} + +} // namespace + +optional LoadCCppProperties( + const std::string& json_full_path, + const std::string& project_dir) { + std::ifstream fc_stream(json_full_path); + if (!fc_stream) + return {}; + std::string filecontent{ std::istreambuf_iterator(fc_stream), + std::istreambuf_iterator() }; + return LoadCCppPropertiesFromStr(filecontent, project_dir); +} + + +TEST_SUITE("CCppProperties") { + TEST_CASE("basic") { + const char* testjson = R"( + { + "configurations": [ + { + "name": ")" CURRENT_PLATFORM R"(", + "browse": { + "path": [ + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + }, + "includePath": [ + "${workspaceFolder}", + "foo", + "${workspaceFolder}/bar" + ], + "defines": [ + "FOO", + "BAR=1" + ], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "clang-x64" + } + ], + "version": 4 + })"; + auto res = LoadCCppPropertiesFromStr(testjson, "/proj/"); + REQUIRE(res.has_value()); + CCppProperties& val = res.value(); + REQUIRE_EQ(val.cStandard, "c11"); + REQUIRE_EQ(val.cppStandard, "c++17"); + std::vector args{"%clang", "-DFOO", "-DBAR=1", "-I/proj/", + "-Ifoo", "-I/proj//bar", "%c -std=c11", "%cpp -std=c++17"}; + bool args_equal = val.args == args; + if (!args_equal) { + if (val.args.size() != args.size()) { + std::cout << "\tval.args size " << val.args.size() + << " , args size " << args.size() << std::endl; + } + for(size_t i = 0; i < std::min(val.args.size(), args.size()); ++i) { + auto& a1 = val.args[i]; + auto& a2 = args[i]; + if (a1 != a2) { + std::cout << "\tArg " << a1 << " != " << a2 << std::endl; + } + } + } + REQUIRE(args_equal); + } +} + diff --git a/src/c_cpp_properties.h b/src/c_cpp_properties.h new file mode 100644 index 000000000..c77dd8a98 --- /dev/null +++ b/src/c_cpp_properties.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include "optional.h" + +struct CCppProperties { + std::string cStandard; + std::string cppStandard; + std::vector args; +}; + +optional LoadCCppProperties( + const std::string& json_full_path, + const std::string& project_dir); diff --git a/src/cache_manager.cc b/src/cache_manager.cc index 5f660af92..adb4015cf 100644 --- a/src/cache_manager.cc +++ b/src/cache_manager.cc @@ -10,145 +10,267 @@ #include #include -namespace { - -// Manages loading caches from file paths for the indexer process. -struct RealCacheManager : ICacheManager { - explicit RealCacheManager() {} - ~RealCacheManager() override = default; - - void WriteToCache(IndexFile& file) override { - std::string cache_path = GetCachePath(file.path); - WriteToFile(cache_path, file.file_contents); - - std::string indexed_content = Serialize(g_config->cacheFormat, file); - WriteToFile(AppendSerializationFormat(cache_path), indexed_content); - } - - optional LoadCachedFileContents( - const std::string& path) override { - return ReadContent(GetCachePath(path)); - } +#include "query.h" +#include "unqlite.h" - std::unique_ptr RawCacheLoad(const std::string& path) override { - std::string cache_path = GetCachePath(path); - optional file_content = ReadContent(cache_path); - optional serialized_indexed_content = - ReadContent(AppendSerializationFormat(cache_path)); - if (!file_content || !serialized_indexed_content) - return nullptr; +namespace { - return Deserialize(g_config->cacheFormat, path, *serialized_indexed_content, - *file_content, IndexFile::kMajorVersion); - } +// Storing index+content in a file directory (possibly shared between multiple +// cquery caches, since it could be a user-setting) The key already has to +// contain the distinction between content and index +struct FileBasedCacheDriver : public ICacheStore { + FileBasedCacheDriver(Config* config, + std::string projectDir, + std::string externalsDir) + : projectDir_(projectDir), externalsDir_(externalsDir) {} - std::string GetCachePath(const std::string& source_file) { + std::string KeyToFilePath(const std::string& key) { assert(!g_config->cacheDirectory.empty()); std::string cache_file; + size_t len = g_config->projectRoot.size(); - if (StartsWith(source_file, g_config->projectRoot)) { - cache_file = EscapeFileName(g_config->projectRoot) + '/' + - EscapeFileName(source_file.substr(len)); + if (StartsWith(key, g_config->projectRoot)) { + cache_file = projectDir_ + '/' + EscapeFileName(key.substr(len)); } else { - cache_file = '@' + EscapeFileName(g_config->projectRoot) + '/' + - EscapeFileName(source_file); + cache_file = externalsDir_ + '/' + EscapeFileName(key); } return g_config->cacheDirectory + cache_file; } - std::string AppendSerializationFormat(const std::string& base) { - switch (g_config->cacheFormat) { - case SerializeFormat::Json: - return base + ".json"; - case SerializeFormat::MessagePack: - return base + ".mpack"; - } - assert(false); - return ".json"; + optional Read(const std::string& key) override { + std::string file_path = KeyToFilePath(key); + return ReadContent(file_path); + } + + void Write(const std::string& key, const std::string& value) override { + std::string file_path = KeyToFilePath(key); + WriteToFile(file_path, value); } + + private: + std::string projectDir_; + std::string externalsDir_; }; -struct FakeCacheManager : ICacheManager { - explicit FakeCacheManager(const std::vector& entries) - : entries_(entries) {} +void UnqliteHandleResult(std::string operation, unqlite* database, int ret) { + if (ret != UNQLITE_OK) { + const char* zBuf; + int iLen; + unqlite_config(database, UNQLITE_CONFIG_ERR_LOG, &zBuf, &iLen); + LOG_S(WARNING) << "Unqlite error: \"" << std::string(zBuf, zBuf + iLen) << "\"."; - void WriteToCache(IndexFile& file) override { assert(false); } + switch(ret) { + case UNQLITE_IOERR: + case UNQLITE_NOMEM: + LOG_S(ERROR) << "Rolling back the last commit."; + unqlite_rollback(database); + } + } +} + +// Storing index+content in an unqlite database (possibly shared between +// multiple cquery caches, since it could be a user-setting) +struct UnqliteCacheDriver : public ICacheStore { + UnqliteCacheDriver(unqlite* database) : database_(database), bytesSinceCommit_(0) {} + + UnqliteCacheDriver(UnqliteCacheDriver&) = delete; + + optional Read(const std::string& key) override { + unqlite_int64 valueLength; + std::string result; + int ret = unqlite_kv_fetch(database_, key.data(), key.size(), nullptr, + &valueLength); - optional LoadCachedFileContents( - const std::string& path) override { - for (const FakeCacheEntry& entry : entries_) { - if (entry.path == path) { - return entry.content; - } + if (ret == UNQLITE_OK) { + result.resize(valueLength); + ret = unqlite_kv_fetch(database_, key.data(), key.size(), + const_cast(result.data()), &valueLength); } - return nullopt; + if (ret == UNQLITE_OK) + { + LOG_S(INFO) << "unqlite: Handing out cache for key \"" << key << "\""; + return std::move(result); + } + else + { + LOG_S(WARNING) << "unqlite: No data for key \"" << key << "\""; + return {}; + } } - std::unique_ptr RawCacheLoad(const std::string& path) override { - for (const FakeCacheEntry& entry : entries_) { - if (entry.path == path) { - return Deserialize(SerializeFormat::Json, path, entry.json, "", - nullopt); - } + void Write(const std::string& key, const std::string& value) override { + int ret; + while ((ret = unqlite_kv_store(database_, key.data(), key.size(), + value.data(), value.size())) == UNQLITE_BUSY) + ; + if (ret != UNQLITE_OK) { + UnqliteHandleResult("unqlite_kv_store", database_, ret); } + else + { + bytesSinceCommit_ += value.size(); - return nullptr; + if (bytesSinceCommit_ > 16*1024*1024) + { + ret = unqlite_commit(database_); + UnqliteHandleResult("unqlite_commit", database_, ret); + if (ret == UNQLITE_OK) bytesSinceCommit_ = 0u; + } + } } - std::vector entries_; + void Close() override { + LOG_S(INFO) << "Unqlite: Closing the store."; + int ret; + while ((ret = unqlite_close(database_)) == UNQLITE_BUSY) + ; + + if (ret != UNQLITE_OK) { + UnqliteHandleResult("unqlite_close", database_, ret); + } + } + + ~UnqliteCacheDriver() override {} + + size_t bytesSinceCommit_; + unqlite* database_; }; +std::string SerializationFormatToSuffix(SerializeFormat format) { + switch (format) { + case SerializeFormat::Json: + return ".json"; + case SerializeFormat::MessagePack: + return ".mpack"; + } + assert(false); + return ".json"; +} + } // namespace -// static -std::shared_ptr ICacheManager::Make() { - return std::make_shared(); -} +IndexCache::IndexCache(std::shared_ptr driver) + : driver_(std::move(driver)) {} -// static -std::shared_ptr ICacheManager::MakeFake( - const std::vector& entries) { - return std::make_shared(entries); -} -ICacheManager::~ICacheManager() = default; +std::unique_ptr IndexCache::LoadIndexFileFromCache( + const AbsolutePath& file) { + optional file_content = driver_->Read(file.path); + optional serialized_indexed_content = driver_->Read( + file.path + SerializationFormatToSuffix(g_config->cacheFormat)); + + if (!file_content || !serialized_indexed_content) + { + LOG_S(WARNING) << "IndexCache::LoadIndexFileFromCache: Cannot serve cache request for \"" << file.path << "\""; + return nullptr; + } + + return Deserialize(g_config->cacheFormat, file.path, + *serialized_indexed_content, *file_content, + IndexFile::kMajorVersion); +} -IndexFile* ICacheManager::TryLoad(const std::string& path) { - auto it = caches_.find(path); +// Tries to recover an index file (content+serialized index) for a given source +// file from the cache store and returns a non-owning reference or null, +// buffering the IndexFile internally for later take +IndexFile* IndexCache::TryLoad(const AbsolutePath& path) { + IndexFile* result = nullptr; + auto it = caches_.find(path.path); if (it != caches_.end()) return it->second.get(); - std::unique_ptr cache = RawCacheLoad(path); - if (!cache) - return nullptr; + auto ptr = LoadIndexFileFromCache(path); + if (ptr) { + result = ptr.get(); + caches_.emplace(path.path, std::move(ptr)); + } + else + { + LOG_S(WARNING) << "IndexCache::TryLoad: Cannot serve cache request for \"" << path.path << "\""; + } - caches_[path] = std::move(cache); - return caches_[path].get(); + return result; } -std::unique_ptr ICacheManager::TryTakeOrLoad( - const std::string& path) { - auto it = caches_.find(path); - if (it != caches_.end()) { +// Tries to recover an index file (content+serialized index) for a given source +// file from the cache store and returns a non-owning reference or null +std::unique_ptr IndexCache::TryTakeOrLoad( + const AbsolutePath& path) { + auto it = caches_.find(path.path); + if (it != caches_.end()) + { auto result = std::move(it->second); caches_.erase(it); return result; } - return RawCacheLoad(path); + return LoadIndexFileFromCache(path); } -std::unique_ptr ICacheManager::TakeOrLoad(const std::string& path) { - auto result = TryTakeOrLoad(path); - assert(result); - return result; +// Only load the buffered file content from the cache +optional IndexCache::TryLoadContent(const AbsolutePath& path) { + return driver_->Read(path.path); +} + +// Write an IndexFile to the cache storage +void IndexCache::Write(IndexFile& file) { + driver_->Write(file.path, file.file_contents); + driver_->Write(file.path + SerializationFormatToSuffix(g_config->cacheFormat), + Serialize(g_config->cacheFormat, file)); } -void ICacheManager::IterateLoadedCaches(std::function fn) { - for (const auto& cache : caches_) { - assert(cache.second); - fn(cache.second.get()); +// Iterate over all loaded caches. +void IndexCache::IterateLoadedCaches(std::function fn) { + for (auto& file : caches_) { + fn(file.second.get()); } } + +std::shared_ptr MakeIndexCache(std::shared_ptr store) { + return std::make_shared(std::move(store)); +} + +// Returns null if the given root path does not exist +std::shared_ptr OpenOrConnectFileStore( + const AbsolutePath& path) { + const std::string projectDirName = EscapeFileName(g_config->projectRoot); + const std::string externalsDirName = + '@' + EscapeFileName(g_config->projectRoot); + + MakeDirectoryRecursive(g_config->cacheDirectory + projectDirName); + MakeDirectoryRecursive(g_config->cacheDirectory + externalsDirName); + + LOG_S(INFO) << "Connecting to file storage in directory \"" << path.path + << "\"."; + + return std::make_shared(g_config, projectDirName, + externalsDirName); +} + +// Return null if the given file path does not exists and cannot be created +std::shared_ptr OpenOrConnectUnqliteStore( + const AbsolutePath& path_to_db) { + std::string databaseFile = + g_config->cacheDirectory + EscapeFileName(g_config->projectRoot) + ".db"; + unqlite* database = nullptr; + + LOG_S(INFO) << "Connecting to unqlite storage in directory \"" << databaseFile + << "\"."; + + int ret = unqlite_open(&database, databaseFile.c_str(), UNQLITE_OPEN_CREATE); + + if (ret != UNQLITE_OK) + LOG_S(WARNING) << "Unqlite: unqlite_open reported error condition " << ret + << "."; + + ret = unqlite_config(database, UNQLITE_CONFIG_MAX_PAGE_CACHE, 64*1024); + + // if (ret == UNQLITE_OK) return + // std::make_shared(database); + if (ret == UNQLITE_OK) + return std::shared_ptr(new UnqliteCacheDriver(database)); + else + return nullptr; +} diff --git a/src/cache_manager.h b/src/cache_manager.h index 4876523dd..f65aa5337 100644 --- a/src/cache_manager.h +++ b/src/cache_manager.h @@ -8,43 +8,61 @@ #include #include +#include "query.h" + struct Config; struct IndexFile; -struct ICacheManager { - struct FakeCacheEntry { - std::string path; - std::string content; - std::string json; - }; +struct ICacheStore; + +std::string PathToContentKey(const AbsolutePath& path); +std::string PathToIndexKey(const AbsolutePath& path); + +struct ICacheStore { + virtual optional Read(const std::string& key) = 0; + virtual void Write(const std::string& key, const std::string& value) = 0; + virtual void Close() {} + virtual ~ICacheStore() {} +}; - static std::shared_ptr Make(); - static std::shared_ptr MakeFake( - const std::vector& entries); +// Returns null if the given root path does not exist +std::shared_ptr OpenOrConnectFileStore(const AbsolutePath& path); - virtual ~ICacheManager(); +// Return null if the given file path does not exists and cannot be created +std::shared_ptr OpenOrConnectUnqliteStore( + const AbsolutePath& path_to_db); - // Tries to load a cache for |path|, returning null if there is none. The - // cache loader still owns the cache. - IndexFile* TryLoad(const std::string& path); - // Takes the existing cache or loads the cache at |path|. May return null if - // the cache does not exist. - std::unique_ptr TryTakeOrLoad(const std::string& path); +// Note that IndexCaches are _not_ thread-safe. +struct IndexCache { + public: + IndexCache(std::shared_ptr driver); - // Takes the existing cache or loads the cache at |path|. Asserts the cache - // exists. - std::unique_ptr TakeOrLoad(const std::string& path); + // Tries to recover an index file (content+serialized index) for a given + // source file from the cache store and returns a non-owning reference or + // null, buffering the IndexFile internally for later take + IndexFile* TryLoad(const AbsolutePath& path); - virtual void WriteToCache(IndexFile& file) = 0; + // Tries to recover an index file (content+serialized index) for a given + // source file from the cache store and returns a non-owning reference or null + std::unique_ptr TryTakeOrLoad(const AbsolutePath& path); - virtual optional LoadCachedFileContents( - const std::string& path) = 0; + // Only load the buffered file content from the cache + optional TryLoadContent(const AbsolutePath& path); + + // Write an IndexFile to the cache storage + void Write(IndexFile& file); // Iterate over all loaded caches. void IterateLoadedCaches(std::function fn); - protected: - virtual std::unique_ptr RawCacheLoad(const std::string& path) = 0; - std::unordered_map> caches_; + private: + std::unique_ptr LoadIndexFileFromCache(const AbsolutePath& path); + + std::shared_ptr driver_; + std::unordered_map> + caches_; // Using NormalizedPath here would add a dependency to this + // header }; + +std::shared_ptr MakeIndexCache(std::shared_ptr store); diff --git a/src/clang_complete.cc b/src/clang_complete.cc index 0108ceddd..aeaec79bc 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -268,9 +268,9 @@ void BuildDetailString(CXCompletionString completion_string, bool include_snippets, int& angle_stack) { int num_chunks = clang_getNumCompletionChunks(completion_string); - auto append = [&](const char* text) { + auto append_possible_snippet = [&](const char* text) { detail += text; - if (do_insert) + if (do_insert && include_snippets) insert += text; }; for (int i = 0; i < num_chunks; ++i) { @@ -341,46 +341,46 @@ void BuildDetailString(CXCompletionString completion_string, } case CXCompletionChunk_LeftParen: - append("("); + append_possible_snippet("("); break; case CXCompletionChunk_RightParen: - append(")"); + append_possible_snippet(")"); break; case CXCompletionChunk_LeftBracket: - append("["); + append_possible_snippet("["); break; case CXCompletionChunk_RightBracket: - append("]"); + append_possible_snippet("]"); break; case CXCompletionChunk_LeftBrace: - append("{"); + append_possible_snippet("{"); break; case CXCompletionChunk_RightBrace: - append("}"); + append_possible_snippet("}"); break; case CXCompletionChunk_LeftAngle: ++angle_stack; - append("<"); + append_possible_snippet("<"); break; case CXCompletionChunk_RightAngle: --angle_stack; - append(">"); + append_possible_snippet(">"); break; case CXCompletionChunk_Comma: - append(", "); + append_possible_snippet(", "); break; case CXCompletionChunk_Colon: - append(":"); + append_possible_snippet(":"); break; case CXCompletionChunk_SemiColon: - append(";"); + append_possible_snippet(";"); break; case CXCompletionChunk_Equal: - append("="); + append_possible_snippet("="); break; case CXCompletionChunk_HorizontalSpace: case CXCompletionChunk_VerticalSpace: - append(" "); + append_possible_snippet(" "); break; } } @@ -569,10 +569,10 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) { // label/filterText/insertText BuildCompletionItemTexts(ls_result, result.CompletionString, - g_config->client.snippetSupport); + g_config->completion.enableSnippets); for (auto i = first_idx; i < ls_result.size(); ++i) { - if (g_config->client.snippetSupport && + if (g_config->completion.enableSnippets && ls_result[i].insertTextFormat == lsInsertTextFormat::Snippet) { ls_result[i].insertText += "$0"; } @@ -589,9 +589,9 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) { ls_completion_item.insertText, do_insert, ls_completion_item.insertTextFormat, &ls_completion_item.parameters_, - g_config->client.snippetSupport, angle_stack); + g_config->completion.enableSnippets, angle_stack); assert(angle_stack == 0); - if (g_config->client.snippetSupport && + if (g_config->completion.enableSnippets && ls_completion_item.insertTextFormat == lsInsertTextFormat::Snippet) { ls_completion_item.insertText += "$0"; @@ -619,7 +619,7 @@ void DiagnosticsQueryMain(ClangCompleteManager* completion_manager) { // Fetching the completion request blocks until we have a request. std::unique_ptr request = completion_manager->diagnostics_request_.Dequeue(); - if (!request) + if (!request || !g_config->diagnostics.onType) continue; std::string path = request->path; @@ -675,7 +675,7 @@ void DiagnosticsQueryMain(ClangCompleteManager* completion_manager) { if (diagnostic && diagnostic->range.start.line >= 0) ls_diagnostics.push_back(*diagnostic); } - completion_manager->on_diagnostic_(session->file.filename, ls_diagnostics); + completion_manager->on_diagnostic_(path, ls_diagnostics); } } @@ -707,12 +707,10 @@ ClangCompleteManager::DiagnosticRequest::DiagnosticRequest( ClangCompleteManager::ClangCompleteManager(Project* project, WorkingFiles* working_files, OnDiagnostic on_diagnostic, - OnIndex on_index, OnDropped on_dropped) : project_(project), working_files_(working_files), on_diagnostic_(on_diagnostic), - on_index_(on_index), on_dropped_(on_dropped), preloaded_sessions_(kMaxPreloadedSessions), completion_sessions_(kMaxCompletionSessions) { @@ -728,9 +726,11 @@ void ClangCompleteManager::CodeComplete( const lsRequestId& id, const lsTextDocumentPositionParams& completion_location, const OnComplete& on_complete) { - completion_request_.PushBack(std::make_unique( - id, completion_location.textDocument.uri.GetAbsolutePath(), - completion_location.position, on_complete)); + completion_request_.Enqueue( + std::make_unique( + id, completion_location.textDocument.uri.GetAbsolutePath(), + completion_location.position, on_complete), + true /*priority*/); } void ClangCompleteManager::DiagnosticsUpdate(const std::string& path) { @@ -741,8 +741,8 @@ void ClangCompleteManager::DiagnosticsUpdate(const std::string& path) { has = true; }); if (!has) { - diagnostics_request_.PushBack(std::make_unique(path), - true /*priority*/); + diagnostics_request_.Enqueue(std::make_unique(path), + true /*priority*/); } } @@ -755,7 +755,7 @@ void ClangCompleteManager::NotifyView(const AbsolutePath& filename) { // Only reparse the file if we create a new CompletionSession. if (EnsureCompletionOrCreatePreloadSession(filename)) - preload_requests_.PushBack(PreloadRequest(filename), true /*priority*/); + preload_requests_.Enqueue(PreloadRequest(filename), true /*priority*/); } void ClangCompleteManager::NotifyEdit(const AbsolutePath& filename) { @@ -774,7 +774,7 @@ void ClangCompleteManager::NotifySave(const AbsolutePath& filename) { // EnsureCompletionOrCreatePreloadSession(filename); - preload_requests_.PushBack(PreloadRequest(filename), true /*priority*/); + preload_requests_.Enqueue(PreloadRequest(filename), true /*priority*/); } void ClangCompleteManager::NotifyClose(const AbsolutePath& filename) { diff --git a/src/clang_complete.h b/src/clang_complete.h index e35f72df0..9c6dde5eb 100644 --- a/src/clang_complete.h +++ b/src/clang_complete.h @@ -47,10 +47,6 @@ struct ClangCompleteManager { using OnDiagnostic = std::function diagnostics)>; - using OnIndex = std::function& unsaved, - const std::string& path, - const std::vector& args)>; using OnComplete = std::function& results, @@ -83,7 +79,6 @@ struct ClangCompleteManager { ClangCompleteManager(Project* project, WorkingFiles* working_files, OnDiagnostic on_diagnostic, - OnIndex on_index, OnDropped on_dropped); ~ClangCompleteManager(); @@ -129,7 +124,6 @@ struct ClangCompleteManager { Project* project_; WorkingFiles* working_files_; OnDiagnostic on_diagnostic_; - OnIndex on_index_; OnDropped on_dropped_; using LruSessionCache = diff --git a/src/clang_cursor.cc b/src/clang_cursor.cc index 8ce4c2626..7be73be17 100644 --- a/src/clang_cursor.cc +++ b/src/clang_cursor.cc @@ -162,6 +162,18 @@ Usr ClangCursor::get_usr_hash() const { return ret; } +optional ClangCursor::get_opt_usr_hash() const { + CXString usr = clang_getCursorUSR(cx_cursor); + const char* str = clang_getCString(usr); + if (!str || str[0] == '\0') { + clang_disposeString(usr); + return nullopt; + } + Usr ret = HashUsr(str); + clang_disposeString(usr); + return ret; +} + bool ClangCursor::is_definition() const { return clang_isCursorDefinition(cx_cursor); } @@ -220,7 +232,7 @@ std::string ClangCursor::get_type_description() const { return ::ToString(clang_getTypeSpelling(type)); } -NtString ClangCursor::get_comments() const { +std::string ClangCursor::get_comments() const { CXSourceRange range = clang_Cursor_getCommentRange(cx_cursor); if (clang_Range_isNull(range)) return {}; @@ -278,7 +290,7 @@ NtString ClangCursor::get_comments() const { ret.pop_back(); if (ret.empty()) return {}; - return static_cast(ret); + return ret; } std::string ClangCursor::ToString() const { diff --git a/src/clang_cursor.h b/src/clang_cursor.h index 23eed1bb1..a89caf4ef 100644 --- a/src/clang_cursor.h +++ b/src/clang_cursor.h @@ -1,6 +1,5 @@ #pragma once -#include "nt_string.h" #include "position.h" #include @@ -65,6 +64,7 @@ class ClangCursor { std::string get_display_name() const; std::string get_usr() const; Usr get_usr_hash() const; + optional get_opt_usr_hash() const; bool is_definition() const; @@ -85,7 +85,7 @@ class ClangCursor { bool is_valid_kind() const; std::string get_type_description() const; - NtString get_comments() const; + std::string get_comments() const; std::string ToString() const; diff --git a/src/clang_indexer.cc b/src/clang_indexer.cc index f445ec844..06a0132a1 100644 --- a/src/clang_indexer.cc +++ b/src/clang_indexer.cc @@ -475,19 +475,42 @@ void SetUsePreflight(IndexFile* db, ClangCursor parent) { // |parent| should be resolved before using |SetUsePreflight| so that |def| will // not be invalidated by |To{Func,Type,Var}Id|. -Use SetUse(IndexFile* db, Range range, ClangCursor parent, Role role) { +IndexId::LexicalRef SetUse(IndexFile* db, + Range range, + ClangCursor parent, + Role role) { switch (GetSymbolKind(parent.get_kind())) { case SymbolKind::Func: - return Use(range, db->ToFuncId(parent.cx_cursor), SymbolKind::Func, role, - {}); + return IndexId::LexicalRef(range, db->ToFuncId(parent.cx_cursor), + SymbolKind::Func, role); case SymbolKind::Type: - return Use(range, db->ToTypeId(parent.cx_cursor), SymbolKind::Type, role, - {}); + return IndexId::LexicalRef(range, db->ToTypeId(parent.cx_cursor), + SymbolKind::Type, role); case SymbolKind::Var: - return Use(range, db->ToVarId(parent.cx_cursor), SymbolKind::Var, role, - {}); + return IndexId::LexicalRef(range, db->ToVarId(parent.cx_cursor), + SymbolKind::Var, role); default: - return Use(range, Id(), SymbolKind::File, role, {}); + return IndexId::LexicalRef(range, AnyId(), SymbolKind::File, role); + } +} +// |parent| should be resolved before using |SetUsePreflight| so that |def| will +// not be invalidated by |To{Func,Type,Var}Id|. +IndexId::LexicalRef SetRef(IndexFile* db, + Range range, + ClangCursor parent, + Role role) { + switch (GetSymbolKind(parent.get_kind())) { + case SymbolKind::Func: + return IndexId::LexicalRef(range, db->ToFuncId(parent.cx_cursor), + SymbolKind::Func, role); + case SymbolKind::Type: + return IndexId::LexicalRef(range, db->ToTypeId(parent.cx_cursor), + SymbolKind::Type, role); + case SymbolKind::Var: + return IndexId::LexicalRef(range, db->ToVarId(parent.cx_cursor), + SymbolKind::Var, role); + default: + return IndexId::LexicalRef(range, AnyId(), SymbolKind::File, role); } } @@ -533,9 +556,9 @@ void SetTypeName(IndexType* type, // strips // qualifies from |cursor| (ie, Foo* => Foo) and removes template arguments // (ie, Foo => Foo<*,*>). -optional ResolveToDeclarationType(IndexFile* db, - ClangCursor cursor, - IndexParam* param) { +optional ResolveToDeclarationType(IndexFile* db, + ClangCursor cursor, + IndexParam* param) { ClangType type = cursor.get_type(); // auto x = new Foo() will not be deduced to |Foo| if we do not use the @@ -553,15 +576,10 @@ optional ResolveToDeclarationType(IndexFile* db, ClangCursor declaration = type.get_declaration().template_specialization_to_template_definition(); - CXString cx_usr = clang_getCursorUSR(declaration.cx_cursor); - const char* str_usr = clang_getCString(cx_usr); - if (!str_usr || str_usr[0] == '\0') { - clang_disposeString(cx_usr); + optional usr = declaration.get_opt_usr_hash(); + if (!usr) return nullopt; - } - Usr usr = HashUsr(str_usr); - clang_disposeString(cx_usr); - IndexTypeId type_id = db->ToTypeId(usr); + IndexId::Type type_id = db->ToTypeId(*usr); IndexType* typ = db->Resolve(type_id); if (typ->def.detailed_name.empty()) { std::string name = declaration.get_spell_name(); @@ -645,7 +663,7 @@ void SetVarDetail(IndexVar* var, def.short_name_size = short_name.size(); if (is_first_seen) { - optional var_type = + optional var_type = ResolveToDeclarationType(db, cursor, param); if (var_type) { // Don't treat enum definition variables as instantiations. @@ -662,32 +680,54 @@ void SetVarDetail(IndexVar* var, void OnIndexReference_Function(IndexFile* db, Range loc, ClangCursor parent_cursor, - IndexFuncId called_id, + IndexId::Func called_id, Role role) { switch (GetSymbolKind(parent_cursor.get_kind())) { case SymbolKind::Func: { IndexFunc* parent = db->Resolve(db->ToFuncId(parent_cursor.cx_cursor)); IndexFunc* called = db->Resolve(called_id); parent->def.callees.push_back( - SymbolRef(loc, called->id, SymbolKind::Func, role)); - called->uses.push_back(Use(loc, parent->id, SymbolKind::Func, role, {})); + IndexId::SymbolRef(loc, called->id, SymbolKind::Func, role)); + called->uses.push_back( + IndexId::LexicalRef(loc, parent->id, SymbolKind::Func, role)); break; } case SymbolKind::Type: { IndexType* parent = db->Resolve(db->ToTypeId(parent_cursor.cx_cursor)); IndexFunc* called = db->Resolve(called_id); called = db->Resolve(called_id); - called->uses.push_back(Use(loc, parent->id, SymbolKind::Type, role, {})); + called->uses.push_back( + IndexId::LexicalRef(loc, parent->id, SymbolKind::Type, role)); break; } default: { IndexFunc* called = db->Resolve(called_id); - called->uses.push_back(Use(loc, Id(), SymbolKind::File, role, {})); + called->uses.push_back( + IndexId::LexicalRef(loc, AnyId(), SymbolKind::File, role)); break; } } } +template +void Uniquify(std::vector>& ids) { + std::unordered_set> seen; + size_t n = 0; + for (size_t i = 0; i < ids.size(); i++) + if (seen.insert(ids[i]).second) + ids[n++] = ids[i]; + ids.resize(n); +} + +void Uniquify(std::vector& refs) { + std::unordered_set seen; + size_t n = 0; + for (size_t i = 0; i < refs.size(); i++) + if (seen.insert(refs[i].range).second) + refs[n++] = refs[i]; + refs.resize(n); +} + } // namespace // static @@ -698,59 +738,59 @@ const int IndexFile::kMinorVersion = 0; IndexFile::IndexFile(const AbsolutePath& path, const std::string& contents) : id_cache(path), path(path), file_contents(contents) {} -IndexTypeId IndexFile::ToTypeId(Usr usr) { +IndexId::Type IndexFile::ToTypeId(Usr usr) { auto it = id_cache.usr_to_type_id.find(usr); if (it != id_cache.usr_to_type_id.end()) return it->second; - IndexTypeId id(types.size()); + IndexId::Type id(types.size()); types.push_back(IndexType(id, usr)); id_cache.usr_to_type_id[usr] = id; id_cache.type_id_to_usr[id] = usr; return id; } -IndexFuncId IndexFile::ToFuncId(Usr usr) { +IndexId::Func IndexFile::ToFuncId(Usr usr) { auto it = id_cache.usr_to_func_id.find(usr); if (it != id_cache.usr_to_func_id.end()) return it->second; - IndexFuncId id(funcs.size()); + IndexId::Func id(funcs.size()); funcs.push_back(IndexFunc(id, usr)); id_cache.usr_to_func_id[usr] = id; id_cache.func_id_to_usr[id] = usr; return id; } -IndexVarId IndexFile::ToVarId(Usr usr) { +IndexId::Var IndexFile::ToVarId(Usr usr) { auto it = id_cache.usr_to_var_id.find(usr); if (it != id_cache.usr_to_var_id.end()) return it->second; - IndexVarId id(vars.size()); + IndexId::Var id(vars.size()); vars.push_back(IndexVar(id, usr)); id_cache.usr_to_var_id[usr] = id; id_cache.var_id_to_usr[id] = usr; return id; } -IndexTypeId IndexFile::ToTypeId(const CXCursor& cursor) { +IndexId::Type IndexFile::ToTypeId(const CXCursor& cursor) { return ToTypeId(ClangCursor(cursor).get_usr_hash()); } -IndexFuncId IndexFile::ToFuncId(const CXCursor& cursor) { +IndexId::Func IndexFile::ToFuncId(const CXCursor& cursor) { return ToFuncId(ClangCursor(cursor).get_usr_hash()); } -IndexVarId IndexFile::ToVarId(const CXCursor& cursor) { +IndexId::Var IndexFile::ToVarId(const CXCursor& cursor) { return ToVarId(ClangCursor(cursor).get_usr_hash()); } -IndexType* IndexFile::Resolve(IndexTypeId id) { +IndexType* IndexFile::Resolve(IndexId::Type id) { return &types[id.id]; } -IndexFunc* IndexFile::Resolve(IndexFuncId id) { +IndexFunc* IndexFile::Resolve(IndexId::Func id) { return &funcs[id.id]; } -IndexVar* IndexFile::Resolve(IndexVarId id) { +IndexVar* IndexFile::Resolve(IndexId::Var id) { return &vars[id.id]; } @@ -758,59 +798,37 @@ std::string IndexFile::ToString() { return Serialize(SerializeFormat::Json, *this); } -IndexType::IndexType(IndexTypeId id, Usr usr) : usr(usr), id(id) {} - -template -void Uniquify(std::vector>& ids) { - std::unordered_set> seen; - size_t n = 0; - for (size_t i = 0; i < ids.size(); i++) - if (seen.insert(ids[i]).second) - ids[n++] = ids[i]; - ids.resize(n); -} - -void Uniquify(std::vector& uses) { - std::unordered_set seen; - size_t n = 0; - for (size_t i = 0; i < uses.size(); i++) - if (seen.insert(uses[i].range).second) - uses[n++] = uses[i]; - uses.resize(n); -} - -// FIXME Reference: set id in call sites and remove this -// void AddUse(std::vector& values, Range value) { -// values.push_back( -// Use(value, Id(), SymbolKind::File, Role::Reference, {})); -//} +IndexType::IndexType(IndexId::Type id, Usr usr) : usr(usr), id(id) {} -void AddUse(IndexFile* db, - std::vector& uses, +void AddRef(IndexFile* db, + std::vector& refs, Range range, ClangCursor parent, Role role = Role::Reference) { switch (GetSymbolKind(parent.get_kind())) { case SymbolKind::Func: - uses.push_back(Use(range, db->ToFuncId(parent.cx_cursor), - SymbolKind::Func, role, {})); + refs.push_back(IndexId::LexicalRef(range, db->ToFuncId(parent.cx_cursor), + SymbolKind::Func, role)); break; case SymbolKind::Type: - uses.push_back(Use(range, db->ToTypeId(parent.cx_cursor), - SymbolKind::Type, role, {})); + refs.push_back(IndexId::LexicalRef(range, db->ToTypeId(parent.cx_cursor), + SymbolKind::Type, role)); break; default: - uses.push_back(Use(range, Id(), SymbolKind::File, role, {})); + refs.push_back( + IndexId::LexicalRef(range, AnyId(), SymbolKind::File, role)); break; } } -CXCursor fromContainer(const CXIdxContainerInfo* parent) { - return parent ? parent->cursor : clang_getNullCursor(); +void AddRefSpell(IndexFile* db, + std::vector& refs, + ClangCursor cursor) { + AddRef(db, refs, cursor.get_spell(), cursor.get_lexical_parent().cx_cursor); } -void AddUseSpell(IndexFile* db, std::vector& uses, ClangCursor cursor) { - AddUse(db, uses, cursor.get_spell(), cursor.get_lexical_parent().cx_cursor); +CXCursor FromContainer(const CXIdxContainerInfo* parent) { + return parent ? parent->cursor : clang_getNullCursor(); } IdCache::IdCache(const AbsolutePath& primary_file) @@ -936,12 +954,13 @@ bool IsTypeDefinition(const CXIdxContainerInfo* container) { struct VisitDeclForTypeUsageParam { IndexFile* db; - optional toplevel_type; + optional toplevel_type; int has_processed_any = false; optional previous_cursor; - optional initial_type; + optional initial_type; - VisitDeclForTypeUsageParam(IndexFile* db, optional toplevel_type) + VisitDeclForTypeUsageParam(IndexFile* db, + optional toplevel_type) : db(db), toplevel_type(toplevel_type) {} }; @@ -967,21 +986,21 @@ void VisitDeclForTypeUsageVisitorHandler(ClangCursor cursor, IndexType* ref_type = db->Resolve(*param->toplevel_type); std::string name = cursor.get_referenced().get_spell_name(); if (name == ref_type->def.ShortName()) { - AddUseSpell(db, ref_type->uses, cursor); + AddRefSpell(db, ref_type->uses, cursor); param->toplevel_type = nullopt; return; } } - std::string referenced_usr = + optional referenced_usr = cursor.get_referenced() .template_specialization_to_template_definition() - .get_usr(); - // TODO: things in STL cause this to be empty. Figure out why and document it. - if (referenced_usr == "") + .get_opt_usr_hash(); + // May be empty, happens in STL. + if (!referenced_usr) return; - IndexTypeId ref_type_id = db->ToTypeId(HashUsr(referenced_usr)); + IndexId::Type ref_type_id = db->ToTypeId(*referenced_usr); if (!param->initial_type) param->initial_type = ref_type_id; @@ -989,7 +1008,7 @@ void VisitDeclForTypeUsageVisitorHandler(ClangCursor cursor, IndexType* ref_type_def = db->Resolve(ref_type_id); // TODO: Should we even be visiting this if the file is not from the main // def? Try adding assert on |loc| later. - AddUseSpell(db, ref_type_def->uses, cursor); + AddRefSpell(db, ref_type_def->uses, cursor); } ClangCursor::VisitResult VisitDeclForTypeUsageVisitor( @@ -1039,10 +1058,10 @@ ClangCursor::VisitResult VisitDeclForTypeUsageVisitor( // template. // We use |toplevel_type| to attribute the use to the specialized template // instead of the primary template. -optional AddDeclTypeUsages( +optional AddDeclTypeUsages( IndexFile* db, ClangCursor decl_cursor, - optional toplevel_type, + optional toplevel_type, const CXIdxContainerInfo* semantic_container, const CXIdxContainerInfo* lexical_container) { // @@ -1206,15 +1225,16 @@ ClangCursor::VisitResult AddDeclInitializerUsagesVisitor(ClangCursor cursor, // cursor.get_referenced().template_specialization_to_template_definition().get_type().strip_qualifiers().get_usr_hash(); // std::string ref_usr = // cursor.get_referenced().template_specialization_to_template_definition().get_type().strip_qualifiers().get_usr_hash(); - auto ref_usr = cursor.get_referenced() - .template_specialization_to_template_definition() - .get_usr(); + optional ref_usr = + cursor.get_referenced() + .template_specialization_to_template_definition() + .get_usr_hash(); // std::string ref_usr = ref.get_usr_hash(); - if (ref_usr == "") + if (!ref_usr) break; - IndexVar* ref_var = db->Resolve(db->ToVarId(HashUsr(ref_usr))); - AddUseSpell(db, ref_var->uses, cursor); + IndexVar* ref_var = db->Resolve(db->ToVarId(*ref_usr)); + AddRefSpell(db, ref_var->uses, cursor); break; } @@ -1268,7 +1288,7 @@ ClangCursor::VisitResult VisitMacroDefinitionAndExpansions(ClangCursor cursor, var_def->def.extent = SetUse( db, ResolveCXSourceRange(cx_extent, nullptr), parent, Role::None); } else - AddUse(db, var_def->uses, decl_loc_spelling, parent); + AddRef(db, var_def->uses, decl_loc_spelling, parent); break; } @@ -1299,7 +1319,7 @@ ClangCursor::VisitResult TemplateVisitor(ClangCursor cursor, case CXCursor_DeclRefExpr: { ClangCursor ref_cursor = clang_getCursorReferenced(cursor.cx_cursor); if (ref_cursor.get_kind() == CXCursor_NonTypeTemplateParameter) { - IndexVarId ref_var_id = db->ToVarId(ref_cursor.get_usr_hash()); + IndexId::Var ref_var_id = db->ToVarId(ref_cursor.get_usr_hash()); IndexVar* ref_var = db->Resolve(ref_var_id); if (ref_var->def.detailed_name.empty()) { ClangCursor sem_parent = ref_cursor.get_semantic_parent(); @@ -1325,11 +1345,11 @@ ClangCursor::VisitResult TemplateVisitor(ClangCursor cursor, // seems no way to extract the spelling range of `type` and we do // not want to do subtraction here. // See https://github.com/jacobdufault/cquery/issues/252 - AddUse(db, ref_type_index->uses, ref_cursor.get_extent(), + AddRef(db, ref_type_index->uses, ref_cursor.get_extent(), ref_cursor.get_lexical_parent()); } } - AddUseSpell(db, ref_var->uses, cursor); + AddRefSpell(db, ref_var->uses, cursor); } break; } @@ -1342,7 +1362,7 @@ ClangCursor::VisitResult TemplateVisitor(ClangCursor cursor, break; case CXCursor_FunctionDecl: case CXCursor_FunctionTemplate: { - IndexFuncId called_id = db->ToFuncId(overloaded.get_usr_hash()); + IndexId::Func called_id = db->ToFuncId(overloaded.get_usr_hash()); OnIndexReference_Function(db, cursor.get_spell(), data->container, called_id, Role::Call); break; @@ -1354,7 +1374,7 @@ ClangCursor::VisitResult TemplateVisitor(ClangCursor cursor, case CXCursor_TemplateRef: { ClangCursor ref_cursor = clang_getCursorReferenced(cursor.cx_cursor); if (ref_cursor.get_kind() == CXCursor_TemplateTemplateParameter) { - IndexTypeId ref_type_id = db->ToTypeId(ref_cursor.get_usr_hash()); + IndexId::Type ref_type_id = db->ToTypeId(ref_cursor.get_usr_hash()); IndexType* ref_type = db->Resolve(ref_type_id); // TODO It seems difficult to get references to template template // parameters. @@ -1381,14 +1401,14 @@ ClangCursor::VisitResult TemplateVisitor(ClangCursor cursor, int16_t(strlen(ref_type->def.detailed_name.c_str())); ref_type->def.kind = lsSymbolKind::TypeParameter; } - AddUseSpell(db, ref_type->uses, cursor); + AddRefSpell(db, ref_type->uses, cursor); } break; } case CXCursor_TypeRef: { ClangCursor ref_cursor = clang_getCursorReferenced(cursor.cx_cursor); if (ref_cursor.get_kind() == CXCursor_TemplateTypeParameter) { - IndexTypeId ref_type_id = db->ToTypeId(ref_cursor.get_usr_hash()); + IndexId::Type ref_type_id = db->ToTypeId(ref_cursor.get_usr_hash()); IndexType* ref_type = db->Resolve(ref_type_id); // TODO It seems difficult to get a FunctionTemplate's template // parameters. @@ -1416,7 +1436,7 @@ ClangCursor::VisitResult TemplateVisitor(ClangCursor cursor, int16_t(strlen(ref_type->def.detailed_name.c_str())); ref_type->def.kind = lsSymbolKind::TypeParameter; } - AddUseSpell(db, ref_type->uses, cursor); + AddRefSpell(db, ref_type->uses, cursor); } break; } @@ -1498,8 +1518,8 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { db->language = decl_lang; } - ClangCursor sem_parent(fromContainer(decl->semanticContainer)); - ClangCursor lex_parent(fromContainer(decl->lexicalContainer)); + ClangCursor sem_parent(FromContainer(decl->semanticContainer)); + ClangCursor lex_parent(FromContainer(decl->lexicalContainer)); SetUsePreflight(db, sem_parent); SetUsePreflight(db, lex_parent); ClangCursor cursor = decl->cursor; @@ -1511,7 +1531,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { case CXIdxEntity_CXXNamespace: { Range spell = cursor.get_spell(); - IndexTypeId ns_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); + IndexId::Type ns_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); IndexType* ns = db->Resolve(ns_id); ns->def.kind = GetSymbolKind(decl->entityInfo->kind); if (ns->def.detailed_name.empty()) { @@ -1521,7 +1541,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { ns->def.extent = SetUse(db, cursor.get_extent(), lex_parent, Role::None); if (decl->semanticContainer) { - IndexTypeId parent_id = db->ToTypeId( + IndexId::Type parent_id = db->ToTypeId( ClangCursor(decl->semanticContainer->cursor).get_usr_hash()); db->Resolve(parent_id)->derived.push_back(ns_id); // |ns| may be invalidated. @@ -1529,7 +1549,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { ns->def.bases.push_back(parent_id); } } - AddUse(db, ns->uses, spell, lex_parent); + AddRef(db, ns->uses, spell, lex_parent); break; } @@ -1549,7 +1569,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { if (cursor != cursor.template_specialization_to_template_definition()) break; - IndexVarId var_id = db->ToVarId(HashUsr(decl->entityInfo->USR)); + IndexId::Var var_id = db->ToVarId(HashUsr(decl->entityInfo->USR)); IndexVar* var = db->Resolve(var_id); // TODO: Eventually run with this if. Right now I want to iron out bugs @@ -1572,7 +1592,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { SetUse(db, cursor.get_extent(), lex_parent, Role::None); } else { var->declarations.push_back( - SetUse(db, spell, lex_parent, Role::Declaration)); + SetRef(db, spell, lex_parent, Role::Declaration)); } cursor.VisitChildren(&AddDeclInitializerUsagesVisitor, db); @@ -1625,7 +1645,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { cursor.template_specialization_to_template_definition(); bool is_template_specialization = cursor != decl_cursor_resolved; - IndexFuncId func_id = db->ToFuncId(decl_cursor_resolved.cx_cursor); + IndexId::Func func_id = db->ToFuncId(decl_cursor_resolved.cx_cursor); IndexFunc* func = db->Resolve(func_id); if (g_config->index.comments) func->def.comments = cursor.get_comments(); @@ -1713,15 +1733,15 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { // definition/declaration. Do it on definition since there should only // ever be one of those in the entire program. if (IsTypeDefinition(decl->semanticContainer)) { - IndexTypeId declaring_type_id = + IndexId::Type declaring_type_id = db->ToTypeId(decl->semanticContainer->cursor); IndexType* declaring_type_def = db->Resolve(declaring_type_id); func->def.declaring_type = declaring_type_id; // Mark a type reference at the ctor/dtor location. if (decl->entityInfo->kind == CXIdxEntity_CXXConstructor) - AddUse(db, declaring_type_def->uses, spell, - fromContainer(decl->lexicalContainer)); + AddRef(db, declaring_type_def->uses, spell, + FromContainer(decl->lexicalContainer)); // Add function to declaring type. declaring_type_def->def.funcs.push_back(func_id); @@ -1738,7 +1758,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { ClangCursor parent = ClangCursor(overridden[i]) .template_specialization_to_template_definition(); - IndexFuncId parent_id = db->ToFuncId(parent.get_usr_hash()); + IndexId::Func parent_id = db->ToFuncId(parent.get_usr_hash()); IndexFunc* parent_def = db->Resolve(parent_id); func = db->Resolve(func_id); // ToFuncId invalidated func_def @@ -1757,10 +1777,10 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { // Note we want to fetch the first TypeRef. Running // ResolveCursorType(decl->cursor) would return // the type of the typedef/using, not the type of the referenced type. - optional alias_of = AddDeclTypeUsages( + optional alias_of = AddDeclTypeUsages( db, cursor, nullopt, decl->semanticContainer, decl->lexicalContainer); - IndexTypeId type_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); + IndexId::Type type_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); IndexType* type = db->Resolve(type_id); if (alias_of) @@ -1797,7 +1817,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { } } - AddUse(db, type->uses, spell, fromContainer(decl->lexicalContainer)); + AddRef(db, type->uses, spell, FromContainer(decl->lexicalContainer)); break; } @@ -1811,7 +1831,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { case CXIdxEntity_CXXClass: { Range spell = cursor.get_spell(); - IndexTypeId type_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); + IndexId::Type type_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); IndexType* type = db->Resolve(type_id); // TODO: Eventually run with this if. Right now I want to iron out bugs @@ -1836,15 +1856,15 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { if (!enum_type.is_builtin()) { IndexType* int_type = db->Resolve(db->ToTypeId(enum_type.get_usr_hash())); - AddUse(db, int_type->uses, spell, - fromContainer(decl->lexicalContainer)); + AddRef(db, int_type->uses, spell, + FromContainer(decl->lexicalContainer)); // type is invalidated. type = db->Resolve(type_id); } } } else - AddUse(db, type->declarations, spell, - fromContainer(decl->lexicalContainer), Role::Declaration); + AddRef(db, type->declarations, spell, + FromContainer(decl->lexicalContainer), Role::Declaration); switch (decl->entityInfo->templateKind) { default: @@ -1854,7 +1874,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { // TODO Use a different dimension ClangCursor origin_cursor = cursor.template_specialization_to_template_definition(); - IndexTypeId origin_id = db->ToTypeId(origin_cursor.get_usr_hash()); + IndexId::Type origin_id = db->ToTypeId(origin_cursor.get_usr_hash()); IndexType* origin = db->Resolve(origin_id); // |type| may be invalidated. type = db->Resolve(type_id); @@ -1911,7 +1931,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { AddDeclTypeUsages(db, base_class->cursor, nullopt, decl->semanticContainer, decl->lexicalContainer); - optional parent_type_id = + optional parent_type_id = ResolveToDeclarationType(db, base_class->cursor, param); // type_def ptr could be invalidated by ResolveToDeclarationType and // TemplateVisitor. @@ -1971,7 +1991,7 @@ void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { return; ClangCursor cursor(ref->cursor); - ClangCursor lex_parent(fromContainer(ref->container)); + ClangCursor lex_parent(FromContainer(ref->container)); ClangCursor referenced; if (ref->referencedEntity) referenced = ref->referencedEntity->cursor; @@ -1984,13 +2004,13 @@ void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { case CXIdxEntity_CXXNamespace: { IndexType* ns = db->Resolve(db->ToTypeId(referenced.get_usr_hash())); - AddUse(db, ns->uses, cursor.get_spell(), fromContainer(ref->container)); + AddRef(db, ns->uses, cursor.get_spell(), FromContainer(ref->container)); break; } case CXIdxEntity_CXXNamespaceAlias: { IndexType* ns = db->Resolve(db->ToTypeId(referenced.get_usr_hash())); - AddUse(db, ns->uses, cursor.get_spell(), fromContainer(ref->container)); + AddRef(db, ns->uses, cursor.get_spell(), FromContainer(ref->container)); if (!ns->def.spell) { ClangCursor sem_parent = referenced.get_semantic_parent(); ClangCursor lex_parent = referenced.get_lexical_parent(); @@ -2017,7 +2037,7 @@ void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { referenced = referenced.template_specialization_to_template_definition(); - IndexVarId var_id = db->ToVarId(referenced.get_usr_hash()); + IndexId::Var var_id = db->ToVarId(referenced.get_usr_hash()); IndexVar* var = db->Resolve(var_id); // Lambda paramaters are not processed by OnIndexDeclaration and // may not have a short_name yet. Note that we only process the lambda @@ -2039,7 +2059,7 @@ void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { var->def.kind = lsSymbolKind::Parameter; } } - AddUse(db, var->uses, loc, fromContainer(ref->container), + AddRef(db, var->uses, loc, FromContainer(ref->container), GetRole(ref, Role::Reference)); break; } @@ -2064,7 +2084,8 @@ void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { // TODO: search full history? Range loc = cursor.get_spell(); - IndexFuncId called_id = db->ToFuncId(HashUsr(ref->referencedEntity->USR)); + IndexId::Func called_id = + db->ToFuncId(HashUsr(ref->referencedEntity->USR)); IndexFunc* called = db->Resolve(called_id); std::string_view short_name = called->def.ShortName(); @@ -2136,8 +2157,8 @@ void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { param->ctors.TryFindConstructorUsr(ctor_type_usr, call_type_desc); if (ctor_usr) { IndexFunc* ctor = db->Resolve(db->ToFuncId(*ctor_usr)); - ctor->uses.push_back(Use(loc, Id(), SymbolKind::File, - Role::Call | Role::Implicit, {})); + ctor->uses.push_back(IndexId::LexicalRef( + loc, AnyId(), SymbolKind::File, Role::Call | Role::Implicit)); } } } @@ -2159,9 +2180,9 @@ void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { IndexType* ref_type = db->Resolve(db->ToTypeId(referenced.get_usr_hash())); if (!ref->parentEntity || IsDeclContext(ref->parentEntity->kind)) - AddUseSpell(db, ref_type->declarations, ref->cursor); + AddRefSpell(db, ref_type->declarations, ref->cursor); else - AddUseSpell(db, ref_type->uses, ref->cursor); + AddRefSpell(db, ref_type->uses, ref->cursor); break; } } @@ -2172,7 +2193,6 @@ optional>> Parse( const std::string& file0, const std::vector& args, const std::vector& file_contents, - PerformanceImportFile* perf, ClangIndex* index, bool dump_ast) { if (!g_config->index.enabled) @@ -2185,8 +2205,6 @@ optional>> Parse( return nullopt; } - Timer timer; - std::vector unsaved_files; for (const FileContents& contents : file_contents) { CXUnsavedFile unsaved; @@ -2203,25 +2221,20 @@ optional>> Parse( if (!tu) return nullopt; - perf->index_parse = timer.ElapsedMicrosecondsAndReset(); - if (dump_ast) Dump(clang_getTranslationUnitCursor(tu->cx_tu)); - return ParseWithTu(file_consumer_shared, perf, tu.get(), index, *file, args, + return ParseWithTu(file_consumer_shared, tu.get(), index, *file, args, unsaved_files); } optional>> ParseWithTu( FileConsumerSharedState* file_consumer_shared, - PerformanceImportFile* perf, ClangTranslationUnit* tu, ClangIndex* index, const AbsolutePath& file, const std::vector& args, const std::vector& file_contents) { - Timer timer; - IndexerCallbacks callback = {0}; // Available callbacks: // - abortQuery @@ -2265,8 +2278,6 @@ optional>> ParseWithTu( ClangCursor(clang_getTranslationUnitCursor(tu->cx_tu)) .VisitChildren(&VisitMacroDefinitionAndExpansions, ¶m); - perf->index_build = timer.ElapsedMicrosecondsAndReset(); - std::unordered_map inc_to_line; // TODO if (param.primary_file) @@ -2364,7 +2375,10 @@ void ClangSanityCheck(const Project::Entry& entry) { unsigned int line, column; CXSourceLocation diag_loc = clang_getDiagnosticLocation(diagnostic); clang_getSpellingLocation(diag_loc, &file, &line, &column, nullptr); - LOG_S(WARNING) << FileName(file)->path << line << ":" << column << " " + std::string file_name; + if (FileName(file)) + file_name = FileName(file)->path + ":"; + LOG_S(WARNING) << file_name << line << ":" << column << " " << ToString(clang_getDiagnosticSpelling(diagnostic)); clang_disposeDiagnostic(diagnostic); } diff --git a/src/clang_system_include_extractor.cc b/src/clang_system_include_extractor.cc index 44c248101..2e8a252d9 100644 --- a/src/clang_system_include_extractor.cc +++ b/src/clang_system_include_extractor.cc @@ -1,5 +1,6 @@ #include "clang_system_include_extractor.h" +#include "compiler.h" #include "platform.h" #include "utils.h" @@ -75,13 +76,13 @@ std::vector FindSystemIncludeDirectories( LOG_S(INFO) << "Using compiler drivers " << StringJoin(compiler_drivers); for (const std::string& compiler_driver : compiler_drivers) { std::vector flags = { - compiler_driver, - "-E", - "-x", - language, - "-", - "-v", - "-working-directory=" + working_directory}; + compiler_driver, "-E", "-x", language, "-", "-v", + }; + + CompilerType compiler_type = FindCompilerType(compiler_driver); + CompilerAppendsFlagIfAccept( + compiler_type, "-working-directory=" + working_directory, flags); + AddRange(&flags, extra_flags); LOG_S(INFO) << "Running " << StringJoin(flags, " "); @@ -139,4 +140,4 @@ TEST_SUITE("System include extraction") { "foobar2"); REQUIRE(paths == std::vector{}); } -} \ No newline at end of file +} diff --git a/src/command_line.cc b/src/command_line.cc index afd819715..66842e675 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -53,8 +53,6 @@ std::string g_init_options; Config* g_config; -extern char** environ; - namespace { std::vector kEmptyArgs; @@ -64,12 +62,11 @@ std::vector kEmptyArgs; bool ShouldDisplayMethodTiming(MethodType type) { return type != kMethodType_TextDocumentPublishDiagnostics && type != kMethodType_CqueryPublishInactiveRegions && - type != kMethodType_Unknown; + type != kMethodType_CqueryQueryDbStatus && type != kMethodType_Unknown; } void PrintHelp() { - std::cout - << R"help(cquery is a low-latency C/C++/Objective-C language server. + std::cout << R"help(cquery is a low-latency C/C++/Objective-C language server. Mode: --check @@ -110,14 +107,36 @@ See more on https://github.com/cquery-project/cquery/wiki } // Writes the environment to stdcerr. -void PrintEnvironment() { - char** s = environ; - while (*s) { - std::cerr << *s << std::endl; - ++s; +void PrintEnvironment(const char** env) { + while (*env) { + std::cerr << *env << std::endl; + ++env; } } +struct Out_CqueryQueryDbStatus : public lsOutMessage { + struct Params { + bool isActive = false; + }; + std::string method = kMethodType_CqueryQueryDbStatus; + Params params; +}; +MAKE_REFLECT_STRUCT(Out_CqueryQueryDbStatus::Params, isActive); +MAKE_REFLECT_STRUCT(Out_CqueryQueryDbStatus, jsonrpc, method, params); + +void WriteQueryDbStatus(bool is_active) { + if (!g_config->emitQueryDbBlocked) + return; + + static bool last_status = false; + if (is_active == last_status) + return; + last_status = is_active; + Out_CqueryQueryDbStatus out; + out.params.isActive = is_active; + QueueManager::WriteStdout(kMethodType_CqueryQueryDbStatus, out); +} + } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -139,7 +158,6 @@ void PrintEnvironment() { //////////////////////////////////////////////////////////////////////////////// bool QueryDbMainLoop(QueryDatabase* db, - MultiQueueWaiter* waiter, Project* project, FileConsumerSharedState* file_consumer_shared, ImportManager* import_manager, @@ -153,26 +171,29 @@ bool QueryDbMainLoop(QueryDatabase* db, CodeCompleteCache* non_global_code_complete_cache, CodeCompleteCache* signature_cache) { auto* queue = QueueManager::instance(); - std::vector> messages = - queue->for_querydb.DequeueAll(); - bool did_work = messages.size(); - for (auto& message : messages) { - // TODO: Consider using std::unordered_map to lookup the handler + bool did_work = false; + + optional> message = + queue->for_querydb.TryDequeue(true /*priority*/); + while (message) { + did_work = true; + + bool found_handler = false; for (MessageHandler* handler : *MessageHandler::message_handlers) { - if (handler->GetMethodType() == message->GetMethodType()) { - handler->Run(std::move(message)); + if (handler->GetMethodType() == (*message)->GetMethodType()) { + handler->Run(std::move(*message)); + found_handler = true; break; } } - if (message) { - LOG_S(FATAL) << "Exiting; no handler for " << message->GetMethodType(); + if (!found_handler) { + LOG_S(FATAL) << "Exiting; no handler for " << (*message)->GetMethodType(); exit(1); } - } - // TODO: consider rate-limiting and checking for IPC messages so we don't - // block requests / we can serve partial requests. + message = queue->for_querydb.TryDequeue(true /*priority*/); + } if (QueryDb_ImportMain(db, import_manager, status, semantic_cache, working_files)) { @@ -182,9 +203,7 @@ bool QueryDbMainLoop(QueryDatabase* db, return did_work; } -void RunQueryDbThread(const std::string& bin_name, - MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter) { +void RunQueryDbThread(const std::string& bin_name) { Project project; SemanticHighlightSymbolCache semantic_cache; WorkingFiles working_files; @@ -196,18 +215,13 @@ void RunQueryDbThread(const std::string& bin_name, [&](std::string path, std::vector diagnostics) { diag_engine.Publish(&working_files, path, diagnostics); }, - [&](ClangTranslationUnit* tu, const std::vector& unsaved, - const std::string& path, const std::vector& args) { - IndexWithTuFromCodeCompletion(&file_consumer_shared, tu, unsaved, path, - args); - }, [](lsRequestId id) { if (id.has_value()) { Out_Error out; out.id = id; out.error.code = lsErrorCodes::InternalError; out.error.message = - "Dropping completion request; a newer request has come in that" + "Dropping completion request; a newer request has come in that " "will be serviced instead. This is not an error."; QueueManager::WriteStdout(kMethodType_Unknown, out); } @@ -225,7 +239,6 @@ void RunQueryDbThread(const std::string& bin_name, // Setup shared references. for (MessageHandler* handler : *MessageHandler::message_handlers) { handler->db = &db; - handler->waiter = indexer_waiter; handler->project = &project; handler->diag_engine = &diag_engine; handler->file_consumer_shared = &file_consumer_shared; @@ -245,20 +258,23 @@ void RunQueryDbThread(const std::string& bin_name, // Run query db main loop. SetCurrentThreadName("querydb"); while (true) { + WriteQueryDbStatus(true); bool did_work = QueryDbMainLoop( - &db, querydb_waiter, &project, &file_consumer_shared, &import_manager, + &db, &project, &file_consumer_shared, &import_manager, &import_pipeline_status, ×tamp_manager, &semantic_cache, &working_files, &clang_complete, &include_complete, global_code_complete_cache.get(), non_global_code_complete_cache.get(), signature_cache.get()); - // Cleanup and free any unused memory. - FreeUnusedMemory(); - if (!did_work) { + // Cleanup and free any unused memory. + FreeUnusedMemory(); + + WriteQueryDbStatus(false); auto* queue = QueueManager::instance(); - querydb_waiter->Wait(&queue->on_indexed, &queue->for_querydb, - &queue->do_id_map); + QueueManager::instance()->querydb_waiter->Wait( + &queue->for_querydb, &queue->do_id_map, + &queue->on_indexed_for_querydb); } } } @@ -319,7 +335,7 @@ void LaunchStdinLoop(std::unordered_map* request_times) { MethodType method_type = message->GetMethodType(); (*request_times)[method_type] = Timer(); - queue->for_querydb.PushBack(std::move(message)); + queue->for_querydb.Enqueue(std::move(message), false /*priority*/); // If the message was to exit then querydb will take care of the actual // exit. Stop reading from stdin since it might be detached. @@ -329,51 +345,41 @@ void LaunchStdinLoop(std::unordered_map* request_times) { }); } -void LaunchStdoutThread(std::unordered_map* request_times, - MultiQueueWaiter* waiter) { +void LaunchStdoutThread(std::unordered_map* request_times) { WorkThread::StartThread("stdout", [=]() { auto* queue = QueueManager::instance(); while (true) { - std::vector messages = queue->for_stdout.DequeueAll(); - if (messages.empty()) { - waiter->Wait(&queue->for_stdout); - continue; - } + Stdout_Request message = queue->for_stdout.Dequeue(); - for (auto& message : messages) { - if (ShouldDisplayMethodTiming(message.method)) { - Timer time = (*request_times)[message.method]; - time.ResetAndPrint("[e2e] Running " + std::string(message.method)); - } + if (ShouldDisplayMethodTiming(message.method)) { + Timer time = (*request_times)[message.method]; + time.ResetAndPrint("[e2e] Running " + std::string(message.method)); + } - RecordOutput(message.content); + RecordOutput(message.content); - fwrite(message.content.c_str(), message.content.size(), 1, stdout); - fflush(stdout); - } + fwrite(message.content.c_str(), message.content.size(), 1, stdout); + fflush(stdout); } }); } -void LanguageServerMain(const std::string& bin_name, - MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter) { +void LanguageServerMain(const std::string& bin_name) { std::unordered_map request_times; LaunchStdinLoop(&request_times); // We run a dedicated thread for writing to stdout because there can be an // unknown number of delays when output information. - LaunchStdoutThread(&request_times, stdout_waiter); + LaunchStdoutThread(&request_times); // Start querydb which takes over this thread. The querydb will launch // indexer threads as needed. - RunQueryDbThread(bin_name, querydb_waiter, indexer_waiter); + RunQueryDbThread(bin_name); } -int main(int argc, char** argv) { +int main(int argc, char** argv, const char** env) { // `clang-format` will not output anything if PATH is not set. if (!getenv("PATH")) { LOG_S(WARNING) << "The \"PATH\" environment variable is not set. " @@ -413,8 +419,7 @@ int main(int argc, char** argv) { loguru::g_flush_interval_ms = 0; loguru::init(argc, argv); - MultiQueueWaiter querydb_waiter, indexer_waiter, stdout_waiter; - QueueManager::Init(&querydb_waiter, &indexer_waiter, &stdout_waiter); + QueueManager::Init(); bool language_server = true; @@ -422,7 +427,7 @@ int main(int argc, char** argv) { IndexInit(); if (HasOption(options, "--print-env")) - PrintEnvironment(); + PrintEnvironment(env); if (HasOption(options, "--record")) EnableRecording(options["--record"]); @@ -430,6 +435,8 @@ int main(int argc, char** argv) { if (HasOption(options, "--check")) { loguru::g_stderr_verbosity = loguru::Verbosity_MAX; + LOG_S(INFO) << "Running --check"; + optional path = NormalizePath(options["--check"]); if (!path) { ABORT_S() << "Cannot find path \"" << options["--check"] << "\". Make " @@ -446,7 +453,9 @@ int main(int argc, char** argv) { language_server = false; Project project; Config config; - config.resourceDirectory = GetDefaultResourceDirectory(); + if (!GetDefaultResourceDirectory()) + ABORT_S() << "Cannot resolve resource directory"; + config.resourceDirectory = GetDefaultResourceDirectory()->path; project.Load(GetWorkingDirectory().path); Project::Entry entry = project.FindCompilationEntryForFile(path->path); LOG_S(INFO) << "Using arguments " << StringJoin(entry.args, " "); @@ -494,9 +503,7 @@ int main(int argc, char** argv) { } } - // std::cerr << "Running language server" << std::endl; - LanguageServerMain(argv[0], &querydb_waiter, &indexer_waiter, - &stdout_waiter); + LanguageServerMain(argv[0]); } if (HasOption(options, "--wait-for-input")) { diff --git a/src/compiler.cc b/src/compiler.cc new file mode 100644 index 000000000..33e8a249f --- /dev/null +++ b/src/compiler.cc @@ -0,0 +1,106 @@ +#include "compiler.h" + +#include "platform.h" +#include "utils.h" + +#include + +#include + +namespace { +CompilerType ExtractCompilerType(const std::string& version_output) { + if (version_output.find("Apple LLVM version") != std::string::npos) + return CompilerType::Clang; + if (version_output.find("clang version") != std::string::npos) + return CompilerType::Clang; + if (version_output.find("GCC") != std::string::npos) + return CompilerType::GCC; + if (version_output.find("Microsoft (R)") != std::string::npos) + return CompilerType::MSVC; + return CompilerType::Unknown; +} + +// FIXME: Make FindCompilerType a class so this is not a global. +std::unordered_map compiler_type_cache_; + +} // namespace + +CompilerType FindCompilerType(const std::string& compiler_driver) { + auto it = compiler_type_cache_.find(compiler_driver); + if (it != compiler_type_cache_.end()) + return it->second; + + std::vector flags = {compiler_driver}; + if (!EndsWith(compiler_driver, "cl.exe")) + flags.push_back("--version"); + optional version_output = RunExecutable(flags, ""); + CompilerType result = CompilerType::Unknown; + if (version_output) + result = ExtractCompilerType(version_output.value()); + + compiler_type_cache_[compiler_driver] = result; + return result; +} + +bool CompilerAcceptsFlag(CompilerType compiler_type, const std::string& flag) { + // MSVC does not accept flag beginning with '-'. + if (compiler_type == CompilerType::MSVC && StartsWith(flag, "-")) + return false; + + // These flags are for clang only. + if (StartsWith(flag, "-working-directory") || + StartsWith(flag, "-resource-dir") || flag == "-fparse-all-comments") + return compiler_type == CompilerType::Clang; + + return true; +} + +void CompilerAppendsFlagIfAccept(CompilerType compiler_type, + const std::string& flag, + std::vector& flags) { + if (CompilerAcceptsFlag(compiler_type, flag)) + flags.emplace_back(flag); +} + +TEST_SUITE("Compiler type extraction") { + TEST_CASE("Apple Clang") { + std::string version_output = + "Apple LLVM version 9.1.0 (clang-902.0.39.1)\n" + "Target: x86_64-apple-darwin17.5.0\n" + "Thread model: posix\n" + "InstalledDir: " + "/Applications/Xcode.app/Contents/Developer/Toolchains/" + "XcodeDefault.xctoolchain/usr/bin\n"; + REQUIRE(CompilerType::Clang == ExtractCompilerType(version_output)); + } + TEST_CASE("LLVM Clang") { + std::string version_output = + "clang version 6.0.0 (tags/RELEASE_600/final)\n" + "Target: x86_64-apple-darwin17.5.0\n" + "Thread model: posix\n" + "InstalledDir: /usr/local/opt/llvm/bin\n"; + REQUIRE(CompilerType::Clang == ExtractCompilerType(version_output)); + } + TEST_CASE("GCC") { + std::string version_output = + "gcc-8 (Homebrew GCC 8.1.0) 8.1.0\n" + "Copyright (C) 2018 Free Software Foundation, Inc.\n" + "This is free software; see the source for copying conditions. There " + "is NO\n" + "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR " + "PURPOSE.\n"; + REQUIRE(CompilerType::GCC == ExtractCompilerType(version_output)); + } + TEST_CASE("MSVC") { + std::string version_output = + "Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24210 for x64\n" + "Copyright (C) Microsoft Corporation. All rights reserved.\n" + "\n" + "usage: cl [ option... ] filename... [ /link linkoption... ]\n"; + REQUIRE(CompilerType::MSVC == ExtractCompilerType(version_output)); + } + TEST_CASE("Unknown") { + std::string version_output = ""; + REQUIRE(CompilerType::Unknown == ExtractCompilerType(version_output)); + } +} diff --git a/src/compiler.h b/src/compiler.h new file mode 100644 index 000000000..b11fd0fa6 --- /dev/null +++ b/src/compiler.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +// Used to identify the compiler type. +enum class CompilerType { + Unknown = 0, + Clang = 1, + GCC = 2, + MSVC = 3, +}; + +// Find out the compiler type for the specific driver. +CompilerType FindCompilerType(const std::string& compiler_driver); + +// Whether the compiler accepts certain flag. +bool CompilerAcceptsFlag(CompilerType compiler_type, const std::string& flag); + +// Append flag if the compiler accepts it. +void CompilerAppendsFlagIfAccept(CompilerType compiler_type, + const std::string& flag, + std::vector& flags); diff --git a/src/config.h b/src/config.h index cd94e38bb..93f99b79c 100644 --- a/src/config.h +++ b/src/config.h @@ -4,6 +4,8 @@ #include +struct ICacheStore; + /* The language client plugin needs to send initialization options in the `initialize` request to the cquery language server. The only required option is @@ -36,6 +38,9 @@ struct Config { // Cache directory for indexed files. std::string cacheDirectory; + std::string cacheType = "unqlite"; + std::shared_ptr cacheStore; + // Cache serialization format. // // "json" generates `cacheDirectory/.../xxx.json` files which can be pretty @@ -85,18 +90,15 @@ struct Config { // interval; it could take significantly longer if cquery is completely idle. int progressReportFrequencyMs = 500; - // If true, document links are reported for #include directives. - bool showDocumentLinksOnIncludes = true; + // If true, cquery will emit $cquery/queryDbStatus notifications whenever the + // querydb thread is busy or idle. + bool emitQueryDbBlocked = false; - // Version of the client. If undefined the version check is skipped. Used to - // inform users their vscode client is too old and needs to be updated. - optional clientVersion; + // If true, inactive regions notifications will be sent to the client. + bool emitInactiveRegions = false; - struct ClientCapability { - // TextDocumentClientCapabilities.completion.completionItem.snippetSupport - bool snippetSupport = false; - }; - ClientCapability client; + // If true, document links are reported for #include directives. + bool showDocumentLinksOnIncludes = true; struct CodeLens { // Enables code lens on parameter and function variables. @@ -105,6 +107,10 @@ struct Config { CodeLens codeLens; struct Completion { + // If this is true and the client reports it can support snippets, + // completion will include snippets. Set to false to disable snippets. + bool enableSnippets = true; + // Some completion UI, such as Emacs' completion-at-point and company-lsp, // display completion item label and detail side by side. // This does not look right, when you see things like: @@ -158,6 +164,12 @@ struct Config { }; Completion completion; + struct Formatting { + // Whether formatting should be enabled. + bool enabled = true; + }; + Formatting formatting; + struct Diagnostics { // Like index.{whitelist,blacklist}, don't publish diagnostics to // blacklisted files. @@ -172,11 +184,16 @@ struct Config { // If true, diagnostics from a full document parse will be reported. bool onParse = true; + // If true, diagnostics from typing will be reported. + bool onType = true; }; Diagnostics diagnostics; // Semantic highlighting struct Highlight { + // Set to true to enable semantic highlighting. + bool enabled = false; + // Like index.{whitelist,blacklist}, don't publish semantic highlighting to // blacklisted files. std::vector blacklist; @@ -200,6 +217,7 @@ struct Config { // // Example: `ash/.*\.cc` std::vector blacklist; + std::vector whitelist; // 0: none, 1: Doxygen, 2: all comments // Plugin support for clients: @@ -216,8 +234,6 @@ struct Config { // Number of indexer threads. If 0, 80% of cores are used. int threads = 0; - - std::vector whitelist; }; Index index; @@ -236,28 +252,29 @@ struct Config { bool enableIndexOnDidChange = false; struct Xref { - // If true, |Location[]| response will include lexical container. - bool container = false; // Maximum number of definition/reference/... results. - int maxNum = 2000; + unsigned int maxNum = 2000; }; Xref xref; }; -MAKE_REFLECT_STRUCT(Config::ClientCapability, snippetSupport); MAKE_REFLECT_STRUCT(Config::CodeLens, localVariables); MAKE_REFLECT_STRUCT(Config::Completion, + enableSnippets, detailedLabel, + dropOldRequests, filterAndSort, includeMaxPathSize, includeSuffixWhitelist, includeBlacklist, includeWhitelist); +MAKE_REFLECT_STRUCT(Config::Formatting, enabled) MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, whitelist, frequencyMs, - onParse) -MAKE_REFLECT_STRUCT(Config::Highlight, blacklist, whitelist) + onParse, + onType) +MAKE_REFLECT_STRUCT(Config::Highlight, enabled, blacklist, whitelist) MAKE_REFLECT_STRUCT(Config::Index, attributeMakeCallsToCtor, blacklist, @@ -267,11 +284,12 @@ MAKE_REFLECT_STRUCT(Config::Index, logSkippedPaths, threads); MAKE_REFLECT_STRUCT(Config::WorkspaceSymbol, maxNum, sort); -MAKE_REFLECT_STRUCT(Config::Xref, container, maxNum); +MAKE_REFLECT_STRUCT(Config::Xref, maxNum); MAKE_REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory, cacheDirectory, + cacheType, cacheFormat, resourceDirectory, @@ -279,14 +297,14 @@ MAKE_REFLECT_STRUCT(Config, extraClangArguments, progressReportFrequencyMs, + emitQueryDbBlocked, + emitInactiveRegions, showDocumentLinksOnIncludes, - clientVersion, - - client, codeLens, completion, + formatting, diagnostics, highlight, index, @@ -295,7 +313,4 @@ MAKE_REFLECT_STRUCT(Config, enableIndexOnDidChange); -// Expected client version. We show an error if this doesn't match. -constexpr const int kExpectedClientVersion = 3; - extern Config* g_config; diff --git a/src/file_types.cc b/src/file_types.cc index 83269bd7f..0afafd722 100644 --- a/src/file_types.cc +++ b/src/file_types.cc @@ -5,6 +5,13 @@ #include +// static +AbsolutePath AbsolutePath::BuildDoNotUse(std::string_view path) { + AbsolutePath p; + p.path = std::string(path); + return p; +} + AbsolutePath::AbsolutePath() {} AbsolutePath::AbsolutePath(const std::string& path, bool validate) diff --git a/src/file_types.h b/src/file_types.h index 08c0fa2b8..3a3b2e544 100644 --- a/src/file_types.h +++ b/src/file_types.h @@ -7,6 +7,8 @@ #include struct AbsolutePath { + static AbsolutePath BuildDoNotUse(std::string_view path); + // Try not to use this. AbsolutePath(); diff --git a/src/iindexer.cc b/src/iindexer.cc index a2fa332d6..10d28dcde 100644 --- a/src/iindexer.cc +++ b/src/iindexer.cc @@ -10,9 +10,8 @@ struct ClangIndexer : IIndexer { FileConsumerSharedState* file_consumer_shared, std::string file, const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf) override { - return Parse(file_consumer_shared, file, args, file_contents, perf, &index, + const std::vector& file_contents) override { + return Parse(file_consumer_shared, file, args, file_contents, &index, false /*dump_ast*/); } @@ -50,8 +49,7 @@ struct TestIndexer : IIndexer { FileConsumerSharedState* file_consumer_shared, std::string file, const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf) override { + const std::vector& file_contents) override { auto it = indexes.find(file); if (it == indexes.end()) { // Don't return any indexes for unexpected data. diff --git a/src/iindexer.h b/src/iindexer.h index d7f16d81b..fdc7b320a 100644 --- a/src/iindexer.h +++ b/src/iindexer.h @@ -16,7 +16,6 @@ struct Config; struct IndexFile; struct FileContents; struct FileConsumerSharedState; -struct PerformanceImportFile; // Abstracts away the actual indexing process. Each IIndexer instance is // per-thread and constructing an instance may be extremely expensive (ie, @@ -38,6 +37,5 @@ struct IIndexer { FileConsumerSharedState* file_consumer_shared, std::string file, const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf) = 0; + const std::vector& file_contents) = 0; }; diff --git a/src/import_manager.cc b/src/import_manager.cc index 11b13f296..7d7d9ade2 100644 --- a/src/import_manager.cc +++ b/src/import_manager.cc @@ -1,8 +1,33 @@ #include "import_manager.h" +bool ImportManager::IsInitialImport(const std::string& path) { + // Try reading the value + { + std::shared_lock lock(initial_import_mutex_); + if (initial_import_.find(path) != initial_import_.end()) + return false; + } + + // Try inserting the value + { + std::unique_lock lock(initial_import_mutex_); + return initial_import_.insert(path).second; + } +} + bool ImportManager::TryMarkDependencyImported(const std::string& path) { - std::lock_guard lock(dependency_mutex_); - return dependency_imported_.insert(path).second; + // Try reading the value + { + std::shared_lock lock(dependency_mutex_); + if (dependency_imported_.find(path) != dependency_imported_.end()) + return false; + } + + // Try inserting the value + { + std::unique_lock lock(dependency_mutex_); + return dependency_imported_.insert(path).second; + } } bool ImportManager::StartQueryDbImport(const std::string& path) { diff --git a/src/import_manager.h b/src/import_manager.h index 57dc7ffe5..07fe62727 100644 --- a/src/import_manager.h +++ b/src/import_manager.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -9,6 +9,9 @@ // // NOTE: This is not thread safe and should only be used on the querydb thread. struct ImportManager { + // Check if this is the first time this file has been imported. + bool IsInitialImport(const std::string& path); + // Try to mark the given dependency as imported. A dependency can only ever be // imported once. bool TryMarkDependencyImported(const std::string& path); @@ -23,7 +26,11 @@ struct ImportManager { std::unordered_set querydb_processing_; - // TODO: use std::shared_mutex so we can have multiple readers. - std::mutex dependency_mutex_; + // TODO: use shared_mutex + std::shared_timed_mutex dependency_mutex_; std::unordered_set dependency_imported_; + + // TODO: use shared_mutex + std::shared_timed_mutex initial_import_mutex_; + std::unordered_set initial_import_; }; \ No newline at end of file diff --git a/src/import_pipeline.cc b/src/import_pipeline.cc index 2764409a1..ddbfa62f4 100644 --- a/src/import_pipeline.cc +++ b/src/import_pipeline.cc @@ -28,7 +28,6 @@ struct Out_Progress : public lsOutMessage { struct Params { int indexRequestCount = 0; int doIdMapCount = 0; - int loadPreviousIndexCount = 0; int onIdMappedCount = 0; int onIndexedCount = 0; int activeThreads = 0; @@ -39,7 +38,6 @@ struct Out_Progress : public lsOutMessage { MAKE_REFLECT_STRUCT(Out_Progress::Params, indexRequestCount, doIdMapCount, - loadPreviousIndexCount, onIdMappedCount, onIndexedCount, activeThreads); @@ -49,12 +47,13 @@ MAKE_REFLECT_STRUCT(Out_Progress, jsonrpc, method, params); // |kIterationSize| messages of a type at one time. While the import time // likely stays the same, this should reduce overall queue lengths which means // the user gets a usable index faster. +constexpr int kIterationSize = 200; struct IterationLoop { - const int kIterationSize = 100; - int count = 0; + int remaining = kIterationSize; + void IncreaseCount() { remaining *= 50; } - bool Next() { return count++ < kIterationSize; } - void Reset() { count = 0; } + bool Next() { return remaining-- > 0; } + void Reset() { remaining = kIterationSize; } }; struct IModificationTimestampFetcher { @@ -111,9 +110,9 @@ struct ActiveThread { Out_Progress out; out.params.indexRequestCount = queue->index_request.Size(); out.params.doIdMapCount = queue->do_id_map.Size(); - out.params.loadPreviousIndexCount = queue->load_previous_index.Size(); out.params.onIdMappedCount = queue->on_id_mapped.Size(); - out.params.onIndexedCount = queue->on_indexed.Size(); + out.params.onIndexedCount = queue->on_indexed_for_merge.Size() + + queue->on_indexed_for_querydb.Size(); out.params.activeThreads = status_->num_active_threads; // Ignore this progress update if the last update was too recent. @@ -121,7 +120,6 @@ struct ActiveThread { // Make sure we output a status update if queue lengths are zero. bool all_zero = out.params.indexRequestCount == 0 && out.params.doIdMapCount == 0 && - out.params.loadPreviousIndexCount == 0 && out.params.onIdMappedCount == 0 && out.params.onIndexedCount == 0 && out.params.activeThreads == 0; if (!all_zero && @@ -149,7 +147,7 @@ ShouldParse FileNeedsParse( TimestampManager* timestamp_manager, IModificationTimestampFetcher* modification_timestamp_fetcher, ImportManager* import_manager, - const std::shared_ptr& cache_manager, + const std::shared_ptr& cache_manager, IndexFile* opt_previous_index, const AbsolutePath& path, const std::vector& args, @@ -212,13 +210,14 @@ CacheLoadResult TryLoadFromCache( TimestampManager* timestamp_manager, IModificationTimestampFetcher* modification_timestamp_fetcher, ImportManager* import_manager, - const std::shared_ptr& cache_manager, + const std::shared_ptr& cache_manager, bool is_interactive, const Project::Entry& entry, const AbsolutePath& path_to_index) { // Always run this block, even if we are interactive, so we can check // dependencies and reset files in |file_consumer_shared|. - IndexFile* previous_index = cache_manager->TryLoad(path_to_index); + IndexFile* previous_index = + cache_manager->TryLoad(path_to_index); if (!previous_index) return CacheLoadResult::Parse; @@ -265,12 +264,9 @@ CacheLoadResult TryLoadFromCache( // No timestamps changed - load directly from cache. LOG_S(INFO) << "Skipping parse; no timestamp change for " << path_to_index; - // TODO/FIXME: real perf - PerformanceImportFile perf; - std::vector result; - result.push_back(Index_DoIdMap(cache_manager->TakeOrLoad(path_to_index), - cache_manager, perf, is_interactive, + result.push_back(Index_DoIdMap(cache_manager->TryTakeOrLoad(path_to_index), + cache_manager, is_interactive, false /*write_to_disk*/)); for (const AbsolutePath& dependency : previous_index->dependencies) { // Only load a dependency if it is not already loaded. @@ -292,18 +288,23 @@ CacheLoadResult TryLoadFromCache( continue; result.push_back(Index_DoIdMap(std::move(dependency_index), cache_manager, - perf, is_interactive, - false /*write_to_disk*/)); + is_interactive, false /*write_to_disk*/)); } - QueueManager::instance()->do_id_map.EnqueueAll(std::move(result)); + // Mark all of the entries as imported so we will load do delta loads next + // time. + for (Index_DoIdMap& entry : result) + import_manager->IsInitialImport(entry.current->path); + + QueueManager::instance()->do_id_map.EnqueueAll(std::move(result), + false /*priority*/); return CacheLoadResult::DoNotParse; } std::vector PreloadFileContents( - const std::shared_ptr& cache_manager, + const std::shared_ptr& cache_manager, const Project::Entry& entry, - const std::string& entry_contents, + const optional& entry_contents, const AbsolutePath& path_to_index) { // Load file contents for all dependencies into memory. If the dependencies // for the file changed we may not end up using all of the files we @@ -337,9 +338,10 @@ std::vector PreloadFileContents( }; std::vector file_contents; - file_contents.push_back(FileContents(entry.filename, entry_contents)); + if (entry_contents) + file_contents.push_back(FileContents(entry.filename, *entry_contents)); cache_manager->IterateLoadedCaches([&](IndexFile* index) { - if (index->path == entry.filename) + if (entry_contents && index->path == entry.filename) return; file_contents.push_back(FileContents( index->path, @@ -365,7 +367,8 @@ void ParseFile(DiagnosticsEngine* diag_engine, // FIXME: don't use absolute path AbsolutePath path_to_index = entry.filename; if (entry.is_inferred) { - IndexFile* entry_cache = request.cache_manager->TryLoad(entry.filename); + IndexFile* entry_cache = + request.cache_manager->TryLoad(entry.filename); if (entry_cache) path_to_index = entry_cache->import_file; } @@ -383,9 +386,8 @@ void ParseFile(DiagnosticsEngine* diag_engine, request.cache_manager, entry, request.contents, path_to_index); std::vector result; - PerformanceImportFile perf; auto indexes = indexer->Index(file_consumer_shared, path_to_index, entry.args, - file_contents, &perf); + file_contents); if (!indexes) { if (g_config->index.enabled && request.id.has_value()) { @@ -399,23 +401,45 @@ void ParseFile(DiagnosticsEngine* diag_engine, } for (std::unique_ptr& new_index : *indexes) { - Timer time; - // Only emit diagnostics for non-interactive sessions, which makes it easier // to identify indexing problems. For interactive sessions, diagnostics are // handled by code completion. - if (!request.is_interactive) + if (!request.is_interactive) { diag_engine->Publish(working_files, new_index->path, new_index->diagnostics_); + } // When main thread does IdMap request it will request the previous index if // needed. LOG_S(INFO) << "Emitting index result for " << new_index->path; result.push_back(Index_DoIdMap(std::move(new_index), request.cache_manager, - perf, request.is_interactive, + request.is_interactive, true /*write_to_disk*/)); } + // Load previous index if the file has already been imported so we can do a + // delta update. + for (Index_DoIdMap& request : result) { + if (!import_manager->IsInitialImport(request.current->path)) { + request.previous = + request.cache_manager->TryTakeOrLoad(request.current->path); + LOG_IF_S(ERROR, !request.previous) + << "Unable to load previous index for already imported index " + << request.current->path; + } + } + + // Write index to disk if requested. + for (Index_DoIdMap& request : result) { + if (request.write_to_disk) { + LOG_S(INFO) << "Writing cached index to disk for " + << request.current->path; + request.cache_manager->Write(*request.current); + timestamp_manager->UpdateCachedModificationTime( + request.current->path, request.current->last_modification_time); + } + } + QueueManager::instance()->do_id_map.EnqueueAll(std::move(result), request.is_interactive); } @@ -429,7 +453,8 @@ bool IndexMain_DoParse( ImportManager* import_manager, IIndexer* indexer) { auto* queue = QueueManager::instance(); - optional request = queue->index_request.TryPopFront(); + optional request = + queue->index_request.TryDequeue(true /*priority*/); if (!request) return false; @@ -448,14 +473,13 @@ bool IndexMain_DoCreateIndexUpdate(TimestampManager* timestamp_manager) { bool did_work = false; IterationLoop loop; while (loop.Next()) { - optional response = queue->on_id_mapped.TryPopFront(); + optional response = + queue->on_id_mapped.TryDequeue(true /*priority*/); if (!response) return did_work; did_work = true; - Timer time; - IdMap* previous_id_map = nullptr; IndexFile* previous_index = nullptr; if (response->previous) { @@ -467,92 +491,48 @@ bool IndexMain_DoCreateIndexUpdate(TimestampManager* timestamp_manager) { IndexUpdate update = IndexUpdate::CreateDelta(previous_id_map, response->current->ids.get(), previous_index, response->current->file.get()); - response->perf.index_make_delta = time.ElapsedMicrosecondsAndReset(); LOG_S(INFO) << "Built index update for " << response->current->file->path << " (is_delta=" << !!response->previous << ")"; - // Write current index to disk if requested. - if (response->write_to_disk) { - LOG_S(INFO) << "Writing cached index to disk for " - << response->current->file->path; - time.Reset(); - response->cache_manager->WriteToCache(*response->current->file); - response->perf.index_save_to_disk = time.ElapsedMicrosecondsAndReset(); - timestamp_manager->UpdateCachedModificationTime( - response->current->file->path, - response->current->file->last_modification_time); - } - -#if false -#define PRINT_SECTION(name) \ - if (response->perf.name) { \ - total += response->perf.name; \ - output << " " << #name << ": " << FormatMicroseconds(response->perf.name); \ - } - std::stringstream output; - long long total = 0; - output << "[perf]"; - PRINT_SECTION(index_parse); - PRINT_SECTION(index_build); - PRINT_SECTION(index_save_to_disk); - PRINT_SECTION(index_load_cached); - PRINT_SECTION(querydb_id_map); - PRINT_SECTION(index_make_delta); - output << "\n total: " << FormatMicroseconds(total); - output << " path: " << response->current_index->path; - LOG_S(INFO) << output.rdbuf(); -#undef PRINT_SECTION - - if (response->is_interactive) - LOG_S(INFO) << "Applying IndexUpdate" << std::endl << update.ToString(); -#endif - - Index_OnIndexed reply(std::move(update), response->perf); - queue->on_indexed.PushBack(std::move(reply), response->is_interactive); + Index_OnIndexed reply(std::move(update)); + const int kMaxSizeForQuerydb = 1000; + ThreadedQueue& q = + queue->on_indexed_for_querydb.Size() < kMaxSizeForQuerydb + ? queue->on_indexed_for_querydb + : queue->on_indexed_for_merge; + q.Enqueue(std::move(reply), response->is_interactive /*priority*/); } return did_work; } -bool IndexMain_LoadPreviousIndex() { - auto* queue = QueueManager::instance(); - optional response = queue->load_previous_index.TryPopFront(); - if (!response) - return false; - - response->previous = - response->cache_manager->TryTakeOrLoad(response->current->path); - LOG_IF_S(ERROR, !response->previous) - << "Unable to load previous index for already imported index " - << response->current->path; - - queue->do_id_map.PushBack(std::move(*response)); - return true; -} - bool IndexMergeIndexUpdates() { + // Merge low-priority requests, since priority requests should get serviced + // by querydb asap. + auto* queue = QueueManager::instance(); - optional root = queue->on_indexed.TryPopBack(); + optional root = + queue->on_indexed_for_merge.TryDequeue(false /*priority*/); if (!root) return false; bool did_merge = false; IterationLoop loop; while (loop.Next()) { - optional to_join = queue->on_indexed.TryPopBack(); + optional to_join = + queue->on_indexed_for_merge.TryDequeue(false /*priority*/); if (!to_join) break; did_merge = true; - // Timer time; root->update.Merge(std::move(to_join->update)); - // time.ResetAndPrint("Joined querydb updates for files: " + - // StringJoinMap(root->update.files_def_update, - //[](const QueryFile::DefUpdate& update) { - // return update.path; - //})); } - queue->on_indexed.PushFront(std::move(*root)); + const int kMaxSizeForQuerydb = 1500; + ThreadedQueue& q = + queue->on_indexed_for_querydb.Size() < kMaxSizeForQuerydb + ? queue->on_indexed_for_querydb + : queue->on_indexed_for_merge; + q.Enqueue(std::move(*root), false /*priority*/); return did_merge; } @@ -561,53 +541,13 @@ bool IndexMergeIndexUpdates() { ImportPipelineStatus::ImportPipelineStatus() : num_active_threads(0), next_progress_output(0) {} -// Index a file using an already-parsed translation unit from code completion. -// Since most of the time for indexing a file comes from parsing, we can do -// real-time indexing. -// TODO: add option to disable this. -void IndexWithTuFromCodeCompletion( - FileConsumerSharedState* file_consumer_shared, - ClangTranslationUnit* tu, - const std::vector& file_contents, - const AbsolutePath& path, - const std::vector& args) { - file_consumer_shared->Reset(path); - - PerformanceImportFile perf; - ClangIndex index; - auto indexes = ParseWithTu(file_consumer_shared, &perf, tu, &index, path, - args, file_contents); - if (!indexes) - return; - - std::vector result; - for (std::unique_ptr& new_index : *indexes) { - Timer time; - - std::shared_ptr cache_manager; - assert(false && "FIXME cache_manager"); - // When main thread does IdMap request it will request the previous index if - // needed. - LOG_S(INFO) << "Emitting index result for " << new_index->path; - result.push_back(Index_DoIdMap(std::move(new_index), cache_manager, perf, - true /*is_interactive*/, - true /*write_to_disk*/)); - } - - LOG_IF_S(WARNING, result.size() > 1) - << "Code completion index update generated more than one index"; - - QueueManager::instance()->do_id_map.EnqueueAll(std::move(result)); -} - void Indexer_Main(DiagnosticsEngine* diag_engine, FileConsumerSharedState* file_consumer_shared, TimestampManager* timestamp_manager, ImportManager* import_manager, ImportPipelineStatus* status, Project* project, - WorkingFiles* working_files, - MultiQueueWaiter* waiter) { + WorkingFiles* working_files) { RealModificationTimestampFetcher modification_timestamp_fetcher; auto* queue = QueueManager::instance(); // Build one index per-indexer, as building the index acquires a global lock. @@ -636,8 +576,6 @@ void Indexer_Main(DiagnosticsEngine* diag_engine, did_work = IndexMain_DoCreateIndexUpdate(timestamp_manager) || did_work; - did_work = IndexMain_LoadPreviousIndex() || did_work; - // Nothing to index and no index updates to create, so join some already // created index updates to reduce work on querydb thread. if (!did_work) @@ -646,8 +584,9 @@ void Indexer_Main(DiagnosticsEngine* diag_engine, // We didn't do any work, so wait for a notification. if (!did_work) { - waiter->Wait(&queue->on_indexed, &queue->index_request, - &queue->on_id_mapped, &queue->load_previous_index); + QueueManager::instance()->indexer_waiter->Wait( + &queue->index_request, &queue->on_id_mapped, + &queue->load_previous_index, &queue->on_indexed_for_merge); } } } @@ -659,18 +598,6 @@ void QueryDb_DoIdMap(QueueManager* queue, Index_DoIdMap* request) { assert(request->current); - // If the request does not have previous state and we have already imported - // it, load the previous state from disk and rerun IdMap logic later. Do not - // do this if we have already attempted in the past. - if (!request->load_previous && !request->previous && - db->usr_to_file.find(NormalizedPath(request->current->path)) != - db->usr_to_file.end()) { - assert(!request->load_previous); - request->load_previous = true; - queue->load_previous_index.PushBack(std::move(*request)); - return; - } - // Check if the file is already being imported into querydb. If it is, drop // the request. // @@ -682,10 +609,8 @@ void QueryDb_DoIdMap(QueueManager* queue, return; } - Index_OnIdMapped response(request->cache_manager, request->perf, - request->is_interactive, request->write_to_disk); - Timer time; - + Index_OnIdMapped response(request->cache_manager, request->is_interactive, + request->write_to_disk); auto make_map = [db](std::unique_ptr file) -> std::unique_ptr { if (!file) @@ -697,9 +622,9 @@ void QueryDb_DoIdMap(QueueManager* queue, }; response.current = make_map(std::move(request->current)); response.previous = make_map(std::move(request->previous)); - response.perf.querydb_id_map = time.ElapsedMicrosecondsAndReset(); - queue->on_id_mapped.PushBack(std::move(response)); + queue->on_id_mapped.Enqueue(std::move(response), + response.is_interactive /*priority*/); } void QueryDb_OnIndexed(QueueManager* queue, @@ -729,8 +654,7 @@ void QueryDb_OnIndexed(QueueManager* queue, EmitInactiveLines(working_file, updated_file.value.inactive_regions); // Semantic highlighting. - QueryFileId file_id = - db->usr_to_file[NormalizedPath(working_file->filename)]; + QueryId::File file_id = db->usr_to_file[working_file->filename]; QueryFile* file = &db->files[file_id.id]; EmitSemanticHighlighting(db, semantic_cache, working_file, file); } @@ -755,8 +679,10 @@ bool QueryDb_ImportMain(QueryDatabase* db, bool did_work = false; IterationLoop loop; + loop.IncreaseCount(); while (loop.Next()) { - optional request = queue->do_id_map.TryPopFront(); + optional request = + queue->do_id_map.TryDequeue(true /*priority*/); if (!request) break; did_work = true; @@ -765,7 +691,8 @@ bool QueryDb_ImportMain(QueryDatabase* db, loop.Reset(); while (loop.Next()) { - optional response = queue->on_indexed.TryPopFront(); + optional response = + queue->on_indexed_for_querydb.TryDequeue(true /*priority*/); if (!response) break; did_work = true; @@ -776,13 +703,74 @@ bool QueryDb_ImportMain(QueryDatabase* db, return did_work; } +<<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> - try to limit unqlite memory buffer to 32*1024 pages (=128MiB) +struct TestStore : public ICacheStore { + optional Read(const std::string& key) override { + auto it = elements_.find(key); + return it != elements_.end() ? it->second : optional{}; + } +<<<<<<< HEAD + + void Write(const std::string& key, const std::string& value) { + elements_[key] = value; + } + + ~TestStore() {} + + std::unordered_map elements_; +======= +struct TestStore : public ICacheStore +{ + optional Read(const std::string& key) override + { + auto it = elements_.find(key); + return it != elements_.end() ? it->second : optional{}; + } + + void Write(const std::string& key, const std::string& value) + { + elements_.insert_or_assign(key, value); + } +======= + + void Write(const std::string& key, const std::string& value) { + elements_[key] = value; + } +>>>>>>> - try to limit unqlite memory buffer to 32*1024 pages (=128MiB) + + ~TestStore() {} + +<<<<<<< HEAD + std::unordered_map< std::string, std::string > elements_; +>>>>>>> Interim fixes to the tests +======= + std::unordered_map elements_; +>>>>>>> - try to limit unqlite memory buffer to 32*1024 pages (=128MiB) +}; + TEST_SUITE("ImportPipeline") { struct Fixture { Fixture() { - QueueManager::Init(&querydb_waiter, &indexer_waiter, &stdout_waiter); + QueueManager::Init(); queue = QueueManager::instance(); - cache_manager = ICacheManager::MakeFake({}); +<<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> Interim fixes to the tests + + cache_store = std::make_shared(); + cache_manager = MakeIndexCache(cache_store); + +<<<<<<< HEAD +======= + //cache_manager = IndexCache::MakeFake({}); +>>>>>>> Experimental unqlite commit +======= +>>>>>>> Interim fixes to the tests indexer = IIndexer::MakeTestIndexer({}); diag_engine.Init(); } @@ -798,14 +786,11 @@ TEST_SUITE("ImportPipeline") { const std::vector& args = {}, bool is_interactive = false, const std::string& contents = "void foo();") { - queue->index_request.PushBack( - Index_Request(path, args, is_interactive, contents, cache_manager)); + queue->index_request.Enqueue( + Index_Request(path, args, is_interactive, contents, cache_manager), + false /*priority*/); } - MultiQueueWaiter querydb_waiter; - MultiQueueWaiter indexer_waiter; - MultiQueueWaiter stdout_waiter; - QueueManager* queue = nullptr; DiagnosticsEngine diag_engine; WorkingFiles working_files; @@ -813,7 +798,15 @@ TEST_SUITE("ImportPipeline") { TimestampManager timestamp_manager; FakeModificationTimestampFetcher modification_timestamp_fetcher; ImportManager import_manager; - std::shared_ptr cache_manager; +<<<<<<< HEAD +<<<<<<< HEAD + std::shared_ptr cache_store; +======= +>>>>>>> Experimental unqlite commit +======= + std::shared_ptr cache_store; +>>>>>>> Interim fixes to the tests + std::shared_ptr cache_manager; std::unique_ptr indexer; }; @@ -880,7 +873,6 @@ TEST_SUITE("ImportPipeline") { // FIXME: add more interesting tests that are not the happy path // FIXME: test // - IndexMain_DoCreateIndexUpdate - // - IndexMain_LoadPreviousIndex // - QueryDb_ImportMain TEST_CASE_FIXTURE(Fixture, "index request with zero results") { diff --git a/src/import_pipeline.h b/src/import_pipeline.h index 0ebeaebdc..0c5ef28fe 100644 --- a/src/import_pipeline.h +++ b/src/import_pipeline.h @@ -14,7 +14,6 @@ struct Config; class DiagnosticsEngine; struct FileConsumerSharedState; struct ImportManager; -struct MultiQueueWaiter; struct Project; struct QueryDatabase; struct SemanticHighlightSymbolCache; @@ -28,21 +27,13 @@ struct ImportPipelineStatus { ImportPipelineStatus(); }; -void IndexWithTuFromCodeCompletion( - FileConsumerSharedState* file_consumer_shared, - ClangTranslationUnit* tu, - const std::vector& file_contents, - const AbsolutePath& path, - const std::vector& args); - void Indexer_Main(DiagnosticsEngine* diag_engine, FileConsumerSharedState* file_consumer_shared, TimestampManager* timestamp_manager, ImportManager* import_manager, ImportPipelineStatus* status, Project* project, - WorkingFiles* working_files, - MultiQueueWaiter* waiter); + WorkingFiles* working_files); bool QueryDb_ImportMain(QueryDatabase* db, ImportManager* import_manager, diff --git a/src/include_complete.cc b/src/include_complete.cc index e6296e1c5..e83de8f6d 100644 --- a/src/include_complete.cc +++ b/src/include_complete.cc @@ -185,7 +185,7 @@ void IncludeComplete::InsertIncludesFromDirectory(std::string directory0, } std::vector results; - GetFilesInFolder( + GetFilesAndDirectoriesInFolder( directory->path, true /*recursive*/, false /*add_folder_to_path*/, [&](const std::string& path) { if (!EndsWithAny(path, g_config->completion.includeSuffixWhitelist)) diff --git a/src/indexer.h b/src/indexer.h index d72afbefd..4f359048f 100644 --- a/src/indexer.h +++ b/src/indexer.h @@ -9,8 +9,6 @@ #include "language.h" #include "lsp.h" #include "maybe.h" -#include "nt_string.h" -#include "performance.h" #include "position.h" #include "project.h" #include "serializer.h" @@ -63,6 +61,7 @@ struct Id { bool operator!=(const Id& o) const { return id != o.id; } bool operator<(const Id& o) const { return id < o.id; } }; +using AnyId = Id; namespace std { template @@ -76,13 +75,8 @@ void Reflect(TVisitor& visitor, Id& id) { Reflect(visitor, id.id); } -using IndexFileId = Id; -using IndexTypeId = Id; -using IndexFuncId = Id; -using IndexVarId = Id; - struct SymbolIdx { - Id id; + AnyId id; SymbolKind kind; bool operator==(const SymbolIdx& o) const { @@ -98,57 +92,55 @@ struct SymbolIdx { MAKE_REFLECT_STRUCT(SymbolIdx, kind, id); MAKE_HASHABLE(SymbolIdx, t.kind, t.id); +// The meaning of |id|, |kind| are determined if this is a SymbolRef or a +// LexicalRef. This type should not be constructed directly. struct Reference { Range range; - Id id; + AnyId id; SymbolKind kind; Role role; bool HasValueForMaybe_() const { return range.HasValueForMaybe_(); } operator SymbolIdx() const { return {id, kind}; } - std::tuple, SymbolKind, Role> ToTuple() const { + std::tuple ToTuple() const { return std::make_tuple(range, id, kind, role); } bool operator==(const Reference& o) const { return ToTuple() == o.ToTuple(); } bool operator<(const Reference& o) const { return ToTuple() < o.ToTuple(); } }; -// |id,kind| refer to the referenced entity. -struct SymbolRef : Reference { - SymbolRef() = default; - SymbolRef(Range range, Id id, SymbolKind kind, Role role) +// |id|,|kind| refer to the referenced entity. +struct IndexSymbolRef : Reference { + IndexSymbolRef() = default; + IndexSymbolRef(Range range, AnyId id, SymbolKind kind, Role role) + : Reference{range, id, kind, role} {} +}; + +// |id|,|kind| refer to the lexical parent. +struct IndexLexicalRef : Reference { + IndexLexicalRef() = default; + IndexLexicalRef(Range range, AnyId id, SymbolKind kind, Role role) : Reference{range, id, kind, role} {} }; -// Represents an occurrence of a variable/type, |id,kind| refer to the lexical -// parent. -struct Use : Reference { - // |file| is used in Query* but not in Index* - Id file; - Use() = default; - Use(Range range, Id id, SymbolKind kind, Role role, Id file) - : Reference{range, id, kind, role}, file(file) {} +struct IndexId { + using File = Id; + using Func = Id; + using Type = Id; + using Var = Id; + using SymbolRef = IndexSymbolRef; + using LexicalRef = IndexLexicalRef; }; -// Used by |HANDLE_MERGEABLE| so only |range| is needed. -MAKE_HASHABLE(Use, t.range); void Reflect(Reader& visitor, Reference& value); void Reflect(Writer& visitor, Reference& value); -struct IndexFamily { - using FileId = Id; - using FuncId = Id; - using TypeId = Id; - using VarId = Id; - using Range = ::Range; -}; - -template +template struct TypeDefDefinitionData { // General metadata. std::string detailed_name; - NtString hover; - NtString comments; + std::string hover; + std::string comments; // While a class/type can technically have a separate declaration/definition, // it doesn't really happen in practice. The declaration never contains @@ -159,21 +151,21 @@ struct TypeDefDefinitionData { // It's also difficult to identify a `class Foo;` statement with the clang // indexer API (it's doable using cursor AST traversal), so we don't bother // supporting the feature. - Maybe spell; - Maybe extent; + Maybe spell; + Maybe extent; // Immediate parent types. - std::vector bases; + std::vector bases; // Types, functions, and variables defined in this type. - std::vector types; - std::vector funcs; - std::vector vars; + std::vector types; + std::vector funcs; + std::vector vars; - typename F::FileId file; + typename Id::File file; // If set, then this is the same underlying type as the given value (ie, this // type comes from a using or typedef statement). - Maybe alias_of; + Maybe alias_of; int16_t short_name_offset = 0; int16_t short_name_size = 0; @@ -217,52 +209,52 @@ void Reflect(TVisitor& visitor, TypeDefDefinitionData& value) { } struct IndexType { - using Def = TypeDefDefinitionData; + using Def = TypeDefDefinitionData; Usr usr; - IndexTypeId id; + IndexId::Type id; Def def; - std::vector declarations; + std::vector declarations; // Immediate derived types. - std::vector derived; + std::vector derived; // Declared variables of this type. - std::vector instances; + std::vector instances; // Every usage, useful for things like renames. // NOTE: Do not insert directly! Use AddUsage instead. - std::vector uses; + std::vector uses; IndexType() {} // For serialization. - IndexType(IndexTypeId id, Usr usr); + IndexType(IndexId::Type id, Usr usr); bool operator<(const IndexType& other) const { return id < other.id; } }; MAKE_HASHABLE(IndexType, t.id); -template +template struct FuncDefDefinitionData { // General metadata. std::string detailed_name; - NtString hover; - NtString comments; - Maybe spell; - Maybe extent; + std::string hover; + std::string comments; + Maybe spell; + Maybe extent; // Method this method overrides. - std::vector bases; + std::vector bases; // Local variables or parameters. - std::vector vars; + std::vector vars; // Functions that this function calls. - std::vector callees; + std::vector callees; - typename F::FileId file; + typename Id::File file; // Type which declares this one (ie, it is a method) - Maybe declaring_type; + Maybe declaring_type; int16_t short_name_offset = 0; int16_t short_name_size = 0; lsSymbolKind kind = lsSymbolKind::Unknown; @@ -312,16 +304,16 @@ void Reflect(TVisitor& visitor, FuncDefDefinitionData& value) { } struct IndexFunc { - using Def = FuncDefDefinitionData; + using Def = FuncDefDefinitionData; Usr usr; - IndexFuncId id; + IndexId::Func id; Def def; struct Declaration { // Range of only the function name. - Use spell; + IndexId::LexicalRef spell; // Location of the parameter names. std::vector param_spellings; }; @@ -330,37 +322,37 @@ struct IndexFunc { std::vector declarations; // Methods which directly override this one. - std::vector derived; + std::vector derived; // Calls/usages of this function. If the call is coming from outside a // function context then the FuncRef will not have an associated id. // // To get all usages, also include the ranges inside of declarations and // def.spell. - std::vector uses; + std::vector uses; IndexFunc() {} // For serialization. - IndexFunc(IndexFuncId id, Usr usr) : usr(usr), id(id) {} + IndexFunc(IndexId::Func id, Usr usr) : usr(usr), id(id) {} bool operator<(const IndexFunc& other) const { return id < other.id; } }; MAKE_HASHABLE(IndexFunc, t.id); MAKE_REFLECT_STRUCT(IndexFunc::Declaration, spell, param_spellings); -template +template struct VarDefDefinitionData { // General metadata. std::string detailed_name; - NtString hover; - NtString comments; + std::string hover; + std::string comments; // TODO: definitions should be a list of ranges, since there can be more // than one - when?? - Maybe spell; - Maybe extent; + Maybe spell; + Maybe extent; - typename F::FileId file; + typename Id::File file; // Type of the variable. - Maybe type; + Maybe type; // Function/type which declares this one. int16_t short_name_offset = 0; @@ -420,18 +412,18 @@ void Reflect(TVisitor& visitor, VarDefDefinitionData& value) { } struct IndexVar { - using Def = VarDefDefinitionData; + using Def = VarDefDefinitionData; Usr usr; - IndexVarId id; + IndexId::Var id; Def def; - std::vector declarations; - std::vector uses; + std::vector declarations; + std::vector uses; IndexVar() {} // For serialization. - IndexVar(IndexVarId id, Usr usr) : usr(usr), id(id) {} + IndexVar(IndexId::Var id, Usr usr) : usr(usr), id(id) {} bool operator<(const IndexVar& other) const { return id < other.id; } }; @@ -439,12 +431,12 @@ MAKE_HASHABLE(IndexVar, t.id); struct IdCache { AbsolutePath primary_file; - std::unordered_map usr_to_type_id; - std::unordered_map usr_to_func_id; - std::unordered_map usr_to_var_id; - std::unordered_map type_id_to_usr; - std::unordered_map func_id_to_usr; - std::unordered_map var_id_to_usr; + std::unordered_map usr_to_type_id; + std::unordered_map usr_to_func_id; + std::unordered_map usr_to_var_id; + std::unordered_map type_id_to_usr; + std::unordered_map func_id_to_usr; + std::unordered_map var_id_to_usr; IdCache(const AbsolutePath& primary_file); }; @@ -495,15 +487,15 @@ struct IndexFile { IndexFile(const AbsolutePath& path, const std::string& contents); - IndexTypeId ToTypeId(Usr usr); - IndexFuncId ToFuncId(Usr usr); - IndexVarId ToVarId(Usr usr); - IndexTypeId ToTypeId(const CXCursor& usr); - IndexFuncId ToFuncId(const CXCursor& usr); - IndexVarId ToVarId(const CXCursor& usr); - IndexType* Resolve(IndexTypeId id); - IndexFunc* Resolve(IndexFuncId id); - IndexVar* Resolve(IndexVarId id); + IndexId::Type ToTypeId(Usr usr); + IndexId::Func ToFuncId(Usr usr); + IndexId::Var ToVarId(Usr usr); + IndexId::Type ToTypeId(const CXCursor& usr); + IndexId::Func ToFuncId(const CXCursor& usr); + IndexId::Var ToVarId(const CXCursor& usr); + IndexType* Resolve(IndexId::Type id); + IndexFunc* Resolve(IndexId::Func id); + IndexVar* Resolve(IndexId::Var id); std::string ToString(); }; @@ -525,12 +517,10 @@ optional>> Parse( const std::string& file, const std::vector& args, const std::vector& file_contents, - PerformanceImportFile* perf, ClangIndex* index, bool dump_ast = false); optional>> ParseWithTu( FileConsumerSharedState* file_consumer_shared, - PerformanceImportFile* perf, ClangTranslationUnit* tu, ClangIndex* index, const AbsolutePath& file, diff --git a/src/lsp.cc b/src/lsp.cc index 71520181b..bfbdc7c75 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -33,6 +33,15 @@ struct UriCache { if (cache.TryGet(path, &resolved)) return resolved; LOG_S(INFO) << "No cached URI for " << path; + + // If we do not have the value in the cache, try to renormalize it. + // Otherwise we will return paths with all lower-case letters which may + // break vscode. + optional normalized = NormalizePath( + path.path, true /*ensure_exists*/, false /*force_lower_on_windows*/); + if (normalized) + return normalized->path; + return path; } @@ -291,7 +300,7 @@ std::string lsDocumentUri::GetRawPath() const { if (raw_uri_.compare(0, 8, "file:///")) return raw_uri_; std::string ret; -#ifdef _WIN32 +#if defined(_WIN32) size_t i = 8; #else size_t i = 7; diff --git a/src/lsp.h b/src/lsp.h index f6e980920..c6da0e459 100644 --- a/src/lsp.h +++ b/src/lsp.h @@ -205,15 +205,6 @@ enum class lsSymbolKind : uint8_t { }; MAKE_REFLECT_TYPE_PROXY(lsSymbolKind); -// cquery extension -struct lsLocationEx : lsLocation { - optional containerName; - optional parentKind; - // Avoid circular dependency on symbol.h - optional role; -}; -MAKE_REFLECT_STRUCT(lsLocationEx, uri, range, containerName, parentKind, role); - template struct lsCommand { // Title of the command (ie, 'save') @@ -398,6 +389,6 @@ void Reflect(TVisitor& visitor, Out_ShowLogMessage& value) { struct Out_LocationList : public lsOutMessage { lsRequestId id; - std::vector result; + std::vector result; }; MAKE_REFLECT_STRUCT(Out_LocationList, jsonrpc, id, result); diff --git a/src/match.cc b/src/match.cc index 21f0b55fb..4fbc6f350 100644 --- a/src/match.cc +++ b/src/match.cc @@ -38,9 +38,7 @@ optional Matcher::Create(const std::string& search) { } bool Matcher::IsMatch(const std::string& value) const { - // std::smatch match; - // return std::regex_match(value, match, regex); - return std::regex_search(value, regex, std::regex_constants::match_any); + return std::regex_match(value, regex, std::regex_constants::match_any); } GroupMatch::GroupMatch(const std::vector& whitelist, diff --git a/src/message_handler.cc b/src/message_handler.cc index acf5f6192..b3843e90a 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -58,22 +58,22 @@ bool FindFileOrFail(QueryDatabase* db, optional id, const AbsolutePath& absolute_path, QueryFile** out_query_file, - QueryFileId* out_file_id) { + QueryId::File* out_file_id) { *out_query_file = nullptr; - auto it = db->usr_to_file.find(NormalizedPath(absolute_path)); + auto it = db->usr_to_file.find(absolute_path); if (it != db->usr_to_file.end()) { QueryFile& file = db->files[it->second.id]; if (file.def) { *out_query_file = &file; if (out_file_id) - *out_file_id = QueryFileId(it->second.id); + *out_file_id = QueryId::File(it->second.id); return true; } } if (out_file_id) - *out_file_id = QueryFileId(); + *out_file_id = QueryId::File(); bool indexing = project->absolute_path_to_entry_index_.find(absolute_path) != project->absolute_path_to_entry_index_.end(); @@ -81,13 +81,6 @@ bool FindFileOrFail(QueryDatabase* db, LOG_S(INFO) << "\"" << absolute_path << "\" is being indexed."; else LOG_S(INFO) << "Unable to find file \"" << absolute_path << "\""; - /* - LOG_S(INFO) << "Files (size=" << db->usr_to_file.size() << "): " - << StringJoinMap(db->usr_to_file, - [](const std::pair& - entry) { return entry.first.path; - }); - */ if (id) { Out_Error out; @@ -107,6 +100,9 @@ bool FindFileOrFail(QueryDatabase* db, void EmitInactiveLines(WorkingFile* working_file, const std::vector& inactive_regions) { + if (!g_config->emitInactiveRegions) + return; + Out_CquerySetInactiveRegion out; out.params.uri = lsDocumentUri::FromPath(working_file->filename); for (Range skipped : inactive_regions) { @@ -121,6 +117,9 @@ void EmitSemanticHighlighting(QueryDatabase* db, SemanticHighlightSymbolCache* semantic_cache, WorkingFile* working_file, QueryFile* file) { + if (!g_config->highlight.enabled) + return; + assert(file->def); if (!semantic_cache->match_->IsMatch(file->def->path)) return; @@ -130,7 +129,7 @@ void EmitSemanticHighlighting(QueryDatabase* db, // Group symbols together. std::unordered_map grouped_symbols; - for (SymbolRef sym : file->def->all_symbols) { + for (QueryId::SymbolRef sym : file->def->all_symbols) { std::string_view detailed_name; lsSymbolKind parent_kind = lsSymbolKind::Unknown; lsSymbolKind kind = lsSymbolKind::Unknown; @@ -145,8 +144,8 @@ void EmitSemanticHighlighting(QueryDatabase* db, if (def->spell) parent_kind = GetSymbolKind(db, *def->spell); if (parent_kind == lsSymbolKind::Unknown) { - for (Use use : func.declarations) { - parent_kind = GetSymbolKind(db, use); + for (QueryId::LexicalRef ref : func.declarations) { + parent_kind = GetSymbolKind(db, ref); break; } } @@ -170,6 +169,9 @@ void EmitSemanticHighlighting(QueryDatabase* db, detailed_name.substr(0, detailed_name.find('<')); int16_t start_line = sym.range.start.line; int16_t start_col = sym.range.start.column; + // The function is not there if this isn't at least zero. + if (start_line < 0) + continue; if (start_line >= 0 && start_line < working_file->index_lines.size()) { std::string_view line = working_file->index_lines[start_line]; sym.range.end.line = start_line; @@ -203,8 +205,8 @@ void EmitSemanticHighlighting(QueryDatabase* db, } } if (parent_kind == lsSymbolKind::Unknown) { - for (Use use : var.declarations) { - parent_kind = GetSymbolKind(db, use); + for (QueryId::LexicalRef ref : var.declarations) { + parent_kind = GetSymbolKind(db, ref); break; } } diff --git a/src/message_handler.h b/src/message_handler.h index 2d08a7abc..2268dbc0e 100644 --- a/src/message_handler.h +++ b/src/message_handler.h @@ -70,7 +70,6 @@ MAKE_REFLECT_STRUCT(Out_CqueryPublishSemanticHighlighting, struct MessageHandler { QueryDatabase* db = nullptr; - MultiQueueWaiter* waiter = nullptr; Project* project = nullptr; DiagnosticsEngine* diag_engine = nullptr; FileConsumerSharedState* file_consumer_shared = nullptr; @@ -109,7 +108,7 @@ bool FindFileOrFail(QueryDatabase* db, optional id, const AbsolutePath& absolute_path, QueryFile** out_query_file, - QueryFileId* out_file_id = nullptr); + QueryId::File* out_file_id = nullptr); void EmitInactiveLines(WorkingFile* working_file, const std::vector& inactive_regions); diff --git a/src/messages/cquery_base.cc b/src/messages/cquery_base.cc index 1b530f86e..b9ad3d732 100644 --- a/src/messages/cquery_base.cc +++ b/src/messages/cquery_base.cc @@ -30,19 +30,19 @@ struct Handler_CqueryBase : BaseMessageHandler { Out_LocationList out; out.id = request->id; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { if (sym.kind == SymbolKind::Type) { - if (const auto* def = db->GetType(sym).AnyDef()) - out.result = GetLsLocationExs( - db, working_files, GetDeclarations(db, def->bases), - g_config->xref.container, g_config->xref.maxNum); + if (const auto* def = db->GetType(sym).AnyDef()) { + out.result = GetLsLocations(db, working_files, + GetDeclarations(db, def->bases)); + } break; } else if (sym.kind == SymbolKind::Func) { - if (const auto* def = db->GetFunc(sym).AnyDef()) - out.result = GetLsLocationExs( - db, working_files, GetDeclarations(db, def->bases), - g_config->xref.container, g_config->xref.maxNum); + if (const auto* def = db->GetFunc(sym).AnyDef()) { + out.result = GetLsLocations(db, working_files, + GetDeclarations(db, def->bases)); + } break; } } diff --git a/src/messages/cquery_call_hierarchy.cc b/src/messages/cquery_call_hierarchy.cc index f609662e4..7d5bdd748 100644 --- a/src/messages/cquery_call_hierarchy.cc +++ b/src/messages/cquery_call_hierarchy.cc @@ -29,7 +29,7 @@ struct In_CqueryCallHierarchy : public RequestInMessage { lsTextDocumentIdentifier textDocument; lsPosition position; - Maybe id; + Maybe id; // true: callee tree (functions called by this function); false: caller tree // (where this function is called) @@ -55,7 +55,7 @@ REGISTER_IN_MESSAGE(In_CqueryCallHierarchy); struct Out_CqueryCallHierarchy : public lsOutMessage { struct Entry { - QueryFuncId id; + QueryId::Func id; std::string_view name; lsLocation location; CallType callType = CallType::Direct; @@ -74,7 +74,10 @@ MAKE_REFLECT_STRUCT(Out_CqueryCallHierarchy::Entry, callType, numChildren, children); -MAKE_REFLECT_STRUCT(Out_CqueryCallHierarchy, jsonrpc, id, result); +MAKE_REFLECT_STRUCT_OPTIONALS_MANDATORY(Out_CqueryCallHierarchy, + jsonrpc, + id, + result); bool Expand(MessageHandler* m, Out_CqueryCallHierarchy::Entry* entry, @@ -87,12 +90,12 @@ bool Expand(MessageHandler* m, entry->numChildren = 0; if (!def) return false; - auto handle = [&](Use use, CallType call_type) { + auto handle = [&](QueryId::LexicalRef ref, CallType call_type) { entry->numChildren++; if (levels > 0) { Out_CqueryCallHierarchy::Entry entry1; - entry1.id = QueryFuncId(use.id); - if (auto loc = GetLsLocation(m->db, m->working_files, use)) + entry1.id = QueryId::Func(ref.id); + if (auto loc = GetLsLocation(m->db, m->working_files, ref)) entry1.location = *loc; entry1.callType = call_type; if (Expand(m, &entry1, callee, call_type, detailed_name, levels - 1)) @@ -102,14 +105,16 @@ bool Expand(MessageHandler* m, auto handle_uses = [&](const QueryFunc& func, CallType call_type) { if (callee) { if (const auto* def = func.AnyDef()) - for (SymbolRef ref : def->callees) + for (const QueryId::SymbolRef& ref : def->callees) { if (ref.kind == SymbolKind::Func) - handle(Use(ref.range, ref.id, ref.kind, ref.role, def->file), + handle(QueryId::LexicalRef(ref.range, ref.id, ref.kind, ref.role, + def->file), call_type); + } } else { - for (Use use : func.uses) - if (use.kind == SymbolKind::Func) - handle(use, call_type); + for (QueryId::LexicalRef ref : func.uses) + if (ref.kind == SymbolKind::Func) + handle(ref, call_type); } }; @@ -162,7 +167,7 @@ struct Handler_CqueryCallHierarchy : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } - optional BuildInitial(QueryFuncId root_id, + optional BuildInitial(QueryId::Func root_id, bool callee, CallType call_type, bool detailed_name, @@ -203,12 +208,12 @@ struct Handler_CqueryCallHierarchy return; WorkingFile* working_file = working_files->GetFileByFilename(file->def->path); - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, params.position)) { if (sym.kind == SymbolKind::Func) { out.result = - BuildInitial(QueryFuncId(sym.id), params.callee, params.callType, - params.detailedName, params.levels); + BuildInitial(QueryId::Func(sym.id), params.callee, + params.callType, params.detailedName, params.levels); break; } } diff --git a/src/messages/cquery_callers.cc b/src/messages/cquery_callers.cc index b3114c5bb..7f9fa64c5 100644 --- a/src/messages/cquery_callers.cc +++ b/src/messages/cquery_callers.cc @@ -27,18 +27,16 @@ struct Handler_CqueryCallers : BaseMessageHandler { Out_LocationList out; out.id = request->id; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { if (sym.kind == SymbolKind::Func) { QueryFunc& func = db->GetFunc(sym); - std::vector uses = func.uses; - for (Use func_ref : GetUsesForAllBases(db, func)) + std::vector uses = func.uses; + for (QueryId::LexicalRef func_ref : GetRefsForAllBases(db, func)) uses.push_back(func_ref); - for (Use func_ref : GetUsesForAllDerived(db, func)) + for (QueryId::LexicalRef func_ref : GetRefsForAllDerived(db, func)) uses.push_back(func_ref); - out.result = - GetLsLocationExs(db, working_files, uses, g_config->xref.container, - g_config->xref.maxNum); + out.result = GetLsLocations(db, working_files, uses); break; } } diff --git a/src/messages/cquery_freshen_index.cc b/src/messages/cquery_freshen_index.cc index ca2e14b71..bfa66e16e 100644 --- a/src/messages/cquery_freshen_index.cc +++ b/src/messages/cquery_freshen_index.cc @@ -40,7 +40,8 @@ struct Handler_CqueryFreshenIndex : BaseMessageHandler { GroupMatch matcher(request->params.whitelist, request->params.blacklist); // Unmark all files whose timestamp has changed. - std::shared_ptr cache_manager = ICacheManager::Make(); + std::shared_ptr cache_manager = + MakeIndexCache(g_config->cacheStore); std::queue q; // |need_index| stores every filename ever enqueued. diff --git a/src/messages/cquery_index_file.cc b/src/messages/cquery_index_file.cc index faebba21e..68d32c5d5 100644 --- a/src/messages/cquery_index_file.cc +++ b/src/messages/cquery_index_file.cc @@ -34,9 +34,11 @@ struct Handler_CqueryIndexFile : BaseMessageHandler { ABORT_S() << "Unable to find " << request->params.path; LOG_S(INFO) << "Indexing file " << request->params.path; - QueueManager::instance()->index_request.PushBack(Index_Request( - path->path, request->params.args, request->params.is_interactive, - request->params.contents, ICacheManager::Make())); + QueueManager::instance()->index_request.Enqueue( + Index_Request(path->path, request->params.args, + request->params.is_interactive, request->params.contents, + MakeIndexCache(g_config->cacheStore)), + true /*priority*/); } }; REGISTER_MESSAGE_HANDLER(Handler_CqueryIndexFile); diff --git a/src/messages/cquery_inheritance_hierarchy.cc b/src/messages/cquery_inheritance_hierarchy.cc index d0e8a949e..b255280b6 100644 --- a/src/messages/cquery_inheritance_hierarchy.cc +++ b/src/messages/cquery_inheritance_hierarchy.cc @@ -13,7 +13,7 @@ struct In_CqueryInheritanceHierarchy : public RequestInMessage { lsTextDocumentIdentifier textDocument; lsPosition position; - Maybe> id; + Maybe id; SymbolKind kind = SymbolKind::Invalid; // true: derived classes/functions; false: base classes/functions @@ -38,7 +38,7 @@ REGISTER_IN_MESSAGE(In_CqueryInheritanceHierarchy); struct Out_CqueryInheritanceHierarchy : public lsOutMessage { struct Entry { - Id id; + AnyId id; SymbolKind kind; std::string_view name; lsLocation location; @@ -58,7 +58,10 @@ MAKE_REFLECT_STRUCT(Out_CqueryInheritanceHierarchy::Entry, location, numChildren, children); -MAKE_REFLECT_STRUCT(Out_CqueryInheritanceHierarchy, jsonrpc, id, result); +MAKE_REFLECT_STRUCT_OPTIONALS_MANDATORY(Out_CqueryInheritanceHierarchy, + jsonrpc, + id, + result); bool Expand(MessageHandler* m, Out_CqueryInheritanceHierarchy::Entry* entry, @@ -132,8 +135,11 @@ struct Handler_CqueryInheritanceHierarchy : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } - optional - BuildInitial(SymbolRef sym, bool derived, bool detailed_name, int levels) { + optional BuildInitial( + QueryId::SymbolRef sym, + bool derived, + bool detailed_name, + int levels) { Out_CqueryInheritanceHierarchy::Entry entry; entry.id = sym.id; entry.kind = sym.kind; @@ -164,8 +170,8 @@ struct Handler_CqueryInheritanceHierarchy WorkingFile* working_file = working_files->GetFileByFilename(file->def->path); - for (SymbolRef sym : FindSymbolsAtLocation(working_file, file, - request->params.position)) { + for (QueryId::SymbolRef sym : FindSymbolsAtLocation( + working_file, file, request->params.position)) { if (sym.kind == SymbolKind::Func || sym.kind == SymbolKind::Type) { out.result = BuildInitial(sym, params.derived, params.detailedName, params.levels); diff --git a/src/messages/cquery_member_hierarchy.cc b/src/messages/cquery_member_hierarchy.cc index 93e01497a..e2b6728da 100644 --- a/src/messages/cquery_member_hierarchy.cc +++ b/src/messages/cquery_member_hierarchy.cc @@ -14,7 +14,7 @@ struct In_CqueryMemberHierarchy : public RequestInMessage { lsTextDocumentIdentifier textDocument; lsPosition position; - Maybe id; + Maybe id; bool detailedName = false; int levels = 1; @@ -34,8 +34,8 @@ REGISTER_IN_MESSAGE(In_CqueryMemberHierarchy); struct Out_CqueryMemberHierarchy : public lsOutMessage { struct Entry { - QueryTypeId id; - std::string_view name; + QueryId::Type id; + std::string name; std::string fieldName; lsLocation location; // For unexpanded nodes, this is an upper bound because some entities may be @@ -54,7 +54,10 @@ MAKE_REFLECT_STRUCT(Out_CqueryMemberHierarchy::Entry, location, numChildren, children); -MAKE_REFLECT_STRUCT(Out_CqueryMemberHierarchy, jsonrpc, id, result); +MAKE_REFLECT_STRUCT_OPTIONALS_MANDATORY(Out_CqueryMemberHierarchy, + jsonrpc, + id, + result); bool Expand(MessageHandler* m, Out_CqueryMemberHierarchy::Entry* entry, @@ -85,7 +88,7 @@ void DoField(MessageHandler* m, if (Expand(m, &entry1, detailed_name, levels)) entry->children.push_back(std::move(entry1)); } else { - entry1.id = QueryTypeId(); + entry1.id = QueryId::Type(); entry->children.push_back(std::move(entry1)); } } @@ -107,7 +110,7 @@ bool Expand(MessageHandler* m, if (detailed_name) entry->name = def->detailed_name; else - entry->name = def->ShortName(); + entry->name = std::string(def->ShortName()); std::unordered_set seen; if (levels > 0) { std::vector stack; @@ -164,7 +167,7 @@ struct Handler_CqueryMemberHierarchy : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } - optional BuildInitial(QueryFuncId root_id, + optional BuildInitial(QueryId::Func root_id, bool detailed_name, int levels) { const auto* def = db->funcs[root_id.id].AnyDef(); @@ -174,7 +177,7 @@ struct Handler_CqueryMemberHierarchy Out_CqueryMemberHierarchy::Entry entry; // Not type, |id| is invalid. if (detailed_name) - entry.name = def->DetailedName(false); + entry.name = std::string(def->DetailedName(false)); else entry.name = std::string(def->ShortName()); if (def->spell) { @@ -188,7 +191,7 @@ struct Handler_CqueryMemberHierarchy return entry; } - optional BuildInitial(QueryTypeId root_id, + optional BuildInitial(QueryId::Type root_id, bool detailed_name, int levels) { const auto* def = db->types[root_id.id].AnyDef(); @@ -225,21 +228,21 @@ struct Handler_CqueryMemberHierarchy return; WorkingFile* working_file = working_files->GetFileByFilename(file->def->path); - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, params.position)) { switch (sym.kind) { case SymbolKind::Func: - out.result = BuildInitial(QueryFuncId(sym.id), params.detailedName, - params.levels); + out.result = BuildInitial(QueryId::Func(sym.id), + params.detailedName, params.levels); break; case SymbolKind::Type: - out.result = BuildInitial(QueryTypeId(sym.id), params.detailedName, - params.levels); + out.result = BuildInitial(QueryId::Type(sym.id), + params.detailedName, params.levels); break; case SymbolKind::Var: { const QueryVar::Def* def = db->GetVar(sym).AnyDef(); if (def && def->type) - out.result = BuildInitial(QueryTypeId(*def->type), + out.result = BuildInitial(QueryId::Type(*def->type), params.detailedName, params.levels); break; } diff --git a/src/messages/cquery_random.cc b/src/messages/cquery_random.cc deleted file mode 100644 index f0665f611..000000000 --- a/src/messages/cquery_random.cc +++ /dev/null @@ -1,148 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -#include -#include -#include - -namespace { -MethodType kMethodType = "$cquery/random"; - -struct In_CqueryRandom : public RequestInMessage { - MethodType GetMethodType() const override { return kMethodType; } -}; -MAKE_REFLECT_STRUCT(In_CqueryRandom, id); -REGISTER_IN_MESSAGE(In_CqueryRandom); - -const double kDeclWeight = 3; -const double kDamping = 0.1; - -template -struct Kind; -template <> -struct Kind { - static constexpr SymbolKind value = SymbolKind::Func; -}; -template <> -struct Kind { - static constexpr SymbolKind value = SymbolKind::Type; -}; -template <> -struct Kind { - static constexpr SymbolKind value = SymbolKind::Var; -}; - -template -void Add(const std::unordered_map& sym2id, - std::vector>& adj, - const std::vector>& ids, - int n, - double w = 1) { - for (Id id : ids) { - auto it = sym2id.find(SymbolIdx{id, Kind::value}); - if (it != sym2id.end()) - adj[it->second][n] += w; - } -} - -struct Handler_CqueryRandom : BaseMessageHandler { - MethodType GetMethodType() const override { return kMethodType; } - - void Run(In_CqueryRandom* request) override { - std::unordered_map sym2id; - std::vector syms; - int n = 0; - - for (RawId i = 0; i < db->funcs.size(); i++) - if (db->funcs[i].AnyDef()) { - syms.push_back(SymbolIdx{Id(i), SymbolKind::Func}); - sym2id[syms.back()] = n++; - } - for (RawId i = 0; i < db->types.size(); i++) - if (db->types[i].AnyDef()) { - syms.push_back(SymbolIdx{Id(i), SymbolKind::Type}); - sym2id[syms.back()] = n++; - } - for (RawId i = 0; i < db->vars.size(); i++) - if (db->vars[i].AnyDef()) { - syms.push_back(SymbolIdx{Id(i), SymbolKind::Var}); - sym2id[syms.back()] = n++; - } - - std::vector> adj(n); - auto add = [&](const std::vector& uses, double w) { - for (Use use : uses) { - auto it = sym2id.find(use); - if (it != sym2id.end()) - adj[it->second][n] += w; - } - }; - n = 0; - for (QueryFunc& func : db->funcs) - if (func.AnyDef()) { - add(func.declarations, kDeclWeight); - add(func.uses, 1); - Add(sym2id, adj, func.derived, n); - n++; - } - for (QueryType& type : db->types) - if (const auto* def = type.AnyDef()) { - add(type.uses, 1); - Add(sym2id, adj, type.instances, n); - Add(sym2id, adj, def->funcs, n); - Add(sym2id, adj, def->types, n); - Add(sym2id, adj, def->vars, n); - n++; - } - for (QueryVar& var : db->vars) - if (var.AnyDef()) { - add(var.declarations, kDeclWeight); - add(var.uses, 1); - n++; - } - for (int i = 0; i < n; i++) { - double sum = 0; - adj[i][i] += 1; - for (auto& it : adj[i]) - sum += it.second; - for (auto& it : adj[i]) - it.second = it.second / sum * (1 - kDamping); - } - - std::vector x(n, 1), y; - for (int j = 0; j < 8; j++) { - y.assign(n, kDamping); - for (int i = 0; i < n; i++) - for (auto& it : adj[i]) - y[it.first] += x[i] * it.second; - double d = 0; - for (int i = 0; i < n; i++) - d = std::max(d, fabs(x[i] - y[i])); - if (d < 1e-5) - break; - x.swap(y); - } - - double sum = std::accumulate(x.begin(), x.end(), 0.); - Out_LocationList out; - out.id = request->id; - double roulette = rand() / (RAND_MAX + 1.0) * sum; - sum = 0; - for (int i = 0; i < n; i++) { - sum += x[i]; - if (sum >= roulette) { - Maybe use = GetDefinitionExtent(db, syms[i]); - if (!use) - continue; - if (auto ls_loc = GetLsLocationEx(db, working_files, *use, - g_config->xref.container)) - out.result.push_back(*ls_loc); - break; - } - } - QueueManager::WriteStdout(kMethodType, out); - } -}; -REGISTER_MESSAGE_HANDLER(Handler_CqueryRandom); -} // namespace diff --git a/src/messages/cquery_vars.cc b/src/messages/cquery_vars.cc index 0ab24d54d..f35e0c2d2 100644 --- a/src/messages/cquery_vars.cc +++ b/src/messages/cquery_vars.cc @@ -29,9 +29,9 @@ struct Handler_CqueryVars : BaseMessageHandler { Out_LocationList out; out.id = request->id; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { - Id id = sym.id; + AnyId id = sym.id; switch (sym.kind) { default: break; @@ -44,9 +44,8 @@ struct Handler_CqueryVars : BaseMessageHandler { // fallthrough case SymbolKind::Type: { QueryType& type = db->types[id.id]; - out.result = GetLsLocationExs( - db, working_files, GetDeclarations(db, type.instances), - g_config->xref.container, g_config->xref.maxNum); + out.result = GetLsLocations(db, working_files, + GetDeclarations(db, type.instances)); break; } } diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index dd337a781..14017236f 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -105,7 +105,10 @@ enum class lsTextDocumentSyncKind { // send. Incremental = 2 }; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" MAKE_REFLECT_TYPE_PROXY(lsTextDocumentSyncKind) +#pragma clang diagnostic pop struct lsTextDocumentSyncOptions { // Open and close notifications are sent to the server. @@ -145,6 +148,8 @@ struct lsServerCapabilities { bool definitionProvider = true; // The server provides Goto Type Definition support. bool typeDefinitionProvider = true; + // The server provides implementation support. + bool implementationProvider = true; // The server provides find references support. bool referencesProvider = true; // The server provides document highlight support. @@ -177,6 +182,7 @@ MAKE_REFLECT_STRUCT(lsServerCapabilities, signatureHelpProvider, definitionProvider, typeDefinitionProvider, + implementationProvider, referencesProvider, documentHighlightProvider, documentSymbolProvider, @@ -287,6 +293,9 @@ struct lsTextDocumentClientCapabilities { // Capabilities specific to the `textDocument/signatureHelp` optional signatureHelp; + // Capabilities specific to the `textDocument/implementation` + optional implementation; + // Capabilities specific to the `textDocument/references` optional references; @@ -348,6 +357,7 @@ MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities, completion, hover, signatureHelp, + implementation, references, documentHighlight, documentSymbol, @@ -536,47 +546,49 @@ struct Handler_Initialize : BaseMessageHandler { } } - // Client capabilities - if (request->params.capabilities.textDocument) { - const auto& cap = *request->params.capabilities.textDocument; - if (cap.completion && cap.completion->completionItem) - g_config->client.snippetSupport = - cap.completion->completionItem->snippetSupport.value_or(false); - } - - // Check client version. - if (g_config->clientVersion.has_value() && - *g_config->clientVersion != kExpectedClientVersion) { - Out_ShowLogMessage out; - out.display_type = Out_ShowLogMessage::DisplayType::Show; - out.params.type = lsMessageType::Error; - out.params.message = - "cquery client (v" + std::to_string(*g_config->clientVersion) + - ") and server (v" + std::to_string(kExpectedClientVersion) + - ") version mismatch. Please update "; - if (g_config->clientVersion > kExpectedClientVersion) - out.params.message += "the cquery binary."; - else - out.params.message += - "your extension client (VSIX file). Make sure to uninstall " - "the cquery extension and restart vscode before " - "reinstalling."; - out.Write(std::cout); + // Should snippets be enabled? + if (!request->params.capabilities.textDocument || + !request->params.capabilities.textDocument->completion || + !request->params.capabilities.textDocument->completion + ->completionItem || + !request->params.capabilities.textDocument->completion->completionItem + ->snippetSupport || + !request->params.capabilities.textDocument->completion->completionItem + ->snippetSupport.value()) { + g_config->completion.enableSnippets = false; } // Ensure there is a resource directory. - if (g_config->resourceDirectory.empty()) - g_config->resourceDirectory = GetDefaultResourceDirectory(); + if (g_config->resourceDirectory.empty()) { + optional resource_dir = GetDefaultResourceDirectory(); + if (!resource_dir) + ABORT_S() << "Cannot resolve resource directory."; + g_config->resourceDirectory = resource_dir->path; + } LOG_S(INFO) << "Using -resource-dir=" << g_config->resourceDirectory; // Send initialization before starting indexers, so we don't send a // status update too early. - // TODO: query request->params.capabilities.textDocument and support - // only things the client supports. Out_InitializeResponse out; out.id = request->id; + // Check if formatting should be enabled. + out.result.capabilities.documentFormattingProvider = false; + out.result.capabilities.documentRangeFormattingProvider = false; + out.result.capabilities.documentOnTypeFormattingProvider.reset(); + if (g_config->formatting.enabled) { + if (request->params.capabilities.textDocument->formatting) { + out.result.capabilities.documentFormattingProvider = true; + } + if (request->params.capabilities.textDocument->rangeFormatting) { + out.result.capabilities.documentRangeFormattingProvider = true; + } + } + + // TODO: query request->params.capabilities.textDocument and support + // only things the client supports. + // out.result.capabilities.textDocumentSync = // lsTextDocumentSyncOptions(); // out.result.capabilities.textDocumentSync->openClose = true; @@ -591,12 +603,19 @@ struct Handler_Initialize : BaseMessageHandler { // Set project root. EnsureEndsInSlash(project_path); g_config->projectRoot = project_path; - // Create two cache directories for files inside and outside of the - // project. - MakeDirectoryRecursive(g_config->cacheDirectory + - EscapeFileName(g_config->projectRoot)); - MakeDirectoryRecursive(g_config->cacheDirectory + '@' + - EscapeFileName(g_config->projectRoot)); + + // Initialize data store + if (g_config->cacheType == "files") { + g_config->cacheStore = + OpenOrConnectFileStore(AbsolutePath{g_config->projectRoot}); + } else if (g_config->cacheType == "unqlite") { + g_config->cacheStore = + OpenOrConnectUnqliteStore(AbsolutePath{g_config->projectRoot}); + } else { + LOG_S(INFO) << "Invalid cache type \"" << g_config->cacheType << "\"."; + } + + assert(g_config->cacheStore); Timer time; diag_engine->Init(); @@ -624,7 +643,7 @@ struct Handler_Initialize : BaseMessageHandler { WorkThread::StartThread("indexer" + std::to_string(i), [=]() { Indexer_Main(diag_engine, file_consumer_shared, timestamp_manager, import_manager, import_pipeline_status, project, - working_files, waiter); + working_files); }); } diff --git a/src/messages/shutdown.cc b/src/messages/shutdown.cc index e16721d47..674be535d 100644 --- a/src/messages/shutdown.cc +++ b/src/messages/shutdown.cc @@ -1,6 +1,9 @@ #include "message_handler.h" #include "queue_manager.h" +#include "cache_manager.h" +#include "config.h" + namespace { MethodType kMethodType = "shutdown"; @@ -19,6 +22,7 @@ MAKE_REFLECT_STRUCT(Out_Shutdown, jsonrpc, id, result); struct Handler_Shutdown : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } void Run(In_Shutdown* request) override { + g_config->cacheStore->Close(); Out_Shutdown out; out.id = request->id; QueueManager::WriteStdout(kMethodType, out); diff --git a/src/messages/text_document_code_action.cc b/src/messages/text_document_code_action.cc index 70a3cce66..e621cf390 100644 --- a/src/messages/text_document_code_action.cc +++ b/src/messages/text_document_code_action.cc @@ -65,17 +65,17 @@ optional FindIncludeLine(const std::vector& lines, return 0; } -optional GetImplementationFile(QueryDatabase* db, - QueryFileId file_id, - QueryFile* file) { - for (SymbolRef sym : file->def->outline) { +optional GetImplementationFile(QueryDatabase* db, + QueryId::File file_id, + QueryFile* file) { + for (QueryId::SymbolRef sym : file->def->outline) { switch (sym.kind) { case SymbolKind::Func: { if (const auto* def = db->GetFunc(sym).AnyDef()) { // Note: we ignore the definition if it is in the same file (ie, // possibly a header). if (def->extent) { - QueryFileId t = def->extent->file; + QueryId::File t = def->extent->file; if (t != file_id) return t; } @@ -87,7 +87,7 @@ optional GetImplementationFile(QueryDatabase* db, // Note: we ignore the definition if it is in the same file (ie, // possibly a header). if (def && def->extent) { - QueryFileId t = def->extent->file; + QueryId::File t = def->extent->file; if (t != file_id) return t; } @@ -100,7 +100,7 @@ optional GetImplementationFile(QueryDatabase* db, // No associated definition, scan the project for a file in the same // directory with the same base-name. - NormalizedPath original_path(file->def->path); + AbsolutePath original_path = file->def->path; std::string target_path = original_path.path; size_t last = target_path.find_last_of('.'); if (last != std::string::npos) { @@ -110,7 +110,7 @@ optional GetImplementationFile(QueryDatabase* db, LOG_S(INFO) << "!! Looking for impl file that starts with " << target_path; for (auto& entry : db->usr_to_file) { - const NormalizedPath& path = entry.first; + const AbsolutePath& path = entry.first; // Do not consider header files for implementation files. // TODO: make file extensions configurable. @@ -126,9 +126,9 @@ optional GetImplementationFile(QueryDatabase* db, } void EnsureImplFile(QueryDatabase* db, - QueryFileId file_id, + QueryId::File file_id, optional& impl_uri, - optional& impl_file_id) { + optional& impl_file_id) { if (!impl_uri.has_value()) { QueryFile& file = db->files[file_id.id]; assert(file.def); @@ -149,12 +149,12 @@ optional BuildAutoImplementForFunction(QueryDatabase* db, WorkingFiles* working_files, WorkingFile* working_file, int default_line, - QueryFileId decl_file_id, - QueryFileId impl_file_id, + QueryId::File decl_file_id, + QueryId::File impl_file_id, QueryFunc& func) { const QueryFunc::Def* def = func.AnyDef(); assert(def); - for (Use decl : func.declarations) { + for (QueryId::LexicalRef decl : func.declarations) { if (decl.file != decl_file_id) continue; @@ -201,7 +201,7 @@ optional BuildAutoImplementForFunction(QueryDatabase* db, QueryFile& file = db->files[impl_file_id.id]; assert(file.def); - for (SymbolRef sym : file.def->outline) { + for (QueryId::SymbolRef sym : file.def->outline) { switch (sym.kind) { case SymbolKind::Func: { QueryFunc& sym_func = db->GetFunc(sym); @@ -209,7 +209,7 @@ optional BuildAutoImplementForFunction(QueryDatabase* db, if (!def1 || !def1->extent) break; - for (Use func_decl : sym_func.declarations) { + for (QueryId::LexicalRef func_decl : sym_func.declarations) { if (func_decl.file == decl_file_id) { int dist = func_decl.range.start.line - decl.range.start.line; if (abs(dist) < abs(best_dist)) { @@ -311,7 +311,7 @@ struct Handler_TextDocumentCodeAction // } // - QueryFileId file_id; + QueryId::File file_id; QueryFile* file; if (!FindFileOrFail(db, project, request->id, request->params.textDocument.uri.GetAbsolutePath(), @@ -339,11 +339,11 @@ struct Handler_TextDocumentCodeAction // them because computing the values could involve an entire project // scan. optional impl_uri; - optional impl_file_id; + optional impl_file_id; - std::vector syms = + std::vector syms = FindSymbolsAtLocation(working_file, file, request->params.range.start); - for (SymbolRef sym : syms) { + for (QueryId::SymbolRef sym : syms) { switch (sym.kind) { case SymbolKind::Type: { QueryType& type = db->GetType(sym); @@ -460,7 +460,7 @@ struct Handler_TextDocumentCodeAction std::string::npos) continue; - Maybe decl_file_id = + optional decl_file_id = GetDeclarationFileForSymbol(db, db->symbols[i]); if (!decl_file_id) continue; diff --git a/src/messages/text_document_code_lens.cc b/src/messages/text_document_code_lens.cc index 400a32241..28465c002 100644 --- a/src/messages/text_document_code_lens.cc +++ b/src/messages/text_document_code_lens.cc @@ -35,32 +35,32 @@ struct CommonCodeLensParams { WorkingFile* working_file; }; -Use OffsetStartColumn(Use use, int16_t offset) { - use.range.start.column += offset; - return use; +QueryId::LexicalRef OffsetStartColumn(QueryId::LexicalRef ref, int16_t offset) { + ref.range.start.column += offset; + return ref; } void AddCodeLens(const char* singular, const char* plural, CommonCodeLensParams* common, - Use use, - const std::vector& uses, + QueryId::LexicalRef ref, + const std::vector& uses, bool force_display) { TCodeLens code_lens; - optional range = GetLsRange(common->working_file, use.range); + optional range = GetLsRange(common->working_file, ref.range); if (!range) return; - if (use.file == QueryFileId()) + if (ref.file == QueryId::File()) return; code_lens.range = *range; code_lens.command = lsCommand(); code_lens.command->command = "cquery.showReferences"; - code_lens.command->arguments.uri = GetLsDocumentUri(common->db, use.file); + code_lens.command->arguments.uri = GetLsDocumentUri(common->db, ref.file); code_lens.command->arguments.position = code_lens.range.start; // Add unique uses. std::unordered_set unique_uses; - for (Use use1 : uses) { + for (QueryId::LexicalRef use1 : uses) { optional location = GetLsLocation(common->db, common->working_files, use1); if (!location) @@ -107,10 +107,11 @@ struct Handler_TextDocumentCodeLens common.working_files = working_files; common.working_file = working_files->GetFileByFilename(file->def->path); - for (SymbolRef sym : file->def->outline) { + for (QueryId::SymbolRef sym : file->def->outline) { // NOTE: We OffsetColumn so that the code lens always show up in a // predictable order. Otherwise, the client may randomize it. - Use use(sym.range, sym.id, sym.kind, sym.role, file->def->file); + QueryId::LexicalRef ref(sym.range, sym.id, sym.kind, sym.role, + file->def->file); switch (sym.kind) { case SymbolKind::Type: { @@ -118,12 +119,12 @@ struct Handler_TextDocumentCodeLens const QueryType::Def* def = type.AnyDef(); if (!def || def->kind == lsSymbolKind::Namespace) continue; - AddCodeLens("ref", "refs", &common, OffsetStartColumn(use, 0), + AddCodeLens("ref", "refs", &common, OffsetStartColumn(ref, 0), type.uses, true /*force_display*/); - AddCodeLens("derived", "derived", &common, OffsetStartColumn(use, 1), + AddCodeLens("derived", "derived", &common, OffsetStartColumn(ref, 1), GetDeclarations(db, type.derived), false /*force_display*/); - AddCodeLens("var", "vars", &common, OffsetStartColumn(use, 2), + AddCodeLens("var", "vars", &common, OffsetStartColumn(ref, 2), GetDeclarations(db, type.instances), false /*force_display*/); break; @@ -139,23 +140,25 @@ struct Handler_TextDocumentCodeLens // For functions, the outline will report a location that is using the // extent since that is better for outline. This tries to convert the // extent location to the spelling location. - auto try_ensure_spelling = [&](Use use) { - Maybe def = GetDefinitionSpell(db, use); - if (!def || def->range.start.line != use.range.start.line) { - return use; + auto try_ensure_spelling = [&](QueryId::LexicalRef ref) { + optional def = GetDefinitionSpell(db, ref); + if (!def || def->range.start.line != ref.range.start.line) { + return ref; } return *def; }; - std::vector base_callers = GetUsesForAllBases(db, func); - std::vector derived_callers = GetUsesForAllDerived(db, func); + std::vector base_callers = + GetRefsForAllBases(db, func); + std::vector derived_callers = + GetRefsForAllDerived(db, func); if (base_callers.empty() && derived_callers.empty()) { - Use loc = try_ensure_spelling(use); + QueryId::LexicalRef loc = try_ensure_spelling(ref); AddCodeLens("call", "calls", &common, OffsetStartColumn(loc, offset++), func.uses, true /*force_display*/); } else { - Use loc = try_ensure_spelling(use); + QueryId::LexicalRef loc = try_ensure_spelling(ref); AddCodeLens("direct call", "direct calls", &common, OffsetStartColumn(loc, offset++), func.uses, false /*force_display*/); @@ -170,12 +173,12 @@ struct Handler_TextDocumentCodeLens } AddCodeLens( - "derived", "derived", &common, OffsetStartColumn(use, offset++), + "derived", "derived", &common, OffsetStartColumn(ref, offset++), GetDeclarations(db, func.derived), false /*force_display*/); // "Base" if (def->bases.size() == 1) { - Maybe base_loc = GetDefinitionSpell( + optional base_loc = GetDefinitionSpell( db, SymbolIdx{def->bases[0], SymbolKind::Func}); if (base_loc) { optional ls_base = @@ -197,7 +200,7 @@ struct Handler_TextDocumentCodeLens } } } else { - AddCodeLens("base", "base", &common, OffsetStartColumn(use, 1), + AddCodeLens("base", "base", &common, OffsetStartColumn(ref, 1), GetDeclarations(db, def->bases), false /*force_display*/); } @@ -216,7 +219,7 @@ struct Handler_TextDocumentCodeLens if (def->kind == lsSymbolKind::Macro) force_display = false; - AddCodeLens("ref", "refs", &common, OffsetStartColumn(use, 0), + AddCodeLens("ref", "refs", &common, OffsetStartColumn(ref, 0), var.uses, force_display); break; } diff --git a/src/messages/text_document_completion.cc b/src/messages/text_document_completion.cc index 6807b2963..7cd8a252b 100644 --- a/src/messages/text_document_completion.cc +++ b/src/messages/text_document_completion.cc @@ -27,7 +27,10 @@ enum class lsCompletionTriggerKind { // the `triggerCharacters` properties of the `CompletionRegistrationOptions`. TriggerCharacter = 2 }; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind); +#pragma clang diagnostic pop // Contains additional information about the context in which a completion // request is triggered. diff --git a/src/messages/text_document_definition.cc b/src/messages/text_document_definition.cc index 2e7c31bed..ad688ea2e 100644 --- a/src/messages/text_document_definition.cc +++ b/src/messages/text_document_definition.cc @@ -17,22 +17,17 @@ struct In_TextDocumentDefinition : public RequestInMessage { MAKE_REFLECT_STRUCT(In_TextDocumentDefinition, id, params); REGISTER_IN_MESSAGE(In_TextDocumentDefinition); -struct Out_TextDocumentDefinition - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentDefinition, jsonrpc, id, result); - -std::vector GetNonDefDeclarationTargets(QueryDatabase* db, SymbolRef sym) { +std::vector GetNonDefDeclarationTargets( + QueryDatabase* db, + QueryId::SymbolRef sym) { switch (sym.kind) { case SymbolKind::Var: { - std::vector ret = GetNonDefDeclarations(db, sym); + std::vector ret = GetNonDefDeclarations(db, sym); // If there is no declaration, jump the its type. if (ret.empty()) { for (auto& def : db->GetVar(sym).def) if (def.type) { - if (Maybe use = GetDefinitionSpell( + if (auto use = GetDefinitionSpell( db, SymbolIdx{*def.type, SymbolKind::Type})) { ret.push_back(*use); break; @@ -50,7 +45,7 @@ struct Handler_TextDocumentDefinition : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } void Run(In_TextDocumentDefinition* request) override { - QueryFileId file_id; + QueryId::File file_id; QueryFile* file; if (!FindFileOrFail(db, project, request->id, request->params.textDocument.uri.GetAbsolutePath(), @@ -61,15 +56,15 @@ struct Handler_TextDocumentDefinition WorkingFile* working_file = working_files->GetFileByFilename(file->def->path); - Out_TextDocumentDefinition out; + Out_LocationList out; out.id = request->id; - Maybe on_def; + Maybe on_def; bool has_symbol = false; int target_line = request->params.position.line; int target_column = request->params.position.character; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { // Found symbol. Return definition. has_symbol = true; @@ -78,10 +73,10 @@ struct Handler_TextDocumentDefinition // - symbol has declaration but no definition (ie, pure virtual) // - start at spelling but end at extent for better mouse tooltip // - goto declaration while in definition of recursive type - std::vector uses; + std::vector uses; EachEntityDef(db, sym, [&](const auto& def) { if (def.spell && def.extent) { - Use spell = *def.spell; + QueryId::LexicalRef spell = *def.spell; // If on a definition, clear |uses| to find declarations below. if (spell.file == file_id && spell.range.Contains(target_line, target_column)) { @@ -105,9 +100,7 @@ struct Handler_TextDocumentDefinition if (uses.empty() && on_def) uses.push_back(*on_def); } - AddRange(&out.result, GetLsLocationExs(db, working_files, uses, - g_config->xref.container, - g_config->xref.maxNum)); + AddRange(&out.result, GetLsLocations(db, working_files, uses)); if (!out.result.empty()) break; } @@ -116,7 +109,7 @@ struct Handler_TextDocumentDefinition if (out.result.empty()) { for (const IndexInclude& include : file->def->includes) { if (include.line == target_line) { - lsLocationEx result; + lsLocation result; result.uri = lsDocumentUri::FromPath(include.resolved_path); out.result.push_back(result); has_symbol = true; @@ -150,9 +143,10 @@ struct Handler_TextDocumentDefinition auto pos = name.rfind(short_query); if (pos == std::string::npos) continue; - if (Maybe use = GetDefinitionSpell(db, db->symbols[i])) { + if (optional use = + GetDefinitionSpell(db, db->symbols[i])) { std::tuple score{ - int(name.size() - short_query.size()), -pos, + int(name.size() - short_query.size()), -int(pos), use->file != file_id, std::abs(use->range.start.line - position.line)}; // Update the score with qualified name if the qualified name @@ -160,7 +154,7 @@ struct Handler_TextDocumentDefinition pos = name.rfind(query); if (pos != std::string::npos) { std::get<0>(score) = int(name.size() - query.size()); - std::get<1>(score) = -pos; + std::get<1>(score) = -int(pos); } if (score < best_score) { best_score = score; @@ -169,10 +163,10 @@ struct Handler_TextDocumentDefinition } } if (best_i != -1) { - Maybe use = GetDefinitionSpell(db, db->symbols[best_i]); + optional use = + GetDefinitionSpell(db, db->symbols[best_i]); assert(use); - if (auto ls_loc = GetLsLocationEx(db, working_files, *use, - g_config->xref.container)) + if (auto ls_loc = GetLsLocation(db, working_files, *use)) out.result.push_back(*ls_loc); } } diff --git a/src/messages/text_document_did_change.cc b/src/messages/text_document_did_change.cc index 3ecabb74f..bfbb2302b 100644 --- a/src/messages/text_document_did_change.cc +++ b/src/messages/text_document_did_change.cc @@ -26,16 +26,12 @@ struct Handler_TextDocumentDidChange AbsolutePath path = request->params.textDocument.uri.GetAbsolutePath(); working_files->OnChange(request->params); if (g_config->enableIndexOnDidChange) { - optional content = ReadContent(path); - if (!content) { - LOG_S(ERROR) << "Unable to read file content after saving " << path; - } else { - Project::Entry entry = project->FindCompilationEntryForFile(path); - QueueManager::instance()->index_request.PushBack( - Index_Request(entry.filename, entry.args, true /*is_interactive*/, - *content, ICacheManager::Make()), - true); - } + WorkingFile* working_file = working_files->GetFileByFilename(path); + Project::Entry entry = project->FindCompilationEntryForFile(path); + QueueManager::instance()->index_request.Enqueue( + Index_Request(entry.filename, entry.args, true /*is_interactive*/, + working_file->buffer_content, MakeIndexCache(g_config->cacheStore)), + true /*priority*/); } clang_complete->NotifyEdit(path); clang_complete->DiagnosticsUpdate(path); diff --git a/src/messages/text_document_did_open.cc b/src/messages/text_document_did_open.cc index 80d72dfb3..5d72843f0 100644 --- a/src/messages/text_document_did_open.cc +++ b/src/messages/text_document_did_open.cc @@ -43,10 +43,11 @@ struct Handler_TextDocumentDidOpen if (ShouldIgnoreFileForIndexing(path)) return; - std::shared_ptr cache_manager = ICacheManager::Make(); + std::shared_ptr cache_manager = + MakeIndexCache(g_config->cacheStore); WorkingFile* working_file = working_files->OnOpen(params.textDocument); optional cached_file_contents = - cache_manager->LoadCachedFileContents(path); + cache_manager->TryLoadContent(path); if (cached_file_contents) working_file->SetIndexContent(*cached_file_contents); @@ -66,11 +67,11 @@ struct Handler_TextDocumentDidOpen // Submit new index request. Project::Entry entry = project->FindCompilationEntryForFile(path); - QueueManager::instance()->index_request.PushBack( + QueueManager::instance()->index_request.Enqueue( Index_Request( entry.filename, params.args.size() ? params.args : entry.args, true /*is_interactive*/, params.textDocument.text, cache_manager), - true /* priority */); + true /*priority*/); clang_complete->FlushSession(entry.filename); LOG_S(INFO) << "Flushed clang complete sessions for " << entry.filename; diff --git a/src/messages/text_document_did_save.cc b/src/messages/text_document_did_save.cc index 4707df711..56490c6d7 100644 --- a/src/messages/text_document_did_save.cc +++ b/src/messages/text_document_did_save.cc @@ -50,16 +50,11 @@ struct Handler_TextDocumentDidSave // if so, ignore that index response. // TODO: send as priority request if (!g_config->enableIndexOnDidChange) { - optional content = ReadContent(path); - if (!content) { - LOG_S(ERROR) << "Unable to read file content after saving " << path; - } else { - Project::Entry entry = project->FindCompilationEntryForFile(path); - QueueManager::instance()->index_request.PushBack( - Index_Request(entry.filename, entry.args, true /*is_interactive*/, - *content, ICacheManager::Make()), - true); - } + Project::Entry entry = project->FindCompilationEntryForFile(path); + QueueManager::instance()->index_request.Enqueue( + Index_Request(entry.filename, entry.args, true /*is_interactive*/, + nullopt, MakeIndexCache(g_config->cacheStore)), + true /*priority*/); } clang_complete->NotifySave(path); diff --git a/src/messages/text_document_document_highlight.cc b/src/messages/text_document_document_highlight.cc index 93b78242e..d8ca24233 100644 --- a/src/messages/text_document_document_highlight.cc +++ b/src/messages/text_document_document_highlight.cc @@ -24,7 +24,7 @@ struct Handler_TextDocumentDocumentHighlight : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } void Run(In_TextDocumentDocumentHighlight* request) override { - QueryFileId file_id; + QueryId::File file_id; QueryFile* file; if (!FindFileOrFail(db, project, request->id, request->params.textDocument.uri.GetAbsolutePath(), @@ -38,23 +38,22 @@ struct Handler_TextDocumentDocumentHighlight Out_TextDocumentDocumentHighlight out; out.id = request->id; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { // Found symbol. Return references to highlight. - EachOccurrence(db, sym, true, [&](Use use) { - if (use.file != file_id) + EachOccurrence(db, sym, true, [&](QueryId::LexicalRef ref) { + if (ref.file != file_id) return; if (optional ls_loc = - GetLsLocation(db, working_files, use)) { + GetLsLocation(db, working_files, ref)) { lsDocumentHighlight highlight; highlight.range = ls_loc->range; - if (use.role & Role::Write) + if (ref.role & Role::Write) highlight.kind = lsDocumentHighlightKind::Write; - else if (use.role & Role::Read) + else if (ref.role & Role::Read) highlight.kind = lsDocumentHighlightKind::Read; else highlight.kind = lsDocumentHighlightKind::Text; - highlight.role = use.role; out.result.push_back(highlight); } }); diff --git a/src/messages/text_document_document_link.cc b/src/messages/text_document_document_link.cc index 23d45368c..755de7eae 100644 --- a/src/messages/text_document_document_link.cc +++ b/src/messages/text_document_document_link.cc @@ -44,7 +44,9 @@ struct Handler_TextDocumentDocumentLink Out_TextDocumentDocumentLink out; out.id = request->id; - if (g_config->showDocumentLinksOnIncludes) { + if (g_config->showDocumentLinksOnIncludes && + !ShouldIgnoreFileForIndexing( + request->params.textDocument.uri.GetAbsolutePath())) { QueryFile* file; if (!FindFileOrFail(db, project, request->id, request->params.textDocument.uri.GetAbsolutePath(), diff --git a/src/messages/text_document_document_symbol.cc b/src/messages/text_document_document_symbol.cc index dd78f8cb1..f01e431b4 100644 --- a/src/messages/text_document_document_symbol.cc +++ b/src/messages/text_document_document_symbol.cc @@ -32,14 +32,14 @@ struct Handler_TextDocumentDocumentSymbol out.id = request->id; QueryFile* file; - QueryFileId file_id; + QueryId::File file_id; if (!FindFileOrFail(db, project, request->id, request->params.textDocument.uri.GetAbsolutePath(), &file, &file_id)) { return; } - for (SymbolRef sym : file->def->outline) { + for (QueryId::SymbolRef sym : file->def->outline) { optional info = GetSymbolInfo(db, working_files, sym, true /*use_short_name*/); if (!info) @@ -56,9 +56,10 @@ struct Handler_TextDocumentDocumentSymbol continue; } - if (optional location = GetLsLocation( - db, working_files, - Use(sym.range, sym.id, sym.kind, sym.role, file_id))) { + if (optional location = + GetLsLocation(db, working_files, + QueryId::LexicalRef(sym.range, sym.id, sym.kind, + sym.role, file_id))) { info->location = *location; out.result.push_back(*info); } diff --git a/src/messages/text_document_hover.cc b/src/messages/text_document_hover.cc index 530fabc63..05a90efa8 100644 --- a/src/messages/text_document_hover.cc +++ b/src/messages/text_document_hover.cc @@ -6,7 +6,8 @@ namespace { MethodType kMethodType = "textDocument/hover"; // Find the comments for |sym|, if any. -optional GetComments(QueryDatabase* db, SymbolRef sym) { +optional GetComments(QueryDatabase* db, + QueryId::SymbolRef sym) { auto make = [](std::string_view comment) -> optional { lsMarkedString result; result.value = std::string(comment.data(), comment.length()); @@ -26,7 +27,7 @@ optional GetComments(QueryDatabase* db, SymbolRef sym) { // Returns the hover or detailed name for `sym`, if any. optional GetHoverOrName(QueryDatabase* db, const std::string& language, - SymbolRef sym) { + QueryId::SymbolRef sym) { auto make = [&](std::string_view comment) { lsMarkedString result; result.language = language; @@ -63,20 +64,10 @@ struct Out_TextDocumentHover : public lsOutMessage { optional result; }; MAKE_REFLECT_STRUCT(Out_TextDocumentHover::Result, contents, range); -void Reflect(Writer& visitor, Out_TextDocumentHover& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(jsonrpc); - REFLECT_MEMBER(id); - if (value.result) - REFLECT_MEMBER(result); - else { - // Empty optional<> is elided by the default serializer, we need to write - // |null| to be compliant with the LSP. - visitor.Key("result"); - visitor.Null(); - } - REFLECT_MEMBER_END(); -} +MAKE_REFLECT_STRUCT_OPTIONALS_MANDATORY(Out_TextDocumentHover, + jsonrpc, + id, + result); struct Handler_TextDocumentHover : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } @@ -94,7 +85,7 @@ struct Handler_TextDocumentHover : BaseMessageHandler { Out_TextDocumentHover out; out.id = request->id; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { // Found symbol. Return hover. optional ls_range = GetLsRange( diff --git a/src/messages/cquery_derived.cc b/src/messages/text_document_implementation.cc similarity index 53% rename from src/messages/cquery_derived.cc rename to src/messages/text_document_implementation.cc index ad50d02f0..d6f88ba96 100644 --- a/src/messages/cquery_derived.cc +++ b/src/messages/text_document_implementation.cc @@ -2,19 +2,23 @@ #include "query_utils.h" #include "queue_manager.h" +#include + namespace { -MethodType kMethodType = "$cquery/derived"; +MethodType kMethodType = "textDocument/implementation"; -struct In_CqueryDerived : public RequestInMessage { +struct In_TextDocumentImplementation : public RequestInMessage { MethodType GetMethodType() const override { return kMethodType; } lsTextDocumentPositionParams params; }; -MAKE_REFLECT_STRUCT(In_CqueryDerived, id, params); -REGISTER_IN_MESSAGE(In_CqueryDerived); +MAKE_REFLECT_STRUCT(In_TextDocumentImplementation, id, params); +REGISTER_IN_MESSAGE(In_TextDocumentImplementation); -struct Handler_CqueryDerived : BaseMessageHandler { +struct Handler_TextDocumentImplementation + : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } - void Run(In_CqueryDerived* request) override { + + void Run(In_TextDocumentImplementation* request) override { QueryFile* file; if (!FindFileOrFail(db, project, request->id, request->params.textDocument.uri.GetAbsolutePath(), @@ -27,24 +31,26 @@ struct Handler_CqueryDerived : BaseMessageHandler { Out_LocationList out; out.id = request->id; - for (SymbolRef sym : + + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { if (sym.kind == SymbolKind::Type) { QueryType& type = db->GetType(sym); - out.result = GetLsLocationExs( - db, working_files, GetDeclarations(db, type.derived), - g_config->xref.container, g_config->xref.maxNum); + out.result = GetLsLocations(db, working_files, + GetDeclarations(db, type.derived)); break; } else if (sym.kind == SymbolKind::Func) { QueryFunc& func = db->GetFunc(sym); - out.result = GetLsLocationExs( - db, working_files, GetDeclarations(db, func.derived), - g_config->xref.container, g_config->xref.maxNum); + out.result = GetLsLocations(db, working_files, + GetDeclarations(db, func.derived)); break; } } + + if (out.result.size() >= g_config->xref.maxNum) + out.result.resize(g_config->xref.maxNum); QueueManager::WriteStdout(kMethodType, out); } }; -REGISTER_MESSAGE_HANDLER(Handler_CqueryDerived); +REGISTER_MESSAGE_HANDLER(Handler_TextDocumentImplementation); } // namespace diff --git a/src/messages/text_document_references.cc b/src/messages/text_document_references.cc index 0e6e2b370..1302c3ecb 100644 --- a/src/messages/text_document_references.cc +++ b/src/messages/text_document_references.cc @@ -33,13 +33,6 @@ MAKE_REFLECT_STRUCT(In_TextDocumentReferences::Params, MAKE_REFLECT_STRUCT(In_TextDocumentReferences, id, params); REGISTER_IN_MESSAGE(In_TextDocumentReferences); -struct Out_TextDocumentReferences - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentReferences, jsonrpc, id, result); - struct Handler_TextDocumentReferences : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } @@ -55,21 +48,18 @@ struct Handler_TextDocumentReferences WorkingFile* working_file = working_files->GetFileByFilename(file->def->path); - Out_TextDocumentReferences out; + Out_LocationList out; out.id = request->id; - bool container = g_config->xref.container; - for (const SymbolRef& sym : + for (const QueryId::SymbolRef& sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { // Found symbol. Return references. EachOccurrenceWithParent( db, sym, request->params.context.includeDeclaration, - [&](Use use, lsSymbolKind parent_kind) { - if (use.role & request->params.context.role) - if (optional ls_loc = - GetLsLocationEx(db, working_files, use, container)) { - if (container) - ls_loc->parentKind = parent_kind; + [&](QueryId::LexicalRef ref, lsSymbolKind parent_kind) { + if (ref.role & request->params.context.role) + if (optional ls_loc = + GetLsLocation(db, working_files, ref)) { out.result.push_back(*ls_loc); } }); @@ -85,7 +75,7 @@ struct Handler_TextDocumentReferences for (const IndexInclude& include1 : file1.def->includes) if (include1.resolved_path == include.resolved_path) { // Another file |file1| has the same include line. - lsLocationEx result; + lsLocation result; result.uri = lsDocumentUri::FromPath(file1.def->path); result.range.start.line = result.range.end.line = include1.line; diff --git a/src/messages/text_document_rename.cc b/src/messages/text_document_rename.cc index de0bca2df..ad577e363 100644 --- a/src/messages/text_document_rename.cc +++ b/src/messages/text_document_rename.cc @@ -7,16 +7,16 @@ MethodType kMethodType = "textDocument/rename"; lsWorkspaceEdit BuildWorkspaceEdit(QueryDatabase* db, WorkingFiles* working_files, - SymbolRef sym, + QueryId::SymbolRef sym, const std::string& new_text) { - std::unordered_map path_to_edit; + std::unordered_map path_to_edit; - EachOccurrence(db, sym, true, [&](Use use) { - optional ls_location = GetLsLocation(db, working_files, use); + EachOccurrence(db, sym, true, [&](QueryId::LexicalRef ref) { + optional ls_location = GetLsLocation(db, working_files, ref); if (!ls_location) return; - QueryFileId file_id = use.file; + QueryId::File file_id = ref.file; if (path_to_edit.find(file_id) == path_to_edit.end()) { path_to_edit[file_id] = lsTextDocumentEdit(); @@ -80,7 +80,7 @@ MAKE_REFLECT_STRUCT(Out_TextDocumentRename, jsonrpc, id, result); struct Handler_TextDocumentRename : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } void Run(In_TextDocumentRename* request) override { - QueryFileId file_id; + QueryId::File file_id; QueryFile* file; if (!FindFileOrFail(db, project, request->id, request->params.textDocument.uri.GetAbsolutePath(), @@ -94,7 +94,7 @@ struct Handler_TextDocumentRename : BaseMessageHandler { Out_TextDocumentRename out; out.id = request->id; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { // Found symbol. Return references to rename. out.result = diff --git a/src/messages/text_document_type_definition.cc b/src/messages/text_document_type_definition.cc index 6ccb1792e..453880aea 100644 --- a/src/messages/text_document_type_definition.cc +++ b/src/messages/text_document_type_definition.cc @@ -12,13 +12,6 @@ struct In_TextDocumentTypeDefinition : public RequestInMessage { MAKE_REFLECT_STRUCT(In_TextDocumentTypeDefinition, id, params); REGISTER_IN_MESSAGE(In_TextDocumentTypeDefinition); -struct Out_TextDocumentTypeDefinition - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentTypeDefinition, jsonrpc, id, result); - struct Handler_TextDocumentTypeDefinition : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } @@ -32,11 +25,11 @@ struct Handler_TextDocumentTypeDefinition WorkingFile* working_file = working_files->GetFileByFilename(file->def->path); - Out_TextDocumentTypeDefinition out; + Out_LocationList out; out.id = request->id; - for (SymbolRef sym : + for (QueryId::SymbolRef sym : FindSymbolsAtLocation(working_file, file, request->params.position)) { - Id id = sym.id; + AnyId id = sym.id; switch (sym.kind) { case SymbolKind::Var: { const QueryVar::Def* def = db->GetVar(sym).AnyDef(); @@ -49,8 +42,7 @@ struct Handler_TextDocumentTypeDefinition QueryType& type = db->types[id.id]; for (const auto& def : type.def) if (def.spell) { - if (auto ls_loc = GetLsLocationEx(db, working_files, *def.spell, - g_config->xref.container)) + if (auto ls_loc = GetLsLocation(db, working_files, *def.spell)) out.result.push_back(*ls_loc); } break; diff --git a/src/messages/workspace_did_change_watched_files.cc b/src/messages/workspace_did_change_watched_files.cc index 19c3bf81e..ae5c20556 100644 --- a/src/messages/workspace_did_change_watched_files.cc +++ b/src/messages/workspace_did_change_watched_files.cc @@ -15,7 +15,10 @@ enum class lsFileChangeType { Changed = 2, Deleted = 3, }; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" MAKE_REFLECT_TYPE_PROXY(lsFileChangeType); +#pragma clang diagnostic pop struct lsFileEvent { lsDocumentUri uri; @@ -50,22 +53,19 @@ struct Handler_WorkspaceDidChangeWatchedFiles switch (event.type) { case lsFileChangeType::Created: case lsFileChangeType::Changed: { - optional content = ReadContent(path); - if (!content) - LOG_S(ERROR) << "Unable to read file content after saving " << path; - else { - QueueManager::instance()->index_request.PushBack( - Index_Request(path, entry.args, is_interactive, *content, - ICacheManager::Make())); - if (is_interactive) - clang_complete->NotifySave(path); - } + QueueManager::instance()->index_request.Enqueue( + Index_Request(path, entry.args, is_interactive, nullopt, + MakeIndexCache(g_config->cacheStore)), + false /*priority*/); + if (is_interactive) + clang_complete->NotifySave(path); break; } case lsFileChangeType::Deleted: - QueueManager::instance()->index_request.PushBack( + QueueManager::instance()->index_request.Enqueue( Index_Request(path, entry.args, is_interactive, std::string(), - ICacheManager::Make())); + MakeIndexCache(g_config->cacheStore)), + false /*priority*/); break; } } diff --git a/src/messages/workspace_execute_command.cc b/src/messages/workspace_execute_command.cc index 2bc9432f9..032323641 100644 --- a/src/messages/workspace_execute_command.cc +++ b/src/messages/workspace_execute_command.cc @@ -20,14 +20,6 @@ struct Out_WorkspaceExecuteCommand }; MAKE_REFLECT_STRUCT(Out_WorkspaceExecuteCommand, jsonrpc, id, result); -void Reflect(Writer& visitor, Out_WorkspaceExecuteCommand& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(jsonrpc); - REFLECT_MEMBER(id); - REFLECT_MEMBER(result); - REFLECT_MEMBER_END(); -} - struct Handler_WorkspaceExecuteCommand : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } diff --git a/src/messages/workspace_symbol.cc b/src/messages/workspace_symbol.cc index 3f69ae207..f9d6c7423 100644 --- a/src/messages/workspace_symbol.cc +++ b/src/messages/workspace_symbol.cc @@ -24,8 +24,8 @@ bool InsertSymbolIntoResult(QueryDatabase* db, if (!info) return false; - Maybe location = GetDefinitionExtent(db, symbol); - Use loc; + optional location = GetDefinitionExtent(db, symbol); + QueryId::LexicalRef loc; if (location) loc = *location; else { diff --git a/src/method.cc b/src/method.cc index adcc9d786..c641ebdd0 100644 --- a/src/method.cc +++ b/src/method.cc @@ -1,7 +1,7 @@ #include "method.h" -#include #include +#include #include "serializers/json.h" MethodType kMethodType_Unknown = "$unknown"; @@ -10,6 +10,7 @@ MethodType kMethodType_TextDocumentPublishDiagnostics = "textDocument/publishDiagnostics"; MethodType kMethodType_CqueryPublishInactiveRegions = "$cquery/publishInactiveRegions"; +MethodType kMethodType_CqueryQueryDbStatus = "$cquery/queryDbStatus"; MethodType kMethodType_CqueryPublishSemanticHighlighting = "$cquery/publishSemanticHighlighting"; @@ -19,7 +20,8 @@ void Reflect(Reader& visitor, lsRequestId& value) { value.value = visitor.GetInt(); } else if (visitor.IsInt64()) { value.type = lsRequestId::kInt; - value.value = visitor.GetInt64(); + // `lsRequestId.value` is an `int`, so we're forced to truncate. + value.value = static_cast(visitor.GetInt64()); } else if (visitor.IsString()) { value.type = lsRequestId::kString; std::string s = visitor.GetString(); diff --git a/src/method.h b/src/method.h index 6a7229f13..74d64af69 100644 --- a/src/method.h +++ b/src/method.h @@ -10,6 +10,7 @@ extern MethodType kMethodType_Unknown; extern MethodType kMethodType_Exit; extern MethodType kMethodType_TextDocumentPublishDiagnostics; extern MethodType kMethodType_CqueryPublishInactiveRegions; +extern MethodType kMethodType_CqueryQueryDbStatus; extern MethodType kMethodType_CqueryPublishSemanticHighlighting; struct lsRequestId { diff --git a/src/nt_string.h b/src/nt_string.h deleted file mode 100644 index 4083535ef..000000000 --- a/src/nt_string.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -// Nullable null-terminated string, which is null if default constructed, -// but non-null if assigned. -// This is used in Query{Func,Type,Var}::def to reduce memory footprint. -class NtString { - using size_type = std::string::size_type; - std::unique_ptr str; - - public: - NtString() = default; - NtString(NtString&& o) = default; - NtString(const NtString& o) { *this = o; } - NtString(std::string_view sv) { *this = sv; } - - const char* c_str() const { return str.get(); } - operator std::string_view() const { - if (c_str()) - return c_str(); - return {}; - } - bool empty() const { return !str || *c_str() == '\0'; } - - void operator=(std::string_view sv) { - str = std::unique_ptr(new char[sv.size() + 1]); - memcpy(str.get(), sv.data(), sv.size()); - str.get()[sv.size()] = '\0'; - } - void operator=(const NtString& o) { - *this = static_cast(o); - } - bool operator==(const NtString& o) const { - return str && o.str ? strcmp(c_str(), o.c_str()) == 0 - : c_str() == o.c_str(); - } -}; diff --git a/src/performance.h b/src/performance.h deleted file mode 100644 index 56c348803..000000000 --- a/src/performance.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "serializer.h" - -#include - -// Contains timing information for the entire pipeline for importing a file -// into the querydb. -struct PerformanceImportFile { - // All units are in microseconds. - - // [indexer] clang parsing the file - uint64_t index_parse = 0; - // [indexer] build the IndexFile object from clang parse - uint64_t index_build = 0; - // [querydb] create IdMap object from IndexFile - uint64_t querydb_id_map = 0; - // [indexer] save the IndexFile to disk - uint64_t index_save_to_disk = 0; - // [indexer] loading previously cached index - uint64_t index_load_cached = 0; - // [indexer] create delta IndexUpdate object - uint64_t index_make_delta = 0; - // [querydb] update WorkingFile indexed file state - // uint64_t querydb_update_working_file = 0; - // [querydb] apply IndexUpdate - // uint64_t querydb_apply_index_update = 0; -}; -MAKE_REFLECT_STRUCT(PerformanceImportFile, - index_parse, - index_build, - querydb_id_map, - index_save_to_disk, - index_load_cached, - index_make_delta); \ No newline at end of file diff --git a/src/platform.h b/src/platform.h index 1b2a08b0a..842f61ad4 100644 --- a/src/platform.h +++ b/src/platform.h @@ -27,7 +27,8 @@ void PlatformInit(); AbsolutePath GetExecutablePath(); AbsolutePath GetWorkingDirectory(); optional NormalizePath(const std::string& path, - bool ensure_exists = true); + bool ensure_exists = true, + bool force_lower_on_windows = true); // Creates a directory at |path|. Creates directories recursively if needed. void MakeDirectoryRecursive(const AbsolutePath& path); @@ -59,3 +60,5 @@ void TraceMe(); optional RunExecutable(const std::vector& command, std::string_view input); + +optional GetGlobalConfigDirectory(); diff --git a/src/platform_posix.cc b/src/platform_posix.cc index d3cb98082..4615e3f22 100644 --- a/src/platform_posix.cc +++ b/src/platform_posix.cc @@ -70,7 +70,7 @@ optional RealPathNotExpandSymlink(std::string path, resolved = "/"; i = 1; } else { - if (ensure_exists && !getcwd(tmp, sizeof tmp)) + if (!getcwd(tmp, sizeof tmp) && ensure_exists) return nullopt; resolved = tmp; } @@ -96,9 +96,9 @@ optional RealPathNotExpandSymlink(std::string path, // Here we differ from realpath(3), we use stat(2) instead of // lstat(2) because we do not want to resolve symlinks. resolved += next_token; - if (ensure_exists && stat(resolved.c_str(), &sb) != 0) + if (stat(resolved.c_str(), &sb) != 0 && ensure_exists) return nullopt; - if (ensure_exists && !S_ISDIR(sb.st_mode) && j < path.size()) { + if (!S_ISDIR(sb.st_mode) && j < path.size() && ensure_exists) { errno = ENOTDIR; return nullopt; } @@ -114,14 +114,14 @@ optional RealPathNotExpandSymlink(std::string path, void PlatformInit() {} -#ifdef __APPLE__ +#if defined(__APPLE__) extern "C" int _NSGetExecutablePath(char* buf, uint32_t* bufsize); #endif // See // https://stackoverflow.com/questions/143174/how-do-i-get-the-directory-that-a-program-is-running-from AbsolutePath GetExecutablePath() { -#ifdef __APPLE__ +#if defined(__APPLE__) uint32_t size = 0; _NSGetExecutablePath(nullptr, &size); char* buffer = new char[size]; @@ -161,7 +161,8 @@ AbsolutePath GetWorkingDirectory() { } optional NormalizePath(const std::string& path, - bool ensure_exists) { + bool ensure_exists, + bool force_lower_on_windows) { return RealPathNotExpandSymlink(path, ensure_exists); } @@ -305,6 +306,13 @@ optional RunExecutable(const std::vector& command, return nullopt; } + // Build argv. We must do this before the call to fork() per + // https://github.com/cquery-project/cquery/issues/693. + auto argv = new char*[command.size() + 1]; + for (size_t i = 0; i < command.size(); i++) + argv[i] = const_cast(command[i].c_str()); + argv[command.size()] = nullptr; + pid_t child = fork(); // fork returns 0 for the child, non-zero for the parent process. if (child == 0) { @@ -322,16 +330,16 @@ optional RunExecutable(const std::vector& command, close(pipe_stdout[kPipeRead]); close(pipe_stdout[kPipeWrite]); - // Build argv - auto argv = new char*[command.size() + 1]; - for (size_t i = 0; i < command.size(); i++) - argv[i] = const_cast(command[i].c_str()); - argv[command.size()] = nullptr; - int exec_result = execvp(argv[0], argv); - exit(exec_result); // Should not be possible. + // Happens if execvp fails, ie, if the target process cannot be found. Call + // _exit instead of exit because exit will run static dtors and the like. + // See https://github.com/cquery-project/cquery/issues/693. + _exit(exec_result); } + // argv is not needed by the cquery process. + delete[] argv; + // The parent cannot read from stdin and can not write to stdout. close(pipe_stdin[kPipeRead]); close(pipe_stdout[kPipeWrite]); @@ -360,4 +368,29 @@ optional RunExecutable(const std::vector& command, return result; } +optional GetGlobalConfigDirectory() { + char const* xdg_config_home = std::getenv("XDG_CONFIG_HOME"); + char const* home = std::getenv("HOME"); + optional config; + + // If it exists use XDG_CONFIG_HOME to comply with the XDG base + // directory spec. Otherwise, use HOME if available. + if (xdg_config_home) { + config = std::string(xdg_config_home); + EnsureEndsInSlash(*config); + *config += "cquery"; + if (!FileExists(*config)) { + MakeDirectoryRecursive(AbsolutePath(*config, false)); + } + } else if (home) { + *config = home; + } + + if (config) { + EnsureEndsInSlash(*config); + } + + return config; +} + #endif diff --git a/src/platform_win.cc b/src/platform_win.cc index a972cb1e0..efdb5f26c 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -9,6 +9,9 @@ #include #include #include +#include +#include +#include #include #include @@ -17,6 +20,8 @@ #include #include #include +#include +#include namespace { void EmitError(const std::string& message) { @@ -56,7 +61,8 @@ AbsolutePath GetWorkingDirectory() { } optional NormalizePath(const std::string& path0, - bool ensure_exists) { + bool ensure_exists, + bool force_lower_on_windows) { // Requires Windows 8 /* if (!PathCanonicalize(buffer, path.c_str())) @@ -93,14 +99,19 @@ optional NormalizePath(const std::string& path0, if (path.empty()) return nullopt; + if (path.size() && path[0] > 0) + path[0] = tolower(path[0]); + // We may need to normalize the drive name to upper-case; at the moment // vscode sends lower-case path names. /* path[0] = toupper(path[0]); */ // Make the path all lower-case, since windows is case-insensitive. - for (size_t i = 0; i < path.size(); ++i) - path[i] = (char)tolower(path[i]); + if (force_lower_on_windows) { + for (size_t i = 0; i < path.size(); ++i) + path[i] = (char)tolower(path[i]); + } // cquery assumes forward-slashes. std::replace(path.begin(), path.end(), '\\', '/'); @@ -138,7 +149,7 @@ void SetCurrentThreadName(const std::string& thread_name) { __try { RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info); -#ifdef _MSC_VER +#if defined(_MSC_VER) } __except (EXCEPTION_EXECUTE_HANDLER) { #else } catch (...) { @@ -319,7 +330,7 @@ optional RunExecutable(const std::vector& command, if (!success || bytes_read == 0) break; - for (int i = 0; i < bytes_read; ++i) + for (unsigned long i = 0; i < bytes_read; ++i) output += buffer[i]; } }; @@ -348,4 +359,27 @@ optional RunExecutable(const std::vector& command, return output; } +optional GetGlobalConfigDirectory() { + wchar_t *roaming_path = NULL; + optional cfg_path = {}; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, + NULL, &roaming_path))) { + std::wstringstream roaming_stream; + roaming_stream << roaming_path << L"/cquery"; + + // Convert the roaming path string to a normal string so it + // is analogous with the string returned by the posix version. + using convert_type = std::codecvt_utf8; + std::wstring_convert converter; + cfg_path = converter.to_bytes(roaming_stream.str()); + + // As per the docs for SHGetKnownFolderPath + // (https://msdn.microsoft.com/en-us/library/bb762188(VS.85).aspx) + // we must free roaming_path using CoTaskMemFree once + // finished with it. + CoTaskMemFree(static_cast(roaming_path)); + } + return cfg_path; +} + #endif diff --git a/src/port.cc b/src/port.cc deleted file mode 100644 index 3359cc2f8..000000000 --- a/src/port.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include "port.h" - -#include -#include - -void cquery_unreachable_internal(const char* file, int line) { - fprintf(stderr, "unreachable %s:%d\n", file, line); - CQUERY_BUILTIN_UNREACHABLE; - abort(); -} diff --git a/src/port.h b/src/port.h deleted file mode 100644 index 0e333fbb2..000000000 --- a/src/port.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#ifndef __has_builtin -#define __has_builtin(x) 0 -#endif - -#if defined(__GNUC__) -#define ATTRIBUTE_UNUSED __attribute__((unused)) -#else -#define ATTRIBUTE_UNUSED -#endif - -// TODO GCC -#if __has_builtin(__builtin_unreachable) -#define CQUERY_BUILTIN_UNREACHABLE __builtin_unreachable() -#elif defined(_MSC_VER) -#define CQUERY_BUILTIN_UNREACHABLE __assume(false) -#else -#define CQUERY_BUILTIN_UNREACHABLE -#endif - -void cquery_unreachable_internal(const char* file, int line); -#ifndef NDEBUG -#define CQUERY_UNREACHABLE() cquery_unreachable_internal(__FILE__, __LINE__) -#else -#define CQUERY_UNREACHABLE() -#endif diff --git a/src/project.cc b/src/project.cc index 0b95ade08..fafb5655a 100644 --- a/src/project.cc +++ b/src/project.cc @@ -1,8 +1,10 @@ #include "project.h" +#include "c_cpp_properties.h" #include "cache_manager.h" #include "clang_system_include_extractor.h" #include "clang_utils.h" +#include "compiler.h" #include "language.h" #include "match.h" #include "platform.h" @@ -21,6 +23,7 @@ #include #endif +#include #include #include #include @@ -69,7 +72,7 @@ struct NormalizationCache { } }; -enum class ProjectMode { CompileCommandsJson, DotCquery, ExternalCommand }; +enum class ProjectMode { CCppProperties, CompileCommandsJson, DotCquery, ExternalCommand }; struct ProjectConfig { std::unordered_map> @@ -120,7 +123,7 @@ const std::vector& GetSystemIncludes( // Capture these flags in |extra_flags|, since they may change system include // directories. static std::vector kFlagsToPass = { - "--gcc-toolchain", "--sysroot", "-isysroot", "-stdlib"}; + "--gcc-toolchain", "--sysroot", "-isysroot", "-stdlib", "--target"}; static std::vector kStandaloneFlagsToPass = { "-nostdinc", "-nostdinc++", "-nobuiltininc"}; std::vector extra_flags; @@ -146,7 +149,8 @@ const std::vector& GetSystemIncludes( } std::vector compiler_drivers = { - GetExecutablePathNextToCqueryBinary("cquery-clang"), "clang++", "g++"}; + GetExecutablePathNextToCqueryBinary("cquery-clang").path, "clang++", + "g++"}; if (IsAbsolutePath(compiler_driver)) { compiler_drivers.insert(compiler_drivers.begin(), compiler_driver); } @@ -173,8 +177,18 @@ std::vector kBlacklistMulti = { "-MF", "-MT", "-MQ", "-o", "--serialize-diagnostics", "-Xclang"}; // Blacklisted flags which are always removed from the command line. -std::vector kBlacklist = { - "-c", "-MP", "-MD", "-MMD", "--fcolor-diagnostics", "-showIncludes"}; +std::vector kBlacklist = {"-c", + "-MP", + "-MD", + "-MMD", + "--fcolor-diagnostics", + "-showIncludes", + "/permissive", + "-march=", + "-Wl,-arch", + /* These are MSVC PCH flags: */ "/Fp", + "/Yc", + "/Yu"}; // Arguments which are followed by a potentially relative path. We need to make // all relative paths absolute, otherwise libclang will not resolve them. @@ -274,7 +288,7 @@ Project::Entry GetCompilationEntryFromCompileCommandEntry( // iterates in reverse to find the last one as soon as possible // in case of multiple --driver-mode flags. for (int i = args.size() - 1; i >= 0; --i) { - if (strstr(args[i].c_str(), "--dirver-mode=")) { + if (strstr(args[i].c_str(), "--driver-mode=")) { clang_cl = clang_cl || strstr(args[i].c_str(), "--driver-mode=cl"); break; } @@ -308,12 +322,12 @@ Project::Entry GetCompilationEntryFromCompileCommandEntry( // Compiler driver. If it looks like a path normalize it. std::string compiler_driver = args[i - 1]; if (FindAnyPartial(compiler_driver, {"/", ".."})) - compiler_driver = cleanup_maybe_relative_path(compiler_driver); + compiler_driver = cleanup_maybe_relative_path(compiler_driver).path; result.args.push_back(compiler_driver); // Add -working-directory if not provided. - if (!AnyStartsWith(args, "-working-directory")) - result.args.emplace_back("-working-directory=" + entry.directory); + if (!clang_cl && !AnyStartsWith(args, "-working-directory")) + result.args.push_back("-working-directory=" + entry.directory); if (!gTestOutputMode) { std::vector platform = GetPlatformClangArguments(); @@ -387,7 +401,7 @@ Project::Entry GetCompilationEntryFromCompileCommandEntry( // slow. See // https://github.com/cquery-project/cquery/commit/af63df09d57d765ce12d40007bf56302a0446678. if (EndsWith(arg, base_name)) - arg = cleanup_maybe_relative_path(arg); + arg = cleanup_maybe_relative_path(arg).path; // TODO Exclude .a .o to make link command in compile_commands.json work. // Also, clang_parseTranslationUnit2FullArgv does not seem to accept // multiple source filenames. @@ -404,17 +418,20 @@ Project::Entry GetCompilationEntryFromCompileCommandEntry( // Add -resource-dir so clang can correctly resolve system includes like // - if (!AnyStartsWith(result.args, "-resource-dir")) + if (!clang_cl && !AnyStartsWith(result.args, "-resource-dir") && + !config->resource_dir.empty()) { result.args.push_back("-resource-dir=" + config->resource_dir); + } // There could be a clang version mismatch between what the project uses and - // what cquery uses. Make sure we do not emit warnings for mismatched options. - if (!AnyStartsWith(result.args, "-Wno-unknown-warning-option")) + // what cquery uses. Make sure we do not emit warnings for mismatched + // options. + if (!clang_cl && !AnyStartsWith(result.args, "-Wno-unknown-warning-option")) result.args.push_back("-Wno-unknown-warning-option"); // Using -fparse-all-comments enables documentation in the indexer and in // code completion. - if (g_config->index.comments > 1 && + if (!clang_cl && g_config->index.comments > 1 && !AnyStartsWith(result.args, "-fparse-all-comments")) { result.args.push_back("-fparse-all-comments"); } @@ -439,36 +456,71 @@ std::vector ReadCompilerArgumentsFromFile( return args; } -std::vector LoadFromDirectoryListing(ProjectConfig* config) { +std::vector LoadFromDirectoryListing( + ProjectConfig* config, + bool use_global_config = false) { std::vector result; config->mode = ProjectMode::DotCquery; std::unordered_map> folder_args; std::vector files; - GetFilesInFolder(config->project_dir, true /*recursive*/, - true /*add_folder_to_path*/, - [&folder_args, &files](const std::string& path) { - if (SourceFileLanguage(path) != LanguageId::Unknown) { - files.push_back(path); - } else if (GetBaseName(path) == ".cquery") { - LOG_S(INFO) << "Using .cquery arguments from " << path; - folder_args.emplace(GetDirName(path), - ReadCompilerArgumentsFromFile(path)); - } - }); - - LOG_IF_S(WARNING, folder_args.empty() && config->extra_flags.empty()) - << "cquery has no clang arguments. Considering adding either a " - "compile_commands.json or .cquery file. See the cquery README for " - "more information."; - - const auto& project_dir_args = folder_args[config->project_dir]; - LOG_IF_S(INFO, !project_dir_args.empty()) - << "Using .cquery arguments " << StringJoin(project_dir_args); + optional c_cpp_props; + std::string c_cpp_props_path = + config->project_dir + ".vscode/c_cpp_properties.json"; + if (FileExists(c_cpp_props_path)) { + LOG_S(INFO) << "Trying to load c_cpp_properties.json"; + c_cpp_props = LoadCCppProperties(c_cpp_props_path, config->project_dir); + if (c_cpp_props) { + LOG_S(INFO) << "Loaded c_cpp_properties.json as args: " << StringJoin(c_cpp_props.value().args); + } else { + LOG_S(WARNING) << "Failed to load c_cpp_properties.json"; + } + } + + GetFilesAndDirectoriesInFolder( + config->project_dir, true /*recursive*/, true /*add_folder_to_path*/, + [&folder_args, &files](const std::string& path) { + if (SourceFileLanguage(path) != LanguageId::Unknown) { + files.push_back(path); + } else if (GetBaseName(path) == ".cquery" && !IsDirectory(GetDirName(path) + ".cquery")) { + LOG_S(INFO) << "Using .cquery arguments from " << path; + folder_args.emplace(GetDirName(path), + ReadCompilerArgumentsFromFile(path)); + } + }); + + if (use_global_config) { + optional maybe_cfg = GetGlobalConfigDirectory(); + if (folder_args.empty() && maybe_cfg) { + std::string cfg = *maybe_cfg + ".cquery"; + if (cfg.size() && FileExists(cfg)) { + LOG_S(INFO) << "Using .cquery arguments from " << cfg; + folder_args.emplace(config->project_dir, + ReadCompilerArgumentsFromFile(cfg)); + } + } + } + + LOG_IF_S(WARNING, folder_args.empty() && config->extra_flags.empty() + && !c_cpp_props) + << "cquery has no clang arguments. Considering adding a " + ".cquery file or c_cpp_properties.json or compile_commands.json. " + "See the cquery README for more information."; + + auto& project_dir_args = folder_args[config->project_dir]; + if (project_dir_args.empty() && c_cpp_props) { + config->mode = ProjectMode::CCppProperties; + project_dir_args = c_cpp_props.value().args; + LOG_S(INFO) << "Using c_cpp_properties.json"; + } else if(!project_dir_args.empty()) { + LOG_S(INFO) << "Using .cquery arguments " << StringJoin(project_dir_args); + } auto GetCompilerArgumentForFile = [&config, - &folder_args](const std::string& path) { + &folder_args, + &project_dir_args + ](const std::string& path) { for (std::string cur = GetDirName(path);; cur = GetDirName(cur)) { auto it = folder_args.find(cur); if (it != folder_args.end()) @@ -482,7 +534,7 @@ std::vector LoadFromDirectoryListing(ProjectConfig* config) { config->project_dir) != 0) break; } - return folder_args[config->project_dir]; + return project_dir_args; }; for (const std::string& file : files) { @@ -503,17 +555,20 @@ std::vector LoadCompilationEntriesFromDirectory( ProjectConfig* project, const std::string& opt_compilation_db_dir) { // If there is a .cquery file always load using directory listing. - if (FileExists(project->project_dir + ".cquery")) + // The .cquery file can be in the project or home dir but the project + // dir takes precedence. + if (FileExists(project->project_dir + ".cquery")) { return LoadFromDirectoryListing(project); + } // If |compilationDatabaseCommand| is specified, execute it to get the compdb. - std::string comp_db_dir; + std::string comp_db_dir(opt_compilation_db_dir); if (g_config->compilationDatabaseCommand.empty()) { project->mode = ProjectMode::CompileCommandsJson; // Try to load compile_commands.json, but fallback to a project listing. } else { project->mode = ProjectMode::ExternalCommand; -#ifdef _WIN32 +#if defined(_WIN32) // TODO #else char tmpdir[] = "/tmp/cquery-compdb-XXXXXX"; @@ -537,8 +592,6 @@ std::vector LoadCompilationEntriesFromDirectory( CXCompilationDatabase_CanNotLoadDatabase; CXCompilationDatabase cx_db = nullptr; - comp_db_dir = opt_compilation_db_dir; - if (!IsAbsolutePath(comp_db_dir)) { comp_db_dir = project->normalization_cache.Get(project->project_dir + comp_db_dir); @@ -560,8 +613,9 @@ std::vector LoadCompilationEntriesFromDirectory( project->project_dir.c_str(), &cx_db_load_error); } } + if (!g_config->compilationDatabaseCommand.empty()) { -#ifdef _WIN32 +#if defined(_WIN32) // TODO #else unlink((comp_db_dir + "compile_commands.json").c_str()); @@ -572,7 +626,7 @@ std::vector LoadCompilationEntriesFromDirectory( if (cx_db_load_error != CXCompilationDatabase_NoError) { LOG_S(INFO) << "Unable to load compile_commands.json located at \"" << comp_db_dir << "\"; using directory listing instead."; - return LoadFromDirectoryListing(project); + return LoadFromDirectoryListing(project, true); } Timer clang_time; @@ -779,17 +833,12 @@ void Project::Index(QueueManager* queue, WorkingFiles* working_files, lsRequestId id) { ForAllFilteredFiles([&](int i, const Project::Entry& entry) { - optional content = ReadContent(entry.filename); - if (!content) { - LOG_S(ERROR) << "When loading project, canont read file " - << entry.filename; - return; - } bool is_interactive = working_files->GetFileByFilename(entry.filename) != nullptr; - queue->index_request.PushBack(Index_Request(entry.filename, entry.args, - is_interactive, *content, - ICacheManager::Make(), id)); + queue->index_request.Enqueue( + Index_Request(entry.filename, entry.args, is_interactive, nullopt, + MakeIndexCache(g_config->cacheStore), id), + false /*priority*/); }); } @@ -862,7 +911,8 @@ TEST_SUITE("Project") { } TEST_CASE("Windows path normalization") { - CheckFlags("E:/workdir", "E:/workdir/bar.cc", /* raw */ {"clang", "bar.cc"}, + CheckFlags("E:/workdir", "E:/workdir/bar.cc", + /* raw */ {"clang", "bar.cc"}, /* expected */ {"clang", "-working-directory=E:/workdir", "&E:/workdir/bar.cc", "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", @@ -878,19 +928,14 @@ TEST_SUITE("Project") { CheckFlags("E:/workdir", "E:/workdir/bar.cc", /* raw */ {"clang-cl.exe", "/I./test", "E:/workdir/bar.cc"}, /* expected */ - {"clang-cl.exe", "-working-directory=E:/workdir", - "/I&E:/workdir/./test", "&E:/workdir/bar.cc", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); + {"clang-cl.exe", "/I&E:/workdir/./test", "&E:/workdir/bar.cc"}); CheckFlags("E:/workdir", "E:/workdir/bar.cc", /* raw */ {"cl.exe", "/I../third_party/test/include", "E:/workdir/bar.cc"}, /* expected */ - {"cl.exe", "-working-directory=E:/workdir", - "/I&E:/workdir/../third_party/test/include", - "&E:/workdir/bar.cc", "-resource-dir=/w/resource_dir/", - "-Wno-unknown-warning-option", "-fparse-all-comments"}); + {"cl.exe", "/I&E:/workdir/../third_party/test/include", + "&E:/workdir/bar.cc"}); } TEST_CASE("Path in args") { @@ -1033,7 +1078,6 @@ TEST_SUITE("Project") { "-fcolor-diagnostics", "-no-canonical-prefixes", "-m64", - "-march=x86-64", "-Wall", "-Werror", "-Wextra", @@ -1211,7 +1255,6 @@ TEST_SUITE("Project") { "-fcolor-diagnostics", "-no-canonical-prefixes", "-m64", - "-march=x86-64", "-Wall", "-Werror", "-Wextra", @@ -1373,7 +1416,6 @@ TEST_SUITE("Project") { "-pthread", "-fcolor-diagnostics", "-m64", - "-march=x86-64", "-Wall", "-Werror", "-Wextra", @@ -1537,7 +1579,6 @@ TEST_SUITE("Project") { "-pthread", "-fcolor-diagnostics", "-m64", - "-march=x86-64", "-Wall", "-Werror", "-Wextra", diff --git a/src/query.cc b/src/query.cc index 2c12077d9..1d52460ab 100644 --- a/src/query.cc +++ b/src/query.cc @@ -15,6 +15,8 @@ #include #include +#include "platform.h" + // TODO: Make all copy constructors explicit. namespace { @@ -241,31 +243,32 @@ QueryFile::DefUpdate BuildFileDefUpdate(const IdMap& id_map, } }(); - auto add_all_symbols = [&](Use use, Id id, SymbolKind kind) { - def.all_symbols.push_back(SymbolRef(use.range, id, kind, use.role)); + auto add_all_symbols = [&](Reference ref, AnyId id, SymbolKind kind) { + def.all_symbols.push_back( + QueryId::SymbolRef(ref.range, id, kind, ref.role)); }; - auto add_outline = [&](Use use, Id id, SymbolKind kind) { - def.outline.push_back(SymbolRef(use.range, id, kind, use.role)); + auto add_outline = [&](Reference ref, AnyId id, SymbolKind kind) { + def.outline.push_back(QueryId::SymbolRef(ref.range, id, kind, ref.role)); }; for (const IndexType& type : indexed.types) { - QueryTypeId id = id_map.ToQuery(type.id); + QueryId::Type id = id_map.ToQuery(type.id); if (type.def.spell) add_all_symbols(*type.def.spell, id, SymbolKind::Type); if (type.def.extent) add_outline(*type.def.extent, id, SymbolKind::Type); - for (Use decl : type.declarations) { + for (auto decl : type.declarations) { add_all_symbols(decl, id, SymbolKind::Type); // Constructor positions have references to the class, // which we do not want to show in textDocument/documentSymbol if (!(decl.role & Role::Reference)) add_outline(decl, id, SymbolKind::Type); } - for (Use use : type.uses) + for (Reference use : type.uses) add_all_symbols(use, id, SymbolKind::Type); } for (const IndexFunc& func : indexed.funcs) { - QueryFuncId id = id_map.ToQuery(func.id); + QueryId::Func id = id_map.ToQuery(func.id); if (func.def.spell) add_all_symbols(*func.def.spell, id, SymbolKind::Func); if (func.def.extent) @@ -274,7 +277,7 @@ QueryFile::DefUpdate BuildFileDefUpdate(const IdMap& id_map, add_all_symbols(decl.spell, id, SymbolKind::Func); add_outline(decl.spell, id, SymbolKind::Func); } - for (Use use : func.uses) { + for (auto use : func.uses) { // Make ranges of implicit function calls larger (spanning one more column // to the left/right). This is hacky but useful. e.g. // textDocument/definition on the space/semicolon in `A a;` or `return @@ -288,90 +291,89 @@ QueryFile::DefUpdate BuildFileDefUpdate(const IdMap& id_map, } } for (const IndexVar& var : indexed.vars) { - QueryVarId id = id_map.ToQuery(var.id); + QueryId::Var id = id_map.ToQuery(var.id); if (var.def.spell) add_all_symbols(*var.def.spell, id, SymbolKind::Var); if (var.def.extent) add_outline(*var.def.extent, id, SymbolKind::Var); - for (Use decl : var.declarations) { + for (auto decl : var.declarations) { add_all_symbols(decl, id, SymbolKind::Var); add_outline(decl, id, SymbolKind::Var); } - for (Use use : var.uses) + for (auto use : var.uses) add_all_symbols(use, id, SymbolKind::Var); } std::sort(def.outline.begin(), def.outline.end(), - [](const SymbolRef& a, const SymbolRef& b) { + [](const QueryId::SymbolRef& a, const QueryId::SymbolRef& b) { return a.range.start < b.range.start; }); std::sort(def.all_symbols.begin(), def.all_symbols.end(), - [](const SymbolRef& a, const SymbolRef& b) { + [](const QueryId::SymbolRef& a, const QueryId::SymbolRef& b) { return a.range.start < b.range.start; }); return QueryFile::DefUpdate(def, indexed.file_contents); } -Maybe GetQueryFileIdFromPath(QueryDatabase* query_db, - const std::string& path, - bool create_if_missing) { - NormalizedPath normalized_path(path); - auto it = query_db->usr_to_file.find(normalized_path); +Maybe GetQueryFileIdFromPath(QueryDatabase* query_db, + const AbsolutePath& path, + bool create_if_missing) { + auto it = query_db->usr_to_file.find(path); if (it != query_db->usr_to_file.end()) - return QueryFileId(it->second.id); + return QueryId::File(it->second.id); if (!create_if_missing) return {}; RawId idx = query_db->files.size(); - query_db->usr_to_file[normalized_path] = QueryFileId(idx); + query_db->usr_to_file[path] = QueryId::File(idx); query_db->files.push_back(QueryFile(path)); - return QueryFileId(idx); + return QueryId::File(idx); } -Maybe GetQueryTypeIdFromUsr(QueryDatabase* query_db, - Usr usr, - bool create_if_missing) { +Maybe GetQueryTypeIdFromUsr(QueryDatabase* query_db, + Usr usr, + bool create_if_missing) { auto it = query_db->usr_to_type.find(usr); if (it != query_db->usr_to_type.end()) - return QueryTypeId(it->second.id); + return QueryId::Type(it->second.id); if (!create_if_missing) return {}; RawId idx = query_db->types.size(); - query_db->usr_to_type[usr] = QueryTypeId(idx); + query_db->usr_to_type[usr] = QueryId::Type(idx); query_db->types.push_back(QueryType(usr)); - return QueryTypeId(idx); + return QueryId::Type(idx); } -Maybe GetQueryFuncIdFromUsr(QueryDatabase* query_db, - Usr usr, - bool create_if_missing) { +Maybe GetQueryFuncIdFromUsr(QueryDatabase* query_db, + Usr usr, + bool create_if_missing) { auto it = query_db->usr_to_func.find(usr); if (it != query_db->usr_to_func.end()) - return QueryFuncId(it->second.id); + return QueryId::Func(it->second.id); if (!create_if_missing) return {}; RawId idx = query_db->funcs.size(); - query_db->usr_to_func[usr] = QueryFuncId(idx); + query_db->usr_to_func[usr] = QueryId::Func(idx); query_db->funcs.push_back(QueryFunc(usr)); - return QueryFuncId(idx); + return QueryId::Func(idx); } -Maybe GetQueryVarIdFromUsr(QueryDatabase* query_db, - Usr usr, - bool create_if_missing) { +Maybe GetQueryVarIdFromUsr(QueryDatabase* query_db, + Usr usr, + bool create_if_missing) { auto it = query_db->usr_to_var.find(usr); if (it != query_db->usr_to_var.end()) - return QueryVarId(it->second.id); + return QueryId::Var(it->second.id); if (!create_if_missing) return {}; RawId idx = query_db->vars.size(); - query_db->usr_to_var[usr] = QueryVarId(idx); + query_db->usr_to_var[usr] = QueryId::Var(idx); query_db->vars.push_back(QueryVar(usr)); - return QueryVarId(idx); + return QueryId::Var(idx); } // Returns true if an element with the same file is found. @@ -388,20 +390,20 @@ bool TryReplaceDef(std::forward_list& def_list, Q&& def) { } // namespace -Maybe QueryDatabase::GetQueryFileIdFromPath( +Maybe QueryDatabase::GetQueryFileIdFromPath( const std::string& path) { return ::GetQueryFileIdFromPath(this, path, false); } -Maybe QueryDatabase::GetQueryTypeIdFromUsr(Usr usr) { +Maybe QueryDatabase::GetQueryTypeIdFromUsr(Usr usr) { return ::GetQueryTypeIdFromUsr(this, usr, false); } -Maybe QueryDatabase::GetQueryFuncIdFromUsr(Usr usr) { +Maybe QueryDatabase::GetQueryFuncIdFromUsr(Usr usr) { return ::GetQueryFuncIdFromUsr(this, usr, false); } -Maybe QueryDatabase::GetQueryVarIdFromUsr(Usr usr) { +Maybe QueryDatabase::GetQueryVarIdFromUsr(Usr usr) { return ::GetQueryVarIdFromUsr(this, usr, false); } @@ -427,49 +429,55 @@ IdMap::IdMap(QueryDatabase* query_db, const IdCache& local_ids) *GetQueryVarIdFromUsr(query_db, entry.second, true); } -QueryTypeId IdMap::ToQuery(IndexTypeId id) const { - assert(cached_type_ids_.find(id) != cached_type_ids_.end()); - return QueryTypeId(cached_type_ids_.find(id)->second); -} -QueryFuncId IdMap::ToQuery(IndexFuncId id) const { - assert(cached_func_ids_.find(id) != cached_func_ids_.end()); - return QueryFuncId(cached_func_ids_.find(id)->second); -} -QueryVarId IdMap::ToQuery(IndexVarId id) const { - assert(cached_var_ids_.find(id) != cached_var_ids_.end()); - return QueryVarId(cached_var_ids_.find(id)->second); -} - -Use IdMap::ToQuery(Reference ref) const { - Use ret(ref.range, ref.id, ref.kind, ref.role, primary_file); - switch (ref.kind) { - case SymbolKind::Invalid: - break; +Id IdMap::ToQuery(SymbolKind kind, Id id) const { + switch (kind) { case SymbolKind::File: - ret.id = primary_file; - break; - case SymbolKind::Func: - ret.id = ToQuery(IndexFuncId(ref.id)); - break; + return primary_file; case SymbolKind::Type: - ret.id = ToQuery(IndexTypeId(ref.id)); - break; + return ToQuery(IndexId::Type(id.id)); + case SymbolKind::Func: + return ToQuery(IndexId::Func(id.id)); case SymbolKind::Var: - ret.id = ToQuery(IndexVarId(ref.id)); + return ToQuery(IndexId::Var(id.id)); + case SymbolKind::Invalid: break; } - return ret; + assert(false); + return Id(-1); } -SymbolRef IdMap::ToQuery(SymbolRef ref) const { - ref.Reference::operator=(ToQuery(static_cast(ref))); - return ref; + +QueryId::Type IdMap::ToQuery(IndexId::Type id) const { + assert(cached_type_ids_.find(id) != cached_type_ids_.end()); + return QueryId::Type(cached_type_ids_.find(id)->second); +} +QueryId::Func IdMap::ToQuery(IndexId::Func id) const { + assert(cached_func_ids_.find(id) != cached_func_ids_.end()); + return QueryId::Func(cached_func_ids_.find(id)->second); } -Use IdMap::ToQuery(Use use) const { - return ToQuery(static_cast(use)); +QueryId::Var IdMap::ToQuery(IndexId::Var id) const { + assert(cached_var_ids_.find(id) != cached_var_ids_.end()); + return QueryId::Var(cached_var_ids_.find(id)->second); } -Use IdMap::ToQuery(IndexFunc::Declaration decl) const { - return ToQuery(static_cast(decl.spell)); +QueryId::SymbolRef IdMap::ToQuery(IndexId::SymbolRef ref) const { + QueryId::SymbolRef result; + result.range = ref.range; + result.id = ToQuery(ref.kind, ref.id); + result.kind = ref.kind; + result.role = ref.role; + return result; +} +QueryId::LexicalRef IdMap::ToQuery(IndexId::LexicalRef ref) const { + QueryId::LexicalRef result; + result.file = primary_file; + result.range = ref.range; + result.id = ToQuery(ref.kind, ref.id); + result.kind = ref.kind; + result.role = ref.role; + return result; +} +QueryId::LexicalRef IdMap::ToQuery(IndexFunc::Declaration decl) const { + return ToQuery(decl.spell); } // ---------------------- @@ -585,11 +593,14 @@ IndexUpdate::IndexUpdate(const IdMap& previous_id_map, current->usr, std::move(*current_remapped_def))); } - PROCESS_UPDATE_DIFF(QueryTypeId, types_declarations, declarations, Use); - PROCESS_UPDATE_DIFF(QueryTypeId, types_derived, derived, QueryTypeId); - PROCESS_UPDATE_DIFF(QueryTypeId, types_instances, instances, - QueryVarId); - PROCESS_UPDATE_DIFF(QueryTypeId, types_uses, uses, Use); + PROCESS_UPDATE_DIFF(QueryId::Type, types_declarations, declarations, + QueryId::LexicalRef); + PROCESS_UPDATE_DIFF(QueryId::Type, types_derived, derived, + QueryId::Type); + PROCESS_UPDATE_DIFF(QueryId::Type, types_instances, instances, + QueryId::Var); + PROCESS_UPDATE_DIFF(QueryId::Type, types_uses, uses, + QueryId::LexicalRef); }); // Functions @@ -646,9 +657,12 @@ IndexUpdate::IndexUpdate(const IdMap& previous_id_map, current->usr, std::move(*current_remapped_def))); } - PROCESS_UPDATE_DIFF(QueryFuncId, funcs_declarations, declarations, Use); - PROCESS_UPDATE_DIFF(QueryFuncId, funcs_derived, derived, QueryFuncId); - PROCESS_UPDATE_DIFF(QueryFuncId, funcs_uses, uses, Use); + PROCESS_UPDATE_DIFF(QueryId::Func, funcs_declarations, declarations, + QueryId::LexicalRef); + PROCESS_UPDATE_DIFF(QueryId::Func, funcs_derived, derived, + QueryId::Func); + PROCESS_UPDATE_DIFF(QueryId::Func, funcs_uses, uses, + QueryId::LexicalRef); }); // Variables @@ -695,8 +709,9 @@ IndexUpdate::IndexUpdate(const IdMap& previous_id_map, vars_def_update.push_back(QueryVar::DefUpdate( current->usr, std::move(*current_remapped_def))); - PROCESS_UPDATE_DIFF(QueryVarId, vars_declarations, declarations, Use); - PROCESS_UPDATE_DIFF(QueryVarId, vars_uses, uses, Use); + PROCESS_UPDATE_DIFF(QueryId::Var, vars_declarations, declarations, + QueryId::LexicalRef); + PROCESS_UPDATE_DIFF(QueryId::Var, vars_uses, uses, QueryId::LexicalRef); }); #undef PROCESS_UPDATE_DIFF @@ -741,17 +756,6 @@ std::string IndexUpdate::ToString() { return output.GetString(); } -NormalizedPath::NormalizedPath(const std::string& path) - : path(LowerPathIfCaseInsensitive(path)) {} - -bool NormalizedPath::operator==(const NormalizedPath& rhs) const { - return path == rhs.path; -} - -bool NormalizedPath::operator!=(const NormalizedPath& rhs) const { - return path != rhs.path; -} - // ------------------------ // QUERYDB THREAD FUNCTIONS // ------------------------ @@ -788,7 +792,7 @@ void QueryDatabase::RemoveUsrs(SymbolKind usr_kind, void QueryDatabase::RemoveUsrs( SymbolKind usr_kind, - const std::vector>& to_remove) { + const std::vector>& to_remove) { switch (usr_kind) { case SymbolKind::Func: { for (const auto& usr_file : to_remove) { @@ -820,8 +824,8 @@ void QueryDatabase::ApplyIndexUpdate(IndexUpdate* update) { // Example types: // storage_name => std::vector> // merge_update => QueryType::DerivedUpdate => -// MergeableUpdate def => QueryType -// def->def_var_name => std::vector +// MergeableUpdate def => +// QueryType def->def_var_name => std::vector #define HANDLE_MERGEABLE(update_var_name, def_var_name, storage_name) \ for (auto merge_update : update->update_var_name) { \ auto& def = storage_name[merge_update.id.id]; \ @@ -830,8 +834,8 @@ void QueryDatabase::ApplyIndexUpdate(IndexUpdate* update) { VerifyUnique(def.def_var_name); \ } - for (const std::string& filename : update->files_removed) - files[usr_to_file[NormalizedPath(filename)].id].def = nullopt; + for (const AbsolutePath& filename : update->files_removed) + files[usr_to_file[filename].id].def = nullopt; ImportOrUpdate(update->files_def_update); RemoveUsrs(SymbolKind::Type, update->types_removed); @@ -860,7 +864,7 @@ void QueryDatabase::ImportOrUpdate( // This function runs on the querydb thread. for (auto& def : updates) { - auto it = usr_to_file.find(NormalizedPath(def.value.path)); + auto it = usr_to_file.find(def.value.path); assert(it != usr_to_file.end()); QueryFile& existing = files[it->second.id]; @@ -927,11 +931,11 @@ void QueryDatabase::ImportOrUpdate(std::vector&& updates) { } } -void QueryDatabase::UpdateSymbols(Maybe>* symbol_idx, +void QueryDatabase::UpdateSymbols(Maybe* symbol_idx, SymbolKind kind, - Id idx) { + AnyId idx) { if (!symbol_idx->HasValue()) { - *symbol_idx = Id(symbols.size()); + *symbol_idx = AnyId(symbols.size()); symbols.push_back(SymbolIdx{idx, kind}); } } @@ -1001,11 +1005,11 @@ TEST_SUITE("query") { IndexFile current(AbsolutePath("foo.cc"), ""); previous.Resolve(previous.ToTypeId(HashUsr("usr1")))->def.spell = - Use(Range(Position(1, 0)), {}, {}, {}, {}); + IndexId::LexicalRef(Range(Position(1, 0)), {}, {}, {}); previous.Resolve(previous.ToFuncId(HashUsr("usr2")))->def.spell = - Use(Range(Position(2, 0)), {}, {}, {}, {}); + IndexId::LexicalRef(Range(Position(2, 0)), {}, {}, {}); previous.Resolve(previous.ToVarId(HashUsr("usr3")))->def.spell = - Use(Range(Position(3, 0)), {}, {}, {}, {}); + IndexId::LexicalRef(Range(Position(3, 0)), {}, {}, {}); IndexUpdate update = GetDelta(previous, current); @@ -1021,11 +1025,14 @@ TEST_SUITE("query") { IndexFile current(AbsolutePath("foo.cc"), ""); previous.Resolve(previous.ToTypeId(HashUsr("usr1"))) - ->uses.push_back(Use{Range(Position(1, 0)), {}, {}, {}, {}}); + ->uses.push_back( + IndexId::LexicalRef(Range(Position(1, 0)), AnyId(0), SymbolKind::Func, {})); previous.Resolve(previous.ToFuncId(HashUsr("usr2"))) - ->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); + ->uses.push_back( + IndexId::LexicalRef(Range(Position(2, 0)), AnyId(0), SymbolKind::Func, {})); previous.Resolve(previous.ToVarId(HashUsr("usr3"))) - ->uses.push_back(Use(Range(Position(3, 0)), {}, {}, {}, {})); + ->uses.push_back( + IndexId::LexicalRef(Range(Position(3, 0)), AnyId(0), SymbolKind::Func, {})); IndexUpdate update = GetDelta(previous, current); @@ -1041,14 +1048,14 @@ TEST_SUITE("query") { IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr"))); IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr"))); - pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {})); - cf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); + pf->uses.push_back(IndexId::LexicalRef(Range(Position(1, 0)), AnyId(0), SymbolKind::Func, {})); + cf->uses.push_back(IndexId::LexicalRef(Range(Position(2, 0)), AnyId(0), SymbolKind::Func, {})); IndexUpdate update = GetDelta(previous, current); REQUIRE(update.funcs_removed.empty()); REQUIRE(update.funcs_uses.size() == 1); - REQUIRE(update.funcs_uses[0].id == QueryFuncId(0)); + REQUIRE(update.funcs_uses[0].id == QueryId::Func(0)); REQUIRE(update.funcs_uses[0].to_remove.size() == 1); REQUIRE(update.funcs_uses[0].to_remove[0].range == Range(Position(1, 0))); REQUIRE(update.funcs_uses[0].to_add.size() == 1); @@ -1062,8 +1069,8 @@ TEST_SUITE("query") { IndexType* pt = previous.Resolve(previous.ToTypeId(HashUsr("usr"))); IndexType* ct = current.Resolve(current.ToTypeId(HashUsr("usr"))); - pt->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {})); - ct->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); + pt->uses.push_back(IndexId::LexicalRef(Range(Position(1, 0)), AnyId(0), SymbolKind::Type, {})); + ct->uses.push_back(IndexId::LexicalRef(Range(Position(2, 0)), AnyId(0), SymbolKind::Type, {})); IndexUpdate update = GetDelta(previous, current); @@ -1082,10 +1089,10 @@ TEST_SUITE("query") { IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr"))); IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr"))); - pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {})); - pf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); - cf->uses.push_back(Use(Range(Position(4, 0)), {}, {}, {}, {})); - cf->uses.push_back(Use(Range(Position(5, 0)), {}, {}, {}, {})); + pf->uses.push_back(IndexId::LexicalRef(Range(Position(1, 0)), AnyId(0), SymbolKind::Func, {})); + pf->uses.push_back(IndexId::LexicalRef(Range(Position(2, 0)), AnyId(0), SymbolKind::Func, {})); + cf->uses.push_back(IndexId::LexicalRef(Range(Position(4, 0)), AnyId(0), SymbolKind::Func, {})); + cf->uses.push_back(IndexId::LexicalRef(Range(Position(5, 0)), AnyId(0), SymbolKind::Func, {})); QueryDatabase db; IdMap previous_map(&db, previous.id_cache); @@ -1110,7 +1117,8 @@ TEST_SUITE("query") { TEST_CASE("Remove variable with usage") { auto load_index_from_json = [](const char* json) { - return Deserialize(SerializeFormat::Json, "foo.cc", json, "", + return Deserialize(SerializeFormat::Json, + AbsolutePath::BuildDoNotUse("foo.cc"), json, "", nullopt); }; diff --git a/src/query.h b/src/query.h index 27f8b3e31..3ac7f4370 100644 --- a/src/query.h +++ b/src/query.h @@ -14,13 +14,38 @@ struct QueryFunc; struct QueryVar; struct QueryDatabase; -using QueryFileId = Id; -using QueryTypeId = Id; -using QueryFuncId = Id; -using QueryVarId = Id; - struct IdMap; +// |id|,|kind| refer to the referenced entity. +struct QuerySymbolRef : Reference { + QuerySymbolRef() = default; + QuerySymbolRef(Range range, AnyId id, SymbolKind kind, Role role) + : Reference{range, id, kind, role} {} +}; + +// |id|,|kind| refer to the lexical parent. +struct QueryLexicalRef : Reference { + Id file; + QueryLexicalRef() = default; + QueryLexicalRef(Range range, + AnyId id, + SymbolKind kind, + Role role, + Id file) + : Reference{range, id, kind, role}, file(file) {} +}; +// Used by |HANDLE_MERGEABLE| so only |range| is needed. +MAKE_HASHABLE(QueryLexicalRef, t.range); + +struct QueryId { + using File = Id; + using Func = Id; + using Type = Id; + using Var = Id; + using SymbolRef = QuerySymbolRef; + using LexicalRef = QueryLexicalRef; +}; + // There are two sources of reindex updates: the (single) definition of a // symbol has changed, or one of many users of the symbol has changed. // @@ -87,17 +112,9 @@ void Reflect(TVisitor& visitor, WithFileContent& value) { REFLECT_MEMBER_END(); } -struct QueryFamily { - using FileId = Id; - using FuncId = Id; - using TypeId = Id; - using VarId = Id; - using Range = Reference; -}; - struct QueryFile { struct Def { - Id file; + QueryId::File file; AbsolutePath path; std::vector args; // Language identifier @@ -105,9 +122,9 @@ struct QueryFile { // Includes in the file. std::vector includes; // Outline of the file (ie, for code lens). - std::vector outline; + std::vector outline; // Every symbol found in the file (ie, for goto definition) - std::vector all_symbols; + std::vector all_symbols; // Parts of the file which are disabled. std::vector inactive_regions; // Used by |$cquery/freshenIndex|. @@ -117,7 +134,7 @@ struct QueryFile { using DefUpdate = WithFileContent; optional def; - Maybe> symbol_idx; + Maybe symbol_idx; explicit QueryFile(const AbsolutePath& path) { def = Def(); @@ -138,8 +155,8 @@ template struct QueryEntity { using Def = QDef; using DefUpdate = WithUsr; - using DeclarationsUpdate = MergeableUpdate, Use>; - using UsesUpdate = MergeableUpdate, Use>; + using DeclarationsUpdate = MergeableUpdate, QueryId::LexicalRef>; + using UsesUpdate = MergeableUpdate, QueryId::LexicalRef>; Def* AnyDef() { Def* ret = nullptr; for (auto& i : static_cast(this)->def) { @@ -152,40 +169,40 @@ struct QueryEntity { const Def* AnyDef() const { return const_cast(this)->AnyDef(); } }; -struct QueryType : QueryEntity> { - using DerivedUpdate = MergeableUpdate; - using InstancesUpdate = MergeableUpdate; +struct QueryType : QueryEntity> { + using DerivedUpdate = MergeableUpdate; + using InstancesUpdate = MergeableUpdate; Usr usr; - Maybe> symbol_idx; + Maybe symbol_idx; std::forward_list def; - std::vector declarations; - std::vector derived; - std::vector instances; - std::vector uses; + std::vector declarations; + std::vector derived; + std::vector instances; + std::vector uses; explicit QueryType(const Usr& usr) : usr(usr) {} }; -struct QueryFunc : QueryEntity> { - using DerivedUpdate = MergeableUpdate; +struct QueryFunc : QueryEntity> { + using DerivedUpdate = MergeableUpdate; Usr usr; - Maybe> symbol_idx; + Maybe symbol_idx; std::forward_list def; - std::vector declarations; - std::vector derived; - std::vector uses; + std::vector declarations; + std::vector derived; + std::vector uses; explicit QueryFunc(const Usr& usr) : usr(usr) {} }; -struct QueryVar : QueryEntity> { +struct QueryVar : QueryEntity> { Usr usr; - Maybe> symbol_idx; + Maybe symbol_idx; std::forward_list def; - std::vector declarations; - std::vector uses; + std::vector declarations; + std::vector uses; explicit QueryVar(const Usr& usr) : usr(usr) {} }; @@ -206,7 +223,7 @@ struct IndexUpdate { std::string ToString(); // File updates. - std::vector files_removed; + std::vector files_removed; std::vector files_def_update; // Type updates. @@ -218,14 +235,14 @@ struct IndexUpdate { std::vector types_uses; // Function updates. - std::vector> funcs_removed; + std::vector> funcs_removed; std::vector funcs_def_update; std::vector funcs_declarations; std::vector funcs_derived; std::vector funcs_uses; // Variable updates. - std::vector> vars_removed; + std::vector> vars_removed; std::vector vars_def_update; std::vector vars_declarations; std::vector vars_uses; @@ -257,15 +274,6 @@ MAKE_REFLECT_STRUCT(IndexUpdate, vars_declarations, vars_uses); -struct NormalizedPath { - explicit NormalizedPath(const std::string& path); - bool operator==(const NormalizedPath& rhs) const; - bool operator!=(const NormalizedPath& rhs) const; - - std::string path; -}; -MAKE_HASHABLE(NormalizedPath, t.path); - // The query database is heavily optimized for fast queries. It is stored // in-memory. struct QueryDatabase { @@ -279,32 +287,30 @@ struct QueryDatabase { std::vector vars; // Lookup symbol based on a usr. - spp::sparse_hash_map usr_to_file; - spp::sparse_hash_map usr_to_type; - spp::sparse_hash_map usr_to_func; - spp::sparse_hash_map usr_to_var; + spp::sparse_hash_map usr_to_file; + spp::sparse_hash_map usr_to_type; + spp::sparse_hash_map usr_to_func; + spp::sparse_hash_map usr_to_var; // Marks the given Usrs as invalid. void RemoveUsrs(SymbolKind usr_kind, const std::vector& to_remove); void RemoveUsrs(SymbolKind usr_kind, - const std::vector>& to_remove); + const std::vector>& to_remove); // Insert the contents of |update| into |db|. void ApplyIndexUpdate(IndexUpdate* update); void ImportOrUpdate(const std::vector& updates); void ImportOrUpdate(std::vector&& updates); void ImportOrUpdate(std::vector&& updates); void ImportOrUpdate(std::vector&& updates); - void UpdateSymbols(Maybe>* symbol_idx, - SymbolKind kind, - Id idx); + void UpdateSymbols(Maybe* symbol_idx, SymbolKind kind, AnyId idx); std::string_view GetSymbolDetailedName(RawId symbol_idx) const; std::string_view GetSymbolShortName(RawId symbol_idx) const; // Query the indexing structure to look up symbol id for given Usr. - Maybe GetQueryFileIdFromPath(const std::string& path); - Maybe GetQueryTypeIdFromUsr(Usr usr); - Maybe GetQueryFuncIdFromUsr(Usr usr); - Maybe GetQueryVarIdFromUsr(Usr usr); + Maybe GetQueryFileIdFromPath(const std::string& path); + Maybe GetQueryTypeIdFromUsr(Usr usr); + Maybe GetQueryFuncIdFromUsr(Usr usr); + Maybe GetQueryVarIdFromUsr(Usr usr); QueryFile& GetFile(SymbolIdx ref) { return files[ref.id.id]; } QueryFunc& GetFunc(SymbolIdx ref) { return funcs[ref.id.id]; } @@ -316,13 +322,13 @@ template struct IndexToQuery; // clang-format off -template <> struct IndexToQuery { using type = QueryFileId; }; -template <> struct IndexToQuery { using type = QueryFuncId; }; -template <> struct IndexToQuery { using type = QueryTypeId; }; -template <> struct IndexToQuery { using type = QueryVarId; }; -template <> struct IndexToQuery { using type = Use; }; -template <> struct IndexToQuery { using type = SymbolRef; }; -template <> struct IndexToQuery { using type = Use; }; +template <> struct IndexToQuery { using type = QueryId::File; }; +template <> struct IndexToQuery { using type = QueryId::Func; }; +template <> struct IndexToQuery { using type = QueryId::Type; }; +template <> struct IndexToQuery { using type = QueryId::Var; }; +template <> struct IndexToQuery { using type = QueryId::SymbolRef; }; +template <> struct IndexToQuery { using type = QueryId::LexicalRef; }; +template <> struct IndexToQuery { using type = QueryId::LexicalRef; }; template struct IndexToQuery> { using type = optional::type>; }; @@ -333,19 +339,18 @@ template struct IndexToQuery> { struct IdMap { const IdCache& local_ids; - QueryFileId primary_file; + QueryId::File primary_file; IdMap(QueryDatabase* query_db, const IdCache& local_ids); - // FIXME Too verbose // clang-format off - QueryTypeId ToQuery(IndexTypeId id) const; - QueryFuncId ToQuery(IndexFuncId id) const; - QueryVarId ToQuery(IndexVarId id) const; - SymbolRef ToQuery(SymbolRef ref) const; - Use ToQuery(Reference ref) const; - Use ToQuery(Use ref) const; - Use ToQuery(IndexFunc::Declaration decl) const; + Id ToQuery(SymbolKind kind, Id id) const; + QueryId::Type ToQuery(IndexId::Type id) const; + QueryId::Func ToQuery(IndexId::Func id) const; + QueryId::Var ToQuery(IndexId::Var id) const; + QueryId::SymbolRef ToQuery(IndexId::SymbolRef ref) const; + QueryId::LexicalRef ToQuery(IndexId::LexicalRef ref) const; + QueryId::LexicalRef ToQuery(IndexFunc::Declaration decl) const; template Maybe::type> ToQuery(Maybe id) const { if (!id) @@ -363,7 +368,7 @@ struct IdMap { // clang-format on private: - spp::sparse_hash_map cached_type_ids_; - spp::sparse_hash_map cached_func_ids_; - spp::sparse_hash_map cached_var_ids_; + spp::sparse_hash_map cached_type_ids_; + spp::sparse_hash_map cached_func_ids_; + spp::sparse_hash_map cached_var_ids_; }; diff --git a/src/query_utils.cc b/src/query_utils.cc index 337e05281..834b5015b 100644 --- a/src/query_utils.cc +++ b/src/query_utils.cc @@ -17,9 +17,10 @@ int ComputeRangeSize(const Range& range) { } template -std::vector GetDeclarations(std::vector& entities, - const std::vector>& ids) { - std::vector ret; +std::vector GetDeclarations( + std::vector& entities, + const std::vector>& ids) { + std::vector ret; ret.reserve(ids.size()); for (auto id : ids) { Q& entity = entities[id.id]; @@ -38,27 +39,29 @@ std::vector GetDeclarations(std::vector& entities, } // namespace -Maybe GetDefinitionSpell(QueryDatabase* db, SymbolIdx sym) { - Maybe ret; +optional GetDefinitionSpell(QueryDatabase* db, + SymbolIdx sym) { + optional ret; EachEntityDef(db, sym, [&](const auto& def) { return !(ret = def.spell); }); return ret; } -Maybe GetDefinitionExtent(QueryDatabase* db, SymbolIdx sym) { +optional GetDefinitionExtent(QueryDatabase* db, + SymbolIdx sym) { // Used to jump to file. if (sym.kind == SymbolKind::File) - return Use(Range(Position(0, 0), Position(0, 0)), sym.id, sym.kind, - Role::None, QueryFileId(sym.id)); - Maybe ret; + return QueryId::LexicalRef(Range(Position(0, 0), Position(0, 0)), sym.id, + sym.kind, Role::None, QueryId::File(sym.id)); + Maybe ret; EachEntityDef(db, sym, [&](const auto& def) { return !(ret = def.extent); }); return ret; } -Maybe GetDeclarationFileForSymbol(QueryDatabase* db, - SymbolIdx sym) { +optional GetDeclarationFileForSymbol(QueryDatabase* db, + SymbolIdx sym) { switch (sym.kind) { case SymbolKind::File: - return QueryFileId(sym.id); + return QueryId::File(sym.id); case SymbolKind::Func: { QueryFunc& func = db->GetFunc(sym); if (!func.declarations.empty()) @@ -85,22 +88,26 @@ Maybe GetDeclarationFileForSymbol(QueryDatabase* db, return nullopt; } -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids) { +std::vector GetDeclarations( + QueryDatabase* db, + const std::vector& ids) { return GetDeclarations(db->funcs, ids); } -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids) { +std::vector GetDeclarations( + QueryDatabase* db, + const std::vector& ids) { return GetDeclarations(db->types, ids); } -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids) { +std::vector GetDeclarations( + QueryDatabase* db, + const std::vector& ids) { return GetDeclarations(db->vars, ids); } -std::vector GetNonDefDeclarations(QueryDatabase* db, SymbolIdx sym) { +std::vector GetNonDefDeclarations(QueryDatabase* db, + SymbolIdx sym) { switch (sym.kind) { case SymbolKind::Func: return db->GetFunc(sym).declarations; @@ -113,8 +120,9 @@ std::vector GetNonDefDeclarations(QueryDatabase* db, SymbolIdx sym) { } } -std::vector GetUsesForAllBases(QueryDatabase* db, QueryFunc& root) { - std::vector ret; +std::vector GetRefsForAllBases(QueryDatabase* db, + QueryFunc& root) { + std::vector ret; std::vector stack{&root}; std::unordered_set seen; seen.insert(root.usr); @@ -135,8 +143,9 @@ std::vector GetUsesForAllBases(QueryDatabase* db, QueryFunc& root) { return ret; } -std::vector GetUsesForAllDerived(QueryDatabase* db, QueryFunc& root) { - std::vector ret; +std::vector GetRefsForAllDerived(QueryDatabase* db, + QueryFunc& root) { + std::vector ret; std::vector stack{&root}; std::unordered_set seen; seen.insert(root.usr); @@ -197,19 +206,19 @@ optional GetLsRange(WorkingFile* working_file, const Range& location) { } lsDocumentUri GetLsDocumentUri(QueryDatabase* db, - QueryFileId file_id, - std::string* path) { + QueryId::File file_id, + AbsolutePath* path) { QueryFile& file = db->files[file_id.id]; if (file.def) { *path = file.def->path; return lsDocumentUri::FromPath(*path); } else { - *path = ""; + path->path = ""; return lsDocumentUri(); } } -lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id) { +lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryId::File file_id) { QueryFile& file = db->files[file_id.id]; if (file.def) { return lsDocumentUri::FromPath(file.def->path); @@ -220,49 +229,31 @@ lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id) { optional GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, - Use use) { - std::string path; - lsDocumentUri uri = GetLsDocumentUri(db, use.file, &path); + QueryId::LexicalRef ref) { + AbsolutePath path; + lsDocumentUri uri = GetLsDocumentUri(db, ref.file, &path); optional range = - GetLsRange(working_files->GetFileByFilename(path), use.range); + GetLsRange(working_files->GetFileByFilename(path), ref.range); if (!range) return nullopt; return lsLocation(uri, *range); } -optional GetLsLocationEx(QueryDatabase* db, - WorkingFiles* working_files, - Use use, - bool container) { - optional ls_loc = GetLsLocation(db, working_files, use); - if (!ls_loc) - return nullopt; - lsLocationEx ret; - ret.lsLocation::operator=(*ls_loc); - if (container) { - ret.role = uint16_t(use.role); - EachEntityDef(db, use, [&](const auto& def) { - ret.containerName = std::string_view(def.detailed_name); - return false; - }); +std::vector GetLsLocations( + QueryDatabase* db, + WorkingFiles* working_files, + const std::vector& refs) { + std::vector result; + for (QueryId::LexicalRef ref : refs) { + if (optional l = GetLsLocation(db, working_files, ref)) + result.push_back(*l); } - return ret; -} -std::vector GetLsLocationExs(QueryDatabase* db, - WorkingFiles* working_files, - const std::vector& uses, - bool container, - int limit) { - std::vector ret; - for (Use use : uses) - if (auto loc = GetLsLocationEx(db, working_files, use, container)) - ret.push_back(*loc); - std::sort(ret.begin(), ret.end()); - ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); - if (ret.size() > limit) - ret.resize(limit); - return ret; + std::sort(result.begin(), result.end()); + result.erase(std::unique(result.begin(), result.end()), result.end()); + if (result.size() > g_config->xref.maxNum) + result.resize(g_config->xref.maxNum); + return result; } lsSymbolKind GetSymbolKind(QueryDatabase* db, SymbolIdx sym) { @@ -317,10 +308,10 @@ optional GetSymbolInfo(QueryDatabase* db, return nullopt; } -std::vector FindSymbolsAtLocation(WorkingFile* working_file, - QueryFile* file, - lsPosition position) { - std::vector symbols; +std::vector FindSymbolsAtLocation(WorkingFile* working_file, + QueryFile* file, + lsPosition position) { + std::vector symbols; symbols.reserve(1); int target_line = position.line; @@ -332,7 +323,7 @@ std::vector FindSymbolsAtLocation(WorkingFile* working_file, target_line = *index_line; } - for (const SymbolRef& sym : file->def->all_symbols) { + for (const QueryId::SymbolRef& sym : file->def->all_symbols) { if (sym.range.Contains(target_line, target_column)) symbols.push_back(sym); } @@ -349,7 +340,7 @@ std::vector FindSymbolsAtLocation(WorkingFile* working_file, // Then order functions before other types, which makes goto definition work // better on constructors. std::sort(symbols.begin(), symbols.end(), - [](const SymbolRef& a, const SymbolRef& b) { + [](const QueryId::SymbolRef& a, const QueryId::SymbolRef& b) { int t = ComputeRangeSize(a.range) - ComputeRangeSize(b.range); if (t) return t < 0; diff --git a/src/query_utils.h b/src/query_utils.h index f7f43a57f..71ad59cc6 100644 --- a/src/query_utils.h +++ b/src/query_utils.h @@ -5,55 +5,59 @@ #include -Maybe GetDefinitionSpell(QueryDatabase* db, SymbolIdx sym); -Maybe GetDefinitionExtent(QueryDatabase* db, SymbolIdx sym); -Maybe GetDeclarationFileForSymbol(QueryDatabase* db, - SymbolIdx sym); +optional GetDefinitionSpell(QueryDatabase* db, + SymbolIdx sym); +optional GetDefinitionExtent(QueryDatabase* db, + SymbolIdx sym); +optional GetDeclarationFileForSymbol(QueryDatabase* db, + SymbolIdx sym); // Get defining declaration (if exists) or an arbitrary declaration (otherwise) // for each id. -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids); -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids); -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids); +std::vector GetDeclarations( + QueryDatabase* db, + const std::vector& ids); +std::vector GetDeclarations( + QueryDatabase* db, + const std::vector& ids); +std::vector GetDeclarations( + QueryDatabase* db, + const std::vector& ids); // Get non-defining declarations. -std::vector GetNonDefDeclarations(QueryDatabase* db, SymbolIdx sym); +std::vector GetNonDefDeclarations(QueryDatabase* db, + SymbolIdx sym); -std::vector GetUsesForAllBases(QueryDatabase* db, QueryFunc& root); -std::vector GetUsesForAllDerived(QueryDatabase* db, QueryFunc& root); +std::vector GetRefsForAllBases(QueryDatabase* db, + QueryFunc& root); +std::vector GetRefsForAllDerived(QueryDatabase* db, + QueryFunc& root); optional GetLsPosition(WorkingFile* working_file, const Position& position); optional GetLsRange(WorkingFile* working_file, const Range& location); lsDocumentUri GetLsDocumentUri(QueryDatabase* db, - QueryFileId file_id, - std::string* path); -lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id); + QueryId::File file_id, + AbsolutePath* path); +lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryId::File file_id); optional GetLsLocation(QueryDatabase* db, WorkingFiles* working_files, - Use use); -optional GetLsLocationEx(QueryDatabase* db, - WorkingFiles* working_files, - Use use, - bool container); -std::vector GetLsLocationExs(QueryDatabase* db, - WorkingFiles* working_files, - const std::vector& refs, - bool container, - int limit); + QueryId::LexicalRef ref); +std::vector GetLsLocations( + QueryDatabase* db, + WorkingFiles* working_files, + const std::vector& refs); // Returns a symbol. The symbol will have *NOT* have a location assigned. optional GetSymbolInfo(QueryDatabase* db, WorkingFiles* working_files, SymbolIdx sym, bool use_short_name); -std::vector FindSymbolsAtLocation(WorkingFile* working_file, - QueryFile* file, - lsPosition position); +std::vector FindSymbolsAtLocation(WorkingFile* working_file, + QueryFile* file, + lsPosition position); +// Calls fn with a QueryFunc, QueryType, or QueryVar instance. template void WithEntity(QueryDatabase* db, SymbolIdx sym, Fn&& fn) { switch (sym.kind) { @@ -87,14 +91,14 @@ void EachOccurrence(QueryDatabase* db, bool include_decl, Fn&& fn) { WithEntity(db, sym, [&](const auto& entity) { - for (Use use : entity.uses) - fn(use); + for (QueryId::LexicalRef ref : entity.uses) + fn(ref); if (include_decl) { for (auto& def : entity.def) if (def.spell) fn(*def.spell); - for (Use use : entity.declarations) - fn(use); + for (QueryId::LexicalRef ref : entity.declarations) + fn(ref); } }); } @@ -113,14 +117,14 @@ void EachOccurrenceWithParent(QueryDatabase* db, parent_kind = GetSymbolKind(db, sym); break; } - for (Use use : entity.uses) - fn(use, parent_kind); + for (QueryId::LexicalRef ref : entity.uses) + fn(ref, parent_kind); if (include_decl) { for (auto& def : entity.def) if (def.spell) fn(*def.spell, parent_kind); - for (Use use : entity.declarations) - fn(use, parent_kind); + for (QueryId::LexicalRef ref : entity.declarations) + fn(ref, parent_kind); } }); } diff --git a/src/queue_manager.cc b/src/queue_manager.cc index edcec6d29..bb0056bc3 100644 --- a/src/queue_manager.cc +++ b/src/queue_manager.cc @@ -7,11 +7,11 @@ #include Index_Request::Index_Request( - const std::string& path, + const AbsolutePath& path, const std::vector& args, bool is_interactive, - const std::string& contents, - const std::shared_ptr& cache_manager, + const optional& contents, + const std::shared_ptr& cache_manager, lsRequestId id) : path(path), args(args), @@ -20,15 +20,12 @@ Index_Request::Index_Request( cache_manager(cache_manager), id(id) {} -Index_DoIdMap::Index_DoIdMap( - std::unique_ptr current, - const std::shared_ptr& cache_manager, - PerformanceImportFile perf, - bool is_interactive, - bool write_to_disk) +Index_DoIdMap::Index_DoIdMap(std::unique_ptr current, + const std::shared_ptr& cache_manager, + bool is_interactive, + bool write_to_disk) : current(std::move(current)), cache_manager(cache_manager), - perf(perf), is_interactive(is_interactive), write_to_disk(write_to_disk) { assert(this->current); @@ -39,27 +36,23 @@ Index_OnIdMapped::File::File(std::unique_ptr file, : file(std::move(file)), ids(std::move(ids)) {} Index_OnIdMapped::Index_OnIdMapped( - const std::shared_ptr& cache_manager, - PerformanceImportFile perf, + const std::shared_ptr& cache_manager, bool is_interactive, bool write_to_disk) : cache_manager(cache_manager), - perf(perf), is_interactive(is_interactive), write_to_disk(write_to_disk) {} -Index_OnIndexed::Index_OnIndexed(IndexUpdate&& update, - PerformanceImportFile perf) - : update(std::move(update)), perf(perf) {} +Index_OnIndexed::Index_OnIndexed(IndexUpdate&& update) + : update(std::move(update)) {} -std::unique_ptr QueueManager::instance_; +QueueManager* QueueManager::instance_; // static -void QueueManager::Init(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter) { - instance_ = std::unique_ptr( - new QueueManager(querydb_waiter, indexer_waiter, stdout_waiter)); +void QueueManager::Init() { + if (instance_) + delete instance_; + instance_ = new QueueManager(); } // static @@ -70,23 +63,24 @@ void QueueManager::WriteStdout(MethodType method, lsBaseOutMessage& response) { Stdout_Request out; out.content = sstream.str(); out.method = method; - instance()->for_stdout.PushBack(std::move(out)); + instance()->for_stdout.Enqueue(std::move(out), false /*priority*/); } -QueueManager::QueueManager(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter) - : for_stdout(stdout_waiter), +QueueManager::QueueManager() + : querydb_waiter(std::make_shared()), + indexer_waiter(std::make_shared()), + stdout_waiter(std::make_shared()), + for_stdout(stdout_waiter), for_querydb(querydb_waiter), do_id_map(querydb_waiter), index_request(indexer_waiter), load_previous_index(indexer_waiter), on_id_mapped(indexer_waiter), - // TODO on_indexed is shared by "querydb" and "indexer" - on_indexed(querydb_waiter, indexer_waiter) {} + on_indexed_for_merge(indexer_waiter), + on_indexed_for_querydb(querydb_waiter) {} bool QueueManager::HasWork() { return !index_request.IsEmpty() || !do_id_map.IsEmpty() || !load_previous_index.IsEmpty() || !on_id_mapped.IsEmpty() || - !on_indexed.IsEmpty(); + !on_indexed_for_merge.IsEmpty() || !on_indexed_for_querydb.IsEmpty(); } diff --git a/src/queue_manager.h b/src/queue_manager.h index 86478a360..f18af3e6f 100644 --- a/src/queue_manager.h +++ b/src/queue_manager.h @@ -1,13 +1,12 @@ #pragma once #include "method.h" -#include "performance.h" #include "query.h" #include "threaded_queue.h" #include -struct ICacheManager; +struct IndexCache; struct lsBaseOutMessage; struct Stdout_Request { @@ -16,35 +15,32 @@ struct Stdout_Request { }; struct Index_Request { - std::string path; + AbsolutePath path; // TODO: make |args| a string that is parsed lazily. std::vector args; bool is_interactive; - std::string contents; // Preloaded contents. - std::shared_ptr cache_manager; + optional contents; + std::shared_ptr cache_manager; lsRequestId id; - Index_Request(const std::string& path, + Index_Request(const AbsolutePath& path, const std::vector& args, bool is_interactive, - const std::string& contents, - const std::shared_ptr& cache_manager, + const optional& contents, + const std::shared_ptr& cache_manager, lsRequestId id = {}); }; struct Index_DoIdMap { std::unique_ptr current; std::unique_ptr previous; - std::shared_ptr cache_manager; + std::shared_ptr cache_manager; - PerformanceImportFile perf; bool is_interactive = false; bool write_to_disk = false; - bool load_previous = false; Index_DoIdMap(std::unique_ptr current, - const std::shared_ptr& cache_manager, - PerformanceImportFile perf, + const std::shared_ptr& cache_manager, bool is_interactive, bool write_to_disk); }; @@ -59,37 +55,34 @@ struct Index_OnIdMapped { std::unique_ptr previous; std::unique_ptr current; - std::shared_ptr cache_manager; + std::shared_ptr cache_manager; - PerformanceImportFile perf; bool is_interactive; bool write_to_disk; - Index_OnIdMapped(const std::shared_ptr& cache_manager, - PerformanceImportFile perf, + Index_OnIdMapped(const std::shared_ptr& cache_manager, bool is_interactive, bool write_to_disk); }; struct Index_OnIndexed { IndexUpdate update; - PerformanceImportFile perf; - Index_OnIndexed(IndexUpdate&& update, PerformanceImportFile perf); + Index_OnIndexed(IndexUpdate&& update); }; class QueueManager { - static std::unique_ptr instance_; - public: - static QueueManager* instance() { return instance_.get(); } - static void Init(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter); + static QueueManager* instance() { return instance_; } + static void Init(); static void WriteStdout(MethodType method, lsBaseOutMessage& response); bool HasWork(); + std::shared_ptr querydb_waiter; + std::shared_ptr indexer_waiter; + std::shared_ptr stdout_waiter; + // Messages received by "stdout" thread. ThreadedQueue for_stdout; @@ -102,12 +95,17 @@ class QueueManager { ThreadedQueue load_previous_index; ThreadedQueue on_id_mapped; - // Shared by querydb and indexer. - // TODO split on_indexed - ThreadedQueue on_indexed; + // Index_OnIndexed is split into two queues. on_indexed_for_querydb is + // limited to a mediumish length and is handled only by querydb. When that + // list grows too big, messages are added to on_indexed_for_merge which will + // be processed by the indexer. + ThreadedQueue on_indexed_for_merge; + ThreadedQueue on_indexed_for_querydb; private: - explicit QueueManager(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter); + explicit QueueManager(); + + // Note: we do not destroy |instance_| on shutdown; see + // https://github.com/cquery-project/cquery/issues/695 + static QueueManager* instance_; }; diff --git a/src/serializer.cc b/src/serializer.cc index a2df7abbf..3cce9eb1e 100644 --- a/src/serializer.cc +++ b/src/serializer.cc @@ -132,16 +132,6 @@ void Reflect(Writer& visitor, std::string_view& data) { visitor.String(&data[0], (rapidjson::SizeType)data.size()); } -void Reflect(Reader& visitor, NtString& value) { - if (!visitor.IsString()) - throw std::invalid_argument("std::string"); - value = visitor.GetString(); -} -void Reflect(Writer& visitor, NtString& value) { - const char* s = value.c_str(); - visitor.String(s ? s : ""); -} - void Reflect(Reader& visitor, JsonNull& value) { visitor.GetNull(); } @@ -357,7 +347,7 @@ std::string Serialize(SerializeFormat format, IndexFile& file) { std::unique_ptr Deserialize( SerializeFormat format, - const std::string& path, + const AbsolutePath& path, const std::string& serialized_index_content, const std::string& file_content, optional expected_version) { diff --git a/src/serializer.h b/src/serializer.h index b86d73ace..d95c851e3 100644 --- a/src/serializer.h +++ b/src/serializer.h @@ -1,8 +1,6 @@ #pragma once #include "maybe.h" -#include "nt_string.h" -#include "port.h" #include #include @@ -14,6 +12,8 @@ #include #include +struct AbsolutePath; + enum class SerializeFormat { Json, MessagePack }; // A tag type that can be used to write `null` to json. @@ -72,26 +72,31 @@ class Writer { struct IndexFile; +struct optionals_mandatory_tag {}; + #define REFLECT_MEMBER_START() ReflectMemberStart(visitor, value) #define REFLECT_MEMBER_END() ReflectMemberEnd(visitor, value); #define REFLECT_MEMBER_END1(value) ReflectMemberEnd(visitor, value); #define REFLECT_MEMBER(name) ReflectMember(visitor, #name, value.name) +#define REFLECT_MEMBER_OPTIONALS(name) \ + ReflectMember(visitor, #name, value.name, optionals_mandatory_tag{}) #define REFLECT_MEMBER2(name, value) ReflectMember(visitor, name, value) #define MAKE_REFLECT_TYPE_PROXY(type_name) \ MAKE_REFLECT_TYPE_PROXY2(type_name, std::underlying_type::type) -#define MAKE_REFLECT_TYPE_PROXY2(type, as_type) \ - ATTRIBUTE_UNUSED inline void Reflect(Reader& visitor, type& value) { \ - as_type value0; \ - ::Reflect(visitor, value0); \ - value = static_cast(value0); \ - } \ - ATTRIBUTE_UNUSED inline void Reflect(Writer& visitor, type& value) { \ - auto value0 = static_cast(value); \ - ::Reflect(visitor, value0); \ +#define MAKE_REFLECT_TYPE_PROXY2(type, as_type) \ + inline void Reflect(Reader& visitor, type& value) { \ + as_type value0; \ + ::Reflect(visitor, value0); \ + value = static_cast(value0); \ + } \ + inline void Reflect(Writer& visitor, type& value) { \ + auto value0 = static_cast(value); \ + ::Reflect(visitor, value0); \ } #define _MAPPABLE_REFLECT_MEMBER(name) REFLECT_MEMBER(name); +#define _MAPPABLE_REFLECT_MEMBER_OPTIONALS(name) REFLECT_MEMBER_OPTIONALS(name); #define MAKE_REFLECT_EMPTY_STRUCT(type, ...) \ template \ @@ -108,6 +113,14 @@ struct IndexFile; REFLECT_MEMBER_END(); \ } +#define MAKE_REFLECT_STRUCT_OPTIONALS_MANDATORY(type, ...) \ + template \ + void Reflect(TVisitor& visitor, type& value) { \ + REFLECT_MEMBER_START(); \ + MACRO_MAP(_MAPPABLE_REFLECT_MEMBER_OPTIONALS, __VA_ARGS__) \ + REFLECT_MEMBER_END(); \ + } + // clang-format off // Config has many fields, we need to support at least its number of fields. #define NUM_VA_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,N,...) N @@ -166,9 +179,6 @@ void Reflect(Writer& visitor, std::string& value); void Reflect(Reader& visitor, std::string_view& view); void Reflect(Writer& visitor, std::string_view& view); -void Reflect(Reader& visitor, NtString& value); -void Reflect(Writer& visitor, NtString& value); - void Reflect(Reader& visitor, JsonNull& value); void Reflect(Writer& visitor, JsonNull& value); @@ -234,6 +244,15 @@ void ReflectMember(Writer& visitor, const char* name, Maybe& value) { } } +template +void ReflectMember(Writer& visitor, + const char* name, + T& value, + optionals_mandatory_tag) { + visitor.Key(name); + Reflect(visitor, value); +} + // std::vector template void Reflect(Reader& visitor, std::vector& values) { @@ -290,7 +309,7 @@ void ReflectMember(Writer& visitor, const char* name, T& value) { std::string Serialize(SerializeFormat format, IndexFile& file); std::unique_ptr Deserialize( SerializeFormat format, - const std::string& path, + const AbsolutePath& path, const std::string& serialized_index_content, const std::string& file_content, optional expected_version); diff --git a/src/symbol.h b/src/symbol.h index bcbc6c64d..997a2422b 100644 --- a/src/symbol.h +++ b/src/symbol.h @@ -74,11 +74,8 @@ struct lsDocumentHighlight { // The highlight kind, default is DocumentHighlightKind.Text. lsDocumentHighlightKind kind = lsDocumentHighlightKind::Text; - - // cquery extension - Role role = Role::None; }; -MAKE_REFLECT_STRUCT(lsDocumentHighlight, range, kind, role); +MAKE_REFLECT_STRUCT(lsDocumentHighlight, range, kind); struct lsSymbolInformation { std::string_view name; diff --git a/src/test.cc b/src/test.cc index a00ec7ced..f00a75ee7 100644 --- a/src/test.cc +++ b/src/test.cc @@ -268,8 +268,8 @@ void VerifySerializeToFrom(IndexFile* file) { std::string expected = file->ToString(); std::string serialized = Serialize(SerializeFormat::Json, *file); std::unique_ptr result = - Deserialize(SerializeFormat::Json, "--.cc", serialized, "", - nullopt /*expected_version*/); + Deserialize(SerializeFormat::Json, AbsolutePath::BuildDoNotUse("--.cc"), + serialized, "", nullopt /*expected_version*/); std::string actual = result->ToString(); if (expected != actual) { std::cerr << "Serialization failure" << std::endl; @@ -320,7 +320,7 @@ bool RunIndexTests(const std::string& filter_path, bool enable_update) { // FIXME: show diagnostics in STL/headers when running tests. At the moment // this can be done by constructing ClangIndex index(1, 1); ClangIndex index; - for (std::string path : GetFilesInFolder("index_tests", true /*recursive*/, + for (std::string path : GetFilesAndDirectoriesInFolder("index_tests", true /*recursive*/, true /*add_folder_to_path*/)) { bool is_fail_allowed = false; @@ -358,7 +358,10 @@ bool RunIndexTests(const std::string& filter_path, bool enable_update) { // Use c++14 by default, because MSVC STL is written assuming that. if (!AnyStartsWith(flags, "-std")) flags.push_back("-std=c++14"); - flags.push_back("-resource-dir=" + GetDefaultResourceDirectory()); + optional resource_dir = GetDefaultResourceDirectory(); + if (!resource_dir) + ABORT_S() << "Cannot resolve resource directory"; + flags.push_back("-resource-dir=" + resource_dir->path); if (had_extra_flags) { std::cout << "For " << path << std::endl; std::cout << " flags: " << StringJoin(flags) << std::endl; @@ -367,8 +370,7 @@ bool RunIndexTests(const std::string& filter_path, bool enable_update) { // Run test. FileConsumerSharedState file_consumer_shared; - PerformanceImportFile perf; - auto dbs = Parse(&file_consumer_shared, path, flags, {}, &perf, &index, + auto dbs = Parse(&file_consumer_shared, path, flags, {}, &index, false /*dump_ast*/); assert(dbs); @@ -378,11 +380,11 @@ bool RunIndexTests(const std::string& filter_path, bool enable_update) { // FIXME: promote to utils, find and remove duplicates (ie, // cquery_call_tree.cc, maybe something in project.cc). - auto basename = [](const std::string& path) -> std::string { - size_t last_index = path.find_last_of('/'); + auto basename = [](const AbsolutePath& path) -> std::string { + size_t last_index = path.path.find_last_of('/'); if (last_index == std::string::npos) - return path; - return path.substr(last_index + 1); + return path.path; + return path.path.substr(last_index + 1); }; auto severity_to_string = [](const lsDiagnosticSeverity& severity) { diff --git a/src/threaded_queue.cc b/src/threaded_queue.cc new file mode 100644 index 000000000..17e965ead --- /dev/null +++ b/src/threaded_queue.cc @@ -0,0 +1,20 @@ +#include "threaded_queue.h" + +// static +bool MultiQueueWaiter::HasState( + std::initializer_list queues) { + for (BaseThreadQueue* queue : queues) { + if (!queue->IsEmpty()) + return true; + } + return false; +} + +bool MultiQueueWaiter::ValidateWaiter( + std::initializer_list queues) { + for (BaseThreadQueue* queue : queues) { + if (queue->waiter.get() != this) + return false; + } + return true; +} diff --git a/src/threaded_queue.h b/src/threaded_queue.h index cccdb2570..c82984a18 100644 --- a/src/threaded_queue.h +++ b/src/threaded_queue.h @@ -8,15 +8,21 @@ #include #include #include +#include #include #include #include // TODO: cleanup includes. +struct MultiQueueWaiter; + struct BaseThreadQueue { - virtual bool IsEmpty() = 0; virtual ~BaseThreadQueue() = default; + + virtual bool IsEmpty() = 0; + + std::shared_ptr waiter; }; // std::lock accepts two or more arguments. We define an overload for one @@ -38,116 +44,80 @@ struct MultiQueueLock { private: template void lock_impl(std::index_sequence) { - std::lock(std::get(tuple_)->mutex_...); + std::lock(std::get(tuple_)->mutex...); } template void unlock_impl(std::index_sequence) { (void)std::initializer_list{ - (std::get(tuple_)->mutex_.unlock(), 0)...}; + (std::get(tuple_)->mutex.unlock(), 0)...}; } std::tuple tuple_; }; struct MultiQueueWaiter { - std::condition_variable_any cv; + static bool HasState(std::initializer_list queues); - static bool HasState(std::initializer_list queues) { - for (BaseThreadQueue* queue : queues) { - if (!queue->IsEmpty()) - return true; - } - return false; - } + bool ValidateWaiter(std::initializer_list queues); template void Wait(BaseThreadQueue... queues) { + assert(ValidateWaiter({queues...})); + MultiQueueLock l(queues...); while (!HasState({queues...})) cv.wait(l); } + + std::condition_variable_any cv; }; // A threadsafe-queue. http://stackoverflow.com/a/16075550 template struct ThreadedQueue : public BaseThreadQueue { public: - ThreadedQueue() : total_count_(0) { - owned_waiter_ = std::make_unique(); - waiter_ = owned_waiter_.get(); - owned_waiter1_ = std::make_unique(); - waiter1_ = owned_waiter1_.get(); - } + ThreadedQueue() : ThreadedQueue(std::make_shared()) {} - // TODO remove waiter1 after split of on_indexed - explicit ThreadedQueue(MultiQueueWaiter* waiter, - MultiQueueWaiter* waiter1 = nullptr) - : total_count_(0), waiter_(waiter), waiter1_(waiter1) {} + explicit ThreadedQueue(std::shared_ptr waiter) + : total_count_(0) { + this->waiter = waiter; + } // Returns the number of elements in the queue. This is lock-free. size_t Size() const { return total_count_; } // Add an element to the queue. - template ::*push)(T&&)> - void Push(T&& t, bool priority) { - std::lock_guard lock(mutex_); - if (priority) - (priority_.*push)(std::move(t)); - else - (queue_.*push)(std::move(t)); - ++total_count_; - waiter_->cv.notify_one(); - if (waiter1_) - waiter1_->cv.notify_one(); - } - - void PushFront(T&& t, bool priority = false) { - Push<&std::deque::push_front>(std::move(t), priority); - } - - void PushBack(T&& t, bool priority = false) { - Push<&std::deque::push_back>(std::move(t), priority); - } - - // Add a set of elements to the queue. - void EnqueueAll(std::vector&& elements, bool priority = false) { - if (elements.empty()) - return; - - std::lock_guard lock(mutex_); - - total_count_ += elements.size(); - - for (T& element : elements) { + void Enqueue(T&& t, bool priority) { + { + std::lock_guard lock(mutex); if (priority) - priority_.push_back(std::move(element)); + priority_.push_back(std::move(t)); else - queue_.push_back(std::move(element)); + queue_.push_back(std::move(t)); + ++total_count_; } - elements.clear(); - - waiter_->cv.notify_all(); + waiter->cv.notify_one(); } - // Return all elements in the queue. - std::vector DequeueAll() { - std::lock_guard lock(mutex_); - - total_count_ = 0; + // Add a set of elements to the queue. + void EnqueueAll(std::vector&& elements, bool priority) { + if (elements.empty()) + return; - std::vector result; - result.reserve(priority_.size() + queue_.size()); - while (!priority_.empty()) { - result.emplace_back(std::move(priority_.front())); - priority_.pop_front(); - } - while (!queue_.empty()) { - result.emplace_back(std::move(queue_.front())); - queue_.pop_front(); + { + std::lock_guard lock(mutex); + total_count_ += elements.size(); + for (T& element : elements) { + if (priority) + priority_.push_back(std::move(element)); + else + queue_.push_back(std::move(element)); + } + elements.clear(); } - return result; + waiter->cv.notify_all(); } // Returns true if the queue is empty. This is lock-free. @@ -155,9 +125,9 @@ struct ThreadedQueue : public BaseThreadQueue { // Get the first element from the queue. Blocks until one is available. T Dequeue() { - std::unique_lock lock(mutex_); - waiter_->cv.wait(lock, - [&]() { return !priority_.empty() || !queue_.empty(); }); + std::unique_lock lock(mutex); + waiter->cv.wait(lock, + [&]() { return !priority_.empty() || !queue_.empty(); }); auto execute = [&](std::deque* q) { auto val = std::move(q->front()); @@ -172,61 +142,43 @@ struct ThreadedQueue : public BaseThreadQueue { // Get the first element from the queue without blocking. Returns a null // value if the queue is empty. - optional TryPopFrontHelper(int which) { - std::lock_guard lock(mutex_); - auto execute = [&](std::deque* q) { + optional TryDequeue(bool priority) { + std::lock_guard lock(mutex); + + auto pop = [&](std::deque* q) { auto val = std::move(q->front()); q->pop_front(); --total_count_; return std::move(val); }; - if (which & 2 && priority_.size()) - return execute(&priority_); - if (which & 1 && queue_.size()) - return execute(&queue_); - return nullopt; - } - - optional TryPopFront() { return TryPopFrontHelper(3); } - optional TryPopBack() { - std::lock_guard lock(mutex_); - auto execute = [&](std::deque* q) { - auto val = std::move(q->back()); - q->pop_back(); - --total_count_; - return std::move(val); + auto get_result = [&](std::deque* first, + std::deque* second) -> optional { + if (!first->empty()) + return pop(first); + if (!second->empty()) + return pop(second); + return nullopt; }; - // Reversed - if (queue_.size()) - return execute(&queue_); - if (priority_.size()) - return execute(&priority_); - return nullopt; - } - - optional TryPopFrontLow() { return TryPopFrontHelper(1); } - optional TryPopFrontHigh() { return TryPopFrontHelper(2); } + if (priority) + return get_result(&priority_, &queue_); + return get_result(&queue_, &priority_); + } template void Iterate(Fn fn) { - std::lock_guard lock(mutex_); + std::lock_guard lock(mutex); for (auto& entry : priority_) fn(entry); for (auto& entry : queue_) fn(entry); } - mutable std::mutex mutex_; + mutable std::mutex mutex; private: std::atomic total_count_; std::deque priority_; std::deque queue_; - MultiQueueWaiter* waiter_; - std::unique_ptr owned_waiter_; - // TODO remove waiter1 after split of on_indexed - MultiQueueWaiter* waiter1_; - std::unique_ptr owned_waiter1_; }; diff --git a/src/timestamp_manager.cc b/src/timestamp_manager.cc index c99241477..d671bff4b 100644 --- a/src/timestamp_manager.cc +++ b/src/timestamp_manager.cc @@ -4,7 +4,7 @@ #include "indexer.h" optional TimestampManager::GetLastCachedModificationTime( - ICacheManager* cache_manager, + IndexCache* cache_manager, const std::string& path) { { std::lock_guard guard(mutex_); @@ -12,7 +12,7 @@ optional TimestampManager::GetLastCachedModificationTime( if (it != timestamps_.end()) return it->second; } - IndexFile* file = cache_manager->TryLoad(path); + IndexFile* file = cache_manager->TryLoad(AbsolutePath{path}); if (!file) return nullopt; diff --git a/src/timestamp_manager.h b/src/timestamp_manager.h index 5fbc0865e..53aa0de61 100644 --- a/src/timestamp_manager.h +++ b/src/timestamp_manager.h @@ -5,13 +5,13 @@ #include #include -struct ICacheManager; +struct IndexCache; // Caches timestamps of cc files so we can avoid a filesystem reads. This is // important for import perf, as during dependency checking the same files are // checked over and over again if they are common headers. struct TimestampManager { - optional GetLastCachedModificationTime(ICacheManager* cache_manager, + optional GetLastCachedModificationTime(IndexCache* cache_manager, const std::string& path); void UpdateCachedModificationTime(const std::string& path, int64_t timestamp); diff --git a/src/utils.cc b/src/utils.cc index 4e30972f3..5b675291b 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -17,11 +17,14 @@ #include #include #include +#include #if !defined(__APPLE__) #include #endif +#include "unqlite.h" + // DEFAULT_RESOURCE_DIRECTORY is passed with quotes for non-MSVC compilers, ie, // foo vs "foo". #if defined(_MSC_VER) @@ -51,20 +54,8 @@ std::string Trim(std::string s) { TrimInPlace(s); return s; } -void RemoveLastCR(std::string& s) { - if (!s.empty() && *s.rbegin() == '\r') - s.pop_back(); -} - -uint64_t HashUsr(const std::string& s) { - return HashUsr(s.c_str(), s.size()); -} - -uint64_t HashUsr(const char* s) { - return HashUsr(s, strlen(s)); -} -uint64_t HashUsr(const char* s, size_t n) { +uint64_t HashUsr(std::string_view s) { union { uint64_t ret; uint8_t out[8]; @@ -72,7 +63,8 @@ uint64_t HashUsr(const char* s, size_t n) { // k is an arbitrary key. Don't change it. const uint8_t k[16] = {0xd0, 0xe5, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x61, 0x79, 0xea, 0x70, 0xca, 0x70, 0xf0, 0x0d}; - (void)siphash(reinterpret_cast(s), n, k, out, 8); + (void)siphash(reinterpret_cast(s.data()), s.length(), k, out, + 8); return ret; } @@ -182,17 +174,6 @@ std::vector SplitString(const std::string& str, return strings; } -std::string LowerPathIfCaseInsensitive(const std::string& path) { - std::string result = path; - /* -#if defined(_WIN32) - for (size_t i = 0; i < result.size(); ++i) - result[i] = (char)tolower(result[i]); -#endif - */ - return result; -} - static void GetFilesInFolderHelper( std::string folder, bool recursive, @@ -212,7 +193,7 @@ static void GetFilesInFolderHelper( if (tinydir_readfile(&dir, &file) == -1) { LOG_S(WARNING) << "Unable to read file " << file.name << " when reading directory " << folder; - goto bail; + goto bailfile; } // Skip all dot files except .cquery. @@ -226,14 +207,14 @@ static void GetFilesInFolderHelper( if (file.is_dir) { if (recursive) { std::string child_dir = q.front().second + file.name + "/"; - if (!IsSymLink(AbsolutePath(file.path))) + if (!IsSymLink(AbsolutePath::BuildDoNotUse(file.path))) q.push(make_pair(file.path, child_dir)); } } else { handler(q.front().second + file.name); } } - + bailfile: if (tinydir_next(&dir) == -1) { LOG_S(WARNING) << "Unable to fetch next file when reading directory " << folder; @@ -247,7 +228,7 @@ static void GetFilesInFolderHelper( } } -std::vector GetFilesInFolder(std::string folder, +std::vector GetFilesAndDirectoriesInFolder(std::string folder, bool recursive, bool add_folder_to_path) { EnsureEndsInSlash(folder); @@ -258,7 +239,7 @@ std::vector GetFilesInFolder(std::string folder, return result; } -void GetFilesInFolder(std::string folder, +void GetFilesAndDirectoriesInFolder(std::string folder, bool recursive, bool add_folder_to_path, const std::function& handler) { @@ -315,10 +296,10 @@ bool FileExists(const std::string& filename) { return cache.is_open(); } -optional ReadContent(const std::string& filename) { +optional ReadContent(const AbsolutePath& filename) { LOG_S(INFO) << "Reading " << filename; std::ifstream cache; - cache.open(filename); + cache.open(filename.path); try { return std::string(std::istreambuf_iterator(cache), @@ -328,10 +309,10 @@ optional ReadContent(const std::string& filename) { } } -std::vector ReadLinesWithEnding(std::string filename) { +std::vector ReadLinesWithEnding(const AbsolutePath& filename) { std::vector result; - std::ifstream input(filename); + std::ifstream input(filename.path); for (std::string line; SafeGetline(input, line);) result.push_back(line); @@ -340,16 +321,22 @@ std::vector ReadLinesWithEnding(std::string filename) { std::vector ToLines(const std::string& content, bool trim_whitespace) { + auto remove_last_cr = [](std::string& s) { + if (!s.empty() && *s.rbegin() == '\r') + s.pop_back(); + }; + std::vector result; std::istringstream lines(content); std::string line; while (getline(lines, line)) { - if (trim_whitespace) + if (trim_whitespace) { TrimInPlace(line); - else - RemoveLastCR(line); + } else { + remove_last_cr(line); + } result.push_back(line); } @@ -406,7 +393,7 @@ std::string FormatMicroseconds(long long microseconds) { return std::to_string(milliseconds) + "." + std::to_string(remaining) + "ms"; } -std::string GetDefaultResourceDirectory() { +optional GetDefaultResourceDirectory() { std::string result; std::string resource_directory = @@ -419,7 +406,7 @@ std::string GetDefaultResourceDirectory() { resource_directory.substr(1, resource_directory.size() - 2); } if (resource_directory.compare(0, 2, "..") == 0) { - std::string executable_path = GetExecutablePath(); + std::string executable_path = GetExecutablePath().path; size_t pos = executable_path.find_last_of('/'); result = executable_path.substr(0, pos + 1); result += resource_directory; @@ -427,12 +414,12 @@ std::string GetDefaultResourceDirectory() { result = resource_directory; } - auto normalized_result = NormalizePath(result); + auto normalized_result = NormalizePath(result, false /*ensure_exists*/); if (!normalized_result) { LOG_S(WARNING) << "Resource directory " << result << " does not exist"; - return result; + return nullopt; } - return normalized_result->path; + return normalized_result; } std::string UpdateToRnNewlines(std::string output) { @@ -458,9 +445,9 @@ std::string UpdateToRnNewlines(std::string output) { } AbsolutePath GetExecutablePathNextToCqueryBinary(const std::string& name) { - std::string executable_path = GetExecutablePath(); + std::string executable_path = GetExecutablePath().path; size_t pos = executable_path.find_last_of('/'); - return AbsolutePath(executable_path.substr(0, pos + 1) + name); + return AbsolutePath::BuildDoNotUse(executable_path.substr(0, pos + 1) + name); } bool IsAbsolutePath(const std::string& path) { @@ -480,6 +467,17 @@ bool IsWindowsAbsolutePath(const std::string& path) { (path[2] == '/' || path[2] == '\\') && is_drive_letter(path[0]); } +bool IsDirectory(const std::string& path) { + struct stat path_stat; + + if (stat(path.c_str(), &path_stat) != 0) { + perror("cannot access path"); + return false; + } + + return path_stat.st_mode & S_IFDIR; +} + TEST_SUITE("AbsolutePath") { TEST_CASE("IsWindowsAbsolutePath works correctly") { REQUIRE(IsWindowsAbsolutePath("C:/Users/projects/")); diff --git a/src/utils.h b/src/utils.h index f8dbe330c..9c62f6aa5 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,9 +20,7 @@ void TrimEndInPlace(std::string& s); void TrimInPlace(std::string& s); std::string Trim(std::string s); -uint64_t HashUsr(const std::string& s); -uint64_t HashUsr(const char* s); -uint64_t HashUsr(const char* s, size_t n); +uint64_t HashUsr(std::string_view s); // Returns true if |value| starts/ends with |start| or |ending|. bool StartsWith(std::string_view value, std::string_view start); @@ -50,8 +48,6 @@ std::string ReplaceAll(const std::string& source, std::vector SplitString(const std::string& str, const std::string& delimiter); -std::string LowerPathIfCaseInsensitive(const std::string& path); - template std::string StringJoinMap(const TValues& values, const TMap& map, @@ -80,10 +76,10 @@ bool ContainsValue(const TCollection& collection, const TValue& value) { } // Finds all files in the given folder. This is recursive. -std::vector GetFilesInFolder(std::string folder, +std::vector GetFilesAndDirectoriesInFolder(std::string folder, bool recursive, bool add_folder_to_path); -void GetFilesInFolder(std::string folder, +void GetFilesAndDirectoriesInFolder(std::string folder, bool recursive, bool add_folder_to_path, const std::function& handler); @@ -97,8 +93,8 @@ std::string EscapeFileName(std::string path); // FIXME: Move ReadContent into ICacheManager? bool FileExists(const std::string& filename); -optional ReadContent(const std::string& filename); -std::vector ReadLinesWithEnding(std::string filename); +optional ReadContent(const AbsolutePath& filename); +std::vector ReadLinesWithEnding(const AbsolutePath& filename); std::vector ToLines(const std::string& content, bool trim_whitespace); @@ -130,7 +126,7 @@ float GetProcessMemoryUsedInMb(); std::string FormatMicroseconds(long long microseconds); -std::string GetDefaultResourceDirectory(); +optional GetDefaultResourceDirectory(); // Makes sure all newlines in |output| are in \r\n format. std::string UpdateToRnNewlines(std::string output); @@ -141,4 +137,6 @@ AbsolutePath GetExecutablePathNextToCqueryBinary(const std::string& name); // Utility methods to check if |path| is absolute. bool IsAbsolutePath(const std::string& path); bool IsUnixAbsolutePath(const std::string& path); -bool IsWindowsAbsolutePath(const std::string& path); \ No newline at end of file +bool IsWindowsAbsolutePath(const std::string& path); + +bool IsDirectory(const std::string& path); diff --git a/src/working_files.cc b/src/working_files.cc index 35585f84b..a3a6ab70e 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -206,7 +206,7 @@ std::vector WorkingFiles::Snapshot::AsUnsavedFiles() const { return result; } -WorkingFile::WorkingFile(const std::string& filename, +WorkingFile::WorkingFile(const AbsolutePath& filename, const std::string& buffer_content) : filename(filename), buffer_content(buffer_content) { OnBufferContentUpdated(); @@ -449,13 +449,13 @@ lsPosition WorkingFile::FindStableCompletionSource( return GetPositionForOffset(buffer_content, offset); } -WorkingFile* WorkingFiles::GetFileByFilename(const std::string& filename) { +WorkingFile* WorkingFiles::GetFileByFilename(const AbsolutePath& filename) { std::lock_guard lock(files_mutex); return GetFileByFilenameNoLock(filename); } WorkingFile* WorkingFiles::GetFileByFilenameNoLock( - const std::string& filename) { + const AbsolutePath& filename) { for (auto& file : files) { if (file->filename == filename) return file.get(); @@ -469,7 +469,7 @@ void WorkingFiles::DoAction(const std::function& action) { } void WorkingFiles::DoActionOnFile( - const std::string& filename, + const AbsolutePath& filename, const std::function& action) { std::lock_guard lock(files_mutex); WorkingFile* file = GetFileByFilenameNoLock(filename); @@ -552,8 +552,9 @@ WorkingFiles::Snapshot WorkingFiles::AsSnapshot( Snapshot result; result.files.reserve(files.size()); for (const auto& file : files) { - if (filter_paths.empty() || FindAnyPartial(file->filename, filter_paths)) - result.files.push_back({file->filename, file->buffer_content}); + if (filter_paths.empty() || + FindAnyPartial(file->filename.path, filter_paths)) + result.files.push_back({file->filename.path, file->buffer_content}); } return result; } @@ -566,7 +567,7 @@ lsPosition CharPos(const WorkingFile& file, TEST_SUITE("WorkingFile") { TEST_CASE("simple call") { - WorkingFile f("foo.cc", "abcd(1, 2"); + WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "abcd(1, 2"); int active_param = 0; REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) == "abcd"); @@ -586,7 +587,7 @@ TEST_SUITE("WorkingFile") { } TEST_CASE("nested call") { - WorkingFile f("foo.cc", "abcd(efg(), 2"); + WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "abcd(efg(), 2"); int active_param = 0; REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) == "abcd"); @@ -615,7 +616,7 @@ TEST_SUITE("WorkingFile") { } TEST_CASE("auto-insert )") { - WorkingFile f("foo.cc", "abc()"); + WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "abc()"); int active_param = 0; REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ')'), &active_param) == "abc"); @@ -623,7 +624,7 @@ TEST_SUITE("WorkingFile") { } TEST_CASE("existing completion") { - WorkingFile f("foo.cc", "zzz.asdf "); + WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "zzz.asdf "); bool is_global_completion; std::string existing_completion; lsPosition end_pos; @@ -656,7 +657,7 @@ TEST_SUITE("WorkingFile") { } TEST_CASE("existing completion underscore") { - WorkingFile f("foo.cc", "ABC_DEF "); + WorkingFile f(AbsolutePath::BuildDoNotUse("foo.cc"), "ABC_DEF "); bool is_global_completion; std::string existing_completion; lsPosition end_pos; diff --git a/src/working_files.h b/src/working_files.h index bd1f56467..2397dbcaf 100644 --- a/src/working_files.h +++ b/src/working_files.h @@ -31,7 +31,7 @@ struct WorkingFile { // lock! std::vector diagnostics_; - WorkingFile(const std::string& filename, const std::string& buffer_content); + WorkingFile(const AbsolutePath& filename, const std::string& buffer_content); // This should be called when the indexed content has changed. void SetIndexContent(const std::string& index_content); @@ -95,14 +95,14 @@ struct WorkingFiles { // // Find the file with the given filename. - WorkingFile* GetFileByFilename(const std::string& filename); - WorkingFile* GetFileByFilenameNoLock(const std::string& filename); + WorkingFile* GetFileByFilename(const AbsolutePath& filename); + WorkingFile* GetFileByFilenameNoLock(const AbsolutePath& filename); // Run |action| under the lock. void DoAction(const std::function& action); // Run |action| on the file identified by |filename|. This executes under the // lock. - void DoActionOnFile(const std::string& filename, + void DoActionOnFile(const AbsolutePath& filename, const std::function& action); WorkingFile* OnOpen(const lsTextDocumentItem& open); diff --git a/third_party/tinydir.h b/third_party/tinydir.h index 2d4418bc0..ffef9df92 100644 --- a/third_party/tinydir.h +++ b/third_party/tinydir.h @@ -1,5 +1,5 @@ /* -Copyright (c) 2013-2016, tinydir authors: +Copyright (c) 2013-2017, tinydir authors: - Cong Xu - Lautis Sun - Baudouin Feildel @@ -294,7 +294,7 @@ int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) #ifdef _MSC_VER _tinydir_strcpy(path_buf, dir->path); _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); - dir->_h = FindFirstFile(path_buf, &dir->_f); + dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); if (dir->_h == INVALID_HANDLE_VALUE) { errno = ENOENT; @@ -628,7 +628,7 @@ int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) _tinydir_char_t *dir_name; _tinydir_char_t *base_name; #if (defined _MSC_VER || defined __MINGW32__) - _tinydir_char_t drive_buf[_TINYDIR_DRIVE_MAX]; + _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; #endif diff --git a/third_party/unqlite/CMakeLists.txt b/third_party/unqlite/CMakeLists.txt new file mode 100644 index 000000000..c088ce68b --- /dev/null +++ b/third_party/unqlite/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(unqlite unqlite.c) +target_include_directories(unqlite PUBLIC .) +target_compile_definitions(unqlite PRIVATE UNQLITE_ENABLE_THREADS) diff --git a/third_party/unqlite/unqlite.c b/third_party/unqlite/unqlite.c new file mode 100644 index 000000000..2b420f380 --- /dev/null +++ b/third_party/unqlite/unqlite.c @@ -0,0 +1,60236 @@ +/* + * Symisc UnQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2018, Symisc Systems http://unqlite.org/ + * Version 1.1.9 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ +/* + * Copyright (C) 2012, 2018 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* $SymiscID: unqlite.c v1.1.9 Win10 2108-04-27 02:35:11 stable $ */ +/* This file is an amalgamation of many separate C source files from unqlite version 1.1.9 + * By combining all the individual C code files into this single large file, the entire code + * can be compiled as a single translation unit. This allows many compilers to do optimization's + * that would not be possible if the files were compiled separately. Performance improvements + * are commonly seen when unqlite is compiled as a single translation unit. + * + * This file is all you need to compile unqlite. To use unqlite in other programs, you need + * this file and the "unqlite.h" header file that defines the programming interface to the + * unqlite engine.(If you do not have the "unqlite.h" header file at hand, you will find + * a copy embedded within the text of this file.Search for "Header file: " to find + * the start of the embedded unqlite.h header file.) Additional code files may be needed if + * you want a wrapper to interface unqlite with your choice of programming language. + * To get the official documentation, please visit http://unqlite.org/ + */ + /* + * Make the sure the following directive is defined in the amalgamation build. + */ + #ifndef UNQLITE_AMALGAMATION + #define UNQLITE_AMALGAMATION + #define JX9_AMALGAMATION + /* Marker for routines not intended for external use */ + #define JX9_PRIVATE static + #endif /* UNQLITE_AMALGAMATION */ +/* + * Embedded header file for unqlite: + */ +/* + * ---------------------------------------------------------- + * File: unqlite.h + * ---------------------------------------------------------- + */ +/* This file was automatically generated. Do not edit (Except for compile time directives)! */ +#ifndef _UNQLITE_H_ +#define _UNQLITE_H_ +/* + * Symisc UnQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2018, Symisc Systems http://unqlite.org/ + * Version 1.1.9 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ +/* + * Copyright (C) 2012, 2018 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* $SymiscID: unqlite.h v1.3 Win10 2108-04-27 02:35:11 stable $ */ +#include /* needed for the definition of va_list */ +/* + * Compile time engine version, signature, identification in the symisc source tree + * and copyright notice. + * Each macro have an equivalent C interface associated with it that provide the same + * information but are associated with the library instead of the header file. + * Refer to [unqlite_lib_version()], [unqlite_lib_signature()], [unqlite_lib_ident()] and + * [unqlite_lib_copyright()] for more information. + */ +/* + * The UNQLITE_VERSION C preprocessor macroevaluates to a string literal + * that is the unqlite version in the format "X.Y.Z" where X is the major + * version number and Y is the minor version number and Z is the release + * number. + */ +#define UNQLITE_VERSION "1.1.9" +/* + * The UNQLITE_VERSION_NUMBER C preprocessor macro resolves to an integer + * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same + * numbers used in [UNQLITE_VERSION]. + */ +#define UNQLITE_VERSION_NUMBER 1001009 +/* + * The UNQLITE_SIG C preprocessor macro evaluates to a string + * literal which is the public signature of the unqlite engine. + * This signature could be included for example in a host-application + * generated Server MIME header as follows: + * Server: YourWebServer/x.x unqlite/x.x.x \r\n + */ +#define UNQLITE_SIG "unqlite/1.1.9" +/* + * UnQLite identification in the Symisc source tree: + * Each particular check-in of a particular software released + * by symisc systems have an unique identifier associated with it. + * This macro hold the one associated with unqlite. + */ +#define UNQLITE_IDENT "unqlite:b172a1e2c3f62fb35c8e1fb2795121f82356cad6" +/* + * Copyright notice. + * If you have any questions about the licensing situation, please + * visit http://unqlite.org/licensing.html + * or contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + */ +#define UNQLITE_COPYRIGHT "Copyright (C) Symisc Systems, S.U.A.R.L [Mrad Chems Eddine ] 2012-2018, http://unqlite.org/" +/* Make sure we can call this stuff from C++ */ +#ifdef __cplusplus +extern "C" { +#endif +/* Forward declaration to public objects */ +typedef struct unqlite_io_methods unqlite_io_methods; +typedef struct unqlite_kv_methods unqlite_kv_methods; +typedef struct unqlite_kv_engine unqlite_kv_engine; +typedef struct jx9_io_stream unqlite_io_stream; +typedef struct jx9_context unqlite_context; +typedef struct jx9_value unqlite_value; +typedef struct unqlite_vfs unqlite_vfs; +typedef struct unqlite_vm unqlite_vm; +typedef struct unqlite unqlite; +/* + * ------------------------------ + * Compile time directives + * ------------------------------ + * For most purposes, UnQLite can be built just fine using the default compilation options. + * However, if required, the compile-time options documented below can be used to omit UnQLite + * features (resulting in a smaller compiled library size) or to change the default values + * of some parameters. + * Every effort has been made to ensure that the various combinations of compilation options + * work harmoniously and produce a working library. + * + * UNQLITE_ENABLE_THREADS + * This option controls whether or not code is included in UnQLite to enable it to operate + * safely in a multithreaded environment. The default is not. All mutexing code is omitted + * and it is unsafe to use UnQLite in a multithreaded program. When compiled with the + * UNQLITE_ENABLE_THREADS directive enabled, UnQLite can be used in a multithreaded program + * and it is safe to share the same virtual machine and engine handle between two or more threads. + * The value of UNQLITE_ENABLE_THREADS can be determined at run-time using the unqlite_lib_is_threadsafe() + * interface. + * When UnQLite has been compiled with threading support then the threading mode can be altered + * at run-time using the unqlite_lib_config() interface together with one of these verbs: + * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE + * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI + * Platforms others than Windows and UNIX systems must install their own mutex subsystem via + * unqlite_lib_config() with a configuration verb set to UNQLITE_LIB_CONFIG_USER_MUTEX. + * Otherwise the library is not threadsafe. + * Note that you must link UnQLite with the POSIX threads library under UNIX systems (i.e: -lpthread). + * + * Options To Omit/Enable Features + * + * The following options can be used to reduce the size of the compiled library by omitting optional + * features. This is probably only useful in embedded systems where space is especially tight, as even + * with all features included the UnQLite library is relatively small. Don't forget to tell your + * compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler + * to optimize for size usually has a much larger impact on library footprint than employing + * any of these compile-time options. + * + * JX9_DISABLE_BUILTIN_FUNC + * Jx9 is shipped with more than 312 built-in functions suitable for most purposes like + * string and INI processing, ZIP extracting, Base64 encoding/decoding, JSON encoding/decoding + * and so forth. + * If this directive is enabled, then all built-in Jx9 functions are omitted from the build. + * Note that special functions such as db_create(), db_store(), db_fetch(), etc. are not omitted + * from the build and are not affected by this directive. + * + * JX9_ENABLE_MATH_FUNC + * If this directive is enabled, built-in math functions such as sqrt(), abs(), log(), ceil(), etc. + * are included in the build. Note that you may need to link UnQLite with the math library in same + * Linux/BSD flavor (i.e: -lm). + * + * JX9_DISABLE_DISK_IO + * If this directive is enabled, built-in VFS functions such as chdir(), mkdir(), chroot(), unlink(), + * sleep(), etc. are omitted from the build. + * + * UNQLITE_ENABLE_JX9_HASH_IO + * If this directive is enabled, built-in hash functions such as md5(), sha1(), md5_file(), crc32(), etc. + * are included in the build. + */ +/* Symisc public definitions */ +#if !defined(SYMISC_STANDARD_DEFS) +#define SYMISC_STANDARD_DEFS +#if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE) +/* Windows Systems */ +#if !defined(__WINNT__) +#define __WINNT__ +#endif +/* + * Determine if we are dealing with WindowsCE - which has a much + * reduced API. + */ +#if defined(_WIN32_WCE) +#ifndef __WIN_CE__ +#define __WIN_CE__ +#endif /* __WIN_CE__ */ +#endif /* _WIN32_WCE */ +#else +/* + * By default we will assume that we are compiling on a UNIX systems. + * Otherwise the OS_OTHER directive must be defined. + */ +#if !defined(OS_OTHER) +#if !defined(__UNIXES__) +#define __UNIXES__ +#endif /* __UNIXES__ */ +#else +#endif /* OS_OTHER */ +#endif /* __WINNT__/__UNIXES__ */ +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#else +typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#endif /* _MSC_VER */ +/* Signature of the consumer routine */ +typedef int (*ProcConsumer)(const void *, unsigned int, void *); +/* Forward reference */ +typedef struct SyMutexMethods SyMutexMethods; +typedef struct SyMemMethods SyMemMethods; +typedef struct SyString SyString; +typedef struct syiovec syiovec; +typedef struct SyMutex SyMutex; +typedef struct Sytm Sytm; +/* Scatter and gather array. */ +struct syiovec +{ +#if defined (__WINNT__) + /* Same fields type and offset as WSABUF structure defined one winsock2 header */ + unsigned long nLen; + char *pBase; +#else + void *pBase; + unsigned long nLen; +#endif +}; +struct SyString +{ + const char *zString; /* Raw string (may not be null terminated) */ + unsigned int nByte; /* Raw string length */ +}; +/* Time structure. */ +struct Sytm +{ + int tm_sec; /* seconds (0 - 60) */ + int tm_min; /* minutes (0 - 59) */ + int tm_hour; /* hours (0 - 23) */ + int tm_mday; /* day of month (1 - 31) */ + int tm_mon; /* month of year (0 - 11) */ + int tm_year; /* year + 1900 */ + int tm_wday; /* day of week (Sunday = 0) */ + int tm_yday; /* day of year (0 - 365) */ + int tm_isdst; /* is summer time in effect? */ + char *tm_zone; /* abbreviation of timezone name */ + long tm_gmtoff; /* offset from UTC in seconds */ +}; +/* Convert a tm structure (struct tm *) found in to a Sytm structure */ +#define STRUCT_TM_TO_SYTM(pTM, pSYTM) \ + (pSYTM)->tm_hour = (pTM)->tm_hour;\ + (pSYTM)->tm_min = (pTM)->tm_min;\ + (pSYTM)->tm_sec = (pTM)->tm_sec;\ + (pSYTM)->tm_mon = (pTM)->tm_mon;\ + (pSYTM)->tm_mday = (pTM)->tm_mday;\ + (pSYTM)->tm_year = (pTM)->tm_year + 1900;\ + (pSYTM)->tm_yday = (pTM)->tm_yday;\ + (pSYTM)->tm_wday = (pTM)->tm_wday;\ + (pSYTM)->tm_isdst = (pTM)->tm_isdst;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_zone = 0; + +/* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */ +#define SYSTEMTIME_TO_SYTM(pSYSTIME, pSYTM) \ + (pSYTM)->tm_hour = (pSYSTIME)->wHour;\ + (pSYTM)->tm_min = (pSYSTIME)->wMinute;\ + (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\ + (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\ + (pSYTM)->tm_mday = (pSYSTIME)->wDay;\ + (pSYTM)->tm_year = (pSYSTIME)->wYear;\ + (pSYTM)->tm_yday = 0;\ + (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_isdst = -1;\ + (pSYTM)->tm_zone = 0; + +/* Dynamic memory allocation methods. */ +struct SyMemMethods +{ + void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ + void * (*xRealloc)(void *, unsigned int); /* [Required:] Re-allocate a memory chunk */ + void (*xFree)(void *); /* [Required:] Release a memory chunk */ + unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ + int (*xInit)(void *); /* [Optional:] Initialization callback */ + void (*xRelease)(void *); /* [Optional:] Release callback */ + void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */ +}; +/* Out of memory callback signature. */ +typedef int (*ProcMemError)(void *); +/* Mutex methods. */ +struct SyMutexMethods +{ + int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */ + void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */ + SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */ + void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */ + void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */ + int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */ + void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */ +}; +#if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec) +#define SX_APIIMPORT __declspec(dllimport) +#define SX_APIEXPORT __declspec(dllexport) +#else +#define SX_APIIMPORT +#define SX_APIEXPORT +#endif +/* Standard return values from Symisc public interfaces */ +#define SXRET_OK 0 /* Not an error */ +#define SXERR_MEM (-1) /* Out of memory */ +#define SXERR_IO (-2) /* IO error */ +#define SXERR_EMPTY (-3) /* Empty field */ +#define SXERR_LOCKED (-4) /* Locked operation */ +#define SXERR_ORANGE (-5) /* Out of range value */ +#define SXERR_NOTFOUND (-6) /* Item not found */ +#define SXERR_LIMIT (-7) /* Limit reached */ +#define SXERR_MORE (-8) /* Need more input */ +#define SXERR_INVALID (-9) /* Invalid parameter */ +#define SXERR_ABORT (-10) /* User callback request an operation abort */ +#define SXERR_EXISTS (-11) /* Item exists */ +#define SXERR_SYNTAX (-12) /* Syntax error */ +#define SXERR_UNKNOWN (-13) /* Unknown error */ +#define SXERR_BUSY (-14) /* Busy operation */ +#define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */ +#define SXERR_WILLBLOCK (-16) /* Operation will block */ +#define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */ +#define SXERR_EOF (-18) /* End of input */ +#define SXERR_PERM (-19) /* Permission error */ +#define SXERR_NOOP (-20) /* No-op */ +#define SXERR_FORMAT (-21) /* Invalid format */ +#define SXERR_NEXT (-22) /* Not an error */ +#define SXERR_OS (-23) /* System call return an error */ +#define SXERR_CORRUPT (-24) /* Corrupted pointer */ +#define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */ +#define SXERR_NOMATCH (-26) /* No match */ +#define SXERR_RESET (-27) /* Operation reset */ +#define SXERR_DONE (-28) /* Not an error */ +#define SXERR_SHORT (-29) /* Buffer too short */ +#define SXERR_PATH (-30) /* Path error */ +#define SXERR_TIMEOUT (-31) /* Timeout */ +#define SXERR_BIG (-32) /* Too big for processing */ +#define SXERR_RETRY (-33) /* Retry your call */ +#define SXERR_IGNORE (-63) /* Ignore */ +#endif /* SYMISC_PUBLIC_DEFS */ +/* + * Marker for exported interfaces. + */ +#define UNQLITE_APIEXPORT SX_APIEXPORT +/* + * If compiling for a processor that lacks floating point + * support, substitute integer for floating-point. + */ +#ifdef UNQLITE_OMIT_FLOATING_POINT +typedef sxi64 uqlite_real; +#else +typedef double unqlite_real; +#endif +typedef sxi64 unqlite_int64; +/* Standard UnQLite return values */ +#define UNQLITE_OK SXRET_OK /* Successful result */ +/* Beginning of error codes */ +#define UNQLITE_NOMEM SXERR_MEM /* Out of memory */ +#define UNQLITE_ABORT SXERR_ABORT /* Another thread have released this instance */ +#define UNQLITE_IOERR SXERR_IO /* IO error */ +#define UNQLITE_CORRUPT SXERR_CORRUPT /* Corrupt pointer */ +#define UNQLITE_LOCKED SXERR_LOCKED /* Forbidden Operation */ +#define UNQLITE_BUSY SXERR_BUSY /* The database file is locked */ +#define UNQLITE_DONE SXERR_DONE /* Operation done */ +#define UNQLITE_PERM SXERR_PERM /* Permission error */ +#define UNQLITE_NOTIMPLEMENTED SXERR_NOTIMPLEMENTED /* Method not implemented by the underlying Key/Value storage engine */ +#define UNQLITE_NOTFOUND SXERR_NOTFOUND /* No such record */ +#define UNQLITE_NOOP SXERR_NOOP /* No such method */ +#define UNQLITE_INVALID SXERR_INVALID /* Invalid parameter */ +#define UNQLITE_EOF SXERR_EOF /* End Of Input */ +#define UNQLITE_UNKNOWN SXERR_UNKNOWN /* Unknown configuration option */ +#define UNQLITE_LIMIT SXERR_LIMIT /* Database limit reached */ +#define UNQLITE_EXISTS SXERR_EXISTS /* Record exists */ +#define UNQLITE_EMPTY SXERR_EMPTY /* Empty record */ +#define UNQLITE_COMPILE_ERR (-70) /* Compilation error */ +#define UNQLITE_VM_ERR (-71) /* Virtual machine error */ +#define UNQLITE_FULL (-73) /* Full database (unlikely) */ +#define UNQLITE_CANTOPEN (-74) /* Unable to open the database file */ +#define UNQLITE_READ_ONLY (-75) /* Read only Key/Value storage engine */ +#define UNQLITE_LOCKERR (-76) /* Locking protocol error */ +/* end-of-error-codes */ +/* + * Database Handle Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure an UnQLite database handle. + * These constants must be passed as the second argument to [unqlite_config()]. + * + * Each options require a variable number of arguments. + * The [unqlite_config()] interface will return UNQLITE_OK on success, any other + * return value indicates failure. + * For a full discussion on the configuration verbs and their expected + * parameters, please refer to this page: + * http://unqlite.org/c_api/unqlite_config.html + */ +#define UNQLITE_CONFIG_JX9_ERR_LOG 1 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ +#define UNQLITE_CONFIG_MAX_PAGE_CACHE 2 /* ONE ARGUMENT: int nMaxPage */ +#define UNQLITE_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ +#define UNQLITE_CONFIG_KV_ENGINE 4 /* ONE ARGUMENT: const char *zKvName */ +#define UNQLITE_CONFIG_DISABLE_AUTO_COMMIT 5 /* NO ARGUMENTS */ +#define UNQLITE_CONFIG_GET_KV_NAME 6 /* ONE ARGUMENT: const char **pzPtr */ +/* + * UnQLite/Jx9 Virtual Machine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the Jx9 (Via UnQLite) Virtual machine. + * These constants must be passed as the second argument to the [unqlite_vm_config()] + * interface. + * Each options require a variable number of arguments. + * The [unqlite_vm_config()] interface will return UNQLITE_OK on success, any other return + * value indicates failure. + * There are many options but the most importants are: UNQLITE_VM_CONFIG_OUTPUT which install + * a VM output consumer callback, UNQLITE_VM_CONFIG_HTTP_REQUEST which parse and register + * a HTTP request and UNQLITE_VM_CONFIG_ARGV_ENTRY which populate the $argv array. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://unqlite.org/c_api/unqlite_vm_config.html + */ +#define UNQLITE_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ +#define UNQLITE_VM_CONFIG_IMPORT_PATH 2 /* ONE ARGUMENT: const char *zIncludePath */ +#define UNQLITE_VM_CONFIG_ERR_REPORT 3 /* NO ARGUMENTS: Report all run-time errors in the VM output */ +#define UNQLITE_VM_CONFIG_RECURSION_DEPTH 4 /* ONE ARGUMENT: int nMaxDepth */ +#define UNQLITE_VM_OUTPUT_LENGTH 5 /* ONE ARGUMENT: unsigned int *pLength */ +#define UNQLITE_VM_CONFIG_CREATE_VAR 6 /* TWO ARGUMENTS: const char *zName, unqlite_value *pValue */ +#define UNQLITE_VM_CONFIG_HTTP_REQUEST 7 /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ +#define UNQLITE_VM_CONFIG_SERVER_ATTR 8 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ +#define UNQLITE_VM_CONFIG_ENV_ATTR 9 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ +#define UNQLITE_VM_CONFIG_EXEC_VALUE 10 /* ONE ARGUMENT: unqlite_value **ppValue */ +#define UNQLITE_VM_CONFIG_IO_STREAM 11 /* ONE ARGUMENT: const unqlite_io_stream *pStream */ +#define UNQLITE_VM_CONFIG_ARGV_ENTRY 12 /* ONE ARGUMENT: const char *zValue */ +#define UNQLITE_VM_CONFIG_EXTRACT_OUTPUT 13 /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ +/* + * Storage engine configuration commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the underlying storage engine (i.e Hash, B+tree, R+tree). + * These constants must be passed as the first argument to [unqlite_kv_config()]. + * Each options require a variable number of arguments. + * The [unqlite_kv_config()] interface will return UNQLITE_OK on success, any other return + * value indicates failure. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://unqlite.org/c_api/unqlite_kv_config.html + */ +#define UNQLITE_KV_CONFIG_HASH_FUNC 1 /* ONE ARGUMENT: unsigned int (*xHash)(const void *,unsigned int) */ +#define UNQLITE_KV_CONFIG_CMP_FUNC 2 /* ONE ARGUMENT: int (*xCmp)(const void *,const void *,unsigned int) */ +/* + * Global Library Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the whole library. + * These constants must be passed as the first argument to [unqlite_lib_config()]. + * + * Each options require a variable number of arguments. + * The [unqlite_lib_config()] interface will return UNQLITE_OK on success, any other return + * value indicates failure. + * Notes: + * The default configuration is recommended for most applications and so the call to + * [unqlite_lib_config()] is usually not necessary. It is provided to support rare + * applications with unusual needs. + * The [unqlite_lib_config()] interface is not threadsafe. The application must insure that + * no other [unqlite_*()] interfaces are invoked by other threads while [unqlite_lib_config()] + * is running. Furthermore, [unqlite_lib_config()] may only be invoked prior to library + * initialization using [unqlite_lib_init()] or [unqlite_init()] or after shutdown + * by [unqlite_lib_shutdown()]. If [unqlite_lib_config()] is called after [unqlite_lib_init()] + * or [unqlite_init()] and before [unqlite_lib_shutdown()] then it will return UNQLITE_LOCKED. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://unqlite.org/c_api/unqlite_lib.html + */ +#define UNQLITE_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ +#define UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ +#define UNQLITE_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ +#define UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ +#define UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ +#define UNQLITE_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const unqlite_vfs *pVfs */ +#define UNQLITE_LIB_CONFIG_STORAGE_ENGINE 7 /* ONE ARGUMENT: unqlite_kv_methods *pStorage */ +#define UNQLITE_LIB_CONFIG_PAGE_SIZE 8 /* ONE ARGUMENT: int iPageSize */ +/* + * These bit values are intended for use in the 3rd parameter to the [unqlite_open()] interface + * and in the 4th parameter to the xOpen method of the [unqlite_vfs] object. + */ +#define UNQLITE_OPEN_READONLY 0x00000001 /* Read only mode. Ok for [unqlite_open] */ +#define UNQLITE_OPEN_READWRITE 0x00000002 /* Ok for [unqlite_open] */ +#define UNQLITE_OPEN_CREATE 0x00000004 /* Ok for [unqlite_open] */ +#define UNQLITE_OPEN_EXCLUSIVE 0x00000008 /* VFS only */ +#define UNQLITE_OPEN_TEMP_DB 0x00000010 /* VFS only */ +#define UNQLITE_OPEN_NOMUTEX 0x00000020 /* Ok for [unqlite_open] */ +#define UNQLITE_OPEN_OMIT_JOURNALING 0x00000040 /* Omit journaling for this database. Ok for [unqlite_open] */ +#define UNQLITE_OPEN_IN_MEMORY 0x00000080 /* An in memory database. Ok for [unqlite_open]*/ +#define UNQLITE_OPEN_MMAP 0x00000100 /* Obtain a memory view of the whole file. Ok for [unqlite_open] */ +/* + * Synchronization Type Flags + * + * When UnQLite invokes the xSync() method of an [unqlite_io_methods] object it uses + * a combination of these integer values as the second argument. + * + * When the UNQLITE_SYNC_DATAONLY flag is used, it means that the sync operation only + * needs to flush data to mass storage. Inode information need not be flushed. + * If the lower four bits of the flag equal UNQLITE_SYNC_NORMAL, that means to use normal + * fsync() semantics. If the lower four bits equal UNQLITE_SYNC_FULL, that means to use + * Mac OS X style fullsync instead of fsync(). + */ +#define UNQLITE_SYNC_NORMAL 0x00002 +#define UNQLITE_SYNC_FULL 0x00003 +#define UNQLITE_SYNC_DATAONLY 0x00010 +/* + * File Locking Levels + * + * UnQLite uses one of these integer values as the second + * argument to calls it makes to the xLock() and xUnlock() methods + * of an [unqlite_io_methods] object. + */ +#define UNQLITE_LOCK_NONE 0 +#define UNQLITE_LOCK_SHARED 1 +#define UNQLITE_LOCK_RESERVED 2 +#define UNQLITE_LOCK_PENDING 3 +#define UNQLITE_LOCK_EXCLUSIVE 4 +/* + * CAPIREF: OS Interface: Open File Handle + * + * An [unqlite_file] object represents an open file in the [unqlite_vfs] OS interface + * layer. + * Individual OS interface implementations will want to subclass this object by appending + * additional fields for their own use. The pMethods entry is a pointer to an + * [unqlite_io_methods] object that defines methods for performing + * I/O operations on the open file. +*/ +typedef struct unqlite_file unqlite_file; +struct unqlite_file { + const unqlite_io_methods *pMethods; /* Methods for an open file. MUST BE FIRST */ +}; +/* + * CAPIREF: OS Interface: File Methods Object + * + * Every file opened by the [unqlite_vfs] xOpen method populates an + * [unqlite_file] object (or, more commonly, a subclass of the + * [unqlite_file] object) with a pointer to an instance of this object. + * This object defines the methods used to perform various operations + * against the open file represented by the [unqlite_file] object. + * + * If the xOpen method sets the unqlite_file.pMethods element + * to a non-NULL pointer, then the unqlite_io_methods.xClose method + * may be invoked even if the xOpen reported that it failed. The + * only way to prevent a call to xClose following a failed xOpen + * is for the xOpen to set the unqlite_file.pMethods element to NULL. + * + * The flags argument to xSync may be one of [UNQLITE_SYNC_NORMAL] or + * [UNQLITE_SYNC_FULL]. The first choice is the normal fsync(). + * The second choice is a Mac OS X style fullsync. The [UNQLITE_SYNC_DATAONLY] + * flag may be ORed in to indicate that only the data of the file + * and not its inode needs to be synced. + * + * The integer values to xLock() and xUnlock() are one of + * + * UNQLITE_LOCK_NONE + * UNQLITE_LOCK_SHARED + * UNQLITE_LOCK_RESERVED + * UNQLITE_LOCK_PENDING + * UNQLITE_LOCK_EXCLUSIVE + * + * xLock() increases the lock. xUnlock() decreases the lock. + * The xCheckReservedLock() method checks whether any database connection, + * either in this process or in some other process, is holding a RESERVED, + * PENDING, or EXCLUSIVE lock on the file. It returns true if such a lock exists + * and false otherwise. + * + * The xSectorSize() method returns the sector size of the device that underlies + * the file. The sector size is the minimum write that can be performed without + * disturbing other bytes in the file. + * + */ +struct unqlite_io_methods { + int iVersion; /* Structure version number (currently 1) */ + int (*xClose)(unqlite_file*); + int (*xRead)(unqlite_file*, void*, unqlite_int64 iAmt, unqlite_int64 iOfst); + int (*xWrite)(unqlite_file*, const void*, unqlite_int64 iAmt, unqlite_int64 iOfst); + int (*xTruncate)(unqlite_file*, unqlite_int64 size); + int (*xSync)(unqlite_file*, int flags); + int (*xFileSize)(unqlite_file*, unqlite_int64 *pSize); + int (*xLock)(unqlite_file*, int); + int (*xUnlock)(unqlite_file*, int); + int (*xCheckReservedLock)(unqlite_file*, int *pResOut); + int (*xSectorSize)(unqlite_file*); +}; +/* + * CAPIREF: OS Interface Object + * + * An instance of the unqlite_vfs object defines the interface between + * the UnQLite core and the underlying operating system. The "vfs" + * in the name of the object stands for "Virtual File System". + * + * Only a single vfs can be registered within the UnQLite core. + * Vfs registration is done using the [unqlite_lib_config()] interface + * with a configuration verb set to UNQLITE_LIB_CONFIG_VFS. + * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users + * does not have to worry about registering and installing a vfs since UnQLite + * come with a built-in vfs for these platforms that implements most the methods + * defined below. + * + * Clients running on exotic systems (ie: Other than Windows and UNIX systems) + * must register their own vfs in order to be able to use the UnQLite library. + * + * The value of the iVersion field is initially 1 but may be larger in + * future versions of UnQLite. + * + * The szOsFile field is the size of the subclassed [unqlite_file] structure + * used by this VFS. mxPathname is the maximum length of a pathname in this VFS. + * + * At least szOsFile bytes of memory are allocated by UnQLite to hold the [unqlite_file] + * structure passed as the third argument to xOpen. The xOpen method does not have to + * allocate the structure; it should just fill it in. Note that the xOpen method must + * set the unqlite_file.pMethods to either a valid [unqlite_io_methods] object or to NULL. + * xOpen must do this even if the open fails. UnQLite expects that the unqlite_file.pMethods + * element will be valid after xOpen returns regardless of the success or failure of the + * xOpen call. + * + */ +struct unqlite_vfs { + const char *zName; /* Name of this virtual file system [i.e: Windows, UNIX, etc.] */ + int iVersion; /* Structure version number (currently 1) */ + int szOsFile; /* Size of subclassed unqlite_file */ + int mxPathname; /* Maximum file pathname length */ + int (*xOpen)(unqlite_vfs*, const char *zName, unqlite_file*,unsigned int flags); + int (*xDelete)(unqlite_vfs*, const char *zName, int syncDir); + int (*xAccess)(unqlite_vfs*, const char *zName, int flags, int *pResOut); + int (*xFullPathname)(unqlite_vfs*, const char *zName,int buf_len,char *zBuf); + int (*xTmpDir)(unqlite_vfs*,char *zBuf,int buf_len); + int (*xSleep)(unqlite_vfs*, int microseconds); + int (*xCurrentTime)(unqlite_vfs*,Sytm *pOut); + int (*xGetLastError)(unqlite_vfs*, int, char *); +}; +/* + * Flags for the xAccess VFS method + * + * These integer constants can be used as the third parameter to + * the xAccess method of an [unqlite_vfs] object. They determine + * what kind of permissions the xAccess method is looking for. + * With UNQLITE_ACCESS_EXISTS, the xAccess method + * simply checks whether the file exists. + * With UNQLITE_ACCESS_READWRITE, the xAccess method + * checks whether the named directory is both readable and writable + * (in other words, if files can be added, removed, and renamed within + * the directory). + * The UNQLITE_ACCESS_READWRITE constant is currently used only by the + * [temp_store_directory pragma], though this could change in a future + * release of UnQLite. + * With UNQLITE_ACCESS_READ, the xAccess method + * checks whether the file is readable. The UNQLITE_ACCESS_READ constant is + * currently unused, though it might be used in a future release of + * UnQLite. + */ +#define UNQLITE_ACCESS_EXISTS 0 +#define UNQLITE_ACCESS_READWRITE 1 +#define UNQLITE_ACCESS_READ 2 +/* + * The type used to represent a page number. The first page in a file + * is called page 1. 0 is used to represent "not a page". + * A page number is an unsigned 64-bit integer. + */ +typedef sxu64 pgno; +/* + * A database disk page is represented by an instance + * of the follwoing structure. + */ +typedef struct unqlite_page unqlite_page; +struct unqlite_page +{ + unsigned char *zData; /* Content of this page */ + void *pUserData; /* Extra content */ + pgno pgno; /* Page number for this page */ +}; +/* + * UnQLite handle to the underlying Key/Value Storage Engine (See below). + */ +typedef void * unqlite_kv_handle; +/* + * UnQLite pager IO methods. + * + * An instance of the following structure define the exported methods of the UnQLite pager + * to the underlying Key/Value storage engine. + */ +typedef struct unqlite_kv_io unqlite_kv_io; +struct unqlite_kv_io +{ + unqlite_kv_handle pHandle; /* UnQLite handle passed as the first parameter to the + * method defined below. + */ + unqlite_kv_methods *pMethods; /* Underlying storage engine */ + /* Pager methods */ + int (*xGet)(unqlite_kv_handle,pgno,unqlite_page **); + int (*xLookup)(unqlite_kv_handle,pgno,unqlite_page **); + int (*xNew)(unqlite_kv_handle,unqlite_page **); + int (*xWrite)(unqlite_page *); + int (*xDontWrite)(unqlite_page *); + int (*xDontJournal)(unqlite_page *); + int (*xDontMkHot)(unqlite_page *); + int (*xPageRef)(unqlite_page *); + int (*xPageUnref)(unqlite_page *); + int (*xPageSize)(unqlite_kv_handle); + int (*xReadOnly)(unqlite_kv_handle); + unsigned char * (*xTmpPage)(unqlite_kv_handle); + void (*xSetUnpin)(unqlite_kv_handle,void (*xPageUnpin)(void *)); + void (*xSetReload)(unqlite_kv_handle,void (*xPageReload)(void *)); + void (*xErr)(unqlite_kv_handle,const char *); +}; +/* + * Key/Value Storage Engine Cursor Object + * + * An instance of a subclass of the following object defines a cursor + * used to scan through a key-value storage engine. + */ +typedef struct unqlite_kv_cursor unqlite_kv_cursor; +struct unqlite_kv_cursor +{ + unqlite_kv_engine *pStore; /* Must be first */ + /* Subclasses will typically add additional fields */ +}; +/* + * Possible seek positions. + */ +#define UNQLITE_CURSOR_MATCH_EXACT 1 +#define UNQLITE_CURSOR_MATCH_LE 2 +#define UNQLITE_CURSOR_MATCH_GE 3 +/* + * Key/Value Storage Engine. + * + * A Key-Value storage engine is defined by an instance of the following + * object. + * UnQLite works with run-time interchangeable storage engines (i.e. Hash, B+Tree, R+Tree, LSM, etc.). + * The storage engine works with key/value pairs where both the key + * and the value are byte arrays of arbitrary length and with no restrictions on content. + * UnQLite come with two built-in KV storage engine: A Virtual Linear Hash (VLH) storage + * engine is used for persistent on-disk databases with O(1) lookup time and an in-memory + * hash-table or Red-black tree storage engine is used for in-memory databases. + * Future versions of UnQLite might add other built-in storage engines (i.e. LSM). + * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] + * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. + */ +struct unqlite_kv_engine +{ + const unqlite_kv_io *pIo; /* IO methods: MUST be first */ + /* Subclasses will typically add additional fields */ +}; +/* + * Key/Value Storage Engine Virtual Method Table. + * + * Key/Value storage engine methods is defined by an instance of the following + * object. + * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] + * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. + */ +struct unqlite_kv_methods +{ + const char *zName; /* Storage engine name [i.e. Hash, B+tree, LSM, R-tree, Mem, etc.]*/ + int szKv; /* 'unqlite_kv_engine' subclass size */ + int szCursor; /* 'unqlite_kv_cursor' subclass size */ + int iVersion; /* Structure version, currently 1 */ + /* Storage engine methods */ + int (*xInit)(unqlite_kv_engine *,int iPageSize); + void (*xRelease)(unqlite_kv_engine *); + int (*xConfig)(unqlite_kv_engine *,int op,va_list ap); + int (*xOpen)(unqlite_kv_engine *,pgno); + int (*xReplace)( + unqlite_kv_engine *, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ); + int (*xAppend)( + unqlite_kv_engine *, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ); + void (*xCursorInit)(unqlite_kv_cursor *); + int (*xSeek)(unqlite_kv_cursor *,const void *pKey,int nByte,int iPos); /* Mandatory */ + int (*xFirst)(unqlite_kv_cursor *); + int (*xLast)(unqlite_kv_cursor *); + int (*xValid)(unqlite_kv_cursor *); + int (*xNext)(unqlite_kv_cursor *); + int (*xPrev)(unqlite_kv_cursor *); + int (*xDelete)(unqlite_kv_cursor *); + int (*xKeyLength)(unqlite_kv_cursor *,int *); + int (*xKey)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); + int (*xDataLength)(unqlite_kv_cursor *,unqlite_int64 *); + int (*xData)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); + void (*xReset)(unqlite_kv_cursor *); + void (*xCursorRelease)(unqlite_kv_cursor *); +}; +/* + * UnQLite journal file suffix. + */ +#ifndef UNQLITE_JOURNAL_FILE_SUFFIX +#define UNQLITE_JOURNAL_FILE_SUFFIX "_unqlite_journal" +#endif +/* + * Call Context - Error Message Serverity Level. + * + * The following constans are the allowed severity level that can + * passed as the second argument to the [unqlite_context_throw_error()] or + * [unqlite_context_throw_error_format()] interfaces. + * Refer to the official documentation for additional information. + */ +#define UNQLITE_CTX_ERR 1 /* Call context error such as unexpected number of arguments, invalid types and so on. */ +#define UNQLITE_CTX_WARNING 2 /* Call context Warning */ +#define UNQLITE_CTX_NOTICE 3 /* Call context Notice */ +/* + * C-API-REF: Please refer to the official documentation for interfaces + * purpose and expected parameters. + */ + +/* Database Engine Handle */ +UNQLITE_APIEXPORT int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode); +UNQLITE_APIEXPORT int unqlite_config(unqlite *pDb,int nOp,...); +UNQLITE_APIEXPORT int unqlite_close(unqlite *pDb); + +/* Key/Value (KV) Store Interfaces */ +UNQLITE_APIEXPORT int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); +UNQLITE_APIEXPORT int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); +UNQLITE_APIEXPORT int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); +UNQLITE_APIEXPORT int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); +UNQLITE_APIEXPORT int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 /* in|out */*pBufLen); +UNQLITE_APIEXPORT int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey, + int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen); +UNQLITE_APIEXPORT int unqlite_kv_config(unqlite *pDb,int iOp,...); + +/* Document (JSON) Store Interfaces powered by the Jx9 Scripting Language */ +UNQLITE_APIEXPORT int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut); +UNQLITE_APIEXPORT int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut); +UNQLITE_APIEXPORT int unqlite_vm_config(unqlite_vm *pVm,int iOp,...); +UNQLITE_APIEXPORT int unqlite_vm_exec(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_reset(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_release(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData); +UNQLITE_APIEXPORT unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname); + +/* Cursor Iterator Interfaces */ +UNQLITE_APIEXPORT int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut); +UNQLITE_APIEXPORT int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur); +UNQLITE_APIEXPORT int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos); +UNQLITE_APIEXPORT int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte); +UNQLITE_APIEXPORT int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnData); +UNQLITE_APIEXPORT int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor); + +/* Manual Transaction Manager */ +UNQLITE_APIEXPORT int unqlite_begin(unqlite *pDb); +UNQLITE_APIEXPORT int unqlite_commit(unqlite *pDb); +UNQLITE_APIEXPORT int unqlite_rollback(unqlite *pDb); + +/* Utility interfaces */ +UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize); +UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize); +UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size); +UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb); + +/* In-process extending interfaces */ +UNQLITE_APIEXPORT int unqlite_create_function(unqlite_vm *pVm,const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData); +UNQLITE_APIEXPORT int unqlite_delete_function(unqlite_vm *pVm, const char *zName); +UNQLITE_APIEXPORT int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_delete_constant(unqlite_vm *pVm, const char *zName); + +/* On Demand Object allocation interfaces */ +UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm); +UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue); +UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx); +UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_array(unqlite_context *pCtx); +UNQLITE_APIEXPORT void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue); + +/* Dynamically Typed Value Object Management Interfaces */ +UNQLITE_APIEXPORT int unqlite_value_int(unqlite_value *pVal, int iValue); +UNQLITE_APIEXPORT int unqlite_value_int64(unqlite_value *pVal, unqlite_int64 iValue); +UNQLITE_APIEXPORT int unqlite_value_bool(unqlite_value *pVal, int iBool); +UNQLITE_APIEXPORT int unqlite_value_null(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_double(unqlite_value *pVal, double Value); +UNQLITE_APIEXPORT int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen); +UNQLITE_APIEXPORT int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...); +UNQLITE_APIEXPORT int unqlite_value_reset_string_cursor(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_resource(unqlite_value *pVal, void *pUserData); +UNQLITE_APIEXPORT int unqlite_value_release(unqlite_value *pVal); + +/* Foreign Function Parameter Values */ +UNQLITE_APIEXPORT int unqlite_value_to_int(unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_value_to_bool(unqlite_value *pValue); +UNQLITE_APIEXPORT unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue); +UNQLITE_APIEXPORT double unqlite_value_to_double(unqlite_value *pValue); +UNQLITE_APIEXPORT const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen); +UNQLITE_APIEXPORT void * unqlite_value_to_resource(unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict); + +/* Setting The Result Of A Foreign Function */ +UNQLITE_APIEXPORT int unqlite_result_int(unqlite_context *pCtx, int iValue); +UNQLITE_APIEXPORT int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue); +UNQLITE_APIEXPORT int unqlite_result_bool(unqlite_context *pCtx, int iBool); +UNQLITE_APIEXPORT int unqlite_result_double(unqlite_context *pCtx, double Value); +UNQLITE_APIEXPORT int unqlite_result_null(unqlite_context *pCtx); +UNQLITE_APIEXPORT int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen); +UNQLITE_APIEXPORT int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...); +UNQLITE_APIEXPORT int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_result_resource(unqlite_context *pCtx, void *pUserData); + +/* Dynamically Typed Value Object Query Interfaces */ +UNQLITE_APIEXPORT int unqlite_value_is_int(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_float(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_bool(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_string(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_null(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_numeric(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_callable(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_scalar(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_json_array(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_json_object(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_resource(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_empty(unqlite_value *pVal); + +/* JSON Array/Object Management Interfaces */ +UNQLITE_APIEXPORT unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte); +UNQLITE_APIEXPORT int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData); +UNQLITE_APIEXPORT int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_array_count(unqlite_value *pArray); + +/* Call Context Handling Interfaces */ +UNQLITE_APIEXPORT int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen); +UNQLITE_APIEXPORT int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...); +UNQLITE_APIEXPORT int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr); +UNQLITE_APIEXPORT int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...); +UNQLITE_APIEXPORT unsigned int unqlite_context_random_num(unqlite_context *pCtx); +UNQLITE_APIEXPORT int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen); +UNQLITE_APIEXPORT void * unqlite_context_user_data(unqlite_context *pCtx); +UNQLITE_APIEXPORT int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData); +UNQLITE_APIEXPORT void * unqlite_context_peek_aux_data(unqlite_context *pCtx); +UNQLITE_APIEXPORT unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx); +UNQLITE_APIEXPORT const char * unqlite_function_name(unqlite_context *pCtx); + +/* Call Context Memory Management Interfaces */ +UNQLITE_APIEXPORT void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); +UNQLITE_APIEXPORT void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte); +UNQLITE_APIEXPORT void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk); + +/* Global Library Management Interfaces */ +UNQLITE_APIEXPORT int unqlite_lib_config(int nConfigOp,...); +UNQLITE_APIEXPORT int unqlite_lib_init(void); +UNQLITE_APIEXPORT int unqlite_lib_shutdown(void); +UNQLITE_APIEXPORT int unqlite_lib_is_threadsafe(void); +UNQLITE_APIEXPORT const char * unqlite_lib_version(void); +UNQLITE_APIEXPORT const char * unqlite_lib_signature(void); +UNQLITE_APIEXPORT const char * unqlite_lib_ident(void); +UNQLITE_APIEXPORT const char * unqlite_lib_copyright(void); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _UNQLITE_H_ */ +/* + * ---------------------------------------------------------- + * File: jx9.h + * MD5: d23a1e182f596794001533e1d6aa16a0 + * ---------------------------------------------------------- + */ +/* This file was automatically generated. Do not edit (except for compile time directive)! */ +#ifndef _JX9H_ +#define _JX9H_ +/* + * Symisc Jx9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ +/* + * Copyright (C) 2012, 2013 Symisc Systems. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Redistributions in any form must be accompanied by information on + * how to obtain complete source code for the JX9 engine and any + * accompanying software that uses the JX9 engine software. + * The source code must either be included in the distribution + * or be available for no more than the cost of distribution plus + * a nominal fee, and must be freely redistributable under reasonable + * conditions. For an executable file, complete source code means + * the source code for all modules it contains.It does not include + * source code for modules or files that typically accompany the major + * components of the operating system on which the executable file runs. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* $SymiscID: jx9.h v2.1 UNIX|WIN32/64 2012-09-15 09:43 stable $ */ +#include "unqlite.h" +/* + * Compile time engine version, signature, identification in the symisc source tree + * and copyright notice. + * Each macro have an equivalent C interface associated with it that provide the same + * information but are associated with the library instead of the header file. + * Refer to [jx9_lib_version()], [jx9_lib_signature()], [jx9_lib_ident()] and + * [jx9_lib_copyright()] for more information. + */ +/* + * The JX9_VERSION C preprocessor macroevaluates to a string literal + * that is the jx9 version in the format "X.Y.Z" where X is the major + * version number and Y is the minor version number and Z is the release + * number. + */ +#define JX9_VERSION "1.7.2" +/* + * The JX9_VERSION_NUMBER C preprocessor macro resolves to an integer + * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same + * numbers used in [JX9_VERSION]. + */ +#define JX9_VERSION_NUMBER 1007002 +/* + * The JX9_SIG C preprocessor macro evaluates to a string + * literal which is the public signature of the jx9 engine. + * This signature could be included for example in a host-application + * generated Server MIME header as follows: + * Server: YourWebServer/x.x Jx9/x.x.x \r\n + */ +#define JX9_SIG "Jx9/1.7.2" +/* + * JX9 identification in the Symisc source tree: + * Each particular check-in of a particular software released + * by symisc systems have an unique identifier associated with it. + * This macro hold the one associated with jx9. + */ +#define JX9_IDENT "jx9:d217a6e8c7f10fb35a8becb2793101fd2036aeb7" +/* + * Copyright notice. + * If you have any questions about the licensing situation, please + * visit http://jx9.symisc.net/licensing.html + * or contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + */ +#define JX9_COPYRIGHT "Copyright (C) Symisc Systems 2012-2013, http://jx9.symisc.net/" + +/* Forward declaration to public objects */ +typedef struct jx9_io_stream jx9_io_stream; +typedef struct jx9_context jx9_context; +typedef struct jx9_value jx9_value; +typedef struct jx9_vfs jx9_vfs; +typedef struct jx9_vm jx9_vm; +typedef struct jx9 jx9; + +#include "unqlite.h" + +#if !defined( UNQLITE_ENABLE_JX9_HASH_FUNC ) +#define JX9_DISABLE_HASH_FUNC +#endif /* UNQLITE_ENABLE_JX9_HASH_FUNC */ +#ifdef UNQLITE_ENABLE_THREADS +#define JX9_ENABLE_THREADS +#endif /* UNQLITE_ENABLE_THREADS */ +/* Standard JX9 return values */ +#define JX9_OK SXRET_OK /* Successful result */ +/* beginning-of-error-codes */ +#define JX9_NOMEM UNQLITE_NOMEM /* Out of memory */ +#define JX9_ABORT UNQLITE_ABORT /* Foreign Function request operation abort/Another thread have released this instance */ +#define JX9_IO_ERR UNQLITE_IOERR /* IO error */ +#define JX9_CORRUPT UNQLITE_CORRUPT /* Corrupt pointer/Unknown configuration option */ +#define JX9_LOOKED UNQLITE_LOCKED /* Forbidden Operation */ +#define JX9_COMPILE_ERR UNQLITE_COMPILE_ERR /* Compilation error */ +#define JX9_VM_ERR UNQLITE_VM_ERR /* Virtual machine error */ +/* end-of-error-codes */ +/* + * If compiling for a processor that lacks floating point + * support, substitute integer for floating-point. + */ +#ifdef JX9_OMIT_FLOATING_POINT +typedef sxi64 jx9_real; +#else +typedef double jx9_real; +#endif +typedef sxi64 jx9_int64; +/* + * Engine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the JX9 engine. + * These constants must be passed as the second argument to the [jx9_config()] + * interface. + * Each options require a variable number of arguments. + * The [jx9_config()] interface will return JX9_OK on success, any other + * return value indicates failure. + * For a full discussion on the configuration verbs and their expected + * parameters, please refer to this page: + * http://jx9.symisc.net/c_api_func.html#jx9_config + */ +#define JX9_CONFIG_ERR_ABORT 1 /* RESERVED FOR FUTURE USE */ +#define JX9_CONFIG_ERR_LOG 2 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ +/* + * Virtual Machine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the JX9 Virtual machine. + * These constants must be passed as the second argument to the [jx9_vm_config()] + * interface. + * Each options require a variable number of arguments. + * The [jx9_vm_config()] interface will return JX9_OK on success, any other return + * value indicates failure. + * There are many options but the most importants are: JX9_VM_CONFIG_OUTPUT which install + * a VM output consumer callback, JX9_VM_CONFIG_HTTP_REQUEST which parse and register + * a HTTP request and JX9_VM_CONFIG_ARGV_ENTRY which populate the $argv array. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://jx9.symisc.net/c_api_func.html#jx9_vm_config + */ +#define JX9_VM_CONFIG_OUTPUT UNQLITE_VM_CONFIG_OUTPUT /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ +#define JX9_VM_CONFIG_IMPORT_PATH UNQLITE_VM_CONFIG_IMPORT_PATH /* ONE ARGUMENT: const char *zIncludePath */ +#define JX9_VM_CONFIG_ERR_REPORT UNQLITE_VM_CONFIG_ERR_REPORT /* NO ARGUMENTS: Report all run-time errors in the VM output */ +#define JX9_VM_CONFIG_RECURSION_DEPTH UNQLITE_VM_CONFIG_RECURSION_DEPTH /* ONE ARGUMENT: int nMaxDepth */ +#define JX9_VM_OUTPUT_LENGTH UNQLITE_VM_OUTPUT_LENGTH /* ONE ARGUMENT: unsigned int *pLength */ +#define JX9_VM_CONFIG_CREATE_VAR UNQLITE_VM_CONFIG_CREATE_VAR /* TWO ARGUMENTS: const char *zName, jx9_value *pValue */ +#define JX9_VM_CONFIG_HTTP_REQUEST UNQLITE_VM_CONFIG_HTTP_REQUEST /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ +#define JX9_VM_CONFIG_SERVER_ATTR UNQLITE_VM_CONFIG_SERVER_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ +#define JX9_VM_CONFIG_ENV_ATTR UNQLITE_VM_CONFIG_ENV_ATTR /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ +#define JX9_VM_CONFIG_EXEC_VALUE UNQLITE_VM_CONFIG_EXEC_VALUE /* ONE ARGUMENT: jx9_value **ppValue */ +#define JX9_VM_CONFIG_IO_STREAM UNQLITE_VM_CONFIG_IO_STREAM /* ONE ARGUMENT: const jx9_io_stream *pStream */ +#define JX9_VM_CONFIG_ARGV_ENTRY UNQLITE_VM_CONFIG_ARGV_ENTRY /* ONE ARGUMENT: const char *zValue */ +#define JX9_VM_CONFIG_EXTRACT_OUTPUT UNQLITE_VM_CONFIG_EXTRACT_OUTPUT /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ +/* + * Global Library Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the whole library. + * These constants must be passed as the first argument to the [jx9_lib_config()] + * interface. + * Each options require a variable number of arguments. + * The [jx9_lib_config()] interface will return JX9_OK on success, any other return + * value indicates failure. + * Notes: + * The default configuration is recommended for most applications and so the call to + * [jx9_lib_config()] is usually not necessary. It is provided to support rare + * applications with unusual needs. + * The [jx9_lib_config()] interface is not threadsafe. The application must insure that + * no other [jx9_*()] interfaces are invoked by other threads while [jx9_lib_config()] + * is running. Furthermore, [jx9_lib_config()] may only be invoked prior to library + * initialization using [jx9_lib_init()] or [jx9_init()] or after shutdown + * by [jx9_lib_shutdown()]. If [jx9_lib_config()] is called after [jx9_lib_init()] + * or [jx9_init()] and before [jx9_lib_shutdown()] then it will return jx9LOCKED. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://jx9.symisc.net/c_api_func.html#Global_Library_Management_Interfaces + */ +#define JX9_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ +#define JX9_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ +#define JX9_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ +#define JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ +#define JX9_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ +#define JX9_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const jx9_vfs *pVfs */ +/* + * Call Context - Error Message Serverity Level. + */ +#define JX9_CTX_ERR UNQLITE_CTX_ERR /* Call context error such as unexpected number of arguments, invalid types and so on. */ +#define JX9_CTX_WARNING UNQLITE_CTX_WARNING /* Call context Warning */ +#define JX9_CTX_NOTICE UNQLITE_CTX_NOTICE /* Call context Notice */ +/* Current VFS structure version*/ +#define JX9_VFS_VERSION 2 +/* + * JX9 Virtual File System (VFS). + * + * An instance of the jx9_vfs object defines the interface between the JX9 core + * and the underlying operating system. The "vfs" in the name of the object stands + * for "virtual file system". The vfs is used to implement JX9 system functions + * such as mkdir(), chdir(), stat(), get_user_name() and many more. + * The value of the iVersion field is initially 2 but may be larger in future versions + * of JX9. + * Additional fields may be appended to this object when the iVersion value is increased. + * Only a single vfs can be registered within the JX9 core. Vfs registration is done + * using the jx9_lib_config() interface with a configuration verb set to JX9_LIB_CONFIG_VFS. + * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users does not have to + * worry about registering and installing a vfs since JX9 come with a built-in vfs for these + * platforms which implement most the methods defined below. + * Host-application running on exotic systems (ie: Other than Windows and UNIX systems) must + * register their own vfs in order to be able to use and call JX9 system functions. + * Also note that the jx9_compile_file() interface depend on the xMmap() method of the underlying + * vfs which mean that this method must be available (Always the case using the built-in VFS) + * in order to use this interface. + * Developers wishing to implement their own vfs an contact symisc systems to obtain + * the JX9 VFS C/C++ Specification manual. + */ +struct jx9_vfs +{ + const char *zName; /* Underlying VFS name [i.e: FreeBSD/Linux/Windows...] */ + int iVersion; /* Current VFS structure version [default 2] */ + /* Directory functions */ + int (*xChdir)(const char *); /* Change directory */ + int (*xChroot)(const char *); /* Change the root directory */ + int (*xGetcwd)(jx9_context *); /* Get the current working directory */ + int (*xMkdir)(const char *, int, int); /* Make directory */ + int (*xRmdir)(const char *); /* Remove directory */ + int (*xIsdir)(const char *); /* Tells whether the filename is a directory */ + int (*xRename)(const char *, const char *); /* Renames a file or directory */ + int (*xRealpath)(const char *, jx9_context *); /* Return canonicalized absolute pathname*/ + /* Systems functions */ + int (*xSleep)(unsigned int); /* Delay execution in microseconds */ + int (*xUnlink)(const char *); /* Deletes a file */ + int (*xFileExists)(const char *); /* Checks whether a file or directory exists */ + int (*xChmod)(const char *, int); /* Changes file mode */ + int (*xChown)(const char *, const char *); /* Changes file owner */ + int (*xChgrp)(const char *, const char *); /* Changes file group */ + jx9_int64 (*xFreeSpace)(const char *); /* Available space on filesystem or disk partition */ + jx9_int64 (*xTotalSpace)(const char *); /* Total space on filesystem or disk partition */ + jx9_int64 (*xFileSize)(const char *); /* Gets file size */ + jx9_int64 (*xFileAtime)(const char *); /* Gets last access time of file */ + jx9_int64 (*xFileMtime)(const char *); /* Gets file modification time */ + jx9_int64 (*xFileCtime)(const char *); /* Gets inode change time of file */ + int (*xStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */ + int (*xlStat)(const char *, jx9_value *, jx9_value *); /* Gives information about a file */ + int (*xIsfile)(const char *); /* Tells whether the filename is a regular file */ + int (*xIslink)(const char *); /* Tells whether the filename is a symbolic link */ + int (*xReadable)(const char *); /* Tells whether a file exists and is readable */ + int (*xWritable)(const char *); /* Tells whether the filename is writable */ + int (*xExecutable)(const char *); /* Tells whether the filename is executable */ + int (*xFiletype)(const char *, jx9_context *); /* Gets file type [i.e: fifo, dir, file..] */ + int (*xGetenv)(const char *, jx9_context *); /* Gets the value of an environment variable */ + int (*xSetenv)(const char *, const char *); /* Sets the value of an environment variable */ + int (*xTouch)(const char *, jx9_int64, jx9_int64); /* Sets access and modification time of file */ + int (*xMmap)(const char *, void **, jx9_int64 *); /* Read-only memory map of the whole file */ + void (*xUnmap)(void *, jx9_int64); /* Unmap a memory view */ + int (*xLink)(const char *, const char *, int); /* Create hard or symbolic link */ + int (*xUmask)(int); /* Change the current umask */ + void (*xTempDir)(jx9_context *); /* Get path of the temporary directory */ + unsigned int (*xProcessId)(void); /* Get running process ID */ + int (*xUid)(void); /* user ID of the process */ + int (*xGid)(void); /* group ID of the process */ + void (*xUsername)(jx9_context *); /* Running username */ + int (*xExec)(const char *, jx9_context *); /* Execute an external program */ +}; +/* Current JX9 IO stream structure version. */ +#define JX9_IO_STREAM_VERSION 1 +/* + * Possible open mode flags that can be passed to the xOpen() routine + * of the underlying IO stream device . + * Refer to the JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html) + * for additional information. + */ +#define JX9_IO_OPEN_RDONLY 0x001 /* Read-only open */ +#define JX9_IO_OPEN_WRONLY 0x002 /* Write-only open */ +#define JX9_IO_OPEN_RDWR 0x004 /* Read-write open. */ +#define JX9_IO_OPEN_CREATE 0x008 /* If the file does not exist it will be created */ +#define JX9_IO_OPEN_TRUNC 0x010 /* Truncate the file to zero length */ +#define JX9_IO_OPEN_APPEND 0x020 /* Append mode.The file offset is positioned at the end of the file */ +#define JX9_IO_OPEN_EXCL 0x040 /* Ensure that this call creates the file, the file must not exist before */ +#define JX9_IO_OPEN_BINARY 0x080 /* Simple hint: Data is binary */ +#define JX9_IO_OPEN_TEMP 0x100 /* Simple hint: Temporary file */ +#define JX9_IO_OPEN_TEXT 0x200 /* Simple hint: Data is textual */ +/* + * JX9 IO Stream Device. + * + * An instance of the jx9_io_stream object defines the interface between the JX9 core + * and the underlying stream device. + * A stream is a smart mechanism for generalizing file, network, data compression + * and other IO operations which share a common set of functions using an abstracted + * unified interface. + * A stream device is additional code which tells the stream how to handle specific + * protocols/encodings. For example, the http device knows how to translate a URL + * into an HTTP/1.1 request for a file on a remote server. + * JX9 come with two built-in IO streams device: + * The file:// stream which perform very efficient disk IO and the jx9:// stream + * which is a special stream that allow access various I/O streams (See the JX9 official + * documentation for more information on this stream). + * A stream is referenced as: scheme://target + * scheme(string) - The name of the wrapper to be used. Examples include: file, http, https, ftp, + * ftps, compress.zlib, compress.bz2, and jx9. If no wrapper is specified, the function default + * is used (typically file://). + * target - Depends on the device used. For filesystem related streams this is typically a path + * and filename of the desired file.For network related streams this is typically a hostname, often + * with a path appended. + * IO stream devices are registered using a call to jx9_vm_config() with a configuration verb + * set to JX9_VM_CONFIG_IO_STREAM. + * Currently the JX9 development team is working on the implementation of the http:// and ftp:// + * IO stream protocols. These devices will be available in the next major release of the JX9 engine. + * Developers wishing to implement their own IO stream devices must understand and follow + * The JX9 IO Stream C/C++ specification manual (http://jx9.symisc.net/io_stream_spec.html). + */ +struct jx9_io_stream +{ + const char *zName; /* Underlying stream name [i.e: file/http/zip/jx9, ..] */ + int iVersion; /* IO stream structure version [default 1]*/ + int (*xOpen)(const char *, int, jx9_value *, void **); /* Open handle*/ + int (*xOpenDir)(const char *, jx9_value *, void **); /* Open directory handle */ + void (*xClose)(void *); /* Close file handle */ + void (*xCloseDir)(void *); /* Close directory handle */ + jx9_int64 (*xRead)(void *, void *, jx9_int64); /* Read from the open stream */ + int (*xReadDir)(void *, jx9_context *); /* Read entry from directory handle */ + jx9_int64 (*xWrite)(void *, const void *, jx9_int64); /* Write to the open stream */ + int (*xSeek)(void *, jx9_int64, int); /* Seek on the open stream */ + int (*xLock)(void *, int); /* Lock/Unlock the open stream */ + void (*xRewindDir)(void *); /* Rewind directory handle */ + jx9_int64 (*xTell)(void *); /* Current position of the stream read/write pointer */ + int (*xTrunc)(void *, jx9_int64); /* Truncates the open stream to a given length */ + int (*xSync)(void *); /* Flush open stream data */ + int (*xStat)(void *, jx9_value *, jx9_value *); /* Stat an open stream handle */ +}; +/* + * C-API-REF: Please refer to the official documentation for interfaces + * purpose and expected parameters. + */ +/* Engine Handling Interfaces */ +JX9_PRIVATE int jx9_init(jx9 **ppEngine); +/*JX9_PRIVATE int jx9_config(jx9 *pEngine, int nConfigOp, ...);*/ +JX9_PRIVATE int jx9_release(jx9 *pEngine); +/* Compile Interfaces */ +JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm); +JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm); +/* Virtual Machine Handling Interfaces */ +JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...); +/*JX9_PRIVATE int jx9_vm_exec(jx9_vm *pVm, int *pExitStatus);*/ +/*JX9_PRIVATE jx9_value * jx9_vm_extract_variable(jx9_vm *pVm,const char *zVarname);*/ +/*JX9_PRIVATE int jx9_vm_reset(jx9_vm *pVm);*/ +JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm); +/*JX9_PRIVATE int jx9_vm_dump_v2(jx9_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData);*/ +/* In-process Extending Interfaces */ +JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData); +/*JX9_PRIVATE int jx9_delete_function(jx9_vm *pVm, const char *zName);*/ +JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData); +/*JX9_PRIVATE int jx9_delete_constant(jx9_vm *pVm, const char *zName);*/ +/* Foreign Function Parameter Values */ +JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue); +JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue); +JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue); +JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue); +JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen); +JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue); +JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict); +/* Setting The Result Of A Foreign Function */ +JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue); +JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue); +JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool); +JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value); +JX9_PRIVATE int jx9_result_null(jx9_context *pCtx); +JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen); +JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...); +JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue); +JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData); +/* Call Context Handling Interfaces */ +JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen); +/*JX9_PRIVATE int jx9_context_output_format(jx9_context *pCtx, const char *zFormat, ...);*/ +JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr); +JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...); +JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx); +JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen); +JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx); +JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData); +JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx); +JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx); +JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx); +JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx); +/* Call Context Memory Management Interfaces */ +JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease); +JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte); +JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk); +/* On Demand Dynamically Typed Value Object allocation interfaces */ +JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm); +JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm); +JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue); +JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx); +JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx); +JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue); +/* Dynamically Typed Value Object Management Interfaces */ +JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue); +JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue); +JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool); +JX9_PRIVATE int jx9_value_null(jx9_value *pVal); +JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value); +JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen); +JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...); +JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal); +JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData); +JX9_PRIVATE int jx9_value_release(jx9_value *pVal); +/* JSON Array/Object Management Interfaces */ +JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte); +JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); +JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue); +JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue); +JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray); +/* Dynamically Typed Value Object Query Interfaces */ +JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal); +JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal); +/* Global Library Management Interfaces */ +/*JX9_PRIVATE int jx9_lib_init(void);*/ +JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...); +JX9_PRIVATE int jx9_lib_shutdown(void); +/*JX9_PRIVATE int jx9_lib_is_threadsafe(void);*/ +/*JX9_PRIVATE const char * jx9_lib_version(void);*/ +JX9_PRIVATE const char * jx9_lib_signature(void); +/*JX9_PRIVATE const char * jx9_lib_ident(void);*/ +/*JX9_PRIVATE const char * jx9_lib_copyright(void);*/ + +#endif /* _JX9H_ */ + +/* + * ---------------------------------------------------------- + * File: jx9Int.h + * MD5: fb8dffc8ba1425a139091aa145067e16 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: jx9Int.h v1.9 FreeBSD 2012-08-13 23:25 devel $ */ +#ifndef __JX9INT_H__ +#define __JX9INT_H__ +/* Internal interface definitions for JX9. */ +#ifdef JX9_AMALGAMATION +#ifndef JX9_PRIVATE +/* Marker for routines not intended for external use */ +#define JX9_PRIVATE static +#endif /* JX9_PRIVATE */ +#else +#define JX9_PRIVATE +#include "jx9.h" +#endif +#ifndef JX9_PI +/* Value of PI */ +#define JX9_PI 3.1415926535898 +#endif +/* + * Constants for the largest and smallest possible 64-bit signed integers. + * These macros are designed to work correctly on both 32-bit and 64-bit + * compilers. + */ +#ifndef LARGEST_INT64 +#define LARGEST_INT64 (0xffffffff|(((sxi64)0x7fffffff)<<32)) +#endif +#ifndef SMALLEST_INT64 +#define SMALLEST_INT64 (((sxi64)-1) - LARGEST_INT64) +#endif +/* Forward declaration of private structures */ +typedef struct jx9_foreach_info jx9_foreach_info; +typedef struct jx9_foreach_step jx9_foreach_step; +typedef struct jx9_hashmap_node jx9_hashmap_node; +typedef struct jx9_hashmap jx9_hashmap; +/* Symisc Standard types */ +#if !defined(SYMISC_STD_TYPES) +#define SYMISC_STD_TYPES +#ifdef __WINNT__ +/* Disable nuisance warnings on Borland compilers */ +#if defined(__BORLANDC__) +#pragma warn -rch /* unreachable code */ +#pragma warn -ccc /* Condition is always true or false */ +#pragma warn -aus /* Assigned value is never used */ +#pragma warn -csu /* Comparing signed and unsigned */ +#pragma warn -spa /* Suspicious pointer arithmetic */ +#endif +#endif +typedef signed char sxi8; /* signed char */ +typedef unsigned char sxu8; /* unsigned char */ +typedef signed short int sxi16; /* 16 bits(2 bytes) signed integer */ +typedef unsigned short int sxu16; /* 16 bits(2 bytes) unsigned integer */ +typedef int sxi32; /* 32 bits(4 bytes) integer */ +typedef unsigned int sxu32; /* 32 bits(4 bytes) unsigned integer */ +typedef long sxptr; +typedef unsigned long sxuptr; +typedef long sxlong; +typedef unsigned long sxulong; +typedef sxi32 sxofft; +typedef sxi64 sxofft64; +typedef long double sxlongreal; +typedef double sxreal; +#define SXI8_HIGH 0x7F +#define SXU8_HIGH 0xFF +#define SXI16_HIGH 0x7FFF +#define SXU16_HIGH 0xFFFF +#define SXI32_HIGH 0x7FFFFFFF +#define SXU32_HIGH 0xFFFFFFFF +#define SXI64_HIGH 0x7FFFFFFFFFFFFFFF +#define SXU64_HIGH 0xFFFFFFFFFFFFFFFF +#if !defined(TRUE) +#define TRUE 1 +#endif +#if !defined(FALSE) +#define FALSE 0 +#endif +/* + * The following macros are used to cast pointers to integers and + * integers to pointers. + */ +#if defined(__PTRDIFF_TYPE__) +# define SX_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) +# define SX_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) +#elif !defined(__GNUC__) +# define SX_INT_TO_PTR(X) ((void*)&((char*)0)[X]) +# define SX_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) +#else +# define SX_INT_TO_PTR(X) ((void*)(X)) +# define SX_PTR_TO_INT(X) ((int)(X)) +#endif +#define SXMIN(a, b) ((a < b) ? (a) : (b)) +#define SXMAX(a, b) ((a < b) ? (b) : (a)) +#endif /* SYMISC_STD_TYPES */ +/* Symisc Run-time API private definitions */ +#if !defined(SYMISC_PRIVATE_DEFS) +#define SYMISC_PRIVATE_DEFS + +typedef sxi32 (*ProcRawStrCmp)(const SyString *, const SyString *); +#define SyStringData(RAW) ((RAW)->zString) +#define SyStringLength(RAW) ((RAW)->nByte) +#define SyStringInitFromBuf(RAW, ZBUF, NLEN){\ + (RAW)->zString = (const char *)ZBUF;\ + (RAW)->nByte = (sxu32)(NLEN);\ +} +#define SyStringUpdatePtr(RAW, NBYTES){\ + if( NBYTES > (RAW)->nByte ){\ + (RAW)->nByte = 0;\ + }else{\ + (RAW)->zString += NBYTES;\ + (RAW)->nByte -= NBYTES;\ + }\ +} +#define SyStringDupPtr(RAW1, RAW2)\ + (RAW1)->zString = (RAW2)->zString;\ + (RAW1)->nByte = (RAW2)->nByte; + +#define SyStringTrimLeadingChar(RAW, CHAR)\ + while((RAW)->nByte > 0 && (RAW)->zString[0] == CHAR ){\ + (RAW)->zString++;\ + (RAW)->nByte--;\ + } +#define SyStringTrimTrailingChar(RAW, CHAR)\ + while((RAW)->nByte > 0 && (RAW)->zString[(RAW)->nByte - 1] == CHAR){\ + (RAW)->nByte--;\ + } +#define SyStringCmp(RAW1, RAW2, xCMP)\ + (((RAW1)->nByte == (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW1)->nByte - (RAW2)->nByte)) + +#define SyStringCmp2(RAW1, RAW2, xCMP)\ + (((RAW1)->nByte >= (RAW2)->nByte) ? xCMP((RAW1)->zString, (RAW2)->zString, (RAW2)->nByte) : (sxi32)((RAW2)->nByte - (RAW1)->nByte)) + +#define SyStringCharCmp(RAW, CHAR) \ + (((RAW)->nByte == sizeof(char)) ? ((RAW)->zString[0] == CHAR ? 0 : CHAR - (RAW)->zString[0]) : ((RAW)->zString[0] == CHAR ? 0 : (RAW)->nByte - sizeof(char))) + +#define SX_ADDR(PTR) ((sxptr)PTR) +#define SX_ARRAYSIZE(X) (sizeof(X)/sizeof(X[0])) +#define SXUNUSED(P) (P = 0) +#define SX_EMPTY(PTR) (PTR == 0) +#define SX_EMPTY_STR(STR) (STR == 0 || STR[0] == 0 ) +typedef struct SyMemBackend SyMemBackend; +typedef struct SyBlob SyBlob; +typedef struct SySet SySet; +/* Standard function signatures */ +typedef sxi32 (*ProcCmp)(const void *, const void *, sxu32); +typedef sxi32 (*ProcPatternMatch)(const char *, sxu32, const char *, sxu32, sxu32 *); +typedef sxi32 (*ProcSearch)(const void *, sxu32, const void *, sxu32, ProcCmp, sxu32 *); +typedef sxu32 (*ProcHash)(const void *, sxu32); +typedef sxi32 (*ProcHashSum)(const void *, sxu32, unsigned char *, sxu32); +typedef sxi32 (*ProcSort)(void *, sxu32, sxu32, ProcCmp); +#define MACRO_LIST_PUSH(Head, Item)\ + Item->pNext = Head;\ + Head = Item; +#define MACRO_LD_PUSH(Head, Item)\ + if( Head == 0 ){\ + Head = Item;\ + }else{\ + Item->pNext = Head;\ + Head->pPrev = Item;\ + Head = Item;\ + } +#define MACRO_LD_REMOVE(Head, Item)\ + if( Head == Item ){\ + Head = Head->pNext;\ + }\ + if( Item->pPrev ){ Item->pPrev->pNext = Item->pNext;}\ + if( Item->pNext ){ Item->pNext->pPrev = Item->pPrev;} +/* + * A generic dynamic set. + */ +struct SySet +{ + SyMemBackend *pAllocator; /* Memory backend */ + void *pBase; /* Base pointer */ + sxu32 nUsed; /* Total number of used slots */ + sxu32 nSize; /* Total number of available slots */ + sxu32 eSize; /* Size of a single slot */ + sxu32 nCursor; /* Loop cursor */ + void *pUserData; /* User private data associated with this container */ +}; +#define SySetBasePtr(S) ((S)->pBase) +#define SySetBasePtrJump(S, OFFT) (&((char *)(S)->pBase)[OFFT*(S)->eSize]) +#define SySetUsed(S) ((S)->nUsed) +#define SySetSize(S) ((S)->nSize) +#define SySetElemSize(S) ((S)->eSize) +#define SySetCursor(S) ((S)->nCursor) +#define SySetGetAllocator(S) ((S)->pAllocator) +#define SySetSetUserData(S, DATA) ((S)->pUserData = DATA) +#define SySetGetUserData(S) ((S)->pUserData) +/* + * A variable length containers for generic data. + */ +struct SyBlob +{ + SyMemBackend *pAllocator; /* Memory backend */ + void *pBlob; /* Base pointer */ + sxu32 nByte; /* Total number of used bytes */ + sxu32 mByte; /* Total number of available bytes */ + sxu32 nFlags; /* Blob internal flags, see below */ +}; +#define SXBLOB_LOCKED 0x01 /* Blob is locked [i.e: Cannot auto grow] */ +#define SXBLOB_STATIC 0x02 /* Not allocated from heap */ +#define SXBLOB_RDONLY 0x04 /* Read-Only data */ + +#define SyBlobFreeSpace(BLOB) ((BLOB)->mByte - (BLOB)->nByte) +#define SyBlobLength(BLOB) ((BLOB)->nByte) +#define SyBlobData(BLOB) ((BLOB)->pBlob) +#define SyBlobCurData(BLOB) ((void*)(&((char*)(BLOB)->pBlob)[(BLOB)->nByte])) +#define SyBlobDataAt(BLOB, OFFT) ((void *)(&((char *)(BLOB)->pBlob)[OFFT])) +#define SyBlobGetAllocator(BLOB) ((BLOB)->pAllocator) + +#define SXMEM_POOL_INCR 3 +#define SXMEM_POOL_NBUCKETS 12 +#define SXMEM_BACKEND_MAGIC 0xBAC3E67D +#define SXMEM_BACKEND_CORRUPT(BACKEND) (BACKEND == 0 || BACKEND->nMagic != SXMEM_BACKEND_MAGIC) + +#define SXMEM_BACKEND_RETRY 3 +/* A memory backend subsystem is defined by an instance of the following structures */ +typedef union SyMemHeader SyMemHeader; +typedef struct SyMemBlock SyMemBlock; +struct SyMemBlock +{ + SyMemBlock *pNext, *pPrev; /* Chain of allocated memory blocks */ +#ifdef UNTRUST + sxu32 nGuard; /* magic number associated with each valid block, so we + * can detect misuse. + */ +#endif +}; +/* + * Header associated with each valid memory pool block. + */ +union SyMemHeader +{ + SyMemHeader *pNext; /* Next chunk of size 1 << (nBucket + SXMEM_POOL_INCR) in the list */ + sxu32 nBucket; /* Bucket index in aPool[] */ +}; +struct SyMemBackend +{ + const SyMutexMethods *pMutexMethods; /* Mutex methods */ + const SyMemMethods *pMethods; /* Memory allocation methods */ + SyMemBlock *pBlocks; /* List of valid memory blocks */ + sxu32 nBlock; /* Total number of memory blocks allocated so far */ + ProcMemError xMemError; /* Out-of memory callback */ + void *pUserData; /* First arg to xMemError() */ + SyMutex *pMutex; /* Per instance mutex */ + sxu32 nMagic; /* Sanity check against misuse */ + SyMemHeader *apPool[SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR]; /* Pool of memory chunks */ +}; +/* Mutex types */ +#define SXMUTEX_TYPE_FAST 1 +#define SXMUTEX_TYPE_RECURSIVE 2 +#define SXMUTEX_TYPE_STATIC_1 3 +#define SXMUTEX_TYPE_STATIC_2 4 +#define SXMUTEX_TYPE_STATIC_3 5 +#define SXMUTEX_TYPE_STATIC_4 6 +#define SXMUTEX_TYPE_STATIC_5 7 +#define SXMUTEX_TYPE_STATIC_6 8 + +#define SyMutexGlobalInit(METHOD){\ + if( (METHOD)->xGlobalInit ){\ + (METHOD)->xGlobalInit();\ + }\ +} +#define SyMutexGlobalRelease(METHOD){\ + if( (METHOD)->xGlobalRelease ){\ + (METHOD)->xGlobalRelease();\ + }\ +} +#define SyMutexNew(METHOD, TYPE) (METHOD)->xNew(TYPE) +#define SyMutexRelease(METHOD, MUTEX){\ + if( MUTEX && (METHOD)->xRelease ){\ + (METHOD)->xRelease(MUTEX);\ + }\ +} +#define SyMutexEnter(METHOD, MUTEX){\ + if( MUTEX ){\ + (METHOD)->xEnter(MUTEX);\ + }\ +} +#define SyMutexTryEnter(METHOD, MUTEX){\ + if( MUTEX && (METHOD)->xTryEnter ){\ + (METHOD)->xTryEnter(MUTEX);\ + }\ +} +#define SyMutexLeave(METHOD, MUTEX){\ + if( MUTEX ){\ + (METHOD)->xLeave(MUTEX);\ + }\ +} +/* Comparison, byte swap, byte copy macros */ +#define SX_MACRO_FAST_CMP(X1, X2, SIZE, RC){\ + register unsigned char *r1 = (unsigned char *)X1;\ + register unsigned char *r2 = (unsigned char *)X2;\ + register sxu32 LEN = SIZE;\ + for(;;){\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + if( !LEN ){ break; }if( r1[0] != r2[0] ){ break; } r1++; r2++; LEN--;\ + }\ + RC = !LEN ? 0 : r1[0] - r2[0];\ +} +#define SX_MACRO_FAST_MEMCPY(SRC, DST, SIZ){\ + register unsigned char *xSrc = (unsigned char *)SRC;\ + register unsigned char *xDst = (unsigned char *)DST;\ + register sxu32 xLen = SIZ;\ + for(;;){\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + if( !xLen ){ break; }xDst[0] = xSrc[0]; xDst++; xSrc++; --xLen;\ + }\ +} +#define SX_MACRO_BYTE_SWAP(X, Y, Z){\ + register unsigned char *s = (unsigned char *)X;\ + register unsigned char *d = (unsigned char *)Y;\ + sxu32 ZLong = Z; \ + sxi32 c; \ + for(;;){\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + if(!ZLong){ break; } c = s[0] ; s[0] = d[0]; d[0] = (unsigned char)c; s++; d++; --ZLong;\ + }\ +} +#define SX_MSEC_PER_SEC (1000) /* Millisec per seconds */ +#define SX_USEC_PER_SEC (1000000) /* Microsec per seconds */ +#define SX_NSEC_PER_SEC (1000000000) /* Nanosec per seconds */ +#endif /* SYMISC_PRIVATE_DEFS */ +/* Symisc Run-time API auxiliary definitions */ +#if !defined(SYMISC_PRIVATE_AUX_DEFS) +#define SYMISC_PRIVATE_AUX_DEFS + +typedef struct SyHashEntry_Pr SyHashEntry_Pr; +typedef struct SyHashEntry SyHashEntry; +typedef struct SyHash SyHash; +/* + * Each public hashtable entry is represented by an instance + * of the following structure. + */ +struct SyHashEntry +{ + const void *pKey; /* Hash key */ + sxu32 nKeyLen; /* Key length */ + void *pUserData; /* User private data */ +}; +#define SyHashEntryGetUserData(ENTRY) ((ENTRY)->pUserData) +#define SyHashEntryGetKey(ENTRY) ((ENTRY)->pKey) +/* Each active hashtable is identified by an instance of the following structure */ +struct SyHash +{ + SyMemBackend *pAllocator; /* Memory backend */ + ProcHash xHash; /* Hash function */ + ProcCmp xCmp; /* Comparison function */ + SyHashEntry_Pr *pList, *pCurrent; /* Linked list of hash entries user for linear traversal */ + sxu32 nEntry; /* Total number of entries */ + SyHashEntry_Pr **apBucket; /* Hash buckets */ + sxu32 nBucketSize; /* Current bucket size */ +}; +#define SXHASH_BUCKET_SIZE 16 /* Initial bucket size: must be a power of two */ +#define SXHASH_FILL_FACTOR 3 +/* Hash access macro */ +#define SyHashFunc(HASH) ((HASH)->xHash) +#define SyHashCmpFunc(HASH) ((HASH)->xCmp) +#define SyHashTotalEntry(HASH) ((HASH)->nEntry) +#define SyHashGetPool(HASH) ((HASH)->pAllocator) +/* + * An instance of the following structure define a single context + * for an Pseudo Random Number Generator. + * + * Nothing in this file or anywhere else in the library does any kind of + * encryption. The RC4 algorithm is being used as a PRNG (pseudo-random + * number generator) not as an encryption device. + * This implementation is taken from the SQLite3 source tree. + */ +typedef struct SyPRNGCtx SyPRNGCtx; +struct SyPRNGCtx +{ + sxu8 i, j; /* State variables */ + unsigned char s[256]; /* State variables */ + sxu16 nMagic; /* Sanity check */ + }; +typedef sxi32 (*ProcRandomSeed)(void *, unsigned int, void *); +/* High resolution timer.*/ +typedef struct sytime sytime; +struct sytime +{ + long tm_sec; /* seconds */ + long tm_usec; /* microseconds */ +}; +/* Forward declaration */ +typedef struct SyStream SyStream; +typedef struct SyToken SyToken; +typedef struct SyLex SyLex; +/* + * Tokenizer callback signature. + */ +typedef sxi32 (*ProcTokenizer)(SyStream *, SyToken *, void *, void *); +/* + * Each token in the input is represented by an instance + * of the following structure. + */ +struct SyToken +{ + SyString sData; /* Token text and length */ + sxu32 nType; /* Token type */ + sxu32 nLine; /* Token line number */ + void *pUserData; /* User private data associated with this token */ +}; +/* + * During tokenization, information about the state of the input + * stream is held in an instance of the following structure. + */ +struct SyStream +{ + const unsigned char *zInput; /* Complete text of the input */ + const unsigned char *zText; /* Current input we are processing */ + const unsigned char *zEnd; /* End of input marker */ + sxu32 nLine; /* Total number of processed lines */ + sxu32 nIgn; /* Total number of ignored tokens */ + SySet *pSet; /* Token containers */ +}; +/* + * Each lexer is represented by an instance of the following structure. + */ +struct SyLex +{ + SyStream sStream; /* Input stream */ + ProcTokenizer xTokenizer; /* Tokenizer callback */ + void * pUserData; /* Third argument to xTokenizer() */ + SySet *pTokenSet; /* Token set */ +}; +#define SyLexTotalToken(LEX) SySetTotalEntry(&(LEX)->aTokenSet) +#define SyLexTotalLines(LEX) ((LEX)->sStream.nLine) +#define SyLexTotalIgnored(LEX) ((LEX)->sStream.nIgn) +#define XLEX_IN_LEN(STREAM) (sxu32)(STREAM->zEnd - STREAM->zText) +#endif /* SYMISC_PRIVATE_AUX_DEFS */ +/* +** Notes on UTF-8 (According to SQLite3 authors): +** +** Byte-0 Byte-1 Byte-2 Byte-3 Value +** 0xxxxxxx 00000000 00000000 0xxxxxxx +** 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx +** 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx +** 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx +** +*/ +/* +** Assuming zIn points to the first byte of a UTF-8 character, +** advance zIn to point to the first byte of the next UTF-8 character. +*/ +#define SX_JMP_UTF8(zIn, zEnd)\ + while(zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ zIn++; } +#define SX_WRITE_UTF8(zOut, c) { \ + if( c<0x00080 ){ \ + *zOut++ = (sxu8)(c&0xFF); \ + }else if( c<0x00800 ){ \ + *zOut++ = 0xC0 + (sxu8)((c>>6)&0x1F); \ + *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ + }else if( c<0x10000 ){ \ + *zOut++ = 0xE0 + (sxu8)((c>>12)&0x0F); \ + *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ + }else{ \ + *zOut++ = 0xF0 + (sxu8)((c>>18) & 0x07); \ + *zOut++ = 0x80 + (sxu8)((c>>12) & 0x3F); \ + *zOut++ = 0x80 + (sxu8)((c>>6) & 0x3F); \ + *zOut++ = 0x80 + (sxu8)(c & 0x3F); \ + } \ +} +/* Rely on the standard ctype */ +#include +#define SyToUpper(c) toupper(c) +#define SyToLower(c) tolower(c) +#define SyisUpper(c) isupper(c) +#define SyisLower(c) islower(c) +#define SyisSpace(c) isspace(c) +#define SyisBlank(c) isspace(c) +#define SyisAlpha(c) isalpha(c) +#define SyisDigit(c) isdigit(c) +#define SyisHex(c) isxdigit(c) +#define SyisPrint(c) isprint(c) +#define SyisPunct(c) ispunct(c) +#define SyisSpec(c) iscntrl(c) +#define SyisCtrl(c) iscntrl(c) +#define SyisAscii(c) isascii(c) +#define SyisAlphaNum(c) isalnum(c) +#define SyisGraph(c) isgraph(c) +#define SyDigToHex(c) "0123456789ABCDEF"[c & 0x0F] +#define SyDigToInt(c) ((c < 0xc0 && SyisDigit(c))? (c - '0') : 0 ) +#define SyCharToUpper(c) ((c < 0xc0 && SyisLower(c))? SyToUpper(c) : c) +#define SyCharToLower(c) ((c < 0xc0 && SyisUpper(c))? SyToLower(c) : c) +/* Remove white space/NUL byte from a raw string */ +#define SyStringLeftTrim(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + } +#define SyStringLeftTrimSafe(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && ((RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + } +#define SyStringRightTrim(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ + (RAW)->nByte--;\ + } +#define SyStringRightTrimSafe(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ + (( RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ + (RAW)->nByte--;\ + } + +#define SyStringFullTrim(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && SyisSpace((RAW)->zString[0])){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + }\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && SyisSpace((RAW)->zString[(RAW)->nByte - 1])){\ + (RAW)->nByte--;\ + } +#define SyStringFullTrimSafe(RAW)\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[0] < 0xc0 && \ + ( (RAW)->zString[0] == 0 || SyisSpace((RAW)->zString[0]))){\ + (RAW)->nByte--;\ + (RAW)->zString++;\ + }\ + while((RAW)->nByte > 0 && (unsigned char)(RAW)->zString[(RAW)->nByte - 1] < 0xc0 && \ + ( (RAW)->zString[(RAW)->nByte - 1] == 0 || SyisSpace((RAW)->zString[(RAW)->nByte - 1]))){\ + (RAW)->nByte--;\ + } +#ifndef JX9_DISABLE_BUILTIN_FUNC +/* + * An XML raw text, CDATA, tag name and son is parsed out and stored + * in an instance of the following structure. + */ +typedef struct SyXMLRawStr SyXMLRawStr; +struct SyXMLRawStr +{ + const char *zString; /* Raw text [UTF-8 ENCODED EXCEPT CDATA] [NOT NULL TERMINATED] */ + sxu32 nByte; /* Text length */ + sxu32 nLine; /* Line number this text occurs */ +}; +/* + * Event callback signatures. + */ +typedef sxi32 (*ProcXMLStartTagHandler)(SyXMLRawStr *, SyXMLRawStr *, sxu32, SyXMLRawStr *, void *); +typedef sxi32 (*ProcXMLTextHandler)(SyXMLRawStr *, void *); +typedef sxi32 (*ProcXMLEndTagHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); +typedef sxi32 (*ProcXMLPIHandler)(SyXMLRawStr *, SyXMLRawStr *, void *); +typedef sxi32 (*ProcXMLDoctypeHandler)(SyXMLRawStr *, void *); +typedef sxi32 (*ProcXMLSyntaxErrorHandler)(const char *, int, SyToken *, void *); +typedef sxi32 (*ProcXMLStartDocument)(void *); +typedef sxi32 (*ProcXMLNameSpaceStart)(SyXMLRawStr *, SyXMLRawStr *, void *); +typedef sxi32 (*ProcXMLNameSpaceEnd)(SyXMLRawStr *, void *); +typedef sxi32 (*ProcXMLEndDocument)(void *); +/* XML processing control flags */ +#define SXML_ENABLE_NAMESPACE 0x01 /* Parse XML with namespace support enbaled */ +#define SXML_ENABLE_QUERY 0x02 /* Not used */ +#define SXML_OPTION_CASE_FOLDING 0x04 /* Controls whether case-folding is enabled for this XML parser */ +#define SXML_OPTION_SKIP_TAGSTART 0x08 /* Specify how many characters should be skipped in the beginning of a tag name.*/ +#define SXML_OPTION_SKIP_WHITE 0x10 /* Whether to skip values consisting of whitespace characters. */ +#define SXML_OPTION_TARGET_ENCODING 0x20 /* Default encoding: UTF-8 */ +/* XML error codes */ +enum xml_err_code{ + SXML_ERROR_NONE = 1, + SXML_ERROR_NO_MEMORY, + SXML_ERROR_SYNTAX, + SXML_ERROR_NO_ELEMENTS, + SXML_ERROR_INVALID_TOKEN, + SXML_ERROR_UNCLOSED_TOKEN, + SXML_ERROR_PARTIAL_CHAR, + SXML_ERROR_TAG_MISMATCH, + SXML_ERROR_DUPLICATE_ATTRIBUTE, + SXML_ERROR_JUNK_AFTER_DOC_ELEMENT, + SXML_ERROR_PARAM_ENTITY_REF, + SXML_ERROR_UNDEFINED_ENTITY, + SXML_ERROR_RECURSIVE_ENTITY_REF, + SXML_ERROR_ASYNC_ENTITY, + SXML_ERROR_BAD_CHAR_REF, + SXML_ERROR_BINARY_ENTITY_REF, + SXML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF, + SXML_ERROR_MISPLACED_XML_PI, + SXML_ERROR_UNKNOWN_ENCODING, + SXML_ERROR_INCORRECT_ENCODING, + SXML_ERROR_UNCLOSED_CDATA_SECTION, + SXML_ERROR_EXTERNAL_ENTITY_HANDLING +}; +/* Each active XML SAX parser is represented by an instance + * of the following structure. + */ +typedef struct SyXMLParser SyXMLParser; +struct SyXMLParser +{ + SyMemBackend *pAllocator; /* Memory backend */ + void *pUserData; /* User private data forwarded varbatim by the XML parser + * as the last argument to the users callbacks. + */ + SyHash hns; /* Namespace hashtable */ + SySet sToken; /* XML tokens */ + SyLex sLex; /* Lexical analyzer */ + sxi32 nFlags; /* Control flags */ + /* User callbacks */ + ProcXMLStartTagHandler xStartTag; /* Start element handler */ + ProcXMLEndTagHandler xEndTag; /* End element handler */ + ProcXMLTextHandler xRaw; /* Raw text/CDATA handler */ + ProcXMLDoctypeHandler xDoctype; /* DOCTYPE handler */ + ProcXMLPIHandler xPi; /* Processing instruction (PI) handler*/ + ProcXMLSyntaxErrorHandler xError; /* Error handler */ + ProcXMLStartDocument xStartDoc; /* StartDoc handler */ + ProcXMLEndDocument xEndDoc; /* EndDoc handler */ + ProcXMLNameSpaceStart xNameSpace; /* Namespace declaration handler */ + ProcXMLNameSpaceEnd xNameSpaceEnd; /* End namespace declaration handler */ +}; +/* + * -------------- + * Archive extractor: + * -------------- + * Each open ZIP/TAR archive is identified by an instance of the following structure. + * That is, a process can open one or more archives and manipulates them in thread safe + * way by simply working with pointers to the following structure. + * Each entry in the archive is remembered in a hashtable. + * Lookup is very fast and entry with the same name are chained together. + */ + typedef struct SyArchiveEntry SyArchiveEntry; + typedef struct SyArchive SyArchive; + struct SyArchive + { + SyMemBackend *pAllocator; /* Memory backend */ + SyArchiveEntry *pCursor; /* Cursor for linear traversal of archive entries */ + SyArchiveEntry *pList; /* Pointer to the List of the loaded archive */ + SyArchiveEntry **apHash; /* Hashtable for archive entry */ + ProcRawStrCmp xCmp; /* Hash comparison function */ + ProcHash xHash; /* Hash Function */ + sxu32 nSize; /* Hashtable size */ + sxu32 nEntry; /* Total number of entries in the zip/tar archive */ + sxu32 nLoaded; /* Total number of entries loaded in memory */ + sxu32 nCentralOfft; /* Central directory offset(ZIP only. Otherwise Zero) */ + sxu32 nCentralSize; /* Central directory size(ZIP only. Otherwise Zero) */ + void *pUserData; /* Upper layer private data */ + sxu32 nMagic; /* Sanity check */ + + }; +#define SXARCH_MAGIC 0xDEAD635A +#define SXARCH_INVALID(ARCH) (ARCH == 0 || ARCH->nMagic != SXARCH_MAGIC) +#define SXARCH_ENTRY_INVALID(ENTRY) (ENTRY == 0 || ENTRY->nMagic != SXARCH_MAGIC) +#define SyArchiveHashFunc(ARCH) (ARCH)->xHash +#define SyArchiveCmpFunc(ARCH) (ARCH)->xCmp +#define SyArchiveUserData(ARCH) (ARCH)->pUserData +#define SyArchiveSetUserData(ARCH, DATA) (ARCH)->pUserData = DATA +/* + * Each loaded archive record is identified by an instance + * of the following structure. + */ + struct SyArchiveEntry + { + sxu32 nByte; /* Contents size before compression */ + sxu32 nByteCompr; /* Contents size after compression */ + sxu32 nReadCount; /* Read counter */ + sxu32 nCrc; /* Contents CRC32 */ + Sytm sFmt; /* Last-modification time */ + sxu32 nOfft; /* Data offset. */ + sxu16 nComprMeth; /* Compression method 0 == stored/8 == deflated and so on (see appnote.txt)*/ + sxu16 nExtra; /* Extra size if any */ + SyString sFileName; /* entry name & length */ + sxu32 nDup; /* Total number of entries with the same name */ + SyArchiveEntry *pNextHash, *pPrevHash; /* Hash collision chains */ + SyArchiveEntry *pNextName; /* Next entry with the same name */ + SyArchiveEntry *pNext, *pPrev; /* Next and previous entry in the list */ + sxu32 nHash; /* Hash of the entry name */ + void *pUserData; /* User data */ + sxu32 nMagic; /* Sanity check */ + }; + /* + * Extra flags for extending the file local header + */ +#define SXZIP_EXTRA_TIMESTAMP 0x001 /* Extended UNIX timestamp */ +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +#ifndef JX9_DISABLE_HASH_FUNC +/* MD5 context */ +typedef struct MD5Context MD5Context; +struct MD5Context { + sxu32 buf[4]; + sxu32 bits[2]; + unsigned char in[64]; +}; +/* SHA1 context */ +typedef struct SHA1Context SHA1Context; +struct SHA1Context { + unsigned int state[5]; + unsigned int count[2]; + unsigned char buffer[64]; +}; +#endif /* JX9_DISABLE_HASH_FUNC */ +/* JX9 private declaration */ +/* + * Memory Objects. + * Internally, the JX9 virtual machine manipulates nearly all JX9 values + * [i.e: string, int, float, resource, object, bool, null] as jx9_values structures. + * Each jx9_values struct may cache multiple representations (string, integer etc.) + * of the same value. + */ +struct jx9_value +{ + union{ + jx9_real rVal; /* Real value */ + sxi64 iVal; /* Integer value */ + void *pOther; /* Other values (Object, Array, Resource, Namespace, etc.) */ + }x; + sxi32 iFlags; /* Control flags (see below) */ + jx9_vm *pVm; /* VM this instance belong */ + SyBlob sBlob; /* String values */ + sxu32 nIdx; /* Object index in the global pool */ +}; +/* Allowed value types. + */ +#define MEMOBJ_STRING 0x001 /* Memory value is a UTF-8 string */ +#define MEMOBJ_INT 0x002 /* Memory value is an integer */ +#define MEMOBJ_REAL 0x004 /* Memory value is a real number */ +#define MEMOBJ_BOOL 0x008 /* Memory value is a boolean */ +#define MEMOBJ_NULL 0x020 /* Memory value is NULL */ +#define MEMOBJ_HASHMAP 0x040 /* Memory value is a hashmap (JSON representation of Array and Objects) */ +#define MEMOBJ_RES 0x100 /* Memory value is a resource [User private data] */ +/* Mask of all known types */ +#define MEMOBJ_ALL (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES) +/* Scalar variables + * According to the JX9 language reference manual + * Scalar variables are those containing an integer, float, string or boolean. + * Types array, object and resource are not scalar. + */ +#define MEMOBJ_SCALAR (MEMOBJ_STRING|MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) +/* + * The following macro clear the current jx9_value type and replace + * it with the given one. + */ +#define MemObjSetType(OBJ, TYPE) ((OBJ)->iFlags = ((OBJ)->iFlags&~MEMOBJ_ALL)|TYPE) +/* jx9_value cast method signature */ +typedef sxi32 (*ProcMemObjCast)(jx9_value *); +/* Forward reference */ +typedef struct jx9_output_consumer jx9_output_consumer; +typedef struct jx9_user_func jx9_user_func; +typedef struct jx9_conf jx9_conf; +/* + * An instance of the following structure store the default VM output + * consumer and it's private data. + * Client-programs can register their own output consumer callback + * via the [JX9_VM_CONFIG_OUTPUT] configuration directive. + * Please refer to the official documentation for more information + * on how to register an output consumer callback. + */ +struct jx9_output_consumer +{ + ProcConsumer xConsumer; /* VM output consumer routine */ + void *pUserData; /* Third argument to xConsumer() */ + ProcConsumer xDef; /* Default output consumer routine */ + void *pDefData; /* Third argument to xDef() */ +}; +/* + * JX9 engine [i.e: jx9 instance] configuration is stored in + * an instance of the following structure. + * Please refer to the official documentation for more information + * on how to configure your jx9 engine instance. + */ +struct jx9_conf +{ + ProcConsumer xErr; /* Compile-time error consumer callback */ + void *pErrData; /* Third argument to xErr() */ + SyBlob sErrConsumer; /* Default error consumer */ +}; +/* + * Signature of the C function responsible of expanding constant values. + */ +typedef void (*ProcConstant)(jx9_value *, void *); +/* + * Each registered constant [i.e: __TIME__, __DATE__, JX9_OS, INT_MAX, etc.] is stored + * in an instance of the following structure. + * Please refer to the official documentation for more information + * on how to create/install foreign constants. + */ +typedef struct jx9_constant jx9_constant; +struct jx9_constant +{ + SyString sName; /* Constant name */ + ProcConstant xExpand; /* Function responsible of expanding constant value */ + void *pUserData; /* Last argument to xExpand() */ +}; +typedef struct jx9_aux_data jx9_aux_data; +/* + * Auxiliary data associated with each foreign function is stored + * in a stack of the following structure. + * Note that automatic tracked chunks are also stored in an instance + * of this structure. + */ +struct jx9_aux_data +{ + void *pAuxData; /* Aux data */ +}; +/* Foreign functions signature */ +typedef int (*ProcHostFunction)(jx9_context *, int, jx9_value **); +/* + * Each installed foreign function is recored in an instance of the following + * structure. + * Please refer to the official documentation for more information on how + * to create/install foreign functions. + */ +struct jx9_user_func +{ + jx9_vm *pVm; /* VM that own this instance */ + SyString sName; /* Foreign function name */ + ProcHostFunction xFunc; /* Implementation of the foreign function */ + void *pUserData; /* User private data [Refer to the official documentation for more information]*/ + SySet aAux; /* Stack of auxiliary data [Refer to the official documentation for more information]*/ +}; +/* + * The 'context' argument for an installable function. A pointer to an + * instance of this structure is the first argument to the routines used + * implement the foreign functions. + */ +struct jx9_context +{ + jx9_user_func *pFunc; /* Function information. */ + jx9_value *pRet; /* Return value is stored here. */ + SySet sVar; /* Container of dynamically allocated jx9_values + * [i.e: Garbage collection purposes.] + */ + SySet sChunk; /* Track dynamically allocated chunks [jx9_aux_data instance]. + * [i.e: Garbage collection purposes.] + */ + jx9_vm *pVm; /* Virtual machine that own this context */ + sxi32 iFlags; /* Call flags */ +}; +/* Hashmap control flags */ +#define HASHMAP_JSON_OBJECT 0x001 /* Hashmap represent JSON Object*/ +/* + * Each hashmap entry [i.e: array(4, 5, 6)] is recorded in an instance + * of the following structure. + */ +struct jx9_hashmap_node +{ + jx9_hashmap *pMap; /* Hashmap that own this instance */ + sxi32 iType; /* Node type */ + union{ + sxi64 iKey; /* Int key */ + SyBlob sKey; /* Blob key */ + }xKey; + sxi32 iFlags; /* Control flags */ + sxu32 nHash; /* Key hash value */ + sxu32 nValIdx; /* Value stored in this node */ + jx9_hashmap_node *pNext, *pPrev; /* Link to other entries [i.e: linear traversal] */ + jx9_hashmap_node *pNextCollide, *pPrevCollide; /* Collision chain */ +}; +/* + * Each active hashmap aka array in the JX9 jargon is represented + * by an instance of the following structure. + */ +struct jx9_hashmap +{ + jx9_vm *pVm; /* VM that own this instance */ + jx9_hashmap_node **apBucket; /* Hash bucket */ + jx9_hashmap_node *pFirst; /* First inserted entry */ + jx9_hashmap_node *pLast; /* Last inserted entry */ + jx9_hashmap_node *pCur; /* Current entry */ + sxu32 nSize; /* Bucket size */ + sxu32 nEntry; /* Total number of inserted entries */ + sxu32 (*xIntHash)(sxi64); /* Hash function for int_keys */ + sxu32 (*xBlobHash)(const void *, sxu32); /* Hash function for blob_keys */ + sxi32 iFlags; /* Hashmap control flags */ + sxi64 iNextIdx; /* Next available automatically assigned index */ + sxi32 iRef; /* Reference count */ +}; +/* An instance of the following structure is the context + * for the FOREACH_STEP/FOREACH_INIT VM instructions. + * Those instructions are used to implement the 'foreach' + * statement. + * This structure is made available to these instructions + * as the P3 operand. + */ +struct jx9_foreach_info +{ + SyString sKey; /* Key name. Empty otherwise*/ + SyString sValue; /* Value name */ + sxi32 iFlags; /* Control flags */ + SySet aStep; /* Stack of steps [i.e: jx9_foreach_step instance] */ +}; +struct jx9_foreach_step +{ + sxi32 iFlags; /* Control flags (see below) */ + /* Iterate on this map*/ + jx9_hashmap *pMap; /* Hashmap [i.e: array in the JX9 jargon] iteration + * Ex: foreach(array(1, 2, 3) as $key=>$value){} + */ + +}; +/* Foreach step control flags */ +#define JX9_4EACH_STEP_KEY 0x001 /* Make Key available */ +/* + * Each JX9 engine is identified by an instance of the following structure. + * Please refer to the official documentation for more information + * on how to configure your JX9 engine instance. + */ +struct jx9 +{ + SyMemBackend sAllocator; /* Low level memory allocation subsystem */ + const jx9_vfs *pVfs; /* Underlying Virtual File System */ + jx9_conf xConf; /* Configuration */ +#if defined(JX9_ENABLE_THREADS) + SyMutex *pMutex; /* Per-engine mutex */ +#endif + jx9_vm *pVms; /* List of active VM */ + sxi32 iVm; /* Total number of active VM */ + jx9 *pNext, *pPrev; /* List of active engines */ + sxu32 nMagic; /* Sanity check against misuse */ +}; +/* Code generation data structures */ +typedef sxi32 (*ProcErrorGen)(void *, sxi32, sxu32, const char *, ...); +typedef struct jx9_expr_node jx9_expr_node; +typedef struct jx9_expr_op jx9_expr_op; +typedef struct jx9_gen_state jx9_gen_state; +typedef struct GenBlock GenBlock; +typedef sxi32 (*ProcLangConstruct)(jx9_gen_state *); +typedef sxi32 (*ProcNodeConstruct)(jx9_gen_state *, sxi32); +/* + * Each supported operator [i.e: +, -, ==, *, %, >>, >=, new, etc.] is represented + * by an instance of the following structure. + * The JX9 parser does not use any external tools and is 100% handcoded. + * That is, the JX9 parser is thread-safe , full reentrant, produce consistant + * compile-time errrors and at least 7 times faster than the standard JX9 parser. + */ +struct jx9_expr_op +{ + SyString sOp; /* String representation of the operator [i.e: "+", "*", "=="...] */ + sxi32 iOp; /* Operator ID */ + sxi32 iPrec; /* Operator precedence: 1 == Highest */ + sxi32 iAssoc; /* Operator associativity (either left, right or non-associative) */ + sxi32 iVmOp; /* VM OP code for this operator [i.e: JX9_OP_EQ, JX9_OP_LT, JX9_OP_MUL...]*/ +}; +/* + * Each expression node is parsed out and recorded + * in an instance of the following structure. + */ +struct jx9_expr_node +{ + const jx9_expr_op *pOp; /* Operator ID or NULL if literal, constant, variable, function or object method call */ + jx9_expr_node *pLeft; /* Left expression tree */ + jx9_expr_node *pRight; /* Right expression tree */ + SyToken *pStart; /* Stream of tokens that belong to this node */ + SyToken *pEnd; /* End of token stream */ + sxi32 iFlags; /* Node construct flags */ + ProcNodeConstruct xCode; /* C routine responsible of compiling this node */ + SySet aNodeArgs; /* Node arguments. Only used by postfix operators [i.e: function call]*/ + jx9_expr_node *pCond; /* Condition: Only used by the ternary operator '?:' */ +}; +/* Node Construct flags */ +#define EXPR_NODE_PRE_INCR 0x01 /* Pre-icrement/decrement [i.e: ++$i, --$j] node */ +/* + * A block of instructions is recorded in an instance of the following structure. + * This structure is used only during compile-time and have no meaning + * during bytecode execution. + */ +struct GenBlock +{ + jx9_gen_state *pGen; /* State of the code generator */ + GenBlock *pParent; /* Upper block or NULL if global */ + sxu32 nFirstInstr; /* First instruction to execute */ + sxi32 iFlags; /* Block control flags (see below) */ + SySet aJumpFix; /* Jump fixup (JumpFixup instance) */ + void *pUserData; /* Upper layer private data */ + /* The following two fields are used only when compiling + * the 'do..while()' language construct. + */ + sxu8 bPostContinue; /* TRUE when compiling the do..while() statement */ + SySet aPostContFix; /* Post-continue jump fix */ +}; +/* + * Code generator state is remembered in an instance of the following + * structure. We put the information in this structure and pass around + * a pointer to this structure, rather than pass around all of the + * information separately. This helps reduce the number of arguments + * to generator functions. + * This structure is used only during compile-time and have no meaning + * during bytecode execution. + */ +struct jx9_gen_state +{ + jx9_vm *pVm; /* VM that own this instance */ + SyHash hLiteral; /* Constant string Literals table */ + SyHash hNumLiteral; /* Numeric literals table */ + SyHash hVar; /* Collected variable hashtable */ + GenBlock *pCurrent; /* Current processed block */ + GenBlock sGlobal; /* Global block */ + ProcConsumer xErr; /* Error consumer callback */ + void *pErrData; /* Third argument to xErr() */ + SyToken *pIn; /* Current processed token */ + SyToken *pEnd; /* Last token in the stream */ + sxu32 nErr; /* Total number of compilation error */ +}; +/* Forward references */ +typedef struct jx9_vm_func_static_var jx9_vm_func_static_var; +typedef struct jx9_vm_func_arg jx9_vm_func_arg; +typedef struct jx9_vm_func jx9_vm_func; +typedef struct VmFrame VmFrame; +/* + * Each collected function argument is recorded in an instance + * of the following structure. + * Note that as an extension, JX9 implements full type hinting + * which mean that any function can have it's own signature. + * Example: + * function foo(int $a, string $b, float $c, ClassInstance $d){} + * This is how the powerful function overloading mechanism is + * implemented. + * Note that as an extension, JX9 allow function arguments to have + * any complex default value associated with them unlike the standard + * JX9 engine. + * Example: + * function foo(int $a = rand() & 1023){} + * now, when foo is called without arguments [i.e: foo()] the + * $a variable (first parameter) will be set to a random number + * between 0 and 1023 inclusive. + * Refer to the official documentation for more information on this + * mechanism and other extension introduced by the JX9 engine. + */ +struct jx9_vm_func_arg +{ + SyString sName; /* Argument name */ + SySet aByteCode; /* Compiled default value associated with this argument */ + sxu32 nType; /* Type of this argument [i.e: array, int, string, float, object, etc.] */ + sxi32 iFlags; /* Configuration flags */ +}; +/* + * Each static variable is parsed out and remembered in an instance + * of the following structure. + * Note that as an extension, JX9 allow static variable have + * any complex default value associated with them unlike the standard + * JX9 engine. + * Example: + * static $rand_str = 'JX9'.rand_str(3); // Concatenate 'JX9' with + * // a random three characters(English alphabet) + * dump($rand_str); + * //You should see something like this + * string(6 'JX9awt'); + */ +struct jx9_vm_func_static_var +{ + SyString sName; /* Static variable name */ + SySet aByteCode; /* Compiled initialization expression */ + sxu32 nIdx; /* Object index in the global memory object container */ +}; +/* Function configuration flags */ +#define VM_FUNC_ARG_HAS_DEF 0x001 /* Argument has default value associated with it */ +#define VM_FUNC_ARG_IGNORE 0x002 /* Do not install argument in the current frame */ +/* + * Each user defined function is parsed out and stored in an instance + * of the following structure. + * JX9 introduced some powerfull extensions to the JX9 5 programming + * language like function overloading, type hinting, complex default + * arguments values and many more. + * Please refer to the official documentation for more information. + */ +struct jx9_vm_func +{ + SySet aArgs; /* Expected arguments (jx9_vm_func_arg instance) */ + SySet aStatic; /* Static variable (jx9_vm_func_static_var instance) */ + SyString sName; /* Function name */ + SySet aByteCode; /* Compiled function body */ + sxi32 iFlags; /* VM function configuration */ + SyString sSignature; /* Function signature used to implement function overloading + * (Refer to the official docuemntation for more information + * on this powerfull feature) + */ + void *pUserData; /* Upper layer private data associated with this instance */ + jx9_vm_func *pNextName; /* Next VM function with the same name as this one */ +}; +/* Forward reference */ +typedef struct jx9_builtin_constant jx9_builtin_constant; +typedef struct jx9_builtin_func jx9_builtin_func; +/* + * Each built-in foreign function (C function) is stored in an + * instance of the following structure. + * Please refer to the official documentation for more information + * on how to create/install foreign functions. + */ +struct jx9_builtin_func +{ + const char *zName; /* Function name [i.e: strlen(), rand(), array_merge(), etc.]*/ + ProcHostFunction xFunc; /* C routine performing the computation */ +}; +/* + * Each built-in foreign constant is stored in an instance + * of the following structure. + * Please refer to the official documentation for more information + * on how to create/install foreign constants. + */ +struct jx9_builtin_constant +{ + const char *zName; /* Constant name */ + ProcConstant xExpand; /* C routine responsible of expanding constant value*/ +}; +/* + * A single instruction of the virtual machine has an opcode + * and as many as three operands. + * Each VM instruction resulting from compiling a JX9 script + * is stored in an instance of the following structure. + */ +typedef struct VmInstr VmInstr; +struct VmInstr +{ + sxu8 iOp; /* Operation to preform */ + sxi32 iP1; /* First operand */ + sxu32 iP2; /* Second operand (Often the jump destination) */ + void *p3; /* Third operand (Often Upper layer private data) */ +}; +/* Forward reference */ +typedef struct jx9_case_expr jx9_case_expr; +typedef struct jx9_switch jx9_switch; +/* + * Each compiled case block in a swicth statement is compiled + * and stored in an instance of the following structure. + */ +struct jx9_case_expr +{ + SySet aByteCode; /* Compiled body of the case block */ + sxu32 nStart; /* First instruction to execute */ +}; +/* + * Each compiled switch statement is parsed out and stored + * in an instance of the following structure. + */ +struct jx9_switch +{ + SySet aCaseExpr; /* Compile case block */ + sxu32 nOut; /* First instruction to execute after this statement */ + sxu32 nDefault; /* First instruction to execute in the default block */ +}; +/* Assertion flags */ +#define JX9_ASSERT_DISABLE 0x01 /* Disable assertion */ +#define JX9_ASSERT_WARNING 0x02 /* Issue a warning for each failed assertion */ +#define JX9_ASSERT_BAIL 0x04 /* Terminate execution on failed assertions */ +#define JX9_ASSERT_QUIET_EVAL 0x08 /* Not used */ +#define JX9_ASSERT_CALLBACK 0x10 /* Callback to call on failed assertions */ +/* + * An instance of the following structure hold the bytecode instructions + * resulting from compiling a JX9 script. + * This structure contains the complete state of the virtual machine. + */ +struct jx9_vm +{ + SyMemBackend sAllocator; /* Memory backend */ +#if defined(JX9_ENABLE_THREADS) + SyMutex *pMutex; /* Recursive mutex associated with this VM. */ +#endif + jx9 *pEngine; /* Interpreter that own this VM */ + SySet aByteCode; /* Default bytecode container */ + SySet *pByteContainer; /* Current bytecode container */ + VmFrame *pFrame; /* Stack of active frames */ + SyPRNGCtx sPrng; /* PRNG context */ + SySet aMemObj; /* Object allocation table */ + SySet aLitObj; /* Literals allocation table */ + jx9_value *aOps; /* Operand stack */ + SySet aFreeObj; /* Stack of free memory objects */ + SyHash hConstant; /* Host-application and user defined constants container */ + SyHash hHostFunction; /* Host-application installable functions */ + SyHash hFunction; /* Compiled functions */ + SyHash hSuper; /* Global variable */ + SyBlob sConsumer; /* Default VM consumer [i.e Redirect all VM output to this blob] */ + SyBlob sWorker; /* General purpose working buffer */ + SyBlob sArgv; /* $argv[] collector [refer to the [getopt()] implementation for more information] */ + SySet aFiles; /* Stack of processed files */ + SySet aPaths; /* Set of import paths */ + SySet aIncluded; /* Set of included files */ + SySet aIOstream; /* Installed IO stream container */ + const jx9_io_stream *pDefStream; /* Default IO stream [i.e: typically this is the 'file://' stream] */ + jx9_value sExec; /* Compiled script return value [Can be extracted via the JX9_VM_CONFIG_EXEC_VALUE directive]*/ + void *pStdin; /* STDIN IO stream */ + void *pStdout; /* STDOUT IO stream */ + void *pStderr; /* STDERR IO stream */ + int bErrReport; /* TRUE to report all runtime Error/Warning/Notice */ + int nRecursionDepth; /* Current recursion depth */ + int nMaxDepth; /* Maximum allowed recusion depth */ + sxu32 nOutputLen; /* Total number of generated output */ + jx9_output_consumer sVmConsumer; /* Registered output consumer callback */ + int iAssertFlags; /* Assertion flags */ + jx9_value sAssertCallback; /* Callback to call on failed assertions */ + sxi32 iExitStatus; /* Script exit status */ + jx9_gen_state sCodeGen; /* Code generator module */ + jx9_vm *pNext, *pPrev; /* List of active VM's */ + sxu32 nMagic; /* Sanity check against misuse */ +}; +/* + * Allowed value for jx9_vm.nMagic + */ +#define JX9_VM_INIT 0xEA12CD72 /* VM correctly initialized */ +#define JX9_VM_RUN 0xBA851227 /* VM ready to execute JX9 bytecode */ +#define JX9_VM_EXEC 0xCDFE1DAD /* VM executing JX9 bytecode */ +#define JX9_VM_STALE 0xDEAD2BAD /* Stale VM */ +/* + * Error codes according to the JX9 language reference manual. + */ +enum iErrCode +{ + E_ABORT = -1, /* deadliness error, should halt script execution. */ + E_ERROR = 1, /* Fatal run-time errors. These indicate errors that can not be recovered + * from, such as a memory allocation problem. Execution of the script is + * halted. + * The only fatal error under JX9 is an out-of-memory. All others erros + * even a call to undefined function will not halt script execution. + */ + E_WARNING , /* Run-time warnings (non-fatal errors). Execution of the script is not halted. */ + E_PARSE , /* Compile-time parse errors. Parse errors should only be generated by the parser.*/ + E_NOTICE , /* Run-time notices. Indicate that the script encountered something that could + * indicate an error, but could also happen in the normal course of running a script. + */ +}; +/* + * Each VM instruction resulting from compiling a JX9 script is represented + * by one of the following OP codes. + * The program consists of a linear sequence of operations. Each operation + * has an opcode and 3 operands.Operands P1 is an integer. + * Operand P2 is an unsigned integer and operand P3 is a memory address. + * Few opcodes use all 3 operands. + */ +enum jx9_vm_op { + JX9_OP_DONE = 1, /* Done */ + JX9_OP_HALT, /* Halt */ + JX9_OP_LOAD, /* Load memory object */ + JX9_OP_LOADC, /* Load constant */ + JX9_OP_LOAD_IDX, /* Load array entry */ + JX9_OP_LOAD_MAP, /* Load hashmap('array') */ + JX9_OP_NOOP, /* NOOP */ + JX9_OP_JMP, /* Unconditional jump */ + JX9_OP_JZ, /* Jump on zero (FALSE jump) */ + JX9_OP_JNZ, /* Jump on non-zero (TRUE jump) */ + JX9_OP_POP, /* Stack POP */ + JX9_OP_CAT, /* Concatenation */ + JX9_OP_CVT_INT, /* Integer cast */ + JX9_OP_CVT_STR, /* String cast */ + JX9_OP_CVT_REAL, /* Float cast */ + JX9_OP_CALL, /* Function call */ + JX9_OP_UMINUS, /* Unary minus '-'*/ + JX9_OP_UPLUS, /* Unary plus '+'*/ + JX9_OP_BITNOT, /* Bitwise not '~' */ + JX9_OP_LNOT, /* Logical not '!' */ + JX9_OP_MUL, /* Multiplication '*' */ + JX9_OP_DIV, /* Division '/' */ + JX9_OP_MOD, /* Modulus '%' */ + JX9_OP_ADD, /* Add '+' */ + JX9_OP_SUB, /* Sub '-' */ + JX9_OP_SHL, /* Left shift '<<' */ + JX9_OP_SHR, /* Right shift '>>' */ + JX9_OP_LT, /* Less than '<' */ + JX9_OP_LE, /* Less or equal '<=' */ + JX9_OP_GT, /* Greater than '>' */ + JX9_OP_GE, /* Greater or equal '>=' */ + JX9_OP_EQ, /* Equal '==' */ + JX9_OP_NEQ, /* Not equal '!=' */ + JX9_OP_TEQ, /* Type equal '===' */ + JX9_OP_TNE, /* Type not equal '!==' */ + JX9_OP_BAND, /* Bitwise and '&' */ + JX9_OP_BXOR, /* Bitwise xor '^' */ + JX9_OP_BOR, /* Bitwise or '|' */ + JX9_OP_LAND, /* Logical and '&&','and' */ + JX9_OP_LOR, /* Logical or '||','or' */ + JX9_OP_LXOR, /* Logical xor 'xor' */ + JX9_OP_STORE, /* Store Object */ + JX9_OP_STORE_IDX, /* Store indexed object */ + JX9_OP_PULL, /* Stack pull */ + JX9_OP_SWAP, /* Stack swap */ + JX9_OP_YIELD, /* Stack yield */ + JX9_OP_CVT_BOOL, /* Boolean cast */ + JX9_OP_CVT_NUMC, /* Numeric (integer, real or both) type cast */ + JX9_OP_INCR, /* Increment ++ */ + JX9_OP_DECR, /* Decrement -- */ + JX9_OP_ADD_STORE, /* Add and store '+=' */ + JX9_OP_SUB_STORE, /* Sub and store '-=' */ + JX9_OP_MUL_STORE, /* Mul and store '*=' */ + JX9_OP_DIV_STORE, /* Div and store '/=' */ + JX9_OP_MOD_STORE, /* Mod and store '%=' */ + JX9_OP_CAT_STORE, /* Cat and store '.=' */ + JX9_OP_SHL_STORE, /* Shift left and store '>>=' */ + JX9_OP_SHR_STORE, /* Shift right and store '<<=' */ + JX9_OP_BAND_STORE, /* Bitand and store '&=' */ + JX9_OP_BOR_STORE, /* Bitor and store '|=' */ + JX9_OP_BXOR_STORE, /* Bitxor and store '^=' */ + JX9_OP_CONSUME, /* Consume VM output */ + JX9_OP_MEMBER, /* Object member run-time access */ + JX9_OP_UPLINK, /* Run-Time frame link */ + JX9_OP_CVT_NULL, /* NULL cast */ + JX9_OP_CVT_ARRAY, /* Array cast */ + JX9_OP_FOREACH_INIT, /* For each init */ + JX9_OP_FOREACH_STEP, /* For each step */ + JX9_OP_SWITCH /* Switch operation */ +}; +/* -- END-OF INSTRUCTIONS -- */ +/* + * Expression Operators ID. + */ +enum jx9_expr_id { + EXPR_OP_DOT, /* Member access */ + EXPR_OP_DC, /* :: */ + EXPR_OP_SUBSCRIPT, /* []: Subscripting */ + EXPR_OP_FUNC_CALL, /* func_call() */ + EXPR_OP_INCR, /* ++ */ + EXPR_OP_DECR, /* -- */ + EXPR_OP_BITNOT, /* ~ */ + EXPR_OP_UMINUS, /* Unary minus */ + EXPR_OP_UPLUS, /* Unary plus */ + EXPR_OP_TYPECAST, /* Type cast [i.e: (int), (float), (string)...] */ + EXPR_OP_ALT, /* @ */ + EXPR_OP_INSTOF, /* instanceof */ + EXPR_OP_LOGNOT, /* logical not ! */ + EXPR_OP_MUL, /* Multiplication */ + EXPR_OP_DIV, /* division */ + EXPR_OP_MOD, /* Modulus */ + EXPR_OP_ADD, /* Addition */ + EXPR_OP_SUB, /* Substraction */ + EXPR_OP_DDOT, /* Concatenation */ + EXPR_OP_SHL, /* Left shift */ + EXPR_OP_SHR, /* Right shift */ + EXPR_OP_LT, /* Less than */ + EXPR_OP_LE, /* Less equal */ + EXPR_OP_GT, /* Greater than */ + EXPR_OP_GE, /* Greater equal */ + EXPR_OP_EQ, /* Equal == */ + EXPR_OP_NE, /* Not equal != <> */ + EXPR_OP_TEQ, /* Type equal === */ + EXPR_OP_TNE, /* Type not equal !== */ + EXPR_OP_SEQ, /* String equal 'eq' */ + EXPR_OP_SNE, /* String not equal 'ne' */ + EXPR_OP_BAND, /* Biwise and '&' */ + EXPR_OP_REF, /* Reference operator '&' */ + EXPR_OP_XOR, /* bitwise xor '^' */ + EXPR_OP_BOR, /* bitwise or '|' */ + EXPR_OP_LAND, /* Logical and '&&','and' */ + EXPR_OP_LOR, /* Logical or '||','or'*/ + EXPR_OP_LXOR, /* Logical xor 'xor' */ + EXPR_OP_QUESTY, /* Ternary operator '?' */ + EXPR_OP_ASSIGN, /* Assignment '=' */ + EXPR_OP_ADD_ASSIGN, /* Combined operator: += */ + EXPR_OP_SUB_ASSIGN, /* Combined operator: -= */ + EXPR_OP_MUL_ASSIGN, /* Combined operator: *= */ + EXPR_OP_DIV_ASSIGN, /* Combined operator: /= */ + EXPR_OP_MOD_ASSIGN, /* Combined operator: %= */ + EXPR_OP_DOT_ASSIGN, /* Combined operator: .= */ + EXPR_OP_AND_ASSIGN, /* Combined operator: &= */ + EXPR_OP_OR_ASSIGN, /* Combined operator: |= */ + EXPR_OP_XOR_ASSIGN, /* Combined operator: ^= */ + EXPR_OP_SHL_ASSIGN, /* Combined operator: <<= */ + EXPR_OP_SHR_ASSIGN, /* Combined operator: >>= */ + EXPR_OP_COMMA /* Comma expression */ +}; +/* + * Lexer token codes + * The following set of constants are the tokens recognized + * by the lexer when processing JX9 input. + * Important: Token values MUST BE A POWER OF TWO. + */ +#define JX9_TK_INTEGER 0x0000001 /* Integer */ +#define JX9_TK_REAL 0x0000002 /* Real number */ +#define JX9_TK_NUM (JX9_TK_INTEGER|JX9_TK_REAL) /* Numeric token, either integer or real */ +#define JX9_TK_KEYWORD 0x0000004 /* Keyword [i.e: while, for, if, foreach...] */ +#define JX9_TK_ID 0x0000008 /* Alphanumeric or UTF-8 stream */ +#define JX9_TK_DOLLAR 0x0000010 /* '$' Dollar sign */ +#define JX9_TK_OP 0x0000020 /* Operator [i.e: +, *, /...] */ +#define JX9_TK_OCB 0x0000040 /* Open curly brace'{' */ +#define JX9_TK_CCB 0x0000080 /* Closing curly brace'}' */ +#define JX9_TK_DOT 0x0000100 /* Dot . */ +#define JX9_TK_LPAREN 0x0000200 /* Left parenthesis '(' */ +#define JX9_TK_RPAREN 0x0000400 /* Right parenthesis ')' */ +#define JX9_TK_OSB 0x0000800 /* Open square bracket '[' */ +#define JX9_TK_CSB 0x0001000 /* Closing square bracket ']' */ +#define JX9_TK_DSTR 0x0002000 /* Double quoted string "$str" */ +#define JX9_TK_SSTR 0x0004000 /* Single quoted string 'str' */ +#define JX9_TK_NOWDOC 0x0010000 /* Nowdoc <<< */ +#define JX9_TK_COMMA 0x0020000 /* Comma ',' */ +#define JX9_TK_SEMI 0x0040000 /* Semi-colon ";" */ +#define JX9_TK_BSTR 0x0080000 /* Backtick quoted string [i.e: Shell command `date`] */ +#define JX9_TK_COLON 0x0100000 /* single Colon ':' */ +#define JX9_TK_AMPER 0x0200000 /* Ampersand '&' */ +#define JX9_TK_EQUAL 0x0400000 /* Equal '=' */ +#define JX9_TK_OTHER 0x1000000 /* Other symbols */ +/* + * JX9 keyword. + * These words have special meaning in JX9. Some of them represent things which look like + * functions, some look like constants, and so on, but they're not, really: they are language constructs. + * You cannot use any of the following words as constants, object names, function or method names. + * Using them as variable names is generally OK, but could lead to confusion. + */ +#define JX9_TKWRD_SWITCH 1 /* switch */ +#define JX9_TKWRD_PRINT 2 /* print */ +#define JX9_TKWRD_ELIF 0x4000000 /* elseif: MUST BE A POWER OF TWO */ +#define JX9_TKWRD_ELSE 0x8000000 /* else: MUST BE A POWER OF TWO */ +#define JX9_TKWRD_IF 3 /* if */ +#define JX9_TKWRD_STATIC 4 /* static */ +#define JX9_TKWRD_CASE 5 /* case */ +#define JX9_TKWRD_FUNCTION 6 /* function */ +#define JX9_TKWRD_CONST 7 /* const */ +/* The number '8' is reserved for JX9_TK_ID */ +#define JX9_TKWRD_WHILE 9 /* while */ +#define JX9_TKWRD_DEFAULT 10 /* default */ +#define JX9_TKWRD_AS 11 /* as */ +#define JX9_TKWRD_CONTINUE 12 /* continue */ +#define JX9_TKWRD_EXIT 13 /* exit */ +#define JX9_TKWRD_DIE 14 /* die */ +#define JX9_TKWRD_IMPORT 15 /* import */ +#define JX9_TKWRD_INCLUDE 16 /* include */ +#define JX9_TKWRD_FOR 17 /* for */ +#define JX9_TKWRD_FOREACH 18 /* foreach */ +#define JX9_TKWRD_RETURN 19 /* return */ +#define JX9_TKWRD_BREAK 20 /* break */ +#define JX9_TKWRD_UPLINK 21 /* uplink */ +#define JX9_TKWRD_BOOL 0x8000 /* bool: MUST BE A POWER OF TWO */ +#define JX9_TKWRD_INT 0x10000 /* int: MUST BE A POWER OF TWO */ +#define JX9_TKWRD_FLOAT 0x20000 /* float: MUST BE A POWER OF TWO */ +#define JX9_TKWRD_STRING 0x40000 /* string: MUST BE A POWER OF TWO */ + +/* api.c */ +JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap); +JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName); +JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName); +/* json.c function prototypes */ +JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut); +JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte); +/* memobj.c function prototypes */ +JX9_PRIVATE sxi32 jx9MemObjDump(SyBlob *pOut, jx9_value *pObj); +JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal); +JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore); +JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest); +JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal); +JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray); +#if 0 +/* Not used in the current release of the JX9 engine */ +JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal); +#endif +JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal); +JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal); +JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen); +#if 0 +/* Not used in the current release of the JX9 engine */ +JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap); +#endif +JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest); +JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest); +JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj); +JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags); +JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj); +JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj); +JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pData); +/* lex.c function prototypes */ +JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput, sxu32 nLen, SySet *pOut); +/* vm.c function prototypes */ +JX9_PRIVATE void jx9VmReleaseContextValue(jx9_context *pCtx, jx9_value *pValue); +JX9_PRIVATE sxi32 jx9VmInitFuncState(jx9_vm *pVm, jx9_vm_func *pFunc, const char *zName, sxu32 nByte, + sxi32 iFlags, void *pUserData); +JX9_PRIVATE sxi32 jx9VmInstallUserFunction(jx9_vm *pVm, jx9_vm_func *pFunc, SyString *pName); +JX9_PRIVATE sxi32 jx9VmRegisterConstant(jx9_vm *pVm, const SyString *pName, ProcConstant xExpand, void *pUserData); +JX9_PRIVATE sxi32 jx9VmInstallForeignFunction(jx9_vm *pVm, const SyString *pName, ProcHostFunction xFunc, void *pUserData); +JX9_PRIVATE sxi32 jx9VmBlobConsumer(const void *pSrc, unsigned int nLen, void *pUserData); +JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIndex); +JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex); +JX9_PRIVATE sxi32 jx9VmOutputConsume(jx9_vm *pVm, SyString *pString); +JX9_PRIVATE sxi32 jx9VmOutputConsumeAp(jx9_vm *pVm, const char *zFormat, va_list ap); +JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap); +JX9_PRIVATE sxi32 jx9VmThrowError(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zMessage); +JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData); +JX9_PRIVATE sxi32 jx9VmDump(jx9_vm *pVm, ProcConsumer xConsumer, void *pUserData); +JX9_PRIVATE sxi32 jx9VmInit(jx9_vm *pVm, jx9 *pEngine); +JX9_PRIVATE sxi32 jx9VmConfigure(jx9_vm *pVm, sxi32 nOp, va_list ap); +JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm); +JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar); +JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm); +JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm); +JX9_PRIVATE sxi32 jx9VmMakeReady(jx9_vm *pVm); +JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm); +JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm); +JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm); +JX9_PRIVATE VmInstr *jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex); +JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm); +JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer); +JX9_PRIVATE sxi32 jx9VmEmitInstr(jx9_vm *pVm, sxi32 iOp, sxi32 iP1, sxu32 iP2, void *p3, sxu32 *pIndex); +JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm); +JX9_PRIVATE sxi32 jx9VmCallUserFunction(jx9_vm *pVm, jx9_value *pFunc, int nArg, jx9_value **apArg, jx9_value *pResult); +JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp(jx9_vm *pVm, jx9_value *pFunc, jx9_value *pResult, ...); +JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm, sxu32 nObjIdx); +JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen); +JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue); +JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew); +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice(jx9_vm *pVm, const char **pzDevice, int nByte); +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +JX9_PRIVATE int jx9Utf8Read( + const unsigned char *z, /* First byte of UTF-8 character */ + const unsigned char *zTerm, /* Pretend this byte is 0x00 */ + const unsigned char **pzNext /* Write first byte past UTF-8 char here */ +); +/* parse.c function prototypes */ +JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID); +JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot); +JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart, SyToken *pEnd, SyToken **ppNext); +JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn, SyToken *pEnd, sxu32 nTokStart, sxu32 nTokEnd, SyToken **ppEnd); +JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast); +JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet); +/* compile.c function prototypes */ +JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType); +JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen, sxi32 iCompileFlag); +JX9_PRIVATE sxi32 jx9InitCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); +JX9_PRIVATE sxi32 jx9ResetCodeGenerator(jx9_vm *pVm, ProcConsumer xErr, void *pErrData); +JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen, sxi32 nErrType, sxu32 nLine, const char *zFormat, ...); +JX9_PRIVATE sxi32 jx9CompileScript(jx9_vm *pVm, SyString *pScript, sxi32 iFlags); +/* constant.c function prototypes */ +JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm); +/* builtin.c function prototypes */ +JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm); +/* hashmap.c function prototypes */ +JX9_PRIVATE jx9_hashmap * jx9NewHashmap(jx9_vm *pVm, sxu32 (*xIntHash)(sxi64), sxu32 (*xBlobHash)(const void *, sxu32)); +JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm); +JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS); +JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap); +JX9_PRIVATE sxi32 jx9HashmapLookup(jx9_hashmap *pMap, jx9_value *pKey, jx9_hashmap_node **ppNode); +JX9_PRIVATE sxi32 jx9HashmapInsert(jx9_hashmap *pMap, jx9_value *pKey, jx9_value *pVal); +JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight); +JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest); +JX9_PRIVATE sxi32 jx9HashmapCmp(jx9_hashmap *pLeft, jx9_hashmap *pRight, int bStrict); +JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap); +JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap); +JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode); +JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore); +JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode, jx9_value *pKey); +JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm); +JX9_PRIVATE sxi32 jx9HashmapWalk(jx9_hashmap *pMap, int (*xWalk)(jx9_value *, jx9_value *, void *), void *pUserData); +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut); +/* builtin.c function prototypes */ +JX9_PRIVATE sxi32 jx9InputFormat(int (*xConsumer)(jx9_context *, const char *, int, void *), + jx9_context *pCtx, const char *zIn, int nByte, int nArg, jx9_value **apArg, void *pUserData, int vf); +JX9_PRIVATE sxi32 jx9ProcessCsv(const char *zInput, int nByte, int delim, int encl, + int escape, sxi32 (*xConsumer)(const char *, int, void *), void *pUserData); +JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData); +JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen); +JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection); +#endif +/* vfs.c */ +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile, + int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew); +JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut); +JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle); +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen); +JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm); +JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void); +JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm); +JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm); +JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm); +/* lib.c function prototypes */ +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp); +JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch); +JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch); +JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry); +JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen); +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData); +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifndef JX9_DISABLE_HASH_FUNC +JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen); +JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len); +JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx); +JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx); +JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]); +JX9_PRIVATE void SHA1Init(SHA1Context *context); +JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len); +JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]); +JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]); +#endif +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen); +JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void *pUserData); +JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...); +JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap); +JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...); +JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...); +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE const char *SyTimeGetMonth(sxi32 iMonth); +JX9_PRIVATE const char *SyTimeGetDay(sxi32 iDay); +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8); +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); +#endif +JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex); +JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp); +JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData); +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); +JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData); +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen); +JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); +JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); +JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); +JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); +JX9_PRIVATE sxi32 SyHexToint(sxi32 c); +JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); +JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void *pOutVal, const char **zRest); +JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail); +JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData); +JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32(*xStep)(SyHashEntry *, void *), void *pUserData); +JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData); +JX9_PRIVATE SyHashEntry *SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen); +JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash); +JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp); +JX9_PRIVATE void *SySetAt(SySet *pSet, sxu32 nIdx); +JX9_PRIVATE void *SySetPop(SySet *pSet); +JX9_PRIVATE void *SySetPeek(SySet *pSet); +JX9_PRIVATE sxi32 SySetRelease(SySet *pSet); +JX9_PRIVATE sxi32 SySetReset(SySet *pSet); +JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet); +JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry); +JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem); +JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem); +JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize); +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft); +#endif +JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob); +JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob); +JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen); +JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest); +JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob); +JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize); +JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte); +JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator); +JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize); +JX9_PRIVATE char *SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize); +JX9_PRIVATE void *SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize); +JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend); +JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void *pUserData); +JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void *pUserData); +JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent); +#if 0 +/* Not used in the current release of the JX9 engine */ +JX9_PRIVATE void *SyMemBackendPoolRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); +#endif +JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void *pChunk); +JX9_PRIVATE void *SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte); +JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void *pChunk); +JX9_PRIVATE void *SyMemBackendRealloc(SyMemBackend *pBackend, void *pOld, sxu32 nByte); +JX9_PRIVATE void *SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte); +JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen); +JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize); +JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize); +JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen); +JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen); +#if !defined(JX9_DISABLE_BUILTIN_FUNC) || defined(__APPLE__) +JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen); +#endif +JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos); +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); +#endif +JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos); +JX9_PRIVATE sxu32 SyStrlen(const char *zSrc); +#if defined(JX9_ENABLE_THREADS) +JX9_PRIVATE const SyMutexMethods *SyMutexExportMethods(void); +JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods); +JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend); +#endif +JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb); +JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB); +JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb); +JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB); +JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64); +JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64); +JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64); +JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32); +JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16); +JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut); +JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut); +#endif /* __JX9INT_H__ */ + +/* + * ---------------------------------------------------------- + * File: unqliteInt.h + * MD5: 325816ce05f6adbaab2c39a41875dedd + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: unqliteInt.h v1.7 FreeBSD 2012-11-02 11:25 devel $ */ +#ifndef __UNQLITEINT_H__ +#define __UNQLITEINT_H__ +/* Internal interface definitions for UnQLite. */ +#ifdef UNQLITE_AMALGAMATION +/* Marker for routines not intended for external use */ +#define UNQLITE_PRIVATE static +#define JX9_AMALGAMATION +#else +#define UNQLITE_PRIVATE +#include "unqlite.h" +#include "jx9Int.h" +#endif +/* forward declaration */ +typedef struct unqlite_db unqlite_db; +/* +** The following values may be passed as the second argument to +** UnqliteOsLock(). The various locks exhibit the following semantics: +** +** SHARED: Any number of processes may hold a SHARED lock simultaneously. +** RESERVED: A single process may hold a RESERVED lock on a file at +** any time. Other processes may hold and obtain new SHARED locks. +** PENDING: A single process may hold a PENDING lock on a file at +** any one time. Existing SHARED locks may persist, but no new +** SHARED locks may be obtained by other processes. +** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. +** +** PENDING_LOCK may not be passed directly to UnqliteOsLock(). Instead, a +** process that requests an EXCLUSIVE lock may actually obtain a PENDING +** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to +** UnqliteOsLock(). +*/ +#define NO_LOCK 0 +#define SHARED_LOCK 1 +#define RESERVED_LOCK 2 +#define PENDING_LOCK 3 +#define EXCLUSIVE_LOCK 4 +/* + * UnQLite Locking Strategy (Same as SQLite3) + * + * The following #defines specify the range of bytes used for locking. + * SHARED_SIZE is the number of bytes available in the pool from which + * a random byte is selected for a shared lock. The pool of bytes for + * shared locks begins at SHARED_FIRST. + * + * The same locking strategy and byte ranges are used for Unix and Windows. + * This leaves open the possiblity of having clients on winNT, and + * unix all talking to the same shared file and all locking correctly. + * To do so would require that samba (or whatever + * tool is being used for file sharing) implements locks correctly between + * windows and unix. I'm guessing that isn't likely to happen, but by + * using the same locking range we are at least open to the possibility. + * + * Locking in windows is mandatory. For this reason, we cannot store + * actual data in the bytes used for locking. The pager never allocates + * the pages involved in locking therefore. SHARED_SIZE is selected so + * that all locks will fit on a single page even at the minimum page size. + * PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE + * is set high so that we don't have to allocate an unused page except + * for very large databases. But one should test the page skipping logic + * by setting PENDING_BYTE low and running the entire regression suite. + * + * Changing the value of PENDING_BYTE results in a subtly incompatible + * file format. Depending on how it is changed, you might not notice + * the incompatibility right away, even running a full regression test. + * The default location of PENDING_BYTE is the first byte past the + * 1GB boundary. + */ +#define PENDING_BYTE (0x40000000) +#define RESERVED_BYTE (PENDING_BYTE+1) +#define SHARED_FIRST (PENDING_BYTE+2) +#define SHARED_SIZE 510 +/* + * The default size of a disk sector in bytes. + */ +#ifndef UNQLITE_DEFAULT_SECTOR_SIZE +#define UNQLITE_DEFAULT_SECTOR_SIZE 512 +#endif +/* + * Each open database file is managed by a separate instance + * of the "Pager" structure. + */ +typedef struct Pager Pager; +/* + * Each database file to be accessed by the system is an instance + * of the following structure. + */ +struct unqlite_db +{ + Pager *pPager; /* Pager and Transaction manager */ + jx9 *pJx9; /* Jx9 Engine handle */ + unqlite_kv_cursor *pCursor; /* Database cursor for common usage */ +}; +/* + * Each database connection is an instance of the following structure. + */ +struct unqlite +{ + SyMemBackend sMem; /* Memory allocator subsystem */ + SyBlob sErr; /* Error log */ + unqlite_db sDB; /* Storage backend */ +#if defined(UNQLITE_ENABLE_THREADS) + const SyMutexMethods *pMethods; /* Mutex methods */ + SyMutex *pMutex; /* Per-handle mutex */ +#endif + unqlite_vm *pVms; /* List of active VM */ + sxi32 iVm; /* Total number of active VM */ + sxi32 iFlags; /* Control flags (See below) */ + unqlite *pNext,*pPrev; /* List of active DB handles */ + sxu32 nMagic; /* Sanity check against misuse */ +}; +#define UNQLITE_FL_DISABLE_AUTO_COMMIT 0x001 /* Disable auto-commit on close */ +/* + * VM control flags (Mostly related to collection handling). + */ +#define UNQLITE_VM_COLLECTION_CREATE 0x001 /* Create a new collection */ +#define UNQLITE_VM_COLLECTION_EXISTS 0x002 /* Exists old collection */ +#define UNQLITE_VM_AUTO_LOAD 0x004 /* Auto load a collection from the vfs */ +/* Forward declaration */ +typedef struct unqlite_col_record unqlite_col_record; +typedef struct unqlite_col unqlite_col; +/* + * Each an in-memory collection record is stored in an instance + * of the following structure. + */ +struct unqlite_col_record +{ + unqlite_col *pCol; /* Collecion this record belong */ + jx9_int64 nId; /* Unique record ID */ + jx9_value sValue; /* In-memory value of the record */ + unqlite_col_record *pNextCol,*pPrevCol; /* Collision chain */ + unqlite_col_record *pNext,*pPrev; /* Linked list of records */ +}; +/* + * Magic number to identify a valid collection on disk. + */ +#define UNQLITE_COLLECTION_MAGIC 0x611E /* sizeof(unsigned short) 2 bytes */ +/* + * A loaded collection is identified by an instance of the following structure. + */ +struct unqlite_col +{ + unqlite_vm *pVm; /* VM that own this instance */ + SyString sName; /* ID of the collection */ + sxu32 nHash; /* sName hash */ + jx9_value sSchema; /* Collection schema */ + sxu32 nSchemaOfft; /* Shema offset in sHeader */ + SyBlob sWorker; /* General purpose working buffer */ + SyBlob sHeader; /* Collection binary header */ + jx9_int64 nLastid; /* Last collection record ID */ + jx9_int64 nCurid; /* Current record ID */ + jx9_int64 nTotRec; /* Total number of records in the collection */ + int iFlags; /* Control flags (see below) */ + unqlite_col_record **apRecord; /* Hashtable of loaded records */ + unqlite_col_record *pList; /* Linked list of records */ + sxu32 nRec; /* Total number of records in apRecord[] */ + sxu32 nRecSize; /* apRecord[] size */ + Sytm sCreation; /* Colleation creation time */ + unqlite_kv_cursor *pCursor; /* Cursor pointing to the raw binary data */ + unqlite_col *pNext,*pPrev; /* Next and previous collection in the chain */ + unqlite_col *pNextCol,*pPrevCol; /* Collision chain */ +}; +/* + * Each unQLite Virtual Machine resulting from successful compilation of + * a Jx9 script is represented by an instance of the following structure. + */ +struct unqlite_vm +{ + unqlite *pDb; /* Database handle that own this instance */ + SyMemBackend sAlloc; /* Private memory allocator */ +#if defined(UNQLITE_ENABLE_THREADS) + SyMutex *pMutex; /* Recursive mutex associated with this VM. */ +#endif + unqlite_col **apCol; /* Table of loaded collections */ + unqlite_col *pCol; /* List of loaded collections */ + sxu32 iCol; /* Total number of loaded collections */ + sxu32 iColSize; /* apCol[] size */ + jx9_vm *pJx9Vm; /* Compiled Jx9 script*/ + unqlite_vm *pNext,*pPrev; /* Linked list of active unQLite VM */ + sxu32 nMagic; /* Magic number to avoid misuse */ +}; +/* + * Database signature to identify a valid database image. + */ +#define UNQLITE_DB_SIG "unqlite" +/* + * Database magic number (4 bytes). + */ +#define UNQLITE_DB_MAGIC 0xDB7C2712 +/* + * Maximum page size in bytes. + */ +#ifdef UNQLITE_MAX_PAGE_SIZE +# undef UNQLITE_MAX_PAGE_SIZE +#endif +#define UNQLITE_MAX_PAGE_SIZE 65536 /* 65K */ +/* + * Minimum page size in bytes. + */ +#ifdef UNQLITE_MIN_PAGE_SIZE +# undef UNQLITE_MIN_PAGE_SIZE +#endif +#define UNQLITE_MIN_PAGE_SIZE 512 +/* + * The default size of a database page. + */ +#ifndef UNQLITE_DEFAULT_PAGE_SIZE +# undef UNQLITE_DEFAULT_PAGE_SIZE +#endif +# define UNQLITE_DEFAULT_PAGE_SIZE 4096 /* 4K */ +/* Forward declaration */ +typedef struct Bitvec Bitvec; +/* Private library functions */ +/* api.c */ +UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void); +UNQLITE_PRIVATE int unqliteDataConsumer( + const void *pOut, /* Data to consume */ + unsigned int nLen, /* Data length */ + void *pUserData /* User private data */ + ); +UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore( + const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */ + sxu32 nByte /* zName length */ + ); +UNQLITE_PRIVATE int unqliteGetPageSize(void); +UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr); +UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...); +UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb); +/* unql_vm.c */ +UNQLITE_PRIVATE int unqliteExistsCollection(unqlite_vm *pVm, SyString *pName); +UNQLITE_PRIVATE int unqliteCreateCollection(unqlite_vm *pVm,SyString *pName); +UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol); +UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol); +UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord(unqlite_col *pCol,jx9_int64 nId); +UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol); +UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol); +UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue); +UNQLITE_PRIVATE int unqliteCollectionFetchRecordById(unqlite_col *pCol,jx9_int64 nId,jx9_value *pValue); +UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch(unqlite_vm *pVm,SyString *pCol,int iFlag); +UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue); +UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag); +UNQLITE_PRIVATE int unqliteCollectionDropRecord(unqlite_col *pCol,jx9_int64 nId,int wr_header,int log_err); +UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol); +/* unql_jx9.c */ +UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm); +/* fastjson.c */ +UNQLITE_PRIVATE sxi32 FastJsonEncode( + jx9_value *pValue, /* Value to encode */ + SyBlob *pOut, /* Store encoded value here */ + int iNest /* Nesting limit */ + ); +UNQLITE_PRIVATE sxi32 FastJsonDecode( + const void *pIn, /* Binary JSON */ + sxu32 nByte, /* Chunk delimiter */ + jx9_value *pOut, /* Decoded value */ + const unsigned char **pzPtr, + int iNest /* Nesting limit */ + ); +/* vfs.c [io_win.c, io_unix.c ] */ +UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void); +/* mem_kv.c */ +UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void); +/* lhash_kv.c */ +UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void); +/* os.c */ +UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset); +UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset); +UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size); +UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags); +UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize); +UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType); +UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType); +UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut); +UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id); +UNQLITE_PRIVATE int unqliteOsOpen( + unqlite_vfs *pVfs, + SyMemBackend *pAlloc, + const char *zPath, + unqlite_file **ppOut, + unsigned int flags +); +UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId); +UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync); +UNQLITE_PRIVATE int unqliteOsAccess(unqlite_vfs *pVfs,const char *zPath,int flags,int *pResOut); +/* bitmap.c */ +UNQLITE_PRIVATE Bitvec *unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize); +UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i); +UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i); +UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p); +/* pager.c */ +UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut); +UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur); +UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage); +UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager); +UNQLITE_PRIVATE int unqlitePagerOpen( + unqlite_vfs *pVfs, /* The virtual file system to use */ + unqlite *pDb, /* Database handle */ + const char *zFilename, /* Name of the database file to open */ + unsigned int iFlags /* flags controlling this file */ + ); +UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods); +UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb); +UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager); +UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager); +UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine); +UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen); +UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager); +#endif /* __UNQLITEINT_H__ */ +/* + * ---------------------------------------------------------- + * File: api.c + * MD5: d79e8404e50dacd0ea75635c1ebe553a + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: api.c v2.0 FreeBSD 2012-11-08 23:07 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* This file implement the public interfaces presented to host-applications. + * Routines in other files are for internal use by UnQLite and should not be + * accessed by users of the library. + */ +#define UNQLITE_DB_MISUSE(DB) (DB == 0 || DB->nMagic != UNQLITE_DB_MAGIC) +#define UNQLITE_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE) +/* If another thread have released a working instance, the following macros + * evaluates to true. These macros are only used when the library + * is built with threading support enabled. + */ +#define UNQLITE_THRD_DB_RELEASE(DB) (DB->nMagic != UNQLITE_DB_MAGIC) +#define UNQLITE_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE) +/* IMPLEMENTATION: unqlite@embedded@symisc 118-09-4785 */ +/* + * All global variables are collected in the structure named "sUnqlMPGlobal". + * That way it is clear in the code when we are using static variable because + * its name start with sUnqlMPGlobal. + */ +static struct unqlGlobal_Data +{ + SyMemBackend sAllocator; /* Global low level memory allocator */ +#if defined(UNQLITE_ENABLE_THREADS) + const SyMutexMethods *pMutexMethods; /* Mutex methods */ + SyMutex *pMutex; /* Global mutex */ + sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded + * The threading level can be set using the [unqlite_lib_config()] + * interface with a configuration verb set to + * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE or + * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI + */ +#endif + SySet kv_storage; /* Installed KV storage engines */ + int iPageSize; /* Default Page size */ + unqlite_vfs *pVfs; /* Underlying virtual file system (Vfs) */ + sxi32 nDB; /* Total number of active DB handles */ + unqlite *pDB; /* List of active DB handles */ + sxu32 nMagic; /* Sanity check against library misuse */ +}sUnqlMPGlobal = { + {0, 0, 0, 0, 0, 0, 0, 0, {0}}, +#if defined(UNQLITE_ENABLE_THREADS) + 0, + 0, + 0, +#endif + {0, 0, 0, 0, 0, 0, 0 }, + UNQLITE_DEFAULT_PAGE_SIZE, + 0, + 0, + 0, + 0 +}; +#define UNQLITE_LIB_MAGIC 0xEA1495BA +#define UNQLITE_LIB_MISUSE (sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC) +/* + * Supported threading level. + * These options have meaning only when the library is compiled with multi-threading + * support. That is, the UNQLITE_ENABLE_THREADS compile time directive must be defined + * when UnQLite is built. + * UNQLITE_THREAD_LEVEL_SINGLE: + * In this mode, mutexing is disabled and the library can only be used by a single thread. + * UNQLITE_THREAD_LEVEL_MULTI + * In this mode, all mutexes including the recursive mutexes on [unqlite] objects + * are enabled so that the application is free to share the same database handle + * between different threads at the same time. + */ +#define UNQLITE_THREAD_LEVEL_SINGLE 1 +#define UNQLITE_THREAD_LEVEL_MULTI 2 +/* + * Find a Key Value storage engine from the set of installed engines. + * Return a pointer to the storage engine methods on success. NULL on failure. + */ +UNQLITE_PRIVATE unqlite_kv_methods * unqliteFindKVStore( + const char *zName, /* Storage engine name [i.e. Hash, B+tree, LSM, etc.] */ + sxu32 nByte /* zName length */ + ) +{ + unqlite_kv_methods **apStore,*pEntry; + sxu32 n,nMax; + /* Point to the set of installed engines */ + apStore = (unqlite_kv_methods **)SySetBasePtr(&sUnqlMPGlobal.kv_storage); + nMax = SySetUsed(&sUnqlMPGlobal.kv_storage); + for( n = 0 ; n < nMax; ++n ){ + pEntry = apStore[n]; + if( nByte == SyStrlen(pEntry->zName) && SyStrnicmp(pEntry->zName,zName,nByte) == 0 ){ + /* Storage engine found */ + return pEntry; + } + } + /* No such entry, return NULL */ + return 0; +} +/* + * Configure the UnQLite library. + * Return UNQLITE_OK on success. Any other return value indicates failure. + * Refer to [unqlite_lib_config()]. + */ +static sxi32 unqliteCoreConfigure(sxi32 nOp, va_list ap) +{ + int rc = UNQLITE_OK; + switch(nOp){ + case UNQLITE_LIB_CONFIG_PAGE_SIZE: { + /* Default page size: Must be a power of two */ + int iPage = va_arg(ap,int); + if( iPage >= UNQLITE_MIN_PAGE_SIZE && iPage <= UNQLITE_MAX_PAGE_SIZE ){ + if( !(iPage & (iPage - 1)) ){ + sUnqlMPGlobal.iPageSize = iPage; + }else{ + /* Invalid page size */ + rc = UNQLITE_INVALID; + } + }else{ + /* Invalid page size */ + rc = UNQLITE_INVALID; + } + break; + } + case UNQLITE_LIB_CONFIG_STORAGE_ENGINE: { + /* Install a key value storage engine */ + unqlite_kv_methods *pMethods = va_arg(ap,unqlite_kv_methods *); + /* Make sure we are delaing with a valid methods */ + if( pMethods == 0 || SX_EMPTY_STR(pMethods->zName) || pMethods->xSeek == 0 || pMethods->xData == 0 + || pMethods->xKey == 0 || pMethods->xDataLength == 0 || pMethods->xKeyLength == 0 + || pMethods->szKv < (int)sizeof(unqlite_kv_engine) ){ + rc = UNQLITE_INVALID; + break; + } + /* Install it */ + rc = SySetPut(&sUnqlMPGlobal.kv_storage,(const void *)&pMethods); + break; + } + case UNQLITE_LIB_CONFIG_VFS:{ + /* Install a virtual file system */ + unqlite_vfs *pVfs = va_arg(ap,unqlite_vfs *); + if( pVfs ){ + sUnqlMPGlobal.pVfs = pVfs; + } + break; + } + case UNQLITE_LIB_CONFIG_USER_MALLOC: { + /* Use an alternative low-level memory allocation routines */ + const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *); + /* Save the memory failure callback (if available) */ + ProcMemError xMemErr = sUnqlMPGlobal.sAllocator.xMemError; + void *pMemErr = sUnqlMPGlobal.sAllocator.pUserData; + if( pMethods == 0 ){ + /* Use the built-in memory allocation subsystem */ + rc = SyMemBackendInit(&sUnqlMPGlobal.sAllocator, xMemErr, pMemErr); + }else{ + rc = SyMemBackendInitFromOthers(&sUnqlMPGlobal.sAllocator, pMethods, xMemErr, pMemErr); + } + break; + } + case UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK: { + /* Memory failure callback */ + ProcMemError xMemErr = va_arg(ap, ProcMemError); + void *pUserData = va_arg(ap, void *); + sUnqlMPGlobal.sAllocator.xMemError = xMemErr; + sUnqlMPGlobal.sAllocator.pUserData = pUserData; + break; + } + case UNQLITE_LIB_CONFIG_USER_MUTEX: { +#if defined(UNQLITE_ENABLE_THREADS) + /* Use an alternative low-level mutex subsystem */ + const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *); +#if defined (UNTRUST) + if( pMethods == 0 ){ + rc = UNQLITE_CORRUPT; + } +#endif + /* Sanity check */ + if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){ + /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */ + rc = UNQLITE_CORRUPT; + break; + } + if( sUnqlMPGlobal.pMutexMethods ){ + /* Overwrite the previous mutex subsystem */ + SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); + if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){ + sUnqlMPGlobal.pMutexMethods->xGlobalRelease(); + } + sUnqlMPGlobal.pMutex = 0; + } + /* Initialize and install the new mutex subsystem */ + if( pMethods->xGlobalInit ){ + rc = pMethods->xGlobalInit(); + if ( rc != UNQLITE_OK ){ + break; + } + } + /* Create the global mutex */ + sUnqlMPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); + if( sUnqlMPGlobal.pMutex == 0 ){ + /* + * If the supplied mutex subsystem is so sick that we are unable to + * create a single mutex, there is no much we can do here. + */ + if( pMethods->xGlobalRelease ){ + pMethods->xGlobalRelease(); + } + rc = UNQLITE_CORRUPT; + break; + } + sUnqlMPGlobal.pMutexMethods = pMethods; + if( sUnqlMPGlobal.nThreadingLevel == 0 ){ + /* Set a default threading level */ + sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI; + } +#endif + break; + } + case UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE: +#if defined(UNQLITE_ENABLE_THREADS) + /* Single thread mode (Only one thread is allowed to play with the library) */ + sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_SINGLE; + jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE); +#endif + break; + case UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI: +#if defined(UNQLITE_ENABLE_THREADS) + /* Multi-threading mode (library is thread safe and database handles and virtual machines + * may be shared between multiple threads). + */ + sUnqlMPGlobal.nThreadingLevel = UNQLITE_THREAD_LEVEL_MULTI; + jx9_lib_config(JX9_LIB_CONFIG_THREAD_LEVEL_MULTI); +#endif + break; + default: + /* Unknown configuration option */ + rc = UNQLITE_CORRUPT; + break; + } + return rc; +} +/* + * [CAPIREF: unqlite_lib_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_lib_config(int nConfigOp,...) +{ + va_list ap; + int rc; + if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){ + /* Library is already initialized, this operation is forbidden */ + return UNQLITE_LOCKED; + } + va_start(ap,nConfigOp); + rc = unqliteCoreConfigure(nConfigOp,ap); + va_end(ap); + return rc; +} +/* + * Global library initialization + * Refer to [unqlite_lib_init()] + * This routine must be called to initialize the memory allocation subsystem, the mutex + * subsystem prior to doing any serious work with the library. The first thread to call + * this routine does the initialization process and set the magic number so no body later + * can re-initialize the library. If subsequent threads call this routine before the first + * thread have finished the initialization process, then the subsequent threads must block + * until the initialization process is done. + */ +static sxi32 unqliteCoreInitialize(void) +{ + const unqlite_kv_methods *pMethods; + const unqlite_vfs *pVfs; /* Built-in vfs */ +#if defined(UNQLITE_ENABLE_THREADS) + const SyMutexMethods *pMutexMethods = 0; + SyMutex *pMaster = 0; +#endif + int rc; + /* + * If the library is already initialized, then a call to this routine + * is a no-op. + */ + if( sUnqlMPGlobal.nMagic == UNQLITE_LIB_MAGIC ){ + return UNQLITE_OK; /* Already initialized */ + } + if( sUnqlMPGlobal.pVfs == 0 ){ + /* Point to the built-in vfs */ + pVfs = unqliteExportBuiltinVfs(); + /* Install it */ + unqlite_lib_config(UNQLITE_LIB_CONFIG_VFS, pVfs); + } +#if defined(UNQLITE_ENABLE_THREADS) + if( sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_SINGLE ){ + pMutexMethods = sUnqlMPGlobal.pMutexMethods; + if( pMutexMethods == 0 ){ + /* Use the built-in mutex subsystem */ + pMutexMethods = SyMutexExportMethods(); + if( pMutexMethods == 0 ){ + return UNQLITE_CORRUPT; /* Can't happen */ + } + /* Install the mutex subsystem */ + rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MUTEX, pMutexMethods); + if( rc != UNQLITE_OK ){ + return rc; + } + } + /* Obtain a static mutex so we can initialize the library without calling malloc() */ + pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1); + if( pMaster == 0 ){ + return UNQLITE_CORRUPT; /* Can't happen */ + } + } + /* Lock the master mutex */ + rc = UNQLITE_OK; + SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ + if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ +#endif + if( sUnqlMPGlobal.sAllocator.pMethods == 0 ){ + /* Install a memory subsystem */ + rc = unqlite_lib_config(UNQLITE_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */ + if( rc != UNQLITE_OK ){ + /* If we are unable to initialize the memory backend, there is no much we can do here.*/ + goto End; + } + } +#if defined(UNQLITE_ENABLE_THREADS) + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ + /* Protect the memory allocation subsystem */ + rc = SyMemBackendMakeThreadSafe(&sUnqlMPGlobal.sAllocator, sUnqlMPGlobal.pMutexMethods); + if( rc != UNQLITE_OK ){ + goto End; + } + } +#endif + SySetInit(&sUnqlMPGlobal.kv_storage,&sUnqlMPGlobal.sAllocator,sizeof(unqlite_kv_methods *)); + /* Install the built-in Key Value storage engines */ + pMethods = unqliteExportMemKvStorage(); /* In-memory storage */ + unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods); + /* Default disk key/value storage engine */ + pMethods = unqliteExportDiskKvStorage(); /* Disk storage */ + unqlite_lib_config(UNQLITE_LIB_CONFIG_STORAGE_ENGINE,pMethods); + /* Default page size */ + if( sUnqlMPGlobal.iPageSize < UNQLITE_MIN_PAGE_SIZE ){ + unqlite_lib_config(UNQLITE_LIB_CONFIG_PAGE_SIZE,UNQLITE_DEFAULT_PAGE_SIZE); + } + /* Our library is initialized, set the magic number */ + sUnqlMPGlobal.nMagic = UNQLITE_LIB_MAGIC; + rc = UNQLITE_OK; +#if defined(UNQLITE_ENABLE_THREADS) + } /* sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC */ +#endif +End: +#if defined(UNQLITE_ENABLE_THREADS) + /* Unlock the master mutex */ + SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ +#endif + return rc; +} +/* Forward declaration */ +static int unqliteVmRelease(unqlite_vm *pVm); +/* + * Release a single instance of an unqlite database handle. + */ +static int unqliteDbRelease(unqlite *pDb) +{ + unqlite_db *pStore = &pDb->sDB; + unqlite_vm *pVm,*pNext; + int rc = UNQLITE_OK; + if( (pDb->iFlags & UNQLITE_FL_DISABLE_AUTO_COMMIT) == 0 ){ + /* Commit any outstanding transaction */ + rc = unqlitePagerCommit(pStore->pPager); + if( rc != UNQLITE_OK ){ + /* Rollback the transaction */ + rc = unqlitePagerRollback(pStore->pPager,FALSE); + } + }else{ + /* Rollback any outstanding transaction */ + rc = unqlitePagerRollback(pStore->pPager,FALSE); + } + /* Close the pager */ + unqlitePagerClose(pStore->pPager); + /* Release any active VM's */ + pVm = pDb->pVms; + for(;;){ + if( pDb->iVm < 1 ){ + break; + } + /* Point to the next entry */ + pNext = pVm->pNext; + unqliteVmRelease(pVm); + pVm = pNext; + pDb->iVm--; + } + /* Release the Jx9 handle */ + jx9_release(pStore->pJx9); + /* Set a dummy magic number */ + pDb->nMagic = 0x7250; + /* Release the whole memory subsystem */ + SyMemBackendRelease(&pDb->sMem); + /* Commit or rollback result */ + return rc; +} +/* + * Release all resources consumed by the library. + * Note: This call is not thread safe. Refer to [unqlite_lib_shutdown()]. + */ +static void unqliteCoreShutdown(void) +{ + unqlite *pDb, *pNext; + /* Release all active databases handles */ + pDb = sUnqlMPGlobal.pDB; + for(;;){ + if( sUnqlMPGlobal.nDB < 1 ){ + break; + } + pNext = pDb->pNext; + unqliteDbRelease(pDb); + pDb = pNext; + sUnqlMPGlobal.nDB--; + } + /* Release the storage methods container */ + SySetRelease(&sUnqlMPGlobal.kv_storage); +#if defined(UNQLITE_ENABLE_THREADS) + /* Release the mutex subsystem */ + if( sUnqlMPGlobal.pMutexMethods ){ + if( sUnqlMPGlobal.pMutex ){ + SyMutexRelease(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); + sUnqlMPGlobal.pMutex = 0; + } + if( sUnqlMPGlobal.pMutexMethods->xGlobalRelease ){ + sUnqlMPGlobal.pMutexMethods->xGlobalRelease(); + } + sUnqlMPGlobal.pMutexMethods = 0; + } + sUnqlMPGlobal.nThreadingLevel = 0; +#endif + if( sUnqlMPGlobal.sAllocator.pMethods ){ + /* Release the memory backend */ + SyMemBackendRelease(&sUnqlMPGlobal.sAllocator); + } + sUnqlMPGlobal.nMagic = 0x1764; + /* Finally, shutdown the Jx9 library */ + jx9_lib_shutdown(); +} +/* + * [CAPIREF: unqlite_lib_init()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_lib_init(void) +{ + int rc; + rc = unqliteCoreInitialize(); + return rc; +} +/* + * [CAPIREF: unqlite_lib_shutdown()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_lib_shutdown(void) +{ + if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ + /* Already shut */ + return UNQLITE_OK; + } + unqliteCoreShutdown(); + return UNQLITE_OK; +} +/* + * [CAPIREF: unqlite_lib_is_threadsafe()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_lib_is_threadsafe(void) +{ + if( sUnqlMPGlobal.nMagic != UNQLITE_LIB_MAGIC ){ + return 0; + } +#if defined(UNQLITE_ENABLE_THREADS) + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ + /* Muli-threading support is enabled */ + return 1; + }else{ + /* Single-threading */ + return 0; + } +#else + return 0; +#endif +} +/* + * + * [CAPIREF: unqlite_lib_version()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * unqlite_lib_version(void) +{ + return UNQLITE_VERSION; +} +/* + * + * [CAPIREF: unqlite_lib_signature()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * unqlite_lib_signature(void) +{ + return UNQLITE_SIG; +} +/* + * + * [CAPIREF: unqlite_lib_ident()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * unqlite_lib_ident(void) +{ + return UNQLITE_IDENT; +} +/* + * + * [CAPIREF: unqlite_lib_copyright()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * unqlite_lib_copyright(void) +{ + return UNQLITE_COPYRIGHT; +} +/* + * Remove harmfull and/or stale flags passed to the [unqlite_open()] interface. + */ +static unsigned int unqliteSanityzeFlag(unsigned int iFlags) +{ + iFlags &= ~UNQLITE_OPEN_EXCLUSIVE; /* Reserved flag */ + if( iFlags & UNQLITE_OPEN_TEMP_DB ){ + /* Omit journaling for temporary database */ + iFlags |= UNQLITE_OPEN_OMIT_JOURNALING|UNQLITE_OPEN_CREATE; + } + if( (iFlags & (UNQLITE_OPEN_READONLY|UNQLITE_OPEN_READWRITE)) == 0 ){ + /* Auto-append the R+W flag */ + iFlags |= UNQLITE_OPEN_READWRITE; + } + if( iFlags & UNQLITE_OPEN_CREATE ){ + iFlags &= ~(UNQLITE_OPEN_MMAP|UNQLITE_OPEN_READONLY); + /* Auto-append the R+W flag */ + iFlags |= UNQLITE_OPEN_READWRITE; + }else{ + if( iFlags & UNQLITE_OPEN_READONLY ){ + iFlags &= ~UNQLITE_OPEN_READWRITE; + }else if( iFlags & UNQLITE_OPEN_READWRITE ){ + iFlags &= ~UNQLITE_OPEN_MMAP; + } + } + return iFlags; +} +/* + * This routine does the work of initializing a database handle on behalf + * of [unqlite_open()]. + */ +static int unqliteInitDatabase( + unqlite *pDB, /* Database handle */ + SyMemBackend *pParent, /* Master memory backend */ + const char *zFilename, /* Target database */ + unsigned int iFlags /* Open flags */ + ) +{ + unqlite_db *pStorage = &pDB->sDB; + int rc; + /* Initialiaze the memory subsystem */ + SyMemBackendInitFromParent(&pDB->sMem,pParent); +//#if defined(UNQLITE_ENABLE_THREADS) +// /* No need for internal mutexes */ +// SyMemBackendDisbaleMutexing(&pDB->sMem); +//#endif + SyBlobInit(&pDB->sErr,&pDB->sMem); + /* Sanityze flags */ + iFlags = unqliteSanityzeFlag(iFlags); + /* Init the pager and the transaction manager */ + rc = unqlitePagerOpen(sUnqlMPGlobal.pVfs,pDB,zFilename,iFlags); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Allocate a new Jx9 engine handle */ + rc = jx9_init(&pStorage->pJx9); + if( rc != JX9_OK ){ + return rc; + } + return UNQLITE_OK; +} +/* + * Allocate and initialize a new UnQLite Virtual Mahcine and attach it + * to the compiled Jx9 script. + */ +static int unqliteInitVm(unqlite *pDb,jx9_vm *pJx9Vm,unqlite_vm **ppOut) +{ + unqlite_vm *pVm; + + *ppOut = 0; + /* Allocate a new VM instance */ + pVm = (unqlite_vm *)SyMemBackendPoolAlloc(&pDb->sMem,sizeof(unqlite_vm)); + if( pVm == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pVm,sizeof(unqlite_vm)); + /* Initialize */ + SyMemBackendInitFromParent(&pVm->sAlloc,&pDb->sMem); + /* Allocate a new collection table */ + pVm->apCol = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc,32 * sizeof(unqlite_col *)); + if( pVm->apCol == 0 ){ + goto fail; + } + pVm->iColSize = 32; /* Must be a power of two */ + /* Zero the table */ + SyZero((void *)pVm->apCol,pVm->iColSize * sizeof(unqlite_col *)); +#if defined(UNQLITE_ENABLE_THREADS) + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE ){ + /* Associate a recursive mutex with this instance */ + pVm->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); + if( pVm->pMutex == 0 ){ + goto fail; + } + } +#endif + /* Link the VM to the list of active virtual machines */ + pVm->pJx9Vm = pJx9Vm; + pVm->pDb = pDb; + MACRO_LD_PUSH(pDb->pVms,pVm); + pDb->iVm++; + /* Register Jx9 functions */ + unqliteRegisterJx9Functions(pVm); + /* Set the magic number */ + pVm->nMagic = JX9_VM_INIT; /* Same magic number as Jx9 */ + /* All done */ + *ppOut = pVm; + return UNQLITE_OK; +fail: + SyMemBackendRelease(&pVm->sAlloc); + SyMemBackendPoolFree(&pDb->sMem,pVm); + return UNQLITE_NOMEM; +} +/* + * Release an active VM. + */ +static int unqliteVmRelease(unqlite_vm *pVm) +{ + /* Release the Jx9 VM */ + jx9_vm_release(pVm->pJx9Vm); + /* Release the private memory backend */ + SyMemBackendRelease(&pVm->sAlloc); + /* Upper layer will discard this VM from the list + * of active VM. + */ + return UNQLITE_OK; +} +/* + * Return the default page size. + */ +UNQLITE_PRIVATE int unqliteGetPageSize(void) +{ + int iSize = sUnqlMPGlobal.iPageSize; + if( iSize < UNQLITE_MIN_PAGE_SIZE || iSize > UNQLITE_MAX_PAGE_SIZE ){ + iSize = UNQLITE_DEFAULT_PAGE_SIZE; + } + return iSize; +} +/* + * Generate an error message. + */ +UNQLITE_PRIVATE int unqliteGenError(unqlite *pDb,const char *zErr) +{ + int rc; + /* Append the error message */ + rc = SyBlobAppend(&pDb->sErr,(const void *)zErr,SyStrlen(zErr)); + /* Append a new line */ + SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char)); + return rc; +} +/* + * Generate an error message (Printf like). + */ +UNQLITE_PRIVATE int unqliteGenErrorFormat(unqlite *pDb,const char *zFmt,...) +{ + va_list ap; + int rc; + va_start(ap,zFmt); + rc = SyBlobFormatAp(&pDb->sErr,zFmt,ap); + va_end(ap); + /* Append a new line */ + SyBlobAppend(&pDb->sErr,(const void *)"\n",sizeof(char)); + return rc; +} +/* + * Generate an error message (Out of memory). + */ +UNQLITE_PRIVATE int unqliteGenOutofMem(unqlite *pDb) +{ + int rc; + rc = unqliteGenError(pDb,"unQLite is running out of memory"); + return rc; +} +/* + * Configure a working UnQLite database handle. + */ +static int unqliteConfigure(unqlite *pDb,int nOp,va_list ap) +{ + int rc = UNQLITE_OK; + switch(nOp){ + case UNQLITE_CONFIG_JX9_ERR_LOG: + /* Jx9 compile-time error log */ + rc = jx9EngineConfig(pDb->sDB.pJx9,JX9_CONFIG_ERR_LOG,ap); + break; + case UNQLITE_CONFIG_MAX_PAGE_CACHE: { + int max_page = va_arg(ap,int); + /* Maximum number of page to cache (Simple hint). */ + rc = unqlitePagerSetCachesize(pDb->sDB.pPager,max_page); + break; + } + case UNQLITE_CONFIG_ERR_LOG: { + /* Database error log if any */ + const char **pzPtr = va_arg(ap, const char **); + int *pLen = va_arg(ap, int *); + if( pzPtr == 0 ){ + rc = JX9_CORRUPT; + break; + } + /* NULL terminate the error-log buffer */ + SyBlobNullAppend(&pDb->sErr); + /* Point to the error-log buffer */ + *pzPtr = (const char *)SyBlobData(&pDb->sErr); + if( pLen ){ + if( SyBlobLength(&pDb->sErr) > 1 /* NULL '\0' terminator */ ){ + *pLen = (int)SyBlobLength(&pDb->sErr); + }else{ + *pLen = 0; + } + } + break; + } + case UNQLITE_CONFIG_DISABLE_AUTO_COMMIT:{ + /* Disable auto-commit */ + pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; + break; + } + case UNQLITE_CONFIG_GET_KV_NAME: { + /* Name of the underlying KV storage engine */ + const char **pzPtr = va_arg(ap,const char **); + if( pzPtr ){ + unqlite_kv_engine *pEngine; + pEngine = unqlitePagerGetKvEngine(pDb); + /* Point to the name */ + *pzPtr = pEngine->pIo->pMethods->zName; + } + break; + } + default: + /* Unknown configuration option */ + rc = UNQLITE_UNKNOWN; + break; + } + return rc; +} +/* + * Export the global (master) memory allocator to submodules. + */ +UNQLITE_PRIVATE const SyMemBackend * unqliteExportMemBackend(void) +{ + return &sUnqlMPGlobal.sAllocator; +} +/* + * [CAPIREF: unqlite_open()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode) +{ + unqlite *pHandle; + int rc; +#if defined(UNTRUST) + if( ppDB == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + *ppDB = 0; + /* One-time automatic library initialization */ + rc = unqliteCoreInitialize(); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Allocate a new database handle */ + pHandle = (unqlite *)SyMemBackendPoolAlloc(&sUnqlMPGlobal.sAllocator, sizeof(unqlite)); + if( pHandle == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pHandle,sizeof(unqlite)); + if( iMode < 1 ){ + /* Assume a read-only database */ + iMode = UNQLITE_OPEN_READONLY|UNQLITE_OPEN_MMAP; + } + /* Init the database */ + rc = unqliteInitDatabase(pHandle,&sUnqlMPGlobal.sAllocator,zFilename,iMode); + if( rc != UNQLITE_OK ){ + goto Release; + } +#if defined(UNQLITE_ENABLE_THREADS) + if( !(iMode & UNQLITE_OPEN_NOMUTEX) && (sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE) ){ + /* Associate a recursive mutex with this instance */ + pHandle->pMutex = SyMutexNew(sUnqlMPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); + if( pHandle->pMutex == 0 ){ + rc = UNQLITE_NOMEM; + goto Release; + } + } +#endif + /* Link to the list of active DB handles */ +#if defined(UNQLITE_ENABLE_THREADS) + /* Enter the global mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ +#endif + MACRO_LD_PUSH(sUnqlMPGlobal.pDB,pHandle); + sUnqlMPGlobal.nDB++; +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave the global mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ +#endif + /* Set the magic number to identify a valid DB handle */ + pHandle->nMagic = UNQLITE_DB_MAGIC; + /* Make the handle available to the caller */ + *ppDB = pHandle; + return UNQLITE_OK; +Release: + SyMemBackendRelease(&pHandle->sMem); + SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pHandle); + return rc; +} +/* + * [CAPIREF: unqlite_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_config(unqlite *pDb,int nConfigOp,...) +{ + va_list ap; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + va_start(ap, nConfigOp); + rc = unqliteConfigure(&(*pDb),nConfigOp, ap); + va_end(ap); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_close()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_close(unqlite *pDb) +{ + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Release the database handle */ + rc = unqliteDbRelease(pDb); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + /* Release DB mutex */ + SyMutexRelease(sUnqlMPGlobal.pMutexMethods, pDb->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif +#if defined(UNQLITE_ENABLE_THREADS) + /* Enter the global mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ +#endif + /* Unlink from the list of active database handles */ + MACRO_LD_REMOVE(sUnqlMPGlobal.pDB, pDb); + sUnqlMPGlobal.nDB--; +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave the global mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods, sUnqlMPGlobal.pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel == UNQLITE_THREAD_LEVEL_SINGLE */ +#endif + /* Release the memory chunk allocated to this handle */ + SyMemBackendPoolFree(&sUnqlMPGlobal.sAllocator,pDb); + return rc; +} +/* + * [CAPIREF: unqlite_compile()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut) +{ + jx9_vm *pVm; + int rc; + if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; + } +#endif + /* Compile the Jx9 script first */ + rc = jx9_compile(pDb->sDB.pJx9,zJx9,nByte,&pVm); + if( rc == JX9_OK ){ + /* Allocate a new unqlite VM instance */ + rc = unqliteInitVm(pDb,pVm,ppOut); + if( rc != UNQLITE_OK ){ + /* Release the Jx9 VM */ + jx9_vm_release(pVm); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_compile_file()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut) +{ + jx9_vm *pVm; + int rc; + if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; + } +#endif + /* Compile the Jx9 script first */ + rc = jx9_compile_file(pDb->sDB.pJx9,zPath,&pVm); + if( rc == JX9_OK ){ + /* Allocate a new unqlite VM instance */ + rc = unqliteInitVm(pDb,pVm,ppOut); + if( rc != UNQLITE_OK ){ + /* Release the Jx9 VM */ + jx9_vm_release(pVm); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * Configure an unqlite virtual machine (Mostly Jx9 VM) instance. + */ +static int unqliteVmConfig(unqlite_vm *pVm,sxi32 iOp,va_list ap) +{ + int rc; + rc = jx9VmConfigure(pVm->pJx9Vm,iOp,ap); + return rc; +} +/* + * [CAPIREF: unqlite_vm_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_vm_config(unqlite_vm *pVm,int iOp,...) +{ + va_list ap; + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + va_start(ap,iOp); + rc = unqliteVmConfig(pVm,iOp,ap); + va_end(ap); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_vm_exec()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_vm_exec(unqlite_vm *pVm) +{ + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Execute the Jx9 bytecode program */ + rc = jx9VmByteCodeExec(pVm->pJx9Vm); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_vm_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_vm_release(unqlite_vm *pVm) +{ + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Release the VM */ + rc = unqliteVmRelease(pVm); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + /* Release VM mutex */ + SyMutexRelease(sUnqlMPGlobal.pMutexMethods,pVm->pMutex) /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + if( rc == UNQLITE_OK ){ + unqlite *pDb = pVm->pDb; + /* Unlink from the list of active VM's */ +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + MACRO_LD_REMOVE(pDb->pVms, pVm); + pDb->iVm--; + /* Release the memory chunk allocated to this instance */ + SyMemBackendPoolFree(&pDb->sMem,pVm); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + } + return rc; +} +/* + * [CAPIREF: unqlite_vm_reset()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_vm_reset(unqlite_vm *pVm) +{ + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Reset the Jx9 VM */ + rc = jx9VmReset(pVm->pJx9Vm); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_vm_dump()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData) +{ + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Dump the Jx9 VM */ + rc = jx9VmDump(pVm->pJx9Vm,xConsumer,pUserData); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_vm_extract_variable()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname) +{ + unqlite_value *pValue; + SyString sVariable; + if( UNQLITE_VM_MISUSE(pVm) ){ + return 0; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return 0; /* Another thread have released this instance */ + } +#endif + /* Extract the target variable */ + SyStringInitFromBuf(&sVariable,zVarname,SyStrlen(zVarname)); + pValue = jx9VmExtractVariable(pVm->pJx9Vm,&sVariable); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return pValue; +} +/* + * [CAPIREF: unqlite_create_function()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_create_function(unqlite_vm *pVm, const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData) +{ + SyString sName; + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } + SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sName); + /* Ticket 1433-003: NULL values are not allowed */ + if( sName.nByte < 1 || xFunc == 0 ){ + return UNQLITE_INVALID; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Install the foreign function */ + rc = jx9VmInstallForeignFunction(pVm->pJx9Vm,&sName,xFunc,pUserData); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_delete_function()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_delete_function(unqlite_vm *pVm, const char *zName) +{ + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Unlink the foreign function */ + rc = jx9DeleteFunction(pVm->pJx9Vm,zName); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_create_constant()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData) +{ + SyString sName; + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } + SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sName); + if( sName.nByte < 1 ){ + /* Empty constant name */ + return UNQLITE_INVALID; + } + /* TICKET 1433-003: NULL pointer is harmless operation */ + if( xExpand == 0 ){ + return UNQLITE_INVALID; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Install the foreign constant */ + rc = jx9VmRegisterConstant(pVm->pJx9Vm,&sName,xExpand,pUserData); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_delete_constant()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_delete_constant(unqlite_vm *pVm, const char *zName) +{ + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Unlink the foreign constant */ + rc = Jx9DeleteConstant(pVm->pJx9Vm,zName); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_value_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_int(unqlite_value *pVal, int iValue) +{ + return jx9_value_int(pVal,iValue); +} +/* + * [CAPIREF: unqlite_value_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_int64(unqlite_value *pVal,unqlite_int64 iValue) +{ + return jx9_value_int64(pVal,iValue); +} +/* + * [CAPIREF: unqlite_value_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_bool(unqlite_value *pVal, int iBool) +{ + return jx9_value_bool(pVal,iBool); +} +/* + * [CAPIREF: unqlite_value_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_null(unqlite_value *pVal) +{ + return jx9_value_null(pVal); +} +/* + * [CAPIREF: unqlite_value_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_double(unqlite_value *pVal, double Value) +{ + return jx9_value_double(pVal,Value); +} +/* + * [CAPIREF: unqlite_value_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen) +{ + return jx9_value_string(pVal,zString,nLen); +} +/* + * [CAPIREF: unqlite_value_string_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...) +{ + va_list ap; + int rc; + if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + MemObjSetType(pVal, MEMOBJ_STRING); + } + va_start(ap, zFormat); + rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap); + va_end(ap); + return UNQLITE_OK; +} +/* + * [CAPIREF: unqlite_value_reset_string_cursor()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_reset_string_cursor(unqlite_value *pVal) +{ + return jx9_value_reset_string_cursor(pVal); +} +/* + * [CAPIREF: unqlite_value_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_resource(unqlite_value *pVal,void *pUserData) +{ + return jx9_value_resource(pVal,pUserData); +} +/* + * [CAPIREF: unqlite_value_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_release(unqlite_value *pVal) +{ + return jx9_value_release(pVal); +} +/* + * [CAPIREF: unqlite_value_to_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_to_int(unqlite_value *pValue) +{ + return jx9_value_to_int(pValue); +} +/* + * [CAPIREF: unqlite_value_to_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_to_bool(unqlite_value *pValue) +{ + return jx9_value_to_bool(pValue); +} +/* + * [CAPIREF: unqlite_value_to_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue) +{ + return jx9_value_to_int64(pValue); +} +/* + * [CAPIREF: unqlite_value_to_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +double unqlite_value_to_double(unqlite_value *pValue) +{ + return jx9_value_to_double(pValue); +} +/* + * [CAPIREF: unqlite_value_to_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen) +{ + return jx9_value_to_string(pValue,pLen); +} +/* + * [CAPIREF: unqlite_value_to_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * unqlite_value_to_resource(unqlite_value *pValue) +{ + return jx9_value_to_resource(pValue); +} +/* + * [CAPIREF: unqlite_value_compare()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict) +{ + return jx9_value_compare(pLeft,pRight,bStrict); +} +/* + * [CAPIREF: unqlite_result_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_int(unqlite_context *pCtx, int iValue) +{ + return jx9_result_int(pCtx,iValue); +} +/* + * [CAPIREF: unqlite_result_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue) +{ + return jx9_result_int64(pCtx,iValue); +} +/* + * [CAPIREF: unqlite_result_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_bool(unqlite_context *pCtx, int iBool) +{ + return jx9_result_bool(pCtx,iBool); +} +/* + * [CAPIREF: unqlite_result_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_double(unqlite_context *pCtx, double Value) +{ + return jx9_result_double(pCtx,Value); +} +/* + * [CAPIREF: unqlite_result_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_null(unqlite_context *pCtx) +{ + return jx9_result_null(pCtx); +} +/* + * [CAPIREF: unqlite_result_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen) +{ + return jx9_result_string(pCtx,zString,nLen); +} +/* + * [CAPIREF: unqlite_result_string_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...) +{ + jx9_value *p; + va_list ap; + int rc; + p = pCtx->pRet; + if( (p->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + jx9MemObjRelease(p); + MemObjSetType(p, MEMOBJ_STRING); + } + /* Format the given string */ + va_start(ap, zFormat); + rc = SyBlobFormatAp(&p->sBlob, zFormat, ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: unqlite_result_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue) +{ + return jx9_result_value(pCtx,pValue); +} +/* + * [CAPIREF: unqlite_result_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_result_resource(unqlite_context *pCtx, void *pUserData) +{ + return jx9_result_resource(pCtx,pUserData); +} +/* + * [CAPIREF: unqlite_value_is_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_int(unqlite_value *pVal) +{ + return jx9_value_is_int(pVal); +} +/* + * [CAPIREF: unqlite_value_is_float()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_float(unqlite_value *pVal) +{ + return jx9_value_is_float(pVal); +} +/* + * [CAPIREF: unqlite_value_is_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_bool(unqlite_value *pVal) +{ + return jx9_value_is_bool(pVal); +} +/* + * [CAPIREF: unqlite_value_is_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_string(unqlite_value *pVal) +{ + return jx9_value_is_string(pVal); +} +/* + * [CAPIREF: unqlite_value_is_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_null(unqlite_value *pVal) +{ + return jx9_value_is_null(pVal); +} +/* + * [CAPIREF: unqlite_value_is_numeric()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_numeric(unqlite_value *pVal) +{ + return jx9_value_is_numeric(pVal); +} +/* + * [CAPIREF: unqlite_value_is_callable()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_callable(unqlite_value *pVal) +{ + return jx9_value_is_callable(pVal); +} +/* + * [CAPIREF: unqlite_value_is_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_scalar(unqlite_value *pVal) +{ + return jx9_value_is_scalar(pVal); +} +/* + * [CAPIREF: unqlite_value_is_json_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_json_array(unqlite_value *pVal) +{ + return jx9_value_is_json_array(pVal); +} +/* + * [CAPIREF: unqlite_value_is_json_object()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_json_object(unqlite_value *pVal) +{ + return jx9_value_is_json_object(pVal); +} +/* + * [CAPIREF: unqlite_value_is_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_resource(unqlite_value *pVal) +{ + return jx9_value_is_resource(pVal); +} +/* + * [CAPIREF: unqlite_value_is_empty()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_value_is_empty(unqlite_value *pVal) +{ + return jx9_value_is_empty(pVal); +} +/* + * [CAPIREF: unqlite_array_fetch()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte) +{ + return jx9_array_fetch(pArray,zKey,nByte); +} +/* + * [CAPIREF: unqlite_array_walk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData) +{ + return jx9_array_walk(pArray,xWalk,pUserData); +} +/* + * [CAPIREF: unqlite_array_add_elem()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue) +{ + return jx9_array_add_elem(pArray,pKey,pValue); +} +/* + * [CAPIREF: unqlite_array_add_strkey_elem()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue) +{ + return jx9_array_add_strkey_elem(pArray,zKey,pValue); +} +/* + * [CAPIREF: unqlite_array_count()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_array_count(unqlite_value *pArray) +{ + return (int)jx9_array_count(pArray); +} +/* + * [CAPIREF: unqlite_vm_new_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm) +{ + unqlite_value *pValue; + if( UNQLITE_VM_MISUSE(pVm) ){ + return 0; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return 0; /* Another thread have released this instance */ + } +#endif + pValue = jx9_new_scalar(pVm->pJx9Vm); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return pValue; +} +/* + * [CAPIREF: unqlite_vm_new_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm) +{ + unqlite_value *pValue; + if( UNQLITE_VM_MISUSE(pVm) ){ + return 0; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return 0; /* Another thread have released this instance */ + } +#endif + pValue = jx9_new_array(pVm->pJx9Vm); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return pValue; +} +/* + * [CAPIREF: unqlite_vm_release_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue) +{ + int rc; + if( UNQLITE_VM_MISUSE(pVm) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_VM_RELEASE(pVm) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + rc = jx9_release_value(pVm->pJx9Vm,pValue); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pVm->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_context_output()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen) +{ + return jx9_context_output(pCtx,zString,nLen); +} +/* + * [CAPIREF: unqlite_context_output_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...) +{ + va_list ap; + int rc; + va_start(ap, zFormat); + rc = jx9VmOutputConsumeAp(pCtx->pVm,zFormat, ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: unqlite_context_throw_error()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr) +{ + return jx9_context_throw_error(pCtx,iErr,zErr); +} +/* + * [CAPIREF: unqlite_context_throw_error_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...) +{ + va_list ap; + int rc; + if( zFormat == 0){ + return JX9_OK; + } + va_start(ap, zFormat); + rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: unqlite_context_random_num()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unsigned int unqlite_context_random_num(unqlite_context *pCtx) +{ + return jx9_context_random_num(pCtx); +} +/* + * [CAPIREF: unqlite_context_random_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen) +{ + return jx9_context_random_string(pCtx,zBuf,nBuflen); +} +/* + * [CAPIREF: unqlite_context_user_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * unqlite_context_user_data(unqlite_context *pCtx) +{ + return jx9_context_user_data(pCtx); +} +/* + * [CAPIREF: unqlite_context_push_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData) +{ + return jx9_context_push_aux_data(pCtx,pUserData); +} +/* + * [CAPIREF: unqlite_context_peek_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * unqlite_context_peek_aux_data(unqlite_context *pCtx) +{ + return jx9_context_peek_aux_data(pCtx); +} +/* + * [CAPIREF: unqlite_context_pop_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * unqlite_context_pop_aux_data(unqlite_context *pCtx) +{ + return jx9_context_pop_aux_data(pCtx); +} +/* + * [CAPIREF: unqlite_context_result_buf_length()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx) +{ + return jx9_context_result_buf_length(pCtx); +} +/* + * [CAPIREF: unqlite_function_name()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +const char * unqlite_function_name(unqlite_context *pCtx) +{ + return jx9_function_name(pCtx); +} +/* + * [CAPIREF: unqlite_context_new_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx) +{ + return jx9_context_new_scalar(pCtx); +} +/* + * [CAPIREF: unqlite_context_new_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +unqlite_value * unqlite_context_new_array(unqlite_context *pCtx) +{ + return jx9_context_new_array(pCtx); +} +/* + * [CAPIREF: unqlite_context_release_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue) +{ + jx9_context_release_value(pCtx,pValue); +} +/* + * [CAPIREF: unqlite_context_alloc_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease) +{ + return jx9_context_alloc_chunk(pCtx,nByte,ZeroChunk,AutoRelease); +} +/* + * [CAPIREF: unqlite_context_realloc_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte) +{ + return jx9_context_realloc_chunk(pCtx,pChunk,nByte); +} +/* + * [CAPIREF: unqlite_context_free_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk) +{ + jx9_context_free_chunk(pCtx,pChunk); +} +/* + * [CAPIREF: unqlite_kv_store()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen) +{ + unqlite_kv_engine *pEngine; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + if( pEngine->pIo->pMethods->xReplace == 0 ){ + /* Storage engine does not implement such method */ + unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine"); + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + unqliteGenError(pDb,"Empty key"); + rc = UNQLITE_EMPTY; + }else{ + /* Perform the requested operation */ + rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,pData,nDataLen); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_store_fmt()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...) +{ + unqlite_kv_engine *pEngine; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + if( pEngine->pIo->pMethods->xReplace == 0 ){ + /* Storage engine does not implement such method */ + unqliteGenError(pDb,"xReplace() method not implemented in the underlying storage engine"); + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + unqliteGenError(pDb,"Empty key"); + rc = UNQLITE_EMPTY; + }else{ + SyBlob sWorker; /* Working buffer */ + va_list ap; + SyBlobInit(&sWorker,&pDb->sMem); + /* Format the data */ + va_start(ap,zFormat); + SyBlobFormatAp(&sWorker,zFormat,ap); + va_end(ap); + /* Perform the requested operation */ + rc = pEngine->pIo->pMethods->xReplace(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker)); + /* Clean up */ + SyBlobRelease(&sWorker); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_append()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen) +{ + unqlite_kv_engine *pEngine; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + if( pEngine->pIo->pMethods->xAppend == 0 ){ + /* Storage engine does not implement such method */ + unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine"); + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + unqliteGenError(pDb,"Empty key"); + rc = UNQLITE_EMPTY; + }else{ + /* Perform the requested operation */ + rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,pData,nDataLen); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_append_fmt()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...) +{ + unqlite_kv_engine *pEngine; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + if( pEngine->pIo->pMethods->xAppend == 0 ){ + /* Storage engine does not implement such method */ + unqliteGenError(pDb,"xAppend() method not implemented in the underlying storage engine"); + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + unqliteGenError(pDb,"Empty key"); + rc = UNQLITE_EMPTY; + }else{ + SyBlob sWorker; /* Working buffer */ + va_list ap; + SyBlobInit(&sWorker,&pDb->sMem); + /* Format the data */ + va_start(ap,zFormat); + SyBlobFormatAp(&sWorker,zFormat,ap); + va_end(ap); + /* Perform the requested operation */ + rc = pEngine->pIo->pMethods->xAppend(pEngine,pKey,nKeyLen,SyBlobData(&sWorker),SyBlobLength(&sWorker)); + /* Clean up */ + SyBlobRelease(&sWorker); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_fetch()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 *pBufLen) +{ + unqlite_kv_methods *pMethods; + unqlite_kv_engine *pEngine; + unqlite_kv_cursor *pCur; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + pMethods = pEngine->pIo->pMethods; + pCur = pDb->sDB.pCursor; + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + unqliteGenError(pDb,"Empty key"); + rc = UNQLITE_EMPTY; + }else{ + /* Seek to the record position */ + rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); + } + if( rc == UNQLITE_OK ){ + if( pBuf == 0 ){ + /* Data length only */ + rc = pMethods->xDataLength(pCur,pBufLen); + }else{ + SyBlob sBlob; + /* Initialize the data consumer */ + SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)*pBufLen); + /* Consume the data */ + rc = pMethods->xData(pCur,unqliteDataConsumer,&sBlob); + /* Data length */ + *pBufLen = (unqlite_int64)SyBlobLength(&sBlob); + /* Cleanup */ + SyBlobRelease(&sBlob); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_fetch_callback()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey,int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + unqlite_kv_methods *pMethods; + unqlite_kv_engine *pEngine; + unqlite_kv_cursor *pCur; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + pMethods = pEngine->pIo->pMethods; + pCur = pDb->sDB.pCursor; + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + unqliteGenError(pDb,"Empty key"); + rc = UNQLITE_EMPTY; + }else{ + /* Seek to the record position */ + rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); + } + if( rc == UNQLITE_OK && xConsumer ){ + /* Consume the data directly */ + rc = pMethods->xData(pCur,xConsumer,pUserData); + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_delete()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen) +{ + unqlite_kv_methods *pMethods; + unqlite_kv_engine *pEngine; + unqlite_kv_cursor *pCur; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + pMethods = pEngine->pIo->pMethods; + pCur = pDb->sDB.pCursor; + if( pMethods->xDelete == 0 ){ + /* Storage engine does not implement such method */ + unqliteGenError(pDb,"xDelete() method not implemented in the underlying storage engine"); + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + unqliteGenError(pDb,"Empty key"); + rc = UNQLITE_EMPTY; + }else{ + /* Seek to the record position */ + rc = pMethods->xSeek(pCur,pKey,nKeyLen,UNQLITE_CURSOR_MATCH_EXACT); + } + if( rc == UNQLITE_OK ){ + /* Exact match found, delete the entry */ + rc = pMethods->xDelete(pCur); + } + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_config(unqlite *pDb,int iOp,...) +{ + unqlite_kv_engine *pEngine; + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Point to the underlying storage engine */ + pEngine = unqlitePagerGetKvEngine(pDb); + if( pEngine->pIo->pMethods->xConfig == 0 ){ + /* Storage engine does not implements such method */ + unqliteGenError(pDb,"xConfig() method not implemented in the underlying storage engine"); + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + va_list ap; + /* Configure the storage engine */ + va_start(ap,iOp); + rc = pEngine->pIo->pMethods->xConfig(pEngine,iOp,ap); + va_end(ap); + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_init()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut) +{ + int rc; + if( UNQLITE_DB_MISUSE(pDb) || ppOut == 0 /* Noop */){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Allocate a new cursor */ + rc = unqliteInitCursor(pDb,ppOut); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur) +{ + int rc; + if( UNQLITE_DB_MISUSE(pDb) || pCur == 0 /* Noop */){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Release the cursor */ + rc = unqliteReleaseCursor(pDb,pCur); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_first_entry()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Check if the requested method is implemented by the underlying storage engine */ + if( pCursor->pStore->pIo->pMethods->xFirst == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + /* Seek to the first entry */ + rc = pCursor->pStore->pIo->pMethods->xFirst(pCursor); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_last_entry()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Check if the requested method is implemented by the underlying storage engine */ + if( pCursor->pStore->pIo->pMethods->xLast == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + /* Seek to the last entry */ + rc = pCursor->pStore->pIo->pMethods->xLast(pCursor); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_valid_entry()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Check if the requested method is implemented by the underlying storage engine */ + if( pCursor->pStore->pIo->pMethods->xValid == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + rc = pCursor->pStore->pIo->pMethods->xValid(pCursor); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_next_entry()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Check if the requested method is implemented by the underlying storage engine */ + if( pCursor->pStore->pIo->pMethods->xNext == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + /* Seek to the next entry */ + rc = pCursor->pStore->pIo->pMethods->xNext(pCursor); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_prev_entry()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Check if the requested method is implemented by the underlying storage engine */ + if( pCursor->pStore->pIo->pMethods->xPrev == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + /* Seek to the previous entry */ + rc = pCursor->pStore->pIo->pMethods->xPrev(pCursor); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_delete_entry()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Check if the requested method is implemented by the underlying storage engine */ + if( pCursor->pStore->pIo->pMethods->xDelete == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + /* Delete the entry */ + rc = pCursor->pStore->pIo->pMethods->xDelete(pCursor); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_reset()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor) +{ + int rc = UNQLITE_OK; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Check if the requested method is implemented by the underlying storage engine */ + if( pCursor->pStore->pIo->pMethods->xReset == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + /* Reset */ + pCursor->pStore->pIo->pMethods->xReset(pCursor); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_seek()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos) +{ + int rc = UNQLITE_OK; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + if( nKeyLen < 0 ){ + /* Assume a null terminated string and compute it's length */ + nKeyLen = SyStrlen((const char *)pKey); + } + if( !nKeyLen ){ + rc = UNQLITE_EMPTY; + }else{ + /* Seek to the desired location */ + rc = pCursor->pStore->pIo->pMethods->xSeek(pCursor,pKey,nKeyLen,iPos); + } + return rc; +} +/* + * Default data consumer callback. That is, all retrieved is redirected to this + * routine which store the output in an internal blob. + */ +UNQLITE_PRIVATE int unqliteDataConsumer( + const void *pOut, /* Data to consume */ + unsigned int nLen, /* Data length */ + void *pUserData /* User private data */ + ) +{ + sxi32 rc; + /* Store the output in an internal BLOB */ + rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen); + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_data_callback()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Consume the key directly */ + rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,xConsumer,pUserData); + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_key()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + if( pBuf == 0 ){ + /* Key length only */ + rc = pCursor->pStore->pIo->pMethods->xKeyLength(pCursor,pnByte); + }else{ + SyBlob sBlob; + if( (*pnByte) < 0 ){ + return UNQLITE_CORRUPT; + } + /* Initialize the data consumer */ + SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte)); + /* Consume the key */ + rc = pCursor->pStore->pIo->pMethods->xKey(pCursor,unqliteDataConsumer,&sBlob); + /* Key length */ + *pnByte = SyBlobLength(&sBlob); + /* Cleanup */ + SyBlobRelease(&sBlob); + } + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_data_callback()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + /* Consume the data directly */ + rc = pCursor->pStore->pIo->pMethods->xData(pCursor,xConsumer,pUserData); + return rc; +} +/* + * [CAPIREF: unqlite_kv_cursor_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnByte) +{ + int rc; +#ifdef UNTRUST + if( pCursor == 0 ){ + return UNQLITE_CORRUPT; + } +#endif + if( pBuf == 0 ){ + /* Data length only */ + rc = pCursor->pStore->pIo->pMethods->xDataLength(pCursor,pnByte); + }else{ + SyBlob sBlob; + if( (*pnByte) < 0 ){ + return UNQLITE_CORRUPT; + } + /* Initialize the data consumer */ + SyBlobInitFromBuf(&sBlob,pBuf,(sxu32)(*pnByte)); + /* Consume the data */ + rc = pCursor->pStore->pIo->pMethods->xData(pCursor,unqliteDataConsumer,&sBlob); + /* Data length */ + *pnByte = SyBlobLength(&sBlob); + /* Cleanup */ + SyBlobRelease(&sBlob); + } + return rc; +} +/* + * [CAPIREF: unqlite_begin()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_begin(unqlite *pDb) +{ + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Begin the write transaction */ + rc = unqlitePagerBegin(pDb->sDB.pPager); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_commit()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_commit(unqlite *pDb) +{ + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Commit the transaction */ + rc = unqlitePagerCommit(pDb->sDB.pPager); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_rollback()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +int unqlite_rollback(unqlite *pDb) +{ + int rc; + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Rollback the transaction */ + rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: unqlite_util_load_mmaped_file()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize) +{ + const jx9_vfs *pVfs; + int rc; + if( SX_EMPTY_STR(zFile) || ppMap == 0 || pFileSize == 0){ + /* Sanity check */ + return UNQLITE_CORRUPT; + } + *ppMap = 0; + /* Extract the Jx9 Vfs */ + pVfs = jx9ExportBuiltinVfs(); + /* + * Check if the underlying vfs implement the memory map routines + * [i.e: mmap() under UNIX/MapViewOfFile() under windows]. + */ + if( pVfs == 0 || pVfs->xMmap == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + /* Try to get a read-only memory view of the whole file */ + rc = pVfs->xMmap(zFile,ppMap,pFileSize); + } + return rc; +} +/* + * [CAPIREF: unqlite_util_release_mmaped_file()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize) +{ + const jx9_vfs *pVfs; + int rc = UNQLITE_OK; + if( pMap == 0 ){ + return UNQLITE_OK; + } + /* Extract the Jx9 Vfs */ + pVfs = jx9ExportBuiltinVfs(); + if( pVfs == 0 || pVfs->xUnmap == 0 ){ + rc = UNQLITE_NOTIMPLEMENTED; + }else{ + pVfs->xUnmap(pMap,iFileSize); + } + return rc; +} +/* + * [CAPIREF: unqlite_util_random_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size) +{ + if( UNQLITE_DB_MISUSE(pDb) ){ + return UNQLITE_CORRUPT; + } + if( zBuf == 0 || buf_size < 3 ){ + /* Buffer must be long enough to hold three bytes */ + return UNQLITE_INVALID; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return UNQLITE_ABORT; /* Another thread have released this instance */ + } +#endif + /* Generate the random string */ + unqlitePagerRandomString(pDb->sDB.pPager,zBuf,buf_size); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return UNQLITE_OK; +} +/* + * [CAPIREF: unqlite_util_random_num()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb) +{ + sxu32 iNum; + if( UNQLITE_DB_MISUSE(pDb) ){ + return 0; + } +#if defined(UNQLITE_ENABLE_THREADS) + /* Acquire DB mutex */ + SyMutexEnter(sUnqlMPGlobal.pMutexMethods, pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ + if( sUnqlMPGlobal.nThreadingLevel > UNQLITE_THREAD_LEVEL_SINGLE && + UNQLITE_THRD_DB_RELEASE(pDb) ){ + return 0; /* Another thread have released this instance */ + } +#endif + /* Generate the random number */ + iNum = unqlitePagerRandomNum(pDb->sDB.pPager); +#if defined(UNQLITE_ENABLE_THREADS) + /* Leave DB mutex */ + SyMutexLeave(sUnqlMPGlobal.pMutexMethods,pDb->pMutex); /* NO-OP if sUnqlMPGlobal.nThreadingLevel != UNQLITE_THREAD_LEVEL_MULTI */ +#endif + return iNum; +} +/* + * ---------------------------------------------------------- + * File: bitvec.c + * MD5: 7e3376710d8454ebcf8c77baacca880f + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: bitvec.c v1.0 Win7 2013-02-27 15:16 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif + +/** This file implements an object that represents a dynmaic +** bitmap. +** +** A bitmap is used to record which pages of a database file have been +** journalled during a transaction, or which pages have the "dont-write" +** property. Usually only a few pages are meet either condition. +** So the bitmap is usually sparse and has low cardinality. +*/ +/* + * Actually, this is not a bitmap but a simple hashtable where page + * number (64-bit unsigned integers) are used as the lookup keys. + */ +typedef struct bitvec_rec bitvec_rec; +struct bitvec_rec +{ + pgno iPage; /* Page number */ + bitvec_rec *pNext,*pNextCol; /* Collison link */ +}; +struct Bitvec +{ + SyMemBackend *pAlloc; /* Memory allocator */ + sxu32 nRec; /* Total number of records */ + sxu32 nSize; /* Table size */ + bitvec_rec **apRec; /* Record table */ + bitvec_rec *pList; /* List of records */ +}; +/* + * Allocate a new bitvec instance. +*/ +UNQLITE_PRIVATE Bitvec * unqliteBitvecCreate(SyMemBackend *pAlloc,pgno iSize) +{ + bitvec_rec **apNew; + Bitvec *p; + + p = (Bitvec *)SyMemBackendAlloc(pAlloc,sizeof(*p) ); + if( p == 0 ){ + SXUNUSED(iSize); /* cc warning */ + return 0; + } + /* Zero the structure */ + SyZero(p,sizeof(Bitvec)); + /* Allocate a new table */ + p->nSize = 64; /* Must be a power of two */ + apNew = (bitvec_rec **)SyMemBackendAlloc(pAlloc,p->nSize * sizeof(bitvec_rec *)); + if( apNew == 0 ){ + SyMemBackendFree(pAlloc,p); + return 0; + } + /* Zero the new table */ + SyZero((void *)apNew,p->nSize * sizeof(bitvec_rec *)); + /* Fill-in */ + p->apRec = apNew; + p->pAlloc = pAlloc; + return p; +} +/* + * Check if the given page number is already installed in the table. + * Return true if installed. False otherwise. + */ +UNQLITE_PRIVATE int unqliteBitvecTest(Bitvec *p,pgno i) +{ + bitvec_rec *pRec; + /* Point to the desired bucket */ + pRec = p->apRec[i & (p->nSize - 1)]; + for(;;){ + if( pRec == 0 ){ break; } + if( pRec->iPage == i ){ + /* Page found */ + return 1; + } + /* Point to the next entry */ + pRec = pRec->pNextCol; + + if( pRec == 0 ){ break; } + if( pRec->iPage == i ){ + /* Page found */ + return 1; + } + /* Point to the next entry */ + pRec = pRec->pNextCol; + + + if( pRec == 0 ){ break; } + if( pRec->iPage == i ){ + /* Page found */ + return 1; + } + /* Point to the next entry */ + pRec = pRec->pNextCol; + + + if( pRec == 0 ){ break; } + if( pRec->iPage == i ){ + /* Page found */ + return 1; + } + /* Point to the next entry */ + pRec = pRec->pNextCol; + } + /* No such entry */ + return 0; +} +/* + * Install a given page number in our bitmap (Actually, our hashtable). + */ +UNQLITE_PRIVATE int unqliteBitvecSet(Bitvec *p,pgno i) +{ + bitvec_rec *pRec; + sxi32 iBuck; + /* Allocate a new instance */ + pRec = (bitvec_rec *)SyMemBackendPoolAlloc(p->pAlloc,sizeof(bitvec_rec)); + if( pRec == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pRec,sizeof(bitvec_rec)); + /* Fill-in */ + pRec->iPage = i; + iBuck = i & (p->nSize - 1); + pRec->pNextCol = p->apRec[iBuck]; + p->apRec[iBuck] = pRec; + pRec->pNext = p->pList; + p->pList = pRec; + p->nRec++; + if( p->nRec >= (p->nSize * 3) && p->nRec < 100000 ){ + /* Grow the hashtable */ + sxu32 nNewSize = p->nSize << 1; + bitvec_rec *pEntry,**apNew; + sxu32 n; + apNew = (bitvec_rec **)SyMemBackendAlloc(p->pAlloc, nNewSize * sizeof(bitvec_rec *)); + if( apNew ){ + sxu32 iBucket; + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(bitvec_rec *)); + /* Rehash all entries */ + n = 0; + pEntry = p->pList; + for(;;){ + /* Loop one */ + if( n >= p->nRec ){ + break; + } + pEntry->pNextCol = 0; + /* Install in the new bucket */ + iBucket = pEntry->iPage & (nNewSize - 1); + pEntry->pNextCol = apNew[iBucket]; + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(p->pAlloc,(void *)p->apRec); + p->apRec = apNew; + p->nSize = nNewSize; + } + } + return UNQLITE_OK; +} +/* + * Destroy a bitvec instance. Reclaim all memory used. + */ +UNQLITE_PRIVATE void unqliteBitvecDestroy(Bitvec *p) +{ + bitvec_rec *pNext,*pRec = p->pList; + SyMemBackend *pAlloc = p->pAlloc; + + for(;;){ + if( p->nRec < 1 ){ + break; + } + pNext = pRec->pNext; + SyMemBackendPoolFree(pAlloc,(void *)pRec); + pRec = pNext; + p->nRec--; + + if( p->nRec < 1 ){ + break; + } + pNext = pRec->pNext; + SyMemBackendPoolFree(pAlloc,(void *)pRec); + pRec = pNext; + p->nRec--; + + + if( p->nRec < 1 ){ + break; + } + pNext = pRec->pNext; + SyMemBackendPoolFree(pAlloc,(void *)pRec); + pRec = pNext; + p->nRec--; + + + if( p->nRec < 1 ){ + break; + } + pNext = pRec->pNext; + SyMemBackendPoolFree(pAlloc,(void *)pRec); + pRec = pNext; + p->nRec--; + } + SyMemBackendFree(pAlloc,(void *)p->apRec); + SyMemBackendFree(pAlloc,p); +} +/* + * ---------------------------------------------------------- + * File: fastjson.c + * MD5: 3693c0022edc7d37b65124d7aef68397 + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: fastjson.c v1.1 FreeBSD 2012-12-05 22:52 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* JSON binary encoding, decoding and stuff like that */ +#ifndef UNQLITE_FAST_JSON_NEST_LIMIT +#if defined(__WINNT__) || defined(__UNIXES__) +#define UNQLITE_FAST_JSON_NEST_LIMIT 64 /* Nesting limit */ +#else +#define UNQLITE_FAST_JSON_NEST_LIMIT 32 /* Nesting limit */ +#endif +#endif /* UNQLITE_FAST_JSON_NEST_LIMIT */ +/* + * JSON to Binary using the FastJSON implementation (BigEndian). + */ +/* + * FastJSON implemented binary token. + */ +#define FJSON_DOC_START 1 /* { */ +#define FJSON_DOC_END 2 /* } */ +#define FJSON_ARRAY_START 3 /* [ */ +#define FJSON_ARRAY_END 4 /* ] */ +#define FJSON_COLON 5 /* : */ +#define FJSON_COMMA 6 /* , */ +#define FJSON_ID 7 /* ID + 4 Bytes length */ +#define FJSON_STRING 8 /* String + 4 bytes length */ +#define FJSON_BYTE 9 /* Byte */ +#define FJSON_INT64 10 /* Integer 64 + 8 bytes */ +#define FJSON_REAL 18 /* Floating point value + 2 bytes */ +#define FJSON_NULL 23 /* NULL */ +#define FJSON_TRUE 24 /* TRUE */ +#define FJSON_FALSE 25 /* FALSE */ +/* + * Encode a Jx9 value to binary JSON. + */ +UNQLITE_PRIVATE sxi32 FastJsonEncode( + jx9_value *pValue, /* Value to encode */ + SyBlob *pOut, /* Store encoded value here */ + int iNest /* Nesting limit */ + ) +{ + sxi32 iType = pValue ? pValue->iFlags : MEMOBJ_NULL; + sxi32 rc = SXRET_OK; + int c; + if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ + /* Nesting limit reached */ + return SXERR_LIMIT; + } + if( iType & (MEMOBJ_NULL|MEMOBJ_RES) ){ + /* + * Resources are encoded as null also. + */ + c = FJSON_NULL; + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + }else if( iType & MEMOBJ_BOOL ){ + c = pValue->x.iVal ? FJSON_TRUE : FJSON_FALSE; + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + }else if( iType & MEMOBJ_STRING ){ + unsigned char zBuf[sizeof(sxu32)]; /* String length */ + c = FJSON_STRING; + SyBigEndianPack32(zBuf,SyBlobLength(&pValue->sBlob)); + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc == SXRET_OK ){ + rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); + if( rc == SXRET_OK ){ + rc = SyBlobAppend(pOut,SyBlobData(&pValue->sBlob),SyBlobLength(&pValue->sBlob)); + } + } + }else if( iType & MEMOBJ_INT ){ + unsigned char zBuf[8]; + /* 64bit big endian integer */ + c = FJSON_INT64; + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc == SXRET_OK ){ + SyBigEndianPack64(zBuf,(sxu64)pValue->x.iVal); + rc = SyBlobAppend(pOut,(const void *)zBuf,sizeof(zBuf)); + } + }else if( iType & MEMOBJ_REAL ){ + /* Real number */ + c = FJSON_REAL; + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc == SXRET_OK ){ + sxu32 iOfft = SyBlobLength(pOut); + rc = SyBlobAppendBig16(pOut,0); + if( rc == SXRET_OK ){ + unsigned char *zBlob; + SyBlobFormat(pOut,"%.15g",pValue->x.rVal); + zBlob = (unsigned char *)SyBlobDataAt(pOut,iOfft); + SyBigEndianPack16(zBlob,(sxu16)(SyBlobLength(pOut) - ( 2 + iOfft))); + } + } + }else if( iType & MEMOBJ_HASHMAP ){ + /* A JSON object or array */ + jx9_hashmap *pMap = (jx9_hashmap *)pValue->x.pOther; + jx9_hashmap_node *pNode; + jx9_value *pEntry; + /* Reset the hashmap loop cursor */ + jx9HashmapResetLoopCursor(pMap); + if( pMap->iFlags & HASHMAP_JSON_OBJECT ){ + jx9_value sKey; + /* A JSON object */ + c = FJSON_DOC_START; /* { */ + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc == SXRET_OK ){ + jx9MemObjInit(pMap->pVm,&sKey); + /* Encode object entries */ + while((pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ + /* Extract the key */ + jx9HashmapExtractNodeKey(pNode,&sKey); + /* Encode it */ + rc = FastJsonEncode(&sKey,pOut,iNest+1); + if( rc != SXRET_OK ){ + break; + } + c = FJSON_COLON; + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc != SXRET_OK ){ + break; + } + /* Extract the value */ + pEntry = jx9HashmapGetNodeValue(pNode); + /* Encode it */ + rc = FastJsonEncode(pEntry,pOut,iNest+1); + if( rc != SXRET_OK ){ + break; + } + /* Delimit the entry */ + c = FJSON_COMMA; + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc != SXRET_OK ){ + break; + } + } + jx9MemObjRelease(&sKey); + if( rc == SXRET_OK ){ + c = FJSON_DOC_END; /* } */ + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + } + } + }else{ + /* A JSON array */ + c = FJSON_ARRAY_START; /* [ */ + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc == SXRET_OK ){ + /* Encode array entries */ + while( (pNode = jx9HashmapGetNextEntry(pMap)) != 0 ){ + /* Extract the value */ + pEntry = jx9HashmapGetNodeValue(pNode); + /* Encode it */ + rc = FastJsonEncode(pEntry,pOut,iNest+1); + if( rc != SXRET_OK ){ + break; + } + /* Delimit the entry */ + c = FJSON_COMMA; + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + if( rc != SXRET_OK ){ + break; + } + } + if( rc == SXRET_OK ){ + c = FJSON_ARRAY_END; /* ] */ + rc = SyBlobAppend(pOut,(const void *)&c,sizeof(char)); + } + } + } + } + return rc; +} +/* + * Decode a FastJSON binary blob. + */ +UNQLITE_PRIVATE sxi32 FastJsonDecode( + const void *pIn, /* Binary JSON */ + sxu32 nByte, /* Chunk delimiter */ + jx9_value *pOut, /* Decoded value */ + const unsigned char **pzPtr, + int iNest /* Nesting limit */ + ) +{ + const unsigned char *zIn = (const unsigned char *)pIn; + const unsigned char *zEnd = &zIn[nByte]; + sxi32 rc = SXRET_OK; + int c; + if( iNest >= UNQLITE_FAST_JSON_NEST_LIMIT ){ + /* Nesting limit reached */ + return SXERR_LIMIT; + } + c = zIn[0]; + /* Advance the stream cursor */ + zIn++; + /* Process the binary token */ + switch(c){ + case FJSON_NULL: + /* null */ + jx9_value_null(pOut); + break; + case FJSON_FALSE: + /* Boolean FALSE */ + jx9_value_bool(pOut,0); + break; + case FJSON_TRUE: + /* Boolean TRUE */ + jx9_value_bool(pOut,1); + break; + case FJSON_INT64: { + /* 64Bit integer */ + sxu64 iVal; + /* Sanity check */ + if( &zIn[8] >= zEnd ){ + /* Corrupt chunk */ + rc = SXERR_CORRUPT; + break; + } + SyBigEndianUnpack64(zIn,&iVal); + /* Advance the pointer */ + zIn += 8; + jx9_value_int64(pOut,(jx9_int64)iVal); + break; + } + case FJSON_REAL: { + /* Real number */ + double iVal = 0; /* cc warning */ + sxu16 iLen; + /* Sanity check */ + if( &zIn[2] >= zEnd ){ + /* Corrupt chunk */ + rc = SXERR_CORRUPT; + break; + } + SyBigEndianUnpack16(zIn,&iLen); + if( &zIn[iLen] >= zEnd ){ + /* Corrupt chunk */ + rc = SXERR_CORRUPT; + break; + } + zIn += 2; + SyStrToReal((const char *)zIn,(sxu32)iLen,&iVal,0); + /* Advance the pointer */ + zIn += iLen; + jx9_value_double(pOut,iVal); + break; + } + case FJSON_STRING: { + /* UTF-8/Binary chunk */ + sxu32 iLength; + /* Sanity check */ + if( &zIn[4] >= zEnd ){ + /* Corrupt chunk */ + rc = SXERR_CORRUPT; + break; + } + SyBigEndianUnpack32(zIn,&iLength); + if( &zIn[iLength] >= zEnd ){ + /* Corrupt chunk */ + rc = SXERR_CORRUPT; + break; + } + zIn += 4; + /* Invalidate any prior representation */ + if( pOut->iFlags & MEMOBJ_STRING ){ + /* Reset the string cursor */ + SyBlobReset(&pOut->sBlob); + } + rc = jx9MemObjStringAppend(pOut,(const char *)zIn,iLength); + /* Update pointer */ + zIn += iLength; + break; + } + case FJSON_ARRAY_START: { + /* Binary JSON array */ + jx9_hashmap *pMap; + jx9_value sVal; + /* Allocate a new hashmap */ + pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); + if( pMap == 0 ){ + rc = SXERR_MEM; + break; + } + jx9MemObjInit(pOut->pVm,&sVal); + jx9MemObjRelease(pOut); + MemObjSetType(pOut,MEMOBJ_HASHMAP); + pOut->x.pOther = pMap; + rc = SXRET_OK; + for(;;){ + /* Jump leading binary commas */ + while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ + zIn++; + } + if( zIn >= zEnd || zIn[0] == FJSON_ARRAY_END ){ + if( zIn < zEnd ){ + zIn++; /* Jump the trailing binary ] */ + } + break; + } + /* Decode the value */ + rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); + if( rc != SXRET_OK ){ + break; + } + /* Insert the decoded value */ + rc = jx9HashmapInsert(pMap,0,&sVal); + if( rc != UNQLITE_OK ){ + break; + } + } + if( rc != SXRET_OK ){ + jx9MemObjRelease(pOut); + } + jx9MemObjRelease(&sVal); + break; + } + case FJSON_DOC_START: { + /* Binary JSON object */ + jx9_value sVal,sKey; + jx9_hashmap *pMap; + /* Allocate a new hashmap */ + pMap = (jx9_hashmap *)jx9NewHashmap(pOut->pVm,0,0); + if( pMap == 0 ){ + rc = SXERR_MEM; + break; + } + jx9MemObjInit(pOut->pVm,&sVal); + jx9MemObjInit(pOut->pVm,&sKey); + jx9MemObjRelease(pOut); + MemObjSetType(pOut,MEMOBJ_HASHMAP); + pOut->x.pOther = pMap; + rc = SXRET_OK; + for(;;){ + /* Jump leading binary commas */ + while (zIn < zEnd && zIn[0] == FJSON_COMMA ){ + zIn++; + } + if( zIn >= zEnd || zIn[0] == FJSON_DOC_END ){ + if( zIn < zEnd ){ + zIn++; /* Jump the trailing binary } */ + } + break; + } + /* Extract the key */ + rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sKey,&zIn,iNest+1); + if( rc != UNQLITE_OK ){ + break; + } + if( zIn >= zEnd || zIn[0] != FJSON_COLON ){ + rc = UNQLITE_CORRUPT; + break; + } + zIn++; /* Jump the binary colon ':' */ + if( zIn >= zEnd ){ + rc = UNQLITE_CORRUPT; + break; + } + /* Decode the value */ + rc = FastJsonDecode((const void *)zIn,(sxu32)(zEnd-zIn),&sVal,&zIn,iNest+1); + if( rc != SXRET_OK ){ + break; + } + /* Insert the key and its associated value */ + rc = jx9HashmapInsert(pMap,&sKey,&sVal); + if( rc != UNQLITE_OK ){ + break; + } + } + if( rc != SXRET_OK ){ + jx9MemObjRelease(pOut); + } + jx9MemObjRelease(&sVal); + jx9MemObjRelease(&sKey); + break; + } + default: + /* Corrupt data */ + rc = SXERR_CORRUPT; + break; + } + if( pzPtr ){ + *pzPtr = zIn; + } + return rc; +} +/* + * ---------------------------------------------------------- + * File: jx9_api.c + * MD5: 73cba599c009cee0ff878666d0543438 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: api.c v1.7 FreeBSD 2012-12-18 06:54 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file implement the public interfaces presented to host-applications. + * Routines in other files are for internal use by JX9 and should not be + * accessed by users of the library. + */ +#define JX9_ENGINE_MAGIC 0xF874BCD7 +#define JX9_ENGINE_MISUSE(ENGINE) (ENGINE == 0 || ENGINE->nMagic != JX9_ENGINE_MAGIC) +#define JX9_VM_MISUSE(VM) (VM == 0 || VM->nMagic == JX9_VM_STALE) +/* If another thread have released a working instance, the following macros + * evaluates to true. These macros are only used when the library + * is built with threading support enabled which is not the case in + * the default built. + */ +#define JX9_THRD_ENGINE_RELEASE(ENGINE) (ENGINE->nMagic != JX9_ENGINE_MAGIC) +#define JX9_THRD_VM_RELEASE(VM) (VM->nMagic == JX9_VM_STALE) +/* IMPLEMENTATION: jx9@embedded@symisc 311-12-32 */ +/* + * All global variables are collected in the structure named "sJx9MPGlobal". + * That way it is clear in the code when we are using static variable because + * its name start with sJx9MPGlobal. + */ +static struct Jx9Global_Data +{ + SyMemBackend sAllocator; /* Global low level memory allocator */ +#if defined(JX9_ENABLE_THREADS) + const SyMutexMethods *pMutexMethods; /* Mutex methods */ + SyMutex *pMutex; /* Global mutex */ + sxu32 nThreadingLevel; /* Threading level: 0 == Single threaded/1 == Multi-Threaded + * The threading level can be set using the [jx9_lib_config()] + * interface with a configuration verb set to + * JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE or + * JX9_LIB_CONFIG_THREAD_LEVEL_MULTI + */ +#endif + const jx9_vfs *pVfs; /* Underlying virtual file system */ + sxi32 nEngine; /* Total number of active engines */ + jx9 *pEngines; /* List of active engine */ + sxu32 nMagic; /* Sanity check against library misuse */ +}sJx9MPGlobal = { + {0, 0, 0, 0, 0, 0, 0, 0, {0}}, +#if defined(JX9_ENABLE_THREADS) + 0, + 0, + 0, +#endif + 0, + 0, + 0, + 0 +}; +#define JX9_LIB_MAGIC 0xEA1495BA +#define JX9_LIB_MISUSE (sJx9MPGlobal.nMagic != JX9_LIB_MAGIC) +/* + * Supported threading level. + * These options have meaning only when the library is compiled with multi-threading + * support.That is, the JX9_ENABLE_THREADS compile time directive must be defined + * when JX9 is built. + * JX9_THREAD_LEVEL_SINGLE: + * In this mode, mutexing is disabled and the library can only be used by a single thread. + * JX9_THREAD_LEVEL_MULTI + * In this mode, all mutexes including the recursive mutexes on [jx9] objects + * are enabled so that the application is free to share the same engine + * between different threads at the same time. + */ +#define JX9_THREAD_LEVEL_SINGLE 1 +#define JX9_THREAD_LEVEL_MULTI 2 +/* + * Configure a running JX9 engine instance. + * return JX9_OK on success.Any other return + * value indicates failure. + * Refer to [jx9_config()]. + */ +JX9_PRIVATE sxi32 jx9EngineConfig(jx9 *pEngine, sxi32 nOp, va_list ap) +{ + jx9_conf *pConf = &pEngine->xConf; + int rc = JX9_OK; + /* Perform the requested operation */ + switch(nOp){ + case JX9_CONFIG_ERR_LOG:{ + /* Extract compile-time error log if any */ + const char **pzPtr = va_arg(ap, const char **); + int *pLen = va_arg(ap, int *); + if( pzPtr == 0 ){ + rc = JX9_CORRUPT; + break; + } + /* NULL terminate the error-log buffer */ + SyBlobNullAppend(&pConf->sErrConsumer); + /* Point to the error-log buffer */ + *pzPtr = (const char *)SyBlobData(&pConf->sErrConsumer); + if( pLen ){ + if( SyBlobLength(&pConf->sErrConsumer) > 1 /* NULL '\0' terminator */ ){ + *pLen = (int)SyBlobLength(&pConf->sErrConsumer); + }else{ + *pLen = 0; + } + } + break; + } + case JX9_CONFIG_ERR_ABORT: + /* Reserved for future use */ + break; + default: + /* Unknown configuration verb */ + rc = JX9_CORRUPT; + break; + } /* Switch() */ + return rc; +} +/* + * Configure the JX9 library. + * Return JX9_OK on success. Any other return value indicates failure. + * Refer to [jx9_lib_config()]. + */ +static sxi32 Jx9CoreConfigure(sxi32 nOp, va_list ap) +{ + int rc = JX9_OK; + switch(nOp){ + case JX9_LIB_CONFIG_VFS:{ + /* Install a virtual file system */ + const jx9_vfs *pVfs = va_arg(ap, const jx9_vfs *); + sJx9MPGlobal.pVfs = pVfs; + break; + } + case JX9_LIB_CONFIG_USER_MALLOC: { + /* Use an alternative low-level memory allocation routines */ + const SyMemMethods *pMethods = va_arg(ap, const SyMemMethods *); + /* Save the memory failure callback (if available) */ + ProcMemError xMemErr = sJx9MPGlobal.sAllocator.xMemError; + void *pMemErr = sJx9MPGlobal.sAllocator.pUserData; + if( pMethods == 0 ){ + /* Use the built-in memory allocation subsystem */ + rc = SyMemBackendInit(&sJx9MPGlobal.sAllocator, xMemErr, pMemErr); + }else{ + rc = SyMemBackendInitFromOthers(&sJx9MPGlobal.sAllocator, pMethods, xMemErr, pMemErr); + } + break; + } + case JX9_LIB_CONFIG_MEM_ERR_CALLBACK: { + /* Memory failure callback */ + ProcMemError xMemErr = va_arg(ap, ProcMemError); + void *pUserData = va_arg(ap, void *); + sJx9MPGlobal.sAllocator.xMemError = xMemErr; + sJx9MPGlobal.sAllocator.pUserData = pUserData; + break; + } + case JX9_LIB_CONFIG_USER_MUTEX: { +#if defined(JX9_ENABLE_THREADS) + /* Use an alternative low-level mutex subsystem */ + const SyMutexMethods *pMethods = va_arg(ap, const SyMutexMethods *); +#if defined (UNTRUST) + if( pMethods == 0 ){ + rc = JX9_CORRUPT; + } +#endif + /* Sanity check */ + if( pMethods->xEnter == 0 || pMethods->xLeave == 0 || pMethods->xNew == 0){ + /* At least three criticial callbacks xEnter(), xLeave() and xNew() must be supplied */ + rc = JX9_CORRUPT; + break; + } + if( sJx9MPGlobal.pMutexMethods ){ + /* Overwrite the previous mutex subsystem */ + SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); + if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){ + sJx9MPGlobal.pMutexMethods->xGlobalRelease(); + } + sJx9MPGlobal.pMutex = 0; + } + /* Initialize and install the new mutex subsystem */ + if( pMethods->xGlobalInit ){ + rc = pMethods->xGlobalInit(); + if ( rc != JX9_OK ){ + break; + } + } + /* Create the global mutex */ + sJx9MPGlobal.pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); + if( sJx9MPGlobal.pMutex == 0 ){ + /* + * If the supplied mutex subsystem is so sick that we are unable to + * create a single mutex, there is no much we can do here. + */ + if( pMethods->xGlobalRelease ){ + pMethods->xGlobalRelease(); + } + rc = JX9_CORRUPT; + break; + } + sJx9MPGlobal.pMutexMethods = pMethods; + if( sJx9MPGlobal.nThreadingLevel == 0 ){ + /* Set a default threading level */ + sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI; + } +#endif + break; + } + case JX9_LIB_CONFIG_THREAD_LEVEL_SINGLE: +#if defined(JX9_ENABLE_THREADS) + /* Single thread mode(Only one thread is allowed to play with the library) */ + sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_SINGLE; +#endif + break; + case JX9_LIB_CONFIG_THREAD_LEVEL_MULTI: +#if defined(JX9_ENABLE_THREADS) + /* Multi-threading mode (library is thread safe and JX9 engines and virtual machines + * may be shared between multiple threads). + */ + sJx9MPGlobal.nThreadingLevel = JX9_THREAD_LEVEL_MULTI; +#endif + break; + default: + /* Unknown configuration option */ + rc = JX9_CORRUPT; + break; + } + return rc; +} +/* + * [CAPIREF: jx9_lib_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_lib_config(int nConfigOp, ...) +{ + va_list ap; + int rc; + if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){ + /* Library is already initialized, this operation is forbidden */ + return JX9_LOOKED; + } + va_start(ap, nConfigOp); + rc = Jx9CoreConfigure(nConfigOp, ap); + va_end(ap); + return rc; +} +/* + * Global library initialization + * Refer to [jx9_lib_init()] + * This routine must be called to initialize the memory allocation subsystem, the mutex + * subsystem prior to doing any serious work with the library.The first thread to call + * this routine does the initialization process and set the magic number so no body later + * can re-initialize the library.If subsequent threads call this routine before the first + * thread have finished the initialization process, then the subsequent threads must block + * until the initialization process is done. + */ +static sxi32 Jx9CoreInitialize(void) +{ + const jx9_vfs *pVfs; /* Built-in vfs */ +#if defined(JX9_ENABLE_THREADS) + const SyMutexMethods *pMutexMethods = 0; + SyMutex *pMaster = 0; +#endif + int rc; + /* + * If the library is already initialized, then a call to this routine + * is a no-op. + */ + if( sJx9MPGlobal.nMagic == JX9_LIB_MAGIC ){ + return JX9_OK; /* Already initialized */ + } + if( sJx9MPGlobal.pVfs == 0 ){ + /* Point to the built-in vfs */ + pVfs = jx9ExportBuiltinVfs(); + /* Install it */ + jx9_lib_config(JX9_LIB_CONFIG_VFS, pVfs); + } +#if defined(JX9_ENABLE_THREADS) + if( sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_SINGLE ){ + pMutexMethods = sJx9MPGlobal.pMutexMethods; + if( pMutexMethods == 0 ){ + /* Use the built-in mutex subsystem */ + pMutexMethods = SyMutexExportMethods(); + if( pMutexMethods == 0 ){ + return JX9_CORRUPT; /* Can't happen */ + } + /* Install the mutex subsystem */ + rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MUTEX, pMutexMethods); + if( rc != JX9_OK ){ + return rc; + } + } + /* Obtain a static mutex so we can initialize the library without calling malloc() */ + pMaster = SyMutexNew(pMutexMethods, SXMUTEX_TYPE_STATIC_1); + if( pMaster == 0 ){ + return JX9_CORRUPT; /* Can't happen */ + } + } + /* Lock the master mutex */ + rc = JX9_OK; + SyMutexEnter(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ + if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){ +#endif + if( sJx9MPGlobal.sAllocator.pMethods == 0 ){ + /* Install a memory subsystem */ + rc = jx9_lib_config(JX9_LIB_CONFIG_USER_MALLOC, 0); /* zero mean use the built-in memory backend */ + if( rc != JX9_OK ){ + /* If we are unable to initialize the memory backend, there is no much we can do here.*/ + goto End; + } + } +#if defined(JX9_ENABLE_THREADS) + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ + /* Protect the memory allocation subsystem */ + rc = SyMemBackendMakeThreadSafe(&sJx9MPGlobal.sAllocator, sJx9MPGlobal.pMutexMethods); + if( rc != JX9_OK ){ + goto End; + } + } +#endif + /* Our library is initialized, set the magic number */ + sJx9MPGlobal.nMagic = JX9_LIB_MAGIC; + rc = JX9_OK; +#if defined(JX9_ENABLE_THREADS) + } /* sJx9MPGlobal.nMagic != JX9_LIB_MAGIC */ +#endif +End: +#if defined(JX9_ENABLE_THREADS) + /* Unlock the master mutex */ + SyMutexLeave(pMutexMethods, pMaster); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ +#endif + return rc; +} +/* + * Release an active JX9 engine and it's associated active virtual machines. + */ +static sxi32 EngineRelease(jx9 *pEngine) +{ + jx9_vm *pVm, *pNext; + /* Release all active VM */ + pVm = pEngine->pVms; + for(;;){ + if( pEngine->iVm < 1 ){ + break; + } + pNext = pVm->pNext; + jx9VmRelease(pVm); + pVm = pNext; + pEngine->iVm--; + } + /* Set a dummy magic number */ + pEngine->nMagic = 0x7635; + /* Release the private memory subsystem */ + SyMemBackendRelease(&pEngine->sAllocator); + return JX9_OK; +} +/* + * Release all resources consumed by the library. + * If JX9 is already shut when this routine is invoked then this + * routine is a harmless no-op. + * Note: This call is not thread safe. Refer to [jx9_lib_shutdown()]. + */ +static void JX9CoreShutdown(void) +{ + jx9 *pEngine, *pNext; + /* Release all active engines first */ + pEngine = sJx9MPGlobal.pEngines; + for(;;){ + if( sJx9MPGlobal.nEngine < 1 ){ + break; + } + pNext = pEngine->pNext; + EngineRelease(pEngine); + pEngine = pNext; + sJx9MPGlobal.nEngine--; + } +#if defined(JX9_ENABLE_THREADS) + /* Release the mutex subsystem */ + if( sJx9MPGlobal.pMutexMethods ){ + if( sJx9MPGlobal.pMutex ){ + SyMutexRelease(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); + sJx9MPGlobal.pMutex = 0; + } + if( sJx9MPGlobal.pMutexMethods->xGlobalRelease ){ + sJx9MPGlobal.pMutexMethods->xGlobalRelease(); + } + sJx9MPGlobal.pMutexMethods = 0; + } + sJx9MPGlobal.nThreadingLevel = 0; +#endif + if( sJx9MPGlobal.sAllocator.pMethods ){ + /* Release the memory backend */ + SyMemBackendRelease(&sJx9MPGlobal.sAllocator); + } + sJx9MPGlobal.nMagic = 0x1928; +} +/* + * [CAPIREF: jx9_lib_shutdown()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_lib_shutdown(void) +{ + if( sJx9MPGlobal.nMagic != JX9_LIB_MAGIC ){ + /* Already shut */ + return JX9_OK; + } + JX9CoreShutdown(); + return JX9_OK; +} +/* + * [CAPIREF: jx9_lib_signature()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE const char * jx9_lib_signature(void) +{ + return JX9_SIG; +} +/* + * [CAPIREF: jx9_init()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_init(jx9 **ppEngine) +{ + jx9 *pEngine; + int rc; +#if defined(UNTRUST) + if( ppEngine == 0 ){ + return JX9_CORRUPT; + } +#endif + *ppEngine = 0; + /* One-time automatic library initialization */ + rc = Jx9CoreInitialize(); + if( rc != JX9_OK ){ + return rc; + } + /* Allocate a new engine */ + pEngine = (jx9 *)SyMemBackendPoolAlloc(&sJx9MPGlobal.sAllocator, sizeof(jx9)); + if( pEngine == 0 ){ + return JX9_NOMEM; + } + /* Zero the structure */ + SyZero(pEngine, sizeof(jx9)); + /* Initialize engine fields */ + pEngine->nMagic = JX9_ENGINE_MAGIC; + rc = SyMemBackendInitFromParent(&pEngine->sAllocator, &sJx9MPGlobal.sAllocator); + if( rc != JX9_OK ){ + goto Release; + } +//#if defined(JX9_ENABLE_THREADS) +// SyMemBackendDisbaleMutexing(&pEngine->sAllocator); +//#endif + /* Default configuration */ + SyBlobInit(&pEngine->xConf.sErrConsumer, &pEngine->sAllocator); + /* Install a default compile-time error consumer routine */ + pEngine->xConf.xErr = jx9VmBlobConsumer; + pEngine->xConf.pErrData = &pEngine->xConf.sErrConsumer; + /* Built-in vfs */ + pEngine->pVfs = sJx9MPGlobal.pVfs; +#if defined(JX9_ENABLE_THREADS) + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ + /* Associate a recursive mutex with this instance */ + pEngine->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); + if( pEngine->pMutex == 0 ){ + rc = JX9_NOMEM; + goto Release; + } + } +#endif + /* Link to the list of active engines */ +#if defined(JX9_ENABLE_THREADS) + /* Enter the global mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ +#endif + MACRO_LD_PUSH(sJx9MPGlobal.pEngines, pEngine); + sJx9MPGlobal.nEngine++; +#if defined(JX9_ENABLE_THREADS) + /* Leave the global mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ +#endif + /* Write a pointer to the new instance */ + *ppEngine = pEngine; + return JX9_OK; +Release: + SyMemBackendRelease(&pEngine->sAllocator); + SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator,pEngine); + return rc; +} +/* + * [CAPIREF: jx9_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_release(jx9 *pEngine) +{ + int rc; + if( JX9_ENGINE_MISUSE(pEngine) ){ + return JX9_CORRUPT; + } +#if defined(JX9_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_ENGINE_RELEASE(pEngine) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + /* Release the engine */ + rc = EngineRelease(&(*pEngine)); +#if defined(JX9_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + /* Release engine mutex */ + SyMutexRelease(sJx9MPGlobal.pMutexMethods, pEngine->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif +#if defined(JX9_ENABLE_THREADS) + /* Enter the global mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ +#endif + /* Unlink from the list of active engines */ + MACRO_LD_REMOVE(sJx9MPGlobal.pEngines, pEngine); + sJx9MPGlobal.nEngine--; +#if defined(JX9_ENABLE_THREADS) + /* Leave the global mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, sJx9MPGlobal.pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel == JX9_THREAD_LEVEL_SINGLE */ +#endif + /* Release the memory chunk allocated to this engine */ + SyMemBackendPoolFree(&sJx9MPGlobal.sAllocator, pEngine); + return rc; +} +/* + * Compile a raw JX9 script. + * To execute a JX9 code, it must first be compiled into a bytecode program using this routine. + * If something goes wrong [i.e: compile-time error], your error log [i.e: error consumer callback] + * should display the appropriate error message and this function set ppVm to null and return + * an error code that is different from JX9_OK. Otherwise when the script is successfully compiled + * ppVm should hold the JX9 bytecode and it's safe to call [jx9_vm_exec(), jx9_vm_reset(), etc.]. + * This API does not actually evaluate the JX9 code. It merely compile and prepares the JX9 script + * for evaluation. + */ +static sxi32 ProcessScript( + jx9 *pEngine, /* Running JX9 engine */ + jx9_vm **ppVm, /* OUT: A pointer to the virtual machine */ + SyString *pScript, /* Raw JX9 script to compile */ + sxi32 iFlags, /* Compile-time flags */ + const char *zFilePath /* File path if script come from a file. NULL otherwise */ + ) +{ + jx9_vm *pVm; + int rc; + /* Allocate a new virtual machine */ + pVm = (jx9_vm *)SyMemBackendPoolAlloc(&pEngine->sAllocator, sizeof(jx9_vm)); + if( pVm == 0 ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. */ + if( ppVm ){ + *ppVm = 0; + } + return JX9_NOMEM; + } + if( iFlags < 0 ){ + /* Default compile-time flags */ + iFlags = 0; + } + /* Initialize the Virtual Machine */ + rc = jx9VmInit(pVm, &(*pEngine)); + if( rc != JX9_OK ){ + SyMemBackendPoolFree(&pEngine->sAllocator, pVm); + if( ppVm ){ + *ppVm = 0; + } + return JX9_VM_ERR; + } + if( zFilePath ){ + /* Push processed file path */ + jx9VmPushFilePath(pVm, zFilePath, -1, TRUE, 0); + } + /* Reset the error message consumer */ + SyBlobReset(&pEngine->xConf.sErrConsumer); + /* Compile the script */ + jx9CompileScript(pVm, &(*pScript), iFlags); + if( pVm->sCodeGen.nErr > 0 || pVm == 0){ + sxu32 nErr = pVm->sCodeGen.nErr; + /* Compilation error or null ppVm pointer, release this VM */ + SyMemBackendRelease(&pVm->sAllocator); + SyMemBackendPoolFree(&pEngine->sAllocator, pVm); + if( ppVm ){ + *ppVm = 0; + } + return nErr > 0 ? JX9_COMPILE_ERR : JX9_OK; + } + /* Prepare the virtual machine for bytecode execution */ + rc = jx9VmMakeReady(pVm); + if( rc != JX9_OK ){ + goto Release; + } + /* Install local import path which is the current directory */ + jx9_vm_config(pVm, JX9_VM_CONFIG_IMPORT_PATH, "./"); +#if defined(JX9_ENABLE_THREADS) + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE ){ + /* Associate a recursive mutex with this instance */ + pVm->pMutex = SyMutexNew(sJx9MPGlobal.pMutexMethods, SXMUTEX_TYPE_RECURSIVE); + if( pVm->pMutex == 0 ){ + goto Release; + } + } +#endif + /* Script successfully compiled, link to the list of active virtual machines */ + MACRO_LD_PUSH(pEngine->pVms, pVm); + pEngine->iVm++; + /* Point to the freshly created VM */ + *ppVm = pVm; + /* Ready to execute JX9 bytecode */ + return JX9_OK; +Release: + SyMemBackendRelease(&pVm->sAllocator); + SyMemBackendPoolFree(&pEngine->sAllocator, pVm); + *ppVm = 0; + return JX9_VM_ERR; +} +/* + * [CAPIREF: jx9_compile()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_compile(jx9 *pEngine, const char *zSource, int nLen, jx9_vm **ppOutVm) +{ + SyString sScript; + int rc; + if( JX9_ENGINE_MISUSE(pEngine) ){ + return JX9_CORRUPT; + } + if( zSource == 0 ){ + /* Empty Jx9 statement ';' */ + zSource = ";"; + nLen = (int)sizeof(char); + } + if( nLen < 0 ){ + /* Compute input length automatically */ + nLen = (int)SyStrlen(zSource); + } + SyStringInitFromBuf(&sScript, zSource, nLen); +#if defined(JX9_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_ENGINE_RELEASE(pEngine) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + /* Compile the script */ + rc = ProcessScript(&(*pEngine),ppOutVm,&sScript,0,0); +#if defined(JX9_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif + /* Compilation result */ + return rc; +} +/* + * [CAPIREF: jx9_compile_file()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_compile_file(jx9 *pEngine, const char *zFilePath, jx9_vm **ppOutVm) +{ + const jx9_vfs *pVfs; + int rc; + if( ppOutVm ){ + *ppOutVm = 0; + } + rc = JX9_OK; /* cc warning */ + if( JX9_ENGINE_MISUSE(pEngine) || SX_EMPTY_STR(zFilePath) ){ + return JX9_CORRUPT; + } +#if defined(JX9_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_ENGINE_RELEASE(pEngine) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + /* + * Check if the underlying vfs implement the memory map + * [i.e: mmap() under UNIX/MapViewOfFile() under windows] function. + */ + pVfs = pEngine->pVfs; + if( pVfs == 0 || pVfs->xMmap == 0 ){ + /* Memory map routine not implemented */ + rc = JX9_IO_ERR; + }else{ + void *pMapView = 0; /* cc warning */ + jx9_int64 nSize = 0; /* cc warning */ + SyString sScript; + /* Try to get a memory view of the whole file */ + rc = pVfs->xMmap(zFilePath, &pMapView, &nSize); + if( rc != JX9_OK ){ + /* Assume an IO error */ + rc = JX9_IO_ERR; + }else{ + /* Compile the file */ + SyStringInitFromBuf(&sScript, pMapView, nSize); + rc = ProcessScript(&(*pEngine), ppOutVm, &sScript,0,zFilePath); + /* Release the memory view of the whole file */ + if( pVfs->xUnmap ){ + pVfs->xUnmap(pMapView, nSize); + } + } + } +#if defined(JX9_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif + /* Compilation result */ + return rc; +} +/* + * [CAPIREF: jx9_vm_config()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_vm_config(jx9_vm *pVm, int iConfigOp, ...) +{ + va_list ap; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( JX9_VM_MISUSE(pVm) ){ + return JX9_CORRUPT; + } +#if defined(JX9_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_VM_RELEASE(pVm) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + /* Confiugure the virtual machine */ + va_start(ap, iConfigOp); + rc = jx9VmConfigure(&(*pVm), iConfigOp, ap); + va_end(ap); +#if defined(JX9_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +/* + * [CAPIREF: jx9_vm_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_vm_release(jx9_vm *pVm) +{ + jx9 *pEngine; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( JX9_VM_MISUSE(pVm) ){ + return JX9_CORRUPT; + } +#if defined(JX9_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_VM_RELEASE(pVm) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + pEngine = pVm->pEngine; + rc = jx9VmRelease(&(*pVm)); +#if defined(JX9_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + /* Release VM mutex */ + SyMutexRelease(sJx9MPGlobal.pMutexMethods, pVm->pMutex) /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif + if( rc == JX9_OK ){ + /* Unlink from the list of active VM */ +#if defined(JX9_ENABLE_THREADS) + /* Acquire engine mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_ENGINE_RELEASE(pEngine) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + MACRO_LD_REMOVE(pEngine->pVms, pVm); + pEngine->iVm--; + /* Release the memory chunk allocated to this VM */ + SyMemBackendPoolFree(&pEngine->sAllocator, pVm); +#if defined(JX9_ENABLE_THREADS) + /* Leave engine mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pEngine->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif + } + return rc; +} +/* + * [CAPIREF: jx9_create_function()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_create_function(jx9_vm *pVm, const char *zName, int (*xFunc)(jx9_context *, int, jx9_value **), void *pUserData) +{ + SyString sName; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( JX9_VM_MISUSE(pVm) ){ + return JX9_CORRUPT; + } + SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sName); + /* Ticket 1433-003: NULL values are not allowed */ + if( sName.nByte < 1 || xFunc == 0 ){ + return JX9_CORRUPT; + } +#if defined(JX9_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_VM_RELEASE(pVm) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + /* Install the foreign function */ + rc = jx9VmInstallForeignFunction(&(*pVm), &sName, xFunc, pUserData); +#if defined(JX9_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +JX9_PRIVATE int jx9DeleteFunction(jx9_vm *pVm,const char *zName) +{ + jx9_user_func *pFunc = 0; /* cc warning */ + int rc; + /* Perform the deletion */ + rc = SyHashDeleteEntry(&pVm->hHostFunction, (const void *)zName, SyStrlen(zName), (void **)&pFunc); + if( rc == JX9_OK ){ + /* Release internal fields */ + SySetRelease(&pFunc->aAux); + SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName)); + SyMemBackendPoolFree(&pVm->sAllocator, pFunc); + } + return rc; +} +/* + * [CAPIREF: jx9_create_constant()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_create_constant(jx9_vm *pVm, const char *zName, void (*xExpand)(jx9_value *, void *), void *pUserData) +{ + SyString sName; + int rc; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( JX9_VM_MISUSE(pVm) ){ + return JX9_CORRUPT; + } + SyStringInitFromBuf(&sName, zName, SyStrlen(zName)); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sName); + if( sName.nByte < 1 ){ + /* Empty constant name */ + return JX9_CORRUPT; + } + /* TICKET 1433-003: NULL pointer is harmless operation */ + if( xExpand == 0 ){ + return JX9_CORRUPT; + } +#if defined(JX9_ENABLE_THREADS) + /* Acquire VM mutex */ + SyMutexEnter(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ + if( sJx9MPGlobal.nThreadingLevel > JX9_THREAD_LEVEL_SINGLE && + JX9_THRD_VM_RELEASE(pVm) ){ + return JX9_ABORT; /* Another thread have released this instance */ + } +#endif + /* Perform the registration */ + rc = jx9VmRegisterConstant(&(*pVm), &sName, xExpand, pUserData); +#if defined(JX9_ENABLE_THREADS) + /* Leave VM mutex */ + SyMutexLeave(sJx9MPGlobal.pMutexMethods, pVm->pMutex); /* NO-OP if sJx9MPGlobal.nThreadingLevel != JX9_THREAD_LEVEL_MULTI */ +#endif + return rc; +} +JX9_PRIVATE int Jx9DeleteConstant(jx9_vm *pVm,const char *zName) +{ + jx9_constant *pCons; + int rc; + /* Query the constant hashtable */ + rc = SyHashDeleteEntry(&pVm->hConstant, (const void *)zName, SyStrlen(zName), (void **)&pCons); + if( rc == JX9_OK ){ + /* Perform the deletion */ + SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pCons->sName)); + SyMemBackendPoolFree(&pVm->sAllocator, pCons); + } + return rc; +} +/* + * [CAPIREF: jx9_new_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE jx9_value * jx9_new_scalar(jx9_vm *pVm) +{ + jx9_value *pObj; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( JX9_VM_MISUSE(pVm) ){ + return 0; + } + /* Allocate a new scalar variable */ + pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value)); + if( pObj == 0 ){ + return 0; + } + /* Nullify the new scalar */ + jx9MemObjInit(pVm, pObj); + return pObj; +} +/* + * [CAPIREF: jx9_new_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE jx9_value * jx9_new_array(jx9_vm *pVm) +{ + jx9_hashmap *pMap; + jx9_value *pObj; + /* Ticket 1433-002: NULL VM is harmless operation */ + if ( JX9_VM_MISUSE(pVm) ){ + return 0; + } + /* Create a new hashmap first */ + pMap = jx9NewHashmap(&(*pVm), 0, 0); + if( pMap == 0 ){ + return 0; + } + /* Associate a new jx9_value with this hashmap */ + pObj = (jx9_value *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_value)); + if( pObj == 0 ){ + jx9HashmapRelease(pMap, TRUE); + return 0; + } + jx9MemObjInitFromArray(pVm, pObj, pMap); + return pObj; +} +/* + * [CAPIREF: jx9_release_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_release_value(jx9_vm *pVm, jx9_value *pValue) +{ + /* Ticket 1433-002: NULL VM is a harmless operation */ + if ( JX9_VM_MISUSE(pVm) ){ + return JX9_CORRUPT; + } + if( pValue ){ + /* Release the value */ + jx9MemObjRelease(pValue); + SyMemBackendPoolFree(&pVm->sAllocator, pValue); + } + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_to_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_to_int(jx9_value *pValue) +{ + int rc; + rc = jx9MemObjToInteger(pValue); + if( rc != JX9_OK ){ + return 0; + } + return (int)pValue->x.iVal; +} +/* + * [CAPIREF: jx9_value_to_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_to_bool(jx9_value *pValue) +{ + int rc; + rc = jx9MemObjToBool(pValue); + if( rc != JX9_OK ){ + return 0; + } + return (int)pValue->x.iVal; +} +/* + * [CAPIREF: jx9_value_to_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE jx9_int64 jx9_value_to_int64(jx9_value *pValue) +{ + int rc; + rc = jx9MemObjToInteger(pValue); + if( rc != JX9_OK ){ + return 0; + } + return pValue->x.iVal; +} +/* + * [CAPIREF: jx9_value_to_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE double jx9_value_to_double(jx9_value *pValue) +{ + int rc; + rc = jx9MemObjToReal(pValue); + if( rc != JX9_OK ){ + return (double)0; + } + return (double)pValue->x.rVal; +} +/* + * [CAPIREF: jx9_value_to_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE const char * jx9_value_to_string(jx9_value *pValue, int *pLen) +{ + jx9MemObjToString(pValue); + if( SyBlobLength(&pValue->sBlob) > 0 ){ + SyBlobNullAppend(&pValue->sBlob); + if( pLen ){ + *pLen = (int)SyBlobLength(&pValue->sBlob); + } + return (const char *)SyBlobData(&pValue->sBlob); + }else{ + /* Return the empty string */ + if( pLen ){ + *pLen = 0; + } + return ""; + } +} +/* + * [CAPIREF: jx9_value_to_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void * jx9_value_to_resource(jx9_value *pValue) +{ + if( (pValue->iFlags & MEMOBJ_RES) == 0 ){ + /* Not a resource, return NULL */ + return 0; + } + return pValue->x.pOther; +} +/* + * [CAPIREF: jx9_value_compare()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_compare(jx9_value *pLeft, jx9_value *pRight, int bStrict) +{ + int rc; + if( pLeft == 0 || pRight == 0 ){ + /* TICKET 1433-24: NULL values is harmless operation */ + return 1; + } + /* Perform the comparison */ + rc = jx9MemObjCmp(&(*pLeft), &(*pRight), bStrict, 0); + /* Comparison result */ + return rc; +} +/* + * [CAPIREF: jx9_result_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_int(jx9_context *pCtx, int iValue) +{ + return jx9_value_int(pCtx->pRet, iValue); +} +/* + * [CAPIREF: jx9_result_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_int64(jx9_context *pCtx, jx9_int64 iValue) +{ + return jx9_value_int64(pCtx->pRet, iValue); +} +/* + * [CAPIREF: jx9_result_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_bool(jx9_context *pCtx, int iBool) +{ + return jx9_value_bool(pCtx->pRet, iBool); +} +/* + * [CAPIREF: jx9_result_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_double(jx9_context *pCtx, double Value) +{ + return jx9_value_double(pCtx->pRet, Value); +} +/* + * [CAPIREF: jx9_result_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_null(jx9_context *pCtx) +{ + /* Invalidate any prior representation and set the NULL flag */ + jx9MemObjRelease(pCtx->pRet); + return JX9_OK; +} +/* + * [CAPIREF: jx9_result_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_string(jx9_context *pCtx, const char *zString, int nLen) +{ + return jx9_value_string(pCtx->pRet, zString, nLen); +} +/* + * [CAPIREF: jx9_result_string_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_string_format(jx9_context *pCtx, const char *zFormat, ...) +{ + jx9_value *p; + va_list ap; + int rc; + p = pCtx->pRet; + if( (p->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + jx9MemObjRelease(p); + MemObjSetType(p, MEMOBJ_STRING); + } + /* Format the given string */ + va_start(ap, zFormat); + rc = SyBlobFormatAp(&p->sBlob, zFormat, ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: jx9_result_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_value(jx9_context *pCtx, jx9_value *pValue) +{ + int rc = JX9_OK; + if( pValue == 0 ){ + jx9MemObjRelease(pCtx->pRet); + }else{ + rc = jx9MemObjStore(pValue, pCtx->pRet); + } + return rc; +} +/* + * [CAPIREF: jx9_result_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_result_resource(jx9_context *pCtx, void *pUserData) +{ + return jx9_value_resource(pCtx->pRet, pUserData); +} +/* + * [CAPIREF: jx9_context_new_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE jx9_value * jx9_context_new_scalar(jx9_context *pCtx) +{ + jx9_value *pVal; + pVal = jx9_new_scalar(pCtx->pVm); + if( pVal ){ + /* Record value address so it can be freed automatically + * when the calling function returns. + */ + SySetPut(&pCtx->sVar, (const void *)&pVal); + } + return pVal; +} +/* + * [CAPIREF: jx9_context_new_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE jx9_value * jx9_context_new_array(jx9_context *pCtx) +{ + jx9_value *pVal; + pVal = jx9_new_array(pCtx->pVm); + if( pVal ){ + /* Record value address so it can be freed automatically + * when the calling function returns. + */ + SySetPut(&pCtx->sVar, (const void *)&pVal); + } + return pVal; +} +/* + * [CAPIREF: jx9_context_release_value()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void jx9_context_release_value(jx9_context *pCtx, jx9_value *pValue) +{ + jx9VmReleaseContextValue(&(*pCtx), pValue); +} +/* + * [CAPIREF: jx9_context_alloc_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void * jx9_context_alloc_chunk(jx9_context *pCtx, unsigned int nByte, int ZeroChunk, int AutoRelease) +{ + void *pChunk; + pChunk = SyMemBackendAlloc(&pCtx->pVm->sAllocator, nByte); + if( pChunk ){ + if( ZeroChunk ){ + /* Zero the memory chunk */ + SyZero(pChunk, nByte); + } + if( AutoRelease ){ + jx9_aux_data sAux; + /* Track the chunk so that it can be released automatically + * upon this context is destroyed. + */ + sAux.pAuxData = pChunk; + SySetPut(&pCtx->sChunk, (const void *)&sAux); + } + } + return pChunk; +} +/* + * Check if the given chunk address is registered in the call context + * chunk container. + * Return TRUE if registered.FALSE otherwise. + * Refer to [jx9_context_realloc_chunk(), jx9_context_free_chunk()]. + */ +static jx9_aux_data * ContextFindChunk(jx9_context *pCtx, void *pChunk) +{ + jx9_aux_data *aAux, *pAux; + sxu32 n; + if( SySetUsed(&pCtx->sChunk) < 1 ){ + /* Don't bother processing, the container is empty */ + return 0; + } + /* Perform the lookup */ + aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk); + for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ + pAux = &aAux[n]; + if( pAux->pAuxData == pChunk ){ + /* Chunk found */ + return pAux; + } + } + /* No such allocated chunk */ + return 0; +} +/* + * [CAPIREF: jx9_context_realloc_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void * jx9_context_realloc_chunk(jx9_context *pCtx, void *pChunk, unsigned int nByte) +{ + jx9_aux_data *pAux; + void *pNew; + pNew = SyMemBackendRealloc(&pCtx->pVm->sAllocator, pChunk, nByte); + if( pNew ){ + pAux = ContextFindChunk(pCtx, pChunk); + if( pAux ){ + pAux->pAuxData = pNew; + } + } + return pNew; +} +/* + * [CAPIREF: jx9_context_free_chunk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void jx9_context_free_chunk(jx9_context *pCtx, void *pChunk) +{ + jx9_aux_data *pAux; + if( pChunk == 0 ){ + /* TICKET-1433-93: NULL chunk is a harmless operation */ + return; + } + pAux = ContextFindChunk(pCtx, pChunk); + if( pAux ){ + /* Mark as destroyed */ + pAux->pAuxData = 0; + } + SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk); +} +/* + * [CAPIREF: jx9_array_fetch()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE jx9_value * jx9_array_fetch(jx9_value *pArray, const char *zKey, int nByte) +{ + jx9_hashmap_node *pNode; + jx9_value *pValue; + jx9_value skey; + int rc; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return 0; + } + if( nByte < 0 ){ + nByte = (int)SyStrlen(zKey); + } + /* Convert the key to a jx9_value */ + jx9MemObjInit(pArray->pVm, &skey); + jx9MemObjStringAppend(&skey, zKey, (sxu32)nByte); + /* Perform the lookup */ + rc = jx9HashmapLookup((jx9_hashmap *)pArray->x.pOther, &skey, &pNode); + jx9MemObjRelease(&skey); + if( rc != JX9_OK ){ + /* No such entry */ + return 0; + } + /* Extract the target value */ + pValue = (jx9_value *)SySetAt(&pArray->pVm->aMemObj, pNode->nValIdx); + return pValue; +} +/* + * [CAPIREF: jx9_array_walk()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_array_walk(jx9_value *pArray, int (*xWalk)(jx9_value *pValue, jx9_value *, void *), void *pUserData) +{ + int rc; + if( xWalk == 0 ){ + return JX9_CORRUPT; + } + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return JX9_CORRUPT; + } + /* Start the walk process */ + rc = jx9HashmapWalk((jx9_hashmap *)pArray->x.pOther, xWalk, pUserData); + return rc != JX9_OK ? JX9_ABORT /* User callback request an operation abort*/ : JX9_OK; +} +/* + * [CAPIREF: jx9_array_add_elem()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_array_add_elem(jx9_value *pArray, jx9_value *pKey, jx9_value *pValue) +{ + int rc; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return JX9_CORRUPT; + } + /* Perform the insertion */ + rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &(*pKey), &(*pValue)); + return rc; +} +/* + * [CAPIREF: jx9_array_add_strkey_elem()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_array_add_strkey_elem(jx9_value *pArray, const char *zKey, jx9_value *pValue) +{ + int rc; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return JX9_CORRUPT; + } + /* Perform the insertion */ + if( SX_EMPTY_STR(zKey) ){ + /* Empty key, assign an automatic index */ + rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, 0, &(*pValue)); + }else{ + jx9_value sKey; + jx9MemObjInitFromString(pArray->pVm, &sKey, 0); + jx9MemObjStringAppend(&sKey, zKey, (sxu32)SyStrlen(zKey)); + rc = jx9HashmapInsert((jx9_hashmap *)pArray->x.pOther, &sKey, &(*pValue)); + jx9MemObjRelease(&sKey); + } + return rc; +} +/* + * [CAPIREF: jx9_array_count()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE unsigned int jx9_array_count(jx9_value *pArray) +{ + jx9_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( (pArray->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return 0; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)pArray->x.pOther; + return pMap->nEntry; +} +/* + * [CAPIREF: jx9_context_output()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_context_output(jx9_context *pCtx, const char *zString, int nLen) +{ + SyString sData; + int rc; + if( nLen < 0 ){ + nLen = (int)SyStrlen(zString); + } + SyStringInitFromBuf(&sData, zString, nLen); + rc = jx9VmOutputConsume(pCtx->pVm, &sData); + return rc; +} +/* + * [CAPIREF: jx9_context_throw_error()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_context_throw_error(jx9_context *pCtx, int iErr, const char *zErr) +{ + int rc = JX9_OK; + if( zErr ){ + rc = jx9VmThrowError(pCtx->pVm, &pCtx->pFunc->sName, iErr, zErr); + } + return rc; +} +/* + * [CAPIREF: jx9_context_throw_error_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_context_throw_error_format(jx9_context *pCtx, int iErr, const char *zFormat, ...) +{ + va_list ap; + int rc; + if( zFormat == 0){ + return JX9_OK; + } + va_start(ap, zFormat); + rc = jx9VmThrowErrorAp(pCtx->pVm, &pCtx->pFunc->sName, iErr, zFormat, ap); + va_end(ap); + return rc; +} +/* + * [CAPIREF: jx9_context_random_num()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE unsigned int jx9_context_random_num(jx9_context *pCtx) +{ + sxu32 n; + n = jx9VmRandomNum(pCtx->pVm); + return n; +} +/* + * [CAPIREF: jx9_context_random_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_context_random_string(jx9_context *pCtx, char *zBuf, int nBuflen) +{ + if( nBuflen < 3 ){ + return JX9_CORRUPT; + } + jx9VmRandomString(pCtx->pVm, zBuf, nBuflen); + return JX9_OK; +} +/* + * [CAPIREF: jx9_context_user_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void * jx9_context_user_data(jx9_context *pCtx) +{ + return pCtx->pFunc->pUserData; +} +/* + * [CAPIREF: jx9_context_push_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_context_push_aux_data(jx9_context *pCtx, void *pUserData) +{ + jx9_aux_data sAux; + int rc; + sAux.pAuxData = pUserData; + rc = SySetPut(&pCtx->pFunc->aAux, (const void *)&sAux); + return rc; +} +/* + * [CAPIREF: jx9_context_peek_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void * jx9_context_peek_aux_data(jx9_context *pCtx) +{ + jx9_aux_data *pAux; + pAux = (jx9_aux_data *)SySetPeek(&pCtx->pFunc->aAux); + return pAux ? pAux->pAuxData : 0; +} +/* + * [CAPIREF: jx9_context_pop_aux_data()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE void * jx9_context_pop_aux_data(jx9_context *pCtx) +{ + jx9_aux_data *pAux; + pAux = (jx9_aux_data *)SySetPop(&pCtx->pFunc->aAux); + return pAux ? pAux->pAuxData : 0; +} +/* + * [CAPIREF: jx9_context_result_buf_length()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE unsigned int jx9_context_result_buf_length(jx9_context *pCtx) +{ + return SyBlobLength(&pCtx->pRet->sBlob); +} +/* + * [CAPIREF: jx9_function_name()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE const char * jx9_function_name(jx9_context *pCtx) +{ + SyString *pName; + pName = &pCtx->pFunc->sName; + return pName->zString; +} +/* + * [CAPIREF: jx9_value_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_int(jx9_value *pVal, int iValue) +{ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + pVal->x.iVal = (jx9_int64)iValue; + MemObjSetType(pVal, MEMOBJ_INT); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_int64()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_int64(jx9_value *pVal, jx9_int64 iValue) +{ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + pVal->x.iVal = iValue; + MemObjSetType(pVal, MEMOBJ_INT); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_bool(jx9_value *pVal, int iBool) +{ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + pVal->x.iVal = iBool ? 1 : 0; + MemObjSetType(pVal, MEMOBJ_BOOL); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_null(jx9_value *pVal) +{ + /* Invalidate any prior representation and set the NULL flag */ + jx9MemObjRelease(pVal); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_double()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_double(jx9_value *pVal, double Value) +{ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + pVal->x.rVal = (jx9_real)Value; + MemObjSetType(pVal, MEMOBJ_REAL); + /* Try to get an integer representation also */ + jx9MemObjTryInteger(pVal); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_string(jx9_value *pVal, const char *zString, int nLen) +{ + if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + MemObjSetType(pVal, MEMOBJ_STRING); + } + if( zString ){ + if( nLen < 0 ){ + /* Compute length automatically */ + nLen = (int)SyStrlen(zString); + } + SyBlobAppend(&pVal->sBlob, (const void *)zString, (sxu32)nLen); + } + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_string_format()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_string_format(jx9_value *pVal, const char *zFormat, ...) +{ + va_list ap; + int rc; + if((pVal->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + MemObjSetType(pVal, MEMOBJ_STRING); + } + va_start(ap, zFormat); + rc = SyBlobFormatAp(&pVal->sBlob, zFormat, ap); + va_end(ap); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_reset_string_cursor()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_reset_string_cursor(jx9_value *pVal) +{ + /* Reset the string cursor */ + SyBlobReset(&pVal->sBlob); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_resource(jx9_value *pVal, void *pUserData) +{ + /* Invalidate any prior representation */ + jx9MemObjRelease(pVal); + /* Reflect the new type */ + pVal->x.pOther = pUserData; + MemObjSetType(pVal, MEMOBJ_RES); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_release()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_release(jx9_value *pVal) +{ + jx9MemObjRelease(pVal); + return JX9_OK; +} +/* + * [CAPIREF: jx9_value_is_int()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_int(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_INT) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_float()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_float(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_REAL) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_bool()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_bool(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_BOOL) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_string()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_string(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_STRING) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_null()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_null(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_NULL) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_numeric()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_numeric(jx9_value *pVal) +{ + int rc; + rc = jx9MemObjIsNumeric(pVal); + return rc; +} +/* + * [CAPIREF: jx9_value_is_callable()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_callable(jx9_value *pVal) +{ + int rc; + rc = jx9VmIsCallable(pVal->pVm, pVal); + return rc; +} +/* + * [CAPIREF: jx9_value_is_scalar()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_scalar(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_SCALAR) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_json_array()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_json_array(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_HASHMAP) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_json_object()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_json_object(jx9_value *pVal) +{ + jx9_hashmap *pMap; + if( (pVal->iFlags & MEMOBJ_HASHMAP) == 0 ){ + return FALSE; + } + pMap = (jx9_hashmap *)pVal->x.pOther; + if( (pMap->iFlags & HASHMAP_JSON_OBJECT) == 0 ){ + return FALSE; + } + return TRUE; +} +/* + * [CAPIREF: jx9_value_is_resource()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_resource(jx9_value *pVal) +{ + return (pVal->iFlags & MEMOBJ_RES) ? TRUE : FALSE; +} +/* + * [CAPIREF: jx9_value_is_empty()] + * Please refer to the official documentation for function purpose and expected parameters. + */ +JX9_PRIVATE int jx9_value_is_empty(jx9_value *pVal) +{ + int rc; + rc = jx9MemObjIsEmpty(pVal); + return rc; +} +/* + * ---------------------------------------------------------- + * File: jx9_builtin.c + * MD5: 97ae6ddf8ded9fe14634060675e12f80 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: builtin.c v1.7 Win7 2012-12-13 00:01 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file implement built-in 'foreign' functions for the JX9 engine */ +/* + * Section: + * Variable handling Functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * bool is_bool($var) + * Finds out whether a variable is a boolean. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is a boolean. False otherwise. + */ +static int jx9Builtin_is_bool(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_bool(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_float($var) + * bool is_real($var) + * bool is_double($var) + * Finds out whether a variable is a float. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is a float. False otherwise. + */ +static int jx9Builtin_is_float(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_float(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_int($var) + * bool is_integer($var) + * bool is_long($var) + * Finds out whether a variable is an integer. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is an integer. False otherwise. + */ +static int jx9Builtin_is_int(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_int(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_string($var) + * Finds out whether a variable is a string. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is string. False otherwise. + */ +static int jx9Builtin_is_string(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_string(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_null($var) + * Finds out whether a variable is NULL. + * Parameters + * $var: The variable being evaluated. + * Return + * TRUE if var is NULL. False otherwise. + */ +static int jx9Builtin_is_null(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_null(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_numeric($var) + * Find out whether a variable is NULL. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is numeric. False otherwise. + */ +static int jx9Builtin_is_numeric(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_numeric(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_scalar($var) + * Find out whether a variable is a scalar. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is scalar. False otherwise. + */ +static int jx9Builtin_is_scalar(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_scalar(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_array($var) + * Find out whether a variable is an array. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is an array. False otherwise. + */ +static int jx9Builtin_is_array(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_json_array(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_object($var) + * Find out whether a variable is an object. + * Parameters + * $var: The variable being evaluated. + * Return + * True if var is an object. False otherwise. + */ +static int jx9Builtin_is_object(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_json_object(apArg[0]); + } + /* Query result */ + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * bool is_resource($var) + * Find out whether a variable is a resource. + * Parameters + * $var: The variable being evaluated. + * Return + * True if a resource. False otherwise. + */ +static int jx9Builtin_is_resource(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 0; /* Assume false by default */ + if( nArg > 0 ){ + res = jx9_value_is_resource(apArg[0]); + } + jx9_result_bool(pCtx, res); + return JX9_OK; +} +/* + * float floatval($var) + * Get float value of a variable. + * Parameter + * $var: The variable being processed. + * Return + * the float value of a variable. + */ +static int jx9Builtin_floatval(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* return 0.0 */ + jx9_result_double(pCtx, 0); + }else{ + double dval; + /* Perform the cast */ + dval = jx9_value_to_double(apArg[0]); + jx9_result_double(pCtx, dval); + } + return JX9_OK; +} +/* + * int intval($var) + * Get integer value of a variable. + * Parameter + * $var: The variable being processed. + * Return + * the int value of a variable. + */ +static int jx9Builtin_intval(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* return 0 */ + jx9_result_int(pCtx, 0); + }else{ + sxi64 iVal; + /* Perform the cast */ + iVal = jx9_value_to_int64(apArg[0]); + jx9_result_int64(pCtx, iVal); + } + return JX9_OK; +} +/* + * string strval($var) + * Get the string representation of a variable. + * Parameter + * $var: The variable being processed. + * Return + * the string value of a variable. + */ +static int jx9Builtin_strval(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* return NULL */ + jx9_result_null(pCtx); + }else{ + const char *zVal; + int iLen = 0; /* cc -O6 warning */ + /* Perform the cast */ + zVal = jx9_value_to_string(apArg[0], &iLen); + jx9_result_string(pCtx, zVal, iLen); + } + return JX9_OK; +} +/* + * bool empty($var) + * Determine whether a variable is empty. + * Parameters + * $var: The variable being checked. + * Return + * 0 if var has a non-empty and non-zero value.1 otherwise. + */ +static int jx9Builtin_empty(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int res = 1; /* Assume empty by default */ + if( nArg > 0 ){ + res = jx9_value_is_empty(apArg[0]); + } + jx9_result_bool(pCtx, res); + return JX9_OK; + +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifdef JX9_ENABLE_MATH_FUNC +/* + * Section: + * Math Functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +#include /* abs */ +#include +/* + * float sqrt(float $arg ) + * Square root of the given number. + * Parameter + * The number to process. + * Return + * The square root of arg or the special value Nan of failure. + */ +static int jx9Builtin_sqrt(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = sqrt(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float exp(float $arg ) + * Calculates the exponent of e. + * Parameter + * The number to process. + * Return + * 'e' raised to the power of arg. + */ +static int jx9Builtin_exp(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = exp(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float floor(float $arg ) + * Round fractions down. + * Parameter + * The number to process. + * Return + * Returns the next lowest integer value by rounding down value if necessary. + */ +static int jx9Builtin_floor(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = floor(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float cos(float $arg ) + * Cosine. + * Parameter + * The number to process. + * Return + * The cosine of arg. + */ +static int jx9Builtin_cos(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = cos(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float acos(float $arg ) + * Arc cosine. + * Parameter + * The number to process. + * Return + * The arc cosine of arg. + */ +static int jx9Builtin_acos(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = acos(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float cosh(float $arg ) + * Hyperbolic cosine. + * Parameter + * The number to process. + * Return + * The hyperbolic cosine of arg. + */ +static int jx9Builtin_cosh(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = cosh(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float sin(float $arg ) + * Sine. + * Parameter + * The number to process. + * Return + * The sine of arg. + */ +static int jx9Builtin_sin(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = sin(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float asin(float $arg ) + * Arc sine. + * Parameter + * The number to process. + * Return + * The arc sine of arg. + */ +static int jx9Builtin_asin(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = asin(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float sinh(float $arg ) + * Hyperbolic sine. + * Parameter + * The number to process. + * Return + * The hyperbolic sine of arg. + */ +static int jx9Builtin_sinh(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = sinh(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float ceil(float $arg ) + * Round fractions up. + * Parameter + * The number to process. + * Return + * The next highest integer value by rounding up value if necessary. + */ +static int jx9Builtin_ceil(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = ceil(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float tan(float $arg ) + * Tangent. + * Parameter + * The number to process. + * Return + * The tangent of arg. + */ +static int jx9Builtin_tan(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = tan(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float atan(float $arg ) + * Arc tangent. + * Parameter + * The number to process. + * Return + * The arc tangent of arg. + */ +static int jx9Builtin_atan(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = atan(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float tanh(float $arg ) + * Hyperbolic tangent. + * Parameter + * The number to process. + * Return + * The Hyperbolic tangent of arg. + */ +static int jx9Builtin_tanh(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = tanh(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float atan2(float $y, float $x) + * Arc tangent of two variable. + * Parameter + * $y = Dividend parameter. + * $x = Divisor parameter. + * Return + * The arc tangent of y/x in radian. + */ +static int jx9Builtin_atan2(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x, y; + if( nArg < 2 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + y = jx9_value_to_double(apArg[0]); + x = jx9_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = atan2(y, x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float/int64 abs(float/int64 $arg ) + * Absolute value. + * Parameter + * The number to process. + * Return + * The absolute value of number. + */ +static int jx9Builtin_abs(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int is_float; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + is_float = jx9_value_is_float(apArg[0]); + if( is_float ){ + double r, x; + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = fabs(x); + jx9_result_double(pCtx, r); + }else{ + int r, x; + x = jx9_value_to_int(apArg[0]); + /* Perform the requested operation */ + r = abs(x); + jx9_result_int(pCtx, r); + } + return JX9_OK; +} +/* + * float log(float $arg, [int/float $base]) + * Natural logarithm. + * Parameter + * $arg: The number to process. + * $base: The optional logarithmic base to use. (only base-10 is supported) + * Return + * The logarithm of arg to base, if given, or the natural logarithm. + * Note: + * only Natural log and base-10 log are supported. + */ +static int jx9Builtin_log(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + if( nArg == 2 && jx9_value_is_numeric(apArg[1]) && jx9_value_to_int(apArg[1]) == 10 ){ + /* Base-10 log */ + r = log10(x); + }else{ + r = log(x); + } + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float log10(float $arg ) + * Base-10 logarithm. + * Parameter + * The number to process. + * Return + * The Base-10 logarithm of the given number. + */ +static int jx9Builtin_log10(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + /* Perform the requested operation */ + r = log10(x); + /* store the result back */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * number pow(number $base, number $exp) + * Exponential expression. + * Parameter + * base + * The base to use. + * exp + * The exponent. + * Return + * base raised to the power of exp. + * If the result can be represented as integer it will be returned + * as type integer, else it will be returned as type float. + */ +static int jx9Builtin_pow(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double r, x, y; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + x = jx9_value_to_double(apArg[0]); + y = jx9_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = pow(x, y); + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float pi(void) + * Returns an approximation of pi. + * Note + * you can use the M_PI constant which yields identical results to pi(). + * Return + * The value of pi as float. + */ +static int jx9Builtin_pi(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + jx9_result_double(pCtx, JX9_PI); + return JX9_OK; +} +/* + * float fmod(float $x, float $y) + * Returns the floating point remainder (modulo) of the division of the arguments. + * Parameters + * $x + * The dividend + * $y + * The divisor + * Return + * The floating point remainder of x/y. + */ +static int jx9Builtin_fmod(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double x, y, r; + if( nArg < 2 ){ + /* Missing arguments */ + jx9_result_double(pCtx, 0); + return JX9_OK; + } + /* Extract given arguments */ + x = jx9_value_to_double(apArg[0]); + y = jx9_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = fmod(x, y); + /* Processing result */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +/* + * float hypot(float $x, float $y) + * Calculate the length of the hypotenuse of a right-angle triangle . + * Parameters + * $x + * Length of first side + * $y + * Length of first side + * Return + * Calculated length of the hypotenuse. + */ +static int jx9Builtin_hypot(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + double x, y, r; + if( nArg < 2 ){ + /* Missing arguments */ + jx9_result_double(pCtx, 0); + return JX9_OK; + } + /* Extract given arguments */ + x = jx9_value_to_double(apArg[0]); + y = jx9_value_to_double(apArg[1]); + /* Perform the requested operation */ + r = hypot(x, y); + /* Processing result */ + jx9_result_double(pCtx, r); + return JX9_OK; +} +#endif /* JX9_ENABLE_MATH_FUNC */ +/* + * float round ( float $val [, int $precision = 0 [, int $mode = JX9_ROUND_HALF_UP ]] ) + * Exponential expression. + * Parameter + * $val + * The value to round. + * $precision + * The optional number of decimal digits to round to. + * $mode + * One of JX9_ROUND_HALF_UP, JX9_ROUND_HALF_DOWN, JX9_ROUND_HALF_EVEN, or JX9_ROUND_HALF_ODD. + * (not supported). + * Return + * The rounded value. + */ +static int jx9Builtin_round(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int n = 0; + double r; + if( nArg < 1 ){ + /* Missing argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the precision if available */ + if( nArg > 1 ){ + n = jx9_value_to_int(apArg[1]); + if( n>30 ){ + n = 30; + } + if( n<0 ){ + n = 0; + } + } + r = jx9_value_to_double(apArg[0]); + /* If Y==0 and X will fit in a 64-bit int, + * handle the rounding directly.Otherwise + * use our own cutsom printf [i.e:SyBufferFormat()]. + */ + if( n==0 && r>=0 && r= 0xc0 ){ + /* UTF-8 stream */ + zString++; + while( zString < zEnd && (((unsigned char)zString[0] & 0xc0) == 0x80) ){ + zString++; + } + }else{ + if( SyisHex(zString[0]) ){ + break; + } + /* Ignore */ + zString++; + } + } + if( zString < zEnd ){ + /* Cast */ + SyHexStrToInt64(zString, (sxu32)(zEnd-zString), (void *)&iVal, 0); + } + }else{ + /* Extract as a 64-bit integer */ + iVal = jx9_value_to_int64(apArg[0]); + } + /* Return the number */ + jx9_result_int64(pCtx, iVal); + return JX9_OK; +} +/* + * int64 bindec(string $bin_string) + * Binary to decimal. + * Parameters + * $bin_string + * The binary string to convert + * Return + * Returns the decimal equivalent of the binary number represented by the binary_string argument. + */ +static int jx9Builtin_bindec(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString; + jx9_int64 iVal; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return -1 */ + jx9_result_int(pCtx, -1); + return JX9_OK; + } + iVal = 0; + if( jx9_value_is_string(apArg[0]) ){ + /* Extract the given string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen > 0 ){ + /* Perform a binary cast */ + SyBinaryStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0); + } + }else{ + /* Extract as a 64-bit integer */ + iVal = jx9_value_to_int64(apArg[0]); + } + /* Return the number */ + jx9_result_int64(pCtx, iVal); + return JX9_OK; +} +/* + * int64 octdec(string $oct_string) + * Octal to decimal. + * Parameters + * $oct_string + * The octal string to convert + * Return + * Returns the decimal equivalent of the octal number represented by the octal_string argument. + */ +static int jx9Builtin_octdec(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString; + jx9_int64 iVal; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return -1 */ + jx9_result_int(pCtx, -1); + return JX9_OK; + } + iVal = 0; + if( jx9_value_is_string(apArg[0]) ){ + /* Extract the given string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen > 0 ){ + /* Perform the cast */ + SyOctalStrToInt64(zString, (sxu32)nLen, (void *)&iVal, 0); + } + }else{ + /* Extract as a 64-bit integer */ + iVal = jx9_value_to_int64(apArg[0]); + } + /* Return the number */ + jx9_result_int64(pCtx, iVal); + return JX9_OK; +} +/* + * string base_convert(string $number, int $frombase, int $tobase) + * Convert a number between arbitrary bases. + * Parameters + * $number + * The number to convert + * $frombase + * The base number is in + * $tobase + * The base to convert number to + * Return + * Number converted to base tobase + */ +static int jx9Builtin_base_convert(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int nLen, iFbase, iTobase; + const char *zNum; + jx9_int64 iNum; + if( nArg < 3 ){ + /* Return the empty string*/ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Base numbers */ + iFbase = jx9_value_to_int(apArg[1]); + iTobase = jx9_value_to_int(apArg[2]); + if( jx9_value_is_string(apArg[0]) ){ + /* Extract the target number */ + zNum = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Return the empty string*/ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Base conversion */ + switch(iFbase){ + case 16: + /* Hex */ + SyHexStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); + break; + case 8: + /* Octal */ + SyOctalStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); + break; + case 2: + /* Binary */ + SyBinaryStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); + break; + default: + /* Decimal */ + SyStrToInt64(zNum, (sxu32)nLen, (void *)&iNum, 0); + break; + } + }else{ + iNum = jx9_value_to_int64(apArg[0]); + } + switch(iTobase){ + case 16: + /* Hex */ + jx9_result_string_format(pCtx, "%qx", iNum); /* Quad hex */ + break; + case 8: + /* Octal */ + jx9_result_string_format(pCtx, "%qo", iNum); /* Quad octal */ + break; + case 2: + /* Binary */ + jx9_result_string_format(pCtx, "%qB", iNum); /* Quad binary */ + break; + default: + /* Decimal */ + jx9_result_string_format(pCtx, "%qd", iNum); /* Quad decimal */ + break; + } + return JX9_OK; +} +/* + * Section: + * String handling Functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * string substr(string $string, int $start[, int $length ]) + * Return part of a string. + * Parameters + * $string + * The input string. Must be one character or longer. + * $start + * If start is non-negative, the returned string will start at the start'th position + * in string, counting from zero. For instance, in the string 'abcdef', the character + * at position 0 is 'a', the character at position 2 is 'c', and so forth. + * If start is negative, the returned string will start at the start'th character + * from the end of string. + * If string is less than or equal to start characters long, FALSE will be returned. + * $length + * If length is given and is positive, the string returned will contain at most length + * characters beginning from start (depending on the length of string). + * If length is given and is negative, then that many characters will be omitted from + * the end of string (after the start position has been calculated when a start is negative). + * If start denotes the position of this truncation or beyond, false will be returned. + * If length is given and is 0, FALSE or NULL an empty string will be returned. + * If length is omitted, the substring starting from start until the end of the string + * will be returned. + * Return + * Returns the extracted part of string, or FALSE on failure or an empty string. + */ +static int jx9Builtin_substr(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zSource, *zOfft; + int nOfft, nLen, nSrcLen; + if( nArg < 2 ){ + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zSource = jx9_value_to_string(apArg[0], &nSrcLen); + if( nSrcLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = nSrcLen; /* cc warning */ + /* Extract the offset */ + nOfft = jx9_value_to_int(apArg[1]); + if( nOfft < 0 ){ + zOfft = &zSource[nSrcLen+nOfft]; + if( zOfft < zSource ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = (int)(&zSource[nSrcLen]-zOfft); + nOfft = (int)(zOfft-zSource); + }else if( nOfft >= nSrcLen ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + }else{ + zOfft = &zSource[nOfft]; + nLen = nSrcLen - nOfft; + } + if( nArg > 2 ){ + /* Extract the length */ + nLen = jx9_value_to_int(apArg[2]); + if( nLen == 0 ){ + /* Invalid length, return an empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + }else if( nLen < 0 ){ + nLen = nSrcLen + nLen - nOfft; + if( nLen < 1 ){ + /* Invalid length */ + nLen = nSrcLen - nOfft; + } + } + if( nLen + nOfft > nSrcLen ){ + /* Invalid length */ + nLen = nSrcLen - nOfft; + } + } + /* Return the substring */ + jx9_result_string(pCtx, zOfft, nLen); + return JX9_OK; +} +/* + * int substr_compare(string $main_str, string $str , int $offset[, int $length[, bool $case_insensitivity = false ]]) + * Binary safe comparison of two strings from an offset, up to length characters. + * Parameters + * $main_str + * The main string being compared. + * $str + * The secondary string being compared. + * $offset + * The start position for the comparison. If negative, it starts counting from + * the end of the string. + * $length + * The length of the comparison. The default value is the largest of the length + * of the str compared to the length of main_str less the offset. + * $case_insensitivity + * If case_insensitivity is TRUE, comparison is case insensitive. + * Return + * Returns < 0 if main_str from position offset is less than str, > 0 if it is greater than + * str, and 0 if they are equal. If offset is equal to or greater than the length of main_str + * or length is set and is less than 1, substr_compare() prints a warning and returns FALSE. + */ +static int jx9Builtin_substr_compare(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zSource, *zOfft, *zSub; + int nOfft, nLen, nSrcLen, nSublen; + int iCase = 0; + int rc; + if( nArg < 3 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zSource = jx9_value_to_string(apArg[0], &nSrcLen); + if( nSrcLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = nSrcLen; /* cc warning */ + /* Extract the substring */ + zSub = jx9_value_to_string(apArg[1], &nSublen); + if( nSublen < 1 || nSublen > nSrcLen){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the offset */ + nOfft = jx9_value_to_int(apArg[2]); + if( nOfft < 0 ){ + zOfft = &zSource[nSrcLen+nOfft]; + if( zOfft < zSource ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = (int)(&zSource[nSrcLen]-zOfft); + nOfft = (int)(zOfft-zSource); + }else if( nOfft >= nSrcLen ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + }else{ + zOfft = &zSource[nOfft]; + nLen = nSrcLen - nOfft; + } + if( nArg > 3 ){ + /* Extract the length */ + nLen = jx9_value_to_int(apArg[3]); + if( nLen < 1 ){ + /* Invalid length */ + jx9_result_int(pCtx, 1); + return JX9_OK; + }else if( nLen + nOfft > nSrcLen ){ + /* Invalid length */ + nLen = nSrcLen - nOfft; + } + if( nArg > 4 ){ + /* Case-sensitive or not */ + iCase = jx9_value_to_bool(apArg[4]); + } + } + /* Perform the comparison */ + if( iCase ){ + rc = SyStrnicmp(zOfft, zSub, (sxu32)nLen); + }else{ + rc = SyStrncmp(zOfft, zSub, (sxu32)nLen); + } + /* Comparison result */ + jx9_result_int(pCtx, rc); + return JX9_OK; +} +/* + * int substr_count(string $haystack, string $needle[, int $offset = 0 [, int $length ]]) + * Count the number of substring occurrences. + * Parameters + * $haystack + * The string to search in + * $needle + * The substring to search for + * $offset + * The offset where to start counting + * $length (NOT USED) + * The maximum length after the specified offset to search for the substring. + * It outputs a warning if the offset plus the length is greater than the haystack length. + * Return + * Toral number of substring occurrences. + */ +static int jx9Builtin_substr_count(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zText, *zPattern, *zEnd; + int nTextlen, nPatlen; + int iCount = 0; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the haystack */ + zText = jx9_value_to_string(apArg[0], &nTextlen); + /* Point to the neddle */ + zPattern = jx9_value_to_string(apArg[1], &nPatlen); + if( nTextlen < 1 || nPatlen < 1 || nPatlen > nTextlen ){ + /* NOOP, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( nArg > 2 ){ + int nOfft; + /* Extract the offset */ + nOfft = jx9_value_to_int(apArg[2]); + if( nOfft < 0 || nOfft > nTextlen ){ + /* Invalid offset, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the desired offset */ + zText = &zText[nOfft]; + /* Adjust length */ + nTextlen -= nOfft; + } + /* Point to the end of the string */ + zEnd = &zText[nTextlen]; + if( nArg > 3 ){ + int nLen; + /* Extract the length */ + nLen = jx9_value_to_int(apArg[3]); + if( nLen < 0 || nLen > nTextlen ){ + /* Invalid length, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Adjust pointer */ + nTextlen = nLen; + zEnd = &zText[nTextlen]; + } + /* Perform the search */ + for(;;){ + rc = SyBlobSearch((const void *)zText, (sxu32)(zEnd-zText), (const void *)zPattern, nPatlen, &nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found, break immediately */ + break; + } + /* Increment counter and update the offset */ + iCount++; + zText += nOfft + nPatlen; + if( zText >= zEnd ){ + break; + } + } + /* Pattern count */ + jx9_result_int(pCtx, iCount); + return JX9_OK; +} +/* + * string chunk_split(string $body[, int $chunklen = 76 [, string $end = "\r\n" ]]) + * Split a string into smaller chunks. + * Parameters + * $body + * The string to be chunked. + * $chunklen + * The chunk length. + * $end + * The line ending sequence. + * Return + * The chunked string or NULL on failure. + */ +static int jx9Builtin_chunk_split(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn, *zEnd, *zSep = "\r\n"; + int nSepLen, nChunkLen, nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Nothing to split, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* initialize/Extract arguments */ + nSepLen = (int)sizeof("\r\n") - 1; + nChunkLen = 76; + zIn = jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nArg > 1 ){ + /* Chunk length */ + nChunkLen = jx9_value_to_int(apArg[1]); + if( nChunkLen < 1 ){ + /* Switch back to the default length */ + nChunkLen = 76; + } + if( nArg > 2 ){ + /* Separator */ + zSep = jx9_value_to_string(apArg[2], &nSepLen); + if( nSepLen < 1 ){ + /* Switch back to the default separator */ + zSep = "\r\n"; + nSepLen = (int)sizeof("\r\n") - 1; + } + } + } + /* Perform the requested operation */ + if( nChunkLen > nLen ){ + /* Nothing to split, return the string and the separator */ + jx9_result_string_format(pCtx, "%.*s%.*s", nLen, zIn, nSepLen, zSep); + return JX9_OK; + } + while( zIn < zEnd ){ + if( nChunkLen > (int)(zEnd-zIn) ){ + nChunkLen = (int)(zEnd - zIn); + } + /* Append the chunk and the separator */ + jx9_result_string_format(pCtx, "%.*s%.*s", nChunkLen, zIn, nSepLen, zSep); + /* Point beyond the chunk */ + zIn += nChunkLen; + } + return JX9_OK; +} +/* + * string htmlspecialchars(string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $charset]]) + * HTML escaping of special characters. + * The translations performed are: + * '&' (ampersand) ==> '&' + * '"' (double quote) ==> '"' when ENT_NOQUOTES is not set. + * "'" (single quote) ==> ''' only when ENT_QUOTES is set. + * '<' (less than) ==> '<' + * '>' (greater than) ==> '>' + * Parameters + * $string + * The string being converted. + * $flags + * A bitmask of one or more of the following flags, which specify how to handle quotes. + * The default is ENT_COMPAT | ENT_HTML401. + * ENT_COMPAT Will convert double-quotes and leave single-quotes alone. + * ENT_QUOTES Will convert both double and single quotes. + * ENT_NOQUOTES Will leave both double and single quotes unconverted. + * ENT_IGNORE Silently discard invalid code unit sequences instead of returning an empty string. + * $charset + * Defines character set used in conversion. The default character set is ISO-8859-1. (Not used) + * Return + * The escaped string or NULL on failure. + */ +static int jx9Builtin_htmlspecialchars(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zCur, *zIn, *zEnd; + int iFlags = 0x01|0x40; /* ENT_COMPAT | ENT_HTML401 */ + int nLen, c; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = jx9_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01|0x40; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '&' && zIn[0] != '\'' && zIn[0] != '"' && zIn[0] != '<' && zIn[0] != '>' ){ + zIn++; + } + if( zCur < zIn ){ + /* Append the raw string verbatim */ + jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); + } + if( zIn >= zEnd ){ + break; + } + c = zIn[0]; + if( c == '&' ){ + /* Expand '&' */ + jx9_result_string(pCtx, "&", (int)sizeof("&")-1); + }else if( c == '<' ){ + /* Expand '<' */ + jx9_result_string(pCtx, "<", (int)sizeof("<")-1); + }else if( c == '>' ){ + /* Expand '>' */ + jx9_result_string(pCtx, ">", (int)sizeof(">")-1); + }else if( c == '\'' ){ + if( iFlags & 0x02 /*ENT_QUOTES*/ ){ + /* Expand ''' */ + jx9_result_string(pCtx, "'", (int)sizeof("'")-1); + }else{ + /* Leave the single quote untouched */ + jx9_result_string(pCtx, "'", (int)sizeof(char)); + } + }else if( c == '"' ){ + if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ + /* Expand '"' */ + jx9_result_string(pCtx, """, (int)sizeof(""")-1); + }else{ + /* Leave the double quote untouched */ + jx9_result_string(pCtx, "\"", (int)sizeof(char)); + } + } + /* Ignore the unsafe HTML character */ + zIn++; + } + return JX9_OK; +} +/* + * string htmlspecialchars_decode(string $string[, int $quote_style = ENT_COMPAT ]) + * Unescape HTML entities. + * Parameters + * $string + * The string to decode + * $quote_style + * The quote style. One of the following constants: + * ENT_COMPAT Will convert double-quotes and leave single-quotes alone (default) + * ENT_QUOTES Will convert both double and single quotes + * ENT_NOQUOTES Will leave both double and single quotes unconverted + * Return + * The unescaped string or NULL on failure. + */ +static int jx9Builtin_htmlspecialchars_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zCur, *zIn, *zEnd; + int iFlags = 0x01; /* ENT_COMPAT */ + int nLen, nJump; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = jx9_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '&' ){ + zIn++; + } + if( zCur < zIn ){ + /* Append the raw string verbatim */ + jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); + } + nLen = (int)(zEnd-zIn); + nJump = (int)sizeof(char); + if( nLen >= (int)sizeof("&")-1 && SyStrnicmp(zIn, "&", sizeof("&")-1) == 0 ){ + /* & ==> '&' */ + jx9_result_string(pCtx, "&", (int)sizeof(char)); + nJump = (int)sizeof("&")-1; + }else if( nLen >= (int)sizeof("<")-1 && SyStrnicmp(zIn, "<", sizeof("<")-1) == 0 ){ + /* < ==> < */ + jx9_result_string(pCtx, "<", (int)sizeof(char)); + nJump = (int)sizeof("<")-1; + }else if( nLen >= (int)sizeof(">")-1 && SyStrnicmp(zIn, ">", sizeof(">")-1) == 0 ){ + /* > ==> '>' */ + jx9_result_string(pCtx, ">", (int)sizeof(char)); + nJump = (int)sizeof(">")-1; + }else if( nLen >= (int)sizeof(""")-1 && SyStrnicmp(zIn, """, sizeof(""")-1) == 0 ){ + /* " ==> '"' */ + if( (iFlags & 0x04) == 0 /*ENT_NOQUOTES*/ ){ + jx9_result_string(pCtx, "\"", (int)sizeof(char)); + }else{ + /* Leave untouched */ + jx9_result_string(pCtx, """, (int)sizeof(""")-1); + } + nJump = (int)sizeof(""")-1; + }else if( nLen >= (int)sizeof("'")-1 && SyStrnicmp(zIn, "'", sizeof("'")-1) == 0 ){ + /* ' ==> ''' */ + if( iFlags & 0x02 /*ENT_QUOTES*/ ){ + /* Expand ''' */ + jx9_result_string(pCtx, "'", (int)sizeof(char)); + }else{ + /* Leave untouched */ + jx9_result_string(pCtx, "'", (int)sizeof("'")-1); + } + nJump = (int)sizeof("'")-1; + }else if( nLen >= (int)sizeof(char) ){ + /* expand '&' */ + jx9_result_string(pCtx, "&", (int)sizeof(char)); + }else{ + /* No more input to process */ + break; + } + zIn += nJump; + } + return JX9_OK; +} +/* HTML encoding/Decoding table + * Source: Symisc RunTime API.[chm@symisc.net] + */ +static const char *azHtmlEscape[] = { + "<", "<", ">", ">", "&", "&", """, "\"", "'", "'", + "!", "!", "$", "$", "#", "#", "%", "%", "(", "(", + ")", ")", "{", "{", "}", "}", "=", "=", "+", "+", + "?", "?", "[", "[", "]", "]", "@", "@", ",", "," + }; +/* + * array get_html_translation_table(void) + * Returns the translation table used by htmlspecialchars() and htmlentities(). + * Parameters + * None + * Return + * The translation table as an array or NULL on failure. + */ +static int jx9Builtin_get_html_translation_table(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray, *pValue; + sxu32 n; + /* Element value */ + pValue = jx9_context_new_scalar(pCtx); + if( pValue == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make the table */ + for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ + /* Prepare the value */ + jx9_value_string(pValue, azHtmlEscape[n], -1 /* Compute length automatically */); + /* Insert the value */ + jx9_array_add_strkey_elem(pArray, azHtmlEscape[n+1], pValue); + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + } + /* + * Return the array. + * Don't worry about freeing memory, everything will be automatically + * released upon we return from this function. + */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * string htmlentities( string $string [, int $flags = ENT_COMPAT | ENT_HTML401]); + * Convert all applicable characters to HTML entities + * Parameters + * $string + * The input string. + * $flags + * A bitmask of one or more of the flags (see block-comment on jx9Builtin_htmlspecialchars()) + * Return + * The encoded string. + */ +static int jx9Builtin_htmlentities(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int iFlags = 0x01; /* ENT_COMPAT */ + const char *zIn, *zEnd; + int nLen, c; + sxu32 n; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = jx9_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + c = zIn[0]; + /* Perform a linear lookup on the decoding table */ + for( n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ + if( azHtmlEscape[n+1][0] == c ){ + /* Got one */ + break; + } + } + if( n < SX_ARRAYSIZE(azHtmlEscape) ){ + /* Output the safe sequence [i.e: '<' ==> '<"] */ + if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ + /* Expand the double quote verbatim */ + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + }else if(c == '\'' && ((iFlags & 0x02 /*ENT_QUOTES*/) == 0 || (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ + /* expand single quote verbatim */ + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + }else{ + jx9_result_string(pCtx, azHtmlEscape[n], -1/*Compute length automatically */); + } + }else{ + /* Output character verbatim */ + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + } + zIn++; + } + return JX9_OK; +} +/* + * string html_entity_decode(string $string [, int $quote_style = ENT_COMPAT [, string $charset = 'UTF-8' ]]) + * Perform the reverse operation of html_entity_decode(). + * Parameters + * $string + * The input string. + * $flags + * A bitmask of one or more of the flags (see comment on jx9Builtin_htmlspecialchars()) + * Return + * The decoded string. + */ +static int jx9Builtin_html_entity_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zCur, *zIn, *zEnd; + int iFlags = 0x01; /* ENT_COMPAT */ + int nLen; + sxu32 n; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + /* Extract the flags if available */ + if( nArg > 1 ){ + iFlags = jx9_value_to_int(apArg[1]); + if( iFlags < 0 ){ + iFlags = 0x01; + } + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '&' ){ + zIn++; + } + if( zCur < zIn ){ + /* Append raw string verbatim */ + jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); + } + if( zIn >= zEnd ){ + break; + } + nLen = (int)(zEnd-zIn); + /* Find an encoded sequence */ + for(n = 0 ; n < SX_ARRAYSIZE(azHtmlEscape) ; n += 2 ){ + int iLen = (int)SyStrlen(azHtmlEscape[n]); + if( nLen >= iLen && SyStrnicmp(zIn, azHtmlEscape[n], (sxu32)iLen) == 0 ){ + /* Got one */ + zIn += iLen; + break; + } + } + if( n < SX_ARRAYSIZE(azHtmlEscape) ){ + int c = azHtmlEscape[n+1][0]; + /* Output the decoded character */ + if( c == '\'' && ((iFlags & 0x02) == 0 /*ENT_QUOTES*/|| (iFlags & 0x04) /*ENT_NOQUOTES*/) ){ + /* Do not process single quotes */ + jx9_result_string(pCtx, azHtmlEscape[n], -1); + }else if( c == '"' && (iFlags & 0x04) /*ENT_NOQUOTES*/ ){ + /* Do not process double quotes */ + jx9_result_string(pCtx, azHtmlEscape[n], -1); + }else{ + jx9_result_string(pCtx, azHtmlEscape[n+1], -1); /* Compute length automatically */ + } + }else{ + /* Append '&' */ + jx9_result_string(pCtx, "&", (int)sizeof(char)); + zIn++; + } + } + return JX9_OK; +} +/* + * int strlen($string) + * return the length of the given string. + * Parameter + * string: The string being measured for length. + * Return + * length of the given string. + */ +static int jx9Builtin_strlen(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int iLen = 0; + if( nArg > 0 ){ + jx9_value_to_string(apArg[0], &iLen); + } + /* String length */ + jx9_result_int(pCtx, iLen); + return JX9_OK; +} +/* + * int strcmp(string $str1, string $str2) + * Perform a binary safe string comparison. + * Parameter + * str1: The first string + * str2: The second string + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int jx9Builtin_strcmp(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *z1, *z2; + int n1, n2; + int res; + if( nArg < 2 ){ + res = nArg == 0 ? 0 : 1; + jx9_result_int(pCtx, res); + return JX9_OK; + } + /* Perform the comparison */ + z1 = jx9_value_to_string(apArg[0], &n1); + z2 = jx9_value_to_string(apArg[1], &n2); + res = SyStrncmp(z1, z2, (sxu32)(SXMAX(n1, n2))); + /* Comparison result */ + jx9_result_int(pCtx, res); + return JX9_OK; +} +/* + * int strncmp(string $str1, string $str2, int n) + * Perform a binary safe string comparison of the first n characters. + * Parameter + * str1: The first string + * str2: The second string + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int jx9Builtin_strncmp(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *z1, *z2; + int res; + int n; + if( nArg < 3 ){ + /* Perform a standard comparison */ + return jx9Builtin_strcmp(pCtx, nArg, apArg); + } + /* Desired comparison length */ + n = jx9_value_to_int(apArg[2]); + if( n < 0 ){ + /* Invalid length */ + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Perform the comparison */ + z1 = jx9_value_to_string(apArg[0], 0); + z2 = jx9_value_to_string(apArg[1], 0); + res = SyStrncmp(z1, z2, (sxu32)n); + /* Comparison result */ + jx9_result_int(pCtx, res); + return JX9_OK; +} +/* + * int strcasecmp(string $str1, string $str2, int n) + * Perform a binary safe case-insensitive string comparison. + * Parameter + * str1: The first string + * str2: The second string + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int jx9Builtin_strcasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *z1, *z2; + int n1, n2; + int res; + if( nArg < 2 ){ + res = nArg == 0 ? 0 : 1; + jx9_result_int(pCtx, res); + return JX9_OK; + } + /* Perform the comparison */ + z1 = jx9_value_to_string(apArg[0], &n1); + z2 = jx9_value_to_string(apArg[1], &n2); + res = SyStrnicmp(z1, z2, (sxu32)(SXMAX(n1, n2))); + /* Comparison result */ + jx9_result_int(pCtx, res); + return JX9_OK; +} +/* + * int strncasecmp(string $str1, string $str2, int n) + * Perform a binary safe case-insensitive string comparison of the first n characters. + * Parameter + * $str1: The first string + * $str2: The second string + * $len: The length of strings to be used in the comparison. + * Return + * Returns < 0 if str1 is less than str2; > 0 if str1 is greater + * than str2, and 0 if they are equal. + */ +static int jx9Builtin_strncasecmp(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *z1, *z2; + int res; + int n; + if( nArg < 3 ){ + /* Perform a standard comparison */ + return jx9Builtin_strcasecmp(pCtx, nArg, apArg); + } + /* Desired comparison length */ + n = jx9_value_to_int(apArg[2]); + if( n < 0 ){ + /* Invalid length */ + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Perform the comparison */ + z1 = jx9_value_to_string(apArg[0], 0); + z2 = jx9_value_to_string(apArg[1], 0); + res = SyStrnicmp(z1, z2, (sxu32)n); + /* Comparison result */ + jx9_result_int(pCtx, res); + return JX9_OK; +} +/* + * Implode context [i.e: it's private data]. + * A pointer to the following structure is forwarded + * verbatim to the array walker callback defined below. + */ +struct implode_data { + jx9_context *pCtx; /* Call context */ + int bRecursive; /* TRUE if recursive implode [this is a symisc eXtension] */ + const char *zSep; /* Arguments separator if any */ + int nSeplen; /* Separator length */ + int bFirst; /* TRUE if first call */ + int nRecCount; /* Recursion count to avoid infinite loop */ +}; +/* + * Implode walker callback for the [jx9_array_walk()] interface. + * The following routine is invoked for each array entry passed + * to the implode() function. + */ +static int implode_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData) +{ + struct implode_data *pData = (struct implode_data *)pUserData; + const char *zData; + int nLen; + if( pData->bRecursive && jx9_value_is_json_array(pValue) && pData->nRecCount < 32 ){ + if( pData->nSeplen > 0 ){ + if( !pData->bFirst ){ + /* append the separator first */ + jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen); + }else{ + pData->bFirst = 0; + } + } + /* Recurse */ + pData->bFirst = 1; + pData->nRecCount++; + jx9HashmapWalk((jx9_hashmap *)pValue->x.pOther, implode_callback, pData); + pData->nRecCount--; + return JX9_OK; + } + /* Extract the string representation of the entry value */ + zData = jx9_value_to_string(pValue, &nLen); + if( nLen > 0 ){ + if( pData->nSeplen > 0 ){ + if( !pData->bFirst ){ + /* append the separator first */ + jx9_result_string(pData->pCtx, pData->zSep, pData->nSeplen); + }else{ + pData->bFirst = 0; + } + } + jx9_result_string(pData->pCtx, zData, nLen); + }else{ + SXUNUSED(pKey); /* cc warning */ + } + return JX9_OK; +} +/* + * string implode(string $glue, array $pieces, ...) + * string implode(array $pieces, ...) + * Join array elements with a string. + * $glue + * Defaults to an empty string. This is not the preferred usage of implode() as glue + * would be the second parameter and thus, the bad prototype would be used. + * $pieces + * The array of strings to implode. + * Return + * Returns a string containing a string representation of all the array elements in the same + * order, with the glue string between each element. + */ +static int jx9Builtin_implode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + struct implode_data imp_data; + int i = 1; + if( nArg < 1 ){ + /* Missing argument, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Prepare the implode context */ + imp_data.pCtx = pCtx; + imp_data.bRecursive = 0; + imp_data.bFirst = 1; + imp_data.nRecCount = 0; + if( !jx9_value_is_json_array(apArg[0]) ){ + imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen); + }else{ + imp_data.zSep = 0; + imp_data.nSeplen = 0; + i = 0; + } + jx9_result_string(pCtx, "", 0); /* Set an empty stirng */ + /* Start the 'join' process */ + while( i < nArg ){ + if( jx9_value_is_json_array(apArg[i]) ){ + /* Iterate throw array entries */ + jx9_array_walk(apArg[i], implode_callback, &imp_data); + }else{ + const char *zData; + int nLen; + /* Extract the string representation of the jx9 value */ + zData = jx9_value_to_string(apArg[i], &nLen); + if( nLen > 0 ){ + if( imp_data.nSeplen > 0 ){ + if( !imp_data.bFirst ){ + /* append the separator first */ + jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen); + }else{ + imp_data.bFirst = 0; + } + } + jx9_result_string(pCtx, zData, nLen); + } + } + i++; + } + return JX9_OK; +} +/* + * string implode_recursive(string $glue, array $pieces, ...) + * Purpose + * Same as implode() but recurse on arrays. + * Example: + * $a = array('usr', array('home', 'dean')); + * print implode_recursive("/", $a); + * Will output + * usr/home/dean. + * While the standard implode would produce. + * usr/Array. + * Parameter + * Refer to implode(). + * Return + * Refer to implode(). + */ +static int jx9Builtin_implode_recursive(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + struct implode_data imp_data; + int i = 1; + if( nArg < 1 ){ + /* Missing argument, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Prepare the implode context */ + imp_data.pCtx = pCtx; + imp_data.bRecursive = 1; + imp_data.bFirst = 1; + imp_data.nRecCount = 0; + if( !jx9_value_is_json_array(apArg[0]) ){ + imp_data.zSep = jx9_value_to_string(apArg[0], &imp_data.nSeplen); + }else{ + imp_data.zSep = 0; + imp_data.nSeplen = 0; + i = 0; + } + jx9_result_string(pCtx, "", 0); /* Set an empty stirng */ + /* Start the 'join' process */ + while( i < nArg ){ + if( jx9_value_is_json_array(apArg[i]) ){ + /* Iterate throw array entries */ + jx9_array_walk(apArg[i], implode_callback, &imp_data); + }else{ + const char *zData; + int nLen; + /* Extract the string representation of the jx9 value */ + zData = jx9_value_to_string(apArg[i], &nLen); + if( nLen > 0 ){ + if( imp_data.nSeplen > 0 ){ + if( !imp_data.bFirst ){ + /* append the separator first */ + jx9_result_string(pCtx, imp_data.zSep, imp_data.nSeplen); + }else{ + imp_data.bFirst = 0; + } + } + jx9_result_string(pCtx, zData, nLen); + } + } + i++; + } + return JX9_OK; +} +/* + * array explode(string $delimiter, string $string[, int $limit ]) + * Returns an array of strings, each of which is a substring of string + * formed by splitting it on boundaries formed by the string delimiter. + * Parameters + * $delimiter + * The boundary string. + * $string + * The input string. + * $limit + * If limit is set and positive, the returned array will contain a maximum + * of limit elements with the last element containing the rest of string. + * If the limit parameter is negative, all fields except the last -limit are returned. + * If the limit parameter is zero, then this is treated as 1. + * Returns + * Returns an array of strings created by splitting the string parameter + * on boundaries formed by the delimiter. + * If delimiter is an empty string (""), explode() will return FALSE. + * If delimiter contains a value that is not contained in string and a negative + * limit is used, then an empty array will be returned, otherwise an array containing string + * will be returned. + * NOTE: + * Negative limit is not supported. + */ +static int jx9Builtin_explode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zDelim, *zString, *zCur, *zEnd; + int nDelim, nStrlen, iLimit; + jx9_value *pArray; + jx9_value *pValue; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the delimiter */ + zDelim = jx9_value_to_string(apArg[0], &nDelim); + if( nDelim < 1 ){ + /* Empty delimiter, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the string */ + zString = jx9_value_to_string(apArg[1], &nStrlen); + if( nStrlen < 1 ){ + /* Empty delimiter, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the end of the string */ + zEnd = &zString[nStrlen]; + /* Create the array */ + pArray = jx9_context_new_array(pCtx); + pValue = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + /* Out of memory, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Set a defualt limit */ + iLimit = SXI32_HIGH; + if( nArg > 2 ){ + iLimit = jx9_value_to_int(apArg[2]); + if( iLimit < 0 ){ + iLimit = -iLimit; + } + if( iLimit == 0 ){ + iLimit = 1; + } + iLimit--; + } + /* Start exploding */ + for(;;){ + if( zString >= zEnd ){ + /* No more entry to process */ + break; + } + rc = SyBlobSearch(zString, (sxu32)(zEnd-zString), zDelim, nDelim, &nOfft); + if( rc != SXRET_OK || iLimit <= (int)jx9_array_count(pArray) ){ + /* Limit reached, insert the rest of the string and break */ + if( zEnd > zString ){ + jx9_value_string(pValue, zString, (int)(zEnd-zString)); + jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue); + } + break; + } + /* Point to the desired offset */ + zCur = &zString[nOfft]; + if( zCur > zString ){ + /* Perform the store operation */ + jx9_value_string(pValue, zString, (int)(zCur-zString)); + jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pValue); + } + /* Point beyond the delimiter */ + zString = &zCur[nDelim]; + /* Reset the cursor */ + jx9_value_reset_string_cursor(pValue); + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + /* NOTE that every allocated jx9_value will be automatically + * released as soon we return from this foregin function. + */ + return JX9_OK; +} +/* + * string trim(string $str[, string $charlist ]) + * Strip whitespace (or other characters) from the beginning and end of a string. + * Parameters + * $str + * The string that will be trimmed. + * $charlist + * Optionally, the stripped characters can also be specified using the charlist parameter. + * Simply list all characters that you want to be stripped. + * With .. you can specify a range of characters. + * Returns. + * Thr processed string. + */ +static int jx9Builtin_trim(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Start the trim process */ + if( nArg < 2 ){ + SyString sStr; + /* Remove white spaces and NUL bytes */ + SyStringInitFromBuf(&sStr, zString, nLen); + SyStringFullTrimSafe(&sStr); + jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); + }else{ + /* Char list */ + const char *zList; + int nListlen; + zList = jx9_value_to_string(apArg[1], &nListlen); + if( nListlen < 1 ){ + /* Return the string unchanged */ + jx9_result_string(pCtx, zString, nLen); + }else{ + const char *zEnd = &zString[nLen]; + const char *zCur = zString; + const char *zPtr; + int i; + /* Left trim */ + for(;;){ + if( zCur >= zEnd ){ + break; + } + zPtr = zCur; + for( i = 0 ; i < nListlen ; i++ ){ + if( zCur < zEnd && zCur[0] == zList[i] ){ + zCur++; + } + } + if( zCur == zPtr ){ + /* No match, break immediately */ + break; + } + } + /* Right trim */ + zEnd--; + for(;;){ + if( zEnd <= zCur ){ + break; + } + zPtr = zEnd; + for( i = 0 ; i < nListlen ; i++ ){ + if( zEnd > zCur && zEnd[0] == zList[i] ){ + zEnd--; + } + } + if( zEnd == zPtr ){ + break; + } + } + if( zCur >= zEnd ){ + /* Return the empty string */ + jx9_result_string(pCtx, "", 0); + }else{ + zEnd++; + jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); + } + } + } + return JX9_OK; +} +/* + * string rtrim(string $str[, string $charlist ]) + * Strip whitespace (or other characters) from the end of a string. + * Parameters + * $str + * The string that will be trimmed. + * $charlist + * Optionally, the stripped characters can also be specified using the charlist parameter. + * Simply list all characters that you want to be stripped. + * With .. you can specify a range of characters. + * Returns. + * Thr processed string. + */ +static int jx9Builtin_rtrim(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Start the trim process */ + if( nArg < 2 ){ + SyString sStr; + /* Remove white spaces and NUL bytes*/ + SyStringInitFromBuf(&sStr, zString, nLen); + SyStringRightTrimSafe(&sStr); + jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); + }else{ + /* Char list */ + const char *zList; + int nListlen; + zList = jx9_value_to_string(apArg[1], &nListlen); + if( nListlen < 1 ){ + /* Return the string unchanged */ + jx9_result_string(pCtx, zString, nLen); + }else{ + const char *zEnd = &zString[nLen - 1]; + const char *zCur = zString; + const char *zPtr; + int i; + /* Right trim */ + for(;;){ + if( zEnd <= zCur ){ + break; + } + zPtr = zEnd; + for( i = 0 ; i < nListlen ; i++ ){ + if( zEnd > zCur && zEnd[0] == zList[i] ){ + zEnd--; + } + } + if( zEnd == zPtr ){ + break; + } + } + if( zEnd <= zCur ){ + /* Return the empty string */ + jx9_result_string(pCtx, "", 0); + }else{ + zEnd++; + jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); + } + } + } + return JX9_OK; +} +/* + * string ltrim(string $str[, string $charlist ]) + * Strip whitespace (or other characters) from the beginning and end of a string. + * Parameters + * $str + * The string that will be trimmed. + * $charlist + * Optionally, the stripped characters can also be specified using the charlist parameter. + * Simply list all characters that you want to be stripped. + * With .. you can specify a range of characters. + * Returns. + * The processed string. + */ +static int jx9Builtin_ltrim(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Start the trim process */ + if( nArg < 2 ){ + SyString sStr; + /* Remove white spaces and NUL byte */ + SyStringInitFromBuf(&sStr, zString, nLen); + SyStringLeftTrimSafe(&sStr); + jx9_result_string(pCtx, sStr.zString, (int)sStr.nByte); + }else{ + /* Char list */ + const char *zList; + int nListlen; + zList = jx9_value_to_string(apArg[1], &nListlen); + if( nListlen < 1 ){ + /* Return the string unchanged */ + jx9_result_string(pCtx, zString, nLen); + }else{ + const char *zEnd = &zString[nLen]; + const char *zCur = zString; + const char *zPtr; + int i; + /* Left trim */ + for(;;){ + if( zCur >= zEnd ){ + break; + } + zPtr = zCur; + for( i = 0 ; i < nListlen ; i++ ){ + if( zCur < zEnd && zCur[0] == zList[i] ){ + zCur++; + } + } + if( zCur == zPtr ){ + /* No match, break immediately */ + break; + } + } + if( zCur >= zEnd ){ + /* Return the empty string */ + jx9_result_string(pCtx, "", 0); + }else{ + jx9_result_string(pCtx, zCur, (int)(zEnd-zCur)); + } + } + } + return JX9_OK; +} +/* + * string strtolower(string $str) + * Make a string lowercase. + * Parameters + * $str + * The input string. + * Returns. + * The lowercased string. + */ +static int jx9Builtin_strtolower(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zCur, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Perform the requested operation */ + zEnd = &zString[nLen]; + for(;;){ + if( zString >= zEnd ){ + /* No more input, break immediately */ + break; + } + if( (unsigned char)zString[0] >= 0xc0 ){ + /* UTF-8 stream, output verbatim */ + zCur = zString; + zString++; + while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ + zString++; + } + /* Append UTF-8 stream */ + jx9_result_string(pCtx, zCur, (int)(zString-zCur)); + }else{ + int c = zString[0]; + if( SyisUpper(c) ){ + c = SyToLower(zString[0]); + } + /* Append character */ + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + /* Advance the cursor */ + zString++; + } + } + return JX9_OK; +} +/* + * string strtolower(string $str) + * Make a string uppercase. + * Parameters + * $str + * The input string. + * Returns. + * The uppercased string. + */ +static int jx9Builtin_strtoupper(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zCur, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Perform the requested operation */ + zEnd = &zString[nLen]; + for(;;){ + if( zString >= zEnd ){ + /* No more input, break immediately */ + break; + } + if( (unsigned char)zString[0] >= 0xc0 ){ + /* UTF-8 stream, output verbatim */ + zCur = zString; + zString++; + while( zString < zEnd && ((unsigned char)zString[0] & 0xc0) == 0x80){ + zString++; + } + /* Append UTF-8 stream */ + jx9_result_string(pCtx, zCur, (int)(zString-zCur)); + }else{ + int c = zString[0]; + if( SyisLower(c) ){ + c = SyToUpper(zString[0]); + } + /* Append character */ + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + /* Advance the cursor */ + zString++; + } + } + return JX9_OK; +} +/* + * int ord(string $string) + * Returns the ASCII value of the first character of string. + * Parameters + * $str + * The input string. + * Returns. + * The ASCII value as an integer. + */ +static int jx9Builtin_ord(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString; + int nLen, c; + if( nArg < 1 ){ + /* Missing arguments, return -1 */ + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return -1 */ + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Extract the ASCII value of the first character */ + c = zString[0]; + /* Return that value */ + jx9_result_int(pCtx, c); + return JX9_OK; +} +/* + * string chr(int $ascii) + * Returns a one-character string containing the character specified by ascii. + * Parameters + * $ascii + * The ascii code. + * Returns. + * The specified character. + */ +static int jx9Builtin_chr(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int c; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the ASCII value */ + c = jx9_value_to_int(apArg[0]); + /* Return the specified character */ + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + return JX9_OK; +} +/* + * Binary to hex consumer callback. + * This callback is the default consumer used by the hash functions + * [i.e: bin2hex(), md5(), sha1(), md5_file() ... ] defined below. + */ +static int HashConsumer(const void *pData, unsigned int nLen, void *pUserData) +{ + /* Append hex chunk verbatim */ + jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); + return SXRET_OK; +} +/* + * string bin2hex(string $str) + * Convert binary data into hexadecimal representation. + * Parameters + * $str + * The input string. + * Returns. + * Returns the hexadecimal representation of the given string. + */ +static int jx9Builtin_bin2hex(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Perform the requested operation */ + SyBinToHexConsumer((const void *)zString, (sxu32)nLen, HashConsumer, pCtx); + return JX9_OK; +} +/* Search callback signature */ +typedef sxi32 (*ProcStringMatch)(const void *, sxu32, const void *, sxu32, sxu32 *); +/* + * Case-insensitive pattern match. + * Brute force is the default search method used here. + * This is due to the fact that brute-forcing works quite + * well for short/medium texts on modern hardware. + */ +static sxi32 iPatternMatch(const void *pText, sxu32 nLen, const void *pPattern, sxu32 iPatLen, sxu32 *pOfft) +{ + const char *zpIn = (const char *)pPattern; + const char *zIn = (const char *)pText; + const char *zpEnd = &zpIn[iPatLen]; + const char *zEnd = &zIn[nLen]; + const char *zPtr, *zPtr2; + int c, d; + if( iPatLen > nLen ){ + /* Don't bother processing */ + return SXERR_NOTFOUND; + } + for(;;){ + if( zIn >= zEnd ){ + break; + } + c = SyToLower(zIn[0]); + d = SyToLower(zpIn[0]); + if( c == d ){ + zPtr = &zIn[1]; + zPtr2 = &zpIn[1]; + for(;;){ + if( zPtr2 >= zpEnd ){ + /* Pattern found */ + if( pOfft ){ *pOfft = (sxu32)(zIn-(const char *)pText); } + return SXRET_OK; + } + if( zPtr >= zEnd ){ + break; + } + c = SyToLower(zPtr[0]); + d = SyToLower(zPtr2[0]); + if( c != d ){ + break; + } + zPtr++; zPtr2++; + } + } + zIn++; + } + /* Pattern not found */ + return SXERR_NOTFOUND; +} +/* + * string strstr(string $haystack, string $needle[, bool $before_needle = false ]) + * Find the first occurrence of a string. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $before_needle + * If TRUE, strstr() returns the part of the haystack before the first occurrence + * of the needle (excluding the needle). + * Return + * Returns the portion of string, or FALSE if needle is not found. + */ +static int jx9Builtin_strstr(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ + const char *zBlob, *zPattern; + int nLen, nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the needle and the haystack */ + zBlob = jx9_value_to_string(apArg[0], &nLen); + zPattern = jx9_value_to_string(apArg[1], &nPatLen); + nOfft = 0; /* cc warning */ + if( nLen > 0 && nPatLen > 0 ){ + int before = 0; + /* Perform the lookup */ + rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return the portion of the string */ + if( nArg > 2 ){ + before = jx9_value_to_int(apArg[2]); + } + if( before ){ + jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob)); + }else{ + jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); + } + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * string stristr(string $haystack, string $needle[, bool $before_needle = false ]) + * Case-insensitive strstr(). + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $before_needle + * If TRUE, strstr() returns the part of the haystack before the first occurrence + * of the needle (excluding the needle). + * Return + * Returns the portion of string, or FALSE if needle is not found. + */ +static int jx9Builtin_stristr(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ + const char *zBlob, *zPattern; + int nLen, nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the needle and the haystack */ + zBlob = jx9_value_to_string(apArg[0], &nLen); + zPattern = jx9_value_to_string(apArg[1], &nPatLen); + nOfft = 0; /* cc warning */ + if( nLen > 0 && nPatLen > 0 ){ + int before = 0; + /* Perform the lookup */ + rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return the portion of the string */ + if( nArg > 2 ){ + before = jx9_value_to_int(apArg[2]); + } + if( before ){ + jx9_result_string(pCtx, zBlob, (int)(&zBlob[nOfft]-zBlob)); + }else{ + jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); + } + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * int strpos(string $haystack, string $needle [, int $offset = 0 ] ) + * Returns the numeric position of the first occurrence of needle in the haystack string. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * This optional offset parameter allows you to specify which character in haystack + * to start searching. The position returned is still relative to the beginning + * of haystack. + * Return + * Returns the position as an integer.If needle is not found, strpos() will return FALSE. + */ +static int jx9Builtin_strpos(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ + const char *zBlob, *zPattern; + int nLen, nPatLen, nStart; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the needle and the haystack */ + zBlob = jx9_value_to_string(apArg[0], &nLen); + zPattern = jx9_value_to_string(apArg[1], &nPatLen); + nOfft = 0; /* cc warning */ + nStart = 0; + /* Peek the starting offset if available */ + if( nArg > 2 ){ + nStart = jx9_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + } + if( nStart >= nLen ){ + /* Invalid offset */ + nStart = 0; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return the pattern position */ + jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart)); + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * int stripos(string $haystack, string $needle [, int $offset = 0 ] ) + * Case-insensitive strpos. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * This optional offset parameter allows you to specify which character in haystack + * to start searching. The position returned is still relative to the beginning + * of haystack. + * Return + * Returns the position as an integer.If needle is not found, strpos() will return FALSE. + */ +static int jx9Builtin_stripos(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ + const char *zBlob, *zPattern; + int nLen, nPatLen, nStart; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the needle and the haystack */ + zBlob = jx9_value_to_string(apArg[0], &nLen); + zPattern = jx9_value_to_string(apArg[1], &nPatLen); + nOfft = 0; /* cc warning */ + nStart = 0; + /* Peek the starting offset if available */ + if( nArg > 2 ){ + nStart = jx9_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + } + if( nStart >= nLen ){ + /* Invalid offset */ + nStart = 0; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + rc = xPatternMatch(zBlob, (sxu32)nLen, zPattern, (sxu32)nPatLen, &nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return the pattern position */ + jx9_result_int64(pCtx, (jx9_int64)(nOfft+nStart)); + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * int strrpos(string $haystack, string $needle [, int $offset = 0 ] ) + * Find the numeric position of the last occurrence of needle in the haystack string. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * If specified, search will start this number of characters counted from the beginning + * of the string. If the value is negative, search will instead start from that many + * characters from the end of the string, searching backwards. + * Return + * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. + */ +static int jx9Builtin_strrpos(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd; + ProcStringMatch xPatternMatch = SyBlobSearch; /* Case-sensitive pattern match */ + int nLen, nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the needle and the haystack */ + zBlob = jx9_value_to_string(apArg[0], &nLen); + zPattern = jx9_value_to_string(apArg[1], &nPatLen); + /* Point to the end of the pattern */ + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + /* Save the starting posistion */ + zStart = zBlob; + nOfft = 0; /* cc warning */ + /* Peek the starting offset if available */ + if( nArg > 2 ){ + int nStart; + nStart = jx9_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + if( nStart >= nLen ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + }else{ + nLen -= nStart; + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + } + }else{ + if( nStart >= nLen ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + for(;;){ + if( zBlob >= zPtr ){ + break; + } + rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft); + if( rc == SXRET_OK ){ + /* Pattern found, return it's position */ + jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart)); + return JX9_OK; + } + zPtr--; + } + /* Pattern not found, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * int strripos(string $haystack, string $needle [, int $offset = 0 ] ) + * Case-insensitive strrpos. + * Parameters + * $haystack + * The input string. + * $needle + * Search pattern (must be a string). + * $offset + * If specified, search will start this number of characters counted from the beginning + * of the string. If the value is negative, search will instead start from that many + * characters from the end of the string, searching backwards. + * Return + * Returns the position as an integer.If needle is not found, strrpos() will return FALSE. + */ +static int jx9Builtin_strripos(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zStart, *zBlob, *zPattern, *zPtr, *zEnd; + ProcStringMatch xPatternMatch = iPatternMatch; /* Case-insensitive pattern match */ + int nLen, nPatLen; + sxu32 nOfft; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the needle and the haystack */ + zBlob = jx9_value_to_string(apArg[0], &nLen); + zPattern = jx9_value_to_string(apArg[1], &nPatLen); + /* Point to the end of the pattern */ + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + /* Save the starting posistion */ + zStart = zBlob; + nOfft = 0; /* cc warning */ + /* Peek the starting offset if available */ + if( nArg > 2 ){ + int nStart; + nStart = jx9_value_to_int(apArg[2]); + if( nStart < 0 ){ + nStart = -nStart; + if( nStart >= nLen ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + }else{ + nLen -= nStart; + zPtr = &zBlob[nLen - 1]; + zEnd = &zBlob[nLen]; + } + }else{ + if( nStart >= nLen ){ + /* Invalid offset */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + }else{ + zBlob += nStart; + nLen -= nStart; + } + } + } + if( nLen > 0 && nPatLen > 0 ){ + /* Perform the lookup */ + for(;;){ + if( zBlob >= zPtr ){ + break; + } + rc = xPatternMatch((const void *)zPtr, (sxu32)(zEnd-zPtr), (const void *)zPattern, (sxu32)nPatLen, &nOfft); + if( rc == SXRET_OK ){ + /* Pattern found, return it's position */ + jx9_result_int64(pCtx, (jx9_int64)(&zPtr[nOfft] - zStart)); + return JX9_OK; + } + zPtr--; + } + /* Pattern not found, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * int strrchr(string $haystack, mixed $needle) + * Find the last occurrence of a character in a string. + * Parameters + * $haystack + * The input string. + * $needle + * If needle contains more than one character, only the first is used. + * This behavior is different from that of strstr(). + * If needle is not a string, it is converted to an integer and applied + * as the ordinal value of a character. + * Return + * This function returns the portion of string, or FALSE if needle is not found. + */ +static int jx9Builtin_strrchr(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zBlob; + int nLen, c; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the haystack */ + zBlob = jx9_value_to_string(apArg[0], &nLen); + c = 0; /* cc warning */ + if( nLen > 0 ){ + sxu32 nOfft; + sxi32 rc; + if( jx9_value_is_string(apArg[1]) ){ + const char *zPattern; + zPattern = jx9_value_to_string(apArg[1], 0); /* Never fail, so there is no need to check + * for NULL pointer. + */ + c = zPattern[0]; + }else{ + /* Int cast */ + c = jx9_value_to_int(apArg[1]); + } + /* Perform the lookup */ + rc = SyByteFind2(zBlob, (sxu32)nLen, c, &nOfft); + if( rc != SXRET_OK ){ + /* No such entry, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return the string portion */ + jx9_result_string(pCtx, &zBlob[nOfft], (int)(&zBlob[nLen]-&zBlob[nOfft])); + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * string strrev(string $string) + * Reverse a string. + * Parameters + * $string + * String to be reversed. + * Return + * The reversed string. + */ +static int jx9Builtin_strrev(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn, *zEnd; + int nLen, c; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string Return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Perform the requested operation */ + zEnd = &zIn[nLen - 1]; + for(;;){ + if( zEnd < zIn ){ + /* No more input to process */ + break; + } + /* Append current character */ + c = zEnd[0]; + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + zEnd--; + } + return JX9_OK; +} +/* + * string str_repeat(string $input, int $multiplier) + * Returns input repeated multiplier times. + * Parameters + * $string + * String to be repeated. + * $multiplier + * Number of time the input string should be repeated. + * multiplier has to be greater than or equal to 0. If the multiplier is set + * to 0, the function will return an empty string. + * Return + * The repeated string. + */ +static int jx9Builtin_str_repeat(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn; + int nLen, nMul; + int rc; + if( nArg < 2 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string.Return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the multiplier */ + nMul = jx9_value_to_int(apArg[1]); + if( nMul < 1 ){ + /* Return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( nMul < 1 ){ + break; + } + /* Append the copy */ + rc = jx9_result_string(pCtx, zIn, nLen); + if( rc != JX9_OK ){ + /* Out of memory, break immediately */ + break; + } + nMul--; + } + return JX9_OK; +} +/* + * string nl2br(string $string[, bool $is_xhtml = true ]) + * Inserts HTML line breaks before all newlines in a string. + * Parameters + * $string + * The input string. + * $is_xhtml + * Whenever to use XHTML compatible line breaks or not. + * Return + * The processed string. + */ +static int jx9Builtin_nl2br(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn, *zCur, *zEnd; + int is_xhtml = 0; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( nArg > 1 ){ + is_xhtml = jx9_value_to_bool(apArg[1]); + } + zEnd = &zIn[nLen]; + /* Perform the requested operation */ + for(;;){ + zCur = zIn; + /* Delimit the string */ + while( zIn < zEnd && (zIn[0] != '\n'&& zIn[0] != '\r') ){ + zIn++; + } + if( zCur < zIn ){ + /* Output chunk verbatim */ + jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); + } + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + /* Output the HTML line break */ + if( is_xhtml ){ + jx9_result_string(pCtx, "
", (int)sizeof("
")-1); + }else{ + jx9_result_string(pCtx, "
", (int)sizeof("
")-1); + } + zCur = zIn; + /* Append trailing line */ + while( zIn < zEnd && (zIn[0] == '\n' || zIn[0] == '\r') ){ + zIn++; + } + if( zCur < zIn ){ + /* Output chunk verbatim */ + jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); + } + } + return JX9_OK; +} +/* + * Format a given string and invoke the given callback on each processed chunk. + * According to the JX9 reference manual. + * The format string is composed of zero or more directives: ordinary characters + * (excluding %) that are copied directly to the result, and conversion + * specifications, each of which results in fetching its own parameter. + * This applies to both sprintf() and printf(). + * Each conversion specification consists of a percent sign (%), followed by one + * or more of these elements, in order: + * An optional sign specifier that forces a sign (- or +) to be used on a number. + * By default, only the - sign is used on a number if it's negative. This specifier forces + * positive numbers to have the + sign attached as well. + * An optional padding specifier that says what character will be used for padding + * the results to the right string size. This may be a space character or a 0 (zero character). + * The default is to pad with spaces. An alternate padding character can be specified by prefixing + * it with a single quote ('). See the examples below. + * An optional alignment specifier that says if the result should be left-justified or right-justified. + * The default is right-justified; a - character here will make it left-justified. + * An optional number, a width specifier that says how many characters (minimum) this conversion + * should result in. + * An optional precision specifier in the form of a period (`.') followed by an optional decimal + * digit string that says how many decimal digits should be displayed for floating-point numbers. + * When using this specifier on a string, it acts as a cutoff point, setting a maximum character + * limit to the string. + * A type specifier that says what type the argument data should be treated as. Possible types: + * % - a literal percent character. No argument is required. + * b - the argument is treated as an integer, and presented as a binary number. + * c - the argument is treated as an integer, and presented as the character with that ASCII value. + * d - the argument is treated as an integer, and presented as a (signed) decimal number. + * e - the argument is treated as scientific notation (e.g. 1.2e+2). The precision specifier stands + * for the number of digits after the decimal point. + * E - like %e but uses uppercase letter (e.g. 1.2E+2). + * u - the argument is treated as an integer, and presented as an unsigned decimal number. + * f - the argument is treated as a float, and presented as a floating-point number (locale aware). + * F - the argument is treated as a float, and presented as a floating-point number (non-locale aware). + * g - shorter of %e and %f. + * G - shorter of %E and %f. + * o - the argument is treated as an integer, and presented as an octal number. + * s - the argument is treated as and presented as a string. + * x - the argument is treated as an integer and presented as a hexadecimal number (with lowercase letters). + * X - the argument is treated as an integer and presented as a hexadecimal number (with uppercase letters). + */ +/* + * This implementation is based on the one found in the SQLite3 source tree. + */ +#define JX9_FMT_BUFSIZ 1024 /* Conversion buffer size */ +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define JX9_FMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ +#define JX9_FMT_FLOAT 2 /* Floating point.%f */ +#define JX9_FMT_EXP 3 /* Exponentional notation.%e and %E */ +#define JX9_FMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ +#define JX9_FMT_SIZE 5 /* Total number of characters processed so far.%n */ +#define JX9_FMT_STRING 6 /* Strings.%s */ +#define JX9_FMT_PERCENT 7 /* Percent symbol.%% */ +#define JX9_FMT_CHARX 8 /* Characters.%c */ +#define JX9_FMT_ERROR 9 /* Used to indicate no such conversion type */ +/* +** Allowed values for jx9_fmt_info.flags +*/ +#define JX9_FMT_FLAG_SIGNED 0x01 +#define JX9_FMT_FLAG_UNSIGNED 0x02 +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct jx9_fmt_info jx9_fmt_info; +struct jx9_fmt_info +{ + char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */ + sxu8 base; /* The base for radix conversion */ + int flags; /* One or more of JX9_FMT_FLAG_ constants below */ + sxu8 type; /* Conversion paradigm */ + char *charset; /* The character set for conversion */ + char *prefix; /* Prefix on non-zero values in alt format */ +}; +#ifndef JX9_OMIT_FLOATING_POINT +/* +** "*val" is a double such that 0.1 <= *val < 10.0 +** Return the ascii code for the leading digit of *val, then +** multiply "*val" by 10.0 to renormalize. +** +** Example: +** input: *val = 3.14159 +** output: *val = 1.4159 function return = '3' +** +** The counter *cnt is incremented each time. After counter exceeds +** 16 (the number of significant digits in a 64-bit float) '0' is +** always returned. +*/ +static int vxGetdigit(sxlongreal *val, int *cnt) +{ + sxlongreal d; + int digit; + + if( (*cnt)++ >= 16 ){ + return '0'; + } + digit = (int)*val; + d = digit; + *val = (*val - d)*10.0; + return digit + '0' ; +} +#endif /* JX9_OMIT_FLOATING_POINT */ +/* + * The following table is searched linearly, so it is good to put the most frequently + * used conversion types first. + */ +static const jx9_fmt_info aFmt[] = { + { 'd', 10, JX9_FMT_FLAG_SIGNED, JX9_FMT_RADIX, "0123456789", 0 }, + { 's', 0, 0, JX9_FMT_STRING, 0, 0 }, + { 'c', 0, 0, JX9_FMT_CHARX, 0, 0 }, + { 'x', 16, 0, JX9_FMT_RADIX, "0123456789abcdef", "x0" }, + { 'X', 16, 0, JX9_FMT_RADIX, "0123456789ABCDEF", "X0" }, + { 'b', 2, 0, JX9_FMT_RADIX, "01", "b0"}, + { 'o', 8, 0, JX9_FMT_RADIX, "01234567", "0" }, + { 'u', 10, 0, JX9_FMT_RADIX, "0123456789", 0 }, + { 'f', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 }, + { 'F', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_FLOAT, 0, 0 }, + { 'e', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "e", 0 }, + { 'E', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_EXP, "E", 0 }, + { 'g', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "e", 0 }, + { 'G', 0, JX9_FMT_FLAG_SIGNED, JX9_FMT_GENERIC, "E", 0 }, + { '%', 0, 0, JX9_FMT_PERCENT, 0, 0 } +}; +/* + * Format a given string. + * The root program. All variations call this core. + * INPUTS: + * xConsumer This is a pointer to a function taking four arguments + * 1. A pointer to the call context. + * 2. A pointer to the list of characters to be output + * (Note, this list is NOT null terminated.) + * 3. An integer number of characters to be output. + * (Note: This number might be zero.) + * 4. Upper layer private data. + * zIn This is the format string, as in the usual print. + * apArg This is a pointer to a list of arguments. + */ +JX9_PRIVATE sxi32 jx9InputFormat( + int (*xConsumer)(jx9_context *, const char *, int, void *), /* Format consumer */ + jx9_context *pCtx, /* call context */ + const char *zIn, /* Format string */ + int nByte, /* Format string length */ + int nArg, /* Total argument of the given arguments */ + jx9_value **apArg, /* User arguments */ + void *pUserData, /* Last argument to xConsumer() */ + int vf /* TRUE if called from vfprintf, vsprintf context */ + ) +{ + char spaces[] = " "; +#define etSPACESIZE ((int)sizeof(spaces)-1) + const char *zCur, *zEnd = &zIn[nByte]; + char *zBuf, zWorker[JX9_FMT_BUFSIZ]; /* Working buffer */ + const jx9_fmt_info *pInfo; /* Pointer to the appropriate info structure */ + int flag_alternateform; /* True if "#" flag is present */ + int flag_leftjustify; /* True if "-" flag is present */ + int flag_blanksign; /* True if " " flag is present */ + int flag_plussign; /* True if "+" flag is present */ + int flag_zeropad; /* True if field width constant starts with zero */ + jx9_value *pArg; /* Current processed argument */ + jx9_int64 iVal; + int precision; /* Precision of the current field */ + char *zExtra; + int c, rc, n; + int length; /* Length of the field */ + int prefix; + sxu8 xtype; /* Conversion paradigm */ + int width; /* Width of the current field */ + int idx; + n = (vf == TRUE) ? 0 : 1; +#define NEXT_ARG ( n < nArg ? apArg[n++] : 0 ) + /* Start the format process */ + for(;;){ + zCur = zIn; + while( zIn < zEnd && zIn[0] != '%' ){ + zIn++; + } + if( zCur < zIn ){ + /* Consume chunk verbatim */ + rc = xConsumer(pCtx, zCur, (int)(zIn-zCur), pUserData); + if( rc == SXERR_ABORT ){ + /* Callback request an operation abort */ + break; + } + } + if( zIn >= zEnd ){ + /* No more input to process, break immediately */ + break; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = + flag_alternateform = flag_zeropad = 0; + zIn++; /* Jump the precent sign */ + do{ + c = zIn[0]; + switch( c ){ + case '-': flag_leftjustify = 1; c = 0; break; + case '+': flag_plussign = 1; c = 0; break; + case ' ': flag_blanksign = 1; c = 0; break; + case '#': flag_alternateform = 1; c = 0; break; + case '0': flag_zeropad = 1; c = 0; break; + case '\'': + zIn++; + if( zIn < zEnd ){ + /* An alternate padding character can be specified by prefixing it with a single quote (') */ + c = zIn[0]; + for(idx = 0 ; idx < etSPACESIZE ; ++idx ){ + spaces[idx] = (char)c; + } + c = 0; + } + break; + default: break; + } + }while( c==0 && (zIn++ < zEnd) ); + /* Get the field width */ + width = 0; + while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ + width = width*10 + (zIn[0] - '0'); + zIn++; + } + if( zIn < zEnd && zIn[0] == '$' ){ + /* Position specifer */ + if( width > 0 ){ + n = width; + if( vf && n > 0 ){ + n--; + } + } + zIn++; + width = 0; + if( zIn < zEnd && zIn[0] == '0' ){ + flag_zeropad = 1; + zIn++; + } + while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ + width = width*10 + (zIn[0] - '0'); + zIn++; + } + } + if( width > JX9_FMT_BUFSIZ-10 ){ + width = JX9_FMT_BUFSIZ-10; + } + /* Get the precision */ + precision = -1; + if( zIn < zEnd && zIn[0] == '.' ){ + precision = 0; + zIn++; + while( zIn < zEnd && ( zIn[0] >='0' && zIn[0] <='9') ){ + precision = precision*10 + (zIn[0] - '0'); + zIn++; + } + } + if( zIn >= zEnd ){ + /* No more input */ + break; + } + /* Fetch the info entry for the field */ + pInfo = 0; + xtype = JX9_FMT_ERROR; + c = zIn[0]; + zIn++; /* Jump the format specifer */ + for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ + if( c==aFmt[idx].fmttype ){ + pInfo = &aFmt[idx]; + xtype = pInfo->type; + break; + } + } + zBuf = zWorker; /* Point to the working buffer */ + length = 0; + zExtra = 0; + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width. This is + ** always non-negative. Zero is the default. + ** precision The specified precision. The default + ** is -1. + */ + switch(xtype){ + case JX9_FMT_PERCENT: + /* A literal percent character */ + zWorker[0] = '%'; + length = (int)sizeof(char); + break; + case JX9_FMT_CHARX: + /* The argument is treated as an integer, and presented as the character + * with that ASCII value + */ + pArg = NEXT_ARG; + if( pArg == 0 ){ + c = 0; + }else{ + c = jx9_value_to_int(pArg); + } + /* NUL byte is an acceptable value */ + zWorker[0] = (char)c; + length = (int)sizeof(char); + break; + case JX9_FMT_STRING: + /* the argument is treated as and presented as a string */ + pArg = NEXT_ARG; + if( pArg == 0 ){ + length = 0; + }else{ + zBuf = (char *)jx9_value_to_string(pArg, &length); + } + if( length < 1 ){ + zBuf = " "; + length = (int)sizeof(char); + } + if( precision>=0 && precisionJX9_FMT_BUFSIZ-40 ){ + precision = JX9_FMT_BUFSIZ-40; + } +#if 1 + /* For the format %#x, the value zero is printed "0" not "0x0". + ** I think this is stupid.*/ + if( iVal==0 ) flag_alternateform = 0; +#else + /* More sensible: turn off the prefix for octal (to prevent "00"), + ** but leave the prefix for hex.*/ + if( iVal==0 && pInfo->base==8 ) flag_alternateform = 0; +#endif + if( pInfo->flags & JX9_FMT_FLAG_SIGNED ){ + if( iVal<0 ){ + iVal = -iVal; + /* Ticket 1433-003 */ + if( iVal < 0 ){ + /* Overflow */ + iVal= 0x7FFFFFFFFFFFFFFF; + } + prefix = '-'; + }else if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + }else{ + if( iVal<0 ){ + iVal = -iVal; + /* Ticket 1433-003 */ + if( iVal < 0 ){ + /* Overflow */ + iVal= 0x7FFFFFFFFFFFFFFF; + } + } + prefix = 0; + } + if( flag_zeropad && precisioncharset; + base = pInfo->base; + do{ /* Convert to ascii */ + *(--zBuf) = cset[iVal%base]; + iVal = iVal/base; + }while( iVal>0 ); + } + length = (int)(&zWorker[JX9_FMT_BUFSIZ-1]-zBuf); + for(idx=precision-length; idx>0; idx--){ + *(--zBuf) = '0'; /* Zero pad */ + } + if( prefix ) *(--zBuf) = (char)prefix; /* Add sign */ + if( flag_alternateform && pInfo->prefix ){ /* Add "0" or "0x" */ + char *pre, x; + pre = pInfo->prefix; + if( *zBuf!=pre[0] ){ + for(pre=pInfo->prefix; (x=(*pre))!=0; pre++) *(--zBuf) = x; + } + } + length = (int)(&zWorker[JX9_FMT_BUFSIZ-1]-zBuf); + break; + case JX9_FMT_FLOAT: + case JX9_FMT_EXP: + case JX9_FMT_GENERIC:{ +#ifndef JX9_OMIT_FLOATING_POINT + long double realvalue; + int exp; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + int flag_dp; /* True if decimal point should be shown */ + int flag_rtz; /* True if trailing zeros should be removed */ + int flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ + pArg = NEXT_ARG; + if( pArg == 0 ){ + realvalue = 0; + }else{ + realvalue = jx9_value_to_double(pArg); + } + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>JX9_FMT_BUFSIZ-40) precision = JX9_FMT_BUFSIZ-40; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( pInfo->type==JX9_FMT_GENERIC && precision>0 ) precision--; + rounder = 0.0; +#if 0 + /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( pInfo->type==JX9_FMT_FLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } + if( exp>350 || exp<-350 ){ + zBuf = "NaN"; + length = 3; + break; + } + } + zBuf = zWorker; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==JX9_FMT_EXP; + if( xtype!=JX9_FMT_FLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==JX9_FMT_GENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = JX9_FMT_EXP; + }else{ + precision = precision - exp; + xtype = JX9_FMT_FLOAT; + } + }else{ + flag_rtz = 0; + } + /* + ** The "exp+precision" test causes output to be of type etEXP if + ** the precision is too large to fit in buf[]. + */ + nsd = 0; + if( xtype==JX9_FMT_FLOAT && exp+precision0 || flag_alternateform); + if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ + if( exp<0 ) *(zBuf++) = '0'; /* Digits before "." */ + else for(; exp>=0; exp--) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); + if( flag_dp ) *(zBuf++) = '.'; /* The decimal point */ + for(exp++; exp<0 && precision>0; precision--, exp++){ + *(zBuf++) = '0'; + } + while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); + *(zBuf--) = 0; /* Null terminate */ + if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ + while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; + if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; + } + zBuf++; /* point to next free slot */ + }else{ /* etEXP or etGENERIC */ + flag_dp = (precision>0 || flag_alternateform); + if( prefix ) *(zBuf++) = (char)prefix; /* Sign */ + *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); /* First digit */ + if( flag_dp ) *(zBuf++) = '.'; /* Decimal point */ + while( (precision--)>0 ) *(zBuf++) = (char)vxGetdigit(&realvalue, &nsd); + zBuf--; /* point to last digit */ + if( flag_rtz && flag_dp ){ /* Remove tail zeros */ + while( zBuf>=zWorker && *zBuf=='0' ) *(zBuf--) = 0; + if( zBuf>=zWorker && *zBuf=='.' ) *(zBuf--) = 0; + } + zBuf++; /* point to next free slot */ + if( exp || flag_exp ){ + *(zBuf++) = pInfo->charset[0]; + if( exp<0 ){ *(zBuf++) = '-'; exp = -exp; } /* sign of exp */ + else { *(zBuf++) = '+'; } + if( exp>=100 ){ + *(zBuf++) = (char)((exp/100)+'0'); /* 100's digit */ + exp %= 100; + } + *(zBuf++) = (char)(exp/10+'0'); /* 10's digit */ + *(zBuf++) = (char)(exp%10+'0'); /* 1's digit */ + } + } + /* The converted number is in buf[] and zero terminated.Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions.*/ + length = (int)(zBuf-zWorker); + zBuf = zWorker; + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + zBuf[i] = zBuf[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) zBuf[i++] = '0'; + length = width; + } +#else + zBuf = " "; + length = (int)sizeof(char); +#endif /* JX9_OMIT_FLOATING_POINT */ + break; + } + default: + /* Invalid format specifer */ + zWorker[0] = '?'; + length = (int)sizeof(char); + break; + } + /* + ** The text of the conversion is pointed to by "zBuf" and is + ** "length" characters long.The field width is "width".Do + ** the output. + */ + if( !flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + if( length>0 ){ + rc = xConsumer(pCtx, zBuf, (unsigned int)length, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + if( flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(pCtx, spaces, etSPACESIZE, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(pCtx, spaces, (unsigned int)nspace, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + }/* for(;;) */ + return SXRET_OK; +} +/* + * Callback [i.e: Formatted input consumer] of the sprintf function. + */ +static int sprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) +{ + /* Consume directly */ + jx9_result_string(pCtx, zInput, nLen); + SXUNUSED(pUserData); /* cc warning */ + return JX9_OK; +} +/* + * string sprintf(string $format[, mixed $args [, mixed $... ]]) + * Return a formatted string. + * Parameters + * $format + * The format string (see block comment above) + * Return + * A string produced according to the formatting string format. + */ +static int jx9Builtin_sprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFormat; + int nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the string format */ + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Format the string */ + jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, nArg, apArg, 0, FALSE); + return JX9_OK; +} +/* + * Callback [i.e: Formatted input consumer] of the printf function. + */ +static int printfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) +{ + jx9_int64 *pCounter = (jx9_int64 *)pUserData; + /* Call the VM output consumer directly */ + jx9_context_output(pCtx, zInput, nLen); + /* Increment counter */ + *pCounter += nLen; + return JX9_OK; +} +/* + * int64 printf(string $format[, mixed $args[, mixed $... ]]) + * Output a formatted string. + * Parameters + * $format + * See sprintf() for a description of format. + * Return + * The length of the outputted string. + */ +static int jx9Builtin_printf(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_int64 nCounter = 0; + const char *zFormat; + int nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the string format */ + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Format the string */ + jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, nArg, apArg, (void *)&nCounter, FALSE); + /* Return the length of the outputted string */ + jx9_result_int64(pCtx, nCounter); + return JX9_OK; +} +/* + * int vprintf(string $format, array $args) + * Output a formatted string. + * Parameters + * $format + * See sprintf() for a description of format. + * Return + * The length of the outputted string. + */ +static int jx9Builtin_vprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_int64 nCounter = 0; + const char *zFormat; + jx9_hashmap *pMap; + SySet sArg; + int nLen, n; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ + /* Missing/Invalid arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the string format */ + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the hashmap */ + pMap = (jx9_hashmap *)apArg[1]->x.pOther; + /* Extract arguments from the hashmap */ + n = jx9HashmapValuesToSet(pMap, &sArg); + /* Format the string */ + jx9InputFormat(printfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&nCounter, TRUE); + /* Return the length of the outputted string */ + jx9_result_int64(pCtx, nCounter); + /* Release the container */ + SySetRelease(&sArg); + return JX9_OK; +} +/* + * int vsprintf(string $format, array $args) + * Output a formatted string. + * Parameters + * $format + * See sprintf() for a description of format. + * Return + * A string produced according to the formatting string format. + */ +static int jx9Builtin_vsprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFormat; + jx9_hashmap *pMap; + SySet sArg; + int nLen, n; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ + /* Missing/Invalid arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the string format */ + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Point to hashmap */ + pMap = (jx9_hashmap *)apArg[1]->x.pOther; + /* Extract arguments from the hashmap */ + n = jx9HashmapValuesToSet(pMap, &sArg); + /* Format the string */ + jx9InputFormat(sprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), 0, TRUE); + /* Release the container */ + SySetRelease(&sArg); + return JX9_OK; +} +/* + * string size_format(int64 $size) + * Return a smart string represenation of the given size [i.e: 64-bit integer] + * Example: + * print size_format(1*1024*1024*1024);// 1GB + * print size_format(512*1024*1024); // 512 MB + * print size_format(file_size(/path/to/my/file_8192)); //8KB + * Parameter + * $size + * Entity size in bytes. + * Return + * Formatted string representation of the given size. + */ +static int jx9Builtin_size_format(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + /*Kilo*/ /*Mega*/ /*Giga*/ /*Tera*/ /*Peta*/ /*Exa*/ /*Zeta*/ + static const char zUnit[] = {"KMGTPEZ"}; + sxi32 nRest, i_32; + jx9_int64 iSize; + int c = -1; /* index in zUnit[] */ + + if( nArg < 1 ){ + /* Missing argument, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the given size */ + iSize = jx9_value_to_int64(apArg[0]); + if( iSize < 100 /* Bytes */ ){ + /* Don't bother formatting, return immediately */ + jx9_result_string(pCtx, "0.1 KB", (int)sizeof("0.1 KB")-1); + return JX9_OK; + } + for(;;){ + nRest = (sxi32)(iSize & 0x3FF); + iSize >>= 10; + c++; + if( (iSize & (~0 ^ 1023)) == 0 ){ + break; + } + } + nRest /= 100; + if( nRest > 9 ){ + nRest = 9; + } + if( iSize > 999 ){ + c++; + nRest = 9; + iSize = 0; + } + i_32 = (sxi32)iSize; + /* Format */ + jx9_result_string_format(pCtx, "%d.%d %cB", i_32, nRest, zUnit[c]); + return JX9_OK; +} +#if !defined(JX9_DISABLE_HASH_FUNC) +/* + * string md5(string $str[, bool $raw_output = false]) + * Calculate the md5 hash of a string. + * Parameter + * $str + * Input string + * $raw_output + * If the optional raw_output is set to TRUE, then the md5 digest + * is instead returned in raw binary format with a length of 16. + * Return + * MD5 Hash as a 32-character hexadecimal string. + */ +static int jx9Builtin_md5(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + unsigned char zDigest[16]; + int raw_output = FALSE; + const void *pIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the input string */ + pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + if( nArg > 1 && jx9_value_is_bool(apArg[1])){ + raw_output = jx9_value_to_bool(apArg[1]); + } + /* Compute the MD5 digest */ + SyMD5Compute(pIn, (sxu32)nLen, zDigest); + if( raw_output ){ + /* Output raw digest */ + jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx); + } + return JX9_OK; +} +/* + * string sha1(string $str[, bool $raw_output = false]) + * Calculate the sha1 hash of a string. + * Parameter + * $str + * Input string + * $raw_output + * If the optional raw_output is set to TRUE, then the md5 digest + * is instead returned in raw binary format with a length of 16. + * Return + * SHA1 Hash as a 40-character hexadecimal string. + */ +static int jx9Builtin_sha1(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + unsigned char zDigest[20]; + int raw_output = FALSE; + const void *pIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the input string */ + pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + if( nArg > 1 && jx9_value_is_bool(apArg[1])){ + raw_output = jx9_value_to_bool(apArg[1]); + } + /* Compute the SHA1 digest */ + SySha1Compute(pIn, (sxu32)nLen, zDigest); + if( raw_output ){ + /* Output raw digest */ + jx9_result_string(pCtx, (const char *)zDigest, (int)sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), HashConsumer, pCtx); + } + return JX9_OK; +} +/* + * int64 crc32(string $str) + * Calculates the crc32 polynomial of a strin. + * Parameter + * $str + * Input string + * Return + * CRC32 checksum of the given input (64-bit integer). + */ +static int jx9Builtin_crc32(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const void *pIn; + sxu32 nCRC; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the input string */ + pIn = (const void *)jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty string */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Calculate the sum */ + nCRC = SyCrc32(pIn, (sxu32)nLen); + /* Return the CRC32 as 64-bit integer */ + jx9_result_int64(pCtx, (jx9_int64)nCRC^ 0xFFFFFFFF); + return JX9_OK; +} +#endif /* JX9_DISABLE_HASH_FUNC */ +/* + * Parse a CSV string and invoke the supplied callback for each processed xhunk. + */ +JX9_PRIVATE sxi32 jx9ProcessCsv( + const char *zInput, /* Raw input */ + int nByte, /* Input length */ + int delim, /* Delimiter */ + int encl, /* Enclosure */ + int escape, /* Escape character */ + sxi32 (*xConsumer)(const char *, int, void *), /* User callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + const char *zEnd = &zInput[nByte]; + const char *zIn = zInput; + const char *zPtr; + int isEnc; + /* Start processing */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + isEnc = 0; + zPtr = zIn; + /* Find the first delimiter */ + while( zIn < zEnd ){ + if( zIn[0] == delim && !isEnc){ + /* Delimiter found, break imediately */ + break; + }else if( zIn[0] == encl ){ + /* Inside enclosure? */ + isEnc = !isEnc; + }else if( zIn[0] == escape ){ + /* Escape sequence */ + zIn++; + } + /* Advance the cursor */ + zIn++; + } + if( zIn > zPtr ){ + int nByte = (int)(zIn-zPtr); + sxi32 rc; + /* Invoke the supllied callback */ + if( zPtr[0] == encl ){ + zPtr++; + nByte-=2; + } + if( nByte > 0 ){ + rc = xConsumer(zPtr, nByte, pUserData); + if( rc == SXERR_ABORT ){ + /* User callback request an operation abort */ + break; + } + } + } + /* Ignore trailing delimiter */ + while( zIn < zEnd && zIn[0] == delim ){ + zIn++; + } + } + return SXRET_OK; +} +/* + * Default consumer callback for the CSV parsing routine defined above. + * All the processed input is insereted into an array passed as the last + * argument to this callback. + */ +JX9_PRIVATE sxi32 jx9CsvConsumer(const char *zToken, int nTokenLen, void *pUserData) +{ + jx9_value *pArray = (jx9_value *)pUserData; + jx9_value sEntry; + SyString sToken; + /* Insert the token in the given array */ + SyStringInitFromBuf(&sToken, zToken, nTokenLen); + /* Remove trailing and leading white spcaces and null bytes */ + SyStringFullTrimSafe(&sToken); + if( sToken.nByte < 1){ + return SXRET_OK; + } + jx9MemObjInitFromString(pArray->pVm, &sEntry, &sToken); + jx9_array_add_elem(pArray, 0, &sEntry); + jx9MemObjRelease(&sEntry); + return SXRET_OK; +} +/* + * array str_getcsv(string $input[, string $delimiter = ', '[, string $enclosure = '"' [, string $escape='\\']]]) + * Parse a CSV string into an array. + * Parameters + * $input + * The string to parse. + * $delimiter + * Set the field delimiter (one character only). + * $enclosure + * Set the field enclosure character (one character only). + * $escape + * Set the escape character (one character only). Defaults as a backslash (\) + * Return + * An indexed array containing the CSV fields or NULL on failure. + */ +static int jx9Builtin_str_getcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zInput, *zPtr; + jx9_value *pArray; + int delim = ','; /* Delimiter */ + int encl = '"' ; /* Enclosure */ + int escape = '\\'; /* Escape character */ + int nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the raw input */ + zInput = jx9_value_to_string(apArg[0], &nLen); + if( nArg > 1 ){ + int i; + if( jx9_value_is_string(apArg[1]) ){ + /* Extract the delimiter */ + zPtr = jx9_value_to_string(apArg[1], &i); + if( i > 0 ){ + delim = zPtr[0]; + } + } + if( nArg > 2 ){ + if( jx9_value_is_string(apArg[2]) ){ + /* Extract the enclosure */ + zPtr = jx9_value_to_string(apArg[2], &i); + if( i > 0 ){ + encl = zPtr[0]; + } + } + if( nArg > 3 ){ + if( jx9_value_is_string(apArg[3]) ){ + /* Extract the escape character */ + zPtr = jx9_value_to_string(apArg[3], &i); + if( i > 0 ){ + escape = zPtr[0]; + } + } + } + } + } + /* Create our array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_null(pCtx); + return JX9_OK; + } + /* Parse the raw input */ + jx9ProcessCsv(zInput, nLen, delim, encl, escape, jx9CsvConsumer, pArray); + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * Extract a tag name from a raw HTML input and insert it in the given + * container. + * Refer to [strip_tags()]. + */ +static sxi32 AddTag(SySet *pSet, const char *zTag, int nByte) +{ + const char *zEnd = &zTag[nByte]; + const char *zPtr; + SyString sEntry; + /* Strip tags */ + for(;;){ + while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' + || zTag[0] == '!' || zTag[0] == '-' || ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ + zTag++; + } + if( zTag >= zEnd ){ + break; + } + zPtr = zTag; + /* Delimit the tag */ + while(zTag < zEnd ){ + if( (unsigned char)zTag[0] >= 0xc0 ){ + /* UTF-8 stream */ + zTag++; + SX_JMP_UTF8(zTag, zEnd); + }else if( !SyisAlphaNum(zTag[0]) ){ + break; + }else{ + zTag++; + } + } + if( zTag > zPtr ){ + /* Perform the insertion */ + SyStringInitFromBuf(&sEntry, zPtr, (int)(zTag-zPtr)); + SyStringFullTrim(&sEntry); + SySetPut(pSet, (const void *)&sEntry); + } + /* Jump the trailing '>' */ + zTag++; + } + return SXRET_OK; +} +/* + * Check if the given HTML tag name is present in the given container. + * Return SXRET_OK if present.SXERR_NOTFOUND otherwise. + * Refer to [strip_tags()]. + */ +static sxi32 FindTag(SySet *pSet, const char *zTag, int nByte) +{ + if( SySetUsed(pSet) > 0 ){ + const char *zCur, *zEnd = &zTag[nByte]; + SyString sTag; + while( zTag < zEnd && (zTag[0] == '<' || zTag[0] == '/' || zTag[0] == '?' || + ((unsigned char)zTag[0] < 0xc0 && SyisSpace(zTag[0]))) ){ + zTag++; + } + /* Delimit the tag */ + zCur = zTag; + while(zTag < zEnd ){ + if( (unsigned char)zTag[0] >= 0xc0 ){ + /* UTF-8 stream */ + zTag++; + SX_JMP_UTF8(zTag, zEnd); + }else if( !SyisAlphaNum(zTag[0]) ){ + break; + }else{ + zTag++; + } + } + SyStringInitFromBuf(&sTag, zCur, zTag-zCur); + /* Trim leading white spaces and null bytes */ + SyStringLeftTrimSafe(&sTag); + if( sTag.nByte > 0 ){ + SyString *aEntry, *pEntry; + sxi32 rc; + sxu32 n; + /* Perform the lookup */ + aEntry = (SyString *)SySetBasePtr(pSet); + for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ + pEntry = &aEntry[n]; + /* Do the comparison */ + rc = SyStringCmp(pEntry, &sTag, SyStrnicmp); + if( !rc ){ + return SXRET_OK; + } + } + } + } + /* No such tag */ + return SXERR_NOTFOUND; +} +/* + * This function tries to return a string [i.e: in the call context result buffer] + * with all NUL bytes, HTML and JX9 tags stripped from a given string. + * Refer to [strip_tags()]. + */ +JX9_PRIVATE sxi32 jx9StripTagsFromString(jx9_context *pCtx, const char *zIn, int nByte, const char *zTaglist, int nTaglen) +{ + const char *zEnd = &zIn[nByte]; + const char *zPtr, *zTag; + SySet sSet; + /* initialize the set of allowed tags */ + SySetInit(&sSet, &pCtx->pVm->sAllocator, sizeof(SyString)); + if( nTaglen > 0 ){ + /* Set of allowed tags */ + AddTag(&sSet, zTaglist, nTaglen); + } + /* Set the empty string */ + jx9_result_string(pCtx, "", 0); + /* Start processing */ + for(;;){ + if(zIn >= zEnd){ + /* No more input to process */ + break; + } + zPtr = zIn; + /* Find a tag */ + while( zIn < zEnd && zIn[0] != '<' && zIn[0] != 0 /* NUL byte */ ){ + zIn++; + } + if( zIn > zPtr ){ + /* Consume raw input */ + jx9_result_string(pCtx, zPtr, (int)(zIn-zPtr)); + } + /* Ignore trailing null bytes */ + while( zIn < zEnd && zIn[0] == 0 ){ + zIn++; + } + if(zIn >= zEnd){ + /* No more input to process */ + break; + } + if( zIn[0] == '<' ){ + sxi32 rc; + zTag = zIn++; + /* Delimit the tag */ + while( zIn < zEnd && zIn[0] != '>' ){ + zIn++; + } + if( zIn < zEnd ){ + zIn++; /* Ignore the trailing closing tag */ + } + /* Query the set */ + rc = FindTag(&sSet, zTag, (int)(zIn-zTag)); + if( rc == SXRET_OK ){ + /* Keep the tag */ + jx9_result_string(pCtx, zTag, (int)(zIn-zTag)); + } + } + } + /* Cleanup */ + SySetRelease(&sSet); + return SXRET_OK; +} +/* + * string strip_tags(string $str[, string $allowable_tags]) + * Strip HTML and JX9 tags from a string. + * Parameters + * $str + * The input string. + * $allowable_tags + * You can use the optional second parameter to specify tags which should not be stripped. + * Return + * Returns the stripped string. + */ +static int jx9Builtin_strip_tags(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zTaglist = 0; + const char *zString; + int nTaglen = 0; + int nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Point to the raw string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nArg > 1 && jx9_value_is_string(apArg[1]) ){ + /* Allowed tag */ + zTaglist = jx9_value_to_string(apArg[1], &nTaglen); + } + /* Process input */ + jx9StripTagsFromString(pCtx, zString, nLen, zTaglist, nTaglen); + return JX9_OK; +} +/* + * array str_split(string $string[, int $split_length = 1 ]) + * Convert a string to an array. + * Parameters + * $str + * The input string. + * $split_length + * Maximum length of the chunk. + * Return + * If the optional split_length parameter is specified, the returned array + * will be broken down into chunks with each being split_length in length, otherwise + * each chunk will be one character in length. FALSE is returned if split_length is less than 1. + * If the split_length length exceeds the length of string, the entire string is returned + * as the first (and only) array element. + */ +static int jx9Builtin_str_split(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zEnd; + jx9_value *pArray, *pValue; + int split_len; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target string */ + zString = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + split_len = (int)sizeof(char); + if( nArg > 1 ){ + /* Split length */ + split_len = jx9_value_to_int(apArg[1]); + if( split_len < 1 ){ + /* Invalid length, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( split_len > nLen ){ + split_len = nLen; + } + } + /* Create the array and the scalar value */ + pArray = jx9_context_new_array(pCtx); + /*Chunk value */ + pValue = jx9_context_new_scalar(pCtx); + if( pValue == 0 || pArray == 0 ){ + /* Return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the end of the string */ + zEnd = &zString[nLen]; + /* Perform the requested operation */ + for(;;){ + int nMax; + if( zString >= zEnd ){ + /* No more input to process */ + break; + } + nMax = (int)(zEnd-zString); + if( nMax < split_len ){ + split_len = nMax; + } + /* Copy the current chunk */ + jx9_value_string(pValue, zString, split_len); + /* Insert it */ + jx9_array_add_elem(pArray, 0, pValue); /* Will make it's own copy */ + /* reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + /* Update position */ + zString += split_len; + } + /* + * Return the array. + * Don't worry about freeing memory, everything will be automatically released + * upon we return from this function. + */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * Tokenize a raw string and extract the first non-space token. + * Refer to [strspn()]. + */ +static sxi32 ExtractNonSpaceToken(const char **pzIn, const char *zEnd, SyString *pOut) +{ + const char *zIn = *pzIn; + const char *zPtr; + /* Ignore leading white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + /* End of input */ + return SXERR_EOF; + } + zPtr = zIn; + /* Extract the token */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && !SyisSpace(zIn[0]) ){ + zIn++; + } + SyStringInitFromBuf(pOut, zPtr, zIn-zPtr); + /* Synchronize pointers */ + *pzIn = zIn; + /* Return to the caller */ + return SXRET_OK; +} +/* + * Check if the given string contains only characters from the given mask. + * return the longest match. + * Refer to [strspn()]. + */ +static int LongestStringMask(const char *zString, int nLen, const char *zMask, int nMaskLen) +{ + const char *zEnd = &zString[nLen]; + const char *zIn = zString; + int i, c; + for(;;){ + if( zString >= zEnd ){ + break; + } + /* Extract current character */ + c = zString[0]; + /* Perform the lookup */ + for( i = 0 ; i < nMaskLen ; i++ ){ + if( c == zMask[i] ){ + /* Character found */ + break; + } + } + if( i >= nMaskLen ){ + /* Character not in the current mask, break immediately */ + break; + } + /* Advance cursor */ + zString++; + } + /* Longest match */ + return (int)(zString-zIn); +} +/* + * Do the reverse operation of the previous function [i.e: LongestStringMask()]. + * Refer to [strcspn()]. + */ +static int LongestStringMask2(const char *zString, int nLen, const char *zMask, int nMaskLen) +{ + const char *zEnd = &zString[nLen]; + const char *zIn = zString; + int i, c; + for(;;){ + if( zString >= zEnd ){ + break; + } + /* Extract current character */ + c = zString[0]; + /* Perform the lookup */ + for( i = 0 ; i < nMaskLen ; i++ ){ + if( c == zMask[i] ){ + break; + } + } + if( i < nMaskLen ){ + /* Character in the current mask, break immediately */ + break; + } + /* Advance cursor */ + zString++; + } + /* Longest match */ + return (int)(zString-zIn); +} +/* + * int strspn(string $str, string $mask[, int $start[, int $length]]) + * Finds the length of the initial segment of a string consisting entirely + * of characters contained within a given mask. + * Parameters + * $str + * The input string. + * $mask + * The list of allowable characters. + * $start + * The position in subject to start searching. + * If start is given and is non-negative, then strspn() will begin examining + * subject at the start'th position. For instance, in the string 'abcdef', the character + * at position 0 is 'a', the character at position 2 is 'c', and so forth. + * If start is given and is negative, then strspn() will begin examining subject at the + * start'th position from the end of subject. + * $length + * The length of the segment from subject to examine. + * If length is given and is non-negative, then subject will be examined for length + * characters after the starting position. + * If lengthis given and is negative, then subject will be examined from the starting + * position up to length characters from the end of subject. + * Return + * Returns the length of the initial segment of subject which consists entirely of characters + * in mask. + */ +static int jx9Builtin_strspn(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zMask, *zEnd; + int iMasklen, iLen; + SyString sToken; + int iCount = 0; + int rc; + if( nArg < 2 ){ + /* Missing agruments, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &iLen); + /* Extract the mask */ + zMask = jx9_value_to_string(apArg[1], &iMasklen); + if( iLen < 1 || iMasklen < 1 ){ + /* Nothing to process, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( nArg > 2 ){ + int nOfft; + /* Extract the offset */ + nOfft = jx9_value_to_int(apArg[2]); + if( nOfft < 0 ){ + const char *zBase = &zString[iLen + nOfft]; + if( zBase > zString ){ + iLen = (int)(&zString[iLen]-zBase); + zString = zBase; + }else{ + /* Invalid offset */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + }else{ + if( nOfft >= iLen ){ + /* Invalid offset */ + jx9_result_int(pCtx, 0); + return JX9_OK; + }else{ + /* Update offset */ + zString += nOfft; + iLen -= nOfft; + } + } + if( nArg > 3 ){ + int iUserlen; + /* Extract the desired length */ + iUserlen = jx9_value_to_int(apArg[3]); + if( iUserlen > 0 && iUserlen < iLen ){ + iLen = iUserlen; + } + } + } + /* Point to the end of the string */ + zEnd = &zString[iLen]; + /* Extract the first non-space token */ + rc = ExtractNonSpaceToken(&zString, zEnd, &sToken); + if( rc == SXRET_OK && sToken.nByte > 0 ){ + /* Compare against the current mask */ + iCount = LongestStringMask(sToken.zString, (int)sToken.nByte, zMask, iMasklen); + } + /* Longest match */ + jx9_result_int(pCtx, iCount); + return JX9_OK; +} +/* + * int strcspn(string $str, string $mask[, int $start[, int $length]]) + * Find length of initial segment not matching mask. + * Parameters + * $str + * The input string. + * $mask + * The list of not allowed characters. + * $start + * The position in subject to start searching. + * If start is given and is non-negative, then strspn() will begin examining + * subject at the start'th position. For instance, in the string 'abcdef', the character + * at position 0 is 'a', the character at position 2 is 'c', and so forth. + * If start is given and is negative, then strspn() will begin examining subject at the + * start'th position from the end of subject. + * $length + * The length of the segment from subject to examine. + * If length is given and is non-negative, then subject will be examined for length + * characters after the starting position. + * If lengthis given and is negative, then subject will be examined from the starting + * position up to length characters from the end of subject. + * Return + * Returns the length of the segment as an integer. + */ +static int jx9Builtin_strcspn(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zMask, *zEnd; + int iMasklen, iLen; + SyString sToken; + int iCount = 0; + int rc; + if( nArg < 2 ){ + /* Missing agruments, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zString = jx9_value_to_string(apArg[0], &iLen); + /* Extract the mask */ + zMask = jx9_value_to_string(apArg[1], &iMasklen); + if( iLen < 1 ){ + /* Nothing to process, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( iMasklen < 1 ){ + /* No given mask, return the string length */ + jx9_result_int(pCtx, iLen); + return JX9_OK; + } + if( nArg > 2 ){ + int nOfft; + /* Extract the offset */ + nOfft = jx9_value_to_int(apArg[2]); + if( nOfft < 0 ){ + const char *zBase = &zString[iLen + nOfft]; + if( zBase > zString ){ + iLen = (int)(&zString[iLen]-zBase); + zString = zBase; + }else{ + /* Invalid offset */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + }else{ + if( nOfft >= iLen ){ + /* Invalid offset */ + jx9_result_int(pCtx, 0); + return JX9_OK; + }else{ + /* Update offset */ + zString += nOfft; + iLen -= nOfft; + } + } + if( nArg > 3 ){ + int iUserlen; + /* Extract the desired length */ + iUserlen = jx9_value_to_int(apArg[3]); + if( iUserlen > 0 && iUserlen < iLen ){ + iLen = iUserlen; + } + } + } + /* Point to the end of the string */ + zEnd = &zString[iLen]; + /* Extract the first non-space token */ + rc = ExtractNonSpaceToken(&zString, zEnd, &sToken); + if( rc == SXRET_OK && sToken.nByte > 0 ){ + /* Compare against the current mask */ + iCount = LongestStringMask2(sToken.zString, (int)sToken.nByte, zMask, iMasklen); + } + /* Longest match */ + jx9_result_int(pCtx, iCount); + return JX9_OK; +} +/* + * string strpbrk(string $haystack, string $char_list) + * Search a string for any of a set of characters. + * Parameters + * $haystack + * The string where char_list is looked for. + * $char_list + * This parameter is case sensitive. + * Return + * Returns a string starting from the character found, or FALSE if it is not found. + */ +static int jx9Builtin_strpbrk(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zList, *zEnd; + int iLen, iListLen, i, c; + sxu32 nOfft, nMax; + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the haystack and the char list */ + zString = jx9_value_to_string(apArg[0], &iLen); + zList = jx9_value_to_string(apArg[1], &iListLen); + if( iLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the end of the string */ + zEnd = &zString[iLen]; + nOfft = nMax = SXU32_HIGH; + /* perform the requested operation */ + for( i = 0 ; i < iListLen ; i++ ){ + c = zList[i]; + rc = SyByteFind(zString, (sxu32)iLen, c, &nMax); + if( rc == SXRET_OK ){ + if( nMax < nOfft ){ + nOfft = nMax; + } + } + } + if( nOfft == SXU32_HIGH ){ + /* No such substring, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Return the substring */ + jx9_result_string(pCtx, &zString[nOfft], (int)(zEnd-&zString[nOfft])); + } + return JX9_OK; +} +/* + * string soundex(string $str) + * Calculate the soundex key of a string. + * Parameters + * $str + * The input string. + * Return + * Returns the soundex key as a string. + * Note: + * This implementation is based on the one found in the SQLite3 + * source tree. + */ +static int jx9Builtin_soundex(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn; + char zResult[8]; + int i, j; + static const unsigned char iCode[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0, + 1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0, + }; + if( nArg < 1 ){ + /* Missing arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + zIn = (unsigned char *)jx9_value_to_string(apArg[0], 0); + for(i=0; zIn[i] && zIn[i] < 0xc0 && !SyisAlpha(zIn[i]); i++){} + if( zIn[i] ){ + unsigned char prevcode = iCode[zIn[i]&0x7f]; + zResult[0] = (char)SyToUpper(zIn[i]); + for(j=1; j<4 && zIn[i]; i++){ + int code = iCode[zIn[i]&0x7f]; + if( code>0 ){ + if( code!=prevcode ){ + prevcode = (unsigned char)code; + zResult[j++] = (char)code + '0'; + } + }else{ + prevcode = 0; + } + } + while( j<4 ){ + zResult[j++] = '0'; + } + jx9_result_string(pCtx, zResult, 4); + }else{ + jx9_result_string(pCtx, "?000", 4); + } + return JX9_OK; +} +/* + * string wordwrap(string $str[, int $width = 75[, string $break = "\n"]]) + * Wraps a string to a given number of characters. + * Parameters + * $str + * The input string. + * $width + * The column width. + * $break + * The line is broken using the optional break parameter. + * Return + * Returns the given string wrapped at the specified column. + */ +static int jx9Builtin_wordwrap(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn, *zEnd, *zBreak; + int iLen, iBreaklen, iChunk; + if( nArg < 1 ){ + /* Missing arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the input string */ + zIn = jx9_value_to_string(apArg[0], &iLen); + if( iLen < 1 ){ + /* Nothing to process, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Chunk length */ + iChunk = 75; + iBreaklen = 0; + zBreak = ""; /* cc warning */ + if( nArg > 1 ){ + iChunk = jx9_value_to_int(apArg[1]); + if( iChunk < 1 ){ + iChunk = 75; + } + if( nArg > 2 ){ + zBreak = jx9_value_to_string(apArg[2], &iBreaklen); + } + } + if( iBreaklen < 1 ){ + /* Set a default column break */ +#ifdef __WINNT__ + zBreak = "\r\n"; + iBreaklen = (int)sizeof("\r\n")-1; +#else + zBreak = "\n"; + iBreaklen = (int)sizeof(char); +#endif + } + /* Perform the requested operation */ + zEnd = &zIn[iLen]; + for(;;){ + int nMax; + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + nMax = (int)(zEnd-zIn); + if( iChunk > nMax ){ + iChunk = nMax; + } + /* Append the column first */ + jx9_result_string(pCtx, zIn, iChunk); /* Will make it's own copy */ + /* Advance the cursor */ + zIn += iChunk; + if( zIn < zEnd ){ + /* Append the line break */ + jx9_result_string(pCtx, zBreak, iBreaklen); + } + } + return JX9_OK; +} +/* + * Check if the given character is a member of the given mask. + * Return TRUE on success. FALSE otherwise. + * Refer to [strtok()]. + */ +static int CheckMask(int c, const char *zMask, int nMasklen, int *pOfft) +{ + int i; + for( i = 0 ; i < nMasklen ; ++i ){ + if( c == zMask[i] ){ + if( pOfft ){ + *pOfft = i; + } + return TRUE; + } + } + return FALSE; +} +/* + * Extract a single token from the input stream. + * Refer to [strtok()]. + */ +static sxi32 ExtractToken(const char **pzIn, const char *zEnd, const char *zMask, int nMasklen, SyString *pOut) +{ + const char *zIn = *pzIn; + const char *zPtr; + /* Ignore leading delimiter */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && CheckMask(zIn[0], zMask, nMasklen, 0) ){ + zIn++; + } + if( zIn >= zEnd ){ + /* End of input */ + return SXERR_EOF; + } + zPtr = zIn; + /* Extract the token */ + while( zIn < zEnd ){ + if( (unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + SX_JMP_UTF8(zIn, zEnd); + }else{ + if( CheckMask(zIn[0], zMask, nMasklen, 0) ){ + break; + } + zIn++; + } + } + SyStringInitFromBuf(pOut, zPtr, zIn-zPtr); + /* Update the cursor */ + *pzIn = zIn; + /* Return to the caller */ + return SXRET_OK; +} +/* strtok auxiliary private data */ +typedef struct strtok_aux_data strtok_aux_data; +struct strtok_aux_data +{ + const char *zDup; /* Complete duplicate of the input */ + const char *zIn; /* Current input stream */ + const char *zEnd; /* End of input */ +}; +/* + * string strtok(string $str, string $token) + * string strtok(string $token) + * strtok() splits a string (str) into smaller strings (tokens), with each token + * being delimited by any character from token. That is, if you have a string like + * "This is an example string" you could tokenize this string into its individual + * words by using the space character as the token. + * Note that only the first call to strtok uses the string argument. Every subsequent + * call to strtok only needs the token to use, as it keeps track of where it is in + * the current string. To start over, or to tokenize a new string you simply call strtok + * with the string argument again to initialize it. Note that you may put multiple tokens + * in the token parameter. The string will be tokenized when any one of the characters in + * the argument are found. + * Parameters + * $str + * The string being split up into smaller strings (tokens). + * $token + * The delimiter used when splitting up str. + * Return + * Current token or FALSE on EOF. + */ +static int jx9Builtin_strtok(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + strtok_aux_data *pAux; + const char *zMask; + SyString sToken; + int nMasklen; + sxi32 rc; + if( nArg < 2 ){ + /* Extract top aux data */ + pAux = (strtok_aux_data *)jx9_context_peek_aux_data(pCtx); + if( pAux == 0 ){ + /* No aux data, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nMasklen = 0; + zMask = ""; /* cc warning */ + if( nArg > 0 ){ + /* Extract the mask */ + zMask = jx9_value_to_string(apArg[0], &nMasklen); + } + if( nMasklen < 1 ){ + /* Invalid mask, return FALSE */ + jx9_context_free_chunk(pCtx, (void *)pAux->zDup); + jx9_context_free_chunk(pCtx, pAux); + (void)jx9_context_pop_aux_data(pCtx); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the token */ + rc = ExtractToken(&pAux->zIn, pAux->zEnd, zMask, nMasklen, &sToken); + if( rc != SXRET_OK ){ + /* EOF , discard the aux data */ + jx9_context_free_chunk(pCtx, (void *)pAux->zDup); + jx9_context_free_chunk(pCtx, pAux); + (void)jx9_context_pop_aux_data(pCtx); + jx9_result_bool(pCtx, 0); + }else{ + /* Return the extracted token */ + jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte); + } + }else{ + const char *zInput, *zCur; + char *zDup; + int nLen; + /* Extract the raw input */ + zCur = zInput = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Empty input, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the mask */ + zMask = jx9_value_to_string(apArg[1], &nMasklen); + if( nMasklen < 1 ){ + /* Set a default mask */ +#define TOK_MASK " \n\t\r\f" + zMask = TOK_MASK; + nMasklen = (int)sizeof(TOK_MASK) - 1; +#undef TOK_MASK + } + /* Extract a single token */ + rc = ExtractToken(&zInput, &zInput[nLen], zMask, nMasklen, &sToken); + if( rc != SXRET_OK ){ + /* Empty input */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + }else{ + /* Return the extracted token */ + jx9_result_string(pCtx, sToken.zString, (int)sToken.nByte); + } + /* Create our auxilliary data and copy the input */ + pAux = (strtok_aux_data *)jx9_context_alloc_chunk(pCtx, sizeof(strtok_aux_data), TRUE, FALSE); + if( pAux ){ + nLen -= (int)(zInput-zCur); + if( nLen < 1 ){ + jx9_context_free_chunk(pCtx, pAux); + return JX9_OK; + } + /* Duplicate input */ + zDup = (char *)jx9_context_alloc_chunk(pCtx, (unsigned int)(nLen+1), TRUE, FALSE); + if( zDup ){ + SyMemcpy(zInput, zDup, (sxu32)nLen); + /* Register the aux data */ + pAux->zDup = pAux->zIn = zDup; + pAux->zEnd = &zDup[nLen]; + jx9_context_push_aux_data(pCtx, pAux); + } + } + } + return JX9_OK; +} +/* + * string str_pad(string $input, int $pad_length[, string $pad_string = " " [, int $pad_type = STR_PAD_RIGHT]]) + * Pad a string to a certain length with another string + * Parameters + * $input + * The input string. + * $pad_length + * If the value of pad_length is negative, less than, or equal to the length of the input + * string, no padding takes place. + * $pad_string + * Note: + * The pad_string WIIL NOT BE truncated if the required number of padding characters can't be evenly + * divided by the pad_string's length. + * $pad_type + * Optional argument pad_type can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type + * is not specified it is assumed to be STR_PAD_RIGHT. + * Return + * The padded string. + */ +static int jx9Builtin_str_pad(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int iLen, iPadlen, iType, i, iDiv, iStrpad, iRealPad, jPad; + const char *zIn, *zPad; + if( nArg < 2 ){ + /* Missing arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = jx9_value_to_string(apArg[0], &iLen); + /* Padding length */ + iRealPad = iPadlen = jx9_value_to_int(apArg[1]); + if( iPadlen > 0 ){ + iPadlen -= iLen; + } + if( iPadlen < 1 ){ + /* Return the string verbatim */ + jx9_result_string(pCtx, zIn, iLen); + return JX9_OK; + } + zPad = " "; /* Whitespace padding */ + iStrpad = (int)sizeof(char); + iType = 1 ; /* STR_PAD_RIGHT */ + if( nArg > 2 ){ + /* Padding string */ + zPad = jx9_value_to_string(apArg[2], &iStrpad); + if( iStrpad < 1 ){ + /* Empty string */ + zPad = " "; /* Whitespace padding */ + iStrpad = (int)sizeof(char); + } + if( nArg > 3 ){ + /* Padd type */ + iType = jx9_value_to_int(apArg[3]); + if( iType != 0 /* STR_PAD_LEFT */ && iType != 2 /* STR_PAD_BOTH */ ){ + iType = 1 ; /* STR_PAD_RIGHT */ + } + } + } + iDiv = 1; + if( iType == 2 ){ + iDiv = 2; /* STR_PAD_BOTH */ + } + /* Perform the requested operation */ + if( iType == 0 /* STR_PAD_LEFT */ || iType == 2 /* STR_PAD_BOTH */ ){ + jPad = iStrpad; + for( i = 0 ; i < iPadlen/iDiv ; i += jPad ){ + /* Padding */ + if( (int)jx9_context_result_buf_length(pCtx) + iLen + jPad >= iRealPad ){ + break; + } + jx9_result_string(pCtx, zPad, jPad); + } + if( iType == 0 /* STR_PAD_LEFT */ ){ + while( (int)jx9_context_result_buf_length(pCtx) + iLen < iRealPad ){ + jPad = iRealPad - (iLen + (int)jx9_context_result_buf_length(pCtx) ); + if( jPad > iStrpad ){ + jPad = iStrpad; + } + if( jPad < 1){ + break; + } + jx9_result_string(pCtx, zPad, jPad); + } + } + } + if( iLen > 0 ){ + /* Append the input string */ + jx9_result_string(pCtx, zIn, iLen); + } + if( iType == 1 /* STR_PAD_RIGHT */ || iType == 2 /* STR_PAD_BOTH */ ){ + for( i = 0 ; i < iPadlen/iDiv ; i += iStrpad ){ + /* Padding */ + if( (int)jx9_context_result_buf_length(pCtx) + iStrpad >= iRealPad ){ + break; + } + jx9_result_string(pCtx, zPad, iStrpad); + } + while( (int)jx9_context_result_buf_length(pCtx) < iRealPad ){ + jPad = iRealPad - (int)jx9_context_result_buf_length(pCtx); + if( jPad > iStrpad ){ + jPad = iStrpad; + } + if( jPad < 1){ + break; + } + jx9_result_string(pCtx, zPad, jPad); + } + } + return JX9_OK; +} +/* + * String replacement private data. + */ +typedef struct str_replace_data str_replace_data; +struct str_replace_data +{ + /* The following two fields are only used by the strtr function */ + SyBlob *pWorker; /* Working buffer */ + ProcStringMatch xMatch; /* Pattern match routine */ + /* The following two fields are only used by the str_replace function */ + SySet *pCollector; /* Argument collector*/ + jx9_context *pCtx; /* Call context */ +}; +/* + * Remove a substring. + */ +#define STRDEL(SRC, SLEN, OFFT, ILEN){\ + for(;;){\ + if( OFFT + ILEN >= SLEN ) break; SRC[OFFT] = SRC[OFFT+ILEN]; ++OFFT;\ + }\ +} +/* + * Shift right and insert algorithm. + */ +#define SHIFTRANDINSERT(SRC, LEN, OFFT, ENTRY, ELEN){\ + sxu32 INLEN = LEN - OFFT;\ + for(;;){\ + if( LEN > 0 ){ LEN--; } if(INLEN < 1 ) break; SRC[LEN + ELEN] = SRC[LEN] ; --INLEN; \ + }\ + for(;;){\ + if(ELEN < 1)break; SRC[OFFT] = ENTRY[0]; OFFT++; ENTRY++; --ELEN;\ + }\ +} +/* + * Replace all occurrences of the search string at offset (nOfft) with the given + * replacement string [i.e: zReplace]. + */ +static int StringReplace(SyBlob *pWorker, sxu32 nOfft, int nLen, const char *zReplace, int nReplen) +{ + char *zInput = (char *)SyBlobData(pWorker); + sxu32 n, m; + n = SyBlobLength(pWorker); + m = nOfft; + /* Delete the old entry */ + STRDEL(zInput, n, m, nLen); + SyBlobLength(pWorker) -= nLen; + if( nReplen > 0 ){ + sxi32 iRep = nReplen; + sxi32 rc; + /* + * Make sure the working buffer is big enough to hold the replacement + * string. + */ + rc = SyBlobAppend(pWorker, 0/* Grow without an append operation*/, (sxu32)nReplen); + if( rc != SXRET_OK ){ + /* Simply ignore any memory failure problem */ + return SXRET_OK; + } + /* Perform the insertion now */ + zInput = (char *)SyBlobData(pWorker); + n = SyBlobLength(pWorker); + SHIFTRANDINSERT(zInput, n, nOfft, zReplace, iRep); + SyBlobLength(pWorker) += nReplen; + } + return SXRET_OK; +} +/* + * String replacement walker callback. + * The following callback is invoked for each array entry that hold + * the replace string. + * Refer to the strtr() implementation for more information. + */ +static int StringReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData) +{ + str_replace_data *pRepData = (str_replace_data *)pUserData; + const char *zTarget, *zReplace; + SyBlob *pWorker; + int tLen, nLen; + sxu32 nOfft; + sxi32 rc; + /* Point to the working buffer */ + pWorker = pRepData->pWorker; + if( !jx9_value_is_string(pKey) ){ + /* Target and replace must be a string */ + return JX9_OK; + } + /* Extract the target and the replace */ + zTarget = jx9_value_to_string(pKey, &tLen); + if( tLen < 1 ){ + /* Empty target, return immediately */ + return JX9_OK; + } + /* Perform a pattern search */ + rc = pRepData->xMatch(SyBlobData(pWorker), SyBlobLength(pWorker), (const void *)zTarget, (sxu32)tLen, &nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found */ + return JX9_OK; + } + /* Extract the replace string */ + zReplace = jx9_value_to_string(pData, &nLen); + /* Perform the replace process */ + StringReplace(pWorker, nOfft, tLen, zReplace, nLen); + /* All done */ + return JX9_OK; +} +/* + * The following walker callback is invoked by the str_rplace() function inorder + * to collect search/replace string. + * This callback is invoked only if the given argument is of type array. + */ +static int StrReplaceWalker(jx9_value *pKey, jx9_value *pData, void *pUserData) +{ + str_replace_data *pRep = (str_replace_data *)pUserData; + SyString sWorker; + const char *zIn; + int nByte; + /* Extract a string representation of the given argument */ + zIn = jx9_value_to_string(pData, &nByte); + SyStringInitFromBuf(&sWorker, 0, 0); + if( nByte > 0 ){ + char *zDup; + /* Duplicate the chunk */ + zDup = (char *)jx9_context_alloc_chunk(pRep->pCtx, (unsigned int)nByte, FALSE, + TRUE /* Release the chunk automatically, upon this context is destroyd */ + ); + if( zDup == 0 ){ + /* Ignore any memory failure problem */ + jx9_context_throw_error(pRep->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + return JX9_OK; + } + SyMemcpy(zIn, zDup, (sxu32)nByte); + /* Save the chunk */ + SyStringInitFromBuf(&sWorker, zDup, nByte); + } + /* Save for later processing */ + SySetPut(pRep->pCollector, (const void *)&sWorker); + /* All done */ + SXUNUSED(pKey); /* cc warning */ + return JX9_OK; +} +/* + * mixed str_replace(mixed $search, mixed $replace, mixed $subject[, int &$count ]) + * mixed str_ireplace(mixed $search, mixed $replace, mixed $subject[, int &$count ]) + * Replace all occurrences of the search string with the replacement string. + * Parameters + * If search and replace are arrays, then str_replace() takes a value from each + * array and uses them to search and replace on subject. If replace has fewer values + * than search, then an empty string is used for the rest of replacement values. + * If search is an array and replace is a string, then this replacement string is used + * for every value of search. The converse would not make sense, though. + * If search or replace are arrays, their elements are processed first to last. + * $search + * The value being searched for, otherwise known as the needle. An array may be used + * to designate multiple needles. + * $replace + * The replacement value that replaces found search values. An array may be used + * to designate multiple replacements. + * $subject + * The string or array being searched and replaced on, otherwise known as the haystack. + * If subject is an array, then the search and replace is performed with every entry + * of subject, and the return value is an array as well. + * $count (Not used) + * If passed, this will be set to the number of replacements performed. + * Return + * This function returns a string or an array with the replaced values. + */ +static int jx9Builtin_str_replace(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyString sTemp, *pSearch, *pReplace; + ProcStringMatch xMatch; + const char *zIn, *zFunc; + str_replace_data sRep; + SyBlob sWorker; + SySet sReplace; + SySet sSearch; + int rep_str; + int nByte; + sxi32 rc; + if( nArg < 3 ){ + /* Missing/Invalid arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Initialize fields */ + SySetInit(&sSearch, &pCtx->pVm->sAllocator, sizeof(SyString)); + SySetInit(&sReplace, &pCtx->pVm->sAllocator, sizeof(SyString)); + SyBlobInit(&sWorker, &pCtx->pVm->sAllocator); + SyZero(&sRep, sizeof(str_replace_data)); + sRep.pCtx = pCtx; + sRep.pCollector = &sSearch; + rep_str = 0; + /* Extract the subject */ + zIn = jx9_value_to_string(apArg[2], &nByte); + if( nByte < 1 ){ + /* Nothing to replace, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Copy the subject */ + SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nByte); + /* Search string */ + if( jx9_value_is_json_array(apArg[0]) ){ + /* Collect search string */ + jx9_array_walk(apArg[0], StrReplaceWalker, &sRep); + }else{ + /* Single pattern */ + zIn = jx9_value_to_string(apArg[0], &nByte); + if( nByte < 1 ){ + /* Return the subject untouched since no search string is available */ + jx9_result_value(pCtx, apArg[2]/* Subject as thrird argument*/); + return JX9_OK; + } + SyStringInitFromBuf(&sTemp, zIn, nByte); + /* Save for later processing */ + SySetPut(&sSearch, (const void *)&sTemp); + } + /* Replace string */ + if( jx9_value_is_json_array(apArg[1]) ){ + /* Collect replace string */ + sRep.pCollector = &sReplace; + jx9_array_walk(apArg[1], StrReplaceWalker, &sRep); + }else{ + /* Single needle */ + zIn = jx9_value_to_string(apArg[1], &nByte); + rep_str = 1; + SyStringInitFromBuf(&sTemp, zIn, nByte); + /* Save for later processing */ + SySetPut(&sReplace, (const void *)&sTemp); + } + /* Reset loop cursors */ + SySetResetCursor(&sSearch); + SySetResetCursor(&sReplace); + pReplace = pSearch = 0; /* cc warning */ + SyStringInitFromBuf(&sTemp, "", 0); + /* Extract function name */ + zFunc = jx9_function_name(pCtx); + /* Set the default pattern match routine */ + xMatch = SyBlobSearch; + if( SyStrncmp(zFunc, "str_ireplace", sizeof("str_ireplace") - 1) == 0 ){ + /* Case insensitive pattern match */ + xMatch = iPatternMatch; + } + /* Start the replace process */ + while( SXRET_OK == SySetGetNextEntry(&sSearch, (void **)&pSearch) ){ + sxu32 nCount, nOfft; + if( pSearch->nByte < 1 ){ + /* Empty string, ignore */ + continue; + } + /* Extract the replace string */ + if( rep_str ){ + pReplace = (SyString *)SySetPeek(&sReplace); + }else{ + if( SXRET_OK != SySetGetNextEntry(&sReplace, (void **)&pReplace) ){ + /* Sepecial case when 'replace set' has fewer values than the search set. + * An empty string is used for the rest of replacement values + */ + pReplace = 0; + } + } + if( pReplace == 0 ){ + /* Use an empty string instead */ + pReplace = &sTemp; + } + nOfft = nCount = 0; + for(;;){ + if( nCount >= SyBlobLength(&sWorker) ){ + break; + } + /* Perform a pattern lookup */ + rc = xMatch(SyBlobDataAt(&sWorker, nCount), SyBlobLength(&sWorker) - nCount, (const void *)pSearch->zString, + pSearch->nByte, &nOfft); + if( rc != SXRET_OK ){ + /* Pattern not found */ + break; + } + /* Perform the replace operation */ + StringReplace(&sWorker, nCount+nOfft, (int)pSearch->nByte, pReplace->zString, (int)pReplace->nByte); + /* Increment offset counter */ + nCount += nOfft + pReplace->nByte; + } + } + /* All done, clean-up the mess left behind */ + jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker), (int)SyBlobLength(&sWorker)); + SySetRelease(&sSearch); + SySetRelease(&sReplace); + SyBlobRelease(&sWorker); + return JX9_OK; +} +/* + * string strtr(string $str, string $from, string $to) + * string strtr(string $str, array $replace_pairs) + * Translate characters or replace substrings. + * Parameters + * $str + * The string being translated. + * $from + * The string being translated to to. + * $to + * The string replacing from. + * $replace_pairs + * The replace_pairs parameter may be used instead of to and + * from, in which case it's an array in the form array('from' => 'to', ...). + * Return + * The translated string. + * If replace_pairs contains a key which is an empty string (""), FALSE will be returned. + */ +static int jx9Builtin_strtr(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Nothing to replace, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 || nArg < 2 ){ + /* Invalid arguments */ + jx9_result_string(pCtx, zIn, nLen); + return JX9_OK; + } + if( nArg == 2 && jx9_value_is_json_array(apArg[1]) ){ + str_replace_data sRepData; + SyBlob sWorker; + /* Initilaize the working buffer */ + SyBlobInit(&sWorker, &pCtx->pVm->sAllocator); + /* Copy raw string */ + SyBlobAppend(&sWorker, (const void *)zIn, (sxu32)nLen); + /* Init our replace data instance */ + sRepData.pWorker = &sWorker; + sRepData.xMatch = SyBlobSearch; + /* Iterate throw array entries and perform the replace operation.*/ + jx9_array_walk(apArg[1], StringReplaceWalker, &sRepData); + /* All done, return the result string */ + jx9_result_string(pCtx, (const char *)SyBlobData(&sWorker), + (int)SyBlobLength(&sWorker)); /* Will make it's own copy */ + /* Clean-up */ + SyBlobRelease(&sWorker); + }else{ + int i, flen, tlen, c, iOfft; + const char *zFrom, *zTo; + if( nArg < 3 ){ + /* Nothing to replace */ + jx9_result_string(pCtx, zIn, nLen); + return JX9_OK; + } + /* Extract given arguments */ + zFrom = jx9_value_to_string(apArg[1], &flen); + zTo = jx9_value_to_string(apArg[2], &tlen); + if( flen < 1 || tlen < 1 ){ + /* Nothing to replace */ + jx9_result_string(pCtx, zIn, nLen); + return JX9_OK; + } + /* Start the replace process */ + for( i = 0 ; i < nLen ; ++i ){ + c = zIn[i]; + if( CheckMask(c, zFrom, flen, &iOfft) ){ + if ( iOfft < tlen ){ + c = zTo[iOfft]; + } + } + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + + } + } + return JX9_OK; +} +/* + * Parse an INI string. + * According to wikipedia + * The INI file format is an informal standard for configuration files for some platforms or software. + * INI files are simple text files with a basic structure composed of "sections" and "properties". + * Format +* Properties +* The basic element contained in an INI file is the property. Every property has a name and a value +* delimited by an equals sign (=). The name appears to the left of the equals sign. +* Example: +* name=value +* Sections +* Properties may be grouped into arbitrarily named sections. The section name appears on a line by itself +* in square brackets ([ and ]). All properties after the section declaration are associated with that section. +* There is no explicit "end of section" delimiter; sections end at the next section declaration +* or the end of the file. Sections may not be nested. +* Example: +* [section] +* Comments +* Semicolons (;) at the beginning of the line indicate a comment. Comment lines are ignored. +* This function return an array holding parsed values on success.FALSE otherwise. +*/ +JX9_PRIVATE sxi32 jx9ParseIniString(jx9_context *pCtx, const char *zIn, sxu32 nByte, int bProcessSection) +{ + jx9_value *pCur, *pArray, *pSection, *pWorker, *pValue; + const char *zCur, *zEnd = &zIn[nByte]; + SyHashEntry *pEntry; + SyString sEntry; + SyHash sHash; + int c; + /* Create an empty array and worker variables */ + pArray = jx9_context_new_array(pCtx); + pWorker = jx9_context_new_scalar(pCtx); + pValue = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pWorker == 0 || pValue == 0){ + /* Out of memory */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + /* Return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + SyHashInit(&sHash, &pCtx->pVm->sAllocator, 0, 0); + pCur = pArray; + /* Start the parse process */ + for(;;){ + /* Ignore leading white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0])){ + zIn++; + } + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + if( zIn[0] == ';' || zIn[0] == '#' ){ + /* Comment til the end of line */ + zIn++; + while(zIn < zEnd && zIn[0] != '\n' ){ + zIn++; + } + continue; + } + /* Reset the string cursor of the working variable */ + jx9_value_reset_string_cursor(pWorker); + if( zIn[0] == '[' ){ + /* Section: Extract the section name */ + zIn++; + zCur = zIn; + while( zIn < zEnd && zIn[0] != ']' ){ + zIn++; + } + if( zIn > zCur && bProcessSection ){ + /* Save the section name */ + SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur)); + SyStringFullTrim(&sEntry); + jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); + if( sEntry.nByte > 0 ){ + /* Associate an array with the section */ + pSection = jx9_context_new_array(pCtx); + if( pSection ){ + jx9_array_add_elem(pArray, pWorker/*Section name*/, pSection); + pCur = pSection; + } + } + } + zIn++; /* Trailing square brackets ']' */ + }else{ + jx9_value *pOldCur; + int is_array; + int iLen; + /* Properties */ + is_array = 0; + zCur = zIn; + iLen = 0; /* cc warning */ + pOldCur = pCur; + while( zIn < zEnd && zIn[0] != '=' ){ + if( zIn[0] == '[' && !is_array ){ + /* Array */ + iLen = (int)(zIn-zCur); + is_array = 1; + if( iLen > 0 ){ + jx9_value *pvArr = 0; /* cc warning */ + /* Query the hashtable */ + SyStringInitFromBuf(&sEntry, zCur, iLen); + SyStringFullTrim(&sEntry); + pEntry = SyHashGet(&sHash, (const void *)sEntry.zString, sEntry.nByte); + if( pEntry ){ + pvArr = (jx9_value *)SyHashEntryGetUserData(pEntry); + }else{ + /* Create an empty array */ + pvArr = jx9_context_new_array(pCtx); + if( pvArr ){ + /* Save the entry */ + SyHashInsert(&sHash, (const void *)sEntry.zString, sEntry.nByte, pvArr); + /* Insert the entry */ + jx9_value_reset_string_cursor(pWorker); + jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); + jx9_array_add_elem(pCur, pWorker, pvArr); + jx9_value_reset_string_cursor(pWorker); + } + } + if( pvArr ){ + pCur = pvArr; + } + } + while ( zIn < zEnd && zIn[0] != ']' ){ + zIn++; + } + } + zIn++; + } + if( !is_array ){ + iLen = (int)(zIn-zCur); + } + /* Trim the key */ + SyStringInitFromBuf(&sEntry, zCur, iLen); + SyStringFullTrim(&sEntry); + if( sEntry.nByte > 0 ){ + if( !is_array ){ + /* Save the key name */ + jx9_value_string(pWorker, sEntry.zString, (int)sEntry.nByte); + } + /* extract key value */ + jx9_value_reset_string_cursor(pValue); + zIn++; /* '=' */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn < zEnd ){ + zCur = zIn; + c = zIn[0]; + if( c == '"' || c == '\'' ){ + zIn++; + /* Delimit the value */ + while( zIn < zEnd ){ + if ( zIn[0] == c && zIn[-1] != '\\' ){ + break; + } + zIn++; + } + if( zIn < zEnd ){ + zIn++; + } + }else{ + while( zIn < zEnd ){ + if( zIn[0] == '\n' ){ + if( zIn[-1] != '\\' ){ + break; + } + }else if( zIn[0] == ';' || zIn[0] == '#' ){ + /* Inline comments */ + break; + } + zIn++; + } + } + /* Trim the value */ + SyStringInitFromBuf(&sEntry, zCur, (int)(zIn-zCur)); + SyStringFullTrim(&sEntry); + if( c == '"' || c == '\'' ){ + SyStringTrimLeadingChar(&sEntry, c); + SyStringTrimTrailingChar(&sEntry, c); + } + if( sEntry.nByte > 0 ){ + jx9_value_string(pValue, sEntry.zString, (int)sEntry.nByte); + } + /* Insert the key and it's value */ + jx9_array_add_elem(pCur, is_array ? 0 /*Automatic index assign */: pWorker, pValue); + } + }else{ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && ( SyisSpace(zIn[0]) || zIn[0] == '=' ) ){ + zIn++; + } + } + pCur = pOldCur; + } + } + SyHashRelease(&sHash); + /* Return the parse of the INI string */ + jx9_result_value(pCtx, pArray); + return SXRET_OK; +} +/* + * array parse_ini_string(string $ini[, bool $process_sections = false[, int $scanner_mode = INI_SCANNER_NORMAL ]]) + * Parse a configuration string. + * Parameters + * $ini + * The contents of the ini file being parsed. + * $process_sections + * By setting the process_sections parameter to TRUE, you get a multidimensional array, with the section names + * and settings included. The default for process_sections is FALSE. + * $scanner_mode (Not used) + * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. If INI_SCANNER_RAW is supplied + * then option values will not be parsed. + * Return + * The settings are returned as an associative array on success, and FALSE on failure. + */ +static int jx9Builtin_parse_ini_string(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIni; + int nByte; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE*/ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the raw INI buffer */ + zIni = jx9_value_to_string(apArg[0], &nByte); + /* Process the INI buffer*/ + jx9ParseIniString(pCtx, zIni, (sxu32)nByte, (nArg > 1) ? jx9_value_to_bool(apArg[1]) : 0); + return JX9_OK; +} +/* + * Ctype Functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * bool ctype_alnum(string $text) + * Checks if all of the characters in the provided string, text, are alphanumeric. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in text is either a letter or a digit, FALSE otherwise. + */ +static int jx9Builtin_ctype_alnum(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( !SyisAlphaNum(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_alpha(string $text) + * Checks if all of the characters in the provided string, text, are alphabetic. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in text is a letter from the current locale, FALSE otherwise. + */ +static int jx9Builtin_ctype_alpha(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( !SyisAlpha(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_cntrl(string $text) + * Checks if all of the characters in the provided string, text, are control characters. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in text is a control characters, FALSE otherwise. + */ +static int jx9Builtin_ctype_cntrl(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisCtrl(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_digit(string $text) + * Checks if all of the characters in the provided string, text, are numerical. + * Parameters + * $text + * The tested string. + * Return + * TRUE if every character in the string text is a decimal digit, FALSE otherwise. + */ +static int jx9Builtin_ctype_digit(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisDigit(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_xdigit(string $text) + * Check for character(s) representing a hexadecimal digit. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is a hexadecimal 'digit', that is + * a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + */ +static int jx9Builtin_ctype_xdigit(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisHex(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_graph(string $text) + * Checks if all of the characters in the provided string, text, creates visible output. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is printable and actually creates visible output + * (no white space), FALSE otherwise. + */ +static int jx9Builtin_ctype_graph(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisGraph(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_print(string $text) + * Checks if all of the characters in the provided string, text, are printable. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text will actually create output (including blanks). + * Returns FALSE if text contains control characters or characters that do not have any output + * or control function at all. + */ +static int jx9Builtin_ctype_print(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisPrint(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_punct(string $text) + * Checks if all of the characters in the provided string, text, are punctuation character. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is printable, but neither letter + * digit or blank, FALSE otherwise. + */ +static int jx9Builtin_ctype_punct(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisPunct(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_space(string $text) + * Checks if all of the characters in the provided string, text, creates whitespace. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. + * Besides the blank character this also includes tab, vertical tab, line feed, carriage return + * and form feed characters. + */ +static int jx9Builtin_ctype_space(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + break; + } + if( !SyisSpace(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_lower(string $text) + * Checks if all of the characters in the provided string, text, are lowercase letters. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is a lowercase letter in the current locale. + */ +static int jx9Builtin_ctype_lower(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( !SyisLower(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * bool ctype_upper(string $text) + * Checks if all of the characters in the provided string, text, are uppercase letters. + * Parameters + * $text + * The tested string. + * Return + * Returns TRUE if every character in text is a uppercase letter in the current locale. + */ +static int jx9Builtin_ctype_upper(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nLen); + zEnd = &zIn[nLen]; + if( nLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + if( zIn >= zEnd ){ + /* If we reach the end of the string, then the test succeeded. */ + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + if( !SyisUpper(zIn[0]) ){ + break; + } + /* Point to the next character */ + zIn++; + } + /* The test failed, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; +} +/* + * Date/Time functions + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Devel. + */ +#include +#ifdef __WINNT__ +/* GetSystemTime() */ +#include +#ifdef _WIN32_WCE +/* +** WindowsCE does not have a localtime() function. So create a +** substitute. +** Taken from the SQLite3 source tree. +** Status: Public domain +*/ +struct tm *__cdecl localtime(const time_t *t) +{ + static struct tm y; + FILETIME uTm, lTm; + SYSTEMTIME pTm; + jx9_int64 t64; + t64 = *t; + t64 = (t64 + 11644473600)*10000000; + uTm.dwLowDateTime = (DWORD)(t64 & 0xFFFFFFFF); + uTm.dwHighDateTime= (DWORD)(t64 >> 32); + FileTimeToLocalFileTime(&uTm, &lTm); + FileTimeToSystemTime(&lTm, &pTm); + y.tm_year = pTm.wYear - 1900; + y.tm_mon = pTm.wMonth - 1; + y.tm_wday = pTm.wDayOfWeek; + y.tm_mday = pTm.wDay; + y.tm_hour = pTm.wHour; + y.tm_min = pTm.wMinute; + y.tm_sec = pTm.wSecond; + return &y; +} +#endif /*_WIN32_WCE */ +#elif defined(__UNIXES__) +#include +#endif /* __WINNT__*/ + /* + * int64 time(void) + * Current Unix timestamp + * Parameters + * None. + * Return + * Returns the current time measured in the number of seconds + * since the Unix Epoch (January 1 1970 00:00:00 GMT). + */ +static int jx9Builtin_time(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + time_t tt; + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Extract the current time */ + time(&tt); + /* Return as 64-bit integer */ + jx9_result_int64(pCtx, (jx9_int64)tt); + return JX9_OK; +} +/* + * string/float microtime([ bool $get_as_float = false ]) + * microtime() returns the current Unix timestamp with microseconds. + * Parameters + * $get_as_float + * If used and set to TRUE, microtime() will return a float instead of a string + * as described in the return values section below. + * Return + * By default, microtime() returns a string in the form "msec sec", where sec + * is the current time measured in the number of seconds since the Unix + * epoch (0:00:00 January 1, 1970 GMT), and msec is the number of microseconds + * that have elapsed since sec expressed in seconds. + * If get_as_float is set to TRUE, then microtime() returns a float, which represents + * the current time in seconds since the Unix epoch accurate to the nearest microsecond. + */ +static int jx9Builtin_microtime(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int bFloat = 0; + sytime sTime; +#if defined(__UNIXES__) + struct timeval tv; + gettimeofday(&tv, 0); + sTime.tm_sec = (long)tv.tv_sec; + sTime.tm_usec = (long)tv.tv_usec; +#else + time_t tt; + time(&tt); + sTime.tm_sec = (long)tt; + sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); +#endif /* __UNIXES__ */ + if( nArg > 0 ){ + bFloat = jx9_value_to_bool(apArg[0]); + } + if( bFloat ){ + /* Return as float */ + jx9_result_double(pCtx, (double)sTime.tm_sec); + }else{ + /* Return as string */ + jx9_result_string_format(pCtx, "%ld %ld", sTime.tm_usec, sTime.tm_sec); + } + return JX9_OK; +} +/* + * array getdate ([ int $timestamp = time() ]) + * Get date/time information. + * Parameter + * $timestamp: The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Returns + * Returns an associative array of information related to the timestamp. + * Elements from the returned associative array are as follows: + * KEY VALUE + * --------- ------- + * "seconds" Numeric representation of seconds 0 to 59 + * "minutes" Numeric representation of minutes 0 to 59 + * "hours" Numeric representation of hours 0 to 23 + * "mday" Numeric representation of the day of the month 1 to 31 + * "wday" Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) + * "mon" Numeric representation of a month 1 through 12 + * "year" A full numeric representation of a year, 4 digits Examples: 1999 or 2003 + * "yday" Numeric representation of the day of the year 0 through 365 + * "weekday" A full textual representation of the day of the week Sunday through Saturday + * "month" A full textual representation of a month, such as January or March January through December + * 0 Seconds since the Unix Epoch, similar to the values returned by time() and used by date(). + * NOTE: + * NULL is returned on failure. + */ +static int jx9Builtin_getdate(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pValue, *pArray; + Sytm sTm; + if( nArg < 1 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; +#ifdef __WINNT__ +#ifdef _MSC_VER +#if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ +#pragma warning(disable:4996) /* _CRT_SECURE...*/ +#endif +#endif +#endif + if( jx9_value_is_int(apArg[0]) ){ + t = (time_t)jx9_value_to_int64(apArg[0]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); + } + /* Element value */ + pValue = jx9_context_new_scalar(pCtx); + if( pValue == 0 ){ + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Fill the array */ + /* Seconds */ + jx9_value_int(pValue, sTm.tm_sec); + jx9_array_add_strkey_elem(pArray, "seconds", pValue); + /* Minutes */ + jx9_value_int(pValue, sTm.tm_min); + jx9_array_add_strkey_elem(pArray, "minutes", pValue); + /* Hours */ + jx9_value_int(pValue, sTm.tm_hour); + jx9_array_add_strkey_elem(pArray, "hours", pValue); + /* mday */ + jx9_value_int(pValue, sTm.tm_mday); + jx9_array_add_strkey_elem(pArray, "mday", pValue); + /* wday */ + jx9_value_int(pValue, sTm.tm_wday); + jx9_array_add_strkey_elem(pArray, "wday", pValue); + /* mon */ + jx9_value_int(pValue, sTm.tm_mon+1); + jx9_array_add_strkey_elem(pArray, "mon", pValue); + /* year */ + jx9_value_int(pValue, sTm.tm_year); + jx9_array_add_strkey_elem(pArray, "year", pValue); + /* yday */ + jx9_value_int(pValue, sTm.tm_yday); + jx9_array_add_strkey_elem(pArray, "yday", pValue); + /* Weekday */ + jx9_value_string(pValue, SyTimeGetDay(sTm.tm_wday), -1); + jx9_array_add_strkey_elem(pArray, "weekday", pValue); + /* Month */ + jx9_value_reset_string_cursor(pValue); + jx9_value_string(pValue, SyTimeGetMonth(sTm.tm_mon), -1); + jx9_array_add_strkey_elem(pArray, "month", pValue); + /* Seconds since the epoch */ + jx9_value_int64(pValue, (jx9_int64)time(0)); + jx9_array_add_elem(pArray, 0 /* Index zero */, pValue); + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * mixed gettimeofday([ bool $return_float = false ] ) + * Returns an associative array containing the data returned from the system call. + * Parameters + * $return_float + * When set to TRUE, a float instead of an array is returned. + * Return + * By default an array is returned. If return_float is set, then + * a float is returned. + */ +static int jx9Builtin_gettimeofday(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int bFloat = 0; + sytime sTime; +#if defined(__UNIXES__) + struct timeval tv; + gettimeofday(&tv, 0); + sTime.tm_sec = (long)tv.tv_sec; + sTime.tm_usec = (long)tv.tv_usec; +#else + time_t tt; + time(&tt); + sTime.tm_sec = (long)tt; + sTime.tm_usec = (long)(tt%SX_USEC_PER_SEC); +#endif /* __UNIXES__ */ + if( nArg > 0 ){ + bFloat = jx9_value_to_bool(apArg[0]); + } + if( bFloat ){ + /* Return as float */ + jx9_result_double(pCtx, (double)sTime.tm_sec); + }else{ + /* Return an associative array */ + jx9_value *pValue, *pArray; + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + /* Element value */ + pValue = jx9_context_new_scalar(pCtx); + if( pValue == 0 || pArray == 0 ){ + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Fill the array */ + /* sec */ + jx9_value_int64(pValue, sTime.tm_sec); + jx9_array_add_strkey_elem(pArray, "sec", pValue); + /* usec */ + jx9_value_int64(pValue, sTime.tm_usec); + jx9_array_add_strkey_elem(pArray, "usec", pValue); + /* Return the array */ + jx9_result_value(pCtx, pArray); + } + return JX9_OK; +} +/* Check if the given year is leap or not */ +#define IS_LEAP_YEAR(YEAR) (YEAR % 400 ? ( YEAR % 100 ? ( YEAR % 4 ? 0 : 1 ) : 0 ) : 1) +/* ISO-8601 numeric representation of the day of the week */ +static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 }; +/* + * Format a given date string. + * Supported format: (Taken from JX9 online docs) + * character Description + * d Day of the month + * D A textual representation of a days + * j Day of the month without leading zeros + * l A full textual representation of the day of the week + * N ISO-8601 numeric representation of the day of the week + * w Numeric representation of the day of the week + * z The day of the year (starting from 0) + * F A full textual representation of a month, such as January or March + * m Numeric representation of a month, with leading zeros 01 through 12 + * M A short textual representation of a month, three letters Jan through Dec + * n Numeric representation of a month, without leading zeros 1 through 12 + * t Number of days in the given month 28 through 31 + * L Whether it's a leap year 1 if it is a leap year, 0 otherwise. + * o ISO-8601 year number. This has the same value as Y, except that if the ISO week number + * (W) belongs to the previous or next year, that year is used instead. (added in JX9 5.1.0) Examples: 1999 or 2003 + * Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003 + * y A two digit representation of a year Examples: 99 or 03 + * a Lowercase Ante meridiem and Post meridiem am or pm + * A Uppercase Ante meridiem and Post meridiem AM or PM + * g 12-hour format of an hour without leading zeros 1 through 12 + * G 24-hour format of an hour without leading zeros 0 through 23 + * h 12-hour format of an hour with leading zeros 01 through 12 + * H 24-hour format of an hour with leading zeros 00 through 23 + * i Minutes with leading zeros 00 to 59 + * s Seconds, with leading zeros 00 through 59 + * u Microseconds Example: 654321 + * e Timezone identifier Examples: UTC, GMT, Atlantic/Azores + * I (capital i) Whether or not the date is in daylight saving time 1 if Daylight Saving Time, 0 otherwise. + * r RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 +0200 + * U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) + * S English ordinal suffix for the day of the month, 2 characters + * O Difference to Greenwich time (GMT) in hours + * Z Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those + * east of UTC is always positive. + * c ISO 8601 date + */ +static sxi32 DateFormat(jx9_context *pCtx, const char *zIn, int nLen, Sytm *pTm) +{ + const char *zEnd = &zIn[nLen]; + const char *zCur; + /* Start the format process */ + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + switch(zIn[0]){ + case 'd': + /* Day of the month, 2 digits with leading zeros */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_mday); + break; + case 'D': + /*A textual representation of a day, three letters*/ + zCur = SyTimeGetDay(pTm->tm_wday); + jx9_result_string(pCtx, zCur, 3); + break; + case 'j': + /* Day of the month without leading zeros */ + jx9_result_string_format(pCtx, "%d", pTm->tm_mday); + break; + case 'l': + /* A full textual representation of the day of the week */ + zCur = SyTimeGetDay(pTm->tm_wday); + jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/); + break; + case 'N':{ + /* ISO-8601 numeric representation of the day of the week */ + jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]); + break; + } + case 'w': + /*Numeric representation of the day of the week*/ + jx9_result_string_format(pCtx, "%d", pTm->tm_wday); + break; + case 'z': + /*The day of the year*/ + jx9_result_string_format(pCtx, "%d", pTm->tm_yday); + break; + case 'F': + /*A full textual representation of a month, such as January or March*/ + zCur = SyTimeGetMonth(pTm->tm_mon); + jx9_result_string(pCtx, zCur, -1/*Compute length automatically*/); + break; + case 'm': + /*Numeric representation of a month, with leading zeros*/ + jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1); + break; + case 'M': + /*A short textual representation of a month, three letters*/ + zCur = SyTimeGetMonth(pTm->tm_mon); + jx9_result_string(pCtx, zCur, 3); + break; + case 'n': + /*Numeric representation of a month, without leading zeros*/ + jx9_result_string_format(pCtx, "%d", pTm->tm_mon + 1); + break; + case 't':{ + static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + int nDays = aMonDays[pTm->tm_mon % 12 ]; + if( pTm->tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(pTm->tm_year) ){ + nDays = 28; + } + /*Number of days in the given month*/ + jx9_result_string_format(pCtx, "%d", nDays); + break; + } + case 'L':{ + int isLeap = IS_LEAP_YEAR(pTm->tm_year); + /* Whether it's a leap year */ + jx9_result_string_format(pCtx, "%d", isLeap); + break; + } + case 'o': + /* ISO-8601 year number.*/ + jx9_result_string_format(pCtx, "%4d", pTm->tm_year); + break; + case 'Y': + /* A full numeric representation of a year, 4 digits */ + jx9_result_string_format(pCtx, "%4d", pTm->tm_year); + break; + case 'y': + /*A two digit representation of a year*/ + jx9_result_string_format(pCtx, "%02d", pTm->tm_year%100); + break; + case 'a': + /* Lowercase Ante meridiem and Post meridiem */ + jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", 2); + break; + case 'A': + /* Uppercase Ante meridiem and Post meridiem */ + jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", 2); + break; + case 'g': + /* 12-hour format of an hour without leading zeros*/ + jx9_result_string_format(pCtx, "%d", 1+(pTm->tm_hour%12)); + break; + case 'G': + /* 24-hour format of an hour without leading zeros */ + jx9_result_string_format(pCtx, "%d", pTm->tm_hour); + break; + case 'h': + /* 12-hour format of an hour with leading zeros */ + jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12)); + break; + case 'H': + /* 24-hour format of an hour with leading zeros */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_hour); + break; + case 'i': + /* Minutes with leading zeros */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_min); + break; + case 's': + /* second with leading zeros */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_sec); + break; + case 'u': + /* Microseconds */ + jx9_result_string_format(pCtx, "%u", pTm->tm_sec * SX_USEC_PER_SEC); + break; + case 'S':{ + /* English ordinal suffix for the day of the month, 2 characters */ + static const char zSuffix[] = "thstndrdthththththth"; + int v = pTm->tm_mday; + jx9_result_string(pCtx, &zSuffix[2 * (int)(v / 10 % 10 != 1 ? v % 10 : 0)], (int)sizeof(char) * 2); + break; + } + case 'e': + /* Timezone identifier */ + zCur = pTm->tm_zone; + if( zCur == 0 ){ + /* Assume GMT */ + zCur = "GMT"; + } + jx9_result_string(pCtx, zCur, -1); + break; + case 'I': + /* Whether or not the date is in daylight saving time */ +#ifdef __WINNT__ +#ifdef _MSC_VER +#ifndef _WIN32_WCE + _get_daylight(&pTm->tm_isdst); +#endif +#endif +#endif + jx9_result_string_format(pCtx, "%d", pTm->tm_isdst == 1); + break; + case 'r': + /* RFC 2822 formatted date Example: Thu, 21 Dec 2000 16:01:07 */ + jx9_result_string_format(pCtx, "%.3s, %02d %.3s %4d %02d:%02d:%02d", + SyTimeGetDay(pTm->tm_wday), + pTm->tm_mday, + SyTimeGetMonth(pTm->tm_mon), + pTm->tm_year, + pTm->tm_hour, + pTm->tm_min, + pTm->tm_sec + ); + break; + case 'U':{ + time_t tt; + /* Seconds since the Unix Epoch */ + time(&tt); + jx9_result_string_format(pCtx, "%u", (unsigned int)tt); + break; + } + case 'O': + case 'P': + /* Difference to Greenwich time (GMT) in hours */ + jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff); + break; + case 'Z': + /* Timezone offset in seconds. The offset for timezones west of UTC + * is always negative, and for those east of UTC is always positive. + */ + jx9_result_string_format(pCtx, "%+05d", pTm->tm_gmtoff); + break; + case 'c': + /* ISO 8601 date */ + jx9_result_string_format(pCtx, "%4d-%02d-%02dT%02d:%02d:%02d%+05d", + pTm->tm_year, + pTm->tm_mon+1, + pTm->tm_mday, + pTm->tm_hour, + pTm->tm_min, + pTm->tm_sec, + pTm->tm_gmtoff + ); + break; + case '\\': + zIn++; + /* Expand verbatim */ + if( zIn < zEnd ){ + jx9_result_string(pCtx, zIn, (int)sizeof(char)); + } + break; + default: + /* Unknown format specifer, expand verbatim */ + jx9_result_string(pCtx, zIn, (int)sizeof(char)); + break; + } + /* Point to the next character */ + zIn++; + } + return SXRET_OK; +} +/* + * JX9 implementation of the strftime() function. + * The following formats are supported: + * %a An abbreviated textual representation of the day + * %A A full textual representation of the day + * %d Two-digit day of the month (with leading zeros) + * %e Day of the month, with a space preceding single digits. + * %j Day of the year, 3 digits with leading zeros + * %u ISO-8601 numeric representation of the day of the week 1 (for Monday) though 7 (for Sunday) + * %w Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) + * %U Week number of the given year, starting with the first Sunday as the first week + * %V ISO-8601:1988 week number of the given year, starting with the first week of the year with at least + * 4 weekdays, with Monday being the start of the week. + * %W A numeric representation of the week of the year + * %b Abbreviated month name, based on the locale + * %B Full month name, based on the locale + * %h Abbreviated month name, based on the locale (an alias of %b) + * %m Two digit representation of the month + * %C Two digit representation of the century (year divided by 100, truncated to an integer) + * %g Two digit representation of the year going by ISO-8601:1988 standards (see %V) + * %G The full four-digit version of %g + * %y Two digit representation of the year + * %Y Four digit representation for the year + * %H Two digit representation of the hour in 24-hour format + * %I Two digit representation of the hour in 12-hour format + * %l (lower-case 'L') Hour in 12-hour format, with a space preceeding single digits + * %M Two digit representation of the minute + * %p UPPER-CASE 'AM' or 'PM' based on the given time + * %P lower-case 'am' or 'pm' based on the given time + * %r Same as "%I:%M:%S %p" + * %R Same as "%H:%M" + * %S Two digit representation of the second + * %T Same as "%H:%M:%S" + * %X Preferred time representation based on locale, without the date + * %z Either the time zone offset from UTC or the abbreviation + * %Z The time zone offset/abbreviation option NOT given by %z + * %c Preferred date and time stamp based on local + * %D Same as "%m/%d/%y" + * %F Same as "%Y-%m-%d" + * %s Unix Epoch Time timestamp (same as the time() function) + * %x Preferred date representation based on locale, without the time + * %n A newline character ("\n") + * %t A Tab character ("\t") + * %% A literal percentage character ("%") + */ +static int jx9Strftime( + jx9_context *pCtx, /* Call context */ + const char *zIn, /* Input string */ + int nLen, /* Input length */ + Sytm *pTm /* Parse of the given time */ + ) +{ + const char *zCur, *zEnd = &zIn[nLen]; + int c; + /* Start the format process */ + for(;;){ + zCur = zIn; + while(zIn < zEnd && zIn[0] != '%' ){ + zIn++; + } + if( zIn > zCur ){ + /* Consume input verbatim */ + jx9_result_string(pCtx, zCur, (int)(zIn-zCur)); + } + zIn++; /* Jump the percent sign */ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + c = zIn[0]; + /* Act according to the current specifer */ + switch(c){ + case '%': + /* A literal percentage character ("%") */ + jx9_result_string(pCtx, "%", (int)sizeof(char)); + break; + case 't': + /* A Tab character */ + jx9_result_string(pCtx, "\t", (int)sizeof(char)); + break; + case 'n': + /* A newline character */ + jx9_result_string(pCtx, "\n", (int)sizeof(char)); + break; + case 'a': + /* An abbreviated textual representation of the day */ + jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), (int)sizeof(char)*3); + break; + case 'A': + /* A full textual representation of the day */ + jx9_result_string(pCtx, SyTimeGetDay(pTm->tm_wday), -1/*Compute length automatically*/); + break; + case 'e': + /* Day of the month, 2 digits with leading space for single digit*/ + jx9_result_string_format(pCtx, "%2d", pTm->tm_mday); + break; + case 'd': + /* Two-digit day of the month (with leading zeros) */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_mon+1); + break; + case 'j': + /*The day of the year, 3 digits with leading zeros*/ + jx9_result_string_format(pCtx, "%03d", pTm->tm_yday); + break; + case 'u': + /* ISO-8601 numeric representation of the day of the week */ + jx9_result_string_format(pCtx, "%d", aISO8601[pTm->tm_wday % 7 ]); + break; + case 'w': + /* Numeric representation of the day of the week */ + jx9_result_string_format(pCtx, "%d", pTm->tm_wday); + break; + case 'b': + case 'h': + /*A short textual representation of a month, three letters (Not based on locale)*/ + jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), (int)sizeof(char)*3); + break; + case 'B': + /* Full month name (Not based on locale) */ + jx9_result_string(pCtx, SyTimeGetMonth(pTm->tm_mon), -1/*Compute length automatically*/); + break; + case 'm': + /*Numeric representation of a month, with leading zeros*/ + jx9_result_string_format(pCtx, "%02d", pTm->tm_mon + 1); + break; + case 'C': + /* Two digit representation of the century */ + jx9_result_string_format(pCtx, "%2d", pTm->tm_year/100); + break; + case 'y': + case 'g': + /* Two digit representation of the year */ + jx9_result_string_format(pCtx, "%2d", pTm->tm_year%100); + break; + case 'Y': + case 'G': + /* Four digit representation of the year */ + jx9_result_string_format(pCtx, "%4d", pTm->tm_year); + break; + case 'I': + /* 12-hour format of an hour with leading zeros */ + jx9_result_string_format(pCtx, "%02d", 1+(pTm->tm_hour%12)); + break; + case 'l': + /* 12-hour format of an hour with leading space */ + jx9_result_string_format(pCtx, "%2d", 1+(pTm->tm_hour%12)); + break; + case 'H': + /* 24-hour format of an hour with leading zeros */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_hour); + break; + case 'M': + /* Minutes with leading zeros */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_min); + break; + case 'S': + /* Seconds with leading zeros */ + jx9_result_string_format(pCtx, "%02d", pTm->tm_sec); + break; + case 'z': + case 'Z': + /* Timezone identifier */ + zCur = pTm->tm_zone; + if( zCur == 0 ){ + /* Assume GMT */ + zCur = "GMT"; + } + jx9_result_string(pCtx, zCur, -1); + break; + case 'T': + case 'X': + /* Same as "%H:%M:%S" */ + jx9_result_string_format(pCtx, "%02d:%02d:%02d", pTm->tm_hour, pTm->tm_min, pTm->tm_sec); + break; + case 'R': + /* Same as "%H:%M" */ + jx9_result_string_format(pCtx, "%02d:%02d", pTm->tm_hour, pTm->tm_min); + break; + case 'P': + /* Lowercase Ante meridiem and Post meridiem */ + jx9_result_string(pCtx, pTm->tm_hour > 12 ? "pm" : "am", (int)sizeof(char)*2); + break; + case 'p': + /* Uppercase Ante meridiem and Post meridiem */ + jx9_result_string(pCtx, pTm->tm_hour > 12 ? "PM" : "AM", (int)sizeof(char)*2); + break; + case 'r': + /* Same as "%I:%M:%S %p" */ + jx9_result_string_format(pCtx, "%02d:%02d:%02d %s", + 1+(pTm->tm_hour%12), + pTm->tm_min, + pTm->tm_sec, + pTm->tm_hour > 12 ? "PM" : "AM" + ); + break; + case 'D': + case 'x': + /* Same as "%m/%d/%y" */ + jx9_result_string_format(pCtx, "%02d/%02d/%02d", + pTm->tm_mon+1, + pTm->tm_mday, + pTm->tm_year%100 + ); + break; + case 'F': + /* Same as "%Y-%m-%d" */ + jx9_result_string_format(pCtx, "%d-%02d-%02d", + pTm->tm_year, + pTm->tm_mon+1, + pTm->tm_mday + ); + break; + case 'c': + jx9_result_string_format(pCtx, "%d-%02d-%02d %02d:%02d:%02d", + pTm->tm_year, + pTm->tm_mon+1, + pTm->tm_mday, + pTm->tm_hour, + pTm->tm_min, + pTm->tm_sec + ); + break; + case 's':{ + time_t tt; + /* Seconds since the Unix Epoch */ + time(&tt); + jx9_result_string_format(pCtx, "%u", (unsigned int)tt); + break; + } + default: + /* unknown specifer, simply ignore*/ + break; + } + /* Advance the cursor */ + zIn++; + } + return SXRET_OK; +} +/* + * string date(string $format [, int $timestamp = time() ] ) + * Returns a string formatted according to the given format string using + * the given integer timestamp or the current time if no timestamp is given. + * In other words, timestamp is optional and defaults to the value of time(). + * Parameters + * $format + * The format of the outputted date string (See code above) + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Return + * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. + */ +static int jx9Builtin_date(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFormat; + int nLen; + Sytm sTm; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Don't bother processing return the empty string */ + jx9_result_string(pCtx, "", 0); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( jx9_value_is_int(apArg[1]) ){ + t = (time_t)jx9_value_to_int64(apArg[1]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); + } + /* Format the given string */ + DateFormat(pCtx, zFormat, nLen, &sTm); + return JX9_OK; +} +/* + * string strftime(string $format [, int $timestamp = time() ] ) + * Format a local time/date (PLATFORM INDEPENDANT IMPLEENTATION NOT BASED ON LOCALE) + * Parameters + * $format + * The format of the outputted date string (See code above) + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Return + * Returns a string formatted according format using the given timestamp + * or the current local time if no timestamp is given. + */ +static int jx9Builtin_strftime(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFormat; + int nLen; + Sytm sTm; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Don't bother processing return FALSE */ + jx9_result_bool(pCtx, 0); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( jx9_value_is_int(apArg[1]) ){ + t = (time_t)jx9_value_to_int64(apArg[1]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); + } + /* Format the given string */ + jx9Strftime(pCtx, zFormat, nLen, &sTm); + if( jx9_context_result_buf_length(pCtx) < 1 ){ + /* Nothing was formatted, return FALSE */ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * string gmdate(string $format [, int $timestamp = time() ] ) + * Identical to the date() function except that the time returned + * is Greenwich Mean Time (GMT). + * Parameters + * $format + * The format of the outputted date string (See code above) + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * Return + * A formatted date string. If a non-numeric value is used for timestamp, FALSE is returned. + */ +static int jx9Builtin_gmdate(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFormat; + int nLen; + Sytm sTm; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Don't bother processing return the empty string */ + jx9_result_string(pCtx, "", 0); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( jx9_value_is_int(apArg[1]) ){ + t = (time_t)jx9_value_to_int64(apArg[1]); + pTm = gmtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); + } + /* Format the given string */ + DateFormat(pCtx, zFormat, nLen, &sTm); + return JX9_OK; +} +/* + * array localtime([ int $timestamp = time() [, bool $is_associative = false ]]) + * Return the local time. + * Parameter + * $timestamp: The optional timestamp parameter is an integer Unix timestamp + * that defaults to the current local time if a timestamp is not given. + * In other words, it defaults to the value of time(). + * $is_associative + * If set to FALSE or not supplied then the array is returned as a regular, numerically + * indexed array. If the argument is set to TRUE then localtime() returns an associative + * array containing all the different elements of the structure returned by the C function + * call to localtime. The names of the different keys of the associative array are as follows: + * "tm_sec" - seconds, 0 to 59 + * "tm_min" - minutes, 0 to 59 + * "tm_hour" - hours, 0 to 23 + * "tm_mday" - day of the month, 1 to 31 + * "tm_mon" - month of the year, 0 (Jan) to 11 (Dec) + * "tm_year" - years since 1900 + * "tm_wday" - day of the week, 0 (Sun) to 6 (Sat) + * "tm_yday" - day of the year, 0 to 365 + * "tm_isdst" - is daylight savings time in effect? Positive if yes, 0 if not, negative if unknown. + * Returns + * An associative array of information related to the timestamp. + */ +static int jx9Builtin_localtime(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pValue, *pArray; + int isAssoc = 0; + Sytm sTm; + if( nArg < 1 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); /* TODO(chems): GMT not local */ + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( jx9_value_is_int(apArg[0]) ){ + t = (time_t)jx9_value_to_int64(apArg[0]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); + } + /* Element value */ + pValue = jx9_context_new_scalar(pCtx); + if( pValue == 0 ){ + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( nArg > 1 ){ + isAssoc = jx9_value_to_bool(apArg[1]); + } + /* Fill the array */ + /* Seconds */ + jx9_value_int(pValue, sTm.tm_sec); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_sec", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* Minutes */ + jx9_value_int(pValue, sTm.tm_min); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_min", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* Hours */ + jx9_value_int(pValue, sTm.tm_hour); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_hour", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* mday */ + jx9_value_int(pValue, sTm.tm_mday); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_mday", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* mon */ + jx9_value_int(pValue, sTm.tm_mon); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_mon", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* year since 1900 */ + jx9_value_int(pValue, sTm.tm_year-1900); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_year", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* wday */ + jx9_value_int(pValue, sTm.tm_wday); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_wday", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* yday */ + jx9_value_int(pValue, sTm.tm_yday); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_yday", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* isdst */ +#ifdef __WINNT__ +#ifdef _MSC_VER +#ifndef _WIN32_WCE + _get_daylight(&sTm.tm_isdst); +#endif +#endif +#endif + jx9_value_int(pValue, sTm.tm_isdst); + if( isAssoc ){ + jx9_array_add_strkey_elem(pArray, "tm_isdst", pValue); + }else{ + jx9_array_add_elem(pArray, 0/* Automatic index */, pValue); + } + /* Return the array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * int idate(string $format [, int $timestamp = time() ]) + * Returns a number formatted according to the given format string + * using the given integer timestamp or the current local time if + * no timestamp is given. In other words, timestamp is optional and defaults + * to the value of time(). + * Unlike the function date(), idate() accepts just one char in the format + * parameter. + * $Parameters + * Supported format + * d Day of the month + * h Hour (12 hour format) + * H Hour (24 hour format) + * i Minutes + * I (uppercase i)1 if DST is activated, 0 otherwise + * L (uppercase l) returns 1 for leap year, 0 otherwise + * m Month number + * s Seconds + * t Days in current month + * U Seconds since the Unix Epoch - January 1 1970 00:00:00 UTC - this is the same as time() + * w Day of the week (0 on Sunday) + * W ISO-8601 week number of year, weeks starting on Monday + * y Year (1 or 2 digits - check note below) + * Y Year (4 digits) + * z Day of the year + * Z Timezone offset in seconds + * $timestamp + * The optional timestamp parameter is an integer Unix timestamp that defaults + * to the current local time if a timestamp is not given. In other words, it defaults + * to the value of time(). + * Return + * An integer. + */ +static int jx9Builtin_idate(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFormat; + jx9_int64 iVal = 0; + int nLen; + Sytm sTm; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return -1 */ + jx9_result_int(pCtx, -1); + return JX9_OK; + } + zFormat = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Don't bother processing return -1*/ + jx9_result_int(pCtx, -1); + } + if( nArg < 2 ){ +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + }else{ + /* Use the given timestamp */ + time_t t; + struct tm *pTm; + if( jx9_value_is_int(apArg[1]) ){ + t = (time_t)jx9_value_to_int64(apArg[1]); + pTm = localtime(&t); + if( pTm == 0 ){ + time(&t); + } + }else{ + time(&t); + } + pTm = localtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); + } + /* Perform the requested operation */ + switch(zFormat[0]){ + case 'd': + /* Day of the month */ + iVal = sTm.tm_mday; + break; + case 'h': + /* Hour (12 hour format)*/ + iVal = 1 + (sTm.tm_hour % 12); + break; + case 'H': + /* Hour (24 hour format)*/ + iVal = sTm.tm_hour; + break; + case 'i': + /*Minutes*/ + iVal = sTm.tm_min; + break; + case 'I': + /* returns 1 if DST is activated, 0 otherwise */ +#ifdef __WINNT__ +#ifdef _MSC_VER +#ifndef _WIN32_WCE + _get_daylight(&sTm.tm_isdst); +#endif +#endif +#endif + iVal = sTm.tm_isdst; + break; + case 'L': + /* returns 1 for leap year, 0 otherwise */ + iVal = IS_LEAP_YEAR(sTm.tm_year); + break; + case 'm': + /* Month number*/ + iVal = sTm.tm_mon; + break; + case 's': + /*Seconds*/ + iVal = sTm.tm_sec; + break; + case 't':{ + /*Days in current month*/ + static const int aMonDays[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + int nDays = aMonDays[sTm.tm_mon % 12 ]; + if( sTm.tm_mon == 1 /* 'February' */ && !IS_LEAP_YEAR(sTm.tm_year) ){ + nDays = 28; + } + iVal = nDays; + break; + } + case 'U': + /*Seconds since the Unix Epoch*/ + iVal = (jx9_int64)time(0); + break; + case 'w': + /* Day of the week (0 on Sunday) */ + iVal = sTm.tm_wday; + break; + case 'W': { + /* ISO-8601 week number of year, weeks starting on Monday */ + static const int aISO8601[] = { 7 /* Sunday */, 1 /* Monday */, 2, 3, 4, 5, 6 }; + iVal = aISO8601[sTm.tm_wday % 7 ]; + break; + } + case 'y': + /* Year (2 digits) */ + iVal = sTm.tm_year % 100; + break; + case 'Y': + /* Year (4 digits) */ + iVal = sTm.tm_year; + break; + case 'z': + /* Day of the year */ + iVal = sTm.tm_yday; + break; + case 'Z': + /*Timezone offset in seconds*/ + iVal = sTm.tm_gmtoff; + break; + default: + /* unknown format, throw a warning */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Unknown date format token"); + break; + } + /* Return the time value */ + jx9_result_int64(pCtx, iVal); + return JX9_OK; +} +/* + * int mktime/gmmktime([ int $hour = date("H") [, int $minute = date("i") [, int $second = date("s") + * [, int $month = date("n") [, int $day = date("j") [, int $year = date("Y") [, int $is_dst = -1 ]]]]]]] ) + * Returns the Unix timestamp corresponding to the arguments given. This timestamp is a 64bit integer + * containing the number of seconds between the Unix Epoch (January 1 1970 00:00:00 GMT) and the time + * specified. + * Arguments may be left out in order from right to left; any arguments thus omitted will be set to + * the current value according to the local date and time. + * Parameters + * $hour + * The number of the hour relevant to the start of the day determined by month, day and year. + * Negative values reference the hour before midnight of the day in question. Values greater + * than 23 reference the appropriate hour in the following day(s). + * $minute + * The number of the minute relevant to the start of the hour. Negative values reference + * the minute in the previous hour. Values greater than 59 reference the appropriate minute + * in the following hour(s). + * $second + * The number of seconds relevant to the start of the minute. Negative values reference + * the second in the previous minute. Values greater than 59 reference the appropriate + * second in the following minute(s). + * $month + * The number of the month relevant to the end of the previous year. Values 1 to 12 reference + * the normal calendar months of the year in question. Values less than 1 (including negative values) + * reference the months in the previous year in reverse order, so 0 is December, -1 is November)... + * $day + * The number of the day relevant to the end of the previous month. Values 1 to 28, 29, 30 or 31 + * (depending upon the month) reference the normal days in the relevant month. Values less than 1 + * (including negative values) reference the days in the previous month, so 0 is the last day + * of the previous month, -1 is the day before that, etc. Values greater than the number of days + * in the relevant month reference the appropriate day in the following month(s). + * $year + * The number of the year, may be a two or four digit value, with values between 0-69 mapping + * to 2000-2069 and 70-100 to 1970-2000. On systems where time_t is a 32bit signed integer, as + * most common today, the valid range for year is somewhere between 1901 and 2038. + * $is_dst + * This parameter can be set to 1 if the time is during daylight savings time (DST), 0 if it is not, + * or -1 (the default) if it is unknown whether the time is within daylight savings time or not. + * Return + * mktime() returns the Unix timestamp of the arguments given. + * If the arguments are invalid, the function returns FALSE + */ +static int jx9Builtin_mktime(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFunction; + jx9_int64 iVal = 0; + struct tm *pTm; + time_t t; + /* Extract function name */ + zFunction = jx9_function_name(pCtx); + /* Get the current time */ + time(&t); + if( zFunction[0] == 'g' /* gmmktime */ ){ + pTm = gmtime(&t); + }else{ + /* localtime */ + pTm = localtime(&t); + } + if( nArg > 0 ){ + int iVal; + /* Hour */ + iVal = jx9_value_to_int(apArg[0]); + pTm->tm_hour = iVal; + if( nArg > 1 ){ + /* Minutes */ + iVal = jx9_value_to_int(apArg[1]); + pTm->tm_min = iVal; + if( nArg > 2 ){ + /* Seconds */ + iVal = jx9_value_to_int(apArg[2]); + pTm->tm_sec = iVal; + if( nArg > 3 ){ + /* Month */ + iVal = jx9_value_to_int(apArg[3]); + pTm->tm_mon = iVal - 1; + if( nArg > 4 ){ + /* mday */ + iVal = jx9_value_to_int(apArg[4]); + pTm->tm_mday = iVal; + if( nArg > 5 ){ + /* Year */ + iVal = jx9_value_to_int(apArg[5]); + if( iVal > 1900 ){ + iVal -= 1900; + } + pTm->tm_year = iVal; + if( nArg > 6 ){ + /* is_dst */ + iVal = jx9_value_to_bool(apArg[6]); + pTm->tm_isdst = iVal; + } + } + } + } + } + } + } + /* Make the time */ + iVal = (jx9_int64)mktime(pTm); + /* Return the timesatmp as a 64bit integer */ + jx9_result_int64(pCtx, iVal); + return JX9_OK; +} +/* + * Section: + * URL handling Functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * Output consumer callback for the standard Symisc routines. + * [i.e: SyBase64Encode(), SyBase64Decode(), SyUriEncode(), ...]. + */ +static int Consumer(const void *pData, unsigned int nLen, void *pUserData) +{ + /* Store in the call context result buffer */ + jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); + return SXRET_OK; +} +/* + * string base64_encode(string $data) + * string convert_uuencode(string $data) + * Encodes data with MIME base64 + * Parameter + * $data + * Data to encode + * Return + * Encoded data or FALSE on failure. + */ +static int jx9Builtin_base64_encode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the input string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the BASE64 encoding */ + SyBase64Encode(zIn, (sxu32)nLen, Consumer, pCtx); + return JX9_OK; +} +/* + * string base64_decode(string $data) + * string convert_uudecode(string $data) + * Decodes data encoded with MIME base64 + * Parameter + * $data + * Encoded data. + * Return + * Returns the original data or FALSE on failure. + */ +static int jx9Builtin_base64_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the input string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the BASE64 decoding */ + SyBase64Decode(zIn, (sxu32)nLen, Consumer, pCtx); + return JX9_OK; +} +/* + * string urlencode(string $str) + * URL encoding + * Parameter + * $data + * Input string. + * Return + * Returns a string in which all non-alphanumeric characters except -_. have + * been replaced with a percent (%) sign followed by two hex digits and spaces + * encoded as plus (+) signs. + */ +static int jx9Builtin_urlencode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the input string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the URL encoding */ + SyUriEncode(zIn, (sxu32)nLen, Consumer, pCtx); + return JX9_OK; +} +/* + * string urldecode(string $str) + * Decodes any %## encoding in the given string. + * Plus symbols ('+') are decoded to a space character. + * Parameter + * $data + * Input string. + * Return + * Decoded URL or FALSE on failure. + */ +static int jx9Builtin_urldecode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn; + int nLen; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the input string */ + zIn = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the URL decoding */ + SyUriDecode(zIn, (sxu32)nLen, Consumer, pCtx, TRUE); + return JX9_OK; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* Table of the built-in functions */ +static const jx9_builtin_func aBuiltInFunc[] = { + /* Variable handling functions */ + { "is_bool" , jx9Builtin_is_bool }, + { "is_float" , jx9Builtin_is_float }, + { "is_real" , jx9Builtin_is_float }, + { "is_double" , jx9Builtin_is_float }, + { "is_int" , jx9Builtin_is_int }, + { "is_integer" , jx9Builtin_is_int }, + { "is_long" , jx9Builtin_is_int }, + { "is_string" , jx9Builtin_is_string }, + { "is_null" , jx9Builtin_is_null }, + { "is_numeric" , jx9Builtin_is_numeric }, + { "is_scalar" , jx9Builtin_is_scalar }, + { "is_array" , jx9Builtin_is_array }, + { "is_object" , jx9Builtin_is_object }, + { "is_resource", jx9Builtin_is_resource }, + { "douleval" , jx9Builtin_floatval }, + { "floatval" , jx9Builtin_floatval }, + { "intval" , jx9Builtin_intval }, + { "strval" , jx9Builtin_strval }, + { "empty" , jx9Builtin_empty }, +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifdef JX9_ENABLE_MATH_FUNC + /* Math functions */ + { "abs" , jx9Builtin_abs }, + { "sqrt" , jx9Builtin_sqrt }, + { "exp" , jx9Builtin_exp }, + { "floor", jx9Builtin_floor }, + { "cos" , jx9Builtin_cos }, + { "sin" , jx9Builtin_sin }, + { "acos" , jx9Builtin_acos }, + { "asin" , jx9Builtin_asin }, + { "cosh" , jx9Builtin_cosh }, + { "sinh" , jx9Builtin_sinh }, + { "ceil" , jx9Builtin_ceil }, + { "tan" , jx9Builtin_tan }, + { "tanh" , jx9Builtin_tanh }, + { "atan" , jx9Builtin_atan }, + { "atan2", jx9Builtin_atan2 }, + { "log" , jx9Builtin_log }, + { "log10" , jx9Builtin_log10 }, + { "pow" , jx9Builtin_pow }, + { "pi", jx9Builtin_pi }, + { "fmod", jx9Builtin_fmod }, + { "hypot", jx9Builtin_hypot }, +#endif /* JX9_ENABLE_MATH_FUNC */ + { "round", jx9Builtin_round }, + { "dechex", jx9Builtin_dechex }, + { "decoct", jx9Builtin_decoct }, + { "decbin", jx9Builtin_decbin }, + { "hexdec", jx9Builtin_hexdec }, + { "bindec", jx9Builtin_bindec }, + { "octdec", jx9Builtin_octdec }, + { "base_convert", jx9Builtin_base_convert }, + /* String handling functions */ + { "substr", jx9Builtin_substr }, + { "substr_compare", jx9Builtin_substr_compare }, + { "substr_count", jx9Builtin_substr_count }, + { "chunk_split", jx9Builtin_chunk_split}, + { "htmlspecialchars", jx9Builtin_htmlspecialchars }, + { "htmlspecialchars_decode", jx9Builtin_htmlspecialchars_decode }, + { "get_html_translation_table", jx9Builtin_get_html_translation_table }, + { "htmlentities", jx9Builtin_htmlentities}, + { "html_entity_decode", jx9Builtin_html_entity_decode}, + { "strlen" , jx9Builtin_strlen }, + { "strcmp" , jx9Builtin_strcmp }, + { "strcoll" , jx9Builtin_strcmp }, + { "strncmp" , jx9Builtin_strncmp }, + { "strcasecmp" , jx9Builtin_strcasecmp }, + { "strncasecmp", jx9Builtin_strncasecmp}, + { "implode" , jx9Builtin_implode }, + { "join" , jx9Builtin_implode }, + { "implode_recursive" , jx9Builtin_implode_recursive }, + { "join_recursive" , jx9Builtin_implode_recursive }, + { "explode" , jx9Builtin_explode }, + { "trim" , jx9Builtin_trim }, + { "rtrim" , jx9Builtin_rtrim }, + { "chop" , jx9Builtin_rtrim }, + { "ltrim" , jx9Builtin_ltrim }, + { "strtolower", jx9Builtin_strtolower }, + { "mb_strtolower", jx9Builtin_strtolower }, /* Only UTF-8 encoding is supported */ + { "strtoupper", jx9Builtin_strtoupper }, + { "mb_strtoupper", jx9Builtin_strtoupper }, /* Only UTF-8 encoding is supported */ + { "ord", jx9Builtin_ord }, + { "chr", jx9Builtin_chr }, + { "bin2hex", jx9Builtin_bin2hex }, + { "strstr", jx9Builtin_strstr }, + { "stristr", jx9Builtin_stristr }, + { "strchr", jx9Builtin_strstr }, + { "strpos", jx9Builtin_strpos }, + { "stripos", jx9Builtin_stripos }, + { "strrpos", jx9Builtin_strrpos }, + { "strripos", jx9Builtin_strripos }, + { "strrchr", jx9Builtin_strrchr }, + { "strrev", jx9Builtin_strrev }, + { "str_repeat", jx9Builtin_str_repeat }, + { "nl2br", jx9Builtin_nl2br }, + { "sprintf", jx9Builtin_sprintf }, + { "printf", jx9Builtin_printf }, + { "vprintf", jx9Builtin_vprintf }, + { "vsprintf", jx9Builtin_vsprintf }, + { "size_format", jx9Builtin_size_format}, +#if !defined(JX9_DISABLE_HASH_FUNC) + { "md5", jx9Builtin_md5 }, + { "sha1", jx9Builtin_sha1 }, + { "crc32", jx9Builtin_crc32 }, +#endif /* JX9_DISABLE_HASH_FUNC */ + { "str_getcsv", jx9Builtin_str_getcsv }, + { "strip_tags", jx9Builtin_strip_tags }, + { "str_split", jx9Builtin_str_split }, + { "strspn", jx9Builtin_strspn }, + { "strcspn", jx9Builtin_strcspn }, + { "strpbrk", jx9Builtin_strpbrk }, + { "soundex", jx9Builtin_soundex }, + { "wordwrap", jx9Builtin_wordwrap }, + { "strtok", jx9Builtin_strtok }, + { "str_pad", jx9Builtin_str_pad }, + { "str_replace", jx9Builtin_str_replace}, + { "str_ireplace", jx9Builtin_str_replace}, + { "strtr", jx9Builtin_strtr }, + { "parse_ini_string", jx9Builtin_parse_ini_string}, + /* Ctype functions */ + { "ctype_alnum", jx9Builtin_ctype_alnum }, + { "ctype_alpha", jx9Builtin_ctype_alpha }, + { "ctype_cntrl", jx9Builtin_ctype_cntrl }, + { "ctype_digit", jx9Builtin_ctype_digit }, + { "ctype_xdigit", jx9Builtin_ctype_xdigit}, + { "ctype_graph", jx9Builtin_ctype_graph }, + { "ctype_print", jx9Builtin_ctype_print }, + { "ctype_punct", jx9Builtin_ctype_punct }, + { "ctype_space", jx9Builtin_ctype_space }, + { "ctype_lower", jx9Builtin_ctype_lower }, + { "ctype_upper", jx9Builtin_ctype_upper }, + /* Time functions */ + { "time" , jx9Builtin_time }, + { "microtime", jx9Builtin_microtime }, + { "getdate" , jx9Builtin_getdate }, + { "gettimeofday", jx9Builtin_gettimeofday }, + { "date", jx9Builtin_date }, + { "strftime", jx9Builtin_strftime }, + { "idate", jx9Builtin_idate }, + { "gmdate", jx9Builtin_gmdate }, + { "localtime", jx9Builtin_localtime }, + { "mktime", jx9Builtin_mktime }, + { "gmmktime", jx9Builtin_mktime }, + /* URL functions */ + { "base64_encode", jx9Builtin_base64_encode }, + { "base64_decode", jx9Builtin_base64_decode }, + { "convert_uuencode", jx9Builtin_base64_encode }, + { "convert_uudecode", jx9Builtin_base64_decode }, + { "urlencode", jx9Builtin_urlencode }, + { "urldecode", jx9Builtin_urldecode }, + { "rawurlencode", jx9Builtin_urlencode }, + { "rawurldecode", jx9Builtin_urldecode }, +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +}; +/* + * Register the built-in functions defined above, the array functions + * defined in hashmap.c and the IO functions defined in vfs.c. + */ +JX9_PRIVATE void jx9RegisterBuiltInFunction(jx9_vm *pVm) +{ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aBuiltInFunc) ; ++n ){ + jx9_create_function(&(*pVm), aBuiltInFunc[n].zName, aBuiltInFunc[n].xFunc, 0); + } + /* Register hashmap functions [i.e: sort(), count(), array_diff(), ...] */ + jx9RegisterHashmapFunctions(&(*pVm)); + /* Register IO functions [i.e: fread(), fwrite(), chdir(), mkdir(), file(), ...] */ + jx9RegisterIORoutine(&(*pVm)); +} + +/* + * ---------------------------------------------------------- + * File: jx9_compile.c + * MD5: 562e73eb7214f890e71713c6b97a7863 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: compile.c v1.7 FreeBSD 2012-12-11 21:46 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* + * This file implement a thread-safe and full-reentrant compiler for the JX9 engine. + * That is, routines defined in this file takes a stream of tokens and output + * JX9 bytecode instructions. + */ +/* Forward declaration */ +typedef struct LangConstruct LangConstruct; +typedef struct JumpFixup JumpFixup; +/* Block [i.e: set of statements] control flags */ +#define GEN_BLOCK_LOOP 0x001 /* Loop block [i.e: for, while, ...] */ +#define GEN_BLOCK_PROTECTED 0x002 /* Protected block */ +#define GEN_BLOCK_COND 0x004 /* Conditional block [i.e: if(condition){} ]*/ +#define GEN_BLOCK_FUNC 0x008 /* Function body */ +#define GEN_BLOCK_GLOBAL 0x010 /* Global block (always set)*/ +#define GEN_BLOC_NESTED_FUNC 0x020 /* Nested function body */ +#define GEN_BLOCK_EXPR 0x040 /* Expression */ +#define GEN_BLOCK_STD 0x080 /* Standard block */ +#define GEN_BLOCK_SWITCH 0x100 /* Switch statement */ +/* + * Compilation of some JX9 constructs such as if, for, while, the logical or + * (||) and logical and (&&) operators in expressions requires the + * generation of forward jumps. + * Since the destination PC target of these jumps isn't known when the jumps + * are emitted, we record each forward jump in an instance of the following + * structure. Those jumps are fixed later when the jump destination is resolved. + */ +struct JumpFixup +{ + sxi32 nJumpType; /* Jump type. Either TRUE jump, FALSE jump or Unconditional jump */ + sxu32 nInstrIdx; /* Instruction index to fix later when the jump destination is resolved. */ +}; +/* + * Each language construct is represented by an instance + * of the following structure. + */ +struct LangConstruct +{ + sxu32 nID; /* Language construct ID [i.e: JX9_TKWRD_WHILE, JX9_TKWRD_FOR, JX9_TKWRD_IF...] */ + ProcLangConstruct xConstruct; /* C function implementing the language construct */ +}; +/* Compilation flags */ +#define JX9_COMPILE_SINGLE_STMT 0x001 /* Compile a single statement */ +/* Token stream synchronization macros */ +#define SWAP_TOKEN_STREAM(GEN, START, END)\ + pTmp = GEN->pEnd;\ + pGen->pIn = START;\ + pGen->pEnd = END +#define UPDATE_TOKEN_STREAM(GEN)\ + if( GEN->pIn < pTmp ){\ + GEN->pIn++;\ + }\ + GEN->pEnd = pTmp +#define SWAP_DELIMITER(GEN, START, END)\ + pTmpIn = GEN->pIn;\ + pTmpEnd = GEN->pEnd;\ + GEN->pIn = START;\ + GEN->pEnd = END +#define RE_SWAP_DELIMITER(GEN)\ + GEN->pIn = pTmpIn;\ + GEN->pEnd = pTmpEnd +/* Flags related to expression compilation */ +#define EXPR_FLAG_LOAD_IDX_STORE 0x001 /* Set the iP2 flag when dealing with the LOAD_IDX instruction */ +#define EXPR_FLAG_RDONLY_LOAD 0x002 /* Read-only load, refer to the 'JX9_OP_LOAD' VM instruction for more information */ +#define EXPR_FLAG_COMMA_STATEMENT 0x004 /* Treat comma expression as a single statement (used by object attributes) */ +/* Forward declaration */ +static sxi32 jx9CompileExpr( + jx9_gen_state *pGen, /* Code generator state */ + sxi32 iFlags, /* Control flags */ + sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ + ); + +/* + * Recover from a compile-time error. In other words synchronize + * the token stream cursor with the first semi-colon seen. + */ +static sxi32 jx9ErrorRecover(jx9_gen_state *pGen) +{ + /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI /*';'*/) == 0){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Check if the given identifier name is reserved or not. + * Return TRUE if reserved.FALSE otherwise. + */ +static int GenStateIsReservedID(SyString *pName) +{ + if( pName->nByte == sizeof("null") - 1 ){ + if( SyStrnicmp(pName->zString, "null", sizeof("null")-1) == 0 ){ + return TRUE; + }else if( SyStrnicmp(pName->zString, "true", sizeof("true")-1) == 0 ){ + return TRUE; + } + }else if( pName->nByte == sizeof("false") - 1 ){ + if( SyStrnicmp(pName->zString, "false", sizeof("false")-1) == 0 ){ + return TRUE; + } + } + /* Not a reserved constant */ + return FALSE; +} +/* + * Check if a given token value is installed in the literal table. + */ +static sxi32 GenStateFindLiteral(jx9_gen_state *pGen, const SyString *pValue, sxu32 *pIdx) +{ + SyHashEntry *pEntry; + pEntry = SyHashGet(&pGen->hLiteral, (const void *)pValue->zString, pValue->nByte); + if( pEntry == 0 ){ + return SXERR_NOTFOUND; + } + *pIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + return SXRET_OK; +} +/* + * Install a given constant index in the literal table. + * In order to be installed, the jx9_value must be of type string. + */ +static sxi32 GenStateInstallLiteral(jx9_gen_state *pGen,jx9_value *pObj, sxu32 nIdx) +{ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + SyHashInsert(&pGen->hLiteral, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), SX_INT_TO_PTR(nIdx)); + } + return SXRET_OK; +} +/* + * Generate a fatal error. + */ +static sxi32 GenStateOutOfMem(jx9_gen_state *pGen) +{ + jx9GenCompileError(pGen,E_ERROR,1,"Fatal, Jx9 compiler is running out of memory"); + /* Abort compilation immediately */ + return SXERR_ABORT; +} +/* + * Fetch a block that correspond to the given criteria from the stack of + * compiled blocks. + * Return a pointer to that block on success. NULL otherwise. + */ +static GenBlock * GenStateFetchBlock(GenBlock *pCurrent, sxi32 iBlockType, sxi32 iCount) +{ + GenBlock *pBlock = pCurrent; + for(;;){ + if( pBlock->iFlags & iBlockType ){ + iCount--; /* Decrement nesting level */ + if( iCount < 1 ){ + /* Block meet with the desired criteria */ + return pBlock; + } + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + if( pBlock == 0 || (pBlock->iFlags & (GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC)) ){ + /* Forbidden */ + break; + } + } + /* No such block */ + return 0; +} +/* + * Initialize a freshly allocated block instance. + */ +static void GenStateInitBlock( + jx9_gen_state *pGen, /* Code generator state */ + GenBlock *pBlock, /* Target block */ + sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ + sxu32 nFirstInstr, /* First instruction to compile */ + void *pUserData /* Upper layer private data */ + ) +{ + /* Initialize block fields */ + pBlock->nFirstInstr = nFirstInstr; + pBlock->pUserData = pUserData; + pBlock->pGen = pGen; + pBlock->iFlags = iType; + pBlock->pParent = 0; + pBlock->bPostContinue = 0; + SySetInit(&pBlock->aJumpFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); + SySetInit(&pBlock->aPostContFix, &pGen->pVm->sAllocator, sizeof(JumpFixup)); +} +/* + * Allocate a new block instance. + * Return SXRET_OK and write a pointer to the new instantiated block + * on success.Otherwise generate a compile-time error and abort + * processing on failure. + */ +static sxi32 GenStateEnterBlock( + jx9_gen_state *pGen, /* Code generator state */ + sxi32 iType, /* Block type [i.e: loop, conditional, function body, etc.]*/ + sxu32 nFirstInstr, /* First instruction to compile */ + void *pUserData, /* Upper layer private data */ + GenBlock **ppBlock /* OUT: instantiated block */ + ) +{ + GenBlock *pBlock; + /* Allocate a new block instance */ + pBlock = (GenBlock *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(GenBlock)); + if( pBlock == 0 ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return GenStateOutOfMem(pGen); + } + /* Zero the structure */ + SyZero(pBlock, sizeof(GenBlock)); + GenStateInitBlock(&(*pGen), pBlock, iType, nFirstInstr, pUserData); + /* Link to the parent block */ + pBlock->pParent = pGen->pCurrent; + /* Mark as the current block */ + pGen->pCurrent = pBlock; + if( ppBlock ){ + /* Write a pointer to the new instance */ + *ppBlock = pBlock; + } + return SXRET_OK; +} +/* + * Release block fields without freeing the whole instance. + */ +static void GenStateReleaseBlock(GenBlock *pBlock) +{ + SySetRelease(&pBlock->aPostContFix); + SySetRelease(&pBlock->aJumpFix); +} +/* + * Release a block. + */ +static void GenStateFreeBlock(GenBlock *pBlock) +{ + jx9_gen_state *pGen = pBlock->pGen; + GenStateReleaseBlock(&(*pBlock)); + /* Free the instance */ + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pBlock); +} +/* + * POP and release a block from the stack of compiled blocks. + */ +static sxi32 GenStateLeaveBlock(jx9_gen_state *pGen, GenBlock **ppBlock) +{ + GenBlock *pBlock = pGen->pCurrent; + if( pBlock == 0 ){ + /* No more block to pop */ + return SXERR_EMPTY; + } + /* Point to the upper block */ + pGen->pCurrent = pBlock->pParent; + if( ppBlock ){ + /* Write a pointer to the popped block */ + *ppBlock = pBlock; + }else{ + /* Safely release the block */ + GenStateFreeBlock(&(*pBlock)); + } + return SXRET_OK; +} +/* + * Emit a forward jump. + * Notes on forward jumps + * Compilation of some JX9 constructs such as if, for, while and the logical or + * (||) and logical and (&&) operators in expressions requires the + * generation of forward jumps. + * Since the destination PC target of these jumps isn't known when the jumps + * are emitted, we record each forward jump in an instance of the following + * structure. Those jumps are fixed later when the jump destination is resolved. + */ +static sxi32 GenStateNewJumpFixup(GenBlock *pBlock, sxi32 nJumpType, sxu32 nInstrIdx) +{ + JumpFixup sJumpFix; + sxi32 rc; + /* Init the JumpFixup structure */ + sJumpFix.nJumpType = nJumpType; + sJumpFix.nInstrIdx = nInstrIdx; + /* Insert in the jump fixup table */ + rc = SySetPut(&pBlock->aJumpFix,(const void *)&sJumpFix); + return rc; +} +/* + * Fix a forward jump now the jump destination is resolved. + * Return the total number of fixed jumps. + * Notes on forward jumps: + * Compilation of some JX9 constructs such as if, for, while and the logical or + * (||) and logical and (&&) operators in expressions requires the + * generation of forward jumps. + * Since the destination PC target of these jumps isn't known when the jumps + * are emitted, we record each forward jump in an instance of the following + * structure.Those jumps are fixed later when the jump destination is resolved. + */ +static sxu32 GenStateFixJumps(GenBlock *pBlock, sxi32 nJumpType, sxu32 nJumpDest) +{ + JumpFixup *aFix; + VmInstr *pInstr; + sxu32 nFixed; + sxu32 n; + /* Point to the jump fixup table */ + aFix = (JumpFixup *)SySetBasePtr(&pBlock->aJumpFix); + /* Fix the desired jumps */ + for( nFixed = n = 0 ; n < SySetUsed(&pBlock->aJumpFix) ; ++n ){ + if( aFix[n].nJumpType < 0 ){ + /* Already fixed */ + continue; + } + if( nJumpType > 0 && aFix[n].nJumpType != nJumpType ){ + /* Not of our interest */ + continue; + } + /* Point to the instruction to fix */ + pInstr = jx9VmGetInstr(pBlock->pGen->pVm, aFix[n].nInstrIdx); + if( pInstr ){ + pInstr->iP2 = nJumpDest; + nFixed++; + /* Mark as fixed */ + aFix[n].nJumpType = -1; + } + } + /* Total number of fixed jumps */ + return nFixed; +} +/* + * Reserve a room for a numeric constant [i.e: 64-bit integer or real number] + * in the constant table. + */ +static jx9_value * GenStateInstallNumLiteral(jx9_gen_state *pGen, sxu32 *pIdx) +{ + jx9_value *pObj; + sxu32 nIdx = 0; /* cc warning */ + /* Reserve a new constant */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + GenStateOutOfMem(pGen); + return 0; + } + *pIdx = nIdx; + /* TODO(chems): Create a numeric table (64bit int keys) same as + * the constant string iterals table [optimization purposes]. + */ + return pObj; +} +/* + * Compile a numeric [i.e: integer or real] literal. + * Notes on the integer type. + * According to the JX9 language reference manual + * Integers can be specified in decimal (base 10), hexadecimal (base 16), octal (base 8) + * or binary (base 2) notation, optionally preceded by a sign (- or +). + * To use octal notation, precede the number with a 0 (zero). To use hexadecimal + * notation precede the number with 0x. To use binary notation precede the number with 0b. + */ +static sxi32 jx9CompileNumLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) +{ + SyToken *pToken = pGen->pIn; /* Raw token */ + sxu32 nIdx = 0; + if( pToken->nType & JX9_TK_INTEGER ){ + jx9_value *pObj; + sxi64 iValue; + iValue = jx9TokenValueToInt64(&pToken->sData); + pObj = GenStateInstallNumLiteral(&(*pGen), &nIdx); + if( pObj == 0 ){ + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + jx9MemObjInitFromInt(pGen->pVm, pObj, iValue); + }else{ + /* Real number */ + jx9_value *pObj; + /* Reserve a new constant */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + return GenStateOutOfMem(pGen); + } + jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); + jx9MemObjToReal(pObj); + } + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a nowdoc string. + * According to the JX9 language reference manual: + * + * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. + * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. + * The construct is ideal for embedding JX9 code or other large blocks of text without the + * need for escaping. It shares some features in common with the SGML + * construct, in that it declares a block of text which is not for parsing. + * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier + * which follows is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc + * identifiers also apply to nowdoc identifiers, especially those regarding the appearance + * of the closing identifier. + */ +static sxi32 jx9CompileNowdoc(jx9_gen_state *pGen,sxi32 iCompileFlag) +{ + SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ + jx9_value *pObj; + sxu32 nIdx; + nIdx = 0; /* Prevent compiler warning */ + if( pStr->nByte <= 0 ){ + /* Empty string, load NULL */ + jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC, 0, 0, 0, 0); + return SXRET_OK; + } + /* Reserve a new constant */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "JX9 engine is running out of memory"); + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + /* No processing is done here, simply a memcpy() operation */ + jx9MemObjInitFromString(pGen->pVm, pObj, pStr); + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a single quoted string. + * According to the JX9 language reference manual: + * + * The simplest way to specify a string is to enclose it in single quotes (the character ' ). + * To specify a literal single quote, escape it with a backslash (\). To specify a literal + * backslash, double it (\\). All other instances of backslash will be treated as a literal + * backslash: this means that the other escape sequences you might be used to, such as \r + * or \n, will be output literally as specified rather than having any special meaning. + * + */ +JX9_PRIVATE sxi32 jx9CompileSimpleString(jx9_gen_state *pGen, sxi32 iCompileFlag) +{ + SyString *pStr = &pGen->pIn->sData; /* Constant string literal */ + const char *zIn, *zCur, *zEnd; + jx9_value *pObj; + sxu32 nIdx; + nIdx = 0; /* Prevent compiler warning */ + /* Delimit the string */ + zIn = pStr->zString; + zEnd = &zIn[pStr->nByte]; + if( zIn > zEnd ){ + /* Empty string, load NULL */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); + return SXRET_OK; + } + if( SXRET_OK == GenStateFindLiteral(&(*pGen), pStr, &nIdx) ){ + /* Already processed, emit the load constant instruction + * and return. + */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + return SXRET_OK; + } + /* Reserve a new constant */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + jx9GenCompileError(&(*pGen), E_ERROR, 1, "JX9 engine is running out of memory"); + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + jx9MemObjInitFromString(pGen->pVm, pObj, 0); + /* Compile the node */ + for(;;){ + if( zIn >= zEnd ){ + /* End of input */ + break; + } + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\\' ){ + zIn++; + } + if( zIn > zCur ){ + /* Append raw contents*/ + jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); + } + else + { + jx9MemObjStringAppend(pObj, "", 0); + } + zIn++; + if( zIn < zEnd ){ + if( zIn[0] == '\\' ){ + /* A literal backslash */ + jx9MemObjStringAppend(pObj, "\\", sizeof(char)); + }else if( zIn[0] == '\'' ){ + /* A single quote */ + jx9MemObjStringAppend(pObj, "'", sizeof(char)); + }else{ + /* verbatim copy */ + zIn--; + jx9MemObjStringAppend(pObj, zIn, sizeof(char)*2); + zIn++; + } + } + /* Advance the stream cursor */ + zIn++; + } + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + if( pStr->nByte < 1024 ){ + /* Install in the literal table */ + GenStateInstallLiteral(pGen, pObj, nIdx); + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Process variable expression [i.e: "$var", "${var}"] embedded in a double quoted/heredoc string. + * According to the JX9 language reference manual + * When a string is specified in double quotes or with heredoc, variables are parsed within it. + * There are two types of syntax: a simple one and a complex one. The simple syntax is the most + * common and convenient. It provides a way to embed a variable, an array value, or an object + * property in a string with a minimum of effort. + * Simple syntax + * If a dollar sign ($) is encountered, the parser will greedily take as many tokens as possible + * to form a valid variable name. Enclose the variable name in curly braces to explicitly specify + * the end of the name. + * Similarly, an array index or an object property can be parsed. With array indices, the closing + * square bracket (]) marks the end of the index. The same rules apply to object properties + * as to simple variables. + * Complex (curly) syntax + * This isn't called complex because the syntax is complex, but because it allows for the use + * of complex expressions. + * Any scalar variable, array element or object property with a string representation can be + * included via this syntax. Simply write the expression the same way as it would appear outside + * the string, and then wrap it in { and }. Since { can not be escaped, this syntax will only + * be recognised when the $ immediately follows the {. Use {\$ to get a literal {$ + */ +static sxi32 GenStateProcessStringExpression( + jx9_gen_state *pGen, /* Code generator state */ + const char *zIn, /* Raw expression */ + const char *zEnd /* End of the expression */ + ) +{ + SyToken *pTmpIn, *pTmpEnd; + SySet sToken; + sxi32 rc; + /* Initialize the token set */ + SySetInit(&sToken, &pGen->pVm->sAllocator, sizeof(SyToken)); + /* Preallocate some slots */ + SySetAlloc(&sToken, 0x08); + /* Tokenize the text */ + jx9Tokenize(zIn,(sxu32)(zEnd-zIn),&sToken); + /* Swap delimiter */ + pTmpIn = pGen->pIn; + pTmpEnd = pGen->pEnd; + pGen->pIn = (SyToken *)SySetBasePtr(&sToken); + pGen->pEnd = &pGen->pIn[SySetUsed(&sToken)]; + /* Compile the expression */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + /* Restore token stream */ + pGen->pIn = pTmpIn; + pGen->pEnd = pTmpEnd; + /* Release the token set */ + SySetRelease(&sToken); + /* Compilation result */ + return rc; +} +/* + * Reserve a new constant for a double quoted/heredoc string. + */ +static jx9_value * GenStateNewStrObj(jx9_gen_state *pGen,sxi32 *pCount) +{ + jx9_value *pConstObj; + sxu32 nIdx = 0; + /* Reserve a new constant */ + pConstObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pConstObj == 0 ){ + GenStateOutOfMem(&(*pGen)); + return 0; + } + (*pCount)++; + jx9MemObjInitFromString(pGen->pVm, pConstObj, 0); + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + return pConstObj; +} +/* + * Compile a double quoted/heredoc string. + * According to the JX9 language reference manual + * Heredoc + * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier + * is provided, then a newline. The string itself follows, and then the same identifier again + * to close the quotation. + * The closing identifier must begin in the first column of the line. Also, the identifier must + * follow the same naming rules as any other label in JX9: it must contain only alphanumeric + * characters and underscores, and must start with a non-digit character or underscore. + * Warning + * It is very important to note that the line with the closing identifier must contain + * no other characters, except possibly a semicolon (;). That means especially that the identifier + * may not be indented, and there may not be any spaces or tabs before or after the semicolon. + * It's also important to realize that the first character before the closing identifier must + * be a newline as defined by the local operating system. This is \n on UNIX systems, including Mac OS X. + * The closing delimiter (possibly followed by a semicolon) must also be followed by a newline. + * If this rule is broken and the closing identifier is not "clean", it will not be considered a closing + * identifier, and JX9 will continue looking for one. If a proper closing identifier is not found before + * the end of the current file, a parse error will result at the last line. + * Heredocs can not be used for initializing object properties. + * Double quoted + * If the string is enclosed in double-quotes ("), JX9 will interpret more escape sequences for special characters: + * Escaped characters Sequence Meaning + * \n linefeed (LF or 0x0A (10) in ASCII) + * \r carriage return (CR or 0x0D (13) in ASCII) + * \t horizontal tab (HT or 0x09 (9) in ASCII) + * \v vertical tab (VT or 0x0B (11) in ASCII) + * \f form feed (FF or 0x0C (12) in ASCII) + * \\ backslash + * \$ dollar sign + * \" double-quote + * \[0-7]{1, 3} the sequence of characters matching the regular expression is a character in octal notation + * \x[0-9A-Fa-f]{1, 2} the sequence of characters matching the regular expression is a character in hexadecimal notation + * As in single quoted strings, escaping any other character will result in the backslash being printed too. + * The most important feature of double-quoted strings is the fact that variable names will be expanded. + * See string parsing for details. + */ +static sxi32 GenStateCompileString(jx9_gen_state *pGen) +{ + SyString *pStr = &pGen->pIn->sData; /* Raw token value */ + const char *zIn, *zCur, *zEnd; + jx9_value *pObj = 0; + sxi32 iCons; + sxi32 rc; + /* Delimit the string */ + zIn = pStr->zString; + zEnd = &zIn[pStr->nByte]; + if( zIn > zEnd ){ + /* Empty string, load NULL */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); + return SXRET_OK; + } + zCur = 0; + /* Compile the node */ + iCons = 0; + for(;;){ + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\\' ){ + if(zIn[0] == '$' && &zIn[1] < zEnd && + (((unsigned char)zIn[1] >= 0xc0 || SyisAlpha(zIn[1]) || zIn[1] == '_')) ){ + break; + } + zIn++; + } + if( zIn > zCur ){ + if( pObj == 0 ){ + pObj = GenStateNewStrObj(&(*pGen), &iCons); + if( pObj == 0 ){ + return SXERR_ABORT; + } + } + jx9MemObjStringAppend(pObj, zCur, (sxu32)(zIn-zCur)); + } + else + { + if( pObj == 0 ){ + pObj = GenStateNewStrObj(&(*pGen), &iCons); + if( pObj == 0 ){ + return SXERR_ABORT; + } + } + jx9MemObjStringAppend(pObj, "", 0); + } + if( zIn >= zEnd ){ + break; + } + if( zIn[0] == '\\' ){ + const char *zPtr = 0; + sxu32 n; + zIn++; + if( zIn >= zEnd ){ + break; + } + if( pObj == 0 ){ + pObj = GenStateNewStrObj(&(*pGen), &iCons); + if( pObj == 0 ){ + return SXERR_ABORT; + } + } + n = sizeof(char); /* size of conversion */ + switch( zIn[0] ){ + case '$': + /* Dollar sign */ + jx9MemObjStringAppend(pObj, "$", sizeof(char)); + break; + case '\\': + /* A literal backslash */ + jx9MemObjStringAppend(pObj, "\\", sizeof(char)); + break; + case 'a': + /* The "alert" character (BEL)[ctrl+g] ASCII code 7 */ + jx9MemObjStringAppend(pObj, "\a", sizeof(char)); + break; + case 'b': + /* Backspace (BS)[ctrl+h] ASCII code 8 */ + jx9MemObjStringAppend(pObj, "\b", sizeof(char)); + break; + case 'f': + /* Form-feed (FF)[ctrl+l] ASCII code 12 */ + jx9MemObjStringAppend(pObj, "\f", sizeof(char)); + break; + case 'n': + /* Line feed(new line) (LF)[ctrl+j] ASCII code 10 */ + jx9MemObjStringAppend(pObj, "\n", sizeof(char)); + break; + case 'r': + /* Carriage return (CR)[ctrl+m] ASCII code 13 */ + jx9MemObjStringAppend(pObj, "\r", sizeof(char)); + break; + case 't': + /* Horizontal tab (HT)[ctrl+i] ASCII code 9 */ + jx9MemObjStringAppend(pObj, "\t", sizeof(char)); + break; + case 'v': + /* Vertical tab(VT)[ctrl+k] ASCII code 11 */ + jx9MemObjStringAppend(pObj, "\v", sizeof(char)); + break; + case '\'': + /* Single quote */ + jx9MemObjStringAppend(pObj, "'", sizeof(char)); + break; + case '"': + /* Double quote */ + jx9MemObjStringAppend(pObj, "\"", sizeof(char)); + break; + case '0': + /* NUL byte */ + jx9MemObjStringAppend(pObj, "\0", sizeof(char)); + break; + case 'x': + if((unsigned char)zIn[1] < 0xc0 && SyisHex(zIn[1]) ){ + int c; + /* Hex digit */ + c = SyHexToint(zIn[1]) << 4; + if( &zIn[2] < zEnd ){ + c += SyHexToint(zIn[2]); + } + /* Output char */ + jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); + n += sizeof(char) * 2; + }else{ + /* Output literal character */ + jx9MemObjStringAppend(pObj, "x", sizeof(char)); + } + break; + case 'o': + if( &zIn[1] < zEnd && (unsigned char)zIn[1] < 0xc0 && SyisDigit(zIn[1]) && (zIn[1] - '0') < 8 ){ + /* Octal digit stream */ + int c; + c = 0; + zIn++; + for( zPtr = zIn ; zPtr < &zIn[3*sizeof(char)] ; zPtr++ ){ + if( zPtr >= zEnd || (unsigned char)zPtr[0] >= 0xc0 || !SyisDigit(zPtr[0]) || (zPtr[0] - '0') > 7 ){ + break; + } + c = c * 8 + (zPtr[0] - '0'); + } + if ( c > 0 ){ + jx9MemObjStringAppend(pObj, (const char *)&c, sizeof(char)); + } + n = (sxu32)(zPtr-zIn); + }else{ + /* Output literal character */ + jx9MemObjStringAppend(pObj, "o", sizeof(char)); + } + break; + default: + /* Output without a slash */ + jx9MemObjStringAppend(pObj, zIn, sizeof(char)); + break; + } + /* Advance the stream cursor */ + zIn += n; + continue; + } + if( zIn[0] == '{' ){ + /* Curly syntax */ + const char *zExpr; + sxi32 iNest = 1; + zIn++; + zExpr = zIn; + /* Synchronize with the next closing curly braces */ + while( zIn < zEnd ){ + if( zIn[0] == '{' ){ + /* Increment nesting level */ + iNest++; + }else if(zIn[0] == '}' ){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + } + zIn++; + } + /* Process the expression */ + rc = GenStateProcessStringExpression(&(*pGen),zExpr,zIn); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc != SXERR_EMPTY ){ + ++iCons; + } + if( zIn < zEnd ){ + /* Jump the trailing curly */ + zIn++; + } + }else{ + /* Simple syntax */ + const char *zExpr = zIn; + /* Assemble variable name */ + for(;;){ + /* Jump leading dollars */ + while( zIn < zEnd && zIn[0] == '$' ){ + zIn++; + } + for(;;){ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_' ) ){ + zIn++; + } + if((unsigned char)zIn[0] >= 0xc0 ){ + /* UTF-8 stream */ + zIn++; + while( zIn < zEnd && (((unsigned char)zIn[0] & 0xc0) == 0x80) ){ + zIn++; + } + continue; + } + break; + } + if( zIn >= zEnd ){ + break; + } + if( zIn[0] == '[' ){ + sxi32 iSquare = 1; + zIn++; + while( zIn < zEnd ){ + if( zIn[0] == '[' ){ + iSquare++; + }else if (zIn[0] == ']' ){ + iSquare--; + if( iSquare <= 0 ){ + break; + } + } + zIn++; + } + if( zIn < zEnd ){ + zIn++; + } + break; + }else if( zIn[0] == '.' ){ + /* Member access operator '.' */ + zIn++; + }else{ + break; + } + } + /* Process the expression */ + rc = GenStateProcessStringExpression(&(*pGen),zExpr, zIn); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc != SXERR_EMPTY ){ + ++iCons; + } + } + /* Invalidate the previously used constant */ + pObj = 0; + }/*for(;;)*/ + if( iCons > 1 ){ + /* Concatenate all compiled constants */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_CAT, iCons, 0, 0, 0); + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a double quoted string. + * See the block-comment above for more information. + */ +JX9_PRIVATE sxi32 jx9CompileString(jx9_gen_state *pGen, sxi32 iCompileFlag) +{ + sxi32 rc; + rc = GenStateCompileString(&(*pGen)); + SXUNUSED(iCompileFlag); /* cc warning */ + /* Compilation result */ + return rc; +} +/* + * Compile a literal which is an identifier(name) for simple values. + */ +JX9_PRIVATE sxi32 jx9CompileLiteral(jx9_gen_state *pGen,sxi32 iCompileFlag) +{ + SyToken *pToken = pGen->pIn; + jx9_value *pObj; + SyString *pStr; + sxu32 nIdx; + /* Extract token value */ + pStr = &pToken->sData; + /* Deal with the reserved literals [i.e: null, false, true, ...] first */ + if( pStr->nByte == sizeof("NULL") - 1 ){ + if( SyStrnicmp(pStr->zString, "null", sizeof("NULL")-1) == 0 ){ + /* NULL constant are always indexed at 0 */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); + return SXRET_OK; + }else if( SyStrnicmp(pStr->zString, "true", sizeof("TRUE")-1) == 0 ){ + /* TRUE constant are always indexed at 1 */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1, 0, 0); + return SXRET_OK; + } + }else if (pStr->nByte == sizeof("FALSE") - 1 && + SyStrnicmp(pStr->zString, "false", sizeof("FALSE")-1) == 0 ){ + /* FALSE constant are always indexed at 2 */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 2, 0, 0); + return SXRET_OK; + }else if(pStr->nByte == sizeof("__LINE__") - 1 && + SyMemcmp(pStr->zString, "__LINE__", sizeof("__LINE__")-1) == 0 ){ + /* TICKET 1433-004: __LINE__ constant must be resolved at compile time, not run time */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + SXUNUSED(iCompileFlag); /* cc warning */ + return GenStateOutOfMem(pGen); + } + jx9MemObjInitFromInt(pGen->pVm, pObj, pToken->nLine); + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + return SXRET_OK; + }else if( pStr->nByte == sizeof("__FUNCTION__") - 1 && + SyMemcmp(pStr->zString, "__FUNCTION__", sizeof("__FUNCTION__")-1) == 0 ){ + GenBlock *pBlock = pGen->pCurrent; + /* TICKET 1433-004: __FUNCTION__/__METHOD__ constants must be resolved at compile time, not run time */ + while( pBlock && (pBlock->iFlags & GEN_BLOCK_FUNC) == 0 ){ + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if( pBlock == 0 ){ + /* Called in the global scope, load NULL */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 0, 0, 0); + }else{ + /* Extract the target function/method */ + jx9_vm_func *pFunc = (jx9_vm_func *)pBlock->pUserData; + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + return GenStateOutOfMem(pGen); + } + jx9MemObjInitFromString(pGen->pVm, pObj, &pFunc->sName); + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + } + return SXRET_OK; + } + /* Query literal table */ + if( SXRET_OK != GenStateFindLiteral(&(*pGen), &pToken->sData, &nIdx) ){ + jx9_value *pObj; + /* Unknown literal, install it in the literal table */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + return GenStateOutOfMem(pGen); + } + jx9MemObjInitFromString(pGen->pVm, pObj, &pToken->sData); + GenStateInstallLiteral(&(*pGen), pObj, nIdx); + } + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm,JX9_OP_LOADC,1,nIdx, 0, 0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile an array entry whether it is a key or a value. + */ +static sxi32 GenStateCompileJSONEntry( + jx9_gen_state *pGen, /* Code generator state */ + SyToken *pIn, /* Token stream */ + SyToken *pEnd, /* End of the token stream */ + sxi32 iFlags, /* Compilation flags */ + sxi32 (*xValidator)(jx9_gen_state *,jx9_expr_node *) /* Expression tree validator callback */ + ) +{ + SyToken *pTmpIn, *pTmpEnd; + sxi32 rc; + /* Swap token stream */ + SWAP_DELIMITER(pGen, pIn, pEnd); + /* Compile the expression*/ + rc = jx9CompileExpr(&(*pGen), iFlags, xValidator); + /* Restore token stream */ + RE_SWAP_DELIMITER(pGen); + return rc; +} +/* + * Compile a Jx9 JSON Array. + */ +JX9_PRIVATE sxi32 jx9CompileJsonArray(jx9_gen_state *pGen, sxi32 iCompileFlag) +{ + sxi32 nPair = 0; + SyToken *pCur; + sxi32 rc; + + pGen->pIn++; /* Jump the open square bracket '['*/ + pGen->pEnd--; + SXUNUSED(iCompileFlag); /* cc warning */ + for(;;){ + /* Jump leading commas */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ + pGen->pIn++; + } + pCur = pGen->pIn; + if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ + /* No more entry to process */ + break; + } + /* Compile entry */ + rc = GenStateCompileJSONEntry(&(*pGen),pCur,pGen->pIn,EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + nPair++; + } + /* Emit the load map instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP,nPair,0,0,0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Node validator for a given JSON key. + */ +static sxi32 GenStateJSONObjectKeyNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) +{ + sxi32 rc = SXRET_OK; + if( pRoot->xCode != jx9CompileVariable && pRoot->xCode != jx9CompileString + && pRoot->xCode != jx9CompileSimpleString && pRoot->xCode != jx9CompileLiteral ){ + /* Unexpected expression */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pRoot->pStart? pRoot->pStart->nLine : 0, + "JSON Object: Unexpected expression, key must be of type string, literal or simple variable"); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + return rc; +} +/* + * Compile a Jx9 JSON Object + */ +JX9_PRIVATE sxi32 jx9CompileJsonObject(jx9_gen_state *pGen, sxi32 iCompileFlag) +{ + SyToken *pKey, *pCur; + sxi32 nPair = 0; + sxi32 rc; + + pGen->pIn++; /* Jump the open querly braces '{'*/ + pGen->pEnd--; + SXUNUSED(iCompileFlag); /* cc warning */ + for(;;){ + /* Jump leading commas */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_COMMA) ){ + pGen->pIn++; + } + pCur = pGen->pIn; + if( SXRET_OK != jx9GetNextExpr(pGen->pIn, pGen->pEnd, &pGen->pIn) ){ + /* No more entry to process */ + break; + } + /* Compile the key */ + pKey = pCur; + while( pCur < pGen->pIn ){ + if( pCur->nType & JX9_TK_COLON /*':'*/ ){ + break; + } + pCur++; + } + rc = SXERR_EMPTY; + if( (pCur->nType & JX9_TK_COLON) == 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ABORT, pCur->nLine, "JSON Object: Missing colon string \":\""); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; + } + + if( pCur < pGen->pIn ){ + if( &pCur[1] >= pGen->pIn ){ + /* Missing value */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pCur->nLine, "JSON Object: Missing entry value"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Compile the expression holding the key */ + rc = GenStateCompileJSONEntry(&(*pGen), pKey, pCur, + EXPR_FLAG_RDONLY_LOAD /* Do not create the variable if inexistant */, + GenStateJSONObjectKeyNodeValidator /* Node validator callback */ + ); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pCur++; /* Jump the double colon ':' */ + }else if( pKey == pCur ){ + /* Key is omitted, emit an error */ + jx9GenCompileError(&(*pGen),E_ERROR, pCur->nLine, "JSON Object: Missing entry key"); + pCur++; /* Jump the double colon ':' */ + }else{ + /* Reset back the cursor and point to the entry value */ + pCur = pKey; + } + /* Compile indice value */ + rc = GenStateCompileJSONEntry(&(*pGen), pCur, pGen->pIn, EXPR_FLAG_RDONLY_LOAD/*Do not create the variable if inexistant*/,0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + nPair++; + } + /* Emit the load map instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD_MAP, nPair * 2, 1, 0, 0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a function [i.e: print, exit(), include(), ...] which is a langauge + * construct. + */ +JX9_PRIVATE sxi32 jx9CompileLangConstruct(jx9_gen_state *pGen,sxi32 iCompileFlag) +{ + SyString *pName; + sxu32 nKeyID; + sxi32 rc; + /* Name of the language construct [i.e: print, die...]*/ + pName = &pGen->pIn->sData; + nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); + pGen->pIn++; /* Jump the language construct keyword */ + if( nKeyID == JX9_TKWRD_PRINT ){ + SyToken *pTmp, *pNext = 0; + /* Compile arguments one after one */ + pTmp = pGen->pEnd; + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, 1 /* Boolean true index */, 0, 0); + while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ + if( pGen->pIn < pNext ){ + pGen->pEnd = pNext; + rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( rc != SXERR_EMPTY ){ + /* Ticket 1433-008: Optimization #1: Consume input directly + * without the overhead of a function call. + * This is a very powerful optimization that improve + * performance greatly. + */ + jx9VmEmitInstr(pGen->pVm,JX9_OP_CONSUME,1,0,0,0); + } + } + /* Jump trailing commas */ + while( pNext < pTmp && (pNext->nType & JX9_TK_COMMA) ){ + pNext++; + } + pGen->pIn = pNext; + } + /* Restore token stream */ + pGen->pEnd = pTmp; + }else{ + sxi32 nArg = 0; + sxu32 nIdx = 0; + rc = jx9CompileExpr(&(*pGen), EXPR_FLAG_RDONLY_LOAD/* Do not create variable if inexistant */, 0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nArg = 1; + } + if( SXRET_OK != GenStateFindLiteral(&(*pGen), pName, &nIdx) ){ + jx9_value *pObj; + /* Emit the call instruction */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + SXUNUSED(iCompileFlag); /* cc warning */ + return GenStateOutOfMem(pGen); + } + jx9MemObjInitFromString(pGen->pVm, pObj, pName); + /* Install in the literal table */ + GenStateInstallLiteral(&(*pGen), pObj, nIdx); + } + /* Emit the call instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + jx9VmEmitInstr(pGen->pVm, JX9_OP_CALL, nArg, 0, 0, 0); + } + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile a node holding a variable declaration. + * According to the J9X language reference + * Variables in JX9 are represented by a dollar sign followed by the name of the variable. + * The variable name is case-sensitive. + * Variable names follow the same rules as other labels in JX9. A valid variable name + * starts with a letter, underscore or any UTF-8 stream, followed by any number of letters + * numbers, or underscores. + * By default, variables are always assigned by value unless the target value is a JSON + * array or a JSON object which is passed by reference. + */ +JX9_PRIVATE sxi32 jx9CompileVariable(jx9_gen_state *pGen,sxi32 iCompileFlag) +{ + sxu32 nLine = pGen->pIn->nLine; + SyHashEntry *pEntry; + SyString *pName; + char *zName = 0; + sxi32 iP1; + void *p3; + sxi32 rc; + + pGen->pIn++; /* Jump the dollar sign '$' */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ + /* Invalid variable name */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Invalid variable name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Extract variable name */ + pName = &pGen->pIn->sData; + /* Advance the stream cursor */ + pGen->pIn++; + pEntry = SyHashGet(&pGen->hVar, (const void *)pName->zString, pName->nByte); + if( pEntry == 0 ){ + /* Duplicate name */ + zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); + if( zName == 0 ){ + return GenStateOutOfMem(pGen); + } + /* Install in the hashtable */ + SyHashInsert(&pGen->hVar, zName, pName->nByte, zName); + }else{ + /* Name already available */ + zName = (char *)pEntry->pUserData; + } + p3 = (void *)zName; + iP1 = 0; + if( iCompileFlag & EXPR_FLAG_RDONLY_LOAD ){ + if( (iCompileFlag & EXPR_FLAG_LOAD_IDX_STORE) == 0 ){ + /* Read-only load.In other words do not create the variable if inexistant */ + iP1 = 1; + } + } + /* Emit the load instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOAD, iP1, 0, p3, 0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* Forward declaration */ +static sxi32 GenStateCompileFunc(jx9_gen_state *pGen,SyString *pName,sxi32 iFlags,jx9_vm_func **ppFunc); +/* + * Compile an annoynmous function or a closure. + * According to the JX9 language reference + * Anonymous functions, also known as closures, allow the creation of functions + * which have no specified name. They are most useful as the value of callback + * parameters, but they have many other uses. Closures can also be used as + * the values of variables; Assigning a closure to a variable uses the same + * syntax as any other assignment, including the trailing semicolon: + * Example Anonymous function variable assignment example + * $greet = function($name) + * { + * printf("Hello %s\r\n", $name); + * }; + * $greet('World'); + * $greet('JX9'); + * Note that the implementation of annoynmous function and closure under + * JX9 is completely different from the one used by the engine. + */ +JX9_PRIVATE sxi32 jx9CompileAnnonFunc(jx9_gen_state *pGen,sxi32 iCompileFlag) +{ + jx9_vm_func *pAnnonFunc; /* Annonymous function body */ + char zName[512]; /* Unique lambda name */ + static int iCnt = 1; /* There is no worry about thread-safety here, because only + * one thread is allowed to compile the script. + */ + jx9_value *pObj; + SyString sName; + sxu32 nIdx; + sxu32 nLen; + sxi32 rc; + + pGen->pIn++; /* Jump the 'function' keyword */ + if( pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ + pGen->pIn++; + } + /* Reserve a constant for the lambda */ + pObj = jx9VmReserveConstObj(pGen->pVm, &nIdx); + if( pObj == 0 ){ + GenStateOutOfMem(pGen); + SXUNUSED(iCompileFlag); /* cc warning */ + return SXERR_ABORT; + } + /* Generate a unique name */ + nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); + /* Make sure the generated name is unique */ + while( SyHashGet(&pGen->pVm->hFunction, zName, nLen) != 0 && nLen < sizeof(zName) - 2 ){ + nLen = SyBufferFormat(zName, sizeof(zName), "[lambda_%d]", iCnt++); + } + SyStringInitFromBuf(&sName, zName, nLen); + jx9MemObjInitFromString(pGen->pVm, pObj, &sName); + /* Compile the lambda body */ + rc = GenStateCompileFunc(&(*pGen),&sName,0,&pAnnonFunc); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Emit the load constant instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_LOADC, 0, nIdx, 0, 0); + /* Node successfully compiled */ + return SXRET_OK; +} +/* + * Compile the 'continue' statement. + * According to the JX9 language reference + * continue is used within looping structures to skip the rest of the current loop iteration + * and continue execution at the condition evaluation and then the beginning of the next + * iteration. + * Note: Note that in JX9 the switch statement is considered a looping structure for + * the purposes of continue. + * continue accepts an optional numeric argument which tells it how many levels + * of enclosing loops it should skip to the end of. + * Note: + * continue 0; and continue 1; is the same as running continue;. + */ +static sxi32 jx9CompileContinue(jx9_gen_state *pGen) +{ + GenBlock *pLoop; /* Target loop */ + sxi32 iLevel; /* How many nesting loop to skip */ + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + iLevel = 0; + /* Jump the 'continue' keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ + /* optional numeric argument which tells us how many levels + * of enclosing loops we should skip to the end of. + */ + iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); + if( iLevel < 2 ){ + iLevel = 0; + } + pGen->pIn++; /* Jump the optional numeric argument */ + } + /* Point to the target loop */ + pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); + if( pLoop == 0 ){ + /* Illegal continue */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "A 'continue' statement may only be used within a loop or switch"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + }else{ + sxu32 nInstrIdx = 0; + /* Emit the unconditional jump to the beginning of the target loop */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pLoop->nFirstInstr, 0, &nInstrIdx); + if( pLoop->bPostContinue == TRUE ){ + JumpFixup sJumpFix; + /* Post-continue */ + sJumpFix.nJumpType = JX9_OP_JMP; + sJumpFix.nInstrIdx = nInstrIdx; + SySetPut(&pLoop->aPostContFix, (const void *)&sJumpFix); + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + /* Not so fatal, emit a warning only */ + jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'continue' statement"); + } + /* Statement successfully compiled */ + return SXRET_OK; +} +/* + * Compile the 'break' statement. + * According to the JX9 language reference + * break ends execution of the current for, foreach, while, do-while or switch + * structure. + * break accepts an optional numeric argument which tells it how many nested + * enclosing structures are to be broken out of. + */ +static sxi32 jx9CompileBreak(jx9_gen_state *pGen) +{ + GenBlock *pLoop; /* Target loop */ + sxi32 iLevel; /* How many nesting loop to skip */ + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + iLevel = 0; + /* Jump the 'break' keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_NUM) ){ + /* optional numeric argument which tells us how many levels + * of enclosing loops we should skip to the end of. + */ + iLevel = (sxi32)jx9TokenValueToInt64(&pGen->pIn->sData); + if( iLevel < 2 ){ + iLevel = 0; + } + pGen->pIn++; /* Jump the optional numeric argument */ + } + /* Extract the target loop */ + pLoop = GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, iLevel); + if( pLoop == 0 ){ + /* Illegal break */ + rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "A 'break' statement may only be used within a loop or switch"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + }else{ + sxu32 nInstrIdx; + rc = jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nInstrIdx); + if( rc == SXRET_OK ){ + /* Fix the jump later when the jump destination is resolved */ + GenStateNewJumpFixup(pLoop, JX9_OP_JMP, nInstrIdx); + } + } + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + /* Not so fatal, emit a warning only */ + jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Expected semi-colon ';' after 'break' statement"); + } + /* Statement successfully compiled */ + return SXRET_OK; +} +/* Forward declaration */ +static sxi32 GenStateCompileChunk(jx9_gen_state *pGen,sxi32 iFlags); +/* + * Compile a JX9 block. + * A block is simply one or more JX9 statements and expressions to compile + * optionally delimited by braces {}. + * Return SXRET_OK on success. Any other return value indicates failure + * and this function takes care of generating the appropriate error + * message. + */ +static sxi32 jx9CompileBlock( + jx9_gen_state *pGen /* Code generator state */ + ) +{ + sxi32 rc; + if( pGen->pIn->nType & JX9_TK_OCB /* '{' */ ){ + sxu32 nLine = pGen->pIn->nLine; + rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, jx9VmInstrLength(pGen->pVm), 0, 0); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + pGen->pIn++; + /* Compile until we hit the closing braces '}' */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + /* No more token to process. Missing closing braces */ + jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing closing braces '}'"); + break; + } + if( pGen->pIn->nType & JX9_TK_CCB/*'}'*/ ){ + /* Closing braces found, break immediately*/ + pGen->pIn++; + break; + } + /* Compile a single statement */ + rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + GenStateLeaveBlock(&(*pGen), 0); + }else{ + /* Compile a single statement */ + rc = GenStateCompileChunk(&(*pGen),JX9_COMPILE_SINGLE_STMT); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Jump trailing semi-colons ';' */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the gentle 'while' statement. + * According to the JX9 language reference + * while loops are the simplest type of loop in JX9.They behave just like their C counterparts. + * The basic form of a while statement is: + * while (expr) + * statement + * The meaning of a while statement is simple. It tells JX9 to execute the nested statement(s) + * repeatedly, as long as the while expression evaluates to TRUE. The value of the expression + * is checked each time at the beginning of the loop, so even if this value changes during + * the execution of the nested statement(s), execution will not stop until the end of the iteration + * (each time JX9 runs the statements in the loop is one iteration). Sometimes, if the while + * expression evaluates to FALSE from the very beginning, the nested statement(s) won't even be run once. + * Like with the if statement, you can group multiple statements within the same while loop by surrounding + * a group of statements with curly braces, or by using the alternate syntax: + * while (expr): + * statement + * endwhile; + */ +static sxi32 jx9CompileWhile(jx9_gen_state *pGen) +{ + GenBlock *pWhileBlock = 0; + SyToken *pTmp, *pEnd = 0; + sxu32 nFalseJump; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'while' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'while' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pWhileBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Delimit the condition */ + jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'while' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Compile the expression */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Update token stream */ + while(pGen->pIn < pEnd ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pGen->pIn++; + } + /* Synchronize pointers */ + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + /* Emit the false jump */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pWhileBlock, JX9_OP_JZ, nFalseJump); + /* Compile the loop body */ + rc = jx9CompileBlock(&(*pGen)); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Emit the unconditional jump to the start of the loop */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pWhileBlock->nFirstInstr, 0, 0); + /* Fix all jumps now the destination is resolved */ + GenStateFixJumps(pWhileBlock, -1, jx9VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen, 0); + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';' so we can avoid + * compiling this erroneous block. + */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the complex and powerful 'for' statement. + * According to the JX9 language reference + * for loops are the most complex loops in JX9. They behave like their C counterparts. + * The syntax of a for loop is: + * for (expr1; expr2; expr3) + * statement + * The first expression (expr1) is evaluated (executed) once unconditionally at + * the beginning of the loop. + * In the beginning of each iteration, expr2 is evaluated. If it evaluates to + * TRUE, the loop continues and the nested statement(s) are executed. If it evaluates + * to FALSE, the execution of the loop ends. + * At the end of each iteration, expr3 is evaluated (executed). + * Each of the expressions can be empty or contain multiple expressions separated by commas. + * In expr2, all expressions separated by a comma are evaluated but the result is taken + * from the last part. expr2 being empty means the loop should be run indefinitely + * (JX9 implicitly considers it as TRUE, like C). This may not be as useless as you might + * think, since often you'd want to end the loop using a conditional break statement instead + * of using the for truth expression. + */ +static sxi32 jx9CompileFor(jx9_gen_state *pGen) +{ + SyToken *pTmp, *pPostStart, *pEnd = 0; + GenBlock *pForBlock = 0; + sxu32 nFalseJump; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'for' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'for' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Delimit the init-expr;condition;post-expr */ + jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "for: Invalid expression"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + /* Synchronize */ + pGen->pIn = pEnd; + if( pGen->pIn < pGen->pEnd ){ + pGen->pIn++; + } + return SXRET_OK; + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Compile initialization expressions if available */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + /* Pop operand lvalues */ + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); + } + if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, + "for: Expected ';' after initialization expressions"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Jump the trailing ';' */ + pGen->pIn++; + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Deffer continue jumps */ + pForBlock->bPostContinue = TRUE; + /* Compile the condition */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + /* Emit the false jump */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pForBlock, JX9_OP_JZ, nFalseJump); + } + if( (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, + "for: Expected ';' after conditionals expressions"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + /* Jump the trailing ';' */ + pGen->pIn++; + /* Save the post condition stream */ + pPostStart = pGen->pIn; + /* Compile the loop body */ + pGen->pIn = &pEnd[1]; /* Jump the trailing parenthesis ')' */ + pGen->pEnd = pTmp; + rc = jx9CompileBlock(&(*pGen)); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Fix post-continue jumps */ + if( SySetUsed(&pForBlock->aPostContFix) > 0 ){ + JumpFixup *aPost; + VmInstr *pInstr; + sxu32 nJumpDest; + sxu32 n; + aPost = (JumpFixup *)SySetBasePtr(&pForBlock->aPostContFix); + nJumpDest = jx9VmInstrLength(pGen->pVm); + for( n = 0 ; n < SySetUsed(&pForBlock->aPostContFix) ; ++n ){ + pInstr = jx9VmGetInstr(pGen->pVm, aPost[n].nInstrIdx); + if( pInstr ){ + /* Fix jump */ + pInstr->iP2 = nJumpDest; + } + } + } + /* compile the post-expressions if available */ + while( pPostStart < pEnd && (pPostStart->nType & JX9_TK_SEMI) ){ + pPostStart++; + } + if( pPostStart < pEnd ){ + SyToken *pTmpIn, *pTmpEnd; + SWAP_DELIMITER(pGen, pPostStart, pEnd); + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( pGen->pIn < pGen->pEnd ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "for: Expected ')' after post-expressions"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + return SXRET_OK; + } + RE_SWAP_DELIMITER(pGen); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY){ + /* Pop operand lvalue */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); + } + } + /* Emit the unconditional jump to the start of the loop */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForBlock->nFirstInstr, 0, 0); + /* Fix all jumps now the destination is resolved */ + GenStateFixJumps(pForBlock, -1, jx9VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen, 0); + /* Statement successfully compiled */ + return SXRET_OK; +} +/* Expression tree validator callback used by the 'foreach' statement. + * Note that only variable expression [i.e: $x; ${'My'.'Var'}; ${$a['key]};...] + * are allowed. + */ +static sxi32 GenStateForEachNodeValidator(jx9_gen_state *pGen,jx9_expr_node *pRoot) +{ + sxi32 rc = SXRET_OK; /* Assume a valid expression tree */ + if( pRoot->xCode != jx9CompileVariable ){ + /* Unexpected expression */ + rc = jx9GenCompileError(&(*pGen), + E_ERROR, + pRoot->pStart? pRoot->pStart->nLine : 0, + "foreach: Expecting a variable name" + ); + if( rc != SXERR_ABORT ){ + rc = SXERR_INVALID; + } + } + return rc; +} +/* + * Compile the 'foreach' statement. + * According to the JX9 language reference + * The foreach construct simply gives an easy way to iterate over arrays. foreach works + * only on arrays (and objects), and will issue an error when you try to use it on a variable + * with a different data type or an uninitialized variable. There are two syntaxes; the second + * is a minor but useful extension of the first: + * foreach (json_array_json_object as $value) + * statement + * foreach (json_array_json_objec as $key,$value) + * statement + * The first form loops over the array given by array_expression. On each loop, the value + * of the current element is assigned to $value and the internal array pointer is advanced + * by one (so on the next loop, you'll be looking at the next element). + * The second form does the same thing, except that the current element's key will be assigned + * to the variable $key on each loop. + * Note: + * When foreach first starts executing, the internal array pointer is automatically reset to the + * first element of the array. This means that you do not need to call reset() before a foreach loop. + */ +static sxi32 jx9CompileForeach(jx9_gen_state *pGen) +{ + SyToken *pCur, *pTmp, *pEnd = 0; + GenBlock *pForeachBlock = 0; + jx9_foreach_info *pInfo; + sxu32 nFalseJump; + VmInstr *pInstr; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'foreach' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Expected '('"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP, jx9VmInstrLength(pGen->pVm), 0, &pForeachBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Delimit the expression */ + jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "foreach: Missing expression"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + /* Synchronize */ + pGen->pIn = pEnd; + if( pGen->pIn < pGen->pEnd ){ + pGen->pIn++; + } + return SXRET_OK; + } + /* Compile the array expression */ + pCur = pGen->pIn; + while( pCur < pEnd ){ + if( pCur->nType & JX9_TK_KEYWORD ){ + sxi32 nKeywrd = SX_PTR_TO_INT(pCur->pUserData); + if( nKeywrd == JX9_TKWRD_AS ){ + /* Break with the first 'as' found */ + break; + } + } + /* Advance the stream cursor */ + pCur++; + } + if( pCur <= pGen->pIn ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, + "foreach: Missing array/object expression"); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pCur; + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Update token stream */ + while(pGen->pIn < pCur ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Unexpected token '%z'", &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + pGen->pIn++; + } + pCur++; /* Jump the 'as' keyword */ + pGen->pIn = pCur; + if( pGen->pIn >= pEnd ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key => $value pair"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + /* Create the foreach context */ + pInfo = (jx9_foreach_info *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_foreach_info)); + if( pInfo == 0 ){ + jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Fatal, JX9 engine is running out-of-memory"); + return SXERR_ABORT; + } + /* Zero the structure */ + SyZero(pInfo, sizeof(jx9_foreach_info)); + /* Initialize structure fields */ + SySetInit(&pInfo->aStep, &pGen->pVm->sAllocator, sizeof(jx9_foreach_step *)); + /* Check if we have a key field */ + while( pCur < pEnd && (pCur->nType & JX9_TK_COMMA) == 0 ){ + pCur++; + } + if( pCur < pEnd ){ + /* Compile the expression holding the key name */ + if( pGen->pIn >= pCur ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $key"); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + }else{ + pGen->pEnd = pCur; + rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + pInstr = jx9VmPopInstr(pGen->pVm); + if( pInstr->p3 ){ + /* Record key name */ + SyStringInitFromBuf(&pInfo->sKey, pInstr->p3, SyStrlen((const char *)pInstr->p3)); + } + pInfo->iFlags |= JX9_4EACH_STEP_KEY; + } + pGen->pIn = &pCur[1]; /* Jump the arrow */ + } + pGen->pEnd = pEnd; + if( pGen->pIn >= pEnd ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "foreach: Missing $value"); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Compile the expression holding the value name */ + rc = jx9CompileExpr(&(*pGen), 0, GenStateForEachNodeValidator); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + pInstr = jx9VmPopInstr(pGen->pVm); + if( pInstr->p3 ){ + /* Record value name */ + SyStringInitFromBuf(&pInfo->sValue, pInstr->p3, SyStrlen((const char *)pInstr->p3)); + } + /* Emit the 'FOREACH_INIT' instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_INIT, 0, 0, pInfo, &nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_INIT, nFalseJump); + /* Record the first instruction to execute */ + pForeachBlock->nFirstInstr = jx9VmInstrLength(pGen->pVm); + /* Emit the FOREACH_STEP instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_FOREACH_STEP, 0, 0, pInfo, &nFalseJump); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pForeachBlock, JX9_OP_FOREACH_STEP, nFalseJump); + /* Compile the loop body */ + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + rc = jx9CompileBlock(&(*pGen)); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + /* Emit the unconditional jump to the start of the loop */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, pForeachBlock->nFirstInstr, 0, 0); + /* Fix all jumps now the destination is resolved */ + GenStateFixJumps(pForeachBlock, -1,jx9VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen, 0); + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';' so we can avoid + * compiling this erroneous block. + */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the infamous if/elseif/else if/else statements. + * According to the JX9 language reference + * The if construct is one of the most important features of many languages JX9 included. + * It allows for conditional execution of code fragments. JX9 features an if structure + * that is similar to that of C: + * if (expr) + * statement + * else construct: + * Often you'd want to execute a statement if a certain condition is met, and a different + * statement if the condition is not met. This is what else is for. else extends an if statement + * to execute a statement in case the expression in the if statement evaluates to FALSE. + * For example, the following code would display a is greater than b if $a is greater than + * $b, and a is NOT greater than b otherwise. + * The else statement is only executed if the if expression evaluated to FALSE, and if there + * were any elseif expressions - only if they evaluated to FALSE as well + * elseif + * elseif, as its name suggests, is a combination of if and else. Like else, it extends + * an if statement to execute a different statement in case the original if expression evaluates + * to FALSE. However, unlike else, it will execute that alternative expression only if the elseif + * conditional expression evaluates to TRUE. For example, the following code would display a is bigger + * than b, a equal to b or a is smaller than b: + * if ($a > $b) { + * print "a is bigger than b"; + * } elseif ($a == $b) { + * print "a is equal to b"; + * } else { + * print "a is smaller than b"; + * } + */ +static sxi32 jx9CompileIf(jx9_gen_state *pGen) +{ + SyToken *pToken, *pTmp, *pEnd = 0; + GenBlock *pCondBlock = 0; + sxu32 nJumpIdx; + sxu32 nKeyID; + sxi32 rc; + /* Jump the 'if' keyword */ + pGen->pIn++; + pToken = pGen->pIn; + /* Create the conditional block */ + rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_COND, jx9VmInstrLength(pGen->pVm), 0, &pCondBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Process as many [if/else if/elseif/else] blocks as we can */ + for(;;){ + if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_LPAREN) == 0 ){ + /* Syntax error */ + if( pToken >= pGen->pEnd ){ + pToken--; + } + rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing '('"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pToken++; + /* Delimit the condition */ + jx9DelimitNestedTokens(pToken, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); + if( pToken >= pEnd || (pEnd->nType & JX9_TK_RPAREN) == 0 ){ + /* Syntax error */ + if( pToken >= pGen->pEnd ){ + pToken--; + } + rc = jx9GenCompileError(pGen, E_ERROR, pToken->nLine, "if/else/elseif: Missing ')'"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Swap token streams */ + SWAP_TOKEN_STREAM(pGen, pToken, pEnd); + /* Compile the condition */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + /* Update token stream */ + while(pGen->pIn < pEnd ){ + jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); + pGen->pIn++; + } + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Emit the false jump */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJumpIdx); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pCondBlock, JX9_OP_JZ, nJumpIdx); + /* Compile the body */ + rc = jx9CompileBlock(&(*pGen)); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ + break; + } + /* Ensure that the keyword ID is 'else if' or 'else' */ + nKeyID = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); + if( (nKeyID & (JX9_TKWRD_ELSE|JX9_TKWRD_ELIF)) == 0 ){ + break; + } + /* Emit the unconditional jump */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJumpIdx); + /* Save the instruction index so we can fix it later when the jump destination is resolved */ + GenStateNewJumpFixup(pCondBlock, JX9_OP_JMP, nJumpIdx); + if( nKeyID & JX9_TKWRD_ELSE ){ + pToken = &pGen->pIn[1]; + if( pToken >= pGen->pEnd || (pToken->nType & JX9_TK_KEYWORD) == 0 || + SX_PTR_TO_INT(pToken->pUserData) != JX9_TKWRD_IF ){ + break; + } + pGen->pIn++; /* Jump the 'else' keyword */ + } + pGen->pIn++; /* Jump the 'elseif/if' keyword */ + /* Synchronize cursors */ + pToken = pGen->pIn; + /* Fix the false jump */ + GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); + } /* For(;;) */ + /* Fix the false jump */ + GenStateFixJumps(pCondBlock, JX9_OP_JZ, jx9VmInstrLength(pGen->pVm)); + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_KEYWORD) && + (SX_PTR_TO_INT(pGen->pIn->pUserData) & JX9_TKWRD_ELSE) ){ + /* Compile the else block */ + pGen->pIn++; + rc = jx9CompileBlock(&(*pGen)); + if( rc == SXERR_ABORT ){ + + return SXERR_ABORT; + } + } + nJumpIdx = jx9VmInstrLength(pGen->pVm); + /* Fix all unconditional jumps now the destination is resolved */ + GenStateFixJumps(pCondBlock, JX9_OP_JMP, nJumpIdx); + /* Release the conditional block */ + GenStateLeaveBlock(pGen, 0); + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';' so we can avoid compiling this erroneous block. + */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the return statement. + * According to the JX9 language reference + * If called from within a function, the return() statement immediately ends execution + * of the current function, and returns its argument as the value of the function call. + * return() will also end the execution of an eval() statement or script file. + * If called from the global scope, then execution of the current script file is ended. + * If the current script file was include()ed or require()ed, then control is passed back + * to the calling file. Furthermore, if the current script file was include()ed, then the value + * given to return() will be returned as the value of the include() call. If return() is called + * from within the main script file, then script execution end. + * Note that since return() is a language construct and not a function, the parentheses + * surrounding its arguments are not required. It is common to leave them out, and you actually + * should do so as JX9 has less work to do in this case. + * Note: If no parameter is supplied, then the parentheses must be omitted and JX9 is returning NULL instead.. + */ +static sxi32 jx9CompileReturn(jx9_gen_state *pGen) +{ + sxi32 nRet = 0; /* TRUE if there is a return value */ + sxi32 rc; + /* Jump the 'return' keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + /* Compile the expression */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nRet = 1; + } + } + /* Emit the done instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, nRet, 0, 0, 0); + return SXRET_OK; +} +/* + * Compile the die/exit language construct. + * The role of these constructs is to terminate execution of the script. + * Shutdown functions will always be executed even if exit() is called. + */ +static sxi32 jx9CompileHalt(jx9_gen_state *pGen) +{ + sxi32 nExpr = 0; + sxi32 rc; + /* Jump the die/exit keyword */ + pGen->pIn++; + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + /* Compile the expression */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nExpr = 1; + } + } + /* Emit the HALT instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_HALT, nExpr, 0, 0, 0); + return SXRET_OK; +} +/* + * Compile the static statement. + * According to the JX9 language reference + * Another important feature of variable scoping is the static variable. + * A static variable exists only in a local function scope, but it does not lose its value + * when program execution leaves this scope. + * Static variables also provide one way to deal with recursive functions. + */ +static sxi32 jx9CompileStatic(jx9_gen_state *pGen) +{ + jx9_vm_func_static_var sStatic; /* Structure describing the static variable */ + jx9_vm_func *pFunc; /* Enclosing function */ + GenBlock *pBlock; + SyString *pName; + char *zDup; + sxu32 nLine; + sxi32 rc; + /* Jump the static keyword */ + nLine = pGen->pIn->nLine; + pGen->pIn++; + /* Extract the enclosing function if any */ + pBlock = pGen->pCurrent; + while( pBlock ){ + if( pBlock->iFlags & GEN_BLOCK_FUNC){ + break; + } + /* Point to the upper block */ + pBlock = pBlock->pParent; + } + if( pBlock == 0 ){ + /* Static statement, called outside of a function body, treat it as a simple variable. */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Compile the expression holding the variable */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if( rc != SXERR_EMPTY ){ + /* Emit the POP instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); + } + return SXRET_OK; + } + pFunc = (jx9_vm_func *)pBlock->pUserData; + /* Make sure we are dealing with a valid statement */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 || &pGen->pIn[1] >= pGen->pEnd || + (pGen->pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Expected variable after 'static' keyword"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; + /* Extract variable name */ + pName = &pGen->pIn->sData; + pGen->pIn++; /* Jump the var name */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_EQUAL/*'='*/)) == 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "static: Unexpected token '%z'", &pGen->pIn->sData); + goto Synchronize; + } + /* Initialize the structure describing the static variable */ + SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); + sStatic.nIdx = SXU32_HIGH; /* Not yet created */ + /* Duplicate variable name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); + if( zDup == 0 ){ + jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Fatal, JX9 engine is running out of memory"); + return SXERR_ABORT; + } + SyStringInitFromBuf(&sStatic.sName, zDup, pName->nByte); + /* Check if we have an expression to compile */ + if( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_EQUAL) ){ + SySet *pInstrContainer; + pGen->pIn++; /* Jump the equal '=' sign */ + /* Swap bytecode container */ + pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); + jx9VmSetByteCodeContainer(pGen->pVm, &sStatic.aByteCode); + /* Compile the expression */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + /* Emit the done instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); + /* Restore default bytecode container */ + jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); + } + /* Finally save the compiled static variable in the appropriate container */ + SySetPut(&pFunc->aStatic, (const void *)&sStatic); + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon ';', so we can avoid compiling this erroneous + * statement. + */ + while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the 'const' statement. + * According to the JX9 language reference + * A constant is an identifier (name) for a simple value. As the name suggests, that value + * cannot change during the execution of the script (except for magic constants, which aren't actually constants). + * A constant is case-sensitive by default. By convention, constant identifiers are always uppercase. + * The name of a constant follows the same rules as any label in JX9. A valid constant name starts + * with a letter or underscore, followed by any number of letters, numbers, or underscores. + * As a regular expression it would be expressed thusly: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* + * Syntax + * You can define a constant by using the define()-function or by using the const keyword outside + * a object definition. Once a constant is defined, it can never be changed or undefined. + * You can get the value of a constant by simply specifying its name. Unlike with variables + * you should not prepend a constant with a $. You can also use the function constant() to read + * a constant's value if you wish to obtain the constant's name dynamically. Use get_defined_constants() + * to get a list of all defined constants. + */ +static sxi32 jx9CompileConstant(jx9_gen_state *pGen) +{ + SySet *pConsCode, *pInstrContainer; + sxu32 nLine = pGen->pIn->nLine; + SyString *pName; + sxi32 rc; + pGen->pIn++; /* Jump the 'const' keyword */ + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ + /* Invalid constant name */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Invalid constant name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Peek constant name */ + pName = &pGen->pIn->sData; + /* Make sure the constant name isn't reserved */ + if( GenStateIsReservedID(pName) ){ + /* Reserved constant */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Cannot redeclare a reserved constant '%z'", pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; + if(pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_EQUAL /* '=' */) == 0 ){ + /* Invalid statement*/ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "const: Expected '=' after constant name"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; /*Jump the equal sign */ + /* Allocate a new constant value container */ + pConsCode = (SySet *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(SySet)); + if( pConsCode == 0 ){ + return GenStateOutOfMem(pGen); + } + SySetInit(pConsCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); + /* Swap bytecode container */ + pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); + jx9VmSetByteCodeContainer(pGen->pVm, pConsCode); + /* Compile constant value */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + /* Emit the done instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); + jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + SySetSetUserData(pConsCode, pGen->pVm); + /* Register the constant */ + rc = jx9VmRegisterConstant(pGen->pVm, pName, jx9VmExpandConstantValue, pConsCode); + if( rc != SXRET_OK ){ + SySetRelease(pConsCode); + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pConsCode); + } + return SXRET_OK; +Synchronize: + /* Synchronize with the next-semi-colon and avoid compiling this erroneous statement */ + while(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Compile the uplink construct. + * According to the JX9 language reference + * In JX9 global variables must be declared uplink inside a function if they are going + * to be used in that function. + * Example #1 Using global + * $a = 1; + * $b = 2; + * function Sum() + * { + * uplink $a, $b; + * $b = $a + $b; + * } + * Sum(); + * print $b; + * ?> + * The above script will output 3. By declaring $a and $b global within the function + * all references to either variable will refer to the global version. There is no limit + * to the number of global variables that can be manipulated by a function. + */ +static sxi32 jx9CompileUplink(jx9_gen_state *pGen) +{ + SyToken *pTmp, *pNext = 0; + sxi32 nExpr; + sxi32 rc; + /* Jump the 'uplink' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_SEMI) ){ + /* Nothing to process */ + return SXRET_OK; + } + pTmp = pGen->pEnd; + nExpr = 0; + while( SXRET_OK == jx9GetNextExpr(pGen->pIn, pTmp, &pNext) ){ + if( pGen->pIn < pNext ){ + pGen->pEnd = pNext; + if( (pGen->pIn->nType & JX9_TK_DOLLAR) == 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "uplink: Expected variable name"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + }else{ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd ){ + /* Emit a warning */ + jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn[-1].nLine, "uplink: Empty variable name"); + }else{ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + }else if(rc != SXERR_EMPTY ){ + nExpr++; + } + } + } + } + /* Next expression in the stream */ + pGen->pIn = pNext; + /* Jump trailing commas */ + while( pGen->pIn < pTmp && (pGen->pIn->nType & JX9_TK_COMMA) ){ + pGen->pIn++; + } + } + /* Restore token stream */ + pGen->pEnd = pTmp; + if( nExpr > 0 ){ + /* Emit the uplink instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_UPLINK, nExpr, 0, 0, 0); + } + return SXRET_OK; +} +/* + * Compile a switch block. + * (See block-comment below for more information) + */ +static sxi32 GenStateCompileSwitchBlock(jx9_gen_state *pGen,sxu32 *pBlockStart) +{ + sxi32 rc = SXRET_OK; + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*':'*/)) == 0 ){ + /* Unexpected token */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pGen->pIn++; + } + pGen->pIn++; + /* First instruction to execute in this block. */ + *pBlockStart = jx9VmInstrLength(pGen->pVm); + /* Compile the block until we hit a case/default/endswitch keyword + * or the '}' token */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + /* No more input to process */ + break; + } + rc = SXRET_OK; + if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ + if( pGen->pIn->nType & JX9_TK_CCB /*'}' */ ){ + rc = SXERR_EOF; + break; + } + }else{ + sxi32 nKwrd; + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == JX9_TKWRD_CASE || nKwrd == JX9_TKWRD_DEFAULT ){ + break; + } + } + /* Compile block */ + rc = jx9CompileBlock(&(*pGen)); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + return rc; +} +/* + * Compile a case eXpression. + * (See block-comment below for more information) + */ +static sxi32 GenStateCompileCaseExpr(jx9_gen_state *pGen, jx9_case_expr *pExpr) +{ + SySet *pInstrContainer; + SyToken *pEnd, *pTmp; + sxi32 iNest = 0; + sxi32 rc; + /* Delimit the expression */ + pEnd = pGen->pIn; + while( pEnd < pGen->pEnd ){ + if( pEnd->nType & JX9_TK_LPAREN /*(*/ ){ + /* Increment nesting level */ + iNest++; + }else if( pEnd->nType & JX9_TK_RPAREN /*)*/ ){ + /* Decrement nesting level */ + iNest--; + }else if( pEnd->nType & (JX9_TK_SEMI/*';'*/|JX9_TK_COLON/*;'*/) && iNest < 1 ){ + break; + } + pEnd++; + } + if( pGen->pIn >= pEnd ){ + rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, "Empty case expression"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + } + /* Swap token stream */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); + jx9VmSetByteCodeContainer(pGen->pVm, &pExpr->aByteCode); + rc = jx9CompileExpr(&(*pGen), 0, 0); + /* Emit the done instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); + jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); + /* Update token stream */ + pGen->pIn = pEnd; + pGen->pEnd = pTmp; + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; +} +/* + * Compile the smart switch statement. + * According to the JX9 language reference manual + * The switch statement is similar to a series of IF statements on the same expression. + * In many occasions, you may want to compare the same variable (or expression) with many + * different values, and execute a different piece of code depending on which value it equals to. + * This is exactly what the switch statement is for. + * Note: Note that unlike some other languages, the continue statement applies to switch and acts + * similar to break. If you have a switch inside a loop and wish to continue to the next iteration + * of the outer loop, use continue 2. + * Note that switch/case does loose comparision. + * It is important to understand how the switch statement is executed in order to avoid mistakes. + * The switch statement executes line by line (actually, statement by statement). + * In the beginning, no code is executed. Only when a case statement is found with a value that + * matches the value of the switch expression does JX9 begin to execute the statements. + * JX9 continues to execute the statements until the end of the switch block, or the first time + * it sees a break statement. If you don't write a break statement at the end of a case's statement list. + * In a switch statement, the condition is evaluated only once and the result is compared to each + * case statement. In an elseif statement, the condition is evaluated again. If your condition + * is more complicated than a simple compare and/or is in a tight loop, a switch may be faster. + * The statement list for a case can also be empty, which simply passes control into the statement + * list for the next case. + * The case expression may be any expression that evaluates to a simple type, that is, integer + * or floating-point numbers and strings. + */ +static sxi32 jx9CompileSwitch(jx9_gen_state *pGen) +{ + GenBlock *pSwitchBlock; + SyToken *pTmp, *pEnd; + jx9_switch *pSwitch; + sxu32 nLine; + sxi32 rc; + nLine = pGen->pIn->nLine; + /* Jump the 'switch' keyword */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after 'switch' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + goto Synchronize; + } + /* Jump the left parenthesis '(' */ + pGen->pIn++; + pEnd = 0; /* cc warning */ + /* Create the loop block */ + rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_LOOP|GEN_BLOCK_SWITCH, + jx9VmInstrLength(pGen->pVm), 0, &pSwitchBlock); + if( rc != SXRET_OK ){ + return SXERR_ABORT; + } + /* Delimit the condition */ + jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); + if( pGen->pIn == pEnd || pEnd >= pGen->pEnd ){ + /* Empty expression */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected expression after 'switch' keyword"); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + } + /* Swap token streams */ + pTmp = pGen->pEnd; + pGen->pEnd = pEnd; + /* Compile the expression */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc == SXERR_ABORT ){ + /* Expression handler request an operation abort [i.e: Out-of-memory] */ + return SXERR_ABORT; + } + /* Update token stream */ + while(pGen->pIn < pEnd ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, + "Switch: Unexpected token '%z'", &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + pGen->pIn++; + } + pGen->pIn = &pEnd[1]; + pGen->pEnd = pTmp; + if( pGen->pIn >= pGen->pEnd || &pGen->pIn[1] >= pGen->pEnd || + (pGen->pIn->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_COLON/*:*/)) == 0 ){ + pTmp = pGen->pIn; + if( pTmp >= pGen->pEnd ){ + pTmp--; + } + /* Unexpected token */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pTmp->nLine, "Switch: Unexpected token '%z'", &pTmp->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + goto Synchronize; + } + pGen->pIn++; /* Jump the leading curly braces/colons */ + /* Create the switch blocks container */ + pSwitch = (jx9_switch *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(jx9_switch)); + if( pSwitch == 0 ){ + /* Abort compilation */ + return GenStateOutOfMem(pGen); + } + /* Zero the structure */ + SyZero(pSwitch, sizeof(jx9_switch)); + /* Initialize fields */ + SySetInit(&pSwitch->aCaseExpr, &pGen->pVm->sAllocator, sizeof(jx9_case_expr)); + /* Emit the switch instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_SWITCH, 0, 0, pSwitch, 0); + /* Compile case blocks */ + for(;;){ + sxu32 nKwrd; + if( pGen->pIn >= pGen->pEnd ){ + /* No more input to process */ + break; + } + if( (pGen->pIn->nType & JX9_TK_KEYWORD) == 0 ){ + if( (pGen->pIn->nType & JX9_TK_CCB /*}*/) == 0 ){ + /* Unexpected token */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* FALL THROUGH */ + } + /* Block compiled */ + break; + } + /* Extract the keyword */ + nKwrd = SX_PTR_TO_INT(pGen->pIn->pUserData); + if( nKwrd == JX9_TKWRD_DEFAULT ){ + /* + * Accroding to the JX9 language reference manual + * A special case is the default case. This case matches anything + * that wasn't matched by the other cases. + */ + if( pSwitch->nDefault > 0 ){ + /* Default case already compiled */ + rc = jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, "Switch: 'default' case already compiled"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + pGen->pIn++; /* Jump the 'default' keyword */ + /* Compile the default block */ + rc = GenStateCompileSwitchBlock(pGen,&pSwitch->nDefault); + if( rc == SXERR_ABORT){ + return SXERR_ABORT; + }else if( rc == SXERR_EOF ){ + break; + } + }else if( nKwrd == JX9_TKWRD_CASE ){ + jx9_case_expr sCase; + /* Standard case block */ + pGen->pIn++; /* Jump the 'case' keyword */ + /* initialize the structure */ + SySetInit(&sCase.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); + /* Compile the case expression */ + rc = GenStateCompileCaseExpr(pGen, &sCase); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Compile the case block */ + rc = GenStateCompileSwitchBlock(pGen,&sCase.nStart); + /* Insert in the switch container */ + SySetPut(&pSwitch->aCaseExpr, (const void *)&sCase); + if( rc == SXERR_ABORT){ + return SXERR_ABORT; + }else if( rc == SXERR_EOF ){ + break; + } + }else{ + /* Unexpected token */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Switch: Unexpected token '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + break; + } + } + /* Fix all jumps now the destination is resolved */ + pSwitch->nOut = jx9VmInstrLength(pGen->pVm); + GenStateFixJumps(pSwitchBlock, -1, jx9VmInstrLength(pGen->pVm)); + /* Release the loop block */ + GenStateLeaveBlock(pGen, 0); + if( pGen->pIn < pGen->pEnd ){ + /* Jump the trailing curly braces */ + pGen->pIn++; + } + /* Statement successfully compiled */ + return SXRET_OK; +Synchronize: + /* Synchronize with the first semi-colon */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; +} +/* + * Process default argument values. That is, a function may define C++-style default value + * as follows: + * function makecoffee($type = "cappuccino") + * { + * return "Making a cup of $type.\n"; + * } + * Some features: + * 1 -) Default arguments value can be any complex expression [i.e: function call, annynoymous + * functions, array member, ..] + * 2 -) Full type hinting: (Arguments are automatically casted to the desired type) + * Example: + * function a(string $a){} function b(int $a, string $c, float $d){} + * 3 -) Function overloading!! + * Example: + * function foo($a) { + * return $a.JX9_EOL; + * } + * function foo($a, $b) { + * return $a + $b; + * } + * print foo(5); // Prints "5" + * print foo(5, 2); // Prints "7" + * // Same arg + * function foo(string $a) + * { + * print "a is a string\n"; + * dump($a); + * } + * function foo(int $a) + * { + * print "a is integer\n"; + * dump($a); + * } + * function foo(array $a) + * { + * print "a is an array\n"; + * dump($a); + * } + * foo('This is a great feature'); // a is a string [first foo] + * foo(52); // a is integer [second foo] + * foo(array(14, __TIME__, __DATE__)); // a is an array [third foo] + * Please refer to the official documentation for more information on the powerful extension + * introduced by the JX9 engine. + */ +static sxi32 GenStateProcessArgValue(jx9_gen_state *pGen, jx9_vm_func_arg *pArg, SyToken *pIn, SyToken *pEnd) +{ + SyToken *pTmpIn, *pTmpEnd; + SySet *pInstrContainer; + sxi32 rc; + /* Swap token stream */ + SWAP_DELIMITER(pGen, pIn, pEnd); + pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); + jx9VmSetByteCodeContainer(pGen->pVm, &pArg->aByteCode); + /* Compile the expression holding the argument value */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + /* Emit the done instruction */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, (rc != SXERR_EMPTY ? 1 : 0), 0, 0, 0); + jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); + RE_SWAP_DELIMITER(pGen); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + return SXRET_OK; +} +/* + * Collect function arguments one after one. + * According to the JX9 language reference manual. + * Information may be passed to functions via the argument list, which is a comma-delimited + * list of expressions. + * JX9 supports passing arguments by value (the default), passing by reference + * and default argument values. Variable-length argument lists are also supported, + * see also the function references for func_num_args(), func_get_arg(), and func_get_args() + * for more information. + * Example #1 Passing arrays to functions + * + * Making arguments be passed by reference + * By default, function arguments are passed by value (so that if the value of the argument + * within the function is changed, it does not get changed outside of the function). + * To allow a function to modify its arguments, they must be passed by reference. + * To have an argument to a function always passed by reference, prepend an ampersand (&) + * to the argument name in the function definition: + * Example #2 Passing function parameters by reference + * + * + * JX9 have introduced powerful extension including full type hinting, function overloading + * complex agrument values.Please refer to the official documentation for more information + * on these extension. + */ +static sxi32 GenStateCollectFuncArgs(jx9_vm_func *pFunc, jx9_gen_state *pGen, SyToken *pEnd) +{ + jx9_vm_func_arg sArg; /* Current processed argument */ + SyToken *pCur, *pIn; /* Token stream */ + SyBlob sSig; /* Function signature */ + char *zDup; /* Copy of argument name */ + sxi32 rc; + + pIn = pGen->pIn; + pCur = 0; + SyBlobInit(&sSig, &pGen->pVm->sAllocator); + /* Process arguments one after one */ + for(;;){ + if( pIn >= pEnd ){ + /* No more arguments to process */ + break; + } + SyZero(&sArg, sizeof(jx9_vm_func_arg)); + SySetInit(&sArg.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); + if( pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD) ){ + if( pIn->nType & JX9_TK_KEYWORD ){ + sxu32 nKey = (sxu32)(SX_PTR_TO_INT(pIn->pUserData)); + if( nKey & JX9_TKWRD_BOOL ){ + sArg.nType = MEMOBJ_BOOL; + }else if( nKey & JX9_TKWRD_INT ){ + sArg.nType = MEMOBJ_INT; + }else if( nKey & JX9_TKWRD_STRING ){ + sArg.nType = MEMOBJ_STRING; + }else if( nKey & JX9_TKWRD_FLOAT ){ + sArg.nType = MEMOBJ_REAL; + }else{ + jx9GenCompileError(&(*pGen), E_WARNING, pGen->pIn->nLine, + "Invalid argument type '%z', Automatic cast will not be performed", + &pIn->sData); + } + } + pIn++; + } + if( pIn >= pEnd ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Missing argument name"); + return rc; + } + if( pIn >= pEnd || (pIn->nType & JX9_TK_DOLLAR) == 0 || &pIn[1] >= pEnd || (pIn[1].nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ + /* Invalid argument */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Invalid argument name"); + return rc; + } + pIn++; /* Jump the dollar sign */ + /* Copy argument name */ + zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, SyStringData(&pIn->sData), SyStringLength(&pIn->sData)); + if( zDup == 0 ){ + return GenStateOutOfMem(pGen); + } + SyStringInitFromBuf(&sArg.sName, zDup, SyStringLength(&pIn->sData)); + pIn++; + if( pIn < pEnd ){ + if( pIn->nType & JX9_TK_EQUAL ){ + SyToken *pDefend; + sxi32 iNest = 0; + pIn++; /* Jump the equal sign */ + pDefend = pIn; + /* Process the default value associated with this argument */ + while( pDefend < pEnd ){ + if( (pDefend->nType & JX9_TK_COMMA) && iNest <= 0 ){ + break; + } + if( pDefend->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*[*/) ){ + /* Increment nesting level */ + iNest++; + }else if( pDefend->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*]*/) ){ + /* Decrement nesting level */ + iNest--; + } + pDefend++; + } + if( pIn >= pDefend ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Missing argument default value"); + return rc; + } + /* Process default value */ + rc = GenStateProcessArgValue(&(*pGen), &sArg, pIn, pDefend); + if( rc != SXRET_OK ){ + return rc; + } + /* Point beyond the default value */ + pIn = pDefend; + } + if( pIn < pEnd && (pIn->nType & JX9_TK_COMMA) == 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pIn->nLine, "Unexpected token '%z'", &pIn->sData); + return rc; + } + pIn++; /* Jump the trailing comma */ + } + /* Append argument signature */ + if( sArg.nType > 0 ){ + int c; + c = 'n'; /* cc warning */ + /* Type leading character */ + switch(sArg.nType){ + case MEMOBJ_HASHMAP: + /* Hashmap aka 'array' */ + c = 'h'; + break; + case MEMOBJ_INT: + /* Integer */ + c = 'i'; + break; + case MEMOBJ_BOOL: + /* Bool */ + c = 'b'; + break; + case MEMOBJ_REAL: + /* Float */ + c = 'f'; + break; + case MEMOBJ_STRING: + /* String */ + c = 's'; + break; + default: + break; + } + SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); + } + /* Save in the argument set */ + SySetPut(&pFunc->aArgs, (const void *)&sArg); + } + if( SyBlobLength(&sSig) > 0 ){ + /* Save function signature */ + SyStringInitFromBuf(&pFunc->sSignature, SyBlobData(&sSig), SyBlobLength(&sSig)); + } + return SXRET_OK; +} +/* + * Compile function [i.e: standard function, annonymous function or closure ] body. + * Return SXRET_OK on success. Any other return value indicates failure + * and this routine takes care of generating the appropriate error message. + */ +static sxi32 GenStateCompileFuncBody( + jx9_gen_state *pGen, /* Code generator state */ + jx9_vm_func *pFunc /* Function state */ + ) +{ + SySet *pInstrContainer; /* Instruction container */ + GenBlock *pBlock; + sxi32 rc; + /* Attach the new function */ + rc = GenStateEnterBlock(&(*pGen), GEN_BLOCK_PROTECTED|GEN_BLOCK_FUNC,jx9VmInstrLength(pGen->pVm), pFunc, &pBlock); + if( rc != SXRET_OK ){ + return GenStateOutOfMem(pGen); + } + /* Swap bytecode containers */ + pInstrContainer = jx9VmGetByteCodeContainer(pGen->pVm); + jx9VmSetByteCodeContainer(pGen->pVm, &pFunc->aByteCode); + /* Compile the body */ + jx9CompileBlock(&(*pGen)); + /* Emit the final return if not yet done */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_DONE, 0, 0, 0, 0); + /* Restore the default container */ + jx9VmSetByteCodeContainer(pGen->pVm, pInstrContainer); + /* Leave function block */ + GenStateLeaveBlock(&(*pGen), 0); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + /* All done, function body compiled */ + return SXRET_OK; +} +/* + * Compile a JX9 function whether is a Standard or Annonymous function. + * According to the JX9 language reference manual. + * Function names follow the same rules as other labels in JX9. A valid function name + * starts with a letter or underscore, followed by any number of letters, numbers, or + * underscores. As a regular expression, it would be expressed thus: + * [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*. + * Functions need not be defined before they are referenced. + * All functions and objectes in JX9 have the global scope - they can be called outside + * a function even if they were defined inside and vice versa. + * It is possible to call recursive functions in JX9. However avoid recursive function/method + * calls with over 32-64 recursion levels. + * + * JX9 have introduced powerful extension including full type hinting, function overloading, + * complex agrument values and more. Please refer to the official documentation for more information + * on these extension. + */ +static sxi32 GenStateCompileFunc( + jx9_gen_state *pGen, /* Code generator state */ + SyString *pName, /* Function name. NULL otherwise */ + sxi32 iFlags, /* Control flags */ + jx9_vm_func **ppFunc /* OUT: function state */ + ) +{ + jx9_vm_func *pFunc; + SyToken *pEnd; + sxu32 nLine; + char *zName; + sxi32 rc; + /* Extract line number */ + nLine = pGen->pIn->nLine; + /* Jump the left parenthesis '(' */ + pGen->pIn++; + /* Delimit the function signature */ + jx9DelimitNestedTokens(pGen->pIn, pGen->pEnd, JX9_TK_LPAREN /* '(' */, JX9_TK_RPAREN /* ')' */, &pEnd); + if( pEnd >= pGen->pEnd ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Missing ')' after function '%z' signature", pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + pGen->pIn = pGen->pEnd; + return SXRET_OK; + } + /* Create the function state */ + pFunc = (jx9_vm_func *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_vm_func)); + if( pFunc == 0 ){ + goto OutOfMem; + } + /* function ID */ + zName = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); + if( zName == 0 ){ + /* Don't worry about freeing memory, everything will be released shortly */ + goto OutOfMem; + } + /* Initialize the function state */ + jx9VmInitFuncState(pGen->pVm, pFunc, zName, pName->nByte, iFlags, 0); + if( pGen->pIn < pEnd ){ + /* Collect function arguments */ + rc = GenStateCollectFuncArgs(pFunc, &(*pGen), pEnd); + if( rc == SXERR_ABORT ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return SXERR_ABORT; + } + } + /* Compile function body */ + pGen->pIn = &pEnd[1]; + /* Compile the body */ + rc = GenStateCompileFuncBody(&(*pGen), pFunc); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + if( ppFunc ){ + *ppFunc = pFunc; + } + /* Finally register the function */ + rc = jx9VmInstallUserFunction(pGen->pVm, pFunc, 0); + return rc; + /* Fall through if something goes wrong */ +OutOfMem: + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return GenStateOutOfMem(pGen); +} +/* + * Compile a standard JX9 function. + * Refer to the block-comment above for more information. + */ +static sxi32 jx9CompileFunction(jx9_gen_state *pGen) +{ + SyString *pName; + sxi32 iFlags; + sxu32 nLine; + sxi32 rc; + + nLine = pGen->pIn->nLine; + pGen->pIn++; /* Jump the 'function' keyword */ + iFlags = 0; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) == 0 ){ + /* Invalid function name */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Invalid function name"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* Sychronize with the next semi-colon or braces*/ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; + } + pName = &pGen->pIn->sData; + nLine = pGen->pIn->nLine; + /* Jump the function name */ + pGen->pIn++; + if( pGen->pIn >= pGen->pEnd || (pGen->pIn->nType & JX9_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, nLine, "Expected '(' after function name '%z'", pName); + if( rc == SXERR_ABORT ){ + /* Error count limit reached, abort immediately */ + return SXERR_ABORT; + } + /* Sychronize with the next semi-colon or '{' */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (JX9_TK_SEMI|JX9_TK_OCB)) == 0 ){ + pGen->pIn++; + } + return SXRET_OK; + } + /* Compile function body */ + rc = GenStateCompileFunc(&(*pGen),pName,iFlags,0); + return rc; +} +/* + * Generate bytecode for a given expression tree. + * If something goes wrong while generating bytecode + * for the expression tree (A very unlikely scenario) + * this function takes care of generating the appropriate + * error message. + */ +static sxi32 GenStateEmitExprCode( + jx9_gen_state *pGen, /* Code generator state */ + jx9_expr_node *pNode, /* Root of the expression tree */ + sxi32 iFlags /* Control flags */ + ) +{ + VmInstr *pInstr; + sxu32 nJmpIdx; + sxi32 iP1 = 0; + sxu32 iP2 = 0; + void *p3 = 0; + sxi32 iVmOp; + sxi32 rc; + if( pNode->xCode ){ + SyToken *pTmpIn, *pTmpEnd; + /* Compile node */ + SWAP_DELIMITER(pGen, pNode->pStart, pNode->pEnd); + rc = pNode->xCode(&(*pGen), iFlags); + RE_SWAP_DELIMITER(pGen); + return rc; + } + if( pNode->pOp == 0 ){ + jx9GenCompileError(&(*pGen), E_ERROR, pNode->pStart->nLine, + "Invalid expression node, JX9 is aborting compilation"); + return SXERR_ABORT; + } + iVmOp = pNode->pOp->iVmOp; + if( pNode->pOp->iOp == EXPR_OP_QUESTY ){ + sxu32 nJz, nJmp; + /* Ternary operator require special handling */ + /* Phase#1: Compile the condition */ + rc = GenStateEmitExprCode(&(*pGen), pNode->pCond, iFlags); + if( rc != SXRET_OK ){ + return rc; + } + nJz = nJmp = 0; /* cc -O6 warning */ + /* Phase#2: Emit the false jump */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 0, 0, 0, &nJz); + if( pNode->pLeft ){ + /* Phase#3: Compile the 'then' expression */ + rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Phase#4: Emit the unconditional jump */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JMP, 0, 0, 0, &nJmp); + /* Phase#5: Fix the false jump now the jump destination is resolved. */ + pInstr = jx9VmGetInstr(pGen->pVm, nJz); + if( pInstr ){ + pInstr->iP2 = jx9VmInstrLength(pGen->pVm); + } + /* Phase#6: Compile the 'else' expression */ + if( pNode->pRight ){ + rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); + if( rc != SXRET_OK ){ + return rc; + } + } + if( nJmp > 0 ){ + /* Phase#7: Fix the unconditional jump */ + pInstr = jx9VmGetInstr(pGen->pVm, nJmp); + if( pInstr ){ + pInstr->iP2 = jx9VmInstrLength(pGen->pVm); + } + } + /* All done */ + return SXRET_OK; + } + /* Generate code for the left tree */ + if( pNode->pLeft ){ + if( iVmOp == JX9_OP_CALL ){ + jx9_expr_node **apNode; + sxi32 n; + /* Recurse and generate bytecodes for function arguments */ + apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); + /* Read-only load */ + iFlags |= EXPR_FLAG_RDONLY_LOAD; + for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ + rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Total number of given arguments */ + iP1 = (sxi32)SySetUsed(&pNode->aNodeArgs); + /* Remove stale flags now */ + iFlags &= ~EXPR_FLAG_RDONLY_LOAD; + } + rc = GenStateEmitExprCode(&(*pGen), pNode->pLeft, iFlags); + if( rc != SXRET_OK ){ + return rc; + } + if( iVmOp == JX9_OP_CALL ){ + pInstr = jx9VmPeekInstr(pGen->pVm); + if( pInstr ){ + if ( pInstr->iOp == JX9_OP_LOADC ){ + /* Prevent constant expansion */ + pInstr->iP1 = 0; + }else if( pInstr->iOp == JX9_OP_MEMBER /* $a.b(1, 2, 3) */ ){ + /* Annonymous function call, flag that */ + pInstr->iP2 = 1; + } + } + }else if( iVmOp == JX9_OP_LOAD_IDX ){ + jx9_expr_node **apNode; + sxi32 n; + /* Recurse and generate bytecodes for array index */ + apNode = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); + for( n = 0 ; n < (sxi32)SySetUsed(&pNode->aNodeArgs) ; ++n ){ + rc = GenStateEmitExprCode(&(*pGen), apNode[n], iFlags&~EXPR_FLAG_LOAD_IDX_STORE); + if( rc != SXRET_OK ){ + return rc; + } + } + if( SySetUsed(&pNode->aNodeArgs) > 0 ){ + iP1 = 1; /* Node have an index associated with it */ + } + if( iFlags & EXPR_FLAG_LOAD_IDX_STORE ){ + /* Create an empty entry when the desired index is not found */ + iP2 = 1; + } + }else if( pNode->pOp->iOp == EXPR_OP_COMMA ){ + /* POP the left node */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); + } + } + rc = SXRET_OK; + nJmpIdx = 0; + /* Generate code for the right tree */ + if( pNode->pRight ){ + if( iVmOp == JX9_OP_LAND ){ + /* Emit the false jump so we can short-circuit the logical and */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); + }else if (iVmOp == JX9_OP_LOR ){ + /* Emit the true jump so we can short-circuit the logical or*/ + jx9VmEmitInstr(pGen->pVm, JX9_OP_JNZ, 1/* Keep the value on the stack */, 0, 0, &nJmpIdx); + }else if( pNode->pOp->iPrec == 18 /* Combined binary operators [i.e: =, '.=', '+=', *=' ...] precedence */ ){ + iFlags |= EXPR_FLAG_LOAD_IDX_STORE; + } + rc = GenStateEmitExprCode(&(*pGen), pNode->pRight, iFlags); + if( iVmOp == JX9_OP_STORE ){ + pInstr = jx9VmPeekInstr(pGen->pVm); + if( pInstr ){ + if(pInstr->iOp == JX9_OP_MEMBER ){ + /* Perform a member store operation [i.e: $this.x = 50] */ + iP2 = 1; + }else{ + if( pInstr->iOp == JX9_OP_LOAD_IDX ){ + /* Transform the STORE instruction to STORE_IDX instruction */ + iVmOp = JX9_OP_STORE_IDX; + iP1 = pInstr->iP1; + }else{ + p3 = pInstr->p3; + } + /* POP the last dynamic load instruction */ + (void)jx9VmPopInstr(pGen->pVm); + } + } + } + } + if( iVmOp > 0 ){ + if( iVmOp == JX9_OP_INCR || iVmOp == JX9_OP_DECR ){ + if( pNode->iFlags & EXPR_NODE_PRE_INCR ){ + /* Pre-increment/decrement operator [i.e: ++$i, --$j ] */ + iP1 = 1; + } + } + /* Finally, emit the VM instruction associated with this operator */ + jx9VmEmitInstr(pGen->pVm, iVmOp, iP1, iP2, p3, 0); + if( nJmpIdx > 0 ){ + /* Fix short-circuited jumps now the destination is resolved */ + pInstr = jx9VmGetInstr(pGen->pVm, nJmpIdx); + if( pInstr ){ + pInstr->iP2 = jx9VmInstrLength(pGen->pVm); + } + } + } + return rc; +} +/* + * Compile a JX9 expression. + * According to the JX9 language reference manual: + * Expressions are the most important building stones of JX9. + * In JX9, almost anything you write is an expression. + * The simplest yet most accurate way to define an expression + * is "anything that has a value". + * If something goes wrong while compiling the expression, this + * function takes care of generating the appropriate error + * message. + */ +static sxi32 jx9CompileExpr( + jx9_gen_state *pGen, /* Code generator state */ + sxi32 iFlags, /* Control flags */ + sxi32 (*xTreeValidator)(jx9_gen_state *, jx9_expr_node *) /* Node validator callback.NULL otherwise */ + ) +{ + jx9_expr_node *pRoot; + SySet sExprNode; + SyToken *pEnd; + sxi32 nExpr; + sxi32 iNest; + sxi32 rc; + /* Initialize worker variables */ + nExpr = 0; + pRoot = 0; + SySetInit(&sExprNode, &pGen->pVm->sAllocator, sizeof(jx9_expr_node *)); + SySetAlloc(&sExprNode, 0x10); + rc = SXRET_OK; + /* Delimit the expression */ + pEnd = pGen->pIn; + iNest = 0; + while( pEnd < pGen->pEnd ){ + if( pEnd->nType & JX9_TK_OCB /* '{' */ ){ + /* Ticket 1433-30: Annonymous/Closure functions body */ + iNest++; + }else if(pEnd->nType & JX9_TK_CCB /* '}' */ ){ + iNest--; + }else if( pEnd->nType & JX9_TK_SEMI /* ';' */ ){ + if( iNest <= 0 ){ + break; + } + } + pEnd++; + } + if( iFlags & EXPR_FLAG_COMMA_STATEMENT ){ + SyToken *pEnd2 = pGen->pIn; + iNest = 0; + /* Stop at the first comma */ + while( pEnd2 < pEnd ){ + if( pEnd2->nType & (JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_LPAREN/*'('*/) ){ + iNest++; + }else if(pEnd2->nType & (JX9_TK_CCB/*'}'*/|JX9_TK_CSB/*']'*/|JX9_TK_RPAREN/*')'*/)){ + iNest--; + }else if( pEnd2->nType & JX9_TK_COMMA /*','*/ ){ + if( iNest <= 0 ){ + break; + } + } + pEnd2++; + } + if( pEnd2 pGen->pIn ){ + SyToken *pTmp = pGen->pEnd; + /* Swap delimiter */ + pGen->pEnd = pEnd; + /* Try to get an expression tree */ + rc = jx9ExprMakeTree(&(*pGen), &sExprNode, &pRoot); + if( rc == SXRET_OK && pRoot ){ + rc = SXRET_OK; + if( xTreeValidator ){ + /* Call the upper layer validator callback */ + rc = xTreeValidator(&(*pGen), pRoot); + } + if( rc != SXERR_ABORT ){ + /* Generate code for the given tree */ + rc = GenStateEmitExprCode(&(*pGen), pRoot, iFlags); + } + nExpr = 1; + } + /* Release the whole tree */ + jx9ExprFreeTree(&(*pGen), &sExprNode); + /* Synchronize token stream */ + pGen->pEnd = pTmp; + pGen->pIn = pEnd; + if( rc == SXERR_ABORT ){ + SySetRelease(&sExprNode); + return SXERR_ABORT; + } + } + SySetRelease(&sExprNode); + return nExpr > 0 ? SXRET_OK : SXERR_EMPTY; +} +/* + * Return a pointer to the node construct handler associated + * with a given node type [i.e: string, integer, float, ...]. + */ +JX9_PRIVATE ProcNodeConstruct jx9GetNodeHandler(sxu32 nNodeType) +{ + if( nNodeType & JX9_TK_NUM ){ + /* Numeric literal: Either real or integer */ + return jx9CompileNumLiteral; + }else if( nNodeType & JX9_TK_DSTR ){ + /* Double quoted string */ + return jx9CompileString; + }else if( nNodeType & JX9_TK_SSTR ){ + /* Single quoted string */ + return jx9CompileSimpleString; + }else if( nNodeType & JX9_TK_NOWDOC ){ + /* Nowdoc */ + return jx9CompileNowdoc; + } + return 0; +} +/* + * Jx9 Language construct table. + */ +static const LangConstruct aLangConstruct[] = { + { JX9_TKWRD_IF, jx9CompileIf }, + { JX9_TKWRD_FUNCTION, jx9CompileFunction }, + { JX9_TKWRD_FOREACH, jx9CompileForeach }, + { JX9_TKWRD_WHILE, jx9CompileWhile }, + { JX9_TKWRD_FOR, jx9CompileFor }, + { JX9_TKWRD_SWITCH, jx9CompileSwitch }, + { JX9_TKWRD_DIE, jx9CompileHalt }, + { JX9_TKWRD_EXIT, jx9CompileHalt }, + { JX9_TKWRD_RETURN, jx9CompileReturn }, + { JX9_TKWRD_BREAK, jx9CompileBreak }, + { JX9_TKWRD_CONTINUE, jx9CompileContinue }, + { JX9_TKWRD_STATIC, jx9CompileStatic }, + { JX9_TKWRD_UPLINK, jx9CompileUplink }, + { JX9_TKWRD_CONST, jx9CompileConstant }, +}; +/* + * Return a pointer to the statement handler routine associated + * with a given JX9 keyword [i.e: if, for, while, ...]. + */ +static ProcLangConstruct GenStateGetStatementHandler( + sxu32 nKeywordID /* Keyword ID*/ + ) +{ + sxu32 n = 0; + for(;;){ + if( n >= SX_ARRAYSIZE(aLangConstruct) ){ + break; + } + if( aLangConstruct[n].nID == nKeywordID ){ + /* Return a pointer to the handler. + */ + return aLangConstruct[n].xConstruct; + } + n++; + } + /* Not a language construct */ + return 0; +} +/* + * Compile a jx9 program. + * If something goes wrong while compiling the Jx9 chunk, this function + * takes care of generating the appropriate error message. + */ +static sxi32 GenStateCompileChunk( + jx9_gen_state *pGen, /* Code generator state */ + sxi32 iFlags /* Compile flags */ + ) +{ + ProcLangConstruct xCons; + sxi32 rc; + rc = SXRET_OK; /* Prevent compiler warning */ + for(;;){ + if( pGen->pIn >= pGen->pEnd ){ + /* No more input to process */ + break; + } + xCons = 0; + if( pGen->pIn->nType & JX9_TK_KEYWORD ){ + sxu32 nKeyword = (sxu32)SX_PTR_TO_INT(pGen->pIn->pUserData); + /* Try to extract a language construct handler */ + xCons = GenStateGetStatementHandler(nKeyword); + if( xCons == 0 && !jx9IsLangConstruct(nKeyword) ){ + rc = jx9GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, + "Syntax error: Unexpected keyword '%z'", + &pGen->pIn->sData); + if( rc == SXERR_ABORT ){ + break; + } + /* Synchronize with the first semi-colon and avoid compiling + * this erroneous statement. + */ + xCons = jx9ErrorRecover; + } + } + if( xCons == 0 ){ + /* Assume an expression an try to compile it */ + rc = jx9CompileExpr(&(*pGen), 0, 0); + if( rc != SXERR_EMPTY ){ + /* Pop l-value */ + jx9VmEmitInstr(pGen->pVm, JX9_OP_POP, 1, 0, 0, 0); + } + }else{ + /* Go compile the sucker */ + rc = xCons(&(*pGen)); + } + if( rc == SXERR_ABORT ){ + /* Request to abort compilation */ + break; + } + /* Ignore trailing semi-colons ';' */ + while( pGen->pIn < pGen->pEnd && (pGen->pIn->nType & JX9_TK_SEMI) ){ + pGen->pIn++; + } + if( iFlags & JX9_COMPILE_SINGLE_STMT ){ + /* Compile a single statement and return */ + break; + } + /* LOOP ONE */ + /* LOOP TWO */ + /* LOOP THREE */ + /* LOOP FOUR */ + } + /* Return compilation status */ + return rc; +} +/* + * Compile a raw chunk. The raw chunk can contain JX9 code embedded + * in HTML, XML and so on. This function handle all the stuff. + * This is the only compile interface exported from this file. + */ +JX9_PRIVATE sxi32 jx9CompileScript( + jx9_vm *pVm, /* Generate JX9 bytecodes for this Virtual Machine */ + SyString *pScript, /* Script to compile */ + sxi32 iFlags /* Compile flags */ + ) +{ + jx9_gen_state *pGen; + SySet aToken; + sxi32 rc; + if( pScript->nByte < 1 ){ + /* Nothing to compile */ + return JX9_OK; + } + /* Initialize the tokens containers */ + SySetInit(&aToken, &pVm->sAllocator, sizeof(SyToken)); + SySetAlloc(&aToken, 0xc0); + pGen = &pVm->sCodeGen; + rc = JX9_OK; + /* Tokenize the JX9 chunk first */ + jx9Tokenize(pScript->zString,pScript->nByte,&aToken); + if( SySetUsed(&aToken) < 1 ){ + return SXERR_EMPTY; + } + /* Point to the head and tail of the token stream. */ + pGen->pIn = (SyToken *)SySetBasePtr(&aToken); + pGen->pEnd = &pGen->pIn[SySetUsed(&aToken)]; + /* Compile the chunk */ + rc = GenStateCompileChunk(pGen,iFlags); + /* Cleanup */ + SySetRelease(&aToken); + return rc; +} +/* + * Utility routines.Initialize the code generator. + */ +JX9_PRIVATE sxi32 jx9InitCodeGenerator( + jx9_vm *pVm, /* Target VM */ + ProcConsumer xErr, /* Error log consumer callabck */ + void *pErrData /* Last argument to xErr() */ + ) +{ + jx9_gen_state *pGen = &pVm->sCodeGen; + /* Zero the structure */ + SyZero(pGen, sizeof(jx9_gen_state)); + /* Initial state */ + pGen->pVm = &(*pVm); + pGen->xErr = xErr; + pGen->pErrData = pErrData; + SyHashInit(&pGen->hLiteral, &pVm->sAllocator, 0, 0); + SyHashInit(&pGen->hVar, &pVm->sAllocator, 0, 0); + /* Create the global scope */ + GenStateInitBlock(pGen, &pGen->sGlobal,GEN_BLOCK_GLOBAL,jx9VmInstrLength(&(*pVm)), 0); + /* Point to the global scope */ + pGen->pCurrent = &pGen->sGlobal; + return SXRET_OK; +} +/* + * Utility routines. Reset the code generator to it's initial state. + */ +JX9_PRIVATE sxi32 jx9ResetCodeGenerator( + jx9_vm *pVm, /* Target VM */ + ProcConsumer xErr, /* Error log consumer callabck */ + void *pErrData /* Last argument to xErr() */ + ) +{ + jx9_gen_state *pGen = &pVm->sCodeGen; + GenBlock *pBlock, *pParent; + /* Point to the global scope */ + pBlock = pGen->pCurrent; + while( pBlock->pParent != 0 ){ + pParent = pBlock->pParent; + GenStateFreeBlock(pBlock); + pBlock = pParent; + } + pGen->xErr = xErr; + pGen->pErrData = pErrData; + pGen->pCurrent = &pGen->sGlobal; + pGen->pIn = pGen->pEnd = 0; + pGen->nErr = 0; + return SXRET_OK; +} +/* + * Generate a compile-time error message. + * If the error count limit is reached (usually 15 error message) + * this function return SXERR_ABORT.In that case upper-layers must + * abort compilation immediately. + */ +JX9_PRIVATE sxi32 jx9GenCompileError(jx9_gen_state *pGen,sxi32 nErrType,sxu32 nLine,const char *zFormat,...) +{ + SyBlob *pWorker = &pGen->pVm->pEngine->xConf.sErrConsumer; + const char *zErr = "Error"; + va_list ap; + if( nErrType == E_ERROR ){ + /* Increment the error counter */ + pGen->nErr++; + if( pGen->nErr > 15 ){ + /* Error count limit reached */ + SyBlobFormat(pWorker, "%u Error count limit reached, JX9 is aborting compilation\n", nLine); + /* Abort immediately */ + return SXERR_ABORT; + } + } + switch(nErrType){ + case E_WARNING: zErr = "Warning"; break; + case E_PARSE: zErr = "Parse error"; break; + case E_NOTICE: zErr = "Notice"; break; + default: + break; + } + /* Format the error message */ + SyBlobFormat(pWorker, "%u %s: ", nLine, zErr); + va_start(ap, zFormat); + SyBlobFormatAp(pWorker, zFormat, ap); + va_end(ap); + /* Append a new line */ + SyBlobAppend(pWorker, (const void *)"\n", sizeof(char)); + return JX9_OK; +} +/* + * ---------------------------------------------------------- + * File: jx9_const.c + * MD5: f3980b00dd1eda0bb2b749424a8dfffe + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: const.c v1.7 Win7 2012-12-13 00:01 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file implement built-in constants for the JX9 engine. */ +/* + * JX9_VERSION + * __JX9__ + * Expand the current version of the JX9 engine. + */ +static void JX9_VER_Const(jx9_value *pVal, void *pUnused) +{ + SXUNUSED(pUnused); + jx9_value_string(pVal, jx9_lib_signature(), -1/*Compute length automatically*/); +} +#ifdef __WINNT__ +#include +#elif defined(__UNIXES__) +#include +#endif +/* + * JX9_OS + * __OS__ + * Expand the name of the host Operating System. + */ +static void JX9_OS_Const(jx9_value *pVal, void *pUnused) +{ +#if defined(__WINNT__) + jx9_value_string(pVal, "WinNT", (int)sizeof("WinNT")-1); +#elif defined(__UNIXES__) + struct utsname sInfo; + if( uname(&sInfo) != 0 ){ + jx9_value_string(pVal, "Unix", (int)sizeof("Unix")-1); + }else{ + jx9_value_string(pVal, sInfo.sysname, -1); + } +#else + jx9_value_string(pVal,"Host OS", (int)sizeof("Host OS")-1); +#endif + SXUNUSED(pUnused); +} +/* + * JX9_EOL + * Expand the correct 'End Of Line' symbol for this platform. + */ +static void JX9_EOL_Const(jx9_value *pVal, void *pUnused) +{ + SXUNUSED(pUnused); +#ifdef __WINNT__ + jx9_value_string(pVal, "\r\n", (int)sizeof("\r\n")-1); +#else + jx9_value_string(pVal, "\n", (int)sizeof(char)); +#endif +} +/* + * JX9_INT_MAX + * Expand the largest integer supported. + * Note that JX9 deals with 64-bit integer for all platforms. + */ +static void JX9_INTMAX_Const(jx9_value *pVal, void *pUnused) +{ + SXUNUSED(pUnused); + jx9_value_int64(pVal, SXI64_HIGH); +} +/* + * JX9_INT_SIZE + * Expand the size in bytes of a 64-bit integer. + */ +static void JX9_INTSIZE_Const(jx9_value *pVal, void *pUnused) +{ + SXUNUSED(pUnused); + jx9_value_int64(pVal, sizeof(sxi64)); +} +/* + * DIRECTORY_SEPARATOR. + * Expand the directory separator character. + */ +static void JX9_DIRSEP_Const(jx9_value *pVal, void *pUnused) +{ + SXUNUSED(pUnused); +#ifdef __WINNT__ + jx9_value_string(pVal, "\\", (int)sizeof(char)); +#else + jx9_value_string(pVal, "/", (int)sizeof(char)); +#endif +} +/* + * PATH_SEPARATOR. + * Expand the path separator character. + */ +static void JX9_PATHSEP_Const(jx9_value *pVal, void *pUnused) +{ + SXUNUSED(pUnused); +#ifdef __WINNT__ + jx9_value_string(pVal, ";", (int)sizeof(char)); +#else + jx9_value_string(pVal, ":", (int)sizeof(char)); +#endif +} +#ifndef __WINNT__ +#include +#endif +/* + * __TIME__ + * Expand the current time (GMT). + */ +static void JX9_TIME_Const(jx9_value *pVal, void *pUnused) +{ + Sytm sTm; +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + SXUNUSED(pUnused); /* cc warning */ + /* Expand */ + jx9_value_string_format(pVal, "%02d:%02d:%02d", sTm.tm_hour, sTm.tm_min, sTm.tm_sec); +} +/* + * __DATE__ + * Expand the current date in the ISO-8601 format. + */ +static void JX9_DATE_Const(jx9_value *pVal, void *pUnused) +{ + Sytm sTm; +#ifdef __WINNT__ + SYSTEMTIME sOS; + GetSystemTime(&sOS); + SYSTEMTIME_TO_SYTM(&sOS, &sTm); +#else + struct tm *pTm; + time_t t; + time(&t); + pTm = gmtime(&t); + STRUCT_TM_TO_SYTM(pTm, &sTm); +#endif + SXUNUSED(pUnused); /* cc warning */ + /* Expand */ + jx9_value_string_format(pVal, "%04d-%02d-%02d", sTm.tm_year, sTm.tm_mon+1, sTm.tm_mday); +} +/* + * __FILE__ + * Path of the processed script. + */ +static void JX9_FILE_Const(jx9_value *pVal, void *pUserData) +{ + jx9_vm *pVm = (jx9_vm *)pUserData; + SyString *pFile; + /* Peek the top entry */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile == 0 ){ + /* Expand the magic word: ":MEMORY:" */ + jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1); + }else{ + jx9_value_string(pVal, pFile->zString, pFile->nByte); + } +} +/* + * __DIR__ + * Directory holding the processed script. + */ +static void JX9_DIR_Const(jx9_value *pVal, void *pUserData) +{ + jx9_vm *pVm = (jx9_vm *)pUserData; + SyString *pFile; + /* Peek the top entry */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile == 0 ){ + /* Expand the magic word: ":MEMORY:" */ + jx9_value_string(pVal, ":MEMORY:", (int)sizeof(":MEMORY:")-1); + }else{ + if( pFile->nByte > 0 ){ + const char *zDir; + int nLen; + zDir = jx9ExtractDirName(pFile->zString, (int)pFile->nByte, &nLen); + jx9_value_string(pVal, zDir, nLen); + }else{ + /* Expand '.' as the current directory*/ + jx9_value_string(pVal, ".", (int)sizeof(char)); + } + } +} +/* + * E_ERROR + * Expands 1 + */ +static void JX9_E_ERROR_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 1); + SXUNUSED(pUserData); +} +/* + * E_WARNING + * Expands 2 + */ +static void JX9_E_WARNING_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 2); + SXUNUSED(pUserData); +} +/* + * E_PARSE + * Expands 4 + */ +static void JX9_E_PARSE_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 4); + SXUNUSED(pUserData); +} +/* + * E_NOTICE + * Expands 8 + */ +static void JX9_E_NOTICE_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 8); + SXUNUSED(pUserData); +} +/* + * CASE_LOWER + * Expands 0. + */ +static void JX9_CASE_LOWER_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 0); + SXUNUSED(pUserData); +} +/* + * CASE_UPPER + * Expands 1. + */ +static void JX9_CASE_UPPER_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 1); + SXUNUSED(pUserData); +} +/* + * STR_PAD_LEFT + * Expands 0. + */ +static void JX9_STR_PAD_LEFT_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 0); + SXUNUSED(pUserData); +} +/* + * STR_PAD_RIGHT + * Expands 1. + */ +static void JX9_STR_PAD_RIGHT_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 1); + SXUNUSED(pUserData); +} +/* + * STR_PAD_BOTH + * Expands 2. + */ +static void JX9_STR_PAD_BOTH_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 2); + SXUNUSED(pUserData); +} +/* + * COUNT_NORMAL + * Expands 0 + */ +static void JX9_COUNT_NORMAL_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 0); + SXUNUSED(pUserData); +} +/* + * COUNT_RECURSIVE + * Expands 1. + */ +static void JX9_COUNT_RECURSIVE_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 1); + SXUNUSED(pUserData); +} +/* + * SORT_ASC + * Expands 1. + */ +static void JX9_SORT_ASC_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 1); + SXUNUSED(pUserData); +} +/* + * SORT_DESC + * Expands 2. + */ +static void JX9_SORT_DESC_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 2); + SXUNUSED(pUserData); +} +/* + * SORT_REGULAR + * Expands 3. + */ +static void JX9_SORT_REG_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 3); + SXUNUSED(pUserData); +} +/* + * SORT_NUMERIC + * Expands 4. + */ +static void JX9_SORT_NUMERIC_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 4); + SXUNUSED(pUserData); +} +/* + * SORT_STRING + * Expands 5. + */ +static void JX9_SORT_STRING_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 5); + SXUNUSED(pUserData); +} +/* + * JX9_ROUND_HALF_UP + * Expands 1. + */ +static void JX9_JX9_ROUND_HALF_UP_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 1); + SXUNUSED(pUserData); +} +/* + * SJX9_ROUND_HALF_DOWN + * Expands 2. + */ +static void JX9_JX9_ROUND_HALF_DOWN_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 2); + SXUNUSED(pUserData); +} +/* + * JX9_ROUND_HALF_EVEN + * Expands 3. + */ +static void JX9_JX9_ROUND_HALF_EVEN_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 3); + SXUNUSED(pUserData); +} +/* + * JX9_ROUND_HALF_ODD + * Expands 4. + */ +static void JX9_JX9_ROUND_HALF_ODD_Const(jx9_value *pVal, void *pUserData) +{ + jx9_value_int(pVal, 4); + SXUNUSED(pUserData); +} +#ifdef JX9_ENABLE_MATH_FUNC +/* + * PI + * Expand the value of pi. + */ +static void JX9_M_PI_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, JX9_PI); +} +/* + * M_E + * Expand 2.7182818284590452354 + */ +static void JX9_M_E_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 2.7182818284590452354); +} +/* + * M_LOG2E + * Expand 2.7182818284590452354 + */ +static void JX9_M_LOG2E_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 1.4426950408889634074); +} +/* + * M_LOG10E + * Expand 0.4342944819032518276 + */ +static void JX9_M_LOG10E_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 0.4342944819032518276); +} +/* + * M_LN2 + * Expand 0.69314718055994530942 + */ +static void JX9_M_LN2_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 0.69314718055994530942); +} +/* + * M_LN10 + * Expand 2.30258509299404568402 + */ +static void JX9_M_LN10_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 2.30258509299404568402); +} +/* + * M_PI_2 + * Expand 1.57079632679489661923 + */ +static void JX9_M_PI_2_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 1.57079632679489661923); +} +/* + * M_PI_4 + * Expand 0.78539816339744830962 + */ +static void JX9_M_PI_4_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 0.78539816339744830962); +} +/* + * M_1_PI + * Expand 0.31830988618379067154 + */ +static void JX9_M_1_PI_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 0.31830988618379067154); +} +/* + * M_2_PI + * Expand 0.63661977236758134308 + */ +static void JX9_M_2_PI_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 0.63661977236758134308); +} +/* + * M_SQRTPI + * Expand 1.77245385090551602729 + */ +static void JX9_M_SQRTPI_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 1.77245385090551602729); +} +/* + * M_2_SQRTPI + * Expand 1.12837916709551257390 + */ +static void JX9_M_2_SQRTPI_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 1.12837916709551257390); +} +/* + * M_SQRT2 + * Expand 1.41421356237309504880 + */ +static void JX9_M_SQRT2_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 1.41421356237309504880); +} +/* + * M_SQRT3 + * Expand 1.73205080756887729352 + */ +static void JX9_M_SQRT3_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 1.73205080756887729352); +} +/* + * M_SQRT1_2 + * Expand 0.70710678118654752440 + */ +static void JX9_M_SQRT1_2_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 0.70710678118654752440); +} +/* + * M_LNPI + * Expand 1.14472988584940017414 + */ +static void JX9_M_LNPI_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 1.14472988584940017414); +} +/* + * M_EULER + * Expand 0.57721566490153286061 + */ +static void JX9_M_EULER_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_double(pVal, 0.57721566490153286061); +} +#endif /* JX9_DISABLE_BUILTIN_MATH */ +/* + * DATE_ATOM + * Expand Atom (example: 2005-08-15T15:52:01+00:00) + */ +static void JX9_DATE_ATOM_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/); +} +/* + * DATE_COOKIE + * HTTP Cookies (example: Monday, 15-Aug-05 15:52:01 UTC) + */ +static void JX9_DATE_COOKIE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/); +} +/* + * DATE_ISO8601 + * ISO-8601 (example: 2005-08-15T15:52:01+0000) + */ +static void JX9_DATE_ISO8601_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "Y-m-d\\TH:i:sO", -1/*Compute length automatically*/); +} +/* + * DATE_RFC822 + * RFC 822 (example: Mon, 15 Aug 05 15:52:01 +0000) + */ +static void JX9_DATE_RFC822_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/); +} +/* + * DATE_RFC850 + * RFC 850 (example: Monday, 15-Aug-05 15:52:01 UTC) + */ +static void JX9_DATE_RFC850_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "l, d-M-y H:i:s T", -1/*Compute length automatically*/); +} +/* + * DATE_RFC1036 + * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void JX9_DATE_RFC1036_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "D, d M y H:i:s O", -1/*Compute length automatically*/); +} +/* + * DATE_RFC1123 + * RFC 1123 (example: Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void JX9_DATE_RFC1123_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); +} +/* + * DATE_RFC2822 + * RFC 2822 (Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void JX9_DATE_RFC2822_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); +} +/* + * DATE_RSS + * RSS (Mon, 15 Aug 2005 15:52:01 +0000) + */ +static void JX9_DATE_RSS_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "D, d M Y H:i:s O", -1/*Compute length automatically*/); +} +/* + * DATE_W3C + * World Wide Web Consortium (example: 2005-08-15T15:52:01+00:00) + */ +static void JX9_DATE_W3C_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_string(pVal, "Y-m-d\\TH:i:sP", -1/*Compute length automatically*/); +} +/* + * ENT_COMPAT + * Expand 0x01 (Must be a power of two) + */ +static void JX9_ENT_COMPAT_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x01); +} +/* + * ENT_QUOTES + * Expand 0x02 (Must be a power of two) + */ +static void JX9_ENT_QUOTES_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x02); +} +/* + * ENT_NOQUOTES + * Expand 0x04 (Must be a power of two) + */ +static void JX9_ENT_NOQUOTES_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x04); +} +/* + * ENT_IGNORE + * Expand 0x08 (Must be a power of two) + */ +static void JX9_ENT_IGNORE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x08); +} +/* + * ENT_SUBSTITUTE + * Expand 0x10 (Must be a power of two) + */ +static void JX9_ENT_SUBSTITUTE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x10); +} +/* + * ENT_DISALLOWED + * Expand 0x20 (Must be a power of two) + */ +static void JX9_ENT_DISALLOWED_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x20); +} +/* + * ENT_HTML401 + * Expand 0x40 (Must be a power of two) + */ +static void JX9_ENT_HTML401_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x40); +} +/* + * ENT_XML1 + * Expand 0x80 (Must be a power of two) + */ +static void JX9_ENT_XML1_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x80); +} +/* + * ENT_XHTML + * Expand 0x100 (Must be a power of two) + */ +static void JX9_ENT_XHTML_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x100); +} +/* + * ENT_HTML5 + * Expand 0x200 (Must be a power of two) + */ +static void JX9_ENT_HTML5_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x200); +} +/* + * ISO-8859-1 + * ISO_8859_1 + * Expand 1 + */ +static void JX9_ISO88591_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * UTF-8 + * UTF8 + * Expand 2 + */ +static void JX9_UTF8_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * HTML_ENTITIES + * Expand 1 + */ +static void JX9_HTML_ENTITIES_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * HTML_SPECIALCHARS + * Expand 2 + */ +static void JX9_HTML_SPECIALCHARS_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 2); +} +/* + * JX9_URL_SCHEME. + * Expand 1 + */ +static void JX9_JX9_URL_SCHEME_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * JX9_URL_HOST. + * Expand 2 + */ +static void JX9_JX9_URL_HOST_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 2); +} +/* + * JX9_URL_PORT. + * Expand 3 + */ +static void JX9_JX9_URL_PORT_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 3); +} +/* + * JX9_URL_USER. + * Expand 4 + */ +static void JX9_JX9_URL_USER_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 4); +} +/* + * JX9_URL_PASS. + * Expand 5 + */ +static void JX9_JX9_URL_PASS_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 5); +} +/* + * JX9_URL_PATH. + * Expand 6 + */ +static void JX9_JX9_URL_PATH_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 6); +} +/* + * JX9_URL_QUERY. + * Expand 7 + */ +static void JX9_JX9_URL_QUERY_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 7); +} +/* + * JX9_URL_FRAGMENT. + * Expand 8 + */ +static void JX9_JX9_URL_FRAGMENT_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 8); +} +/* + * JX9_QUERY_RFC1738 + * Expand 1 + */ +static void JX9_JX9_QUERY_RFC1738_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * JX9_QUERY_RFC3986 + * Expand 1 + */ +static void JX9_JX9_QUERY_RFC3986_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 2); +} +/* + * FNM_NOESCAPE + * Expand 0x01 (Must be a power of two) + */ +static void JX9_FNM_NOESCAPE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x01); +} +/* + * FNM_PATHNAME + * Expand 0x02 (Must be a power of two) + */ +static void JX9_FNM_PATHNAME_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x02); +} +/* + * FNM_PERIOD + * Expand 0x04 (Must be a power of two) + */ +static void JX9_FNM_PERIOD_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x04); +} +/* + * FNM_CASEFOLD + * Expand 0x08 (Must be a power of two) + */ +static void JX9_FNM_CASEFOLD_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x08); +} +/* + * PATHINFO_DIRNAME + * Expand 1. + */ +static void JX9_PATHINFO_DIRNAME_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * PATHINFO_BASENAME + * Expand 2. + */ +static void JX9_PATHINFO_BASENAME_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 2); +} +/* + * PATHINFO_EXTENSION + * Expand 3. + */ +static void JX9_PATHINFO_EXTENSION_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 3); +} +/* + * PATHINFO_FILENAME + * Expand 4. + */ +static void JX9_PATHINFO_FILENAME_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 4); +} +/* + * ASSERT_ACTIVE. + * Expand the value of JX9_ASSERT_ACTIVE defined in jx9Int.h + */ +static void JX9_ASSERT_ACTIVE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, JX9_ASSERT_DISABLE); +} +/* + * ASSERT_WARNING. + * Expand the value of JX9_ASSERT_WARNING defined in jx9Int.h + */ +static void JX9_ASSERT_WARNING_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, JX9_ASSERT_WARNING); +} +/* + * ASSERT_BAIL. + * Expand the value of JX9_ASSERT_BAIL defined in jx9Int.h + */ +static void JX9_ASSERT_BAIL_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, JX9_ASSERT_BAIL); +} +/* + * ASSERT_QUIET_EVAL. + * Expand the value of JX9_ASSERT_QUIET_EVAL defined in jx9Int.h + */ +static void JX9_ASSERT_QUIET_EVAL_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, JX9_ASSERT_QUIET_EVAL); +} +/* + * ASSERT_CALLBACK. + * Expand the value of JX9_ASSERT_CALLBACK defined in jx9Int.h + */ +static void JX9_ASSERT_CALLBACK_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, JX9_ASSERT_CALLBACK); +} +/* + * SEEK_SET. + * Expand 0 + */ +static void JX9_SEEK_SET_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0); +} +/* + * SEEK_CUR. + * Expand 1 + */ +static void JX9_SEEK_CUR_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * SEEK_END. + * Expand 2 + */ +static void JX9_SEEK_END_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 2); +} +/* + * LOCK_SH. + * Expand 2 + */ +static void JX9_LOCK_SH_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * LOCK_NB. + * Expand 5 + */ +static void JX9_LOCK_NB_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 5); +} +/* + * LOCK_EX. + * Expand 0x01 (MUST BE A POWER OF TWO) + */ +static void JX9_LOCK_EX_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x01); +} +/* + * LOCK_UN. + * Expand 0 + */ +static void JX9_LOCK_UN_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0); +} +/* + * FILE_USE_INC_PATH + * Expand 0x01 (Must be a power of two) + */ +static void JX9_FILE_USE_INCLUDE_PATH_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x1); +} +/* + * FILE_IGN_NL + * Expand 0x02 (Must be a power of two) + */ +static void JX9_FILE_IGNORE_NEW_LINES_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x2); +} +/* + * FILE_SKIP_EL + * Expand 0x04 (Must be a power of two) + */ +static void JX9_FILE_SKIP_EMPTY_LINES_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x4); +} +/* + * FILE_APPEND + * Expand 0x08 (Must be a power of two) + */ +static void JX9_FILE_APPEND_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x08); +} +/* + * SCANDIR_SORT_ASCENDING + * Expand 0 + */ +static void JX9_SCANDIR_SORT_ASCENDING_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0); +} +/* + * SCANDIR_SORT_DESCENDING + * Expand 1 + */ +static void JX9_SCANDIR_SORT_DESCENDING_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * SCANDIR_SORT_NONE + * Expand 2 + */ +static void JX9_SCANDIR_SORT_NONE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 2); +} +/* + * GLOB_MARK + * Expand 0x01 (must be a power of two) + */ +static void JX9_GLOB_MARK_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x01); +} +/* + * GLOB_NOSORT + * Expand 0x02 (must be a power of two) + */ +static void JX9_GLOB_NOSORT_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x02); +} +/* + * GLOB_NOCHECK + * Expand 0x04 (must be a power of two) + */ +static void JX9_GLOB_NOCHECK_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x04); +} +/* + * GLOB_NOESCAPE + * Expand 0x08 (must be a power of two) + */ +static void JX9_GLOB_NOESCAPE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x08); +} +/* + * GLOB_BRACE + * Expand 0x10 (must be a power of two) + */ +static void JX9_GLOB_BRACE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x10); +} +/* + * GLOB_ONLYDIR + * Expand 0x20 (must be a power of two) + */ +static void JX9_GLOB_ONLYDIR_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x20); +} +/* + * GLOB_ERR + * Expand 0x40 (must be a power of two) + */ +static void JX9_GLOB_ERR_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x40); +} +/* + * STDIN + * Expand the STDIN handle as a resource. + */ +static void JX9_STDIN_Const(jx9_value *pVal, void *pUserData) +{ + jx9_vm *pVm = (jx9_vm *)pUserData; + void *pResource; + pResource = jx9ExportStdin(pVm); + jx9_value_resource(pVal, pResource); +} +/* + * STDOUT + * Expand the STDOUT handle as a resource. + */ +static void JX9_STDOUT_Const(jx9_value *pVal, void *pUserData) +{ + jx9_vm *pVm = (jx9_vm *)pUserData; + void *pResource; + pResource = jx9ExportStdout(pVm); + jx9_value_resource(pVal, pResource); +} +/* + * STDERR + * Expand the STDERR handle as a resource. + */ +static void JX9_STDERR_Const(jx9_value *pVal, void *pUserData) +{ + jx9_vm *pVm = (jx9_vm *)pUserData; + void *pResource; + pResource = jx9ExportStderr(pVm); + jx9_value_resource(pVal, pResource); +} +/* + * INI_SCANNER_NORMAL + * Expand 1 + */ +static void JX9_INI_SCANNER_NORMAL_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 1); +} +/* + * INI_SCANNER_RAW + * Expand 2 + */ +static void JX9_INI_SCANNER_RAW_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 2); +} +/* + * EXTR_OVERWRITE + * Expand 0x01 (Must be a power of two) + */ +static void JX9_EXTR_OVERWRITE_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x1); +} +/* + * EXTR_SKIP + * Expand 0x02 (Must be a power of two) + */ +static void JX9_EXTR_SKIP_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x2); +} +/* + * EXTR_PREFIX_SAME + * Expand 0x04 (Must be a power of two) + */ +static void JX9_EXTR_PREFIX_SAME_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x4); +} +/* + * EXTR_PREFIX_ALL + * Expand 0x08 (Must be a power of two) + */ +static void JX9_EXTR_PREFIX_ALL_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x8); +} +/* + * EXTR_PREFIX_INVALID + * Expand 0x10 (Must be a power of two) + */ +static void JX9_EXTR_PREFIX_INVALID_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x10); +} +/* + * EXTR_IF_EXISTS + * Expand 0x20 (Must be a power of two) + */ +static void JX9_EXTR_IF_EXISTS_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x20); +} +/* + * EXTR_PREFIX_IF_EXISTS + * Expand 0x40 (Must be a power of two) + */ +static void JX9_EXTR_PREFIX_IF_EXISTS_Const(jx9_value *pVal, void *pUserData) +{ + SXUNUSED(pUserData); /* cc warning */ + jx9_value_int(pVal, 0x40); +} +/* + * Table of built-in constants. + */ +static const jx9_builtin_constant aBuiltIn[] = { + {"JX9_VERSION", JX9_VER_Const }, + {"JX9_ENGINE", JX9_VER_Const }, + {"__JX9__", JX9_VER_Const }, + {"JX9_OS", JX9_OS_Const }, + {"__OS__", JX9_OS_Const }, + {"JX9_EOL", JX9_EOL_Const }, + {"JX9_INT_MAX", JX9_INTMAX_Const }, + {"MAXINT", JX9_INTMAX_Const }, + {"JX9_INT_SIZE", JX9_INTSIZE_Const }, + {"PATH_SEPARATOR", JX9_PATHSEP_Const }, + {"DIRECTORY_SEPARATOR", JX9_DIRSEP_Const }, + {"DIR_SEP", JX9_DIRSEP_Const }, + {"__TIME__", JX9_TIME_Const }, + {"__DATE__", JX9_DATE_Const }, + {"__FILE__", JX9_FILE_Const }, + {"__DIR__", JX9_DIR_Const }, + {"E_ERROR", JX9_E_ERROR_Const }, + {"E_WARNING", JX9_E_WARNING_Const}, + {"E_PARSE", JX9_E_PARSE_Const }, + {"E_NOTICE", JX9_E_NOTICE_Const }, + {"CASE_LOWER", JX9_CASE_LOWER_Const }, + {"CASE_UPPER", JX9_CASE_UPPER_Const }, + {"STR_PAD_LEFT", JX9_STR_PAD_LEFT_Const }, + {"STR_PAD_RIGHT", JX9_STR_PAD_RIGHT_Const}, + {"STR_PAD_BOTH", JX9_STR_PAD_BOTH_Const }, + {"COUNT_NORMAL", JX9_COUNT_NORMAL_Const }, + {"COUNT_RECURSIVE", JX9_COUNT_RECURSIVE_Const }, + {"SORT_ASC", JX9_SORT_ASC_Const }, + {"SORT_DESC", JX9_SORT_DESC_Const }, + {"SORT_REGULAR", JX9_SORT_REG_Const }, + {"SORT_NUMERIC", JX9_SORT_NUMERIC_Const }, + {"SORT_STRING", JX9_SORT_STRING_Const }, + {"JX9_ROUND_HALF_DOWN", JX9_JX9_ROUND_HALF_DOWN_Const }, + {"JX9_ROUND_HALF_EVEN", JX9_JX9_ROUND_HALF_EVEN_Const }, + {"JX9_ROUND_HALF_UP", JX9_JX9_ROUND_HALF_UP_Const }, + {"JX9_ROUND_HALF_ODD", JX9_JX9_ROUND_HALF_ODD_Const }, +#ifdef JX9_ENABLE_MATH_FUNC + {"PI", JX9_M_PI_Const }, + {"M_E", JX9_M_E_Const }, + {"M_LOG2E", JX9_M_LOG2E_Const }, + {"M_LOG10E", JX9_M_LOG10E_Const }, + {"M_LN2", JX9_M_LN2_Const }, + {"M_LN10", JX9_M_LN10_Const }, + {"M_PI_2", JX9_M_PI_2_Const }, + {"M_PI_4", JX9_M_PI_4_Const }, + {"M_1_PI", JX9_M_1_PI_Const }, + {"M_2_PI", JX9_M_2_PI_Const }, + {"M_SQRTPI", JX9_M_SQRTPI_Const }, + {"M_2_SQRTPI", JX9_M_2_SQRTPI_Const }, + {"M_SQRT2", JX9_M_SQRT2_Const }, + {"M_SQRT3", JX9_M_SQRT3_Const }, + {"M_SQRT1_2", JX9_M_SQRT1_2_Const }, + {"M_LNPI", JX9_M_LNPI_Const }, + {"M_EULER", JX9_M_EULER_Const }, +#endif /* JX9_ENABLE_MATH_FUNC */ + {"DATE_ATOM", JX9_DATE_ATOM_Const }, + {"DATE_COOKIE", JX9_DATE_COOKIE_Const }, + {"DATE_ISO8601", JX9_DATE_ISO8601_Const }, + {"DATE_RFC822", JX9_DATE_RFC822_Const }, + {"DATE_RFC850", JX9_DATE_RFC850_Const }, + {"DATE_RFC1036", JX9_DATE_RFC1036_Const }, + {"DATE_RFC1123", JX9_DATE_RFC1123_Const }, + {"DATE_RFC2822", JX9_DATE_RFC2822_Const }, + {"DATE_RFC3339", JX9_DATE_ATOM_Const }, + {"DATE_RSS", JX9_DATE_RSS_Const }, + {"DATE_W3C", JX9_DATE_W3C_Const }, + {"ENT_COMPAT", JX9_ENT_COMPAT_Const }, + {"ENT_QUOTES", JX9_ENT_QUOTES_Const }, + {"ENT_NOQUOTES", JX9_ENT_NOQUOTES_Const }, + {"ENT_IGNORE", JX9_ENT_IGNORE_Const }, + {"ENT_SUBSTITUTE", JX9_ENT_SUBSTITUTE_Const}, + {"ENT_DISALLOWED", JX9_ENT_DISALLOWED_Const}, + {"ENT_HTML401", JX9_ENT_HTML401_Const }, + {"ENT_XML1", JX9_ENT_XML1_Const }, + {"ENT_XHTML", JX9_ENT_XHTML_Const }, + {"ENT_HTML5", JX9_ENT_HTML5_Const }, + {"ISO-8859-1", JX9_ISO88591_Const }, + {"ISO_8859_1", JX9_ISO88591_Const }, + {"UTF-8", JX9_UTF8_Const }, + {"UTF8", JX9_UTF8_Const }, + {"HTML_ENTITIES", JX9_HTML_ENTITIES_Const}, + {"HTML_SPECIALCHARS", JX9_HTML_SPECIALCHARS_Const }, + {"JX9_URL_SCHEME", JX9_JX9_URL_SCHEME_Const}, + {"JX9_URL_HOST", JX9_JX9_URL_HOST_Const}, + {"JX9_URL_PORT", JX9_JX9_URL_PORT_Const}, + {"JX9_URL_USER", JX9_JX9_URL_USER_Const}, + {"JX9_URL_PASS", JX9_JX9_URL_PASS_Const}, + {"JX9_URL_PATH", JX9_JX9_URL_PATH_Const}, + {"JX9_URL_QUERY", JX9_JX9_URL_QUERY_Const}, + {"JX9_URL_FRAGMENT", JX9_JX9_URL_FRAGMENT_Const}, + {"JX9_QUERY_RFC1738", JX9_JX9_QUERY_RFC1738_Const}, + {"JX9_QUERY_RFC3986", JX9_JX9_QUERY_RFC3986_Const}, + {"FNM_NOESCAPE", JX9_FNM_NOESCAPE_Const }, + {"FNM_PATHNAME", JX9_FNM_PATHNAME_Const }, + {"FNM_PERIOD", JX9_FNM_PERIOD_Const }, + {"FNM_CASEFOLD", JX9_FNM_CASEFOLD_Const }, + {"PATHINFO_DIRNAME", JX9_PATHINFO_DIRNAME_Const }, + {"PATHINFO_BASENAME", JX9_PATHINFO_BASENAME_Const }, + {"PATHINFO_EXTENSION", JX9_PATHINFO_EXTENSION_Const}, + {"PATHINFO_FILENAME", JX9_PATHINFO_FILENAME_Const }, + {"ASSERT_ACTIVE", JX9_ASSERT_ACTIVE_Const }, + {"ASSERT_WARNING", JX9_ASSERT_WARNING_Const }, + {"ASSERT_BAIL", JX9_ASSERT_BAIL_Const }, + {"ASSERT_QUIET_EVAL", JX9_ASSERT_QUIET_EVAL_Const }, + {"ASSERT_CALLBACK", JX9_ASSERT_CALLBACK_Const }, + {"SEEK_SET", JX9_SEEK_SET_Const }, + {"SEEK_CUR", JX9_SEEK_CUR_Const }, + {"SEEK_END", JX9_SEEK_END_Const }, + {"LOCK_EX", JX9_LOCK_EX_Const }, + {"LOCK_SH", JX9_LOCK_SH_Const }, + {"LOCK_NB", JX9_LOCK_NB_Const }, + {"LOCK_UN", JX9_LOCK_UN_Const }, + {"FILE_USE_INC_PATH", JX9_FILE_USE_INCLUDE_PATH_Const}, + {"FILE_IGN_NL", JX9_FILE_IGNORE_NEW_LINES_Const}, + {"FILE_SKIP_EL", JX9_FILE_SKIP_EMPTY_LINES_Const}, + {"FILE_APPEND", JX9_FILE_APPEND_Const }, + {"SCANDIR_SORT_ASC", JX9_SCANDIR_SORT_ASCENDING_Const }, + {"SCANDIR_SORT_DESC", JX9_SCANDIR_SORT_DESCENDING_Const }, + {"SCANDIR_SORT_NONE", JX9_SCANDIR_SORT_NONE_Const }, + {"GLOB_MARK", JX9_GLOB_MARK_Const }, + {"GLOB_NOSORT", JX9_GLOB_NOSORT_Const }, + {"GLOB_NOCHECK", JX9_GLOB_NOCHECK_Const }, + {"GLOB_NOESCAPE", JX9_GLOB_NOESCAPE_Const}, + {"GLOB_BRACE", JX9_GLOB_BRACE_Const }, + {"GLOB_ONLYDIR", JX9_GLOB_ONLYDIR_Const }, + {"GLOB_ERR", JX9_GLOB_ERR_Const }, + {"STDIN", JX9_STDIN_Const }, + {"stdin", JX9_STDIN_Const }, + {"STDOUT", JX9_STDOUT_Const }, + {"stdout", JX9_STDOUT_Const }, + {"STDERR", JX9_STDERR_Const }, + {"stderr", JX9_STDERR_Const }, + {"INI_SCANNER_NORMAL", JX9_INI_SCANNER_NORMAL_Const }, + {"INI_SCANNER_RAW", JX9_INI_SCANNER_RAW_Const }, + {"EXTR_OVERWRITE", JX9_EXTR_OVERWRITE_Const }, + {"EXTR_SKIP", JX9_EXTR_SKIP_Const }, + {"EXTR_PREFIX_SAME", JX9_EXTR_PREFIX_SAME_Const }, + {"EXTR_PREFIX_ALL", JX9_EXTR_PREFIX_ALL_Const }, + {"EXTR_PREFIX_INVALID", JX9_EXTR_PREFIX_INVALID_Const }, + {"EXTR_IF_EXISTS", JX9_EXTR_IF_EXISTS_Const }, + {"EXTR_PREFIX_IF_EXISTS", JX9_EXTR_PREFIX_IF_EXISTS_Const} +}; +/* + * Register the built-in constants defined above. + */ +JX9_PRIVATE void jx9RegisterBuiltInConstant(jx9_vm *pVm) +{ + sxu32 n; + /* + * Note that all built-in constants have access to the jx9 virtual machine + * that trigger the constant invocation as their private data. + */ + for( n = 0 ; n < SX_ARRAYSIZE(aBuiltIn) ; ++n ){ + jx9_create_constant(&(*pVm), aBuiltIn[n].zName, aBuiltIn[n].xExpand, &(*pVm)); + } +} +/* + * ---------------------------------------------------------- + * File: jx9_hashmap.c + * MD5: 4e93d15cd37e6093e25d8ede3064e210 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: hashmap.c v2.6 Win7 2012-12-11 00:50 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file implement generic hashmaps used to represent JSON arrays and objects */ +/* Allowed node types */ +#define HASHMAP_INT_NODE 1 /* Node with an int [i.e: 64-bit integer] key */ +#define HASHMAP_BLOB_NODE 2 /* Node with a string/BLOB key */ +/* + * Default hash function for int [i.e; 64-bit integer] keys. + */ +static sxu32 IntHash(sxi64 iKey) +{ + return (sxu32)(iKey ^ (iKey << 8) ^ (iKey >> 8)); +} +/* + * Default hash function for string/BLOB keys. + */ +static sxu32 BinHash(const void *pSrc, sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +/* + * Return the total number of entries in a given hashmap. + * If bRecurisve is set to TRUE then recurse on hashmap entries. + * If the nesting limit is reached, this function abort immediately. + */ +static sxi64 HashmapCount(jx9_hashmap *pMap, int bRecursive, int iRecCount) +{ + sxi64 iCount = 0; + if( !bRecursive ){ + iCount = pMap->nEntry; + }else{ + /* Recursive hashmap walk */ + jx9_hashmap_node *pEntry = pMap->pLast; + jx9_value *pElem; + sxu32 n = 0; + for(;;){ + if( n >= pMap->nEntry ){ + break; + } + /* Point to the element value */ + pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pEntry->nValIdx); + if( pElem ){ + if( pElem->iFlags & MEMOBJ_HASHMAP ){ + if( iRecCount > 31 ){ + /* Nesting limit reached */ + return iCount; + } + /* Recurse */ + iRecCount++; + iCount += HashmapCount((jx9_hashmap *)pElem->x.pOther, TRUE, iRecCount); + iRecCount--; + } + } + /* Point to the next entry */ + pEntry = pEntry->pNext; + ++n; + } + /* Update count */ + iCount += pMap->nEntry; + } + return iCount; +} +/* + * Allocate a new hashmap node with a 64-bit integer key. + * If something goes wrong [i.e: out of memory], this function return NULL. + * Otherwise a fresh [jx9_hashmap_node] instance is returned. + */ +static jx9_hashmap_node * HashmapNewIntNode(jx9_hashmap *pMap, sxi64 iKey, sxu32 nHash, sxu32 nValIdx) +{ + jx9_hashmap_node *pNode; + /* Allocate a new node */ + pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); + if( pNode == 0 ){ + return 0; + } + /* Zero the stucture */ + SyZero(pNode, sizeof(jx9_hashmap_node)); + /* Fill in the structure */ + pNode->pMap = &(*pMap); + pNode->iType = HASHMAP_INT_NODE; + pNode->nHash = nHash; + pNode->xKey.iKey = iKey; + pNode->nValIdx = nValIdx; + return pNode; +} +/* + * Allocate a new hashmap node with a BLOB key. + * If something goes wrong [i.e: out of memory], this function return NULL. + * Otherwise a fresh [jx9_hashmap_node] instance is returned. + */ +static jx9_hashmap_node * HashmapNewBlobNode(jx9_hashmap *pMap, const void *pKey, sxu32 nKeyLen, sxu32 nHash, sxu32 nValIdx) +{ + jx9_hashmap_node *pNode; + /* Allocate a new node */ + pNode = (jx9_hashmap_node *)SyMemBackendPoolAlloc(&pMap->pVm->sAllocator, sizeof(jx9_hashmap_node)); + if( pNode == 0 ){ + return 0; + } + /* Zero the stucture */ + SyZero(pNode, sizeof(jx9_hashmap_node)); + /* Fill in the structure */ + pNode->pMap = &(*pMap); + pNode->iType = HASHMAP_BLOB_NODE; + pNode->nHash = nHash; + SyBlobInit(&pNode->xKey.sKey, &pMap->pVm->sAllocator); + SyBlobAppend(&pNode->xKey.sKey, pKey, nKeyLen); + pNode->nValIdx = nValIdx; + return pNode; +} +/* + * link a hashmap node to the given bucket index (last argument to this function). + */ +static void HashmapNodeLink(jx9_hashmap *pMap, jx9_hashmap_node *pNode, sxu32 nBucketIdx) +{ + /* Link */ + if( pMap->apBucket[nBucketIdx] != 0 ){ + pNode->pNextCollide = pMap->apBucket[nBucketIdx]; + pMap->apBucket[nBucketIdx]->pPrevCollide = pNode; + } + pMap->apBucket[nBucketIdx] = pNode; + /* Link to the map list */ + if( pMap->pFirst == 0 ){ + pMap->pFirst = pMap->pLast = pNode; + /* Point to the first inserted node */ + pMap->pCur = pNode; + }else{ + MACRO_LD_PUSH(pMap->pLast, pNode); + } + ++pMap->nEntry; +} +/* + * Unlink a node from the hashmap. + * If the node count reaches zero then release the whole hash-bucket. + */ +static void jx9HashmapUnlinkNode(jx9_hashmap_node *pNode) +{ + jx9_hashmap *pMap = pNode->pMap; + jx9_vm *pVm = pMap->pVm; + /* Unlink from the corresponding bucket */ + if( pNode->pPrevCollide == 0 ){ + pMap->apBucket[pNode->nHash & (pMap->nSize - 1)] = pNode->pNextCollide; + }else{ + pNode->pPrevCollide->pNextCollide = pNode->pNextCollide; + } + if( pNode->pNextCollide ){ + pNode->pNextCollide->pPrevCollide = pNode->pPrevCollide; + } + if( pMap->pFirst == pNode ){ + pMap->pFirst = pNode->pPrev; + } + if( pMap->pCur == pNode ){ + /* Advance the node cursor */ + pMap->pCur = pMap->pCur->pPrev; /* Reverse link */ + } + /* Unlink from the map list */ + MACRO_LD_REMOVE(pMap->pLast, pNode); + /* Restore to the free list */ + jx9VmUnsetMemObj(pVm, pNode->nValIdx); + if( pNode->iType == HASHMAP_BLOB_NODE ){ + SyBlobRelease(&pNode->xKey.sKey); + } + SyMemBackendPoolFree(&pVm->sAllocator, pNode); + pMap->nEntry--; + if( pMap->nEntry < 1 ){ + /* Free the hash-bucket */ + SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); + pMap->apBucket = 0; + pMap->nSize = 0; + pMap->pFirst = pMap->pLast = pMap->pCur = 0; + } +} +#define HASHMAP_FILL_FACTOR 3 +/* + * Grow the hash-table and rehash all entries. + */ +static sxi32 HashmapGrowBucket(jx9_hashmap *pMap) +{ + if( pMap->nEntry >= pMap->nSize * HASHMAP_FILL_FACTOR ){ + jx9_hashmap_node **apOld = pMap->apBucket; + jx9_hashmap_node *pEntry, **apNew; + sxu32 nNew = pMap->nSize << 1; + sxu32 nBucket; + sxu32 n; + if( nNew < 1 ){ + nNew = 16; + } + /* Allocate a new bucket */ + apNew = (jx9_hashmap_node **)SyMemBackendAlloc(&pMap->pVm->sAllocator, nNew * sizeof(jx9_hashmap_node *)); + if( apNew == 0 ){ + if( pMap->nSize < 1 ){ + return SXERR_MEM; /* Fatal */ + } + /* Not so fatal here, simply a performance hit */ + return SXRET_OK; + } + /* Zero the table */ + SyZero((void *)apNew, nNew * sizeof(jx9_hashmap_node *)); + /* Reflect the change */ + pMap->apBucket = apNew; + pMap->nSize = nNew; + if( apOld == 0 ){ + /* First allocated table [i.e: no entry], return immediately */ + return SXRET_OK; + } + /* Rehash old entries */ + pEntry = pMap->pFirst; + n = 0; + for( ;; ){ + if( n >= pMap->nEntry ){ + break; + } + /* Clear the old collision link */ + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Link to the new bucket */ + nBucket = pEntry->nHash & (nNew - 1); + if( pMap->apBucket[nBucket] != 0 ){ + pEntry->pNextCollide = pMap->apBucket[nBucket]; + pMap->apBucket[nBucket]->pPrevCollide = pEntry; + } + pMap->apBucket[nBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n++; + } + /* Free the old table */ + SyMemBackendFree(&pMap->pVm->sAllocator, (void *)apOld); + } + return SXRET_OK; +} +/* + * Insert a 64-bit integer key and it's associated value (if any) in the given + * hashmap. + */ +static sxi32 HashmapInsertIntKey(jx9_hashmap *pMap,sxi64 iKey,jx9_value *pValue) +{ + jx9_hashmap_node *pNode; + jx9_value *pObj; + sxu32 nIdx; + sxu32 nHash; + sxi32 rc; + /* Reserve a jx9_value for the value */ + pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); + if( pObj == 0 ){ + return SXERR_MEM; + } + if( pValue ){ + /* Duplicate the value */ + jx9MemObjStore(pValue, pObj); + } + /* Hash the key */ + nHash = pMap->xIntHash(iKey); + /* Allocate a new int node */ + pNode = HashmapNewIntNode(&(*pMap), iKey, nHash, nIdx); + if( pNode == 0 ){ + return SXERR_MEM; + } + /* Make sure the bucket is big enough to hold the new entry */ + rc = HashmapGrowBucket(&(*pMap)); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); + return rc; + } + /* Perform the insertion */ + HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); + /* All done */ + return SXRET_OK; +} +/* + * Insert a BLOB key and it's associated value (if any) in the given + * hashmap. + */ +static sxi32 HashmapInsertBlobKey(jx9_hashmap *pMap,const void *pKey,sxu32 nKeyLen,jx9_value *pValue) +{ + jx9_hashmap_node *pNode; + jx9_value *pObj; + sxu32 nHash; + sxu32 nIdx; + sxi32 rc; + /* Reserve a jx9_value for the value */ + pObj = jx9VmReserveMemObj(pMap->pVm,&nIdx); + if( pObj == 0 ){ + return SXERR_MEM; + } + if( pValue ){ + /* Duplicate the value */ + jx9MemObjStore(pValue, pObj); + } + /* Hash the key */ + nHash = pMap->xBlobHash(pKey, nKeyLen); + /* Allocate a new blob node */ + pNode = HashmapNewBlobNode(&(*pMap), pKey, nKeyLen, nHash, nIdx); + if( pNode == 0 ){ + return SXERR_MEM; + } + /* Make sure the bucket is big enough to hold the new entry */ + rc = HashmapGrowBucket(&(*pMap)); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pMap->pVm->sAllocator, pNode); + return rc; + } + /* Perform the insertion */ + HashmapNodeLink(&(*pMap), pNode, nHash & (pMap->nSize - 1)); + /* All done */ + return SXRET_OK; +} +/* + * Check if a given 64-bit integer key exists in the given hashmap. + * Write a pointer to the target node on success. Otherwise + * SXERR_NOTFOUND is returned on failure. + */ +static sxi32 HashmapLookupIntKey( + jx9_hashmap *pMap, /* Target hashmap */ + sxi64 iKey, /* lookup key */ + jx9_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + jx9_hashmap_node *pNode; + sxu32 nHash; + if( pMap->nEntry < 1 ){ + /* Don't bother hashing, there is no entry anyway */ + return SXERR_NOTFOUND; + } + /* Hash the key first */ + nHash = pMap->xIntHash(iKey); + /* Point to the appropriate bucket */ + pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; + /* Perform the lookup */ + for(;;){ + if( pNode == 0 ){ + break; + } + if( pNode->iType == HASHMAP_INT_NODE + && pNode->nHash == nHash + && pNode->xKey.iKey == iKey ){ + /* Node found */ + if( ppNode ){ + *ppNode = pNode; + } + return SXRET_OK; + } + /* Follow the collision link */ + pNode = pNode->pNextCollide; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Check if a given BLOB key exists in the given hashmap. + * Write a pointer to the target node on success. Otherwise + * SXERR_NOTFOUND is returned on failure. + */ +static sxi32 HashmapLookupBlobKey( + jx9_hashmap *pMap, /* Target hashmap */ + const void *pKey, /* Lookup key */ + sxu32 nKeyLen, /* Key length in bytes */ + jx9_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + jx9_hashmap_node *pNode; + sxu32 nHash; + if( pMap->nEntry < 1 ){ + /* Don't bother hashing, there is no entry anyway */ + return SXERR_NOTFOUND; + } + /* Hash the key first */ + nHash = pMap->xBlobHash(pKey, nKeyLen); + /* Point to the appropriate bucket */ + pNode = pMap->apBucket[nHash & (pMap->nSize - 1)]; + /* Perform the lookup */ + for(;;){ + if( pNode == 0 ){ + break; + } + if( pNode->iType == HASHMAP_BLOB_NODE + && pNode->nHash == nHash + && SyBlobLength(&pNode->xKey.sKey) == nKeyLen + && SyMemcmp(SyBlobData(&pNode->xKey.sKey), pKey, nKeyLen) == 0 ){ + /* Node found */ + if( ppNode ){ + *ppNode = pNode; + } + return SXRET_OK; + } + /* Follow the collision link */ + pNode = pNode->pNextCollide; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Check if the given BLOB key looks like a decimal number. + * Retrurn TRUE on success.FALSE otherwise. + */ +static int HashmapIsIntKey(SyBlob *pKey) +{ + const char *zIn = (const char *)SyBlobData(pKey); + const char *zEnd = &zIn[SyBlobLength(pKey)]; + if( (int)(zEnd-zIn) > 1 && zIn[0] == '0' ){ + /* Octal not decimal number */ + return FALSE; + } + if( (zIn[0] == '-' || zIn[0] == '+') && &zIn[1] < zEnd ){ + zIn++; + } + for(;;){ + if( zIn >= zEnd ){ + return TRUE; + } + if( (unsigned char)zIn[0] >= 0xc0 /* UTF-8 stream */ || !SyisDigit(zIn[0]) ){ + break; + } + zIn++; + } + /* Key does not look like a decimal number */ + return FALSE; +} +/* + * Check if a given key exists in the given hashmap. + * Write a pointer to the target node on success. + * Otherwise SXERR_NOTFOUND is returned on failure. + */ +static sxi32 HashmapLookup( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_hashmap_node **ppNode /* OUT: target node on success */ + ) +{ + jx9_hashmap_node *pNode = 0; /* cc -O6 warning */ + sxi32 rc; + if( pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES) ){ + if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(&(*pKey)); + } + if( SyBlobLength(&pKey->sBlob) > 0 ){ + /* Perform a blob lookup */ + rc = HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), SyBlobLength(&pKey->sBlob), &pNode); + goto result; + } + } + /* Perform an int lookup */ + if((pKey->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an integer cast */ + jx9MemObjToInteger(pKey); + } + /* Perform an int lookup */ + rc = HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode); +result: + if( rc == SXRET_OK ){ + /* Node found */ + if( ppNode ){ + *ppNode = pNode; + } + return SXRET_OK; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Insert a given key and it's associated value (if any) in the given + * hashmap. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +static sxi32 HashmapInsert( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_value *pVal /* Node value */ + ) +{ + jx9_hashmap_node *pNode = 0; + sxi32 rc = SXRET_OK; + if( pMap->nEntry < 1 && pKey && (pKey->iFlags & MEMOBJ_STRING) ){ + pMap->iFlags |= HASHMAP_JSON_OBJECT; + } + if( pKey && (pKey->iFlags & (MEMOBJ_STRING|MEMOBJ_HASHMAP|MEMOBJ_RES)) ){ + if( (pKey->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(&(*pKey)); + } + if( SyBlobLength(&pKey->sBlob) < 1 || HashmapIsIntKey(&pKey->sBlob) ){ + if(SyBlobLength(&pKey->sBlob) < 1){ + /* Automatic index assign */ + pKey = 0; + } + goto IntKey; + } + if( SXRET_OK == HashmapLookupBlobKey(&(*pMap), SyBlobData(&pKey->sBlob), + SyBlobLength(&pKey->sBlob), &pNode) ){ + /* Overwrite the old value */ + jx9_value *pElem; + pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); + if( pElem ){ + if( pVal ){ + jx9MemObjStore(pVal, pElem); + }else{ + /* Nullify the entry */ + jx9MemObjToNull(pElem); + } + } + return SXRET_OK; + } + /* Perform a blob-key insertion */ + rc = HashmapInsertBlobKey(&(*pMap),SyBlobData(&pKey->sBlob),SyBlobLength(&pKey->sBlob),&(*pVal)); + return rc; + } +IntKey: + if( pKey ){ + if((pKey->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an integer cast */ + jx9MemObjToInteger(pKey); + } + if( SXRET_OK == HashmapLookupIntKey(&(*pMap), pKey->x.iVal, &pNode) ){ + /* Overwrite the old value */ + jx9_value *pElem; + pElem = (jx9_value *)SySetAt(&pMap->pVm->aMemObj, pNode->nValIdx); + if( pElem ){ + if( pVal ){ + jx9MemObjStore(pVal, pElem); + }else{ + /* Nullify the entry */ + jx9MemObjToNull(pElem); + } + } + return SXRET_OK; + } + /* Perform a 64-bit-int-key insertion */ + rc = HashmapInsertIntKey(&(*pMap), pKey->x.iVal, &(*pVal)); + if( rc == SXRET_OK ){ + if( pKey->x.iVal >= pMap->iNextIdx ){ + /* Increment the automatic index */ + pMap->iNextIdx = pKey->x.iVal + 1; + /* Make sure the automatic index is not reserved */ + while( SXRET_OK == HashmapLookupIntKey(&(*pMap), pMap->iNextIdx, 0) ){ + pMap->iNextIdx++; + } + } + } + }else{ + /* Assign an automatic index */ + rc = HashmapInsertIntKey(&(*pMap),pMap->iNextIdx,&(*pVal)); + if( rc == SXRET_OK ){ + ++pMap->iNextIdx; + } + } + /* Insertion result */ + return rc; +} +/* + * Extract node value. + */ +static jx9_value * HashmapExtractNodeValue(jx9_hashmap_node *pNode) +{ + /* Point to the desired object */ + jx9_value *pObj; + pObj = (jx9_value *)SySetAt(&pNode->pMap->pVm->aMemObj, pNode->nValIdx); + return pObj; +} +/* + * Insert a node in the given hashmap. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +static sxi32 HashmapInsertNode(jx9_hashmap *pMap, jx9_hashmap_node *pNode, int bPreserve) +{ + jx9_value *pObj; + sxi32 rc; + /* Extract the node value */ + pObj = HashmapExtractNodeValue(&(*pNode)); + if( pObj == 0 ){ + return SXERR_EMPTY; + } + /* Preserve key */ + if( pNode->iType == HASHMAP_INT_NODE){ + /* Int64 key */ + if( !bPreserve ){ + /* Assign an automatic index */ + rc = HashmapInsert(&(*pMap), 0, pObj); + }else{ + rc = HashmapInsertIntKey(&(*pMap), pNode->xKey.iKey, pObj); + } + }else{ + /* Blob key */ + rc = HashmapInsertBlobKey(&(*pMap), SyBlobData(&pNode->xKey.sKey), + SyBlobLength(&pNode->xKey.sKey), pObj); + } + return rc; +} +/* + * Compare two node values. + * Return 0 if the node values are equals, > 0 if pLeft is greater than pRight + * or < 0 if pRight is greater than pLeft. + * For a full description on jx9_values comparison, refer to the implementation + * of the [jx9MemObjCmp()] function defined in memobj.c or the official + * documenation. + */ +static sxi32 HashmapNodeCmp(jx9_hashmap_node *pLeft, jx9_hashmap_node *pRight, int bStrict) +{ + jx9_value sObj1, sObj2; + sxi32 rc; + if( pLeft == pRight ){ + /* + * Same node.Refer to the sort() implementation defined + * below for more information on this sceanario. + */ + return 0; + } + /* Do the comparison */ + jx9MemObjInit(pLeft->pMap->pVm, &sObj1); + jx9MemObjInit(pLeft->pMap->pVm, &sObj2); + jx9HashmapExtractNodeValue(pLeft, &sObj1, FALSE); + jx9HashmapExtractNodeValue(pRight, &sObj2, FALSE); + rc = jx9MemObjCmp(&sObj1, &sObj2, bStrict, 0); + jx9MemObjRelease(&sObj1); + jx9MemObjRelease(&sObj2); + return rc; +} +/* + * Rehash a node with a 64-bit integer key. + * Refer to [merge_sort(), array_shift()] implementations for more information. + */ +static void HashmapRehashIntNode(jx9_hashmap_node *pEntry) +{ + jx9_hashmap *pMap = pEntry->pMap; + sxu32 nBucket; + /* Remove old collision links */ + if( pEntry->pPrevCollide ){ + pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; + }else{ + pMap->apBucket[pEntry->nHash & (pMap->nSize - 1)] = pEntry->pNextCollide; + } + if( pEntry->pNextCollide ){ + pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; + } + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Compute the new hash */ + pEntry->nHash = pMap->xIntHash(pMap->iNextIdx); + pEntry->xKey.iKey = pMap->iNextIdx; + nBucket = pEntry->nHash & (pMap->nSize - 1); + /* Link to the new bucket */ + pEntry->pNextCollide = pMap->apBucket[nBucket]; + if( pMap->apBucket[nBucket] ){ + pMap->apBucket[nBucket]->pPrevCollide = pEntry; + } + pEntry->pNextCollide = pMap->apBucket[nBucket]; + pMap->apBucket[nBucket] = pEntry; + /* Increment the automatic index */ + pMap->iNextIdx++; +} +/* + * Perform a linear search on a given hashmap. + * Write a pointer to the target node on success. + * Otherwise SXERR_NOTFOUND is returned on failure. + * Refer to [array_intersect(), array_diff(), in_array(), ...] implementations + * for more information. + */ +static int HashmapFindValue( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pNeedle, /* Lookup key */ + jx9_hashmap_node **ppNode, /* OUT: target node on success */ + int bStrict /* TRUE for strict comparison */ + ) +{ + jx9_hashmap_node *pEntry; + jx9_value sVal, *pVal; + jx9_value sNeedle; + sxi32 rc; + sxu32 n; + /* Perform a linear search since we cannot sort the hashmap based on values */ + pEntry = pMap->pFirst; + n = pMap->nEntry; + jx9MemObjInit(pMap->pVm, &sVal); + jx9MemObjInit(pMap->pVm, &sNeedle); + for(;;){ + if( n < 1 ){ + break; + } + /* Extract node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + if( (pVal->iFlags|pNeedle->iFlags) & MEMOBJ_NULL ){ + sxi32 iF1 = pVal->iFlags; + sxi32 iF2 = pNeedle->iFlags; + if( iF1 == iF2 ){ + /* NULL values are equals */ + if( ppNode ){ + *ppNode = pEntry; + } + return SXRET_OK; + } + }else{ + /* Duplicate value */ + jx9MemObjLoad(pVal, &sVal); + jx9MemObjLoad(pNeedle, &sNeedle); + rc = jx9MemObjCmp(&sNeedle, &sVal, bStrict, 0); + jx9MemObjRelease(&sVal); + jx9MemObjRelease(&sNeedle); + if( rc == 0 ){ + if( ppNode ){ + *ppNode = pEntry; + } + /* Match found*/ + return SXRET_OK; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* No such entry */ + return SXERR_NOTFOUND; +} +/* + * Compare two hashmaps. + * Return 0 if the hashmaps are equals.Any other value indicates inequality. + * Note on array comparison operators. + * According to the JX9 language reference manual. + * Array Operators Example Name Result + * $a + $b Union Union of $a and $b. + * $a == $b Equality TRUE if $a and $b have the same key/value pairs. + * $a === $b Identity TRUE if $a and $b have the same key/value pairs in the same + * order and of the same types. + * $a != $b Inequality TRUE if $a is not equal to $b. + * $a <> $b Inequality TRUE if $a is not equal to $b. + * $a !== $b Non-identity TRUE if $a is not identical to $b. + * The + operator returns the right-hand array appended to the left-hand array; + * For keys that exist in both arrays, the elements from the left-hand array will be used + * and the matching elements from the right-hand array will be ignored. + * "apple", "b" => "banana"); + * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); + * $c = $a + $b; // Union of $a and $b + * print "Union of \$a and \$b: \n"; + * dump($c); + * $c = $b + $a; // Union of $b and $a + * print "Union of \$b and \$a: \n"; + * dump($c); + * ?> + * When executed, this script will print the following: + * Union of $a and $b: + * array(3) { + * ["a"]=> + * string(5) "apple" + * ["b"]=> + * string(6) "banana" + * ["c"]=> + * string(6) "cherry" + * } + * Union of $b and $a: + * array(3) { + * ["a"]=> + * string(4) "pear" + * ["b"]=> + * string(10) "strawberry" + * ["c"]=> + * string(6) "cherry" + * } + * Elements of arrays are equal for the comparison if they have the same key and value. + */ +JX9_PRIVATE sxi32 jx9HashmapCmp( + jx9_hashmap *pLeft, /* Left hashmap */ + jx9_hashmap *pRight, /* Right hashmap */ + int bStrict /* TRUE for strict comparison */ + ) +{ + jx9_hashmap_node *pLe, *pRe; + sxi32 rc; + sxu32 n; + if( pLeft == pRight ){ + /* Same hashmap instance. This can easily happen since hashmaps are passed by reference. + * Unlike the engine. + */ + return 0; + } + if( pLeft->nEntry != pRight->nEntry ){ + /* Must have the same number of entries */ + return pLeft->nEntry > pRight->nEntry ? 1 : -1; + } + /* Point to the first inserted entry of the left hashmap */ + pLe = pLeft->pFirst; + pRe = 0; /* cc warning */ + /* Perform the comparison */ + n = pLeft->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + if( pLe->iType == HASHMAP_INT_NODE){ + /* Int key */ + rc = HashmapLookupIntKey(&(*pRight), pLe->xKey.iKey, &pRe); + }else{ + SyBlob *pKey = &pLe->xKey.sKey; + /* Blob key */ + rc = HashmapLookupBlobKey(&(*pRight), SyBlobData(pKey), SyBlobLength(pKey), &pRe); + } + if( rc != SXRET_OK ){ + /* No such entry in the right side */ + return 1; + } + rc = 0; + if( bStrict ){ + /* Make sure, the keys are of the same type */ + if( pLe->iType != pRe->iType ){ + rc = 1; + } + } + if( !rc ){ + /* Compare nodes */ + rc = HashmapNodeCmp(pLe, pRe, bStrict); + } + if( rc != 0 ){ + /* Nodes key/value differ */ + return rc; + } + /* Point to the next entry */ + pLe = pLe->pPrev; /* Reverse link */ + n--; + } + return 0; /* Hashmaps are equals */ +} +/* + * Merge two hashmaps. + * Note on the merge process + * According to the JX9 language reference manual. + * Merges the elements of two arrays together so that the values of one are appended + * to the end of the previous one. It returns the resulting array (pDest). + * If the input arrays have the same string keys, then the later value for that key + * will overwrite the previous one. If, however, the arrays contain numeric keys + * the later value will not overwrite the original value, but will be appended. + * Values in the input array with numeric keys will be renumbered with incrementing + * keys starting from zero in the result array. + */ +static sxi32 HashmapMerge(jx9_hashmap *pSrc, jx9_hashmap *pDest) +{ + jx9_hashmap_node *pEntry; + jx9_value sKey, *pVal; + sxi32 rc; + sxu32 n; + if( pSrc == pDest ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the engine. + */ + return SXRET_OK; + } + /* Point to the first inserted entry in the source */ + pEntry = pSrc->pFirst; + /* Perform the merge */ + for( n = 0 ; n < pSrc->nEntry ; ++n ){ + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + /* Blob key insertion */ + jx9MemObjInitFromString(pDest->pVm, &sKey, 0); + jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); + rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); + jx9MemObjRelease(&sKey); + }else{ + rc = HashmapInsert(&(*pDest), 0/* Automatic index assign */, pVal); + } + if( rc != SXRET_OK ){ + return rc; + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * Duplicate the contents of a hashmap. Store the copy in pDest. + * Refer to the [array_pad(), array_copy(), ...] implementation for more information. + */ +JX9_PRIVATE sxi32 jx9HashmapDup(jx9_hashmap *pSrc, jx9_hashmap *pDest) +{ + jx9_hashmap_node *pEntry; + jx9_value sKey, *pVal; + sxi32 rc; + sxu32 n; + if( pSrc == pDest ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the engine. + */ + return SXRET_OK; + } + /* Point to the first inserted entry in the source */ + pEntry = pSrc->pFirst; + /* Perform the duplication */ + for( n = 0 ; n < pSrc->nEntry ; ++n ){ + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + /* Blob key insertion */ + jx9MemObjInitFromString(pDest->pVm, &sKey, 0); + jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey)); + rc = jx9HashmapInsert(&(*pDest), &sKey, pVal); + jx9MemObjRelease(&sKey); + }else{ + /* Int key insertion */ + rc = HashmapInsertIntKey(&(*pDest), pEntry->xKey.iKey, pVal); + } + if( rc != SXRET_OK ){ + return rc; + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * Perform the union of two hashmaps. + * This operation is performed only if the user uses the '+' operator + * with a variable holding an array as follows: + * "apple", "b" => "banana"); + * $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); + * $c = $a + $b; // Union of $a and $b + * print "Union of \$a and \$b: \n"; + * dump($c); + * $c = $b + $a; // Union of $b and $a + * print "Union of \$b and \$a: \n"; + * dump($c); + * ?> + * When executed, this script will print the following: + * Union of $a and $b: + * array(3) { + * ["a"]=> + * string(5) "apple" + * ["b"]=> + * string(6) "banana" + * ["c"]=> + * string(6) "cherry" + * } + * Union of $b and $a: + * array(3) { + * ["a"]=> + * string(4) "pear" + * ["b"]=> + * string(10) "strawberry" + * ["c"]=> + * string(6) "cherry" + * } + * The + operator returns the right-hand array appended to the left-hand array; + * For keys that exist in both arrays, the elements from the left-hand array will be used + * and the matching elements from the right-hand array will be ignored. + */ +JX9_PRIVATE sxi32 jx9HashmapUnion(jx9_hashmap *pLeft, jx9_hashmap *pRight) +{ + jx9_hashmap_node *pEntry; + sxi32 rc = SXRET_OK; + jx9_value *pObj; + sxu32 n; + if( pLeft == pRight ){ + /* Same map. This can easily happen since hashmaps are passed by reference. + * Unlike the engine. + */ + return SXRET_OK; + } + /* Perform the union */ + pEntry = pRight->pFirst; + for(n = 0 ; n < pRight->nEntry ; ++n ){ + /* Make sure the given key does not exists in the left array */ + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + /* BLOB key */ + if( SXRET_OK != + HashmapLookupBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), SyBlobLength(&pEntry->xKey.sKey), 0) ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj ){ + /* Perform the insertion */ + rc = HashmapInsertBlobKey(&(*pLeft), SyBlobData(&pEntry->xKey.sKey), + SyBlobLength(&pEntry->xKey.sKey),pObj); + if( rc != SXRET_OK ){ + return rc; + } + } + } + }else{ + /* INT key */ + if( SXRET_OK != HashmapLookupIntKey(&(*pLeft), pEntry->xKey.iKey, 0) ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj ){ + /* Perform the insertion */ + rc = HashmapInsertIntKey(&(*pLeft), pEntry->xKey.iKey, pObj); + if( rc != SXRET_OK ){ + return rc; + } + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + return SXRET_OK; +} +/* + * Allocate a new hashmap. + * Return a pointer to the freshly allocated hashmap on success.NULL otherwise. + */ +JX9_PRIVATE jx9_hashmap * jx9NewHashmap( + jx9_vm *pVm, /* VM that trigger the hashmap creation */ + sxu32 (*xIntHash)(sxi64), /* Hash function for int keys.NULL otherwise*/ + sxu32 (*xBlobHash)(const void *, sxu32) /* Hash function for BLOB keys.NULL otherwise */ + ) +{ + jx9_hashmap *pMap; + /* Allocate a new instance */ + pMap = (jx9_hashmap *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_hashmap)); + if( pMap == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pMap, sizeof(jx9_hashmap)); + /* Fill in the structure */ + pMap->pVm = &(*pVm); + pMap->iRef = 1; + /* pMap->iFlags = 0; */ + /* Default hash functions */ + pMap->xIntHash = xIntHash ? xIntHash : IntHash; + pMap->xBlobHash = xBlobHash ? xBlobHash : BinHash; + return pMap; +} +/* + * Install superglobals in the given virtual machine. + * Note on superglobals. + * According to the JX9 language reference manual. + * Superglobals are built-in variables that are always available in all scopes. +* Description +* All predefined variables in JX9 are "superglobals", which means they +* are available in all scopes throughout a script. +* These variables are: +* $_SERVER +* $_GET +* $_POST +* $_FILES +* $_REQUEST +* $_ENV +*/ +JX9_PRIVATE sxi32 jx9HashmapLoadBuiltin(jx9_vm *pVm) +{ + static const char * azSuper[] = { + "_SERVER", /* $_SERVER */ + "_GET", /* $_GET */ + "_POST", /* $_POST */ + "_FILES", /* $_FILES */ + "_REQUEST", /* $_REQUEST */ + "_COOKIE", /* $_COOKIE */ + "_ENV", /* $_ENV */ + "_HEADER", /* $_HEADER */ + "argv" /* $argv */ + }; + SyString *pFile; + sxi32 rc; + sxu32 n; + /* Install globals variable now */ + for( n = 0 ; n < SX_ARRAYSIZE(azSuper) ; n++ ){ + jx9_value *pSuper; + /* Request an empty array */ + pSuper = jx9_new_array(&(*pVm)); + if( pSuper == 0 ){ + return SXERR_MEM; + } + /* Install */ + rc = jx9_vm_config(&(*pVm),JX9_VM_CONFIG_CREATE_VAR, azSuper[n]/* Super-global name*/, pSuper/* Super-global value */); + if( rc != SXRET_OK ){ + return rc; + } + /* Release the value now it have been installed */ + jx9_release_value(&(*pVm), pSuper); + } + /* Set some $_SERVER entries */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + /* + * 'SCRIPT_FILENAME' + * The absolute pathname of the currently executing script. + */ + jx9_vm_config(pVm, JX9_VM_CONFIG_SERVER_ATTR, + "SCRIPT_FILENAME", + pFile ? pFile->zString : ":Memory:", + pFile ? pFile->nByte : sizeof(":Memory:") - 1 + ); + /* All done, all global variables are installed now */ + return SXRET_OK; +} +/* + * Release a hashmap. + */ +JX9_PRIVATE sxi32 jx9HashmapRelease(jx9_hashmap *pMap, int FreeDS) +{ + jx9_hashmap_node *pEntry, *pNext; + jx9_vm *pVm = pMap->pVm; + sxu32 n; + /* Start the release process */ + n = 0; + pEntry = pMap->pFirst; + for(;;){ + if( n >= pMap->nEntry ){ + break; + } + pNext = pEntry->pPrev; /* Reverse link */ + /* Restore the jx9_value to the free list */ + jx9VmUnsetMemObj(pVm, pEntry->nValIdx); + /* Release the node */ + if( pEntry->iType == HASHMAP_BLOB_NODE ){ + SyBlobRelease(&pEntry->xKey.sKey); + } + SyMemBackendPoolFree(&pVm->sAllocator, pEntry); + /* Point to the next entry */ + pEntry = pNext; + n++; + } + if( pMap->nEntry > 0 ){ + /* Release the hash bucket */ + SyMemBackendFree(&pVm->sAllocator, pMap->apBucket); + } + if( FreeDS ){ + /* Free the whole instance */ + SyMemBackendPoolFree(&pVm->sAllocator, pMap); + }else{ + /* Keep the instance but reset it's fields */ + pMap->apBucket = 0; + pMap->iNextIdx = 0; + pMap->nEntry = pMap->nSize = 0; + pMap->pFirst = pMap->pLast = pMap->pCur = 0; + } + return SXRET_OK; +} +/* + * Decrement the reference count of a given hashmap. + * If the count reaches zero which mean no more variables + * are pointing to this hashmap, then release the whole instance. + */ +JX9_PRIVATE void jx9HashmapUnref(jx9_hashmap *pMap) +{ + pMap->iRef--; + if( pMap->iRef < 1 ){ + jx9HashmapRelease(pMap, TRUE); + } +} +/* + * Check if a given key exists in the given hashmap. + * Write a pointer to the target node on success. + * Otherwise SXERR_NOTFOUND is returned on failure. + */ +JX9_PRIVATE sxi32 jx9HashmapLookup( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_hashmap_node **ppNode /* OUT: Target node on success */ + ) +{ + sxi32 rc; + if( pMap->nEntry < 1 ){ + /* TICKET 1433-25: Don't bother hashing, the hashmap is empty anyway. + */ + return SXERR_NOTFOUND; + } + rc = HashmapLookup(&(*pMap), &(*pKey), ppNode); + return rc; +} +/* + * Insert a given key and it's associated value (if any) in the given + * hashmap. + * If a node with the given key already exists in the database + * then this function overwrite the old value. + */ +JX9_PRIVATE sxi32 jx9HashmapInsert( + jx9_hashmap *pMap, /* Target hashmap */ + jx9_value *pKey, /* Lookup key */ + jx9_value *pVal /* Node value.NULL otherwise */ + ) +{ + sxi32 rc; + rc = HashmapInsert(&(*pMap), &(*pKey), &(*pVal)); + return rc; +} +/* + * Reset the node cursor of a given hashmap. + */ +JX9_PRIVATE void jx9HashmapResetLoopCursor(jx9_hashmap *pMap) +{ + /* Reset the loop cursor */ + pMap->pCur = pMap->pFirst; +} +/* + * Return a pointer to the node currently pointed by the node cursor. + * If the cursor reaches the end of the list, then this function + * return NULL. + * Note that the node cursor is automatically advanced by this function. + */ +JX9_PRIVATE jx9_hashmap_node * jx9HashmapGetNextEntry(jx9_hashmap *pMap) +{ + jx9_hashmap_node *pCur = pMap->pCur; + if( pCur == 0 ){ + /* End of the list, return null */ + return 0; + } + /* Advance the node cursor */ + pMap->pCur = pCur->pPrev; /* Reverse link */ + return pCur; +} +/* + * Extract a node value. + */ +JX9_PRIVATE jx9_value * jx9HashmapGetNodeValue(jx9_hashmap_node *pNode) +{ + jx9_value *pValue; + pValue = HashmapExtractNodeValue(pNode); + return pValue; +} +/* + * Extract a node value (Second). + */ +JX9_PRIVATE void jx9HashmapExtractNodeValue(jx9_hashmap_node *pNode, jx9_value *pValue, int bStore) +{ + jx9_value *pEntry = HashmapExtractNodeValue(pNode); + if( pEntry ){ + if( bStore ){ + jx9MemObjStore(pEntry, pValue); + }else{ + jx9MemObjLoad(pEntry, pValue); + } + }else{ + jx9MemObjRelease(pValue); + } +} +/* + * Extract a node key. + */ +JX9_PRIVATE void jx9HashmapExtractNodeKey(jx9_hashmap_node *pNode,jx9_value *pKey) +{ + /* Fill with the current key */ + if( pNode->iType == HASHMAP_INT_NODE ){ + if( SyBlobLength(&pKey->sBlob) > 0 ){ + SyBlobRelease(&pKey->sBlob); + } + pKey->x.iVal = pNode->xKey.iKey; + MemObjSetType(pKey, MEMOBJ_INT); + }else{ + SyBlobReset(&pKey->sBlob); + SyBlobAppend(&pKey->sBlob, SyBlobData(&pNode->xKey.sKey), SyBlobLength(&pNode->xKey.sKey)); + MemObjSetType(pKey, MEMOBJ_STRING); + } +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +/* + * Store the address of nodes value in the given container. + * Refer to the [vfprintf(), vprintf(), vsprintf()] implementations + * defined in 'builtin.c' for more information. + */ +JX9_PRIVATE int jx9HashmapValuesToSet(jx9_hashmap *pMap, SySet *pOut) +{ + jx9_hashmap_node *pEntry = pMap->pFirst; + jx9_value *pValue; + sxu32 n; + /* Initialize the container */ + SySetInit(pOut, &pMap->pVm->sAllocator, sizeof(jx9_value *)); + for(n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + SySetPut(pOut, (const void *)&pValue); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Total inserted entries */ + return (int)SySetUsed(pOut); +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* + * Merge sort. + * The merge sort implementation is based on the one found in the SQLite3 source tree. + * Status: Public domain + */ +/* Node comparison callback signature */ +typedef sxi32 (*ProcNodeCmp)(jx9_hashmap_node *, jx9_hashmap_node *, void *); +/* +** Inputs: +** a: A sorted, null-terminated linked list. (May be null). +** b: A sorted, null-terminated linked list. (May be null). +** cmp: A pointer to the comparison function. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** of both a and b. +** +** Side effects: +** The "next", "prev" pointers for elements in the lists a and b are +** changed. +*/ +static jx9_hashmap_node * HashmapNodeMerge(jx9_hashmap_node *pA, jx9_hashmap_node *pB, ProcNodeCmp xCmp, void *pCmpData) +{ + jx9_hashmap_node result, *pTail; + /* Prevent compiler warning */ + result.pNext = result.pPrev = 0; + pTail = &result; + while( pA && pB ){ + if( xCmp(pA, pB, pCmpData) < 0 ){ + pTail->pPrev = pA; + pA->pNext = pTail; + pTail = pA; + pA = pA->pPrev; + }else{ + pTail->pPrev = pB; + pB->pNext = pTail; + pTail = pB; + pB = pB->pPrev; + } + } + if( pA ){ + pTail->pPrev = pA; + pA->pNext = pTail; + }else if( pB ){ + pTail->pPrev = pB; + pB->pNext = pTail; + }else{ + pTail->pPrev = pTail->pNext = 0; + } + return result.pPrev; +} +/* +** Inputs: +** Map: Input hashmap +** cmp: A comparison function. +** +** Return Value: +** Sorted hashmap. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define N_SORT_BUCKET 32 +static sxi32 HashmapMergeSort(jx9_hashmap *pMap, ProcNodeCmp xCmp, void *pCmpData) +{ + jx9_hashmap_node *a[N_SORT_BUCKET], *p, *pIn; + sxu32 i; + SyZero(a, sizeof(a)); + /* Point to the first inserted entry */ + pIn = pMap->pFirst; + while( pIn ){ + p = pIn; + pIn = p->pPrev; + p->pPrev = 0; + for(i=0; ipNext = 0; + /* Reflect the change */ + pMap->pFirst = p; + /* Reset the loop cursor */ + pMap->pCur = pMap->pFirst; + return SXRET_OK; +} +/* + * Node comparison callback. + * used-by: [sort(), asort(), ...] + */ +static sxi32 HashmapCmpCallback1(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) +{ + jx9_value sA, sB; + sxi32 iFlags; + int rc; + if( pCmpData == 0 ){ + /* Perform a standard comparison */ + rc = HashmapNodeCmp(pA, pB, FALSE); + return rc; + } + iFlags = SX_PTR_TO_INT(pCmpData); + /* Duplicate node values */ + jx9MemObjInit(pA->pMap->pVm, &sA); + jx9MemObjInit(pA->pMap->pVm, &sB); + jx9HashmapExtractNodeValue(pA, &sA, FALSE); + jx9HashmapExtractNodeValue(pB, &sB, FALSE); + if( iFlags == 5 ){ + /* String cast */ + if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sA); + } + if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sB); + } + }else{ + /* Numeric cast */ + jx9MemObjToNumeric(&sA); + jx9MemObjToNumeric(&sB); + } + /* Perform the comparison */ + rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); + jx9MemObjRelease(&sA); + jx9MemObjRelease(&sB); + return rc; +} +/* + * Node comparison callback. + * Used by: [rsort(), arsort()]; + */ +static sxi32 HashmapCmpCallback3(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) +{ + jx9_value sA, sB; + sxi32 iFlags; + int rc; + if( pCmpData == 0 ){ + /* Perform a standard comparison */ + rc = HashmapNodeCmp(pA, pB, FALSE); + return -rc; + } + iFlags = SX_PTR_TO_INT(pCmpData); + /* Duplicate node values */ + jx9MemObjInit(pA->pMap->pVm, &sA); + jx9MemObjInit(pA->pMap->pVm, &sB); + jx9HashmapExtractNodeValue(pA, &sA, FALSE); + jx9HashmapExtractNodeValue(pB, &sB, FALSE); + if( iFlags == 5 ){ + /* String cast */ + if( (sA.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sA); + } + if( (sB.iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(&sB); + } + }else{ + /* Numeric cast */ + jx9MemObjToNumeric(&sA); + jx9MemObjToNumeric(&sB); + } + /* Perform the comparison */ + rc = jx9MemObjCmp(&sA, &sB, FALSE, 0); + jx9MemObjRelease(&sA); + jx9MemObjRelease(&sB); + return -rc; +} +/* + * Node comparison callback: Invoke an user-defined callback for the purpose of node comparison. + * used-by: [usort(), uasort()] + */ +static sxi32 HashmapCmpCallback4(jx9_hashmap_node *pA, jx9_hashmap_node *pB, void *pCmpData) +{ + jx9_value sResult, *pCallback; + jx9_value *pV1, *pV2; + jx9_value *apArg[2]; /* Callback arguments */ + sxi32 rc; + /* Point to the desired callback */ + pCallback = (jx9_value *)pCmpData; + /* initialize the result value */ + jx9MemObjInit(pA->pMap->pVm, &sResult); + /* Extract nodes values */ + pV1 = HashmapExtractNodeValue(pA); + pV2 = HashmapExtractNodeValue(pB); + apArg[0] = pV1; + apArg[1] = pV2; + /* Invoke the callback */ + rc = jx9VmCallUserFunction(pA->pMap->pVm, pCallback, 2, apArg, &sResult); + if( rc != SXRET_OK ){ + /* An error occured while calling user defined function [i.e: not defined] */ + rc = -1; /* Set a dummy result */ + }else{ + /* Extract callback result */ + if((sResult.iFlags & MEMOBJ_INT) == 0 ){ + /* Perform an int cast */ + jx9MemObjToInteger(&sResult); + } + rc = (sxi32)sResult.x.iVal; + } + jx9MemObjRelease(&sResult); + /* Callback result */ + return rc; +} +/* + * Rehash all nodes keys after a merge-sort have been applied. + * Used by [sort(), usort() and rsort()]. + */ +static void HashmapSortRehash(jx9_hashmap *pMap) +{ + jx9_hashmap_node *p, *pLast; + sxu32 i; + /* Rehash all entries */ + pLast = p = pMap->pFirst; + pMap->iNextIdx = 0; /* Reset the automatic index */ + i = 0; + for( ;; ){ + if( i >= pMap->nEntry ){ + pMap->pLast = pLast; /* Fix the last link broken by the merge-sort */ + break; + } + if( p->iType == HASHMAP_BLOB_NODE ){ + /* Do not maintain index association as requested by the JX9 specification */ + SyBlobRelease(&p->xKey.sKey); + /* Change key type */ + p->iType = HASHMAP_INT_NODE; + } + HashmapRehashIntNode(p); + /* Point to the next entry */ + i++; + pLast = p; + p = p->pPrev; /* Reverse link */ + } +} +/* + * Array functions implementation. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * bool sort(array &$array[, int $sort_flags = SORT_REGULAR ] ) + * Sort an array. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + * + */ +static int jx9_hashmap_sort(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = jx9_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap, HashmapCmpCallback1, SX_INT_TO_PTR(iCmpFlags)); + /* Rehash [Do not maintain index association as requested by the JX9 specification] */ + HashmapSortRehash(pMap); + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * bool rsort(array &$array[, int $sort_flags = SORT_REGULAR ] ) + * Sort an array in reverse order. + * Parameters + * $array + * The input array. + * $sort_flags + * The optional second parameter sort_flags may be used to modify the sorting behavior using these values: + * Sorting type flags: + * SORT_REGULAR - compare items normally (don't change types) + * SORT_NUMERIC - compare items numerically + * SORT_STRING - compare items as strings + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9_hashmap_rsort(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + sxi32 iCmpFlags = 0; + if( nArg > 1 ){ + /* Extract comparison flags */ + iCmpFlags = jx9_value_to_int(apArg[1]); + if( iCmpFlags == 3 /* SORT_REGULAR */ ){ + iCmpFlags = 0; /* Standard comparison */ + } + } + /* Do the merge sort */ + HashmapMergeSort(pMap, HashmapCmpCallback3, SX_INT_TO_PTR(iCmpFlags)); + /* Rehash [Do not maintain index association as requested by the JX9 specification] */ + HashmapSortRehash(pMap); + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * bool usort(array &$array, callable $cmp_function) + * Sort an array by values using a user-defined comparison function. + * Parameters + * $array + * The input array. + * $cmp_function + * The comparison function must return an integer less than, equal to, or greater + * than zero if the first argument is considered to be respectively less than, equal + * to, or greater than the second. + * int callback ( mixed $a, mixed $b ) + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9_hashmap_usort(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + /* Make sure we are dealing with a valid hashmap */ + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry > 1 ){ + jx9_value *pCallback = 0; + ProcNodeCmp xCmp; + xCmp = HashmapCmpCallback4; /* User-defined function as the comparison callback */ + if( nArg > 1 && jx9_value_is_callable(apArg[1]) ){ + /* Point to the desired callback */ + pCallback = apArg[1]; + }else{ + /* Use the default comparison function */ + xCmp = HashmapCmpCallback1; + } + /* Do the merge sort */ + HashmapMergeSort(pMap, xCmp, pCallback); + /* Rehash [Do not maintain index association as requested by the JX9 specification] */ + HashmapSortRehash(pMap); + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * int count(array $var [, int $mode = COUNT_NORMAL ]) + * Count all elements in an array, or something in an object. + * Parameters + * $var + * The array or the object. + * $mode + * If the optional mode parameter is set to COUNT_RECURSIVE (or 1), count() + * will recursively count the array. This is particularly useful for counting + * all the elements of a multidimensional array. count() does not detect infinite + * recursion. + * Return + * Returns the number of elements in the array. + */ +static int jx9_hashmap_count(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int bRecursive = FALSE; + sxi64 iCount; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( !jx9_value_is_json_array(apArg[0]) ){ + /* TICKET 1433-19: Handle objects */ + int res = !jx9_value_is_null(apArg[0]); + jx9_result_int(pCtx, res); + return JX9_OK; + } + if( nArg > 1 ){ + /* Recursive count? */ + bRecursive = jx9_value_to_int(apArg[1]) == 1 /* COUNT_RECURSIVE */; + } + /* Count */ + iCount = HashmapCount((jx9_hashmap *)apArg[0]->x.pOther, bRecursive, 0); + jx9_result_int64(pCtx, iCount); + return JX9_OK; +} +/* + * bool array_key_exists(value $key, array $search) + * Checks if the given key or index exists in the array. + * Parameters + * $key + * Value to check. + * $search + * An array with keys to check. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9_hashmap_key_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + sxi32 rc; + if( nArg < 2 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[1]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the lookup */ + rc = jx9HashmapLookup((jx9_hashmap *)apArg[1]->x.pOther, apArg[0], 0); + /* lookup result */ + jx9_result_bool(pCtx, rc == SXRET_OK ? 1 : 0); + return JX9_OK; +} +/* + * value array_pop(array $array) + * POP the last inserted element from the array. + * Parameter + * The array to get the value from. + * Return + * Poped value or NULL on failure. + */ +static int jx9_hashmap_pop(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Noting to pop, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_hashmap_node *pLast = pMap->pLast; + jx9_value *pObj; + pObj = HashmapExtractNodeValue(pLast); + if( pObj ){ + /* Node value */ + jx9_result_value(pCtx, pObj); + /* Unlink the node */ + jx9HashmapUnlinkNode(pLast); + }else{ + jx9_result_null(pCtx); + } + /* Reset the cursor */ + pMap->pCur = pMap->pFirst; + } + return JX9_OK; +} +/* + * int array_push($array, $var, ...) + * Push one or more elements onto the end of array. (Stack insertion) + * Parameters + * array + * The input array. + * var + * On or more value to push. + * Return + * New array count (including old items). + */ +static int jx9_hashmap_push(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + sxi32 rc; + int i; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Start pushing given values */ + for( i = 1 ; i < nArg ; ++i ){ + rc = jx9HashmapInsert(pMap, 0, apArg[i]); + if( rc != SXRET_OK ){ + break; + } + } + /* Return the new count */ + jx9_result_int64(pCtx, (sxi64)pMap->nEntry); + return JX9_OK; +} +/* + * value array_shift(array $array) + * Shift an element off the beginning of array. + * Parameter + * The array to get the value from. + * Return + * Shifted value or NULL on failure. + */ +static int jx9_hashmap_shift(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Empty hashmap, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_hashmap_node *pEntry = pMap->pFirst; + jx9_value *pObj; + sxu32 n; + pObj = HashmapExtractNodeValue(pEntry); + if( pObj ){ + /* Node value */ + jx9_result_value(pCtx, pObj); + /* Unlink the first node */ + jx9HashmapUnlinkNode(pEntry); + }else{ + jx9_result_null(pCtx); + } + /* Rehash all int keys */ + n = pMap->nEntry; + pEntry = pMap->pFirst; + pMap->iNextIdx = 0; /* Reset the automatic index */ + for(;;){ + if( n < 1 ){ + break; + } + if( pEntry->iType == HASHMAP_INT_NODE ){ + HashmapRehashIntNode(pEntry); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Reset the cursor */ + pMap->pCur = pMap->pFirst; + } + return JX9_OK; +} +/* + * Extract the node cursor value. + */ +static sxi32 HashmapCurrentValue(jx9_context *pCtx, jx9_hashmap *pMap, int iDirection) +{ + jx9_hashmap_node *pCur = pMap->pCur; + jx9_value *pVal; + if( pCur == 0 ){ + /* Cursor does not point to anything, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( iDirection != 0 ){ + if( iDirection > 0 ){ + /* Point to the next entry */ + pMap->pCur = pCur->pPrev; /* Reverse link */ + pCur = pMap->pCur; + }else{ + /* Point to the previous entry */ + pMap->pCur = pCur->pNext; /* Reverse link */ + pCur = pMap->pCur; + } + if( pCur == 0 ){ + /* End of input reached, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + } + /* Point to the desired element */ + pVal = HashmapExtractNodeValue(pCur); + if( pVal ){ + jx9_result_value(pCtx, pVal); + }else{ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * value current(array $array) + * Return the current element in an array. + * Parameter + * $input: The input array. + * Return + * The current() function simply returns the value of the array element that's currently + * being pointed to by the internal pointer. It does not move the pointer in any way. + * If the internal pointer points beyond the end of the elements list or the array + * is empty, current() returns FALSE. + */ +static int jx9_hashmap_current(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 0); + return JX9_OK; +} +/* + * value next(array $input) + * Advance the internal array pointer of an array. + * Parameter + * $input: The input array. + * Return + * next() behaves like current(), with one difference. It advances the internal array + * pointer one place forward before returning the element value. That means it returns + * the next array value and advances the internal array pointer by one. + */ +static int jx9_hashmap_next(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, 1); + return JX9_OK; +} +/* + * value prev(array $input) + * Rewind the internal array pointer. + * Parameter + * $input: The input array. + * Return + * Returns the array value in the previous place that's pointed + * to by the internal array pointer, or FALSE if there are no more + * elements. + */ +static int jx9_hashmap_prev(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + HashmapCurrentValue(&(*pCtx), (jx9_hashmap *)apArg[0]->x.pOther, -1); + return JX9_OK; +} +/* + * value end(array $input) + * Set the internal pointer of an array to its last element. + * Parameter + * $input: The input array. + * Return + * Returns the value of the last element or FALSE for empty array. + */ +static int jx9_hashmap_end(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Point to the last node */ + pMap->pCur = pMap->pLast; + /* Return the last node value */ + HashmapCurrentValue(&(*pCtx), pMap, 0); + return JX9_OK; +} +/* + * value reset(array $array ) + * Set the internal pointer of an array to its first element. + * Parameter + * $input: The input array. + * Return + * Returns the value of the first array element, or FALSE if the array is empty. + */ +static int jx9_hashmap_reset(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Point to the first node */ + pMap->pCur = pMap->pFirst; + /* Return the last node value if available */ + HashmapCurrentValue(&(*pCtx), pMap, 0); + return JX9_OK; +} +/* + * value key(array $array) + * Fetch a key from an array + * Parameter + * $input + * The input array. + * Return + * The key() function simply returns the key of the array element that's currently + * being pointed to by the internal pointer. It does not move the pointer in any way. + * If the internal pointer points beyond the end of the elements list or the array + * is empty, key() returns NULL. + */ +static int jx9_hashmap_simple_key(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pCur; + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + pCur = pMap->pCur; + if( pCur == 0 ){ + /* Cursor does not point to anything, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( pCur->iType == HASHMAP_INT_NODE){ + /* Key is integer */ + jx9_result_int64(pCtx, pCur->xKey.iKey); + }else{ + /* Key is blob */ + jx9_result_string(pCtx, + (const char *)SyBlobData(&pCur->xKey.sKey), (int)SyBlobLength(&pCur->xKey.sKey)); + } + return JX9_OK; +} +/* + * array each(array $input) + * Return the current key and value pair from an array and advance the array cursor. + * Parameter + * $input + * The input array. + * Return + * Returns the current key and value pair from the array array. This pair is returned + * in a four-element array, with the keys 0, 1, key, and value. Elements 0 and key + * contain the key name of the array element, and 1 and value contain the data. + * If the internal pointer for the array points past the end of the array contents + * each() returns FALSE. + */ +static int jx9_hashmap_each(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pCur; + jx9_hashmap *pMap; + jx9_value *pArray; + jx9_value *pVal; + jx9_value sKey; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the internal representation that describe the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->pCur == 0 ){ + /* Cursor does not point to anything, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pCur = pMap->pCur; + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pVal = HashmapExtractNodeValue(pCur); + /* Insert the current value */ + jx9_array_add_strkey_elem(pArray, "1", pVal); + jx9_array_add_strkey_elem(pArray, "value", pVal); + /* Make the key */ + if( pCur->iType == HASHMAP_INT_NODE ){ + jx9MemObjInitFromInt(pMap->pVm, &sKey, pCur->xKey.iKey); + }else{ + jx9MemObjInitFromString(pMap->pVm, &sKey, 0); + jx9MemObjStringAppend(&sKey, (const char *)SyBlobData(&pCur->xKey.sKey), SyBlobLength(&pCur->xKey.sKey)); + } + /* Insert the current key */ + jx9_array_add_elem(pArray, 0, &sKey); + jx9_array_add_strkey_elem(pArray, "key", &sKey); + jx9MemObjRelease(&sKey); + /* Advance the cursor */ + pMap->pCur = pCur->pPrev; /* Reverse link */ + /* Return the current entry */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * array array_values(array $input) + * Returns all the values from the input array and indexes numerically the array. + * Parameters + * input: The input array. + * Return + * An indexed array of values or NULL on failure. + */ +static int jx9_hashmap_values(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pNode; + jx9_hashmap *pMap; + jx9_value *pArray; + jx9_value *pObj; + sxu32 n; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation that describe the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Perform the requested operation */ + pNode = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; ++n ){ + pObj = HashmapExtractNodeValue(pNode); + if( pObj ){ + /* perform the insertion */ + jx9_array_add_elem(pArray, 0/* Automatic index assign */, pObj); + } + /* Point to the next entry */ + pNode = pNode->pPrev; /* Reverse link */ + } + /* return the new array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * bool array_same(array $arr1, array $arr2) + * Return TRUE if the given arrays are the same instance. + * This function is useful under JX9 since arrays and objects + * are passed by reference. + * Parameters + * $arr1 + * First array + * $arr2 + * Second array + * Return + * TRUE if the arrays are the same instance. FALSE otherwise. + * Note + * This function is a symisc eXtension. + */ +static int jx9_hashmap_same(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *p1, *p2; + int rc; + if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ + /* Missing or invalid arguments, return FALSE*/ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the hashmaps */ + p1 = (jx9_hashmap *)apArg[0]->x.pOther; + p2 = (jx9_hashmap *)apArg[1]->x.pOther; + rc = (p1 == p2); + /* Same instance? */ + jx9_result_bool(pCtx, rc); + return JX9_OK; +} +/* + * array array_merge(array $array1, ...) + * Merge one or more arrays. + * Parameters + * $array1 + * Initial array to merge. + * ... + * More array to merge. + * Return + * The resulting array. + */ +static int jx9_hashmap_merge(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap, *pSrc; + jx9_value *pArray; + int i; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)pArray->x.pOther; + /* Start merging */ + for( i = 0 ; i < nArg ; i++ ){ + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[i]) ){ + /* Insert scalar value */ + jx9_array_add_elem(pArray, 0, apArg[i]); + }else{ + pSrc = (jx9_hashmap *)apArg[i]->x.pOther; + /* Merge the two hashmaps */ + HashmapMerge(pSrc, pMap); + } + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * bool in_array(value $needle, array $haystack[, bool $strict = FALSE ]) + * Checks if a value exists in an array. + * Parameters + * $needle + * The searched value. + * Note: + * If needle is a string, the comparison is done in a case-sensitive manner. + * $haystack + * The target array. + * $strict + * If the third parameter strict is set to TRUE then the in_array() function + * will also check the types of the needle in the haystack. + */ +static int jx9_hashmap_in_array(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pNeedle; + int bStrict; + int rc; + if( nArg < 2 ){ + /* Missing argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pNeedle = apArg[0]; + bStrict = 0; + if( nArg > 2 ){ + bStrict = jx9_value_to_bool(apArg[2]); + } + if( !jx9_value_is_json_array(apArg[1]) ){ + /* haystack must be an array, perform a standard comparison */ + rc = jx9_value_compare(pNeedle, apArg[1], bStrict); + /* Set the comparison result */ + jx9_result_bool(pCtx, rc == 0); + return JX9_OK; + } + /* Perform the lookup */ + rc = HashmapFindValue((jx9_hashmap *)apArg[1]->x.pOther, pNeedle, 0, bStrict); + /* Lookup result */ + jx9_result_bool(pCtx, rc == SXRET_OK); + return JX9_OK; +} +/* + * array array_copy(array $source) + * Make a blind copy of the target array. + * Parameters + * $source + * Target array + * Return + * Copy of the target array on success. NULL otherwise. + * Note + * This function is a symisc eXtension. + */ +static int jx9_hashmap_copy(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + jx9_value *pArray; + if( nArg < 1 ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)pArray->x.pOther; + if( jx9_value_is_json_array(apArg[0])){ + /* Point to the internal representation of the source */ + jx9_hashmap *pSrc = (jx9_hashmap *)apArg[0]->x.pOther; + /* Perform the copy */ + jx9HashmapDup(pSrc, pMap); + }else{ + /* Simple insertion */ + jx9HashmapInsert(pMap, 0/* Automatic index assign*/, apArg[0]); + } + /* Return the duplicated array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * bool array_erase(array $source) + * Remove all elements from a given array. + * Parameters + * $source + * Target array + * Return + * TRUE on success. FALSE otherwise. + * Note + * This function is a symisc eXtension. + */ +static int jx9_hashmap_erase(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + if( nArg < 1 ){ + /* Missing arguments */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + /* Erase */ + jx9HashmapRelease(pMap, FALSE); + return JX9_OK; +} +/* + * array array_diff(array $array1, array $array2, ...) + * Computes the difference of arrays. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an array containing all the entries from array1 that + * are not present in any of the other arrays. + */ +static int jx9_hashmap_diff(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pEntry; + jx9_hashmap *pSrc, *pMap; + jx9_value *pArray; + jx9_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + jx9_result_value(pCtx, apArg[0]); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (jx9_hashmap *)apArg[0]->x.pOther; + /* Perform the diff */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + for( i = 1 ; i < nArg ; i++ ){ + if( !jx9_value_is_json_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)apArg[i]->x.pOther; + /* Perform the lookup */ + rc = HashmapFindValue(pMap, pVal, 0, TRUE); + if( rc == SXRET_OK ){ + /* Value exist */ + break; + } + } + if( i >= nArg ){ + /* Perform the insertion */ + HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * array array_intersect(array $array1 , array $array2, ...) + * Computes the intersection of arrays. + * Parameters + * $array1 + * The array to compare from + * $array2 + * An array to compare against + * $... + * More arrays to compare against + * Return + * Returns an array containing all of the values in array1 whose values exist + * in all of the parameters. . + * Note that NULL is returned on failure. + */ +static int jx9_hashmap_intersect(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap_node *pEntry; + jx9_hashmap *pSrc, *pMap; + jx9_value *pArray; + jx9_value *pVal; + sxi32 rc; + sxu32 n; + int i; + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + if( nArg == 1 ){ + /* Return the first array since we cannot perform a diff */ + jx9_result_value(pCtx, apArg[0]); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the source hashmap */ + pSrc = (jx9_hashmap *)apArg[0]->x.pOther; + /* Perform the intersection */ + pEntry = pSrc->pFirst; + n = pSrc->nEntry; + for(;;){ + if( n < 1 ){ + break; + } + /* Extract the node value */ + pVal = HashmapExtractNodeValue(pEntry); + if( pVal ){ + for( i = 1 ; i < nArg ; i++ ){ + if( !jx9_value_is_json_array(apArg[i])) { + /* ignore */ + continue; + } + /* Point to the internal representation of the hashmap */ + pMap = (jx9_hashmap *)apArg[i]->x.pOther; + /* Perform the lookup */ + rc = HashmapFindValue(pMap, pVal, 0, TRUE); + if( rc != SXRET_OK ){ + /* Value does not exist */ + break; + } + } + if( i >= nArg ){ + /* Perform the insertion */ + HashmapInsertNode((jx9_hashmap *)pArray->x.pOther, pEntry, TRUE); + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * number array_sum(array $array ) + * Calculate the sum of values in an array. + * Parameters + * $array: The input array. + * Return + * Returns the sum of values as an integer or float. + */ +static void DoubleSum(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_value *pObj; + double dSum = 0; + sxu32 n; + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + dSum += pObj->x.rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + dSum += (double)pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + double dv = 0; + SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); + dSum += dv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return sum */ + jx9_result_double(pCtx, dSum); +} +static void Int64Sum(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_value *pObj; + sxi64 nSum = 0; + sxu32 n; + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + nSum += (sxi64)pObj->x.rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + nSum += pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + sxi64 nv = 0; + SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); + nSum += nv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return sum */ + jx9_result_int64(pCtx, nSum); +} +/* number array_sum(array $array ) + * (See block-coment above) + */ +static int jx9_hashmap_sum(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + jx9_value *pObj; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Nothing to compute, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* If the first element is of type float, then perform floating + * point computaion.Otherwise switch to int64 computaion. + */ + pObj = HashmapExtractNodeValue(pMap->pFirst); + if( pObj == 0 ){ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( pObj->iFlags & MEMOBJ_REAL ){ + DoubleSum(pCtx, pMap); + }else{ + Int64Sum(pCtx, pMap); + } + return JX9_OK; +} +/* + * number array_product(array $array ) + * Calculate the product of values in an array. + * Parameters + * $array: The input array. + * Return + * Returns the product of values as an integer or float. + */ +static void DoubleProd(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_value *pObj; + double dProd; + sxu32 n; + pEntry = pMap->pFirst; + dProd = 1; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + dProd *= pObj->x.rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + dProd *= (double)pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + double dv = 0; + SyStrToReal((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&dv, 0); + dProd *= dv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return product */ + jx9_result_double(pCtx, dProd); +} +static void Int64Prod(jx9_context *pCtx, jx9_hashmap *pMap) +{ + jx9_hashmap_node *pEntry; + jx9_value *pObj; + sxi64 nProd; + sxu32 n; + pEntry = pMap->pFirst; + nProd = 1; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + pObj = HashmapExtractNodeValue(pEntry); + if( pObj && (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0){ + if( pObj->iFlags & MEMOBJ_REAL ){ + nProd *= (sxi64)pObj->x.rVal; + }else if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + nProd *= pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) > 0 ){ + sxi64 nv = 0; + SyStrToInt64((const char *)SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob), (void *)&nv, 0); + nProd *= nv; + } + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* Return product */ + jx9_result_int64(pCtx, nProd); +} +/* number array_product(array $array ) + * (See block-block comment above) + */ +static int jx9_hashmap_product(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_hashmap *pMap; + jx9_value *pObj; + if( nArg < 1 ){ + /* Missing arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid hashmap */ + if( !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid argument, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Nothing to compute, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* If the first element is of type float, then perform floating + * point computaion.Otherwise switch to int64 computaion. + */ + pObj = HashmapExtractNodeValue(pMap->pFirst); + if( pObj == 0 ){ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + if( pObj->iFlags & MEMOBJ_REAL ){ + DoubleProd(pCtx, pMap); + }else{ + Int64Prod(pCtx, pMap); + } + return JX9_OK; +} +/* + * array array_map(callback $callback, array $arr1) + * Applies the callback to the elements of the given arrays. + * Parameters + * $callback + * Callback function to run for each element in each array. + * $arr1 + * An array to run through the callback function. + * Return + * Returns an array containing all the elements of arr1 after applying + * the callback function to each one. + * NOTE: + * array_map() passes only a single value to the callback. + */ +static int jx9_hashmap_map(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray, *pValue, sKey, sResult; + jx9_hashmap_node *pEntry; + jx9_hashmap *pMap; + sxu32 n; + if( nArg < 2 || !jx9_value_is_json_array(apArg[1]) ){ + /* Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[1]->x.pOther; + jx9MemObjInit(pMap->pVm, &sResult); + jx9MemObjInit(pMap->pVm, &sKey); + /* Perform the requested operation */ + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extrcat the node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + sxi32 rc; + /* Invoke the supplied callback */ + rc = jx9VmCallUserFunction(pMap->pVm, apArg[0], 1, &pValue, &sResult); + /* Extract the node key */ + jx9HashmapExtractNodeKey(pEntry, &sKey); + if( rc != SXRET_OK ){ + /* An error occured while invoking the supplied callback [i.e: not defined] */ + jx9_array_add_elem(pArray, &sKey, pValue); /* Keep the same value */ + }else{ + /* Insert the callback return value */ + jx9_array_add_elem(pArray, &sKey, &sResult); + } + jx9MemObjRelease(&sKey); + jx9MemObjRelease(&sResult); + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * bool array_walk(array &$array, callback $funcname [, value $userdata ] ) + * Apply a user function to every member of an array. + * Parameters + * $array + * The input array. + * $funcname + * Typically, funcname takes on two parameters.The array parameter's value being + * the first, and the key/index second. + * Note: + * If funcname needs to be working with the actual values of the array, specify the first + * parameter of funcname as a reference. Then, any changes made to those elements will + * be made in the original array itself. + * $userdata + * If the optional userdata parameter is supplied, it will be passed as the third parameter + * to the callback funcname. + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int jx9_hashmap_walk(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pValue, *pUserData, sKey; + jx9_hashmap_node *pEntry; + jx9_hashmap *pMap; + sxi32 rc; + sxu32 n; + if( nArg < 2 || !jx9_value_is_json_array(apArg[0]) ){ + /* Invalid/Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pUserData = nArg > 2 ? apArg[2] : 0; + /* Point to the internal representation of the input hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + jx9MemObjInit(pMap->pVm, &sKey); + /* Perform the desired operation */ + pEntry = pMap->pFirst; + for( n = 0 ; n < pMap->nEntry ; n++ ){ + /* Extract the node value */ + pValue = HashmapExtractNodeValue(pEntry); + if( pValue ){ + /* Extract the entry key */ + jx9HashmapExtractNodeKey(pEntry, &sKey); + /* Invoke the supplied callback */ + rc = jx9VmCallUserFunctionAp(pMap->pVm, apArg[1], 0, pValue, &sKey, pUserData, 0); + jx9MemObjRelease(&sKey); + if( rc != SXRET_OK ){ + /* An error occured while invoking the supplied callback [i.e: not defined] */ + jx9_result_bool(pCtx, 0); /* return FALSE */ + return JX9_OK; + } + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + } + /* All done, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * Table of built-in hashmap functions. + */ +static const jx9_builtin_func aHashmapFunc[] = { + {"count", jx9_hashmap_count }, + {"sizeof", jx9_hashmap_count }, + {"array_key_exists", jx9_hashmap_key_exists }, + {"array_pop", jx9_hashmap_pop }, + {"array_push", jx9_hashmap_push }, + {"array_shift", jx9_hashmap_shift }, + {"array_product", jx9_hashmap_product }, + {"array_sum", jx9_hashmap_sum }, + {"array_values", jx9_hashmap_values }, + {"array_same", jx9_hashmap_same }, + {"array_merge", jx9_hashmap_merge }, + {"array_diff", jx9_hashmap_diff }, + {"array_intersect", jx9_hashmap_intersect}, + {"in_array", jx9_hashmap_in_array }, + {"array_copy", jx9_hashmap_copy }, + {"array_erase", jx9_hashmap_erase }, + {"array_map", jx9_hashmap_map }, + {"array_walk", jx9_hashmap_walk }, + {"sort", jx9_hashmap_sort }, + {"rsort", jx9_hashmap_rsort }, + {"usort", jx9_hashmap_usort }, + {"current", jx9_hashmap_current }, + {"each", jx9_hashmap_each }, + {"pos", jx9_hashmap_current }, + {"next", jx9_hashmap_next }, + {"prev", jx9_hashmap_prev }, + {"end", jx9_hashmap_end }, + {"reset", jx9_hashmap_reset }, + {"key", jx9_hashmap_simple_key } +}; +/* + * Register the built-in hashmap functions defined above. + */ +JX9_PRIVATE void jx9RegisterHashmapFunctions(jx9_vm *pVm) +{ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aHashmapFunc) ; n++ ){ + jx9_create_function(&(*pVm), aHashmapFunc[n].zName, aHashmapFunc[n].xFunc, 0); + } +} +/* + * Iterate throw hashmap entries and invoke the given callback [i.e: xWalk()] for each + * retrieved entry. + * Note that argument are passed to the callback by copy. That is, any modification to + * the entry value in the callback body will not alter the real value. + * If the callback wishes to abort processing [i.e: it's invocation] it must return + * a value different from JX9_OK. + * Refer to [jx9_array_walk()] for more information. + */ +JX9_PRIVATE sxi32 jx9HashmapWalk( + jx9_hashmap *pMap, /* Target hashmap */ + int (*xWalk)(jx9_value *, jx9_value *, void *), /* Walker callback */ + void *pUserData /* Last argument to xWalk() */ + ) +{ + jx9_hashmap_node *pEntry; + jx9_value sKey, sValue; + sxi32 rc; + sxu32 n; + /* Initialize walker parameter */ + rc = SXRET_OK; + jx9MemObjInit(pMap->pVm, &sKey); + jx9MemObjInit(pMap->pVm, &sValue); + n = pMap->nEntry; + pEntry = pMap->pFirst; + /* Start the iteration process */ + for(;;){ + if( n < 1 ){ + break; + } + /* Extract a copy of the key and a copy the current value */ + jx9HashmapExtractNodeKey(pEntry, &sKey); + jx9HashmapExtractNodeValue(pEntry, &sValue, FALSE); + /* Invoke the user callback */ + rc = xWalk(&sKey, &sValue, pUserData); + /* Release the copy of the key and the value */ + jx9MemObjRelease(&sKey); + jx9MemObjRelease(&sValue); + if( rc != JX9_OK ){ + /* Callback request an operation abort */ + return SXERR_ABORT; + } + /* Point to the next entry */ + pEntry = pEntry->pPrev; /* Reverse link */ + n--; + } + /* All done */ + return SXRET_OK; +} +/* + * ---------------------------------------------------------- + * File: jx9_json.c + * MD5: 31a27f8797418de511c669feed763341 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: json.c v1.0 FreeBSD 2012-12-16 00:28 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file deals with JSON serialization, decoding and stuff like that. */ +/* + * Section: + * JSON encoding/decoding routines. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Devel. + */ +/* Forward reference */ +static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); +static int VmJsonObjectEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData); +/* + * JSON encoder state is stored in an instance + * of the following structure. + */ +typedef struct json_private_data json_private_data; +struct json_private_data +{ + SyBlob *pOut; /* Output consumer buffer */ + int isFirst; /* True if first encoded entry */ + int iFlags; /* JSON encoding flags */ + int nRecCount; /* Recursion count */ +}; +/* + * Returns the JSON representation of a value.In other word perform a JSON encoding operation. + * According to wikipedia + * JSON's basic types are: + * Number (double precision floating-point format in JavaScript, generally depends on implementation) + * String (double-quoted Unicode, with backslash escaping) + * Boolean (true or false) + * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values + * do not need to be of the same type) + * Object (an unordered collection of key:value pairs with the ':' character separating the key + * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should + * be distinct from each other) + * null (empty) + * Non-significant white space may be added freely around the "structural characters" + * (i.e. the brackets "[{]}", colon ":" and comma ","). + */ +static sxi32 VmJsonEncode( + jx9_value *pIn, /* Encode this value */ + json_private_data *pData /* Context data */ + ){ + SyBlob *pOut = pData->pOut; + int nByte; + if( jx9_value_is_null(pIn) || jx9_value_is_resource(pIn)){ + /* null */ + SyBlobAppend(pOut, "null", sizeof("null")-1); + }else if( jx9_value_is_bool(pIn) ){ + int iBool = jx9_value_to_bool(pIn); + sxu32 iLen; + /* true/false */ + iLen = iBool ? sizeof("true") : sizeof("false"); + SyBlobAppend(pOut, iBool ? "true" : "false", iLen-1); + }else if( jx9_value_is_numeric(pIn) && !jx9_value_is_string(pIn) ){ + const char *zNum; + /* Get a string representation of the number */ + zNum = jx9_value_to_string(pIn, &nByte); + SyBlobAppend(pOut,zNum,nByte); + }else if( jx9_value_is_string(pIn) ){ + const char *zIn, *zEnd; + int c; + /* Encode the string */ + zIn = jx9_value_to_string(pIn, &nByte); + zEnd = &zIn[nByte]; + /* Append the double quote */ + SyBlobAppend(pOut,"\"", sizeof(char)); + for(;;){ + if( zIn >= zEnd ){ + /* No more input to process */ + break; + } + c = zIn[0]; + /* Advance the stream cursor */ + zIn++; + if( c == '"' || c == '\\' ){ + /* Unescape the character */ + SyBlobAppend(pOut,"\\", sizeof(char)); + } + /* Append character verbatim */ + SyBlobAppend(pOut,(const char *)&c,sizeof(char)); + } + /* Append the double quote */ + SyBlobAppend(pOut,"\"",sizeof(char)); + }else if( jx9_value_is_json_array(pIn) ){ + /* Encode the array/object */ + pData->isFirst = 1; + if( jx9_value_is_json_object(pIn) ){ + /* Encode the object instance */ + pData->isFirst = 1; + /* Append the curly braces */ + SyBlobAppend(pOut, "{",sizeof(char)); + /* Iterate throw object attribute */ + jx9_array_walk(pIn,VmJsonObjectEncode, pData); + /* Append the closing curly braces */ + SyBlobAppend(pOut, "}",sizeof(char)); + }else{ + /* Append the square bracket or curly braces */ + SyBlobAppend(pOut,"[",sizeof(char)); + /* Iterate throw array entries */ + jx9_array_walk(pIn, VmJsonArrayEncode, pData); + /* Append the closing square bracket or curly braces */ + SyBlobAppend(pOut,"]",sizeof(char)); + } + }else{ + /* Can't happen */ + SyBlobAppend(pOut,"null",sizeof("null")-1); + } + /* All done */ + return JX9_OK; +} +/* + * The following walker callback is invoked each time we need + * to encode an array to JSON. + */ +static int VmJsonArrayEncode(jx9_value *pKey, jx9_value *pValue, void *pUserData) +{ + json_private_data *pJson = (json_private_data *)pUserData; + if( pJson->nRecCount > 31 ){ + /* Recursion limit reached, return immediately */ + SXUNUSED(pKey); /* cc warning */ + return JX9_OK; + } + if( !pJson->isFirst ){ + /* Append the colon first */ + SyBlobAppend(pJson->pOut,",",(int)sizeof(char)); + } + /* Encode the value */ + pJson->nRecCount++; + VmJsonEncode(pValue, pJson); + pJson->nRecCount--; + pJson->isFirst = 0; + return JX9_OK; +} +/* + * The following walker callback is invoked each time we need to encode + * a object instance [i.e: Object in the JX9 jargon] to JSON. + */ +static int VmJsonObjectEncode(jx9_value *pKey,jx9_value *pValue,void *pUserData) +{ + json_private_data *pJson = (json_private_data *)pUserData; + const char *zKey; + int nByte; + if( pJson->nRecCount > 31 ){ + /* Recursion limit reached, return immediately */ + return JX9_OK; + } + if( !pJson->isFirst ){ + /* Append the colon first */ + SyBlobAppend(pJson->pOut,",",sizeof(char)); + } + /* Extract a string representation of the key */ + zKey = jx9_value_to_string(pKey, &nByte); + /* Append the key and the double colon */ + if( nByte > 0 ){ + SyBlobAppend(pJson->pOut,"\"",sizeof(char)); + SyBlobAppend(pJson->pOut,zKey,(sxu32)nByte); + SyBlobAppend(pJson->pOut,"\"",sizeof(char)); + }else{ + /* Can't happen */ + SyBlobAppend(pJson->pOut,"null",sizeof("null")-1); + } + SyBlobAppend(pJson->pOut,":",sizeof(char)); + /* Encode the value */ + pJson->nRecCount++; + VmJsonEncode(pValue, pJson); + pJson->nRecCount--; + pJson->isFirst = 0; + return JX9_OK; +} +/* + * Returns a string containing the JSON representation of value. + * In other words, perform the serialization of the given JSON object. + */ +JX9_PRIVATE int jx9JsonSerialize(jx9_value *pValue,SyBlob *pOut) +{ + json_private_data sJson; + /* Prepare the JSON data */ + sJson.nRecCount = 0; + sJson.pOut = pOut; + sJson.isFirst = 1; + sJson.iFlags = 0; + /* Perform the encoding operation */ + VmJsonEncode(pValue, &sJson); + /* All done */ + return JX9_OK; +} +/* Possible tokens from the JSON tokenization process */ +#define JSON_TK_TRUE 0x001 /* Boolean true */ +#define JSON_TK_FALSE 0x002 /* Boolean false */ +#define JSON_TK_STR 0x004 /* String enclosed in double quotes */ +#define JSON_TK_NULL 0x008 /* null */ +#define JSON_TK_NUM 0x010 /* Numeric */ +#define JSON_TK_OCB 0x020 /* Open curly braces '{' */ +#define JSON_TK_CCB 0x040 /* Closing curly braces '}' */ +#define JSON_TK_OSB 0x080 /* Open square bracke '[' */ +#define JSON_TK_CSB 0x100 /* Closing square bracket ']' */ +#define JSON_TK_COLON 0x200 /* Single colon ':' */ +#define JSON_TK_COMMA 0x400 /* Single comma ',' */ +#define JSON_TK_ID 0x800 /* ID */ +#define JSON_TK_INVALID 0x1000 /* Unexpected token */ +/* + * Tokenize an entire JSON input. + * Get a single low-level token from the input file. + * Update the stream pointer so that it points to the first + * character beyond the extracted token. + */ +static sxi32 VmJsonTokenize(SyStream *pStream, SyToken *pToken, void *pUserData, void *pCtxData) +{ + int *pJsonErr = (int *)pUserData; + SyString *pStr; + int c; + /* Ignore leading white spaces */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ + /* Advance the stream cursor */ + if( pStream->zText[0] == '\n' ){ + /* Update line counter */ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + /* End of input reached */ + SXUNUSED(pCtxData); /* cc warning */ + return SXERR_EOF; + } + /* Record token starting position and line */ + pToken->nLine = pStream->nLine; + pToken->pUserData = 0; + pStr = &pToken->sData; + SyStringInitFromBuf(pStr, pStream->zText, 0); + if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ + /* The following code fragment is taken verbatim from the xPP source tree. + * xPP is a modern embeddable macro processor with advanced features useful for + * application seeking for a production quality, ready to use macro processor. + * xPP is a widely used library developed and maintened by Symisc Systems. + * You can reach the xPP home page by following this link: + * http://xpp.symisc.net/ + */ + const unsigned char *zIn; + /* Isolate UTF-8 or alphanumeric stream */ + if( pStream->zText[0] < 0xc0 ){ + pStream->zText++; + } + for(;;){ + zIn = pStream->zText; + if( zIn[0] >= 0xc0 ){ + zIn++; + /* UTF-8 stream */ + while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ + zIn++; + } + } + /* Skip alphanumeric stream */ + while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ + zIn++; + } + if( zIn == pStream->zText ){ + /* Not an UTF-8 or alphanumeric stream */ + break; + } + /* Synchronize pointers */ + pStream->zText = zIn; + } + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + /* A simple identifier */ + pToken->nType = JSON_TK_ID; + if( pStr->nByte == sizeof("true") -1 && SyStrnicmp(pStr->zString, "true", sizeof("true")-1) == 0 ){ + /* boolean true */ + pToken->nType = JSON_TK_TRUE; + }else if( pStr->nByte == sizeof("false") -1 && SyStrnicmp(pStr->zString,"false", sizeof("false")-1) == 0 ){ + /* boolean false */ + pToken->nType = JSON_TK_FALSE; + }else if( pStr->nByte == sizeof("null") -1 && SyStrnicmp(pStr->zString,"null", sizeof("null")-1) == 0 ){ + /* NULL */ + pToken->nType = JSON_TK_NULL; + } + return SXRET_OK; + } + if( pStream->zText[0] == '{' || pStream->zText[0] == '[' || pStream->zText[0] == '}' || pStream->zText[0] == ']' + || pStream->zText[0] == ':' || pStream->zText[0] == ',' ){ + /* Single character */ + c = pStream->zText[0]; + /* Set token type */ + switch(c){ + case '[': pToken->nType = JSON_TK_OSB; break; + case '{': pToken->nType = JSON_TK_OCB; break; + case '}': pToken->nType = JSON_TK_CCB; break; + case ']': pToken->nType = JSON_TK_CSB; break; + case ':': pToken->nType = JSON_TK_COLON; break; + case ',': pToken->nType = JSON_TK_COMMA; break; + default: + break; + } + /* Advance the stream cursor */ + pStream->zText++; + }else if( pStream->zText[0] == '"') { + /* JSON string */ + pStream->zText++; + pStr->zString++; + /* Delimit the string */ + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '"' && pStream->zText[-1] != '\\' ){ + break; + } + if( pStream->zText[0] == '\n' ){ + /* Update line counter */ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + /* Missing closing '"' */ + pToken->nType = JSON_TK_INVALID; + *pJsonErr = SXERR_SYNTAX; + }else{ + pToken->nType = JSON_TK_STR; + pStream->zText++; /* Jump the closing double quotes */ + } + }else if( pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + /* Number */ + pStream->zText++; + pToken->nType = JSON_TK_NUM; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c == '.' ){ + /* Real number */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c=='e' || c=='E' ){ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c =='+' || c=='-' ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + } + } + }else if( c=='e' || c=='E' ){ + /* Real number */ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c =='+' || c=='-' ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + } + } + }else{ + /* Unexpected token */ + pToken->nType = JSON_TK_INVALID; + /* Advance the stream cursor */ + pStream->zText++; + *pJsonErr = SXERR_SYNTAX; + /* Abort processing immediatley */ + return SXERR_ABORT; + } + /* record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + if( pToken->nType == JSON_TK_STR ){ + pStr->nByte--; + } + /* Return to the lexer */ + return SXRET_OK; +} +/* + * JSON decoded input consumer callback signature. + */ +typedef int (*ProcJSONConsumer)(jx9_context *, jx9_value *, jx9_value *, void *); +/* + * JSON decoder state is kept in the following structure. + */ +typedef struct json_decoder json_decoder; +struct json_decoder +{ + jx9_context *pCtx; /* Call context */ + ProcJSONConsumer xConsumer; /* Consumer callback */ + void *pUserData; /* Last argument to xConsumer() */ + int iFlags; /* Configuration flags */ + SyToken *pIn; /* Token stream */ + SyToken *pEnd; /* End of the token stream */ + int rec_count; /* Current nesting level */ + int *pErr; /* JSON decoding error if any */ +}; +/* Forward declaration */ +static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData); +/* + * Dequote [i.e: Resolve all backslash escapes ] a JSON string and store + * the result in the given jx9_value. + */ +static void VmJsonDequoteString(const SyString *pStr, jx9_value *pWorker) +{ + const char *zIn = pStr->zString; + const char *zEnd = &pStr->zString[pStr->nByte]; + const char *zCur; + int c; + /* Mark the value as a string */ + jx9_value_string(pWorker, "", 0); /* Empty string */ + for(;;){ + zCur = zIn; + while( zIn < zEnd && zIn[0] != '\\' ){ + zIn++; + } + if( zIn > zCur ){ + /* Append chunk verbatim */ + jx9_value_string(pWorker, zCur, (int)(zIn-zCur)); + } + zIn++; + if( zIn >= zEnd ){ + /* End of the input reached */ + break; + } + c = zIn[0]; + /* Unescape the character */ + switch(c){ + case '"': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; + case '\\': jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); break; + case 'n': jx9_value_string(pWorker, "\n", (int)sizeof(char)); break; + case 'r': jx9_value_string(pWorker, "\r", (int)sizeof(char)); break; + case 't': jx9_value_string(pWorker, "\t", (int)sizeof(char)); break; + case 'f': jx9_value_string(pWorker, "\f", (int)sizeof(char)); break; + default: + jx9_value_string(pWorker, (const char *)&c, (int)sizeof(char)); + break; + } + /* Advance the stream cursor */ + zIn++; + } +} +/* + * Returns a jx9_value holding the image of a JSON string. In other word perform a JSON decoding operation. + * According to wikipedia + * JSON's basic types are: + * Number (double precision floating-point format in JavaScript, generally depends on implementation) + * String (double-quoted Unicode, with backslash escaping) + * Boolean (true or false) + * Array (an ordered sequence of values, comma-separated and enclosed in square brackets; the values + * do not need to be of the same type) + * Object (an unordered collection of key:value pairs with the ':' character separating the key + * and the value, comma-separated and enclosed in curly braces; the keys must be strings and should + * be distinct from each other) + * null (empty) + * Non-significant white space may be added freely around the "structural characters" (i.e. the brackets "[{]}", colon ":" and comma ", "). + */ +static sxi32 VmJsonDecode( + json_decoder *pDecoder, /* JSON decoder */ + jx9_value *pArrayKey /* Key for the decoded array */ + ){ + jx9_value *pWorker; /* Worker variable */ + sxi32 rc; + /* Check if we do not nest to much */ + if( pDecoder->rec_count > 31 ){ + /* Nesting limit reached, abort decoding immediately */ + return SXERR_ABORT; + } + if( pDecoder->pIn->nType & (JSON_TK_STR|JSON_TK_ID|JSON_TK_TRUE|JSON_TK_FALSE|JSON_TK_NULL|JSON_TK_NUM) ){ + /* Scalar value */ + pWorker = jx9_context_new_scalar(pDecoder->pCtx); + if( pWorker == 0 ){ + jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + /* Abort the decoding operation immediately */ + return SXERR_ABORT; + } + /* Reflect the JSON image */ + if( pDecoder->pIn->nType & JSON_TK_NULL ){ + /* Nullify the value.*/ + jx9_value_null(pWorker); + }else if( pDecoder->pIn->nType & (JSON_TK_TRUE|JSON_TK_FALSE) ){ + /* Boolean value */ + jx9_value_bool(pWorker, (pDecoder->pIn->nType & JSON_TK_TRUE) ? 1 : 0 ); + }else if( pDecoder->pIn->nType & JSON_TK_NUM ){ + SyString *pStr = &pDecoder->pIn->sData; + /* + * Numeric value. + * Get a string representation first then try to get a numeric + * value. + */ + jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); + /* Obtain a numeric representation */ + jx9MemObjToNumeric(pWorker); + }else if( pDecoder->pIn->nType & JSON_TK_ID ){ + SyString *pStr = &pDecoder->pIn->sData; + jx9_value_string(pWorker, pStr->zString, (int)pStr->nByte); + }else{ + /* Dequote the string */ + VmJsonDequoteString(&pDecoder->pIn->sData, pWorker); + } + /* Invoke the consumer callback */ + rc = pDecoder->xConsumer(pDecoder->pCtx, pArrayKey, pWorker, pDecoder->pUserData); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + /* All done, advance the stream cursor */ + pDecoder->pIn++; + }else if( pDecoder->pIn->nType & JSON_TK_OSB /*'[' */) { + ProcJSONConsumer xOld; + void *pOld; + /* Array representation*/ + pDecoder->pIn++; + /* Create a working array */ + pWorker = jx9_context_new_array(pDecoder->pCtx); + if( pWorker == 0 ){ + jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + /* Abort the decoding operation immediately */ + return SXERR_ABORT; + } + /* Save the old consumer */ + xOld = pDecoder->xConsumer; + pOld = pDecoder->pUserData; + /* Set the new consumer */ + pDecoder->xConsumer = VmJsonArrayDecoder; + pDecoder->pUserData = pWorker; + /* Decode the array */ + for(;;){ + /* Jump trailing comma. Note that the standard JX9 engine will not let you + * do this. + */ + while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ + pDecoder->pIn++; + } + if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CSB) /*']'*/ ){ + if( pDecoder->pIn < pDecoder->pEnd ){ + pDecoder->pIn++; /* Jump the trailing ']' */ + } + break; + } + /* Recurse and decode the entry */ + pDecoder->rec_count++; + rc = VmJsonDecode(pDecoder, 0); + pDecoder->rec_count--; + if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + return SXERR_ABORT; + } + /*The cursor is automatically advanced by the VmJsonDecode() function */ + if( (pDecoder->pIn < pDecoder->pEnd) && + ((pDecoder->pIn->nType & (JSON_TK_CSB/*']'*/|JSON_TK_COMMA/*','*/))==0) ){ + /* Unexpected token, abort immediatley */ + *pDecoder->pErr = SXERR_SYNTAX; + return SXERR_ABORT; + } + } + /* Restore the old consumer */ + pDecoder->xConsumer = xOld; + pDecoder->pUserData = pOld; + /* Invoke the old consumer on the decoded array */ + xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); + }else if( pDecoder->pIn->nType & JSON_TK_OCB /*'{' */) { + ProcJSONConsumer xOld; + jx9_value *pKey; + void *pOld; + /* Object representation*/ + pDecoder->pIn++; + /* Create a working array */ + pWorker = jx9_context_new_array(pDecoder->pCtx); + pKey = jx9_context_new_scalar(pDecoder->pCtx); + if( pWorker == 0 || pKey == 0){ + jx9_context_throw_error(pDecoder->pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + /* Abort the decoding operation immediately */ + return SXERR_ABORT; + } + /* Save the old consumer */ + xOld = pDecoder->xConsumer; + pOld = pDecoder->pUserData; + /* Set the new consumer */ + pDecoder->xConsumer = VmJsonArrayDecoder; + pDecoder->pUserData = pWorker; + /* Decode the object */ + for(;;){ + /* Jump trailing comma. Note that the standard JX9 engine will not let you + * do this. + */ + while( (pDecoder->pIn < pDecoder->pEnd) && (pDecoder->pIn->nType & JSON_TK_COMMA) ){ + pDecoder->pIn++; + } + if( pDecoder->pIn >= pDecoder->pEnd || (pDecoder->pIn->nType & JSON_TK_CCB) /*'}'*/ ){ + if( pDecoder->pIn < pDecoder->pEnd ){ + pDecoder->pIn++; /* Jump the trailing ']' */ + } + break; + } + if( (pDecoder->pIn->nType & (JSON_TK_ID|JSON_TK_STR)) == 0 || &pDecoder->pIn[1] >= pDecoder->pEnd + || (pDecoder->pIn[1].nType & JSON_TK_COLON) == 0){ + /* Syntax error, return immediately */ + *pDecoder->pErr = SXERR_SYNTAX; + return SXERR_ABORT; + } + if( pDecoder->pIn->nType & JSON_TK_ID ){ + SyString *pStr = &pDecoder->pIn->sData; + jx9_value_string(pKey, pStr->zString, (int)pStr->nByte); + }else{ + /* Dequote the key */ + VmJsonDequoteString(&pDecoder->pIn->sData, pKey); + } + /* Jump the key and the colon */ + pDecoder->pIn += 2; + /* Recurse and decode the value */ + pDecoder->rec_count++; + rc = VmJsonDecode(pDecoder, pKey); + pDecoder->rec_count--; + if( rc == SXERR_ABORT ){ + /* Abort processing immediately */ + return SXERR_ABORT; + } + /* Reset the internal buffer of the key */ + jx9_value_reset_string_cursor(pKey); + /*The cursor is automatically advanced by the VmJsonDecode() function */ + } + /* Restore the old consumer */ + pDecoder->xConsumer = xOld; + pDecoder->pUserData = pOld; + /* Invoke the old consumer on the decoded object*/ + xOld(pDecoder->pCtx, pArrayKey, pWorker, pOld); + /* Release the key */ + jx9_context_release_value(pDecoder->pCtx, pKey); + }else{ + /* Unexpected token */ + return SXERR_ABORT; /* Abort immediately */ + } + /* Release the worker variable */ + jx9_context_release_value(pDecoder->pCtx, pWorker); + return SXRET_OK; +} +/* + * The following JSON decoder callback is invoked each time + * a JSON array representation [i.e: [15, "hello", FALSE] ] + * is being decoded. + */ +static int VmJsonArrayDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) +{ + jx9_value *pArray = (jx9_value *)pUserData; + /* Insert the entry */ + jx9_array_add_elem(pArray, pKey, pWorker); /* Will make it's own copy */ + SXUNUSED(pCtx); /* cc warning */ + /* All done */ + return SXRET_OK; +} +/* + * Standard JSON decoder callback. + */ +static int VmJsonDefaultDecoder(jx9_context *pCtx, jx9_value *pKey, jx9_value *pWorker, void *pUserData) +{ + /* Return the value directly */ + jx9_result_value(pCtx, pWorker); /* Will make it's own copy */ + SXUNUSED(pKey); /* cc warning */ + SXUNUSED(pUserData); + /* All done */ + return SXRET_OK; +} +/* + * Exported JSON decoding interface + */ +JX9_PRIVATE int jx9JsonDecode(jx9_context *pCtx,const char *zJSON,int nByte) +{ + jx9_vm *pVm = pCtx->pVm; + json_decoder sDecoder; + SySet sToken; + SyLex sLex; + sxi32 rc; + /* Tokenize the input */ + SySetInit(&sToken, &pVm->sAllocator, sizeof(SyToken)); + rc = SXRET_OK; + SyLexInit(&sLex, &sToken, VmJsonTokenize, &rc); + SyLexTokenizeInput(&sLex,zJSON,(sxu32)nByte, 0, 0, 0); + if( rc != SXRET_OK ){ + /* Something goes wrong while tokenizing input. [i.e: Unexpected token] */ + SyLexRelease(&sLex); + SySetRelease(&sToken); + /* return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Fill the decoder */ + sDecoder.pCtx = pCtx; + sDecoder.pErr = &rc; + sDecoder.pIn = (SyToken *)SySetBasePtr(&sToken); + sDecoder.pEnd = &sDecoder.pIn[SySetUsed(&sToken)]; + sDecoder.iFlags = 0; + sDecoder.rec_count = 0; + /* Set a default consumer */ + sDecoder.xConsumer = VmJsonDefaultDecoder; + sDecoder.pUserData = 0; + /* Decode the raw JSON input */ + rc = VmJsonDecode(&sDecoder, 0); + if( rc == SXERR_ABORT ){ + /* + * Something goes wrong while decoding JSON input.Return NULL. + */ + jx9_result_null(pCtx); + } + /* Clean-up the mess left behind */ + SyLexRelease(&sLex); + SySetRelease(&sToken); + /* All done */ + return JX9_OK; +} +/* + * ---------------------------------------------------------- + * File: jx9_lex.c + * MD5: a79518c0635dbaf5dcfaca62efa2faf8 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: lex.c v1.0 FreeBSD 2012-12-09 00:19 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file implements a thread-safe and full reentrant lexical analyzer for the Jx9 programming language */ +/* Forward declarations */ +static sxu32 keywordCode(const char *z,int n); +static sxi32 LexExtractNowdoc(SyStream *pStream,SyToken *pToken); +/* + * Tokenize a raw jx9 input. + * Get a single low-level token from the input file. Update the stream pointer so that + * it points to the first character beyond the extracted token. + */ +static sxi32 jx9TokenizeInput(SyStream *pStream,SyToken *pToken,void *pUserData,void *pCtxData) +{ + SyString *pStr; + sxi32 rc; + /* Ignore leading white spaces */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisSpace(pStream->zText[0]) ){ + /* Advance the stream cursor */ + if( pStream->zText[0] == '\n' ){ + /* Update line counter */ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + /* End of input reached */ + return SXERR_EOF; + } + /* Record token starting position and line */ + pToken->nLine = pStream->nLine; + pToken->pUserData = 0; + pStr = &pToken->sData; + SyStringInitFromBuf(pStr, pStream->zText, 0); + if( pStream->zText[0] >= 0xc0 || SyisAlpha(pStream->zText[0]) || pStream->zText[0] == '_' ){ + /* The following code fragment is taken verbatim from the xPP source tree. + * xPP is a modern embeddable macro processor with advanced features useful for + * application seeking for a production quality, ready to use macro processor. + * xPP is a widely used library developed and maintened by Symisc Systems. + * You can reach the xPP home page by following this link: + * http://xpp.symisc.net/ + */ + const unsigned char *zIn; + sxu32 nKeyword; + /* Isolate UTF-8 or alphanumeric stream */ + if( pStream->zText[0] < 0xc0 ){ + pStream->zText++; + } + for(;;){ + zIn = pStream->zText; + if( zIn[0] >= 0xc0 ){ + zIn++; + /* UTF-8 stream */ + while( zIn < pStream->zEnd && ((zIn[0] & 0xc0) == 0x80) ){ + zIn++; + } + } + /* Skip alphanumeric stream */ + while( zIn < pStream->zEnd && zIn[0] < 0xc0 && (SyisAlphaNum(zIn[0]) || zIn[0] == '_') ){ + zIn++; + } + if( zIn == pStream->zText ){ + /* Not an UTF-8 or alphanumeric stream */ + break; + } + /* Synchronize pointers */ + pStream->zText = zIn; + } + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + nKeyword = keywordCode(pStr->zString, (int)pStr->nByte); + if( nKeyword != JX9_TK_ID ){ + /* We are dealing with a keyword [i.e: if, function, CREATE, ...], save the keyword ID */ + pToken->nType = JX9_TK_KEYWORD; + pToken->pUserData = SX_INT_TO_PTR(nKeyword); + }else{ + /* A simple identifier */ + pToken->nType = JX9_TK_ID; + } + }else{ + sxi32 c; + /* Non-alpha stream */ + if( pStream->zText[0] == '#' || + ( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '/') ){ + pStream->zText++; + /* Inline comments */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] != '\n' ){ + pStream->zText++; + } + /* Tell the upper-layer to ignore this token */ + return SXERR_CONTINUE; + }else if( pStream->zText[0] == '/' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '*' ){ + pStream->zText += 2; + /* Block comment */ + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '*' ){ + if( &pStream->zText[1] >= pStream->zEnd || pStream->zText[1] == '/' ){ + break; + } + } + if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + pStream->zText += 2; + /* Tell the upper-layer to ignore this token */ + return SXERR_CONTINUE; + }else if( SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + /* Decimal digit stream */ + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + /* Mark the token as integer until we encounter a real number */ + pToken->nType = JX9_TK_INTEGER; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c == '.' ){ + /* Real number */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( c=='e' || c=='E' ){ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && + pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + } + } + pToken->nType = JX9_TK_REAL; + }else if( c=='e' || c=='E' ){ + SXUNUSED(pUserData); /* Prevent compiler warning */ + SXUNUSED(pCtxData); + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + c = pStream->zText[0]; + if( (c =='+' || c=='-') && &pStream->zText[1] < pStream->zEnd && + pStream->zText[1] < 0xc0 && SyisDigit(pStream->zText[1]) ){ + pStream->zText++; + } + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisDigit(pStream->zText[0]) ){ + pStream->zText++; + } + } + pToken->nType = JX9_TK_REAL; + }else if( c == 'x' || c == 'X' ){ + /* Hex digit stream */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && pStream->zText[0] < 0xc0 && SyisHex(pStream->zText[0]) ){ + pStream->zText++; + } + }else if(c == 'b' || c == 'B' ){ + /* Binary digit stream */ + pStream->zText++; + while( pStream->zText < pStream->zEnd && (pStream->zText[0] == '0' || pStream->zText[0] == '1') ){ + pStream->zText++; + } + } + } + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + return SXRET_OK; + } + c = pStream->zText[0]; + pStream->zText++; /* Advance the stream cursor */ + /* Assume we are dealing with an operator*/ + pToken->nType = JX9_TK_OP; + switch(c){ + case '$': pToken->nType = JX9_TK_DOLLAR; break; + case '{': pToken->nType = JX9_TK_OCB; break; + case '}': pToken->nType = JX9_TK_CCB; break; + case '(': pToken->nType = JX9_TK_LPAREN; break; + case '[': pToken->nType |= JX9_TK_OSB; break; /* Bitwise operation here, since the square bracket token '[' + * is a potential operator [i.e: subscripting] */ + case ']': pToken->nType = JX9_TK_CSB; break; + case ')': { + SySet *pTokSet = pStream->pSet; + /* Assemble type cast operators [i.e: (int), (float), (bool)...] */ + if( pTokSet->nUsed >= 2 ){ + SyToken *pTmp; + /* Peek the last recongnized token */ + pTmp = (SyToken *)SySetPeek(pTokSet); + if( pTmp->nType & JX9_TK_KEYWORD ){ + sxi32 nID = SX_PTR_TO_INT(pTmp->pUserData); + if( (sxu32)nID & (JX9_TKWRD_INT|JX9_TKWRD_FLOAT|JX9_TKWRD_STRING|JX9_TKWRD_BOOL) ){ + pTmp = (SyToken *)SySetAt(pTokSet, pTokSet->nUsed - 2); + if( pTmp->nType & JX9_TK_LPAREN ){ + /* Merge the three tokens '(' 'TYPE' ')' into a single one */ + const char * zTypeCast = "(int)"; + if( nID & JX9_TKWRD_FLOAT ){ + zTypeCast = "(float)"; + }else if( nID & JX9_TKWRD_BOOL ){ + zTypeCast = "(bool)"; + }else if( nID & JX9_TKWRD_STRING ){ + zTypeCast = "(string)"; + } + /* Reflect the change */ + pToken->nType = JX9_TK_OP; + SyStringInitFromBuf(&pToken->sData, zTypeCast, SyStrlen(zTypeCast)); + /* Save the instance associated with the type cast operator */ + pToken->pUserData = (void *)jx9ExprExtractOperator(&pToken->sData, 0); + /* Remove the two previous tokens */ + pTokSet->nUsed -= 2; + return SXRET_OK; + } + } + } + } + pToken->nType = JX9_TK_RPAREN; + break; + } + case '\'':{ + /* Single quoted string */ + pStr->zString++; + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '\'' ){ + if( pStream->zText[-1] != '\\' ){ + break; + }else{ + const unsigned char *zPtr = &pStream->zText[-2]; + sxi32 i = 1; + while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ + zPtr--; + i++; + } + if((i&1)==0){ + break; + } + } + } + if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + /* Record token length and type */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + pToken->nType = JX9_TK_SSTR; + /* Jump the trailing single quote */ + pStream->zText++; + return SXRET_OK; + } + case '"':{ + sxi32 iNest; + /* Double quoted string */ + pStr->zString++; + while( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '{' && &pStream->zText[1] < pStream->zEnd && pStream->zText[1] == '$'){ + iNest = 1; + pStream->zText++; + /* TICKET 1433-40: Hnadle braces'{}' in double quoted string where everything is allowed */ + while(pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '{' ){ + iNest++; + }else if (pStream->zText[0] == '}' ){ + iNest--; + if( iNest <= 0 ){ + pStream->zText++; + break; + } + }else if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + if( pStream->zText >= pStream->zEnd ){ + break; + } + } + if( pStream->zText[0] == '"' ){ + if( pStream->zText[-1] != '\\' ){ + break; + }else{ + const unsigned char *zPtr = &pStream->zText[-2]; + sxi32 i = 1; + while( zPtr > pStream->zInput && zPtr[0] == '\\' ){ + zPtr--; + i++; + } + if((i&1)==0){ + break; + } + } + } + if( pStream->zText[0] == '\n' ){ + pStream->nLine++; + } + pStream->zText++; + } + /* Record token length and type */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + pToken->nType = JX9_TK_DSTR; + /* Jump the trailing quote */ + pStream->zText++; + return SXRET_OK; + } + case ':': + pToken->nType = JX9_TK_COLON; /* Single colon */ + break; + case ',': pToken->nType |= JX9_TK_COMMA; break; /* Comma is also an operator */ + case ';': pToken->nType = JX9_TK_SEMI; break; + /* Handle combined operators [i.e: +=, ===, !=== ...] */ + case '=': + pToken->nType |= JX9_TK_EQUAL; + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '=' ){ + pToken->nType &= ~JX9_TK_EQUAL; + /* Current operator: == */ + pStream->zText++; + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: === */ + pStream->zText++; + } + } + } + break; + case '!': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: != */ + pStream->zText++; + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: !== */ + pStream->zText++; + } + } + break; + case '&': + pToken->nType |= JX9_TK_AMPER; + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '&' ){ + pToken->nType &= ~JX9_TK_AMPER; + /* Current operator: && */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + pToken->nType &= ~JX9_TK_AMPER; + /* Current operator: &= */ + pStream->zText++; + } + } + case '.': + if( pStream->zText < pStream->zEnd && (pStream->zText[0] == '.' || pStream->zText[0] == '=') ){ + /* Concatenation operator: '..' or '.=' */ + pStream->zText++; + } + break; + case '|': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '|' ){ + /* Current operator: || */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: |= */ + pStream->zText++; + } + } + break; + case '+': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '+' ){ + /* Current operator: ++ */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: += */ + pStream->zText++; + } + } + break; + case '-': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '-' ){ + /* Current operator: -- */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: -= */ + pStream->zText++; + }else if( pStream->zText[0] == '>' ){ + /* Current operator: -> */ + pStream->zText++; + } + } + break; + case '*': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: *= */ + pStream->zText++; + } + break; + case '/': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: /= */ + pStream->zText++; + } + break; + case '%': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: %= */ + pStream->zText++; + } + break; + case '^': + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: ^= */ + pStream->zText++; + } + break; + case '<': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '<' ){ + /* Current operator: << */ + pStream->zText++; + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '=' ){ + /* Current operator: <<= */ + pStream->zText++; + }else if( pStream->zText[0] == '<' ){ + /* Current Token: <<< */ + pStream->zText++; + /* This may be the beginning of a Heredoc/Nowdoc string, try to delimit it */ + rc = LexExtractNowdoc(&(*pStream), &(*pToken)); + if( rc == SXRET_OK ){ + /* Here/Now doc successfuly extracted */ + return SXRET_OK; + } + } + } + }else if( pStream->zText[0] == '>' ){ + /* Current operator: <> */ + pStream->zText++; + }else if( pStream->zText[0] == '=' ){ + /* Current operator: <= */ + pStream->zText++; + } + } + break; + case '>': + if( pStream->zText < pStream->zEnd ){ + if( pStream->zText[0] == '>' ){ + /* Current operator: >> */ + pStream->zText++; + if( pStream->zText < pStream->zEnd && pStream->zText[0] == '=' ){ + /* Current operator: >>= */ + pStream->zText++; + } + }else if( pStream->zText[0] == '=' ){ + /* Current operator: >= */ + pStream->zText++; + } + } + break; + default: + break; + } + if( pStr->nByte <= 0 ){ + /* Record token length */ + pStr->nByte = (sxu32)((const char *)pStream->zText-pStr->zString); + } + if( pToken->nType & JX9_TK_OP ){ + const jx9_expr_op *pOp; + /* Check if the extracted token is an operator */ + pOp = jx9ExprExtractOperator(pStr, (SyToken *)SySetPeek(pStream->pSet)); + if( pOp == 0 ){ + /* Not an operator */ + pToken->nType &= ~JX9_TK_OP; + if( pToken->nType <= 0 ){ + pToken->nType = JX9_TK_OTHER; + } + }else{ + /* Save the instance associated with this operator for later processing */ + pToken->pUserData = (void *)pOp; + } + } + } + /* Tell the upper-layer to save the extracted token for later processing */ + return SXRET_OK; +} +/***** This file contains automatically generated code ****** +** +** The code in this file has been automatically generated by +** +** $Header: /sqlite/sqlite/tool/mkkeywordhash.c,v 1.38 2011/12/21 01:00:46 $ +** +** The code in this file implements a function that determines whether +** or not a given identifier is really a JX9 keyword. The same thing +** might be implemented more directly using a hand-written hash table. +** But by using this automatically generated code, the size of the code +** is substantially reduced. This is important for embedded applications +** on platforms with limited memory. +*/ +/* Hash score: 35 */ +static sxu32 keywordCode(const char *z, int n) +{ + /* zText[] encodes 188 bytes of keywords in 128 bytes */ + /* printegereturnconstaticaselseifloatincludefaultDIEXITcontinue */ + /* diewhileASPRINTbooleanbreakforeachfunctionimportstringswitch */ + /* uplink */ + static const char zText[127] = { + 'p','r','i','n','t','e','g','e','r','e','t','u','r','n','c','o','n','s', + 't','a','t','i','c','a','s','e','l','s','e','i','f','l','o','a','t','i', + 'n','c','l','u','d','e','f','a','u','l','t','D','I','E','X','I','T','c', + 'o','n','t','i','n','u','e','d','i','e','w','h','i','l','e','A','S','P', + 'R','I','N','T','b','o','o','l','e','a','n','b','r','e','a','k','f','o', + 'r','e','a','c','h','f','u','n','c','t','i','o','n','i','m','p','o','r', + 't','s','t','r','i','n','g','s','w','i','t','c','h','u','p','l','i','n', + 'k', + }; + static const unsigned char aHash[59] = { + 0, 0, 0, 0, 15, 0, 30, 0, 0, 2, 19, 18, 0, + 0, 10, 3, 12, 0, 28, 29, 23, 0, 13, 22, 0, 0, + 14, 24, 25, 31, 11, 0, 0, 0, 0, 1, 5, 0, 0, + 20, 0, 27, 9, 0, 0, 0, 8, 0, 0, 26, 6, 0, + 0, 17, 0, 0, 0, 0, 0, + }; + static const unsigned char aNext[31] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 21, 7, + 0, 0, 0, 0, 0, + }; + static const unsigned char aLen[31] = { + 5, 7, 3, 6, 5, 6, 4, 2, 6, 4, 2, 5, 7, + 7, 3, 4, 8, 3, 5, 2, 5, 4, 7, 5, 3, 7, + 8, 6, 6, 6, 6, + }; + static const sxu16 aOffset[31] = { + 0, 2, 2, 8, 14, 17, 22, 23, 25, 25, 29, 30, 35, + 40, 47, 49, 53, 61, 64, 69, 71, 76, 76, 83, 88, 88, + 95, 103, 109, 115, 121, + }; + static const sxu32 aCode[31] = { + JX9_TKWRD_PRINT, JX9_TKWRD_INT, JX9_TKWRD_INT, JX9_TKWRD_RETURN, JX9_TKWRD_CONST, + JX9_TKWRD_STATIC, JX9_TKWRD_CASE, JX9_TKWRD_AS, JX9_TKWRD_ELIF, JX9_TKWRD_ELSE, + JX9_TKWRD_IF, JX9_TKWRD_FLOAT, JX9_TKWRD_INCLUDE, JX9_TKWRD_DEFAULT, JX9_TKWRD_DIE, + JX9_TKWRD_EXIT, JX9_TKWRD_CONTINUE, JX9_TKWRD_DIE, JX9_TKWRD_WHILE, JX9_TKWRD_AS, + JX9_TKWRD_PRINT, JX9_TKWRD_BOOL, JX9_TKWRD_BOOL, JX9_TKWRD_BREAK, JX9_TKWRD_FOR, + JX9_TKWRD_FOREACH, JX9_TKWRD_FUNCTION, JX9_TKWRD_IMPORT, JX9_TKWRD_STRING, JX9_TKWRD_SWITCH, + JX9_TKWRD_UPLINK, + }; + int h, i; + if( n<2 ) return JX9_TK_ID; + h = (((int)z[0]*4) ^ ((int)z[n-1]*3) ^ n) % 59; + for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){ + if( (int)aLen[i]==n && SyMemcmp(&zText[aOffset[i]],z,n)==0 ){ + /* JX9_TKWRD_PRINT */ + /* JX9_TKWRD_INT */ + /* JX9_TKWRD_INT */ + /* JX9_TKWRD_RETURN */ + /* JX9_TKWRD_CONST */ + /* JX9_TKWRD_STATIC */ + /* JX9_TKWRD_CASE */ + /* JX9_TKWRD_AS */ + /* JX9_TKWRD_ELIF */ + /* JX9_TKWRD_ELSE */ + /* JX9_TKWRD_IF */ + /* JX9_TKWRD_FLOAT */ + /* JX9_TKWRD_INCLUDE */ + /* JX9_TKWRD_DEFAULT */ + /* JX9_TKWRD_DIE */ + /* JX9_TKWRD_EXIT */ + /* JX9_TKWRD_CONTINUE */ + /* JX9_TKWRD_DIE */ + /* JX9_TKWRD_WHILE */ + /* JX9_TKWRD_AS */ + /* JX9_TKWRD_PRINT */ + /* JX9_TKWRD_BOOL */ + /* JX9_TKWRD_BOOL */ + /* JX9_TKWRD_BREAK */ + /* JX9_TKWRD_FOR */ + /* JX9_TKWRD_FOREACH */ + /* JX9_TKWRD_FUNCTION */ + /* JX9_TKWRD_IMPORT */ + /* JX9_TKWRD_STRING */ + /* JX9_TKWRD_SWITCH */ + /* JX9_TKWRD_UPLINK */ + return aCode[i]; + } + } + return JX9_TK_ID; +} +/* + * Extract a heredoc/nowdoc text from a raw JX9 input. + * According to the JX9 language reference manual: + * A third way to delimit strings is the heredoc syntax: <<<. After this operator, an identifier + * is provided, then a newline. The string itself follows, and then the same identifier again + * to close the quotation. + * The closing identifier must begin in the first column of the line. Also, the identifier must + * follow the same naming rules as any other label in JX9: it must contain only alphanumeric + * characters and underscores, and must start with a non-digit character or underscore. + * Heredoc text behaves just like a double-quoted string, without the double quotes. + * This means that quotes in a heredoc do not need to be escaped, but the escape codes listed + * above can still be used. Variables are expanded, but the same care must be taken when expressing + * complex variables inside a heredoc as with strings. + * Nowdocs are to single-quoted strings what heredocs are to double-quoted strings. + * A nowdoc is specified similarly to a heredoc, but no parsing is done inside a nowdoc. + * The construct is ideal for embedding JX9 code or other large blocks of text without the need + * for escaping. It shares some features in common with the SGML construct, in that + * it declares a block of text which is not for parsing. + * A nowdoc is identified with the same <<< sequence used for heredocs, but the identifier which follows + * is enclosed in single quotes, e.g. <<<'EOT'. All the rules for heredoc identifiers also apply to nowdoc + * identifiers, especially those regarding the appearance of the closing identifier. + */ +static sxi32 LexExtractNowdoc(SyStream *pStream, SyToken *pToken) +{ + const unsigned char *zIn = pStream->zText; + const unsigned char *zEnd = pStream->zEnd; + const unsigned char *zPtr; + SyString sDelim; + SyString sStr; + /* Jump leading white spaces */ + while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ + zIn++; + } + if( zIn >= zEnd ){ + /* A simple symbol, return immediately */ + return SXERR_CONTINUE; + } + if( zIn[0] == '\'' || zIn[0] == '"' ){ + zIn++; + } + if( zIn[0] < 0xc0 && !SyisAlphaNum(zIn[0]) && zIn[0] != '_' ){ + /* Invalid delimiter, return immediately */ + return SXERR_CONTINUE; + } + /* Isolate the identifier */ + sDelim.zString = (const char *)zIn; + for(;;){ + zPtr = zIn; + /* Skip alphanumeric stream */ + while( zPtr < zEnd && zPtr[0] < 0xc0 && (SyisAlphaNum(zPtr[0]) || zPtr[0] == '_') ){ + zPtr++; + } + if( zPtr < zEnd && zPtr[0] >= 0xc0 ){ + zPtr++; + /* UTF-8 stream */ + while( zPtr < zEnd && ((zPtr[0] & 0xc0) == 0x80) ){ + zPtr++; + } + } + if( zPtr == zIn ){ + /* Not an UTF-8 or alphanumeric stream */ + break; + } + /* Synchronize pointers */ + zIn = zPtr; + } + /* Get the identifier length */ + sDelim.nByte = (sxu32)((const char *)zIn-sDelim.zString); + if( zIn[0] == '"' || zIn[0] == '\'' ){ + /* Jump the trailing single quote */ + zIn++; + } + /* Jump trailing white spaces */ + while( zIn < zEnd && zIn[0] < 0xc0 && SyisSpace(zIn[0]) && zIn[0] != '\n' ){ + zIn++; + } + if( sDelim.nByte <= 0 || zIn >= zEnd || zIn[0] != '\n' ){ + /* Invalid syntax */ + return SXERR_CONTINUE; + } + pStream->nLine++; /* Increment line counter */ + zIn++; + /* Isolate the delimited string */ + sStr.zString = (const char *)zIn; + /* Go and found the closing delimiter */ + for(;;){ + /* Synchronize with the next line */ + while( zIn < zEnd && zIn[0] != '\n' ){ + zIn++; + } + if( zIn >= zEnd ){ + /* End of the input reached, break immediately */ + pStream->zText = pStream->zEnd; + break; + } + pStream->nLine++; /* Increment line counter */ + zIn++; + if( (sxu32)(zEnd - zIn) >= sDelim.nByte && SyMemcmp((const void *)sDelim.zString, (const void *)zIn, sDelim.nByte) == 0 ){ + zPtr = &zIn[sDelim.nByte]; + while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ + zPtr++; + } + if( zPtr >= zEnd ){ + /* End of input */ + pStream->zText = zPtr; + break; + } + if( zPtr[0] == ';' ){ + const unsigned char *zCur = zPtr; + zPtr++; + while( zPtr < zEnd && zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) && zPtr[0] != '\n' ){ + zPtr++; + } + if( zPtr >= zEnd || zPtr[0] == '\n' ){ + /* Closing delimiter found, break immediately */ + pStream->zText = zCur; /* Keep the semi-colon */ + break; + } + }else if( zPtr[0] == '\n' ){ + /* Closing delimiter found, break immediately */ + pStream->zText = zPtr; /* Synchronize with the stream cursor */ + break; + } + /* Synchronize pointers and continue searching */ + zIn = zPtr; + } + } /* For(;;) */ + /* Get the delimited string length */ + sStr.nByte = (sxu32)((const char *)zIn-sStr.zString); + /* Record token type and length */ + pToken->nType = JX9_TK_NOWDOC; + SyStringDupPtr(&pToken->sData, &sStr); + /* Remove trailing white spaces */ + SyStringRightTrim(&pToken->sData); + /* All done */ + return SXRET_OK; +} +/* + * Tokenize a raw jx9 input. + * This is the public tokenizer called by most code generator routines. + */ +JX9_PRIVATE sxi32 jx9Tokenize(const char *zInput,sxu32 nLen,SySet *pOut) +{ + SyLex sLexer; + sxi32 rc; + /* Initialize the lexer */ + rc = SyLexInit(&sLexer, &(*pOut),jx9TokenizeInput,0); + if( rc != SXRET_OK ){ + return rc; + } + /* Tokenize input */ + rc = SyLexTokenizeInput(&sLexer, zInput, nLen, 0, 0, 0); + /* Release the lexer */ + SyLexRelease(&sLexer); + /* Tokenization result */ + return rc; +} + +/* + * ---------------------------------------------------------- + * File: jx9_lib.c + * MD5: a684fb6677b1ab0110d03536f1280c50 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: lib.c v5.1 Win7 2012-08-08 04:19 stable $ */ +/* + * Symisc Run-Time API: A modern thread safe replacement of the standard libc + * Copyright (C) Symisc Systems 2007-2012, http://www.symisc.net/ + * + * The Symisc Run-Time API is an independent project developed by symisc systems + * internally as a secure replacement of the standard libc. + * The library is re-entrant, thread-safe and platform independent. + */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +#if defined(__WINNT__) +#include +#else +#include +#endif +#if defined(JX9_ENABLE_THREADS) +/* SyRunTimeApi: sxmutex.c */ +#if defined(__WINNT__) +struct SyMutex +{ + CRITICAL_SECTION sMutex; + sxu32 nType; /* Mutex type, one of SXMUTEX_TYPE_* */ +}; +/* Preallocated static mutex */ +static SyMutex aStaticMutexes[] = { + {{0}, SXMUTEX_TYPE_STATIC_1}, + {{0}, SXMUTEX_TYPE_STATIC_2}, + {{0}, SXMUTEX_TYPE_STATIC_3}, + {{0}, SXMUTEX_TYPE_STATIC_4}, + {{0}, SXMUTEX_TYPE_STATIC_5}, + {{0}, SXMUTEX_TYPE_STATIC_6} +}; +static BOOL winMutexInit = FALSE; +static LONG winMutexLock = 0; + +static sxi32 WinMutexGlobaInit(void) +{ + LONG rc; + rc = InterlockedCompareExchange(&winMutexLock, 1, 0); + if ( rc == 0 ){ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ + InitializeCriticalSection(&aStaticMutexes[n].sMutex); + } + winMutexInit = TRUE; + }else{ + /* Someone else is doing this for us */ + while( winMutexInit == FALSE ){ + Sleep(1); + } + } + return SXRET_OK; +} +static void WinMutexGlobalRelease(void) +{ + LONG rc; + rc = InterlockedCompareExchange(&winMutexLock, 0, 1); + if( rc == 1 ){ + /* The first to decrement to zero does the actual global release */ + if( winMutexInit == TRUE ){ + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aStaticMutexes) ; ++n ){ + DeleteCriticalSection(&aStaticMutexes[n].sMutex); + } + winMutexInit = FALSE; + } + } +} +static SyMutex * WinMutexNew(int nType) +{ + SyMutex *pMutex = 0; + if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ + /* Allocate a new mutex */ + pMutex = (SyMutex *)HeapAlloc(GetProcessHeap(), 0, sizeof(SyMutex)); + if( pMutex == 0 ){ + return 0; + } + InitializeCriticalSection(&pMutex->sMutex); + }else{ + /* Use a pre-allocated static mutex */ + if( nType > SXMUTEX_TYPE_STATIC_6 ){ + nType = SXMUTEX_TYPE_STATIC_6; + } + pMutex = &aStaticMutexes[nType - 3]; + } + pMutex->nType = nType; + return pMutex; +} +static void WinMutexRelease(SyMutex *pMutex) +{ + if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ + DeleteCriticalSection(&pMutex->sMutex); + HeapFree(GetProcessHeap(), 0, pMutex); + } +} +static void WinMutexEnter(SyMutex *pMutex) +{ + EnterCriticalSection(&pMutex->sMutex); +} +static sxi32 WinMutexTryEnter(SyMutex *pMutex) +{ +#ifdef _WIN32_WINNT + BOOL rc; + /* Only WindowsNT platforms */ + rc = TryEnterCriticalSection(&pMutex->sMutex); + if( rc ){ + return SXRET_OK; + }else{ + return SXERR_BUSY; + } +#else + return SXERR_NOTIMPLEMENTED; +#endif +} +static void WinMutexLeave(SyMutex *pMutex) +{ + LeaveCriticalSection(&pMutex->sMutex); +} +/* Export Windows mutex interfaces */ +static const SyMutexMethods sWinMutexMethods = { + WinMutexGlobaInit, /* xGlobalInit() */ + WinMutexGlobalRelease, /* xGlobalRelease() */ + WinMutexNew, /* xNew() */ + WinMutexRelease, /* xRelease() */ + WinMutexEnter, /* xEnter() */ + WinMutexTryEnter, /* xTryEnter() */ + WinMutexLeave /* xLeave() */ +}; +JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) +{ + return &sWinMutexMethods; +} +#elif defined(__UNIXES__) +#include +struct SyMutex +{ + pthread_mutex_t sMutex; + sxu32 nType; +}; +static SyMutex * UnixMutexNew(int nType) +{ + static SyMutex aStaticMutexes[] = { + {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_1}, + {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_2}, + {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_3}, + {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_4}, + {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_5}, + {PTHREAD_MUTEX_INITIALIZER, SXMUTEX_TYPE_STATIC_6} + }; + SyMutex *pMutex; + + if( nType == SXMUTEX_TYPE_FAST || nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutexattr_t sRecursiveAttr; + /* Allocate a new mutex */ + pMutex = (SyMutex *)malloc(sizeof(SyMutex)); + if( pMutex == 0 ){ + return 0; + } + if( nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutexattr_init(&sRecursiveAttr); + pthread_mutexattr_settype(&sRecursiveAttr, PTHREAD_MUTEX_RECURSIVE); + } + pthread_mutex_init(&pMutex->sMutex, nType == SXMUTEX_TYPE_RECURSIVE ? &sRecursiveAttr : 0 ); + if( nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutexattr_destroy(&sRecursiveAttr); + } + }else{ + /* Use a pre-allocated static mutex */ + if( nType > SXMUTEX_TYPE_STATIC_6 ){ + nType = SXMUTEX_TYPE_STATIC_6; + } + pMutex = &aStaticMutexes[nType - 3]; + } + pMutex->nType = nType; + + return pMutex; +} +static void UnixMutexRelease(SyMutex *pMutex) +{ + if( pMutex->nType == SXMUTEX_TYPE_FAST || pMutex->nType == SXMUTEX_TYPE_RECURSIVE ){ + pthread_mutex_destroy(&pMutex->sMutex); + free(pMutex); + } +} +static void UnixMutexEnter(SyMutex *pMutex) +{ + pthread_mutex_lock(&pMutex->sMutex); +} +static void UnixMutexLeave(SyMutex *pMutex) +{ + pthread_mutex_unlock(&pMutex->sMutex); +} +/* Export pthread mutex interfaces */ +static const SyMutexMethods sPthreadMutexMethods = { + 0, /* xGlobalInit() */ + 0, /* xGlobalRelease() */ + UnixMutexNew, /* xNew() */ + UnixMutexRelease, /* xRelease() */ + UnixMutexEnter, /* xEnter() */ + 0, /* xTryEnter() */ + UnixMutexLeave /* xLeave() */ +}; +JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) +{ + return &sPthreadMutexMethods; +} +#else +/* Host application must register their own mutex subsystem if the target + * platform is not an UNIX-like or windows systems. + */ +struct SyMutex +{ + sxu32 nType; +}; +static SyMutex * DummyMutexNew(int nType) +{ + static SyMutex sMutex; + SXUNUSED(nType); + return &sMutex; +} +static void DummyMutexRelease(SyMutex *pMutex) +{ + SXUNUSED(pMutex); +} +static void DummyMutexEnter(SyMutex *pMutex) +{ + SXUNUSED(pMutex); +} +static void DummyMutexLeave(SyMutex *pMutex) +{ + SXUNUSED(pMutex); +} +/* Export the dummy mutex interfaces */ +static const SyMutexMethods sDummyMutexMethods = { + 0, /* xGlobalInit() */ + 0, /* xGlobalRelease() */ + DummyMutexNew, /* xNew() */ + DummyMutexRelease, /* xRelease() */ + DummyMutexEnter, /* xEnter() */ + 0, /* xTryEnter() */ + DummyMutexLeave /* xLeave() */ +}; +JX9_PRIVATE const SyMutexMethods * SyMutexExportMethods(void) +{ + return &sDummyMutexMethods; +} +#endif /* __WINNT__ */ +#endif /* JX9_ENABLE_THREADS */ +static void * SyOSHeapAlloc(sxu32 nByte) +{ + void *pNew; +#if defined(__WINNT__) + pNew = HeapAlloc(GetProcessHeap(), 0, nByte); +#else + pNew = malloc((size_t)nByte); +#endif + return pNew; +} +static void * SyOSHeapRealloc(void *pOld, sxu32 nByte) +{ + void *pNew; +#if defined(__WINNT__) + pNew = HeapReAlloc(GetProcessHeap(), 0, pOld, nByte); +#else + pNew = realloc(pOld, (size_t)nByte); +#endif + return pNew; +} +static void SyOSHeapFree(void *pPtr) +{ +#if defined(__WINNT__) + HeapFree(GetProcessHeap(), 0, pPtr); +#else + free(pPtr); +#endif +} +/* SyRunTimeApi:sxstr.c */ +JX9_PRIVATE sxu32 SyStrlen(const char *zSrc) +{ + register const char *zIn = zSrc; +#if defined(UNTRUST) + if( zIn == 0 ){ + return 0; + } +#endif + for(;;){ + if( !zIn[0] ){ break; } zIn++; + if( !zIn[0] ){ break; } zIn++; + if( !zIn[0] ){ break; } zIn++; + if( !zIn[0] ){ break; } zIn++; + } + return (sxu32)(zIn - zSrc); +} +JX9_PRIVATE sxi32 SyByteFind(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos) +{ + const char *zIn = zStr; + const char *zEnd; + + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + if( zIn >= zEnd ){ break; }if( zIn[0] == c ){ if( pPos ){ *pPos = (sxu32)(zIn - zStr); } return SXRET_OK; } zIn++; + } + return SXERR_NOTFOUND; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyByteFind2(const char *zStr, sxu32 nLen, sxi32 c, sxu32 *pPos) +{ + const char *zIn = zStr; + const char *zEnd; + + zEnd = &zIn[nLen - 1]; + for( ;; ){ + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + if( zEnd < zIn ){ break; } if( zEnd[0] == c ){ if( pPos ){ *pPos = (sxu32)(zEnd - zIn);} return SXRET_OK; } zEnd--; + } + return SXERR_NOTFOUND; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +JX9_PRIVATE sxi32 SyByteListFind(const char *zSrc, sxu32 nLen, const char *zList, sxu32 *pFirstPos) +{ + const char *zIn = zSrc; + const char *zPtr; + const char *zEnd; + sxi32 c; + zEnd = &zSrc[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + if( zIn >= zEnd ){ break; } for(zPtr = zList ; (c = zPtr[0]) != 0 ; zPtr++ ){ if( zIn[0] == c ){ if( pFirstPos ){ *pFirstPos = (sxu32)(zIn - zSrc); } return SXRET_OK; } } zIn++; + } + return SXERR_NOTFOUND; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyStrncmp(const char *zLeft, const char *zRight, sxu32 nLen) +{ + const unsigned char *zP = (const unsigned char *)zLeft; + const unsigned char *zQ = (const unsigned char *)zRight; + + if( SX_EMPTY_STR(zP) || SX_EMPTY_STR(zQ) ){ + return SX_EMPTY_STR(zP) ? (SX_EMPTY_STR(zQ) ? 0 : -1) :1; + } + if( nLen <= 0 ){ + return 0; + } + for(;;){ + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + if( nLen <= 0 ){ return 0; } if( zP[0] == 0 || zQ[0] == 0 || zP[0] != zQ[0] ){ break; } zP++; zQ++; nLen--; + } + return (sxi32)(zP[0] - zQ[0]); +} +#endif +JX9_PRIVATE sxi32 SyStrnicmp(const char *zLeft, const char *zRight, sxu32 SLen) +{ + register unsigned char *p = (unsigned char *)zLeft; + register unsigned char *q = (unsigned char *)zRight; + + if( SX_EMPTY_STR(p) || SX_EMPTY_STR(q) ){ + return SX_EMPTY_STR(p)? SX_EMPTY_STR(q) ? 0 : -1 :1; + } + for(;;){ + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + if( !SLen ){ return 0; }if( !*p || !*q || SyCharToLower(*p) != SyCharToLower(*q) ){ break; }p++;q++;--SLen; + + } + return (sxi32)(SyCharToLower(p[0]) - SyCharToLower(q[0])); +} +JX9_PRIVATE sxu32 Systrcpy(char *zDest, sxu32 nDestLen, const char *zSrc, sxu32 nLen) +{ + unsigned char *zBuf = (unsigned char *)zDest; + unsigned char *zIn = (unsigned char *)zSrc; + unsigned char *zEnd; +#if defined(UNTRUST) + if( zSrc == (const char *)zDest ){ + return 0; + } +#endif + if( nLen <= 0 ){ + nLen = SyStrlen(zSrc); + } + zEnd = &zBuf[nDestLen - 1]; /* reserve a room for the null terminator */ + for(;;){ + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + if( zBuf >= zEnd || nLen == 0 ){ break;} zBuf[0] = zIn[0]; zIn++; zBuf++; nLen--; + } + zBuf[0] = 0; + return (sxu32)(zBuf-(unsigned char *)zDest); +} +/* SyRunTimeApi:sxmem.c */ +JX9_PRIVATE void SyZero(void *pSrc, sxu32 nSize) +{ + register unsigned char *zSrc = (unsigned char *)pSrc; + unsigned char *zEnd; +#if defined(UNTRUST) + if( zSrc == 0 || nSize <= 0 ){ + return ; + } +#endif + zEnd = &zSrc[nSize]; + for(;;){ + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + if( zSrc >= zEnd ){break;} zSrc[0] = 0; zSrc++; + } +} +JX9_PRIVATE sxi32 SyMemcmp(const void *pB1, const void *pB2, sxu32 nSize) +{ + sxi32 rc; + if( nSize <= 0 ){ + return 0; + } + if( pB1 == 0 || pB2 == 0 ){ + return pB1 != 0 ? 1 : (pB2 == 0 ? 0 : -1); + } + SX_MACRO_FAST_CMP(pB1, pB2, nSize, rc); + return rc; +} +JX9_PRIVATE sxu32 SyMemcpy(const void *pSrc, void *pDest, sxu32 nLen) +{ + if( pSrc == 0 || pDest == 0 ){ + return 0; + } + if( pSrc == (const void *)pDest ){ + return nLen; + } + SX_MACRO_FAST_MEMCPY(pSrc, pDest, nLen); + return nLen; +} +static void * MemOSAlloc(sxu32 nBytes) +{ + sxu32 *pChunk; + pChunk = (sxu32 *)SyOSHeapAlloc(nBytes + sizeof(sxu32)); + if( pChunk == 0 ){ + return 0; + } + pChunk[0] = nBytes; + return (void *)&pChunk[1]; +} +static void * MemOSRealloc(void *pOld, sxu32 nBytes) +{ + sxu32 *pOldChunk; + sxu32 *pChunk; + pOldChunk = (sxu32 *)(((char *)pOld)-sizeof(sxu32)); + if( pOldChunk[0] >= nBytes ){ + return pOld; + } + pChunk = (sxu32 *)SyOSHeapRealloc(pOldChunk, nBytes + sizeof(sxu32)); + if( pChunk == 0 ){ + return 0; + } + pChunk[0] = nBytes; + return (void *)&pChunk[1]; +} +static void MemOSFree(void *pBlock) +{ + void *pChunk; + pChunk = (void *)(((char *)pBlock)-sizeof(sxu32)); + SyOSHeapFree(pChunk); +} +static sxu32 MemOSChunkSize(void *pBlock) +{ + sxu32 *pChunk; + pChunk = (sxu32 *)(((char *)pBlock)-sizeof(sxu32)); + return pChunk[0]; +} +/* Export OS allocation methods */ +static const SyMemMethods sOSAllocMethods = { + MemOSAlloc, + MemOSRealloc, + MemOSFree, + MemOSChunkSize, + 0, + 0, + 0 +}; +static void * MemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte) +{ + SyMemBlock *pBlock; + sxi32 nRetry = 0; + + /* Append an extra block so we can tracks allocated chunks and avoid memory + * leaks. + */ + nByte += sizeof(SyMemBlock); + for(;;){ + pBlock = (SyMemBlock *)pBackend->pMethods->xAlloc(nByte); + if( pBlock != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY + || SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ + break; + } + nRetry++; + } + if( pBlock == 0 ){ + return 0; + } + pBlock->pNext = pBlock->pPrev = 0; + /* Link to the list of already tracked blocks */ + MACRO_LD_PUSH(pBackend->pBlocks, pBlock); +#if defined(UNTRUST) + pBlock->nGuard = SXMEM_BACKEND_MAGIC; +#endif + pBackend->nBlock++; + return (void *)&pBlock[1]; +} +JX9_PRIVATE void * SyMemBackendAlloc(SyMemBackend *pBackend, sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); + } + pChunk = MemBackendAlloc(&(*pBackend), nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); + } + return pChunk; +} +static void * MemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) +{ + SyMemBlock *pBlock, *pNew, *pPrev, *pNext; + sxu32 nRetry = 0; + + if( pOld == 0 ){ + return MemBackendAlloc(&(*pBackend), nByte); + } + pBlock = (SyMemBlock *)(((char *)pOld) - sizeof(SyMemBlock)); +#if defined(UNTRUST) + if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ + return 0; + } +#endif + nByte += sizeof(SyMemBlock); + pPrev = pBlock->pPrev; + pNext = pBlock->pNext; + for(;;){ + pNew = (SyMemBlock *)pBackend->pMethods->xRealloc(pBlock, nByte); + if( pNew != 0 || pBackend->xMemError == 0 || nRetry > SXMEM_BACKEND_RETRY || + SXERR_RETRY != pBackend->xMemError(pBackend->pUserData) ){ + break; + } + nRetry++; + } + if( pNew == 0 ){ + return 0; + } + if( pNew != pBlock ){ + if( pPrev == 0 ){ + pBackend->pBlocks = pNew; + }else{ + pPrev->pNext = pNew; + } + if( pNext ){ + pNext->pPrev = pNew; + } +#if defined(UNTRUST) + pNew->nGuard = SXMEM_BACKEND_MAGIC; +#endif + } + return (void *)&pNew[1]; +} +JX9_PRIVATE void * SyMemBackendRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); + } + pChunk = MemBackendRealloc(&(*pBackend), pOld, nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); + } + return pChunk; +} +static sxi32 MemBackendFree(SyMemBackend *pBackend, void * pChunk) +{ + SyMemBlock *pBlock; + pBlock = (SyMemBlock *)(((char *)pChunk) - sizeof(SyMemBlock)); +#if defined(UNTRUST) + if( pBlock->nGuard != SXMEM_BACKEND_MAGIC ){ + return SXERR_CORRUPT; + } +#endif + /* Unlink from the list of active blocks */ + if( pBackend->nBlock > 0 ){ + /* Release the block */ +#if defined(UNTRUST) + /* Mark as stale block */ + pBlock->nGuard = 0x635B; +#endif + MACRO_LD_REMOVE(pBackend->pBlocks, pBlock); + pBackend->nBlock--; + pBackend->pMethods->xFree(pBlock); + } + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyMemBackendFree(SyMemBackend *pBackend, void * pChunk) +{ + sxi32 rc; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return SXERR_CORRUPT; + } +#endif + if( pChunk == 0 ){ + return SXRET_OK; + } + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); + } + rc = MemBackendFree(&(*pBackend), pChunk); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); + } + return rc; +} +#if defined(JX9_ENABLE_THREADS) +JX9_PRIVATE sxi32 SyMemBackendMakeThreadSafe(SyMemBackend *pBackend, const SyMutexMethods *pMethods) +{ + SyMutex *pMutex; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) || pMethods == 0 || pMethods->xNew == 0){ + return SXERR_CORRUPT; + } +#endif + pMutex = pMethods->xNew(SXMUTEX_TYPE_FAST); + if( pMutex == 0 ){ + return SXERR_OS; + } + /* Attach the mutex to the memory backend */ + pBackend->pMutex = pMutex; + pBackend->pMutexMethods = pMethods; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyMemBackendDisbaleMutexing(SyMemBackend *pBackend) +{ +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return SXERR_CORRUPT; + } +#endif + if( pBackend->pMutex == 0 ){ + /* There is no mutex subsystem at all */ + return SXRET_OK; + } + SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex); + pBackend->pMutexMethods = 0; + pBackend->pMutex = 0; + return SXRET_OK; +} +#endif +/* + * Memory pool allocator + */ +#define SXMEM_POOL_MAGIC 0xDEAD +#define SXMEM_POOL_MAXALLOC (1<<(SXMEM_POOL_NBUCKETS+SXMEM_POOL_INCR)) +#define SXMEM_POOL_MINALLOC (1<<(SXMEM_POOL_INCR)) +static sxi32 MemPoolBucketAlloc(SyMemBackend *pBackend, sxu32 nBucket) +{ + char *zBucket, *zBucketEnd; + SyMemHeader *pHeader; + sxu32 nBucketSize; + + /* Allocate one big block first */ + zBucket = (char *)MemBackendAlloc(&(*pBackend), SXMEM_POOL_MAXALLOC); + if( zBucket == 0 ){ + return SXERR_MEM; + } + zBucketEnd = &zBucket[SXMEM_POOL_MAXALLOC]; + /* Divide the big block into mini bucket pool */ + nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); + pBackend->apPool[nBucket] = pHeader = (SyMemHeader *)zBucket; + for(;;){ + if( &zBucket[nBucketSize] >= zBucketEnd ){ + break; + } + pHeader->pNext = (SyMemHeader *)&zBucket[nBucketSize]; + /* Advance the cursor to the next available chunk */ + pHeader = pHeader->pNext; + zBucket += nBucketSize; + } + pHeader->pNext = 0; + + return SXRET_OK; +} +static void * MemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte) +{ + SyMemHeader *pBucket, *pNext; + sxu32 nBucketSize; + sxu32 nBucket; + + if( nByte + sizeof(SyMemHeader) >= SXMEM_POOL_MAXALLOC ){ + /* Allocate a big chunk directly */ + pBucket = (SyMemHeader *)MemBackendAlloc(&(*pBackend), nByte+sizeof(SyMemHeader)); + if( pBucket == 0 ){ + return 0; + } + /* Record as big block */ + pBucket->nBucket = (sxu32)(SXMEM_POOL_MAGIC << 16) | SXU16_HIGH; + return (void *)(pBucket+1); + } + /* Locate the appropriate bucket */ + nBucket = 0; + nBucketSize = SXMEM_POOL_MINALLOC; + while( nByte + sizeof(SyMemHeader) > nBucketSize ){ + nBucketSize <<= 1; + nBucket++; + } + pBucket = pBackend->apPool[nBucket]; + if( pBucket == 0 ){ + sxi32 rc; + rc = MemPoolBucketAlloc(&(*pBackend), nBucket); + if( rc != SXRET_OK ){ + return 0; + } + pBucket = pBackend->apPool[nBucket]; + } + /* Remove from the free list */ + pNext = pBucket->pNext; + pBackend->apPool[nBucket] = pNext; + /* Record bucket&magic number */ + pBucket->nBucket = (SXMEM_POOL_MAGIC << 16) | nBucket; + return (void *)&pBucket[1]; +} +JX9_PRIVATE void * SyMemBackendPoolAlloc(SyMemBackend *pBackend, sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); + } + pChunk = MemBackendPoolAlloc(&(*pBackend), nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); + } + return pChunk; +} +static sxi32 MemBackendPoolFree(SyMemBackend *pBackend, void * pChunk) +{ + SyMemHeader *pHeader; + sxu32 nBucket; + /* Get the corresponding bucket */ + pHeader = (SyMemHeader *)(((char *)pChunk) - sizeof(SyMemHeader)); + /* Sanity check to avoid misuse */ + if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ + return SXERR_CORRUPT; + } + nBucket = pHeader->nBucket & 0xFFFF; + if( nBucket == SXU16_HIGH ){ + /* Free the big block */ + MemBackendFree(&(*pBackend), pHeader); + }else{ + /* Return to the free list */ + pHeader->pNext = pBackend->apPool[nBucket & 0x0f]; + pBackend->apPool[nBucket & 0x0f] = pHeader; + } + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyMemBackendPoolFree(SyMemBackend *pBackend, void * pChunk) +{ + sxi32 rc; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) || pChunk == 0 ){ + return SXERR_CORRUPT; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); + } + rc = MemBackendPoolFree(&(*pBackend), pChunk); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); + } + return rc; +} +#if 0 +static void * MemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) +{ + sxu32 nBucket, nBucketSize; + SyMemHeader *pHeader; + void * pNew; + + if( pOld == 0 ){ + /* Allocate a new pool */ + pNew = MemBackendPoolAlloc(&(*pBackend), nByte); + return pNew; + } + /* Get the corresponding bucket */ + pHeader = (SyMemHeader *)(((char *)pOld) - sizeof(SyMemHeader)); + /* Sanity check to avoid misuse */ + if( (pHeader->nBucket >> 16) != SXMEM_POOL_MAGIC ){ + return 0; + } + nBucket = pHeader->nBucket & 0xFFFF; + if( nBucket == SXU16_HIGH ){ + /* Big block */ + return MemBackendRealloc(&(*pBackend), pHeader, nByte); + } + nBucketSize = 1 << (nBucket + SXMEM_POOL_INCR); + if( nBucketSize >= nByte + sizeof(SyMemHeader) ){ + /* The old bucket can honor the requested size */ + return pOld; + } + /* Allocate a new pool */ + pNew = MemBackendPoolAlloc(&(*pBackend), nByte); + if( pNew == 0 ){ + return 0; + } + /* Copy the old data into the new block */ + SyMemcpy(pOld, pNew, nBucketSize); + /* Free the stale block */ + MemBackendPoolFree(&(*pBackend), pOld); + return pNew; +} +JX9_PRIVATE void * SyMemBackendPoolRealloc(SyMemBackend *pBackend, void * pOld, sxu32 nByte) +{ + void *pChunk; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return 0; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); + } + pChunk = MemBackendPoolRealloc(&(*pBackend), pOld, nByte); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); + } + return pChunk; +} +#endif +JX9_PRIVATE sxi32 SyMemBackendInit(SyMemBackend *pBackend, ProcMemError xMemErr, void * pUserData) +{ +#if defined(UNTRUST) + if( pBackend == 0 ){ + return SXERR_EMPTY; + } +#endif + /* Zero the allocator first */ + SyZero(&(*pBackend), sizeof(SyMemBackend)); + pBackend->xMemError = xMemErr; + pBackend->pUserData = pUserData; + /* Switch to the OS memory allocator */ + pBackend->pMethods = &sOSAllocMethods; + if( pBackend->pMethods->xInit ){ + /* Initialize the backend */ + if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ + return SXERR_ABORT; + } + } +#if defined(UNTRUST) + pBackend->nMagic = SXMEM_BACKEND_MAGIC; +#endif + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyMemBackendInitFromOthers(SyMemBackend *pBackend, const SyMemMethods *pMethods, ProcMemError xMemErr, void * pUserData) +{ +#if defined(UNTRUST) + if( pBackend == 0 || pMethods == 0){ + return SXERR_EMPTY; + } +#endif + if( pMethods->xAlloc == 0 || pMethods->xRealloc == 0 || pMethods->xFree == 0 || pMethods->xChunkSize == 0 ){ + /* mandatory methods are missing */ + return SXERR_INVALID; + } + /* Zero the allocator first */ + SyZero(&(*pBackend), sizeof(SyMemBackend)); + pBackend->xMemError = xMemErr; + pBackend->pUserData = pUserData; + /* Switch to the host application memory allocator */ + pBackend->pMethods = pMethods; + if( pBackend->pMethods->xInit ){ + /* Initialize the backend */ + if( SXRET_OK != pBackend->pMethods->xInit(pBackend->pMethods->pUserData) ){ + return SXERR_ABORT; + } + } +#if defined(UNTRUST) + pBackend->nMagic = SXMEM_BACKEND_MAGIC; +#endif + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyMemBackendInitFromParent(SyMemBackend *pBackend,const SyMemBackend *pParent) +{ + sxu8 bInheritMutex; +#if defined(UNTRUST) + if( pBackend == 0 || SXMEM_BACKEND_CORRUPT(pParent) ){ + return SXERR_CORRUPT; + } +#endif + /* Zero the allocator first */ + SyZero(&(*pBackend), sizeof(SyMemBackend)); + pBackend->pMethods = pParent->pMethods; + pBackend->xMemError = pParent->xMemError; + pBackend->pUserData = pParent->pUserData; + bInheritMutex = pParent->pMutexMethods ? TRUE : FALSE; + if( bInheritMutex ){ + pBackend->pMutexMethods = pParent->pMutexMethods; + /* Create a private mutex */ + pBackend->pMutex = pBackend->pMutexMethods->xNew(SXMUTEX_TYPE_FAST); + if( pBackend->pMutex == 0){ + return SXERR_OS; + } + } +#if defined(UNTRUST) + pBackend->nMagic = SXMEM_BACKEND_MAGIC; +#endif + return SXRET_OK; +} +static sxi32 MemBackendRelease(SyMemBackend *pBackend) +{ + SyMemBlock *pBlock, *pNext; + + pBlock = pBackend->pBlocks; + for(;;){ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP ONE */ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP TWO */ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP THREE */ + if( pBackend->nBlock == 0 ){ + break; + } + pNext = pBlock->pNext; + pBackend->pMethods->xFree(pBlock); + pBlock = pNext; + pBackend->nBlock--; + /* LOOP FOUR */ + } + if( pBackend->pMethods->xRelease ){ + pBackend->pMethods->xRelease(pBackend->pMethods->pUserData); + } + pBackend->pMethods = 0; + pBackend->pBlocks = 0; +#if defined(UNTRUST) + pBackend->nMagic = 0x2626; +#endif + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyMemBackendRelease(SyMemBackend *pBackend) +{ + sxi32 rc; +#if defined(UNTRUST) + if( SXMEM_BACKEND_CORRUPT(pBackend) ){ + return SXERR_INVALID; + } +#endif + if( pBackend->pMutexMethods ){ + SyMutexEnter(pBackend->pMutexMethods, pBackend->pMutex); + } + rc = MemBackendRelease(&(*pBackend)); + if( pBackend->pMutexMethods ){ + SyMutexLeave(pBackend->pMutexMethods, pBackend->pMutex); + SyMutexRelease(pBackend->pMutexMethods, pBackend->pMutex); + } + return rc; +} +JX9_PRIVATE void * SyMemBackendDup(SyMemBackend *pBackend, const void *pSrc, sxu32 nSize) +{ + void *pNew; +#if defined(UNTRUST) + if( pSrc == 0 || nSize <= 0 ){ + return 0; + } +#endif + pNew = SyMemBackendAlloc(&(*pBackend), nSize); + if( pNew ){ + SyMemcpy(pSrc, pNew, nSize); + } + return pNew; +} +JX9_PRIVATE char * SyMemBackendStrDup(SyMemBackend *pBackend, const char *zSrc, sxu32 nSize) +{ + char *zDest; + zDest = (char *)SyMemBackendAlloc(&(*pBackend), nSize + 1); + if( zDest ){ + Systrcpy(zDest, nSize+1, zSrc, nSize); + } + return zDest; +} +JX9_PRIVATE sxi32 SyBlobInitFromBuf(SyBlob *pBlob, void *pBuffer, sxu32 nSize) +{ +#if defined(UNTRUST) + if( pBlob == 0 || pBuffer == 0 || nSize < 1 ){ + return SXERR_EMPTY; + } +#endif + pBlob->pBlob = pBuffer; + pBlob->mByte = nSize; + pBlob->nByte = 0; + pBlob->pAllocator = 0; + pBlob->nFlags = SXBLOB_LOCKED|SXBLOB_STATIC; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyBlobInit(SyBlob *pBlob, SyMemBackend *pAllocator) +{ +#if defined(UNTRUST) + if( pBlob == 0 ){ + return SXERR_EMPTY; + } +#endif + pBlob->pBlob = 0; + pBlob->mByte = pBlob->nByte = 0; + pBlob->pAllocator = &(*pAllocator); + pBlob->nFlags = 0; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyBlobReadOnly(SyBlob *pBlob, const void *pData, sxu32 nByte) +{ +#if defined(UNTRUST) + if( pBlob == 0 ){ + return SXERR_EMPTY; + } +#endif + pBlob->pBlob = (void *)pData; + pBlob->nByte = nByte; + pBlob->mByte = 0; + pBlob->nFlags |= SXBLOB_RDONLY; + return SXRET_OK; +} +#ifndef SXBLOB_MIN_GROWTH +#define SXBLOB_MIN_GROWTH 16 +#endif +static sxi32 BlobPrepareGrow(SyBlob *pBlob, sxu32 *pByte) +{ + sxu32 nByte; + void *pNew; + nByte = *pByte; + if( pBlob->nFlags & (SXBLOB_LOCKED|SXBLOB_STATIC) ){ + if ( SyBlobFreeSpace(pBlob) < nByte ){ + *pByte = SyBlobFreeSpace(pBlob); + if( (*pByte) == 0 ){ + return SXERR_SHORT; + } + } + return SXRET_OK; + } + if( pBlob->nFlags & SXBLOB_RDONLY ){ + /* Make a copy of the read-only item */ + if( pBlob->nByte > 0 ){ + pNew = SyMemBackendDup(pBlob->pAllocator, pBlob->pBlob, pBlob->nByte); + if( pNew == 0 ){ + return SXERR_MEM; + } + pBlob->pBlob = pNew; + pBlob->mByte = pBlob->nByte; + }else{ + pBlob->pBlob = 0; + pBlob->mByte = 0; + } + /* Remove the read-only flag */ + pBlob->nFlags &= ~SXBLOB_RDONLY; + } + if( SyBlobFreeSpace(pBlob) >= nByte ){ + return SXRET_OK; + } + if( pBlob->mByte > 0 ){ + nByte = nByte + pBlob->mByte * 2 + SXBLOB_MIN_GROWTH; + }else if ( nByte < SXBLOB_MIN_GROWTH ){ + nByte = SXBLOB_MIN_GROWTH; + } + pNew = SyMemBackendRealloc(pBlob->pAllocator, pBlob->pBlob, nByte); + if( pNew == 0 ){ + return SXERR_MEM; + } + pBlob->pBlob = pNew; + pBlob->mByte = nByte; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyBlobAppend(SyBlob *pBlob, const void *pData, sxu32 nSize) +{ + sxu8 *zBlob; + sxi32 rc; + if( nSize < 1 ){ + return SXRET_OK; + } + rc = BlobPrepareGrow(&(*pBlob), &nSize); + if( SXRET_OK != rc ){ + return rc; + } + if( pData ){ + zBlob = (sxu8 *)pBlob->pBlob ; + zBlob = &zBlob[pBlob->nByte]; + pBlob->nByte += nSize; + SX_MACRO_FAST_MEMCPY(pData, zBlob, nSize); + } + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyBlobNullAppend(SyBlob *pBlob) +{ + sxi32 rc; + sxu32 n; + n = pBlob->nByte; + rc = SyBlobAppend(&(*pBlob), (const void *)"\0", sizeof(char)); + if (rc == SXRET_OK ){ + pBlob->nByte = n; + } + return rc; +} +JX9_PRIVATE sxi32 SyBlobDup(SyBlob *pSrc, SyBlob *pDest) +{ + sxi32 rc = SXRET_OK; + if( pSrc->nByte > 0 ){ + rc = SyBlobAppend(&(*pDest), pSrc->pBlob, pSrc->nByte); + } + return rc; +} +JX9_PRIVATE sxi32 SyBlobReset(SyBlob *pBlob) +{ + pBlob->nByte = 0; + if( pBlob->nFlags & SXBLOB_RDONLY ){ + /* Read-only (Not malloced chunk) */ + pBlob->pBlob = 0; + pBlob->mByte = 0; + pBlob->nFlags &= ~SXBLOB_RDONLY; + } + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyBlobTruncate(SyBlob *pBlob,sxu32 nNewLen) +{ + if( nNewLen < pBlob->nByte ){ + pBlob->nByte = nNewLen; + } + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyBlobRelease(SyBlob *pBlob) +{ + if( (pBlob->nFlags & (SXBLOB_STATIC|SXBLOB_RDONLY)) == 0 && pBlob->mByte > 0 ){ + SyMemBackendFree(pBlob->pAllocator, pBlob->pBlob); + } + pBlob->pBlob = 0; + pBlob->nByte = pBlob->mByte = 0; + pBlob->nFlags = 0; + return SXRET_OK; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyBlobSearch(const void *pBlob, sxu32 nLen, const void *pPattern, sxu32 pLen, sxu32 *pOfft) +{ + const char *zIn = (const char *)pBlob; + const char *zEnd; + sxi32 rc; + if( pLen > nLen ){ + return SXERR_NOTFOUND; + } + zEnd = &zIn[nLen-pLen]; + for(;;){ + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + if( zIn > zEnd ){break;} SX_MACRO_FAST_CMP(zIn, pPattern, pLen, rc); if( rc == 0 ){ if( pOfft ){ *pOfft = (sxu32)(zIn - (const char *)pBlob);} return SXRET_OK; } zIn++; + } + return SXERR_NOTFOUND; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* SyRunTimeApi:sxds.c */ +JX9_PRIVATE sxi32 SySetInit(SySet *pSet, SyMemBackend *pAllocator, sxu32 ElemSize) +{ + pSet->nSize = 0 ; + pSet->nUsed = 0; + pSet->nCursor = 0; + pSet->eSize = ElemSize; + pSet->pAllocator = pAllocator; + pSet->pBase = 0; + pSet->pUserData = 0; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SySetPut(SySet *pSet, const void *pItem) +{ + unsigned char *zbase; + if( pSet->nUsed >= pSet->nSize ){ + void *pNew; + if( pSet->pAllocator == 0 ){ + return SXERR_LOCKED; + } + if( pSet->nSize <= 0 ){ + pSet->nSize = 4; + } + pNew = SyMemBackendRealloc(pSet->pAllocator, pSet->pBase, pSet->eSize * pSet->nSize * 2); + if( pNew == 0 ){ + return SXERR_MEM; + } + pSet->pBase = pNew; + pSet->nSize <<= 1; + } + zbase = (unsigned char *)pSet->pBase; + SX_MACRO_FAST_MEMCPY(pItem, &zbase[pSet->nUsed * pSet->eSize], pSet->eSize); + pSet->nUsed++; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SySetAlloc(SySet *pSet, sxi32 nItem) +{ + if( pSet->nSize > 0 ){ + return SXERR_LOCKED; + } + if( nItem < 8 ){ + nItem = 8; + } + pSet->pBase = SyMemBackendAlloc(pSet->pAllocator, pSet->eSize * nItem); + if( pSet->pBase == 0 ){ + return SXERR_MEM; + } + pSet->nSize = nItem; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SySetReset(SySet *pSet) +{ + pSet->nUsed = 0; + pSet->nCursor = 0; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SySetResetCursor(SySet *pSet) +{ + pSet->nCursor = 0; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SySetGetNextEntry(SySet *pSet, void **ppEntry) +{ + register unsigned char *zSrc; + if( pSet->nCursor >= pSet->nUsed ){ + /* Reset cursor */ + pSet->nCursor = 0; + return SXERR_EOF; + } + zSrc = (unsigned char *)SySetBasePtr(pSet); + if( ppEntry ){ + *ppEntry = (void *)&zSrc[pSet->nCursor * pSet->eSize]; + } + pSet->nCursor++; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SySetRelease(SySet *pSet) +{ + sxi32 rc = SXRET_OK; + if( pSet->pAllocator && pSet->pBase ){ + rc = SyMemBackendFree(pSet->pAllocator, pSet->pBase); + } + pSet->pBase = 0; + pSet->nUsed = 0; + pSet->nCursor = 0; + return rc; +} +JX9_PRIVATE void * SySetPeek(SySet *pSet) +{ + const char *zBase; + if( pSet->nUsed <= 0 ){ + return 0; + } + zBase = (const char *)pSet->pBase; + return (void *)&zBase[(pSet->nUsed - 1) * pSet->eSize]; +} +JX9_PRIVATE void * SySetPop(SySet *pSet) +{ + const char *zBase; + void *pData; + if( pSet->nUsed <= 0 ){ + return 0; + } + zBase = (const char *)pSet->pBase; + pSet->nUsed--; + pData = (void *)&zBase[pSet->nUsed * pSet->eSize]; + return pData; +} +JX9_PRIVATE void * SySetAt(SySet *pSet, sxu32 nIdx) +{ + const char *zBase; + if( nIdx >= pSet->nUsed ){ + /* Out of range */ + return 0; + } + zBase = (const char *)pSet->pBase; + return (void *)&zBase[nIdx * pSet->eSize]; +} +/* Private hash entry */ +struct SyHashEntry_Pr +{ + const void *pKey; /* Hash key */ + sxu32 nKeyLen; /* Key length */ + void *pUserData; /* User private data */ + /* Private fields */ + sxu32 nHash; + SyHash *pHash; + SyHashEntry_Pr *pNext, *pPrev; /* Next and previous entry in the list */ + SyHashEntry_Pr *pNextCollide, *pPrevCollide; /* Collision list */ +}; +#define INVALID_HASH(H) ((H)->apBucket == 0) +JX9_PRIVATE sxi32 SyHashInit(SyHash *pHash, SyMemBackend *pAllocator, ProcHash xHash, ProcCmp xCmp) +{ + SyHashEntry_Pr **apNew; +#if defined(UNTRUST) + if( pHash == 0 ){ + return SXERR_EMPTY; + } +#endif + /* Allocate a new table */ + apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(&(*pAllocator), sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); + if( apNew == 0 ){ + return SXERR_MEM; + } + SyZero((void *)apNew, sizeof(SyHashEntry_Pr *) * SXHASH_BUCKET_SIZE); + pHash->pAllocator = &(*pAllocator); + pHash->xHash = xHash ? xHash : SyBinHash; + pHash->xCmp = xCmp ? xCmp : SyMemcmp; + pHash->pCurrent = pHash->pList = 0; + pHash->nEntry = 0; + pHash->apBucket = apNew; + pHash->nBucketSize = SXHASH_BUCKET_SIZE; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyHashRelease(SyHash *pHash) +{ + SyHashEntry_Pr *pEntry, *pNext; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return SXERR_EMPTY; + } +#endif + pEntry = pHash->pList; + for(;;){ + if( pHash->nEntry == 0 ){ + break; + } + pNext = pEntry->pNext; + SyMemBackendPoolFree(pHash->pAllocator, pEntry); + pEntry = pNext; + pHash->nEntry--; + } + if( pHash->apBucket ){ + SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket); + } + pHash->apBucket = 0; + pHash->nBucketSize = 0; + pHash->pAllocator = 0; + return SXRET_OK; +} +static SyHashEntry_Pr * HashGetEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen) +{ + SyHashEntry_Pr *pEntry; + sxu32 nHash; + + nHash = pHash->xHash(pKey, nKeyLen); + pEntry = pHash->apBucket[nHash & (pHash->nBucketSize - 1)]; + for(;;){ + if( pEntry == 0 ){ + break; + } + if( pEntry->nHash == nHash && pEntry->nKeyLen == nKeyLen && + pHash->xCmp(pEntry->pKey, pKey, nKeyLen) == 0 ){ + return pEntry; + } + pEntry = pEntry->pNextCollide; + } + /* Entry not found */ + return 0; +} +JX9_PRIVATE SyHashEntry * SyHashGet(SyHash *pHash, const void *pKey, sxu32 nKeyLen) +{ + SyHashEntry_Pr *pEntry; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return 0; + } +#endif + if( pHash->nEntry < 1 || nKeyLen < 1 ){ + /* Don't bother hashing, return immediately */ + return 0; + } + pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen); + if( pEntry == 0 ){ + return 0; + } + return (SyHashEntry *)pEntry; +} +static sxi32 HashDeleteEntry(SyHash *pHash, SyHashEntry_Pr *pEntry, void **ppUserData) +{ + sxi32 rc; + if( pEntry->pPrevCollide == 0 ){ + pHash->apBucket[pEntry->nHash & (pHash->nBucketSize - 1)] = pEntry->pNextCollide; + }else{ + pEntry->pPrevCollide->pNextCollide = pEntry->pNextCollide; + } + if( pEntry->pNextCollide ){ + pEntry->pNextCollide->pPrevCollide = pEntry->pPrevCollide; + } + MACRO_LD_REMOVE(pHash->pList, pEntry); + pHash->nEntry--; + if( ppUserData ){ + /* Write a pointer to the user data */ + *ppUserData = pEntry->pUserData; + } + /* Release the entry */ + rc = SyMemBackendPoolFree(pHash->pAllocator, pEntry); + return rc; +} +JX9_PRIVATE sxi32 SyHashDeleteEntry(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void **ppUserData) +{ + SyHashEntry_Pr *pEntry; + sxi32 rc; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) ){ + return SXERR_CORRUPT; + } +#endif + pEntry = HashGetEntry(&(*pHash), pKey, nKeyLen); + if( pEntry == 0 ){ + return SXERR_NOTFOUND; + } + rc = HashDeleteEntry(&(*pHash), pEntry, ppUserData); + return rc; +} +JX9_PRIVATE sxi32 SyHashForEach(SyHash *pHash, sxi32 (*xStep)(SyHashEntry *, void *), void *pUserData) +{ + SyHashEntry_Pr *pEntry; + sxi32 rc; + sxu32 n; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) || xStep == 0){ + return 0; + } +#endif + pEntry = pHash->pList; + for( n = 0 ; n < pHash->nEntry ; n++ ){ + /* Invoke the callback */ + rc = xStep((SyHashEntry *)pEntry, pUserData); + if( rc != SXRET_OK ){ + return rc; + } + /* Point to the next entry */ + pEntry = pEntry->pNext; + } + return SXRET_OK; +} +static sxi32 HashGrowTable(SyHash *pHash) +{ + sxu32 nNewSize = pHash->nBucketSize * 2; + SyHashEntry_Pr *pEntry; + SyHashEntry_Pr **apNew; + sxu32 n, iBucket; + + /* Allocate a new larger table */ + apNew = (SyHashEntry_Pr **)SyMemBackendAlloc(pHash->pAllocator, nNewSize * sizeof(SyHashEntry_Pr *)); + if( apNew == 0 ){ + /* Not so fatal, simply a performance hit */ + return SXRET_OK; + } + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(SyHashEntry_Pr *)); + /* Rehash all entries */ + for( n = 0, pEntry = pHash->pList; n < pHash->nEntry ; n++ ){ + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextCollide = apNew[iBucket]; + if( apNew[iBucket] != 0 ){ + apNew[iBucket]->pPrevCollide = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(pHash->pAllocator, (void *)pHash->apBucket); + pHash->apBucket = apNew; + pHash->nBucketSize = nNewSize; + return SXRET_OK; +} +static sxi32 HashInsert(SyHash *pHash, SyHashEntry_Pr *pEntry) +{ + sxu32 iBucket = pEntry->nHash & (pHash->nBucketSize - 1); + /* Insert the entry in its corresponding bcuket */ + pEntry->pNextCollide = pHash->apBucket[iBucket]; + if( pHash->apBucket[iBucket] != 0 ){ + pHash->apBucket[iBucket]->pPrevCollide = pEntry; + } + pHash->apBucket[iBucket] = pEntry; + /* Link to the entry list */ + MACRO_LD_PUSH(pHash->pList, pEntry); + if( pHash->nEntry == 0 ){ + pHash->pCurrent = pHash->pList; + } + pHash->nEntry++; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyHashInsert(SyHash *pHash, const void *pKey, sxu32 nKeyLen, void *pUserData) +{ + SyHashEntry_Pr *pEntry; + sxi32 rc; +#if defined(UNTRUST) + if( INVALID_HASH(pHash) || pKey == 0 ){ + return SXERR_CORRUPT; + } +#endif + if( pHash->nEntry >= pHash->nBucketSize * SXHASH_FILL_FACTOR ){ + rc = HashGrowTable(&(*pHash)); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Allocate a new hash entry */ + pEntry = (SyHashEntry_Pr *)SyMemBackendPoolAlloc(pHash->pAllocator, sizeof(SyHashEntry_Pr)); + if( pEntry == 0 ){ + return SXERR_MEM; + } + /* Zero the entry */ + SyZero(pEntry, sizeof(SyHashEntry_Pr)); + pEntry->pHash = pHash; + pEntry->pKey = pKey; + pEntry->nKeyLen = nKeyLen; + pEntry->pUserData = pUserData; + pEntry->nHash = pHash->xHash(pEntry->pKey, pEntry->nKeyLen); + /* Finally insert the entry in its corresponding bucket */ + rc = HashInsert(&(*pHash), pEntry); + return rc; +} +/* SyRunTimeApi:sxutils.c */ +JX9_PRIVATE sxi32 SyStrIsNumeric(const char *zSrc, sxu32 nLen, sxu8 *pReal, const char **pzTail) +{ + const char *zCur, *zEnd; +#ifdef UNTRUST + if( SX_EMPTY_STR(zSrc) ){ + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + /* Jump leading white spaces */ + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ + zSrc++; + } + zCur = zSrc; + if( pReal ){ + *pReal = FALSE; + } + for(;;){ + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + if( zSrc >= zEnd || (unsigned char)zSrc[0] >= 0xc0 || !SyisDigit(zSrc[0]) ){ break; } zSrc++; + }; + if( zSrc < zEnd && zSrc > zCur ){ + int c = zSrc[0]; + if( c == '.' ){ + zSrc++; + if( pReal ){ + *pReal = TRUE; + } + if( pzTail ){ + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && (zSrc[0] == 'e' || zSrc[0] == 'E') ){ + zSrc++; + if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ + zSrc++; + } + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ + zSrc++; + } + } + } + }else if( c == 'e' || c == 'E' ){ + zSrc++; + if( pReal ){ + *pReal = TRUE; + } + if( pzTail ){ + if( zSrc < zEnd && (zSrc[0] == '+' || zSrc[0] == '-') ){ + zSrc++; + } + while( zSrc < zEnd && (unsigned char)zSrc[0] < 0xc0 && SyisDigit(zSrc[0]) ){ + zSrc++; + } + } + } + } + if( pzTail ){ + /* Point to the non numeric part */ + *pzTail = zSrc; + } + return zSrc > zCur ? SXRET_OK /* String prefix is numeric */ : SXERR_INVALID /* Not a digit stream */; +} +#define SXINT32_MIN_STR "2147483648" +#define SXINT32_MAX_STR "2147483647" +#define SXINT64_MIN_STR "9223372036854775808" +#define SXINT64_MAX_STR "9223372036854775807" +JX9_PRIVATE sxi32 SyStrToInt32(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) +{ + int isNeg = FALSE; + const char *zEnd; + sxi32 nVal = 0; + sxi16 i; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + i = 10; + if( (sxu32)(zEnd-zSrc) >= 10 ){ + /* Handle overflow */ + i = SyMemcmp(zSrc, (isNeg == TRUE) ? SXINT32_MIN_STR : SXINT32_MAX_STR, nLen) <= 0 ? 10 : 9; + } + for(;;){ + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = (char *)zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi32 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +JX9_PRIVATE sxi32 SyStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) +{ + int isNeg = FALSE; + const char *zEnd; + sxi64 nVal; + sxi16 i; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + i = 19; + if( (sxu32)(zEnd-zSrc) >= 19 ){ + i = SyMemcmp(zSrc, isNeg ? SXINT64_MIN_STR : SXINT64_MAX_STR, 19) <= 0 ? 19 : 18 ; + } + nVal = 0; + for(;;){ + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + if(zSrc >= zEnd || !i || !SyisDigit(zSrc[0])){ break; } nVal = nVal * 10 + ( zSrc[0] - '0' ) ; --i ; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = (char *)zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +JX9_PRIVATE sxi32 SyHexToint(sxi32 c) +{ + switch(c){ + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'A': case 'a': return 10; + case 'B': case 'b': return 11; + case 'C': case 'c': return 12; + case 'D': case 'd': return 13; + case 'E': case 'e': return 14; + case 'F': case 'f': return 15; + } + return -1; +} +JX9_PRIVATE sxi32 SyHexStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) +{ + const char *zIn, *zEnd; + int isNeg = FALSE; + sxi64 nVal = 0; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( *zSrc == '-' || *zSrc == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'x' || zSrc[1] == 'X') ){ + /* Bypass hex prefix */ + zSrc += sizeof(char) * 2; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + zIn = zSrc; + for(;;){ + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + if(zSrc >= zEnd || !SyisHex(zSrc[0]) || (int)(zSrc-zIn) > 15) break; nVal = nVal * 16 + SyHexToint(zSrc[0]); zSrc++ ; + } + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; +} +JX9_PRIVATE sxi32 SyOctalStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) +{ + const char *zIn, *zEnd; + int isNeg = FALSE; + sxi64 nVal = 0; + int c; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + zIn = zSrc; + for(;;){ + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + if(zSrc >= zEnd || !SyisDigit(zSrc[0])){ break; } if( (c=zSrc[0]-'0') > 7 || (int)(zSrc-zIn) > 20){ break;} nVal = nVal * 8 + c; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +JX9_PRIVATE sxi32 SyBinaryStrToInt64(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) +{ + const char *zIn, *zEnd; + int isNeg = FALSE; + sxi64 nVal = 0; + int c; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxi32 *)pOutVal = 0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while(zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+' ) ){ + isNeg = (zSrc[0] == '-') ? TRUE :FALSE; + zSrc++; + } + if( zSrc < &zEnd[-2] && zSrc[0] == '0' && (zSrc[1] == 'b' || zSrc[1] == 'B') ){ + /* Bypass binary prefix */ + zSrc += sizeof(char) * 2; + } + /* Skip leading zero */ + while(zSrc < zEnd && zSrc[0] == '0' ){ + zSrc++; + } + zIn = zSrc; + for(;;){ + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + if(zSrc >= zEnd || (zSrc[0] != '1' && zSrc[0] != '0') || (int)(zSrc-zIn) > 62){ break; } c = zSrc[0] - '0'; nVal = (nVal << 1) + c; zSrc++; + } + /* Skip trailing spaces */ + while(zSrc < zEnd && SyisSpace(zSrc[0])){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + if( isNeg == TRUE && nVal != 0 ){ + nVal = -nVal; + } + *(sxi64 *)pOutVal = nVal; + } + return (zSrc >= zEnd) ? SXRET_OK : SXERR_SYNTAX; +} +JX9_PRIVATE sxi32 SyStrToReal(const char *zSrc, sxu32 nLen, void * pOutVal, const char **zRest) +{ +#define SXDBL_DIG 15 +#define SXDBL_MAX_EXP 308 +#define SXDBL_MIN_EXP_PLUS 307 + static const sxreal aTab[] = { + 10, + 1.0e2, + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 + }; + sxu8 neg = FALSE; + sxreal Val = 0.0; + const char *zEnd; + sxi32 Lim, exp; + sxreal *p = 0; +#ifdef UNTRUST + if( SX_EMPTY_STR(zSrc) ){ + if( pOutVal ){ + *(sxreal *)pOutVal = 0.0; + } + return SXERR_EMPTY; + } +#endif + zEnd = &zSrc[nLen]; + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zSrc < zEnd && (zSrc[0] == '-' || zSrc[0] == '+' ) ){ + neg = zSrc[0] == '-' ? TRUE : FALSE ; + zSrc++; + } + Lim = SXDBL_DIG ; + for(;;){ + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; zSrc++ ; --Lim; + } + if( zSrc < zEnd && ( zSrc[0] == '.' || zSrc[0] == ',' ) ){ + sxreal dec = 1.0; + zSrc++; + for(;;){ + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + if(zSrc >= zEnd||!Lim||!SyisDigit(zSrc[0])) break ; Val = Val * 10.0 + (zSrc[0] - '0') ; dec *= 10.0; zSrc++ ;--Lim; + } + Val /= dec; + } + if( neg == TRUE && Val != 0.0 ) { + Val = -Val ; + } + if( Lim <= 0 ){ + /* jump overflow digit */ + while( zSrc < zEnd ){ + if( zSrc[0] == 'e' || zSrc[0] == 'E' ){ + break; + } + zSrc++; + } + } + neg = FALSE; + if( zSrc < zEnd && ( zSrc[0] == 'e' || zSrc[0] == 'E' ) ){ + zSrc++; + if( zSrc < zEnd && ( zSrc[0] == '-' || zSrc[0] == '+') ){ + neg = zSrc[0] == '-' ? TRUE : FALSE ; + zSrc++; + } + exp = 0; + while( zSrc < zEnd && SyisDigit(zSrc[0]) && exp < SXDBL_MAX_EXP ){ + exp = exp * 10 + (zSrc[0] - '0'); + zSrc++; + } + if( neg ){ + if( exp > SXDBL_MIN_EXP_PLUS ) exp = SXDBL_MIN_EXP_PLUS ; + }else if ( exp > SXDBL_MAX_EXP ){ + exp = SXDBL_MAX_EXP; + } + for( p = (sxreal *)aTab ; exp ; exp >>= 1 , p++ ){ + if( exp & 01 ){ + if( neg ){ + Val /= *p ; + }else{ + Val *= *p; + } + } + } + } + while( zSrc < zEnd && SyisSpace(zSrc[0]) ){ + zSrc++; + } + if( zRest ){ + *zRest = zSrc; + } + if( pOutVal ){ + *(sxreal *)pOutVal = Val; + } + return zSrc >= zEnd ? SXRET_OK : SXERR_SYNTAX; +} +/* SyRunTimeApi:sxlib.c */ +JX9_PRIVATE sxu32 SyBinHash(const void *pSrc, sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyBase64Encode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) +{ + static const unsigned char zBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + unsigned char *zIn = (unsigned char *)zSrc; + unsigned char z64[4]; + sxu32 i; + sxi32 rc; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) || xConsumer == 0){ + return SXERR_EMPTY; + } +#endif + for(i = 0; i + 2 < nLen; i += 3){ + z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; + z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; + z64[2] = zBase64[( ((zIn[i+1] & 0x0F) << 2) | (zIn[i + 2] >> 6) ) & 0x3F]; + z64[3] = zBase64[ zIn[i + 2] & 0x3F]; + + rc = xConsumer((const void *)z64, sizeof(z64), pUserData); + if( rc != SXRET_OK ){return SXERR_ABORT;} + + } + if ( i+1 < nLen ){ + z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; + z64[1] = zBase64[( ((zIn[i] & 0x03) << 4) | (zIn[i+1] >> 4)) & 0x3F]; + z64[2] = zBase64[(zIn[i+1] & 0x0F) << 2 ]; + z64[3] = '='; + + rc = xConsumer((const void *)z64, sizeof(z64), pUserData); + if( rc != SXRET_OK ){return SXERR_ABORT;} + + }else if( i < nLen ){ + z64[0] = zBase64[(zIn[i] >> 2) & 0x3F]; + z64[1] = zBase64[(zIn[i] & 0x03) << 4]; + z64[2] = '='; + z64[3] = '='; + + rc = xConsumer((const void *)z64, sizeof(z64), pUserData); + if( rc != SXRET_OK ){return SXERR_ABORT;} + } + + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyBase64Decode(const char *zB64, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) +{ + static const sxu32 aBase64Trans[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, + 0, 0, 0 + }; + sxu32 n, w, x, y, z; + sxi32 rc; + unsigned char zOut[10]; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zB64) || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + while(nLen > 0 && zB64[nLen - 1] == '=' ){ + nLen--; + } + for( n = 0 ; n+3>4) & 0x03); + zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); + zOut[2] = ((y<<6) & 0xC0) | (z & 0x3F); + + rc = xConsumer((const void *)zOut, sizeof(unsigned char)*3, pUserData); + if( rc != SXRET_OK ){ return SXERR_ABORT;} + } + if( n+2 < nLen ){ + w = aBase64Trans[zB64[n] & 0x7F]; + x = aBase64Trans[zB64[n+1] & 0x7F]; + y = aBase64Trans[zB64[n+2] & 0x7F]; + + zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); + zOut[1] = ((x<<4) & 0xF0) | ((y>>2) & 0x0F); + + rc = xConsumer((const void *)zOut, sizeof(unsigned char)*2, pUserData); + if( rc != SXRET_OK ){ return SXERR_ABORT;} + }else if( n+1 < nLen ){ + w = aBase64Trans[zB64[n] & 0x7F]; + x = aBase64Trans[zB64[n+1] & 0x7F]; + + zOut[0] = ((w<<2) & 0xFC) | ((x>>4) & 0x03); + + rc = xConsumer((const void *)zOut, sizeof(unsigned char)*1, pUserData); + if( rc != SXRET_OK ){ return SXERR_ABORT;} + } + return SXRET_OK; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +#define INVALID_LEXER(LEX) ( LEX == 0 || LEX->xTokenizer == 0 ) +JX9_PRIVATE sxi32 SyLexInit(SyLex *pLex, SySet *pSet, ProcTokenizer xTokenizer, void *pUserData) +{ + SyStream *pStream; +#if defined (UNTRUST) + if ( pLex == 0 || xTokenizer == 0 ){ + return SXERR_CORRUPT; + } +#endif + pLex->pTokenSet = 0; + /* Initialize lexer fields */ + if( pSet ){ + if ( SySetElemSize(pSet) != sizeof(SyToken) ){ + return SXERR_INVALID; + } + pLex->pTokenSet = pSet; + } + pStream = &pLex->sStream; + pLex->xTokenizer = xTokenizer; + pLex->pUserData = pUserData; + + pStream->nLine = 1; + pStream->nIgn = 0; + pStream->zText = pStream->zEnd = 0; + pStream->pSet = pSet; + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyLexTokenizeInput(SyLex *pLex, const char *zInput, sxu32 nLen, void *pCtxData, ProcSort xSort, ProcCmp xCmp) +{ + const unsigned char *zCur; + SyStream *pStream; + SyToken sToken; + sxi32 rc; +#if defined (UNTRUST) + if ( INVALID_LEXER(pLex) || zInput == 0 ){ + return SXERR_CORRUPT; + } +#endif + pStream = &pLex->sStream; + /* Point to the head of the input */ + pStream->zText = pStream->zInput = (const unsigned char *)zInput; + /* Point to the end of the input */ + pStream->zEnd = &pStream->zInput[nLen]; + for(;;){ + if( pStream->zText >= pStream->zEnd ){ + /* End of the input reached */ + break; + } + zCur = pStream->zText; + /* Call the tokenizer callback */ + rc = pLex->xTokenizer(pStream, &sToken, pLex->pUserData, pCtxData); + if( rc != SXRET_OK && rc != SXERR_CONTINUE ){ + /* Tokenizer callback request an operation abort */ + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + break; + } + if( rc == SXERR_CONTINUE ){ + /* Request to ignore this token */ + pStream->nIgn++; + }else if( pLex->pTokenSet ){ + /* Put the token in the set */ + rc = SySetPut(pLex->pTokenSet, (const void *)&sToken); + if( rc != SXRET_OK ){ + break; + } + } + if( zCur >= pStream->zText ){ + /* Automatic advance of the stream cursor */ + pStream->zText = &zCur[1]; + } + } + if( xSort && pLex->pTokenSet ){ + SyToken *aToken = (SyToken *)SySetBasePtr(pLex->pTokenSet); + /* Sort the extrated tokens */ + if( xCmp == 0 ){ + /* Use a default comparison function */ + xCmp = SyMemcmp; + } + xSort(aToken, SySetUsed(pLex->pTokenSet), sizeof(SyToken), xCmp); + } + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyLexRelease(SyLex *pLex) +{ + sxi32 rc = SXRET_OK; +#if defined (UNTRUST) + if ( INVALID_LEXER(pLex) ){ + return SXERR_CORRUPT; + } +#else + SXUNUSED(pLex); /* Prevent compiler warning */ +#endif + return rc; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +#define SAFE_HTTP(C) (SyisAlphaNum(c) || c == '_' || c == '-' || c == '$' || c == '.' ) +JX9_PRIVATE sxi32 SyUriEncode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData) +{ + unsigned char *zIn = (unsigned char *)zSrc; + unsigned char zHex[3] = { '%', 0, 0 }; + unsigned char zOut[2]; + unsigned char *zCur, *zEnd; + sxi32 c; + sxi32 rc; +#ifdef UNTRUST + if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + rc = SXRET_OK; + zEnd = &zIn[nLen]; zCur = zIn; + for(;;){ + if( zCur >= zEnd ){ + if( zCur != zIn ){ + rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData); + } + break; + } + c = zCur[0]; + if( SAFE_HTTP(c) ){ + zCur++; continue; + } + if( zCur != zIn && SXRET_OK != (rc = xConsumer(zIn, (sxu32)(zCur-zIn), pUserData))){ + break; + } + if( c == ' ' ){ + zOut[0] = '+'; + rc = xConsumer((const void *)zOut, sizeof(unsigned char), pUserData); + }else{ + zHex[1] = "0123456789ABCDEF"[(c >> 4) & 0x0F]; + zHex[2] = "0123456789ABCDEF"[c & 0x0F]; + rc = xConsumer(zHex, sizeof(zHex), pUserData); + } + if( SXRET_OK != rc ){ + break; + } + zIn = &zCur[1]; zCur = zIn ; + } + return rc == SXRET_OK ? SXRET_OK : SXERR_ABORT; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +static sxi32 SyAsciiToHex(sxi32 c) +{ + if( c >= 'a' && c <= 'f' ){ + c += 10 - 'a'; + return c; + } + if( c >= '0' && c <= '9' ){ + c -= '0'; + return c; + } + if( c >= 'A' && c <= 'F') { + c += 10 - 'A'; + return c; + } + return 0; +} +JX9_PRIVATE sxi32 SyUriDecode(const char *zSrc, sxu32 nLen, ProcConsumer xConsumer, void *pUserData, int bUTF8) +{ + static const sxu8 Utf8Trans[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00 + }; + const char *zIn = zSrc; + const char *zEnd; + const char *zCur; + sxu8 *zOutPtr; + sxu8 zOut[10]; + sxi32 c, d; + sxi32 rc; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zSrc) || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + rc = SXRET_OK; + zEnd = &zSrc[nLen]; + zCur = zIn; + for(;;){ + while(zCur < zEnd && zCur[0] != '%' && zCur[0] != '+' ){ + zCur++; + } + if( zCur != zIn ){ + /* Consume input */ + rc = xConsumer(zIn, (unsigned int)(zCur-zIn), pUserData); + if( rc != SXRET_OK ){ + /* User consumer routine request an operation abort */ + break; + } + } + if( zCur >= zEnd ){ + rc = SXRET_OK; + break; + } + /* Decode unsafe HTTP characters */ + zOutPtr = zOut; + if( zCur[0] == '+' ){ + *zOutPtr++ = ' '; + zCur++; + }else{ + if( &zCur[2] >= zEnd ){ + rc = SXERR_OVERFLOW; + break; + } + c = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); + zCur += 3; + if( c < 0x000C0 ){ + *zOutPtr++ = (sxu8)c; + }else{ + c = Utf8Trans[c-0xC0]; + while( zCur[0] == '%' ){ + d = (SyAsciiToHex(zCur[1]) <<4) | SyAsciiToHex(zCur[2]); + if( (d&0xC0) != 0x80 ){ + break; + } + c = (c<<6) + (0x3f & d); + zCur += 3; + } + if( bUTF8 == FALSE ){ + *zOutPtr++ = (sxu8)c; + }else{ + SX_WRITE_UTF8(zOutPtr, c); + } + } + + } + /* Consume the decoded characters */ + rc = xConsumer((const void *)zOut, (unsigned int)(zOutPtr-zOut), pUserData); + if( rc != SXRET_OK ){ + break; + } + /* Synchronize pointers */ + zIn = zCur; + } + return rc; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +static const char *zEngDay[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" +}; +static const char *zEngMonth[] = { + "January", "February", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December" +}; +static const char * GetDay(sxi32 i) +{ + return zEngDay[ i % 7 ]; +} +static const char * GetMonth(sxi32 i) +{ + return zEngMonth[ i % 12 ]; +} +JX9_PRIVATE const char * SyTimeGetDay(sxi32 iDay) +{ + return GetDay(iDay); +} +JX9_PRIVATE const char * SyTimeGetMonth(sxi32 iMonth) +{ + return GetMonth(iMonth); +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* SyRunTimeApi: sxfmt.c */ +#define SXFMT_BUFSIZ 1024 /* Conversion buffer size */ +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +#define SXFMT_RADIX 1 /* Integer types.%d, %x, %o, and so forth */ +#define SXFMT_FLOAT 2 /* Floating point.%f */ +#define SXFMT_EXP 3 /* Exponentional notation.%e and %E */ +#define SXFMT_GENERIC 4 /* Floating or exponential, depending on exponent.%g */ +#define SXFMT_SIZE 5 /* Total number of characters processed so far.%n */ +#define SXFMT_STRING 6 /* Strings.%s */ +#define SXFMT_PERCENT 7 /* Percent symbol.%% */ +#define SXFMT_CHARX 8 /* Characters.%c */ +#define SXFMT_ERROR 9 /* Used to indicate no such conversion type */ +/* Extension by Symisc Systems */ +#define SXFMT_RAWSTR 13 /* %z Pointer to raw string (SyString *) */ +#define SXFMT_UNUSED 15 +/* +** Allowed values for SyFmtInfo.flags +*/ +#define SXFLAG_SIGNED 0x01 +#define SXFLAG_UNSIGNED 0x02 +/* Allowed values for SyFmtConsumer.nType */ +#define SXFMT_CONS_PROC 1 /* Consumer is a procedure */ +#define SXFMT_CONS_STR 2 /* Consumer is a managed string */ +#define SXFMT_CONS_FILE 5 /* Consumer is an open File */ +#define SXFMT_CONS_BLOB 6 /* Consumer is a BLOB */ +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct SyFmtInfo SyFmtInfo; +struct SyFmtInfo +{ + char fmttype; /* The format field code letter [i.e: 'd', 's', 'x'] */ + sxu8 base; /* The base for radix conversion */ + int flags; /* One or more of SXFLAG_ constants below */ + sxu8 type; /* Conversion paradigm */ + char *charset; /* The character set for conversion */ + char *prefix; /* Prefix on non-zero values in alt format */ +}; +typedef struct SyFmtConsumer SyFmtConsumer; +struct SyFmtConsumer +{ + sxu32 nLen; /* Total output length */ + sxi32 nType; /* Type of the consumer see below */ + sxi32 rc; /* Consumer return value;Abort processing if rc != SXRET_OK */ + union{ + struct{ + ProcConsumer xUserConsumer; + void *pUserData; + }sFunc; + SyBlob *pBlob; + }uConsumer; +}; +#ifndef SX_OMIT_FLOATINGPOINT +static int getdigit(sxlongreal *val, int *cnt) +{ + sxlongreal d; + int digit; + + if( (*cnt)++ >= 16 ){ + return '0'; + } + digit = (int)*val; + d = digit; + *val = (*val - d)*10.0; + return digit + '0' ; +} +#endif /* SX_OMIT_FLOATINGPOINT */ +/* + * The following routine was taken from the SQLITE2 source tree and was + * extended by Symisc Systems to fit its need. + * Status: Public Domain + */ +static sxi32 InternFormat(ProcConsumer xConsumer, void *pUserData, const char *zFormat, va_list ap) +{ + /* + * The following table is searched linearly, so it is good to put the most frequently + * used conversion types first. + */ +static const SyFmtInfo aFmt[] = { + { 'd', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, + { 's', 0, 0, SXFMT_STRING, 0, 0 }, + { 'c', 0, 0, SXFMT_CHARX, 0, 0 }, + { 'x', 16, 0, SXFMT_RADIX, "0123456789abcdef", "x0" }, + { 'X', 16, 0, SXFMT_RADIX, "0123456789ABCDEF", "X0" }, + /* -- Extensions by Symisc Systems -- */ + { 'z', 0, 0, SXFMT_RAWSTR, 0, 0 }, /* Pointer to a raw string (SyString *) */ + { 'B', 2, 0, SXFMT_RADIX, "01", "b0"}, + /* -- End of Extensions -- */ + { 'o', 8, 0, SXFMT_RADIX, "01234567", "0" }, + { 'u', 10, 0, SXFMT_RADIX, "0123456789", 0 }, +#ifndef SX_OMIT_FLOATINGPOINT + { 'f', 0, SXFLAG_SIGNED, SXFMT_FLOAT, 0, 0 }, + { 'e', 0, SXFLAG_SIGNED, SXFMT_EXP, "e", 0 }, + { 'E', 0, SXFLAG_SIGNED, SXFMT_EXP, "E", 0 }, + { 'g', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "e", 0 }, + { 'G', 0, SXFLAG_SIGNED, SXFMT_GENERIC, "E", 0 }, +#endif + { 'i', 10, SXFLAG_SIGNED, SXFMT_RADIX, "0123456789", 0 }, + { 'n', 0, 0, SXFMT_SIZE, 0, 0 }, + { '%', 0, 0, SXFMT_PERCENT, 0, 0 }, + { 'p', 10, 0, SXFMT_RADIX, "0123456789", 0 } +}; + int c; /* Next character in the format string */ + char *bufpt; /* Pointer to the conversion buffer */ + int precision; /* Precision of the current field */ + int length; /* Length of the field */ + int idx; /* A general purpose loop counter */ + int width; /* Width of the current field */ + sxu8 flag_leftjustify; /* True if "-" flag is present */ + sxu8 flag_plussign; /* True if "+" flag is present */ + sxu8 flag_blanksign; /* True if " " flag is present */ + sxu8 flag_alternateform; /* True if "#" flag is present */ + sxu8 flag_zeropad; /* True if field width constant starts with zero */ + sxu8 flag_long; /* True if "l" flag is present */ + sxi64 longvalue; /* Value for integer types */ + const SyFmtInfo *infop; /* Pointer to the appropriate info structure */ + char buf[SXFMT_BUFSIZ]; /* Conversion buffer */ + char prefix; /* Prefix character."+" or "-" or " " or '\0'.*/ + sxu8 errorflag = 0; /* True if an error is encountered */ + sxu8 xtype; /* Conversion paradigm */ + char *zExtra; + static char spaces[] = " "; +#define etSPACESIZE ((int)sizeof(spaces)-1) +#ifndef SX_OMIT_FLOATINGPOINT + sxlongreal realvalue; /* Value for real types */ + int exp; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + sxu8 flag_dp; /* True if decimal point should be shown */ + sxu8 flag_rtz; /* True if trailing zeros should be removed */ + sxu8 flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ +#endif + int rc; + + length = 0; + bufpt = 0; + for(; (c=(*zFormat))!=0; ++zFormat){ + if( c!='%' ){ + unsigned int amt; + bufpt = (char *)zFormat; + amt = 1; + while( (c=(*++zFormat))!='%' && c!=0 ) amt++; + rc = xConsumer((const void *)bufpt, amt, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + if( c==0 ){ + return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; + } + } + if( (c=(*++zFormat))==0 ){ + errorflag = 1; + rc = xConsumer("%", sizeof("%")-1, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + return errorflag > 0 ? SXERR_FORMAT : SXRET_OK; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = + flag_alternateform = flag_zeropad = 0; + do{ + switch( c ){ + case '-': flag_leftjustify = 1; c = 0; break; + case '+': flag_plussign = 1; c = 0; break; + case ' ': flag_blanksign = 1; c = 0; break; + case '#': flag_alternateform = 1; c = 0; break; + case '0': flag_zeropad = 1; c = 0; break; + default: break; + } + }while( c==0 && (c=(*++zFormat))!=0 ); + /* Get the field width */ + width = 0; + if( c=='*' ){ + width = va_arg(ap, int); + if( width<0 ){ + flag_leftjustify = 1; + width = -width; + } + c = *++zFormat; + }else{ + while( c>='0' && c<='9' ){ + width = width*10 + c - '0'; + c = *++zFormat; + } + } + if( width > SXFMT_BUFSIZ-10 ){ + width = SXFMT_BUFSIZ-10; + } + /* Get the precision */ + precision = -1; + if( c=='.' ){ + precision = 0; + c = *++zFormat; + if( c=='*' ){ + precision = va_arg(ap, int); + if( precision<0 ) precision = -precision; + c = *++zFormat; + }else{ + while( c>='0' && c<='9' ){ + precision = precision*10 + c - '0'; + c = *++zFormat; + } + } + } + /* Get the conversion type modifier */ + flag_long = 0; + if( c=='l' || c == 'q' /* BSD quad (expect a 64-bit integer) */ ){ + flag_long = (c == 'q') ? 2 : 1; + c = *++zFormat; + if( c == 'l' ){ + /* Standard printf emulation 'lld' (expect a 64bit integer) */ + flag_long = 2; + } + } + /* Fetch the info entry for the field */ + infop = 0; + xtype = SXFMT_ERROR; + for(idx=0; idx< (int)SX_ARRAYSIZE(aFmt); idx++){ + if( c==aFmt[idx].fmttype ){ + infop = &aFmt[idx]; + xtype = infop->type; + break; + } + } + zExtra = 0; + + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** flag_long TRUE if the letter 'l' (ell) or 'q'(BSD quad) prefixed + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width.This is + ** always non-negative.Zero is the default. + ** precision The specified precision.The default + ** is -1. + ** xtype The object of the conversion. + ** infop Pointer to the appropriate info struct. + */ + switch( xtype ){ + case SXFMT_RADIX: + if( flag_long > 0 ){ + if( flag_long > 1 ){ + /* BSD quad: expect a 64-bit integer */ + longvalue = va_arg(ap, sxi64); + }else{ + longvalue = va_arg(ap, sxlong); + } + }else{ + if( infop->flags & SXFLAG_SIGNED ){ + longvalue = va_arg(ap, sxi32); + }else{ + longvalue = va_arg(ap, sxu32); + } + } + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; +#if 1 + /* For the format %#x, the value zero is printed "0" not "0x0". + ** I think this is stupid.*/ + if( longvalue==0 ) flag_alternateform = 0; +#else + /* More sensible: turn off the prefix for octal (to prevent "00"), + ** but leave the prefix for hex.*/ + if( longvalue==0 && infop->base==8 ) flag_alternateform = 0; +#endif + if( infop->flags & SXFLAG_SIGNED ){ + if( longvalue<0 ){ + longvalue = -longvalue; + /* Ticket 1433-003 */ + if( longvalue < 0 ){ + /* Overflow */ + longvalue= 0x7FFFFFFFFFFFFFFF; + } + prefix = '-'; + }else if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + }else{ + if( longvalue<0 ){ + longvalue = -longvalue; + /* Ticket 1433-003 */ + if( longvalue < 0 ){ + /* Overflow */ + longvalue= 0x7FFFFFFFFFFFFFFF; + } + } + prefix = 0; + } + if( flag_zeropad && precisioncharset; + base = infop->base; + do{ /* Convert to ascii */ + *(--bufpt) = cset[longvalue%base]; + longvalue = longvalue/base; + }while( longvalue>0 ); + } + length = (int)(&buf[SXFMT_BUFSIZ-1]-bufpt); + for(idx=precision-length; idx>0; idx--){ + *(--bufpt) = '0'; /* Zero pad */ + } + if( prefix ) *(--bufpt) = prefix; /* Add sign */ + if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ + char *pre, x; + pre = infop->prefix; + if( *bufpt!=pre[0] ){ + for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x; + } + } + length = (int)(&buf[SXFMT_BUFSIZ-1]-bufpt); + break; + case SXFMT_FLOAT: + case SXFMT_EXP: + case SXFMT_GENERIC: +#ifndef SX_OMIT_FLOATINGPOINT + realvalue = va_arg(ap, double); + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>SXFMT_BUFSIZ-40) precision = SXFMT_BUFSIZ-40; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( infop->type==SXFMT_GENERIC && precision>0 ) precision--; + rounder = 0.0; +#if 0 + /* Rounding works like BSD when the constant 0.4999 is used.Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( infop->type==SXFMT_FLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; } + if( exp>350 || exp<-350 ){ + bufpt = "NaN"; + length = 3; + break; + } + } + bufpt = buf; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==SXFMT_EXP; + if( xtype!=SXFMT_FLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==SXFMT_GENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = SXFMT_EXP; + }else{ + precision = precision - exp; + xtype = SXFMT_FLOAT; + } + }else{ + flag_rtz = 0; + } + /* + ** The "exp+precision" test causes output to be of type etEXP if + ** the precision is too large to fit in buf[]. + */ + nsd = 0; + if( xtype==SXFMT_FLOAT && exp+precision0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */ + else for(; exp>=0; exp--) *(bufpt++) = (char)getdigit(&realvalue, &nsd); + if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */ + for(exp++; exp<0 && precision>0; precision--, exp++){ + *(bufpt++) = '0'; + } + while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd); + *(bufpt--) = 0; /* Null terminate */ + if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + }else{ /* etEXP or etGENERIC */ + flag_dp = (precision>0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + *(bufpt++) = (char)getdigit(&realvalue, &nsd); /* First digit */ + if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */ + while( (precision--)>0 ) *(bufpt++) = (char)getdigit(&realvalue, &nsd); + bufpt--; /* point to last digit */ + if( flag_rtz && flag_dp ){ /* Remove tail zeros */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + if( exp || flag_exp ){ + *(bufpt++) = infop->charset[0]; + if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */ + else { *(bufpt++) = '+'; } + if( exp>=100 ){ + *(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */ + exp %= 100; + } + *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */ + *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */ + } + } + /* The converted number is in buf[] and zero terminated.Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions.*/ + length = (int)(bufpt-buf); + bufpt = buf; + + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + bufpt[i] = bufpt[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) bufpt[i++] = '0'; + length = width; + } +#else + bufpt = " "; + length = (int)sizeof(" ") - 1; +#endif /* SX_OMIT_FLOATINGPOINT */ + break; + case SXFMT_SIZE:{ + int *pSize = va_arg(ap, int *); + *pSize = ((SyFmtConsumer *)pUserData)->nLen; + length = width = 0; + } + break; + case SXFMT_PERCENT: + buf[0] = '%'; + bufpt = buf; + length = 1; + break; + case SXFMT_CHARX: + c = va_arg(ap, int); + buf[0] = (char)c; + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>SXFMT_BUFSIZ-40 ) precision = SXFMT_BUFSIZ-40; + if( precision>=0 ){ + for(idx=1; idx=0 && precisionzString == 0 ){ + bufpt = " "; + length = (int)sizeof(char); + break; + } + bufpt = (char *)pStr->zString; + length = (int)pStr->nByte; + break; + } + case SXFMT_ERROR: + buf[0] = '?'; + bufpt = buf; + length = (int)sizeof(char); + if( c==0 ) zFormat--; + break; + }/* End switch over the format type */ + /* + ** The text of the conversion is pointed to by "bufpt" and is + ** "length" characters long.The field width is "width".Do + ** the output. + */ + if( !flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(spaces, etSPACESIZE, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(spaces, (unsigned int)nspace, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + if( length>0 ){ + rc = xConsumer(bufpt, (unsigned int)length, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + if( flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + while( nspace>=etSPACESIZE ){ + rc = xConsumer(spaces, etSPACESIZE, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + nspace -= etSPACESIZE; + } + if( nspace>0 ){ + rc = xConsumer(spaces, (unsigned int)nspace, pUserData); + if( rc != SXRET_OK ){ + return SXERR_ABORT; /* Consumer routine request an operation abort */ + } + } + } + } + }/* End for loop over the format string */ + return errorflag ? SXERR_FORMAT : SXRET_OK; +} +static sxi32 FormatConsumer(const void *pSrc, unsigned int nLen, void *pData) +{ + SyFmtConsumer *pConsumer = (SyFmtConsumer *)pData; + sxi32 rc = SXERR_ABORT; + switch(pConsumer->nType){ + case SXFMT_CONS_PROC: + /* User callback */ + rc = pConsumer->uConsumer.sFunc.xUserConsumer(pSrc, nLen, pConsumer->uConsumer.sFunc.pUserData); + break; + case SXFMT_CONS_BLOB: + /* Blob consumer */ + rc = SyBlobAppend(pConsumer->uConsumer.pBlob, pSrc, (sxu32)nLen); + break; + default: + /* Unknown consumer */ + break; + } + /* Update total number of bytes consumed so far */ + pConsumer->nLen += nLen; + pConsumer->rc = rc; + return rc; +} +static sxi32 FormatMount(sxi32 nType, void *pConsumer, ProcConsumer xUserCons, void *pUserData, sxu32 *pOutLen, const char *zFormat, va_list ap) +{ + SyFmtConsumer sCons; + sCons.nType = nType; + sCons.rc = SXRET_OK; + sCons.nLen = 0; + if( pOutLen ){ + *pOutLen = 0; + } + switch(nType){ + case SXFMT_CONS_PROC: +#if defined(UNTRUST) + if( xUserCons == 0 ){ + return SXERR_EMPTY; + } +#endif + sCons.uConsumer.sFunc.xUserConsumer = xUserCons; + sCons.uConsumer.sFunc.pUserData = pUserData; + break; + case SXFMT_CONS_BLOB: + sCons.uConsumer.pBlob = (SyBlob *)pConsumer; + break; + default: + return SXERR_UNKNOWN; + } + InternFormat(FormatConsumer, &sCons, zFormat, ap); + if( pOutLen ){ + *pOutLen = sCons.nLen; + } + return sCons.rc; +} +JX9_PRIVATE sxi32 SyProcFormat(ProcConsumer xConsumer, void *pData, const char *zFormat, ...) +{ + va_list ap; + sxi32 rc; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return SXERR_EMPTY; + } +#endif + va_start(ap, zFormat); + rc = FormatMount(SXFMT_CONS_PROC, 0, xConsumer, pData, 0, zFormat, ap); + va_end(ap); + return rc; +} +JX9_PRIVATE sxu32 SyBlobFormat(SyBlob *pBlob, const char *zFormat, ...) +{ + va_list ap; + sxu32 n; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return 0; + } +#endif + va_start(ap, zFormat); + FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); + va_end(ap); + return n; +} +JX9_PRIVATE sxu32 SyBlobFormatAp(SyBlob *pBlob, const char *zFormat, va_list ap) +{ + sxu32 n = 0; /* cc warning */ +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return 0; + } +#endif + FormatMount(SXFMT_CONS_BLOB, &(*pBlob), 0, 0, &n, zFormat, ap); + return n; +} +JX9_PRIVATE sxu32 SyBufferFormat(char *zBuf, sxu32 nLen, const char *zFormat, ...) +{ + SyBlob sBlob; + va_list ap; + sxu32 n; +#if defined(UNTRUST) + if( SX_EMPTY_STR(zFormat) ){ + return 0; + } +#endif + if( SXRET_OK != SyBlobInitFromBuf(&sBlob, zBuf, nLen - 1) ){ + return 0; + } + va_start(ap, zFormat); + FormatMount(SXFMT_CONS_BLOB, &sBlob, 0, 0, 0, zFormat, ap); + va_end(ap); + n = SyBlobLength(&sBlob); + /* Append the null terminator */ + sBlob.mByte++; + SyBlobAppend(&sBlob, "\0", sizeof(char)); + return n; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +/* + * Zip File Format: + * + * Byte order: Little-endian + * + * [Local file header + Compressed data [+ Extended local header]?]* + * [Central directory]* + * [End of central directory record] + * + * Local file header:* + * Offset Length Contents + * 0 4 bytes Local file header signature (0x04034b50) + * 4 2 bytes Version needed to extract + * 6 2 bytes General purpose bit flag + * 8 2 bytes Compression method + * 10 2 bytes Last mod file time + * 12 2 bytes Last mod file date + * 14 4 bytes CRC-32 + * 18 4 bytes Compressed size (n) + * 22 4 bytes Uncompressed size + * 26 2 bytes Filename length (f) + * 28 2 bytes Extra field length (e) + * 30 (f)bytes Filename + * (e)bytes Extra field + * (n)bytes Compressed data + * + * Extended local header:* + * Offset Length Contents + * 0 4 bytes Extended Local file header signature (0x08074b50) + * 4 4 bytes CRC-32 + * 8 4 bytes Compressed size + * 12 4 bytes Uncompressed size + * + * Extra field:?(if any) + * Offset Length Contents + * 0 2 bytes Header ID (0x001 until 0xfb4a) see extended appnote from Info-zip + * 2 2 bytes Data size (g) + * (g) bytes (g) bytes of extra field + * + * Central directory:* + * Offset Length Contents + * 0 4 bytes Central file header signature (0x02014b50) + * 4 2 bytes Version made by + * 6 2 bytes Version needed to extract + * 8 2 bytes General purpose bit flag + * 10 2 bytes Compression method + * 12 2 bytes Last mod file time + * 14 2 bytes Last mod file date + * 16 4 bytes CRC-32 + * 20 4 bytes Compressed size + * 24 4 bytes Uncompressed size + * 28 2 bytes Filename length (f) + * 30 2 bytes Extra field length (e) + * 32 2 bytes File comment length (c) + * 34 2 bytes Disk number start + * 36 2 bytes Internal file attributes + * 38 4 bytes External file attributes + * 42 4 bytes Relative offset of local header + * 46 (f)bytes Filename + * (e)bytes Extra field + * (c)bytes File comment + * + * End of central directory record: + * Offset Length Contents + * 0 4 bytes End of central dir signature (0x06054b50) + * 4 2 bytes Number of this disk + * 6 2 bytes Number of the disk with the start of the central directory + * 8 2 bytes Total number of entries in the central dir on this disk + * 10 2 bytes Total number of entries in the central dir + * 12 4 bytes Size of the central directory + * 16 4 bytes Offset of start of central directory with respect to the starting disk number + * 20 2 bytes zipfile comment length (c) + * 22 (c)bytes zipfile comment + * + * compression method: (2 bytes) + * 0 - The file is stored (no compression) + * 1 - The file is Shrunk + * 2 - The file is Reduced with compression factor 1 + * 3 - The file is Reduced with compression factor 2 + * 4 - The file is Reduced with compression factor 3 + * 5 - The file is Reduced with compression factor 4 + * 6 - The file is Imploded + * 7 - Reserved for Tokenizing compression algorithm + * 8 - The file is Deflated + */ + +#define SXMAKE_ZIP_WORKBUF (SXU16_HIGH/2) /* 32KB Initial working buffer size */ +#define SXMAKE_ZIP_EXTRACT_VER 0x000a /* Version needed to extract */ +#define SXMAKE_ZIP_VER 0x003 /* Version made by */ + +#define SXZIP_CENTRAL_MAGIC 0x02014b50 +#define SXZIP_END_CENTRAL_MAGIC 0x06054b50 +#define SXZIP_LOCAL_MAGIC 0x04034b50 +/*#define SXZIP_CRC32_START 0xdebb20e3*/ + +#define SXZIP_LOCAL_HDRSZ 30 /* Local header size */ +#define SXZIP_LOCAL_EXT_HDRZ 16 /* Extended local header(footer) size */ +#define SXZIP_CENTRAL_HDRSZ 46 /* Central directory header size */ +#define SXZIP_END_CENTRAL_HDRSZ 22 /* End of central directory header size */ + +#define SXARCHIVE_HASH_SIZE 64 /* Starting hash table size(MUST BE POWER OF 2)*/ +static sxi32 SyLittleEndianUnpack32(sxu32 *uNB, const unsigned char *buf, sxu32 Len) +{ + if( Len < sizeof(sxu32) ){ + return SXERR_SHORT; + } + *uNB = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + return SXRET_OK; +} +static sxi32 SyLittleEndianUnpack16(sxu16 *pOut, const unsigned char *zBuf, sxu32 nLen) +{ + if( nLen < sizeof(sxu16) ){ + return SXERR_SHORT; + } + *pOut = zBuf[0] + (zBuf[1] <<8); + + return SXRET_OK; +} +/* + * Archive hashtable manager + */ +static sxi32 ArchiveHashGetEntry(SyArchive *pArch, const char *zName, sxu32 nLen, SyArchiveEntry **ppEntry) +{ + SyArchiveEntry *pBucketEntry; + SyString sEntry; + sxu32 nHash; + + nHash = pArch->xHash(zName, nLen); + pBucketEntry = pArch->apHash[nHash & (pArch->nSize - 1)]; + + SyStringInitFromBuf(&sEntry, zName, nLen); + + for(;;){ + if( pBucketEntry == 0 ){ + break; + } + if( nHash == pBucketEntry->nHash && pArch->xCmp(&sEntry, &pBucketEntry->sFileName) == 0 ){ + if( ppEntry ){ + *ppEntry = pBucketEntry; + } + return SXRET_OK; + } + pBucketEntry = pBucketEntry->pNextHash; + } + return SXERR_NOTFOUND; +} +static void ArchiveHashBucketInstall(SyArchiveEntry **apTable, sxu32 nBucket, SyArchiveEntry *pEntry) +{ + pEntry->pNextHash = apTable[nBucket]; + if( apTable[nBucket] != 0 ){ + apTable[nBucket]->pPrevHash = pEntry; + } + apTable[nBucket] = pEntry; +} +static sxi32 ArchiveHashGrowTable(SyArchive *pArch) +{ + sxu32 nNewSize = pArch->nSize * 2; + SyArchiveEntry **apNew; + SyArchiveEntry *pEntry; + sxu32 n; + + /* Allocate a new table */ + apNew = (SyArchiveEntry **)SyMemBackendAlloc(pArch->pAllocator, nNewSize * sizeof(SyArchiveEntry *)); + if( apNew == 0 ){ + return SXRET_OK; /* Not so fatal, simply a performance hit */ + } + SyZero(apNew, nNewSize * sizeof(SyArchiveEntry *)); + /* Rehash old entries */ + for( n = 0 , pEntry = pArch->pList ; n < pArch->nLoaded ; n++ , pEntry = pEntry->pNext ){ + pEntry->pNextHash = pEntry->pPrevHash = 0; + ArchiveHashBucketInstall(apNew, pEntry->nHash & (nNewSize - 1), pEntry); + } + /* Release the old table */ + SyMemBackendFree(pArch->pAllocator, pArch->apHash); + pArch->apHash = apNew; + pArch->nSize = nNewSize; + + return SXRET_OK; +} +static sxi32 ArchiveHashInstallEntry(SyArchive *pArch, SyArchiveEntry *pEntry) +{ + if( pArch->nLoaded > pArch->nSize * 3 ){ + ArchiveHashGrowTable(&(*pArch)); + } + pEntry->nHash = pArch->xHash(SyStringData(&pEntry->sFileName), SyStringLength(&pEntry->sFileName)); + /* Install the entry in its bucket */ + ArchiveHashBucketInstall(pArch->apHash, pEntry->nHash & (pArch->nSize - 1), pEntry); + MACRO_LD_PUSH(pArch->pList, pEntry); + pArch->nLoaded++; + + return SXRET_OK; +} + /* + * Parse the End of central directory and report status + */ + static sxi32 ParseEndOfCentralDirectory(SyArchive *pArch, const unsigned char *zBuf) + { + sxu32 nMagic = 0; /* cc -O6 warning */ + sxi32 rc; + + /* Sanity check */ + rc = SyLittleEndianUnpack32(&nMagic, zBuf, sizeof(sxu32)); + if( /* rc != SXRET_OK || */nMagic != SXZIP_END_CENTRAL_MAGIC ){ + return SXERR_CORRUPT; + } + /* # of entries */ + rc = SyLittleEndianUnpack16((sxu16 *)&pArch->nEntry, &zBuf[8], sizeof(sxu16)); + if( /* rc != SXRET_OK || */ pArch->nEntry > SXI16_HIGH /* SXU16_HIGH */ ){ + return SXERR_CORRUPT; + } + /* Size of central directory */ + rc = SyLittleEndianUnpack32(&pArch->nCentralSize, &zBuf[12], sizeof(sxu32)); + if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ + return SXERR_CORRUPT; + } + /* Starting offset of central directory */ + rc = SyLittleEndianUnpack32(&pArch->nCentralOfft, &zBuf[16], sizeof(sxu32)); + if( /*rc != SXRET_OK ||*/ pArch->nCentralSize > SXI32_HIGH ){ + return SXERR_CORRUPT; + } + + return SXRET_OK; + } + /* + * Fill the zip entry with the appropriate information from the central directory + */ +static sxi32 GetCentralDirectoryEntry(SyArchive *pArch, SyArchiveEntry *pEntry, const unsigned char *zCentral, sxu32 *pNextOffset) + { + SyString *pName = &pEntry->sFileName; /* File name */ + sxu16 nDosDate, nDosTime; + sxu16 nComment = 0 ; + sxu32 nMagic = 0; /* cc -O6 warning */ + sxi32 rc; + nDosDate = nDosTime = 0; /* cc -O6 warning */ + SXUNUSED(pArch); + /* Sanity check */ + rc = SyLittleEndianUnpack32(&nMagic, zCentral, sizeof(sxu32)); + if( /* rc != SXRET_OK || */ nMagic != SXZIP_CENTRAL_MAGIC ){ + rc = SXERR_CORRUPT; + /* + * Try to recover by examing the next central directory record. + * Dont worry here, there is no risk of an infinite loop since + * the buffer size is delimited. + */ + + /* pName->nByte = 0; nComment = 0; pName->nExtra = 0 */ + goto update; + } + /* + * entry name length + */ + SyLittleEndianUnpack16((sxu16 *)&pName->nByte, &zCentral[28], sizeof(sxu16)); + if( pName->nByte > SXI16_HIGH /* SXU16_HIGH */){ + rc = SXERR_BIG; + goto update; + } + /* Extra information */ + SyLittleEndianUnpack16(&pEntry->nExtra, &zCentral[30], sizeof(sxu16)); + /* Comment length */ + SyLittleEndianUnpack16(&nComment, &zCentral[32], sizeof(sxu16)); + /* Compression method 0 == stored / 8 == deflated */ + rc = SyLittleEndianUnpack16(&pEntry->nComprMeth, &zCentral[10], sizeof(sxu16)); + /* DOS Timestamp */ + SyLittleEndianUnpack16(&nDosTime, &zCentral[12], sizeof(sxu16)); + SyLittleEndianUnpack16(&nDosDate, &zCentral[14], sizeof(sxu16)); + SyDosTimeFormat((nDosDate << 16 | nDosTime), &pEntry->sFmt); + /* Little hack to fix month index */ + pEntry->sFmt.tm_mon--; + /* CRC32 */ + rc = SyLittleEndianUnpack32(&pEntry->nCrc, &zCentral[16], sizeof(sxu32)); + /* Content size before compression */ + rc = SyLittleEndianUnpack32(&pEntry->nByte, &zCentral[24], sizeof(sxu32)); + if( pEntry->nByte > SXI32_HIGH ){ + rc = SXERR_BIG; + goto update; + } + /* + * Content size after compression. + * Note that if the file is stored pEntry->nByte should be equal to pEntry->nByteCompr + */ + rc = SyLittleEndianUnpack32(&pEntry->nByteCompr, &zCentral[20], sizeof(sxu32)); + if( pEntry->nByteCompr > SXI32_HIGH ){ + rc = SXERR_BIG; + goto update; + } + /* Finally grab the contents offset */ + SyLittleEndianUnpack32(&pEntry->nOfft, &zCentral[42], sizeof(sxu32)); + if( pEntry->nOfft > SXI32_HIGH ){ + rc = SXERR_BIG; + goto update; + } + rc = SXRET_OK; +update: + /* Update the offset to point to the next central directory record */ + *pNextOffset = SXZIP_CENTRAL_HDRSZ + pName->nByte + pEntry->nExtra + nComment; + return rc; /* Report failure or success */ +} +static sxi32 ZipFixOffset(SyArchiveEntry *pEntry, void *pSrc) +{ + sxu16 nExtra, nNameLen; + unsigned char *zHdr; + nExtra = nNameLen = 0; + zHdr = (unsigned char *)pSrc; + zHdr = &zHdr[pEntry->nOfft]; + if( SyMemcmp(zHdr, "PK\003\004", sizeof(sxu32)) != 0 ){ + return SXERR_CORRUPT; + } + SyLittleEndianUnpack16(&nNameLen, &zHdr[26], sizeof(sxu16)); + SyLittleEndianUnpack16(&nExtra, &zHdr[28], sizeof(sxu16)); + /* Fix contents offset */ + pEntry->nOfft += SXZIP_LOCAL_HDRSZ + nExtra + nNameLen; + return SXRET_OK; +} +/* + * Extract all valid entries from the central directory + */ +static sxi32 ZipExtract(SyArchive *pArch, const unsigned char *zCentral, sxu32 nLen, void *pSrc) +{ + SyArchiveEntry *pEntry, *pDup; + const unsigned char *zEnd ; /* End of central directory */ + sxu32 nIncr, nOfft; /* Central Offset */ + SyString *pName; /* Entry name */ + char *zName; + sxi32 rc; + + nOfft = nIncr = 0; + zEnd = &zCentral[nLen]; + + for(;;){ + if( &zCentral[nOfft] >= zEnd ){ + break; + } + /* Add a new entry */ + pEntry = (SyArchiveEntry *)SyMemBackendPoolAlloc(pArch->pAllocator, sizeof(SyArchiveEntry)); + if( pEntry == 0 ){ + break; + } + SyZero(pEntry, sizeof(SyArchiveEntry)); + pEntry->nMagic = SXARCH_MAGIC; + nIncr = 0; + rc = GetCentralDirectoryEntry(&(*pArch), pEntry, &zCentral[nOfft], &nIncr); + if( rc == SXRET_OK ){ + /* Fix the starting record offset so we can access entry contents correctly */ + rc = ZipFixOffset(pEntry, pSrc); + } + if(rc != SXRET_OK ){ + sxu32 nJmp = 0; + SyMemBackendPoolFree(pArch->pAllocator, pEntry); + /* Try to recover by brute-forcing for a valid central directory record */ + if( SXRET_OK == SyBlobSearch((const void *)&zCentral[nOfft + nIncr], (sxu32)(zEnd - &zCentral[nOfft + nIncr]), + (const void *)"PK\001\002", sizeof(sxu32), &nJmp)){ + nOfft += nIncr + nJmp; /* Check next entry */ + continue; + } + break; /* Giving up, archive is hopelessly corrupted */ + } + pName = &pEntry->sFileName; + pName->zString = (const char *)&zCentral[nOfft + SXZIP_CENTRAL_HDRSZ]; + if( pName->nByte <= 0 || ( pEntry->nByte <= 0 && pName->zString[pName->nByte - 1] != '/') ){ + /* Ignore zero length records (except folders) and records without names */ + SyMemBackendPoolFree(pArch->pAllocator, pEntry); + nOfft += nIncr; /* Check next entry */ + continue; + } + zName = SyMemBackendStrDup(pArch->pAllocator, pName->zString, pName->nByte); + if( zName == 0 ){ + SyMemBackendPoolFree(pArch->pAllocator, pEntry); + nOfft += nIncr; /* Check next entry */ + continue; + } + pName->zString = (const char *)zName; + /* Check for duplicates */ + rc = ArchiveHashGetEntry(&(*pArch), pName->zString, pName->nByte, &pDup); + if( rc == SXRET_OK ){ + /* Another entry with the same name exists ; link them together */ + pEntry->pNextName = pDup->pNextName; + pDup->pNextName = pEntry; + pDup->nDup++; + }else{ + /* Insert in hashtable */ + ArchiveHashInstallEntry(pArch, pEntry); + } + nOfft += nIncr; /* Check next record */ + } + pArch->pCursor = pArch->pList; + + return pArch->nLoaded > 0 ? SXRET_OK : SXERR_EMPTY; +} +JX9_PRIVATE sxi32 SyZipExtractFromBuf(SyArchive *pArch, const char *zBuf, sxu32 nLen) + { + const unsigned char *zCentral, *zEnd; + sxi32 rc; +#if defined(UNTRUST) + if( SXARCH_INVALID(pArch) || zBuf == 0 ){ + return SXERR_INVALID; + } +#endif + /* The miminal size of a zip archive: + * LOCAL_HDR_SZ + CENTRAL_HDR_SZ + END_OF_CENTRAL_HDR_SZ + * 30 46 22 + */ + if( nLen < SXZIP_LOCAL_HDRSZ + SXZIP_CENTRAL_HDRSZ + SXZIP_END_CENTRAL_HDRSZ ){ + return SXERR_CORRUPT; /* Don't bother processing return immediately */ + } + + zEnd = (unsigned char *)&zBuf[nLen - SXZIP_END_CENTRAL_HDRSZ]; + /* Find the end of central directory */ + while( ((sxu32)((unsigned char *)&zBuf[nLen] - zEnd) < (SXZIP_END_CENTRAL_HDRSZ + SXI16_HIGH)) && + zEnd > (unsigned char *)zBuf && SyMemcmp(zEnd, "PK\005\006", sizeof(sxu32)) != 0 ){ + zEnd--; + } + /* Parse the end of central directory */ + rc = ParseEndOfCentralDirectory(&(*pArch), zEnd); + if( rc != SXRET_OK ){ + return rc; + } + + /* Find the starting offset of the central directory */ + zCentral = &zEnd[-(sxi32)pArch->nCentralSize]; + if( zCentral <= (unsigned char *)zBuf || SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){ + if( pArch->nCentralOfft >= nLen ){ + /* Corrupted central directory offset */ + return SXERR_CORRUPT; + } + zCentral = (unsigned char *)&zBuf[pArch->nCentralOfft]; + if( SyMemcmp(zCentral, "PK\001\002", sizeof(sxu32)) != 0 ){ + /* Corrupted zip archive */ + return SXERR_CORRUPT; + } + /* Fall thru and extract all valid entries from the central directory */ + } + rc = ZipExtract(&(*pArch), zCentral, (sxu32)(zEnd - zCentral), (void *)zBuf); + return rc; + } +/* + * Default comparison function. + */ + static sxi32 ArchiveHashCmp(const SyString *pStr1, const SyString *pStr2) + { + sxi32 rc; + rc = SyStringCmp(pStr1, pStr2, SyMemcmp); + return rc; + } +JX9_PRIVATE sxi32 SyArchiveInit(SyArchive *pArch, SyMemBackend *pAllocator, ProcHash xHash, ProcRawStrCmp xCmp) + { + SyArchiveEntry **apHash; +#if defined(UNTRUST) + if( pArch == 0 ){ + return SXERR_EMPTY; + } +#endif + SyZero(pArch, sizeof(SyArchive)); + /* Allocate a new hashtable */ + apHash = (SyArchiveEntry **)SyMemBackendAlloc(&(*pAllocator), SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); + if( apHash == 0){ + return SXERR_MEM; + } + SyZero(apHash, SXARCHIVE_HASH_SIZE * sizeof(SyArchiveEntry *)); + pArch->apHash = apHash; + pArch->xHash = xHash ? xHash : SyBinHash; + pArch->xCmp = xCmp ? xCmp : ArchiveHashCmp; + pArch->nSize = SXARCHIVE_HASH_SIZE; + pArch->pAllocator = &(*pAllocator); + pArch->nMagic = SXARCH_MAGIC; + return SXRET_OK; + } + static sxi32 ArchiveReleaseEntry(SyMemBackend *pAllocator, SyArchiveEntry *pEntry) + { + SyArchiveEntry *pDup = pEntry->pNextName; + SyArchiveEntry *pNextDup; + + /* Release duplicates first since there are not stored in the hashtable */ + for(;;){ + if( pEntry->nDup == 0 ){ + break; + } + pNextDup = pDup->pNextName; + pDup->nMagic = 0x2661; + SyMemBackendFree(pAllocator, (void *)SyStringData(&pDup->sFileName)); + SyMemBackendPoolFree(pAllocator, pDup); + pDup = pNextDup; + pEntry->nDup--; + } + pEntry->nMagic = 0x2661; + SyMemBackendFree(pAllocator, (void *)SyStringData(&pEntry->sFileName)); + SyMemBackendPoolFree(pAllocator, pEntry); + return SXRET_OK; + } +JX9_PRIVATE sxi32 SyArchiveRelease(SyArchive *pArch) + { + SyArchiveEntry *pEntry, *pNext; + pEntry = pArch->pList; + for(;;){ + if( pArch->nLoaded < 1 ){ + break; + } + pNext = pEntry->pNext; + MACRO_LD_REMOVE(pArch->pList, pEntry); + ArchiveReleaseEntry(pArch->pAllocator, pEntry); + pEntry = pNext; + pArch->nLoaded--; + } + SyMemBackendFree(pArch->pAllocator, pArch->apHash); + pArch->pCursor = 0; + pArch->nMagic = 0x2626; + return SXRET_OK; + } + JX9_PRIVATE sxi32 SyArchiveResetLoopCursor(SyArchive *pArch) + { + pArch->pCursor = pArch->pList; + return SXRET_OK; + } + JX9_PRIVATE sxi32 SyArchiveGetNextEntry(SyArchive *pArch, SyArchiveEntry **ppEntry) + { + SyArchiveEntry *pNext; + if( pArch->pCursor == 0 ){ + /* Rewind the cursor */ + pArch->pCursor = pArch->pList; + return SXERR_EOF; + } + *ppEntry = pArch->pCursor; + pNext = pArch->pCursor->pNext; + /* Advance the cursor to the next entry */ + pArch->pCursor = pNext; + return SXRET_OK; + } +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* + * Psuedo Random Number Generator (PRNG) + * @authors: SQLite authors + * @status: Public Domain + * NOTE: + * Nothing in this file or anywhere else in the library does any kind of + * encryption.The RC4 algorithm is being used as a PRNG (pseudo-random + * number generator) not as an encryption device. + */ +#define SXPRNG_MAGIC 0x13C4 +#ifdef __UNIXES__ +#include +#include +#include +#include +#include +#include +#include +#endif +static sxi32 SyOSUtilRandomSeed(void *pBuf, sxu32 nLen, void *pUnused) +{ + char *zBuf = (char *)pBuf; +#ifdef __WINNT__ + DWORD nProcessID; /* Yes, keep it uninitialized when compiling using the MinGW32 builds tools */ +#elif defined(__UNIXES__) + pid_t pid; + int fd; +#else + char zGarbage[128]; /* Yes, keep this buffer uninitialized */ +#endif + SXUNUSED(pUnused); +#ifdef __WINNT__ +#ifndef __MINGW32__ + nProcessID = GetProcessId(GetCurrentProcess()); +#endif + SyMemcpy((const void *)&nProcessID, zBuf, SXMIN(nLen, sizeof(DWORD))); + if( (sxu32)(&zBuf[nLen] - &zBuf[sizeof(DWORD)]) >= sizeof(SYSTEMTIME) ){ + GetSystemTime((LPSYSTEMTIME)&zBuf[sizeof(DWORD)]); + } +#elif defined(__UNIXES__) + fd = open("/dev/urandom", O_RDONLY); + if (fd >= 0 ){ + if( read(fd, zBuf, nLen) > 0 ){ + close(fd); + return SXRET_OK; + } + /* FALL THRU */ + close(fd); + } + pid = getpid(); + SyMemcpy((const void *)&pid, zBuf, SXMIN(nLen, sizeof(pid_t))); + if( &zBuf[nLen] - &zBuf[sizeof(pid_t)] >= (int)sizeof(struct timeval) ){ + gettimeofday((struct timeval *)&zBuf[sizeof(pid_t)], 0); + } +#else + /* Fill with uninitialized data */ + SyMemcpy(zGarbage, zBuf, SXMIN(nLen, sizeof(zGarbage))); +#endif + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyRandomnessInit(SyPRNGCtx *pCtx, ProcRandomSeed xSeed, void * pUserData) +{ + char zSeed[256]; + sxu8 t; + sxi32 rc; + sxu32 i; + if( pCtx->nMagic == SXPRNG_MAGIC ){ + return SXRET_OK; /* Already initialized */ + } + /* Initialize the state of the random number generator once, + ** the first time this routine is called.The seed value does + ** not need to contain a lot of randomness since we are not + ** trying to do secure encryption or anything like that... + */ + if( xSeed == 0 ){ + xSeed = SyOSUtilRandomSeed; + } + rc = xSeed(zSeed, sizeof(zSeed), pUserData); + if( rc != SXRET_OK ){ + return rc; + } + pCtx->i = pCtx->j = 0; + for(i=0; i < SX_ARRAYSIZE(pCtx->s) ; i++){ + pCtx->s[i] = (unsigned char)i; + } + for(i=0; i < sizeof(zSeed) ; i++){ + pCtx->j += pCtx->s[i] + zSeed[i]; + t = pCtx->s[pCtx->j]; + pCtx->s[pCtx->j] = pCtx->s[i]; + pCtx->s[i] = t; + } + pCtx->nMagic = SXPRNG_MAGIC; + + return SXRET_OK; +} +/* + * Get a single 8-bit random value using the RC4 PRNG. + */ +static sxu8 randomByte(SyPRNGCtx *pCtx) +{ + sxu8 t; + + /* Generate and return single random byte */ + pCtx->i++; + t = pCtx->s[pCtx->i]; + pCtx->j += t; + pCtx->s[pCtx->i] = pCtx->s[pCtx->j]; + pCtx->s[pCtx->j] = t; + t += pCtx->s[pCtx->i]; + return pCtx->s[t]; +} +JX9_PRIVATE sxi32 SyRandomness(SyPRNGCtx *pCtx, void *pBuf, sxu32 nLen) +{ + unsigned char *zBuf = (unsigned char *)pBuf; + unsigned char *zEnd = &zBuf[nLen]; +#if defined(UNTRUST) + if( pCtx == 0 || pBuf == 0 || nLen <= 0 ){ + return SXERR_EMPTY; + } +#endif + if(pCtx->nMagic != SXPRNG_MAGIC ){ + return SXERR_CORRUPT; + } + for(;;){ + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + if( zBuf >= zEnd ){break;} zBuf[0] = randomByte(pCtx); zBuf++; + } + return SXRET_OK; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifndef JX9_DISABLE_HASH_FUNC +/* SyRunTimeApi: sxhash.c */ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest.This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + */ +#define SX_MD5_BINSZ 16 +#define SX_MD5_HEXSZ 32 +/* + * Note: this code is harmless on little-endian machines. + */ +static void byteReverse (unsigned char *buf, unsigned longs) +{ + sxu32 t; + do { + t = (sxu32)((unsigned)buf[3]<<8 | buf[2]) << 16 | + ((unsigned)buf[1]<<8 | buf[0]); + *(sxu32*)buf = t; + buf += 4; + } while (--longs); +} +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#ifdef F1 +#undef F1 +#endif +#ifdef F2 +#undef F2 +#endif +#ifdef F3 +#undef F3 +#endif +#ifdef F4 +#undef F4 +#endif + +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm.*/ +#define SX_MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data.MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void MD5Transform(sxu32 buf[4], const sxu32 in[16]) +{ + register sxu32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + SX_MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7); + SX_MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12); + SX_MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17); + SX_MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22); + SX_MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7); + SX_MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12); + SX_MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17); + SX_MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22); + SX_MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7); + SX_MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12); + SX_MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17); + SX_MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22); + SX_MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7); + SX_MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12); + SX_MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17); + SX_MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22); + + SX_MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5); + SX_MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9); + SX_MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14); + SX_MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20); + SX_MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5); + SX_MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9); + SX_MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14); + SX_MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20); + SX_MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5); + SX_MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9); + SX_MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14); + SX_MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20); + SX_MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5); + SX_MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9); + SX_MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14); + SX_MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20); + + SX_MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4); + SX_MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11); + SX_MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16); + SX_MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23); + SX_MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4); + SX_MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11); + SX_MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16); + SX_MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23); + SX_MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4); + SX_MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11); + SX_MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16); + SX_MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23); + SX_MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4); + SX_MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11); + SX_MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16); + SX_MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23); + + SX_MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6); + SX_MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10); + SX_MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15); + SX_MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21); + SX_MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6); + SX_MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10); + SX_MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15); + SX_MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21); + SX_MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6); + SX_MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10); + SX_MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15); + SX_MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21); + SX_MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6); + SX_MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10); + SX_MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15); + SX_MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +JX9_PRIVATE void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len) +{ + sxu32 t; + + /* Update bitcount */ + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((sxu32)len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + /* Handle any leading odd-sized chunks */ + if ( t ) { + unsigned char *p = (unsigned char *)ctx->in + t; + + t = 64-t; + if (len < t) { + SyMemcpy(buf, p, len); + return; + } + SyMemcpy(buf, p, t); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (sxu32*)ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + while (len >= 64) { + SyMemcpy(buf, ctx->in, 64); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (sxu32*)ctx->in); + buf += 64; + len -= 64; + } + /* Handle any remaining bytes of data.*/ + SyMemcpy(buf, ctx->in, len); +} +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +JX9_PRIVATE void MD5Final(unsigned char digest[16], MD5Context *ctx){ + unsigned count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80.This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + SyZero(p, count); + byteReverse(ctx->in, 16); + MD5Transform(ctx->buf, (sxu32*)ctx->in); + + /* Now fill the next block with 56 bytes */ + SyZero(ctx->in, 56); + } else { + /* Pad block to 56 bytes */ + SyZero(p, count-8); + } + byteReverse(ctx->in, 14); + + /* Append length in bits and transform */ + ((sxu32*)ctx->in)[ 14 ] = ctx->bits[0]; + ((sxu32*)ctx->in)[ 15 ] = ctx->bits[1]; + + MD5Transform(ctx->buf, (sxu32*)ctx->in); + byteReverse((unsigned char *)ctx->buf, 4); + SyMemcpy(ctx->buf, digest, 0x10); + SyZero(ctx, sizeof(ctx)); /* In case it's sensitive */ +} +#undef F1 +#undef F2 +#undef F3 +#undef F4 +JX9_PRIVATE sxi32 MD5Init(MD5Context *pCtx) +{ + pCtx->buf[0] = 0x67452301; + pCtx->buf[1] = 0xefcdab89; + pCtx->buf[2] = 0x98badcfe; + pCtx->buf[3] = 0x10325476; + pCtx->bits[0] = 0; + pCtx->bits[1] = 0; + + return SXRET_OK; +} +JX9_PRIVATE sxi32 SyMD5Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[16]) +{ + MD5Context sCtx; + MD5Init(&sCtx); + MD5Update(&sCtx, (const unsigned char *)pIn, nLen); + MD5Final(zDigest, &sCtx); + return SXRET_OK; +} +/* + * SHA-1 in C + * By Steve Reid + * Status: Public Domain + */ +/* + * blk0() and blk() perform the initial expand. + * I got the idea of expanding during the round function from SSLeay + * + * blk0le() for little-endian and blk0be() for big-endian. + */ +#if __GNUC__ && (defined(__i386__) || defined(__x86_64__)) +/* + * GCC by itself only generates left rotates. Use right rotates if + * possible to be kinder to dinky implementations with iterative rotate + * instructions. + */ +#define SHA_ROT(op, x, k) \ + ({ unsigned int y; asm(op " %1, %0" : "=r" (y) : "I" (k), "0" (x)); y; }) +#define rol(x, k) SHA_ROT("roll", x, k) +#define ror(x, k) SHA_ROT("rorl", x, k) + +#else +/* Generic C equivalent */ +#define SHA_ROT(x, l, r) ((x) << (l) | (x) >> (r)) +#define rol(x, k) SHA_ROT(x, k, 32-(k)) +#define ror(x, k) SHA_ROT(x, 32-(k), k) +#endif + +#define blk0le(i) (block[i] = (ror(block[i], 8)&0xFF00FF00) \ + |(rol(block[i], 8)&0x00FF00FF)) +#define blk0be(i) block[i] +#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \ + ^block[(i+2)&15]^block[i&15], 1)) + +/* + * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 + * + * Rl0() for little-endian and Rb0() for big-endian. Endianness is + * determined at run-time. + */ +#define Rl0(v, w, x, y, z, i) \ + z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v, 5);w=ror(w, 2); +#define Rb0(v, w, x, y, z, i) \ + z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v, 5);w=ror(w, 2); +#define R1(v, w, x, y, z, i) \ + z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v, 5);w=ror(w, 2); +#define R2(v, w, x, y, z, i) \ + z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v, 5);w=ror(w, 2); +#define R3(v, w, x, y, z, i) \ + z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v, 5);w=ror(w, 2); +#define R4(v, w, x, y, z, i) \ + z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v, 5);w=ror(w, 2); + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ +#define a qq[0] +#define b qq[1] +#define c qq[2] +#define d qq[3] +#define e qq[4] + +static void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) +{ + unsigned int qq[5]; /* a, b, c, d, e; */ + static int one = 1; + unsigned int block[16]; + SyMemcpy(buffer, (void *)block, 64); + SyMemcpy(state, qq, 5*sizeof(unsigned int)); + + /* Copy context->state[] to working vars */ + /* + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + */ + + /* 4 rounds of 20 operations each. Loop unrolled. */ + if( 1 == *(unsigned char*)&one ){ + Rl0(a, b, c, d, e, 0); Rl0(e, a, b, c, d, 1); Rl0(d, e, a, b, c, 2); Rl0(c, d, e, a, b, 3); + Rl0(b, c, d, e, a, 4); Rl0(a, b, c, d, e, 5); Rl0(e, a, b, c, d, 6); Rl0(d, e, a, b, c, 7); + Rl0(c, d, e, a, b, 8); Rl0(b, c, d, e, a, 9); Rl0(a, b, c, d, e, 10); Rl0(e, a, b, c, d, 11); + Rl0(d, e, a, b, c, 12); Rl0(c, d, e, a, b, 13); Rl0(b, c, d, e, a, 14); Rl0(a, b, c, d, e, 15); + }else{ + Rb0(a, b, c, d, e, 0); Rb0(e, a, b, c, d, 1); Rb0(d, e, a, b, c, 2); Rb0(c, d, e, a, b, 3); + Rb0(b, c, d, e, a, 4); Rb0(a, b, c, d, e, 5); Rb0(e, a, b, c, d, 6); Rb0(d, e, a, b, c, 7); + Rb0(c, d, e, a, b, 8); Rb0(b, c, d, e, a, 9); Rb0(a, b, c, d, e, 10); Rb0(e, a, b, c, d, 11); + Rb0(d, e, a, b, c, 12); Rb0(c, d, e, a, b, 13); Rb0(b, c, d, e, a, 14); Rb0(a, b, c, d, e, 15); + } + R1(e, a, b, c, d, 16); R1(d, e, a, b, c, 17); R1(c, d, e, a, b, 18); R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); R2(e, a, b, c, d, 21); R2(d, e, a, b, c, 22); R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); R2(a, b, c, d, e, 25); R2(e, a, b, c, d, 26); R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); R2(b, c, d, e, a, 29); R2(a, b, c, d, e, 30); R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); R2(c, d, e, a, b, 33); R2(b, c, d, e, a, 34); R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); R2(d, e, a, b, c, 37); R2(c, d, e, a, b, 38); R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); R3(e, a, b, c, d, 41); R3(d, e, a, b, c, 42); R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); R3(a, b, c, d, e, 45); R3(e, a, b, c, d, 46); R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); R3(b, c, d, e, a, 49); R3(a, b, c, d, e, 50); R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); R3(c, d, e, a, b, 53); R3(b, c, d, e, a, 54); R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); R3(d, e, a, b, c, 57); R3(c, d, e, a, b, 58); R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); R4(e, a, b, c, d, 61); R4(d, e, a, b, c, 62); R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); R4(a, b, c, d, e, 65); R4(e, a, b, c, d, 66); R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); R4(b, c, d, e, a, 69); R4(a, b, c, d, e, 70); R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); R4(c, d, e, a, b, 73); R4(b, c, d, e, a, 74); R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); R4(d, e, a, b, c, 77); R4(c, d, e, a, b, 78); R4(b, c, d, e, a, 79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} +#undef a +#undef b +#undef c +#undef d +#undef e +/* + * SHA1Init - Initialize new context + */ +JX9_PRIVATE void SHA1Init(SHA1Context *context){ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} +/* + * Run your data through this. + */ +JX9_PRIVATE void SHA1Update(SHA1Context *context, const unsigned char *data, unsigned int len){ + unsigned int i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1] += (len>>29)+1; + j = (j >> 3) & 63; + if ((j + len) > 63) { + (void)SyMemcpy(data, &context->buffer[j], (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + SHA1Transform(context->state, &data[i]); + j = 0; + } else { + i = 0; + } + (void)SyMemcpy(&data[i], &context->buffer[j], len - i); +} +/* + * Add padding and return the message digest. + */ +JX9_PRIVATE void SHA1Final(SHA1Context *context, unsigned char digest[20]){ + unsigned int i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (const unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) + SHA1Update(context, (const unsigned char *)"\0", 1); + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + + if (digest) { + for (i = 0; i < 20; i++) + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} +#undef Rl0 +#undef Rb0 +#undef R1 +#undef R2 +#undef R3 +#undef R4 + +JX9_PRIVATE sxi32 SySha1Compute(const void *pIn, sxu32 nLen, unsigned char zDigest[20]) +{ + SHA1Context sCtx; + SHA1Init(&sCtx); + SHA1Update(&sCtx, (const unsigned char *)pIn, nLen); + SHA1Final(&sCtx, zDigest); + return SXRET_OK; +} +static const sxu32 crc32_table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +}; +#define CRC32C(c, d) (c = ( crc32_table[(c ^ (d)) & 0xFF] ^ (c>>8) ) ) +static sxu32 SyCrc32Update(sxu32 crc32, const void *pSrc, sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + if( zIn == 0 ){ + return crc32; + } + zEnd = &zIn[nLen]; + for(;;){ + if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; + if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; + if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; + if(zIn >= zEnd ){ break; } CRC32C(crc32, zIn[0]); zIn++; + } + + return crc32; +} +JX9_PRIVATE sxu32 SyCrc32(const void *pSrc, sxu32 nLen) +{ + return SyCrc32Update(SXU32_HIGH, pSrc, nLen); +} +#endif /* JX9_DISABLE_HASH_FUNC */ +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +#ifndef JX9_DISABLE_BUILTIN_FUNC +JX9_PRIVATE sxi32 SyBinToHexConsumer(const void *pIn, sxu32 nLen, ProcConsumer xConsumer, void *pConsumerData) +{ + static const unsigned char zHexTab[] = "0123456789abcdef"; + const unsigned char *zIn, *zEnd; + unsigned char zOut[3]; + sxi32 rc; +#if defined(UNTRUST) + if( pIn == 0 || xConsumer == 0 ){ + return SXERR_EMPTY; + } +#endif + zIn = (const unsigned char *)pIn; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ + break; + } + zOut[0] = zHexTab[zIn[0] >> 4]; zOut[1] = zHexTab[zIn[0] & 0x0F]; + rc = xConsumer((const void *)zOut, sizeof(char)*2, pConsumerData); + if( rc != SXRET_OK ){ + return rc; + } + zIn++; + } + return SXRET_OK; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +JX9_PRIVATE void SyBigEndianPack32(unsigned char *buf,sxu32 nb) +{ + buf[3] = nb & 0xFF ; nb >>=8; + buf[2] = nb & 0xFF ; nb >>=8; + buf[1] = nb & 0xFF ; nb >>=8; + buf[0] = (unsigned char)nb ; +} +JX9_PRIVATE void SyBigEndianUnpack32(const unsigned char *buf,sxu32 *uNB) +{ + *uNB = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); +} +JX9_PRIVATE void SyBigEndianPack16(unsigned char *buf,sxu16 nb) +{ + buf[1] = nb & 0xFF ; nb >>=8; + buf[0] = (unsigned char)nb ; +} +JX9_PRIVATE void SyBigEndianUnpack16(const unsigned char *buf,sxu16 *uNB) +{ + *uNB = buf[1] + (buf[0] << 8); +} +JX9_PRIVATE void SyBigEndianPack64(unsigned char *buf,sxu64 n64) +{ + buf[7] = n64 & 0xFF; n64 >>=8; + buf[6] = n64 & 0xFF; n64 >>=8; + buf[5] = n64 & 0xFF; n64 >>=8; + buf[4] = n64 & 0xFF; n64 >>=8; + buf[3] = n64 & 0xFF; n64 >>=8; + buf[2] = n64 & 0xFF; n64 >>=8; + buf[1] = n64 & 0xFF; n64 >>=8; + buf[0] = (sxu8)n64 ; +} +JX9_PRIVATE void SyBigEndianUnpack64(const unsigned char *buf,sxu64 *n64) +{ + sxu32 u1,u2; + u1 = buf[7] + (buf[6] << 8) + (buf[5] << 16) + (buf[4] << 24); + u2 = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); + *n64 = (((sxu64)u2) << 32) | u1; +} +JX9_PRIVATE sxi32 SyBlobAppendBig64(SyBlob *pBlob,sxu64 n64) +{ + unsigned char zBuf[8]; + sxi32 rc; + SyBigEndianPack64(zBuf,n64); + rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); + return rc; +} +JX9_PRIVATE sxi32 SyBlobAppendBig32(SyBlob *pBlob,sxu32 n32) +{ + unsigned char zBuf[4]; + sxi32 rc; + SyBigEndianPack32(zBuf,n32); + rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); + return rc; +} +JX9_PRIVATE sxi32 SyBlobAppendBig16(SyBlob *pBlob,sxu16 n16) +{ + unsigned char zBuf[2]; + sxi32 rc; + SyBigEndianPack16(zBuf,n16); + rc = SyBlobAppend(pBlob,(const void *)zBuf,sizeof(zBuf)); + return rc; +} +JX9_PRIVATE void SyTimeFormatToDos(Sytm *pFmt,sxu32 *pOut) +{ + sxi32 nDate,nTime; + nDate = ((pFmt->tm_year - 1980) << 9) + (pFmt->tm_mon << 5) + pFmt->tm_mday; + nTime = (pFmt->tm_hour << 11) + (pFmt->tm_min << 5)+ (pFmt->tm_sec >> 1); + *pOut = (nDate << 16) | nTime; +} +JX9_PRIVATE void SyDosTimeFormat(sxu32 nDosDate, Sytm *pOut) +{ + sxu16 nDate; + sxu16 nTime; + nDate = nDosDate >> 16; + nTime = nDosDate & 0xFFFF; + pOut->tm_isdst = 0; + pOut->tm_year = 1980 + (nDate >> 9); + pOut->tm_mon = (nDate % (1<<9))>>5; + pOut->tm_mday = (nDate % (1<<9))&0x1F; + pOut->tm_hour = nTime >> 11; + pOut->tm_min = (nTime % (1<<11)) >> 5; + pOut->tm_sec = ((nTime % (1<<11))& 0x1F )<<1; +} +/* + * ---------------------------------------------------------- + * File: jx9_memobj.c + * MD5: 8692d7f4cb297c0946066b4a9034c637 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: memobj.c v2.7 FreeBSD 2012-08-09 03:40 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* This file manage low-level stuff related to indexed memory objects [i.e: jx9_value] */ +/* + * Notes on memory objects [i.e: jx9_value]. + * Internally, the JX9 virtual machine manipulates nearly all JX9 values + * [i.e: string, int, float, resource, object, bool, null..] as jx9_values structures. + * Each jx9_values struct may cache multiple representations (string, + * integer etc.) of the same value. + */ +/* + * Convert a 64-bit IEEE double into a 64-bit signed integer. + * If the double is too large, return 0x8000000000000000. + * + * Most systems appear to do this simply by assigning ariables and without + * the extra range tests. + * But there are reports that windows throws an expection if the floating + * point value is out of range. + */ +static sxi64 MemObjRealToInt(jx9_value *pObj) +{ +#ifdef JX9_OMIT_FLOATING_POINT + /* Real and 64bit integer are the same when floating point arithmetic + * is omitted from the build. + */ + return pObj->x.rVal; +#else + /* + ** Many compilers we encounter do not define constants for the + ** minimum and maximum 64-bit integers, or they define them + ** inconsistently. And many do not understand the "LL" notation. + ** So we define our own static constants here using nothing + ** larger than a 32-bit integer constant. + */ + static const sxi64 maxInt = LARGEST_INT64; + static const sxi64 minInt = SMALLEST_INT64; + jx9_real r = pObj->x.rVal; + if( r<(jx9_real)minInt ){ + return minInt; + }else if( r>(jx9_real)maxInt ){ + /* minInt is correct here - not maxInt. It turns out that assigning + ** a very large positive number to an integer results in a very large + ** negative integer. This makes no sense, but it is what x86 hardware + ** does so for compatibility we will do the same in software. */ + return minInt; + }else{ + return (sxi64)r; + } +#endif +} +/* + * Convert a raw token value typically a stream of digit [i.e: hex, octal, binary or decimal] + * to a 64-bit integer. + */ +JX9_PRIVATE sxi64 jx9TokenValueToInt64(SyString *pVal) +{ + sxi64 iVal = 0; + if( pVal->nByte <= 0 ){ + return 0; + } + if( pVal->zString[0] == '0' ){ + sxi32 c; + if( pVal->nByte == sizeof(char) ){ + return 0; + } + c = pVal->zString[1]; + if( c == 'x' || c == 'X' ){ + /* Hex digit stream */ + SyHexStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); + }else if( c == 'b' || c == 'B' ){ + /* Binary digit stream */ + SyBinaryStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); + }else{ + /* Octal digit stream */ + SyOctalStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); + } + }else{ + /* Decimal digit stream */ + SyStrToInt64(pVal->zString, pVal->nByte, (void *)&iVal, 0); + } + return iVal; +} +/* + * Return some kind of 64-bit integer value which is the best we can + * do at representing the value that pObj describes as a string + * representation. + */ +static sxi64 MemObjStringToInt(jx9_value *pObj) +{ + SyString sVal; + SyStringInitFromBuf(&sVal, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); + return jx9TokenValueToInt64(&sVal); +} +/* + * Return some kind of integer value which is the best we can + * do at representing the value that pObj describes as an integer. + * If pObj is an integer, then the value is exact. If pObj is + * a floating-point then the value returned is the integer part. + * If pObj is a string, then we make an attempt to convert it into + * a integer and return that. + * If pObj represents a NULL value, return 0. + */ +static sxi64 MemObjIntValue(jx9_value *pObj) +{ + sxi32 iFlags; + iFlags = pObj->iFlags; + if (iFlags & MEMOBJ_REAL ){ + return MemObjRealToInt(&(*pObj)); + }else if( iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + return pObj->x.iVal; + }else if (iFlags & MEMOBJ_STRING) { + return MemObjStringToInt(&(*pObj)); + }else if( iFlags & MEMOBJ_NULL ){ + return 0; + }else if( iFlags & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; + sxu32 n = pMap->nEntry; + jx9HashmapUnref(pMap); + /* Return total number of entries in the hashmap */ + return n; + }else if(iFlags & MEMOBJ_RES ){ + return pObj->x.pOther != 0; + } + /* CANT HAPPEN */ + return 0; +} +/* + * Return some kind of real value which is the best we can + * do at representing the value that pObj describes as a real. + * If pObj is a real, then the value is exact.If pObj is an + * integer then the integer is promoted to real and that value + * is returned. + * If pObj is a string, then we make an attempt to convert it + * into a real and return that. + * If pObj represents a NULL value, return 0.0 + */ +static jx9_real MemObjRealValue(jx9_value *pObj) +{ + sxi32 iFlags; + iFlags = pObj->iFlags; + if( iFlags & MEMOBJ_REAL ){ + return pObj->x.rVal; + }else if (iFlags & (MEMOBJ_INT|MEMOBJ_BOOL) ){ + return (jx9_real)pObj->x.iVal; + }else if (iFlags & MEMOBJ_STRING){ + SyString sString; +#ifdef JX9_OMIT_FLOATING_POINT + jx9_real rVal = 0; +#else + jx9_real rVal = 0.0; +#endif + SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); + if( SyBlobLength(&pObj->sBlob) > 0 ){ + /* Convert as much as we can */ +#ifdef JX9_OMIT_FLOATING_POINT + rVal = MemObjStringToInt(&(*pObj)); +#else + SyStrToReal(sString.zString, sString.nByte, (void *)&rVal, 0); +#endif + } + return rVal; + }else if( iFlags & MEMOBJ_NULL ){ +#ifdef JX9_OMIT_FLOATING_POINT + return 0; +#else + return 0.0; +#endif + }else if( iFlags & MEMOBJ_HASHMAP ){ + /* Return the total number of entries in the hashmap */ + jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; + jx9_real n = (jx9_real)pMap->nEntry; + jx9HashmapUnref(pMap); + return n; + }else if(iFlags & MEMOBJ_RES ){ + return (jx9_real)(pObj->x.pOther != 0); + } + /* NOT REACHED */ + return 0; +} +/* + * Return the string representation of a given jx9_value. + * This function never fail and always return SXRET_OK. + */ +static sxi32 MemObjStringValue(SyBlob *pOut,jx9_value *pObj) +{ + if( pObj->iFlags & MEMOBJ_REAL ){ + SyBlobFormat(&(*pOut), "%.15g", pObj->x.rVal); + }else if( pObj->iFlags & MEMOBJ_INT ){ + SyBlobFormat(&(*pOut), "%qd", pObj->x.iVal); + /* %qd (BSD quad) is equivalent to %lld in the libc printf */ + }else if( pObj->iFlags & MEMOBJ_BOOL ){ + if( pObj->x.iVal ){ + SyBlobAppend(&(*pOut),"true", sizeof("true")-1); + }else{ + SyBlobAppend(&(*pOut),"false", sizeof("false")-1); + } + }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ + /* Serialize JSON object or array */ + jx9JsonSerialize(pObj,pOut); + jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther); + }else if(pObj->iFlags & MEMOBJ_RES ){ + SyBlobFormat(&(*pOut), "ResourceID_%#x", pObj->x.pOther); + } + return SXRET_OK; +} +/* + * Return some kind of boolean value which is the best we can do + * at representing the value that pObj describes as a boolean. + * When converting to boolean, the following values are considered FALSE: + * NULL + * the boolean FALSE itself. + * the integer 0 (zero). + * the real 0.0 (zero). + * the empty string, a stream of zero [i.e: "0", "00", "000", ...] and the string + * "false". + * an array with zero elements. + */ +static sxi32 MemObjBooleanValue(jx9_value *pObj) +{ + sxi32 iFlags; + iFlags = pObj->iFlags; + if (iFlags & MEMOBJ_REAL ){ +#ifdef JX9_OMIT_FLOATING_POINT + return pObj->x.rVal ? 1 : 0; +#else + return pObj->x.rVal != 0.0 ? 1 : 0; +#endif + }else if( iFlags & MEMOBJ_INT ){ + return pObj->x.iVal ? 1 : 0; + }else if (iFlags & MEMOBJ_STRING) { + SyString sString; + SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); + if( sString.nByte == 0 ){ + /* Empty string */ + return 0; + }else if( (sString.nByte == sizeof("true") - 1 && SyStrnicmp(sString.zString, "true", sizeof("true")-1) == 0) || + (sString.nByte == sizeof("on") - 1 && SyStrnicmp(sString.zString, "on", sizeof("on")-1) == 0) || + (sString.nByte == sizeof("yes") - 1 && SyStrnicmp(sString.zString, "yes", sizeof("yes")-1) == 0) ){ + return 1; + }else if( sString.nByte == sizeof("false") - 1 && SyStrnicmp(sString.zString, "false", sizeof("false")-1) == 0 ){ + return 0; + }else{ + const char *zIn, *zEnd; + zIn = sString.zString; + zEnd = &zIn[sString.nByte]; + while( zIn < zEnd && zIn[0] == '0' ){ + zIn++; + } + return zIn >= zEnd ? 0 : 1; + } + }else if( iFlags & MEMOBJ_NULL ){ + return 0; + }else if( iFlags & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; + sxu32 n = pMap->nEntry; + jx9HashmapUnref(pMap); + return n > 0 ? TRUE : FALSE; + }else if(iFlags & MEMOBJ_RES ){ + return pObj->x.pOther != 0; + } + /* NOT REACHED */ + return 0; +} +/* + * If the jx9_value is of type real, try to make it an integer also. + */ +static sxi32 MemObjTryIntger(jx9_value *pObj) +{ + sxi64 iVal = MemObjRealToInt(&(*pObj)); + /* Only mark the value as an integer if + ** + ** (1) the round-trip conversion real->int->real is a no-op, and + ** (2) The integer is neither the largest nor the smallest + ** possible integer + ** + ** The second and third terms in the following conditional enforces + ** the second condition under the assumption that addition overflow causes + ** values to wrap around. On x86 hardware, the third term is always + ** true and could be omitted. But we leave it in because other + ** architectures might behave differently. + */ + if( pObj->x.rVal ==(jx9_real)iVal && iVal>SMALLEST_INT64 && iValx.iVal = iVal; + pObj->iFlags = MEMOBJ_INT; + } + return SXRET_OK; +} +/* + * Convert a jx9_value to type integer.Invalidate any prior representations. + */ +JX9_PRIVATE sxi32 jx9MemObjToInteger(jx9_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_INT) == 0 ){ + /* Preform the conversion */ + pObj->x.iVal = MemObjIntValue(&(*pObj)); + /* Invalidate any prior representations */ + SyBlobRelease(&pObj->sBlob); + MemObjSetType(pObj, MEMOBJ_INT); + } + return SXRET_OK; +} +/* + * Convert a jx9_value to type real (Try to get an integer representation also). + * Invalidate any prior representations + */ +JX9_PRIVATE sxi32 jx9MemObjToReal(jx9_value *pObj) +{ + if((pObj->iFlags & MEMOBJ_REAL) == 0 ){ + /* Preform the conversion */ + pObj->x.rVal = MemObjRealValue(&(*pObj)); + /* Invalidate any prior representations */ + SyBlobRelease(&pObj->sBlob); + MemObjSetType(pObj, MEMOBJ_REAL); + } + return SXRET_OK; +} +/* + * Convert a jx9_value to type boolean.Invalidate any prior representations. + */ +JX9_PRIVATE sxi32 jx9MemObjToBool(jx9_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_BOOL) == 0 ){ + /* Preform the conversion */ + pObj->x.iVal = MemObjBooleanValue(&(*pObj)); + /* Invalidate any prior representations */ + SyBlobRelease(&pObj->sBlob); + MemObjSetType(pObj, MEMOBJ_BOOL); + } + return SXRET_OK; +} +/* + * Convert a jx9_value to type string.Prior representations are NOT invalidated. + */ +JX9_PRIVATE sxi32 jx9MemObjToString(jx9_value *pObj) +{ + sxi32 rc = SXRET_OK; + if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ + /* Perform the conversion */ + SyBlobReset(&pObj->sBlob); /* Reset the internal buffer */ + rc = MemObjStringValue(&pObj->sBlob, &(*pObj)); + MemObjSetType(pObj, MEMOBJ_STRING); + } + return rc; +} +/* + * Nullify a jx9_value.In other words invalidate any prior + * representation. + */ +JX9_PRIVATE sxi32 jx9MemObjToNull(jx9_value *pObj) +{ + return jx9MemObjRelease(pObj); +} +/* + * Convert a jx9_value to type array.Invalidate any prior representations. + * According to the JX9 language reference manual. + * For any of the types: integer, float, string, boolean converting a value + * to an array results in an array with a single element with index zero + * and the value of the scalar which was converted. + */ +JX9_PRIVATE sxi32 jx9MemObjToHashmap(jx9_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ + jx9_hashmap *pMap; + /* Allocate a new hashmap instance */ + pMap = jx9NewHashmap(pObj->pVm, 0, 0); + if( pMap == 0 ){ + return SXERR_MEM; + } + if( (pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_RES)) == 0 ){ + /* + * According to the JX9 language reference manual. + * For any of the types: integer, float, string, boolean converting a value + * to an array results in an array with a single element with index zero + * and the value of the scalar which was converted. + */ + /* Insert a single element */ + jx9HashmapInsert(pMap, 0/* Automatic index assign */, &(*pObj)); + SyBlobRelease(&pObj->sBlob); + } + /* Invalidate any prior representation */ + MemObjSetType(pObj, MEMOBJ_HASHMAP); + pObj->x.pOther = pMap; + } + return SXRET_OK; +} +/* + * Return a pointer to the appropriate convertion method associated + * with the given type. + * Note on type juggling. + * Accoding to the JX9 language reference manual + * JX9 does not require (or support) explicit type definition in variable + * declaration; a variable's type is determined by the context in which + * the variable is used. That is to say, if a string value is assigned + * to variable $var, $var becomes a string. If an integer value is then + * assigned to $var, it becomes an integer. + */ +JX9_PRIVATE ProcMemObjCast jx9MemObjCastMethod(sxi32 iFlags) +{ + if( iFlags & MEMOBJ_STRING ){ + return jx9MemObjToString; + }else if( iFlags & MEMOBJ_INT ){ + return jx9MemObjToInteger; + }else if( iFlags & MEMOBJ_REAL ){ + return jx9MemObjToReal; + }else if( iFlags & MEMOBJ_BOOL ){ + return jx9MemObjToBool; + }else if( iFlags & MEMOBJ_HASHMAP ){ + return jx9MemObjToHashmap; + } + /* NULL cast */ + return jx9MemObjToNull; +} +/* + * Check whether the jx9_value is numeric [i.e: int/float/bool] or looks + * like a numeric number [i.e: if the jx9_value is of type string.]. + * Return TRUE if numeric.FALSE otherwise. + */ +JX9_PRIVATE sxi32 jx9MemObjIsNumeric(jx9_value *pObj) +{ + if( pObj->iFlags & ( MEMOBJ_BOOL|MEMOBJ_INT|MEMOBJ_REAL) ){ + return TRUE; + }else if( pObj->iFlags & (MEMOBJ_NULL|MEMOBJ_HASHMAP|MEMOBJ_RES) ){ + return FALSE; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + SyString sStr; + sxi32 rc; + SyStringInitFromBuf(&sStr, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); + if( sStr.nByte <= 0 ){ + /* Empty string */ + return FALSE; + } + /* Check if the string representation looks like a numeric number */ + rc = SyStrIsNumeric(sStr.zString, sStr.nByte, 0, 0); + return rc == SXRET_OK ? TRUE : FALSE; + } + /* NOT REACHED */ + return FALSE; +} +/* + * Check whether the jx9_value is empty.Return TRUE if empty. + * FALSE otherwise. + * An jx9_value is considered empty if the following are true: + * NULL value. + * Boolean FALSE. + * Integer/Float with a 0 (zero) value. + * An empty string or a stream of 0 (zero) [i.e: "0", "00", "000", ...]. + * An empty array. + * NOTE + * OBJECT VALUE MUST NOT BE MODIFIED. + */ +JX9_PRIVATE sxi32 jx9MemObjIsEmpty(jx9_value *pObj) +{ + if( pObj->iFlags & MEMOBJ_NULL ){ + return TRUE; + }else if( pObj->iFlags & MEMOBJ_INT ){ + return pObj->x.iVal == 0 ? TRUE : FALSE; + }else if( pObj->iFlags & MEMOBJ_REAL ){ + return pObj->x.rVal == (jx9_real)0 ? TRUE : FALSE; + }else if( pObj->iFlags & MEMOBJ_BOOL ){ + return !pObj->x.iVal; + }else if( pObj->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pObj->sBlob) <= 0 ){ + return TRUE; + }else{ + const char *zIn, *zEnd; + zIn = (const char *)SyBlobData(&pObj->sBlob); + zEnd = &zIn[SyBlobLength(&pObj->sBlob)]; + while( zIn < zEnd ){ + if( zIn[0] != '0' ){ + break; + } + zIn++; + } + return zIn >= zEnd ? TRUE : FALSE; + } + }else if( pObj->iFlags & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; + return pMap->nEntry == 0 ? TRUE : FALSE; + }else if ( pObj->iFlags & (MEMOBJ_RES) ){ + return FALSE; + } + /* Assume empty by default */ + return TRUE; +} +/* + * Convert a jx9_value so that it has types MEMOBJ_REAL or MEMOBJ_INT + * or both. + * Invalidate any prior representations. Every effort is made to force + * the conversion, even if the input is a string that does not look + * completely like a number.Convert as much of the string as we can + * and ignore the rest. + */ +JX9_PRIVATE sxi32 jx9MemObjToNumeric(jx9_value *pObj) +{ + if( pObj->iFlags & (MEMOBJ_INT|MEMOBJ_REAL|MEMOBJ_BOOL|MEMOBJ_NULL) ){ + if( pObj->iFlags & (MEMOBJ_BOOL|MEMOBJ_NULL) ){ + if( pObj->iFlags & MEMOBJ_NULL ){ + pObj->x.iVal = 0; + } + MemObjSetType(pObj, MEMOBJ_INT); + } + /* Already numeric */ + return SXRET_OK; + } + if( pObj->iFlags & MEMOBJ_STRING ){ + sxi32 rc = SXERR_INVALID; + sxu8 bReal = FALSE; + SyString sString; + SyStringInitFromBuf(&sString, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); + /* Check if the given string looks like a numeric number */ + if( sString.nByte > 0 ){ + rc = SyStrIsNumeric(sString.zString, sString.nByte, &bReal, 0); + } + if( bReal ){ + jx9MemObjToReal(&(*pObj)); + }else{ + if( rc != SXRET_OK ){ + /* The input does not look at all like a number, set the value to 0 */ + pObj->x.iVal = 0; + }else{ + /* Convert as much as we can */ + pObj->x.iVal = MemObjStringToInt(&(*pObj)); + } + MemObjSetType(pObj, MEMOBJ_INT); + SyBlobRelease(&pObj->sBlob); + } + }else if(pObj->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)){ + jx9MemObjToInteger(pObj); + }else{ + /* Perform a blind cast */ + jx9MemObjToReal(&(*pObj)); + } + return SXRET_OK; +} +/* + * Try a get an integer representation of the given jx9_value. + * If the jx9_value is not of type real, this function is a no-op. + */ +JX9_PRIVATE sxi32 jx9MemObjTryInteger(jx9_value *pObj) +{ + if( pObj->iFlags & MEMOBJ_REAL ){ + /* Work only with reals */ + MemObjTryIntger(&(*pObj)); + } + return SXRET_OK; +} +/* + * Initialize a jx9_value to the null type. + */ +JX9_PRIVATE sxi32 jx9MemObjInit(jx9_vm *pVm, jx9_value *pObj) +{ + /* Zero the structure */ + SyZero(pObj, sizeof(jx9_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob, &pVm->sAllocator); + /* Set the NULL type */ + pObj->iFlags = MEMOBJ_NULL; + return SXRET_OK; +} +/* + * Initialize a jx9_value to the integer type. + */ +JX9_PRIVATE sxi32 jx9MemObjInitFromInt(jx9_vm *pVm, jx9_value *pObj, sxi64 iVal) +{ + /* Zero the structure */ + SyZero(pObj, sizeof(jx9_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob, &pVm->sAllocator); + /* Set the desired type */ + pObj->x.iVal = iVal; + pObj->iFlags = MEMOBJ_INT; + return SXRET_OK; +} +/* + * Initialize a jx9_value to the boolean type. + */ +JX9_PRIVATE sxi32 jx9MemObjInitFromBool(jx9_vm *pVm, jx9_value *pObj, sxi32 iVal) +{ + /* Zero the structure */ + SyZero(pObj, sizeof(jx9_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob, &pVm->sAllocator); + /* Set the desired type */ + pObj->x.iVal = iVal ? 1 : 0; + pObj->iFlags = MEMOBJ_BOOL; + return SXRET_OK; +} +#if 0 +/* + * Initialize a jx9_value to the real type. + */ +JX9_PRIVATE sxi32 jx9MemObjInitFromReal(jx9_vm *pVm, jx9_value *pObj, jx9_real rVal) +{ + /* Zero the structure */ + SyZero(pObj, sizeof(jx9_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob, &pVm->sAllocator); + /* Set the desired type */ + pObj->x.rVal = rVal; + pObj->iFlags = MEMOBJ_REAL; + return SXRET_OK; +} +#endif +/* + * Initialize a jx9_value to the array type. + */ +JX9_PRIVATE sxi32 jx9MemObjInitFromArray(jx9_vm *pVm, jx9_value *pObj, jx9_hashmap *pArray) +{ + /* Zero the structure */ + SyZero(pObj, sizeof(jx9_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob, &pVm->sAllocator); + /* Set the desired type */ + pObj->iFlags = MEMOBJ_HASHMAP; + pObj->x.pOther = pArray; + return SXRET_OK; +} +/* + * Initialize a jx9_value to the string type. + */ +JX9_PRIVATE sxi32 jx9MemObjInitFromString(jx9_vm *pVm, jx9_value *pObj, const SyString *pVal) +{ + /* Zero the structure */ + SyZero(pObj, sizeof(jx9_value)); + /* Initialize fields */ + pObj->pVm = pVm; + SyBlobInit(&pObj->sBlob, &pVm->sAllocator); + if( pVal ){ + /* Append contents */ + SyBlobAppend(&pObj->sBlob, (const void *)pVal->zString, pVal->nByte); + } + /* Set the desired type */ + pObj->iFlags = MEMOBJ_STRING; + return SXRET_OK; +} +/* + * Append some contents to the internal buffer of a given jx9_value. + * If the given jx9_value is not of type string, this function + * invalidate any prior representation and set the string type. + * Then a simple append operation is performed. + */ +JX9_PRIVATE sxi32 jx9MemObjStringAppend(jx9_value *pObj, const char *zData, sxu32 nLen) +{ + sxi32 rc; + if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + jx9MemObjRelease(pObj); + MemObjSetType(pObj, MEMOBJ_STRING); + } + /* Append contents */ + rc = SyBlobAppend(&pObj->sBlob, zData, nLen); + return rc; +} +#if 0 +/* + * Format and append some contents to the internal buffer of a given jx9_value. + * If the given jx9_value is not of type string, this function invalidate + * any prior representation and set the string type. + * Then a simple format and append operation is performed. + */ +JX9_PRIVATE sxi32 jx9MemObjStringFormat(jx9_value *pObj, const char *zFormat, va_list ap) +{ + sxi32 rc; + if( (pObj->iFlags & MEMOBJ_STRING) == 0 ){ + /* Invalidate any prior representation */ + jx9MemObjRelease(pObj); + MemObjSetType(pObj, MEMOBJ_STRING); + } + /* Format and append contents */ + rc = SyBlobFormatAp(&pObj->sBlob, zFormat, ap); + return rc; +} +#endif +/* + * Duplicate the contents of a jx9_value. + */ +JX9_PRIVATE sxi32 jx9MemObjStore(jx9_value *pSrc, jx9_value *pDest) +{ + jx9_hashmap *pMap = 0; + sxi32 rc; + if( pSrc->iFlags & MEMOBJ_HASHMAP ){ + /* Increment reference count */ + ((jx9_hashmap *)pSrc->x.pOther)->iRef++; + } + if( pDest->iFlags & MEMOBJ_HASHMAP ){ + pMap = (jx9_hashmap *)pDest->x.pOther; + } + SyMemcpy((const void *)&(*pSrc), &(*pDest), sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32))); + rc = SXRET_OK; + if( SyBlobLength(&pSrc->sBlob) > 0 ){ + SyBlobReset(&pDest->sBlob); + rc = SyBlobDup(&pSrc->sBlob, &pDest->sBlob); + }else{ + if( SyBlobLength(&pDest->sBlob) > 0 ){ + SyBlobRelease(&pDest->sBlob); + } + } + if( pMap ){ + jx9HashmapUnref(pMap); + } + return rc; +} +/* + * Duplicate the contents of a jx9_value but do not copy internal + * buffer contents, simply point to it. + */ +JX9_PRIVATE sxi32 jx9MemObjLoad(jx9_value *pSrc, jx9_value *pDest) +{ + SyMemcpy((const void *)&(*pSrc), &(*pDest), + sizeof(jx9_value)-(sizeof(jx9_vm *)+sizeof(SyBlob)+sizeof(sxu32))); + if( pSrc->iFlags & MEMOBJ_HASHMAP ){ + /* Increment reference count */ + ((jx9_hashmap *)pSrc->x.pOther)->iRef++; + } + if( SyBlobLength(&pDest->sBlob) > 0 ){ + SyBlobRelease(&pDest->sBlob); + } + if( SyBlobLength(&pSrc->sBlob) > 0 ){ + SyBlobReadOnly(&pDest->sBlob, SyBlobData(&pSrc->sBlob), SyBlobLength(&pSrc->sBlob)); + } + return SXRET_OK; +} +/* + * Invalidate any prior representation of a given jx9_value. + */ +JX9_PRIVATE sxi32 jx9MemObjRelease(jx9_value *pObj) +{ + if( (pObj->iFlags & MEMOBJ_NULL) == 0 ){ + if( pObj->iFlags & MEMOBJ_HASHMAP ){ + jx9HashmapUnref((jx9_hashmap *)pObj->x.pOther); + } + /* Release the internal buffer */ + SyBlobRelease(&pObj->sBlob); + /* Invalidate any prior representation */ + pObj->iFlags = MEMOBJ_NULL; + } + return SXRET_OK; +} +/* + * Compare two jx9_values. + * Return 0 if the values are equals, > 0 if pObj1 is greater than pObj2 + * or < 0 if pObj2 is greater than pObj1. + * Type comparison table taken from the JX9 language reference manual. + * Comparisons of $x with JX9 functions Expression + * gettype() empty() is_null() isset() boolean : if($x) + * $x = ""; string TRUE FALSE TRUE FALSE + * $x = null NULL TRUE TRUE FALSE FALSE + * var $x; NULL TRUE TRUE FALSE FALSE + * $x is undefined NULL TRUE TRUE FALSE FALSE + * $x = array(); array TRUE FALSE TRUE FALSE + * $x = false; boolean TRUE FALSE TRUE FALSE + * $x = true; boolean FALSE FALSE TRUE TRUE + * $x = 1; integer FALSE FALSE TRUE TRUE + * $x = 42; integer FALSE FALSE TRUE TRUE + * $x = 0; integer TRUE FALSE TRUE FALSE + * $x = -1; integer FALSE FALSE TRUE TRUE + * $x = "1"; string FALSE FALSE TRUE TRUE + * $x = "0"; string TRUE FALSE TRUE FALSE + * $x = "-1"; string FALSE FALSE TRUE TRUE + * $x = "jx9"; string FALSE FALSE TRUE TRUE + * $x = "true"; string FALSE FALSE TRUE TRUE + * $x = "false"; string FALSE FALSE TRUE TRUE + * Loose comparisons with == + * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" "" + * TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE TRUE FALSE FALSE TRUE FALSE + * FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE TRUE FALSE TRUE + * 1 TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE + * 0 FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE TRUE FALSE TRUE TRUE + * -1 TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE + * "1" TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE + * "0" FALSE TRUE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE + * "-1" TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE + * NULL FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE TRUE FALSE TRUE + * array() FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE + * "jx9" TRUE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE + * "" FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE FALSE FALSE TRUE + * Strict comparisons with === + * TRUE FALSE 1 0 -1 "1" "0" "-1" NULL array() "jx9" "" + * TRUE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * 1 FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * 0 FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * -1 FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE + * "1" FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE + * "0" FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE + * "-1" FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE + * NULL FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE + * array() FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE + * "jx9" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE + * "" FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE + */ +JX9_PRIVATE sxi32 jx9MemObjCmp(jx9_value *pObj1, jx9_value *pObj2, int bStrict, int iNest) +{ + sxi32 iComb; + sxi32 rc; + if( bStrict ){ + sxi32 iF1, iF2; + /* Strict comparisons with === */ + iF1 = pObj1->iFlags; + iF2 = pObj2->iFlags; + if( iF1 != iF2 ){ + /* Not of the same type */ + return 1; + } + } + /* Combine flag together */ + iComb = pObj1->iFlags|pObj2->iFlags; + if( iComb & (MEMOBJ_RES|MEMOBJ_BOOL) ){ + /* Convert to boolean: Keep in mind FALSE < TRUE */ + if( (pObj1->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pObj1); + } + if( (pObj2->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pObj2); + } + return (sxi32)((pObj1->x.iVal != 0) - (pObj2->x.iVal != 0)); + }else if( iComb & MEMOBJ_NULL ){ + if( (pObj1->iFlags & MEMOBJ_NULL) == 0 ){ + return 1; + } + if( (pObj2->iFlags & MEMOBJ_NULL) == 0 ){ + return -1; + } + }else if ( iComb & MEMOBJ_HASHMAP ){ + /* Hashmap aka 'array' comparison */ + if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Array is always greater */ + return -1; + } + if( (pObj2->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Array is always greater */ + return 1; + } + /* Perform the comparison */ + rc = jx9HashmapCmp((jx9_hashmap *)pObj1->x.pOther, (jx9_hashmap *)pObj2->x.pOther, bStrict); + return rc; + }else if ( iComb & MEMOBJ_STRING ){ + SyString s1, s2; + /* Perform a strict string comparison.*/ + if( (pObj1->iFlags&MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pObj1); + } + if( (pObj2->iFlags&MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pObj2); + } + SyStringInitFromBuf(&s1, SyBlobData(&pObj1->sBlob), SyBlobLength(&pObj1->sBlob)); + SyStringInitFromBuf(&s2, SyBlobData(&pObj2->sBlob), SyBlobLength(&pObj2->sBlob)); + /* + * Strings are compared using memcmp(). If one value is an exact prefix of the + * other, then the shorter value is less than the longer value. + */ + rc = SyMemcmp((const void *)s1.zString, (const void *)s2.zString, SXMIN(s1.nByte, s2.nByte)); + if( rc == 0 ){ + if( s1.nByte != s2.nByte ){ + rc = s1.nByte < s2.nByte ? -1 : 1; + } + } + return rc; + }else if( iComb & (MEMOBJ_INT|MEMOBJ_REAL) ){ + /* Perform a numeric comparison if one of the operand is numeric(integer or real) */ + if( (pObj1->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ + jx9MemObjToNumeric(pObj1); + } + if( (pObj2->iFlags & (MEMOBJ_INT|MEMOBJ_REAL)) == 0 ){ + jx9MemObjToNumeric(pObj2); + } + if( (pObj1->iFlags & pObj2->iFlags & MEMOBJ_INT) == 0) { + jx9_real r1, r2; + /* Compare as reals */ + if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pObj1); + } + r1 = pObj1->x.rVal; + if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pObj2); + } + r2 = pObj2->x.rVal; + if( r1 > r2 ){ + return 1; + }else if( r1 < r2 ){ + return -1; + } + return 0; + }else{ + /* Integer comparison */ + if( pObj1->x.iVal > pObj2->x.iVal ){ + return 1; + }else if( pObj1->x.iVal < pObj2->x.iVal ){ + return -1; + } + return 0; + } + } + /* NOT REACHED */ + SXUNUSED(iNest); + return 0; +} +/* + * Perform an addition operation of two jx9_values. + * The reason this function is implemented here rather than 'vm.c' + * is that the '+' operator is overloaded. + * That is, the '+' operator is used for arithmetic operation and also + * used for operation on arrays [i.e: union]. When used with an array + * The + operator returns the right-hand array appended to the left-hand array. + * For keys that exist in both arrays, the elements from the left-hand array + * will be used, and the matching elements from the right-hand array will + * be ignored. + * This function take care of handling all the scenarios. + */ +JX9_PRIVATE sxi32 jx9MemObjAdd(jx9_value *pObj1, jx9_value *pObj2, int bAddStore) +{ + if( ((pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP) == 0 ){ + /* Arithemtic operation */ + jx9MemObjToNumeric(pObj1); + jx9MemObjToNumeric(pObj2); + if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_REAL ){ + /* Floating point arithmetic */ + jx9_real a, b; + if( (pObj1->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pObj1); + } + if( (pObj2->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pObj2); + } + a = pObj1->x.rVal; + b = pObj2->x.rVal; + pObj1->x.rVal = a+b; + MemObjSetType(pObj1, MEMOBJ_REAL); + /* Try to get an integer representation also */ + MemObjTryIntger(&(*pObj1)); + }else{ + /* Integer arithmetic */ + sxi64 a, b; + a = pObj1->x.iVal; + b = pObj2->x.iVal; + pObj1->x.iVal = a+b; + MemObjSetType(pObj1, MEMOBJ_INT); + } + }else{ + if( (pObj1->iFlags|pObj2->iFlags) & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap; + sxi32 rc; + if( bAddStore ){ + /* Do not duplicate the hashmap, use the left one since its an add&store operation. + */ + if( (pObj1->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Force a hashmap cast */ + rc = jx9MemObjToHashmap(pObj1); + if( rc != SXRET_OK ){ + jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array"); + return rc; + } + } + /* Point to the structure that describe the hashmap */ + pMap = (jx9_hashmap *)pObj1->x.pOther; + }else{ + /* Create a new hashmap */ + pMap = jx9NewHashmap(pObj1->pVm, 0, 0); + if( pMap == 0){ + jx9VmThrowError(pObj1->pVm, 0, JX9_CTX_ERR, "JX9 is running out of memory while creating array"); + return SXERR_MEM; + } + } + if( !bAddStore ){ + if(pObj1->iFlags & MEMOBJ_HASHMAP ){ + /* Perform a hashmap duplication */ + jx9HashmapDup((jx9_hashmap *)pObj1->x.pOther, pMap); + }else{ + if((pObj1->iFlags & MEMOBJ_NULL) == 0 ){ + /* Simple insertion */ + jx9HashmapInsert(pMap, 0, pObj1); + } + } + } + /* Perform the union */ + if(pObj2->iFlags & MEMOBJ_HASHMAP ){ + jx9HashmapUnion(pMap, (jx9_hashmap *)pObj2->x.pOther); + }else{ + if((pObj2->iFlags & MEMOBJ_NULL) == 0 ){ + /* Simple insertion */ + jx9HashmapInsert(pMap, 0, pObj2); + } + } + /* Reflect the change */ + if( pObj1->iFlags & MEMOBJ_STRING ){ + SyBlobRelease(&pObj1->sBlob); + } + pObj1->x.pOther = pMap; + MemObjSetType(pObj1, MEMOBJ_HASHMAP); + } + } + return SXRET_OK; +} +/* + * Return a printable representation of the type of a given + * jx9_value. + */ +JX9_PRIVATE const char * jx9MemObjTypeDump(jx9_value *pVal) +{ + const char *zType = ""; + if( pVal->iFlags & MEMOBJ_NULL ){ + zType = "null"; + }else if( pVal->iFlags & MEMOBJ_INT ){ + zType = "int"; + }else if( pVal->iFlags & MEMOBJ_REAL ){ + zType = "float"; + }else if( pVal->iFlags & MEMOBJ_STRING ){ + zType = "string"; + }else if( pVal->iFlags & MEMOBJ_BOOL ){ + zType = "bool"; + }else if( pVal->iFlags & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap = (jx9_hashmap *)pVal->x.pOther; + if( pMap->iFlags & HASHMAP_JSON_OBJECT ){ + zType = "JSON Object"; + }else{ + zType = "JSON Array"; + } + }else if( pVal->iFlags & MEMOBJ_RES ){ + zType = "resource"; + } + return zType; +} +/* + * Dump a jx9_value [i.e: get a printable representation of it's type and contents.]. + * Store the dump in the given blob. + */ +JX9_PRIVATE sxi32 jx9MemObjDump( + SyBlob *pOut, /* Store the dump here */ + jx9_value *pObj /* Dump this */ + ) +{ + sxi32 rc = SXRET_OK; + const char *zType; + /* Get value type first */ + zType = jx9MemObjTypeDump(pObj); + SyBlobAppend(&(*pOut), zType, SyStrlen(zType)); + if((pObj->iFlags & MEMOBJ_NULL) == 0 ){ + SyBlobAppend(&(*pOut), "(", sizeof(char)); + if( pObj->iFlags & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap = (jx9_hashmap *)pObj->x.pOther; + SyBlobFormat(pOut,"%u ",pMap->nEntry); + /* Dump hashmap entries */ + rc = jx9JsonSerialize(pObj,pOut); + }else{ + SyBlob *pContents = &pObj->sBlob; + /* Get a printable representation of the contents */ + if((pObj->iFlags & MEMOBJ_STRING) == 0 ){ + MemObjStringValue(&(*pOut), &(*pObj)); + }else{ + /* Append length first */ + SyBlobFormat(&(*pOut), "%u '", SyBlobLength(&pObj->sBlob)); + if( SyBlobLength(pContents) > 0 ){ + SyBlobAppend(&(*pOut), SyBlobData(pContents), SyBlobLength(pContents)); + } + SyBlobAppend(&(*pOut), "'", sizeof(char)); + } + } + SyBlobAppend(&(*pOut), ")", sizeof(char)); + } +#ifdef __WINNT__ + SyBlobAppend(&(*pOut), "\r\n", sizeof("\r\n")-1); +#else + SyBlobAppend(&(*pOut), "\n", sizeof(char)); +#endif + return rc; +} +/* + * ---------------------------------------------------------- + * File: jx9_parse.c + * MD5: d8fcac4c6cd7672f0103c0bf4a4b61fc + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: parse.c v1.2 FreeBSD 2012-12-11 00:46 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* Expression parser for the Jx9 programming language */ +/* Operators associativity */ +#define EXPR_OP_ASSOC_LEFT 0x01 /* Left associative operator */ +#define EXPR_OP_ASSOC_RIGHT 0x02 /* Right associative operator */ +#define EXPR_OP_NON_ASSOC 0x04 /* Non-associative operator */ +/* + * Operators table + * This table is sorted by operators priority (highest to lowest) according + * the JX9 language reference manual. + * JX9 implements all the 60 JX9 operators and have introduced the eq and ne operators. + * The operators precedence table have been improved dramatically so that you can do same + * amazing things now such as array dereferencing, on the fly function call, anonymous function + * as array values, object member access on instantiation and so on. + * Refer to the following page for a full discussion on these improvements: + * http://jx9.symisc.net/features.html + */ +static const jx9_expr_op aOpTable[] = { + /* Postfix operators */ + /* Precedence 2(Highest), left-associative */ + { {".", sizeof(char)}, EXPR_OP_DOT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_MEMBER }, + { {"[", sizeof(char)}, EXPR_OP_SUBSCRIPT, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_LOAD_IDX}, + /* Precedence 3, non-associative */ + { {"++", sizeof(char)*2}, EXPR_OP_INCR, 3, EXPR_OP_NON_ASSOC , JX9_OP_INCR}, + { {"--", sizeof(char)*2}, EXPR_OP_DECR, 3, EXPR_OP_NON_ASSOC , JX9_OP_DECR}, + /* Unary operators */ + /* Precedence 4, right-associative */ + { {"-", sizeof(char)}, EXPR_OP_UMINUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UMINUS }, + { {"+", sizeof(char)}, EXPR_OP_UPLUS, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_UPLUS }, + { {"~", sizeof(char)}, EXPR_OP_BITNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_BITNOT }, + { {"!", sizeof(char)}, EXPR_OP_LOGNOT, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_LNOT }, + /* Cast operators */ + { {"(int)", sizeof("(int)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_INT }, + { {"(bool)", sizeof("(bool)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_BOOL }, + { {"(string)", sizeof("(string)")-1}, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_STR }, + { {"(float)", sizeof("(float)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_REAL }, + { {"(array)", sizeof("(array)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */ + { {"(object)", sizeof("(object)")-1 }, EXPR_OP_TYPECAST, 4, EXPR_OP_ASSOC_RIGHT, JX9_OP_CVT_ARRAY }, /* Not used, but reserved for future use */ + /* Binary operators */ + /* Precedence 7, left-associative */ + { {"*", sizeof(char)}, EXPR_OP_MUL, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MUL}, + { {"/", sizeof(char)}, EXPR_OP_DIV, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_DIV}, + { {"%", sizeof(char)}, EXPR_OP_MOD, 7, EXPR_OP_ASSOC_LEFT , JX9_OP_MOD}, + /* Precedence 8, left-associative */ + { {"+", sizeof(char)}, EXPR_OP_ADD, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_ADD}, + { {"-", sizeof(char)}, EXPR_OP_SUB, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_SUB}, + { {"..", sizeof(char)*2},EXPR_OP_DDOT, 8, EXPR_OP_ASSOC_LEFT, JX9_OP_CAT}, + /* Precedence 9, left-associative */ + { {"<<", sizeof(char)*2}, EXPR_OP_SHL, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHL}, + { {">>", sizeof(char)*2}, EXPR_OP_SHR, 9, EXPR_OP_ASSOC_LEFT, JX9_OP_SHR}, + /* Precedence 10, non-associative */ + { {"<", sizeof(char)}, EXPR_OP_LT, 10, EXPR_OP_NON_ASSOC, JX9_OP_LT}, + { {">", sizeof(char)}, EXPR_OP_GT, 10, EXPR_OP_NON_ASSOC, JX9_OP_GT}, + { {"<=", sizeof(char)*2}, EXPR_OP_LE, 10, EXPR_OP_NON_ASSOC, JX9_OP_LE}, + { {">=", sizeof(char)*2}, EXPR_OP_GE, 10, EXPR_OP_NON_ASSOC, JX9_OP_GE}, + { {"<>", sizeof(char)*2}, EXPR_OP_NE, 10, EXPR_OP_NON_ASSOC, JX9_OP_NEQ}, + /* Precedence 11, non-associative */ + { {"==", sizeof(char)*2}, EXPR_OP_EQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_EQ}, + { {"!=", sizeof(char)*2}, EXPR_OP_NE, 11, EXPR_OP_NON_ASSOC, JX9_OP_NEQ}, + { {"===", sizeof(char)*3}, EXPR_OP_TEQ, 11, EXPR_OP_NON_ASSOC, JX9_OP_TEQ}, + { {"!==", sizeof(char)*3}, EXPR_OP_TNE, 11, EXPR_OP_NON_ASSOC, JX9_OP_TNE}, + /* Precedence 12, left-associative */ + { {"&", sizeof(char)}, EXPR_OP_BAND, 12, EXPR_OP_ASSOC_LEFT, JX9_OP_BAND}, + /* Binary operators */ + /* Precedence 13, left-associative */ + { {"^", sizeof(char)}, EXPR_OP_XOR, 13, EXPR_OP_ASSOC_LEFT, JX9_OP_BXOR}, + /* Precedence 14, left-associative */ + { {"|", sizeof(char)}, EXPR_OP_BOR, 14, EXPR_OP_ASSOC_LEFT, JX9_OP_BOR}, + /* Precedence 15, left-associative */ + { {"&&", sizeof(char)*2}, EXPR_OP_LAND, 15, EXPR_OP_ASSOC_LEFT, JX9_OP_LAND}, + /* Precedence 16, left-associative */ + { {"||", sizeof(char)*2}, EXPR_OP_LOR, 16, EXPR_OP_ASSOC_LEFT, JX9_OP_LOR}, + /* Ternary operator */ + /* Precedence 17, left-associative */ + { {"?", sizeof(char)}, EXPR_OP_QUESTY, 17, EXPR_OP_ASSOC_LEFT, 0}, + /* Combined binary operators */ + /* Precedence 18, right-associative */ + { {"=", sizeof(char)}, EXPR_OP_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_STORE}, + { {"+=", sizeof(char)*2}, EXPR_OP_ADD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_ADD_STORE }, + { {"-=", sizeof(char)*2}, EXPR_OP_SUB_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SUB_STORE }, + { {".=", sizeof(char)*2}, EXPR_OP_DOT_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_CAT_STORE }, + { {"*=", sizeof(char)*2}, EXPR_OP_MUL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MUL_STORE }, + { {"/=", sizeof(char)*2}, EXPR_OP_DIV_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_DIV_STORE }, + { {"%=", sizeof(char)*2}, EXPR_OP_MOD_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_MOD_STORE }, + { {"&=", sizeof(char)*2}, EXPR_OP_AND_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BAND_STORE }, + { {"|=", sizeof(char)*2}, EXPR_OP_OR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BOR_STORE }, + { {"^=", sizeof(char)*2}, EXPR_OP_XOR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_BXOR_STORE }, + { {"<<=", sizeof(char)*3}, EXPR_OP_SHL_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHL_STORE }, + { {">>=", sizeof(char)*3}, EXPR_OP_SHR_ASSIGN, 18, EXPR_OP_ASSOC_RIGHT, JX9_OP_SHR_STORE }, + /* Precedence 22, left-associative [Lowest operator] */ + { {",", sizeof(char)}, EXPR_OP_COMMA, 22, EXPR_OP_ASSOC_LEFT, 0}, /* IMP-0139-COMMA: Symisc eXtension */ +}; +/* Function call operator need special handling */ +static const jx9_expr_op sFCallOp = {{"(", sizeof(char)}, EXPR_OP_FUNC_CALL, 2, EXPR_OP_ASSOC_LEFT , JX9_OP_CALL}; +/* + * Check if the given token is a potential operator or not. + * This function is called by the lexer each time it extract a token that may + * look like an operator. + * Return a structure [i.e: jx9_expr_op instnace ] that describe the operator on success. + * Otherwise NULL. + * Note that the function take care of handling ambiguity [i.e: whether we are dealing with + * a binary minus or unary minus.] + */ +JX9_PRIVATE const jx9_expr_op * jx9ExprExtractOperator(SyString *pStr, SyToken *pLast) +{ + sxu32 n = 0; + sxi32 rc; + /* Do a linear lookup on the operators table */ + for(;;){ + if( n >= SX_ARRAYSIZE(aOpTable) ){ + break; + } + rc = SyStringCmp(pStr, &aOpTable[n].sOp, SyMemcmp); + if( rc == 0 ){ + if( aOpTable[n].sOp.nByte != sizeof(char) || (aOpTable[n].iOp != EXPR_OP_UMINUS && aOpTable[n].iOp != EXPR_OP_UPLUS) || pLast == 0 ){ + if( aOpTable[n].iOp == EXPR_OP_SUBSCRIPT && (pLast == 0 || (pLast->nType & (JX9_TK_ID|JX9_TK_CSB/*]*/|JX9_TK_RPAREN/*)*/)) == 0) ){ + /* JSON Array not subscripting, return NULL */ + return 0; + } + /* There is no ambiguity here, simply return the first operator seen */ + return &aOpTable[n]; + } + /* Handle ambiguity */ + if( pLast->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OCB/*'{'*/|JX9_TK_OSB/*'['*/|JX9_TK_COLON/*:*/|JX9_TK_COMMA/*, '*/) ){ + /* Unary opertors have prcedence here over binary operators */ + return &aOpTable[n]; + } + if( pLast->nType & JX9_TK_OP ){ + const jx9_expr_op *pOp = (const jx9_expr_op *)pLast->pUserData; + /* Ticket 1433-31: Handle the '++', '--' operators case */ + if( pOp->iOp != EXPR_OP_INCR && pOp->iOp != EXPR_OP_DECR ){ + /* Unary opertors have prcedence here over binary operators */ + return &aOpTable[n]; + } + + } + } + ++n; /* Next operator in the table */ + } + /* No such operator */ + return 0; +} +/* + * Delimit a set of token stream. + * This function take care of handling the nesting level and stops when it hit + * the end of the input or the ending token is found and the nesting level is zero. + */ +JX9_PRIVATE void jx9DelimitNestedTokens(SyToken *pIn,SyToken *pEnd,sxu32 nTokStart,sxu32 nTokEnd,SyToken **ppEnd) +{ + SyToken *pCur = pIn; + sxi32 iNest = 1; + for(;;){ + if( pCur >= pEnd ){ + break; + } + if( pCur->nType & nTokStart ){ + /* Increment nesting level */ + iNest++; + }else if( pCur->nType & nTokEnd ){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + } + /* Advance cursor */ + pCur++; + } + /* Point to the end of the chunk */ + *ppEnd = pCur; +} +/* + * Retrun TRUE if the given ID represent a language construct [i.e: print, print..]. FALSE otherwise. + * Note on reserved keywords. + * According to the JX9 language reference manual: + * These words have special meaning in JX9. Some of them represent things which look like + * functions, some look like constants, and so on--but they're not, really: they are language + * constructs. You cannot use any of the following words as constants, object names, function + * or method names. Using them as variable names is generally OK, but could lead to confusion. + */ +JX9_PRIVATE int jx9IsLangConstruct(sxu32 nKeyID) +{ + if( nKeyID == JX9_TKWRD_PRINT || nKeyID == JX9_TKWRD_EXIT || nKeyID == JX9_TKWRD_DIE + || nKeyID == JX9_TKWRD_INCLUDE|| nKeyID == JX9_TKWRD_IMPORT ){ + return TRUE; + } + /* Not a language construct */ + return FALSE; +} +/* + * Point to the next expression that should be evaluated shortly. + * The cursor stops when it hit a comma ', ' or a semi-colon and the nesting + * level is zero. + */ +JX9_PRIVATE sxi32 jx9GetNextExpr(SyToken *pStart,SyToken *pEnd,SyToken **ppNext) +{ + SyToken *pCur = pStart; + sxi32 iNest = 0; + if( pCur >= pEnd || (pCur->nType & JX9_TK_SEMI/*';'*/) ){ + /* Last expression */ + return SXERR_EOF; + } + while( pCur < pEnd ){ + if( (pCur->nType & (JX9_TK_COMMA/*','*/|JX9_TK_SEMI/*';'*/)) && iNest <= 0){ + break; + } + if( pCur->nType & (JX9_TK_LPAREN/*'('*/|JX9_TK_OSB/*'['*/|JX9_TK_OCB/*'{'*/) ){ + iNest++; + }else if( pCur->nType & (JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*']'*/|JX9_TK_CCB/*'}*/) ){ + iNest--; + } + pCur++; + } + *ppNext = pCur; + return SXRET_OK; +} +/* + * Collect and assemble tokens holding annonymous functions/closure body. + * When errors, JX9 take care of generating the appropriate error message. + * Note on annonymous functions. + * According to the JX9 language reference manual: + * Anonymous functions, also known as closures, allow the creation of functions + * which have no specified name. They are most useful as the value of callback + * parameters, but they have many other uses. + * Closures may also inherit variables from the parent scope. Any such variables + * must be declared in the function header. Inheriting variables from the parent + * scope is not the same as using global variables. Global variables exist in the global scope + * which is the same no matter what function is executing. The parent scope of a closure is the + * function in which the closure was declared (not necessarily the function it was called from). + * + * Some example: + * $greet = function($name) + * { + * printf("Hello %s\r\n", $name); + * }; + * $greet('World'); + * $greet('JX9'); + * + * $double = function($a) { + * return $a * 2; + * }; + * // This is our range of numbers + * $numbers = range(1, 5); + * // Use the Annonymous function as a callback here to + * // double the size of each element in our + * // range + * $new_numbers = array_map($double, $numbers); + * print implode(' ', $new_numbers); + */ +static sxi32 ExprAssembleAnnon(jx9_gen_state *pGen,SyToken **ppCur, SyToken *pEnd) +{ + SyToken *pIn = *ppCur; + sxu32 nLine; + sxi32 rc; + /* Jump the 'function' keyword */ + nLine = pIn->nLine; + pIn++; + if( pIn < pEnd && (pIn->nType & (JX9_TK_ID|JX9_TK_KEYWORD)) ){ + pIn++; + } + if( pIn >= pEnd || (pIn->nType & JX9_TK_LPAREN) == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Missing opening parenthesis '(' while declaring annonymous function"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + goto Synchronize; + } + pIn++; /* Jump the leading parenthesis '(' */ + jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_LPAREN/*'('*/, JX9_TK_RPAREN/*')'*/, &pIn); + if( pIn >= pEnd || &pIn[1] >= pEnd ){ + /* Syntax error */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + goto Synchronize; + } + pIn++; /* Jump the trailing parenthesis */ + if( pIn->nType & JX9_TK_OCB /*'{'*/ ){ + pIn++; /* Jump the leading curly '{' */ + jx9DelimitNestedTokens(pIn, pEnd, JX9_TK_OCB/*'{'*/, JX9_TK_CCB/*'}'*/, &pIn); + if( pIn < pEnd ){ + pIn++; + } + }else{ + /* Syntax error */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, nLine, "Syntax error while declaring annonymous function, missing '{'"); + if( rc == SXERR_ABORT ){ + return SXERR_ABORT; + } + } + rc = SXRET_OK; +Synchronize: + /* Synchronize pointers */ + *ppCur = pIn; + return rc; +} +/* + * Make sure we are dealing with a valid expression tree. + * This function check for balanced parenthesis, braces, brackets and so on. + * When errors, JX9 take care of generating the appropriate error message. + * Return SXRET_OK on success. Any other return value indicates syntax error. + */ +static sxi32 ExprVerifyNodes(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nNode) +{ + sxi32 iParen, iSquare, iBraces; + sxi32 i, rc; + + if( nNode > 0 && apNode[0]->pOp && (apNode[0]->pOp->iOp == EXPR_OP_ADD || apNode[0]->pOp->iOp == EXPR_OP_SUB) ){ + /* Fix and mark as an unary not binary plus/minus operator */ + apNode[0]->pOp = jx9ExprExtractOperator(&apNode[0]->pStart->sData, 0); + apNode[0]->pStart->pUserData = (void *)apNode[0]->pOp; + } + iParen = iSquare = iBraces = 0; + for( i = 0 ; i < nNode ; ++i ){ + if( apNode[i]->pStart->nType & JX9_TK_LPAREN /*'('*/){ + if( i > 0 && ( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral || + (apNode[i - 1]->pStart->nType & (JX9_TK_ID|JX9_TK_KEYWORD|JX9_TK_SSTR|JX9_TK_DSTR|JX9_TK_RPAREN/*')'*/|JX9_TK_CSB/*]*/))) ){ + /* Ticket 1433-033: Take care to ignore alpha-stream [i.e: or, xor] operators followed by an opening parenthesis */ + if( (apNode[i - 1]->pStart->nType & JX9_TK_OP) == 0 ){ + /* We are dealing with a postfix [i.e: function call] operator + * not a simple left parenthesis. Mark the node. + */ + apNode[i]->pStart->nType |= JX9_TK_OP; + apNode[i]->pStart->pUserData = (void *)&sFCallOp; /* Function call operator */ + apNode[i]->pOp = &sFCallOp; + } + } + iParen++; + }else if( apNode[i]->pStart->nType & JX9_TK_RPAREN/*')*/){ + if( iParen <= 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ')'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + iParen--; + }else if( apNode[i]->pStart->nType & JX9_TK_OSB /*'['*/ && apNode[i]->xCode == 0 ){ + iSquare++; + }else if (apNode[i]->pStart->nType & JX9_TK_CSB /*']'*/){ + if( iSquare <= 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token ']'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + iSquare--; + }else if( apNode[i]->pStart->nType & JX9_TK_OCB /*'{'*/ && apNode[i]->xCode == 0 ){ + iBraces++; + }else if (apNode[i]->pStart->nType & JX9_TK_CCB /*'}'*/){ + if( iBraces <= 0 ){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[i]->pStart->nLine, "Syntax error: Unexpected token '}'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + iBraces--; + }else if( apNode[i]->pStart->nType & JX9_TK_OP ){ + const jx9_expr_op *pOp = (const jx9_expr_op *)apNode[i]->pOp; + if( i > 0 && (pOp->iOp == EXPR_OP_UMINUS || pOp->iOp == EXPR_OP_UPLUS)){ + if( apNode[i-1]->xCode == jx9CompileVariable || apNode[i-1]->xCode == jx9CompileLiteral ){ + sxi32 iExprOp = EXPR_OP_SUB; /* Binary minus */ + sxu32 n = 0; + if( pOp->iOp == EXPR_OP_UPLUS ){ + iExprOp = EXPR_OP_ADD; /* Binary plus */ + } + /* + * TICKET 1433-013: This is a fix around an obscure bug when the user uses + * a variable name which is an alpha-stream operator [i.e: $and, $xor, $eq..]. + */ + while( n < SX_ARRAYSIZE(aOpTable) && aOpTable[n].iOp != iExprOp ){ + ++n; + } + pOp = &aOpTable[n]; + /* Mark as binary '+' or '-', not an unary */ + apNode[i]->pOp = pOp; + apNode[i]->pStart->pUserData = (void *)pOp; + } + } + } + } + if( iParen != 0 || iSquare != 0 || iBraces != 0){ + rc = jx9GenCompileError(&(*pGen), E_ERROR, apNode[0]->pStart->nLine, "Syntax error, mismatched '(', '[' or '{'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + return SXRET_OK; +} +/* + * Extract a single expression node from the input. + * On success store the freshly extractd node in ppNode. + * When errors, JX9 take care of generating the appropriate error message. + * An expression node can be a variable [i.e: $var], an operator [i.e: ++] + * an annonymous function [i.e: function(){ return "Hello"; }, a double/single + * quoted string, a heredoc/nowdoc, a literal [i.e: JX9_EOL], a namespace path + * [i.e: namespaces\path\to..], a array/list [i.e: array(4, 5, 6)] and so on. + */ +static sxi32 ExprExtractNode(jx9_gen_state *pGen, jx9_expr_node **ppNode) +{ + jx9_expr_node *pNode; + SyToken *pCur; + sxi32 rc; + /* Allocate a new node */ + pNode = (jx9_expr_node *)SyMemBackendPoolAlloc(&pGen->pVm->sAllocator, sizeof(jx9_expr_node)); + if( pNode == 0 ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return SXERR_MEM; + } + /* Zero the structure */ + SyZero(pNode, sizeof(jx9_expr_node)); + SySetInit(&pNode->aNodeArgs, &pGen->pVm->sAllocator, sizeof(jx9_expr_node **)); + /* Point to the head of the token stream */ + pCur = pNode->pStart = pGen->pIn; + /* Start collecting tokens */ + if( pCur->nType & JX9_TK_OP ){ + /* Point to the instance that describe this operator */ + pNode->pOp = (const jx9_expr_op *)pCur->pUserData; + /* Advance the stream cursor */ + pCur++; + }else if( pCur->nType & JX9_TK_DOLLAR ){ + /* Isolate variable */ + pCur++; /* Jump the dollar sign */ + if( pCur >= pGen->pEnd ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"Invalid variable name"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); + return rc; + } + pCur++; /* Jump the variable name */ + pNode->xCode = jx9CompileVariable; + }else if( pCur->nType & JX9_TK_OCB /* '{' */ ){ + /* JSON Object, assemble tokens */ + pCur++; + jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OCB /* '[' */, JX9_TK_CCB /* ']' */, &pCur); + if( pCur < pGen->pEnd ){ + pCur++; + }else{ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Object: Missing closing braces '}'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); + return rc; + } + pNode->xCode = jx9CompileJsonObject; + }else if( pCur->nType & JX9_TK_OSB /* '[' */ && !(pCur->nType & JX9_TK_OP) ){ + /* JSON Array, assemble tokens */ + pCur++; + jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_OSB /* '[' */, JX9_TK_CSB /* ']' */, &pCur); + if( pCur < pGen->pEnd ){ + pCur++; + }else{ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine,"JSON Array: Missing closing square bracket ']'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); + return rc; + } + pNode->xCode = jx9CompileJsonArray; + }else if( pCur->nType & JX9_TK_KEYWORD ){ + int nKeyword = SX_PTR_TO_INT(pCur->pUserData); + if( nKeyword == JX9_TKWRD_FUNCTION ){ + /* Annonymous function */ + if( &pCur[1] >= pGen->pEnd ){ + /* Assume a literal */ + pCur++; + pNode->xCode = jx9CompileLiteral; + }else{ + /* Assemble annonymous functions body */ + rc = ExprAssembleAnnon(&(*pGen), &pCur, pGen->pEnd); + if( rc != SXRET_OK ){ + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); + return rc; + } + pNode->xCode = jx9CompileAnnonFunc; + } + }else if( jx9IsLangConstruct(nKeyword) && &pCur[1] < pGen->pEnd ){ + /* Language constructs [i.e: print,die...] require special handling */ + jx9DelimitNestedTokens(pCur, pGen->pEnd, JX9_TK_LPAREN|JX9_TK_OCB|JX9_TK_OSB, JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB, &pCur); + pNode->xCode = jx9CompileLangConstruct; + }else{ + /* Assume a literal */ + pCur++; + pNode->xCode = jx9CompileLiteral; + } + }else if( pCur->nType & (JX9_TK_ID) ){ + /* Constants, function name, namespace path, object name... */ + pCur++; + pNode->xCode = jx9CompileLiteral; + }else{ + if( (pCur->nType & (JX9_TK_LPAREN|JX9_TK_RPAREN|JX9_TK_COMMA|JX9_TK_CSB|JX9_TK_OCB|JX9_TK_CCB|JX9_TK_COLON)) == 0 ){ + /* Point to the code generator routine */ + pNode->xCode = jx9GetNodeHandler(pCur->nType); + if( pNode->xCode == 0 ){ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Syntax error: Unexpected token '%z'", &pNode->pStart->sData); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); + return rc; + } + } + /* Advance the stream cursor */ + pCur++; + } + /* Point to the end of the token stream */ + pNode->pEnd = pCur; + /* Save the node for later processing */ + *ppNode = pNode; + /* Synchronize cursors */ + pGen->pIn = pCur; + return SXRET_OK; +} +/* + * Free an expression tree. + */ +static void ExprFreeTree(jx9_gen_state *pGen, jx9_expr_node *pNode) +{ + if( pNode->pLeft ){ + /* Release the left tree */ + ExprFreeTree(&(*pGen), pNode->pLeft); + } + if( pNode->pRight ){ + /* Release the right tree */ + ExprFreeTree(&(*pGen), pNode->pRight); + } + if( pNode->pCond ){ + /* Release the conditional tree used by the ternary operator */ + ExprFreeTree(&(*pGen), pNode->pCond); + } + if( SySetUsed(&pNode->aNodeArgs) > 0 ){ + jx9_expr_node **apArg; + sxu32 n; + /* Release node arguments */ + apArg = (jx9_expr_node **)SySetBasePtr(&pNode->aNodeArgs); + for( n = 0 ; n < SySetUsed(&pNode->aNodeArgs) ; ++n ){ + ExprFreeTree(&(*pGen), apArg[n]); + } + SySetRelease(&pNode->aNodeArgs); + } + /* Finally, release this node */ + SyMemBackendPoolFree(&pGen->pVm->sAllocator, pNode); +} +/* + * Free an expression tree. + * This function is a wrapper around ExprFreeTree() defined above. + */ +JX9_PRIVATE sxi32 jx9ExprFreeTree(jx9_gen_state *pGen, SySet *pNodeSet) +{ + jx9_expr_node **apNode; + sxu32 n; + apNode = (jx9_expr_node **)SySetBasePtr(pNodeSet); + for( n = 0 ; n < SySetUsed(pNodeSet) ; ++n ){ + if( apNode[n] ){ + ExprFreeTree(&(*pGen), apNode[n]); + } + } + return SXRET_OK; +} +/* + * Check if the given node is a modifialbe l/r-value. + * Return TRUE if modifiable.FALSE otherwise. + */ +static int ExprIsModifiableValue(jx9_expr_node *pNode) +{ + sxi32 iExprOp; + if( pNode->pOp == 0 ){ + return pNode->xCode == jx9CompileVariable ? TRUE : FALSE; + } + iExprOp = pNode->pOp->iOp; + if( iExprOp == EXPR_OP_DOT /*'.' */ ){ + return TRUE; + } + if( iExprOp == EXPR_OP_SUBSCRIPT/*'[]'*/ ){ + if( pNode->pLeft->pOp ) { + if( pNode->pLeft->pOp->iOp != EXPR_OP_SUBSCRIPT /*'['*/ && pNode->pLeft->pOp->iOp != EXPR_OP_DOT /*'.'*/){ + return FALSE; + } + }else if( pNode->pLeft->xCode != jx9CompileVariable ){ + return FALSE; + } + return TRUE; + } + /* Not a modifiable l or r-value */ + return FALSE; +} +/* Forward declaration */ +static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken); +/* Macro to check if the given node is a terminal */ +#define NODE_ISTERM(NODE) (apNode[NODE] && (!apNode[NODE]->pOp || apNode[NODE]->pLeft )) +/* + * Buid an expression tree for each given function argument. + * When errors, JX9 take care of generating the appropriate error message. + */ +static sxi32 ExprProcessFuncArguments(jx9_gen_state *pGen, jx9_expr_node *pOp, jx9_expr_node **apNode, sxi32 nToken) +{ + sxi32 iNest, iCur, iNode; + sxi32 rc; + /* Process function arguments from left to right */ + iCur = 0; + for(;;){ + if( iCur >= nToken ){ + /* No more arguments to process */ + break; + } + iNode = iCur; + iNest = 0; + while( iCur < nToken ){ + if( apNode[iCur] ){ + if( (apNode[iCur]->pStart->nType & JX9_TK_COMMA) && apNode[iCur]->pLeft == 0 && iNest <= 0 ){ + break; + }else if( apNode[iCur]->pStart->nType & (JX9_TK_LPAREN|JX9_TK_OSB|JX9_TK_OCB) ){ + iNest++; + }else if( apNode[iCur]->pStart->nType & (JX9_TK_RPAREN|JX9_TK_CCB|JX9_TK_CSB) ){ + iNest--; + } + } + iCur++; + } + if( iCur > iNode ){ + ExprMakeTree(&(*pGen), &apNode[iNode], iCur-iNode); + if( apNode[iNode] ){ + /* Put a pointer to the root of the tree in the arguments set */ + SySetPut(&pOp->aNodeArgs, (const void *)&apNode[iNode]); + }else{ + /* Empty function argument */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Empty function argument"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + }else{ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Jump trailing comma */ + if( iCur < nToken && apNode[iCur] && (apNode[iCur]->pStart->nType & JX9_TK_COMMA) ){ + iCur++; + if( iCur >= nToken ){ + /* missing function argument */ + rc = jx9GenCompileError(&(*pGen), E_ERROR, pOp->pStart->nLine, "Missing function argument"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + } + return SXRET_OK; +} +/* + * Create an expression tree from an array of tokens. + * If successful, the root of the tree is stored in apNode[0]. + * When errors, JX9 take care of generating the appropriate error message. + */ + static sxi32 ExprMakeTree(jx9_gen_state *pGen, jx9_expr_node **apNode, sxi32 nToken) + { + sxi32 i, iLeft, iRight; + jx9_expr_node *pNode; + sxi32 iCur; + sxi32 rc; + if( nToken <= 0 || (nToken == 1 && apNode[0]->xCode) ){ + /* TICKET 1433-17: self evaluating node */ + return SXRET_OK; + } + /* Process expressions enclosed in parenthesis first */ + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + sxi32 iNest; + /* Note that, we use strict comparison here '!=' instead of the bitwise and '&' operator + * since the LPAREN token can also be an operator [i.e: Function call]. + */ + if( apNode[iCur] == 0 || apNode[iCur]->pStart->nType != JX9_TK_LPAREN ){ + continue; + } + iNest = 1; + iLeft = iCur; + /* Find the closing parenthesis */ + iCur++; + while( iCur < nToken ){ + if( apNode[iCur] ){ + if( apNode[iCur]->pStart->nType & JX9_TK_RPAREN /* ')' */){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + }else if( apNode[iCur]->pStart->nType & JX9_TK_LPAREN /* '(' */ ){ + /* Increment nesting level */ + iNest++; + } + } + iCur++; + } + if( iCur - iLeft > 1 ){ + /* Recurse and process this expression */ + rc = ExprMakeTree(&(*pGen), &apNode[iLeft + 1], iCur - iLeft - 1); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Free the left and right nodes */ + ExprFreeTree(&(*pGen), apNode[iLeft]); + ExprFreeTree(&(*pGen), apNode[iCur]); + apNode[iLeft] = 0; + apNode[iCur] = 0; + } + /* Handle postfix [i.e: function call, member access] operators with precedence 2 */ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 2 && pNode->pLeft == 0 ){ + if( pNode->pOp->iOp == EXPR_OP_FUNC_CALL ){ + /* Collect function arguments */ + sxi32 iPtr = 0; + sxi32 nFuncTok = 0; + while( nFuncTok + iCur < nToken ){ + if( apNode[nFuncTok+iCur] ){ + if( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_LPAREN /*'('*/ ){ + iPtr++; + }else if ( apNode[nFuncTok+iCur]->pStart->nType & JX9_TK_RPAREN /*')'*/){ + iPtr--; + if( iPtr <= 0 ){ + break; + } + } + } + nFuncTok++; + } + if( nFuncTok + iCur >= nToken ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Missing right parenthesis ')'"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( iLeft < 0 || !NODE_ISTERM(iLeft) /*|| ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2)*/ ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "Invalid function name"); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( nFuncTok > 1 ){ + /* Process function arguments */ + rc = ExprProcessFuncArguments(&(*pGen), pNode, &apNode[iCur+1], nFuncTok-1); + if( rc != SXRET_OK ){ + return rc; + } + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + for( iPtr = 1; iPtr <= nFuncTok ; iPtr++ ){ + apNode[iCur+iPtr] = 0; + } + }else if (pNode->pOp->iOp == EXPR_OP_SUBSCRIPT ){ + /* Subscripting */ + sxi32 iArrTok = iCur + 1; + sxi32 iNest = 1; + if( iLeft >= 0 && (apNode[iLeft]->xCode == jx9CompileVariable || (apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* postfix */) ) ){ + /* Collect index tokens */ + while( iArrTok < nToken ){ + if( apNode[iArrTok] ){ + if( apNode[iArrTok]->pStart->nType & JX9_TK_OSB /*'['*/){ + /* Increment nesting level */ + iNest++; + }else if( apNode[iArrTok]->pStart->nType & JX9_TK_CSB /*']'*/){ + /* Decrement nesting level */ + iNest--; + if( iNest <= 0 ){ + break; + } + } + } + ++iArrTok; + } + if( iArrTok > iCur + 1 ){ + /* Recurse and process this expression */ + rc = ExprMakeTree(&(*pGen), &apNode[iCur+1], iArrTok - iCur - 1); + if( rc != SXRET_OK ){ + return rc; + } + /* Link the node to it's index */ + SySetPut(&pNode->aNodeArgs, (const void *)&apNode[iCur+1]); + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + pNode->pRight = 0; + apNode[iLeft] = 0; + for( iNest = iCur + 1 ; iNest <= iArrTok ; ++iNest ){ + apNode[iNest] = 0; + } + } + }else{ + /* Member access operators [i.e: '.' ] */ + iRight = iCur + 1; + while( iRight < nToken && apNode[iRight] == 0 ){ + iRight++; + } + if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid member name", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + if( pNode->pLeft->pOp == 0 && pNode->pLeft->xCode != jx9CompileVariable ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, + "'%z': Expecting a variable as left operand", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + pNode->pRight = apNode[iRight]; + apNode[iLeft] = apNode[iRight] = 0; + } + } + iLeft = iCur; + } + /* Handle post/pre icrement/decrement [i.e: ++/--] operators with precedence 3 */ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ + if( iLeft >= 0 && ((apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec == 2 /* Postfix */) + || apNode[iLeft]->xCode == jx9CompileVariable) ){ + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + } + } + iLeft = iCur; + } + iLeft = -1; + for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 3 && pNode->pLeft == 0){ + if( iLeft < 0 || (apNode[iLeft]->pOp == 0 && apNode[iLeft]->xCode != jx9CompileVariable) + || ( apNode[iLeft]->pOp && apNode[iLeft]->pOp->iPrec != 2 /* Postfix */) ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z' operator needs l-value", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + /* Mark as pre-increment/decrement node */ + pNode->iFlags |= EXPR_NODE_PRE_INCR; + } + iLeft = iCur; + } + /* Handle right associative unary and cast operators [i.e: !, (string), ~...] with precedence 4 */ + iLeft = 0; + for( iCur = nToken - 1 ; iCur >= 0 ; iCur-- ){ + if( apNode[iCur] ){ + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 4 && pNode->pLeft == 0){ + if( iLeft > 0 ){ + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + apNode[iLeft] = 0; + if( pNode->pLeft && pNode->pLeft->pOp && pNode->pLeft->pOp->iPrec > 4 ){ + if( pNode->pLeft->pLeft == 0 || pNode->pLeft->pRight == 0 ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pLeft->pStart->nLine, "'%z': Missing operand", &pNode->pLeft->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + }else{ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing operand", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + /* Save terminal position */ + iLeft = iCur; + } + } + /* Process left and non-associative binary operators [i.e: *, /, &&, ||...]*/ + for( i = 7 ; i < 17 ; i++ ){ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == i && pNode->pLeft == 0 ){ + /* Get the right node */ + iRight = iCur + 1; + while( iRight < nToken && apNode[iRight] == 0 ){ + iRight++; + } + if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + pNode->pRight = apNode[iRight]; + apNode[iLeft] = apNode[iRight] = 0; + } + iLeft = iCur; + } + } + /* Handle the ternary operator. (expr1) ? (expr2) : (expr3) + * Note that we do not need a precedence loop here since + * we are dealing with a single operator. + */ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iOp == EXPR_OP_QUESTY && pNode->pLeft == 0 ){ + sxi32 iNest = 1; + if( iLeft < 0 || !NODE_ISTERM(iLeft) ){ + /* Missing condition */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Syntax error", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Get the right node */ + iRight = iCur + 1; + while( iRight < nToken ){ + if( apNode[iRight] ){ + if( apNode[iRight]->pOp && apNode[iRight]->pOp->iOp == EXPR_OP_QUESTY && apNode[iRight]->pCond == 0){ + /* Increment nesting level */ + ++iNest; + }else if( apNode[iRight]->pStart->nType & JX9_TK_COLON /*:*/ ){ + /* Decrement nesting level */ + --iNest; + if( iNest <= 0 ){ + break; + } + } + } + iRight++; + } + if( iRight > iCur + 1 ){ + /* Recurse and process the then expression */ + rc = ExprMakeTree(&(*pGen), &apNode[iCur + 1], iRight - iCur - 1); + if( rc != SXRET_OK ){ + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iCur + 1]; + }else{ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'then' expression", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + apNode[iCur + 1] = 0; + if( iRight + 1 < nToken ){ + /* Recurse and process the else expression */ + rc = ExprMakeTree(&(*pGen), &apNode[iRight + 1], nToken - iRight - 1); + if( rc != SXRET_OK ){ + return rc; + } + /* Link the node to the tree */ + pNode->pRight = apNode[iRight + 1]; + apNode[iRight + 1] = apNode[iRight] = 0; + }else{ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing 'else' expression", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Point to the condition */ + pNode->pCond = apNode[iLeft]; + apNode[iLeft] = 0; + break; + } + iLeft = iCur; + } + /* Process right associative binary operators [i.e: '=', '+=', '/='] + * Note: All right associative binary operators have precedence 18 + * so there is no need for a precedence loop here. + */ + iRight = -1; + for( iCur = nToken - 1 ; iCur >= 0 ; iCur--){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 18 && pNode->pLeft == 0 ){ + /* Get the left node */ + iLeft = iCur - 1; + while( iLeft >= 0 && apNode[iLeft] == 0 ){ + iLeft--; + } + if( iLeft < 0 || iRight < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + if( ExprIsModifiableValue(apNode[iLeft]) == FALSE ){ + if( pNode->pOp->iVmOp != JX9_OP_STORE ){ + /* Left operand must be a modifiable l-value */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, + "'%z': Left operand must be a modifiable l-value", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + } + /* Link the node to the tree (Reverse) */ + pNode->pLeft = apNode[iRight]; + pNode->pRight = apNode[iLeft]; + apNode[iLeft] = apNode[iRight] = 0; + } + iRight = iCur; + } + /* Process the lowest precedence operator (22, comma) */ + iLeft = -1; + for( iCur = 0 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] == 0 ){ + continue; + } + pNode = apNode[iCur]; + if( pNode->pOp && pNode->pOp->iPrec == 22 /* ',' */ && pNode->pLeft == 0 ){ + /* Get the right node */ + iRight = iCur + 1; + while( iRight < nToken && apNode[iRight] == 0 ){ + iRight++; + } + if( iRight >= nToken || iLeft < 0 || !NODE_ISTERM(iRight) || !NODE_ISTERM(iLeft) ){ + /* Syntax error */ + rc = jx9GenCompileError(pGen, E_ERROR, pNode->pStart->nLine, "'%z': Missing/Invalid operand", &pNode->pOp->sOp); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + /* Link the node to the tree */ + pNode->pLeft = apNode[iLeft]; + pNode->pRight = apNode[iRight]; + apNode[iLeft] = apNode[iRight] = 0; + } + iLeft = iCur; + } + /* Point to the root of the expression tree */ + for( iCur = 1 ; iCur < nToken ; ++iCur ){ + if( apNode[iCur] ){ + if( (apNode[iCur]->pOp || apNode[iCur]->xCode ) && apNode[0] != 0){ + rc = jx9GenCompileError(pGen, E_ERROR, apNode[iCur]->pStart->nLine, "Unexpected token '%z'", &apNode[iCur]->pStart->sData); + if( rc != SXERR_ABORT ){ + rc = SXERR_SYNTAX; + } + return rc; + } + apNode[0] = apNode[iCur]; + apNode[iCur] = 0; + } + } + return SXRET_OK; + } + /* + * Build an expression tree from the freshly extracted raw tokens. + * If successful, the root of the tree is stored in ppRoot. + * When errors, JX9 take care of generating the appropriate error message. + * This is the public interface used by the most code generator routines. + */ +JX9_PRIVATE sxi32 jx9ExprMakeTree(jx9_gen_state *pGen, SySet *pExprNode, jx9_expr_node **ppRoot) +{ + jx9_expr_node **apNode; + jx9_expr_node *pNode; + sxi32 rc; + /* Reset node container */ + SySetReset(pExprNode); + pNode = 0; /* Prevent compiler warning */ + /* Extract nodes one after one until we hit the end of the input */ + while( pGen->pIn < pGen->pEnd ){ + rc = ExprExtractNode(&(*pGen), &pNode); + if( rc != SXRET_OK ){ + return rc; + } + /* Save the extracted node */ + SySetPut(pExprNode, (const void *)&pNode); + } + if( SySetUsed(pExprNode) < 1 ){ + /* Empty expression [i.e: A semi-colon;] */ + *ppRoot = 0; + return SXRET_OK; + } + apNode = (jx9_expr_node **)SySetBasePtr(pExprNode); + /* Make sure we are dealing with valid nodes */ + rc = ExprVerifyNodes(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory, upper layer will + * cleanup the mess left behind. + */ + *ppRoot = 0; + return rc; + } + /* Build the tree */ + rc = ExprMakeTree(&(*pGen), apNode, (sxi32)SySetUsed(pExprNode)); + if( rc != SXRET_OK ){ + /* Something goes wrong [i.e: Syntax error] */ + *ppRoot = 0; + return rc; + } + /* Point to the root of the tree */ + *ppRoot = apNode[0]; + return SXRET_OK; +} +/* + * ---------------------------------------------------------- + * File: jx9_vfs.c + * MD5: 8b73046a366acaf6aa7227c2133e16c0 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: vfs.c v2.1 Ubuntu 2012-12-13 00:013 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* + * This file implement a virtual file systems (VFS) for the JX9 engine. + */ +/* + * Given a string containing the path of a file or directory, this function + * return the parent directory's path. + */ +JX9_PRIVATE const char * jx9ExtractDirName(const char *zPath, int nByte, int *pLen) +{ + const char *zEnd = &zPath[nByte - 1]; + int c, d; + c = d = '/'; +#ifdef __WINNT__ + d = '\\'; +#endif + while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ + zEnd--; + } + *pLen = (int)(zEnd-zPath); +#ifdef __WINNT__ + if( (*pLen) == (int)sizeof(char) && zPath[0] == '/' ){ + /* Normalize path on windows */ + return "\\"; + } +#endif + if( zEnd == zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d) ){ + /* No separator, return "." as the current directory */ + *pLen = sizeof(char); + return "."; + } + if( (*pLen) == 0 ){ + *pLen = sizeof(char); +#ifdef __WINNT__ + return "\\"; +#else + return "/"; +#endif + } + return zPath; +} +/* + * Omit the vfs layer implementation from the built if the JX9_DISABLE_BUILTIN_FUNC directive is defined. + */ +#ifndef JX9_DISABLE_BUILTIN_FUNC +/* + * bool chdir(string $directory) + * Change the current directory. + * Parameters + * $directory + * The new current directory + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_chdir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChdir == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xChdir(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool chroot(string $directory) + * Change the root directory. + * Parameters + * $directory + * The path to change the root directory to + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_chroot(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChroot == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xChroot(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * string getcwd(void) + * Gets the current working directory. + * Parameters + * None + * Return + * Returns the current working directory on success, or FALSE on failure. + */ +static int jx9Vfs_getcwd(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vfs *pVfs; + int rc; + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xGetcwd == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + jx9_result_string(pCtx, "", 0); + /* Perform the requested operation */ + rc = pVfs->xGetcwd(pCtx); + if( rc != JX9_OK ){ + /* Error, return FALSE */ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * bool rmdir(string $directory) + * Removes directory. + * Parameters + * $directory + * The path to the directory + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_rmdir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xRmdir == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xRmdir(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool is_dir(string $filename) + * Tells whether the given filename is a directory. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_is_dir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xIsdir == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xIsdir(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool mkdir(string $pathname[, int $mode = 0777]) + * Make a directory. + * Parameters + * $pathname + * The directory path. + * $mode + * The mode is 0777 by default, which means the widest possible access. + * Note: + * mode is ignored on Windows. + * Note that you probably want to specify the mode as an octal number, which means + * it should have a leading zero. The mode is also modified by the current umask + * which you can change using umask(). + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_mkdir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int iRecursive = 0; + const char *zPath; + jx9_vfs *pVfs; + int iMode, rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xMkdir == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); +#ifdef __WINNT__ + iMode = 0; +#else + /* Assume UNIX */ + iMode = 0777; +#endif + if( nArg > 1 ){ + iMode = jx9_value_to_int(apArg[1]); + if( nArg > 2 ){ + iRecursive = jx9_value_to_bool(apArg[2]); + } + } + /* Perform the requested operation */ + rc = pVfs->xMkdir(zPath, iMode, iRecursive); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool rename(string $oldname, string $newname) + * Attempts to rename oldname to newname. + * Parameters + * $oldname + * Old name. + * $newname + * New name. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_rename(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zOld, *zNew; + jx9_vfs *pVfs; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xRename == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + zOld = jx9_value_to_string(apArg[0], 0); + zNew = jx9_value_to_string(apArg[1], 0); + rc = pVfs->xRename(zOld, zNew); + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK ); + return JX9_OK; +} +/* + * string realpath(string $path) + * Returns canonicalized absolute pathname. + * Parameters + * $path + * Target path. + * Return + * Canonicalized absolute pathname on success. or FALSE on failure. + */ +static int jx9Vfs_realpath(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xRealpath == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Set an empty string untnil the underlying OS interface change that */ + jx9_result_string(pCtx, "", 0); + /* Perform the requested operation */ + zPath = jx9_value_to_string(apArg[0], 0); + rc = pVfs->xRealpath(zPath, pCtx); + if( rc != JX9_OK ){ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * int sleep(int $seconds) + * Delays the program execution for the given number of seconds. + * Parameters + * $seconds + * Halt time in seconds. + * Return + * Zero on success or FALSE on failure. + */ +static int jx9Vfs_sleep(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vfs *pVfs; + int rc, nSleep; + if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xSleep == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Amount to sleep */ + nSleep = jx9_value_to_int(apArg[0]); + if( nSleep < 0 ){ + /* Invalid value, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation (Microseconds) */ + rc = pVfs->xSleep((unsigned int)(nSleep * SX_USEC_PER_SEC)); + if( rc != JX9_OK ){ + /* Return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Return zero */ + jx9_result_int(pCtx, 0); + } + return JX9_OK; +} +/* + * void usleep(int $micro_seconds) + * Delays program execution for the given number of micro seconds. + * Parameters + * $micro_seconds + * Halt time in micro seconds. A micro second is one millionth of a second. + * Return + * None. + */ +static int jx9Vfs_usleep(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vfs *pVfs; + int nSleep; + if( nArg < 1 || !jx9_value_is_int(apArg[0]) ){ + /* Missing/Invalid argument, return immediately */ + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xSleep == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + jx9_function_name(pCtx) + ); + return JX9_OK; + } + /* Amount to sleep */ + nSleep = jx9_value_to_int(apArg[0]); + if( nSleep < 0 ){ + /* Invalid value, return immediately */ + return JX9_OK; + } + /* Perform the requested operation (Microseconds) */ + pVfs->xSleep((unsigned int)nSleep); + return JX9_OK; +} +/* + * bool unlink (string $filename) + * Delete a file. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_unlink(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUnlink == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xUnlink(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool chmod(string $filename, int $mode) + * Attempts to change the mode of the specified file to that given in mode. + * Parameters + * $filename + * Path to the file. + * $mode + * Mode (Must be an integer) + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_chmod(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int iMode; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChmod == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Extract the mode */ + iMode = jx9_value_to_int(apArg[1]); + /* Perform the requested operation */ + rc = pVfs->xChmod(zPath, iMode); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool chown(string $filename, string $user) + * Attempts to change the owner of the file filename to user user. + * Parameters + * $filename + * Path to the file. + * $user + * Username. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_chown(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath, *zUser; + jx9_vfs *pVfs; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChown == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Extract the user */ + zUser = jx9_value_to_string(apArg[1], 0); + /* Perform the requested operation */ + rc = pVfs->xChown(zPath, zUser); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool chgrp(string $filename, string $group) + * Attempts to change the group of the file filename to group. + * Parameters + * $filename + * Path to the file. + * $group + * groupname. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_chgrp(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath, *zGroup; + jx9_vfs *pVfs; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xChgrp == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Extract the user */ + zGroup = jx9_value_to_string(apArg[1], 0); + /* Perform the requested operation */ + rc = pVfs->xChgrp(zPath, zGroup); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * int64 disk_free_space(string $directory) + * Returns available space on filesystem or disk partition. + * Parameters + * $directory + * A directory of the filesystem or disk partition. + * Return + * Returns the number of available bytes as a 64-bit integer or FALSE on failure. + */ +static int jx9Vfs_disk_free_space(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_int64 iSize; + jx9_vfs *pVfs; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFreeSpace == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + iSize = pVfs->xFreeSpace(zPath); + /* IO return value */ + jx9_result_int64(pCtx, iSize); + return JX9_OK; +} +/* + * int64 disk_total_space(string $directory) + * Returns the total size of a filesystem or disk partition. + * Parameters + * $directory + * A directory of the filesystem or disk partition. + * Return + * Returns the number of available bytes as a 64-bit integer or FALSE on failure. + */ +static int jx9Vfs_disk_total_space(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_int64 iSize; + jx9_vfs *pVfs; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xTotalSpace == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + iSize = pVfs->xTotalSpace(zPath); + /* IO return value */ + jx9_result_int64(pCtx, iSize); + return JX9_OK; +} +/* + * bool file_exists(string $filename) + * Checks whether a file or directory exists. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_file_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileExists == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xFileExists(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * int64 file_size(string $filename) + * Gets the size for the given file. + * Parameters + * $filename + * Path to the file. + * Return + * File size on success or FALSE on failure. + */ +static int jx9Vfs_file_size(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_int64 iSize; + jx9_vfs *pVfs; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileSize == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + iSize = pVfs->xFileSize(zPath); + /* IO return value */ + jx9_result_int64(pCtx, iSize); + return JX9_OK; +} +/* + * int64 fileatime(string $filename) + * Gets the last access time of the given file. + * Parameters + * $filename + * Path to the file. + * Return + * File atime on success or FALSE on failure. + */ +static int jx9Vfs_file_atime(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_int64 iTime; + jx9_vfs *pVfs; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileAtime == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + iTime = pVfs->xFileAtime(zPath); + /* IO return value */ + jx9_result_int64(pCtx, iTime); + return JX9_OK; +} +/* + * int64 filemtime(string $filename) + * Gets file modification time. + * Parameters + * $filename + * Path to the file. + * Return + * File mtime on success or FALSE on failure. + */ +static int jx9Vfs_file_mtime(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_int64 iTime; + jx9_vfs *pVfs; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileMtime == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + iTime = pVfs->xFileMtime(zPath); + /* IO return value */ + jx9_result_int64(pCtx, iTime); + return JX9_OK; +} +/* + * int64 filectime(string $filename) + * Gets inode change time of file. + * Parameters + * $filename + * Path to the file. + * Return + * File ctime on success or FALSE on failure. + */ +static int jx9Vfs_file_ctime(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_int64 iTime; + jx9_vfs *pVfs; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFileCtime == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + iTime = pVfs->xFileCtime(zPath); + /* IO return value */ + jx9_result_int64(pCtx, iTime); + return JX9_OK; +} +/* + * bool is_file(string $filename) + * Tells whether the filename is a regular file. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_is_file(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xIsfile == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xIsfile(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool is_link(string $filename) + * Tells whether the filename is a symbolic link. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_is_link(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xIslink == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xIslink(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool is_readable(string $filename) + * Tells whether a file exists and is readable. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_is_readable(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xReadable == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xReadable(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool is_writable(string $filename) + * Tells whether the filename is writable. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_is_writable(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xWritable == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xWritable(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool is_executable(string $filename) + * Tells whether the filename is executable. + * Parameters + * $filename + * Path to the file. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_is_executable(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xExecutable == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xExecutable(zPath); + /* IO return value */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * string filetype(string $filename) + * Gets file type. + * Parameters + * $filename + * Path to the file. + * Return + * The type of the file. Possible values are fifo, char, dir, block, link + * file, socket and unknown. + */ +static int jx9Vfs_filetype(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + jx9_vfs *pVfs; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return 'unknown' */ + jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xFiletype == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the desired directory */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Set the empty string as the default return value */ + jx9_result_string(pCtx, "", 0); + /* Perform the requested operation */ + pVfs->xFiletype(zPath, pCtx); + return JX9_OK; +} +/* + * array stat(string $filename) + * Gives information about a file. + * Parameters + * $filename + * Path to the file. + * Return + * An associative array on success holding the following entries on success + * 0 dev device number + * 1 ino inode number (zero on windows) + * 2 mode inode protection mode + * 3 nlink number of links + * 4 uid userid of owner (zero on windows) + * 5 gid groupid of owner (zero on windows) + * 6 rdev device type, if inode device + * 7 size size in bytes + * 8 atime time of last access (Unix timestamp) + * 9 mtime time of last modification (Unix timestamp) + * 10 ctime time of last inode change (Unix timestamp) + * 11 blksize blocksize of filesystem IO (zero on windows) + * 12 blocks number of 512-byte blocks allocated. + * Note: + * FALSE is returned on failure. + */ +static int jx9Vfs_stat(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray, *pValue; + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xStat == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Create the array and the working value */ + pArray = jx9_context_new_array(pCtx); + pValue = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xStat(zPath, pArray, pValue); + if( rc != JX9_OK ){ + /* IO error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Return the associative array */ + jx9_result_value(pCtx, pArray); + } + /* Don't worry about freeing memory here, everything will be released + * automatically as soon we return from this function. */ + return JX9_OK; +} +/* + * array lstat(string $filename) + * Gives information about a file or symbolic link. + * Parameters + * $filename + * Path to the file. + * Return + * An associative array on success holding the following entries on success + * 0 dev device number + * 1 ino inode number (zero on windows) + * 2 mode inode protection mode + * 3 nlink number of links + * 4 uid userid of owner (zero on windows) + * 5 gid groupid of owner (zero on windows) + * 6 rdev device type, if inode device + * 7 size size in bytes + * 8 atime time of last access (Unix timestamp) + * 9 mtime time of last modification (Unix timestamp) + * 10 ctime time of last inode change (Unix timestamp) + * 11 blksize blocksize of filesystem IO (zero on windows) + * 12 blocks number of 512-byte blocks allocated. + * Note: + * FALSE is returned on failure. + */ +static int jx9Vfs_lstat(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray, *pValue; + const char *zPath; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xlStat == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Create the array and the working value */ + pArray = jx9_context_new_array(pCtx); + pValue = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zPath = jx9_value_to_string(apArg[0], 0); + /* Perform the requested operation */ + rc = pVfs->xlStat(zPath, pArray, pValue); + if( rc != JX9_OK ){ + /* IO error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Return the associative array */ + jx9_result_value(pCtx, pArray); + } + /* Don't worry about freeing memory here, everything will be released + * automatically as soon we return from this function. */ + return JX9_OK; +} +/* + * string getenv(string $varname) + * Gets the value of an environment variable. + * Parameters + * $varname + * The variable name. + * Return + * Returns the value of the environment variable varname, or FALSE if the environment + * variable varname does not exist. + */ +static int jx9Vfs_getenv(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zEnv; + jx9_vfs *pVfs; + int iLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xGetenv == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the environment variable */ + zEnv = jx9_value_to_string(apArg[0], &iLen); + /* Set a boolean FALSE as the default return value */ + jx9_result_bool(pCtx, 0); + if( iLen < 1 ){ + /* Empty string */ + return JX9_OK; + } + /* Perform the requested operation */ + pVfs->xGetenv(zEnv, pCtx); + return JX9_OK; +} +/* + * bool putenv(string $settings) + * Set the value of an environment variable. + * Parameters + * $setting + * The setting, like "FOO=BAR" + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_putenv(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zName, *zValue; + char *zSettings, *zEnd; + jx9_vfs *pVfs; + int iLen, rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the setting variable */ + zSettings = (char *)jx9_value_to_string(apArg[0], &iLen); + if( iLen < 1 ){ + /* Empty string, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Parse the setting */ + zEnd = &zSettings[iLen]; + zValue = 0; + zName = zSettings; + while( zSettings < zEnd ){ + if( zSettings[0] == '=' ){ + /* Null terminate the name */ + zSettings[0] = 0; + zValue = &zSettings[1]; + break; + } + zSettings++; + } + /* Install the environment variable in the $_Env array */ + if( zValue == 0 || zName[0] == 0 || zValue >= zEnd || zName >= zValue ){ + /* Invalid settings, retun FALSE */ + jx9_result_bool(pCtx, 0); + if( zSettings < zEnd ){ + zSettings[0] = '='; + } + return JX9_OK; + } + jx9_vm_config(pCtx->pVm, JX9_VM_CONFIG_ENV_ATTR, zName, zValue, (int)(zEnd-zValue)); + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xSetenv == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + zSettings[0] = '='; + return JX9_OK; + } + /* Perform the requested operation */ + rc = pVfs->xSetenv(zName, zValue); + jx9_result_bool(pCtx, rc == JX9_OK ); + zSettings[0] = '='; + return JX9_OK; +} +/* + * bool touch(string $filename[, int64 $time = time()[, int64 $atime]]) + * Sets access and modification time of file. + * Note: On windows + * If the file does not exists, it will not be created. + * Parameters + * $filename + * The name of the file being touched. + * $time + * The touch time. If time is not supplied, the current system time is used. + * $atime + * If present, the access time of the given filename is set to the value of atime. + * Otherwise, it is set to the value passed to the time parameter. If neither are + * present, the current system time is used. + * Return + * TRUE on success or FALSE on failure. +*/ +static int jx9Vfs_touch(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_int64 nTime, nAccess; + const char *zFile; + jx9_vfs *pVfs; + int rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xTouch == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + nTime = nAccess = -1; + zFile = jx9_value_to_string(apArg[0], 0); + if( nArg > 1 ){ + nTime = jx9_value_to_int64(apArg[1]); + if( nArg > 2 ){ + nAccess = jx9_value_to_int64(apArg[1]); + }else{ + nAccess = nTime; + } + } + rc = pVfs->xTouch(zFile, nTime, nAccess); + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * Path processing functions that do not need access to the VFS layer + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * string dirname(string $path) + * Returns parent directory's path. + * Parameters + * $path + * Target path. + * On Windows, both slash (/) and backslash (\) are used as directory separator character. + * In other environments, it is the forward slash (/). + * Return + * The path of the parent directory. If there are no slashes in path, a dot ('.') + * is returned, indicating the current directory. + */ +static int jx9Builtin_dirname(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath, *zDir; + int iLen, iDirlen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Point to the target path */ + zPath = jx9_value_to_string(apArg[0], &iLen); + if( iLen < 1 ){ + /* Reuturn "." */ + jx9_result_string(pCtx, ".", sizeof(char)); + return JX9_OK; + } + /* Perform the requested operation */ + zDir = jx9ExtractDirName(zPath, iLen, &iDirlen); + /* Return directory name */ + jx9_result_string(pCtx, zDir, iDirlen); + return JX9_OK; +} +/* + * string basename(string $path[, string $suffix ]) + * Returns trailing name component of path. + * Parameters + * $path + * Target path. + * On Windows, both slash (/) and backslash (\) are used as directory separator character. + * In other environments, it is the forward slash (/). + * $suffix + * If the name component ends in suffix this will also be cut off. + * Return + * The base name of the given path. + */ +static int jx9Builtin_basename(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath, *zBase, *zEnd; + int c, d, iLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + c = d = '/'; +#ifdef __WINNT__ + d = '\\'; +#endif + /* Point to the target path */ + zPath = jx9_value_to_string(apArg[0], &iLen); + if( iLen < 1 ){ + /* Empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Perform the requested operation */ + zEnd = &zPath[iLen - 1]; + /* Ignore trailing '/' */ + while( zEnd > zPath && ( (int)zEnd[0] == c || (int)zEnd[0] == d ) ){ + zEnd--; + } + iLen = (int)(&zEnd[1]-zPath); + while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ + zEnd--; + } + zBase = (zEnd > zPath) ? &zEnd[1] : zPath; + zEnd = &zPath[iLen]; + if( nArg > 1 && jx9_value_is_string(apArg[1]) ){ + const char *zSuffix; + int nSuffix; + /* Strip suffix */ + zSuffix = jx9_value_to_string(apArg[1], &nSuffix); + if( nSuffix > 0 && nSuffix < iLen && SyMemcmp(&zEnd[-nSuffix], zSuffix, nSuffix) == 0 ){ + zEnd -= nSuffix; + } + } + /* Store the basename */ + jx9_result_string(pCtx, zBase, (int)(zEnd-zBase)); + return JX9_OK; +} +/* + * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) + * Returns information about a file path. + * Parameter + * $path + * The path to be parsed. + * $options + * If present, specifies a specific element to be returned; one of + * PATHINFO_DIRNAME, PATHINFO_BASENAME, PATHINFO_EXTENSION or PATHINFO_FILENAME. + * Return + * If the options parameter is not passed, an associative array containing the following + * elements is returned: dirname, basename, extension (if any), and filename. + * If options is present, returns a string containing the requested element. + */ +typedef struct path_info path_info; +struct path_info +{ + SyString sDir; /* Directory [i.e: /var/www] */ + SyString sBasename; /* Basename [i.e httpd.conf] */ + SyString sExtension; /* File extension [i.e xml, pdf..] */ + SyString sFilename; /* Filename */ +}; +/* + * Extract path fields. + */ +static sxi32 ExtractPathInfo(const char *zPath, int nByte, path_info *pOut) +{ + const char *zPtr, *zEnd = &zPath[nByte - 1]; + SyString *pCur; + int c, d; + c = d = '/'; +#ifdef __WINNT__ + d = '\\'; +#endif + /* Zero the structure */ + SyZero(pOut, sizeof(path_info)); + /* Handle special case */ + if( nByte == sizeof(char) && ( (int)zPath[0] == c || (int)zPath[0] == d ) ){ +#ifdef __WINNT__ + SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char)); +#else + SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char)); +#endif + return SXRET_OK; + } + /* Extract the basename */ + while( zEnd > zPath && ( (int)zEnd[0] != c && (int)zEnd[0] != d ) ){ + zEnd--; + } + zPtr = (zEnd > zPath) ? &zEnd[1] : zPath; + zEnd = &zPath[nByte]; + /* dirname */ + pCur = &pOut->sDir; + SyStringInitFromBuf(pCur, zPath, zPtr-zPath); + if( pCur->nByte > 1 ){ + SyStringTrimTrailingChar(pCur, '/'); +#ifdef __WINNT__ + SyStringTrimTrailingChar(pCur, '\\'); +#endif + }else if( (int)zPath[0] == c || (int)zPath[0] == d ){ +#ifdef __WINNT__ + SyStringInitFromBuf(&pOut->sDir, "\\", sizeof(char)); +#else + SyStringInitFromBuf(&pOut->sDir, "/", sizeof(char)); +#endif + } + /* basename/filename */ + pCur = &pOut->sBasename; + SyStringInitFromBuf(pCur, zPtr, zEnd-zPtr); + SyStringTrimLeadingChar(pCur, '/'); +#ifdef __WINNT__ + SyStringTrimLeadingChar(pCur, '\\'); +#endif + SyStringDupPtr(&pOut->sFilename, pCur); + if( pCur->nByte > 0 ){ + /* extension */ + zEnd--; + while( zEnd > pCur->zString /*basename*/ && zEnd[0] != '.' ){ + zEnd--; + } + if( zEnd > pCur->zString ){ + zEnd++; /* Jump leading dot */ + SyStringInitFromBuf(&pOut->sExtension, zEnd, &zPath[nByte]-zEnd); + /* Fix filename */ + pCur = &pOut->sFilename; + if( pCur->nByte > SyStringLength(&pOut->sExtension) ){ + pCur->nByte -= 1 + SyStringLength(&pOut->sExtension); + } + } + } + return SXRET_OK; +} +/* + * value pathinfo(string $path [, int $options = PATHINFO_DIRNAME | PATHINFO_BASENAME | PATHINFO_EXTENSION | PATHINFO_FILENAME ]) + * See block comment above. + */ +static int jx9Builtin_pathinfo(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zPath; + path_info sInfo; + SyString *pComp; + int iLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid argument, return the empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Point to the target path */ + zPath = jx9_value_to_string(apArg[0], &iLen); + if( iLen < 1 ){ + /* Empty string */ + jx9_result_string(pCtx, "", 0); + return JX9_OK; + } + /* Extract path info */ + ExtractPathInfo(zPath, iLen, &sInfo); + if( nArg > 1 && jx9_value_is_int(apArg[1]) ){ + /* Return path component */ + int nComp = jx9_value_to_int(apArg[1]); + switch(nComp){ + case 1: /* PATHINFO_DIRNAME */ + pComp = &sInfo.sDir; + if( pComp->nByte > 0 ){ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + }else{ + /* Expand the empty string */ + jx9_result_string(pCtx, "", 0); + } + break; + case 2: /*PATHINFO_BASENAME*/ + pComp = &sInfo.sBasename; + if( pComp->nByte > 0 ){ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + }else{ + /* Expand the empty string */ + jx9_result_string(pCtx, "", 0); + } + break; + case 3: /*PATHINFO_EXTENSION*/ + pComp = &sInfo.sExtension; + if( pComp->nByte > 0 ){ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + }else{ + /* Expand the empty string */ + jx9_result_string(pCtx, "", 0); + } + break; + case 4: /*PATHINFO_FILENAME*/ + pComp = &sInfo.sFilename; + if( pComp->nByte > 0 ){ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + }else{ + /* Expand the empty string */ + jx9_result_string(pCtx, "", 0); + } + break; + default: + /* Expand the empty string */ + jx9_result_string(pCtx, "", 0); + break; + } + }else{ + /* Return an associative array */ + jx9_value *pArray, *pValue; + pArray = jx9_context_new_array(pCtx); + pValue = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + /* Out of mem, return NULL */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* dirname */ + pComp = &sInfo.sDir; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + /* Perform the insertion */ + jx9_array_add_strkey_elem(pArray, "dirname", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + /* basername */ + pComp = &sInfo.sBasename; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + /* Perform the insertion */ + jx9_array_add_strkey_elem(pArray, "basename", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + /* extension */ + pComp = &sInfo.sExtension; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + /* Perform the insertion */ + jx9_array_add_strkey_elem(pArray, "extension", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + /* filename */ + pComp = &sInfo.sFilename; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + /* Perform the insertion */ + jx9_array_add_strkey_elem(pArray, "filename", pValue); /* Will make it's own copy */ + } + /* Return the created array */ + jx9_result_value(pCtx, pArray); + /* Don't worry about freeing memory, everything will be released + * automatically as soon we return from this foreign function. + */ + } + return JX9_OK; +} +/* + * Globbing implementation extracted from the sqlite3 source tree. + * Original author: D. Richard Hipp (http://www.sqlite.org) + * Status: Public Domain + */ +typedef unsigned char u8; +/* An array to map all upper-case characters into their corresponding +** lower-case character. +** +** SQLite only considers US-ASCII (or EBCDIC) characters. We do not +** handle case conversions for the UTF character set since the tables +** involved are nearly as big or bigger than SQLite itself. +*/ +static const unsigned char sqlite3UpperToLower[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, + 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, + 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, + 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, + 252, 253, 254, 255 +}; +#define GlogUpperToLower(A) if( A<0x80 ){ A = sqlite3UpperToLower[A]; } +/* +** Assuming zIn points to the first byte of a UTF-8 character, +** advance zIn to point to the first byte of the next UTF-8 character. +*/ +#define SQLITE_SKIP_UTF8(zIn) { \ + if( (*(zIn++))>=0xc0 ){ \ + while( (*zIn & 0xc0)==0x80 ){ zIn++; } \ + } \ +} +/* +** Compare two UTF-8 strings for equality where the first string can +** potentially be a "glob" expression. Return true (1) if they +** are the same and false (0) if they are different. +** +** Globbing rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. +** +** [^...] Matches one character not in the enclosed list. +** +** With the [...] and [^...] matching, a ']' character can be included +** in the list by making it the first character after '[' or '^'. A +** range of characters can be specified using '-'. Example: +** "[a-z]" matches any single lower-case letter. To match a '-', make +** it the last character in the list. +** +** This routine is usually quick, but can be N**2 in the worst case. +** +** Hints: to match '*' or '?', put them in "[]". Like this: +** +** abc[*]xyz Matches "abc*xyz" only +*/ +static int patternCompare( + const u8 *zPattern, /* The glob pattern */ + const u8 *zString, /* The string to compare against the glob */ + const int esc, /* The escape character */ + int noCase +){ + int c, c2; + int invert; + int seen; + u8 matchOne = '?'; + u8 matchAll = '*'; + u8 matchSet = '['; + int prevEscape = 0; /* True if the previous character was 'escape' */ + + if( !zPattern || !zString ) return 0; + while( (c = jx9Utf8Read(zPattern, 0, &zPattern))!=0 ){ + if( !prevEscape && c==matchAll ){ + while( (c= jx9Utf8Read(zPattern, 0, &zPattern)) == matchAll + || c == matchOne ){ + if( c==matchOne && jx9Utf8Read(zString, 0, &zString)==0 ){ + return 0; + } + } + if( c==0 ){ + return 1; + }else if( c==esc ){ + c = jx9Utf8Read(zPattern, 0, &zPattern); + if( c==0 ){ + return 0; + } + }else if( c==matchSet ){ + if( (esc==0) || (matchSet<0x80) ) return 0; + while( *zString && patternCompare(&zPattern[-1], zString, esc, noCase)==0 ){ + SQLITE_SKIP_UTF8(zString); + } + return *zString!=0; + } + while( (c2 = jx9Utf8Read(zString, 0, &zString))!=0 ){ + if( noCase ){ + GlogUpperToLower(c2); + GlogUpperToLower(c); + while( c2 != 0 && c2 != c ){ + c2 = jx9Utf8Read(zString, 0, &zString); + GlogUpperToLower(c2); + } + }else{ + while( c2 != 0 && c2 != c ){ + c2 = jx9Utf8Read(zString, 0, &zString); + } + } + if( c2==0 ) return 0; + if( patternCompare(zPattern, zString, esc, noCase) ) return 1; + } + return 0; + }else if( !prevEscape && c==matchOne ){ + if( jx9Utf8Read(zString, 0, &zString)==0 ){ + return 0; + } + }else if( c==matchSet ){ + int prior_c = 0; + if( esc == 0 ) return 0; + seen = 0; + invert = 0; + c = jx9Utf8Read(zString, 0, &zString); + if( c==0 ) return 0; + c2 = jx9Utf8Read(zPattern, 0, &zPattern); + if( c2=='^' ){ + invert = 1; + c2 = jx9Utf8Read(zPattern, 0, &zPattern); + } + if( c2==']' ){ + if( c==']' ) seen = 1; + c2 = jx9Utf8Read(zPattern, 0, &zPattern); + } + while( c2 && c2!=']' ){ + if( c2=='-' && zPattern[0]!=']' && zPattern[0]!=0 && prior_c>0 ){ + c2 = jx9Utf8Read(zPattern, 0, &zPattern); + if( c>=prior_c && c<=c2 ) seen = 1; + prior_c = 0; + }else{ + if( c==c2 ){ + seen = 1; + } + prior_c = c2; + } + c2 = jx9Utf8Read(zPattern, 0, &zPattern); + } + if( c2==0 || (seen ^ invert)==0 ){ + return 0; + } + }else if( esc==c && !prevEscape ){ + prevEscape = 1; + }else{ + c2 = jx9Utf8Read(zString, 0, &zString); + if( noCase ){ + GlogUpperToLower(c); + GlogUpperToLower(c2); + } + if( c!=c2 ){ + return 0; + } + prevEscape = 0; + } + } + return *zString==0; +} +/* + * Wrapper around patternCompare() defined above. + * See block comment above for more information. + */ +static int Glob(const unsigned char *zPattern, const unsigned char *zString, int iEsc, int CaseCompare) +{ + int rc; + if( iEsc < 0 ){ + iEsc = '\\'; + } + rc = patternCompare(zPattern, zString, iEsc, CaseCompare); + return rc; +} +/* + * bool fnmatch(string $pattern, string $string[, int $flags = 0 ]) + * Match filename against a pattern. + * Parameters + * $pattern + * The shell wildcard pattern. + * $string + * The tested string. + * $flags + * A list of possible flags: + * FNM_NOESCAPE Disable backslash escaping. + * FNM_PATHNAME Slash in string only matches slash in the given pattern. + * FNM_PERIOD Leading period in string must be exactly matched by period in the given pattern. + * FNM_CASEFOLD Caseless match. + * Return + * TRUE if there is a match, FALSE otherwise. + */ +static int jx9Builtin_fnmatch(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zPattern; + int iEsc = '\\'; + int noCase = 0; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the pattern and the string */ + zPattern = jx9_value_to_string(apArg[0], 0); + zString = jx9_value_to_string(apArg[1], 0); + /* Extract the flags if avaialble */ + if( nArg > 2 && jx9_value_is_int(apArg[2]) ){ + rc = jx9_value_to_int(apArg[2]); + if( rc & 0x01 /*FNM_NOESCAPE*/){ + iEsc = 0; + } + if( rc & 0x08 /*FNM_CASEFOLD*/){ + noCase = 1; + } + } + /* Go globbing */ + rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, noCase); + /* Globbing result */ + jx9_result_bool(pCtx, rc); + return JX9_OK; +} +/* + * bool strglob(string $pattern, string $string) + * Match string against a pattern. + * Parameters + * $pattern + * The shell wildcard pattern. + * $string + * The tested string. + * Return + * TRUE if there is a match, FALSE otherwise. + */ +static int jx9Builtin_strglob(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zString, *zPattern; + int iEsc = '\\'; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the pattern and the string */ + zPattern = jx9_value_to_string(apArg[0], 0); + zString = jx9_value_to_string(apArg[1], 0); + /* Go globbing */ + rc = Glob((const unsigned char *)zPattern, (const unsigned char *)zString, iEsc, 0); + /* Globbing result */ + jx9_result_bool(pCtx, rc); + return JX9_OK; +} +/* + * bool link(string $target, string $link) + * Create a hard link. + * Parameters + * $target + * Target of the link. + * $link + * The link name. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_link(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zTarget, *zLink; + jx9_vfs *pVfs; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xLink == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the given arguments */ + zTarget = jx9_value_to_string(apArg[0], 0); + zLink = jx9_value_to_string(apArg[1], 0); + /* Perform the requested operation */ + rc = pVfs->xLink(zTarget, zLink, 0/*Not a symbolic link */); + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK ); + return JX9_OK; +} +/* + * bool symlink(string $target, string $link) + * Creates a symbolic link. + * Parameters + * $target + * Target of the link. + * $link + * The link name. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Vfs_symlink(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zTarget, *zLink; + jx9_vfs *pVfs; + int rc; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xLink == 0 ){ + /* IO routine not implemented, return NULL */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS, JX9 is returning FALSE", + jx9_function_name(pCtx) + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the given arguments */ + zTarget = jx9_value_to_string(apArg[0], 0); + zLink = jx9_value_to_string(apArg[1], 0); + /* Perform the requested operation */ + rc = pVfs->xLink(zTarget, zLink, 1/*A symbolic link */); + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK ); + return JX9_OK; +} +/* + * int umask([ int $mask ]) + * Changes the current umask. + * Parameters + * $mask + * The new umask. + * Return + * umask() without arguments simply returns the current umask. + * Otherwise the old umask is returned. + */ +static int jx9Vfs_umask(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int iOld, iNew; + jx9_vfs *pVfs; + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUmask == 0 ){ + /* IO routine not implemented, return -1 */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + jx9_function_name(pCtx) + ); + jx9_result_int(pCtx, 0); + return JX9_OK; + } + iNew = 0; + if( nArg > 0 ){ + iNew = jx9_value_to_int(apArg[0]); + } + /* Perform the requested operation */ + iOld = pVfs->xUmask(iNew); + /* Old mask */ + jx9_result_int(pCtx, iOld); + return JX9_OK; +} +/* + * string sys_get_temp_dir() + * Returns directory path used for temporary files. + * Parameters + * None + * Return + * Returns the path of the temporary directory. + */ +static int jx9Vfs_sys_get_temp_dir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vfs *pVfs; + /* Set the empty string as the default return value */ + jx9_result_string(pCtx, "", 0); + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xTempDir == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented, return "" */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + jx9_function_name(pCtx) + ); + return JX9_OK; + } + /* Perform the requested operation */ + pVfs->xTempDir(pCtx); + return JX9_OK; +} +/* + * string get_current_user() + * Returns the name of the current working user. + * Parameters + * None + * Return + * Returns the name of the current working user. + */ +static int jx9Vfs_get_current_user(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vfs *pVfs; + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUsername == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + jx9_function_name(pCtx) + ); + /* Set a dummy username */ + jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); + return JX9_OK; + } + /* Perform the requested operation */ + pVfs->xUsername(pCtx); + return JX9_OK; +} +/* + * int64 getmypid() + * Gets process ID. + * Parameters + * None + * Return + * Returns the process ID. + */ +static int jx9Vfs_getmypid(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_int64 nProcessId; + jx9_vfs *pVfs; + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xProcessId == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented, return -1 */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + jx9_function_name(pCtx) + ); + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Perform the requested operation */ + nProcessId = (jx9_int64)pVfs->xProcessId(); + /* Set the result */ + jx9_result_int64(pCtx, nProcessId); + return JX9_OK; +} +/* + * int getmyuid() + * Get user ID. + * Parameters + * None + * Return + * Returns the user ID. + */ +static int jx9Vfs_getmyuid(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vfs *pVfs; + int nUid; + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xUid == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented, return -1 */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + jx9_function_name(pCtx) + ); + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Perform the requested operation */ + nUid = pVfs->xUid(); + /* Set the result */ + jx9_result_int(pCtx, nUid); + return JX9_OK; +} +/* + * int getmygid() + * Get group ID. + * Parameters + * None + * Return + * Returns the group ID. + */ +static int jx9Vfs_getmygid(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vfs *pVfs; + int nGid; + /* Point to the underlying vfs */ + pVfs = (jx9_vfs *)jx9_context_user_data(pCtx); + if( pVfs == 0 || pVfs->xGid == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* IO routine not implemented, return -1 */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying VFS", + jx9_function_name(pCtx) + ); + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Perform the requested operation */ + nGid = pVfs->xGid(); + /* Set the result */ + jx9_result_int(pCtx, nGid); + return JX9_OK; +} +#ifdef __WINNT__ +#include +#elif defined(__UNIXES__) +#include +#endif +/* + * string uname([ string $mode = "a" ]) + * Returns information about the host operating system. + * Parameters + * $mode + * mode is a single character that defines what information is returned: + * 'a': This is the default. Contains all modes in the sequence "s n r v m". + * 's': Operating system name. eg. FreeBSD. + * 'n': Host name. eg. localhost.example.com. + * 'r': Release name. eg. 5.1.2-RELEASE. + * 'v': Version information. Varies a lot between operating systems. + * 'm': Machine type. eg. i386. + * Return + * OS description as a string. + */ +static int jx9Vfs_uname(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ +#if defined(__WINNT__) + const char *zName = "Microsoft Windows"; + OSVERSIONINFOW sVer; +#elif defined(__UNIXES__) + struct utsname sName; +#endif + const char *zMode = "a"; + if( nArg > 0 && jx9_value_is_string(apArg[0]) ){ + /* Extract the desired mode */ + zMode = jx9_value_to_string(apArg[0], 0); + } +#if defined(__WINNT__) + sVer.dwOSVersionInfoSize = sizeof(sVer); + if( TRUE != GetVersionExW(&sVer)){ + jx9_result_string(pCtx, zName, -1); + return JX9_OK; + } + if( sVer.dwPlatformId == VER_PLATFORM_WIN32_NT ){ + if( sVer.dwMajorVersion <= 4 ){ + zName = "Microsoft Windows NT"; + }else if( sVer.dwMajorVersion == 5 ){ + switch(sVer.dwMinorVersion){ + case 0: zName = "Microsoft Windows 2000"; break; + case 1: zName = "Microsoft Windows XP"; break; + case 2: zName = "Microsoft Windows Server 2003"; break; + } + }else if( sVer.dwMajorVersion == 6){ + switch(sVer.dwMinorVersion){ + case 0: zName = "Microsoft Windows Vista"; break; + case 1: zName = "Microsoft Windows 7"; break; + case 2: zName = "Microsoft Windows 8"; break; + default: break; + } + } + } + switch(zMode[0]){ + case 's': + /* Operating system name */ + jx9_result_string(pCtx, zName, -1/* Compute length automatically*/); + break; + case 'n': + /* Host name */ + jx9_result_string(pCtx, "localhost", (int)sizeof("localhost")-1); + break; + case 'r': + case 'v': + /* Version information. */ + jx9_result_string_format(pCtx, "%u.%u build %u", + sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber + ); + break; + case 'm': + /* Machine name */ + jx9_result_string(pCtx, "x86", (int)sizeof("x86")-1); + break; + default: + jx9_result_string_format(pCtx, "%s localhost %u.%u build %u x86", + zName, + sVer.dwMajorVersion, sVer.dwMinorVersion, sVer.dwBuildNumber + ); + break; + } +#elif defined(__UNIXES__) + if( uname(&sName) != 0 ){ + jx9_result_string(pCtx, "Unix", (int)sizeof("Unix")-1); + return JX9_OK; + } + switch(zMode[0]){ + case 's': + /* Operating system name */ + jx9_result_string(pCtx, sName.sysname, -1/* Compute length automatically*/); + break; + case 'n': + /* Host name */ + jx9_result_string(pCtx, sName.nodename, -1/* Compute length automatically*/); + break; + case 'r': + /* Release information */ + jx9_result_string(pCtx, sName.release, -1/* Compute length automatically*/); + break; + case 'v': + /* Version information. */ + jx9_result_string(pCtx, sName.version, -1/* Compute length automatically*/); + break; + case 'm': + /* Machine name */ + jx9_result_string(pCtx, sName.machine, -1/* Compute length automatically*/); + break; + default: + jx9_result_string_format(pCtx, + "%s %s %s %s %s", + sName.sysname, + sName.release, + sName.version, + sName.nodename, + sName.machine + ); + break; + } +#else + jx9_result_string(pCtx, "Host Operating System/localhost", (int)sizeof("Host Operating System/localhost")-1); +#endif + return JX9_OK; +} +/* + * Section: + * IO stream implementation. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +typedef struct io_private io_private; +struct io_private +{ + const jx9_io_stream *pStream; /* Underlying IO device */ + void *pHandle; /* IO handle */ + /* Unbuffered IO */ + SyBlob sBuffer; /* Working buffer */ + sxu32 nOfft; /* Current read offset */ + sxu32 iMagic; /* Sanity check to avoid misuse */ +}; +#define IO_PRIVATE_MAGIC 0xFEAC14 +/* Make sure we are dealing with a valid io_private instance */ +#define IO_PRIVATE_INVALID(IO) ( IO == 0 || IO->iMagic != IO_PRIVATE_MAGIC ) +/* Forward declaration */ +static void ResetIOPrivate(io_private *pDev); +/* + * bool ftruncate(resource $handle, int64 $size) + * Truncates a file to a given length. + * Parameters + * $handle + * The file pointer. + * Note: + * The handle must be open for writing. + * $size + * The size to truncate to. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Builtin_ftruncate(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xTrunc == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + rc = pStream->xTrunc(pDev->pHandle, jx9_value_to_int64(apArg[1])); + if( rc == JX9_OK ){ + /* Discard buffered data */ + ResetIOPrivate(pDev); + } + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * int fseek(resource $handle, int $offset[, int $whence = SEEK_SET ]) + * Seeks on a file pointer. + * Parameters + * $handle + * A file system pointer resource that is typically created using fopen(). + * $offset + * The offset. + * To move to a position before the end-of-file, you need to pass a negative + * value in offset and set whence to SEEK_END. + * whence + * whence values are: + * SEEK_SET - Set position equal to offset bytes. + * SEEK_CUR - Set position to current location plus offset. + * SEEK_END - Set position to end-of-file plus offset. + * Return + * 0 on success, -1 on failure + */ +static int jx9Builtin_fseek(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + jx9_int64 iOfft; + int whence; + int rc; + if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xSeek == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_int(pCtx, -1); + return JX9_OK; + } + /* Extract the offset */ + iOfft = jx9_value_to_int64(apArg[1]); + whence = 0;/* SEEK_SET */ + if( nArg > 2 && jx9_value_is_int(apArg[2]) ){ + whence = jx9_value_to_int(apArg[2]); + } + /* Perform the requested operation */ + rc = pStream->xSeek(pDev->pHandle, iOfft, whence); + if( rc == JX9_OK ){ + /* Ignore buffered data */ + ResetIOPrivate(pDev); + } + /* IO result */ + jx9_result_int(pCtx, rc == JX9_OK ? 0 : - 1); + return JX9_OK; +} +/* + * int64 ftell(resource $handle) + * Returns the current position of the file read/write pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns the position of the file pointer referenced by handle + * as an integer; i.e., its offset into the file stream. + * FALSE is returned on failure. + */ +static int jx9Builtin_ftell(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + jx9_int64 iOfft; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xTell == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + iOfft = pStream->xTell(pDev->pHandle); + /* IO result */ + jx9_result_int64(pCtx, iOfft); + return JX9_OK; +} +/* + * bool rewind(resource $handle) + * Rewind the position of a file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Builtin_rewind(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xSeek == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + rc = pStream->xSeek(pDev->pHandle, 0, 0/*SEEK_SET*/); + if( rc == JX9_OK ){ + /* Ignore buffered data */ + ResetIOPrivate(pDev); + } + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool fflush(resource $handle) + * Flushes the output to a file. + * Parameters + * $handle + * The file pointer. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Builtin_fflush(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xSync == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + rc = pStream->xSync(pDev->pHandle); + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * bool feof(resource $handle) + * Tests for end-of-file on a file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns TRUE if the file pointer is at EOF.FALSE otherwise + */ +static int jx9Builtin_feof(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 1); + return JX9_OK; + } + rc = SXERR_EOF; + /* Perform the requested operation */ + if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ + /* Data is available */ + rc = JX9_OK; + }else{ + char zBuf[4096]; + jx9_int64 n; + /* Perform a buffered read */ + n = pStream->xRead(pDev->pHandle, zBuf, sizeof(zBuf)); + if( n > 0 ){ + /* Copy buffered data */ + SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n); + rc = JX9_OK; + } + } + /* EOF or not */ + jx9_result_bool(pCtx, rc == SXERR_EOF); + return JX9_OK; +} +/* + * Read n bytes from the underlying IO stream device. + * Return total numbers of bytes readen on success. A number < 1 on failure + * [i.e: IO error ] or EOF. + */ +static jx9_int64 StreamRead(io_private *pDev, void *pBuf, jx9_int64 nLen) +{ + const jx9_io_stream *pStream = pDev->pStream; + char *zBuf = (char *)pBuf; + jx9_int64 n, nRead; + n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; + if( n > 0 ){ + if( n > nLen ){ + n = nLen; + } + /* Copy the buffered data */ + SyMemcpy(SyBlobDataAt(&pDev->sBuffer, pDev->nOfft), pBuf, (sxu32)n); + /* Update the read offset */ + pDev->nOfft += (sxu32)n; + if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ + /* Reset the working buffer so that we avoid excessive memory allocation */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + } + nLen -= n; + if( nLen < 1 ){ + /* All done */ + return n; + } + /* Advance the cursor */ + zBuf += n; + } + /* Read without buffering */ + nRead = pStream->xRead(pDev->pHandle, zBuf, nLen); + if( nRead > 0 ){ + n += nRead; + }else if( n < 1 ){ + /* EOF or IO error */ + return nRead; + } + return n; +} +/* + * Extract a single line from the buffered input. + */ +static sxi32 GetLine(io_private *pDev, jx9_int64 *pLen, const char **pzLine) +{ + const char *zIn, *zEnd, *zPtr; + zIn = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); + zEnd = &zIn[SyBlobLength(&pDev->sBuffer)-pDev->nOfft]; + zPtr = zIn; + while( zIn < zEnd ){ + if( zIn[0] == '\n' ){ + /* Line found */ + zIn++; /* Include the line ending as requested by the JX9 specification */ + *pLen = (jx9_int64)(zIn-zPtr); + *pzLine = zPtr; + return SXRET_OK; + } + zIn++; + } + /* No line were found */ + return SXERR_NOTFOUND; +} +/* + * Read a single line from the underlying IO stream device. + */ +static jx9_int64 StreamReadLine(io_private *pDev, const char **pzData, jx9_int64 nMaxLen) +{ + const jx9_io_stream *pStream = pDev->pStream; + char zBuf[8192]; + jx9_int64 n; + sxi32 rc; + n = 0; + if( pDev->nOfft >= SyBlobLength(&pDev->sBuffer) ){ + /* Reset the working buffer so that we avoid excessive memory allocation */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + } + if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ + /* Check if there is a line */ + rc = GetLine(pDev, &n, pzData); + if( rc == SXRET_OK ){ + /* Got line, update the cursor */ + pDev->nOfft += (sxu32)n; + return n; + } + } + /* Perform the read operation until a new line is extracted or length + * limit is reached. + */ + for(;;){ + n = pStream->xRead(pDev->pHandle, zBuf, (nMaxLen > 0 && nMaxLen < sizeof(zBuf)) ? nMaxLen : sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error */ + break; + } + /* Append the data just read */ + SyBlobAppend(&pDev->sBuffer, zBuf, (sxu32)n); + /* Try to extract a line */ + rc = GetLine(pDev, &n, pzData); + if( rc == SXRET_OK ){ + /* Got one, return immediately */ + pDev->nOfft += (sxu32)n; + return n; + } + if( nMaxLen > 0 && (SyBlobLength(&pDev->sBuffer) - pDev->nOfft >= nMaxLen) ){ + /* Read limit reached, return the available data */ + *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); + n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; + /* Reset the working buffer */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + return n; + } + } + if( SyBlobLength(&pDev->sBuffer) - pDev->nOfft > 0 ){ + /* Read limit reached, return the available data */ + *pzData = (const char *)SyBlobDataAt(&pDev->sBuffer, pDev->nOfft); + n = SyBlobLength(&pDev->sBuffer) - pDev->nOfft; + /* Reset the working buffer */ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; + } + return n; +} +/* + * Open an IO stream handle. + * Notes on stream: + * According to the JX9 reference manual. + * In its simplest definition, a stream is a resource object which exhibits streamable behavior. + * That is, it can be read from or written to in a linear fashion, and may be able to fseek() + * to an arbitrary locations within the stream. + * A wrapper is additional code which tells the stream how to handle specific protocols/encodings. + * For example, the http wrapper knows how to translate a URL into an HTTP/1.0 request for a file + * on a remote server. + * A stream is referenced as: scheme://target + * scheme(string) - The name of the wrapper to be used. Examples include: file, http... + * If no wrapper is specified, the function default is used (typically file://). + * target - Depends on the wrapper used. For filesystem related streams this is typically a path + * and filename of the desired file. For network related streams this is typically a hostname, often + * with a path appended. + * + * Note that JX9 IO streams looks like JX9 streams but their implementation differ greately. + * Please refer to the official documentation for a full discussion. + * This function return a handle on success. Otherwise null. + */ +JX9_PRIVATE void * jx9StreamOpenHandle(jx9_vm *pVm, const jx9_io_stream *pStream, const char *zFile, + int iFlags, int use_include, jx9_value *pResource, int bPushInclude, int *pNew) +{ + void *pHandle = 0; /* cc warning */ + SyString sFile; + int rc; + if( pStream == 0 ){ + /* No such stream device */ + return 0; + } + SyStringInitFromBuf(&sFile, zFile, SyStrlen(zFile)); + if( use_include ){ + if( sFile.zString[0] == '/' || +#ifdef __WINNT__ + (sFile.nByte > 2 && sFile.zString[1] == ':' && (sFile.zString[2] == '\\' || sFile.zString[2] == '/') ) || +#endif + (sFile.nByte > 1 && sFile.zString[0] == '.' && sFile.zString[1] == '/') || + (sFile.nByte > 2 && sFile.zString[0] == '.' && sFile.zString[1] == '.' && sFile.zString[2] == '/') ){ + /* Open the file directly */ + rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle); + }else{ + SyString *pPath; + SyBlob sWorker; +#ifdef __WINNT__ + static const int c = '\\'; +#else + static const int c = '/'; +#endif + /* Init the path builder working buffer */ + SyBlobInit(&sWorker, &pVm->sAllocator); + /* Build a path from the set of include path */ + SySetResetCursor(&pVm->aPaths); + rc = SXERR_IO; + while( SXRET_OK == SySetGetNextEntry(&pVm->aPaths, (void **)&pPath) ){ + /* Build full path */ + SyBlobFormat(&sWorker, "%z%c%z", pPath, c, &sFile); + /* Append null terminator */ + if( SXRET_OK != SyBlobNullAppend(&sWorker) ){ + continue; + } + /* Try to open the file */ + rc = pStream->xOpen((const char *)SyBlobData(&sWorker), iFlags, pResource, &pHandle); + if( rc == JX9_OK ){ + if( bPushInclude ){ + /* Mark as included */ + jx9VmPushFilePath(pVm, (const char *)SyBlobData(&sWorker), SyBlobLength(&sWorker), FALSE, pNew); + } + break; + } + /* Reset the working buffer */ + SyBlobReset(&sWorker); + /* Check the next path */ + } + SyBlobRelease(&sWorker); + } + if( rc == JX9_OK ){ + if( bPushInclude ){ + /* Mark as included */ + jx9VmPushFilePath(pVm, sFile.zString, sFile.nByte, FALSE, pNew); + } + } + }else{ + /* Open the URI direcly */ + rc = pStream->xOpen(zFile, iFlags, pResource, &pHandle); + } + if( rc != JX9_OK ){ + /* IO error */ + return 0; + } + /* Return the file handle */ + return pHandle; +} +/* + * Read the whole contents of an open IO stream handle [i.e local file/URL..] + * Store the read data in the given BLOB (last argument). + * The read operation is stopped when he hit the EOF or an IO error occurs. + */ +JX9_PRIVATE sxi32 jx9StreamReadWholeFile(void *pHandle, const jx9_io_stream *pStream, SyBlob *pOut) +{ + jx9_int64 nRead; + char zBuf[8192]; /* 8K */ + int rc; + /* Perform the requested operation */ + for(;;){ + nRead = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); + if( nRead < 1 ){ + /* EOF or IO error */ + break; + } + /* Append contents */ + rc = SyBlobAppend(pOut, zBuf, (sxu32)nRead); + if( rc != SXRET_OK ){ + break; + } + } + return SyBlobLength(pOut) > 0 ? SXRET_OK : -1; +} +/* + * Close an open IO stream handle [i.e local file/URI..]. + */ +JX9_PRIVATE void jx9StreamCloseHandle(const jx9_io_stream *pStream, void *pHandle) +{ + if( pStream->xClose ){ + pStream->xClose(pHandle); + } +} +/* + * string fgetc(resource $handle) + * Gets a character from the given file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns a string containing a single character read from the file + * pointed to by handle. Returns FALSE on EOF. + * WARNING + * This operation is extremely slow.Avoid using it. + */ +static int jx9Builtin_fgetc(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + int c, n; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + n = (int)StreamRead(pDev, (void *)&c, sizeof(char)); + /* IO result */ + if( n < 1 ){ + /* EOF or error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Return the string holding the character */ + jx9_result_string(pCtx, (const char *)&c, sizeof(char)); + } + return JX9_OK; +} +/* + * string fgets(resource $handle[, int64 $length ]) + * Gets line from file pointer. + * Parameters + * $handle + * The file pointer. + * $length + * Reading ends when length - 1 bytes have been read, on a newline + * (which is included in the return value), or on EOF (whichever comes first). + * If no length is specified, it will keep reading from the stream until it reaches + * the end of the line. + * Return + * Returns a string of up to length - 1 bytes read from the file pointed to by handle. + * If there is no more data to read in the file pointer, then FALSE is returned. + * If an error occurs, FALSE is returned. + */ +static int jx9Builtin_fgets(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + const char *zLine; + io_private *pDev; + jx9_int64 n, nLen; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = -1; + if( nArg > 1 ){ + /* Maximum data to read */ + nLen = jx9_value_to_int64(apArg[1]); + } + /* Perform the requested operation */ + n = StreamReadLine(pDev, &zLine, nLen); + if( n < 1 ){ + /* EOF or IO error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Return the freshly extracted line */ + jx9_result_string(pCtx, zLine, (int)n); + } + return JX9_OK; +} +/* + * string fread(resource $handle, int64 $length) + * Binary-safe file read. + * Parameters + * $handle + * The file pointer. + * $length + * Up to length number of bytes read. + * Return + * The data readen on success or FALSE on failure. + */ +static int jx9Builtin_fread(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + jx9_int64 nRead; + void *pBuf; + int nLen; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = 4096; + if( nArg > 1 ){ + nLen = jx9_value_to_int(apArg[1]); + if( nLen < 1 ){ + /* Invalid length, set a default length */ + nLen = 4096; + } + } + /* Allocate enough buffer */ + pBuf = jx9_context_alloc_chunk(pCtx, (unsigned int)nLen, FALSE, FALSE); + if( pBuf == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + nRead = StreamRead(pDev, pBuf, (jx9_int64)nLen); + if( nRead < 1 ){ + /* Nothing read, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Make a copy of the data just read */ + jx9_result_string(pCtx, (const char *)pBuf, (int)nRead); + } + /* Release the buffer */ + jx9_context_free_chunk(pCtx, pBuf); + return JX9_OK; +} +/* + * array fgetcsv(resource $handle [, int $length = 0 + * [, string $delimiter = ', '[, string $enclosure = '"'[, string $escape='\\']]]]) + * Gets line from file pointer and parse for CSV fields. + * Parameters + * $handle + * The file pointer. + * $length + * Reading ends when length - 1 bytes have been read, on a newline + * (which is included in the return value), or on EOF (whichever comes first). + * If no length is specified, it will keep reading from the stream until it reaches + * the end of the line. + * $delimiter + * Set the field delimiter (one character only). + * $enclosure + * Set the field enclosure character (one character only). + * $escape + * Set the escape character (one character only). Defaults as a backslash (\) + * Return + * Returns a string of up to length - 1 bytes read from the file pointed to by handle. + * If there is no more data to read in the file pointer, then FALSE is returned. + * If an error occurs, FALSE is returned. + */ +static int jx9Builtin_fgetcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + const char *zLine; + io_private *pDev; + jx9_int64 n, nLen; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = -1; + if( nArg > 1 ){ + /* Maximum data to read */ + nLen = jx9_value_to_int64(apArg[1]); + } + /* Perform the requested operation */ + n = StreamReadLine(pDev, &zLine, nLen); + if( n < 1 ){ + /* EOF or IO error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + jx9_value *pArray; + int delim = ','; /* Delimiter */ + int encl = '"' ; /* Enclosure */ + int escape = '\\'; /* Escape character */ + if( nArg > 2 ){ + const char *zPtr; + int i; + if( jx9_value_is_string(apArg[2]) ){ + /* Extract the delimiter */ + zPtr = jx9_value_to_string(apArg[2], &i); + if( i > 0 ){ + delim = zPtr[0]; + } + } + if( nArg > 3 ){ + if( jx9_value_is_string(apArg[3]) ){ + /* Extract the enclosure */ + zPtr = jx9_value_to_string(apArg[3], &i); + if( i > 0 ){ + encl = zPtr[0]; + } + } + if( nArg > 4 ){ + if( jx9_value_is_string(apArg[4]) ){ + /* Extract the escape character */ + zPtr = jx9_value_to_string(apArg[4], &i); + if( i > 0 ){ + escape = zPtr[0]; + } + } + } + } + } + /* Create our array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_null(pCtx); + return JX9_OK; + } + /* Parse the raw input */ + jx9ProcessCsv(zLine, (int)n, delim, encl, escape, jx9CsvConsumer, pArray); + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + } + return JX9_OK; +} +/* + * string fgetss(resource $handle [, int $length [, string $allowable_tags ]]) + * Gets line from file pointer and strip HTML tags. + * Parameters + * $handle + * The file pointer. + * $length + * Reading ends when length - 1 bytes have been read, on a newline + * (which is included in the return value), or on EOF (whichever comes first). + * If no length is specified, it will keep reading from the stream until it reaches + * the end of the line. + * $allowable_tags + * You can use the optional second parameter to specify tags which should not be stripped. + * Return + * Returns a string of up to length - 1 bytes read from the file pointed to by + * handle, with all HTML and JX9 code stripped. If an error occurs, returns FALSE. + */ +static int jx9Builtin_fgetss(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + const char *zLine; + io_private *pDev; + jx9_int64 n, nLen; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nLen = -1; + if( nArg > 1 ){ + /* Maximum data to read */ + nLen = jx9_value_to_int64(apArg[1]); + } + /* Perform the requested operation */ + n = StreamReadLine(pDev, &zLine, nLen); + if( n < 1 ){ + /* EOF or IO error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + const char *zTaglist = 0; + int nTaglen = 0; + if( nArg > 2 && jx9_value_is_string(apArg[2]) ){ + /* Allowed tag */ + zTaglist = jx9_value_to_string(apArg[2], &nTaglen); + } + /* Process data just read */ + jx9StripTagsFromString(pCtx, zLine, (int)n, zTaglist, nTaglen); + } + return JX9_OK; +} +/* + * string readdir(resource $dir_handle) + * Read entry from directory handle. + * Parameter + * $dir_handle + * The directory handle resource previously opened with opendir(). + * Return + * Returns the filename on success or FALSE on failure. + */ +static int jx9Builtin_readdir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + int rc; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xReadDir == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + jx9_result_bool(pCtx, 0); + /* Perform the requested operation */ + rc = pStream->xReadDir(pDev->pHandle, pCtx); + if( rc != JX9_OK ){ + /* Return FALSE */ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * void rewinddir(resource $dir_handle) + * Rewind directory handle. + * Parameter + * $dir_handle + * The directory handle resource previously opened with opendir(). + * Return + * FALSE on failure. + */ +static int jx9Builtin_rewinddir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xRewindDir == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + pStream->xRewindDir(pDev->pHandle); + return JX9_OK; + } +/* Forward declaration */ +static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut); +static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev); +/* + * void closedir(resource $dir_handle) + * Close directory handle. + * Parameter + * $dir_handle + * The directory handle resource previously opened with opendir(). + * Return + * FALSE on failure. + */ +static int jx9Builtin_closedir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xCloseDir == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + pStream->xCloseDir(pDev->pHandle); + /* Release the private stucture */ + ReleaseIOPrivate(pCtx, pDev); + jx9MemObjRelease(apArg[0]); + return JX9_OK; + } +/* + * resource opendir(string $path[, resource $context]) + * Open directory handle. + * Parameters + * $path + * The directory path that is to be opened. + * $context + * A context stream resource. + * Return + * A directory handle resource on success, or FALSE on failure. + */ +static int jx9Builtin_opendir(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + const char *zPath; + io_private *pDev; + int iLen, rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a directory path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the target path */ + zPath = jx9_value_to_string(apArg[0], &iLen); + /* Try to extract a stream */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zPath, iLen); + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "No stream device is associated with the given path(%s)", zPath); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( pStream->xOpenDir == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + jx9_function_name(pCtx), pStream->zName + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Allocate a new IO private instance */ + pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); + if( pDev == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Initialize the structure */ + InitIOPrivate(pCtx->pVm, pStream, pDev); + /* Open the target directory */ + rc = pStream->xOpenDir(zPath, nArg > 1 ? apArg[1] : 0, &pDev->pHandle); + if( rc != JX9_OK ){ + /* IO error, return FALSE */ + ReleaseIOPrivate(pCtx, pDev); + jx9_result_bool(pCtx, 0); + }else{ + /* Return the handle as a resource */ + jx9_result_resource(pCtx, pDev); + } + return JX9_OK; +} +/* + * int readfile(string $filename[, bool $use_include_path = false [, resource $context ]]) + * Reads a file and writes it to the output buffer. + * Parameters + * $filename + * The filename being read. + * $use_include_path + * You can use the optional second parameter and set it to + * TRUE, if you want to search for the file in the include_path, too. + * $context + * A context stream resource. + * Return + * The number of bytes read from the file on success or FALSE on failure. + */ +static int jx9Builtin_readfile(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int use_include = FALSE; + const jx9_io_stream *pStream; + jx9_int64 n, nRead; + const char *zFile; + char zBuf[8192]; + void *pHandle; + int rc, nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( nArg > 1 ){ + use_include = jx9_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, + use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); + if( pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + nRead = 0; + for(;;){ + n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error, break immediately */ + break; + } + /* Output data */ + rc = jx9_context_output(pCtx, zBuf, (int)n); + if( rc == JX9_ABORT ){ + break; + } + /* Increment counter */ + nRead += n; + } + /* Close the stream */ + jx9StreamCloseHandle(pStream, pHandle); + /* Total number of bytes readen */ + jx9_result_int64(pCtx, nRead); + return JX9_OK; +} +/* + * string file_get_contents(string $filename[, bool $use_include_path = false + * [, resource $context [, int $offset = -1 [, int $maxlen ]]]]) + * Reads entire file into a string. + * Parameters + * $filename + * The filename being read. + * $use_include_path + * You can use the optional second parameter and set it to + * TRUE, if you want to search for the file in the include_path, too. + * $context + * A context stream resource. + * $offset + * The offset where the reading starts on the original stream. + * $maxlen + * Maximum length of data read. The default is to read until end of file + * is reached. Note that this parameter is applied to the stream processed by the filters. + * Return + * The function returns the read data or FALSE on failure. + */ +static int jx9Builtin_file_get_contents(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + jx9_int64 n, nRead, nMaxlen; + int use_include = FALSE; + const char *zFile; + char zBuf[8192]; + void *pHandle; + int nLen; + + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + nMaxlen = -1; + if( nArg > 1 ){ + use_include = jx9_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); + if( pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( nArg > 3 ){ + /* Extract the offset */ + n = jx9_value_to_int64(apArg[3]); + if( n > 0 ){ + if( pStream->xSeek ){ + /* Seek to the desired offset */ + pStream->xSeek(pHandle, n, 0/*SEEK_SET*/); + } + } + if( nArg > 4 ){ + /* Maximum data to read */ + nMaxlen = jx9_value_to_int64(apArg[4]); + } + } + /* Perform the requested operation */ + nRead = 0; + for(;;){ + n = pStream->xRead(pHandle, zBuf, + (nMaxlen > 0 && (nMaxlen < sizeof(zBuf))) ? nMaxlen : sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error, break immediately */ + break; + } + /* Append data */ + jx9_result_string(pCtx, zBuf, (int)n); + /* Increment read counter */ + nRead += n; + if( nMaxlen > 0 && nRead >= nMaxlen ){ + /* Read limit reached */ + break; + } + } + /* Close the stream */ + jx9StreamCloseHandle(pStream, pHandle); + /* Check if we have read something */ + if( jx9_context_result_buf_length(pCtx) < 1 ){ + /* Nothing read, return FALSE */ + jx9_result_bool(pCtx, 0); + } + return JX9_OK; +} +/* + * int file_put_contents(string $filename, mixed $data[, int $flags = 0[, resource $context]]) + * Write a string to a file. + * Parameters + * $filename + * Path to the file where to write the data. + * $data + * The data to write(Must be a string). + * $flags + * The value of flags can be any combination of the following + * flags, joined with the binary OR (|) operator. + * FILE_USE_INCLUDE_PATH Search for filename in the include directory. See include_path for more information. + * FILE_APPEND If file filename already exists, append the data to the file instead of overwriting it. + * LOCK_EX Acquire an exclusive lock on the file while proceeding to the writing. + * context + * A context stream resource. + * Return + * The function returns the number of bytes that were written to the file, or FALSE on failure. + */ +static int jx9Builtin_file_put_contents(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + int use_include = FALSE; + const jx9_io_stream *pStream; + const char *zFile; + const char *zData; + int iOpenFlags; + void *pHandle; + int iFlags; + int nLen; + + if( nArg < 2 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Data to write */ + zData = jx9_value_to_string(apArg[1], &nLen); + if( nLen < 1 ){ + /* Nothing to write, return immediately */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Try to open the file in read-write mode */ + iOpenFlags = JX9_IO_OPEN_CREATE|JX9_IO_OPEN_RDWR|JX9_IO_OPEN_TRUNC; + /* Extract the flags */ + iFlags = 0; + if( nArg > 2 ){ + iFlags = jx9_value_to_int(apArg[2]); + if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/){ + use_include = TRUE; + } + if( iFlags & 0x08 /* FILE_APPEND */){ + /* If the file already exists, append the data to the file + * instead of overwriting it. + */ + iOpenFlags &= ~JX9_IO_OPEN_TRUNC; + /* Append mode */ + iOpenFlags |= JX9_IO_OPEN_APPEND; + } + } + pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, iOpenFlags, use_include, + nArg > 3 ? apArg[3] : 0, FALSE, FALSE); + if( pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( pStream->xWrite ){ + jx9_int64 n; + if( (iFlags & 0x01/* LOCK_EX */) && pStream->xLock ){ + /* Try to acquire an exclusive lock */ + pStream->xLock(pHandle, 1/* LOCK_EX */); + } + /* Perform the write operation */ + n = pStream->xWrite(pHandle, (const void *)zData, nLen); + if( n < 1 ){ + /* IO error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Total number of bytes written */ + jx9_result_int64(pCtx, n); + } + }else{ + /* Read-only stream */ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, + "Read-only stream(%s): Cannot perform write operation", + pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + } + /* Close the handle */ + jx9StreamCloseHandle(pStream, pHandle); + return JX9_OK; +} +/* + * array file(string $filename[, int $flags = 0[, resource $context]]) + * Reads entire file into an array. + * Parameters + * $filename + * The filename being read. + * $flags + * The optional parameter flags can be one, or more, of the following constants: + * FILE_USE_INCLUDE_PATH + * Search for the file in the include_path. + * FILE_IGNORE_NEW_LINES + * Do not add newline at the end of each array element + * FILE_SKIP_EMPTY_LINES + * Skip empty lines + * $context + * A context stream resource. + * Return + * The function returns the read data or FALSE on failure. + */ +static int jx9Builtin_file(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zFile, *zPtr, *zEnd, *zBuf; + jx9_value *pArray, *pLine; + const jx9_io_stream *pStream; + int use_include = 0; + io_private *pDev; + jx9_int64 n; + int iFlags; + int nLen; + + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Allocate a new IO private instance */ + pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); + if( pDev == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Initialize the structure */ + InitIOPrivate(pCtx->pVm, pStream, pDev); + iFlags = 0; + if( nArg > 1 ){ + iFlags = jx9_value_to_int(apArg[1]); + } + if( iFlags & 0x01 /*FILE_USE_INCLUDE_PATH*/ ){ + use_include = TRUE; + } + /* Create the array and the working value */ + pArray = jx9_context_new_array(pCtx); + pLine = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pLine == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Try to open the file in read-only mode */ + pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, use_include, nArg > 2 ? apArg[2] : 0, FALSE, 0); + if( pDev->pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + /* Don't worry about freeing memory, everything will be released automatically + * as soon we return from this function. + */ + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + /* Try to extract a line */ + n = StreamReadLine(pDev, &zBuf, -1); + if( n < 1 ){ + /* EOF or IO error */ + break; + } + /* Reset the cursor */ + jx9_value_reset_string_cursor(pLine); + /* Remove line ending if requested by the caller */ + zPtr = zBuf; + zEnd = &zBuf[n]; + if( iFlags & 0x02 /* FILE_IGNORE_NEW_LINES */ ){ + /* Ignore trailig lines */ + while( zPtr < zEnd && (zEnd[-1] == '\n' +#ifdef __WINNT__ + || zEnd[-1] == '\r' +#endif + )){ + n--; + zEnd--; + } + } + if( iFlags & 0x04 /* FILE_SKIP_EMPTY_LINES */ ){ + /* Ignore empty lines */ + while( zPtr < zEnd && (unsigned char)zPtr[0] < 0xc0 && SyisSpace(zPtr[0]) ){ + zPtr++; + } + if( zPtr >= zEnd ){ + /* Empty line */ + continue; + } + } + jx9_value_string(pLine, zBuf, (int)(zEnd-zBuf)); + /* Insert line */ + jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pLine); + } + /* Close the stream */ + jx9StreamCloseHandle(pStream, pDev->pHandle); + /* Release the io_private instance */ + ReleaseIOPrivate(pCtx, pDev); + /* Return the created array */ + jx9_result_value(pCtx, pArray); + return JX9_OK; +} +/* + * bool copy(string $source, string $dest[, resource $context ] ) + * Makes a copy of the file source to dest. + * Parameters + * $source + * Path to the source file. + * $dest + * The destination path. If dest is a URL, the copy operation + * may fail if the wrapper does not support overwriting of existing files. + * $context + * A context stream resource. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Builtin_copy(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pSin, *pSout; + const char *zFile; + char zBuf[8192]; + void *pIn, *pOut; + jx9_int64 n; + int nLen; + if( nArg < 2 || !jx9_value_is_string(apArg[0]) || !jx9_value_is_string(apArg[1])){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a source and a destination path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the source name */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pSin = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pSin == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Try to open the source file in a read-only mode */ + pIn = jx9StreamOpenHandle(pCtx->pVm, pSin, zFile, JX9_IO_OPEN_RDONLY, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0); + if( pIn == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening source: '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the destination name */ + zFile = jx9_value_to_string(apArg[1], &nLen); + /* Point to the target IO stream device */ + pSout = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pSout == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + jx9StreamCloseHandle(pSin, pIn); + return JX9_OK; + } + if( pSout->xWrite == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pSin->zName + ); + jx9_result_bool(pCtx, 0); + jx9StreamCloseHandle(pSin, pIn); + return JX9_OK; + } + /* Try to open the destination file in a read-write mode */ + pOut = jx9StreamOpenHandle(pCtx->pVm, pSout, zFile, + JX9_IO_OPEN_CREATE|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_RDWR, FALSE, nArg > 2 ? apArg[2] : 0, FALSE, 0); + if( pOut == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening destination: '%s'", zFile); + jx9_result_bool(pCtx, 0); + jx9StreamCloseHandle(pSin, pIn); + return JX9_OK; + } + /* Perform the requested operation */ + for(;;){ + /* Read from source */ + n = pSin->xRead(pIn, zBuf, sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error, break immediately */ + break; + } + /* Write to dest */ + n = pSout->xWrite(pOut, zBuf, n); + if( n < 1 ){ + /* IO error, break immediately */ + break; + } + } + /* Close the streams */ + jx9StreamCloseHandle(pSin, pIn); + jx9StreamCloseHandle(pSout, pOut); + /* Return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * array fstat(resource $handle) + * Gets information about a file using an open file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Returns an array with the statistics of the file or FALSE on failure. + */ +static int jx9Builtin_fstat(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray, *pValue; + const jx9_io_stream *pStream; + io_private *pDev; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /* Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xStat == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Create the array and the working value */ + pArray = jx9_context_new_array(pCtx); + pValue = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pValue == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + pStream->xStat(pDev->pHandle, pArray, pValue); + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + /* Don't worry about freeing memory here, everything will be + * released automatically as soon we return from this function. + */ + return JX9_OK; +} +/* + * int fwrite(resource $handle, string $string[, int $length]) + * Writes the contents of string to the file stream pointed to by handle. + * Parameters + * $handle + * The file pointer. + * $string + * The string that is to be written. + * $length + * If the length argument is given, writing will stop after length bytes have been written + * or the end of string is reached, whichever comes first. + * Return + * Returns the number of bytes written, or FALSE on error. + */ +static int jx9Builtin_fwrite(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + const char *zString; + io_private *pDev; + int nLen, n; + if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /* Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xWrite == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the data to write */ + zString = jx9_value_to_string(apArg[1], &nLen); + if( nArg > 2 ){ + /* Maximum data length to write */ + n = jx9_value_to_int(apArg[2]); + if( n >= 0 && n < nLen ){ + nLen = n; + } + } + if( nLen < 1 ){ + /* Nothing to write */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + n = (int)pStream->xWrite(pDev->pHandle, (const void *)zString, nLen); + if( n < 0 ){ + /* IO error, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* #Bytes written */ + jx9_result_int(pCtx, n); + } + return JX9_OK; +} +/* + * bool flock(resource $handle, int $operation) + * Portable advisory file locking. + * Parameters + * $handle + * The file pointer. + * $operation + * operation is one of the following: + * LOCK_SH to acquire a shared lock (reader). + * LOCK_EX to acquire an exclusive lock (writer). + * LOCK_UN to release a lock (shared or exclusive). + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int jx9Builtin_flock(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + int nLock; + int rc; + if( nArg < 2 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xLock == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Requested lock operation */ + nLock = jx9_value_to_int(apArg[1]); + /* Lock operation */ + rc = pStream->xLock(pDev->pHandle, nLock); + /* IO result */ + jx9_result_bool(pCtx, rc == JX9_OK); + return JX9_OK; +} +/* + * int fpassthru(resource $handle) + * Output all remaining data on a file pointer. + * Parameters + * $handle + * The file pointer. + * Return + * Total number of characters read from handle and passed through + * to the output on success or FALSE on failure. + */ +static int jx9Builtin_fpassthru(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + jx9_int64 n, nRead; + char zBuf[8192]; + int rc; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Perform the requested operation */ + nRead = 0; + for(;;){ + n = StreamRead(pDev, zBuf, sizeof(zBuf)); + if( n < 1 ){ + /* Error or EOF */ + break; + } + /* Increment the read counter */ + nRead += n; + /* Output data */ + rc = jx9_context_output(pCtx, zBuf, (int)nRead /* FIXME: 64-bit issues */); + if( rc == JX9_ABORT ){ + /* Consumer callback request an operation abort */ + break; + } + } + /* Total number of bytes readen */ + jx9_result_int64(pCtx, nRead); + return JX9_OK; +} +/* CSV reader/writer private data */ +struct csv_data +{ + int delimiter; /* Delimiter. Default ', ' */ + int enclosure; /* Enclosure. Default '"'*/ + io_private *pDev; /* Open stream handle */ + int iCount; /* Counter */ +}; +/* + * The following callback is used by the fputcsv() function inorder to iterate + * throw array entries and output CSV data based on the current key and it's + * associated data. + */ +static int csv_write_callback(jx9_value *pKey, jx9_value *pValue, void *pUserData) +{ + struct csv_data *pData = (struct csv_data *)pUserData; + const char *zData; + int nLen, c2; + sxu32 n; + /* Point to the raw data */ + zData = jx9_value_to_string(pValue, &nLen); + if( nLen < 1 ){ + /* Nothing to write */ + return JX9_OK; + } + if( pData->iCount > 0 ){ + /* Write the delimiter */ + pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->delimiter, sizeof(char)); + } + n = 1; + c2 = 0; + if( SyByteFind(zData, (sxu32)nLen, pData->delimiter, 0) == SXRET_OK || + SyByteFind(zData, (sxu32)nLen, pData->enclosure, &n) == SXRET_OK ){ + c2 = 1; + if( n == 0 ){ + c2 = 2; + } + /* Write the enclosure */ + pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); + if( c2 > 1 ){ + pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); + } + } + /* Write the data */ + if( pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)zData, (jx9_int64)nLen) < 1 ){ + SXUNUSED(pKey); /* cc warning */ + return JX9_ABORT; + } + if( c2 > 0 ){ + /* Write the enclosure */ + pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); + if( c2 > 1 ){ + pData->pDev->pStream->xWrite(pData->pDev->pHandle, (const void *)&pData->enclosure, sizeof(char)); + } + } + pData->iCount++; + return JX9_OK; +} +/* + * int fputcsv(resource $handle, array $fields[, string $delimiter = ', '[, string $enclosure = '"' ]]) + * Format line as CSV and write to file pointer. + * Parameters + * $handle + * Open file handle. + * $fields + * An array of values. + * $delimiter + * The optional delimiter parameter sets the field delimiter (one character only). + * $enclosure + * The optional enclosure parameter sets the field enclosure (one character only). + */ +static int jx9Builtin_fputcsv(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + struct csv_data sCsv; + io_private *pDev; + char *zEol; + int eolen; + if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_json_array(apArg[1]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Missing/Invalid arguments"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 || pStream->xWrite == 0){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Set default csv separator */ + sCsv.delimiter = ','; + sCsv.enclosure = '"'; + sCsv.pDev = pDev; + sCsv.iCount = 0; + if( nArg > 2 ){ + /* User delimiter */ + const char *z; + int n; + z = jx9_value_to_string(apArg[2], &n); + if( n > 0 ){ + sCsv.delimiter = z[0]; + } + if( nArg > 3 ){ + z = jx9_value_to_string(apArg[3], &n); + if( n > 0 ){ + sCsv.enclosure = z[0]; + } + } + } + /* Iterate throw array entries and write csv data */ + jx9_array_walk(apArg[1], csv_write_callback, &sCsv); + /* Write a line ending */ +#ifdef __WINNT__ + zEol = "\r\n"; + eolen = (int)sizeof("\r\n")-1; +#else + /* Assume UNIX LF */ + zEol = "\n"; + eolen = (int)sizeof(char); +#endif + pDev->pStream->xWrite(pDev->pHandle, (const void *)zEol, eolen); + return JX9_OK; +} +/* + * fprintf, vfprintf private data. + * An instance of the following structure is passed to the formatted + * input consumer callback defined below. + */ +typedef struct fprintf_data fprintf_data; +struct fprintf_data +{ + io_private *pIO; /* IO stream */ + jx9_int64 nCount; /* Total number of bytes written */ +}; +/* + * Callback [i.e: Formatted input consumer] for the fprintf function. + */ +static int fprintfConsumer(jx9_context *pCtx, const char *zInput, int nLen, void *pUserData) +{ + fprintf_data *pFdata = (fprintf_data *)pUserData; + jx9_int64 n; + /* Write the formatted data */ + n = pFdata->pIO->pStream->xWrite(pFdata->pIO->pHandle, (const void *)zInput, nLen); + if( n < 1 ){ + SXUNUSED(pCtx); /* cc warning */ + /* IO error, abort immediately */ + return SXERR_ABORT; + } + /* Increment counter */ + pFdata->nCount += n; + return JX9_OK; +} +/* + * int fprintf(resource $handle, string $format[, mixed $args [, mixed $... ]]) + * Write a formatted string to a stream. + * Parameters + * $handle + * The file pointer. + * $format + * String format (see sprintf()). + * Return + * The length of the written string. + */ +static int jx9Builtin_fprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + fprintf_data sFdata; + const char *zFormat; + io_private *pDev; + int nLen; + if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) ){ + /* Missing/Invalid arguments, return zero */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments"); + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream" + ); + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the string format */ + zFormat = jx9_value_to_string(apArg[1], &nLen); + if( nLen < 1 ){ + /* Empty string, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Prepare our private data */ + sFdata.nCount = 0; + sFdata.pIO = pDev; + /* Format the string */ + jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, nArg - 1, &apArg[1], (void *)&sFdata, FALSE); + /* Return total number of bytes written */ + jx9_result_int64(pCtx, sFdata.nCount); + return JX9_OK; +} +/* + * int vfprintf(resource $handle, string $format, array $args) + * Write a formatted string to a stream. + * Parameters + * $handle + * The file pointer. + * $format + * String format (see sprintf()). + * $args + * User arguments. + * Return + * The length of the written string. + */ +static int jx9Builtin_vfprintf(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + fprintf_data sFdata; + const char *zFormat; + jx9_hashmap *pMap; + io_private *pDev; + SySet sArg; + int n, nLen; + if( nArg < 3 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_string(apArg[1]) || !jx9_value_is_json_array(apArg[2]) ){ + /* Missing/Invalid arguments, return zero */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Invalid arguments"); + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + if( pDev->pStream == 0 || pDev->pStream->xWrite == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device", + jx9_function_name(pCtx), pDev->pStream ? pDev->pStream->zName : "null_stream" + ); + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Extract the string format */ + zFormat = jx9_value_to_string(apArg[1], &nLen); + if( nLen < 1 ){ + /* Empty string, return zero */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to hashmap */ + pMap = (jx9_hashmap *)apArg[2]->x.pOther; + /* Extract arguments from the hashmap */ + n = jx9HashmapValuesToSet(pMap, &sArg); + /* Prepare our private data */ + sFdata.nCount = 0; + sFdata.pIO = pDev; + /* Format the string */ + jx9InputFormat(fprintfConsumer, pCtx, zFormat, nLen, n, (jx9_value **)SySetBasePtr(&sArg), (void *)&sFdata, TRUE); + /* Return total number of bytes written*/ + jx9_result_int64(pCtx, sFdata.nCount); + SySetRelease(&sArg); + return JX9_OK; +} +/* + * Convert open modes (string passed to the fopen() function) [i.e: 'r', 'w+', 'a', ...] into JX9 flags. + * According to the JX9 reference manual: + * The mode parameter specifies the type of access you require to the stream. It may be any of the following + * 'r' Open for reading only; place the file pointer at the beginning of the file. + * 'r+' Open for reading and writing; place the file pointer at the beginning of the file. + * 'w' Open for writing only; place the file pointer at the beginning of the file and truncate the file + * to zero length. If the file does not exist, attempt to create it. + * 'w+' Open for reading and writing; place the file pointer at the beginning of the file and truncate + * the file to zero length. If the file does not exist, attempt to create it. + * 'a' Open for writing only; place the file pointer at the end of the file. If the file does not + * exist, attempt to create it. + * 'a+' Open for reading and writing; place the file pointer at the end of the file. If the file does + * not exist, attempt to create it. + * 'x' Create and open for writing only; place the file pointer at the beginning of the file. If the file + * already exists, + * the fopen() call will fail by returning FALSE and generating an error of level E_WARNING. If the file + * does not exist attempt to create it. This is equivalent to specifying O_EXCL|O_CREAT flags for + * the underlying open(2) system call. + * 'x+' Create and open for reading and writing; otherwise it has the same behavior as 'x'. + * 'c' Open the file for writing only. If the file does not exist, it is created. If it exists, it is neither truncated + * (as opposed to 'w'), nor the call to this function fails (as is the case with 'x'). The file pointer + * is positioned on the beginning of the file. + * This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file + * as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can + * be used after the lock is requested). + * 'c+' Open the file for reading and writing; otherwise it has the same behavior as 'c'. + */ +static int StrModeToFlags(jx9_context *pCtx, const char *zMode, int nLen) +{ + const char *zEnd = &zMode[nLen]; + int iFlag = 0; + int c; + if( nLen < 1 ){ + /* Open in a read-only mode */ + return JX9_IO_OPEN_RDONLY; + } + c = zMode[0]; + if( c == 'r' || c == 'R' ){ + /* Read-only access */ + iFlag = JX9_IO_OPEN_RDONLY; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' || c == 'w' || c == 'W' ){ + /* Read+Write access */ + iFlag = JX9_IO_OPEN_RDWR; + } + } + }else if( c == 'w' || c == 'W' ){ + /* Overwrite mode. + * If the file does not exists, try to create it + */ + iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_TRUNC|JX9_IO_OPEN_CREATE; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' || c == 'r' || c == 'R' ){ + /* Read+Write access */ + iFlag &= ~JX9_IO_OPEN_WRONLY; + iFlag |= JX9_IO_OPEN_RDWR; + } + } + }else if( c == 'a' || c == 'A' ){ + /* Append mode (place the file pointer at the end of the file). + * Create the file if it does not exists. + */ + iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_APPEND|JX9_IO_OPEN_CREATE; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' ){ + /* Read-Write access */ + iFlag &= ~JX9_IO_OPEN_WRONLY; + iFlag |= JX9_IO_OPEN_RDWR; + } + } + }else if( c == 'x' || c == 'X' ){ + /* Exclusive access. + * If the file already exists, return immediately with a failure code. + * Otherwise create a new file. + */ + iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_EXCL; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' || c == 'r' || c == 'R' ){ + /* Read-Write access */ + iFlag &= ~JX9_IO_OPEN_WRONLY; + iFlag |= JX9_IO_OPEN_RDWR; + } + } + }else if( c == 'c' || c == 'C' ){ + /* Overwrite mode.Create the file if it does not exists.*/ + iFlag = JX9_IO_OPEN_WRONLY|JX9_IO_OPEN_CREATE; + zMode++; /* Advance */ + if( zMode < zEnd ){ + c = zMode[0]; + if( c == '+' ){ + /* Read-Write access */ + iFlag &= ~JX9_IO_OPEN_WRONLY; + iFlag |= JX9_IO_OPEN_RDWR; + } + } + }else{ + /* Invalid mode. Assume a read only open */ + jx9_context_throw_error(pCtx, JX9_CTX_NOTICE, "Invalid open mode, JX9 is assuming a Read-Only open"); + iFlag = JX9_IO_OPEN_RDONLY; + } + while( zMode < zEnd ){ + c = zMode[0]; + if( c == 'b' || c == 'B' ){ + iFlag &= ~JX9_IO_OPEN_TEXT; + iFlag |= JX9_IO_OPEN_BINARY; + }else if( c == 't' || c == 'T' ){ + iFlag &= ~JX9_IO_OPEN_BINARY; + iFlag |= JX9_IO_OPEN_TEXT; + } + zMode++; + } + return iFlag; +} +/* + * Initialize the IO private structure. + */ +static void InitIOPrivate(jx9_vm *pVm, const jx9_io_stream *pStream, io_private *pOut) +{ + pOut->pStream = pStream; + SyBlobInit(&pOut->sBuffer, &pVm->sAllocator); + pOut->nOfft = 0; + /* Set the magic number */ + pOut->iMagic = IO_PRIVATE_MAGIC; +} +/* + * Release the IO private structure. + */ +static void ReleaseIOPrivate(jx9_context *pCtx, io_private *pDev) +{ + SyBlobRelease(&pDev->sBuffer); + pDev->iMagic = 0x2126; /* Invalid magic number so we can detetct misuse */ + /* Release the whole structure */ + jx9_context_free_chunk(pCtx, pDev); +} +/* + * Reset the IO private structure. + */ +static void ResetIOPrivate(io_private *pDev) +{ + SyBlobReset(&pDev->sBuffer); + pDev->nOfft = 0; +} +/* Forward declaration */ +static int is_jx9_stream(const jx9_io_stream *pStream); +/* + * resource fopen(string $filename, string $mode [, bool $use_include_path = false[, resource $context ]]) + * Open a file, a URL or any other IO stream. + * Parameters + * $filename + * If filename is of the form "scheme://...", it is assumed to be a URL and JX9 will search + * for a protocol handler (also known as a wrapper) for that scheme. If no scheme is given + * then a regular file is assumed. + * $mode + * The mode parameter specifies the type of access you require to the stream + * See the block comment associated with the StrModeToFlags() for the supported + * modes. + * $use_include_path + * You can use the optional second parameter and set it to + * TRUE, if you want to search for the file in the include_path, too. + * $context + * A context stream resource. + * Return + * File handle on success or FALSE on failure. + */ +static int jx9Builtin_fopen(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + const char *zUri, *zMode; + jx9_value *pResource; + io_private *pDev; + int iLen, imLen; + int iOpenFlags; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path or URL"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the URI and the desired access mode */ + zUri = jx9_value_to_string(apArg[0], &iLen); + if( nArg > 1 ){ + zMode = jx9_value_to_string(apArg[1], &imLen); + }else{ + /* Set a default read-only mode */ + zMode = "r"; + imLen = (int)sizeof(char); + } + /* Try to extract a stream */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zUri, iLen); + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "No stream device is associated with the given URI(%s)", zUri); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Allocate a new IO private instance */ + pDev = (io_private *)jx9_context_alloc_chunk(pCtx, sizeof(io_private), TRUE, FALSE); + if( pDev == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pResource = 0; + if( nArg > 3 ){ + pResource = apArg[3]; + }else if( is_jx9_stream(pStream) ){ + /* TICKET 1433-80: The jx9:// stream need a jx9_value to access the underlying + * virtual machine. + */ + pResource = apArg[0]; + } + /* Initialize the structure */ + InitIOPrivate(pCtx->pVm, pStream, pDev); + /* Convert open mode to JX9 flags */ + iOpenFlags = StrModeToFlags(pCtx, zMode, imLen); + /* Try to get a handle */ + pDev->pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zUri, iOpenFlags, + nArg > 2 ? jx9_value_to_bool(apArg[2]) : FALSE, pResource, FALSE, 0); + if( pDev->pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zUri); + jx9_result_bool(pCtx, 0); + jx9_context_free_chunk(pCtx, pDev); + return JX9_OK; + } + /* All done, return the io_private instance as a resource */ + jx9_result_resource(pCtx, pDev); + return JX9_OK; +} +/* + * bool fclose(resource $handle) + * Closes an open file pointer + * Parameters + * $handle + * The file pointer. + * Return + * TRUE on success or FALSE on failure. + */ +static int jx9Builtin_fclose(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + io_private *pDev; + jx9_vm *pVm; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract our private data */ + pDev = (io_private *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid io_private instance */ + if( IO_PRIVATE_INVALID(pDev) ){ + /*Expecting an IO handle */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting an IO handle"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the target IO stream device */ + pStream = pDev->pStream; + if( pStream == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, + "IO routine(%s) not implemented in the underlying stream(%s) device, JX9 is returning FALSE", + jx9_function_name(pCtx), pStream ? pStream->zName : "null_stream" + ); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the VM that own this context */ + pVm = pCtx->pVm; + /* TICKET 1433-62: Keep the STDIN/STDOUT/STDERR handles open */ + if( pDev != pVm->pStdin && pDev != pVm->pStdout && pDev != pVm->pStderr ){ + /* Perform the requested operation */ + jx9StreamCloseHandle(pStream, pDev->pHandle); + /* Release the IO private structure */ + ReleaseIOPrivate(pCtx, pDev); + /* Invalidate the resource handle */ + jx9_value_release(apArg[0]); + } + /* Return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +#if !defined(JX9_DISABLE_HASH_FUNC) +/* + * MD5/SHA1 digest consumer. + */ +static int vfsHashConsumer(const void *pData, unsigned int nLen, void *pUserData) +{ + /* Append hex chunk verbatim */ + jx9_result_string((jx9_context *)pUserData, (const char *)pData, (int)nLen); + return SXRET_OK; +} +/* + * string md5_file(string $uri[, bool $raw_output = false ]) + * Calculates the md5 hash of a given file. + * Parameters + * $uri + * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) + * $raw_output + * When TRUE, returns the digest in raw binary format with a length of 16. + * Return + * Return the MD5 digest on success or FALSE on failure. + */ +static int jx9Builtin_md5_file(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + unsigned char zDigest[16]; + int raw_output = FALSE; + const char *zFile; + MD5Context sCtx; + char zBuf[8192]; + void *pHandle; + jx9_int64 n; + int nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( nArg > 1 ){ + raw_output = jx9_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); + if( pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Init the MD5 context */ + MD5Init(&sCtx); + /* Perform the requested operation */ + for(;;){ + n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error, break immediately */ + break; + } + MD5Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n); + } + /* Close the stream */ + jx9StreamCloseHandle(pStream, pHandle); + /* Extract the digest */ + MD5Final(zDigest, &sCtx); + if( raw_output ){ + /* Output raw digest */ + jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx); + } + return JX9_OK; +} +/* + * string sha1_file(string $uri[, bool $raw_output = false ]) + * Calculates the SHA1 hash of a given file. + * Parameters + * $uri + * Target URI (file(/path/to/something) or URL(http://www.symisc.net/)) + * $raw_output + * When TRUE, returns the digest in raw binary format with a length of 20. + * Return + * Return the SHA1 digest on success or FALSE on failure. + */ +static int jx9Builtin_sha1_file(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + unsigned char zDigest[20]; + int raw_output = FALSE; + const char *zFile; + SHA1Context sCtx; + char zBuf[8192]; + void *pHandle; + jx9_int64 n; + int nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( nArg > 1 ){ + raw_output = jx9_value_to_bool(apArg[1]); + } + /* Try to open the file in read-only mode */ + pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); + if( pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Init the SHA1 context */ + SHA1Init(&sCtx); + /* Perform the requested operation */ + for(;;){ + n = pStream->xRead(pHandle, zBuf, sizeof(zBuf)); + if( n < 1 ){ + /* EOF or IO error, break immediately */ + break; + } + SHA1Update(&sCtx, (const unsigned char *)zBuf, (unsigned int)n); + } + /* Close the stream */ + jx9StreamCloseHandle(pStream, pHandle); + /* Extract the digest */ + SHA1Final(&sCtx, zDigest); + if( raw_output ){ + /* Output raw digest */ + jx9_result_string(pCtx, (const char *)zDigest, sizeof(zDigest)); + }else{ + /* Perform a binary to hex conversion */ + SyBinToHexConsumer((const void *)zDigest, sizeof(zDigest), vfsHashConsumer, pCtx); + } + return JX9_OK; +} +#endif /* JX9_DISABLE_HASH_FUNC */ +/* + * array parse_ini_file(string $filename[, bool $process_sections = false [, int $scanner_mode = INI_SCANNER_NORMAL ]] ) + * Parse a configuration file. + * Parameters + * $filename + * The filename of the ini file being parsed. + * $process_sections + * By setting the process_sections parameter to TRUE, you get a multidimensional array + * with the section names and settings included. + * The default for process_sections is FALSE. + * $scanner_mode + * Can either be INI_SCANNER_NORMAL (default) or INI_SCANNER_RAW. + * If INI_SCANNER_RAW is supplied, then option values will not be parsed. + * Return + * The settings are returned as an associative array on success. + * Otherwise is returned. + */ +static int jx9Builtin_parse_ini_file(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + const char *zFile; + SyBlob sContents; + void *pHandle; + int nLen; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Try to open the file in read-only mode */ + pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); + if( pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + SyBlobInit(&sContents, &pCtx->pVm->sAllocator); + /* Read the whole file */ + jx9StreamReadWholeFile(pHandle, pStream, &sContents); + if( SyBlobLength(&sContents) < 1 ){ + /* Empty buffer, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Process the raw INI buffer */ + jx9ParseIniString(pCtx, (const char *)SyBlobData(&sContents), SyBlobLength(&sContents), + nArg > 1 ? jx9_value_to_bool(apArg[1]) : 0); + } + /* Close the stream */ + jx9StreamCloseHandle(pStream, pHandle); + /* Release the working buffer */ + SyBlobRelease(&sContents); + return JX9_OK; +} +/* + * Section: + * ZIP archive processing. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +typedef struct zip_raw_data zip_raw_data; +struct zip_raw_data +{ + int iType; /* Where the raw data is stored */ + union raw_data{ + struct mmap_data{ + void *pMap; /* Memory mapped data */ + jx9_int64 nSize; /* Map size */ + const jx9_vfs *pVfs; /* Underlying vfs */ + }mmap; + SyBlob sBlob; /* Memory buffer */ + }raw; +}; +#define ZIP_RAW_DATA_MMAPED 1 /* Memory mapped ZIP raw data */ +#define ZIP_RAW_DATA_MEMBUF 2 /* ZIP raw data stored in a dynamically + * allocated memory chunk. + */ + /* + * mixed zip_open(string $filename) + * Opens a new zip archive for reading. + * Parameters + * $filename + * The file name of the ZIP archive to open. + * Return + * A resource handle for later use with zip_read() and zip_close() or FALSE on failure. + */ +static int jx9Builtin_zip_open(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const jx9_io_stream *pStream; + SyArchive *pArchive; + zip_raw_data *pRaw; + const char *zFile; + SyBlob *pContents; + void *pHandle; + int nLen; + sxi32 rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Expecting a file path"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the file path */ + zFile = jx9_value_to_string(apArg[0], &nLen); + /* Point to the target IO stream device */ + pStream = jx9VmGetStreamDevice(pCtx->pVm, &zFile, nLen); + if( pStream == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "No such stream device, JX9 is returning FALSE"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Create an in-memory archive */ + pArchive = (SyArchive *)jx9_context_alloc_chunk(pCtx, sizeof(SyArchive)+sizeof(zip_raw_data), TRUE, FALSE); + if( pArchive == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pRaw = (zip_raw_data *)&pArchive[1]; + /* Initialize the archive */ + SyArchiveInit(pArchive, &pCtx->pVm->sAllocator, 0, 0); + /* Extract the default stream */ + if( pStream == pCtx->pVm->pDefStream /* file:// stream*/){ + const jx9_vfs *pVfs; + /* Try to get a memory view of the whole file since ZIP files + * tends to be very big this days, this is a huge performance win. + */ + pVfs = jx9ExportBuiltinVfs(); + if( pVfs && pVfs->xMmap ){ + rc = pVfs->xMmap(zFile, &pRaw->raw.mmap.pMap, &pRaw->raw.mmap.nSize); + if( rc == JX9_OK ){ + /* Nice, Extract the whole archive */ + rc = SyZipExtractFromBuf(pArchive, (const char *)pRaw->raw.mmap.pMap, (sxu32)pRaw->raw.mmap.nSize); + if( rc != SXRET_OK ){ + if( pVfs->xUnmap ){ + pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize); + } + /* Release the allocated chunk */ + jx9_context_free_chunk(pCtx, pArchive); + /* Something goes wrong with this ZIP archive, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Archive successfully opened */ + pRaw->iType = ZIP_RAW_DATA_MMAPED; + pRaw->raw.mmap.pVfs = pVfs; + goto success; + } + } + /* FALL THROUGH */ + } + /* Try to open the file in read-only mode */ + pHandle = jx9StreamOpenHandle(pCtx->pVm, pStream, zFile, JX9_IO_OPEN_RDONLY, FALSE, 0, FALSE, 0); + if( pHandle == 0 ){ + jx9_context_throw_error_format(pCtx, JX9_CTX_ERR, "IO error while opening '%s'", zFile); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + pContents = &pRaw->raw.sBlob; + SyBlobInit(pContents, &pCtx->pVm->sAllocator); + /* Read the whole file */ + jx9StreamReadWholeFile(pHandle, pStream, pContents); + /* Assume an invalid ZIP file */ + rc = SXERR_INVALID; + if( SyBlobLength(pContents) > 0 ){ + /* Extract archive entries */ + rc = SyZipExtractFromBuf(pArchive, (const char *)SyBlobData(pContents), SyBlobLength(pContents)); + } + pRaw->iType = ZIP_RAW_DATA_MEMBUF; + /* Close the stream */ + jx9StreamCloseHandle(pStream, pHandle); + if( rc != SXRET_OK ){ + /* Release the working buffer */ + SyBlobRelease(pContents); + /* Release the allocated chunk */ + jx9_context_free_chunk(pCtx, pArchive); + /* Something goes wrong with this ZIP archive, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } +success: + /* Reset the loop cursor */ + SyArchiveResetLoopCursor(pArchive); + /* Return the in-memory archive as a resource handle */ + jx9_result_resource(pCtx, pArchive); + return JX9_OK; +} +/* + * void zip_close(resource $zip) + * Close an in-memory ZIP archive. + * Parameters + * $zip + * A ZIP file previously opened with zip_open(). + * Return + * null. + */ +static int jx9Builtin_zip_close(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchive *pArchive; + zip_raw_data *pRaw; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); + return JX9_OK; + } + /* Point to the in-memory archive */ + pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid ZIP archive */ + if( SXARCH_INVALID(pArchive) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); + return JX9_OK; + } + /* Release the archive */ + SyArchiveRelease(pArchive); + pRaw = (zip_raw_data *)&pArchive[1]; + if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ + SyBlobRelease(&pRaw->raw.sBlob); + }else{ + const jx9_vfs *pVfs = pRaw->raw.mmap.pVfs; + if( pVfs->xUnmap ){ + /* Unmap the memory view */ + pVfs->xUnmap(pRaw->raw.mmap.pMap, pRaw->raw.mmap.nSize); + } + } + /* Release the memory chunk */ + jx9_context_free_chunk(pCtx, pArchive); + return JX9_OK; +} +/* + * mixed zip_read(resource $zip) + * Reads the next entry from an in-memory ZIP archive. + * Parameters + * $zip + * A ZIP file previously opened with zip_open(). + * Return + * A directory entry resource for later use with the zip_entry_... functions + * or FALSE if there are no more entries to read, or an error code if an error occurred. + */ +static int jx9Builtin_zip_read(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pNext = 0; /* cc warning */ + SyArchive *pArchive; + sxi32 rc; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the in-memory archive */ + pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid ZIP archive */ + if( SXARCH_INVALID(pArchive) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the next entry */ + rc = SyArchiveGetNextEntry(pArchive, &pNext); + if( rc != SXRET_OK ){ + /* No more entries in the central directory, return FALSE */ + jx9_result_bool(pCtx, 0); + }else{ + /* Return as a resource handle */ + jx9_result_resource(pCtx, pNext); + /* Point to the ZIP raw data */ + pNext->pUserData = (void *)&pArchive[1]; + } + return JX9_OK; +} +/* + * bool zip_entry_open(resource $zip, resource $zip_entry[, string $mode ]) + * Open a directory entry for reading + * Parameters + * $zip + * A ZIP file previously opened with zip_open(). + * $zip_entry + * A directory entry returned by zip_read(). + * $mode + * Not used + * Return + * A directory entry resource for later use with the zip_entry_... functions + * or FALSE if there are no more entries to read, or an error code if an error occurred. + */ +static int jx9Builtin_zip_entry_open(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + SyArchive *pArchive; + if( nArg < 2 || !jx9_value_is_resource(apArg[0]) || !jx9_value_is_resource(apArg[1]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Point to the in-memory archive */ + pArchive = (SyArchive *)jx9_value_to_resource(apArg[0]); + /* Make sure we are dealing with a valid ZIP archive */ + if( SXARCH_INVALID(pArchive) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[1]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* All done. Actually this function is a no-op, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * bool zip_entry_close(resource $zip_entry) + * Close a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * Returns TRUE on success or FALSE on failure. + */ +static int jx9Builtin_zip_entry_close(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Reset the read cursor */ + pEntry->nReadCount = 0; + /*All done. Actually this function is a no-op, return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * string zip_entry_name(resource $zip_entry) + * Retrieve the name of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The name of the directory entry. + */ +static int jx9Builtin_zip_entry_name(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + SyString *pName; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return entry name */ + pName = &pEntry->sFileName; + jx9_result_string(pCtx, pName->zString, (int)pName->nByte); + return JX9_OK; +} +/* + * int64 zip_entry_filesize(resource $zip_entry) + * Retrieve the actual file size of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The size of the directory entry. + */ +static int jx9Builtin_zip_entry_filesize(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return entry size */ + jx9_result_int64(pCtx, (jx9_int64)pEntry->nByte); + return JX9_OK; +} +/* + * int64 zip_entry_compressedsize(resource $zip_entry) + * Retrieve the compressed size of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The compressed size. + */ +static int jx9Builtin_zip_entry_compressedsize(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Return entry compressed size */ + jx9_result_int64(pCtx, (jx9_int64)pEntry->nByteCompr); + return JX9_OK; +} +/* + * string zip_entry_read(resource $zip_entry[, int $length]) + * Reads from an open directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * $length + * The number of bytes to return. If not specified, this function + * will attempt to read 1024 bytes. + * Return + * Returns the data read, or FALSE if the end of the file is reached. + */ +static int jx9Builtin_zip_entry_read(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + zip_raw_data *pRaw; + const char *zData; + int iLength; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + zData = 0; + if( pEntry->nReadCount >= pEntry->nByteCompr ){ + /* No more data to read, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Set a default read length */ + iLength = 1024; + if( nArg > 1 ){ + iLength = jx9_value_to_int(apArg[1]); + if( iLength < 1 ){ + iLength = 1024; + } + } + if( (sxu32)iLength > pEntry->nByteCompr - pEntry->nReadCount ){ + iLength = (int)(pEntry->nByteCompr - pEntry->nReadCount); + } + /* Return the entry contents */ + pRaw = (zip_raw_data *)pEntry->pUserData; + if( pRaw->iType == ZIP_RAW_DATA_MEMBUF ){ + zData = (const char *)SyBlobDataAt(&pRaw->raw.sBlob, (pEntry->nOfft+pEntry->nReadCount)); + }else{ + const char *zMap = (const char *)pRaw->raw.mmap.pMap; + /* Memory mmaped chunk */ + zData = &zMap[pEntry->nOfft+pEntry->nReadCount]; + } + /* Increment the read counter */ + pEntry->nReadCount += iLength; + /* Return the raw data */ + jx9_result_string(pCtx, zData, iLength); + return JX9_OK; +} +/* + * bool zip_entry_reset_cursor(resource $zip_entry) + * Reset the read cursor of an open directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * TRUE on success, FALSE on failure. + */ +static int jx9Builtin_zip_entry_reset_cursor(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Reset the cursor */ + pEntry->nReadCount = 0; + /* Return TRUE */ + jx9_result_bool(pCtx, 1); + return JX9_OK; +} +/* + * string zip_entry_compressionmethod(resource $zip_entry) + * Retrieve the compression method of a directory entry. + * Parameters + * $zip_entry + * A directory entry returned by zip_read(). + * Return + * The compression method on success or FALSE on failure. + */ +static int jx9Builtin_zip_entry_compressionmethod(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyArchiveEntry *pEntry; + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Make sure we are dealing with a valid ZIP archive entry */ + pEntry = (SyArchiveEntry *)jx9_value_to_resource(apArg[0]); + if( SXARCH_ENTRY_INVALID(pEntry) ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Expecting a ZIP archive entry"); + /* return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + switch(pEntry->nComprMeth){ + case 0: + /* No compression;entry is stored */ + jx9_result_string(pCtx, "stored", (int)sizeof("stored")-1); + break; + case 8: + /* Entry is deflated (Default compression algorithm) */ + jx9_result_string(pCtx, "deflate", (int)sizeof("deflate")-1); + break; + /* Exotic compression algorithms */ + case 1: + jx9_result_string(pCtx, "shrunk", (int)sizeof("shrunk")-1); + break; + case 2: + case 3: + case 4: + case 5: + /* Entry is reduced */ + jx9_result_string(pCtx, "reduced", (int)sizeof("reduced")-1); + break; + case 6: + /* Entry is imploded */ + jx9_result_string(pCtx, "implode", (int)sizeof("implode")-1); + break; + default: + jx9_result_string(pCtx, "unknown", (int)sizeof("unknown")-1); + break; + } + return JX9_OK; +} +#endif /* #ifndef JX9_DISABLE_BUILTIN_FUNC*/ +/* NULL VFS [i.e: a no-op VFS]*/ +static const jx9_vfs null_vfs = { + "null_vfs", + JX9_VFS_VERSION, + 0, /* int (*xChdir)(const char *) */ + 0, /* int (*xChroot)(const char *); */ + 0, /* int (*xGetcwd)(jx9_context *) */ + 0, /* int (*xMkdir)(const char *, int, int) */ + 0, /* int (*xRmdir)(const char *) */ + 0, /* int (*xIsdir)(const char *) */ + 0, /* int (*xRename)(const char *, const char *) */ + 0, /*int (*xRealpath)(const char *, jx9_context *)*/ + 0, /* int (*xSleep)(unsigned int) */ + 0, /* int (*xUnlink)(const char *) */ + 0, /* int (*xFileExists)(const char *) */ + 0, /*int (*xChmod)(const char *, int)*/ + 0, /*int (*xChown)(const char *, const char *)*/ + 0, /*int (*xChgrp)(const char *, const char *)*/ + 0, /* jx9_int64 (*xFreeSpace)(const char *) */ + 0, /* jx9_int64 (*xTotalSpace)(const char *) */ + 0, /* jx9_int64 (*xFileSize)(const char *) */ + 0, /* jx9_int64 (*xFileAtime)(const char *) */ + 0, /* jx9_int64 (*xFileMtime)(const char *) */ + 0, /* jx9_int64 (*xFileCtime)(const char *) */ + 0, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ + 0, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ + 0, /* int (*xIsfile)(const char *) */ + 0, /* int (*xIslink)(const char *) */ + 0, /* int (*xReadable)(const char *) */ + 0, /* int (*xWritable)(const char *) */ + 0, /* int (*xExecutable)(const char *) */ + 0, /* int (*xFiletype)(const char *, jx9_context *) */ + 0, /* int (*xGetenv)(const char *, jx9_context *) */ + 0, /* int (*xSetenv)(const char *, const char *) */ + 0, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ + 0, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ + 0, /* void (*xUnmap)(void *, jx9_int64); */ + 0, /* int (*xLink)(const char *, const char *, int) */ + 0, /* int (*xUmask)(int) */ + 0, /* void (*xTempDir)(jx9_context *) */ + 0, /* unsigned int (*xProcessId)(void) */ + 0, /* int (*xUid)(void) */ + 0, /* int (*xGid)(void) */ + 0, /* void (*xUsername)(jx9_context *) */ + 0 /* int (*xExec)(const char *, jx9_context *) */ +}; +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifndef JX9_DISABLE_DISK_IO +#ifdef __WINNT__ +/* + * Windows VFS implementation for the JX9 engine. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* What follows here is code that is specific to windows systems. */ +#include +/* +** Convert a UTF-8 string to microsoft unicode (UTF-16?). +** +** Space to hold the returned string is obtained from HeapAlloc(). +** Taken from the sqlite3 source tree +** status: Public Domain +*/ +static WCHAR *jx9utf8ToUnicode(const char *zFilename){ + int nChar; + WCHAR *zWideFilename; + + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0); + zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, nChar*sizeof(zWideFilename[0])); + if( zWideFilename == 0 ){ + return 0; + } + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar); + if( nChar==0 ){ + HeapFree(GetProcessHeap(), 0, zWideFilename); + return 0; + } + return zWideFilename; +} +/* +** Convert a UTF-8 filename into whatever form the underlying +** operating system wants filenames in.Space to hold the result +** is obtained from HeapAlloc() and must be freed by the calling +** function. +** Taken from the sqlite3 source tree +** status: Public Domain +*/ +static void *jx9convertUtf8Filename(const char *zFilename){ + void *zConverted; + zConverted = jx9utf8ToUnicode(zFilename); + return zConverted; +} +/* +** Convert microsoft unicode to UTF-8. Space to hold the returned string is +** obtained from HeapAlloc(). +** Taken from the sqlite3 source tree +** status: Public Domain +*/ +static char *jx9unicodeToUtf8(const WCHAR *zWideFilename){ + char *zFilename; + int nByte; + + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); + zFilename = (char *)HeapAlloc(GetProcessHeap(), 0, nByte); + if( zFilename == 0 ){ + return 0; + } + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, 0, 0); + if( nByte == 0 ){ + HeapFree(GetProcessHeap(), 0, zFilename); + return 0; + } + return zFilename; +} +/* int (*xchdir)(const char *) */ +static int WinVfs_chdir(const char *zPath) +{ + void * pConverted; + BOOL rc; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + rc = SetCurrentDirectoryW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + return rc ? JX9_OK : -1; +} +/* int (*xGetcwd)(jx9_context *) */ +static int WinVfs_getcwd(jx9_context *pCtx) +{ + WCHAR zDir[2048]; + char *zConverted; + DWORD rc; + /* Get the current directory */ + rc = GetCurrentDirectoryW(sizeof(zDir), zDir); + if( rc < 1 ){ + return -1; + } + zConverted = jx9unicodeToUtf8(zDir); + if( zConverted == 0 ){ + return -1; + } + jx9_result_string(pCtx, zConverted, -1/*Compute length automatically*/); /* Will make it's own copy */ + HeapFree(GetProcessHeap(), 0, zConverted); + return JX9_OK; +} +/* int (*xMkdir)(const char *, int, int) */ +static int WinVfs_mkdir(const char *zPath, int mode, int recursive) +{ + void * pConverted; + BOOL rc; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + mode= 0; /* MSVC warning */ + recursive = 0; + rc = CreateDirectoryW((LPCWSTR)pConverted, 0); + HeapFree(GetProcessHeap(), 0, pConverted); + return rc ? JX9_OK : -1; +} +/* int (*xRmdir)(const char *) */ +static int WinVfs_rmdir(const char *zPath) +{ + void * pConverted; + BOOL rc; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + rc = RemoveDirectoryW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + return rc ? JX9_OK : -1; +} +/* int (*xIsdir)(const char *) */ +static int WinVfs_isdir(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return (dwAttr & FILE_ATTRIBUTE_DIRECTORY) ? JX9_OK : -1; +} +/* int (*xRename)(const char *, const char *) */ +static int WinVfs_Rename(const char *zOld, const char *zNew) +{ + void *pOld, *pNew; + BOOL rc = 0; + pOld = jx9convertUtf8Filename(zOld); + if( pOld == 0 ){ + return -1; + } + pNew = jx9convertUtf8Filename(zNew); + if( pNew ){ + rc = MoveFileW((LPCWSTR)pOld, (LPCWSTR)pNew); + } + HeapFree(GetProcessHeap(), 0, pOld); + if( pNew ){ + HeapFree(GetProcessHeap(), 0, pNew); + } + return rc ? JX9_OK : - 1; +} +/* int (*xRealpath)(const char *, jx9_context *) */ +static int WinVfs_Realpath(const char *zPath, jx9_context *pCtx) +{ + WCHAR zTemp[2048]; + void *pPath; + char *zReal; + DWORD n; + pPath = jx9convertUtf8Filename(zPath); + if( pPath == 0 ){ + return -1; + } + n = GetFullPathNameW((LPCWSTR)pPath, 0, 0, 0); + if( n > 0 ){ + if( n >= sizeof(zTemp) ){ + n = sizeof(zTemp) - 1; + } + GetFullPathNameW((LPCWSTR)pPath, n, zTemp, 0); + } + HeapFree(GetProcessHeap(), 0, pPath); + if( !n ){ + return -1; + } + zReal = jx9unicodeToUtf8(zTemp); + if( zReal == 0 ){ + return -1; + } + jx9_result_string(pCtx, zReal, -1); /* Will make it's own copy */ + HeapFree(GetProcessHeap(), 0, zReal); + return JX9_OK; +} +/* int (*xSleep)(unsigned int) */ +static int WinVfs_Sleep(unsigned int uSec) +{ + Sleep(uSec/1000/*uSec per Millisec */); + return JX9_OK; +} +/* int (*xUnlink)(const char *) */ +static int WinVfs_unlink(const char *zPath) +{ + void * pConverted; + BOOL rc; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + rc = DeleteFileW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + return rc ? JX9_OK : - 1; +} +/* jx9_int64 (*xFreeSpace)(const char *) */ +static jx9_int64 WinVfs_DiskFreeSpace(const char *zPath) +{ +#ifdef _WIN32_WCE + /* GetDiskFreeSpace is not supported under WINCE */ + SXUNUSED(zPath); + return 0; +#else + DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters; + void * pConverted; + WCHAR *p; + BOOL rc; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return 0; + } + p = (WCHAR *)pConverted; + for(;*p;p++){ + if( *p == '\\' || *p == '/'){ + *p = '\0'; + break; + } + } + rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters); + if( !rc ){ + return 0; + } + return (jx9_int64)dwFreeClusters * dwSectPerClust * dwBytesPerSect; +#endif +} +/* jx9_int64 (*xTotalSpace)(const char *) */ +static jx9_int64 WinVfs_DiskTotalSpace(const char *zPath) +{ +#ifdef _WIN32_WCE + /* GetDiskFreeSpace is not supported under WINCE */ + SXUNUSED(zPath); + return 0; +#else + DWORD dwSectPerClust, dwBytesPerSect, dwFreeClusters, dwTotalClusters; + void * pConverted; + WCHAR *p; + BOOL rc; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return 0; + } + p = (WCHAR *)pConverted; + for(;*p;p++){ + if( *p == '\\' || *p == '/'){ + *p = '\0'; + break; + } + } + rc = GetDiskFreeSpaceW((LPCWSTR)pConverted, &dwSectPerClust, &dwBytesPerSect, &dwFreeClusters, &dwTotalClusters); + if( !rc ){ + return 0; + } + return (jx9_int64)dwTotalClusters * dwSectPerClust * dwBytesPerSect; +#endif +} +/* int (*xFileExists)(const char *) */ +static int WinVfs_FileExists(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return JX9_OK; +} +/* Open a file in a read-only mode */ +static HANDLE OpenReadOnly(LPCWSTR pPath) +{ + DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; + DWORD dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; + DWORD dwAccess = GENERIC_READ; + DWORD dwCreate = OPEN_EXISTING; + HANDLE pHandle; + pHandle = CreateFileW(pPath, dwAccess, dwShare, 0, dwCreate, dwType, 0); + if( pHandle == INVALID_HANDLE_VALUE){ + return 0; + } + return pHandle; +} +/* jx9_int64 (*xFileSize)(const char *) */ +static jx9_int64 WinVfs_FileSize(const char *zPath) +{ + DWORD dwLow, dwHigh; + void * pConverted; + jx9_int64 nSize; + HANDLE pHandle; + + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( pHandle ){ + dwLow = GetFileSize(pHandle, &dwHigh); + nSize = dwHigh; + nSize <<= 32; + nSize += dwLow; + CloseHandle(pHandle); + }else{ + nSize = -1; + } + return nSize; +} +#define TICKS_PER_SECOND 10000000 +#define EPOCH_DIFFERENCE 11644473600LL +/* Convert Windows timestamp to UNIX timestamp */ +static jx9_int64 convertWindowsTimeToUnixTime(LPFILETIME pTime) +{ + jx9_int64 input, temp; + input = pTime->dwHighDateTime; + input <<= 32; + input += pTime->dwLowDateTime; + temp = input / TICKS_PER_SECOND; /*convert from 100ns intervals to seconds*/ + temp = temp - EPOCH_DIFFERENCE; /*subtract number of seconds between epochs*/ + return temp; +} +/* Convert UNIX timestamp to Windows timestamp */ +static void convertUnixTimeToWindowsTime(jx9_int64 nUnixtime, LPFILETIME pOut) +{ + jx9_int64 result = EPOCH_DIFFERENCE; + result += nUnixtime; + result *= 10000000LL; + pOut->dwHighDateTime = (DWORD)(nUnixtime>>32); + pOut->dwLowDateTime = (DWORD)nUnixtime; +} +/* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ +static int WinVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time) +{ + FILETIME sTouch, sAccess; + void *pConverted; + void *pHandle; + BOOL rc = 0; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + if( touch_time < 0 ){ + GetSystemTimeAsFileTime(&sTouch); + }else{ + convertUnixTimeToWindowsTime(touch_time, &sTouch); + } + if( access_time < 0 ){ + /* Use the touch time */ + sAccess = sTouch; /* Structure assignment */ + }else{ + convertUnixTimeToWindowsTime(access_time, &sAccess); + } + rc = SetFileTime(pHandle, &sTouch, &sAccess, 0); + /* Close the handle */ + CloseHandle(pHandle); + } + HeapFree(GetProcessHeap(), 0, pConverted); + return rc ? JX9_OK : -1; +} +/* jx9_int64 (*xFileAtime)(const char *) */ +static jx9_int64 WinVfs_FileAtime(const char *zPath) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void * pConverted; + jx9_int64 atime; + HANDLE pHandle; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + BOOL rc; + rc = GetFileInformationByHandle(pHandle, &sInfo); + if( rc ){ + atime = convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime); + }else{ + atime = -1; + } + CloseHandle(pHandle); + }else{ + atime = -1; + } + HeapFree(GetProcessHeap(), 0, pConverted); + return atime; +} +/* jx9_int64 (*xFileMtime)(const char *) */ +static jx9_int64 WinVfs_FileMtime(const char *zPath) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void * pConverted; + jx9_int64 mtime; + HANDLE pHandle; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + BOOL rc; + rc = GetFileInformationByHandle(pHandle, &sInfo); + if( rc ){ + mtime = convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime); + }else{ + mtime = -1; + } + CloseHandle(pHandle); + }else{ + mtime = -1; + } + HeapFree(GetProcessHeap(), 0, pConverted); + return mtime; +} +/* jx9_int64 (*xFileCtime)(const char *) */ +static jx9_int64 WinVfs_FileCtime(const char *zPath) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void * pConverted; + jx9_int64 ctime; + HANDLE pHandle; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + if( pHandle ){ + BOOL rc; + rc = GetFileInformationByHandle(pHandle, &sInfo); + if( rc ){ + ctime = convertWindowsTimeToUnixTime(&sInfo.ftCreationTime); + }else{ + ctime = -1; + } + CloseHandle(pHandle); + }else{ + ctime = -1; + } + HeapFree(GetProcessHeap(), 0, pConverted); + return ctime; +} +/* int (*xStat)(const char *, jx9_value *, jx9_value *) */ +/* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ +static int WinVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + void *pConverted; + HANDLE pHandle; + BOOL rc; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Open the file in read-only mode */ + pHandle = OpenReadOnly((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( pHandle == 0 ){ + return -1; + } + rc = GetFileInformationByHandle(pHandle, &sInfo); + CloseHandle(pHandle); + if( !rc ){ + return -1; + } + /* dev */ + jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber); + jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ + /* ino */ + jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); + jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ + /* mode */ + jx9_value_int(pWorker, 0); + jx9_array_add_strkey_elem(pArray, "mode", pWorker); + /* nlink */ + jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks); + jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ + /* uid, gid, rdev */ + jx9_value_int(pWorker, 0); + jx9_array_add_strkey_elem(pArray, "uid", pWorker); + jx9_array_add_strkey_elem(pArray, "gid", pWorker); + jx9_array_add_strkey_elem(pArray, "rdev", pWorker); + /* size */ + jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); + jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ + /* atime */ + jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); + jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ + /* mtime */ + jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); + jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ + /* ctime */ + jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); + jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ + /* blksize, blocks */ + jx9_value_int(pWorker, 0); + jx9_array_add_strkey_elem(pArray, "blksize", pWorker); + jx9_array_add_strkey_elem(pArray, "blocks", pWorker); + return JX9_OK; +} +/* int (*xIsfile)(const char *) */ +static int WinVfs_isfile(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return (dwAttr & (FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE)) ? JX9_OK : -1; +} +/* int (*xIslink)(const char *) */ +static int WinVfs_islink(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + return (dwAttr & FILE_ATTRIBUTE_REPARSE_POINT) ? JX9_OK : -1; +} +/* int (*xWritable)(const char *) */ +static int WinVfs_iswritable(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + if( (dwAttr & (FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL)) == 0 ){ + /* Not a regular file */ + return -1; + } + if( dwAttr & FILE_ATTRIBUTE_READONLY ){ + /* Read-only file */ + return -1; + } + /* File is writable */ + return JX9_OK; +} +/* int (*xExecutable)(const char *) */ +static int WinVfs_isexecutable(const char *zPath) +{ + void * pConverted; + DWORD dwAttr; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + return -1; + } + if( (dwAttr & FILE_ATTRIBUTE_NORMAL) == 0 ){ + /* Not a regular file */ + return -1; + } + /* File is executable */ + return JX9_OK; +} +/* int (*xFiletype)(const char *, jx9_context *) */ +static int WinVfs_Filetype(const char *zPath, jx9_context *pCtx) +{ + void * pConverted; + DWORD dwAttr; + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + /* Expand 'unknown' */ + jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); + return -1; + } + dwAttr = GetFileAttributesW((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( dwAttr == INVALID_FILE_ATTRIBUTES ){ + /* Expand 'unknown' */ + jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); + return -1; + } + if(dwAttr & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_ARCHIVE) ){ + jx9_result_string(pCtx, "file", sizeof("file")-1); + }else if(dwAttr & FILE_ATTRIBUTE_DIRECTORY){ + jx9_result_string(pCtx, "dir", sizeof("dir")-1); + }else if(dwAttr & FILE_ATTRIBUTE_REPARSE_POINT){ + jx9_result_string(pCtx, "link", sizeof("link")-1); + }else if(dwAttr & (FILE_ATTRIBUTE_DEVICE)){ + jx9_result_string(pCtx, "block", sizeof("block")-1); + }else{ + jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); + } + return JX9_OK; +} +/* int (*xGetenv)(const char *, jx9_context *) */ +static int WinVfs_Getenv(const char *zVar, jx9_context *pCtx) +{ + char zValue[1024]; + DWORD n; + /* + * According to MSDN + * If lpBuffer is not large enough to hold the data, the return + * value is the buffer size, in characters, required to hold the + * string and its terminating null character and the contents + * of lpBuffer are undefined. + */ + n = sizeof(zValue); + SyMemcpy("Undefined", zValue, sizeof("Undefined")-1); + /* Extract the environment value */ + n = GetEnvironmentVariableA(zVar, zValue, sizeof(zValue)); + if( !n ){ + /* No such variable*/ + return -1; + } + jx9_result_string(pCtx, zValue, (int)n); + return JX9_OK; +} +/* int (*xSetenv)(const char *, const char *) */ +static int WinVfs_Setenv(const char *zName, const char *zValue) +{ + BOOL rc; + rc = SetEnvironmentVariableA(zName, zValue); + return rc ? JX9_OK : -1; +} +/* int (*xMmap)(const char *, void **, jx9_int64 *) */ +static int WinVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize) +{ + DWORD dwSizeLow, dwSizeHigh; + HANDLE pHandle, pMapHandle; + void *pConverted, *pView; + + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + pHandle = OpenReadOnly((LPCWSTR)pConverted); + HeapFree(GetProcessHeap(), 0, pConverted); + if( pHandle == 0 ){ + return -1; + } + /* Get the file size */ + dwSizeLow = GetFileSize(pHandle, &dwSizeHigh); + /* Create the mapping */ + pMapHandle = CreateFileMappingW(pHandle, 0, PAGE_READONLY, dwSizeHigh, dwSizeLow, 0); + if( pMapHandle == 0 ){ + CloseHandle(pHandle); + return -1; + } + *pSize = ((jx9_int64)dwSizeHigh << 32) | dwSizeLow; + /* Obtain the view */ + pView = MapViewOfFile(pMapHandle, FILE_MAP_READ, 0, 0, (SIZE_T)(*pSize)); + if( pView ){ + /* Let the upper layer point to the view */ + *ppMap = pView; + } + /* Close the handle + * According to MSDN it's OK the close the HANDLES. + */ + CloseHandle(pMapHandle); + CloseHandle(pHandle); + return pView ? JX9_OK : -1; +} +/* void (*xUnmap)(void *, jx9_int64) */ +static void WinVfs_Unmap(void *pView, jx9_int64 nSize) +{ + nSize = 0; /* Compiler warning */ + UnmapViewOfFile(pView); +} +/* void (*xTempDir)(jx9_context *) */ +static void WinVfs_TempDir(jx9_context *pCtx) +{ + CHAR zTemp[1024]; + DWORD n; + n = GetTempPathA(sizeof(zTemp), zTemp); + if( n < 1 ){ + /* Assume the default windows temp directory */ + jx9_result_string(pCtx, "C:\\Windows\\Temp", -1/*Compute length automatically*/); + }else{ + jx9_result_string(pCtx, zTemp, (int)n); + } +} +/* unsigned int (*xProcessId)(void) */ +static unsigned int WinVfs_ProcessId(void) +{ + DWORD nID = 0; +#ifndef __MINGW32__ + nID = GetProcessId(GetCurrentProcess()); +#endif /* __MINGW32__ */ + return (unsigned int)nID; +} + +/* Export the windows vfs */ +static const jx9_vfs sWinVfs = { + "Windows_vfs", + JX9_VFS_VERSION, + WinVfs_chdir, /* int (*xChdir)(const char *) */ + 0, /* int (*xChroot)(const char *); */ + WinVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */ + WinVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */ + WinVfs_rmdir, /* int (*xRmdir)(const char *) */ + WinVfs_isdir, /* int (*xIsdir)(const char *) */ + WinVfs_Rename, /* int (*xRename)(const char *, const char *) */ + WinVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/ + WinVfs_Sleep, /* int (*xSleep)(unsigned int) */ + WinVfs_unlink, /* int (*xUnlink)(const char *) */ + WinVfs_FileExists, /* int (*xFileExists)(const char *) */ + 0, /*int (*xChmod)(const char *, int)*/ + 0, /*int (*xChown)(const char *, const char *)*/ + 0, /*int (*xChgrp)(const char *, const char *)*/ + WinVfs_DiskFreeSpace, /* jx9_int64 (*xFreeSpace)(const char *) */ + WinVfs_DiskTotalSpace, /* jx9_int64 (*xTotalSpace)(const char *) */ + WinVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */ + WinVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */ + WinVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */ + WinVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */ + WinVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ + WinVfs_Stat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ + WinVfs_isfile, /* int (*xIsfile)(const char *) */ + WinVfs_islink, /* int (*xIslink)(const char *) */ + WinVfs_isfile, /* int (*xReadable)(const char *) */ + WinVfs_iswritable, /* int (*xWritable)(const char *) */ + WinVfs_isexecutable, /* int (*xExecutable)(const char *) */ + WinVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */ + WinVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */ + WinVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */ + WinVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ + WinVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ + WinVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */ + 0, /* int (*xLink)(const char *, const char *, int) */ + 0, /* int (*xUmask)(int) */ + WinVfs_TempDir, /* void (*xTempDir)(jx9_context *) */ + WinVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ + 0, /* int (*xUid)(void) */ + 0, /* int (*xGid)(void) */ + 0, /* void (*xUsername)(jx9_context *) */ + 0 /* int (*xExec)(const char *, jx9_context *) */ +}; +/* Windows file IO */ +#ifndef INVALID_SET_FILE_POINTER +# define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif +/* int (*xOpen)(const char *, int, jx9_value *, void **) */ +static int WinFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle) +{ + DWORD dwType = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS; + DWORD dwAccess = GENERIC_READ; + DWORD dwShare, dwCreate; + void *pConverted; + HANDLE pHandle; + + pConverted = jx9convertUtf8Filename(zPath); + if( pConverted == 0 ){ + return -1; + } + /* Set the desired flags according to the open mode */ + if( iOpenMode & JX9_IO_OPEN_CREATE ){ + /* Open existing file, or create if it doesn't exist */ + dwCreate = OPEN_ALWAYS; + if( iOpenMode & JX9_IO_OPEN_TRUNC ){ + /* If the specified file exists and is writable, the function overwrites the file */ + dwCreate = CREATE_ALWAYS; + } + }else if( iOpenMode & JX9_IO_OPEN_EXCL ){ + /* Creates a new file, only if it does not already exist. + * If the file exists, it fails. + */ + dwCreate = CREATE_NEW; + }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){ + /* Opens a file and truncates it so that its size is zero bytes + * The file must exist. + */ + dwCreate = TRUNCATE_EXISTING; + }else{ + /* Opens a file, only if it exists. */ + dwCreate = OPEN_EXISTING; + } + if( iOpenMode & JX9_IO_OPEN_RDWR ){ + /* Read+Write access */ + dwAccess |= GENERIC_WRITE; + }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){ + /* Write only access */ + dwAccess = GENERIC_WRITE; + } + if( iOpenMode & JX9_IO_OPEN_APPEND ){ + /* Append mode */ + dwAccess = FILE_APPEND_DATA; + } + if( iOpenMode & JX9_IO_OPEN_TEMP ){ + /* File is temporary */ + dwType = FILE_ATTRIBUTE_TEMPORARY; + } + dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE; + pHandle = CreateFileW((LPCWSTR)pConverted, dwAccess, dwShare, 0, dwCreate, dwType, 0); + HeapFree(GetProcessHeap(), 0, pConverted); + if( pHandle == INVALID_HANDLE_VALUE){ + SXUNUSED(pResource); /* MSVC warning */ + return -1; + } + /* Make the handle accessible to the upper layer */ + *ppHandle = (void *)pHandle; + return JX9_OK; +} +/* An instance of the following structure is used to record state information + * while iterating throw directory entries. + */ +typedef struct WinDir_Info WinDir_Info; +struct WinDir_Info +{ + HANDLE pDirHandle; + void *pPath; + WIN32_FIND_DATAW sInfo; + int rc; +}; +/* int (*xOpenDir)(const char *, jx9_value *, void **) */ +static int WinDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle) +{ + WinDir_Info *pDirInfo; + void *pConverted; + char *zPrep; + sxu32 n; + /* Prepare the path */ + n = SyStrlen(zPath); + zPrep = (char *)HeapAlloc(GetProcessHeap(), 0, n+sizeof("\\*")+4); + if( zPrep == 0 ){ + return -1; + } + SyMemcpy((const void *)zPath, zPrep, n); + zPrep[n] = '\\'; + zPrep[n+1] = '*'; + zPrep[n+2] = 0; + pConverted = jx9convertUtf8Filename(zPrep); + HeapFree(GetProcessHeap(), 0, zPrep); + if( pConverted == 0 ){ + return -1; + } + /* Allocate a new instance */ + pDirInfo = (WinDir_Info *)HeapAlloc(GetProcessHeap(), 0, sizeof(WinDir_Info)); + if( pDirInfo == 0 ){ + pResource = 0; /* Compiler warning */ + return -1; + } + pDirInfo->rc = SXRET_OK; + pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pConverted, &pDirInfo->sInfo); + if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ + /* Cannot open directory */ + HeapFree(GetProcessHeap(), 0, pConverted); + HeapFree(GetProcessHeap(), 0, pDirInfo); + return -1; + } + /* Save the path */ + pDirInfo->pPath = pConverted; + /* Save our structure */ + *ppHandle = pDirInfo; + return JX9_OK; +} +/* void (*xCloseDir)(void *) */ +static void WinDir_Close(void *pUserData) +{ + WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; + if( pDirInfo->pDirHandle != INVALID_HANDLE_VALUE ){ + FindClose(pDirInfo->pDirHandle); + } + HeapFree(GetProcessHeap(), 0, pDirInfo->pPath); + HeapFree(GetProcessHeap(), 0, pDirInfo); +} +/* void (*xClose)(void *); */ +static void WinFile_Close(void *pUserData) +{ + HANDLE pHandle = (HANDLE)pUserData; + CloseHandle(pHandle); +} +/* int (*xReadDir)(void *, jx9_context *) */ +static int WinDir_Read(void *pUserData, jx9_context *pCtx) +{ + WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; + LPWIN32_FIND_DATAW pData; + char *zName; + BOOL rc; + sxu32 n; + if( pDirInfo->rc != SXRET_OK ){ + /* No more entry to process */ + return -1; + } + pData = &pDirInfo->sInfo; + for(;;){ + zName = jx9unicodeToUtf8(pData->cFileName); + if( zName == 0 ){ + /* Out of memory */ + return -1; + } + n = SyStrlen(zName); + /* Ignore '.' && '..' */ + if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ + break; + } + HeapFree(GetProcessHeap(), 0, zName); + rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo); + if( !rc ){ + return -1; + } + } + /* Return the current file name */ + jx9_result_string(pCtx, zName, -1); + HeapFree(GetProcessHeap(), 0, zName); + /* Point to the next entry */ + rc = FindNextFileW(pDirInfo->pDirHandle, &pDirInfo->sInfo); + if( !rc ){ + pDirInfo->rc = SXERR_EOF; + } + return JX9_OK; +} +/* void (*xRewindDir)(void *) */ +static void WinDir_RewindDir(void *pUserData) +{ + WinDir_Info *pDirInfo = (WinDir_Info *)pUserData; + FindClose(pDirInfo->pDirHandle); + pDirInfo->pDirHandle = FindFirstFileW((LPCWSTR)pDirInfo->pPath, &pDirInfo->sInfo); + if( pDirInfo->pDirHandle == INVALID_HANDLE_VALUE ){ + pDirInfo->rc = SXERR_EOF; + }else{ + pDirInfo->rc = SXRET_OK; + } +} +/* jx9_int64 (*xRead)(void *, void *, jx9_int64); */ +static jx9_int64 WinFile_Read(void *pOS, void *pBuffer, jx9_int64 nDatatoRead) +{ + HANDLE pHandle = (HANDLE)pOS; + DWORD nRd; + BOOL rc; + rc = ReadFile(pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0); + if( !rc ){ + /* EOF or IO error */ + return -1; + } + return (jx9_int64)nRd; +} +/* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */ +static jx9_int64 WinFile_Write(void *pOS, const void *pBuffer, jx9_int64 nWrite) +{ + const char *zData = (const char *)pBuffer; + HANDLE pHandle = (HANDLE)pOS; + jx9_int64 nCount; + DWORD nWr; + BOOL rc; + nWr = 0; + nCount = 0; + for(;;){ + if( nWrite < 1 ){ + break; + } + rc = WriteFile(pHandle, zData, (DWORD)nWrite, &nWr, 0); + if( !rc ){ + /* IO error */ + break; + } + nWrite -= nWr; + nCount += nWr; + zData += nWr; + } + if( nWrite > 0 ){ + return -1; + } + return nCount; +} +/* int (*xSeek)(void *, jx9_int64, int) */ +static int WinFile_Seek(void *pUserData, jx9_int64 iOfft, int whence) +{ + HANDLE pHandle = (HANDLE)pUserData; + DWORD dwMove, dwNew; + LONG nHighOfft; + switch(whence){ + case 1:/*SEEK_CUR*/ + dwMove = FILE_CURRENT; + break; + case 2: /* SEEK_END */ + dwMove = FILE_END; + break; + case 0: /* SEEK_SET */ + default: + dwMove = FILE_BEGIN; + break; + } + nHighOfft = (LONG)(iOfft >> 32); + dwNew = SetFilePointer(pHandle, (LONG)iOfft, &nHighOfft, dwMove); + if( dwNew == INVALID_SET_FILE_POINTER ){ + return -1; + } + return JX9_OK; +} +/* int (*xLock)(void *, int) */ +static int WinFile_Lock(void *pUserData, int lock_type) +{ + HANDLE pHandle = (HANDLE)pUserData; + static DWORD dwLo = 0, dwHi = 0; /* xx: MT-SAFE */ + OVERLAPPED sDummy; + BOOL rc; + SyZero(&sDummy, sizeof(sDummy)); + /* Get the file size */ + if( lock_type < 1 ){ + /* Unlock the file */ + rc = UnlockFileEx(pHandle, 0, dwLo, dwHi, &sDummy); + }else{ + DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; /* Shared non-blocking lock by default*/ + /* Lock the file */ + if( lock_type == 1 /* LOCK_EXCL */ ){ + dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; + } + dwLo = GetFileSize(pHandle, &dwHi); + rc = LockFileEx(pHandle, dwFlags, 0, dwLo, dwHi, &sDummy); + } + return rc ? JX9_OK : -1 /* Lock error */; +} +/* jx9_int64 (*xTell)(void *) */ +static jx9_int64 WinFile_Tell(void *pUserData) +{ + HANDLE pHandle = (HANDLE)pUserData; + DWORD dwNew; + dwNew = SetFilePointer(pHandle, 0, 0, FILE_CURRENT/* SEEK_CUR */); + if( dwNew == INVALID_SET_FILE_POINTER ){ + return -1; + } + return (jx9_int64)dwNew; +} +/* int (*xTrunc)(void *, jx9_int64) */ +static int WinFile_Trunc(void *pUserData, jx9_int64 nOfft) +{ + HANDLE pHandle = (HANDLE)pUserData; + LONG HighOfft; + DWORD dwNew; + BOOL rc; + HighOfft = (LONG)(nOfft >> 32); + dwNew = SetFilePointer(pHandle, (LONG)nOfft, &HighOfft, FILE_BEGIN); + if( dwNew == INVALID_SET_FILE_POINTER ){ + return -1; + } + rc = SetEndOfFile(pHandle); + return rc ? JX9_OK : -1; +} +/* int (*xSync)(void *); */ +static int WinFile_Sync(void *pUserData) +{ + HANDLE pHandle = (HANDLE)pUserData; + BOOL rc; + rc = FlushFileBuffers(pHandle); + return rc ? JX9_OK : - 1; +} +/* int (*xStat)(void *, jx9_value *, jx9_value *) */ +static int WinFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker) +{ + BY_HANDLE_FILE_INFORMATION sInfo; + HANDLE pHandle = (HANDLE)pUserData; + BOOL rc; + rc = GetFileInformationByHandle(pHandle, &sInfo); + if( !rc ){ + return -1; + } + /* dev */ + jx9_value_int64(pWorker, (jx9_int64)sInfo.dwVolumeSerialNumber); + jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ + /* ino */ + jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileIndexHigh << 32) | sInfo.nFileIndexLow)); + jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ + /* mode */ + jx9_value_int(pWorker, 0); + jx9_array_add_strkey_elem(pArray, "mode", pWorker); + /* nlink */ + jx9_value_int(pWorker, (int)sInfo.nNumberOfLinks); + jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ + /* uid, gid, rdev */ + jx9_value_int(pWorker, 0); + jx9_array_add_strkey_elem(pArray, "uid", pWorker); + jx9_array_add_strkey_elem(pArray, "gid", pWorker); + jx9_array_add_strkey_elem(pArray, "rdev", pWorker); + /* size */ + jx9_value_int64(pWorker, (jx9_int64)(((jx9_int64)sInfo.nFileSizeHigh << 32) | sInfo.nFileSizeLow)); + jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ + /* atime */ + jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastAccessTime)); + jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ + /* mtime */ + jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftLastWriteTime)); + jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ + /* ctime */ + jx9_value_int64(pWorker, convertWindowsTimeToUnixTime(&sInfo.ftCreationTime)); + jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ + /* blksize, blocks */ + jx9_value_int(pWorker, 0); + jx9_array_add_strkey_elem(pArray, "blksize", pWorker); + jx9_array_add_strkey_elem(pArray, "blocks", pWorker); + return JX9_OK; +} +/* Export the file:// stream */ +static const jx9_io_stream sWinFileStream = { + "file", /* Stream name */ + JX9_IO_STREAM_VERSION, + WinFile_Open, /* xOpen */ + WinDir_Open, /* xOpenDir */ + WinFile_Close, /* xClose */ + WinDir_Close, /* xCloseDir */ + WinFile_Read, /* xRead */ + WinDir_Read, /* xReadDir */ + WinFile_Write, /* xWrite */ + WinFile_Seek, /* xSeek */ + WinFile_Lock, /* xLock */ + WinDir_RewindDir, /* xRewindDir */ + WinFile_Tell, /* xTell */ + WinFile_Trunc, /* xTrunc */ + WinFile_Sync, /* xSeek */ + WinFile_Stat /* xStat */ +}; +#elif defined(__UNIXES__) +/* + * UNIX VFS implementation for the JX9 engine. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +/* int (*xchdir)(const char *) */ +static int UnixVfs_chdir(const char *zPath) +{ + int rc; + rc = chdir(zPath); + return rc == 0 ? JX9_OK : -1; +} +/* int (*xGetcwd)(jx9_context *) */ +static int UnixVfs_getcwd(jx9_context *pCtx) +{ + char zBuf[4096]; + char *zDir; + /* Get the current directory */ + zDir = getcwd(zBuf, sizeof(zBuf)); + if( zDir == 0 ){ + return -1; + } + jx9_result_string(pCtx, zDir, -1/*Compute length automatically*/); + return JX9_OK; +} +/* int (*xMkdir)(const char *, int, int) */ +static int UnixVfs_mkdir(const char *zPath, int mode, int recursive) +{ + int rc; + rc = mkdir(zPath, mode); + recursive = 0; /* cc warning */ + return rc == 0 ? JX9_OK : -1; +} +/* int (*xRmdir)(const char *) */ +static int UnixVfs_rmdir(const char *zPath) +{ + int rc; + rc = rmdir(zPath); + return rc == 0 ? JX9_OK : -1; +} +/* int (*xIsdir)(const char *) */ +static int UnixVfs_isdir(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + rc = S_ISDIR(st.st_mode); + return rc ? JX9_OK : -1 ; +} +/* int (*xRename)(const char *, const char *) */ +static int UnixVfs_Rename(const char *zOld, const char *zNew) +{ + int rc; + rc = rename(zOld, zNew); + return rc == 0 ? JX9_OK : -1; +} +/* int (*xRealpath)(const char *, jx9_context *) */ +static int UnixVfs_Realpath(const char *zPath, jx9_context *pCtx) +{ +#ifndef JX9_UNIX_OLD_LIBC + char *zReal; + zReal = realpath(zPath, 0); + if( zReal == 0 ){ + return -1; + } + jx9_result_string(pCtx, zReal, -1/*Compute length automatically*/); + /* Release the allocated buffer */ + free(zReal); + return JX9_OK; +#else + zPath = 0; /* cc warning */ + pCtx = 0; + return -1; +#endif +} +/* int (*xSleep)(unsigned int) */ +static int UnixVfs_Sleep(unsigned int uSec) +{ + usleep(uSec); + return JX9_OK; +} +/* int (*xUnlink)(const char *) */ +static int UnixVfs_unlink(const char *zPath) +{ + int rc; + rc = unlink(zPath); + return rc == 0 ? JX9_OK : -1 ; +} +/* int (*xFileExists)(const char *) */ +static int UnixVfs_FileExists(const char *zPath) +{ + int rc; + rc = access(zPath, F_OK); + return rc == 0 ? JX9_OK : -1; +} +/* jx9_int64 (*xFileSize)(const char *) */ +static jx9_int64 UnixVfs_FileSize(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + return (jx9_int64)st.st_size; +} +/* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ +static int UnixVfs_Touch(const char *zPath, jx9_int64 touch_time, jx9_int64 access_time) +{ + struct utimbuf ut; + int rc; + ut.actime = (time_t)access_time; + ut.modtime = (time_t)touch_time; + rc = utime(zPath, &ut); + if( rc != 0 ){ + return -1; + } + return JX9_OK; +} +/* jx9_int64 (*xFileAtime)(const char *) */ +static jx9_int64 UnixVfs_FileAtime(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + return (jx9_int64)st.st_atime; +} +/* jx9_int64 (*xFileMtime)(const char *) */ +static jx9_int64 UnixVfs_FileMtime(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + return (jx9_int64)st.st_mtime; +} +/* jx9_int64 (*xFileCtime)(const char *) */ +static jx9_int64 UnixVfs_FileCtime(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + return (jx9_int64)st.st_ctime; +} +/* int (*xStat)(const char *, jx9_value *, jx9_value *) */ +static int UnixVfs_Stat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + /* dev */ + jx9_value_int64(pWorker, (jx9_int64)st.st_dev); + jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ + /* ino */ + jx9_value_int64(pWorker, (jx9_int64)st.st_ino); + jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ + /* mode */ + jx9_value_int(pWorker, (int)st.st_mode); + jx9_array_add_strkey_elem(pArray, "mode", pWorker); + /* nlink */ + jx9_value_int(pWorker, (int)st.st_nlink); + jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ + /* uid, gid, rdev */ + jx9_value_int(pWorker, (int)st.st_uid); + jx9_array_add_strkey_elem(pArray, "uid", pWorker); + jx9_value_int(pWorker, (int)st.st_gid); + jx9_array_add_strkey_elem(pArray, "gid", pWorker); + jx9_value_int(pWorker, (int)st.st_rdev); + jx9_array_add_strkey_elem(pArray, "rdev", pWorker); + /* size */ + jx9_value_int64(pWorker, (jx9_int64)st.st_size); + jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ + /* atime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_atime); + jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ + /* mtime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); + jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ + /* ctime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); + jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ + /* blksize, blocks */ + jx9_value_int(pWorker, (int)st.st_blksize); + jx9_array_add_strkey_elem(pArray, "blksize", pWorker); + jx9_value_int(pWorker, (int)st.st_blocks); + jx9_array_add_strkey_elem(pArray, "blocks", pWorker); + return JX9_OK; +} +/* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ +static int UnixVfs_lStat(const char *zPath, jx9_value *pArray, jx9_value *pWorker) +{ + struct stat st; + int rc; + rc = lstat(zPath, &st); + if( rc != 0 ){ + return -1; + } + /* dev */ + jx9_value_int64(pWorker, (jx9_int64)st.st_dev); + jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ + /* ino */ + jx9_value_int64(pWorker, (jx9_int64)st.st_ino); + jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ + /* mode */ + jx9_value_int(pWorker, (int)st.st_mode); + jx9_array_add_strkey_elem(pArray, "mode", pWorker); + /* nlink */ + jx9_value_int(pWorker, (int)st.st_nlink); + jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ + /* uid, gid, rdev */ + jx9_value_int(pWorker, (int)st.st_uid); + jx9_array_add_strkey_elem(pArray, "uid", pWorker); + jx9_value_int(pWorker, (int)st.st_gid); + jx9_array_add_strkey_elem(pArray, "gid", pWorker); + jx9_value_int(pWorker, (int)st.st_rdev); + jx9_array_add_strkey_elem(pArray, "rdev", pWorker); + /* size */ + jx9_value_int64(pWorker, (jx9_int64)st.st_size); + jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ + /* atime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_atime); + jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ + /* mtime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); + jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ + /* ctime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); + jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ + /* blksize, blocks */ + jx9_value_int(pWorker, (int)st.st_blksize); + jx9_array_add_strkey_elem(pArray, "blksize", pWorker); + jx9_value_int(pWorker, (int)st.st_blocks); + jx9_array_add_strkey_elem(pArray, "blocks", pWorker); + return JX9_OK; +} +/* int (*xChmod)(const char *, int) */ +static int UnixVfs_Chmod(const char *zPath, int mode) +{ + int rc; + rc = chmod(zPath, (mode_t)mode); + return rc == 0 ? JX9_OK : - 1; +} +/* int (*xChown)(const char *, const char *) */ +static int UnixVfs_Chown(const char *zPath, const char *zUser) +{ +#ifndef JX9_UNIX_STATIC_BUILD + struct passwd *pwd; + uid_t uid; + int rc; + pwd = getpwnam(zUser); /* Try getting UID for username */ + if (pwd == 0) { + return -1; + } + uid = pwd->pw_uid; + rc = chown(zPath, uid, -1); + return rc == 0 ? JX9_OK : -1; +#else + SXUNUSED(zPath); + SXUNUSED(zUser); + return -1; +#endif /* JX9_UNIX_STATIC_BUILD */ +} +/* int (*xChgrp)(const char *, const char *) */ +static int UnixVfs_Chgrp(const char *zPath, const char *zGroup) +{ +#ifndef JX9_UNIX_STATIC_BUILD + struct group *group; + gid_t gid; + int rc; + group = getgrnam(zGroup); + if (group == 0) { + return -1; + } + gid = group->gr_gid; + rc = chown(zPath, -1, gid); + return rc == 0 ? JX9_OK : -1; +#else + SXUNUSED(zPath); + SXUNUSED(zGroup); + return -1; +#endif /* JX9_UNIX_STATIC_BUILD */ +} +/* int (*xIsfile)(const char *) */ +static int UnixVfs_isfile(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + rc = S_ISREG(st.st_mode); + return rc ? JX9_OK : -1 ; +} +/* int (*xIslink)(const char *) */ +static int UnixVfs_islink(const char *zPath) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + return -1; + } + rc = S_ISLNK(st.st_mode); + return rc ? JX9_OK : -1 ; +} +/* int (*xReadable)(const char *) */ +static int UnixVfs_isreadable(const char *zPath) +{ + int rc; + rc = access(zPath, R_OK); + return rc == 0 ? JX9_OK : -1; +} +/* int (*xWritable)(const char *) */ +static int UnixVfs_iswritable(const char *zPath) +{ + int rc; + rc = access(zPath, W_OK); + return rc == 0 ? JX9_OK : -1; +} +/* int (*xExecutable)(const char *) */ +static int UnixVfs_isexecutable(const char *zPath) +{ + int rc; + rc = access(zPath, X_OK); + return rc == 0 ? JX9_OK : -1; +} +/* int (*xFiletype)(const char *, jx9_context *) */ +static int UnixVfs_Filetype(const char *zPath, jx9_context *pCtx) +{ + struct stat st; + int rc; + rc = stat(zPath, &st); + if( rc != 0 ){ + /* Expand 'unknown' */ + jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); + return -1; + } + if(S_ISREG(st.st_mode) ){ + jx9_result_string(pCtx, "file", sizeof("file")-1); + }else if(S_ISDIR(st.st_mode)){ + jx9_result_string(pCtx, "dir", sizeof("dir")-1); + }else if(S_ISLNK(st.st_mode)){ + jx9_result_string(pCtx, "link", sizeof("link")-1); + }else if(S_ISBLK(st.st_mode)){ + jx9_result_string(pCtx, "block", sizeof("block")-1); + }else if(S_ISSOCK(st.st_mode)){ + jx9_result_string(pCtx, "socket", sizeof("socket")-1); + }else if(S_ISFIFO(st.st_mode)){ + jx9_result_string(pCtx, "fifo", sizeof("fifo")-1); + }else{ + jx9_result_string(pCtx, "unknown", sizeof("unknown")-1); + } + return JX9_OK; +} +/* int (*xGetenv)(const char *, jx9_context *) */ +static int UnixVfs_Getenv(const char *zVar, jx9_context *pCtx) +{ + char *zEnv; + zEnv = getenv(zVar); + if( zEnv == 0 ){ + return -1; + } + jx9_result_string(pCtx, zEnv, -1/*Compute length automatically*/); + return JX9_OK; +} +/* int (*xSetenv)(const char *, const char *) */ +static int UnixVfs_Setenv(const char *zName, const char *zValue) +{ + int rc; + rc = setenv(zName, zValue, 1); + return rc == 0 ? JX9_OK : -1; +} +/* int (*xMmap)(const char *, void **, jx9_int64 *) */ +static int UnixVfs_Mmap(const char *zPath, void **ppMap, jx9_int64 *pSize) +{ + struct stat st; + void *pMap; + int fd; + int rc; + /* Open the file in a read-only mode */ + fd = open(zPath, O_RDONLY); + if( fd < 0 ){ + return -1; + } + /* stat the handle */ + fstat(fd, &st); + /* Obtain a memory view of the whole file */ + pMap = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE|MAP_FILE, fd, 0); + rc = JX9_OK; + if( pMap == MAP_FAILED ){ + rc = -1; + }else{ + /* Point to the memory view */ + *ppMap = pMap; + *pSize = (jx9_int64)st.st_size; + } + close(fd); + return rc; +} +/* void (*xUnmap)(void *, jx9_int64) */ +static void UnixVfs_Unmap(void *pView, jx9_int64 nSize) +{ + munmap(pView, (size_t)nSize); +} +/* void (*xTempDir)(jx9_context *) */ +static void UnixVfs_TempDir(jx9_context *pCtx) +{ + static const char *azDirs[] = { + "/var/tmp", + "/usr/tmp", + "/usr/local/tmp" + }; + unsigned int i; + struct stat buf; + const char *zDir; + zDir = getenv("TMPDIR"); + if( zDir && zDir[0] != 0 && !access(zDir, 07) ){ + jx9_result_string(pCtx, zDir, -1); + return; + } + for(i=0; ipw_name, -1); +#else + jx9_result_string(pCtx, "Unknown", -1); +#endif /* JX9_UNIX_STATIC_BUILD */ + return; +} +/* int (*xLink)(const char *, const char *, int) */ +static int UnixVfs_link(const char *zSrc, const char *zTarget, int is_sym) +{ + int rc; + if( is_sym ){ + /* Symbolic link */ + rc = symlink(zSrc, zTarget); + }else{ + /* Hard link */ + rc = link(zSrc, zTarget); + } + return rc == 0 ? JX9_OK : -1; +} +/* int (*xChroot)(const char *) */ +static int UnixVfs_chroot(const char *zRootDir) +{ + int rc; + rc = chroot(zRootDir); + return rc == 0 ? JX9_OK : -1; +} +/* Export the UNIX vfs */ +static const jx9_vfs sUnixVfs = { + "Unix_vfs", + JX9_VFS_VERSION, + UnixVfs_chdir, /* int (*xChdir)(const char *) */ + UnixVfs_chroot, /* int (*xChroot)(const char *); */ + UnixVfs_getcwd, /* int (*xGetcwd)(jx9_context *) */ + UnixVfs_mkdir, /* int (*xMkdir)(const char *, int, int) */ + UnixVfs_rmdir, /* int (*xRmdir)(const char *) */ + UnixVfs_isdir, /* int (*xIsdir)(const char *) */ + UnixVfs_Rename, /* int (*xRename)(const char *, const char *) */ + UnixVfs_Realpath, /*int (*xRealpath)(const char *, jx9_context *)*/ + UnixVfs_Sleep, /* int (*xSleep)(unsigned int) */ + UnixVfs_unlink, /* int (*xUnlink)(const char *) */ + UnixVfs_FileExists, /* int (*xFileExists)(const char *) */ + UnixVfs_Chmod, /*int (*xChmod)(const char *, int)*/ + UnixVfs_Chown, /*int (*xChown)(const char *, const char *)*/ + UnixVfs_Chgrp, /*int (*xChgrp)(const char *, const char *)*/ + 0, /* jx9_int64 (*xFreeSpace)(const char *) */ + 0, /* jx9_int64 (*xTotalSpace)(const char *) */ + UnixVfs_FileSize, /* jx9_int64 (*xFileSize)(const char *) */ + UnixVfs_FileAtime, /* jx9_int64 (*xFileAtime)(const char *) */ + UnixVfs_FileMtime, /* jx9_int64 (*xFileMtime)(const char *) */ + UnixVfs_FileCtime, /* jx9_int64 (*xFileCtime)(const char *) */ + UnixVfs_Stat, /* int (*xStat)(const char *, jx9_value *, jx9_value *) */ + UnixVfs_lStat, /* int (*xlStat)(const char *, jx9_value *, jx9_value *) */ + UnixVfs_isfile, /* int (*xIsfile)(const char *) */ + UnixVfs_islink, /* int (*xIslink)(const char *) */ + UnixVfs_isreadable, /* int (*xReadable)(const char *) */ + UnixVfs_iswritable, /* int (*xWritable)(const char *) */ + UnixVfs_isexecutable, /* int (*xExecutable)(const char *) */ + UnixVfs_Filetype, /* int (*xFiletype)(const char *, jx9_context *) */ + UnixVfs_Getenv, /* int (*xGetenv)(const char *, jx9_context *) */ + UnixVfs_Setenv, /* int (*xSetenv)(const char *, const char *) */ + UnixVfs_Touch, /* int (*xTouch)(const char *, jx9_int64, jx9_int64) */ + UnixVfs_Mmap, /* int (*xMmap)(const char *, void **, jx9_int64 *) */ + UnixVfs_Unmap, /* void (*xUnmap)(void *, jx9_int64); */ + UnixVfs_link, /* int (*xLink)(const char *, const char *, int) */ + UnixVfs_Umask, /* int (*xUmask)(int) */ + UnixVfs_TempDir, /* void (*xTempDir)(jx9_context *) */ + UnixVfs_ProcessId, /* unsigned int (*xProcessId)(void) */ + UnixVfs_uid, /* int (*xUid)(void) */ + UnixVfs_gid, /* int (*xGid)(void) */ + UnixVfs_Username, /* void (*xUsername)(jx9_context *) */ + 0 /* int (*xExec)(const char *, jx9_context *) */ +}; +/* UNIX File IO */ +#define JX9_UNIX_OPEN_MODE 0640 /* Default open mode */ +/* int (*xOpen)(const char *, int, jx9_value *, void **) */ +static int UnixFile_Open(const char *zPath, int iOpenMode, jx9_value *pResource, void **ppHandle) +{ + int iOpen = O_RDONLY; + int fd; + /* Set the desired flags according to the open mode */ + if( iOpenMode & JX9_IO_OPEN_CREATE ){ + /* Open existing file, or create if it doesn't exist */ + iOpen = O_CREAT; + if( iOpenMode & JX9_IO_OPEN_TRUNC ){ + /* If the specified file exists and is writable, the function overwrites the file */ + iOpen |= O_TRUNC; + SXUNUSED(pResource); /* cc warning */ + } + }else if( iOpenMode & JX9_IO_OPEN_EXCL ){ + /* Creates a new file, only if it does not already exist. + * If the file exists, it fails. + */ + iOpen = O_CREAT|O_EXCL; + }else if( iOpenMode & JX9_IO_OPEN_TRUNC ){ + /* Opens a file and truncates it so that its size is zero bytes + * The file must exist. + */ + iOpen = O_RDWR|O_TRUNC; + } + if( iOpenMode & JX9_IO_OPEN_RDWR ){ + /* Read+Write access */ + iOpen &= ~O_RDONLY; + iOpen |= O_RDWR; + }else if( iOpenMode & JX9_IO_OPEN_WRONLY ){ + /* Write only access */ + iOpen &= ~O_RDONLY; + iOpen |= O_WRONLY; + } + if( iOpenMode & JX9_IO_OPEN_APPEND ){ + /* Append mode */ + iOpen |= O_APPEND; + } +#ifdef O_TEMP + if( iOpenMode & JX9_IO_OPEN_TEMP ){ + /* File is temporary */ + iOpen |= O_TEMP; + } +#endif + /* Open the file now */ + fd = open(zPath, iOpen, JX9_UNIX_OPEN_MODE); + if( fd < 0 ){ + /* IO error */ + return -1; + } + /* Save the handle */ + *ppHandle = SX_INT_TO_PTR(fd); + return JX9_OK; +} +/* int (*xOpenDir)(const char *, jx9_value *, void **) */ +static int UnixDir_Open(const char *zPath, jx9_value *pResource, void **ppHandle) +{ + DIR *pDir; + /* Open the target directory */ + pDir = opendir(zPath); + if( pDir == 0 ){ + pResource = 0; /* Compiler warning */ + return -1; + } + /* Save our structure */ + *ppHandle = pDir; + return JX9_OK; +} +/* void (*xCloseDir)(void *) */ +static void UnixDir_Close(void *pUserData) +{ + closedir((DIR *)pUserData); +} +/* void (*xClose)(void *); */ +static void UnixFile_Close(void *pUserData) +{ + close(SX_PTR_TO_INT(pUserData)); +} +/* int (*xReadDir)(void *, jx9_context *) */ +static int UnixDir_Read(void *pUserData, jx9_context *pCtx) +{ + DIR *pDir = (DIR *)pUserData; + struct dirent *pEntry; + char *zName = 0; /* cc warning */ + sxu32 n = 0; + for(;;){ + pEntry = readdir(pDir); + if( pEntry == 0 ){ + /* No more entries to process */ + return -1; + } + zName = pEntry->d_name; + n = SyStrlen(zName); + /* Ignore '.' && '..' */ + if( n > sizeof("..")-1 || zName[0] != '.' || ( n == sizeof("..")-1 && zName[1] != '.') ){ + break; + } + /* Next entry */ + } + /* Return the current file name */ + jx9_result_string(pCtx, zName, (int)n); + return JX9_OK; +} +/* void (*xRewindDir)(void *) */ +static void UnixDir_Rewind(void *pUserData) +{ + rewinddir((DIR *)pUserData); +} +/* jx9_int64 (*xRead)(void *, void *, jx9_int64); */ +static jx9_int64 UnixFile_Read(void *pUserData, void *pBuffer, jx9_int64 nDatatoRead) +{ + ssize_t nRd; + nRd = read(SX_PTR_TO_INT(pUserData), pBuffer, (size_t)nDatatoRead); + if( nRd < 1 ){ + /* EOF or IO error */ + return -1; + } + return (jx9_int64)nRd; +} +/* jx9_int64 (*xWrite)(void *, const void *, jx9_int64); */ +static jx9_int64 UnixFile_Write(void *pUserData, const void *pBuffer, jx9_int64 nWrite) +{ + const char *zData = (const char *)pBuffer; + int fd = SX_PTR_TO_INT(pUserData); + jx9_int64 nCount; + ssize_t nWr; + nCount = 0; + for(;;){ + if( nWrite < 1 ){ + break; + } + nWr = write(fd, zData, (size_t)nWrite); + if( nWr < 1 ){ + /* IO error */ + break; + } + nWrite -= nWr; + nCount += nWr; + zData += nWr; + } + if( nWrite > 0 ){ + return -1; + } + return nCount; +} +/* int (*xSeek)(void *, jx9_int64, int) */ +static int UnixFile_Seek(void *pUserData, jx9_int64 iOfft, int whence) +{ + off_t iNew; + switch(whence){ + case 1:/*SEEK_CUR*/ + whence = SEEK_CUR; + break; + case 2: /* SEEK_END */ + whence = SEEK_END; + break; + case 0: /* SEEK_SET */ + default: + whence = SEEK_SET; + break; + } + iNew = lseek(SX_PTR_TO_INT(pUserData), (off_t)iOfft, whence); + if( iNew < 0 ){ + return -1; + } + return JX9_OK; +} +/* int (*xLock)(void *, int) */ +static int UnixFile_Lock(void *pUserData, int lock_type) +{ + int fd = SX_PTR_TO_INT(pUserData); + int rc = JX9_OK; /* cc warning */ + if( lock_type < 0 ){ + /* Unlock the file */ + rc = flock(fd, LOCK_UN); + }else{ + if( lock_type == 1 ){ + /* Exculsive lock */ + rc = flock(fd, LOCK_EX); + }else{ + /* Shared lock */ + rc = flock(fd, LOCK_SH); + } + } + return !rc ? JX9_OK : -1; +} +/* jx9_int64 (*xTell)(void *) */ +static jx9_int64 UnixFile_Tell(void *pUserData) +{ + off_t iNew; + iNew = lseek(SX_PTR_TO_INT(pUserData), 0, SEEK_CUR); + return (jx9_int64)iNew; +} +/* int (*xTrunc)(void *, jx9_int64) */ +static int UnixFile_Trunc(void *pUserData, jx9_int64 nOfft) +{ + int rc; + rc = ftruncate(SX_PTR_TO_INT(pUserData), (off_t)nOfft); + if( rc != 0 ){ + return -1; + } + return JX9_OK; +} +/* int (*xSync)(void *); */ +static int UnixFile_Sync(void *pUserData) +{ + int rc; + rc = fsync(SX_PTR_TO_INT(pUserData)); + return rc == 0 ? JX9_OK : - 1; +} +/* int (*xStat)(void *, jx9_value *, jx9_value *) */ +static int UnixFile_Stat(void *pUserData, jx9_value *pArray, jx9_value *pWorker) +{ + struct stat st; + int rc; + rc = fstat(SX_PTR_TO_INT(pUserData), &st); + if( rc != 0 ){ + return -1; + } + /* dev */ + jx9_value_int64(pWorker, (jx9_int64)st.st_dev); + jx9_array_add_strkey_elem(pArray, "dev", pWorker); /* Will make it's own copy */ + /* ino */ + jx9_value_int64(pWorker, (jx9_int64)st.st_ino); + jx9_array_add_strkey_elem(pArray, "ino", pWorker); /* Will make it's own copy */ + /* mode */ + jx9_value_int(pWorker, (int)st.st_mode); + jx9_array_add_strkey_elem(pArray, "mode", pWorker); + /* nlink */ + jx9_value_int(pWorker, (int)st.st_nlink); + jx9_array_add_strkey_elem(pArray, "nlink", pWorker); /* Will make it's own copy */ + /* uid, gid, rdev */ + jx9_value_int(pWorker, (int)st.st_uid); + jx9_array_add_strkey_elem(pArray, "uid", pWorker); + jx9_value_int(pWorker, (int)st.st_gid); + jx9_array_add_strkey_elem(pArray, "gid", pWorker); + jx9_value_int(pWorker, (int)st.st_rdev); + jx9_array_add_strkey_elem(pArray, "rdev", pWorker); + /* size */ + jx9_value_int64(pWorker, (jx9_int64)st.st_size); + jx9_array_add_strkey_elem(pArray, "size", pWorker); /* Will make it's own copy */ + /* atime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_atime); + jx9_array_add_strkey_elem(pArray, "atime", pWorker); /* Will make it's own copy */ + /* mtime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_mtime); + jx9_array_add_strkey_elem(pArray, "mtime", pWorker); /* Will make it's own copy */ + /* ctime */ + jx9_value_int64(pWorker, (jx9_int64)st.st_ctime); + jx9_array_add_strkey_elem(pArray, "ctime", pWorker); /* Will make it's own copy */ + /* blksize, blocks */ + jx9_value_int(pWorker, (int)st.st_blksize); + jx9_array_add_strkey_elem(pArray, "blksize", pWorker); + jx9_value_int(pWorker, (int)st.st_blocks); + jx9_array_add_strkey_elem(pArray, "blocks", pWorker); + return JX9_OK; +} +/* Export the file:// stream */ +static const jx9_io_stream sUnixFileStream = { + "file", /* Stream name */ + JX9_IO_STREAM_VERSION, + UnixFile_Open, /* xOpen */ + UnixDir_Open, /* xOpenDir */ + UnixFile_Close, /* xClose */ + UnixDir_Close, /* xCloseDir */ + UnixFile_Read, /* xRead */ + UnixDir_Read, /* xReadDir */ + UnixFile_Write, /* xWrite */ + UnixFile_Seek, /* xSeek */ + UnixFile_Lock, /* xLock */ + UnixDir_Rewind, /* xRewindDir */ + UnixFile_Tell, /* xTell */ + UnixFile_Trunc, /* xTrunc */ + UnixFile_Sync, /* xSeek */ + UnixFile_Stat /* xStat */ +}; +#endif /* __WINNT__/__UNIXES__ */ +#endif /* JX9_DISABLE_DISK_IO */ +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* + * Export the builtin vfs. + * Return a pointer to the builtin vfs if available. + * Otherwise return the null_vfs [i.e: a no-op vfs] instead. + * Note: + * The built-in vfs is always available for Windows/UNIX systems. + * Note: + * If the engine is compiled with the JX9_DISABLE_DISK_IO/JX9_DISABLE_BUILTIN_FUNC + * directives defined then this function return the null_vfs instead. + */ +JX9_PRIVATE const jx9_vfs * jx9ExportBuiltinVfs(void) +{ +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifdef JX9_DISABLE_DISK_IO + return &null_vfs; +#else +#ifdef __WINNT__ + return &sWinVfs; +#elif defined(__UNIXES__) + return &sUnixVfs; +#else + return &null_vfs; +#endif /* __WINNT__/__UNIXES__ */ +#endif /*JX9_DISABLE_DISK_IO*/ +#else + return &null_vfs; +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifndef JX9_DISABLE_DISK_IO +/* + * The following defines are mostly used by the UNIX built and have + * no particular meaning on windows. + */ +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif +/* + * jx9:// Accessing various I/O streams + * According to the JX9 langage reference manual + * JX9 provides a number of miscellaneous I/O streams that allow access to JX9's own input + * and output streams, the standard input, output and error file descriptors. + * jx9://stdin, jx9://stdout and jx9://stderr: + * Allow direct access to the corresponding input or output stream of the JX9 process. + * The stream references a duplicate file descriptor, so if you open jx9://stdin and later + * close it, you close only your copy of the descriptor-the actual stream referenced by STDIN is unaffected. + * jx9://stdin is read-only, whereas jx9://stdout and jx9://stderr are write-only. + * jx9://output + * jx9://output is a write-only stream that allows you to write to the output buffer + * mechanism in the same way as print and print. + */ +typedef struct jx9_stream_data jx9_stream_data; +/* Supported IO streams */ +#define JX9_IO_STREAM_STDIN 1 /* jx9://stdin */ +#define JX9_IO_STREAM_STDOUT 2 /* jx9://stdout */ +#define JX9_IO_STREAM_STDERR 3 /* jx9://stderr */ +#define JX9_IO_STREAM_OUTPUT 4 /* jx9://output */ + /* The following structure is the private data associated with the jx9:// stream */ +struct jx9_stream_data +{ + jx9_vm *pVm; /* VM that own this instance */ + int iType; /* Stream type */ + union{ + void *pHandle; /* Stream handle */ + jx9_output_consumer sConsumer; /* VM output consumer */ + }x; +}; +/* + * Allocate a new instance of the jx9_stream_data structure. + */ +static jx9_stream_data * JX9StreamDataInit(jx9_vm *pVm, int iType) +{ + jx9_stream_data *pData; + if( pVm == 0 ){ + return 0; + } + /* Allocate a new instance */ + pData = (jx9_stream_data *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(jx9_stream_data)); + if( pData == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pData, sizeof(jx9_stream_data)); + /* Initialize fields */ + pData->iType = iType; + if( iType == JX9_IO_STREAM_OUTPUT ){ + /* Point to the default VM consumer routine. */ + pData->x.sConsumer = pVm->sVmConsumer; + }else{ +#ifdef __WINNT__ + DWORD nChannel; + switch(iType){ + case JX9_IO_STREAM_STDOUT: nChannel = STD_OUTPUT_HANDLE; break; + case JX9_IO_STREAM_STDERR: nChannel = STD_ERROR_HANDLE; break; + default: + nChannel = STD_INPUT_HANDLE; + break; + } + pData->x.pHandle = GetStdHandle(nChannel); +#else + /* Assume an UNIX system */ + int ifd = STDIN_FILENO; + switch(iType){ + case JX9_IO_STREAM_STDOUT: ifd = STDOUT_FILENO; break; + case JX9_IO_STREAM_STDERR: ifd = STDERR_FILENO; break; + default: + break; + } + pData->x.pHandle = SX_INT_TO_PTR(ifd); +#endif + } + pData->pVm = pVm; + return pData; +} +/* + * Implementation of the jx9:// IO streams routines + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* int (*xOpen)(const char *, int, jx9_value *, void **) */ +static int JX9StreamData_Open(const char *zName, int iMode, jx9_value *pResource, void ** ppHandle) +{ + jx9_stream_data *pData; + SyString sStream; + SyStringInitFromBuf(&sStream, zName, SyStrlen(zName)); + /* Trim leading and trailing white spaces */ + SyStringFullTrim(&sStream); + /* Stream to open */ + if( SyStrnicmp(sStream.zString, "stdin", sizeof("stdin")-1) == 0 ){ + iMode = JX9_IO_STREAM_STDIN; + }else if( SyStrnicmp(sStream.zString, "output", sizeof("output")-1) == 0 ){ + iMode = JX9_IO_STREAM_OUTPUT; + }else if( SyStrnicmp(sStream.zString, "stdout", sizeof("stdout")-1) == 0 ){ + iMode = JX9_IO_STREAM_STDOUT; + }else if( SyStrnicmp(sStream.zString, "stderr", sizeof("stderr")-1) == 0 ){ + iMode = JX9_IO_STREAM_STDERR; + }else{ + /* unknown stream name */ + return -1; + } + /* Create our handle */ + pData = JX9StreamDataInit(pResource?pResource->pVm:0, iMode); + if( pData == 0 ){ + return -1; + } + /* Make the handle public */ + *ppHandle = (void *)pData; + return JX9_OK; +} +/* jx9_int64 (*xRead)(void *, void *, jx9_int64) */ +static jx9_int64 JX9StreamData_Read(void *pHandle, void *pBuffer, jx9_int64 nDatatoRead) +{ + jx9_stream_data *pData = (jx9_stream_data *)pHandle; + if( pData == 0 ){ + return -1; + } + if( pData->iType != JX9_IO_STREAM_STDIN ){ + /* Forbidden */ + return -1; + } +#ifdef __WINNT__ + { + DWORD nRd; + BOOL rc; + rc = ReadFile(pData->x.pHandle, pBuffer, (DWORD)nDatatoRead, &nRd, 0); + if( !rc ){ + /* IO error */ + return -1; + } + return (jx9_int64)nRd; + } +#elif defined(__UNIXES__) + { + ssize_t nRd; + int fd; + fd = SX_PTR_TO_INT(pData->x.pHandle); + nRd = read(fd, pBuffer, (size_t)nDatatoRead); + if( nRd < 1 ){ + return -1; + } + return (jx9_int64)nRd; + } +#else + return -1; +#endif +} +/* jx9_int64 (*xWrite)(void *, const void *, jx9_int64) */ +static jx9_int64 JX9StreamData_Write(void *pHandle, const void *pBuf, jx9_int64 nWrite) +{ + jx9_stream_data *pData = (jx9_stream_data *)pHandle; + if( pData == 0 ){ + return -1; + } + if( pData->iType == JX9_IO_STREAM_STDIN ){ + /* Forbidden */ + return -1; + }else if( pData->iType == JX9_IO_STREAM_OUTPUT ){ + jx9_output_consumer *pCons = &pData->x.sConsumer; + int rc; + /* Call the vm output consumer */ + rc = pCons->xConsumer(pBuf, (unsigned int)nWrite, pCons->pUserData); + if( rc == JX9_ABORT ){ + return -1; + } + return nWrite; + } +#ifdef __WINNT__ + { + DWORD nWr; + BOOL rc; + rc = WriteFile(pData->x.pHandle, pBuf, (DWORD)nWrite, &nWr, 0); + if( !rc ){ + /* IO error */ + return -1; + } + return (jx9_int64)nWr; + } +#elif defined(__UNIXES__) + { + ssize_t nWr; + int fd; + fd = SX_PTR_TO_INT(pData->x.pHandle); + nWr = write(fd, pBuf, (size_t)nWrite); + if( nWr < 1 ){ + return -1; + } + return (jx9_int64)nWr; + } +#else + return -1; +#endif +} +/* void (*xClose)(void *) */ +static void JX9StreamData_Close(void *pHandle) +{ + jx9_stream_data *pData = (jx9_stream_data *)pHandle; + jx9_vm *pVm; + if( pData == 0 ){ + return; + } + pVm = pData->pVm; + /* Free the instance */ + SyMemBackendFree(&pVm->sAllocator, pData); +} +/* Export the jx9:// stream */ +static const jx9_io_stream sjx9Stream = { + "jx9", + JX9_IO_STREAM_VERSION, + JX9StreamData_Open, /* xOpen */ + 0, /* xOpenDir */ + JX9StreamData_Close, /* xClose */ + 0, /* xCloseDir */ + JX9StreamData_Read, /* xRead */ + 0, /* xReadDir */ + JX9StreamData_Write, /* xWrite */ + 0, /* xSeek */ + 0, /* xLock */ + 0, /* xRewindDir */ + 0, /* xTell */ + 0, /* xTrunc */ + 0, /* xSeek */ + 0 /* xStat */ +}; +#endif /* JX9_DISABLE_DISK_IO */ +/* + * Return TRUE if we are dealing with the jx9:// stream. + * FALSE otherwise. + */ +static int is_jx9_stream(const jx9_io_stream *pStream) +{ +#ifndef JX9_DISABLE_DISK_IO + return pStream == &sjx9Stream; +#else + SXUNUSED(pStream); /* cc warning */ + return 0; +#endif /* JX9_DISABLE_DISK_IO */ +} + +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* + * Export the IO routines defined above and the built-in IO streams + * [i.e: file://, jx9://]. + * Note: + * If the engine is compiled with the JX9_DISABLE_BUILTIN_FUNC directive + * defined then this function is a no-op. + */ +JX9_PRIVATE sxi32 jx9RegisterIORoutine(jx9_vm *pVm) +{ +#ifndef JX9_DISABLE_BUILTIN_FUNC + /* VFS functions */ + static const jx9_builtin_func aVfsFunc[] = { + {"chdir", jx9Vfs_chdir }, + {"chroot", jx9Vfs_chroot }, + {"getcwd", jx9Vfs_getcwd }, + {"rmdir", jx9Vfs_rmdir }, + {"is_dir", jx9Vfs_is_dir }, + {"mkdir", jx9Vfs_mkdir }, + {"rename", jx9Vfs_rename }, + {"realpath", jx9Vfs_realpath}, + {"sleep", jx9Vfs_sleep }, + {"usleep", jx9Vfs_usleep }, + {"unlink", jx9Vfs_unlink }, + {"delete", jx9Vfs_unlink }, + {"chmod", jx9Vfs_chmod }, + {"chown", jx9Vfs_chown }, + {"chgrp", jx9Vfs_chgrp }, + {"disk_free_space", jx9Vfs_disk_free_space }, + {"disk_total_space", jx9Vfs_disk_total_space}, + {"file_exists", jx9Vfs_file_exists }, + {"filesize", jx9Vfs_file_size }, + {"fileatime", jx9Vfs_file_atime }, + {"filemtime", jx9Vfs_file_mtime }, + {"filectime", jx9Vfs_file_ctime }, + {"is_file", jx9Vfs_is_file }, + {"is_link", jx9Vfs_is_link }, + {"is_readable", jx9Vfs_is_readable }, + {"is_writable", jx9Vfs_is_writable }, + {"is_executable", jx9Vfs_is_executable}, + {"filetype", jx9Vfs_filetype }, + {"stat", jx9Vfs_stat }, + {"lstat", jx9Vfs_lstat }, + {"getenv", jx9Vfs_getenv }, + {"setenv", jx9Vfs_putenv }, + {"putenv", jx9Vfs_putenv }, + {"touch", jx9Vfs_touch }, + {"link", jx9Vfs_link }, + {"symlink", jx9Vfs_symlink }, + {"umask", jx9Vfs_umask }, + {"sys_get_temp_dir", jx9Vfs_sys_get_temp_dir }, + {"get_current_user", jx9Vfs_get_current_user }, + {"getpid", jx9Vfs_getmypid }, + {"getuid", jx9Vfs_getmyuid }, + {"getgid", jx9Vfs_getmygid }, + {"uname", jx9Vfs_uname}, + /* Path processing */ + {"dirname", jx9Builtin_dirname }, + {"basename", jx9Builtin_basename }, + {"pathinfo", jx9Builtin_pathinfo }, + {"strglob", jx9Builtin_strglob }, + {"fnmatch", jx9Builtin_fnmatch }, + /* ZIP processing */ + {"zip_open", jx9Builtin_zip_open }, + {"zip_close", jx9Builtin_zip_close}, + {"zip_read", jx9Builtin_zip_read }, + {"zip_entry_open", jx9Builtin_zip_entry_open }, + {"zip_entry_close", jx9Builtin_zip_entry_close}, + {"zip_entry_name", jx9Builtin_zip_entry_name }, + {"zip_entry_filesize", jx9Builtin_zip_entry_filesize }, + {"zip_entry_compressedsize", jx9Builtin_zip_entry_compressedsize }, + {"zip_entry_read", jx9Builtin_zip_entry_read }, + {"zip_entry_reset_cursor", jx9Builtin_zip_entry_reset_cursor}, + {"zip_entry_compressionmethod", jx9Builtin_zip_entry_compressionmethod} + }; + /* IO stream functions */ + static const jx9_builtin_func aIOFunc[] = { + {"ftruncate", jx9Builtin_ftruncate }, + {"fseek", jx9Builtin_fseek }, + {"ftell", jx9Builtin_ftell }, + {"rewind", jx9Builtin_rewind }, + {"fflush", jx9Builtin_fflush }, + {"feof", jx9Builtin_feof }, + {"fgetc", jx9Builtin_fgetc }, + {"fgets", jx9Builtin_fgets }, + {"fread", jx9Builtin_fread }, + {"fgetcsv", jx9Builtin_fgetcsv}, + {"fgetss", jx9Builtin_fgetss }, + {"readdir", jx9Builtin_readdir}, + {"rewinddir", jx9Builtin_rewinddir }, + {"closedir", jx9Builtin_closedir}, + {"opendir", jx9Builtin_opendir }, + {"readfile", jx9Builtin_readfile}, + {"file_get_contents", jx9Builtin_file_get_contents}, + {"file_put_contents", jx9Builtin_file_put_contents}, + {"file", jx9Builtin_file }, + {"copy", jx9Builtin_copy }, + {"fstat", jx9Builtin_fstat }, + {"fwrite", jx9Builtin_fwrite }, + {"fputs", jx9Builtin_fwrite }, + {"flock", jx9Builtin_flock }, + {"fclose", jx9Builtin_fclose }, + {"fopen", jx9Builtin_fopen }, + {"fpassthru", jx9Builtin_fpassthru }, + {"fputcsv", jx9Builtin_fputcsv }, + {"fprintf", jx9Builtin_fprintf }, +#if !defined(JX9_DISABLE_HASH_FUNC) + {"md5_file", jx9Builtin_md5_file}, + {"sha1_file", jx9Builtin_sha1_file}, +#endif /* JX9_DISABLE_HASH_FUNC */ + {"parse_ini_file", jx9Builtin_parse_ini_file}, + {"vfprintf", jx9Builtin_vfprintf} + }; + const jx9_io_stream *pFileStream = 0; + sxu32 n = 0; + /* Register the functions defined above */ + for( n = 0 ; n < SX_ARRAYSIZE(aVfsFunc) ; ++n ){ + jx9_create_function(&(*pVm), aVfsFunc[n].zName, aVfsFunc[n].xFunc, (void *)pVm->pEngine->pVfs); + } + for( n = 0 ; n < SX_ARRAYSIZE(aIOFunc) ; ++n ){ + jx9_create_function(&(*pVm), aIOFunc[n].zName, aIOFunc[n].xFunc, pVm); + } +#ifndef JX9_DISABLE_DISK_IO + /* Register the file stream if available */ +#ifdef __WINNT__ + pFileStream = &sWinFileStream; +#elif defined(__UNIXES__) + pFileStream = &sUnixFileStream; +#endif + /* Install the jx9:// stream */ + jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, &sjx9Stream); +#endif /* JX9_DISABLE_DISK_IO */ + if( pFileStream ){ + /* Install the file:// stream */ + jx9_vm_config(pVm, JX9_VM_CONFIG_IO_STREAM, pFileStream); + } +#else + SXUNUSED(pVm); /* cc warning */ +#endif /* JX9_DISABLE_BUILTIN_FUNC */ + return SXRET_OK; +} +/* + * Export the STDIN handle. + */ +JX9_PRIVATE void * jx9ExportStdin(jx9_vm *pVm) +{ +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifndef JX9_DISABLE_DISK_IO + if( pVm->pStdin == 0 ){ + io_private *pIn; + /* Allocate an IO private instance */ + pIn = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); + if( pIn == 0 ){ + return 0; + } + InitIOPrivate(pVm, &sjx9Stream, pIn); + /* Initialize the handle */ + pIn->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDIN); + /* Install the STDIN stream */ + pVm->pStdin = pIn; + return pIn; + }else{ + /* NULL or STDIN */ + return pVm->pStdin; + } +#else + return 0; +#endif +#else + SXUNUSED(pVm); /* cc warning */ + return 0; +#endif +} +/* + * Export the STDOUT handle. + */ +JX9_PRIVATE void * jx9ExportStdout(jx9_vm *pVm) +{ +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifndef JX9_DISABLE_DISK_IO + if( pVm->pStdout == 0 ){ + io_private *pOut; + /* Allocate an IO private instance */ + pOut = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); + if( pOut == 0 ){ + return 0; + } + InitIOPrivate(pVm, &sjx9Stream, pOut); + /* Initialize the handle */ + pOut->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDOUT); + /* Install the STDOUT stream */ + pVm->pStdout = pOut; + return pOut; + }else{ + /* NULL or STDOUT */ + return pVm->pStdout; + } +#else + return 0; +#endif +#else + SXUNUSED(pVm); /* cc warning */ + return 0; +#endif +} +/* + * Export the STDERR handle. + */ +JX9_PRIVATE void * jx9ExportStderr(jx9_vm *pVm) +{ +#ifndef JX9_DISABLE_BUILTIN_FUNC +#ifndef JX9_DISABLE_DISK_IO + if( pVm->pStderr == 0 ){ + io_private *pErr; + /* Allocate an IO private instance */ + pErr = (io_private *)SyMemBackendAlloc(&pVm->sAllocator, sizeof(io_private)); + if( pErr == 0 ){ + return 0; + } + InitIOPrivate(pVm, &sjx9Stream, pErr); + /* Initialize the handle */ + pErr->pHandle = JX9StreamDataInit(pVm, JX9_IO_STREAM_STDERR); + /* Install the STDERR stream */ + pVm->pStderr = pErr; + return pErr; + }else{ + /* NULL or STDERR */ + return pVm->pStderr; + } +#else + return 0; +#endif +#else + SXUNUSED(pVm); /* cc warning */ + return 0; +#endif +} + +/* + * ---------------------------------------------------------- + * File: jx9_vm.c + * MD5: beca4be65a9a49c932c356d7680034c9 + * ---------------------------------------------------------- + */ +/* + * Symisc JX9: A Highly Efficient Embeddable Scripting Engine Based on JSON. + * Copyright (C) 2012-2013, Symisc Systems http://jx9.symisc.net/ + * Version 1.7.2 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://jx9.symisc.net/ + */ + /* $SymiscID: jx9_vm.c v1.0 FreeBSD 2012-12-09 00:19 stable $ */ +#ifndef JX9_AMALGAMATION +#include "jx9Int.h" +#endif +/* + * The code in this file implements execution method of the JX9 Virtual Machine. + * The JX9 compiler (implemented in 'compiler.c' and 'parse.c') generates a bytecode program + * which is then executed by the virtual machine implemented here to do the work of the JX9 + * statements. + * JX9 bytecode programs are similar in form to assembly language. The program consists + * of a linear sequence of operations .Each operation has an opcode and 3 operands. + * Operands P1 and P2 are integers where the first is signed while the second is unsigned. + * Operand P3 is an arbitrary pointer specific to each instruction. The P2 operand is usually + * the jump destination used by the OP_JMP, OP_JZ, OP_JNZ, ... instructions. + * Opcodes will typically ignore one or more operands. Many opcodes ignore all three operands. + * Computation results are stored on a stack. Each entry on the stack is of type jx9_value. + * JX9 uses the jx9_value object to represent all values that can be stored in a JX9 variable. + * Since JX9 uses dynamic typing for the values it stores. Values stored in jx9_value objects + * can be integers, floating point values, strings, arrays, object instances (object in the JX9 jargon) + * and so on. + * Internally, the JX9 virtual machine manipulates nearly all values as jx9_values structures. + * Each jx9_value may cache multiple representations(string, integer etc.) of the same value. + * An implicit conversion from one type to the other occurs as necessary. + * Most of the code in this file is taken up by the [VmByteCodeExec()] function which does + * the work of interpreting a JX9 bytecode program. But other routines are also provided + * to help in building up a program instruction by instruction. + */ +/* + * Each active virtual machine frame is represented by an instance + * of the following structure. + * VM Frame hold local variables and other stuff related to function call. + */ +struct VmFrame +{ + VmFrame *pParent; /* Parent frame or NULL if global scope */ + void *pUserData; /* Upper layer private data associated with this frame */ + SySet sLocal; /* Local variables container (VmSlot instance) */ + jx9_vm *pVm; /* VM that own this frame */ + SyHash hVar; /* Variable hashtable for fast lookup */ + SySet sArg; /* Function arguments container */ + sxi32 iFlags; /* Frame configuration flags (See below)*/ + sxu32 iExceptionJump; /* Exception jump destination */ +}; +/* + * When a user defined variable is garbage collected, memory object index + * is stored in an instance of the following structure and put in the free object + * table so that it can be reused again without allocating a new memory object. + */ +typedef struct VmSlot VmSlot; +struct VmSlot +{ + sxu32 nIdx; /* Index in pVm->aMemObj[] */ + void *pUserData; /* Upper-layer private data */ +}; +/* + * Each parsed URI is recorded and stored in an instance of the following structure. + * This structure and it's related routines are taken verbatim from the xHT project + * [A modern embeddable HTTP engine implementing all the RFC2616 methods] + * the xHT project is developed internally by Symisc Systems. + */ +typedef struct SyhttpUri SyhttpUri; +struct SyhttpUri +{ + SyString sHost; /* Hostname or IP address */ + SyString sPort; /* Port number */ + SyString sPath; /* Mandatory resource path passed verbatim (Not decoded) */ + SyString sQuery; /* Query part */ + SyString sFragment; /* Fragment part */ + SyString sScheme; /* Scheme */ + SyString sUser; /* Username */ + SyString sPass; /* Password */ + SyString sRaw; /* Raw URI */ +}; +/* + * An instance of the following structure is used to record all MIME headers seen + * during a HTTP interaction. + * This structure and it's related routines are taken verbatim from the xHT project + * [A modern embeddable HTTP engine implementing all the RFC2616 methods] + * the xHT project is developed internally by Symisc Systems. + */ +typedef struct SyhttpHeader SyhttpHeader; +struct SyhttpHeader +{ + SyString sName; /* Header name [i.e:"Content-Type", "Host", "User-Agent"]. NOT NUL TERMINATED */ + SyString sValue; /* Header values [i.e: "text/html"]. NOT NUL TERMINATED */ +}; +/* + * Supported HTTP methods. + */ +#define HTTP_METHOD_GET 1 /* GET */ +#define HTTP_METHOD_HEAD 2 /* HEAD */ +#define HTTP_METHOD_POST 3 /* POST */ +#define HTTP_METHOD_PUT 4 /* PUT */ +#define HTTP_METHOD_OTHR 5 /* Other HTTP methods [i.e: DELETE, TRACE, OPTIONS...]*/ +/* + * Supported HTTP protocol version. + */ +#define HTTP_PROTO_10 1 /* HTTP/1.0 */ +#define HTTP_PROTO_11 2 /* HTTP/1.1 */ +/* + * Register a constant and it's associated expansion callback so that + * it can be expanded from the target JX9 program. + * The constant expansion mechanism under JX9 is extremely powerful yet + * simple and work as follows: + * Each registered constant have a C procedure associated with it. + * This procedure known as the constant expansion callback is responsible + * of expanding the invoked constant to the desired value, for example: + * The C procedure associated with the "__PI__" constant expands to 3.14 (the value of PI). + * The "__OS__" constant procedure expands to the name of the host Operating Systems + * (Windows, Linux, ...) and so on. + * Please refer to the official documentation for additional information. + */ +JX9_PRIVATE sxi32 jx9VmRegisterConstant( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Constant name */ + ProcConstant xExpand, /* Constant expansion callback */ + void *pUserData /* Last argument to xExpand() */ + ) +{ + jx9_constant *pCons; + SyHashEntry *pEntry; + char *zDupName; + sxi32 rc; + pEntry = SyHashGet(&pVm->hConstant, (const void *)pName->zString, pName->nByte); + if( pEntry ){ + /* Overwrite the old definition and return immediately */ + pCons = (jx9_constant *)pEntry->pUserData; + pCons->xExpand = xExpand; + pCons->pUserData = pUserData; + return SXRET_OK; + } + /* Allocate a new constant instance */ + pCons = (jx9_constant *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_constant)); + if( pCons == 0 ){ + return 0; + } + /* Duplicate constant name */ + zDupName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); + if( zDupName == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator, pCons); + return 0; + } + /* Install the constant */ + SyStringInitFromBuf(&pCons->sName, zDupName, pName->nByte); + pCons->xExpand = xExpand; + pCons->pUserData = pUserData; + rc = SyHashInsert(&pVm->hConstant, (const void *)zDupName, SyStringLength(&pCons->sName), pCons); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator, zDupName); + SyMemBackendPoolFree(&pVm->sAllocator, pCons); + return rc; + } + /* All done, constant can be invoked from JX9 code */ + return SXRET_OK; +} +/* + * Allocate a new foreign function instance. + * This function return SXRET_OK on success. Any other + * return value indicates failure. + * Please refer to the official documentation for an introduction to + * the foreign function mechanism. + */ +static sxi32 jx9NewForeignFunction( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Foreign function name */ + ProcHostFunction xFunc, /* Foreign function implementation */ + void *pUserData, /* Foreign function private data */ + jx9_user_func **ppOut /* OUT: VM image of the foreign function */ + ) +{ + jx9_user_func *pFunc; + char *zDup; + /* Allocate a new user function */ + pFunc = (jx9_user_func *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_user_func)); + if( pFunc == 0 ){ + return SXERR_MEM; + } + /* Duplicate function name */ + zDup = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); + if( zDup == 0 ){ + SyMemBackendPoolFree(&pVm->sAllocator, pFunc); + return SXERR_MEM; + } + /* Zero the structure */ + SyZero(pFunc, sizeof(jx9_user_func)); + /* Initialize structure fields */ + SyStringInitFromBuf(&pFunc->sName, zDup, pName->nByte); + pFunc->pVm = pVm; + pFunc->xFunc = xFunc; + pFunc->pUserData = pUserData; + SySetInit(&pFunc->aAux, &pVm->sAllocator, sizeof(jx9_aux_data)); + /* Write a pointer to the new function */ + *ppOut = pFunc; + return SXRET_OK; +} +/* + * Install a foreign function and it's associated callback so that + * it can be invoked from the target JX9 code. + * This function return SXRET_OK on successful registration. Any other + * return value indicates failure. + * Please refer to the official documentation for an introduction to + * the foreign function mechanism. + */ +JX9_PRIVATE sxi32 jx9VmInstallForeignFunction( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Foreign function name */ + ProcHostFunction xFunc, /* Foreign function implementation */ + void *pUserData /* Foreign function private data */ + ) +{ + jx9_user_func *pFunc; + SyHashEntry *pEntry; + sxi32 rc; + /* Overwrite any previously registered function with the same name */ + pEntry = SyHashGet(&pVm->hHostFunction, pName->zString, pName->nByte); + if( pEntry ){ + pFunc = (jx9_user_func *)pEntry->pUserData; + pFunc->pUserData = pUserData; + pFunc->xFunc = xFunc; + SySetReset(&pFunc->aAux); + return SXRET_OK; + } + /* Create a new user function */ + rc = jx9NewForeignFunction(&(*pVm), &(*pName), xFunc, pUserData, &pFunc); + if( rc != SXRET_OK ){ + return rc; + } + /* Install the function in the corresponding hashtable */ + rc = SyHashInsert(&pVm->hHostFunction, SyStringData(&pFunc->sName), pName->nByte, pFunc); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName)); + SyMemBackendPoolFree(&pVm->sAllocator, pFunc); + return rc; + } + /* User function successfully installed */ + return SXRET_OK; +} +/* + * Initialize a VM function. + */ +JX9_PRIVATE sxi32 jx9VmInitFuncState( + jx9_vm *pVm, /* Target VM */ + jx9_vm_func *pFunc, /* Target Fucntion */ + const char *zName, /* Function name */ + sxu32 nByte, /* zName length */ + sxi32 iFlags, /* Configuration flags */ + void *pUserData /* Function private data */ + ) +{ + /* Zero the structure */ + SyZero(pFunc, sizeof(jx9_vm_func)); + /* Initialize structure fields */ + /* Arguments container */ + SySetInit(&pFunc->aArgs, &pVm->sAllocator, sizeof(jx9_vm_func_arg)); + /* Static variable container */ + SySetInit(&pFunc->aStatic, &pVm->sAllocator, sizeof(jx9_vm_func_static_var)); + /* Bytecode container */ + SySetInit(&pFunc->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); + /* Preallocate some instruction slots */ + SySetAlloc(&pFunc->aByteCode, 0x10); + pFunc->iFlags = iFlags; + pFunc->pUserData = pUserData; + SyStringInitFromBuf(&pFunc->sName, zName, nByte); + return SXRET_OK; +} +/* + * Install a user defined function in the corresponding VM container. + */ +JX9_PRIVATE sxi32 jx9VmInstallUserFunction( + jx9_vm *pVm, /* Target VM */ + jx9_vm_func *pFunc, /* Target function */ + SyString *pName /* Function name */ + ) +{ + SyHashEntry *pEntry; + sxi32 rc; + if( pName == 0 ){ + /* Use the built-in name */ + pName = &pFunc->sName; + } + /* Check for duplicates (functions with the same name) first */ + pEntry = SyHashGet(&pVm->hFunction, pName->zString, pName->nByte); + if( pEntry ){ + jx9_vm_func *pLink = (jx9_vm_func *)pEntry->pUserData; + if( pLink != pFunc ){ + /* Link */ + pFunc->pNextName = pLink; + pEntry->pUserData = pFunc; + } + return SXRET_OK; + } + /* First time seen */ + pFunc->pNextName = 0; + rc = SyHashInsert(&pVm->hFunction, pName->zString, pName->nByte, pFunc); + return rc; +} +/* + * Instruction builder interface. + */ +JX9_PRIVATE sxi32 jx9VmEmitInstr( + jx9_vm *pVm, /* Target VM */ + sxi32 iOp, /* Operation to perform */ + sxi32 iP1, /* First operand */ + sxu32 iP2, /* Second operand */ + void *p3, /* Third operand */ + sxu32 *pIndex /* Instruction index. NULL otherwise */ + ) +{ + VmInstr sInstr; + sxi32 rc; + /* Fill the VM instruction */ + sInstr.iOp = (sxu8)iOp; + sInstr.iP1 = iP1; + sInstr.iP2 = iP2; + sInstr.p3 = p3; + if( pIndex ){ + /* Instruction index in the bytecode array */ + *pIndex = SySetUsed(pVm->pByteContainer); + } + /* Finally, record the instruction */ + rc = SySetPut(pVm->pByteContainer, (const void *)&sInstr); + if( rc != SXRET_OK ){ + jx9GenCompileError(&pVm->sCodeGen, E_ERROR, 1, "Fatal, Cannot emit instruction due to a memory failure"); + /* Fall throw */ + } + return rc; +} +/* + * Swap the current bytecode container with the given one. + */ +JX9_PRIVATE sxi32 jx9VmSetByteCodeContainer(jx9_vm *pVm, SySet *pContainer) +{ + if( pContainer == 0 ){ + /* Point to the default container */ + pVm->pByteContainer = &pVm->aByteCode; + }else{ + /* Change container */ + pVm->pByteContainer = &(*pContainer); + } + return SXRET_OK; +} +/* + * Return the current bytecode container. + */ +JX9_PRIVATE SySet * jx9VmGetByteCodeContainer(jx9_vm *pVm) +{ + return pVm->pByteContainer; +} +/* + * Extract the VM instruction rooted at nIndex. + */ +JX9_PRIVATE VmInstr * jx9VmGetInstr(jx9_vm *pVm, sxu32 nIndex) +{ + VmInstr *pInstr; + pInstr = (VmInstr *)SySetAt(pVm->pByteContainer, nIndex); + return pInstr; +} +/* + * Return the total number of VM instructions recorded so far. + */ +JX9_PRIVATE sxu32 jx9VmInstrLength(jx9_vm *pVm) +{ + return SySetUsed(pVm->pByteContainer); +} +/* + * Pop the last VM instruction. + */ +JX9_PRIVATE VmInstr * jx9VmPopInstr(jx9_vm *pVm) +{ + return (VmInstr *)SySetPop(pVm->pByteContainer); +} +/* + * Peek the last VM instruction. + */ +JX9_PRIVATE VmInstr * jx9VmPeekInstr(jx9_vm *pVm) +{ + return (VmInstr *)SySetPeek(pVm->pByteContainer); +} +/* + * Allocate a new virtual machine frame. + */ +static VmFrame * VmNewFrame( + jx9_vm *pVm, /* Target VM */ + void *pUserData /* Upper-layer private data */ + ) +{ + VmFrame *pFrame; + /* Allocate a new vm frame */ + pFrame = (VmFrame *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmFrame)); + if( pFrame == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pFrame, sizeof(VmFrame)); + /* Initialize frame fields */ + pFrame->pUserData = pUserData; + pFrame->pVm = pVm; + SyHashInit(&pFrame->hVar, &pVm->sAllocator, 0, 0); + SySetInit(&pFrame->sArg, &pVm->sAllocator, sizeof(VmSlot)); + SySetInit(&pFrame->sLocal, &pVm->sAllocator, sizeof(VmSlot)); + return pFrame; +} +/* + * Enter a VM frame. + */ +static sxi32 VmEnterFrame( + jx9_vm *pVm, /* Target VM */ + void *pUserData, /* Upper-layer private data */ + VmFrame **ppFrame /* OUT: Top most active frame */ + ) +{ + VmFrame *pFrame; + /* Allocate a new frame */ + pFrame = VmNewFrame(&(*pVm), pUserData); + if( pFrame == 0 ){ + return SXERR_MEM; + } + /* Link to the list of active VM frame */ + pFrame->pParent = pVm->pFrame; + pVm->pFrame = pFrame; + if( ppFrame ){ + /* Write a pointer to the new VM frame */ + *ppFrame = pFrame; + } + return SXRET_OK; +} +/* + * Link a foreign variable with the TOP most active frame. + * Refer to the JX9_OP_UPLINK instruction implementation for more + * information. + */ +static sxi32 VmFrameLink(jx9_vm *pVm,SyString *pName) +{ + VmFrame *pTarget, *pFrame; + SyHashEntry *pEntry = 0; + sxi32 rc; + /* Point to the upper frame */ + pFrame = pVm->pFrame; + pTarget = pFrame; + pFrame = pTarget->pParent; + while( pFrame ){ + /* Query the current frame */ + pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); + if( pEntry ){ + /* Variable found */ + break; + } + /* Point to the upper frame */ + pFrame = pFrame->pParent; + } + if( pEntry == 0 ){ + /* Inexistant variable */ + return SXERR_NOTFOUND; + } + /* Link to the current frame */ + rc = SyHashInsert(&pTarget->hVar, pEntry->pKey, pEntry->nKeyLen, pEntry->pUserData); + return rc; +} +/* + * Leave the top-most active frame. + */ +static void VmLeaveFrame(jx9_vm *pVm) +{ + VmFrame *pFrame = pVm->pFrame; + if( pFrame ){ + /* Unlink from the list of active VM frame */ + pVm->pFrame = pFrame->pParent; + if( pFrame->pParent ){ + VmSlot *aSlot; + sxu32 n; + /* Restore local variable to the free pool so that they can be reused again */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sLocal); + for(n = 0 ; n < SySetUsed(&pFrame->sLocal) ; ++n ){ + /* Unset the local variable */ + jx9VmUnsetMemObj(&(*pVm), aSlot[n].nIdx); + } + } + /* Release internal containers */ + SyHashRelease(&pFrame->hVar); + SySetRelease(&pFrame->sArg); + SySetRelease(&pFrame->sLocal); + /* Release the whole structure */ + SyMemBackendPoolFree(&pVm->sAllocator, pFrame); + } +} +/* + * Compare two functions signature and return the comparison result. + */ +static int VmOverloadCompare(SyString *pFirst, SyString *pSecond) +{ + const char *zSend = &pSecond->zString[pSecond->nByte]; + const char *zFend = &pFirst->zString[pFirst->nByte]; + const char *zSin = pSecond->zString; + const char *zFin = pFirst->zString; + const char *zPtr = zFin; + for(;;){ + if( zFin >= zFend || zSin >= zSend ){ + break; + } + if( zFin[0] != zSin[0] ){ + /* mismatch */ + break; + } + zFin++; + zSin++; + } + return (int)(zFin-zPtr); +} +/* + * Select the appropriate VM function for the current call context. + * This is the implementation of the powerful 'function overloading' feature + * introduced by the version 2 of the JX9 engine. + * Refer to the official documentation for more information. + */ +static jx9_vm_func * VmOverload( + jx9_vm *pVm, /* Target VM */ + jx9_vm_func *pList, /* Linked list of candidates for overloading */ + jx9_value *aArg, /* Array of passed arguments */ + int nArg /* Total number of passed arguments */ + ) +{ + int iTarget, i, j, iCur, iMax; + jx9_vm_func *apSet[10]; /* Maximum number of candidates */ + jx9_vm_func *pLink; + SyString sArgSig; + SyBlob sSig; + + pLink = pList; + i = 0; + /* Put functions expecting the same number of passed arguments */ + while( i < (int)SX_ARRAYSIZE(apSet) ){ + if( pLink == 0 ){ + break; + } + if( (int)SySetUsed(&pLink->aArgs) == nArg ){ + /* Candidate for overloading */ + apSet[i++] = pLink; + } + /* Point to the next entry */ + pLink = pLink->pNextName; + } + if( i < 1 ){ + /* No candidates, return the head of the list */ + return pList; + } + if( nArg < 1 || i < 2 ){ + /* Return the only candidate */ + return apSet[0]; + } + /* Calculate function signature */ + SyBlobInit(&sSig, &pVm->sAllocator); + for( j = 0 ; j < nArg ; j++ ){ + int c = 'n'; /* null */ + if( aArg[j].iFlags & MEMOBJ_HASHMAP ){ + /* Hashmap */ + c = 'h'; + }else if( aArg[j].iFlags & MEMOBJ_BOOL ){ + /* bool */ + c = 'b'; + }else if( aArg[j].iFlags & MEMOBJ_INT ){ + /* int */ + c = 'i'; + }else if( aArg[j].iFlags & MEMOBJ_STRING ){ + /* String */ + c = 's'; + }else if( aArg[j].iFlags & MEMOBJ_REAL ){ + /* Float */ + c = 'f'; + } + if( c > 0 ){ + SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); + } + } + SyStringInitFromBuf(&sArgSig, SyBlobData(&sSig), SyBlobLength(&sSig)); + iTarget = 0; + iMax = -1; + /* Select the appropriate function */ + for( j = 0 ; j < i ; j++ ){ + /* Compare the two signatures */ + iCur = VmOverloadCompare(&sArgSig, &apSet[j]->sSignature); + if( iCur > iMax ){ + iMax = iCur; + iTarget = j; + } + } + SyBlobRelease(&sSig); + /* Appropriate function for the current call context */ + return apSet[iTarget]; +} +/* + * Dummy read-only buffer used for slot reservation. + */ +static const char zDummy[sizeof(jx9_value)] = { 0 }; /* Must be >= sizeof(jx9_value) */ +/* + * Reserve a constant memory object. + * Return a pointer to the raw jx9_value on success. NULL on failure. + */ +JX9_PRIVATE jx9_value * jx9VmReserveConstObj(jx9_vm *pVm, sxu32 *pIndex) +{ + jx9_value *pObj; + sxi32 rc; + if( pIndex ){ + /* Object index in the object table */ + *pIndex = SySetUsed(&pVm->aLitObj); + } + /* Reserve a slot for the new object */ + rc = SySetPut(&pVm->aLitObj, (const void *)zDummy); + if( rc != SXRET_OK ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return 0; + } + pObj = (jx9_value *)SySetPeek(&pVm->aLitObj); + return pObj; +} +/* + * Reserve a memory object. + * Return a pointer to the raw jx9_value on success. NULL on failure. + */ +static jx9_value * VmReserveMemObj(jx9_vm *pVm, sxu32 *pIndex) +{ + jx9_value *pObj; + sxi32 rc; + if( pIndex ){ + /* Object index in the object table */ + *pIndex = SySetUsed(&pVm->aMemObj); + } + /* Reserve a slot for the new object */ + rc = SySetPut(&pVm->aMemObj, (const void *)zDummy); + if( rc != SXRET_OK ){ + /* If the supplied memory subsystem is so sick that we are unable to allocate + * a tiny chunk of memory, there is no much we can do here. + */ + return 0; + } + pObj = (jx9_value *)SySetPeek(&pVm->aMemObj); + return pObj; +} +/* Forward declaration */ +static sxi32 VmEvalChunk(jx9_vm *pVm, jx9_context *pCtx, SyString *pChunk, int iFlags, int bTrueReturn); +/* + * Built-in functions that cannot be implemented directly as foreign functions. + */ +#define JX9_BUILTIN_LIB \ + "function scandir(string $directory, int $sort_order = SCANDIR_SORT_ASCENDING)"\ + "{"\ + " if( func_num_args() < 1 ){ return FALSE; }"\ + " $aDir = [];"\ + " $pHandle = opendir($directory);"\ + " if( $pHandle == FALSE ){ return FALSE; }"\ + " while(FALSE !== ($pEntry = readdir($pHandle)) ){"\ + " $aDir[] = $pEntry;"\ + " }"\ + " closedir($pHandle);"\ + " if( $sort_order == SCANDIR_SORT_DESCENDING ){"\ + " rsort($aDir);"\ + " }else if( $sort_order == SCANDIR_SORT_ASCENDING ){"\ + " sort($aDir);"\ + " }"\ + " return $aDir;"\ + "}"\ + "function glob(string $pattern, int $iFlags = 0){"\ + "/* Open the target directory */"\ + "$zDir = dirname($pattern);"\ + "if(!is_string($zDir) ){ $zDir = './'; }"\ + "$pHandle = opendir($zDir);"\ + "if( $pHandle == FALSE ){"\ + " /* IO error while opening the current directory, return FALSE */"\ + " return FALSE;"\ + "}"\ + "$pattern = basename($pattern);"\ + "$pArray = []; /* Empty array */"\ + "/* Loop throw available entries */"\ + "while( FALSE !== ($pEntry = readdir($pHandle)) ){"\ + " /* Use the built-in strglob function which is a Symisc eXtension for wildcard comparison*/"\ + " $rc = strglob($pattern, $pEntry);"\ + " if( $rc ){"\ + " if( is_dir($pEntry) ){"\ + " if( $iFlags & GLOB_MARK ){"\ + " /* Adds a slash to each directory returned */"\ + " $pEntry .= DIRECTORY_SEPARATOR;"\ + " }"\ + " }else if( $iFlags & GLOB_ONLYDIR ){"\ + " /* Not a directory, ignore */"\ + " continue;"\ + " }"\ + " /* Add the entry */"\ + " $pArray[] = $pEntry;"\ + " }"\ + " }"\ + "/* Close the handle */"\ + "closedir($pHandle);"\ + "if( ($iFlags & GLOB_NOSORT) == 0 ){"\ + " /* Sort the array */"\ + " sort($pArray);"\ + "}"\ + "if( ($iFlags & GLOB_NOCHECK) && sizeof($pArray) < 1 ){"\ + " /* Return the search pattern if no files matching were found */"\ + " $pArray[] = $pattern;"\ + "}"\ + "/* Return the created array */"\ + "return $pArray;"\ + "}"\ + "/* Creates a temporary file */"\ + "function tmpfile(){"\ + " /* Extract the temp directory */"\ + " $zTempDir = sys_get_temp_dir();"\ + " if( strlen($zTempDir) < 1 ){"\ + " /* Use the current dir */"\ + " $zTempDir = '.';"\ + " }"\ + " /* Create the file */"\ + " $pHandle = fopen($zTempDir.DIRECTORY_SEPARATOR.'JX9'.rand_str(12), 'w+');"\ + " return $pHandle;"\ + "}"\ + "/* Creates a temporary filename */"\ + "function tempnam(string $zDir = sys_get_temp_dir() /* Symisc eXtension */, string $zPrefix = 'JX9')"\ + "{"\ + " return $zDir.DIRECTORY_SEPARATOR.$zPrefix.rand_str(12);"\ + "}"\ + "function max(){"\ + " $pArgs = func_get_args();"\ + " if( sizeof($pArgs) < 1 ){"\ + " return null;"\ + " }"\ + " if( sizeof($pArgs) < 2 ){"\ + " $pArg = $pArgs[0];"\ + " if( !is_array($pArg) ){"\ + " return $pArg; "\ + " }"\ + " if( sizeof($pArg) < 1 ){"\ + " return null;"\ + " }"\ + " $pArg = array_copy($pArgs[0]);"\ + " reset($pArg);"\ + " $max = current($pArg);"\ + " while( FALSE !== ($val = next($pArg)) ){"\ + " if( $val > $max ){"\ + " $max = $val;"\ + " }"\ + " }"\ + " return $max;"\ + " }"\ + " $max = $pArgs[0];"\ + " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ + " $val = $pArgs[$i];"\ + "if( $val > $max ){"\ + " $max = $val;"\ + "}"\ + " }"\ + " return $max;"\ + "}"\ + "function min(){"\ + " $pArgs = func_get_args();"\ + " if( sizeof($pArgs) < 1 ){"\ + " return null;"\ + " }"\ + " if( sizeof($pArgs) < 2 ){"\ + " $pArg = $pArgs[0];"\ + " if( !is_array($pArg) ){"\ + " return $pArg; "\ + " }"\ + " if( sizeof($pArg) < 1 ){"\ + " return null;"\ + " }"\ + " $pArg = array_copy($pArgs[0]);"\ + " reset($pArg);"\ + " $min = current($pArg);"\ + " while( FALSE !== ($val = next($pArg)) ){"\ + " if( $val < $min ){"\ + " $min = $val;"\ + " }"\ + " }"\ + " return $min;"\ + " }"\ + " $min = $pArgs[0];"\ + " for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ + " $val = $pArgs[$i];"\ + "if( $val < $min ){"\ + " $min = $val;"\ + " }"\ + " }"\ + " return $min;"\ + "}" +/* + * Initialize a freshly allocated JX9 Virtual Machine so that we can + * start compiling the target JX9 program. + */ +JX9_PRIVATE sxi32 jx9VmInit( + jx9_vm *pVm, /* Initialize this */ + jx9 *pEngine /* Master engine */ + ) +{ + SyString sBuiltin; + jx9_value *pObj; + sxi32 rc; + /* Zero the structure */ + SyZero(pVm, sizeof(jx9_vm)); + /* Initialize VM fields */ + pVm->pEngine = &(*pEngine); + SyMemBackendInitFromParent(&pVm->sAllocator, &pEngine->sAllocator); + /* Instructions containers */ + SySetInit(&pVm->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); + SySetAlloc(&pVm->aByteCode, 0xFF); + pVm->pByteContainer = &pVm->aByteCode; + /* Object containers */ + SySetInit(&pVm->aMemObj, &pVm->sAllocator, sizeof(jx9_value)); + SySetAlloc(&pVm->aMemObj, 0xFF); + /* Virtual machine internal containers */ + SyBlobInit(&pVm->sConsumer, &pVm->sAllocator); + SyBlobInit(&pVm->sWorker, &pVm->sAllocator); + SyBlobInit(&pVm->sArgv, &pVm->sAllocator); + SySetInit(&pVm->aLitObj, &pVm->sAllocator, sizeof(jx9_value)); + SySetAlloc(&pVm->aLitObj, 0xFF); + SyHashInit(&pVm->hHostFunction, &pVm->sAllocator, 0, 0); + SyHashInit(&pVm->hFunction, &pVm->sAllocator, 0, 0); + SyHashInit(&pVm->hConstant, &pVm->sAllocator, 0, 0); + SyHashInit(&pVm->hSuper, &pVm->sAllocator, 0, 0); + SySetInit(&pVm->aFreeObj, &pVm->sAllocator, sizeof(VmSlot)); + /* Configuration containers */ + SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString)); + SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString)); + SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString)); + SySetInit(&pVm->aIOstream, &pVm->sAllocator, sizeof(jx9_io_stream *)); + /* Error callbacks containers */ + jx9MemObjInit(&(*pVm), &pVm->sAssertCallback); + /* Set a default recursion limit */ +#if defined(__WINNT__) || defined(__UNIXES__) + pVm->nMaxDepth = 32; +#else + pVm->nMaxDepth = 16; +#endif + /* Default assertion flags */ + pVm->iAssertFlags = JX9_ASSERT_WARNING; /* Issue a warning for each failed assertion */ + /* PRNG context */ + SyRandomnessInit(&pVm->sPrng, 0, 0); + /* Install the null constant */ + pObj = jx9VmReserveConstObj(&(*pVm), 0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + jx9MemObjInit(pVm, pObj); + /* Install the boolean TRUE constant */ + pObj = jx9VmReserveConstObj(&(*pVm), 0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + jx9MemObjInitFromBool(pVm, pObj, 1); + /* Install the boolean FALSE constant */ + pObj = jx9VmReserveConstObj(&(*pVm), 0); + if( pObj == 0 ){ + rc = SXERR_MEM; + goto Err; + } + jx9MemObjInitFromBool(pVm, pObj, 0); + /* Create the global frame */ + rc = VmEnterFrame(&(*pVm), 0, 0); + if( rc != SXRET_OK ){ + goto Err; + } + /* Initialize the code generator */ + rc = jx9InitCodeGenerator(pVm, pEngine->xConf.xErr, pEngine->xConf.pErrData); + if( rc != SXRET_OK ){ + goto Err; + } + /* VM correctly initialized, set the magic number */ + pVm->nMagic = JX9_VM_INIT; + SyStringInitFromBuf(&sBuiltin,JX9_BUILTIN_LIB, sizeof(JX9_BUILTIN_LIB)-1); + /* Compile the built-in library */ + VmEvalChunk(&(*pVm), 0, &sBuiltin, 0, FALSE); + /* Reset the code generator */ + jx9ResetCodeGenerator(&(*pVm), pEngine->xConf.xErr, pEngine->xConf.pErrData); + return SXRET_OK; +Err: + SyMemBackendRelease(&pVm->sAllocator); + return rc; +} +/* + * Default VM output consumer callback.That is, all VM output is redirected to this + * routine which store the output in an internal blob. + * The output can be extracted later after program execution [jx9_vm_exec()] via + * the [jx9_vm_config()] interface with a configuration verb set to + * jx9VM_CONFIG_EXTRACT_OUTPUT. + * Refer to the official docurmentation for additional information. + * Note that for performance reason it's preferable to install a VM output + * consumer callback via (jx9VM_CONFIG_OUTPUT) rather than waiting for the VM + * to finish executing and extracting the output. + */ +JX9_PRIVATE sxi32 jx9VmBlobConsumer( + const void *pOut, /* VM Generated output*/ + unsigned int nLen, /* Generated output length */ + void *pUserData /* User private data */ + ) +{ + sxi32 rc; + /* Store the output in an internal BLOB */ + rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen); + return rc; +} +#define VM_STACK_GUARD 16 +/* + * Allocate a new operand stack so that we can start executing + * our compiled JX9 program. + * Return a pointer to the operand stack (array of jx9_values) + * on success. NULL (Fatal error) on failure. + */ +static jx9_value * VmNewOperandStack( + jx9_vm *pVm, /* Target VM */ + sxu32 nInstr /* Total numer of generated bytecode instructions */ + ) +{ + jx9_value *pStack; + /* No instruction ever pushes more than a single element onto the + ** stack and the stack never grows on successive executions of the + ** same loop. So the total number of instructions is an upper bound + ** on the maximum stack depth required. + ** + ** Allocation all the stack space we will ever need. + */ + nInstr += VM_STACK_GUARD; + pStack = (jx9_value *)SyMemBackendAlloc(&pVm->sAllocator, nInstr * sizeof(jx9_value)); + if( pStack == 0 ){ + return 0; + } + /* Initialize the operand stack */ + while( nInstr > 0 ){ + jx9MemObjInit(&(*pVm), &pStack[nInstr - 1]); + --nInstr; + } + /* Ready for bytecode execution */ + return pStack; +} +/* Forward declaration */ +static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm); +/* + * Prepare the Virtual Machine for bytecode execution. + * This routine gets called by the JX9 engine after + * successful compilation of the target JX9 program. + */ +JX9_PRIVATE sxi32 jx9VmMakeReady( + jx9_vm *pVm /* Target VM */ + ) +{ + sxi32 rc; + if( pVm->nMagic != JX9_VM_INIT ){ + /* Initialize your VM first */ + return SXERR_CORRUPT; + } + /* Mark the VM ready for bytecode execution */ + pVm->nMagic = JX9_VM_RUN; + /* Release the code generator now we have compiled our program */ + jx9ResetCodeGenerator(pVm, 0, 0); + /* Emit the DONE instruction */ + rc = jx9VmEmitInstr(&(*pVm), JX9_OP_DONE, 0, 0, 0, 0); + if( rc != SXRET_OK ){ + return SXERR_MEM; + } + /* Script return value */ + jx9MemObjInit(&(*pVm), &pVm->sExec); /* Assume a NULL return value */ + /* Allocate a new operand stack */ + pVm->aOps = VmNewOperandStack(&(*pVm), SySetUsed(pVm->pByteContainer)); + if( pVm->aOps == 0 ){ + return SXERR_MEM; + } + /* Set the default VM output consumer callback and it's + * private data. */ + pVm->sVmConsumer.xConsumer = jx9VmBlobConsumer; + pVm->sVmConsumer.pUserData = &pVm->sConsumer; + /* Register special functions first [i.e: print, func_get_args(), die, etc.] */ + rc = VmRegisterSpecialFunction(&(*pVm)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return rc; + } + /* Create superglobals [i.e: $GLOBALS, $_GET, $_POST...] */ + rc = jx9HashmapLoadBuiltin(&(*pVm)); + if( rc != SXRET_OK ){ + /* Don't worry about freeing memory, everything will be released shortly */ + return rc; + } + /* Register built-in constants [i.e: JX9_EOL, JX9_OS...] */ + jx9RegisterBuiltInConstant(&(*pVm)); + /* Register built-in functions [i.e: is_null(), array_diff(), strlen(), etc.] */ + jx9RegisterBuiltInFunction(&(*pVm)); + /* VM is ready for bytecode execution */ + return SXRET_OK; +} +/* + * Reset a Virtual Machine to it's initial state. + */ +JX9_PRIVATE sxi32 jx9VmReset(jx9_vm *pVm) +{ + if( pVm->nMagic != JX9_VM_RUN && pVm->nMagic != JX9_VM_EXEC ){ + return SXERR_CORRUPT; + } + /* TICKET 1433-003: As of this version, the VM is automatically reset */ + SyBlobReset(&pVm->sConsumer); + jx9MemObjRelease(&pVm->sExec); + /* Set the ready flag */ + pVm->nMagic = JX9_VM_RUN; + return SXRET_OK; +} +/* + * Release a Virtual Machine. + * Every virtual machine must be destroyed in order to avoid memory leaks. + */ +JX9_PRIVATE sxi32 jx9VmRelease(jx9_vm *pVm) +{ + /* Set the stale magic number */ + pVm->nMagic = JX9_VM_STALE; + /* Release the private memory subsystem */ + SyMemBackendRelease(&pVm->sAllocator); + return SXRET_OK; +} +/* + * Initialize a foreign function call context. + * The context in which a foreign function executes is stored in a jx9_context object. + * A pointer to a jx9_context object is always first parameter to application-defined foreign + * functions. + * The application-defined foreign function implementation will pass this pointer through into + * calls to dozens of interfaces, these includes jx9_result_int(), jx9_result_string(), jx9_result_value(), + * jx9_context_new_scalar(), jx9_context_alloc_chunk(), jx9_context_output(), jx9_context_throw_error() + * and many more. Refer to the C/C++ Interfaces documentation for additional information. + */ +static sxi32 VmInitCallContext( + jx9_context *pOut, /* Call Context */ + jx9_vm *pVm, /* Target VM */ + jx9_user_func *pFunc, /* Foreign function to execute shortly */ + jx9_value *pRet, /* Store return value here*/ + sxi32 iFlags /* Control flags */ + ) +{ + pOut->pFunc = pFunc; + pOut->pVm = pVm; + SySetInit(&pOut->sVar, &pVm->sAllocator, sizeof(jx9_value *)); + SySetInit(&pOut->sChunk, &pVm->sAllocator, sizeof(jx9_aux_data)); + /* Assume a null return value */ + MemObjSetType(pRet, MEMOBJ_NULL); + pOut->pRet = pRet; + pOut->iFlags = iFlags; + return SXRET_OK; +} +/* + * Release a foreign function call context and cleanup the mess + * left behind. + */ +static void VmReleaseCallContext(jx9_context *pCtx) +{ + sxu32 n; + if( SySetUsed(&pCtx->sVar) > 0 ){ + jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar); + for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ + if( apObj[n] == 0 ){ + /* Already released */ + continue; + } + jx9MemObjRelease(apObj[n]); + SyMemBackendPoolFree(&pCtx->pVm->sAllocator, apObj[n]); + } + SySetRelease(&pCtx->sVar); + } + if( SySetUsed(&pCtx->sChunk) > 0 ){ + jx9_aux_data *aAux; + void *pChunk; + /* Automatic release of dynamically allocated chunk + * using [jx9_context_alloc_chunk()]. + */ + aAux = (jx9_aux_data *)SySetBasePtr(&pCtx->sChunk); + for( n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n ){ + pChunk = aAux[n].pAuxData; + /* Release the chunk */ + if( pChunk ){ + SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk); + } + } + SySetRelease(&pCtx->sChunk); + } +} +/* + * Release a jx9_value allocated from the body of a foreign function. + * Refer to [jx9_context_release_value()] for additional information. + */ +JX9_PRIVATE void jx9VmReleaseContextValue( + jx9_context *pCtx, /* Call context */ + jx9_value *pValue /* Release this value */ + ) +{ + if( pValue == 0 ){ + /* NULL value is a harmless operation */ + return; + } + if( SySetUsed(&pCtx->sVar) > 0 ){ + jx9_value **apObj = (jx9_value **)SySetBasePtr(&pCtx->sVar); + sxu32 n; + for( n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n ){ + if( apObj[n] == pValue ){ + jx9MemObjRelease(pValue); + SyMemBackendPoolFree(&pCtx->pVm->sAllocator, pValue); + /* Mark as released */ + apObj[n] = 0; + break; + } + } + } +} +/* + * Pop and release as many memory object from the operand stack. + */ +static void VmPopOperand( + jx9_value **ppTos, /* Operand stack */ + sxi32 nPop /* Total number of memory objects to pop */ + ) +{ + jx9_value *pTos = *ppTos; + while( nPop > 0 ){ + jx9MemObjRelease(pTos); + pTos--; + nPop--; + } + /* Top of the stack */ + *ppTos = pTos; +} +/* + * Reserve a memory object. + * Return a pointer to the raw jx9_value on success. NULL on failure. + */ +JX9_PRIVATE jx9_value * jx9VmReserveMemObj(jx9_vm *pVm,sxu32 *pIdx) +{ + jx9_value *pObj = 0; + VmSlot *pSlot; + sxu32 nIdx; + /* Check for a free slot */ + nIdx = SXU32_HIGH; /* cc warning */ + pSlot = (VmSlot *)SySetPop(&pVm->aFreeObj); + if( pSlot ){ + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx); + nIdx = pSlot->nIdx; + } + if( pObj == 0 ){ + /* Reserve a new memory object */ + pObj = VmReserveMemObj(&(*pVm), &nIdx); + if( pObj == 0 ){ + return 0; + } + } + /* Set a null default value */ + jx9MemObjInit(&(*pVm), pObj); + if( pIdx ){ + *pIdx = nIdx; + } + pObj->nIdx = nIdx; + return pObj; +} +/* + * Extract a variable value from the top active VM frame. + * Return a pointer to the variable value on success. + * NULL otherwise (non-existent variable/Out-of-memory, ...). + */ +static jx9_value * VmExtractMemObj( + jx9_vm *pVm, /* Target VM */ + const SyString *pName, /* Variable name */ + int bDup, /* True to duplicate variable name */ + int bCreate /* True to create the variable if non-existent */ + ) +{ + int bNullify = FALSE; + SyHashEntry *pEntry; + VmFrame *pFrame; + jx9_value *pObj; + sxu32 nIdx; + sxi32 rc; + /* Point to the top active frame */ + pFrame = pVm->pFrame; + /* Perform the lookup */ + if( pName == 0 || pName->nByte < 1 ){ + static const SyString sAnnon = { " " , sizeof(char) }; + pName = &sAnnon; + /* Always nullify the object */ + bNullify = TRUE; + bDup = FALSE; + } + /* Check the superglobals table first */ + pEntry = SyHashGet(&pVm->hSuper, (const void *)pName->zString, pName->nByte); + if( pEntry == 0 ){ + /* Query the top active frame */ + pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); + if( pEntry == 0 ){ + char *zName = (char *)pName->zString; + VmSlot sLocal; + if( !bCreate ){ + /* Do not create the variable, return NULL */ + return 0; + } + /* No such variable, automatically create a new one and install + * it in the current frame. + */ + pObj = jx9VmReserveMemObj(&(*pVm),&nIdx); + if( pObj == 0 ){ + return 0; + } + if( bDup ){ + /* Duplicate name */ + zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); + if( zName == 0 ){ + return 0; + } + } + /* Link to the top active VM frame */ + rc = SyHashInsert(&pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx)); + if( rc != SXRET_OK ){ + /* Return the slot to the free pool */ + sLocal.nIdx = nIdx; + sLocal.pUserData = 0; + SySetPut(&pVm->aFreeObj, (const void *)&sLocal); + return 0; + } + if( pFrame->pParent != 0 ){ + /* Local variable */ + sLocal.nIdx = nIdx; + SySetPut(&pFrame->sLocal, (const void *)&sLocal); + } + }else{ + /* Extract variable contents */ + nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( bNullify && pObj ){ + jx9MemObjRelease(pObj); + } + } + }else{ + /* Superglobal */ + nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + } + return pObj; +} +/* + * Extract a superglobal variable such as $_GET, $_POST, $_HEADERS, .... + * Return a pointer to the variable value on success.NULL otherwise. + */ +static jx9_value * VmExtractSuper( + jx9_vm *pVm, /* Target VM */ + const char *zName, /* Superglobal name: NOT NULL TERMINATED */ + sxu32 nByte /* zName length */ + ) +{ + SyHashEntry *pEntry; + jx9_value *pValue; + sxu32 nIdx; + /* Query the superglobal table */ + pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); + if( pEntry == 0 ){ + /* No such entry */ + return 0; + } + /* Extract the superglobal index in the global object pool */ + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + /* Extract the variable value */ + pValue = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + return pValue; +} +/* + * Perform a raw hashmap insertion. + * Refer to the [jx9VmConfigure()] implementation for additional information. + */ +static sxi32 VmHashmapInsert( + jx9_hashmap *pMap, /* Target hashmap */ + const char *zKey, /* Entry key */ + int nKeylen, /* zKey length*/ + const char *zData, /* Entry data */ + int nLen /* zData length */ + ) +{ + jx9_value sKey,sValue; + jx9_value *pKey; + sxi32 rc; + pKey = 0; + jx9MemObjInit(pMap->pVm, &sKey); + jx9MemObjInitFromString(pMap->pVm, &sValue, 0); + if( zKey ){ + if( nKeylen < 0 ){ + nKeylen = (int)SyStrlen(zKey); + } + jx9MemObjStringAppend(&sKey, zKey, (sxu32)nKeylen); + pKey = &sKey; + } + if( zData ){ + if( nLen < 0 ){ + /* Compute length automatically */ + nLen = (int)SyStrlen(zData); + } + jx9MemObjStringAppend(&sValue, zData, (sxu32)nLen); + } + /* Perform the insertion */ + rc = jx9HashmapInsert(&(*pMap),pKey,&sValue); + jx9MemObjRelease(&sKey); + jx9MemObjRelease(&sValue); + return rc; +} +/* Forward declaration */ +static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte); +/* + * Configure a working virtual machine instance. + * + * This routine is used to configure a JX9 virtual machine obtained by a prior + * successful call to one of the compile interface such as jx9_compile() + * jx9_compile_v2() or jx9_compile_file(). + * The second argument to this function is an integer configuration option + * that determines what property of the JX9 virtual machine is to be configured. + * Subsequent arguments vary depending on the configuration option in the second + * argument. There are many verbs but the most important are JX9_VM_CONFIG_OUTPUT, + * JX9_VM_CONFIG_HTTP_REQUEST and JX9_VM_CONFIG_ARGV_ENTRY. + * Refer to the official documentation for the list of allowed verbs. + */ +JX9_PRIVATE sxi32 jx9VmConfigure( + jx9_vm *pVm, /* Target VM */ + sxi32 nOp, /* Configuration verb */ + va_list ap /* Subsequent option arguments */ + ) +{ + sxi32 rc = SXRET_OK; + switch(nOp){ + case JX9_VM_CONFIG_OUTPUT: { + ProcConsumer xConsumer = va_arg(ap, ProcConsumer); + void *pUserData = va_arg(ap, void *); + /* VM output consumer callback */ +#ifdef UNTRUST + if( xConsumer == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + /* Install the output consumer */ + pVm->sVmConsumer.xConsumer = xConsumer; + pVm->sVmConsumer.pUserData = pUserData; + break; + } + case JX9_VM_CONFIG_IMPORT_PATH: { + /* Import path */ + const char *zPath; + SyString sPath; + zPath = va_arg(ap, const char *); +#if defined(UNTRUST) + if( zPath == 0 ){ + rc = SXERR_EMPTY; + break; + } +#endif + SyStringInitFromBuf(&sPath, zPath, SyStrlen(zPath)); + /* Remove trailing slashes and backslashes */ +#ifdef __WINNT__ + SyStringTrimTrailingChar(&sPath, '\\'); +#endif + SyStringTrimTrailingChar(&sPath, '/'); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sPath); + if( sPath.nByte > 0 ){ + /* Store the path in the corresponding conatiner */ + rc = SySetPut(&pVm->aPaths, (const void *)&sPath); + } + break; + } + case JX9_VM_CONFIG_ERR_REPORT: + /* Run-Time Error report */ + pVm->bErrReport = 1; + break; + case JX9_VM_CONFIG_RECURSION_DEPTH:{ + /* Recursion depth */ + int nDepth = va_arg(ap, int); + if( nDepth > 2 && nDepth < 1024 ){ + pVm->nMaxDepth = nDepth; + } + break; + } + case JX9_VM_OUTPUT_LENGTH: { + /* VM output length in bytes */ + sxu32 *pOut = va_arg(ap, sxu32 *); +#ifdef UNTRUST + if( pOut == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *pOut = pVm->nOutputLen; + break; + } + case JX9_VM_CONFIG_CREATE_VAR: { + /* Create a new superglobal/global variable */ + const char *zName = va_arg(ap, const char *); + jx9_value *pValue = va_arg(ap, jx9_value *); + SyHashEntry *pEntry; + jx9_value *pObj; + sxu32 nByte; + sxu32 nIdx; +#ifdef UNTRUST + if( SX_EMPTY_STR(zName) || pValue == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + nByte = SyStrlen(zName); + /* Check if the superglobal is already installed */ + pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); + if( pEntry ){ + /* Variable already installed */ + nIdx = SX_PTR_TO_INT(pEntry->pUserData); + /* Extract contents */ + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( pObj ){ + /* Overwrite old contents */ + jx9MemObjStore(pValue, pObj); + } + }else{ + /* Install a new variable */ + pObj = jx9VmReserveMemObj(&(*pVm),&nIdx); + if( pObj == 0 ){ + rc = SXERR_MEM; + break; + } + /* Copy value */ + jx9MemObjStore(pValue, pObj); + /* Install the superglobal */ + rc = SyHashInsert(&pVm->hSuper, (const void *)zName, nByte, SX_INT_TO_PTR(nIdx)); + } + break; + } + case JX9_VM_CONFIG_SERVER_ATTR: + case JX9_VM_CONFIG_ENV_ATTR: { + const char *zKey = va_arg(ap, const char *); + const char *zValue = va_arg(ap, const char *); + int nLen = va_arg(ap, int); + jx9_hashmap *pMap; + jx9_value *pValue; + if( nOp == JX9_VM_CONFIG_ENV_ATTR ){ + /* Extract the $_ENV superglobal */ + pValue = VmExtractSuper(&(*pVm), "_ENV", sizeof("_ENV")-1); + }else{ + /* Extract the $_SERVER superglobal */ + pValue = VmExtractSuper(&(*pVm), "_SERVER", sizeof("_SERVER")-1); + } + if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* No such entry */ + rc = SXERR_NOTFOUND; + break; + } + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pValue->x.pOther; + /* Perform the insertion */ + rc = VmHashmapInsert(pMap, zKey, -1, zValue, nLen); + break; + } + case JX9_VM_CONFIG_ARGV_ENTRY:{ + /* Script arguments */ + const char *zValue = va_arg(ap, const char *); + jx9_hashmap *pMap; + jx9_value *pValue; + /* Extract the $argv array */ + pValue = VmExtractSuper(&(*pVm), "argv", sizeof("argv")-1); + if( pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* No such entry */ + rc = SXERR_NOTFOUND; + break; + } + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pValue->x.pOther; + /* Perform the insertion */ + rc = VmHashmapInsert(pMap, 0, 0, zValue,-1); + if( rc == SXRET_OK && zValue && zValue[0] != 0 ){ + if( pMap->nEntry > 1 ){ + /* Append space separator first */ + SyBlobAppend(&pVm->sArgv, (const void *)" ", sizeof(char)); + } + SyBlobAppend(&pVm->sArgv, (const void *)zValue,SyStrlen(zValue)); + } + break; + } + case JX9_VM_CONFIG_EXEC_VALUE: { + /* Script return value */ + jx9_value **ppValue = va_arg(ap, jx9_value **); +#ifdef UNTRUST + if( ppValue == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *ppValue = &pVm->sExec; + break; + } + case JX9_VM_CONFIG_IO_STREAM: { + /* Register an IO stream device */ + const jx9_io_stream *pStream = va_arg(ap, const jx9_io_stream *); + /* Make sure we are dealing with a valid IO stream */ + if( pStream == 0 || pStream->zName == 0 || pStream->zName[0] == 0 || + pStream->xOpen == 0 || pStream->xRead == 0 ){ + /* Invalid stream */ + rc = SXERR_INVALID; + break; + } + if( pVm->pDefStream == 0 && SyStrnicmp(pStream->zName, "file", sizeof("file")-1) == 0 ){ + /* Make the 'file://' stream the defaut stream device */ + pVm->pDefStream = pStream; + } + /* Insert in the appropriate container */ + rc = SySetPut(&pVm->aIOstream, (const void *)&pStream); + break; + } + case JX9_VM_CONFIG_EXTRACT_OUTPUT: { + /* Point to the VM internal output consumer buffer */ + const void **ppOut = va_arg(ap, const void **); + unsigned int *pLen = va_arg(ap, unsigned int *); +#ifdef UNTRUST + if( ppOut == 0 || pLen == 0 ){ + rc = SXERR_CORRUPT; + break; + } +#endif + *ppOut = SyBlobData(&pVm->sConsumer); + *pLen = SyBlobLength(&pVm->sConsumer); + break; + } + case JX9_VM_CONFIG_HTTP_REQUEST:{ + /* Raw HTTP request*/ + const char *zRequest = va_arg(ap, const char *); + int nByte = va_arg(ap, int); + if( SX_EMPTY_STR(zRequest) ){ + rc = SXERR_EMPTY; + break; + } + if( nByte < 0 ){ + /* Compute length automatically */ + nByte = (int)SyStrlen(zRequest); + } + /* Process the request */ + rc = VmHttpProcessRequest(&(*pVm), zRequest, nByte); + break; + } + default: + /* Unknown configuration option */ + rc = SXERR_UNKNOWN; + break; + } + return rc; +} +/* Forward declaration */ +static const char * VmInstrToString(sxi32 nOp); +/* + * This routine is used to dump JX9 bytecode instructions to a human readable + * format. + * The dump is redirected to the given consumer callback which is responsible + * of consuming the generated dump perhaps redirecting it to its standard output + * (STDOUT). + */ +static sxi32 VmByteCodeDump( + SySet *pByteCode, /* Bytecode container */ + ProcConsumer xConsumer, /* Dump consumer callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + static const char zDump[] = { + "====================================================\n" + "JX9 VM Dump Copyright (C) 2012-2013 Symisc Systems\n" + " http://jx9.symisc.net/\n" + "====================================================\n" + }; + VmInstr *pInstr, *pEnd; + sxi32 rc = SXRET_OK; + sxu32 n; + /* Point to the JX9 instructions */ + pInstr = (VmInstr *)SySetBasePtr(pByteCode); + pEnd = &pInstr[SySetUsed(pByteCode)]; + n = 0; + xConsumer((const void *)zDump, sizeof(zDump)-1, pUserData); + /* Dump instructions */ + for(;;){ + if( pInstr >= pEnd ){ + /* No more instructions */ + break; + } + /* Format and call the consumer callback */ + rc = SyProcFormat(xConsumer, pUserData, "%s %8d %8u %#8x [%u]\n", + VmInstrToString(pInstr->iOp), pInstr->iP1, pInstr->iP2, + SX_PTR_TO_INT(pInstr->p3), n); + if( rc != SXRET_OK ){ + /* Consumer routine request an operation abort */ + return rc; + } + ++n; + pInstr++; /* Next instruction in the stream */ + } + return rc; +} +/* + * Consume a generated run-time error message by invoking the VM output + * consumer callback. + */ +static sxi32 VmCallErrorHandler(jx9_vm *pVm, SyBlob *pMsg) +{ + jx9_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + /* Append a new line */ +#ifdef __WINNT__ + SyBlobAppend(pMsg, "\r\n", sizeof("\r\n")-1); +#else + SyBlobAppend(pMsg, "\n", sizeof(char)); +#endif + /* Invoke the output consumer callback */ + rc = pCons->xConsumer(SyBlobData(pMsg), SyBlobLength(pMsg), pCons->pUserData); + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(pMsg); + + return rc; +} +/* + * Throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error()] for additional + * information. + */ +JX9_PRIVATE sxi32 jx9VmThrowError( + jx9_vm *pVm, /* Target VM */ + SyString *pFuncName, /* Function name. NULL otherwise */ + sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice]*/ + const char *zMessage /* Null terminated error message */ + ) +{ + SyBlob *pWorker = &pVm->sWorker; + SyString *pFile; + char *zErr; + sxi32 rc; + if( !pVm->bErrReport ){ + /* Don't bother reporting errors */ + return SXRET_OK; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Peek the processed file if available */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + /* Append file name */ + SyBlobAppend(pWorker, pFile->zString, pFile->nByte); + SyBlobAppend(pWorker, (const void *)" ", sizeof(char)); + } + zErr = "Error: "; + switch(iErr){ + case JX9_CTX_WARNING: zErr = "Warning: "; break; + case JX9_CTX_NOTICE: zErr = "Notice: "; break; + default: + iErr = JX9_CTX_ERR; + break; + } + SyBlobAppend(pWorker, zErr, SyStrlen(zErr)); + if( pFuncName ){ + /* Append function name first */ + SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte); + SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1); + } + SyBlobAppend(pWorker, zMessage, SyStrlen(zMessage)); + /* Consume the error message */ + rc = VmCallErrorHandler(&(*pVm), pWorker); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error_format()] for additional + * information. + */ +static sxi32 VmThrowErrorAp( + jx9_vm *pVm, /* Target VM */ + SyString *pFuncName, /* Function name. NULL otherwise */ + sxi32 iErr, /* Severity level: [i.e: Error, Warning or Notice] */ + const char *zFormat, /* Format message */ + va_list ap /* Variable list of arguments */ + ) +{ + SyBlob *pWorker = &pVm->sWorker; + SyString *pFile; + char *zErr; + sxi32 rc; + if( !pVm->bErrReport ){ + /* Don't bother reporting errors */ + return SXRET_OK; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Peek the processed file if available */ + pFile = (SyString *)SySetPeek(&pVm->aFiles); + if( pFile ){ + /* Append file name */ + SyBlobAppend(pWorker, pFile->zString, pFile->nByte); + SyBlobAppend(pWorker, (const void *)" ", sizeof(char)); + } + zErr = "Error: "; + switch(iErr){ + case JX9_CTX_WARNING: zErr = "Warning: "; break; + case JX9_CTX_NOTICE: zErr = "Notice: "; break; + default: + iErr = JX9_CTX_ERR; + break; + } + SyBlobAppend(pWorker, zErr, SyStrlen(zErr)); + if( pFuncName ){ + /* Append function name first */ + SyBlobAppend(pWorker, pFuncName->zString, pFuncName->nByte); + SyBlobAppend(pWorker, "(): ", sizeof("(): ")-1); + } + SyBlobFormatAp(pWorker, zFormat, ap); + /* Consume the error message */ + rc = VmCallErrorHandler(&(*pVm), pWorker); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error_format()] for additional + * information. + * ------------------------------------ + * Simple boring wrapper function. + * ------------------------------------ + */ +static sxi32 VmErrorFormat(jx9_vm *pVm, sxi32 iErr, const char *zFormat, ...) +{ + va_list ap; + sxi32 rc; + va_start(ap, zFormat); + rc = VmThrowErrorAp(&(*pVm), 0, iErr, zFormat, ap); + va_end(ap); + return rc; +} +/* + * Format and throw a run-time error and invoke the supplied VM output consumer callback. + * Refer to the implementation of [jx9_context_throw_error_format()] for additional + * information. + * ------------------------------------ + * Simple boring wrapper function. + * ------------------------------------ + */ +JX9_PRIVATE sxi32 jx9VmThrowErrorAp(jx9_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap) +{ + sxi32 rc; + rc = VmThrowErrorAp(&(*pVm), &(*pFuncName), iErr, zFormat, ap); + return rc; +} +/* Forward declaration */ +static sxi32 VmLocalExec(jx9_vm *pVm,SySet *pByteCode,jx9_value *pResult); +/* + * Execute as much of a JX9 bytecode program as we can then return. + * + * [jx9VmMakeReady()] must be called before this routine in order to + * close the program with a final OP_DONE and to set up the default + * consumer routines and other stuff. Refer to the implementation + * of [jx9VmMakeReady()] for additional information. + * If the installed VM output consumer callback ever returns JX9_ABORT + * then the program execution is halted. + * After this routine has finished, [jx9VmRelease()] or [jx9VmReset()] + * should be used respectively to clean up the mess that was left behind + * or to reset the VM to it's initial state. + */ +static sxi32 VmByteCodeExec( + jx9_vm *pVm, /* Target VM */ + VmInstr *aInstr, /* JX9 bytecode program */ + jx9_value *pStack, /* Operand stack */ + int nTos, /* Top entry in the operand stack (usually -1) */ + jx9_value *pResult /* Store program return value here. NULL otherwise */ + ) +{ + VmInstr *pInstr; + jx9_value *pTos; + SySet aArg; + sxi32 pc; + sxi32 rc; + /* Argument container */ + SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *)); + if( nTos < 0 ){ + pTos = &pStack[-1]; + }else{ + pTos = &pStack[nTos]; + } + pc = 0; + /* Execute as much as we can */ + for(;;){ + /* Fetch the instruction to execute */ + pInstr = &aInstr[pc]; + rc = SXRET_OK; +/* + * What follows here is a massive switch statement where each case implements a + * separate instruction in the virtual machine. If we follow the usual + * indentation convention each case should be indented by 6 spaces. But + * that is a lot of wasted space on the left margin. So the code within + * the switch statement will break with convention and be flush-left. + */ + switch(pInstr->iOp){ +/* + * DONE: P1 * * + * + * Program execution completed: Clean up the mess left behind + * and return immediately. + */ +case JX9_OP_DONE: + if( pInstr->iP1 ){ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pResult ){ + /* Execution result */ + jx9MemObjStore(pTos, pResult); + } + VmPopOperand(&pTos, 1); + } + goto Done; +/* + * HALT: P1 * * + * + * Program execution aborted: Clean up the mess left behind + * and abort immediately. + */ +case JX9_OP_HALT: + if( pInstr->iP1 ){ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pTos->iFlags & MEMOBJ_STRING ){ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + /* Output the exit message */ + pVm->sVmConsumer.xConsumer(SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob), + pVm->sVmConsumer.pUserData); + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&pTos->sBlob); + } + }else if(pTos->iFlags & MEMOBJ_INT ){ + /* Record exit status */ + pVm->iExitStatus = (sxi32)pTos->x.iVal; + } + VmPopOperand(&pTos, 1); + } + goto Abort; +/* + * JMP: * P2 * + * + * Unconditional jump: The next instruction executed will be + * the one at index P2 from the beginning of the program. + */ +case JX9_OP_JMP: + pc = pInstr->iP2 - 1; + break; +/* + * JZ: P1 P2 * + * + * Take the jump if the top value is zero (FALSE jump).Pop the top most + * entry in the stack if P1 is zero. + */ +case JX9_OP_JZ: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Get a boolean value */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if( !pTos->x.iVal ){ + /* Take the jump */ + pc = pInstr->iP2 - 1; + } + if( !pInstr->iP1 ){ + VmPopOperand(&pTos, 1); + } + break; +/* + * JNZ: P1 P2 * + * + * Take the jump if the top value is not zero (TRUE jump).Pop the top most + * entry in the stack if P1 is zero. + */ +case JX9_OP_JNZ: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Get a boolean value */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if( pTos->x.iVal ){ + /* Take the jump */ + pc = pInstr->iP2 - 1; + } + if( !pInstr->iP1 ){ + VmPopOperand(&pTos, 1); + } + break; +/* + * NOOP: * * * + * + * Do nothing. This instruction is often useful as a jump + * destination. + */ +case JX9_OP_NOOP: + break; +/* + * POP: P1 * * + * + * Pop P1 elements from the operand stack. + */ +case JX9_OP_POP: { + sxi32 n = pInstr->iP1; + if( &pTos[-n+1] < pStack ){ + /* TICKET 1433-51 Stack underflow must be handled at run-time */ + n = (sxi32)(pTos - pStack); + } + VmPopOperand(&pTos, n); + break; + } +/* + * CVT_INT: * * * + * + * Force the top of the stack to be an integer. + */ +case JX9_OP_CVT_INT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_INT); + break; +/* + * CVT_REAL: * * * + * + * Force the top of the stack to be a real. + */ +case JX9_OP_CVT_REAL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_REAL); + break; +/* + * CVT_STR: * * * + * + * Force the top of the stack to be a string. + */ +case JX9_OP_CVT_STR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pTos); + } + break; +/* + * CVT_BOOL: * * * + * + * Force the top of the stack to be a boolean. + */ +case JX9_OP_CVT_BOOL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + break; +/* + * CVT_NULL: * * * + * + * Nullify the top of the stack. + */ +case JX9_OP_CVT_NULL: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + jx9MemObjRelease(pTos); + break; +/* + * CVT_NUMC: * * * + * + * Force the top of the stack to be a numeric type (integer, real or both). + */ +case JX9_OP_CVT_NUMC: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric cast */ + jx9MemObjToNumeric(pTos); + break; +/* + * CVT_ARRAY: * * * + * + * Force the top of the stack to be a hashmap aka 'array'. + */ +case JX9_OP_CVT_ARRAY: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a hashmap cast */ + rc = jx9MemObjToHashmap(pTos); + if( rc != SXRET_OK ){ + /* Not so fatal, emit a simple warning */ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING, + "JX9 engine is running out of memory while performing an array cast"); + } + break; +/* + * LOADC P1 P2 * + * + * Load a constant [i.e: JX9_EOL, JX9_OS, __TIME__, ...] indexed at P2 in the constant pool. + * If P1 is set, then this constant is candidate for expansion via user installable callbacks. + */ +case JX9_OP_LOADC: { + jx9_value *pObj; + /* Reserve a room */ + pTos++; + if( (pObj = (jx9_value *)SySetAt(&pVm->aLitObj, pInstr->iP2)) != 0 ){ + if( pInstr->iP1 == 1 && SyBlobLength(&pObj->sBlob) <= 64 ){ + SyHashEntry *pEntry; + /* Candidate for expansion via user defined callbacks */ + pEntry = SyHashGet(&pVm->hConstant, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob)); + if( pEntry ){ + jx9_constant *pCons = (jx9_constant *)pEntry->pUserData; + /* Set a NULL default value */ + MemObjSetType(pTos, MEMOBJ_NULL); + SyBlobReset(&pTos->sBlob); + /* Invoke the callback and deal with the expanded value */ + pCons->xExpand(pTos, pCons->pUserData); + /* Mark as constant */ + pTos->nIdx = SXU32_HIGH; + break; + } + } + jx9MemObjLoad(pObj, pTos); + }else{ + /* Set a NULL value */ + MemObjSetType(pTos, MEMOBJ_NULL); + } + /* Mark as constant */ + pTos->nIdx = SXU32_HIGH; + break; + } +/* + * LOAD: P1 * P3 + * + * Load a variable where it's name is taken from the top of the stack or + * from the P3 operand. + * If P1 is set, then perform a lookup only.In other words do not create + * the variable if non existent and push the NULL constant instead. + */ +case JX9_OP_LOAD:{ + jx9_value *pObj; + SyString sName; + if( pInstr->p3 == 0 ){ + /* Take the variable name from the top of the stack */ +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a string cast */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pTos); + } + SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + }else{ + SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3)); + /* Reserve a room for the target object */ + pTos++; + } + /* Extract the requested memory object */ + pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, pInstr->iP1 != 1); + if( pObj == 0 ){ + if( pInstr->iP1 ){ + /* Variable not found, load NULL */ + if( !pInstr->p3 ){ + jx9MemObjRelease(pTos); + }else{ + MemObjSetType(pTos, MEMOBJ_NULL); + } + pTos->nIdx = SXU32_HIGH; /* Mark as constant */ + break; + }else{ + /* Fatal error */ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName); + goto Abort; + } + } + /* Load variable contents */ + jx9MemObjLoad(pObj, pTos); + pTos->nIdx = pObj->nIdx; + break; + } +/* + * LOAD_MAP P1 * * + * + * Allocate a new empty hashmap (array in the JX9 jargon) and push it on the stack. + * If the P1 operand is greater than zero then pop P1 elements from the + * stack and insert them (key => value pair) in the new hashmap. + */ +case JX9_OP_LOAD_MAP: { + jx9_hashmap *pMap; + int is_json_object; /* TRUE if we are dealing with a JSON object */ + int iIncr = 1; + /* Allocate a new hashmap instance */ + pMap = jx9NewHashmap(&(*pVm), 0, 0); + if( pMap == 0 ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "Fatal, JX9 engine is running out of memory while loading JSON array/object at instruction #:%d", pc); + goto Abort; + } + is_json_object = 0; + if( pInstr->iP2 ){ + /* JSON object, record that */ + pMap->iFlags |= HASHMAP_JSON_OBJECT; + is_json_object = 1; + iIncr = 2; + } + if( pInstr->iP1 > 0 ){ + jx9_value *pEntry = &pTos[-pInstr->iP1+1]; /* Point to the first entry */ + /* Perform the insertion */ + while( pEntry <= pTos ){ + /* Standard insertion */ + jx9HashmapInsert(pMap, + is_json_object ? pEntry : 0 /* Automatic index assign */, + is_json_object ? &pEntry[1] : pEntry + ); + /* Next pair on the stack */ + pEntry += iIncr; + } + /* Pop P1 elements */ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Push the hashmap */ + pTos++; + pTos->x.pOther = pMap; + MemObjSetType(pTos, MEMOBJ_HASHMAP); + break; + } +/* + * LOAD_IDX: P1 P2 * + * + * Load a hasmap entry where it's index (either numeric or string) is taken + * from the stack. + * If the index does not refer to a valid element, then push the NULL constant + * instead. + */ +case JX9_OP_LOAD_IDX: { + jx9_hashmap_node *pNode = 0; /* cc warning */ + jx9_hashmap *pMap = 0; + jx9_value *pIdx; + pIdx = 0; + if( pInstr->iP1 == 0 ){ + if( !pInstr->iP2){ + /* No available index, load NULL */ + if( pTos >= pStack ){ + jx9MemObjRelease(pTos); + }else{ + /* TICKET 1433-020: Empty stack */ + pTos++; + MemObjSetType(pTos, MEMOBJ_NULL); + pTos->nIdx = SXU32_HIGH; + } + /* Emit a notice */ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_NOTICE, + "JSON Array/Object: Attempt to access an undefined member, JX9 is loading NULL"); + break; + } + }else{ + pIdx = pTos; + pTos--; + } + if( pTos->iFlags & MEMOBJ_STRING ){ + /* String access */ + if( pIdx ){ + sxu32 nOfft; + if( (pIdx->iFlags & MEMOBJ_INT) == 0 ){ + /* Force an int cast */ + jx9MemObjToInteger(pIdx); + } + nOfft = (sxu32)pIdx->x.iVal; + if( nOfft >= SyBlobLength(&pTos->sBlob) ){ + /* Invalid offset, load null */ + jx9MemObjRelease(pTos); + }else{ + const char *zData = (const char *)SyBlobData(&pTos->sBlob); + int c = zData[nOfft]; + jx9MemObjRelease(pTos); + MemObjSetType(pTos, MEMOBJ_STRING); + SyBlobAppend(&pTos->sBlob, (const void *)&c, sizeof(char)); + } + }else{ + /* No available index, load NULL */ + MemObjSetType(pTos, MEMOBJ_NULL); + } + break; + } + if( pInstr->iP2 && (pTos->iFlags & MEMOBJ_HASHMAP) == 0 ){ + if( pTos->nIdx != SXU32_HIGH ){ + jx9_value *pObj; + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjToHashmap(pObj); + jx9MemObjLoad(pObj, pTos); + } + } + } + rc = SXERR_NOTFOUND; /* Assume the index is invalid */ + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pTos->x.pOther; + if( pIdx ){ + /* Load the desired entry */ + rc = jx9HashmapLookup(pMap, pIdx, &pNode); + } + if( rc != SXRET_OK && pInstr->iP2 ){ + /* Create a new empty entry */ + rc = jx9HashmapInsert(pMap, pIdx, 0); + if( rc == SXRET_OK ){ + /* Point to the last inserted entry */ + pNode = pMap->pLast; + } + } + } + if( pIdx ){ + jx9MemObjRelease(pIdx); + } + if( rc == SXRET_OK ){ + /* Load entry contents */ + if( pMap->iRef < 2 ){ + /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy + * of the entry value, rather than pointing to it. + */ + pTos->nIdx = SXU32_HIGH; + jx9HashmapExtractNodeValue(pNode, pTos, TRUE); + }else{ + pTos->nIdx = pNode->nValIdx; + jx9HashmapExtractNodeValue(pNode, pTos, FALSE); + jx9HashmapUnref(pMap); + } + }else{ + /* No such entry, load NULL */ + jx9MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + } + break; + } +/* + * STORE * P2 P3 + * + * Perform a store (Assignment) operation. + */ +case JX9_OP_STORE: { + jx9_value *pObj; + SyString sName; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( pInstr->iP2 ){ + sxu32 nIdx; + /* Member store operation */ + nIdx = pTos->nIdx; + VmPopOperand(&pTos, 1); + if( nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, + "Cannot perform assignment on a constant object attribute, JX9 is loading NULL"); + pTos->nIdx = SXU32_HIGH; + }else{ + /* Point to the desired memory object */ + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( pObj ){ + /* Perform the store operation */ + jx9MemObjStore(pTos, pObj); + } + } + break; + }else if( pInstr->p3 == 0 ){ + /* Take the variable name from the next on the stack */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + pTos--; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + }else{ + SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3)); + } + /* Extract the desired variable and if not available dynamically create it */ + pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE, TRUE); + if( pObj == 0 ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "Fatal, JX9 engine is running out of memory while loading variable '%z'", &sName); + goto Abort; + } + if( !pInstr->p3 ){ + jx9MemObjRelease(&pTos[1]); + } + /* Perform the store operation */ + jx9MemObjStore(pTos, pObj); + break; + } +/* + * STORE_IDX: P1 * P3 + * + * Perfrom a store operation an a hashmap entry. + */ +case JX9_OP_STORE_IDX: { + jx9_hashmap *pMap = 0; /* cc warning */ + jx9_value *pKey; + sxu32 nIdx; + if( pInstr->iP1 ){ + /* Key is next on stack */ + pKey = pTos; + pTos--; + }else{ + pKey = 0; + } + nIdx = pTos->nIdx; + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Hashmap already loaded */ + pMap = (jx9_hashmap *)pTos->x.pOther; + if( pMap->iRef < 2 ){ + /* TICKET 1433-48: Prevent garbage collection */ + pMap->iRef = 2; + } + }else{ + jx9_value *pObj; + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx); + if( pObj == 0 ){ + if( pKey ){ + jx9MemObjRelease(pKey); + } + VmPopOperand(&pTos, 1); + break; + } + /* Phase#1: Load the array */ + if( (pObj->iFlags & MEMOBJ_STRING) ){ + VmPopOperand(&pTos, 1); + if( (pTos->iFlags&MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + if( pKey == 0 ){ + /* Append string */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + } + }else{ + sxu32 nOfft; + if((pKey->iFlags & MEMOBJ_INT)){ + /* Force an int cast */ + jx9MemObjToInteger(pKey); + } + nOfft = (sxu32)pKey->x.iVal; + if( nOfft < SyBlobLength(&pObj->sBlob) && SyBlobLength(&pTos->sBlob) > 0 ){ + const char *zBlob = (const char *)SyBlobData(&pTos->sBlob); + char *zData = (char *)SyBlobData(&pObj->sBlob); + zData[nOfft] = zBlob[0]; + }else{ + if( SyBlobLength(&pTos->sBlob) >= sizeof(char) ){ + /* Perform an append operation */ + SyBlobAppend(&pObj->sBlob, SyBlobData(&pTos->sBlob), sizeof(char)); + } + } + } + if( pKey ){ + jx9MemObjRelease(pKey); + } + break; + }else if( (pObj->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* Force a hashmap cast */ + rc = jx9MemObjToHashmap(pObj); + if( rc != SXRET_OK ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Fatal, JX9 engine is running out of memory while creating a new array"); + goto Abort; + } + } + pMap = (jx9_hashmap *)pObj->x.pOther; + } + VmPopOperand(&pTos, 1); + /* Phase#2: Perform the insertion */ + jx9HashmapInsert(pMap, pKey, pTos); + if( pKey ){ + jx9MemObjRelease(pKey); + } + break; + } +/* + * INCR: P1 * * + * + * Force a numeric cast and increment the top of the stack by 1. + * If the P1 operand is set then perform a duplication of the top of + * the stack and increment after that. + */ +case JX9_OP_INCR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES)) == 0 ){ + if( pTos->nIdx != SXU32_HIGH ){ + jx9_value *pObj; + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pObj); + if( pObj->iFlags & MEMOBJ_REAL ){ + pObj->x.rVal++; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pObj->x.iVal++; + MemObjSetType(pTos, MEMOBJ_INT); + } + if( pInstr->iP1 ){ + /* Pre-icrement */ + jx9MemObjStore(pObj, pTos); + } + } + }else{ + if( pInstr->iP1 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pTos); + /* Pre-increment */ + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal++; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pTos->x.iVal++; + MemObjSetType(pTos, MEMOBJ_INT); + } + } + } + } + break; +/* + * DECR: P1 * * + * + * Force a numeric cast and decrement the top of the stack by 1. + * If the P1 operand is set then perform a duplication of the top of the stack + * and decrement after that. + */ +case JX9_OP_DECR: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( (pTos->iFlags & (MEMOBJ_HASHMAP|MEMOBJ_RES|MEMOBJ_NULL)) == 0 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pTos); + if( pTos->nIdx != SXU32_HIGH ){ + jx9_value *pObj; + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + /* Force a numeric cast */ + jx9MemObjToNumeric(pObj); + if( pObj->iFlags & MEMOBJ_REAL ){ + pObj->x.rVal--; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pObj->x.iVal--; + MemObjSetType(pTos, MEMOBJ_INT); + } + if( pInstr->iP1 ){ + /* Pre-icrement */ + jx9MemObjStore(pObj, pTos); + } + } + }else{ + if( pInstr->iP1 ){ + /* Pre-increment */ + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal--; + /* Try to get an integer representation */ + jx9MemObjTryInteger(pTos); + }else{ + pTos->x.iVal--; + MemObjSetType(pTos, MEMOBJ_INT); + } + } + } + } + break; +/* + * UMINUS: * * * + * + * Perform a unary minus operation. + */ +case JX9_OP_UMINUS: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric (integer, real or both) cast */ + jx9MemObjToNumeric(pTos); + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal = -pTos->x.rVal; + } + if( pTos->iFlags & MEMOBJ_INT ){ + pTos->x.iVal = -pTos->x.iVal; + } + break; +/* + * UPLUS: * * * + * + * Perform a unary plus operation. + */ +case JX9_OP_UPLUS: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a numeric (integer, real or both) cast */ + jx9MemObjToNumeric(pTos); + if( pTos->iFlags & MEMOBJ_REAL ){ + pTos->x.rVal = +pTos->x.rVal; + } + if( pTos->iFlags & MEMOBJ_INT ){ + pTos->x.iVal = +pTos->x.iVal; + } + break; +/* + * OP_LNOT: * * * + * + * Interpret the top of the stack as a boolean value. Replace it + * with its complement. + */ +case JX9_OP_LNOT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if( (pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + pTos->x.iVal = !pTos->x.iVal; + break; +/* + * OP_BITNOT: * * * + * + * Interpret the top of the stack as an value.Replace it + * with its ones-complement. + */ +case JX9_OP_BITNOT: +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + /* Force an integer cast */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + pTos->x.iVal = ~pTos->x.iVal; + break; +/* OP_MUL * * * + * OP_MUL_STORE * * * + * + * Pop the top two elements from the stack, multiply them together, + * and push the result back onto the stack. + */ +case JX9_OP_MUL: +case JX9_OP_MUL_STORE: { + jx9_value *pNos = &pTos[-1]; + /* Force the operand to be numeric */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + jx9MemObjToNumeric(pTos); + jx9MemObjToNumeric(pNos); + /* Perform the requested operation */ + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + jx9_real a, b, r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + a = pNos->x.rVal; + b = pTos->x.rVal; + r = a * b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a, b, r; + a = pNos->x.iVal; + b = pTos->x.iVal; + r = a * b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + } + if( pInstr->iOp == JX9_OP_MUL_STORE ){ + jx9_value *pObj; + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_ADD * * * + * + * Pop the top two elements from the stack, add them together, + * and push the result back onto the stack. + */ +case JX9_OP_ADD:{ + jx9_value *pNos = &pTos[-1]; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Perform the addition */ + jx9MemObjAdd(pNos, pTos, FALSE); + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_ADD_STORE * * * + * + * Pop the top two elements from the stack, add them together, + * and push the result back onto the stack. + */ +case JX9_OP_ADD_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxu32 nIdx; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Perform the addition */ + nIdx = pTos->nIdx; + jx9MemObjAdd(pTos, pNos, TRUE); + /* Peform the store operation */ + if( nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nIdx)) != 0 ){ + jx9MemObjStore(pTos, pObj); + } + /* Ticket 1433-35: Perform a stack dup */ + jx9MemObjStore(pTos, pNos); + VmPopOperand(&pTos, 1); + break; + } +/* OP_SUB * * * + * + * Pop the top two elements from the stack, subtract the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result back onto the stack. + */ +case JX9_OP_SUB: { + jx9_value *pNos = &pTos[-1]; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + jx9_real a, b, r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + a = pNos->x.rVal; + b = pTos->x.rVal; + r = a - b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a, b, r; + a = pNos->x.iVal; + b = pTos->x.iVal; + r = a - b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_SUB_STORE * * * + * + * Pop the top two elements from the stack, subtract the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result back onto the stack. + */ +case JX9_OP_SUB_STORE: { + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if( MEMOBJ_REAL & (pTos->iFlags|pNos->iFlags) ){ + /* Floating point arithemic */ + jx9_real a, b, r; + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + a = pTos->x.rVal; + b = pNos->x.rVal; + r = a - b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + }else{ + /* Integer arithmetic */ + sxi64 a, b, r; + a = pTos->x.iVal; + b = pNos->x.iVal; + r = a - b; + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + } + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } + +/* + * OP_MOD * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the remainder after division + * onto the stack. + * Note: Only integer arithemtic is allowed. + */ +case JX9_OP_MOD:{ + jx9_value *pNos = &pTos[-1]; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = pTos->x.iVal; + if( b == 0 ){ + r = 0; + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a); + /* goto Abort; */ + }else{ + r = a%b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_MOD_STORE * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the remainder after division + * onto the stack. + * Note: Only integer arithemtic is allowed. + */ +case JX9_OP_MOD_STORE: { + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = pNos->x.iVal; + if( b == 0 ){ + r = 0; + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd%%0", a); + /* goto Abort; */ + }else{ + r = a%b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_DIV * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result onto the stack. + * Note: Only floating point arithemtic is allowed. + */ +case JX9_OP_DIV:{ + jx9_value *pNos = &pTos[-1]; + jx9_real a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be real */ + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + /* Perform the requested operation */ + a = pNos->x.rVal; + b = pTos->x.rVal; + if( b == 0 ){ + /* Division by zero */ + r = 0; + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Division by zero"); + /* goto Abort; */ + }else{ + r = a/b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + } + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_DIV_STORE * * * + * + * Pop the top two elements from the stack, divide the + * first (what was next on the stack) from the second (the + * top of the stack) and push the result onto the stack. + * Note: Only floating point arithemtic is allowed. + */ +case JX9_OP_DIV_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + jx9_real a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be real */ + if( (pTos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pTos); + } + if( (pNos->iFlags & MEMOBJ_REAL) == 0 ){ + jx9MemObjToReal(pNos); + } + /* Perform the requested operation */ + a = pTos->x.rVal; + b = pNos->x.rVal; + if( b == 0 ){ + /* Division by zero */ + r = 0; + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "Division by zero %qd/0", a); + /* goto Abort; */ + }else{ + r = a/b; + /* Push the result */ + pNos->x.rVal = r; + MemObjSetType(pNos, MEMOBJ_REAL); + /* Try to get an integer representation */ + jx9MemObjTryInteger(pNos); + } + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_BAND * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise AND of the + * two elements. +*/ +/* OP_BOR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise OR of the + * two elements. + */ +/* OP_BXOR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise XOR of the + * two elements. + */ +case JX9_OP_BAND: +case JX9_OP_BOR: +case JX9_OP_BXOR:{ + jx9_value *pNos = &pTos[-1]; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = pTos->x.iVal; + switch(pInstr->iOp){ + case JX9_OP_BOR_STORE: + case JX9_OP_BOR: r = a|b; break; + case JX9_OP_BXOR_STORE: + case JX9_OP_BXOR: r = a^b; break; + case JX9_OP_BAND_STORE: + case JX9_OP_BAND: + default: r = a&b; break; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + VmPopOperand(&pTos, 1); + break; + } +/* OP_BAND_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise AND of the + * two elements. +*/ +/* OP_BOR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise OR of the + * two elements. + */ +/* OP_BXOR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the bit-wise XOR of the + * two elements. + */ +case JX9_OP_BAND_STORE: +case JX9_OP_BOR_STORE: +case JX9_OP_BXOR_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxi64 a, b, r; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = pNos->x.iVal; + switch(pInstr->iOp){ + case JX9_OP_BOR_STORE: + case JX9_OP_BOR: r = a|b; break; + case JX9_OP_BXOR_STORE: + case JX9_OP_BXOR: r = a^b; break; + case JX9_OP_BAND_STORE: + case JX9_OP_BAND: + default: r = a&b; break; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* OP_SHL * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * left by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +/* OP_SHR * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * right by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +case JX9_OP_SHL: +case JX9_OP_SHR: { + jx9_value *pNos = &pTos[-1]; + sxi64 a, r; + sxi32 b; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pNos->x.iVal; + b = (sxi32)pTos->x.iVal; + if( pInstr->iOp == JX9_OP_SHL ){ + r = a << b; + }else{ + r = a >> b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + VmPopOperand(&pTos, 1); + break; + } +/* OP_SHL_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * left by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +/* OP_SHR_STORE * * * + * + * Pop the top two elements from the stack. Convert both elements + * to integers. Push back onto the stack the second element shifted + * right by N bits where N is the top element on the stack. + * Note: Only integer arithmetic is allowed. + */ +case JX9_OP_SHL_STORE: +case JX9_OP_SHR_STORE: { + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; + sxi64 a, r; + sxi32 b; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force the operands to be integer */ + if( (pTos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pTos); + } + if( (pNos->iFlags & MEMOBJ_INT) == 0 ){ + jx9MemObjToInteger(pNos); + } + /* Perform the requested operation */ + a = pTos->x.iVal; + b = (sxi32)pNos->x.iVal; + if( pInstr->iOp == JX9_OP_SHL_STORE ){ + r = a << b; + }else{ + r = a >> b; + } + /* Push the result */ + pNos->x.iVal = r; + MemObjSetType(pNos, MEMOBJ_INT); + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pNos, pObj); + } + VmPopOperand(&pTos, 1); + break; + } +/* CAT: P1 * * + * + * Pop P1 elements from the stack. Concatenate them togeher and push the result + * back. + */ +case JX9_OP_CAT:{ + jx9_value *pNos, *pCur; + if( pInstr->iP1 < 1 ){ + pNos = &pTos[-1]; + }else{ + pNos = &pTos[-pInstr->iP1+1]; + } +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a string cast */ + if( (pNos->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pNos); + } + pCur = &pNos[1]; + while( pCur <= pTos ){ + if( (pCur->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pCur); + } + /* Perform the concatenation */ + if( SyBlobLength(&pCur->sBlob) > 0 ){ + jx9MemObjStringAppend(pNos, (const char *)SyBlobData(&pCur->sBlob), SyBlobLength(&pCur->sBlob)); + } + SyBlobRelease(&pCur->sBlob); + pCur++; + } + pTos = pNos; + break; + } +/* CAT_STORE: * * * + * + * Pop two elements from the stack. Concatenate them togeher and push the result + * back. + */ +case JX9_OP_CAT_STORE:{ + jx9_value *pNos = &pTos[-1]; + jx9_value *pObj; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + if((pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + if((pNos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pNos); + } + /* Perform the concatenation (Reverse order) */ + if( SyBlobLength(&pNos->sBlob) > 0 ){ + jx9MemObjStringAppend(pTos, (const char *)SyBlobData(&pNos->sBlob), SyBlobLength(&pNos->sBlob)); + } + /* Perform the store operation */ + if( pTos->nIdx == SXU32_HIGH ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "Cannot perform assignment on a constant object attribute"); + }else if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pTos->nIdx)) != 0 ){ + jx9MemObjStore(pTos, pObj); + } + jx9MemObjStore(pTos, pNos); + VmPopOperand(&pTos, 1); + break; + } +/* OP_AND: * * * + * + * Pop two values off the stack. Take the logical AND of the + * two values and push the resulting boolean value back onto the + * stack. + */ +/* OP_OR: * * * + * + * Pop two values off the stack. Take the logical OR of the + * two values and push the resulting boolean value back onto the + * stack. + */ +case JX9_OP_LAND: +case JX9_OP_LOR: { + jx9_value *pNos = &pTos[-1]; + sxi32 v1, v2; /* 0==TRUE, 1==FALSE, 2==UNKNOWN or NULL */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pNos); + } + v1 = pNos->x.iVal == 0 ? 1 : 0; + v2 = pTos->x.iVal == 0 ? 1 : 0; + if( pInstr->iOp == JX9_OP_LAND ){ + static const unsigned char and_logic[] = { 0, 1, 2, 1, 1, 1, 2, 1, 2 }; + v1 = and_logic[v1*3+v2]; + }else{ + static const unsigned char or_logic[] = { 0, 0, 0, 0, 1, 2, 0, 2, 2 }; + v1 = or_logic[v1*3+v2]; + } + if( v1 == 2 ){ + v1 = 1; + } + VmPopOperand(&pTos, 1); + pTos->x.iVal = v1 == 0 ? 1 : 0; + MemObjSetType(pTos, MEMOBJ_BOOL); + break; + } +/* OP_LXOR: * * * + * + * Pop two values off the stack. Take the logical XOR of the + * two values and push the resulting boolean value back onto the + * stack. + * According to the JX9 language reference manual: + * $a xor $b is evaluated to TRUE if either $a or $b is + * TRUE, but not both. + */ +case JX9_OP_LXOR:{ + jx9_value *pNos = &pTos[-1]; + sxi32 v = 0; +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + /* Force a boolean cast */ + if((pTos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pTos); + } + if((pNos->iFlags & MEMOBJ_BOOL) == 0 ){ + jx9MemObjToBool(pNos); + } + if( (pNos->x.iVal && !pTos->x.iVal) || (pTos->x.iVal && !pNos->x.iVal) ){ + v = 1; + } + VmPopOperand(&pTos, 1); + pTos->x.iVal = v; + MemObjSetType(pTos, MEMOBJ_BOOL); + break; + } +/* OP_EQ P1 P2 P3 + * + * Pop the top two elements from the stack. If they are equal, then + * jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +/* OP_NEQ P1 P2 P3 + * + * Pop the top two elements from the stack. If they are not equal, then + * jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +case JX9_OP_EQ: +case JX9_OP_NEQ: { + jx9_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = jx9MemObjCmp(pNos, pTos, FALSE, 0); + if( pInstr->iOp == JX9_OP_EQ ){ + rc = rc == 0; + }else{ + rc = rc != 0; + } + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_TEQ P1 P2 * + * + * Pop the top two elements from the stack. If they have the same type and are equal + * then jump to instruction P2. Otherwise, continue to the next instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + */ +case JX9_OP_TEQ: { + jx9_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) == 0; + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_TNE P1 P2 * + * + * Pop the top two elements from the stack.If they are not equal an they are not + * of the same type, then jump to instruction P2. Otherwise, continue to the next + * instruction. + * If P2 is zero, do not jump. Instead, push a boolean 1 (TRUE) onto the + * stack if the jump would have been taken, or a 0 (FALSE) if not. + * + */ +case JX9_OP_TNE: { + jx9_value *pNos = &pTos[-1]; + /* Perform the comparison and act accordingly */ +#ifdef UNTRUST + if( pNos < pStack ){ + goto Abort; + } +#endif + rc = jx9MemObjCmp(pNos, pTos, TRUE, 0) != 0; + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_LT P1 P2 P3 + * + * Pop the top two elements from the stack. If the second element (the top of stack) + * is less than the first (next on stack), then jump to instruction P2.Otherwise + * continue to the next instruction. In other words, jump if pNosiOp == JX9_OP_LE ){ + rc = rc < 1; + }else{ + rc = rc < 0; + } + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* OP_GT P1 P2 P3 + * + * Pop the top two elements from the stack. If the second element (the top of stack) + * is greater than the first (next on stack), then jump to instruction P2.Otherwise + * continue to the next instruction. In other words, jump if pNosiOp == JX9_OP_GE ){ + rc = rc >= 0; + }else{ + rc = rc > 0; + } + VmPopOperand(&pTos, 1); + if( !pInstr->iP2 ){ + /* Push comparison result without taking the jump */ + jx9MemObjRelease(pTos); + pTos->x.iVal = rc; + /* Invalidate any prior representation */ + MemObjSetType(pTos, MEMOBJ_BOOL); + }else{ + if( rc ){ + /* Jump to the desired location */ + pc = pInstr->iP2 - 1; + VmPopOperand(&pTos, 1); + } + } + break; + } +/* + * OP_FOREACH_INIT * P2 P3 + * Prepare a foreach step. + */ +case JX9_OP_FOREACH_INIT: { + jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3; + void *pName; +#ifdef UNTRUST + if( pTos < pStack ){ + goto Abort; + } +#endif + if( SyStringLength(&pInfo->sValue) < 1 ){ + /* Take the variable name from the top of the stack */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + /* Duplicate name */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + SyStringInitFromBuf(&pInfo->sValue, pName, SyBlobLength(&pTos->sBlob)); + } + VmPopOperand(&pTos, 1); + } + if( (pInfo->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) < 1 ){ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pTos); + } + /* Duplicate name */ + if( SyBlobLength(&pTos->sBlob) > 0 ){ + pName = SyMemBackendDup(&pVm->sAllocator, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + SyStringInitFromBuf(&pInfo->sKey, pName, SyBlobLength(&pTos->sBlob)); + } + VmPopOperand(&pTos, 1); + } + /* Make sure we are dealing with a hashmap [i.e. JSON array or object ]*/ + if( (pTos->iFlags & (MEMOBJ_HASHMAP)) == 0 || SyStringLength(&pInfo->sValue) < 1 ){ + /* Jump out of the loop */ + if( (pTos->iFlags & MEMOBJ_NULL) == 0 ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_WARNING, + "Invalid argument supplied for the foreach statement, expecting JSON array or object instance"); + } + pc = pInstr->iP2 - 1; + }else{ + jx9_foreach_step *pStep; + pStep = (jx9_foreach_step *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(jx9_foreach_step)); + if( pStep == 0 ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step"); + /* Jump out of the loop */ + pc = pInstr->iP2 - 1; + }else{ + /* Zero the structure */ + SyZero(pStep, sizeof(jx9_foreach_step)); + /* Prepare the step */ + pStep->iFlags = pInfo->iFlags; + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + jx9_hashmap *pMap = (jx9_hashmap *)pTos->x.pOther; + /* Reset the internal loop cursor */ + jx9HashmapResetLoopCursor(pMap); + /* Mark the step */ + pStep->pMap = pMap; + pMap->iRef++; + } + } + if( SXRET_OK != SySetPut(&pInfo->aStep, (const void *)&pStep) ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, "JX9 is running out of memory while preparing the 'foreach' step"); + SyMemBackendPoolFree(&pVm->sAllocator, pStep); + /* Jump out of the loop */ + pc = pInstr->iP2 - 1; + } + } + VmPopOperand(&pTos, 1); + break; + } +/* + * OP_FOREACH_STEP * P2 P3 + * Perform a foreach step. Jump to P2 at the end of the step. + */ +case JX9_OP_FOREACH_STEP: { + jx9_foreach_info *pInfo = (jx9_foreach_info *)pInstr->p3; + jx9_foreach_step **apStep, *pStep; + jx9_hashmap_node *pNode; + jx9_hashmap *pMap; + jx9_value *pValue; + /* Peek the last step */ + apStep = (jx9_foreach_step **)SySetBasePtr(&pInfo->aStep); + pStep = apStep[SySetUsed(&pInfo->aStep) - 1]; + pMap = pStep->pMap; + /* Extract the current node value */ + pNode = jx9HashmapGetNextEntry(pMap); + if( pNode == 0 ){ + /* No more entry to process */ + pc = pInstr->iP2 - 1; /* Jump to this destination */ + /* Automatically reset the loop cursor */ + jx9HashmapResetLoopCursor(pMap); + /* Cleanup the mess left behind */ + SyMemBackendPoolFree(&pVm->sAllocator, pStep); + SySetPop(&pInfo->aStep); + jx9HashmapUnref(pMap); + }else{ + if( (pStep->iFlags & JX9_4EACH_STEP_KEY) && SyStringLength(&pInfo->sKey) > 0 ){ + jx9_value *pKey = VmExtractMemObj(&(*pVm), &pInfo->sKey, FALSE, TRUE); + if( pKey ){ + jx9HashmapExtractNodeKey(pNode, pKey); + } + } + /* Make a copy of the entry value */ + pValue = VmExtractMemObj(&(*pVm), &pInfo->sValue, FALSE, TRUE); + if( pValue ){ + jx9HashmapExtractNodeValue(pNode, pValue, TRUE); + } + } + break; + } +/* + * OP_MEMBER P1 P2 + * Load JSON object entry on the stack. + */ +case JX9_OP_MEMBER: { + jx9_hashmap_node *pNode = 0; /* cc warning */ + jx9_hashmap *pMap = 0; + jx9_value *pIdx; + pIdx = pTos; + pTos--; + rc = SXERR_NOTFOUND; /* Assume the index is invalid */ + if( pTos->iFlags & MEMOBJ_HASHMAP ){ + /* Point to the hashmap */ + pMap = (jx9_hashmap *)pTos->x.pOther; + /* Load the desired entry */ + rc = jx9HashmapLookup(pMap, pIdx, &pNode); + } + jx9MemObjRelease(pIdx); + if( rc == SXRET_OK ){ + /* Load entry contents */ + if( pMap->iRef < 2 ){ + /* TICKET 1433-42: Array will be deleted shortly, so we will make a copy + * of the entry value, rather than pointing to it. + */ + pTos->nIdx = SXU32_HIGH; + jx9HashmapExtractNodeValue(pNode, pTos, TRUE); + }else{ + pTos->nIdx = pNode->nValIdx; + jx9HashmapExtractNodeValue(pNode, pTos, FALSE); + jx9HashmapUnref(pMap); + } + }else{ + /* No such entry, load NULL */ + jx9MemObjRelease(pTos); + pTos->nIdx = SXU32_HIGH; + } + break; + } +/* + * OP_SWITCH * * P3 + * This is the bytecode implementation of the complex switch() JX9 construct. + */ +case JX9_OP_SWITCH: { + jx9_switch *pSwitch = (jx9_switch *)pInstr->p3; + jx9_case_expr *aCase, *pCase; + jx9_value sValue, sCaseValue; + sxu32 n, nEntry; +#ifdef UNTRUST + if( pSwitch == 0 || pTos < pStack ){ + goto Abort; + } +#endif + /* Point to the case table */ + aCase = (jx9_case_expr *)SySetBasePtr(&pSwitch->aCaseExpr); + nEntry = SySetUsed(&pSwitch->aCaseExpr); + /* Select the appropriate case block to execute */ + jx9MemObjInit(pVm, &sValue); + jx9MemObjInit(pVm, &sCaseValue); + for( n = 0 ; n < nEntry ; ++n ){ + pCase = &aCase[n]; + jx9MemObjLoad(pTos, &sValue); + /* Execute the case expression first */ + VmLocalExec(pVm,&pCase->aByteCode, &sCaseValue); + /* Compare the two expression */ + rc = jx9MemObjCmp(&sValue, &sCaseValue, FALSE, 0); + jx9MemObjRelease(&sValue); + jx9MemObjRelease(&sCaseValue); + if( rc == 0 ){ + /* Value match, jump to this block */ + pc = pCase->nStart - 1; + break; + } + } + VmPopOperand(&pTos, 1); + if( n >= nEntry ){ + /* No approprite case to execute, jump to the default case */ + if( pSwitch->nDefault > 0 ){ + pc = pSwitch->nDefault - 1; + }else{ + /* No default case, jump out of this switch */ + pc = pSwitch->nOut - 1; + } + } + break; + } +/* + * OP_UPLINK P1 * * + * Link a variable to the top active VM frame. + * This is used to implement the 'uplink' JX9 construct. + */ +case JX9_OP_UPLINK: { + if( pVm->pFrame->pParent ){ + jx9_value *pLink = &pTos[-pInstr->iP1+1]; + SyString sName; + /* Perform the link */ + while( pLink <= pTos ){ + if((pLink->iFlags & MEMOBJ_STRING) == 0 ){ + /* Force a string cast */ + jx9MemObjToString(pLink); + } + SyStringInitFromBuf(&sName, SyBlobData(&pLink->sBlob), SyBlobLength(&pLink->sBlob)); + if( sName.nByte > 0 ){ + VmFrameLink(&(*pVm), &sName); + } + pLink++; + } + } + VmPopOperand(&pTos, pInstr->iP1); + break; + } +/* + * OP_CALL P1 * * + * Call a JX9 or a foreign function and push the return value of the called + * function on the stack. + */ +case JX9_OP_CALL: { + jx9_value *pArg = &pTos[-pInstr->iP1]; + SyHashEntry *pEntry; + SyString sName; + /* Extract function name */ + if( (pTos->iFlags & MEMOBJ_STRING) == 0 ){ + /* Raise exception: Invalid function name */ + VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Invalid function name, JX9 is returning NULL."); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob)); + /* Check for a compiled function first */ + pEntry = SyHashGet(&pVm->hFunction, (const void *)sName.zString, sName.nByte); + if( pEntry ){ + jx9_vm_func_arg *aFormalArg; + jx9_value *pFrameStack; + jx9_vm_func *pVmFunc; + VmFrame *pFrame; + jx9_value *pObj; + VmSlot sArg; + sxu32 n; + pVmFunc = (jx9_vm_func *)pEntry->pUserData; + /* Check The recursion limit */ + if( pVm->nRecursionDepth > pVm->nMaxDepth ){ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "Recursion limit reached while invoking user function '%z', JX9 will set a NULL return value", + &pVmFunc->sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + if( pVmFunc->pNextName ){ + /* Function is candidate for overloading, select the appropriate function to call */ + pVmFunc = VmOverload(&(*pVm), pVmFunc, pArg, (int)(pTos-pArg)); + } + /* Extract the formal argument set */ + aFormalArg = (jx9_vm_func_arg *)SySetBasePtr(&pVmFunc->aArgs); + /* Create a new VM frame */ + rc = VmEnterFrame(&(*pVm),pVmFunc,&pFrame); + if( rc != SXRET_OK ){ + /* Raise exception: Out of memory */ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, + "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.", + &pVmFunc->sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + if( SySetUsed(&pVmFunc->aStatic) > 0 ){ + jx9_vm_func_static_var *pStatic, *aStatic; + /* Install static variables */ + aStatic = (jx9_vm_func_static_var *)SySetBasePtr(&pVmFunc->aStatic); + for( n = 0 ; n < SySetUsed(&pVmFunc->aStatic) ; ++n ){ + pStatic = &aStatic[n]; + if( pStatic->nIdx == SXU32_HIGH ){ + /* Initialize the static variables */ + pObj = VmReserveMemObj(&(*pVm), &pStatic->nIdx); + if( pObj ){ + /* Assume a NULL initialization value */ + jx9MemObjInit(&(*pVm), pObj); + if( SySetUsed(&pStatic->aByteCode) > 0 ){ + /* Evaluate initialization expression (Any complex expression) */ + VmLocalExec(&(*pVm), &pStatic->aByteCode, pObj); + } + pObj->nIdx = pStatic->nIdx; + }else{ + continue; + } + } + /* Install in the current frame */ + SyHashInsert(&pFrame->hVar, SyStringData(&pStatic->sName), SyStringLength(&pStatic->sName), + SX_INT_TO_PTR(pStatic->nIdx)); + } + } + /* Push arguments in the local frame */ + n = 0; + while( pArg < pTos ){ + if( n < SySetUsed(&pVmFunc->aArgs) ){ + if( (pArg->iFlags & MEMOBJ_NULL) && SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ + /* NULL values are redirected to default arguments */ + rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pArg); + if( rc == JX9_ABORT ){ + goto Abort; + } + } + /* Make sure the given arguments are of the correct type */ + if( aFormalArg[n].nType > 0 ){ + if( ((pArg->iFlags & aFormalArg[n].nType) == 0) ){ + ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType); + /* Cast to the desired type */ + if( xCast ){ + xCast(pArg); + } + } + } + /* Pass by value, make a copy of the given argument */ + pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE); + }else{ + char zName[32]; + SyString sName; + /* Set a dummy name */ + sName.nByte = SyBufferFormat(zName, sizeof(zName), "[%u]apArg", n); + sName.zString = zName; + /* Annonymous argument */ + pObj = VmExtractMemObj(&(*pVm), &sName, TRUE, TRUE); + } + if( pObj ){ + jx9MemObjStore(pArg, pObj); + /* Insert argument index */ + sArg.nIdx = pObj->nIdx; + sArg.pUserData = 0; + SySetPut(&pFrame->sArg, (const void *)&sArg); + } + jx9MemObjRelease(pArg); + pArg++; + ++n; + } + /* Process default values */ + while( n < SySetUsed(&pVmFunc->aArgs) ){ + if( SySetUsed(&aFormalArg[n].aByteCode) > 0 ){ + pObj = VmExtractMemObj(&(*pVm), &aFormalArg[n].sName, FALSE, TRUE); + if( pObj ){ + /* Evaluate the default value and extract it's result */ + rc = VmLocalExec(&(*pVm), &aFormalArg[n].aByteCode, pObj); + if( rc == JX9_ABORT ){ + goto Abort; + } + /* Insert argument index */ + sArg.nIdx = pObj->nIdx; + sArg.pUserData = 0; + SySetPut(&pFrame->sArg, (const void *)&sArg); + /* Make sure the default argument is of the correct type */ + if( aFormalArg[n].nType > 0 && ((pObj->iFlags & aFormalArg[n].nType) == 0) ){ + ProcMemObjCast xCast = jx9MemObjCastMethod(aFormalArg[n].nType); + /* Cast to the desired type */ + xCast(pObj); + } + } + } + ++n; + } + /* Pop arguments, function name from the operand stack and assume the function + * does not return anything. + */ + jx9MemObjRelease(pTos); + pTos = &pTos[-pInstr->iP1]; + /* Allocate a new operand stack and evaluate the function body */ + pFrameStack = VmNewOperandStack(&(*pVm), SySetUsed(&pVmFunc->aByteCode)); + if( pFrameStack == 0 ){ + /* Raise exception: Out of memory */ + VmErrorFormat(&(*pVm), JX9_CTX_ERR, "JX9 is running out of memory while calling function '%z', JX9 is returning NULL.", + &pVmFunc->sName); + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + break; + } + /* Increment nesting level */ + pVm->nRecursionDepth++; + /* Execute function body */ + rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(&pVmFunc->aByteCode), pFrameStack, -1, pTos); + /* Decrement nesting level */ + pVm->nRecursionDepth--; + /* Free the operand stack */ + SyMemBackendFree(&pVm->sAllocator, pFrameStack); + /* Leave the frame */ + VmLeaveFrame(&(*pVm)); + if( rc == JX9_ABORT ){ + /* Abort processing immeditaley */ + goto Abort; + } + }else{ + jx9_user_func *pFunc; + jx9_context sCtx; + jx9_value sRet; + /* Look for an installed foreign function */ + pEntry = SyHashGet(&pVm->hHostFunction, (const void *)sName.zString, sName.nByte); + if( pEntry == 0 ){ + /* Call to undefined function */ + VmErrorFormat(&(*pVm), JX9_CTX_WARNING, "Call to undefined function '%z', JX9 is returning NULL.", &sName); + /* Pop given arguments */ + if( pInstr->iP1 > 0 ){ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Assume a null return value so that the program continue it's execution normally */ + jx9MemObjRelease(pTos); + break; + } + pFunc = (jx9_user_func *)pEntry->pUserData; + /* Start collecting function arguments */ + SySetReset(&aArg); + while( pArg < pTos ){ + SySetPut(&aArg, (const void *)&pArg); + pArg++; + } + /* Assume a null return value */ + jx9MemObjInit(&(*pVm), &sRet); + /* Init the call context */ + VmInitCallContext(&sCtx, &(*pVm), pFunc, &sRet, 0); + /* Call the foreign function */ + rc = pFunc->xFunc(&sCtx, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg)); + /* Release the call context */ + VmReleaseCallContext(&sCtx); + if( rc == JX9_ABORT ){ + goto Abort; + } + if( pInstr->iP1 > 0 ){ + /* Pop function name and arguments */ + VmPopOperand(&pTos, pInstr->iP1); + } + /* Save foreign function return value */ + jx9MemObjStore(&sRet, pTos); + jx9MemObjRelease(&sRet); + } + break; + } +/* + * OP_CONSUME: P1 * * + * Consume (Invoke the installed VM output consumer callback) and POP P1 elements from the stack. + */ +case JX9_OP_CONSUME: { + jx9_output_consumer *pCons = &pVm->sVmConsumer; + jx9_value *pCur, *pOut = pTos; + + pOut = &pTos[-pInstr->iP1 + 1]; + pCur = pOut; + /* Start the consume process */ + while( pOut <= pTos ){ + /* Force a string cast */ + if( (pOut->iFlags & MEMOBJ_STRING) == 0 ){ + jx9MemObjToString(pOut); + } + if( SyBlobLength(&pOut->sBlob) > 0 ){ + /*SyBlobNullAppend(&pOut->sBlob);*/ + /* Invoke the output consumer callback */ + rc = pCons->xConsumer(SyBlobData(&pOut->sBlob), SyBlobLength(&pOut->sBlob), pCons->pUserData); + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&pOut->sBlob); + SyBlobRelease(&pOut->sBlob); + if( rc == SXERR_ABORT ){ + /* Output consumer callback request an operation abort. */ + goto Abort; + } + } + pOut++; + } + pTos = &pCur[-1]; + break; + } + + } /* Switch() */ + pc++; /* Next instruction in the stream */ + } /* For(;;) */ +Done: + SySetRelease(&aArg); + return SXRET_OK; +Abort: + SySetRelease(&aArg); + while( pTos >= pStack ){ + jx9MemObjRelease(pTos); + pTos--; + } + return JX9_ABORT; +} +/* + * Execute as much of a local JX9 bytecode program as we can then return. + * This function is a wrapper around [VmByteCodeExec()]. + * See block-comment on that function for additional information. + */ +static sxi32 VmLocalExec(jx9_vm *pVm, SySet *pByteCode,jx9_value *pResult) +{ + jx9_value *pStack; + sxi32 rc; + /* Allocate a new operand stack */ + pStack = VmNewOperandStack(&(*pVm), SySetUsed(pByteCode)); + if( pStack == 0 ){ + return SXERR_MEM; + } + /* Execute the program */ + rc = VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pByteCode), pStack, -1, &(*pResult)); + /* Free the operand stack */ + SyMemBackendFree(&pVm->sAllocator, pStack); + /* Execution result */ + return rc; +} +/* + * Execute as much of a JX9 bytecode program as we can then return. + * This function is a wrapper around [VmByteCodeExec()]. + * See block-comment on that function for additional information. + */ +JX9_PRIVATE sxi32 jx9VmByteCodeExec(jx9_vm *pVm) +{ + /* Make sure we are ready to execute this program */ + if( pVm->nMagic != JX9_VM_RUN ){ + return pVm->nMagic == JX9_VM_EXEC ? SXERR_LOCKED /* Locked VM */ : SXERR_CORRUPT; /* Stale VM */ + } + /* Set the execution magic number */ + pVm->nMagic = JX9_VM_EXEC; + /* Execute the program */ + VmByteCodeExec(&(*pVm), (VmInstr *)SySetBasePtr(pVm->pByteContainer), pVm->aOps, -1, &pVm->sExec); + /* + * TICKET 1433-100: Do not remove the JX9_VM_EXEC magic number + * so that any following call to [jx9_vm_exec()] without calling + * [jx9_vm_reset()] first would fail. + */ + return SXRET_OK; +} +/* + * Extract a memory object (i.e. a variable) from the running script. + * This function must be called after calling jx9_vm_exec(). Otherwise + * NULL is returned. + */ +JX9_PRIVATE jx9_value * jx9VmExtractVariable(jx9_vm *pVm,SyString *pVar) +{ + jx9_value *pValue; + if( pVm->nMagic != JX9_VM_EXEC ){ + /* call jx9_vm_exec() first */ + return 0; + } + /* Perform the lookup */ + pValue = VmExtractMemObj(pVm,pVar,FALSE,FALSE); + /* Lookup result */ + return pValue; +} +/* + * Invoke the installed VM output consumer callback to consume + * the desired message. + * Refer to the implementation of [jx9_context_output()] defined + * in 'api.c' for additional information. + */ +JX9_PRIVATE sxi32 jx9VmOutputConsume( + jx9_vm *pVm, /* Target VM */ + SyString *pString /* Message to output */ + ) +{ + jx9_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + /* Call the output consumer */ + if( pString->nByte > 0 ){ + rc = pCons->xConsumer((const void *)pString->zString, pString->nByte, pCons->pUserData); + /* Increment output length */ + pVm->nOutputLen += pString->nByte; + } + return rc; +} +/* + * Format a message and invoke the installed VM output consumer + * callback to consume the formatted message. + * Refer to the implementation of [jx9_context_output_format()] defined + * in 'api.c' for additional information. + */ +JX9_PRIVATE sxi32 jx9VmOutputConsumeAp( + jx9_vm *pVm, /* Target VM */ + const char *zFormat, /* Formatted message to output */ + va_list ap /* Variable list of arguments */ + ) +{ + jx9_output_consumer *pCons = &pVm->sVmConsumer; + sxi32 rc = SXRET_OK; + SyBlob sWorker; + /* Format the message and call the output consumer */ + SyBlobInit(&sWorker, &pVm->sAllocator); + SyBlobFormatAp(&sWorker, zFormat, ap); + if( SyBlobLength(&sWorker) > 0 ){ + /* Consume the formatted message */ + rc = pCons->xConsumer(SyBlobData(&sWorker), SyBlobLength(&sWorker), pCons->pUserData); + } + /* Increment output length */ + pVm->nOutputLen += SyBlobLength(&sWorker); + /* Release the working buffer */ + SyBlobRelease(&sWorker); + return rc; +} +/* + * Return a string representation of the given JX9 OP code. + * This function never fail and always return a pointer + * to a null terminated string. + */ +static const char * VmInstrToString(sxi32 nOp) +{ + const char *zOp = "Unknown "; + switch(nOp){ + case JX9_OP_DONE: zOp = "DONE "; break; + case JX9_OP_HALT: zOp = "HALT "; break; + case JX9_OP_LOAD: zOp = "LOAD "; break; + case JX9_OP_LOADC: zOp = "LOADC "; break; + case JX9_OP_LOAD_MAP: zOp = "LOAD_MAP "; break; + case JX9_OP_LOAD_IDX: zOp = "LOAD_IDX "; break; + case JX9_OP_NOOP: zOp = "NOOP "; break; + case JX9_OP_JMP: zOp = "JMP "; break; + case JX9_OP_JZ: zOp = "JZ "; break; + case JX9_OP_JNZ: zOp = "JNZ "; break; + case JX9_OP_POP: zOp = "POP "; break; + case JX9_OP_CAT: zOp = "CAT "; break; + case JX9_OP_CVT_INT: zOp = "CVT_INT "; break; + case JX9_OP_CVT_STR: zOp = "CVT_STR "; break; + case JX9_OP_CVT_REAL: zOp = "CVT_REAL "; break; + case JX9_OP_CALL: zOp = "CALL "; break; + case JX9_OP_UMINUS: zOp = "UMINUS "; break; + case JX9_OP_UPLUS: zOp = "UPLUS "; break; + case JX9_OP_BITNOT: zOp = "BITNOT "; break; + case JX9_OP_LNOT: zOp = "LOGNOT "; break; + case JX9_OP_MUL: zOp = "MUL "; break; + case JX9_OP_DIV: zOp = "DIV "; break; + case JX9_OP_MOD: zOp = "MOD "; break; + case JX9_OP_ADD: zOp = "ADD "; break; + case JX9_OP_SUB: zOp = "SUB "; break; + case JX9_OP_SHL: zOp = "SHL "; break; + case JX9_OP_SHR: zOp = "SHR "; break; + case JX9_OP_LT: zOp = "LT "; break; + case JX9_OP_LE: zOp = "LE "; break; + case JX9_OP_GT: zOp = "GT "; break; + case JX9_OP_GE: zOp = "GE "; break; + case JX9_OP_EQ: zOp = "EQ "; break; + case JX9_OP_NEQ: zOp = "NEQ "; break; + case JX9_OP_TEQ: zOp = "TEQ "; break; + case JX9_OP_TNE: zOp = "TNE "; break; + case JX9_OP_BAND: zOp = "BITAND "; break; + case JX9_OP_BXOR: zOp = "BITXOR "; break; + case JX9_OP_BOR: zOp = "BITOR "; break; + case JX9_OP_LAND: zOp = "LOGAND "; break; + case JX9_OP_LOR: zOp = "LOGOR "; break; + case JX9_OP_LXOR: zOp = "LOGXOR "; break; + case JX9_OP_STORE: zOp = "STORE "; break; + case JX9_OP_STORE_IDX: zOp = "STORE_IDX "; break; + case JX9_OP_PULL: zOp = "PULL "; break; + case JX9_OP_SWAP: zOp = "SWAP "; break; + case JX9_OP_YIELD: zOp = "YIELD "; break; + case JX9_OP_CVT_BOOL: zOp = "CVT_BOOL "; break; + case JX9_OP_CVT_NULL: zOp = "CVT_NULL "; break; + case JX9_OP_CVT_ARRAY: zOp = "CVT_JSON "; break; + case JX9_OP_CVT_NUMC: zOp = "CVT_NUMC "; break; + case JX9_OP_INCR: zOp = "INCR "; break; + case JX9_OP_DECR: zOp = "DECR "; break; + case JX9_OP_ADD_STORE: zOp = "ADD_STORE "; break; + case JX9_OP_SUB_STORE: zOp = "SUB_STORE "; break; + case JX9_OP_MUL_STORE: zOp = "MUL_STORE "; break; + case JX9_OP_DIV_STORE: zOp = "DIV_STORE "; break; + case JX9_OP_MOD_STORE: zOp = "MOD_STORE "; break; + case JX9_OP_CAT_STORE: zOp = "CAT_STORE "; break; + case JX9_OP_SHL_STORE: zOp = "SHL_STORE "; break; + case JX9_OP_SHR_STORE: zOp = "SHR_STORE "; break; + case JX9_OP_BAND_STORE: zOp = "BAND_STORE "; break; + case JX9_OP_BOR_STORE: zOp = "BOR_STORE "; break; + case JX9_OP_BXOR_STORE: zOp = "BXOR_STORE "; break; + case JX9_OP_CONSUME: zOp = "CONSUME "; break; + case JX9_OP_MEMBER: zOp = "MEMBER "; break; + case JX9_OP_UPLINK: zOp = "UPLINK "; break; + case JX9_OP_SWITCH: zOp = "SWITCH "; break; + case JX9_OP_FOREACH_INIT: + zOp = "4EACH_INIT "; break; + case JX9_OP_FOREACH_STEP: + zOp = "4EACH_STEP "; break; + default: + break; + } + return zOp; +} +/* + * Dump JX9 bytecodes instructions to a human readable format. + * The xConsumer() callback which is an used defined function + * is responsible of consuming the generated dump. + */ +JX9_PRIVATE sxi32 jx9VmDump( + jx9_vm *pVm, /* Target VM */ + ProcConsumer xConsumer, /* Output [i.e: dump] consumer callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + sxi32 rc; + rc = VmByteCodeDump(pVm->pByteContainer, xConsumer, pUserData); + return rc; +} +/* + * Default constant expansion callback used by the 'const' statement if used + * outside a object body [i.e: global or function scope]. + * Refer to the implementation of [JX9_CompileConstant()] defined + * in 'compile.c' for additional information. + */ +JX9_PRIVATE void jx9VmExpandConstantValue(jx9_value *pVal, void *pUserData) +{ + SySet *pByteCode = (SySet *)pUserData; + /* Evaluate and expand constant value */ + VmLocalExec((jx9_vm *)SySetGetUserData(pByteCode), pByteCode, (jx9_value *)pVal); +} +/* + * Section: + * Function handling functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * int func_num_args(void) + * Returns the number of arguments passed to the function. + * Parameters + * None. + * Return + * Total number of arguments passed into the current user-defined function + * or -1 if called from the globe scope. + */ +static int vm_builtin_func_num_args(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + VmFrame *pFrame; + jx9_vm *pVm; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Current frame */ + pFrame = pVm->pFrame; + if( pFrame->pParent == 0 ){ + SXUNUSED(nArg); + SXUNUSED(apArg); + /* Global frame, return -1 */ + jx9_result_int(pCtx, -1); + return SXRET_OK; + } + /* Total number of arguments passed to the enclosing function */ + nArg = (int)SySetUsed(&pFrame->sArg); + jx9_result_int(pCtx, nArg); + return SXRET_OK; +} +/* + * value func_get_arg(int $arg_num) + * Return an item from the argument list. + * Parameters + * Argument number(index start from zero). + * Return + * Returns the specified argument or FALSE on error. + */ +static int vm_builtin_func_get_arg(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pObj = 0; + VmSlot *pSlot = 0; + VmFrame *pFrame; + jx9_vm *pVm; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Current frame */ + pFrame = pVm->pFrame; + if( nArg < 1 || pFrame->pParent == 0 ){ + /* Global frame or Missing arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope"); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Extract the desired index */ + nArg = jx9_value_to_int(apArg[0]); + if( nArg < 0 || nArg >= (int)SySetUsed(&pFrame->sArg) ){ + /* Invalid index, return FALSE */ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Extract the desired argument */ + if( (pSlot = (VmSlot *)SySetAt(&pFrame->sArg, (sxu32)nArg)) != 0 ){ + if( (pObj = (jx9_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx)) != 0 ){ + /* Return the desired argument */ + jx9_result_value(pCtx, (jx9_value *)pObj); + }else{ + /* No such argument, return false */ + jx9_result_bool(pCtx, 0); + } + }else{ + /* CAN'T HAPPEN */ + jx9_result_bool(pCtx, 0); + } + return SXRET_OK; +} +/* + * array func_get_args(void) + * Returns an array comprising a copy of function's argument list. + * Parameters + * None. + * Return + * Returns an array in which each element is a copy of the corresponding + * member of the current user-defined function's argument list. + * Otherwise FALSE is returned on failure. + */ +static int vm_builtin_func_get_args(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pObj = 0; + jx9_value *pArray; + VmFrame *pFrame; + VmSlot *aSlot; + sxu32 n; + /* Point to the current frame */ + pFrame = pCtx->pVm->pFrame; + if( pFrame->pParent == 0 ){ + /* Global frame, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_WARNING, "Called in the global scope"); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Create a new array */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Start filling the array with the given arguments */ + aSlot = (VmSlot *)SySetBasePtr(&pFrame->sArg); + for( n = 0; n < SySetUsed(&pFrame->sArg) ; n++ ){ + pObj = (jx9_value *)SySetAt(&pCtx->pVm->aMemObj, aSlot[n].nIdx); + if( pObj ){ + jx9_array_add_elem(pArray, 0/* Automatic index assign*/, pObj); + } + } + /* Return the freshly created array */ + jx9_result_value(pCtx, pArray); + return SXRET_OK; +} +/* + * bool function_exists(string $name) + * Return TRUE if the given function has been defined. + * Parameters + * The name of the desired function. + * Return + * Return TRUE if the given function has been defined.False otherwise + */ +static int vm_builtin_func_exists(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zName; + jx9_vm *pVm; + int nLen; + int res; + if( nArg < 1 ){ + /* Missing argument, return FALSE */ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Extract the function name */ + zName = jx9_value_to_string(apArg[0], &nLen); + /* Assume the function is not defined */ + res = 0; + /* Perform the lookup */ + if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 || + SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){ + /* Function is defined */ + res = 1; + } + jx9_result_bool(pCtx, res); + return SXRET_OK; +} +/* + * Verify that the contents of a variable can be called as a function. + * [i.e: Whether it is callable or not]. + * Return TRUE if callable.FALSE otherwise. + */ +JX9_PRIVATE int jx9VmIsCallable(jx9_vm *pVm, jx9_value *pValue) +{ + int res = 0; + if( pValue->iFlags & MEMOBJ_STRING ){ + const char *zName; + int nLen; + /* Extract the name */ + zName = jx9_value_to_string(pValue, &nLen); + /* Perform the lookup */ + if( SyHashGet(&pVm->hFunction, (const void *)zName, (sxu32)nLen) != 0 || + SyHashGet(&pVm->hHostFunction, (const void *)zName, (sxu32)nLen) != 0 ){ + /* Function is callable */ + res = 1; + } + } + return res; +} +/* + * bool is_callable(callable $name[, bool $syntax_only = false]) + * Verify that the contents of a variable can be called as a function. + * Parameters + * $name + * The callback function to check + * $syntax_only + * If set to TRUE the function only verifies that name might be a function or method. + * It will only reject simple variables that are not strings, or an array that does + * not have a valid structure to be used as a callback. The valid ones are supposed + * to have only 2 entries, the first of which is an object or a string, and the second + * a string. + * Return + * TRUE if name is callable, FALSE otherwise. + */ +static int vm_builtin_is_callable(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_vm *pVm; + int res; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Perform the requested operation */ + res = jx9VmIsCallable(pVm, apArg[0]); + jx9_result_bool(pCtx, res); + return SXRET_OK; +} +/* + * Hash walker callback used by the [get_defined_functions()] function + * defined below. + */ +static int VmHashFuncStep(SyHashEntry *pEntry, void *pUserData) +{ + jx9_value *pArray = (jx9_value *)pUserData; + jx9_value sName; + sxi32 rc; + /* Prepare the function name for insertion */ + jx9MemObjInitFromString(pArray->pVm, &sName, 0); + jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen); + /* Perform the insertion */ + rc = jx9_array_add_elem(pArray, 0/* Automatic index assign */, &sName); /* Will make it's own copy */ + jx9MemObjRelease(&sName); + return rc; +} +/* + * array get_defined_functions(void) + * Returns an array of all defined functions. + * Parameter + * None. + * Return + * Returns an multidimensional array containing a list of all defined functions + * both built-in (internal) and user-defined. + * The internal functions will be accessible via $arr["internal"], and the user + * defined ones using $arr["user"]. + * Note: + * NULL is returned on failure. + */ +static int vm_builtin_get_defined_func(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray; + /* NOTE: + * Don't worry about freeing memory here, every allocated resource will be released + * automatically by the engine as soon we return from this foreign function. + */ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Fill with the appropriate information */ + SyHashForEach(&pCtx->pVm->hHostFunction,VmHashFuncStep,pArray); + /* Fill with the appropriate information */ + SyHashForEach(&pCtx->pVm->hFunction, VmHashFuncStep,pArray); + /* Return a copy of the array array */ + jx9_result_value(pCtx, pArray); + return SXRET_OK; +} +/* + * Call a user defined or foreign function where the name of the function + * is stored in the pFunc parameter and the given arguments are stored + * in the apArg[] array. + * Return SXRET_OK if the function was successfuly called.Any other + * return value indicates failure. + */ +JX9_PRIVATE sxi32 jx9VmCallUserFunction( + jx9_vm *pVm, /* Target VM */ + jx9_value *pFunc, /* Callback name */ + int nArg, /* Total number of given arguments */ + jx9_value **apArg, /* Callback arguments */ + jx9_value *pResult /* Store callback return value here. NULL otherwise */ + ) +{ + jx9_value *aStack; + VmInstr aInstr[2]; + int i; + if((pFunc->iFlags & (MEMOBJ_STRING)) == 0 ){ + /* Don't bother processing, it's invalid anyway */ + if( pResult ){ + /* Assume a null return value */ + jx9MemObjRelease(pResult); + } + return SXERR_INVALID; + } + /* Create a new operand stack */ + aStack = VmNewOperandStack(&(*pVm), 1+nArg); + if( aStack == 0 ){ + jx9VmThrowError(&(*pVm), 0, JX9_CTX_ERR, + "JX9 is running out of memory while invoking user callback"); + if( pResult ){ + /* Assume a null return value */ + jx9MemObjRelease(pResult); + } + return SXERR_MEM; + } + /* Fill the operand stack with the given arguments */ + for( i = 0 ; i < nArg ; i++ ){ + jx9MemObjLoad(apArg[i], &aStack[i]); + aStack[i].nIdx = apArg[i]->nIdx; + } + /* Push the function name */ + jx9MemObjLoad(pFunc, &aStack[i]); + aStack[i].nIdx = SXU32_HIGH; /* Mark as constant */ + /* Emit the CALL istruction */ + aInstr[0].iOp = JX9_OP_CALL; + aInstr[0].iP1 = nArg; /* Total number of given arguments */ + aInstr[0].iP2 = 0; + aInstr[0].p3 = 0; + /* Emit the DONE instruction */ + aInstr[1].iOp = JX9_OP_DONE; + aInstr[1].iP1 = 1; /* Extract function return value if available */ + aInstr[1].iP2 = 0; + aInstr[1].p3 = 0; + /* Execute the function body (if available) */ + VmByteCodeExec(&(*pVm), aInstr, aStack, nArg, pResult); + /* Clean up the mess left behind */ + SyMemBackendFree(&pVm->sAllocator, aStack); + return JX9_OK; +} +/* + * Call a user defined or foreign function whith a varibale number + * of arguments where the name of the function is stored in the pFunc + * parameter. + * Return SXRET_OK if the function was successfuly called.Any other + * return value indicates failure. + */ +JX9_PRIVATE sxi32 jx9VmCallUserFunctionAp( + jx9_vm *pVm, /* Target VM */ + jx9_value *pFunc, /* Callback name */ + jx9_value *pResult, /* Store callback return value here. NULL otherwise */ + ... /* 0 (Zero) or more Callback arguments */ + ) +{ + jx9_value *pArg; + SySet aArg; + va_list ap; + sxi32 rc; + SySetInit(&aArg, &pVm->sAllocator, sizeof(jx9_value *)); + /* Copy arguments one after one */ + va_start(ap, pResult); + for(;;){ + pArg = va_arg(ap, jx9_value *); + if( pArg == 0 ){ + break; + } + SySetPut(&aArg, (const void *)&pArg); + } + /* Call the core routine */ + rc = jx9VmCallUserFunction(&(*pVm), pFunc, (int)SySetUsed(&aArg), (jx9_value **)SySetBasePtr(&aArg), pResult); + /* Cleanup */ + SySetRelease(&aArg); + return rc; +} +/* + * bool defined(string $name) + * Checks whether a given named constant exists. + * Parameter: + * Name of the desired constant. + * Return + * TRUE if the given constant exists.FALSE otherwise. + */ +static int vm_builtin_defined(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zName; + int nLen = 0; + int res = 0; + if( nArg < 1 ){ + /* Missing constant name, return FALSE */ + jx9_context_throw_error(pCtx,JX9_CTX_NOTICE,"Missing constant name"); + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + /* Extract constant name */ + zName = jx9_value_to_string(apArg[0], &nLen); + /* Perform the lookup */ + if( nLen > 0 && SyHashGet(&pCtx->pVm->hConstant, (const void *)zName, (sxu32)nLen) != 0 ){ + /* Already defined */ + res = 1; + } + jx9_result_bool(pCtx, res); + return SXRET_OK; +} +/* + * Hash walker callback used by the [get_defined_constants()] function + * defined below. + */ +static int VmHashConstStep(SyHashEntry *pEntry, void *pUserData) +{ + jx9_value *pArray = (jx9_value *)pUserData; + jx9_value sName; + sxi32 rc; + /* Prepare the constant name for insertion */ + jx9MemObjInitFromString(pArray->pVm, &sName, 0); + jx9MemObjStringAppend(&sName, (const char *)pEntry->pKey, pEntry->nKeyLen); + /* Perform the insertion */ + rc = jx9_array_add_elem(pArray, 0, &sName); /* Will make it's own copy */ + jx9MemObjRelease(&sName); + return rc; +} +/* + * array get_defined_constants(void) + * Returns an associative array with the names of all defined + * constants. + * Parameters + * NONE. + * Returns + * Returns the names of all the constants currently defined. + */ +static int vm_builtin_get_defined_constants(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + jx9_value *pArray; + /* Create the array first*/ + pArray = jx9_context_new_array(pCtx); + if( pArray == 0 ){ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + /* Return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Fill the array with the defined constants */ + SyHashForEach(&pCtx->pVm->hConstant, VmHashConstStep, pArray); + /* Return the created array */ + jx9_result_value(pCtx, pArray); + return SXRET_OK; +} +/* + * Section: + * Random numbers/string generators. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * Generate a random 32-bit unsigned integer. + * JX9 use it's own private PRNG which is based on the one + * used by te SQLite3 library. + */ +JX9_PRIVATE sxu32 jx9VmRandomNum(jx9_vm *pVm) +{ + sxu32 iNum; + SyRandomness(&pVm->sPrng, (void *)&iNum, sizeof(sxu32)); + return iNum; +} +/* + * Generate a random string (English Alphabet) of length nLen. + * Note that the generated string is NOT null terminated. + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +JX9_PRIVATE void jx9VmRandomString(jx9_vm *pVm, char *zBuf, int nLen) +{ + static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */ + int i; + /* Generate a binary string first */ + SyRandomness(&pVm->sPrng, zBuf, (sxu32)nLen); + /* Turn the binary string into english based alphabet */ + for( i = 0 ; i < nLen ; ++i ){ + zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)]; + } +} +/* + * int rand() + * Generate a random (unsigned 32-bit) integer. + * Parameter + * $min + * The lowest value to return (default: 0) + * $max + * The highest value to return (default: getrandmax()) + * Return + * A pseudo random value between min (or 0) and max (or getrandmax(), inclusive). + * Note: + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_rand(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + sxu32 iNum; + /* Generate the random number */ + iNum = jx9VmRandomNum(pCtx->pVm); + if( nArg > 1 ){ + sxu32 iMin, iMax; + iMin = (sxu32)jx9_value_to_int(apArg[0]); + iMax = (sxu32)jx9_value_to_int(apArg[1]); + if( iMin < iMax ){ + sxu32 iDiv = iMax+1-iMin; + if( iDiv > 0 ){ + iNum = (iNum % iDiv)+iMin; + } + }else if(iMax > 0 ){ + iNum %= iMax; + } + } + /* Return the number */ + jx9_result_int64(pCtx, (jx9_int64)iNum); + return SXRET_OK; +} +/* + * int getrandmax(void) + * Show largest possible random value + * Return + * The largest possible random value returned by rand() which is in + * this implementation 0xFFFFFFFF. + * Note: + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_getrandmax(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SXUNUSED(nArg); /* cc warning */ + SXUNUSED(apArg); + jx9_result_int64(pCtx, SXU32_HIGH); + return SXRET_OK; +} +/* + * string rand_str() + * string rand_str(int $len) + * Generate a random string (English alphabet). + * Parameter + * $len + * Length of the desired string (default: 16, Min: 1, Max: 1024) + * Return + * A pseudo random string. + * Note: + * JX9 use it's own private PRNG which is based on the one used + * by te SQLite3 library. + */ +static int vm_builtin_rand_str(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + char zString[1024]; + int iLen = 0x10; + if( nArg > 0 ){ + /* Get the desired length */ + iLen = jx9_value_to_int(apArg[0]); + if( iLen < 1 || iLen > 1024 ){ + /* Default length */ + iLen = 0x10; + } + } + /* Generate the random string */ + jx9VmRandomString(pCtx->pVm, zString, iLen); + /* Return the generated string */ + jx9_result_string(pCtx, zString, iLen); /* Will make it's own copy */ + return SXRET_OK; +} +/* + * Section: + * Language construct implementation as foreign functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * void print($string...) + * Output one or more messages. + * Parameters + * $string + * Message to output. + * Return + * NULL. + */ +static int vm_builtin_print(jx9_context *pCtx, int nArg,jx9_value **apArg) +{ + const char *zData; + int nDataLen = 0; + jx9_vm *pVm; + int i, rc; + /* Point to the target VM */ + pVm = pCtx->pVm; + /* Output */ + for( i = 0 ; i < nArg ; ++i ){ + zData = jx9_value_to_string(apArg[i], &nDataLen); + if( nDataLen > 0 ){ + rc = pVm->sVmConsumer.xConsumer((const void *)zData, (unsigned int)nDataLen, pVm->sVmConsumer.pUserData); + /* Increment output length */ + pVm->nOutputLen += nDataLen; + if( rc == SXERR_ABORT ){ + /* Output consumer callback request an operation abort */ + return JX9_ABORT; + } + } + } + return SXRET_OK; +} +/* + * void exit(string $msg) + * void exit(int $status) + * void die(string $ms) + * void die(int $status) + * Output a message and terminate program execution. + * Parameter + * If status is a string, this function prints the status just before exiting. + * If status is an integer, that value will be used as the exit status + * and not printed + * Return + * NULL + */ +static int vm_builtin_exit(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg > 0 ){ + if( jx9_value_is_string(apArg[0]) ){ + const char *zData; + int iLen = 0; + /* Print exit message */ + zData = jx9_value_to_string(apArg[0], &iLen); + jx9_context_output(pCtx, zData, iLen); + }else if(jx9_value_is_int(apArg[0]) ){ + sxi32 iExitStatus; + /* Record exit status code */ + iExitStatus = jx9_value_to_int(apArg[0]); + pCtx->pVm->iExitStatus = iExitStatus; + } + } + /* Abort processing immediately */ + return JX9_ABORT; +} +/* + * Unset a memory object [i.e: a jx9_value]. + */ +JX9_PRIVATE sxi32 jx9VmUnsetMemObj(jx9_vm *pVm,sxu32 nObjIdx) +{ + jx9_value *pObj; + pObj = (jx9_value *)SySetAt(&pVm->aMemObj, nObjIdx); + if( pObj ){ + VmSlot sFree; + /* Release the object */ + jx9MemObjRelease(pObj); + /* Restore to the free list */ + sFree.nIdx = nObjIdx; + sFree.pUserData = 0; + SySetPut(&pVm->aFreeObj, (const void *)&sFree); + } + return SXRET_OK; +} +/* + * string gettype($var) + * Get the type of a variable + * Parameters + * $var + * The variable being type checked. + * Return + * String representation of the given variable type. + */ +static int vm_builtin_gettype(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zType = "null"; + if( nArg > 0 ){ + zType = jx9MemObjTypeDump(apArg[0]); + } + /* Return the variable type */ + jx9_result_string(pCtx, zType, -1/*Compute length automatically*/); + return SXRET_OK; +} +/* + * string get_resource_type(resource $handle) + * This function gets the type of the given resource. + * Parameters + * $handle + * The evaluated resource handle. + * Return + * If the given handle is a resource, this function will return a string + * representing its type. If the type is not identified by this function + * the return value will be the string Unknown. + * This function will return FALSE and generate an error if handle + * is not a resource. + */ +static int vm_builtin_get_resource_type(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + if( nArg < 1 || !jx9_value_is_resource(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE*/ + jx9_result_bool(pCtx, 0); + return SXRET_OK; + } + jx9_result_string_format(pCtx, "resID_%#x", apArg[0]->x.pOther); + return SXRET_OK; +} +/* + * void dump(expression, ....) + * dump — Dumps information about a variable + * Parameters + * One or more expression to dump. + * Returns + * Nothing. + */ +static int vm_builtin_dump(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyBlob sDump; /* Generated dump is stored here */ + int i; + SyBlobInit(&sDump,&pCtx->pVm->sAllocator); + /* Dump one or more expressions */ + for( i = 0 ; i < nArg ; i++ ){ + jx9_value *pObj = apArg[i]; + /* Reset the working buffer */ + SyBlobReset(&sDump); + /* Dump the given expression */ + jx9MemObjDump(&sDump,pObj); + /* Output */ + if( SyBlobLength(&sDump) > 0 ){ + jx9_context_output(pCtx, (const char *)SyBlobData(&sDump), (int)SyBlobLength(&sDump)); + } + } + /* Release the working buffer */ + SyBlobRelease(&sDump); + return SXRET_OK; +} +/* + * Section: + * Version, Credits and Copyright related functions. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Stable. + */ +/* + * string jx9_version(void) + * string jx9_credits(void) + * Returns the running version of the jx9 version. + * Parameters + * None + * Return + * Current jx9 version. + */ +static int vm_builtin_jx9_version(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SXUNUSED(nArg); + SXUNUSED(apArg); /* cc warning */ + /* Current engine version, signature and cipyright notice */ + jx9_result_string_format(pCtx,"%s %s, %s",JX9_VERSION,JX9_SIG,JX9_COPYRIGHT); + return JX9_OK; +} +/* + * Section: + * URL related routines. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* Forward declaration */ +static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen); +/* + * value parse_url(string $url [, int $component = -1 ]) + * Parse a URL and return its fields. + * Parameters + * $url + * The URL to parse. + * $component + * Specify one of JX9_URL_SCHEME, JX9_URL_HOST, JX9_URL_PORT, JX9_URL_USER + * JX9_URL_PASS, JX9_URL_PATH, JX9_URL_QUERY or JX9_URL_FRAGMENT to retrieve + * just a specific URL component as a string (except when JX9_URL_PORT is given + * in which case the return value will be an integer). + * Return + * If the component parameter is omitted, an associative array is returned. + * At least one element will be present within the array. Potential keys within + * this array are: + * scheme - e.g. http + * host + * port + * user + * pass + * path + * query - after the question mark ? + * fragment - after the hashmark # + * Note: + * FALSE is returned on failure. + * This function work with relative URL unlike the one shipped + * with the standard JX9 engine. + */ +static int vm_builtin_parse_url(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zStr; /* Input string */ + SyString *pComp; /* Pointer to the URI component */ + SyhttpUri sURI; /* Parse of the given URI */ + int nLen; + sxi32 rc; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract the given URI */ + zStr = jx9_value_to_string(apArg[0], &nLen); + if( nLen < 1 ){ + /* Nothing to process, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Get a parse */ + rc = VmHttpSplitURI(&sURI, zStr, (sxu32)nLen); + if( rc != SXRET_OK ){ + /* Malformed input, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( nArg > 1 ){ + int nComponent = jx9_value_to_int(apArg[1]); + /* Refer to constant.c for constants values */ + switch(nComponent){ + case 1: /* JX9_URL_SCHEME */ + pComp = &sURI.sScheme; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 2: /* JX9_URL_HOST */ + pComp = &sURI.sHost; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 3: /* JX9_URL_PORT */ + pComp = &sURI.sPort; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + int iPort = 0; + /* Cast the value to integer */ + SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0); + jx9_result_int(pCtx, iPort); + } + break; + case 4: /* JX9_URL_USER */ + pComp = &sURI.sUser; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 5: /* JX9_URL_PASS */ + pComp = &sURI.sPass; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 7: /* JX9_URL_QUERY */ + pComp = &sURI.sQuery; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 8: /* JX9_URL_FRAGMENT */ + pComp = &sURI.sFragment; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + case 6: /* JX9_URL_PATH */ + pComp = &sURI.sPath; + if( pComp->nByte < 1 ){ + /* No available value, return NULL */ + jx9_result_null(pCtx); + }else{ + jx9_result_string(pCtx, pComp->zString, (int)pComp->nByte); + } + break; + default: + /* No such entry, return NULL */ + jx9_result_null(pCtx); + break; + } + }else{ + jx9_value *pArray, *pValue; + /* Return an associative array */ + pArray = jx9_context_new_array(pCtx); /* Empty array */ + pValue = jx9_context_new_scalar(pCtx); /* Array value */ + if( pArray == 0 || pValue == 0 ){ + /* Out of memory */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "jx9 engine is running out of memory"); + /* Return false */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Fill the array */ + pComp = &sURI.sScheme; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "scheme", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sHost; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "host", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sPort; + if( pComp->nByte > 0 ){ + int iPort = 0;/* cc warning */ + /* Convert to integer */ + SyStrToInt32(pComp->zString, pComp->nByte, (void *)&iPort, 0); + jx9_value_int(pValue, iPort); + jx9_array_add_strkey_elem(pArray, "port", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sUser; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "user", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sPass; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "pass", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sPath; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "path", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sQuery; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "query", pValue); /* Will make it's own copy */ + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pValue); + pComp = &sURI.sFragment; + if( pComp->nByte > 0 ){ + jx9_value_string(pValue, pComp->zString, (int)pComp->nByte); + jx9_array_add_strkey_elem(pArray, "fragment", pValue); /* Will make it's own copy */ + } + /* Return the created array */ + jx9_result_value(pCtx, pArray); + /* NOTE: + * Don't worry about freeing 'pValue', everything will be released + * automatically as soon we return from this function. + */ + } + /* All done */ + return JX9_OK; +} +/* + * Section: + * Array related routines. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + * Note 2012-5-21 01:04:15: + * Array related functions that need access to the underlying + * virtual machine are implemented here rather than 'hashmap.c' + */ +/* + * The [extract()] function store it's state information in an instance + * of the following structure. + */ +typedef struct extract_aux_data extract_aux_data; +struct extract_aux_data +{ + jx9_vm *pVm; /* VM that own this instance */ + int iCount; /* Number of variables successfully imported */ + const char *zPrefix; /* Prefix name */ + int Prefixlen; /* Prefix length */ + int iFlags; /* Control flags */ + char zWorker[1024]; /* Working buffer */ +}; +/* Forward declaration */ +static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData); +/* + * int extract(array $var_array[, int $extract_type = EXTR_OVERWRITE[, string $prefix = NULL ]]) + * Import variables into the current symbol table from an array. + * Parameters + * $var_array + * An associative array. This function treats keys as variable names and values + * as variable values. For each key/value pair it will create a variable in the current symbol + * table, subject to extract_type and prefix parameters. + * You must use an associative array; a numerically indexed array will not produce results + * unless you use EXTR_PREFIX_ALL or EXTR_PREFIX_INVALID. + * $extract_type + * The way invalid/numeric keys and collisions are treated is determined by the extract_type. + * It can be one of the following values: + * EXTR_OVERWRITE + * If there is a collision, overwrite the existing variable. + * EXTR_SKIP + * If there is a collision, don't overwrite the existing variable. + * EXTR_PREFIX_SAME + * If there is a collision, prefix the variable name with prefix. + * EXTR_PREFIX_ALL + * Prefix all variable names with prefix. + * EXTR_PREFIX_INVALID + * Only prefix invalid/numeric variable names with prefix. + * EXTR_IF_EXISTS + * Only overwrite the variable if it already exists in the current symbol table + * otherwise do nothing. + * This is useful for defining a list of valid variables and then extracting only those + * variables you have defined out of $_REQUEST, for example. + * EXTR_PREFIX_IF_EXISTS + * Only create prefixed variable names if the non-prefixed version of the same variable exists in + * the current symbol table. + * $prefix + * Note that prefix is only required if extract_type is EXTR_PREFIX_SAME, EXTR_PREFIX_ALL + * EXTR_PREFIX_INVALID or EXTR_PREFIX_IF_EXISTS. If the prefixed result is not a valid variable name + * it is not imported into the symbol table. Prefixes are automatically separated from the array key by an + * underscore character. + * Return + * Returns the number of variables successfully imported into the symbol table. + */ +static int vm_builtin_extract(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + extract_aux_data sAux; + jx9_hashmap *pMap; + if( nArg < 1 || !jx9_value_is_json_array(apArg[0]) ){ + /* Missing/Invalid arguments, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Point to the target hashmap */ + pMap = (jx9_hashmap *)apArg[0]->x.pOther; + if( pMap->nEntry < 1 ){ + /* Empty map, return 0 */ + jx9_result_int(pCtx, 0); + return JX9_OK; + } + /* Prepare the aux data */ + SyZero(&sAux, sizeof(extract_aux_data)-sizeof(sAux.zWorker)); + if( nArg > 1 ){ + sAux.iFlags = jx9_value_to_int(apArg[1]); + if( nArg > 2 ){ + sAux.zPrefix = jx9_value_to_string(apArg[2], &sAux.Prefixlen); + } + } + sAux.pVm = pCtx->pVm; + /* Invoke the worker callback */ + jx9HashmapWalk(pMap, VmExtractCallback, &sAux); + /* Number of variables successfully imported */ + jx9_result_int(pCtx, sAux.iCount); + return JX9_OK; +} +/* + * Worker callback for the [extract()] function defined + * below. + */ +static int VmExtractCallback(jx9_value *pKey, jx9_value *pValue, void *pUserData) +{ + extract_aux_data *pAux = (extract_aux_data *)pUserData; + int iFlags = pAux->iFlags; + jx9_vm *pVm = pAux->pVm; + jx9_value *pObj; + SyString sVar; + if( (iFlags & 0x10/* EXTR_PREFIX_INVALID */) && (pKey->iFlags & (MEMOBJ_INT|MEMOBJ_BOOL|MEMOBJ_REAL))){ + iFlags |= 0x08; /*EXTR_PREFIX_ALL*/ + } + /* Perform a string cast */ + jx9MemObjToString(pKey); + if( SyBlobLength(&pKey->sBlob) < 1 ){ + /* Unavailable variable name */ + return SXRET_OK; + } + sVar.nByte = 0; /* cc warning */ + if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/ ) && pAux->Prefixlen > 0 ){ + sVar.nByte = (sxu32)SyBufferFormat(pAux->zWorker, sizeof(pAux->zWorker), "%.*s_%.*s", + pAux->Prefixlen, pAux->zPrefix, + SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob) + ); + }else{ + sVar.nByte = (sxu32) SyMemcpy(SyBlobData(&pKey->sBlob), pAux->zWorker, + SXMIN(SyBlobLength(&pKey->sBlob), sizeof(pAux->zWorker))); + } + sVar.zString = pAux->zWorker; + /* Try to extract the variable */ + pObj = VmExtractMemObj(pVm, &sVar, TRUE, FALSE); + if( pObj ){ + /* Collision */ + if( iFlags & 0x02 /* EXTR_SKIP */ ){ + return SXRET_OK; + } + if( iFlags & 0x04 /* EXTR_PREFIX_SAME */ ){ + if( (iFlags & 0x08/*EXTR_PREFIX_ALL*/) || pAux->Prefixlen < 1){ + /* Already prefixed */ + return SXRET_OK; + } + sVar.nByte = SyBufferFormat( + pAux->zWorker, sizeof(pAux->zWorker), + "%.*s_%.*s", + pAux->Prefixlen, pAux->zPrefix, + SyBlobLength(&pKey->sBlob), SyBlobData(&pKey->sBlob) + ); + pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE); + } + }else{ + /* Create the variable */ + pObj = VmExtractMemObj(pVm, &sVar, TRUE, TRUE); + } + if( pObj ){ + /* Overwrite the old value */ + jx9MemObjStore(pValue, pObj); + /* Increment counter */ + pAux->iCount++; + } + return SXRET_OK; +} +/* + * Compile and evaluate a JX9 chunk at run-time. + * Refer to the include language construct implementation for more + * information. + */ +static sxi32 VmEvalChunk( + jx9_vm *pVm, /* Underlying Virtual Machine */ + jx9_context *pCtx, /* Call Context */ + SyString *pChunk, /* JX9 chunk to evaluate */ + int iFlags, /* Compile flag */ + int bTrueReturn /* TRUE to return execution result */ + ) +{ + SySet *pByteCode, aByteCode; + ProcConsumer xErr = 0; + void *pErrData = 0; + /* Initialize bytecode container */ + SySetInit(&aByteCode, &pVm->sAllocator, sizeof(VmInstr)); + SySetAlloc(&aByteCode, 0x20); + /* Reset the code generator */ + if( bTrueReturn ){ + /* Included file, log compile-time errors */ + xErr = pVm->pEngine->xConf.xErr; + pErrData = pVm->pEngine->xConf.pErrData; + } + jx9ResetCodeGenerator(pVm, xErr, pErrData); + /* Swap bytecode container */ + pByteCode = pVm->pByteContainer; + pVm->pByteContainer = &aByteCode; + /* Compile the chunk */ + jx9CompileScript(pVm, pChunk, iFlags); + if( pVm->sCodeGen.nErr > 0 ){ + /* Compilation error, return false */ + if( pCtx ){ + jx9_result_bool(pCtx, 0); + } + }else{ + jx9_value sResult; /* Return value */ + if( SXRET_OK != jx9VmEmitInstr(pVm, JX9_OP_DONE, 0, 0, 0, 0) ){ + /* Out of memory */ + if( pCtx ){ + jx9_result_bool(pCtx, 0); + } + goto Cleanup; + } + if( bTrueReturn ){ + /* Assume a boolean true return value */ + jx9MemObjInitFromBool(pVm, &sResult, 1); + }else{ + /* Assume a null return value */ + jx9MemObjInit(pVm, &sResult); + } + /* Execute the compiled chunk */ + VmLocalExec(pVm, &aByteCode, &sResult); + if( pCtx ){ + /* Set the execution result */ + jx9_result_value(pCtx, &sResult); + } + jx9MemObjRelease(&sResult); + } +Cleanup: + /* Cleanup the mess left behind */ + pVm->pByteContainer = pByteCode; + SySetRelease(&aByteCode); + return SXRET_OK; +} +/* + * Check if a file path is already included. + */ +static int VmIsIncludedFile(jx9_vm *pVm, SyString *pFile) +{ + SyString *aEntries; + sxu32 n; + aEntries = (SyString *)SySetBasePtr(&pVm->aIncluded); + /* Perform a linear search */ + for( n = 0 ; n < SySetUsed(&pVm->aIncluded) ; ++n ){ + if( SyStringCmp(pFile, &aEntries[n], SyMemcmp) == 0 ){ + /* Already included */ + return TRUE; + } + } + return FALSE; +} +/* + * Push a file path in the appropriate VM container. + */ +JX9_PRIVATE sxi32 jx9VmPushFilePath(jx9_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew) +{ + SyString sPath; + char *zDup; +#ifdef __WINNT__ + char *zCur; +#endif + sxi32 rc; + if( nLen < 0 ){ + nLen = SyStrlen(zPath); + } + /* Duplicate the file path first */ + zDup = SyMemBackendStrDup(&pVm->sAllocator, zPath, nLen); + if( zDup == 0 ){ + return SXERR_MEM; + } +#ifdef __WINNT__ + /* Normalize path on windows + * Example: + * Path/To/File.jx9 + * becomes + * path\to\file.jx9 + */ + zCur = zDup; + while( zCur[0] != 0 ){ + if( zCur[0] == '/' ){ + zCur[0] = '\\'; + }else if( (unsigned char)zCur[0] < 0xc0 && SyisUpper(zCur[0]) ){ + int c = SyToLower(zCur[0]); + zCur[0] = (char)c; /* MSVC stupidity */ + } + zCur++; + } +#endif + /* Install the file path */ + SyStringInitFromBuf(&sPath, zDup, nLen); + if( !bMain ){ + if( VmIsIncludedFile(&(*pVm), &sPath) ){ + /* Already included */ + *pNew = 0; + }else{ + /* Insert in the corresponding container */ + rc = SySetPut(&pVm->aIncluded, (const void *)&sPath); + if( rc != SXRET_OK ){ + SyMemBackendFree(&pVm->sAllocator, zDup); + return rc; + } + *pNew = 1; + } + } + SySetPut(&pVm->aFiles, (const void *)&sPath); + return SXRET_OK; +} +/* + * Compile and Execute a JX9 script at run-time. + * SXRET_OK is returned on sucessful evaluation.Any other return values + * indicates failure. + * Note that the JX9 script to evaluate can be a local or remote file.In + * either cases the [jx9StreamReadWholeFile()] function handle all the underlying + * operations. + * If the [jJX9_DISABLE_BUILTIN_FUNC] compile-time directive is defined, then + * this function is a no-op. + * Refer to the implementation of the include(), import() language + * constructs for more information. + */ +static sxi32 VmExecIncludedFile( + jx9_context *pCtx, /* Call Context */ + SyString *pPath, /* Script path or URL*/ + int IncludeOnce /* TRUE if called from import() or require_once() */ + ) +{ + sxi32 rc; +#ifndef JX9_DISABLE_BUILTIN_FUNC + const jx9_io_stream *pStream; + SyBlob sContents; + void *pHandle; + jx9_vm *pVm; + int isNew; + /* Initialize fields */ + pVm = pCtx->pVm; + SyBlobInit(&sContents, &pVm->sAllocator); + isNew = 0; + /* Extract the associated stream */ + pStream = jx9VmGetStreamDevice(pVm, &pPath->zString, pPath->nByte); + /* + * Open the file or the URL [i.e: http://jx9.symisc.net/example/hello.jx9.txt"] + * in a read-only mode. + */ + pHandle = jx9StreamOpenHandle(pVm, pStream,pPath->zString, JX9_IO_OPEN_RDONLY, TRUE, 0, TRUE, &isNew); + if( pHandle == 0 ){ + return SXERR_IO; + } + rc = SXRET_OK; /* Stupid cc warning */ + if( IncludeOnce && !isNew ){ + /* Already included */ + rc = SXERR_EXISTS; + }else{ + /* Read the whole file contents */ + rc = jx9StreamReadWholeFile(pHandle, pStream, &sContents); + if( rc == SXRET_OK ){ + SyString sScript; + /* Compile and execute the script */ + SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents)); + VmEvalChunk(pCtx->pVm, &(*pCtx), &sScript, 0, TRUE); + } + } + /* Pop from the set of included file */ + (void)SySetPop(&pVm->aFiles); + /* Close the handle */ + jx9StreamCloseHandle(pStream, pHandle); + /* Release the working buffer */ + SyBlobRelease(&sContents); +#else + pCtx = 0; /* cc warning */ + pPath = 0; + IncludeOnce = 0; + rc = SXERR_IO; +#endif /* JX9_DISABLE_BUILTIN_FUNC */ + return rc; +} +/* * include: + * According to the JX9 reference manual. + * The include() function includes and evaluates the specified file. + * Files are included based on the file path given or, if none is given + * the include_path specified.If the file isn't found in the include_path + * include() will finally check in the calling script's own directory + * and the current working directory before failing. The include() + * construct will emit a warning if it cannot find a file; this is different + * behavior from require(), which will emit a fatal error. + * If a path is defined — whether absolute (starting with a drive letter + * or \ on Windows, or / on Unix/Linux systems) or relative to the current + * directory (starting with . or ..) — the include_path will be ignored altogether. + * For example, if a filename begins with ../, the parser will look in the parent + * directory to find the requested file. + * When a file is included, the code it contains inherits the variable scope + * of the line on which the include occurs. Any variables available at that line + * in the calling file will be available within the called file, from that point forward. + * However, all functions and objectes defined in the included file have the global scope. + */ +static int vm_builtin_include(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Open, compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx), &sFile, FALSE); + if( rc != SXRET_OK ){ + /* Emit a warning and return false */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile); + jx9_result_bool(pCtx, 0); + } + return SXRET_OK; +} +/* + * import: + * According to the JX9 reference manual. + * The import() statement includes and evaluates the specified file during + * the execution of the script. This is a behavior similar to the include() + * statement, with the only difference being that if the code from a file has already + * been included, it will not be included again. As the name suggests, it will be included + * just once. + */ +static int vm_builtin_import(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + SyString sFile; + sxi32 rc; + if( nArg < 1 ){ + /* Nothing to evaluate, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* File to include */ + sFile.zString = jx9_value_to_string(apArg[0], (int *)&sFile.nByte); + if( sFile.nByte < 1 ){ + /* Empty string, return NULL */ + jx9_result_null(pCtx); + return SXRET_OK; + } + /* Open, compile and execute the desired script */ + rc = VmExecIncludedFile(&(*pCtx), &sFile, TRUE); + if( rc == SXERR_EXISTS ){ + /* File already included, return TRUE */ + jx9_result_bool(pCtx, 1); + return SXRET_OK; + } + if( rc != SXRET_OK ){ + /* Emit a warning and return false */ + jx9_context_throw_error_format(pCtx, JX9_CTX_WARNING, "IO error while importing: '%z'", &sFile); + jx9_result_bool(pCtx, 0); + } + return SXRET_OK; +} +/* + * Section: + * Command line arguments processing. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ +/* + * Check if a short option argument [i.e: -c] is available in the command + * line string. Return a pointer to the start of the stream on success. + * NULL otherwise. + */ +static const char * VmFindShortOpt(int c, const char *zIn, const char *zEnd) +{ + while( zIn < zEnd ){ + if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == c ){ + /* Got one */ + return &zIn[1]; + } + /* Advance the cursor */ + zIn++; + } + /* No such option */ + return 0; +} +/* + * Check if a long option argument [i.e: --opt] is available in the command + * line string. Return a pointer to the start of the stream on success. + * NULL otherwise. + */ +static const char * VmFindLongOpt(const char *zLong, int nByte, const char *zIn, const char *zEnd) +{ + const char *zOpt; + while( zIn < zEnd ){ + if( zIn[0] == '-' && &zIn[1] < zEnd && (int)zIn[1] == '-' ){ + zIn += 2; + zOpt = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + if( zIn[0] == '=' /* --opt=val */){ + break; + } + zIn++; + } + /* Test */ + if( (int)(zIn-zOpt) == nByte && SyMemcmp(zOpt, zLong, nByte) == 0 ){ + /* Got one, return it's value */ + return zIn; + } + + }else{ + zIn++; + } + } + /* No such option */ + return 0; +} +/* + * Long option [i.e: --opt] arguments private data structure. + */ +struct getopt_long_opt +{ + const char *zArgIn, *zArgEnd; /* Command line arguments */ + jx9_value *pWorker; /* Worker variable*/ + jx9_value *pArray; /* getopt() return value */ + jx9_context *pCtx; /* Call Context */ +}; +/* Forward declaration */ +static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData); +/* + * Extract short or long argument option values. + */ +static void VmExtractOptArgValue( + jx9_value *pArray, /* getopt() return value */ + jx9_value *pWorker, /* Worker variable */ + const char *zArg, /* Argument stream */ + const char *zArgEnd, /* End of the argument stream */ + int need_val, /* TRUE to fetch option argument */ + jx9_context *pCtx, /* Call Context */ + const char *zName /* Option name */) +{ + jx9_value_bool(pWorker, 0); + if( !need_val ){ + /* + * Option does not need arguments. + * Insert the option name and a boolean FALSE. + */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ + }else{ + const char *zCur; + /* Extract option argument */ + zArg++; + if( zArg < zArgEnd && zArg[0] == '=' ){ + zArg++; + } + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + if( zArg >= zArgEnd || zArg[0] == '-' ){ + /* + * Argument not found. + * Insert the option name and a boolean FALSE. + */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ + return; + } + /* Delimit the value */ + zCur = zArg; + if( zArg[0] == '\'' || zArg[0] == '"' ){ + int d = zArg[0]; + /* Delimt the argument */ + zArg++; + zCur = zArg; + while( zArg < zArgEnd ){ + if( zArg[0] == d && zArg[-1] != '\\' ){ + /* Delimiter found, exit the loop */ + break; + } + zArg++; + } + /* Save the value */ + jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); + if( zArg < zArgEnd ){ zArg++; } + }else{ + while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ + zArg++; + } + /* Save the value */ + jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); + } + /* + * Check if we are dealing with multiple values. + * If so, create an array to hold them, rather than a scalar variable. + */ + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + if( zArg < zArgEnd && zArg[0] != '-' ){ + jx9_value *pOptArg; /* Array of option arguments */ + pOptArg = jx9_context_new_array(pCtx); + if( pOptArg == 0 ){ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "JX9 is running out of memory"); + }else{ + /* Insert the first value */ + jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */ + for(;;){ + if( zArg >= zArgEnd || zArg[0] == '-' ){ + /* No more value */ + break; + } + /* Delimit the value */ + zCur = zArg; + if( zArg < zArgEnd && zArg[0] == '\\' ){ + zArg++; + zCur = zArg; + } + while( zArg < zArgEnd && !SyisSpace(zArg[0]) ){ + zArg++; + } + /* Reset the string cursor */ + jx9_value_reset_string_cursor(pWorker); + /* Save the value */ + jx9_value_string(pWorker, zCur, (int)(zArg-zCur)); + /* Insert */ + jx9_array_add_elem(pOptArg, 0, pWorker); /* Will make it's own copy */ + /* Jump trailing white spaces */ + while( zArg < zArgEnd && (unsigned char)zArg[0] < 0xc0 && SyisSpace(zArg[0]) ){ + zArg++; + } + } + /* Insert the option arg array */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pOptArg); /* Will make it's own copy */ + /* Safely release */ + jx9_context_release_value(pCtx, pOptArg); + } + }else{ + /* Single value */ + jx9_array_add_strkey_elem(pArray, (const char *)zName, pWorker); /* Will make it's own copy */ + } + } +} +/* + * array getopt(string $options[, array $longopts ]) + * Gets options from the command line argument list. + * Parameters + * $options + * Each character in this string will be used as option characters + * and matched against options passed to the script starting with + * a single hyphen (-). For example, an option string "x" recognizes + * an option -x. Only a-z, A-Z and 0-9 are allowed. + * $longopts + * An array of options. Each element in this array will be used as option + * strings and matched against options passed to the script starting with + * two hyphens (--). For example, an longopts element "opt" recognizes an + * option --opt. + * Return + * This function will return an array of option / argument pairs or FALSE + * on failure. + */ +static int vm_builtin_getopt(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zIn, *zEnd, *zArg, *zArgIn, *zArgEnd; + struct getopt_long_opt sLong; + jx9_value *pArray, *pWorker; + SyBlob *pArg; + int nByte; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return FALSE */ + jx9_context_throw_error(pCtx, JX9_CTX_ERR, "Missing/Invalid option arguments"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Extract option arguments */ + zIn = jx9_value_to_string(apArg[0], &nByte); + zEnd = &zIn[nByte]; + /* Point to the string representation of the $argv[] array */ + pArg = &pCtx->pVm->sArgv; + /* Create a new empty array and a worker variable */ + pArray = jx9_context_new_array(pCtx); + pWorker = jx9_context_new_scalar(pCtx); + if( pArray == 0 || pWorker == 0 ){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR, "JX9 is running out of memory"); + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + if( SyBlobLength(pArg) < 1 ){ + /* Empty command line, return the empty array*/ + jx9_result_value(pCtx, pArray); + /* Everything will be released automatically when we return + * from this function. + */ + return JX9_OK; + } + zArgIn = (const char *)SyBlobData(pArg); + zArgEnd = &zArgIn[SyBlobLength(pArg)]; + /* Fill the long option structure */ + sLong.pArray = pArray; + sLong.pWorker = pWorker; + sLong.zArgIn = zArgIn; + sLong.zArgEnd = zArgEnd; + sLong.pCtx = pCtx; + /* Start processing */ + while( zIn < zEnd ){ + int c = zIn[0]; + int need_val = 0; + /* Advance the stream cursor */ + zIn++; + /* Ignore non-alphanum characters */ + if( !SyisAlphaNum(c) ){ + continue; + } + if( zIn < zEnd && zIn[0] == ':' ){ + zIn++; + need_val = 1; + if( zIn < zEnd && zIn[0] == ':' ){ + zIn++; + } + } + /* Find option */ + zArg = VmFindShortOpt(c, zArgIn, zArgEnd); + if( zArg == 0 ){ + /* No such option */ + continue; + } + /* Extract option argument value */ + VmExtractOptArgValue(pArray, pWorker, zArg, zArgEnd, need_val, pCtx, (const char *)&c); + } + if( nArg > 1 && jx9_value_is_json_array(apArg[1]) && jx9_array_count(apArg[1]) > 0 ){ + /* Process long options */ + jx9_array_walk(apArg[1], VmProcessLongOpt, &sLong); + } + /* Return the option array */ + jx9_result_value(pCtx, pArray); + /* + * Don't worry about freeing memory, everything will be released + * automatically as soon we return from this foreign function. + */ + return JX9_OK; +} +/* + * Array walker callback used for processing long options values. + */ +static int VmProcessLongOpt(jx9_value *pKey, jx9_value *pValue, void *pUserData) +{ + struct getopt_long_opt *pOpt = (struct getopt_long_opt *)pUserData; + const char *zArg, *zOpt, *zEnd; + int need_value = 0; + int nByte; + /* Value must be of type string */ + if( !jx9_value_is_string(pValue) ){ + /* Simply ignore */ + return JX9_OK; + } + zOpt = jx9_value_to_string(pValue, &nByte); + if( nByte < 1 ){ + /* Empty string, ignore */ + return JX9_OK; + } + zEnd = &zOpt[nByte - 1]; + if( zEnd[0] == ':' ){ + char *zTerm; + /* Try to extract a value */ + need_value = 1; + while( zEnd >= zOpt && zEnd[0] == ':' ){ + zEnd--; + } + if( zOpt >= zEnd ){ + /* Empty string, ignore */ + SXUNUSED(pKey); + return JX9_OK; + } + zEnd++; + zTerm = (char *)zEnd; + zTerm[0] = 0; + }else{ + zEnd = &zOpt[nByte]; + } + /* Find the option */ + zArg = VmFindLongOpt(zOpt, (int)(zEnd-zOpt), pOpt->zArgIn, pOpt->zArgEnd); + if( zArg == 0 ){ + /* No such option, return immediately */ + return JX9_OK; + } + /* Try to extract a value */ + VmExtractOptArgValue(pOpt->pArray, pOpt->pWorker, zArg, pOpt->zArgEnd, need_value, pOpt->pCtx, zOpt); + return JX9_OK; +} +/* + * int utf8_encode(string $input) + * UTF-8 encoding. + * This function encodes the string data to UTF-8, and returns the encoded version. + * UTF-8 is a standard mechanism used by Unicode for encoding wide character values + * into a byte stream. UTF-8 is transparent to plain ASCII characters, is self-synchronized + * (meaning it is possible for a program to figure out where in the bytestream characters start) + * and can be used with normal string comparison functions for sorting and such. + * Notes on UTF-8 (According to SQLite3 authors): + * Byte-0 Byte-1 Byte-2 Byte-3 Value + * 0xxxxxxx 00000000 00000000 0xxxxxxx + * 110yyyyy 10xxxxxx 00000000 00000yyy yyxxxxxx + * 1110zzzz 10yyyyyy 10xxxxxx 00000000 zzzzyyyy yyxxxxxx + * 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx 000uuuuu zzzzyyyy yyxxxxxx + * Parameters + * $input + * String to encode or NULL on failure. + * Return + * An UTF-8 encoded string. + */ +static int vm_builtin_utf8_encode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nByte, c, e; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte); + if( nByte < 1 ){ + /* Empty string, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + zEnd = &zIn[nByte]; + /* Start the encoding process */ + for(;;){ + if( zIn >= zEnd ){ + /* End of input */ + break; + } + c = zIn[0]; + /* Advance the stream cursor */ + zIn++; + /* Encode */ + if( c<0x00080 ){ + e = (c&0xFF); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + }else if( c<0x00800 ){ + e = 0xC0 + ((c>>6)&0x1F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + (c & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + }else if( c<0x10000 ){ + e = 0xE0 + ((c>>12)&0x0F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + ((c>>6) & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + (c & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + }else{ + e = 0xF0 + ((c>>18) & 0x07); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + ((c>>12) & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + ((c>>6) & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + e = 0x80 + (c & 0x3F); + jx9_result_string(pCtx, (const char *)&e, (int)sizeof(char)); + } + } + /* All done */ + return JX9_OK; +} +/* + * UTF-8 decoding routine extracted from the sqlite3 source tree. + * Original author: D. Richard Hipp (http://www.sqlite.org) + * Status: Public Domain + */ +/* +** This lookup table is used to help decode the first byte of +** a multi-byte UTF8 character. +*/ +static const unsigned char UtfTrans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00, +}; +/* +** Translate a single UTF-8 character. Return the unicode value. +** +** During translation, assume that the byte that zTerm points +** is a 0x00. +** +** Write a pointer to the next unread byte back into *pzNext. +** +** Notes On Invalid UTF-8: +** +** * This routine never allows a 7-bit character (0x00 through 0x7f) to +** be encoded as a multi-byte character. Any multi-byte character that +** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd. +** +** * This routine never allows a UTF16 surrogate value to be encoded. +** If a multi-byte character attempts to encode a value between +** 0xd800 and 0xe000 then it is rendered as 0xfffd. +** +** * Bytes in the range of 0x80 through 0xbf which occur as the first +** byte of a character are interpreted as single-byte characters +** and rendered as themselves even though they are technically +** invalid characters. +** +** * This routine accepts an infinite number of different UTF8 encodings +** for unicode values 0x80 and greater. It do not change over-length +** encodings to 0xfffd as some systems recommend. +*/ +#define READ_UTF8(zIn, zTerm, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = UtfTrans1[c-0xc0]; \ + while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + if( c<0x80 \ + || (c&0xFFFFF800)==0xD800 \ + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ + } +JX9_PRIVATE int jx9Utf8Read( + const unsigned char *z, /* First byte of UTF-8 character */ + const unsigned char *zTerm, /* Pretend this byte is 0x00 */ + const unsigned char **pzNext /* Write first byte past UTF-8 char here */ +){ + int c; + READ_UTF8(z, zTerm, c); + *pzNext = z; + return c; +} +/* + * string utf8_decode(string $data) + * This function decodes data, assumed to be UTF-8 encoded, to unicode. + * Parameters + * data + * An UTF-8 encoded string. + * Return + * Unicode decoded string or NULL on failure. + */ +static int vm_builtin_utf8_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const unsigned char *zIn, *zEnd; + int nByte, c; + if( nArg < 1 ){ + /* Missing arguments, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the target string */ + zIn = (const unsigned char *)jx9_value_to_string(apArg[0], &nByte); + if( nByte < 1 ){ + /* Empty string, return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + zEnd = &zIn[nByte]; + /* Start the decoding process */ + while( zIn < zEnd ){ + c = jx9Utf8Read(zIn, zEnd, &zIn); + if( c == 0x0 ){ + break; + } + jx9_result_string(pCtx, (const char *)&c, (int)sizeof(char)); + } + return JX9_OK; +} +/* + * string json_encode(mixed $value) + * Returns a string containing the JSON representation of value. + * Parameters + * $value + * The value being encoded. Can be any type except a resource. + * Return + * Returns a JSON encoded string on success. FALSE otherwise + */ +static int vm_builtin_json_encode(jx9_context *pCtx,int nArg,jx9_value **apArg) +{ + SyBlob sBlob; + if( nArg < 1 ){ + /* Missing arguments, return FALSE */ + jx9_result_bool(pCtx, 0); + return JX9_OK; + } + /* Init the working buffer */ + SyBlobInit(&sBlob,&pCtx->pVm->sAllocator); + /* Perform the encoding operation */ + jx9JsonSerialize(apArg[0],&sBlob); + /* Return the serialized value */ + jx9_result_string(pCtx,(const char *)SyBlobData(&sBlob),(int)SyBlobLength(&sBlob)); + /* Cleanup */ + SyBlobRelease(&sBlob); + /* All done */ + return JX9_OK; +} +/* + * mixed json_decode(string $json) + * Takes a JSON encoded string and converts it into a JX9 variable. + * Parameters + * $json + * The json string being decoded. + * Return + * The value encoded in json in appropriate JX9 type. Values true, false and null (case-insensitive) + * are returned as TRUE, FALSE and NULL respectively. NULL is returned if the json cannot be decoded + * or if the encoded data is deeper than the recursion limit. + */ +static int vm_builtin_json_decode(jx9_context *pCtx, int nArg, jx9_value **apArg) +{ + const char *zJSON; + int nByte; + if( nArg < 1 || !jx9_value_is_string(apArg[0]) ){ + /* Missing/Invalid arguments, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the JSON string */ + zJSON = jx9_value_to_string(apArg[0], &nByte); + if( nByte < 1 ){ + /* Empty string, return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Decode the raw JSON */ + jx9JsonDecode(pCtx,zJSON,nByte); + return JX9_OK; +} +/* Table of built-in VM functions. */ +static const jx9_builtin_func aVmFunc[] = { + /* JSON Encoding/Decoding */ + { "json_encode", vm_builtin_json_encode }, + { "json_decode", vm_builtin_json_decode }, + /* Functions calls */ + { "func_num_args" , vm_builtin_func_num_args }, + { "func_get_arg" , vm_builtin_func_get_arg }, + { "func_get_args" , vm_builtin_func_get_args }, + { "function_exists", vm_builtin_func_exists }, + { "is_callable" , vm_builtin_is_callable }, + { "get_defined_functions", vm_builtin_get_defined_func }, + /* Constants management */ + { "defined", vm_builtin_defined }, + { "get_defined_constants", vm_builtin_get_defined_constants }, + /* Random numbers/strings generators */ + { "rand", vm_builtin_rand }, + { "rand_str", vm_builtin_rand_str }, + { "getrandmax", vm_builtin_getrandmax }, + /* Language constructs functions */ + { "print", vm_builtin_print }, + { "exit", vm_builtin_exit }, + { "die", vm_builtin_exit }, + /* Variable handling functions */ + { "gettype", vm_builtin_gettype }, + { "get_resource_type", vm_builtin_get_resource_type}, + /* Variable dumping */ + { "dump", vm_builtin_dump }, + /* Release info */ + {"jx9_version", vm_builtin_jx9_version }, + {"jx9_credits", vm_builtin_jx9_version }, + {"jx9_info", vm_builtin_jx9_version }, + {"jx9_copyright", vm_builtin_jx9_version }, + /* hashmap */ + {"extract", vm_builtin_extract }, + /* URL related function */ + {"parse_url", vm_builtin_parse_url }, + /* UTF-8 encoding/decoding */ + {"utf8_encode", vm_builtin_utf8_encode}, + {"utf8_decode", vm_builtin_utf8_decode}, + /* Command line processing */ + {"getopt", vm_builtin_getopt }, + /* Files/URI inclusion facility */ + { "include", vm_builtin_include }, + { "import", vm_builtin_import } +}; +/* + * Register the built-in VM functions defined above. + */ +static sxi32 VmRegisterSpecialFunction(jx9_vm *pVm) +{ + sxi32 rc; + sxu32 n; + for( n = 0 ; n < SX_ARRAYSIZE(aVmFunc) ; ++n ){ + /* Note that these special functions have access + * to the underlying virtual machine as their + * private data. + */ + rc = jx9_create_function(&(*pVm), aVmFunc[n].zName, aVmFunc[n].xFunc, &(*pVm)); + if( rc != SXRET_OK ){ + return rc; + } + } + return SXRET_OK; +} +#ifndef JX9_DISABLE_BUILTIN_FUNC +/* + * Extract the IO stream device associated with a given scheme. + * Return a pointer to an instance of jx9_io_stream when the scheme + * have an associated IO stream registered with it. NULL otherwise. + * If no scheme:// is avalilable then the file:// scheme is assumed. + * For more information on how to register IO stream devices, please + * refer to the official documentation. + */ +JX9_PRIVATE const jx9_io_stream * jx9VmGetStreamDevice( + jx9_vm *pVm, /* Target VM */ + const char **pzDevice, /* Full path, URI, ... */ + int nByte /* *pzDevice length*/ + ) +{ + const char *zIn, *zEnd, *zCur, *zNext; + jx9_io_stream **apStream, *pStream; + SyString sDev, sCur; + sxu32 n, nEntry; + int rc; + /* Check if a scheme [i.e: file://, http://, zip://...] is available */ + zNext = zCur = zIn = *pzDevice; + zEnd = &zIn[nByte]; + while( zIn < zEnd ){ + if( zIn < &zEnd[-3]/*://*/ && zIn[0] == ':' && zIn[1] == '/' && zIn[2] == '/' ){ + /* Got one */ + zNext = &zIn[sizeof("://")-1]; + break; + } + /* Advance the cursor */ + zIn++; + } + if( zIn >= zEnd ){ + /* No such scheme, return the default stream */ + return pVm->pDefStream; + } + SyStringInitFromBuf(&sDev, zCur, zIn-zCur); + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sDev); + /* Perform a linear lookup on the installed stream devices */ + apStream = (jx9_io_stream **)SySetBasePtr(&pVm->aIOstream); + nEntry = SySetUsed(&pVm->aIOstream); + for( n = 0 ; n < nEntry ; n++ ){ + pStream = apStream[n]; + SyStringInitFromBuf(&sCur, pStream->zName, SyStrlen(pStream->zName)); + /* Perfrom a case-insensitive comparison */ + rc = SyStringCmp(&sDev, &sCur, SyStrnicmp); + if( rc == 0 ){ + /* Stream device found */ + *pzDevice = zNext; + return pStream; + } + } + /* No such stream, return NULL */ + return 0; +} +#endif /* JX9_DISABLE_BUILTIN_FUNC */ +/* + * Section: + * HTTP/URI related routines. + * Authors: + * Symisc Systems, devel@symisc.net. + * Copyright (C) Symisc Systems, http://jx9.symisc.net + * Status: + * Stable. + */ + /* + * URI Parser: Split an URI into components [i.e: Host, Path, Query, ...]. + * URI syntax: [method:/][/[user[:pwd]@]host[:port]/][document] + * This almost, but not quite, RFC1738 URI syntax. + * This routine is not a validator, it does not check for validity + * nor decode URI parts, the only thing this routine does is splitting + * the input to its fields. + * Upper layer are responsible of decoding and validating URI parts. + * On success, this function populate the "SyhttpUri" structure passed + * as the first argument. Otherwise SXERR_* is returned when a malformed + * input is encountered. + */ + static sxi32 VmHttpSplitURI(SyhttpUri *pOut, const char *zUri, sxu32 nLen) + { + const char *zEnd = &zUri[nLen]; + sxu8 bHostOnly = FALSE; + sxu8 bIPv6 = FALSE ; + const char *zCur; + SyString *pComp; + sxu32 nPos = 0; + sxi32 rc; + /* Zero the structure first */ + SyZero(pOut, sizeof(SyhttpUri)); + /* Remove leading and trailing white spaces */ + SyStringInitFromBuf(&pOut->sRaw, zUri, nLen); + SyStringFullTrim(&pOut->sRaw); + /* Find the first '/' separator */ + rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos); + if( rc != SXRET_OK ){ + /* Assume a host name only */ + zCur = zEnd; + bHostOnly = TRUE; + goto ProcessHost; + } + zCur = &zUri[nPos]; + if( zUri != zCur && zCur[-1] == ':' ){ + /* Extract a scheme: + * Not that we can get an invalid scheme here. + * Fortunately the caller can discard any URI by comparing this scheme with its + * registered schemes and will report the error as soon as his comparison function + * fail. + */ + pComp = &pOut->sScheme; + SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri - 1)); + SyStringLeftTrim(pComp); + } + if( zCur[1] != '/' ){ + if( zCur == zUri || zCur[-1] == ':' ){ + /* No authority */ + goto PathSplit; + } + /* There is something here , we will assume its an authority + * and someone has forgot the two prefix slashes "//", + * sooner or later we will detect if we are dealing with a malicious + * user or not, but now assume we are dealing with an authority + * and let the caller handle all the validation process. + */ + goto ProcessHost; + } + zUri = &zCur[2]; + zCur = zEnd; + rc = SyByteFind(zUri, (sxu32)(zEnd - zUri), '/', &nPos); + if( rc == SXRET_OK ){ + zCur = &zUri[nPos]; + } + ProcessHost: + /* Extract user information if present */ + rc = SyByteFind(zUri, (sxu32)(zCur - zUri), '@', &nPos); + if( rc == SXRET_OK ){ + if( nPos > 0 ){ + sxu32 nPassOfft; /* Password offset */ + pComp = &pOut->sUser; + SyStringInitFromBuf(pComp, zUri, nPos); + /* Extract the password if available */ + rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPassOfft); + if( rc == SXRET_OK && nPassOfft < nPos){ + pComp->nByte = nPassOfft; + pComp = &pOut->sPass; + pComp->zString = &zUri[nPassOfft+sizeof(char)]; + pComp->nByte = nPos - nPassOfft - 1; + } + /* Update the cursor */ + zUri = &zUri[nPos+1]; + }else{ + zUri++; + } + } + pComp = &pOut->sHost; + while( zUri < zCur && SyisSpace(zUri[0])){ + zUri++; + } + SyStringInitFromBuf(pComp, zUri, (sxu32)(zCur - zUri)); + if( pComp->zString[0] == '[' ){ + /* An IPv6 Address: Make a simple naive test + */ + zUri++; pComp->zString++; pComp->nByte = 0; + while( ((unsigned char)zUri[0] < 0xc0 && SyisHex(zUri[0])) || zUri[0] == ':' ){ + zUri++; pComp->nByte++; + } + if( zUri[0] != ']' ){ + return SXERR_CORRUPT; /* Malformed IPv6 address */ + } + zUri++; + bIPv6 = TRUE; + } + /* Extract a port number if available */ + rc = SyByteFind(zUri, (sxu32)(zCur - zUri), ':', &nPos); + if( rc == SXRET_OK ){ + if( bIPv6 == FALSE ){ + pComp->nByte = (sxu32)(&zUri[nPos] - zUri); + } + pComp = &pOut->sPort; + SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zCur - &zUri[nPos+1])); + } + if( bHostOnly == TRUE ){ + return SXRET_OK; + } +PathSplit: + zUri = zCur; + pComp = &pOut->sPath; + SyStringInitFromBuf(pComp, zUri, (sxu32)(zEnd-zUri)); + if( pComp->nByte == 0 ){ + return SXRET_OK; /* Empty path */ + } + if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '?', &nPos) ){ + pComp->nByte = nPos; /* Update path length */ + pComp = &pOut->sQuery; + SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1])); + } + if( SXRET_OK == SyByteFind(zUri, (sxu32)(zEnd-zUri), '#', &nPos) ){ + /* Update path or query length */ + if( pComp == &pOut->sPath ){ + pComp->nByte = nPos; + }else{ + if( &zUri[nPos] < (char *)SyStringData(pComp) ){ + /* Malformed syntax : Query must be present before fragment */ + return SXERR_SYNTAX; + } + pComp->nByte -= (sxu32)(zEnd - &zUri[nPos]); + } + pComp = &pOut->sFragment; + SyStringInitFromBuf(pComp, &zUri[nPos+1], (sxu32)(zEnd-&zUri[nPos+1])) + } + return SXRET_OK; + } + /* + * Extract a single line from a raw HTTP request. + * Return SXRET_OK on success, SXERR_EOF when end of input + * and SXERR_MORE when more input is needed. + */ +static sxi32 VmGetNextLine(SyString *pCursor, SyString *pCurrent) +{ + const char *zIn; + sxu32 nPos; + /* Jump leading white spaces */ + SyStringLeftTrim(pCursor); + if( pCursor->nByte < 1 ){ + SyStringInitFromBuf(pCurrent, 0, 0); + return SXERR_EOF; /* End of input */ + } + zIn = SyStringData(pCursor); + if( SXRET_OK != SyByteListFind(pCursor->zString, pCursor->nByte, "\r\n", &nPos) ){ + /* Line not found, tell the caller to read more input from source */ + SyStringDupPtr(pCurrent, pCursor); + return SXERR_MORE; + } + pCurrent->zString = zIn; + pCurrent->nByte = nPos; + /* advance the cursor so we can call this routine again */ + pCursor->zString = &zIn[nPos]; + pCursor->nByte -= nPos; + return SXRET_OK; + } + /* + * Split a single MIME header into a name value pair. + * This function return SXRET_OK, SXERR_CONTINUE on success. + * Otherwise SXERR_NEXT is returned when a malformed header + * is encountered. + * Note: This function handle also mult-line headers. + */ + static sxi32 VmHttpProcessOneHeader(SyhttpHeader *pHdr, SyhttpHeader *pLast, const char *zLine, sxu32 nLen) + { + SyString *pName; + sxu32 nPos; + sxi32 rc; + if( nLen < 1 ){ + return SXERR_NEXT; + } + /* Check for multi-line header */ + if( pLast && (zLine[-1] == ' ' || zLine[-1] == '\t') ){ + SyString *pTmp = &pLast->sValue; + SyStringFullTrim(pTmp); + if( pTmp->nByte == 0 ){ + SyStringInitFromBuf(pTmp, zLine, nLen); + }else{ + /* Update header value length */ + pTmp->nByte = (sxu32)(&zLine[nLen] - pTmp->zString); + } + /* Simply tell the caller to reset its states and get another line */ + return SXERR_CONTINUE; + } + /* Split the header */ + pName = &pHdr->sName; + rc = SyByteFind(zLine, nLen, ':', &nPos); + if(rc != SXRET_OK ){ + return SXERR_NEXT; /* Malformed header;Check the next entry */ + } + SyStringInitFromBuf(pName, zLine, nPos); + SyStringFullTrim(pName); + /* Extract a header value */ + SyStringInitFromBuf(&pHdr->sValue, &zLine[nPos + 1], nLen - nPos - 1); + /* Remove leading and trailing whitespaces */ + SyStringFullTrim(&pHdr->sValue); + return SXRET_OK; + } + /* + * Extract all MIME headers associated with a HTTP request. + * After processing the first line of a HTTP request, the following + * routine is called in order to extract MIME headers. + * This function return SXRET_OK on success, SXERR_MORE when it needs + * more inputs. + * Note: Any malformed header is simply discarded. + */ + static sxi32 VmHttpExtractHeaders(SyString *pRequest, SySet *pOut) + { + SyhttpHeader *pLast = 0; + SyString sCurrent; + SyhttpHeader sHdr; + sxu8 bEol; + sxi32 rc; + if( SySetUsed(pOut) > 0 ){ + pLast = (SyhttpHeader *)SySetAt(pOut, SySetUsed(pOut)-1); + } + bEol = FALSE; + for(;;){ + SyZero(&sHdr, sizeof(SyhttpHeader)); + /* Extract a single line from the raw HTTP request */ + rc = VmGetNextLine(pRequest, &sCurrent); + if(rc != SXRET_OK ){ + if( sCurrent.nByte < 1 ){ + break; + } + bEol = TRUE; + } + /* Process the header */ + if( SXRET_OK == VmHttpProcessOneHeader(&sHdr, pLast, sCurrent.zString, sCurrent.nByte)){ + if( SXRET_OK != SySetPut(pOut, (const void *)&sHdr) ){ + break; + } + /* Retrieve the last parsed header so we can handle multi-line header + * in case we face one of them. + */ + pLast = (SyhttpHeader *)SySetPeek(pOut); + } + if( bEol ){ + break; + } + } /* for(;;) */ + return SXRET_OK; + } + /* + * Process the first line of a HTTP request. + * This routine perform the following operations + * 1) Extract the HTTP method. + * 2) Split the request URI to it's fields [ie: host, path, query, ...]. + * 3) Extract the HTTP protocol version. + */ + static sxi32 VmHttpProcessFirstLine( + SyString *pRequest, /* Raw HTTP request */ + sxi32 *pMethod, /* OUT: HTTP method */ + SyhttpUri *pUri, /* OUT: Parse of the URI */ + sxi32 *pProto /* OUT: HTTP protocol */ + ) + { + static const char *azMethods[] = { "get", "post", "head", "put"}; + static const sxi32 aMethods[] = { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_HEAD, HTTP_METHOD_PUT}; + const char *zIn, *zEnd, *zPtr; + SyString sLine; + sxu32 nLen; + sxi32 rc; + /* Extract the first line and update the pointer */ + rc = VmGetNextLine(pRequest, &sLine); + if( rc != SXRET_OK ){ + return rc; + } + if ( sLine.nByte < 1 ){ + /* Empty HTTP request */ + return SXERR_EMPTY; + } + /* Delimit the line and ignore trailing and leading white spaces */ + zIn = sLine.zString; + zEnd = &zIn[sLine.nByte]; + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the HTTP method */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + *pMethod = HTTP_METHOD_OTHR; + if( zIn > zPtr ){ + sxu32 i; + nLen = (sxu32)(zIn-zPtr); + for( i = 0 ; i < SX_ARRAYSIZE(azMethods) ; ++i ){ + if( SyStrnicmp(azMethods[i], zPtr, nLen) == 0 ){ + *pMethod = aMethods[i]; + break; + } + } + } + /* Jump trailing white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the request URI */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn > zPtr ){ + nLen = (sxu32)(zIn-zPtr); + /* Split raw URI to it's fields */ + VmHttpSplitURI(pUri, zPtr, nLen); + } + /* Jump trailing white spaces */ + while( zIn < zEnd && (unsigned char)zIn[0] < 0xc0 && SyisSpace(zIn[0]) ){ + zIn++; + } + /* Extract the HTTP version */ + zPtr = zIn; + while( zIn < zEnd && !SyisSpace(zIn[0]) ){ + zIn++; + } + *pProto = HTTP_PROTO_11; /* HTTP/1.1 */ + rc = 1; + if( zIn > zPtr ){ + rc = SyStrnicmp(zPtr, "http/1.0", (sxu32)(zIn-zPtr)); + } + if( !rc ){ + *pProto = HTTP_PROTO_10; /* HTTP/1.0 */ + } + return SXRET_OK; + } + /* + * Tokenize, decode and split a raw query encoded as: "x-www-form-urlencoded" + * into a name value pair. + * Note that this encoding is implicit in GET based requests. + * After the tokenization process, register the decoded queries + * in the $_GET/$_POST/$_REQUEST superglobals arrays. + */ + static sxi32 VmHttpSplitEncodedQuery( + jx9_vm *pVm, /* Target VM */ + SyString *pQuery, /* Raw query to decode */ + SyBlob *pWorker, /* Working buffer */ + int is_post /* TRUE if we are dealing with a POST request */ + ) + { + const char *zEnd = &pQuery->zString[pQuery->nByte]; + const char *zIn = pQuery->zString; + jx9_value *pGet, *pRequest; + SyString sName, sValue; + const char *zPtr; + sxu32 nBlobOfft; + /* Extract superglobals */ + if( is_post ){ + /* $_POST superglobal */ + pGet = VmExtractSuper(&(*pVm), "_POST", sizeof("_POST")-1); + }else{ + /* $_GET superglobal */ + pGet = VmExtractSuper(&(*pVm), "_GET", sizeof("_GET")-1); + } + pRequest = VmExtractSuper(&(*pVm), "_REQUEST", sizeof("_REQUEST")-1); + /* Split up the raw query */ + for(;;){ + /* Jump leading white spaces */ + while(zIn < zEnd && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + break; + } + zPtr = zIn; + while( zPtr < zEnd && zPtr[0] != '=' && zPtr[0] != '&' && zPtr[0] != ';' ){ + zPtr++; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Decode the entry */ + SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); + /* Save the entry */ + sName.nByte = SyBlobLength(pWorker); + sValue.zString = 0; + sValue.nByte = 0; + if( zPtr < zEnd && zPtr[0] == '=' ){ + zPtr++; + zIn = zPtr; + /* Store field value */ + while( zPtr < zEnd && zPtr[0] != '&' && zPtr[0] != ';' ){ + zPtr++; + } + if( zPtr > zIn ){ + /* Decode the value */ + nBlobOfft = SyBlobLength(pWorker); + SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); + sValue.zString = (const char *)SyBlobDataAt(pWorker, nBlobOfft); + sValue.nByte = SyBlobLength(pWorker) - nBlobOfft; + + } + /* Synchronize pointers */ + zIn = zPtr; + } + sName.zString = (const char *)SyBlobData(pWorker); + /* Install the decoded query in the $_GET/$_REQUEST array */ + if( pGet && (pGet->iFlags & MEMOBJ_HASHMAP) ){ + VmHashmapInsert((jx9_hashmap *)pGet->x.pOther, + sName.zString, (int)sName.nByte, + sValue.zString, (int)sValue.nByte + ); + } + if( pRequest && (pRequest->iFlags & MEMOBJ_HASHMAP) ){ + VmHashmapInsert((jx9_hashmap *)pRequest->x.pOther, + sName.zString, (int)sName.nByte, + sValue.zString, (int)sValue.nByte + ); + } + /* Advance the pointer */ + zIn = &zPtr[1]; + } + /* All done*/ + return SXRET_OK; + } + /* + * Extract MIME header value from the given set. + * Return header value on success. NULL otherwise. + */ + static SyString * VmHttpExtractHeaderValue(SySet *pSet, const char *zMime, sxu32 nByte) + { + SyhttpHeader *aMime, *pMime; + SyString sMime; + sxu32 n; + SyStringInitFromBuf(&sMime, zMime, nByte); + /* Point to the MIME entries */ + aMime = (SyhttpHeader *)SySetBasePtr(pSet); + /* Perform the lookup */ + for( n = 0 ; n < SySetUsed(pSet) ; ++n ){ + pMime = &aMime[n]; + if( SyStringCmp(&sMime, &pMime->sName, SyStrnicmp) == 0 ){ + /* Header found, return it's associated value */ + return &pMime->sValue; + } + } + /* No such MIME header */ + return 0; + } + /* + * Tokenize and decode a raw "Cookie:" MIME header into a name value pair + * and insert it's fields [i.e name, value] in the $_COOKIE superglobal. + */ + static sxi32 VmHttpPorcessCookie(jx9_vm *pVm, SyBlob *pWorker, const char *zIn, sxu32 nByte) + { + const char *zPtr, *zDelimiter, *zEnd = &zIn[nByte]; + SyString sName, sValue; + jx9_value *pCookie; + sxu32 nOfft; + /* Make sure the $_COOKIE superglobal is available */ + pCookie = VmExtractSuper(&(*pVm), "_COOKIE", sizeof("_COOKIE")-1); + if( pCookie == 0 || (pCookie->iFlags & MEMOBJ_HASHMAP) == 0 ){ + /* $_COOKIE superglobal not available */ + return SXERR_NOTFOUND; + } + for(;;){ + /* Jump leading white spaces */ + while( zIn < zEnd && SyisSpace(zIn[0]) ){ + zIn++; + } + if( zIn >= zEnd ){ + break; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + zDelimiter = zIn; + /* Delimit the name[=value]; pair */ + while( zDelimiter < zEnd && zDelimiter[0] != ';' ){ + zDelimiter++; + } + zPtr = zIn; + while( zPtr < zDelimiter && zPtr[0] != '=' ){ + zPtr++; + } + /* Decode the cookie */ + SyUriDecode(zIn, (sxu32)(zPtr-zIn), jx9VmBlobConsumer, pWorker, TRUE); + sName.nByte = SyBlobLength(pWorker); + zPtr++; + sValue.zString = 0; + sValue.nByte = 0; + if( zPtr < zDelimiter ){ + /* Got a Cookie value */ + nOfft = SyBlobLength(pWorker); + SyUriDecode(zPtr, (sxu32)(zDelimiter-zPtr), jx9VmBlobConsumer, pWorker, TRUE); + SyStringInitFromBuf(&sValue, SyBlobDataAt(pWorker, nOfft), SyBlobLength(pWorker)-nOfft); + } + /* Synchronize pointers */ + zIn = &zDelimiter[1]; + /* Perform the insertion */ + sName.zString = (const char *)SyBlobData(pWorker); + VmHashmapInsert((jx9_hashmap *)pCookie->x.pOther, + sName.zString, (int)sName.nByte, + sValue.zString, (int)sValue.nByte + ); + } + return SXRET_OK; + } + /* + * Process a full HTTP request and populate the appropriate arrays + * such as $_SERVER, $_GET, $_POST, $_COOKIE, $_REQUEST, ... with the information + * extracted from the raw HTTP request. As an extension Symisc introduced + * the $_HEADER array which hold a copy of the processed HTTP MIME headers + * and their associated values. [i.e: $_HEADER['Server'], $_HEADER['User-Agent'], ...]. + * This function return SXRET_OK on success. Any other return value indicates + * a malformed HTTP request. + */ + static sxi32 VmHttpProcessRequest(jx9_vm *pVm, const char *zRequest, int nByte) + { + SyString *pName, *pValue, sRequest; /* Raw HTTP request */ + jx9_value *pHeaderArray; /* $_HEADER superglobal (Symisc eXtension to the JX9 specification)*/ + SyhttpHeader *pHeader; /* MIME header */ + SyhttpUri sUri; /* Parse of the raw URI*/ + SyBlob sWorker; /* General purpose working buffer */ + SySet sHeader; /* MIME headers set */ + sxi32 iMethod; /* HTTP method [i.e: GET, POST, HEAD...]*/ + sxi32 iVer; /* HTTP protocol version */ + sxi32 rc; + SyStringInitFromBuf(&sRequest, zRequest, nByte); + SySetInit(&sHeader, &pVm->sAllocator, sizeof(SyhttpHeader)); + SyBlobInit(&sWorker, &pVm->sAllocator); + /* Ignore leading and trailing white spaces*/ + SyStringFullTrim(&sRequest); + /* Process the first line */ + rc = VmHttpProcessFirstLine(&sRequest, &iMethod, &sUri, &iVer); + if( rc != SXRET_OK ){ + return rc; + } + /* Process MIME headers */ + VmHttpExtractHeaders(&sRequest, &sHeader); + /* + * Setup $_SERVER environments + */ + /* 'SERVER_PROTOCOL': Name and revision of the information protocol via which the page was requested */ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "SERVER_PROTOCOL", + iVer == HTTP_PROTO_10 ? "HTTP/1.0" : "HTTP/1.1", + sizeof("HTTP/1.1")-1 + ); + /* 'REQUEST_METHOD': Which request method was used to access the page */ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "REQUEST_METHOD", + iMethod == HTTP_METHOD_GET ? "GET" : + (iMethod == HTTP_METHOD_POST ? "POST": + (iMethod == HTTP_METHOD_PUT ? "PUT" : + (iMethod == HTTP_METHOD_HEAD ? "HEAD" : "OTHER"))), + -1 /* Compute attribute length automatically */ + ); + if( SyStringLength(&sUri.sQuery) > 0 && iMethod == HTTP_METHOD_GET ){ + pValue = &sUri.sQuery; + /* 'QUERY_STRING': The query string, if any, via which the page was accessed */ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "QUERY_STRING", + pValue->zString, + pValue->nByte + ); + /* Decoded the raw query */ + VmHttpSplitEncodedQuery(&(*pVm), pValue, &sWorker, FALSE); + } + /* REQUEST_URI: The URI which was given in order to access this page; for instance, '/index.html' */ + pValue = &sUri.sRaw; + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "REQUEST_URI", + pValue->zString, + pValue->nByte + ); + /* + * 'PATH_INFO' + * 'ORIG_PATH_INFO' + * Contains any client-provided pathname information trailing the actual script filename but preceding + * the query string, if available. For instance, if the current script was accessed via the URL + * http://www.example.com/jx9/path_info.jx9/some/stuff?foo=bar, then $_SERVER['PATH_INFO'] would contain + * /some/stuff. + */ + pValue = &sUri.sPath; + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "PATH_INFO", + pValue->zString, + pValue->nByte + ); + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "ORIG_PATH_INFO", + pValue->zString, + pValue->nByte + ); + /* 'HTTP_ACCEPT': Contents of the Accept: header from the current request, if there is one */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept", sizeof("Accept")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_CHARSET': Contents of the Accept-Charset: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Charset", sizeof("Accept-Charset")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_CHARSET", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_ENCODING': Contents of the Accept-Encoding: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Encoding", sizeof("Accept-Encoding")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_ENCODING", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_ACCEPT_LANGUAGE': Contents of the Accept-Language: header from the current request, if there is one */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Accept-Language", sizeof("Accept-Language")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_ACCEPT_LANGUAGE", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_CONNECTION': Contents of the Connection: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Connection", sizeof("Connection")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_CONNECTION", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_HOST': Contents of the Host: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Host", sizeof("Host")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_HOST", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_REFERER': Contents of the Referer: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Referer", sizeof("Referer")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_REFERER", + pValue->zString, + pValue->nByte + ); + } + /* 'HTTP_USER_AGENT': Contents of the Referer: header from the current request, if there is one. */ + pValue = VmHttpExtractHeaderValue(&sHeader, "User-Agent", sizeof("User-Agent")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "HTTP_USER_AGENT", + pValue->zString, + pValue->nByte + ); + } + /* 'JX9_AUTH_DIGEST': When doing Digest HTTP authentication this variable is set to the 'Authorization' + * header sent by the client (which you should then use to make the appropriate validation). + */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Authorization", sizeof("Authorization")-1); + if( pValue ){ + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "JX9_AUTH_DIGEST", + pValue->zString, + pValue->nByte + ); + jx9_vm_config(pVm, + JX9_VM_CONFIG_SERVER_ATTR, + "JX9_AUTH", + pValue->zString, + pValue->nByte + ); + } + /* Install all clients HTTP headers in the $_HEADER superglobal */ + pHeaderArray = VmExtractSuper(&(*pVm), "_HEADER", sizeof("_HEADER")-1); + /* Iterate throw the available MIME headers*/ + SySetResetCursor(&sHeader); + pHeader = 0; /* stupid cc warning */ + while( SXRET_OK == SySetGetNextEntry(&sHeader, (void **)&pHeader) ){ + pName = &pHeader->sName; + pValue = &pHeader->sValue; + if( pHeaderArray && (pHeaderArray->iFlags & MEMOBJ_HASHMAP)){ + /* Insert the MIME header and it's associated value */ + VmHashmapInsert((jx9_hashmap *)pHeaderArray->x.pOther, + pName->zString, (int)pName->nByte, + pValue->zString, (int)pValue->nByte + ); + } + if( pName->nByte == sizeof("Cookie")-1 && SyStrnicmp(pName->zString, "Cookie", sizeof("Cookie")-1) == 0 + && pValue->nByte > 0){ + /* Process the name=value pair and insert them in the $_COOKIE superglobal array */ + VmHttpPorcessCookie(&(*pVm), &sWorker, pValue->zString, pValue->nByte); + } + } + if( iMethod == HTTP_METHOD_POST ){ + /* Extract raw POST data */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Type", sizeof("Content-Type") - 1); + if( pValue && pValue->nByte >= sizeof("application/x-www-form-urlencoded") - 1 && + SyMemcmp("application/x-www-form-urlencoded", pValue->zString, pValue->nByte) == 0 ){ + /* Extract POST data length */ + pValue = VmHttpExtractHeaderValue(&sHeader, "Content-Length", sizeof("Content-Length") - 1); + if( pValue ){ + sxi32 iLen = 0; /* POST data length */ + SyStrToInt32(pValue->zString, pValue->nByte, (void *)&iLen, 0); + if( iLen > 0 ){ + /* Remove leading and trailing white spaces */ + SyStringFullTrim(&sRequest); + if( (int)sRequest.nByte > iLen ){ + sRequest.nByte = (sxu32)iLen; + } + /* Decode POST data now */ + VmHttpSplitEncodedQuery(&(*pVm), &sRequest, &sWorker, TRUE); + } + } + } + } + /* All done, clean-up the mess left behind */ + SySetRelease(&sHeader); + SyBlobRelease(&sWorker); + return SXRET_OK; + } + +/* + * ---------------------------------------------------------- + * File: lhash_kv.c + * MD5: 581b07ce2984fd95740677285d8a11d3 + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: lhash_kv.c v1.7 Solaris 2013-01-14 12:56 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* + * This file implements disk based hashtable using the linear hashing algorithm. + * This implementation is the one decribed in the paper: + * LINEAR HASHING : A NEW TOOL FOR FILE AND TABLE ADDRESSING. Witold Litwin. I. N. Ft. I. A.. 78 150 Le Chesnay, France. + * Plus a smart extension called Virtual Bucket Table. (contact devel@symisc.net for additional information). + */ +/* Magic number identifying a valid storage image */ +#define L_HASH_MAGIC 0xFA782DCB +/* + * Magic word to hash to identify a valid hash function. + */ +#define L_HASH_WORD "chm@symisc" +/* + * Cell size on disk. + */ +#define L_HASH_CELL_SZ (4/*Hash*/+4/*Key*/+8/*Data*/+2/* Offset of the next cell */+8/*Overflow*/) +/* + * Primary page (not overflow pages) header size on disk. + */ +#define L_HASH_PAGE_HDR_SZ (2/* Cell offset*/+2/* Free block offset*/+8/*Slave page number*/) +/* + * The maximum amount of payload (in bytes) that can be stored locally for + * a database entry. If the entry contains more data than this, the + * extra goes onto overflow pages. +*/ +#define L_HASH_MX_PAYLOAD(PageSize) (PageSize-(L_HASH_PAGE_HDR_SZ+L_HASH_CELL_SZ)) +/* + * Maxium free space on a single page. + */ +#define L_HASH_MX_FREE_SPACE(PageSize) (PageSize - (L_HASH_PAGE_HDR_SZ)) +/* +** The maximum number of bytes of payload allowed on a single overflow page. +*/ +#define L_HASH_OVERFLOW_SIZE(PageSize) (PageSize-8) +/* Forward declaration */ +typedef struct lhash_kv_engine lhash_kv_engine; +typedef struct lhpage lhpage; +/* + * Each record in the database is identified either in-memory or in + * disk by an instance of the following structure. + */ +typedef struct lhcell lhcell; +struct lhcell +{ + /* Disk-data (Big-Endian) */ + sxu32 nHash; /* Hash of the key: 4 bytes */ + sxu32 nKey; /* Key length: 4 bytes */ + sxu64 nData; /* Data length: 8 bytes */ + sxu16 iNext; /* Offset of the next cell: 2 bytes */ + pgno iOvfl; /* Overflow page number if any: 8 bytes */ + /* In-memory data only */ + lhpage *pPage; /* Page this cell belongs */ + sxu16 iStart; /* Offset of this cell */ + pgno iDataPage; /* Data page number when overflow */ + sxu16 iDataOfft; /* Offset of the data in iDataPage */ + SyBlob sKey; /* Record key for fast lookup (Kept in-memory if < 256KB ) */ + lhcell *pNext,*pPrev; /* Linked list of the loaded memory cells */ + lhcell *pNextCol,*pPrevCol; /* Collison chain */ +}; +/* +** Each database page has a header that is an instance of this +** structure. +*/ +typedef struct lhphdr lhphdr; +struct lhphdr +{ + sxu16 iOfft; /* Offset of the first cell */ + sxu16 iFree; /* Offset of the first free block*/ + pgno iSlave; /* Slave page number */ +}; +/* + * Each loaded primary disk page is represented in-memory using + * an instance of the following structure. + */ +struct lhpage +{ + lhash_kv_engine *pHash; /* KV Storage engine that own this page */ + unqlite_page *pRaw; /* Raw page contents */ + lhphdr sHdr; /* Processed page header */ + lhcell **apCell; /* Cell buckets */ + lhcell *pList,*pFirst; /* Linked list of cells */ + sxu32 nCell; /* Total number of cells */ + sxu32 nCellSize; /* apCell[] size */ + lhpage *pMaster; /* Master page in case we are dealing with a slave page */ + lhpage *pSlave; /* List of slave pages */ + lhpage *pNextSlave; /* Next slave page on the list */ + sxi32 iSlave; /* Total number of slave pages */ + sxu16 nFree; /* Amount of free space available in the page */ +}; +/* + * A Bucket map record which is used to map logical bucket number to real + * bucket number is represented by an instance of the following structure. + */ +typedef struct lhash_bmap_rec lhash_bmap_rec; +struct lhash_bmap_rec +{ + pgno iLogic; /* Logical bucket number */ + pgno iReal; /* Real bucket number */ + lhash_bmap_rec *pNext,*pPrev; /* Link to other bucket map */ + lhash_bmap_rec *pNextCol,*pPrevCol; /* Collision links */ +}; +typedef struct lhash_bmap_page lhash_bmap_page; +struct lhash_bmap_page +{ + pgno iNum; /* Page number where this entry is stored */ + sxu16 iPtr; /* Offset to start reading/writing from */ + sxu32 nRec; /* Total number of records in this page */ + pgno iNext; /* Next map page */ +}; +/* + * An in memory linear hash implemenation is represented by in an isntance + * of the following structure. + */ +struct lhash_kv_engine +{ + const unqlite_kv_io *pIo; /* IO methods: Must be first */ + /* Private fields */ + SyMemBackend sAllocator; /* Private memory backend */ + ProcHash xHash; /* Default hash function */ + ProcCmp xCmp; /* Default comparison function */ + unqlite_page *pHeader; /* Page one to identify a valid implementation */ + lhash_bmap_rec **apMap; /* Buckets map records */ + sxu32 nBuckRec; /* Total number of bucket map records */ + sxu32 nBuckSize; /* apMap[] size */ + lhash_bmap_rec *pList; /* List of bucket map records */ + lhash_bmap_rec *pFirst; /* First record*/ + lhash_bmap_page sPageMap; /* Primary bucket map */ + int iPageSize; /* Page size */ + pgno nFreeList; /* List of free pages */ + pgno split_bucket; /* Current split bucket: MUST BE A POWER OF TWO */ + pgno max_split_bucket; /* Maximum split bucket: MUST BE A POWER OF TWO */ + pgno nmax_split_nucket; /* Next maximum split bucket (1 << nMsb): In-memory only */ + sxu32 nMagic; /* Magic number to identify a valid linear hash disk database */ +}; +/* + * Given a logical bucket number, return the record associated with it. + */ +static lhash_bmap_rec * lhMapFindBucket(lhash_kv_engine *pEngine,pgno iLogic) +{ + lhash_bmap_rec *pRec; + if( pEngine->nBuckRec < 1 ){ + /* Don't bother */ + return 0; + } + pRec = pEngine->apMap[iLogic & (pEngine->nBuckSize - 1)]; + for(;;){ + if( pRec == 0 ){ + break; + } + if( pRec->iLogic == iLogic ){ + return pRec; + } + /* Point to the next entry */ + pRec = pRec->pNextCol; + } + /* No such record */ + return 0; +} +/* + * Install a new bucket map record. + */ +static int lhMapInstallBucket(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal) +{ + lhash_bmap_rec *pRec; + sxu32 iBucket; + /* Allocate a new instance */ + pRec = (lhash_bmap_rec *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhash_bmap_rec)); + if( pRec == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pRec,sizeof(lhash_bmap_rec)); + /* Fill in the structure */ + pRec->iLogic = iLogic; + pRec->iReal = iReal; + iBucket = iLogic & (pEngine->nBuckSize - 1); + pRec->pNextCol = pEngine->apMap[iBucket]; + if( pEngine->apMap[iBucket] ){ + pEngine->apMap[iBucket]->pPrevCol = pRec; + } + pEngine->apMap[iBucket] = pRec; + /* Link */ + if( pEngine->pFirst == 0 ){ + pEngine->pFirst = pEngine->pList = pRec; + }else{ + MACRO_LD_PUSH(pEngine->pList,pRec); + } + pEngine->nBuckRec++; + if( (pEngine->nBuckRec >= pEngine->nBuckSize * 3) && pEngine->nBuckRec < 100000 ){ + /* Allocate a new larger table */ + sxu32 nNewSize = pEngine->nBuckSize << 1; + lhash_bmap_rec *pEntry; + lhash_bmap_rec **apNew; + sxu32 n; + + apNew = (lhash_bmap_rec **)SyMemBackendAlloc(&pEngine->sAllocator, nNewSize * sizeof(lhash_bmap_rec *)); + if( apNew ){ + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(lhash_bmap_rec *)); + /* Rehash all entries */ + n = 0; + pEntry = pEngine->pList; + for(;;){ + /* Loop one */ + if( n >= pEngine->nBuckRec ){ + break; + } + pEntry->pNextCol = pEntry->pPrevCol = 0; + /* Install in the new bucket */ + iBucket = pEntry->iLogic & (nNewSize - 1); + pEntry->pNextCol = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevCol = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(&pEngine->sAllocator,(void *)pEngine->apMap); + pEngine->apMap = apNew; + pEngine->nBuckSize = nNewSize; + } + } + return UNQLITE_OK; +} +/* + * Process a raw bucket map record. + */ +static int lhMapLoadPage(lhash_kv_engine *pEngine,lhash_bmap_page *pMap,const unsigned char *zRaw) +{ + const unsigned char *zEnd = &zRaw[pEngine->iPageSize]; + const unsigned char *zPtr = zRaw; + pgno iLogic,iReal; + sxu32 n; + int rc; + if( pMap->iPtr == 0 ){ + /* Read the map header */ + SyBigEndianUnpack64(zRaw,&pMap->iNext); + zRaw += 8; + SyBigEndianUnpack32(zRaw,&pMap->nRec); + zRaw += 4; + }else{ + /* Mostly page one of the database */ + zRaw += pMap->iPtr; + } + /* Start processing */ + for( n = 0; n < pMap->nRec ; ++n ){ + if( zRaw >= zEnd ){ + break; + } + /* Extract the logical and real bucket number */ + SyBigEndianUnpack64(zRaw,&iLogic); + zRaw += 8; + SyBigEndianUnpack64(zRaw,&iReal); + zRaw += 8; + /* Install the record in the map */ + rc = lhMapInstallBucket(pEngine,iLogic,iReal); + if( rc != UNQLITE_OK ){ + return rc; + } + } + pMap->iPtr = (sxu16)(zRaw-zPtr); + /* All done */ + return UNQLITE_OK; +} +/* + * Allocate a new cell instance. + */ +static lhcell * lhNewCell(lhash_kv_engine *pEngine,lhpage *pPage) +{ + lhcell *pCell; + pCell = (lhcell *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhcell)); + if( pCell == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pCell,sizeof(lhcell)); + /* Fill in the structure */ + SyBlobInit(&pCell->sKey,&pEngine->sAllocator); + pCell->pPage = pPage; + return pCell; +} +/* + * Discard a cell from the page table. + */ +static void lhCellDiscard(lhcell *pCell) +{ + lhpage *pPage = pCell->pPage->pMaster; + + if( pCell->pPrevCol ){ + pCell->pPrevCol->pNextCol = pCell->pNextCol; + }else{ + pPage->apCell[pCell->nHash & (pPage->nCellSize - 1)] = pCell->pNextCol; + } + if( pCell->pNextCol ){ + pCell->pNextCol->pPrevCol = pCell->pPrevCol; + } + MACRO_LD_REMOVE(pPage->pList,pCell); + if( pCell == pPage->pFirst ){ + pPage->pFirst = pCell->pPrev; + } + pPage->nCell--; + /* Release the cell */ + SyBlobRelease(&pCell->sKey); + SyMemBackendPoolFree(&pPage->pHash->sAllocator,pCell); +} +/* + * Install a cell in the page table. + */ +static int lhInstallCell(lhcell *pCell) +{ + lhpage *pPage = pCell->pPage->pMaster; + sxu32 iBucket; + if( pPage->nCell < 1 ){ + sxu32 nTableSize = 32; /* Must be a power of two */ + lhcell **apTable; + /* Allocate a new cell table */ + apTable = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nTableSize * sizeof(lhcell *)); + if( apTable == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the new table */ + SyZero((void *)apTable, nTableSize * sizeof(lhcell *)); + /* Install it */ + pPage->apCell = apTable; + pPage->nCellSize = nTableSize; + } + iBucket = pCell->nHash & (pPage->nCellSize - 1); + pCell->pNextCol = pPage->apCell[iBucket]; + if( pPage->apCell[iBucket] ){ + pPage->apCell[iBucket]->pPrevCol = pCell; + } + pPage->apCell[iBucket] = pCell; + if( pPage->pFirst == 0 ){ + pPage->pFirst = pPage->pList = pCell; + }else{ + MACRO_LD_PUSH(pPage->pList,pCell); + } + pPage->nCell++; + if( (pPage->nCell >= pPage->nCellSize * 3) && pPage->nCell < 100000 ){ + /* Allocate a new larger table */ + sxu32 nNewSize = pPage->nCellSize << 1; + lhcell *pEntry; + lhcell **apNew; + sxu32 n; + + apNew = (lhcell **)SyMemBackendAlloc(&pPage->pHash->sAllocator, nNewSize * sizeof(lhcell *)); + if( apNew ){ + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(lhcell *)); + /* Rehash all entries */ + n = 0; + pEntry = pPage->pList; + for(;;){ + /* Loop one */ + if( n >= pPage->nCell ){ + break; + } + pEntry->pNextCol = pEntry->pPrevCol = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextCol = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevCol = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(&pPage->pHash->sAllocator,(void *)pPage->apCell); + pPage->apCell = apNew; + pPage->nCellSize = nNewSize; + } + } + return UNQLITE_OK; +} +/* + * Private data of lhKeyCmp(). + */ +struct lhash_key_cmp +{ + const char *zIn; /* Start of the stream */ + const char *zEnd; /* End of the stream */ + ProcCmp xCmp; /* Comparison function */ +}; +/* + * Comparsion callback for large key > 256 KB + */ +static int lhKeyCmp(const void *pData,sxu32 nLen,void *pUserData) +{ + struct lhash_key_cmp *pCmp = (struct lhash_key_cmp *)pUserData; + int rc; + if( pCmp->zIn >= pCmp->zEnd ){ + if( nLen > 0 ){ + return UNQLITE_ABORT; + } + return UNQLITE_OK; + } + /* Perform the comparison */ + rc = pCmp->xCmp((const void *)pCmp->zIn,pData,nLen); + if( rc != 0 ){ + /* Abort comparison */ + return UNQLITE_ABORT; + } + /* Advance the cursor */ + pCmp->zIn += nLen; + return UNQLITE_OK; +} +/* Forward declaration */ +static int lhConsumeCellkey(lhcell *pCell,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData,int offt_only); +/* + * given a key, return the cell associated with it on success. NULL otherwise. + */ +static lhcell * lhFindCell( + lhpage *pPage, /* Target page */ + const void *pKey, /* Lookup key */ + sxu32 nByte, /* Key length */ + sxu32 nHash /* Hash of the key */ + ) +{ + lhcell *pEntry; + if( pPage->nCell < 1 ){ + /* Don't bother hashing */ + return 0; + } + /* Point to the corresponding bucket */ + pEntry = pPage->apCell[nHash & (pPage->nCellSize - 1)]; + for(;;){ + if( pEntry == 0 ){ + break; + } + if( pEntry->nHash == nHash && pEntry->nKey == nByte ){ + if( SyBlobLength(&pEntry->sKey) < 1 ){ + /* Large key (> 256 KB) are not kept in-memory */ + struct lhash_key_cmp sCmp; + int rc; + /* Fill-in the structure */ + sCmp.zIn = (const char *)pKey; + sCmp.zEnd = &sCmp.zIn[nByte]; + sCmp.xCmp = pPage->pHash->xCmp; + /* Fetch the key from disk and perform the comparison */ + rc = lhConsumeCellkey(pEntry,lhKeyCmp,&sCmp,0); + if( rc == UNQLITE_OK ){ + /* Cell found */ + return pEntry; + } + }else if ( pPage->pHash->xCmp(pKey,SyBlobData(&pEntry->sKey),nByte) == 0 ){ + /* Cell found */ + return pEntry; + } + } + /* Point to the next entry */ + pEntry = pEntry->pNextCol; + } + /* No such entry */ + return 0; +} +/* + * Parse a raw cell fetched from disk. + */ +static int lhParseOneCell(lhpage *pPage,const unsigned char *zRaw,const unsigned char *zEnd,lhcell **ppOut) +{ + sxu16 iNext,iOfft; + sxu32 iHash,nKey; + lhcell *pCell; + sxu64 nData; + int rc; + /* Offset this cell is stored */ + iOfft = (sxu16)(zRaw - (const unsigned char *)pPage->pRaw->zData); + /* 4 byte hash number */ + SyBigEndianUnpack32(zRaw,&iHash); + zRaw += 4; + /* 4 byte key length */ + SyBigEndianUnpack32(zRaw,&nKey); + zRaw += 4; + /* 8 byte data length */ + SyBigEndianUnpack64(zRaw,&nData); + zRaw += 8; + /* 2 byte offset of the next cell */ + SyBigEndianUnpack16(zRaw,&iNext); + /* Perform a sanity check */ + if( iNext > 0 && &pPage->pRaw->zData[iNext] >= zEnd ){ + return UNQLITE_CORRUPT; + } + zRaw += 2; + pCell = lhNewCell(pPage->pHash,pPage); + if( pCell == 0 ){ + return UNQLITE_NOMEM; + } + /* Fill in the structure */ + pCell->iNext = iNext; + pCell->nKey = nKey; + pCell->nData = nData; + pCell->nHash = iHash; + /* Overflow page if any */ + SyBigEndianUnpack64(zRaw,&pCell->iOvfl); + zRaw += 8; + /* Cell offset */ + pCell->iStart = iOfft; + /* Consume the key */ + rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,pCell->nKey > 262144 /* 256 KB */? 1 : 0); + if( rc != UNQLITE_OK ){ + /* TICKET: 14-32-chm@symisc.net: Key too large for memory */ + SyBlobRelease(&pCell->sKey); + } + /* Finally install the cell */ + rc = lhInstallCell(pCell); + if( rc != UNQLITE_OK ){ + return rc; + } + if( ppOut ){ + *ppOut = pCell; + } + return UNQLITE_OK; +} +/* + * Compute the total number of free space on a given page. + */ +static int lhPageFreeSpace(lhpage *pPage) +{ + const unsigned char *zEnd,*zRaw = pPage->pRaw->zData; + lhphdr *pHdr = &pPage->sHdr; + sxu16 iNext,iAmount; + sxu16 nFree = 0; + if( pHdr->iFree < 1 ){ + /* Don't bother processing, the page is full */ + pPage->nFree = 0; + return UNQLITE_OK; + } + /* Point to first free block */ + zEnd = &zRaw[pPage->pHash->iPageSize]; + zRaw += pHdr->iFree; + for(;;){ + /* Offset of the next free block */ + SyBigEndianUnpack16(zRaw,&iNext); + zRaw += 2; + /* Available space on this block */ + SyBigEndianUnpack16(zRaw,&iAmount); + nFree += iAmount; + if( iNext < 1 ){ + /* No more free blocks */ + break; + } + /* Point to the next free block*/ + zRaw = &pPage->pRaw->zData[iNext]; + if( zRaw >= zEnd ){ + /* Corrupt page */ + return UNQLITE_CORRUPT; + } + } + /* Save the amount of free space */ + pPage->nFree = nFree; + return UNQLITE_OK; +} +/* + * Given a primary page, load all its cell. + */ +static int lhLoadCells(lhpage *pPage) +{ + const unsigned char *zEnd,*zRaw = pPage->pRaw->zData; + lhphdr *pHdr = &pPage->sHdr; + lhcell *pCell = 0; /* cc warning */ + int rc; + /* Calculate the amount of free space available first */ + rc = lhPageFreeSpace(pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pHdr->iOfft < 1 ){ + /* Don't bother processing, the page is empty */ + return UNQLITE_OK; + } + /* Point to first cell */ + zRaw += pHdr->iOfft; + zEnd = &zRaw[pPage->pHash->iPageSize]; + for(;;){ + /* Parse a single cell */ + rc = lhParseOneCell(pPage,zRaw,zEnd,&pCell); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pCell->iNext < 1 ){ + /* No more cells */ + break; + } + /* Point to the next cell */ + zRaw = &pPage->pRaw->zData[pCell->iNext]; + if( zRaw >= zEnd ){ + /* Corrupt page */ + return UNQLITE_CORRUPT; + } + } + /* All done */ + return UNQLITE_OK; +} +/* + * Given a page, parse its raw headers. + */ +static int lhParsePageHeader(lhpage *pPage) +{ + const unsigned char *zRaw = pPage->pRaw->zData; + lhphdr *pHdr = &pPage->sHdr; + /* Offset of the first cell */ + SyBigEndianUnpack16(zRaw,&pHdr->iOfft); + zRaw += 2; + /* Offset of the first free block */ + SyBigEndianUnpack16(zRaw,&pHdr->iFree); + zRaw += 2; + /* Slave page number */ + SyBigEndianUnpack64(zRaw,&pHdr->iSlave); + /* All done */ + return UNQLITE_OK; +} +/* + * Allocate a new page instance. + */ +static lhpage * lhNewPage( + lhash_kv_engine *pEngine, /* KV store which own this instance */ + unqlite_page *pRaw, /* Raw page contents */ + lhpage *pMaster /* Master page in case we are dealing with a slave page */ + ) +{ + lhpage *pPage; + /* Allocate a new instance */ + pPage = (lhpage *)SyMemBackendPoolAlloc(&pEngine->sAllocator,sizeof(lhpage)); + if( pPage == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pPage,sizeof(lhpage)); + /* Fill-in the structure */ + pPage->pHash = pEngine; + pPage->pRaw = pRaw; + pPage->pMaster = pMaster ? pMaster /* Slave page */ : pPage /* Master page */ ; + if( pPage->pMaster != pPage ){ + /* Slave page, attach it to its master */ + pPage->pNextSlave = pMaster->pSlave; + pMaster->pSlave = pPage; + pMaster->iSlave++; + } + /* Save this instance for future fast lookup */ + pRaw->pUserData = pPage; + /* All done */ + return pPage; +} +/* + * Load a primary and its associated slave pages from disk. + */ +static int lhLoadPage(lhash_kv_engine *pEngine,pgno pnum,lhpage *pMaster,lhpage **ppOut,int iNest) +{ + unqlite_page *pRaw; + lhpage *pPage = 0; /* cc warning */ + int rc; + /* Aquire the page from the pager first */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pnum,&pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pRaw->pUserData ){ + /* The page is already parsed and loaded in memory. Point to it */ + pPage = (lhpage *)pRaw->pUserData; + }else{ + /* Allocate a new page */ + pPage = lhNewPage(pEngine,pRaw,pMaster); + if( pPage == 0 ){ + return UNQLITE_NOMEM; + } + /* Process the page */ + rc = lhParsePageHeader(pPage); + if( rc == UNQLITE_OK ){ + /* Load cells */ + rc = lhLoadCells(pPage); + } + if( rc != UNQLITE_OK ){ + pEngine->pIo->xPageUnref(pPage->pRaw); /* pPage will be released inside this call */ + return rc; + } + if( pPage->sHdr.iSlave > 0 && iNest < 128 ){ + if( pMaster == 0 ){ + pMaster = pPage; + } + /* Slave page. Not a fatal error if something goes wrong here */ + lhLoadPage(pEngine,pPage->sHdr.iSlave,pMaster,0,iNest++); + } + } + if( ppOut ){ + *ppOut = pPage; + } + return UNQLITE_OK; +} +/* + * Given a cell, Consume its key by invoking the given callback for each extracted chunk. + */ +static int lhConsumeCellkey( + lhcell *pCell, /* Target cell */ + int (*xConsumer)(const void *,unsigned int,void *), /* Consumer callback */ + void *pUserData, /* Last argument to xConsumer() */ + int offt_only + ) +{ + lhpage *pPage = pCell->pPage; + const unsigned char *zRaw = pPage->pRaw->zData; + const unsigned char *zPayload; + int rc; + /* Point to the payload area */ + zPayload = &zRaw[pCell->iStart]; + if( pCell->iOvfl == 0 ){ + /* Best scenario, consume the key directly without any overflow page */ + zPayload += L_HASH_CELL_SZ; + rc = xConsumer((const void *)zPayload,pCell->nKey,pUserData); + if( rc != UNQLITE_OK ){ + rc = UNQLITE_ABORT; + } + }else{ + lhash_kv_engine *pEngine = pPage->pHash; + sxu32 nByte,nData = pCell->nKey; + unqlite_page *pOvfl; + int data_offset = 0; + pgno iOvfl; + /* Overflow page */ + iOvfl = pCell->iOvfl; + /* Total usable bytes in an overflow page */ + nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize); + for(;;){ + if( iOvfl == 0 || nData < 1 ){ + /* no more overflow page */ + break; + } + /* Point to the overflow page */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + zPayload = &pOvfl->zData[8]; + /* Point to the raw content */ + if( !data_offset ){ + /* Get the data page and offset */ + SyBigEndianUnpack64(zPayload,&pCell->iDataPage); + zPayload += 8; + SyBigEndianUnpack16(zPayload,&pCell->iDataOfft); + zPayload += 2; + if( offt_only ){ + /* Key too large, grab the data offset and return */ + pEngine->pIo->xPageUnref(pOvfl); + return UNQLITE_OK; + } + data_offset = 1; + } + /* Consume the key */ + if( nData <= nByte ){ + rc = xConsumer((const void *)zPayload,nData,pUserData); + if( rc != UNQLITE_OK ){ + pEngine->pIo->xPageUnref(pOvfl); + return UNQLITE_ABORT; + } + nData = 0; + }else{ + rc = xConsumer((const void *)zPayload,nByte,pUserData); + if( rc != UNQLITE_OK ){ + pEngine->pIo->xPageUnref(pOvfl); + return UNQLITE_ABORT; + } + nData -= nByte; + } + /* Next overflow page in the chain */ + SyBigEndianUnpack64(pOvfl->zData,&iOvfl); + /* Unref the page */ + pEngine->pIo->xPageUnref(pOvfl); + } + rc = UNQLITE_OK; + } + return rc; +} +/* + * Given a cell, Consume its data by invoking the given callback for each extracted chunk. + */ +static int lhConsumeCellData( + lhcell *pCell, /* Target cell */ + int (*xConsumer)(const void *,unsigned int,void *), /* Data consumer callback */ + void *pUserData /* Last argument to xConsumer() */ + ) +{ + lhpage *pPage = pCell->pPage; + const unsigned char *zRaw = pPage->pRaw->zData; + const unsigned char *zPayload; + int rc; + /* Point to the payload area */ + zPayload = &zRaw[pCell->iStart]; + if( pCell->iOvfl == 0 ){ + /* Best scenario, consume the data directly without any overflow page */ + zPayload += L_HASH_CELL_SZ + pCell->nKey; + rc = xConsumer((const void *)zPayload,(sxu32)pCell->nData,pUserData); + if( rc != UNQLITE_OK ){ + rc = UNQLITE_ABORT; + } + }else{ + lhash_kv_engine *pEngine = pPage->pHash; + sxu64 nData = pCell->nData; + unqlite_page *pOvfl; + int fix_offset = 0; + sxu32 nByte; + pgno iOvfl; + /* Overflow page where data is stored */ + iOvfl = pCell->iDataPage; + for(;;){ + if( iOvfl == 0 || nData < 1 ){ + /* no more overflow page */ + break; + } + /* Point to the overflow page */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Point to the raw content */ + zPayload = pOvfl->zData; + if( !fix_offset ){ + /* Point to the data */ + zPayload += pCell->iDataOfft; + nByte = pEngine->iPageSize - pCell->iDataOfft; + fix_offset = 1; + }else{ + zPayload += 8; + /* Total usable bytes in an overflow page */ + nByte = L_HASH_OVERFLOW_SIZE(pEngine->iPageSize); + } + /* Consume the data */ + if( nData <= (sxu64)nByte ){ + rc = xConsumer((const void *)zPayload,(unsigned int)nData,pUserData); + if( rc != UNQLITE_OK ){ + pEngine->pIo->xPageUnref(pOvfl); + return UNQLITE_ABORT; + } + nData = 0; + }else{ + if( nByte > 0 ){ + rc = xConsumer((const void *)zPayload,nByte,pUserData); + if( rc != UNQLITE_OK ){ + pEngine->pIo->xPageUnref(pOvfl); + return UNQLITE_ABORT; + } + nData -= nByte; + } + } + /* Next overflow page in the chain */ + SyBigEndianUnpack64(pOvfl->zData,&iOvfl); + /* Unref the page */ + pEngine->pIo->xPageUnref(pOvfl); + } + rc = UNQLITE_OK; + } + return rc; +} +/* + * Read the linear hash header (Page one of the database). + */ +static int lhash_read_header(lhash_kv_engine *pEngine,unqlite_page *pHeader) +{ + const unsigned char *zRaw = pHeader->zData; + lhash_bmap_page *pMap; + sxu32 nHash; + int rc; + pEngine->pHeader = pHeader; + /* 4 byte magic number */ + SyBigEndianUnpack32(zRaw,&pEngine->nMagic); + zRaw += 4; + if( pEngine->nMagic != L_HASH_MAGIC ){ + /* Corrupt implementation */ + return UNQLITE_CORRUPT; + } + /* 4 byte hash value to identify a valid hash function */ + SyBigEndianUnpack32(zRaw,&nHash); + zRaw += 4; + /* Sanity check */ + if( pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1) != nHash ){ + /* Different hash function */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Invalid hash function"); + return UNQLITE_INVALID; + } + /* List of free pages */ + SyBigEndianUnpack64(zRaw,&pEngine->nFreeList); + zRaw += 8; + /* Current split bucket */ + SyBigEndianUnpack64(zRaw,&pEngine->split_bucket); + zRaw += 8; + /* Maximum split bucket */ + SyBigEndianUnpack64(zRaw,&pEngine->max_split_bucket); + zRaw += 8; + /* Next generation */ + pEngine->nmax_split_nucket = pEngine->max_split_bucket << 1; + /* Initialiaze the bucket map */ + pMap = &pEngine->sPageMap; + /* Fill in the structure */ + pMap->iNum = pHeader->pgno; + /* Next page in the bucket map */ + SyBigEndianUnpack64(zRaw,&pMap->iNext); + zRaw += 8; + /* Total number of records in the bucket map (This page only) */ + SyBigEndianUnpack32(zRaw,&pMap->nRec); + zRaw += 4; + pMap->iPtr = (sxu16)(zRaw - pHeader->zData); + /* Load the map in memory */ + rc = lhMapLoadPage(pEngine,pMap,pHeader->zData); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Load the bucket map chain if any */ + for(;;){ + pgno iNext = pMap->iNext; + unqlite_page *pPage; + if( iNext == 0 ){ + /* No more map pages */ + break; + } + /* Point to the target page */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Fill in the structure */ + pMap->iNum = iNext; + pMap->iPtr = 0; + /* Load the map in memory */ + rc = lhMapLoadPage(pEngine,pMap,pPage->zData); + if( rc != UNQLITE_OK ){ + return rc; + } + } + /* All done */ + return UNQLITE_OK; +} +/* + * Perform a record lookup. + */ +static int lhRecordLookup( + lhash_kv_engine *pEngine, /* KV storage engine */ + const void *pKey, /* Lookup key */ + sxu32 nByte, /* Key length */ + lhcell **ppCell /* OUT: Target cell on success */ + ) +{ + lhash_bmap_rec *pRec; + lhpage *pPage; + lhcell *pCell; + pgno iBucket; + sxu32 nHash; + int rc; + /* Acquire the first page (hash Header) so that everything gets loaded autmatically */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Compute the hash of the key first */ + nHash = pEngine->xHash(pKey,nByte); + /* Extract the logical (i.e. not real) page number */ + iBucket = nHash & (pEngine->nmax_split_nucket - 1); + if( iBucket >= (pEngine->split_bucket + pEngine->max_split_bucket) ){ + /* Low mask */ + iBucket = nHash & (pEngine->max_split_bucket - 1); + } + /* Map the logical bucket number to real page number */ + pRec = lhMapFindBucket(pEngine,iBucket); + if( pRec == 0 ){ + /* No such entry */ + return UNQLITE_NOTFOUND; + } + /* Load the master page and it's slave page in-memory */ + rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0); + if( rc != UNQLITE_OK ){ + /* IO error, unlikely scenario */ + return rc; + } + /* Lookup for the cell */ + pCell = lhFindCell(pPage,pKey,nByte,nHash); + if( pCell == 0 ){ + /* No such entry */ + return UNQLITE_NOTFOUND; + } + if( ppCell ){ + *ppCell = pCell; + } + return UNQLITE_OK; +} +/* + * Acquire a new page either from the free list or ask the pager + * for a new one. + */ +static int lhAcquirePage(lhash_kv_engine *pEngine,unqlite_page **ppOut) +{ + unqlite_page *pPage; + int rc; + if( pEngine->nFreeList != 0 ){ + /* Acquire one from the free list */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pEngine->nFreeList,&pPage); + if( rc == UNQLITE_OK ){ + /* Point to the next free page */ + SyBigEndianUnpack64(pPage->zData,&pEngine->nFreeList); + /* Update the database header */ + rc = pEngine->pIo->xWrite(pEngine->pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList); + /* Tell the pager do not journal this page */ + pEngine->pIo->xDontJournal(pPage); + /* Return to the caller */ + *ppOut = pPage; + /* All done */ + return UNQLITE_OK; + } + } + /* Acquire a new page */ + rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Point to the target page */ + *ppOut = pPage; + return UNQLITE_OK; +} +/* + * Write a bucket map record to disk. + */ +static int lhMapWriteRecord(lhash_kv_engine *pEngine,pgno iLogic,pgno iReal) +{ + lhash_bmap_page *pMap = &pEngine->sPageMap; + unqlite_page *pPage = 0; + int rc; + if( pMap->iPtr > (pEngine->iPageSize - 16) /* 8 byte logical bucket number + 8 byte real bucket number */ ){ + unqlite_page *pOld; + /* Point to the old page */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pOld); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Acquire a new page */ + rc = lhAcquirePage(pEngine,&pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Reflect the change */ + pMap->iNext = 0; + pMap->iNum = pPage->pgno; + pMap->nRec = 0; + pMap->iPtr = 8/* Next page number */+4/* Total records in the map*/; + /* Link this page */ + rc = pEngine->pIo->xWrite(pOld); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pOld->pgno == pEngine->pHeader->pgno ){ + /* First page (Hash header) */ + SyBigEndianPack64(&pOld->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/],pPage->pgno); + }else{ + /* Link the new page */ + SyBigEndianPack64(pOld->zData,pPage->pgno); + /* Unref */ + pEngine->pIo->xPageUnref(pOld); + } + /* Assume the last bucket map page */ + rc = pEngine->pIo->xWrite(pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + SyBigEndianPack64(pPage->zData,0); /* Next bucket map page on the list */ + } + if( pPage == 0){ + /* Point to the current map page */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pMap->iNum,&pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + } + /* Make page writable */ + rc = pEngine->pIo->xWrite(pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Write the data */ + SyBigEndianPack64(&pPage->zData[pMap->iPtr],iLogic); + pMap->iPtr += 8; + SyBigEndianPack64(&pPage->zData[pMap->iPtr],iReal); + pMap->iPtr += 8; + /* Install the bucket map */ + rc = lhMapInstallBucket(pEngine,iLogic,iReal); + if( rc == UNQLITE_OK ){ + /* Total number of records */ + pMap->nRec++; + if( pPage->pgno == pEngine->pHeader->pgno ){ + /* Page one: Always writable */ + SyBigEndianPack32( + &pPage->zData[4/*magic*/+4/*hash*/+8/* Free page */+8/*current split bucket*/+8/*Maximum split bucket*/+8/*Next map page*/], + pMap->nRec); + }else{ + /* Make page writable */ + rc = pEngine->pIo->xWrite(pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + SyBigEndianPack32(&pPage->zData[8],pMap->nRec); + } + } + return rc; +} +/* + * Defragment a page. + */ +static int lhPageDefragment(lhpage *pPage) +{ + lhash_kv_engine *pEngine = pPage->pHash; + unsigned char *zTmp,*zPtr,*zEnd,*zPayload; + lhcell *pCell; + /* Get a temporary page from the pager. This opertaion never fail */ + zTmp = pEngine->pIo->xTmpPage(pEngine->pIo->pHandle); + /* Move the target cells to the beginning */ + pCell = pPage->pMaster->pList; + /* Write the slave page number */ + SyBigEndianPack64(&zTmp[2/*Offset of the first cell */+2/*Offset of the first free block */],pPage->sHdr.iSlave); + zPtr = &zTmp[L_HASH_PAGE_HDR_SZ]; /* Offset to start writing from */ + zEnd = &zTmp[pEngine->iPageSize]; + pPage->sHdr.iOfft = 0; /* Offset of the first cell */ + for(;;){ + if( pCell == 0 ){ + /* No more cells */ + break; + } + if( pCell->pPage->pRaw->pgno == pPage->pRaw->pgno ){ + /* Cell payload if locally stored */ + zPayload = 0; + if( pCell->iOvfl == 0 ){ + zPayload = &pCell->pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ]; + } + /* Move the cell */ + pCell->iNext = pPage->sHdr.iOfft; + pCell->iStart = (sxu16)(zPtr - zTmp); /* Offset where this cell start */ + pPage->sHdr.iOfft = pCell->iStart; + /* Write the cell header */ + /* 4 byte hash number */ + SyBigEndianPack32(zPtr,pCell->nHash); + zPtr += 4; + /* 4 byte ley length */ + SyBigEndianPack32(zPtr,pCell->nKey); + zPtr += 4; + /* 8 byte data length */ + SyBigEndianPack64(zPtr,pCell->nData); + zPtr += 8; + /* 2 byte offset of the next cell */ + SyBigEndianPack16(zPtr,pCell->iNext); + zPtr += 2; + /* 8 byte overflow page number */ + SyBigEndianPack64(zPtr,pCell->iOvfl); + zPtr += 8; + if( zPayload ){ + /* Local payload */ + SyMemcpy((const void *)zPayload,zPtr,(sxu32)(pCell->nKey + pCell->nData)); + zPtr += pCell->nKey + pCell->nData; + } + if( zPtr >= zEnd ){ + /* Can't happen */ + break; + } + } + /* Point to the next page */ + pCell = pCell->pNext; + } + /* Mark the free block */ + pPage->nFree = (sxu16)(zEnd - zPtr); /* Block length */ + if( pPage->nFree > 3 ){ + pPage->sHdr.iFree = (sxu16)(zPtr - zTmp); /* Offset of the free block */ + /* Mark the block */ + SyBigEndianPack16(zPtr,0); /* Offset of the next free block */ + SyBigEndianPack16(&zPtr[2],pPage->nFree); /* Block length */ + }else{ + /* Block of length less than 4 bytes are simply discarded */ + pPage->nFree = 0; + pPage->sHdr.iFree = 0; + } + /* Reflect the change */ + SyBigEndianPack16(zTmp,pPage->sHdr.iOfft); /* Offset of the first cell */ + SyBigEndianPack16(&zTmp[2],pPage->sHdr.iFree); /* Offset of the first free block */ + SyMemcpy((const void *)zTmp,pPage->pRaw->zData,pEngine->iPageSize); + /* All done */ + return UNQLITE_OK; +} +/* +** Allocate nByte bytes of space on a page. +** +** Return the index into pPage->pRaw->zData[] of the first byte of +** the new allocation. Or return 0 if there is not enough free +** space on the page to satisfy the allocation request. +** +** If the page contains nBytes of free space but does not contain +** nBytes of contiguous free space, then this routine automatically +** calls defragementPage() to consolidate all free space before +** allocating the new chunk. +*/ +static int lhAllocateSpace(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft) +{ + const unsigned char *zEnd,*zPtr; + sxu16 iNext,iBlksz,nByte; + unsigned char *zPrev; + int rc; + if( (sxu64)pPage->nFree < nAmount ){ + /* Don't bother looking for a free chunk */ + return UNQLITE_FULL; + } + if( pPage->nCell < 10 && ((int)nAmount >= (pPage->pHash->iPageSize / 2)) ){ + /* Big chunk need an overflow page for its data */ + return UNQLITE_FULL; + } + zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree]; + zEnd = &pPage->pRaw->zData[pPage->pHash->iPageSize]; + nByte = (sxu16)nAmount; + zPrev = 0; + iBlksz = 0; /* cc warning */ + /* Perform the lookup */ + for(;;){ + if( zPtr >= zEnd ){ + return UNQLITE_FULL; + } + /* Offset of the next free block */ + SyBigEndianUnpack16(zPtr,&iNext); + /* Block size */ + SyBigEndianUnpack16(&zPtr[2],&iBlksz); + if( iBlksz >= nByte ){ + /* Got one */ + break; + } + zPrev = (unsigned char *)zPtr; + if( iNext == 0 ){ + /* No more free blocks, defragment the page */ + rc = lhPageDefragment(pPage); + if( rc == UNQLITE_OK && pPage->nFree >= nByte) { + /* Free blocks are merged together */ + iNext = 0; + zPtr = &pPage->pRaw->zData[pPage->sHdr.iFree]; + iBlksz = pPage->nFree; + zPrev = 0; + break; + }else{ + return UNQLITE_FULL; + } + } + /* Point to the next free block */ + zPtr = &pPage->pRaw->zData[iNext]; + } + /* Acquire writer lock on this page */ + rc = pPage->pHash->pIo->xWrite(pPage->pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Save block offset */ + *pOfft = (sxu16)(zPtr - pPage->pRaw->zData); + /* Fix pointers */ + if( iBlksz >= nByte && (iBlksz - nByte) > 3 ){ + unsigned char *zBlock = &pPage->pRaw->zData[(*pOfft) + nByte]; + /* Create a new block */ + zPtr = zBlock; + SyBigEndianPack16(zBlock,iNext); /* Offset of the next block */ + SyBigEndianPack16(&zBlock[2],iBlksz-nByte); /* Block size*/ + /* Offset of the new block */ + iNext = (sxu16)(zPtr - pPage->pRaw->zData); + iBlksz = nByte; + } + /* Fix offsets */ + if( zPrev ){ + SyBigEndianPack16(zPrev,iNext); + }else{ + /* First block */ + pPage->sHdr.iFree = iNext; + /* Reflect on the page header */ + SyBigEndianPack16(&pPage->pRaw->zData[2/* Offset of the first cell1*/],iNext); + } + /* All done */ + pPage->nFree -= iBlksz; + return UNQLITE_OK; +} +/* + * Write the cell header into the corresponding offset. + */ +static int lhCellWriteHeader(lhcell *pCell) +{ + lhpage *pPage = pCell->pPage; + unsigned char *zRaw = pPage->pRaw->zData; + /* Seek to the desired location */ + zRaw += pCell->iStart; + /* 4 byte hash number */ + SyBigEndianPack32(zRaw,pCell->nHash); + zRaw += 4; + /* 4 byte key length */ + SyBigEndianPack32(zRaw,pCell->nKey); + zRaw += 4; + /* 8 byte data length */ + SyBigEndianPack64(zRaw,pCell->nData); + zRaw += 8; + /* 2 byte offset of the next cell */ + pCell->iNext = pPage->sHdr.iOfft; + SyBigEndianPack16(zRaw,pCell->iNext); + zRaw += 2; + /* 8 byte overflow page number */ + SyBigEndianPack64(zRaw,pCell->iOvfl); + /* Update the page header */ + pPage->sHdr.iOfft = pCell->iStart; + /* pEngine->pIo->xWrite() has been successfully called on this page */ + SyBigEndianPack16(pPage->pRaw->zData,pCell->iStart); + /* All done */ + return UNQLITE_OK; +} +/* + * Write local payload. + */ +static int lhCellWriteLocalPayload(lhcell *pCell, + const void *pKey,sxu32 nKeylen, + const void *pData,unqlite_int64 nDatalen + ) +{ + /* A writer lock have been acquired on this page */ + lhpage *pPage = pCell->pPage; + unsigned char *zRaw = pPage->pRaw->zData; + /* Seek to the desired location */ + zRaw += pCell->iStart + L_HASH_CELL_SZ; + /* Write the key */ + SyMemcpy(pKey,(void *)zRaw,nKeylen); + zRaw += nKeylen; + if( nDatalen > 0 ){ + /* Write the Data */ + SyMemcpy(pData,(void *)zRaw,(sxu32)nDatalen); + } + return UNQLITE_OK; +} +/* + * Allocate as much overflow page we need to store the cell payload. + */ +static int lhCellWriteOvflPayload(lhcell *pCell,const void *pKey,sxu32 nKeylen,...) +{ + lhpage *pPage = pCell->pPage; + lhash_kv_engine *pEngine = pPage->pHash; + unqlite_page *pOvfl,*pFirst,*pNew; + const unsigned char *zPtr,*zEnd; + unsigned char *zRaw,*zRawEnd; + sxu32 nAvail; + va_list ap; + int rc; + /* Acquire a new overflow page */ + rc = lhAcquirePage(pEngine,&pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Acquire a writer lock */ + rc = pEngine->pIo->xWrite(pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + pFirst = pOvfl; + /* Link */ + pCell->iOvfl = pOvfl->pgno; + /* Update the cell header */ + SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4/*Hash*/ + 4/*Key*/ + 8/*Data*/ + 2 /*Next cell*/],pCell->iOvfl); + /* Start the write process */ + zPtr = (const unsigned char *)pKey; + zEnd = &zPtr[nKeylen]; + SyBigEndianPack64(pOvfl->zData,0); /* Next overflow page on the chain */ + zRaw = &pOvfl->zData[8/* Next ovfl page*/ + 8 /* Data page */ + 2 /* Data offset*/]; + zRawEnd = &pOvfl->zData[pEngine->iPageSize]; + pNew = pOvfl; + /* Write the key */ + for(;;){ + if( zPtr >= zEnd ){ + break; + } + if( zRaw >= zRawEnd ){ + /* Acquire a new page */ + rc = lhAcquirePage(pEngine,&pNew); + if( rc != UNQLITE_OK ){ + return rc; + } + rc = pEngine->pIo->xWrite(pNew); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Link */ + SyBigEndianPack64(pOvfl->zData,pNew->pgno); + pEngine->pIo->xPageUnref(pOvfl); + SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ + pOvfl = pNew; + zRaw = &pNew->zData[8]; + zRawEnd = &pNew->zData[pEngine->iPageSize]; + } + nAvail = (sxu32)(zRawEnd-zRaw); + nKeylen = (sxu32)(zEnd-zPtr); + if( nKeylen > nAvail ){ + nKeylen = nAvail; + } + SyMemcpy((const void *)zPtr,(void *)zRaw,nKeylen); + /* Synchronize pointers */ + zPtr += nKeylen; + zRaw += nKeylen; + } + rc = UNQLITE_OK; + va_start(ap,nKeylen); + pCell->iDataPage = pNew->pgno; + pCell->iDataOfft = (sxu16)(zRaw-pNew->zData); + /* Write the data page and its offset */ + SyBigEndianPack64(&pFirst->zData[8/*Next ovfl*/],pCell->iDataPage); + SyBigEndianPack16(&pFirst->zData[8/*Next ovfl*/+8/*Data page*/],pCell->iDataOfft); + /* Write data */ + for(;;){ + const void *pData; + sxu32 nDatalen; + sxu64 nData; + pData = va_arg(ap,const void *); + nData = va_arg(ap,sxu64); + if( pData == 0 ){ + /* No more chunks */ + break; + } + /* Write this chunk */ + zPtr = (const unsigned char *)pData; + zEnd = &zPtr[nData]; + for(;;){ + if( zPtr >= zEnd ){ + break; + } + if( zRaw >= zRawEnd ){ + /* Acquire a new page */ + rc = lhAcquirePage(pEngine,&pNew); + if( rc != UNQLITE_OK ){ + va_end(ap); + return rc; + } + rc = pEngine->pIo->xWrite(pNew); + if( rc != UNQLITE_OK ){ + va_end(ap); + return rc; + } + /* Link */ + SyBigEndianPack64(pOvfl->zData,pNew->pgno); + pEngine->pIo->xPageUnref(pOvfl); + SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ + pOvfl = pNew; + zRaw = &pNew->zData[8]; + zRawEnd = &pNew->zData[pEngine->iPageSize]; + } + nAvail = (sxu32)(zRawEnd-zRaw); + nDatalen = (sxu32)(zEnd-zPtr); + if( nDatalen > nAvail ){ + nDatalen = nAvail; + } + SyMemcpy((const void *)zPtr,(void *)zRaw,nDatalen); + /* Synchronize pointers */ + zPtr += nDatalen; + zRaw += nDatalen; + } + } + /* Unref the overflow page */ + pEngine->pIo->xPageUnref(pOvfl); + va_end(ap); + return UNQLITE_OK; +} +/* + * Restore a page to the free list. + */ +static int lhRestorePage(lhash_kv_engine *pEngine,unqlite_page *pPage) +{ + int rc; + rc = pEngine->pIo->xWrite(pEngine->pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + rc = pEngine->pIo->xWrite(pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Link to the list of free page */ + SyBigEndianPack64(pPage->zData,pEngine->nFreeList); + pEngine->nFreeList = pPage->pgno; + SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/],pEngine->nFreeList); + /* All done */ + return UNQLITE_OK; +} +/* + * Restore cell space and mark it as a free block. + */ +static int lhRestoreSpace(lhpage *pPage,sxu16 iOfft,sxu16 nByte) +{ + unsigned char *zRaw; + if( nByte < 4 ){ + /* At least 4 bytes of freespace must form a valid block */ + return UNQLITE_OK; + } + /* pEngine->pIo->xWrite() has been successfully called on this page */ + zRaw = &pPage->pRaw->zData[iOfft]; + /* Mark as a free block */ + SyBigEndianPack16(zRaw,pPage->sHdr.iFree); /* Offset of the next free block */ + zRaw += 2; + SyBigEndianPack16(zRaw,nByte); + /* Link */ + SyBigEndianPack16(&pPage->pRaw->zData[2/* offset of the first cell */],iOfft); + pPage->sHdr.iFree = iOfft; + pPage->nFree += nByte; + return UNQLITE_OK; +} +/* Forward declaration */ +static lhcell * lhFindSibeling(lhcell *pCell); +/* + * Unlink a cell. + */ +static int lhUnlinkCell(lhcell *pCell) +{ + lhash_kv_engine *pEngine = pCell->pPage->pHash; + lhpage *pPage = pCell->pPage; + sxu16 nByte = L_HASH_CELL_SZ; + lhcell *pPrev; + int rc; + rc = pEngine->pIo->xWrite(pPage->pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Bring the link */ + pPrev = lhFindSibeling(pCell); + if( pPrev ){ + pPrev->iNext = pCell->iNext; + /* Fix offsets in the page header */ + SyBigEndianPack16(&pPage->pRaw->zData[pPrev->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext); + }else{ + /* First entry on this page (either master or slave) */ + pPage->sHdr.iOfft = pCell->iNext; + /* Update the page header */ + SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext); + } + /* Restore cell space */ + if( pCell->iOvfl == 0 ){ + nByte += (sxu16)(pCell->nData + pCell->nKey); + } + lhRestoreSpace(pPage,pCell->iStart,nByte); + /* Discard the cell from the in-memory hashtable */ + lhCellDiscard(pCell); + return UNQLITE_OK; +} +/* + * Remove a cell and its paylod (key + data). + */ +static int lhRecordRemove(lhcell *pCell) +{ + lhash_kv_engine *pEngine = pCell->pPage->pHash; + int rc; + if( pCell->iOvfl > 0){ + /* Discard overflow pages */ + unqlite_page *pOvfl; + pgno iNext = pCell->iOvfl; + for(;;){ + /* Point to the overflow page */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iNext,&pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Next page on the chain */ + SyBigEndianUnpack64(pOvfl->zData,&iNext); + /* Restore the page to the free list */ + rc = lhRestorePage(pEngine,pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Unref */ + pEngine->pIo->xPageUnref(pOvfl); + if( iNext == 0 ){ + break; + } + } + } + /* Unlink the cell */ + rc = lhUnlinkCell(pCell); + return rc; +} +/* + * Find cell sibeling. + */ +static lhcell * lhFindSibeling(lhcell *pCell) +{ + lhpage *pPage = pCell->pPage->pMaster; + lhcell *pEntry; + pEntry = pPage->pFirst; + while( pEntry ){ + if( pEntry->pPage == pCell->pPage && pEntry->iNext == pCell->iStart ){ + /* Sibeling found */ + return pEntry; + } + /* Point to the previous entry */ + pEntry = pEntry->pPrev; + } + /* Last inserted cell */ + return 0; +} +/* + * Move a cell to a new location with its new data. + */ +static int lhMoveLocalCell( + lhcell *pCell, + sxu16 iOfft, + const void *pData, + unqlite_int64 nData + ) +{ + sxu16 iKeyOfft = pCell->iStart + L_HASH_CELL_SZ; + lhpage *pPage = pCell->pPage; + lhcell *pSibeling; + pSibeling = lhFindSibeling(pCell); + if( pSibeling ){ + /* Fix link */ + SyBigEndianPack16(&pPage->pRaw->zData[pSibeling->iStart + 4/*Hash*/+4/*Key*/+8/*Data*/],pCell->iNext); + pSibeling->iNext = pCell->iNext; + }else{ + /* First cell, update page header only */ + SyBigEndianPack16(pPage->pRaw->zData,pCell->iNext); + pPage->sHdr.iOfft = pCell->iNext; + } + /* Set the new offset */ + pCell->iStart = iOfft; + pCell->nData = (sxu64)nData; + /* Write the cell payload */ + lhCellWriteLocalPayload(pCell,(const void *)&pPage->pRaw->zData[iKeyOfft],pCell->nKey,pData,nData); + /* Finally write the cell header */ + lhCellWriteHeader(pCell); + /* All done */ + return UNQLITE_OK; +} +/* + * Overwrite an existing record. + */ +static int lhRecordOverwrite( + lhcell *pCell, + const void *pData,unqlite_int64 nByte + ) +{ + lhash_kv_engine *pEngine = pCell->pPage->pHash; + unsigned char *zRaw,*zRawEnd,*zPayload; + const unsigned char *zPtr,*zEnd; + unqlite_page *pOvfl,*pOld,*pNew; + lhpage *pPage = pCell->pPage; + sxu32 nAvail; + pgno iOvfl; + int rc; + /* Acquire a writer lock on this page */ + rc = pEngine->pIo->xWrite(pPage->pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pCell->iOvfl == 0 ){ + /* Local payload, try to deal with the free space issues */ + zPayload = &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey]; + if( pCell->nData == (sxu64)nByte ){ + /* Best scenario, simply a memcpy operation */ + SyMemcpy(pData,(void *)zPayload,(sxu32)nByte); + }else if( (sxu64)nByte < pCell->nData ){ + /* Shorter data, not so ugly */ + SyMemcpy(pData,(void *)zPayload,(sxu32)nByte); + /* Update the cell header */ + SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],nByte); + /* Restore freespace */ + lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ + pCell->nKey + nByte),(sxu16)(pCell->nData - nByte)); + /* New data size */ + pCell->nData = (sxu64)nByte; + }else{ + sxu16 iOfft = 0; /* cc warning */ + /* Check if another chunk is available for this cell */ + rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + nByte,&iOfft); + if( rc != UNQLITE_OK ){ + /* Transfer the payload to an overflow page */ + rc = lhCellWriteOvflPayload(pCell,&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey,pData,nByte,(const void *)0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Update the cell header */ + SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],(sxu64)nByte); + /* Restore freespace */ + lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData)); + /* New data size */ + pCell->nData = (sxu64)nByte; + }else{ + sxu16 iOldOfft = pCell->iStart; + sxu32 iOld = (sxu32)pCell->nData; + /* Space is available, transfer the cell */ + lhMoveLocalCell(pCell,iOfft,pData,nByte); + /* Restore cell space */ + lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld)); + } + } + return UNQLITE_OK; + } + /* Point to the overflow page */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Relase all old overflow pages first */ + SyBigEndianUnpack64(pOvfl->zData,&iOvfl); + pOld = pOvfl; + for(;;){ + if( iOvfl == 0 ){ + /* No more overflow pages on the chain */ + break; + } + /* Point to the target page */ + if( UNQLITE_OK != pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pOld) ){ + /* Not so fatal if something goes wrong here */ + break; + } + /* Next overflow page to be released */ + SyBigEndianUnpack64(pOld->zData,&iOvfl); + if( pOld != pOvfl ){ /* xx: chm is maniac */ + /* Restore the page to the free list */ + lhRestorePage(pEngine,pOld); + /* Unref */ + pEngine->pIo->xPageUnref(pOld); + } + } + /* Point to the data offset */ + zRaw = &pOvfl->zData[pCell->iDataOfft]; + zRawEnd = &pOvfl->zData[pEngine->iPageSize]; + /* The data to be stored */ + zPtr = (const unsigned char *)pData; + zEnd = &zPtr[nByte]; + /* Start the overwrite process */ + /* Acquire a writer lock */ + rc = pEngine->pIo->xWrite(pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + SyBigEndianPack64(pOvfl->zData,0); + for(;;){ + sxu32 nLen; + if( zPtr >= zEnd ){ + break; + } + if( zRaw >= zRawEnd ){ + /* Acquire a new page */ + rc = lhAcquirePage(pEngine,&pNew); + if( rc != UNQLITE_OK ){ + return rc; + } + rc = pEngine->pIo->xWrite(pNew); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Link */ + SyBigEndianPack64(pOvfl->zData,pNew->pgno); + pEngine->pIo->xPageUnref(pOvfl); + SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ + pOvfl = pNew; + zRaw = &pNew->zData[8]; + zRawEnd = &pNew->zData[pEngine->iPageSize]; + } + nAvail = (sxu32)(zRawEnd-zRaw); + nLen = (sxu32)(zEnd-zPtr); + if( nLen > nAvail ){ + nLen = nAvail; + } + SyMemcpy((const void *)zPtr,(void *)zRaw,nLen); + /* Synchronize pointers */ + zPtr += nLen; + zRaw += nLen; + } + /* Unref the last overflow page */ + pEngine->pIo->xPageUnref(pOvfl); + /* Finally, update the cell header */ + pCell->nData = (sxu64)nByte; + SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData); + /* All done */ + return UNQLITE_OK; +} +/* + * Append data to an existing record. + */ +static int lhRecordAppend( + lhcell *pCell, + const void *pData,unqlite_int64 nByte + ) +{ + lhash_kv_engine *pEngine = pCell->pPage->pHash; + const unsigned char *zPtr,*zEnd; + lhpage *pPage = pCell->pPage; + unsigned char *zRaw,*zRawEnd; + unqlite_page *pOvfl,*pNew; + sxu64 nDatalen; + sxu32 nAvail; + pgno iOvfl; + int rc; + if( pCell->nData + nByte < pCell->nData ){ + /* Overflow */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); + return UNQLITE_LIMIT; + } + /* Acquire a writer lock on this page */ + rc = pEngine->pIo->xWrite(pPage->pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pCell->iOvfl == 0 ){ + sxu16 iOfft = 0; /* cc warning */ + /* Local payload, check for a bigger place */ + rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ + pCell->nKey + pCell->nData + nByte,&iOfft); + if( rc != UNQLITE_OK ){ + /* Transfer the payload to an overflow page */ + rc = lhCellWriteOvflPayload(pCell, + &pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ],pCell->nKey, + (const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],pCell->nData, + pData,nByte, + (const void *)0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Update the cell header */ + SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData + nByte); + /* Restore freespace */ + lhRestoreSpace(pPage,(sxu16)(pCell->iStart + L_HASH_CELL_SZ),(sxu16)(pCell->nKey + pCell->nData)); + /* New data size */ + pCell->nData += nByte; + }else{ + sxu16 iOldOfft = pCell->iStart; + sxu32 iOld = (sxu32)pCell->nData; + SyBlob sWorker; + SyBlobInit(&sWorker,&pEngine->sAllocator); + /* Copy the old data */ + rc = SyBlobAppend(&sWorker,(const void *)&pPage->pRaw->zData[pCell->iStart + L_HASH_CELL_SZ + pCell->nKey],(sxu32)pCell->nData); + if( rc == SXRET_OK ){ + /* Append the new data */ + rc = SyBlobAppend(&sWorker,pData,(sxu32)nByte); + } + if( rc != UNQLITE_OK ){ + SyBlobRelease(&sWorker); + return rc; + } + /* Space is available, transfer the cell */ + lhMoveLocalCell(pCell,iOfft,SyBlobData(&sWorker),(unqlite_int64)SyBlobLength(&sWorker)); + /* Restore cell space */ + lhRestoreSpace(pPage,iOldOfft,(sxu16)(L_HASH_CELL_SZ + pCell->nKey + iOld)); + /* All done */ + SyBlobRelease(&sWorker); + } + return UNQLITE_OK; + } + /* Point to the overflow page which hold the data */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,pCell->iDataPage,&pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Next overflow page in the chain */ + SyBigEndianUnpack64(pOvfl->zData,&iOvfl); + /* Point to the end of the chunk */ + zRaw = &pOvfl->zData[pCell->iDataOfft]; + zRawEnd = &pOvfl->zData[pEngine->iPageSize]; + nDatalen = pCell->nData; + nAvail = (sxu32)(zRawEnd - zRaw); + for(;;){ + if( zRaw >= zRawEnd ){ + if( iOvfl == 0 ){ + /* Cant happen */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Corrupt overflow page"); + return UNQLITE_CORRUPT; + } + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,iOvfl,&pNew); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Next overflow page on the chain */ + SyBigEndianUnpack64(pNew->zData,&iOvfl); + /* Unref the previous overflow page */ + pEngine->pIo->xPageUnref(pOvfl); + /* Point to the new chunk */ + zRaw = &pNew->zData[8]; + zRawEnd = &pNew->zData[pCell->pPage->pHash->iPageSize]; + nAvail = L_HASH_OVERFLOW_SIZE(pCell->pPage->pHash->iPageSize); + pOvfl = pNew; + } + if( (sxu64)nAvail > nDatalen ){ + zRaw += nDatalen; + break; + }else{ + nDatalen -= nAvail; + } + zRaw += nAvail; + } + /* Start the append process */ + zPtr = (const unsigned char *)pData; + zEnd = &zPtr[nByte]; + /* Acquire a writer lock */ + rc = pEngine->pIo->xWrite(pOvfl); + if( rc != UNQLITE_OK ){ + return rc; + } + for(;;){ + sxu32 nLen; + if( zPtr >= zEnd ){ + break; + } + if( zRaw >= zRawEnd ){ + /* Acquire a new page */ + rc = lhAcquirePage(pEngine,&pNew); + if( rc != UNQLITE_OK ){ + return rc; + } + rc = pEngine->pIo->xWrite(pNew); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Link */ + SyBigEndianPack64(pOvfl->zData,pNew->pgno); + pEngine->pIo->xPageUnref(pOvfl); + SyBigEndianPack64(pNew->zData,0); /* Next overflow page on the chain */ + pOvfl = pNew; + zRaw = &pNew->zData[8]; + zRawEnd = &pNew->zData[pEngine->iPageSize]; + } + nAvail = (sxu32)(zRawEnd-zRaw); + nLen = (sxu32)(zEnd-zPtr); + if( nLen > nAvail ){ + nLen = nAvail; + } + SyMemcpy((const void *)zPtr,(void *)zRaw,nLen); + /* Synchronize pointers */ + zPtr += nLen; + zRaw += nLen; + } + /* Unref the last overflow page */ + pEngine->pIo->xPageUnref(pOvfl); + /* Finally, update the cell header */ + pCell->nData += nByte; + SyBigEndianPack64(&pPage->pRaw->zData[pCell->iStart + 4 /* Hash */ + 4 /* Key */],pCell->nData); + /* All done */ + return UNQLITE_OK; +} +/* + * A write privilege have been acquired on this page. + * Mark it as an empty page (No cells). + */ +static int lhSetEmptyPage(lhpage *pPage) +{ + unsigned char *zRaw = pPage->pRaw->zData; + lhphdr *pHeader = &pPage->sHdr; + sxu16 nByte; + int rc; + /* Acquire a writer lock */ + rc = pPage->pHash->pIo->xWrite(pPage->pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Offset of the first cell */ + SyBigEndianPack16(zRaw,0); + zRaw += 2; + /* Offset of the first free block */ + pHeader->iFree = L_HASH_PAGE_HDR_SZ; + SyBigEndianPack16(zRaw,L_HASH_PAGE_HDR_SZ); + zRaw += 2; + /* Slave page number */ + SyBigEndianPack64(zRaw,0); + zRaw += 8; + /* Fill the free block */ + SyBigEndianPack16(zRaw,0); /* Offset of the next free block */ + zRaw += 2; + nByte = (sxu16)L_HASH_MX_FREE_SPACE(pPage->pHash->iPageSize); + SyBigEndianPack16(zRaw,nByte); + pPage->nFree = nByte; + /* Do not add this page to the hot dirty list */ + pPage->pHash->pIo->xDontMkHot(pPage->pRaw); + return UNQLITE_OK; +} +/* Forward declaration */ +static int lhSlaveStore( + lhpage *pPage, + const void *pKey,sxu32 nKeyLen, + const void *pData,unqlite_int64 nDataLen, + sxu32 nHash + ); +/* + * Store a cell and its payload in a given page. + */ +static int lhStoreCell( + lhpage *pPage, /* Target page */ + const void *pKey,sxu32 nKeyLen, /* Payload: Key */ + const void *pData,unqlite_int64 nDataLen, /* Payload: Data */ + sxu32 nHash, /* Hash of the key */ + int auto_append /* Auto append a slave page if full */ + ) +{ + lhash_kv_engine *pEngine = pPage->pHash; + int iNeedOvfl = 0; /* Need overflow page for this cell and its payload*/ + lhcell *pCell; + sxu16 nOfft; + int rc; + /* Acquire a writer lock on this page first */ + rc = pEngine->pIo->xWrite(pPage->pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Check for a free block */ + rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ+nKeyLen+nDataLen,&nOfft); + if( rc != UNQLITE_OK ){ + /* Check for a free block to hold a single cell only (without payload) */ + rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft); + if( rc != UNQLITE_OK ){ + if( !auto_append ){ + /* A split must be done */ + return UNQLITE_FULL; + }else{ + /* Store this record in a slave page */ + rc = lhSlaveStore(pPage,pKey,nKeyLen,pData,nDataLen,nHash); + return rc; + } + } + iNeedOvfl = 1; + } + /* Allocate a new cell instance */ + pCell = lhNewCell(pEngine,pPage); + if( pCell == 0 ){ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"KV store is running out of memory"); + return UNQLITE_NOMEM; + } + /* Fill-in the structure */ + pCell->iStart = nOfft; + pCell->nKey = nKeyLen; + pCell->nData = (sxu64)nDataLen; + pCell->nHash = nHash; + if( nKeyLen < 262144 /* 256 KB */ ){ + /* Keep the key in-memory for fast lookup */ + SyBlobAppend(&pCell->sKey,pKey,nKeyLen); + } + /* Link the cell */ + rc = lhInstallCell(pCell); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Write the payload */ + if( iNeedOvfl ){ + rc = lhCellWriteOvflPayload(pCell,pKey,nKeyLen,pData,nDataLen,(const void *)0); + if( rc != UNQLITE_OK ){ + lhCellDiscard(pCell); + return rc; + } + }else{ + lhCellWriteLocalPayload(pCell,pKey,nKeyLen,pData,nDataLen); + } + /* Finally, Write the cell header */ + lhCellWriteHeader(pCell); + /* All done */ + return UNQLITE_OK; +} +/* + * Find a slave page capable of hosting the given amount. + */ +static int lhFindSlavePage(lhpage *pPage,sxu64 nAmount,sxu16 *pOfft,lhpage **ppSlave) +{ + lhash_kv_engine *pEngine = pPage->pHash; + lhpage *pMaster = pPage->pMaster; + lhpage *pSlave = pMaster->pSlave; + unqlite_page *pRaw; + lhpage *pNew; + sxu16 iOfft; + sxi32 i; + int rc; + /* Look for an already attached slave page */ + for( i = 0 ; i < pMaster->iSlave ; ++i ){ + /* Find a free chunk big enough */ + sxu16 size = (sxu16)(L_HASH_CELL_SZ + nAmount); + rc = lhAllocateSpace(pSlave,size,&iOfft); + if( rc != UNQLITE_OK ){ + /* A space for cell header only */ + size = L_HASH_CELL_SZ; + rc = lhAllocateSpace(pSlave,size,&iOfft); + } + if( rc == UNQLITE_OK ){ + /* All done */ + if( pOfft ){ + *pOfft = iOfft; + }else{ + rc = lhRestoreSpace(pSlave, iOfft, size); + } + *ppSlave = pSlave; + return rc; + } + /* Point to the next slave page */ + pSlave = pSlave->pNextSlave; + } + /* Acquire a new slave page */ + rc = lhAcquirePage(pEngine,&pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Last slave page */ + pSlave = pMaster->pSlave; + if( pSlave == 0 ){ + /* First slave page */ + pSlave = pMaster; + } + /* Initialize the page */ + pNew = lhNewPage(pEngine,pRaw,pMaster); + if( pNew == 0 ){ + return UNQLITE_NOMEM; + } + /* Mark as an empty page */ + rc = lhSetEmptyPage(pNew); + if( rc != UNQLITE_OK ){ + goto fail; + } + if( pOfft ){ + /* Look for a free block */ + if( UNQLITE_OK != lhAllocateSpace(pNew,L_HASH_CELL_SZ+nAmount,&iOfft) ){ + /* Cell header only */ + lhAllocateSpace(pNew,L_HASH_CELL_SZ,&iOfft); /* Never fail */ + } + *pOfft = iOfft; + } + /* Link this page to the previous slave page */ + rc = pEngine->pIo->xWrite(pSlave->pRaw); + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Reflect in the page header */ + SyBigEndianPack64(&pSlave->pRaw->zData[2/*Cell offset*/+2/*Free block offset*/],pRaw->pgno); + pSlave->sHdr.iSlave = pRaw->pgno; + /* All done */ + *ppSlave = pNew; + return UNQLITE_OK; +fail: + pEngine->pIo->xPageUnref(pNew->pRaw); /* pNew will be released in this call */ + return rc; + +} +/* + * Perform a store operation in a slave page. + */ +static int lhSlaveStore( + lhpage *pPage, /* Master page */ + const void *pKey,sxu32 nKeyLen, /* Payload: key */ + const void *pData,unqlite_int64 nDataLen, /* Payload: data */ + sxu32 nHash /* Hash of the key */ + ) +{ + lhpage *pSlave; + int rc; + /* Find a slave page */ + rc = lhFindSlavePage(pPage,nKeyLen + nDataLen,0,&pSlave); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Perform the insertion in the slave page */ + rc = lhStoreCell(pSlave,pKey,nKeyLen,pData,nDataLen,nHash,1); + return rc; +} +/* + * Transfer a cell to a new page (either a master or slave). + */ +static int lhTransferCell(lhcell *pTarget,lhpage *pPage) +{ + lhcell *pCell; + sxu16 nOfft; + int rc; + /* Check for a free block to hold a single cell only */ + rc = lhAllocateSpace(pPage,L_HASH_CELL_SZ,&nOfft); + if( rc != UNQLITE_OK ){ + /* Store in a slave page */ + rc = lhFindSlavePage(pPage,L_HASH_CELL_SZ,&nOfft,&pPage); + if( rc != UNQLITE_OK ){ + return rc; + } + } + /* Allocate a new cell instance */ + pCell = lhNewCell(pPage->pHash,pPage); + if( pCell == 0 ){ + return UNQLITE_NOMEM; + } + /* Fill-in the structure */ + pCell->iStart = nOfft; + pCell->nData = pTarget->nData; + pCell->nKey = pTarget->nKey; + pCell->iOvfl = pTarget->iOvfl; + pCell->iDataOfft = pTarget->iDataOfft; + pCell->iDataPage = pTarget->iDataPage; + pCell->nHash = pTarget->nHash; + SyBlobDup(&pTarget->sKey,&pCell->sKey); + /* Link the cell */ + rc = lhInstallCell(pCell); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Finally, Write the cell header */ + lhCellWriteHeader(pCell); + /* All done */ + return UNQLITE_OK; +} +/* + * Perform a page split. + */ +static int lhPageSplit( + lhpage *pOld, /* Page to be split */ + lhpage *pNew, /* New page */ + pgno split_bucket, /* Current split bucket */ + pgno high_mask /* High mask (Max split bucket - 1) */ + ) +{ + lhcell *pCell,*pNext; + SyBlob sWorker; + pgno iBucket; + int rc; + SyBlobInit(&sWorker,&pOld->pHash->sAllocator); + /* Perform the split */ + pCell = pOld->pList; + for( ;; ){ + if( pCell == 0 ){ + /* No more cells */ + break; + } + /* Obtain the new logical bucket */ + iBucket = pCell->nHash & high_mask; + pNext = pCell->pNext; + if( iBucket != split_bucket){ + rc = UNQLITE_OK; + if( pCell->iOvfl ){ + /* Transfer the cell only */ + rc = lhTransferCell(pCell,pNew); + }else{ + /* Transfer the cell and its payload */ + SyBlobReset(&sWorker); + if( SyBlobLength(&pCell->sKey) < 1 ){ + /* Consume the key */ + rc = lhConsumeCellkey(pCell,unqliteDataConsumer,&pCell->sKey,0); + if( rc != UNQLITE_OK ){ + goto fail; + } + } + /* Consume the data (Very small data < 65k) */ + rc = lhConsumeCellData(pCell,unqliteDataConsumer,&sWorker); + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Perform the transfer */ + rc = lhStoreCell( + pNew, + SyBlobData(&pCell->sKey),(int)SyBlobLength(&pCell->sKey), + SyBlobData(&sWorker),SyBlobLength(&sWorker), + pCell->nHash, + 1 + ); + } + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Discard the cell from the old page */ + lhUnlinkCell(pCell); + } + /* Point to the next cell */ + pCell = pNext; + } + /* All done */ + rc = UNQLITE_OK; +fail: + SyBlobRelease(&sWorker); + return rc; +} +/* + * Perform the infamous linear hash split operation. + */ +static int lhSplit(lhpage *pTarget,int *pRetry) +{ + lhash_kv_engine *pEngine = pTarget->pHash; + lhash_bmap_rec *pRec; + lhpage *pOld,*pNew; + unqlite_page *pRaw; + int rc; + /* Get the real page number of the bucket to split */ + pRec = lhMapFindBucket(pEngine,pEngine->split_bucket); + if( pRec == 0 ){ + /* Can't happen */ + return UNQLITE_CORRUPT; + } + /* Load the page to be split */ + rc = lhLoadPage(pEngine,pRec->iReal,0,&pOld,0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Request a new page */ + rc = lhAcquirePage(pEngine,&pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Initialize the page */ + pNew = lhNewPage(pEngine,pRaw,0); + if( pNew == 0 ){ + return UNQLITE_NOMEM; + } + /* Mark as an empty page */ + rc = lhSetEmptyPage(pNew); + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Install and write the logical map record */ + rc = lhMapWriteRecord(pEngine, + pEngine->split_bucket + pEngine->max_split_bucket, + pRaw->pgno + ); + if( rc != UNQLITE_OK ){ + goto fail; + } + if( pTarget->pRaw->pgno == pOld->pRaw->pgno ){ + *pRetry = 1; + } + /* Perform the split */ + rc = lhPageSplit(pOld,pNew,pEngine->split_bucket,pEngine->nmax_split_nucket - 1); + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Update the database header */ + pEngine->split_bucket++; + /* Acquire a writer lock on the first page */ + rc = pEngine->pIo->xWrite(pEngine->pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pEngine->split_bucket >= pEngine->max_split_bucket ){ + /* Increment the generation number */ + pEngine->split_bucket = 0; + pEngine->max_split_bucket = pEngine->nmax_split_nucket; + pEngine->nmax_split_nucket <<= 1; + if( !pEngine->nmax_split_nucket ){ + /* If this happen to your installation, please tell us */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Database page (64-bit integer) limit reached"); + return UNQLITE_LIMIT; + } + /* Reflect in the page header */ + SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket); + SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/+8/*Split bucket*/],pEngine->max_split_bucket); + }else{ + /* Modify only the split bucket */ + SyBigEndianPack64(&pEngine->pHeader->zData[4/*Magic*/+4/*Hash*/+8/*Free list*/],pEngine->split_bucket); + } + /* All done */ + return UNQLITE_OK; +fail: + pEngine->pIo->xPageUnref(pNew->pRaw); + return rc; +} +/* + * Store a record in the target page. + */ +static int lhRecordInstall( + lhpage *pPage, /* Target page */ + sxu32 nHash, /* Hash of the key */ + const void *pKey,sxu32 nKeyLen, /* Payload: Key */ + const void *pData,unqlite_int64 nDataLen /* Payload: Data */ + ) +{ + int rc; + rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,0); + if( rc == UNQLITE_FULL ){ + int do_retry = 0; + /* Split */ + rc = lhSplit(pPage,&do_retry); + if( rc == UNQLITE_OK ){ + if( do_retry ){ + /* Re-calculate logical bucket number */ + return SXERR_RETRY; + } + /* Perform the store */ + rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1); + } + } + return rc; +} +/* + * Insert a record (Either overwrite or append operation) in our database. + */ +static int lh_record_insert( + unqlite_kv_engine *pKv, /* KV store */ + const void *pKey,sxu32 nKeyLen, /* Payload: Key */ + const void *pData,unqlite_int64 nDataLen, /* Payload: data */ + int is_append /* True for an append operation */ + ) +{ + lhash_kv_engine *pEngine = (lhash_kv_engine *)pKv; + lhash_bmap_rec *pRec; + unqlite_page *pRaw; + lhpage *pPage; + lhcell *pCell; + pgno iBucket; + sxu32 nHash; + int iCnt; + int rc; + + /* Acquire the first page (DB hash Header) so that everything gets loaded automatically */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); + if( rc != UNQLITE_OK ){ + return rc; + } + iCnt = 0; + /* Compute the hash of the key first */ + nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); +retry: + /* Extract the logical bucket number */ + iBucket = nHash & (pEngine->nmax_split_nucket - 1); + if( iBucket >= pEngine->split_bucket + pEngine->max_split_bucket ){ + /* Low mask */ + iBucket = nHash & (pEngine->max_split_bucket - 1); + } + /* Map the logical bucket number to real page number */ + pRec = lhMapFindBucket(pEngine,iBucket); + if( pRec == 0 ){ + /* Request a new page */ + rc = lhAcquirePage(pEngine,&pRaw); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Initialize the page */ + pPage = lhNewPage(pEngine,pRaw,0); + if( pPage == 0 ){ + return UNQLITE_NOMEM; + } + /* Mark as an empty page */ + rc = lhSetEmptyPage(pPage); + if( rc != UNQLITE_OK ){ + pEngine->pIo->xPageUnref(pRaw); /* pPage will be released during this call */ + return rc; + } + /* Store the cell */ + rc = lhStoreCell(pPage,pKey,nKeyLen,pData,nDataLen,nHash,1); + if( rc == UNQLITE_OK ){ + /* Install and write the logical map record */ + rc = lhMapWriteRecord(pEngine,iBucket,pRaw->pgno); + } + pEngine->pIo->xPageUnref(pRaw); + return rc; + }else{ + /* Load the page */ + rc = lhLoadPage(pEngine,pRec->iReal,0,&pPage,0); + if( rc != UNQLITE_OK ){ + /* IO error, unlikely scenario */ + return rc; + } + /* Do not add this page to the hot dirty list */ + pEngine->pIo->xDontMkHot(pPage->pRaw); + /* Lookup for the cell */ + pCell = lhFindCell(pPage,pKey,(sxu32)nKeyLen,nHash); + if( pCell == 0 ){ + /* Create the record */ + rc = lhRecordInstall(pPage,nHash,pKey,nKeyLen,pData,nDataLen); + if( rc == SXERR_RETRY && iCnt++ < 2 ){ + rc = UNQLITE_OK; + goto retry; + } + }else{ + if( is_append ){ + /* Append operation */ + rc = lhRecordAppend(pCell,pData,nDataLen); + }else{ + /* Overwrite old value */ + rc = lhRecordOverwrite(pCell,pData,nDataLen); + } + } + pEngine->pIo->xPageUnref(pPage->pRaw); + } + return rc; +} +/* + * Replace method. + */ +static int lhash_kv_replace( + unqlite_kv_engine *pKv, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ) +{ + int rc; + rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,0); + return rc; +} +/* + * Append method. + */ +static int lhash_kv_append( + unqlite_kv_engine *pKv, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ) +{ + int rc; + rc = lh_record_insert(pKv,pKey,(sxu32)nKeyLen,pData,nDataLen,1); + return rc; +} +/* + * Write the hash header (Page one). + */ +static int lhash_write_header(lhash_kv_engine *pEngine,unqlite_page *pHeader) +{ + unsigned char *zRaw = pHeader->zData; + lhash_bmap_page *pMap; + + pEngine->pHeader = pHeader; + /* 4 byte magic number */ + SyBigEndianPack32(zRaw,pEngine->nMagic); + zRaw += 4; + /* 4 byte hash value to identify a valid hash function */ + SyBigEndianPack32(zRaw,pEngine->xHash(L_HASH_WORD,sizeof(L_HASH_WORD)-1)); + zRaw += 4; + /* List of free pages: Empty */ + SyBigEndianPack64(zRaw,0); + zRaw += 8; + /* Current split bucket */ + SyBigEndianPack64(zRaw,pEngine->split_bucket); + zRaw += 8; + /* Maximum split bucket */ + SyBigEndianPack64(zRaw,pEngine->max_split_bucket); + zRaw += 8; + /* Initialize the bucket map */ + pMap = &pEngine->sPageMap; + /* Fill in the structure */ + pMap->iNum = pHeader->pgno; + /* Next page in the bucket map */ + SyBigEndianPack64(zRaw,0); + zRaw += 8; + /* Total number of records in the bucket map */ + SyBigEndianPack32(zRaw,0); + zRaw += 4; + pMap->iPtr = (sxu16)(zRaw - pHeader->zData); + /* All done */ + return UNQLITE_OK; + } +/* + * Exported: xOpen() method. + */ +static int lhash_kv_open(unqlite_kv_engine *pEngine,pgno dbSize) +{ + lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; + unqlite_page *pHeader; + int rc; + if( dbSize < 1 ){ + /* A new database, create the header */ + rc = pEngine->pIo->xNew(pEngine->pIo->pHandle,&pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Acquire a writer lock */ + rc = pEngine->pIo->xWrite(pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Write the hash header */ + rc = lhash_write_header(pHash,pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + }else{ + /* Acquire the page one of the database */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,&pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Read the database header */ + rc = lhash_read_header(pHash,pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + } + return UNQLITE_OK; +} +/* + * Release a master or slave page. (xUnpin callback). + */ +static void lhash_page_release(void *pUserData) +{ + lhpage *pPage = (lhpage *)pUserData; + lhash_kv_engine *pEngine = pPage->pHash; + lhcell *pNext,*pCell = pPage->pList; + unqlite_page *pRaw = pPage->pRaw; + sxu32 n; + /* Drop in-memory cells */ + for( n = 0 ; n < pPage->nCell ; ++n ){ + pNext = pCell->pNext; + SyBlobRelease(&pCell->sKey); + /* Release the cell instance */ + SyMemBackendPoolFree(&pEngine->sAllocator,(void *)pCell); + /* Point to the next entry */ + pCell = pNext; + } + if( pPage->apCell ){ + /* Release the cell table */ + SyMemBackendFree(&pEngine->sAllocator,(void *)pPage->apCell); + } + /* Finally, release the whole page */ + SyMemBackendPoolFree(&pEngine->sAllocator,pPage); + pRaw->pUserData = 0; +} +/* + * Default hash function (DJB). + */ +static sxu32 lhash_bin_hash(const void *pSrc,sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + if( nLen > 2048 /* 2K */ ){ + nLen = 2048; + } + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +/* + * Exported: xInit() method. + * Initialize the Key value storage engine. + */ +static int lhash_kv_init(unqlite_kv_engine *pEngine,int iPageSize) +{ + lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; + int rc; + + /* This structure is always zeroed, go to the initialization directly */ + SyMemBackendInitFromParent(&pHash->sAllocator,unqliteExportMemBackend()); + pHash->iPageSize = iPageSize; + /* Default hash function */ + pHash->xHash = lhash_bin_hash; + /* Default comparison function */ + pHash->xCmp = SyMemcmp; + /* Allocate a new record map */ + pHash->nBuckSize = 32; + pHash->apMap = (lhash_bmap_rec **)SyMemBackendAlloc(&pHash->sAllocator,pHash->nBuckSize *sizeof(lhash_bmap_rec *)); + if( pHash->apMap == 0 ){ + rc = UNQLITE_NOMEM; + goto err; + } + /* Zero the table */ + SyZero(pHash->apMap,pHash->nBuckSize * sizeof(lhash_bmap_rec *)); + /* Linear hashing components */ + pHash->split_bucket = 0; /* Logical not real bucket number */ + pHash->max_split_bucket = 1; + pHash->nmax_split_nucket = 2; + pHash->nMagic = L_HASH_MAGIC; + /* Install the cache unpin and reload callbacks */ + pHash->pIo->xSetUnpin(pHash->pIo->pHandle,lhash_page_release); + pHash->pIo->xSetReload(pHash->pIo->pHandle,lhash_page_release); + return UNQLITE_OK; +err: + SyMemBackendRelease(&pHash->sAllocator); + return rc; +} +/* + * Exported: xRelease() method. + * Release the Key value storage engine. + */ +static void lhash_kv_release(unqlite_kv_engine *pEngine) +{ + lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; + /* Release the private memory backend */ + SyMemBackendRelease(&pHash->sAllocator); +} +/* + * Exported: xConfig() method. + * Configure the linear hash KV store. + */ +static int lhash_kv_config(unqlite_kv_engine *pEngine,int op,va_list ap) +{ + lhash_kv_engine *pHash = (lhash_kv_engine *)pEngine; + int rc = UNQLITE_OK; + switch(op){ + case UNQLITE_KV_CONFIG_HASH_FUNC: { + /* Default hash function */ + if( pHash->nBuckRec > 0 ){ + /* Locked operation */ + rc = UNQLITE_LOCKED; + }else{ + ProcHash xHash = va_arg(ap,ProcHash); + if( xHash ){ + pHash->xHash = xHash; + } + } + break; + } + case UNQLITE_KV_CONFIG_CMP_FUNC: { + /* Default comparison function */ + ProcCmp xCmp = va_arg(ap,ProcCmp); + if( xCmp ){ + pHash->xCmp = xCmp; + } + break; + } + default: + /* Unknown OP */ + rc = UNQLITE_UNKNOWN; + break; + } + return rc; +} +/* + * Each public cursor is identified by an instance of this structure. + */ +typedef struct lhash_kv_cursor lhash_kv_cursor; +struct lhash_kv_cursor +{ + unqlite_kv_engine *pStore; /* Must be first */ + /* Private fields */ + int iState; /* Current state of the cursor */ + int is_first; /* True to read the database header */ + lhcell *pCell; /* Current cell we are processing */ + unqlite_page *pRaw; /* Raw disk page */ + lhash_bmap_rec *pRec; /* Logical to real bucket map */ +}; +/* + * Possible state of the cursor + */ +#define L_HASH_CURSOR_STATE_NEXT_PAGE 1 /* Next page in the list */ +#define L_HASH_CURSOR_STATE_CELL 2 /* Processing Cell */ +#define L_HASH_CURSOR_STATE_DONE 3 /* Cursor does not point to anything */ +/* + * Initialize the cursor. + */ +static void lhInitCursor(unqlite_kv_cursor *pPtr) +{ + lhash_kv_engine *pEngine = (lhash_kv_engine *)pPtr->pStore; + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; + /* Init */ + pCur->iState = L_HASH_CURSOR_STATE_NEXT_PAGE; + pCur->pCell = 0; + pCur->pRec = pEngine->pFirst; + pCur->pRaw = 0; + pCur->is_first = 1; +} +/* + * Point to the next page on the database. + */ +static int lhCursorNextPage(lhash_kv_cursor *pPtr) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; + lhash_bmap_rec *pRec; + lhpage *pPage; + int rc; + for(;;){ + pRec = pCur->pRec; + if( pRec == 0 ){ + pCur->iState = L_HASH_CURSOR_STATE_DONE; + return UNQLITE_DONE; + } + if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){ + /* Unref this page */ + pCur->pStore->pIo->xPageUnref(pPtr->pRaw); + pPtr->pRaw = 0; + } + /* Advance the map cursor */ + pCur->pRec = pRec->pPrev; /* Not a bug, reverse link */ + /* Load the next page on the list */ + rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pPage->pList ){ + /* Reflect the change */ + pCur->pCell = pPage->pList; + pCur->iState = L_HASH_CURSOR_STATE_CELL; + pCur->pRaw = pPage->pRaw; + break; + } + /* Empty page, discard this page and continue */ + pPage->pHash->pIo->xPageUnref(pPage->pRaw); + } + return UNQLITE_OK; +} +/* + * Point to the previous page on the database. + */ +static int lhCursorPrevPage(lhash_kv_cursor *pPtr) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; + lhash_bmap_rec *pRec; + lhpage *pPage; + int rc; + for(;;){ + pRec = pCur->pRec; + if( pRec == 0 ){ + pCur->iState = L_HASH_CURSOR_STATE_DONE; + return UNQLITE_DONE; + } + if( pPtr->iState == L_HASH_CURSOR_STATE_CELL && pPtr->pRaw ){ + /* Unref this page */ + pCur->pStore->pIo->xPageUnref(pPtr->pRaw); + pPtr->pRaw = 0; + } + /* Advance the map cursor */ + pCur->pRec = pRec->pNext; /* Not a bug, reverse link */ + /* Load the previous page on the list */ + rc = lhLoadPage((lhash_kv_engine *)pCur->pStore,pRec->iReal,0,&pPage,0); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pPage->pFirst ){ + /* Reflect the change */ + pCur->pCell = pPage->pFirst; + pCur->iState = L_HASH_CURSOR_STATE_CELL; + pCur->pRaw = pPage->pRaw; + break; + } + /* Discard this page and continue */ + pPage->pHash->pIo->xPageUnref(pPage->pRaw); + } + return UNQLITE_OK; +} +/* + * Is a valid cursor. + */ +static int lhCursorValid(unqlite_kv_cursor *pPtr) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pPtr; + return (pCur->iState == L_HASH_CURSOR_STATE_CELL) && pCur->pCell; +} +/* + * Point to the first record. + */ +static int lhCursorFirst(unqlite_kv_cursor *pCursor) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore; + int rc; + if( pCur->is_first ){ + /* Read the database header first */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); + if( rc != UNQLITE_OK ){ + return rc; + } + pCur->is_first = 0; + } + /* Point to the first map record */ + pCur->pRec = pEngine->pFirst; + /* Load the cells */ + rc = lhCursorNextPage(pCur); + return rc; +} +/* + * Point to the last record. + */ +static int lhCursorLast(unqlite_kv_cursor *pCursor) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhash_kv_engine *pEngine = (lhash_kv_engine *)pCursor->pStore; + int rc; + if( pCur->is_first ){ + /* Read the database header first */ + rc = pEngine->pIo->xGet(pEngine->pIo->pHandle,1,0); + if( rc != UNQLITE_OK ){ + return rc; + } + pCur->is_first = 0; + } + /* Point to the last map record */ + pCur->pRec = pEngine->pList; + /* Load the cells */ + rc = lhCursorPrevPage(pCur); + return rc; +} +/* + * Reset the cursor. + */ +static void lhCursorReset(unqlite_kv_cursor *pCursor) +{ + lhCursorFirst(pCursor); +} +/* + * Point to the next record. + */ +static int lhCursorNext(unqlite_kv_cursor *pCursor) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhcell *pCell; + int rc; + if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ + /* Load the cells of the next page */ + rc = lhCursorNextPage(pCur); + return rc; + } + pCell = pCur->pCell; + pCur->pCell = pCell->pNext; + if( pCur->pCell == 0 ){ + /* Load the cells of the next page */ + rc = lhCursorNextPage(pCur); + return rc; + } + return UNQLITE_OK; +} +/* + * Point to the previous record. + */ +static int lhCursorPrev(unqlite_kv_cursor *pCursor) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhcell *pCell; + int rc; + if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ + /* Load the cells of the previous page */ + rc = lhCursorPrevPage(pCur); + return rc; + } + pCell = pCur->pCell; + pCur->pCell = pCell->pPrev; + if( pCur->pCell == 0 ){ + /* Load the cells of the previous page */ + rc = lhCursorPrevPage(pCur); + return rc; + } + return UNQLITE_OK; +} +/* + * Return key length. + */ +static int lhCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhcell *pCell; + + if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ + /* Invalid state */ + return UNQLITE_INVALID; + } + /* Point to the target cell */ + pCell = pCur->pCell; + /* Return key length */ + *pLen = (int)pCell->nKey; + return UNQLITE_OK; +} +/* + * Return data length. + */ +static int lhCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhcell *pCell; + + if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ + /* Invalid state */ + return UNQLITE_INVALID; + } + /* Point to the target cell */ + pCell = pCur->pCell; + /* Return data length */ + *pLen = (unqlite_int64)pCell->nData; + return UNQLITE_OK; +} +/* + * Consume the key. + */ +static int lhCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhcell *pCell; + int rc; + if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ + /* Invalid state */ + return UNQLITE_INVALID; + } + /* Point to the target cell */ + pCell = pCur->pCell; + if( SyBlobLength(&pCell->sKey) > 0 ){ + /* Consume the key directly */ + rc = xConsumer(SyBlobData(&pCell->sKey),SyBlobLength(&pCell->sKey),pUserData); + }else{ + /* Very large key */ + rc = lhConsumeCellkey(pCell,xConsumer,pUserData,0); + } + return rc; +} +/* + * Consume the data. + */ +static int lhCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhcell *pCell; + int rc; + if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ + /* Invalid state */ + return UNQLITE_INVALID; + } + /* Point to the target cell */ + pCell = pCur->pCell; + /* Consume the data */ + rc = lhConsumeCellData(pCell,xConsumer,pUserData); + return rc; +} +/* + * Find a partiuclar record. + */ +static int lhCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + int rc; + /* Perform a lookup */ + rc = lhRecordLookup((lhash_kv_engine *)pCur->pStore,pKey,nByte,&pCur->pCell); + if( rc != UNQLITE_OK ){ + SXUNUSED(iPos); + pCur->pCell = 0; + pCur->iState = L_HASH_CURSOR_STATE_DONE; + return rc; + } + pCur->iState = L_HASH_CURSOR_STATE_CELL; + return UNQLITE_OK; +} +/* + * Remove a particular record. + */ +static int lhCursorDelete(unqlite_kv_cursor *pCursor) +{ + lhash_kv_cursor *pCur = (lhash_kv_cursor *)pCursor; + lhcell *pCell; + int rc; + if( pCur->iState != L_HASH_CURSOR_STATE_CELL || pCur->pCell == 0 ){ + /* Invalid state */ + return UNQLITE_INVALID; + } + /* Point to the target cell */ + pCell = pCur->pCell; + /* Point to the next entry */ + pCur->pCell = pCell->pNext; + /* Perform the deletion */ + rc = lhRecordRemove(pCell); + return rc; +} +/* + * Export the linear-hash storage engine. + */ +UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportDiskKvStorage(void) +{ + static const unqlite_kv_methods sDiskStore = { + "hash", /* zName */ + sizeof(lhash_kv_engine), /* szKv */ + sizeof(lhash_kv_cursor), /* szCursor */ + 1, /* iVersion */ + lhash_kv_init, /* xInit */ + lhash_kv_release, /* xRelease */ + lhash_kv_config, /* xConfig */ + lhash_kv_open, /* xOpen */ + lhash_kv_replace, /* xReplace */ + lhash_kv_append, /* xAppend */ + lhInitCursor, /* xCursorInit */ + lhCursorSeek, /* xSeek */ + lhCursorFirst, /* xFirst */ + lhCursorLast, /* xLast */ + lhCursorValid, /* xValid */ + lhCursorNext, /* xNext */ + lhCursorPrev, /* xPrev */ + lhCursorDelete, /* xDelete */ + lhCursorKeyLength, /* xKeyLength */ + lhCursorKey, /* xKey */ + lhCursorDataLength, /* xDataLength */ + lhCursorData, /* xData */ + lhCursorReset, /* xReset */ + 0 /* xRelease */ + }; + return &sDiskStore; +} +/* + * ---------------------------------------------------------- + * File: mem_kv.c + * MD5: 32e2610c95f53038114d9566f0d0489e + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: mem_kv.c v1.7 Win7 2012-11-28 01:41 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* + * This file implements an in-memory key value storage engine for unQLite. + * Note that this storage engine does not support transactions. + * + * Normaly, I (chm@symisc.net) planned to implement a red-black tree + * which is suitable for this kind of operation, but due to the lack + * of time, I decided to implement a tunned hashtable which everybody + * know works very well for this kind of operation. + * Again, I insist on a red-black tree implementation for future version + * of Unqlite. + */ +/* Forward declaration */ +typedef struct mem_hash_kv_engine mem_hash_kv_engine; +/* + * Each record is storead in an instance of the following structure. + */ +typedef struct mem_hash_record mem_hash_record; +struct mem_hash_record +{ + mem_hash_kv_engine *pEngine; /* Storage engine */ + sxu32 nHash; /* Hash of the key */ + const void *pKey; /* Key */ + sxu32 nKeyLen; /* Key size (Max 1GB) */ + const void *pData; /* Data */ + sxu32 nDataLen; /* Data length (Max 4GB) */ + mem_hash_record *pNext,*pPrev; /* Link to other records */ + mem_hash_record *pNextHash,*pPrevHash; /* Collision link */ +}; +/* + * Each in-memory KV engine is represented by an instance + * of the following structure. + */ +struct mem_hash_kv_engine +{ + const unqlite_kv_io *pIo; /* IO methods: MUST be first */ + /* Private data */ + SyMemBackend sAlloc; /* Private memory allocator */ + ProcHash xHash; /* Default hash function */ + ProcCmp xCmp; /* Default comparison function */ + sxu32 nRecord; /* Total number of records */ + sxu32 nBucket; /* Bucket size: Must be a power of two */ + mem_hash_record **apBucket; /* Hash bucket */ + mem_hash_record *pFirst; /* First inserted entry */ + mem_hash_record *pLast; /* Last inserted entry */ +}; +/* + * Allocate a new hash record. + */ +static mem_hash_record * MemHashNewRecord( + mem_hash_kv_engine *pEngine, + const void *pKey,int nKey, + const void *pData,unqlite_int64 nData, + sxu32 nHash + ) +{ + SyMemBackend *pAlloc = &pEngine->sAlloc; + mem_hash_record *pRecord; + void *pDupData; + sxu32 nByte; + char *zPtr; + + /* Total number of bytes to alloc */ + nByte = sizeof(mem_hash_record) + nKey; + /* Allocate a new instance */ + pRecord = (mem_hash_record *)SyMemBackendAlloc(pAlloc,nByte); + if( pRecord == 0 ){ + return 0; + } + pDupData = (void *)SyMemBackendAlloc(pAlloc,(sxu32)nData); + if( pDupData == 0 ){ + SyMemBackendFree(pAlloc,pRecord); + return 0; + } + zPtr = (char *)pRecord; + zPtr += sizeof(mem_hash_record); + /* Zero the structure */ + SyZero(pRecord,sizeof(mem_hash_record)); + /* Fill in the structure */ + pRecord->pEngine = pEngine; + pRecord->nDataLen = (sxu32)nData; + pRecord->nKeyLen = (sxu32)nKey; + pRecord->nHash = nHash; + SyMemcpy(pKey,zPtr,pRecord->nKeyLen); + pRecord->pKey = (const void *)zPtr; + SyMemcpy(pData,pDupData,pRecord->nDataLen); + pRecord->pData = pDupData; + /* All done */ + return pRecord; +} +/* + * Install a given record in the hashtable. + */ +static void MemHashLinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pRecord) +{ + sxu32 nBucket = pRecord->nHash & (pEngine->nBucket - 1); + pRecord->pNextHash = pEngine->apBucket[nBucket]; + if( pEngine->apBucket[nBucket] ){ + pEngine->apBucket[nBucket]->pPrevHash = pRecord; + } + pEngine->apBucket[nBucket] = pRecord; + if( pEngine->pFirst == 0 ){ + pEngine->pFirst = pEngine->pLast = pRecord; + }else{ + MACRO_LD_PUSH(pEngine->pLast,pRecord); + } + pEngine->nRecord++; +} +/* + * Unlink a given record from the hashtable. + */ +static void MemHashUnlinkRecord(mem_hash_kv_engine *pEngine,mem_hash_record *pEntry) +{ + sxu32 nBucket = pEntry->nHash & (pEngine->nBucket - 1); + SyMemBackend *pAlloc = &pEngine->sAlloc; + if( pEntry->pPrevHash == 0 ){ + pEngine->apBucket[nBucket] = pEntry->pNextHash; + }else{ + pEntry->pPrevHash->pNextHash = pEntry->pNextHash; + } + if( pEntry->pNextHash ){ + pEntry->pNextHash->pPrevHash = pEntry->pPrevHash; + } + MACRO_LD_REMOVE(pEngine->pLast,pEntry); + if( pEntry == pEngine->pFirst ){ + pEngine->pFirst = pEntry->pPrev; + } + pEngine->nRecord--; + /* Release the entry */ + SyMemBackendFree(pAlloc,(void *)pEntry->pData); + SyMemBackendFree(pAlloc,pEntry); /* Key is also stored here */ +} +/* + * Perform a lookup for a given entry. + */ +static mem_hash_record * MemHashGetEntry( + mem_hash_kv_engine *pEngine, + const void *pKey,int nKeyLen + ) +{ + mem_hash_record *pEntry; + sxu32 nHash,nBucket; + /* Hash the entry */ + nHash = pEngine->xHash(pKey,(sxu32)nKeyLen); + nBucket = nHash & (pEngine->nBucket - 1); + pEntry = pEngine->apBucket[nBucket]; + for(;;){ + if( pEntry == 0 ){ + break; + } + if( pEntry->nHash == nHash && pEntry->nKeyLen == (sxu32)nKeyLen && + pEngine->xCmp(pEntry->pKey,pKey,pEntry->nKeyLen) == 0 ){ + return pEntry; + } + pEntry = pEntry->pNextHash; + } + /* No such entry */ + return 0; +} +/* + * Rehash all the entries in the given table. + */ +static int MemHashGrowTable(mem_hash_kv_engine *pEngine) +{ + sxu32 nNewSize = pEngine->nBucket << 1; + mem_hash_record *pEntry; + mem_hash_record **apNew; + sxu32 n,iBucket; + /* Allocate a new larger table */ + apNew = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc, nNewSize * sizeof(mem_hash_record *)); + if( apNew == 0 ){ + /* Not so fatal, simply a performance hit */ + return UNQLITE_OK; + } + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(mem_hash_record *)); + /* Rehash all entries */ + n = 0; + pEntry = pEngine->pLast; + for(;;){ + + /* Loop one */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + + /* Loop two */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + + /* Loop three */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + + /* Loop four */ + if( n >= pEngine->nRecord ){ + break; + } + pEntry->pNextHash = pEntry->pPrevHash = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextHash = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevHash = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(&pEngine->sAlloc,(void *)pEngine->apBucket); + pEngine->apBucket = apNew; + pEngine->nBucket = nNewSize; + return UNQLITE_OK; +} +/* + * Exported Interfaces. + */ +/* + * Each public cursor is identified by an instance of this structure. + */ +typedef struct mem_hash_cursor mem_hash_cursor; +struct mem_hash_cursor +{ + unqlite_kv_engine *pStore; /* Must be first */ + /* Private fields */ + mem_hash_record *pCur; /* Current hash record */ +}; +/* + * Initialize the cursor. + */ +static void MemHashInitCursor(unqlite_kv_cursor *pCursor) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + /* Point to the first inserted entry */ + pMem->pCur = pEngine->pFirst; +} +/* + * Point to the first entry. + */ +static int MemHashCursorFirst(unqlite_kv_cursor *pCursor) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + pMem->pCur = pEngine->pFirst; + return UNQLITE_OK; +} +/* + * Point to the last entry. + */ +static int MemHashCursorLast(unqlite_kv_cursor *pCursor) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + pMem->pCur = pEngine->pLast; + return UNQLITE_OK; +} +/* + * is a Valid Cursor. + */ +static int MemHashCursorValid(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + return pMem->pCur != 0 ? 1 : 0; +} +/* + * Point to the next entry. + */ +static int MemHashCursorNext(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + pMem->pCur = pMem->pCur->pPrev; /* Reverse link: Not a Bug */ + return UNQLITE_OK; +} +/* + * Point to the previous entry. + */ +static int MemHashCursorPrev(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + pMem->pCur = pMem->pCur->pNext; /* Reverse link: Not a Bug */ + return UNQLITE_OK; +} +/* + * Return key length. + */ +static int MemHashCursorKeyLength(unqlite_kv_cursor *pCursor,int *pLen) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + *pLen = (int)pMem->pCur->nKeyLen; + return UNQLITE_OK; +} +/* + * Return data length. + */ +static int MemHashCursorDataLength(unqlite_kv_cursor *pCursor,unqlite_int64 *pLen) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + if( pMem->pCur == 0 ){ + return UNQLITE_EOF; + } + *pLen = pMem->pCur->nDataLen; + return UNQLITE_OK; +} +/* + * Consume the key. + */ +static int MemHashCursorKey(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + int rc; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + /* Invoke the callback */ + rc = xConsumer(pMem->pCur->pKey,pMem->pCur->nKeyLen,pUserData); + /* Callback result */ + return rc; +} +/* + * Consume the data. + */ +static int MemHashCursorData(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + int rc; + if( pMem->pCur == 0){ + return UNQLITE_EOF; + } + /* Invoke the callback */ + rc = xConsumer(pMem->pCur->pData,pMem->pCur->nDataLen,pUserData); + /* Callback result */ + return rc; +} +/* + * Reset the cursor. + */ +static void MemHashCursorReset(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + pMem->pCur = ((mem_hash_kv_engine *)pCursor->pStore)->pFirst; +} +/* + * Remove a particular record. + */ +static int MemHashCursorDelete(unqlite_kv_cursor *pCursor) +{ + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + mem_hash_record *pNext; + if( pMem->pCur == 0 ){ + /* Cursor does not point to anything */ + return UNQLITE_NOTFOUND; + } + pNext = pMem->pCur->pPrev; + /* Perform the deletion */ + MemHashUnlinkRecord(pMem->pCur->pEngine,pMem->pCur); + /* Point to the next entry */ + pMem->pCur = pNext; + return UNQLITE_OK; +} +/* + * Find a particular record. + */ +static int MemHashCursorSeek(unqlite_kv_cursor *pCursor,const void *pKey,int nByte,int iPos) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pCursor->pStore; + mem_hash_cursor *pMem = (mem_hash_cursor *)pCursor; + /* Perform the lookup */ + pMem->pCur = MemHashGetEntry(pEngine,pKey,nByte); + if( pMem->pCur == 0 ){ + if( iPos != UNQLITE_CURSOR_MATCH_EXACT ){ + /* noop; */ + } + /* No such record */ + return UNQLITE_NOTFOUND; + } + return UNQLITE_OK; +} +/* + * Builtin hash function. + */ +static sxu32 MemHashFunc(const void *pSrc,sxu32 nLen) +{ + register unsigned char *zIn = (unsigned char *)pSrc; + unsigned char *zEnd; + sxu32 nH = 5381; + zEnd = &zIn[nLen]; + for(;;){ + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + if( zIn >= zEnd ){ break; } nH = nH * 33 + zIn[0] ; zIn++; + } + return nH; +} +/* Default bucket size */ +#define MEM_HASH_BUCKET_SIZE 64 +/* Default fill factor */ +#define MEM_HASH_FILL_FACTOR 4 /* or 3 */ +/* + * Initialize the in-memory storage engine. + */ +static int MemHashInit(unqlite_kv_engine *pKvEngine,int iPageSize) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; + /* Note that this instance is already zeroed */ + /* Memory backend */ + SyMemBackendInitFromParent(&pEngine->sAlloc,unqliteExportMemBackend()); +//#if defined(UNQLITE_ENABLE_THREADS) +// /* Already protected by the upper layers */ +// SyMemBackendDisbaleMutexing(&pEngine->sAlloc); +//#endif + /* Default hash & comparison function */ + pEngine->xHash = MemHashFunc; + pEngine->xCmp = SyMemcmp; + /* Allocate a new bucket */ + pEngine->apBucket = (mem_hash_record **)SyMemBackendAlloc(&pEngine->sAlloc,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); + if( pEngine->apBucket == 0 ){ + SXUNUSED(iPageSize); /* cc warning */ + return UNQLITE_NOMEM; + } + /* Zero the bucket */ + SyZero(pEngine->apBucket,MEM_HASH_BUCKET_SIZE * sizeof(mem_hash_record *)); + pEngine->nRecord = 0; + pEngine->nBucket = MEM_HASH_BUCKET_SIZE; + return UNQLITE_OK; +} +/* + * Release the in-memory storage engine. + */ +static void MemHashRelease(unqlite_kv_engine *pKvEngine) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; + /* Release the private memory backend */ + SyMemBackendRelease(&pEngine->sAlloc); +} +/* + * Configure the in-memory storage engine. + */ +static int MemHashConfigure(unqlite_kv_engine *pKvEngine,int iOp,va_list ap) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKvEngine; + int rc = UNQLITE_OK; + switch(iOp){ + case UNQLITE_KV_CONFIG_HASH_FUNC:{ + /* Use a default hash function */ + if( pEngine->nRecord > 0 ){ + rc = UNQLITE_LOCKED; + }else{ + ProcHash xHash = va_arg(ap,ProcHash); + if( xHash ){ + pEngine->xHash = xHash; + } + } + break; + } + case UNQLITE_KV_CONFIG_CMP_FUNC: { + /* Default comparison function */ + ProcCmp xCmp = va_arg(ap,ProcCmp); + if( xCmp ){ + pEngine->xCmp = xCmp; + } + break; + } + default: + /* Unknown configuration option */ + rc = UNQLITE_UNKNOWN; + } + return rc; +} +/* + * Replace method. + */ +static int MemHashReplace( + unqlite_kv_engine *pKv, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; + mem_hash_record *pRecord; + if( nDataLen > SXU32_HIGH ){ + /* Database limit */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); + return UNQLITE_LIMIT; + } + /* Fetch the record first */ + pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); + if( pRecord == 0 ){ + /* Allocate a new record */ + pRecord = MemHashNewRecord(pEngine, + pKey,nKeyLen, + pData,nDataLen, + pEngine->xHash(pKey,nKeyLen) + ); + if( pRecord == 0 ){ + return UNQLITE_NOMEM; + } + /* Link the entry */ + MemHashLinkRecord(pEngine,pRecord); + if( (pEngine->nRecord >= pEngine->nBucket * MEM_HASH_FILL_FACTOR) && pEngine->nRecord < 100000 ){ + /* Rehash the table */ + MemHashGrowTable(pEngine); + } + }else{ + sxu32 nData = (sxu32)nDataLen; + void *pNew; + /* Replace an existing record */ + if( nData == pRecord->nDataLen ){ + /* No need to free the old chunk */ + pNew = (void *)pRecord->pData; + }else{ + pNew = SyMemBackendAlloc(&pEngine->sAlloc,nData); + if( pNew == 0 ){ + return UNQLITE_NOMEM; + } + /* Release the old data */ + SyMemBackendFree(&pEngine->sAlloc,(void *)pRecord->pData); + } + /* Reflect the change */ + pRecord->nDataLen = nData; + SyMemcpy(pData,pNew,nData); + pRecord->pData = pNew; + } + return UNQLITE_OK; +} +/* + * Append method. + */ +static int MemHashAppend( + unqlite_kv_engine *pKv, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ) +{ + mem_hash_kv_engine *pEngine = (mem_hash_kv_engine *)pKv; + mem_hash_record *pRecord; + if( nDataLen > SXU32_HIGH ){ + /* Database limit */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Record size limit reached"); + return UNQLITE_LIMIT; + } + /* Fetch the record first */ + pRecord = MemHashGetEntry(pEngine,pKey,nKeyLen); + if( pRecord == 0 ){ + /* Allocate a new record */ + pRecord = MemHashNewRecord(pEngine, + pKey,nKeyLen, + pData,nDataLen, + pEngine->xHash(pKey,nKeyLen) + ); + if( pRecord == 0 ){ + return UNQLITE_NOMEM; + } + /* Link the entry */ + MemHashLinkRecord(pEngine,pRecord); + if( pEngine->nRecord * MEM_HASH_FILL_FACTOR >= pEngine->nBucket && pEngine->nRecord < 100000 ){ + /* Rehash the table */ + MemHashGrowTable(pEngine); + } + }else{ + unqlite_int64 nNew = pRecord->nDataLen + nDataLen; + void *pOld = (void *)pRecord->pData; + sxu32 nData; + char *zNew; + /* Append data to the existing record */ + if( nNew > SXU32_HIGH ){ + /* Overflow */ + pEngine->pIo->xErr(pEngine->pIo->pHandle,"Append operation will cause data overflow"); + return UNQLITE_LIMIT; + } + nData = (sxu32)nNew; + /* Allocate bigger chunk */ + zNew = (char *)SyMemBackendRealloc(&pEngine->sAlloc,pOld,nData); + if( zNew == 0 ){ + return UNQLITE_NOMEM; + } + /* Reflect the change */ + SyMemcpy(pData,&zNew[pRecord->nDataLen],(sxu32)nDataLen); + pRecord->pData = (const void *)zNew; + pRecord->nDataLen = nData; + } + return UNQLITE_OK; +} +/* + * Export the in-memory storage engine. + */ +UNQLITE_PRIVATE const unqlite_kv_methods * unqliteExportMemKvStorage(void) +{ + static const unqlite_kv_methods sMemStore = { + "mem", /* zName */ + sizeof(mem_hash_kv_engine), /* szKv */ + sizeof(mem_hash_cursor), /* szCursor */ + 1, /* iVersion */ + MemHashInit, /* xInit */ + MemHashRelease, /* xRelease */ + MemHashConfigure, /* xConfig */ + 0, /* xOpen */ + MemHashReplace, /* xReplace */ + MemHashAppend, /* xAppend */ + MemHashInitCursor, /* xCursorInit */ + MemHashCursorSeek, /* xSeek */ + MemHashCursorFirst, /* xFirst */ + MemHashCursorLast, /* xLast */ + MemHashCursorValid, /* xValid */ + MemHashCursorNext, /* xNext */ + MemHashCursorPrev, /* xPrev */ + MemHashCursorDelete, /* xDelete */ + MemHashCursorKeyLength, /* xKeyLength */ + MemHashCursorKey, /* xKey */ + MemHashCursorDataLength, /* xDataLength */ + MemHashCursorData, /* xData */ + MemHashCursorReset, /* xReset */ + 0 /* xRelease */ + }; + return &sMemStore; +} +/* + * ---------------------------------------------------------- + * File: os.c + * MD5: e7ad243c3cd9e6aac5fba406eedb7766 + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: os.c v1.0 FreeBSD 2012-11-12 21:27 devel $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* OS interfaces abstraction layers: Mostly SQLite3 source tree */ +/* +** The following routines are convenience wrappers around methods +** of the unqlite_file object. This is mostly just syntactic sugar. All +** of this would be completely automatic if UnQLite were coded using +** C++ instead of plain old C. +*/ +UNQLITE_PRIVATE int unqliteOsRead(unqlite_file *id, void *pBuf, unqlite_int64 amt, unqlite_int64 offset) +{ + return id->pMethods->xRead(id, pBuf, amt, offset); +} +UNQLITE_PRIVATE int unqliteOsWrite(unqlite_file *id, const void *pBuf, unqlite_int64 amt, unqlite_int64 offset) +{ + return id->pMethods->xWrite(id, pBuf, amt, offset); +} +UNQLITE_PRIVATE int unqliteOsTruncate(unqlite_file *id, unqlite_int64 size) +{ + return id->pMethods->xTruncate(id, size); +} +UNQLITE_PRIVATE int unqliteOsSync(unqlite_file *id, int flags) +{ + return id->pMethods->xSync(id, flags); +} +UNQLITE_PRIVATE int unqliteOsFileSize(unqlite_file *id, unqlite_int64 *pSize) +{ + return id->pMethods->xFileSize(id, pSize); +} +UNQLITE_PRIVATE int unqliteOsLock(unqlite_file *id, int lockType) +{ + return id->pMethods->xLock(id, lockType); +} +UNQLITE_PRIVATE int unqliteOsUnlock(unqlite_file *id, int lockType) +{ + return id->pMethods->xUnlock(id, lockType); +} +UNQLITE_PRIVATE int unqliteOsCheckReservedLock(unqlite_file *id, int *pResOut) +{ + return id->pMethods->xCheckReservedLock(id, pResOut); +} +UNQLITE_PRIVATE int unqliteOsSectorSize(unqlite_file *id) +{ + if( id->pMethods->xSectorSize ){ + return id->pMethods->xSectorSize(id); + } + return UNQLITE_DEFAULT_SECTOR_SIZE; +} +/* +** The next group of routines are convenience wrappers around the +** VFS methods. +*/ +UNQLITE_PRIVATE int unqliteOsOpen( + unqlite_vfs *pVfs, + SyMemBackend *pAlloc, + const char *zPath, + unqlite_file **ppOut, + unsigned int flags +) +{ + unqlite_file *pFile; + int rc; + *ppOut = 0; + if( zPath == 0 ){ + /* May happen if dealing with an in-memory database */ + return SXERR_EMPTY; + } + /* Allocate a new instance */ + pFile = (unqlite_file *)SyMemBackendAlloc(pAlloc,sizeof(unqlite_file)+pVfs->szOsFile); + if( pFile == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pFile,sizeof(unqlite_file)+pVfs->szOsFile); + /* Invoke the xOpen method of the underlying VFS */ + rc = pVfs->xOpen(pVfs, zPath, pFile, flags); + if( rc != UNQLITE_OK ){ + SyMemBackendFree(pAlloc,pFile); + pFile = 0; + } + *ppOut = pFile; + return rc; +} +UNQLITE_PRIVATE int unqliteOsCloseFree(SyMemBackend *pAlloc,unqlite_file *pId) +{ + int rc = UNQLITE_OK; + if( pId ){ + rc = pId->pMethods->xClose(pId); + SyMemBackendFree(pAlloc,pId); + } + return rc; +} +UNQLITE_PRIVATE int unqliteOsDelete(unqlite_vfs *pVfs, const char *zPath, int dirSync){ + return pVfs->xDelete(pVfs, zPath, dirSync); +} +UNQLITE_PRIVATE int unqliteOsAccess( + unqlite_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + return pVfs->xAccess(pVfs, zPath, flags, pResOut); +} +/* + * ---------------------------------------------------------- + * File: os_unix.c + * MD5: 5efd57d03f8fb988d081c5bcf5cc2998 + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: os_unix.c v1.3 FreeBSD 2013-04-05 01:10 devel $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* + * Omit the whole layer from the build if compiling for platforms other than Unix (Linux, BSD, Solaris, OS X, etc.). + * Note: Mostly SQLite3 source tree. + */ +#if defined(__UNIXES__) +/** This file contains the VFS implementation for unix-like operating systems +** include Linux, MacOSX, *BSD, QNX, VxWorks, AIX, HPUX, and others. +** +** There are actually several different VFS implementations in this file. +** The differences are in the way that file locking is done. The default +** implementation uses Posix Advisory Locks. Alternative implementations +** use flock(), dot-files, various proprietary locking schemas, or simply +** skip locking all together. +** +** This source file is organized into divisions where the logic for various +** subfunctions is contained within the appropriate division. PLEASE +** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed +** in the correct division and should be clearly labeled. +** +*/ +/* +** standard include files. +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) +# include +#endif +/* +** Allowed values of unixFile.fsFlags +*/ +#define UNQLITE_FSFLAGS_IS_MSDOS 0x1 + +/* +** Default permissions when creating a new file +*/ +#ifndef UNQLITE_DEFAULT_FILE_PERMISSIONS +# define UNQLITE_DEFAULT_FILE_PERMISSIONS 0644 +#endif +/* + ** Default permissions when creating auto proxy dir + */ +#ifndef UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS +# define UNQLITE_DEFAULT_PROXYDIR_PERMISSIONS 0755 +#endif +/* +** Maximum supported path-length. +*/ +#define MAX_PATHNAME 512 +/* +** Only set the lastErrno if the error code is a real error and not +** a normal expected return code of UNQLITE_BUSY or UNQLITE_OK +*/ +#define IS_LOCK_ERROR(x) ((x != UNQLITE_OK) && (x != UNQLITE_BUSY)) +/* Forward references */ +typedef struct unixInodeInfo unixInodeInfo; /* An i-node */ +typedef struct UnixUnusedFd UnixUnusedFd; /* An unused file descriptor */ +/* +** Sometimes, after a file handle is closed by SQLite, the file descriptor +** cannot be closed immediately. In these cases, instances of the following +** structure are used to store the file descriptor while waiting for an +** opportunity to either close or reuse it. +*/ +struct UnixUnusedFd { + int fd; /* File descriptor to close */ + int flags; /* Flags this file descriptor was opened with */ + UnixUnusedFd *pNext; /* Next unused file descriptor on same file */ +}; +/* +** The unixFile structure is subclass of unqlite3_file specific to the unix +** VFS implementations. +*/ +typedef struct unixFile unixFile; +struct unixFile { + const unqlite_io_methods *pMethod; /* Always the first entry */ + unixInodeInfo *pInode; /* Info about locks on this inode */ + int h; /* The file descriptor */ + int dirfd; /* File descriptor for the directory */ + unsigned char eFileLock; /* The type of lock held on this fd */ + int lastErrno; /* The unix errno from last I/O error */ + void *lockingContext; /* Locking style specific state */ + UnixUnusedFd *pUnused; /* Pre-allocated UnixUnusedFd */ + int fileFlags; /* Miscellanous flags */ + const char *zPath; /* Name of the file */ + unsigned fsFlags; /* cached details from statfs() */ +}; +/* +** The following macros define bits in unixFile.fileFlags +*/ +#define UNQLITE_WHOLE_FILE_LOCKING 0x0001 /* Use whole-file locking */ +/* +** Define various macros that are missing from some systems. +*/ +#ifndef O_LARGEFILE +# define O_LARGEFILE 0 +#endif +#ifndef O_NOFOLLOW +# define O_NOFOLLOW 0 +#endif +#ifndef O_BINARY +# define O_BINARY 0 +#endif +/* +** Helper functions to obtain and relinquish the global mutex. The +** global mutex is used to protect the unixInodeInfo and +** vxworksFileId objects used by this file, all of which may be +** shared by multiple threads. +** +** Function unixMutexHeld() is used to assert() that the global mutex +** is held when required. This function is only used as part of assert() +** statements. e.g. +** +** unixEnterMutex() +** assert( unixMutexHeld() ); +** unixEnterLeave() +*/ +static void unixEnterMutex(void){ +#ifdef UNQLITE_ENABLE_THREADS + const SyMutexMethods *pMutexMethods = SyMutexExportMethods(); + if( pMutexMethods ){ + SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */ + SyMutexEnter(pMutexMethods,pMutex); + } +#endif /* UNQLITE_ENABLE_THREADS */ +} +static void unixLeaveMutex(void){ +#ifdef UNQLITE_ENABLE_THREADS + const SyMutexMethods *pMutexMethods = SyMutexExportMethods(); + if( pMutexMethods ){ + SyMutex *pMutex = pMutexMethods->xNew(SXMUTEX_TYPE_STATIC_2); /* pre-allocated, never fail */ + SyMutexLeave(pMutexMethods,pMutex); + } +#endif /* UNQLITE_ENABLE_THREADS */ +} +/* +** This routine translates a standard POSIX errno code into something +** useful to the clients of the unqlite3 functions. Specifically, it is +** intended to translate a variety of "try again" errors into UNQLITE_BUSY +** and a variety of "please close the file descriptor NOW" errors into +** UNQLITE_IOERR +** +** Errors during initialization of locks, or file system support for locks, +** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately. +*/ +static int unqliteErrorFromPosixError(int posixError, int unqliteIOErr) { + switch (posixError) { + case 0: + return UNQLITE_OK; + + case EAGAIN: + case ETIMEDOUT: + case EBUSY: + case EINTR: + case ENOLCK: + /* random NFS retry error, unless during file system support + * introspection, in which it actually means what it says */ + return UNQLITE_BUSY; + + case EACCES: + /* EACCES is like EAGAIN during locking operations, but not any other time*/ + return UNQLITE_BUSY; + + case EPERM: + return UNQLITE_PERM; + + case EDEADLK: + return UNQLITE_IOERR; + +#if EOPNOTSUPP!=ENOTSUP + case EOPNOTSUPP: + /* something went terribly awry, unless during file system support + * introspection, in which it actually means what it says */ +#endif +#ifdef ENOTSUP + case ENOTSUP: + /* invalid fd, unless during file system support introspection, in which + * it actually means what it says */ +#endif + case EIO: + case EBADF: + case EINVAL: + case ENOTCONN: + case ENODEV: + case ENXIO: + case ENOENT: + case ESTALE: + case ENOSYS: + /* these should force the client to close the file and reconnect */ + + default: + return unqliteIOErr; + } +} +/****************************************************************************** +*************************** Posix Advisory Locking **************************** +** +** POSIX advisory locks are broken by design. ANSI STD 1003.1 (1996) +** section 6.5.2.2 lines 483 through 490 specify that when a process +** sets or clears a lock, that operation overrides any prior locks set +** by the same process. It does not explicitly say so, but this implies +** that it overrides locks set by the same process using a different +** file descriptor. Consider this test case: +** +** int fd1 = open("./file1", O_RDWR|O_CREAT, 0644); +** int fd2 = open("./file2", O_RDWR|O_CREAT, 0644); +** +** Suppose ./file1 and ./file2 are really the same file (because +** one is a hard or symbolic link to the other) then if you set +** an exclusive lock on fd1, then try to get an exclusive lock +** on fd2, it works. I would have expected the second lock to +** fail since there was already a lock on the file due to fd1. +** But not so. Since both locks came from the same process, the +** second overrides the first, even though they were on different +** file descriptors opened on different file names. +** +** This means that we cannot use POSIX locks to synchronize file access +** among competing threads of the same process. POSIX locks will work fine +** to synchronize access for threads in separate processes, but not +** threads within the same process. +** +** To work around the problem, SQLite has to manage file locks internally +** on its own. Whenever a new database is opened, we have to find the +** specific inode of the database file (the inode is determined by the +** st_dev and st_ino fields of the stat structure that fstat() fills in) +** and check for locks already existing on that inode. When locks are +** created or removed, we have to look at our own internal record of the +** locks to see if another thread has previously set a lock on that same +** inode. +** +** (Aside: The use of inode numbers as unique IDs does not work on VxWorks. +** For VxWorks, we have to use the alternative unique ID system based on +** canonical filename and implemented in the previous division.) +** +** There is one locking structure +** per inode, so if the same inode is opened twice, both unixFile structures +** point to the same locking structure. The locking structure keeps +** a reference count (so we will know when to delete it) and a "cnt" +** field that tells us its internal lock status. cnt==0 means the +** file is unlocked. cnt==-1 means the file has an exclusive lock. +** cnt>0 means there are cnt shared locks on the file. +** +** Any attempt to lock or unlock a file first checks the locking +** structure. The fcntl() system call is only invoked to set a +** POSIX lock if the internal lock structure transitions between +** a locked and an unlocked state. +** +** But wait: there are yet more problems with POSIX advisory locks. +** +** If you close a file descriptor that points to a file that has locks, +** all locks on that file that are owned by the current process are +** released. To work around this problem, each unixInodeInfo object +** maintains a count of the number of pending locks on that inode. +** When an attempt is made to close an unixFile, if there are +** other unixFile open on the same inode that are holding locks, the call +** to close() the file descriptor is deferred until all of the locks clear. +** The unixInodeInfo structure keeps a list of file descriptors that need to +** be closed and that list is walked (and cleared) when the last lock +** clears. +** +** Yet another problem: LinuxThreads do not play well with posix locks. +** +** Many older versions of linux use the LinuxThreads library which is +** not posix compliant. Under LinuxThreads, a lock created by thread +** A cannot be modified or overridden by a different thread B. +** Only thread A can modify the lock. Locking behavior is correct +** if the appliation uses the newer Native Posix Thread Library (NPTL) +** on linux - with NPTL a lock created by thread A can override locks +** in thread B. But there is no way to know at compile-time which +** threading library is being used. So there is no way to know at +** compile-time whether or not thread A can override locks on thread B. +** One has to do a run-time check to discover the behavior of the +** current process. +** +*/ + +/* +** An instance of the following structure serves as the key used +** to locate a particular unixInodeInfo object. +*/ +struct unixFileId { + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ +}; +/* +** An instance of the following structure is allocated for each open +** inode. Or, on LinuxThreads, there is one of these structures for +** each inode opened by each thread. +** +** A single inode can have multiple file descriptors, so each unixFile +** structure contains a pointer to an instance of this object and this +** object keeps a count of the number of unixFile pointing to it. +*/ +struct unixInodeInfo { + struct unixFileId fileId; /* The lookup key */ + int nShared; /* Number of SHARED locks held */ + int eFileLock; /* One of SHARED_LOCK, RESERVED_LOCK etc. */ + int nRef; /* Number of pointers to this structure */ + int nLock; /* Number of outstanding file locks */ + UnixUnusedFd *pUnused; /* Unused file descriptors to close */ + unixInodeInfo *pNext; /* List of all unixInodeInfo objects */ + unixInodeInfo *pPrev; /* .... doubly linked */ +}; + +static unixInodeInfo *inodeList = 0; +/* + * Local memory allocation stuff. + */ +void * unqlite_malloc(unsigned int nByte) +{ + SyMemBackend *pAlloc; + void *p; + pAlloc = (SyMemBackend *)unqliteExportMemBackend(); + p = SyMemBackendAlloc(pAlloc,nByte); + return p; +} +void unqlite_free(void *p) +{ + SyMemBackend *pAlloc; + pAlloc = (SyMemBackend *)unqliteExportMemBackend(); + SyMemBackendFree(pAlloc,p); +} +/* +** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. +** If all such file descriptors are closed without error, the list is +** cleared and UNQLITE_OK returned. +** +** Otherwise, if an error occurs, then successfully closed file descriptor +** entries are removed from the list, and UNQLITE_IOERR_CLOSE returned. +** not deleted and UNQLITE_IOERR_CLOSE returned. +*/ +static int closePendingFds(unixFile *pFile){ + int rc = UNQLITE_OK; + unixInodeInfo *pInode = pFile->pInode; + UnixUnusedFd *pError = 0; + UnixUnusedFd *p; + UnixUnusedFd *pNext; + for(p=pInode->pUnused; p; p=pNext){ + pNext = p->pNext; + if( close(p->fd) ){ + pFile->lastErrno = errno; + rc = UNQLITE_IOERR; + p->pNext = pError; + pError = p; + }else{ + unqlite_free(p); + } + } + pInode->pUnused = pError; + return rc; +} +/* +** Release a unixInodeInfo structure previously allocated by findInodeInfo(). +** +** The mutex entered using the unixEnterMutex() function must be held +** when this function is called. +*/ +static void releaseInodeInfo(unixFile *pFile){ + unixInodeInfo *pInode = pFile->pInode; + if( pInode ){ + pInode->nRef--; + if( pInode->nRef==0 ){ + closePendingFds(pFile); + if( pInode->pPrev ){ + pInode->pPrev->pNext = pInode->pNext; + }else{ + inodeList = pInode->pNext; + } + if( pInode->pNext ){ + pInode->pNext->pPrev = pInode->pPrev; + } + unqlite_free(pInode); + } + } +} +/* +** Given a file descriptor, locate the unixInodeInfo object that +** describes that file descriptor. Create a new one if necessary. The +** return value might be uninitialized if an error occurs. +** +** The mutex entered using the unixEnterMutex() function must be held +** when this function is called. +** +** Return an appropriate error code. +*/ +static int findInodeInfo( + unixFile *pFile, /* Unix file with file desc used in the key */ + unixInodeInfo **ppInode /* Return the unixInodeInfo object here */ +){ + int rc; /* System call return code */ + int fd; /* The file descriptor for pFile */ + struct unixFileId fileId; /* Lookup key for the unixInodeInfo */ + struct stat statbuf; /* Low-level file information */ + unixInodeInfo *pInode = 0; /* Candidate unixInodeInfo object */ + + /* Get low-level information about the file that we can used to + ** create a unique name for the file. + */ + fd = pFile->h; + rc = fstat(fd, &statbuf); + if( rc!=0 ){ + pFile->lastErrno = errno; +#ifdef EOVERFLOW + if( pFile->lastErrno==EOVERFLOW ) return UNQLITE_NOTIMPLEMENTED; +#endif + return UNQLITE_IOERR; + } + +#ifdef __APPLE__ + /* On OS X on an msdos filesystem, the inode number is reported + ** incorrectly for zero-size files. See ticket #3260. To work + ** around this problem (we consider it a bug in OS X, not SQLite) + ** we always increase the file size to 1 by writing a single byte + ** prior to accessing the inode number. The one byte written is + ** an ASCII 'S' character which also happens to be the first byte + ** in the header of every SQLite database. In this way, if there + ** is a race condition such that another thread has already populated + ** the first page of the database, no damage is done. + */ + if( statbuf.st_size==0 && (pFile->fsFlags & UNQLITE_FSFLAGS_IS_MSDOS)!=0 ){ + rc = write(fd, "S", 1); + if( rc!=1 ){ + pFile->lastErrno = errno; + return UNQLITE_IOERR; + } + rc = fstat(fd, &statbuf); + if( rc!=0 ){ + pFile->lastErrno = errno; + return UNQLITE_IOERR; + } + } +#endif + SyZero(&fileId,sizeof(fileId)); + fileId.dev = statbuf.st_dev; + fileId.ino = statbuf.st_ino; + pInode = inodeList; + while( pInode && SyMemcmp((const void *)&fileId,(const void *)&pInode->fileId, sizeof(fileId)) ){ + pInode = pInode->pNext; + } + if( pInode==0 ){ + pInode = (unixInodeInfo *)unqlite_malloc( sizeof(*pInode) ); + if( pInode==0 ){ + return UNQLITE_NOMEM; + } + SyZero(pInode,sizeof(*pInode)); + SyMemcpy((const void *)&fileId,(void *)&pInode->fileId,sizeof(fileId)); + pInode->nRef = 1; + pInode->pNext = inodeList; + pInode->pPrev = 0; + if( inodeList ) inodeList->pPrev = pInode; + inodeList = pInode; + }else{ + pInode->nRef++; + } + *ppInode = pInode; + return UNQLITE_OK; +} +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, set *pResOut +** to a non-zero value otherwise *pResOut is set to zero. The return value +** is set to UNQLITE_OK unless an I/O error occurs during lock checking. +*/ +static int unixCheckReservedLock(unqlite_file *id, int *pResOut){ + int rc = UNQLITE_OK; + int reserved = 0; + unixFile *pFile = (unixFile*)id; + + + unixEnterMutex(); /* Because pFile->pInode is shared across threads */ + + /* Check if a thread in this process holds such a lock */ + if( pFile->pInode->eFileLock>SHARED_LOCK ){ + reserved = 1; + } + + /* Otherwise see if some other process holds it. + */ + if( !reserved ){ + struct flock lock; + lock.l_whence = SEEK_SET; + lock.l_start = RESERVED_BYTE; + lock.l_len = 1; + lock.l_type = F_WRLCK; + if (-1 == fcntl(pFile->h, F_GETLK, &lock)) { + int tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + pFile->lastErrno = tErrno; + } else if( lock.l_type!=F_UNLCK ){ + reserved = 1; + } + } + + unixLeaveMutex(); + + *pResOut = reserved; + return rc; +} +/* +** Lock the file with the lock specified by parameter eFileLock - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. Use the unqliteOsUnlock() +** routine to lower a locking level. +*/ +static int unixLock(unqlite_file *id, int eFileLock){ + /* The following describes the implementation of the various locks and + ** lock transitions in terms of the POSIX advisory shared and exclusive + ** lock primitives (called read-locks and write-locks below, to avoid + ** confusion with SQLite lock names). The algorithms are complicated + ** slightly in order to be compatible with unixdows systems simultaneously + ** accessing the same database file, in case that is ever required. + ** + ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved + ** byte', each single bytes at well known offsets, and the 'shared byte + ** range', a range of 510 bytes at a well known offset. + ** + ** To obtain a SHARED lock, a read-lock is obtained on the 'pending + ** byte'. If this is successful, a random byte from the 'shared byte + ** range' is read-locked and the lock on the 'pending byte' released. + ** + ** A process may only obtain a RESERVED lock after it has a SHARED lock. + ** A RESERVED lock is implemented by grabbing a write-lock on the + ** 'reserved byte'. + ** + ** A process may only obtain a PENDING lock after it has obtained a + ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock + ** on the 'pending byte'. This ensures that no new SHARED locks can be + ** obtained, but existing SHARED locks are allowed to persist. A process + ** does not have to obtain a RESERVED lock on the way to a PENDING lock. + ** This property is used by the algorithm for rolling back a journal file + ** after a crash. + ** + ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is + ** implemented by obtaining a write-lock on the entire 'shared byte + ** range'. Since all other locks require a read-lock on one of the bytes + ** within this range, this ensures that no other locks are held on the + ** database. + ** + ** The reason a single byte cannot be used instead of the 'shared byte + ** range' is that some versions of unixdows do not support read-locks. By + ** locking a random byte from a range, concurrent SHARED locks may exist + ** even if the locking primitive used is always a write-lock. + */ + int rc = UNQLITE_OK; + unixFile *pFile = (unixFile*)id; + unixInodeInfo *pInode = pFile->pInode; + struct flock lock; + int s = 0; + int tErrno = 0; + + /* If there is already a lock of this type or more restrictive on the + ** unixFile, do nothing. Don't use the end_lock: exit path, as + ** unixEnterMutex() hasn't been called yet. + */ + if( pFile->eFileLock>=eFileLock ){ + return UNQLITE_OK; + } + /* This mutex is needed because pFile->pInode is shared across threads + */ + unixEnterMutex(); + pInode = pFile->pInode; + + /* If some thread using this PID has a lock via a different unixFile* + ** handle that precludes the requested lock, return BUSY. + */ + if( (pFile->eFileLock!=pInode->eFileLock && + (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) + ){ + rc = UNQLITE_BUSY; + goto end_lock; + } + + /* If a SHARED lock is requested, and some thread using this PID already + ** has a SHARED or RESERVED lock, then increment reference counts and + ** return UNQLITE_OK. + */ + if( eFileLock==SHARED_LOCK && + (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ + pFile->eFileLock = SHARED_LOCK; + pInode->nShared++; + pInode->nLock++; + goto end_lock; + } + /* A PENDING lock is needed before acquiring a SHARED lock and before + ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will + ** be released. + */ + lock.l_len = 1L; + lock.l_whence = SEEK_SET; + if( eFileLock==SHARED_LOCK + || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLockh, F_SETLK, &lock); + if( s==(-1) ){ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_lock; + } + } + /* If control gets to this point, then actually go ahead and make + ** operating system calls for the specified lock. + */ + if( eFileLock==SHARED_LOCK ){ + /* Now get the read-lock */ + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + if( (s = fcntl(pFile->h, F_SETLK, &lock))==(-1) ){ + tErrno = errno; + } + /* Drop the temporary PENDING lock */ + lock.l_start = PENDING_BYTE; + lock.l_len = 1L; + lock.l_type = F_UNLCK; + if( fcntl(pFile->h, F_SETLK, &lock)!=0 ){ + if( s != -1 ){ + /* This could happen with a network mount */ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_lock; + } + } + if( s==(-1) ){ + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + }else{ + pFile->eFileLock = SHARED_LOCK; + pInode->nLock++; + pInode->nShared = 1; + } + }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){ + /* We are trying for an exclusive lock but another thread in this + ** same process is still holding a shared lock. */ + rc = UNQLITE_BUSY; + }else{ + /* The request was for a RESERVED or EXCLUSIVE lock. It is + ** assumed that there is a SHARED or greater lock on the file + ** already. + */ + lock.l_type = F_WRLCK; + switch( eFileLock ){ + case RESERVED_LOCK: + lock.l_start = RESERVED_BYTE; + break; + case EXCLUSIVE_LOCK: + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + break; + default: + /* Can't happen */ + break; + } + s = fcntl(pFile->h, F_SETLK, &lock); + if( s==(-1) ){ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + } + } + if( rc==UNQLITE_OK ){ + pFile->eFileLock = eFileLock; + pInode->eFileLock = eFileLock; + }else if( eFileLock==EXCLUSIVE_LOCK ){ + pFile->eFileLock = PENDING_LOCK; + pInode->eFileLock = PENDING_LOCK; + } +end_lock: + unixLeaveMutex(); + return rc; +} +/* +** Add the file descriptor used by file handle pFile to the corresponding +** pUnused list. +*/ +static void setPendingFd(unixFile *pFile){ + unixInodeInfo *pInode = pFile->pInode; + UnixUnusedFd *p = pFile->pUnused; + p->pNext = pInode->pUnused; + pInode->pUnused = p; + pFile->h = -1; + pFile->pUnused = 0; +} +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED +** the byte range is divided into 2 parts and the first part is unlocked then +** set to a read lock, then the other part is simply unlocked. This works +** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to +** remove the write lock on a region when a read lock is set. +*/ +static int _posixUnlock(unqlite_file *id, int eFileLock, int handleNFSUnlock){ + unixFile *pFile = (unixFile*)id; + unixInodeInfo *pInode; + struct flock lock; + int rc = UNQLITE_OK; + int h; + int tErrno; /* Error code from system call errors */ + + if( pFile->eFileLock<=eFileLock ){ + return UNQLITE_OK; + } + unixEnterMutex(); + + h = pFile->h; + pInode = pFile->pInode; + + if( pFile->eFileLock>SHARED_LOCK ){ + /* downgrading to a shared lock on NFS involves clearing the write lock + ** before establishing the readlock - to avoid a race condition we downgrade + ** the lock in 2 blocks, so that part of the range will be covered by a + ** write lock until the rest is covered by a read lock: + ** 1: [WWWWW] + ** 2: [....W] + ** 3: [RRRRW] + ** 4: [RRRR.] + */ + if( eFileLock==SHARED_LOCK ){ + if( handleNFSUnlock ){ + off_t divSize = SHARED_SIZE - 1; + + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = divSize; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = divSize; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST+divSize; + lock.l_len = SHARED_SIZE-divSize; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + }else{ + lock.l_type = F_RDLCK; + lock.l_whence = SEEK_SET; + lock.l_start = SHARED_FIRST; + lock.l_len = SHARED_SIZE; + if( fcntl(h, F_SETLK, &lock)==(-1) ){ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + } + } + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = PENDING_BYTE; + lock.l_len = 2L; + if( fcntl(h, F_SETLK, &lock)!=(-1) ){ + pInode->eFileLock = SHARED_LOCK; + }else{ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + goto end_unlock; + } + } + if( eFileLock==NO_LOCK ){ + /* Decrement the shared lock counter. Release the lock using an + ** OS call only when all threads in this same process have released + ** the lock. + */ + pInode->nShared--; + if( pInode->nShared==0 ){ + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = lock.l_len = 0L; + + if( fcntl(h, F_SETLK, &lock)!=(-1) ){ + pInode->eFileLock = NO_LOCK; + }else{ + tErrno = errno; + rc = unqliteErrorFromPosixError(tErrno, UNQLITE_LOCKERR); + if( IS_LOCK_ERROR(rc) ){ + pFile->lastErrno = tErrno; + } + pInode->eFileLock = NO_LOCK; + pFile->eFileLock = NO_LOCK; + } + } + + /* Decrement the count of locks against this same file. When the + ** count reaches zero, close any other file descriptors whose close + ** was deferred because of outstanding locks. + */ + pInode->nLock--; + + if( pInode->nLock==0 ){ + int rc2 = closePendingFds(pFile); + if( rc==UNQLITE_OK ){ + rc = rc2; + } + } + } + +end_unlock: + + unixLeaveMutex(); + + if( rc==UNQLITE_OK ) pFile->eFileLock = eFileLock; + return rc; +} +/* +** Lower the locking level on file descriptor pFile to eFileLock. eFileLock +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +*/ +static int unixUnlock(unqlite_file *id, int eFileLock){ + return _posixUnlock(id, eFileLock, 0); +} +/* +** This function performs the parts of the "close file" operation +** common to all locking schemes. It closes the directory and file +** handles, if they are valid, and sets all fields of the unixFile +** structure to 0. +** +*/ +static int closeUnixFile(unqlite_file *id){ + unixFile *pFile = (unixFile*)id; + if( pFile ){ + if( pFile->dirfd>=0 ){ + int err = close(pFile->dirfd); + if( err ){ + pFile->lastErrno = errno; + return UNQLITE_IOERR; + }else{ + pFile->dirfd=-1; + } + } + if( pFile->h>=0 ){ + int err = close(pFile->h); + if( err ){ + pFile->lastErrno = errno; + return UNQLITE_IOERR; + } + } + unqlite_free(pFile->pUnused); + SyZero(pFile,sizeof(unixFile)); + } + return UNQLITE_OK; +} +/* +** Close a file. +*/ +static int unixClose(unqlite_file *id){ + int rc = UNQLITE_OK; + if( id ){ + unixFile *pFile = (unixFile *)id; + unixUnlock(id, NO_LOCK); + unixEnterMutex(); + if( pFile->pInode && pFile->pInode->nLock ){ + /* If there are outstanding locks, do not actually close the file just + ** yet because that would clear those locks. Instead, add the file + ** descriptor to pInode->pUnused list. It will be automatically closed + ** when the last lock is cleared. + */ + setPendingFd(pFile); + } + releaseInodeInfo(pFile); + rc = closeUnixFile(id); + unixLeaveMutex(); + } + return rc; +} +/************** End of the posix advisory lock implementation ***************** +******************************************************************************/ +/* +** +** The next division contains implementations for all methods of the +** unqlite_file object other than the locking methods. The locking +** methods were defined in divisions above (one locking method per +** division). Those methods that are common to all locking modes +** are gather together into this division. +*/ +/* +** Seek to the offset passed as the second argument, then read cnt +** bytes into pBuf. Return the number of bytes actually read. +** +** NB: If you define USE_PREAD or USE_PREAD64, then it might also +** be necessary to define _XOPEN_SOURCE to be 500. This varies from +** one system to another. Since SQLite does not define USE_PREAD +** any form by default, we will not attempt to define _XOPEN_SOURCE. +** See tickets #2741 and #2681. +** +** To avoid stomping the errno value on a failed read the lastErrno value +** is set before returning. +*/ +static int seekAndRead(unixFile *id, unqlite_int64 offset, void *pBuf, int cnt){ + int got; +#if (!defined(USE_PREAD) && !defined(USE_PREAD64)) + unqlite_int64 newOffset; +#endif + +#if defined(USE_PREAD) + got = pread(id->h, pBuf, cnt, offset); +#elif defined(USE_PREAD64) + got = pread64(id->h, pBuf, cnt, offset); +#else + newOffset = lseek(id->h, offset, SEEK_SET); + + if( newOffset!=offset ){ + if( newOffset == -1 ){ + ((unixFile*)id)->lastErrno = errno; + }else{ + ((unixFile*)id)->lastErrno = 0; + } + return -1; + } + got = read(id->h, pBuf, cnt); +#endif + if( got<0 ){ + ((unixFile*)id)->lastErrno = errno; + } + return got; +} +/* +** Read data from a file into a buffer. Return UNQLITE_OK if all +** bytes were read successfully and UNQLITE_IOERR if anything goes +** wrong. +*/ +static int unixRead( + unqlite_file *id, + void *pBuf, + unqlite_int64 amt, + unqlite_int64 offset +){ + unixFile *pFile = (unixFile *)id; + int got; + + got = seekAndRead(pFile, offset, pBuf, (int)amt); + if( got==(int)amt ){ + return UNQLITE_OK; + }else if( got<0 ){ + /* lastErrno set by seekAndRead */ + return UNQLITE_IOERR; + }else{ + pFile->lastErrno = 0; /* not a system error */ + /* Unread parts of the buffer must be zero-filled */ + SyZero(&((char*)pBuf)[got],(sxu32)amt-got); + return UNQLITE_IOERR; + } +} +/* +** Seek to the offset in id->offset then read cnt bytes into pBuf. +** Return the number of bytes actually read. Update the offset. +** +** To avoid stomping the errno value on a failed write the lastErrno value +** is set before returning. +*/ +static int seekAndWrite(unixFile *id, unqlite_int64 offset, const void *pBuf, unqlite_int64 cnt){ + int got; +#if (!defined(USE_PREAD) && !defined(USE_PREAD64)) + unqlite_int64 newOffset; +#endif + +#if defined(USE_PREAD) + got = pwrite(id->h, pBuf, cnt, offset); +#elif defined(USE_PREAD64) + got = pwrite64(id->h, pBuf, cnt, offset); +#else + newOffset = lseek(id->h, offset, SEEK_SET); + if( newOffset!=offset ){ + if( newOffset == -1 ){ + ((unixFile*)id)->lastErrno = errno; + }else{ + ((unixFile*)id)->lastErrno = 0; + } + return -1; + } + got = write(id->h, pBuf, cnt); +#endif + if( got<0 ){ + ((unixFile*)id)->lastErrno = errno; + } + return got; +} +/* +** Write data from a buffer into a file. Return UNQLITE_OK on success +** or some other error code on failure. +*/ +static int unixWrite( + unqlite_file *id, + const void *pBuf, + unqlite_int64 amt, + unqlite_int64 offset +){ + unixFile *pFile = (unixFile*)id; + int wrote = 0; + + while( amt>0 && (wrote = seekAndWrite(pFile, offset, pBuf, amt))>0 ){ + amt -= wrote; + offset += wrote; + pBuf = &((char*)pBuf)[wrote]; + } + + if( amt>0 ){ + if( wrote<0 ){ + /* lastErrno set by seekAndWrite */ + return UNQLITE_IOERR; + }else{ + pFile->lastErrno = 0; /* not a system error */ + return UNQLITE_FULL; + } + } + return UNQLITE_OK; +} +/* +** We do not trust systems to provide a working fdatasync(). Some do. +** Others do no. To be safe, we will stick with the (slower) fsync(). +** If you know that your system does support fdatasync() correctly, +** then simply compile with -Dfdatasync=fdatasync +*/ +#if !defined(fdatasync) && !defined(__linux__) +# define fdatasync fsync +#endif + +/* +** Define HAVE_FULLFSYNC to 0 or 1 depending on whether or not +** the F_FULLFSYNC macro is defined. F_FULLFSYNC is currently +** only available on Mac OS X. But that could change. +*/ +#ifdef F_FULLFSYNC +# define HAVE_FULLFSYNC 1 +#else +# define HAVE_FULLFSYNC 0 +#endif +/* +** The fsync() system call does not work as advertised on many +** unix systems. The following procedure is an attempt to make +** it work better. +** +** +** SQLite sets the dataOnly flag if the size of the file is unchanged. +** The idea behind dataOnly is that it should only write the file content +** to disk, not the inode. We only set dataOnly if the file size is +** unchanged since the file size is part of the inode. However, +** Ted Ts'o tells us that fdatasync() will also write the inode if the +** file size has changed. The only real difference between fdatasync() +** and fsync(), Ted tells us, is that fdatasync() will not flush the +** inode if the mtime or owner or other inode attributes have changed. +** We only care about the file size, not the other file attributes, so +** as far as SQLite is concerned, an fdatasync() is always adequate. +** So, we always use fdatasync() if it is available, regardless of +** the value of the dataOnly flag. +*/ +static int full_fsync(int fd, int fullSync, int dataOnly){ + int rc; +#if HAVE_FULLFSYNC + SXUNUSED(dataOnly); +#else + SXUNUSED(fullSync); + SXUNUSED(dataOnly); +#endif + + /* If we compiled with the UNQLITE_NO_SYNC flag, then syncing is a + ** no-op + */ +#if HAVE_FULLFSYNC + if( fullSync ){ + rc = fcntl(fd, F_FULLFSYNC, 0); + }else{ + rc = 1; + } + /* If the FULLFSYNC failed, fall back to attempting an fsync(). + ** It shouldn't be possible for fullfsync to fail on the local + ** file system (on OSX), so failure indicates that FULLFSYNC + ** isn't supported for this file system. So, attempt an fsync + ** and (for now) ignore the overhead of a superfluous fcntl call. + ** It'd be better to detect fullfsync support once and avoid + ** the fcntl call every time sync is called. + */ + if( rc ) rc = fsync(fd); + +#elif defined(__APPLE__) + /* fdatasync() on HFS+ doesn't yet flush the file size if it changed correctly + ** so currently we default to the macro that redefines fdatasync to fsync + */ + rc = fsync(fd); +#else + rc = fdatasync(fd); +#endif /* ifdef UNQLITE_NO_SYNC elif HAVE_FULLFSYNC */ + if( rc!= -1 ){ + rc = 0; + } + return rc; +} +/* +** Make sure all writes to a particular file are committed to disk. +** +** If dataOnly==0 then both the file itself and its metadata (file +** size, access time, etc) are synced. If dataOnly!=0 then only the +** file data is synced. +** +** Under Unix, also make sure that the directory entry for the file +** has been created by fsync-ing the directory that contains the file. +** If we do not do this and we encounter a power failure, the directory +** entry for the journal might not exist after we reboot. The next +** SQLite to access the file will not know that the journal exists (because +** the directory entry for the journal was never created) and the transaction +** will not roll back - possibly leading to database corruption. +*/ +static int unixSync(unqlite_file *id, int flags){ + int rc; + unixFile *pFile = (unixFile*)id; + + int isDataOnly = (flags&UNQLITE_SYNC_DATAONLY); + int isFullsync = (flags&0x0F)==UNQLITE_SYNC_FULL; + + rc = full_fsync(pFile->h, isFullsync, isDataOnly); + + if( rc ){ + pFile->lastErrno = errno; + return UNQLITE_IOERR; + } + if( pFile->dirfd>=0 ){ + int err; +#ifndef UNQLITE_DISABLE_DIRSYNC + /* The directory sync is only attempted if full_fsync is + ** turned off or unavailable. If a full_fsync occurred above, + ** then the directory sync is superfluous. + */ + if( (!HAVE_FULLFSYNC || !isFullsync) && full_fsync(pFile->dirfd,0,0) ){ + /* + ** We have received multiple reports of fsync() returning + ** errors when applied to directories on certain file systems. + ** A failed directory sync is not a big deal. So it seems + ** better to ignore the error. Ticket #1657 + */ + /* pFile->lastErrno = errno; */ + /* return UNQLITE_IOERR; */ + } +#endif + err = close(pFile->dirfd); /* Only need to sync once, so close the */ + if( err==0 ){ /* directory when we are done */ + pFile->dirfd = -1; + }else{ + pFile->lastErrno = errno; + rc = UNQLITE_IOERR; + } + } + return rc; +} +/* +** Truncate an open file to a specified size +*/ +static int unixTruncate(unqlite_file *id, sxi64 nByte){ + unixFile *pFile = (unixFile *)id; + int rc; + + rc = ftruncate(pFile->h, (off_t)nByte); + if( rc ){ + pFile->lastErrno = errno; + return UNQLITE_IOERR; + }else{ + return UNQLITE_OK; + } +} +/* +** Determine the current size of a file in bytes +*/ +static int unixFileSize(unqlite_file *id,sxi64 *pSize){ + int rc; + struct stat buf; + + rc = fstat(((unixFile*)id)->h, &buf); + + if( rc!=0 ){ + ((unixFile*)id)->lastErrno = errno; + return UNQLITE_IOERR; + } + *pSize = buf.st_size; + + /* When opening a zero-size database, the findInodeInfo() procedure + ** writes a single byte into that file in order to work around a bug + ** in the OS-X msdos filesystem. In order to avoid problems with upper + ** layers, we need to report this file size as zero even though it is + ** really 1. Ticket #3260. + */ + if( *pSize==1 ) *pSize = 0; + + return UNQLITE_OK; +} +/* +** Return the sector size in bytes of the underlying block device for +** the specified file. This is almost always 512 bytes, but may be +** larger for some devices. +** +** SQLite code assumes this function cannot fail. It also assumes that +** if two files are created in the same file-system directory (i.e. +** a database and its journal file) that the sector size will be the +** same for both. +*/ +static int unixSectorSize(unqlite_file *NotUsed){ + SXUNUSED(NotUsed); + return UNQLITE_DEFAULT_SECTOR_SIZE; +} +/* +** This vector defines all the methods that can operate on an +** unqlite_file for Windows systems. +*/ +static const unqlite_io_methods unixIoMethod = { + 1, /* iVersion */ + unixClose, /* xClose */ + unixRead, /* xRead */ + unixWrite, /* xWrite */ + unixTruncate, /* xTruncate */ + unixSync, /* xSync */ + unixFileSize, /* xFileSize */ + unixLock, /* xLock */ + unixUnlock, /* xUnlock */ + unixCheckReservedLock, /* xCheckReservedLock */ + unixSectorSize, /* xSectorSize */ +}; +/**************************************************************************** +**************************** unqlite_vfs methods **************************** +** +** This division contains the implementation of methods on the +** unqlite_vfs object. +*/ +/* +** Initialize the contents of the unixFile structure pointed to by pId. +*/ +static int fillInUnixFile( + unqlite_vfs *pVfs, /* Pointer to vfs object */ + int h, /* Open file descriptor of file being opened */ + int dirfd, /* Directory file descriptor */ + unqlite_file *pId, /* Write to the unixFile structure here */ + const char *zFilename, /* Name of the file being opened */ + int noLock, /* Omit locking if true */ + int isDelete /* Delete on close if true */ +){ + const unqlite_io_methods *pLockingStyle = &unixIoMethod; + unixFile *pNew = (unixFile *)pId; + int rc = UNQLITE_OK; + + /* Parameter isDelete is only used on vxworks. Express this explicitly + ** here to prevent compiler warnings about unused parameters. + */ + SXUNUSED(isDelete); + SXUNUSED(noLock); + SXUNUSED(pVfs); + + pNew->h = h; + pNew->dirfd = dirfd; + pNew->fileFlags = 0; + pNew->zPath = zFilename; + + unixEnterMutex(); + rc = findInodeInfo(pNew, &pNew->pInode); + if( rc!=UNQLITE_OK ){ + /* If an error occured in findInodeInfo(), close the file descriptor + ** immediately, before releasing the mutex. findInodeInfo() may fail + ** in two scenarios: + ** + ** (a) A call to fstat() failed. + ** (b) A malloc failed. + ** + ** Scenario (b) may only occur if the process is holding no other + ** file descriptors open on the same file. If there were other file + ** descriptors on this file, then no malloc would be required by + ** findInodeInfo(). If this is the case, it is quite safe to close + ** handle h - as it is guaranteed that no posix locks will be released + ** by doing so. + ** + ** If scenario (a) caused the error then things are not so safe. The + ** implicit assumption here is that if fstat() fails, things are in + ** such bad shape that dropping a lock or two doesn't matter much. + */ + close(h); + h = -1; + } + unixLeaveMutex(); + + pNew->lastErrno = 0; + if( rc!=UNQLITE_OK ){ + if( dirfd>=0 ) close(dirfd); /* silent leak if fail, already in error */ + if( h>=0 ) close(h); + }else{ + pNew->pMethod = pLockingStyle; + } + return rc; +} +/* +** Open a file descriptor to the directory containing file zFilename. +** If successful, *pFd is set to the opened file descriptor and +** UNQLITE_OK is returned. If an error occurs, either UNQLITE_NOMEM +** or UNQLITE_CANTOPEN is returned and *pFd is set to an undefined +** value. +** +** If UNQLITE_OK is returned, the caller is responsible for closing +** the file descriptor *pFd using close(). +*/ +static int openDirectory(const char *zFilename, int *pFd){ + sxu32 ii; + int fd = -1; + char zDirname[MAX_PATHNAME+1]; + sxu32 n; + n = Systrcpy(zDirname,sizeof(zDirname),zFilename,0); + for(ii=n; ii>1 && zDirname[ii]!='/'; ii--); + if( ii>0 ){ + zDirname[ii] = '\0'; + fd = open(zDirname, O_RDONLY|O_BINARY, 0); + if( fd>=0 ){ +#ifdef FD_CLOEXEC + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); +#endif + } + } + *pFd = fd; + return (fd>=0?UNQLITE_OK: UNQLITE_IOERR ); +} +/* +** Search for an unused file descriptor that was opened on the database +** file (not a journal or master-journal file) identified by pathname +** zPath with UNQLITE_OPEN_XXX flags matching those passed as the second +** argument to this function. +** +** Such a file descriptor may exist if a database connection was closed +** but the associated file descriptor could not be closed because some +** other file descriptor open on the same file is holding a file-lock. +** Refer to comments in the unixClose() function and the lengthy comment +** describing "Posix Advisory Locking" at the start of this file for +** further details. Also, ticket #4018. +** +** If a suitable file descriptor is found, then it is returned. If no +** such file descriptor is located, -1 is returned. +*/ +static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ + UnixUnusedFd *pUnused = 0; + struct stat sStat; /* Results of stat() call */ + /* A stat() call may fail for various reasons. If this happens, it is + ** almost certain that an open() call on the same path will also fail. + ** For this reason, if an error occurs in the stat() call here, it is + ** ignored and -1 is returned. The caller will try to open a new file + ** descriptor on the same path, fail, and return an error to SQLite. + ** + ** Even if a subsequent open() call does succeed, the consequences of + ** not searching for a resusable file descriptor are not dire. */ + if( 0==stat(zPath, &sStat) ){ + unixInodeInfo *pInode; + + unixEnterMutex(); + pInode = inodeList; + while( pInode && (pInode->fileId.dev!=sStat.st_dev + || pInode->fileId.ino!=sStat.st_ino) ){ + pInode = pInode->pNext; + } + if( pInode ){ + UnixUnusedFd **pp; + for(pp=&pInode->pUnused; *pp && (*pp)->flags!=flags; pp=&((*pp)->pNext)); + pUnused = *pp; + if( pUnused ){ + *pp = pUnused->pNext; + } + } + unixLeaveMutex(); + } + return pUnused; +} +/* +** This function is called by unixOpen() to determine the unix permissions +** to create new files with. If no error occurs, then UNQLITE_OK is returned +** and a value suitable for passing as the third argument to open(2) is +** written to *pMode. If an IO error occurs, an SQLite error code is +** returned and the value of *pMode is not modified. +** +** If the file being opened is a temporary file, it is always created with +** the octal permissions 0600 (read/writable by owner only). If the file +** is a database or master journal file, it is created with the permissions +** mask UNQLITE_DEFAULT_FILE_PERMISSIONS. +** +** Finally, if the file being opened is a WAL or regular journal file, then +** this function queries the file-system for the permissions on the +** corresponding database file and sets *pMode to this value. Whenever +** possible, WAL and journal files are created using the same permissions +** as the associated database file. +*/ +static int findCreateFileMode( + const char *zPath, /* Path of file (possibly) being created */ + int flags, /* Flags passed as 4th argument to xOpen() */ + mode_t *pMode /* OUT: Permissions to open file with */ +){ + int rc = UNQLITE_OK; /* Return Code */ + if( flags & UNQLITE_OPEN_TEMP_DB ){ + *pMode = 0600; + SXUNUSED(zPath); + }else{ + *pMode = UNQLITE_DEFAULT_FILE_PERMISSIONS; + } + return rc; +} +/* +** Open the file zPath. +** +** Previously, the SQLite OS layer used three functions in place of this +** one: +** +** unqliteOsOpenReadWrite(); +** unqliteOsOpenReadOnly(); +** unqliteOsOpenExclusive(); +** +** These calls correspond to the following combinations of flags: +** +** ReadWrite() -> (READWRITE | CREATE) +** ReadOnly() -> (READONLY) +** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE) +** +** The old OpenExclusive() accepted a boolean argument - "delFlag". If +** true, the file was configured to be automatically deleted when the +** file handle closed. To achieve the same effect using this new +** interface, add the DELETEONCLOSE flag to those specified above for +** OpenExclusive(). +*/ +static int unixOpen( + unqlite_vfs *pVfs, /* The VFS for which this is the xOpen method */ + const char *zPath, /* Pathname of file to be opened */ + unqlite_file *pFile, /* The file descriptor to be filled in */ + unsigned int flags /* Input flags to control the opening */ +){ + unixFile *p = (unixFile *)pFile; + int fd = -1; /* File descriptor returned by open() */ + int dirfd = -1; /* Directory file descriptor */ + int openFlags = 0; /* Flags to pass to open() */ + int noLock; /* True to omit locking primitives */ + int rc = UNQLITE_OK; /* Function Return Code */ + UnixUnusedFd *pUnused; + int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE); + int isDelete = (flags & UNQLITE_OPEN_TEMP_DB); + int isCreate = (flags & UNQLITE_OPEN_CREATE); + int isReadonly = (flags & UNQLITE_OPEN_READONLY); + int isReadWrite = (flags & UNQLITE_OPEN_READWRITE); + /* If creating a master or main-file journal, this function will open + ** a file-descriptor on the directory too. The first time unixSync() + ** is called the directory file descriptor will be fsync()ed and close()d. + */ + int isOpenDirectory = isCreate; + const char *zName = zPath; + + SyZero(p,sizeof(unixFile)); + + pUnused = findReusableFd(zName, flags); + if( pUnused ){ + fd = pUnused->fd; + }else{ + pUnused = unqlite_malloc(sizeof(*pUnused)); + if( !pUnused ){ + return UNQLITE_NOMEM; + } + } + p->pUnused = pUnused; + + /* Determine the value of the flags parameter passed to POSIX function + ** open(). These must be calculated even if open() is not called, as + ** they may be stored as part of the file handle and used by the + ** 'conch file' locking functions later on. */ + if( isReadonly ) openFlags |= O_RDONLY; + if( isReadWrite ) openFlags |= O_RDWR; + if( isCreate ) openFlags |= O_CREAT; + if( isExclusive ) openFlags |= (O_EXCL|O_NOFOLLOW); + openFlags |= (O_LARGEFILE|O_BINARY); + + if( fd<0 ){ + mode_t openMode; /* Permissions to create file with */ + rc = findCreateFileMode(zName, flags, &openMode); + if( rc!=UNQLITE_OK ){ + return rc; + } + fd = open(zName, openFlags, openMode); + if( fd<0 ){ + rc = UNQLITE_IOERR; + goto open_finished; + } + } + + if( p->pUnused ){ + p->pUnused->fd = fd; + p->pUnused->flags = flags; + } + + if( isDelete ){ + unlink(zName); + } + + if( isOpenDirectory ){ + rc = openDirectory(zPath, &dirfd); + if( rc!=UNQLITE_OK ){ + /* It is safe to close fd at this point, because it is guaranteed not + ** to be open on a database file. If it were open on a database file, + ** it would not be safe to close as this would release any locks held + ** on the file by this process. */ + close(fd); /* silently leak if fail, already in error */ + goto open_finished; + } + } + +#ifdef FD_CLOEXEC + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); +#endif + + noLock = 0; + +#if defined(__APPLE__) + struct statfs fsInfo; + if( fstatfs(fd, &fsInfo) == -1 ){ + ((unixFile*)pFile)->lastErrno = errno; + if( dirfd>=0 ) close(dirfd); /* silently leak if fail, in error */ + close(fd); /* silently leak if fail, in error */ + return UNQLITE_IOERR; + } + if (0 == SyStrncmp("msdos", fsInfo.f_fstypename, 5)) { + ((unixFile*)pFile)->fsFlags |= UNQLITE_FSFLAGS_IS_MSDOS; + } +#endif + + rc = fillInUnixFile(pVfs, fd, dirfd, pFile, zPath, noLock, isDelete); +open_finished: + if( rc!=UNQLITE_OK ){ + unqlite_free(p->pUnused); + } + return rc; +} +/* +** Delete the file at zPath. If the dirSync argument is true, fsync() +** the directory after deleting the file. +*/ +static int unixDelete( + unqlite_vfs *NotUsed, /* VFS containing this as the xDelete method */ + const char *zPath, /* Name of file to be deleted */ + int dirSync /* If true, fsync() directory after deleting file */ +){ + int rc = UNQLITE_OK; + SXUNUSED(NotUsed); + + if( unlink(zPath)==(-1) && errno!=ENOENT ){ + return UNQLITE_IOERR; + } +#ifndef UNQLITE_DISABLE_DIRSYNC + if( dirSync ){ + int fd; + rc = openDirectory(zPath, &fd); + if( rc==UNQLITE_OK ){ + if( fsync(fd) ) + { + rc = UNQLITE_IOERR; + } + if( close(fd) && !rc ){ + rc = UNQLITE_IOERR; + } + } + } +#endif + return rc; +} +/* +** Sleep for a little while. Return the amount of time slept. +** The argument is the number of microseconds we want to sleep. +** The return value is the number of microseconds of sleep actually +** requested from the underlying operating system, a number which +** might be greater than or equal to the argument, but not less +** than the argument. +*/ +static int unixSleep(unqlite_vfs *NotUsed, int microseconds) +{ +#if defined(HAVE_USLEEP) && HAVE_USLEEP + usleep(microseconds); + SXUNUSED(NotUsed); + return microseconds; +#else + int seconds = (microseconds+999999)/1000000; + SXUNUSED(NotUsed); + sleep(seconds); + return seconds*1000000; +#endif +} +/* + * Export the current system time. + */ +static int unixCurrentTime(unqlite_vfs *pVfs,Sytm *pOut) +{ + struct tm *pTm; + time_t tt; + SXUNUSED(pVfs); + time(&tt); + pTm = gmtime(&tt); + if( pTm ){ /* Yes, it can fail */ + STRUCT_TM_TO_SYTM(pTm,pOut); + } + return UNQLITE_OK; +} +/* +** Test the existance of or access permissions of file zPath. The +** test performed depends on the value of flags: +** +** UNQLITE_ACCESS_EXISTS: Return 1 if the file exists +** UNQLITE_ACCESS_READWRITE: Return 1 if the file is read and writable. +** UNQLITE_ACCESS_READONLY: Return 1 if the file is readable. +** +** Otherwise return 0. +*/ +static int unixAccess( + unqlite_vfs *NotUsed, /* The VFS containing this xAccess method */ + const char *zPath, /* Path of the file to examine */ + int flags, /* What do we want to learn about the zPath file? */ + int *pResOut /* Write result boolean here */ +){ + int amode = 0; + SXUNUSED(NotUsed); + switch( flags ){ + case UNQLITE_ACCESS_EXISTS: + amode = F_OK; + break; + case UNQLITE_ACCESS_READWRITE: + amode = W_OK|R_OK; + break; + case UNQLITE_ACCESS_READ: + amode = R_OK; + break; + default: + /* Can't happen */ + break; + } + *pResOut = (access(zPath, amode)==0); + if( flags==UNQLITE_ACCESS_EXISTS && *pResOut ){ + struct stat buf; + if( 0==stat(zPath, &buf) && buf.st_size==0 ){ + *pResOut = 0; + } + } + return UNQLITE_OK; +} +/* +** Turn a relative pathname into a full pathname. The relative path +** is stored as a nul-terminated string in the buffer pointed to by +** zPath. +** +** zOut points to a buffer of at least unqlite_vfs.mxPathname bytes +** (in this case, MAX_PATHNAME bytes). The full-path is written to +** this buffer before returning. +*/ +static int unixFullPathname( + unqlite_vfs *pVfs, /* Pointer to vfs object */ + const char *zPath, /* Possibly relative input path */ + int nOut, /* Size of output buffer in bytes */ + char *zOut /* Output buffer */ +){ + if( zPath[0]=='/' ){ + Systrcpy(zOut,(sxu32)nOut,zPath,0); + SXUNUSED(pVfs); + }else{ + sxu32 nCwd; + zOut[nOut-1] = '\0'; + if( getcwd(zOut, nOut-1)==0 ){ + return UNQLITE_IOERR; + } + nCwd = SyStrlen(zOut); + SyBufferFormat(&zOut[nCwd],(sxu32)nOut-nCwd,"/%s",zPath); + } + return UNQLITE_OK; +} +/* + * Export the Unix Vfs. + */ +UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void) +{ + static const unqlite_vfs sUnixvfs = { + "Unix", /* Vfs name */ + 1, /* Vfs structure version */ + sizeof(unixFile), /* szOsFile */ + MAX_PATHNAME, /* mxPathName */ + unixOpen, /* xOpen */ + unixDelete, /* xDelete */ + unixAccess, /* xAccess */ + unixFullPathname, /* xFullPathname */ + 0, /* xTmp */ + unixSleep, /* xSleep */ + unixCurrentTime, /* xCurrentTime */ + 0, /* xGetLastError */ + }; + return &sUnixvfs; +} + +#endif /* __UNIXES__ */ + +/* + * ---------------------------------------------------------- + * File: os_win.c + * MD5: ab70fb386c21b39a08b0eb776a8391ab + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: os_win.c v1.2 Win7 2012-11-10 12:10 devel $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* Omit the whole layer from the build if compiling for platforms other than Windows */ +#ifdef __WINNT__ +/* This file contains code that is specific to windows. (Mostly SQLite3 source tree) */ +#include +/* +** Some microsoft compilers lack this definition. +*/ +#ifndef INVALID_FILE_ATTRIBUTES +# define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif +/* +** WinCE lacks native support for file locking so we have to fake it +** with some code of our own. +*/ +#ifdef __WIN_CE__ +typedef struct winceLock { + int nReaders; /* Number of reader locks obtained */ + BOOL bPending; /* Indicates a pending lock has been obtained */ + BOOL bReserved; /* Indicates a reserved lock has been obtained */ + BOOL bExclusive; /* Indicates an exclusive lock has been obtained */ +} winceLock; +#define AreFileApisANSI() 1 +#define FormatMessageW(a,b,c,d,e,f,g) 0 +#endif + +/* +** The winFile structure is a subclass of unqlite_file* specific to the win32 +** portability layer. +*/ +typedef struct winFile winFile; +struct winFile { + const unqlite_io_methods *pMethod; /*** Must be first ***/ + unqlite_vfs *pVfs; /* The VFS used to open this file */ + HANDLE h; /* Handle for accessing the file */ + sxu8 locktype; /* Type of lock currently held on this file */ + short sharedLockByte; /* Randomly chosen byte used as a shared lock */ + DWORD lastErrno; /* The Windows errno from the last I/O error */ + DWORD sectorSize; /* Sector size of the device file is on */ + int szChunk; /* Chunk size */ +#ifdef __WIN_CE__ + WCHAR *zDeleteOnClose; /* Name of file to delete when closing */ + HANDLE hMutex; /* Mutex used to control access to shared lock */ + HANDLE hShared; /* Shared memory segment used for locking */ + winceLock local; /* Locks obtained by this instance of winFile */ + winceLock *shared; /* Global shared lock memory for the file */ +#endif +}; +/* +** Convert a UTF-8 string to microsoft unicode (UTF-16?). +** +** Space to hold the returned string is obtained from HeapAlloc(). +*/ +static WCHAR *utf8ToUnicode(const char *zFilename){ + int nChar; + WCHAR *zWideFilename; + + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, 0, 0); + zWideFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nChar*sizeof(zWideFilename[0]) ); + if( zWideFilename==0 ){ + return 0; + } + nChar = MultiByteToWideChar(CP_UTF8, 0, zFilename, -1, zWideFilename, nChar); + if( nChar==0 ){ + HeapFree(GetProcessHeap(),0,zWideFilename); + zWideFilename = 0; + } + return zWideFilename; +} + +/* +** Convert microsoft unicode to UTF-8. Space to hold the returned string is +** obtained from malloc(). +*/ +static char *unicodeToUtf8(const WCHAR *zWideFilename){ + int nByte; + char *zFilename; + + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, 0, 0, 0, 0); + zFilename = (char *)HeapAlloc(GetProcessHeap(),0,nByte ); + if( zFilename==0 ){ + return 0; + } + nByte = WideCharToMultiByte(CP_UTF8, 0, zWideFilename, -1, zFilename, nByte, + 0, 0); + if( nByte == 0 ){ + HeapFree(GetProcessHeap(),0,zFilename); + zFilename = 0; + } + return zFilename; +} + +/* +** Convert an ansi string to microsoft unicode, based on the +** current codepage settings for file apis. +** +** Space to hold the returned string is obtained +** from malloc. +*/ +static WCHAR *mbcsToUnicode(const char *zFilename){ + int nByte; + WCHAR *zMbcsFilename; + int codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; + + nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, 0,0)*sizeof(WCHAR); + zMbcsFilename = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zMbcsFilename[0]) ); + if( zMbcsFilename==0 ){ + return 0; + } + nByte = MultiByteToWideChar(codepage, 0, zFilename, -1, zMbcsFilename, nByte); + if( nByte==0 ){ + HeapFree(GetProcessHeap(),0,zMbcsFilename); + zMbcsFilename = 0; + } + return zMbcsFilename; +} +/* +** Convert multibyte character string to UTF-8. Space to hold the +** returned string is obtained from malloc(). +*/ +char *unqlite_win32_mbcs_to_utf8(const char *zFilename){ + char *zFilenameUtf8; + WCHAR *zTmpWide; + + zTmpWide = mbcsToUnicode(zFilename); + if( zTmpWide==0 ){ + return 0; + } + zFilenameUtf8 = unicodeToUtf8(zTmpWide); + HeapFree(GetProcessHeap(),0,zTmpWide); + return zFilenameUtf8; +} +/* +** Some microsoft compilers lack this definition. +*/ +#ifndef INVALID_SET_FILE_POINTER +# define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + +/* +** Move the current position of the file handle passed as the first +** argument to offset iOffset within the file. If successful, return 0. +** Otherwise, set pFile->lastErrno and return non-zero. +*/ +static int seekWinFile(winFile *pFile, unqlite_int64 iOffset){ + LONG upperBits; /* Most sig. 32 bits of new offset */ + LONG lowerBits; /* Least sig. 32 bits of new offset */ + DWORD dwRet; /* Value returned by SetFilePointer() */ + + upperBits = (LONG)((iOffset>>32) & 0x7fffffff); + lowerBits = (LONG)(iOffset & 0xffffffff); + + /* API oddity: If successful, SetFilePointer() returns a dword + ** containing the lower 32-bits of the new file-offset. Or, if it fails, + ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, + ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine + ** whether an error has actually occured, it is also necessary to call + ** GetLastError(). + */ + dwRet = SetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN); + if( (dwRet==INVALID_SET_FILE_POINTER && GetLastError()!=NO_ERROR) ){ + pFile->lastErrno = GetLastError(); + return 1; + } + return 0; +} +/* +** Close a file. +** +** It is reported that an attempt to close a handle might sometimes +** fail. This is a very unreasonable result, but windows is notorious +** for being unreasonable so I do not doubt that it might happen. If +** the close fails, we pause for 100 milliseconds and try again. As +** many as MX_CLOSE_ATTEMPT attempts to close the handle are made before +** giving up and returning an error. +*/ +#define MX_CLOSE_ATTEMPT 3 +static int winClose(unqlite_file *id) +{ + int rc, cnt = 0; + winFile *pFile = (winFile*)id; + do{ + rc = CloseHandle(pFile->h); + }while( rc==0 && ++cnt < MX_CLOSE_ATTEMPT && (Sleep(100), 1) ); + + return rc ? UNQLITE_OK : UNQLITE_IOERR; +} +/* +** Read data from a file into a buffer. Return UNQLITE_OK if all +** bytes were read successfully and UNQLITE_IOERR if anything goes +** wrong. +*/ +static int winRead( + unqlite_file *id, /* File to read from */ + void *pBuf, /* Write content into this buffer */ + unqlite_int64 amt, /* Number of bytes to read */ + unqlite_int64 offset /* Begin reading at this offset */ +){ + winFile *pFile = (winFile*)id; /* file handle */ + DWORD nRead; /* Number of bytes actually read from file */ + + if( seekWinFile(pFile, offset) ){ + return UNQLITE_FULL; + } + if( !ReadFile(pFile->h, pBuf, (DWORD)amt, &nRead, 0) ){ + pFile->lastErrno = GetLastError(); + return UNQLITE_IOERR; + } + if( nRead<(DWORD)amt ){ + /* Unread parts of the buffer must be zero-filled */ + SyZero(&((char*)pBuf)[nRead],(sxu32)(amt-nRead)); + return UNQLITE_IOERR; + } + + return UNQLITE_OK; +} + +/* +** Write data from a buffer into a file. Return UNQLITE_OK on success +** or some other error code on failure. +*/ +static int winWrite( + unqlite_file *id, /* File to write into */ + const void *pBuf, /* The bytes to be written */ + unqlite_int64 amt, /* Number of bytes to write */ + unqlite_int64 offset /* Offset into the file to begin writing at */ +){ + int rc; /* True if error has occured, else false */ + winFile *pFile = (winFile*)id; /* File handle */ + + rc = seekWinFile(pFile, offset); + if( rc==0 ){ + sxu8 *aRem = (sxu8 *)pBuf; /* Data yet to be written */ + unqlite_int64 nRem = amt; /* Number of bytes yet to be written */ + DWORD nWrite; /* Bytes written by each WriteFile() call */ + + while( nRem>0 && WriteFile(pFile->h, aRem, (DWORD)nRem, &nWrite, 0) && nWrite>0 ){ + aRem += nWrite; + nRem -= nWrite; + } + if( nRem>0 ){ + pFile->lastErrno = GetLastError(); + rc = 1; + } + } + if( rc ){ + if( pFile->lastErrno==ERROR_HANDLE_DISK_FULL ){ + return UNQLITE_FULL; + } + return UNQLITE_IOERR; + } + return UNQLITE_OK; +} + +/* +** Truncate an open file to a specified size +*/ +static int winTruncate(unqlite_file *id, unqlite_int64 nByte){ + winFile *pFile = (winFile*)id; /* File handle object */ + int rc = UNQLITE_OK; /* Return code for this function */ + + + /* If the user has configured a chunk-size for this file, truncate the + ** file so that it consists of an integer number of chunks (i.e. the + ** actual file size after the operation may be larger than the requested + ** size). + */ + if( pFile->szChunk ){ + nByte = ((nByte + pFile->szChunk - 1)/pFile->szChunk) * pFile->szChunk; + } + + /* SetEndOfFile() returns non-zero when successful, or zero when it fails. */ + if( seekWinFile(pFile, nByte) ){ + rc = UNQLITE_IOERR; + }else if( 0==SetEndOfFile(pFile->h) ){ + pFile->lastErrno = GetLastError(); + rc = UNQLITE_IOERR; + } + return rc; +} +/* +** Make sure all writes to a particular file are committed to disk. +*/ +static int winSync(unqlite_file *id, int flags){ + winFile *pFile = (winFile*)id; + SXUNUSED(flags); /* MSVC warning */ + if( FlushFileBuffers(pFile->h) ){ + return UNQLITE_OK; + }else{ + pFile->lastErrno = GetLastError(); + return UNQLITE_IOERR; + } +} +/* +** Determine the current size of a file in bytes +*/ +static int winFileSize(unqlite_file *id, unqlite_int64 *pSize){ + DWORD upperBits; + DWORD lowerBits; + winFile *pFile = (winFile*)id; + DWORD error; + lowerBits = GetFileSize(pFile->h, &upperBits); + if( (lowerBits == INVALID_FILE_SIZE) + && ((error = GetLastError()) != NO_ERROR) ) + { + pFile->lastErrno = error; + return UNQLITE_IOERR; + } + *pSize = (((unqlite_int64)upperBits)<<32) + lowerBits; + return UNQLITE_OK; +} +/* +** LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. +*/ +#ifndef LOCKFILE_FAIL_IMMEDIATELY +# define LOCKFILE_FAIL_IMMEDIATELY 1 +#endif + +/* +** Acquire a reader lock. +*/ +static int getReadLock(winFile *pFile){ + int res; + OVERLAPPED ovlp; + ovlp.Offset = SHARED_FIRST; + ovlp.OffsetHigh = 0; + ovlp.hEvent = 0; + res = LockFileEx(pFile->h, LOCKFILE_FAIL_IMMEDIATELY,0, SHARED_SIZE, 0, &ovlp); + if( res == 0 ){ + pFile->lastErrno = GetLastError(); + } + return res; +} +/* +** Undo a readlock +*/ +static int unlockReadLock(winFile *pFile){ + int res; + res = UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + if( res == 0 ){ + pFile->lastErrno = GetLastError(); + } + return res; +} +/* +** Lock the file with the lock specified by parameter locktype - one +** of the following: +** +** (1) SHARED_LOCK +** (2) RESERVED_LOCK +** (3) PENDING_LOCK +** (4) EXCLUSIVE_LOCK +** +** Sometimes when requesting one lock state, additional lock states +** are inserted in between. The locking might fail on one of the later +** transitions leaving the lock state different from what it started but +** still short of its goal. The following chart shows the allowed +** transitions and the inserted intermediate states: +** +** UNLOCKED -> SHARED +** SHARED -> RESERVED +** SHARED -> (PENDING) -> EXCLUSIVE +** RESERVED -> (PENDING) -> EXCLUSIVE +** PENDING -> EXCLUSIVE +** +** This routine will only increase a lock. The winUnlock() routine +** erases all locks at once and returns us immediately to locking level 0. +** It is not possible to lower the locking level one step at a time. You +** must go straight to locking level 0. +*/ +static int winLock(unqlite_file *id, int locktype){ + int rc = UNQLITE_OK; /* Return code from subroutines */ + int res = 1; /* Result of a windows lock call */ + int newLocktype; /* Set pFile->locktype to this value before exiting */ + int gotPendingLock = 0;/* True if we acquired a PENDING lock this time */ + winFile *pFile = (winFile*)id; + DWORD error = NO_ERROR; + + /* If there is already a lock of this type or more restrictive on the + ** OsFile, do nothing. + */ + if( pFile->locktype>=locktype ){ + return UNQLITE_OK; + } + + /* Make sure the locking sequence is correct + assert( pFile->locktype!=NO_LOCK || locktype==SHARED_LOCK ); + assert( locktype!=PENDING_LOCK ); + assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK ); + */ + /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or + ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of + ** the PENDING_LOCK byte is temporary. + */ + newLocktype = pFile->locktype; + if( (pFile->locktype==NO_LOCK) + || ( (locktype==EXCLUSIVE_LOCK) + && (pFile->locktype==RESERVED_LOCK)) + ){ + int cnt = 3; + while( cnt-->0 && (res = LockFile(pFile->h, PENDING_BYTE, 0, 1, 0))==0 ){ + /* Try 3 times to get the pending lock. The pending lock might be + ** held by another reader process who will release it momentarily. + */ + Sleep(1); + } + gotPendingLock = res; + if( !res ){ + error = GetLastError(); + } + } + + /* Acquire a shared lock + */ + if( locktype==SHARED_LOCK && res ){ + /* assert( pFile->locktype==NO_LOCK ); */ + res = getReadLock(pFile); + if( res ){ + newLocktype = SHARED_LOCK; + }else{ + error = GetLastError(); + } + } + + /* Acquire a RESERVED lock + */ + if( locktype==RESERVED_LOCK && res ){ + /* assert( pFile->locktype==SHARED_LOCK ); */ + res = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); + if( res ){ + newLocktype = RESERVED_LOCK; + }else{ + error = GetLastError(); + } + } + + /* Acquire a PENDING lock + */ + if( locktype==EXCLUSIVE_LOCK && res ){ + newLocktype = PENDING_LOCK; + gotPendingLock = 0; + } + + /* Acquire an EXCLUSIVE lock + */ + if( locktype==EXCLUSIVE_LOCK && res ){ + /* assert( pFile->locktype>=SHARED_LOCK ); */ + res = unlockReadLock(pFile); + res = LockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + if( res ){ + newLocktype = EXCLUSIVE_LOCK; + }else{ + error = GetLastError(); + getReadLock(pFile); + } + } + + /* If we are holding a PENDING lock that ought to be released, then + ** release it now. + */ + if( gotPendingLock && locktype==SHARED_LOCK ){ + UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0); + } + + /* Update the state of the lock has held in the file descriptor then + ** return the appropriate result code. + */ + if( res ){ + rc = UNQLITE_OK; + }else{ + pFile->lastErrno = error; + rc = UNQLITE_BUSY; + } + pFile->locktype = (sxu8)newLocktype; + return rc; +} +/* +** This routine checks if there is a RESERVED lock held on the specified +** file by this or any other process. If such a lock is held, return +** non-zero, otherwise zero. +*/ +static int winCheckReservedLock(unqlite_file *id, int *pResOut){ + int rc; + winFile *pFile = (winFile*)id; + if( pFile->locktype>=RESERVED_LOCK ){ + rc = 1; + }else{ + rc = LockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); + if( rc ){ + UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); + } + rc = !rc; + } + *pResOut = rc; + return UNQLITE_OK; +} +/* +** Lower the locking level on file descriptor id to locktype. locktype +** must be either NO_LOCK or SHARED_LOCK. +** +** If the locking level of the file descriptor is already at or below +** the requested locking level, this routine is a no-op. +** +** It is not possible for this routine to fail if the second argument +** is NO_LOCK. If the second argument is SHARED_LOCK then this routine +** might return UNQLITE_IOERR; +*/ +static int winUnlock(unqlite_file *id, int locktype){ + int type; + winFile *pFile = (winFile*)id; + int rc = UNQLITE_OK; + + type = pFile->locktype; + if( type>=EXCLUSIVE_LOCK ){ + UnlockFile(pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); + if( locktype==SHARED_LOCK && !getReadLock(pFile) ){ + /* This should never happen. We should always be able to + ** reacquire the read lock */ + rc = UNQLITE_IOERR; + } + } + if( type>=RESERVED_LOCK ){ + UnlockFile(pFile->h, RESERVED_BYTE, 0, 1, 0); + } + if( locktype==NO_LOCK && type>=SHARED_LOCK ){ + unlockReadLock(pFile); + } + if( type>=PENDING_LOCK ){ + UnlockFile(pFile->h, PENDING_BYTE, 0, 1, 0); + } + pFile->locktype = (sxu8)locktype; + return rc; +} +/* +** Return the sector size in bytes of the underlying block device for +** the specified file. This is almost always 512 bytes, but may be +** larger for some devices. +** +*/ +static int winSectorSize(unqlite_file *id){ + return (int)(((winFile*)id)->sectorSize); +} +/* +** This vector defines all the methods that can operate on an +** unqlite_file for Windows systems. +*/ +static const unqlite_io_methods winIoMethod = { + 1, /* iVersion */ + winClose, /* xClose */ + winRead, /* xRead */ + winWrite, /* xWrite */ + winTruncate, /* xTruncate */ + winSync, /* xSync */ + winFileSize, /* xFileSize */ + winLock, /* xLock */ + winUnlock, /* xUnlock */ + winCheckReservedLock, /* xCheckReservedLock */ + winSectorSize, /* xSectorSize */ +}; +/* + * Windows VFS Methods. + */ +/* +** Convert a UTF-8 filename into whatever form the underlying +** operating system wants filenames in. Space to hold the result +** is obtained from malloc and must be freed by the calling +** function. +*/ +static void *convertUtf8Filename(const char *zFilename) +{ + void *zConverted; + zConverted = utf8ToUnicode(zFilename); + /* caller will handle out of memory */ + return zConverted; +} +/* +** Delete the named file. +** +** Note that windows does not allow a file to be deleted if some other +** process has it open. Sometimes a virus scanner or indexing program +** will open a journal file shortly after it is created in order to do +** whatever it does. While this other process is holding the +** file open, we will be unable to delete it. To work around this +** problem, we delay 100 milliseconds and try to delete again. Up +** to MX_DELETION_ATTEMPTs deletion attempts are run before giving +** up and returning an error. +*/ +#define MX_DELETION_ATTEMPTS 5 +static int winDelete( + unqlite_vfs *pVfs, /* Not used on win32 */ + const char *zFilename, /* Name of file to delete */ + int syncDir /* Not used on win32 */ +){ + int cnt = 0; + DWORD rc; + DWORD error = 0; + void *zConverted; + zConverted = convertUtf8Filename(zFilename); + if( zConverted==0 ){ + SXUNUSED(pVfs); + SXUNUSED(syncDir); + return UNQLITE_NOMEM; + } + do{ + DeleteFileW((LPCWSTR)zConverted); + }while( ( ((rc = GetFileAttributesW((LPCWSTR)zConverted)) != INVALID_FILE_ATTRIBUTES) + || ((error = GetLastError()) == ERROR_ACCESS_DENIED)) + && (++cnt < MX_DELETION_ATTEMPTS) + && (Sleep(100), 1) + ); + HeapFree(GetProcessHeap(),0,zConverted); + + return ( (rc == INVALID_FILE_ATTRIBUTES) + && (error == ERROR_FILE_NOT_FOUND)) ? UNQLITE_OK : UNQLITE_IOERR; +} +/* +** Check the existance and status of a file. +*/ +static int winAccess( + unqlite_vfs *pVfs, /* Not used */ + const char *zFilename, /* Name of file to check */ + int flags, /* Type of test to make on this file */ + int *pResOut /* OUT: Result */ +){ + WIN32_FILE_ATTRIBUTE_DATA sAttrData; + DWORD attr; + int rc = 0; + void *zConverted; + SXUNUSED(pVfs); + + zConverted = convertUtf8Filename(zFilename); + if( zConverted==0 ){ + return UNQLITE_NOMEM; + } + SyZero(&sAttrData,sizeof(sAttrData)); + if( GetFileAttributesExW((WCHAR*)zConverted, + GetFileExInfoStandard, + &sAttrData) ){ + /* For an UNQLITE_ACCESS_EXISTS query, treat a zero-length file + ** as if it does not exist. + */ + if( flags==UNQLITE_ACCESS_EXISTS + && sAttrData.nFileSizeHigh==0 + && sAttrData.nFileSizeLow==0 ){ + attr = INVALID_FILE_ATTRIBUTES; + }else{ + attr = sAttrData.dwFileAttributes; + } + }else{ + if( GetLastError()!=ERROR_FILE_NOT_FOUND ){ + HeapFree(GetProcessHeap(),0,zConverted); + return UNQLITE_IOERR; + }else{ + attr = INVALID_FILE_ATTRIBUTES; + } + } + HeapFree(GetProcessHeap(),0,zConverted); + switch( flags ){ + case UNQLITE_ACCESS_READWRITE: + rc = (attr & FILE_ATTRIBUTE_READONLY)==0; + break; + case UNQLITE_ACCESS_READ: + case UNQLITE_ACCESS_EXISTS: + default: + rc = attr!=INVALID_FILE_ATTRIBUTES; + break; + } + *pResOut = rc; + return UNQLITE_OK; +} +/* +** Turn a relative pathname into a full pathname. Write the full +** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname +** bytes in size. +*/ +static int winFullPathname( + unqlite_vfs *pVfs, /* Pointer to vfs object */ + const char *zRelative, /* Possibly relative input path */ + int nFull, /* Size of output buffer in bytes */ + char *zFull /* Output buffer */ +){ + int nByte; + void *zConverted; + WCHAR *zTemp; + char *zOut; + SXUNUSED(nFull); + zConverted = convertUtf8Filename(zRelative); + if( zConverted == 0 ){ + return UNQLITE_NOMEM; + } + nByte = GetFullPathNameW((WCHAR*)zConverted, 0, 0, 0) + 3; + zTemp = (WCHAR *)HeapAlloc(GetProcessHeap(),0,nByte*sizeof(zTemp[0]) ); + if( zTemp==0 ){ + HeapFree(GetProcessHeap(),0,zConverted); + return UNQLITE_NOMEM; + } + GetFullPathNameW((WCHAR*)zConverted, nByte, zTemp, 0); + HeapFree(GetProcessHeap(),0,zConverted); + zOut = unicodeToUtf8(zTemp); + HeapFree(GetProcessHeap(),0,zTemp); + if( zOut == 0 ){ + return UNQLITE_NOMEM; + } + Systrcpy(zFull,(sxu32)pVfs->mxPathname,zOut,0); + HeapFree(GetProcessHeap(),0,zOut); + return UNQLITE_OK; +} +/* +** Get the sector size of the device used to store +** file. +*/ +static int getSectorSize( + unqlite_vfs *pVfs, + const char *zRelative /* UTF-8 file name */ +){ + DWORD bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE; + char zFullpath[MAX_PATH+1]; + int rc; + DWORD dwRet = 0; + DWORD dwDummy; + /* + ** We need to get the full path name of the file + ** to get the drive letter to look up the sector + ** size. + */ + rc = winFullPathname(pVfs, zRelative, MAX_PATH, zFullpath); + if( rc == UNQLITE_OK ) + { + void *zConverted = convertUtf8Filename(zFullpath); + if( zConverted ){ + /* trim path to just drive reference */ + WCHAR *p = (WCHAR *)zConverted; + for(;*p;p++){ + if( *p == '\\' ){ + *p = '\0'; + break; + } + } + dwRet = GetDiskFreeSpaceW((WCHAR*)zConverted, + &dwDummy, + &bytesPerSector, + &dwDummy, + &dwDummy); + HeapFree(GetProcessHeap(),0,zConverted); + } + if( !dwRet ){ + bytesPerSector = UNQLITE_DEFAULT_SECTOR_SIZE; + } + } + return (int) bytesPerSector; +} +/* +** Sleep for a little while. Return the amount of time slept. +*/ +static int winSleep(unqlite_vfs *pVfs, int microsec){ + Sleep((microsec+999)/1000); + SXUNUSED(pVfs); + return ((microsec+999)/1000)*1000; +} +/* + * Export the current system time. + */ +static int winCurrentTime(unqlite_vfs *pVfs,Sytm *pOut) +{ + SYSTEMTIME sSys; + SXUNUSED(pVfs); + GetSystemTime(&sSys); + SYSTEMTIME_TO_SYTM(&sSys,pOut); + return UNQLITE_OK; +} +/* +** The idea is that this function works like a combination of +** GetLastError() and FormatMessage() on windows (or errno and +** strerror_r() on unix). After an error is returned by an OS +** function, UnQLite calls this function with zBuf pointing to +** a buffer of nBuf bytes. The OS layer should populate the +** buffer with a nul-terminated UTF-8 encoded error message +** describing the last IO error to have occurred within the calling +** thread. +** +** If the error message is too large for the supplied buffer, +** it should be truncated. The return value of xGetLastError +** is zero if the error message fits in the buffer, or non-zero +** otherwise (if the message was truncated). If non-zero is returned, +** then it is not necessary to include the nul-terminator character +** in the output buffer. +*/ +static int winGetLastError(unqlite_vfs *pVfs, int nBuf, char *zBuf) +{ + /* FormatMessage returns 0 on failure. Otherwise it + ** returns the number of TCHARs written to the output + ** buffer, excluding the terminating null char. + */ + DWORD error = GetLastError(); + WCHAR *zTempWide = 0; + DWORD dwLen; + char *zOut = 0; + + SXUNUSED(pVfs); + dwLen = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + error, + 0, + (LPWSTR) &zTempWide, + 0, + 0 + ); + if( dwLen > 0 ){ + /* allocate a buffer and convert to UTF8 */ + zOut = unicodeToUtf8(zTempWide); + /* free the system buffer allocated by FormatMessage */ + LocalFree(zTempWide); + } + if( 0 == dwLen ){ + Systrcpy(zBuf,(sxu32)nBuf,"OS Error",sizeof("OS Error")-1); + }else{ + /* copy a maximum of nBuf chars to output buffer */ + Systrcpy(zBuf,(sxu32)nBuf,zOut,0 /* Compute input length automatically */); + /* free the UTF8 buffer */ + HeapFree(GetProcessHeap(),0,zOut); + } + return 0; +} +/* +** Open a file. +*/ +static int winOpen( + unqlite_vfs *pVfs, /* Not used */ + const char *zName, /* Name of the file (UTF-8) */ + unqlite_file *id, /* Write the UnQLite file handle here */ + unsigned int flags /* Open mode flags */ +){ + HANDLE h; + DWORD dwDesiredAccess; + DWORD dwShareMode; + DWORD dwCreationDisposition; + DWORD dwFlagsAndAttributes = 0; + winFile *pFile = (winFile*)id; + void *zConverted; /* Filename in OS encoding */ + const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ + int isExclusive = (flags & UNQLITE_OPEN_EXCLUSIVE); + int isDelete = (flags & UNQLITE_OPEN_TEMP_DB); + int isCreate = (flags & UNQLITE_OPEN_CREATE); + int isReadWrite = (flags & UNQLITE_OPEN_READWRITE); + + pFile->h = INVALID_HANDLE_VALUE; + /* Convert the filename to the system encoding. */ + zConverted = convertUtf8Filename(zUtf8Name); + if( zConverted==0 ){ + return UNQLITE_NOMEM; + } + if( isReadWrite ){ + dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; + }else{ + dwDesiredAccess = GENERIC_READ; + } + /* UNQLITE_OPEN_EXCLUSIVE is used to make sure that a new file is + ** created. + */ + if( isExclusive ){ + /* Creates a new file, only if it does not already exist. */ + /* If the file exists, it fails. */ + dwCreationDisposition = CREATE_NEW; + }else if( isCreate ){ + /* Open existing file, or create if it doesn't exist */ + dwCreationDisposition = OPEN_ALWAYS; + }else{ + /* Opens a file, only if it exists. */ + dwCreationDisposition = OPEN_EXISTING; + } + + dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + if( isDelete ){ + dwFlagsAndAttributes = FILE_ATTRIBUTE_TEMPORARY + | FILE_ATTRIBUTE_HIDDEN + | FILE_FLAG_DELETE_ON_CLOSE; + }else{ + dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; + } + h = CreateFileW((WCHAR*)zConverted, + dwDesiredAccess, + dwShareMode, + NULL, + dwCreationDisposition, + dwFlagsAndAttributes, + NULL + ); + if( h==INVALID_HANDLE_VALUE ){ + pFile->lastErrno = GetLastError(); + HeapFree(GetProcessHeap(),0,zConverted); + return UNQLITE_IOERR; + } + SyZero(pFile,sizeof(*pFile)); + pFile->pMethod = &winIoMethod; + pFile->h = h; + pFile->lastErrno = NO_ERROR; + pFile->pVfs = pVfs; + pFile->sectorSize = getSectorSize(pVfs, zUtf8Name); + HeapFree(GetProcessHeap(),0,zConverted); + return UNQLITE_OK; +} +/* + * Export the Windows Vfs. + */ +UNQLITE_PRIVATE const unqlite_vfs * unqliteExportBuiltinVfs(void) +{ + static const unqlite_vfs sWinvfs = { + "Windows", /* Vfs name */ + 1, /* Vfs structure version */ + sizeof(winFile), /* szOsFile */ + MAX_PATH, /* mxPathName */ + winOpen, /* xOpen */ + winDelete, /* xDelete */ + winAccess, /* xAccess */ + winFullPathname, /* xFullPathname */ + 0, /* xTmp */ + winSleep, /* xSleep */ + winCurrentTime, /* xCurrentTime */ + winGetLastError, /* xGetLastError */ + }; + return &sWinvfs; +} +#endif /* __WINNT__ */ +/* + * ---------------------------------------------------------- + * File: pager.c + * MD5: 57ff77347402fbf6892af589ff8a5df7 + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: pager.c v1.1 Win7 2012-11-29 03:46 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* +** This file implements the pager and the transaction manager for UnQLite (Mostly inspired from the SQLite3 Source tree). +** +** The Pager.eState variable stores the current 'state' of a pager. A +** pager may be in any one of the seven states shown in the following +** state diagram. +** +** OPEN <------+------+ +** | | | +** V | | +** +---------> READER-------+ | +** | | | +** | V | +** |<-------WRITER_LOCKED--------->| +** | | | +** | V | +** |<------WRITER_CACHEMOD-------->| +** | | | +** | V | +** |<-------WRITER_DBMOD---------->| +** | | | +** | V | +** +<------WRITER_FINISHED-------->+ +** +** OPEN: +** +** The pager starts up in this state. Nothing is guaranteed in this +** state - the file may or may not be locked and the database size is +** unknown. The database may not be read or written. +** +** * No read or write transaction is active. +** * Any lock, or no lock at all, may be held on the database file. +** * The dbSize and dbOrigSize variables may not be trusted. +** +** READER: +** +** In this state all the requirements for reading the database in +** rollback mode are met. Unless the pager is (or recently +** was) in exclusive-locking mode, a user-level read transaction is +** open. The database size is known in this state. +** +** * A read transaction may be active (but a write-transaction cannot). +** * A SHARED or greater lock is held on the database file. +** * The dbSize variable may be trusted (even if a user-level read +** transaction is not active). The dbOrigSize variables +** may not be trusted at this point. +** * Even if a read-transaction is not open, it is guaranteed that +** there is no hot-journal in the file-system. +** +** WRITER_LOCKED: +** +** The pager moves to this state from READER when a write-transaction +** is first opened on the database. In WRITER_LOCKED state, all locks +** required to start a write-transaction are held, but no actual +** modifications to the cache or database have taken place. +** +** In rollback mode, a RESERVED or (if the transaction was opened with +** EXCLUSIVE flag) EXCLUSIVE lock is obtained on the database file when +** moving to this state, but the journal file is not written to or opened +** to in this state. If the transaction is committed or rolled back while +** in WRITER_LOCKED state, all that is required is to unlock the database +** file. +** +** * A write transaction is active. +** * If the connection is open in rollback-mode, a RESERVED or greater +** lock is held on the database file. +** * The dbSize and dbOrigSize variables are all valid. +** * The contents of the pager cache have not been modified. +** * The journal file may or may not be open. +** * Nothing (not even the first header) has been written to the journal. +** +** WRITER_CACHEMOD: +** +** A pager moves from WRITER_LOCKED state to this state when a page is +** first modified by the upper layer. In rollback mode the journal file +** is opened (if it is not already open) and a header written to the +** start of it. The database file on disk has not been modified. +** +** * A write transaction is active. +** * A RESERVED or greater lock is held on the database file. +** * The journal file is open and the first header has been written +** to it, but the header has not been synced to disk. +** * The contents of the page cache have been modified. +** +** WRITER_DBMOD: +** +** The pager transitions from WRITER_CACHEMOD into WRITER_DBMOD state +** when it modifies the contents of the database file. +** +** * A write transaction is active. +** * An EXCLUSIVE or greater lock is held on the database file. +** * The journal file is open and the first header has been written +** and synced to disk. +** * The contents of the page cache have been modified (and possibly +** written to disk). +** +** WRITER_FINISHED: +** +** A rollback-mode pager changes to WRITER_FINISHED state from WRITER_DBMOD +** state after the entire transaction has been successfully written into the +** database file. In this state the transaction may be committed simply +** by finalizing the journal file. Once in WRITER_FINISHED state, it is +** not possible to modify the database further. At this point, the upper +** layer must either commit or rollback the transaction. +** +** * A write transaction is active. +** * An EXCLUSIVE or greater lock is held on the database file. +** * All writing and syncing of journal and database data has finished. +** If no error occured, all that remains is to finalize the journal to +** commit the transaction. If an error did occur, the caller will need +** to rollback the transaction. +** +** +*/ +#define PAGER_OPEN 0 +#define PAGER_READER 1 +#define PAGER_WRITER_LOCKED 2 +#define PAGER_WRITER_CACHEMOD 3 +#define PAGER_WRITER_DBMOD 4 +#define PAGER_WRITER_FINISHED 5 +/* +** Journal files begin with the following magic string. The data +** was obtained from /dev/random. It is used only as a sanity check. +** +** NOTE: These values must be different from the one used by SQLite3 +** to avoid journal file collision. +** +*/ +static const unsigned char aJournalMagic[] = { + 0xa6, 0xe8, 0xcd, 0x2b, 0x1c, 0x92, 0xdb, 0x9f, +}; +/* +** The journal header size for this pager. This is usually the same +** size as a single disk sector. See also setSectorSize(). +*/ +#define JOURNAL_HDR_SZ(pPager) (pPager->iSectorSize) +/* + * Database page handle. + * Each raw disk page is represented in memory by an instance + * of the following structure. + */ +typedef struct Page Page; +struct Page { + /* Must correspond to unqlite_page */ + unsigned char *zData; /* Content of this page */ + void *pUserData; /* Extra content */ + pgno pgno; /* Page number for this page */ + /********************************************************************** + ** Elements above are public. All that follows is private to pcache.c + ** and should not be accessed by other modules. + */ + Pager *pPager; /* The pager this page is part of */ + int flags; /* Page flags defined below */ + int nRef; /* Number of users of this page */ + Page *pNext, *pPrev; /* A list of all pages */ + Page *pDirtyNext; /* Next element in list of dirty pages */ + Page *pDirtyPrev; /* Previous element in list of dirty pages */ + Page *pNextCollide,*pPrevCollide; /* Collission chain */ + Page *pNextHot,*pPrevHot; /* Hot dirty pages chain */ +}; +/* Bit values for Page.flags */ +#define PAGE_DIRTY 0x002 /* Page has changed */ +#define PAGE_NEED_SYNC 0x004 /* fsync the rollback journal before + ** writing this page to the database */ +#define PAGE_DONT_WRITE 0x008 /* Dont write page content to disk */ +#define PAGE_NEED_READ 0x010 /* Content is unread */ +#define PAGE_IN_JOURNAL 0x020 /* Page written to the journal */ +#define PAGE_HOT_DIRTY 0x040 /* Hot dirty page */ +#define PAGE_DONT_MAKE_HOT 0x080 /* Dont make this page Hot. In other words, + * do not link it to the hot dirty list. + */ +/* + * Each active database pager is represented by an instance of + * the following structure. + */ +struct Pager +{ + SyMemBackend *pAllocator; /* Memory backend */ + unqlite *pDb; /* DB handle that own this instance */ + unqlite_kv_engine *pEngine; /* Underlying KV storage engine */ + char *zFilename; /* Name of the database file */ + char *zJournal; /* Name of the journal file */ + unqlite_vfs *pVfs; /* Underlying virtual file system */ + unqlite_file *pfd,*pjfd; /* File descriptors for database and journal */ + pgno dbSize; /* Number of pages in the file */ + pgno dbOrigSize; /* dbSize before the current change */ + sxi64 dbByteSize; /* Database size in bytes */ + void *pMmap; /* Read-only Memory view (mmap) of the whole file if requested (UNQLITE_OPEN_MMAP). */ + sxu32 nRec; /* Number of pages written to the journal */ + SyPRNGCtx sPrng; /* PRNG Context */ + sxu32 cksumInit; /* Quasi-random value added to every checksum */ + sxu32 iOpenFlags; /* Flag passed to unqlite_open() after processing */ + sxi64 iJournalOfft; /* Journal offset we are reading from */ + int (*xBusyHandler)(void *); /* Busy handler */ + void *pBusyHandlerArg; /* First arg to xBusyHandler() */ + void (*xPageUnpin)(void *); /* Page Unpin callback */ + void (*xPageReload)(void *); /* Page Reload callback */ + Bitvec *pVec; /* Bitmap */ + Page *pHeader; /* Page one of the database (Unqlite header) */ + Sytm tmCreate; /* Database creation time */ + SyString sKv; /* Underlying Key/Value storage engine name */ + int iState; /* Pager state */ + int iLock; /* Lock state */ + sxi32 iFlags; /* Control flags (see below) */ + int is_mem; /* True for an in-memory database */ + int is_rdonly; /* True for a read-only database */ + int no_jrnl; /* TRUE to omit journaling */ + int iPageSize; /* Page size in bytes (default 4K) */ + int iSectorSize; /* Size of a single sector on disk */ + unsigned char *zTmpPage; /* Temporary page */ + Page *pFirstDirty; /* First dirty pages */ + Page *pDirty; /* Transient list of dirty pages */ + Page *pAll; /* List of all pages */ + Page *pHotDirty; /* List of hot dirty pages */ + Page *pFirstHot; /* First hot dirty page */ + sxu32 nHot; /* Total number of hot dirty pages */ + Page **apHash; /* Page table */ + sxu32 nSize; /* apHash[] size: Must be a power of two */ + sxu32 nPage; /* Total number of page loaded in memory */ + sxu32 nCacheMax; /* Maximum page to cache*/ +}; +/* Control flags */ +#define PAGER_CTRL_COMMIT_ERR 0x001 /* Commit error */ +#define PAGER_CTRL_DIRTY_COMMIT 0x002 /* Dirty commit has been applied */ +/* +** Read a 32-bit integer from the given file descriptor. +** All values are stored on disk as big-endian. +*/ +static int ReadInt32(unqlite_file *pFd,sxu32 *pOut,sxi64 iOfft) +{ + unsigned char zBuf[4]; + int rc; + rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + SyBigEndianUnpack32(zBuf,pOut); + return UNQLITE_OK; +} +/* +** Read a 64-bit integer from the given file descriptor. +** All values are stored on disk as big-endian. +*/ +static int ReadInt64(unqlite_file *pFd,sxu64 *pOut,sxi64 iOfft) +{ + unsigned char zBuf[8]; + int rc; + rc = unqliteOsRead(pFd,zBuf,sizeof(zBuf),iOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + SyBigEndianUnpack64(zBuf,pOut); + return UNQLITE_OK; +} +/* +** Write a 32-bit integer into the given file descriptor. +*/ +static int WriteInt32(unqlite_file *pFd,sxu32 iNum,sxi64 iOfft) +{ + unsigned char zBuf[4]; + int rc; + SyBigEndianPack32(zBuf,iNum); + rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft); + return rc; +} +/* +** Write a 64-bit integer into the given file descriptor. +*/ +static int WriteInt64(unqlite_file *pFd,sxu64 iNum,sxi64 iOfft) +{ + unsigned char zBuf[8]; + int rc; + SyBigEndianPack64(zBuf,iNum); + rc = unqliteOsWrite(pFd,zBuf,sizeof(zBuf),iOfft); + return rc; +} +/* +** The maximum allowed sector size. 64KiB. If the xSectorsize() method +** returns a value larger than this, then MAX_SECTOR_SIZE is used instead. +** This could conceivably cause corruption following a power failure on +** such a system. This is currently an undocumented limit. +*/ +#define MAX_SECTOR_SIZE 0x10000 +/* +** Get the size of a single sector on disk. +** The sector size will be used used to determine the size +** and alignment of journal header and within created journal files. +** +** The default sector size is set to 512. +*/ +static int GetSectorSize(unqlite_file *pFd) +{ + int iSectorSize = UNQLITE_DEFAULT_SECTOR_SIZE; + if( pFd ){ + iSectorSize = unqliteOsSectorSize(pFd); + if( iSectorSize < 32 ){ + iSectorSize = 512; + } + if( iSectorSize > MAX_SECTOR_SIZE ){ + iSectorSize = MAX_SECTOR_SIZE; + } + } + return iSectorSize; +} +/* Hash function for page number */ +#define PAGE_HASH(PNUM) (PNUM) +/* + * Fetch a page from the cache. + */ +static Page * pager_fetch_page(Pager *pPager,pgno page_num) +{ + Page *pEntry; + if( pPager->nPage < 1 ){ + /* Don't bother hashing */ + return 0; + } + /* Perform the lookup */ + pEntry = pPager->apHash[PAGE_HASH(page_num) & (pPager->nSize - 1)]; + for(;;){ + if( pEntry == 0 ){ + break; + } + if( pEntry->pgno == page_num ){ + return pEntry; + } + /* Point to the next entry in the colission chain */ + pEntry = pEntry->pNextCollide; + } + /* No such page */ + return 0; +} +/* + * Allocate and initialize a new page. + */ +static Page * pager_alloc_page(Pager *pPager,pgno num_page) +{ + Page *pNew; + + pNew = (Page *)SyMemBackendPoolAlloc(pPager->pAllocator,sizeof(Page)+pPager->iPageSize); + if( pNew == 0 ){ + return 0; + } + /* Zero the structure */ + SyZero(pNew,sizeof(Page)+pPager->iPageSize); + /* Page data */ + pNew->zData = (unsigned char *)&pNew[1]; + /* Fill in the structure */ + pNew->pPager = pPager; + pNew->nRef = 1; + pNew->pgno = num_page; + return pNew; +} +/* + * Increment the reference count of a given page. + */ +static void page_ref(Page *pPage) +{ + if( pPage->pPager->pAllocator->pMutexMethods ){ + SyMutexEnter(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); + } + pPage->nRef++; + if( pPage->pPager->pAllocator->pMutexMethods ){ + SyMutexLeave(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); + } +} +/* + * Release an in-memory page after its reference count reach zero. + */ +static int pager_release_page(Pager *pPager,Page *pPage) +{ + int rc = UNQLITE_OK; + if( !(pPage->flags & PAGE_DIRTY)){ + /* Invoke the unpin callback if available */ + if( pPager->xPageUnpin && pPage->pUserData ){ + pPager->xPageUnpin(pPage->pUserData); + } + pPage->pUserData = 0; + SyMemBackendPoolFree(pPager->pAllocator,pPage); + }else{ + /* Dirty page, it will be released later when a dirty commit + * or the final commit have been applied. + */ + rc = UNQLITE_LOCKED; + } + return rc; +} +/* Forward declaration */ +static int pager_unlink_page(Pager *pPager,Page *pPage); +/* + * Decrement the reference count of a given page. + */ +static void page_unref(Page *pPage) +{ + int nRef; + if( pPage->pPager->pAllocator->pMutexMethods ){ + SyMutexEnter(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); + } + nRef = pPage->nRef--; + if( pPage->pPager->pAllocator->pMutexMethods ){ + SyMutexLeave(pPage->pPager->pAllocator->pMutexMethods, pPage->pPager->pAllocator->pMutex); + } + if( nRef == 0){ + Pager *pPager = pPage->pPager; + if( !(pPage->flags & PAGE_DIRTY) ){ + pager_unlink_page(pPager,pPage); + /* Release the page */ + pager_release_page(pPager,pPage); + }else{ + if( pPage->flags & PAGE_DONT_MAKE_HOT ){ + /* Do not add this page to the hot dirty list */ + return; + } + if( !(pPage->flags & PAGE_HOT_DIRTY) ){ + /* Add to the hot dirty list */ + pPage->pPrevHot = 0; + if( pPager->pFirstHot == 0 ){ + pPager->pFirstHot = pPager->pHotDirty = pPage; + }else{ + pPage->pNextHot = pPager->pHotDirty; + if( pPager->pHotDirty ){ + pPager->pHotDirty->pPrevHot = pPage; + } + pPager->pHotDirty = pPage; + } + pPager->nHot++; + pPage->flags |= PAGE_HOT_DIRTY; + } + } + } +} +/* + * Link a freshly created page to the list of active page. + */ +static int pager_link_page(Pager *pPager,Page *pPage) +{ + sxu32 nBucket; + /* Install in the corresponding bucket */ + nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1); + pPage->pNextCollide = pPager->apHash[nBucket]; + if( pPager->apHash[nBucket] ){ + pPager->apHash[nBucket]->pPrevCollide = pPage; + } + pPager->apHash[nBucket] = pPage; + /* Link to the list of active pages */ + MACRO_LD_PUSH(pPager->pAll,pPage); + pPager->nPage++; + if( (pPager->nPage >= pPager->nSize * 4) && pPager->nPage < 100000 ){ + /* Grow the hashtable */ + sxu32 nNewSize = pPager->nSize << 1; + Page *pEntry,**apNew; + sxu32 n; + apNew = (Page **)SyMemBackendAlloc(pPager->pAllocator, nNewSize * sizeof(Page *)); + if( apNew ){ + sxu32 iBucket; + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(Page *)); + /* Rehash all entries */ + n = 0; + pEntry = pPager->pAll; + for(;;){ + /* Loop one */ + if( n >= pPager->nPage ){ + break; + } + pEntry->pNextCollide = pEntry->pPrevCollide = 0; + /* Install in the new bucket */ + iBucket = PAGE_HASH(pEntry->pgno) & (nNewSize - 1); + pEntry->pNextCollide = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevCollide = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(pPager->pAllocator,(void *)pPager->apHash); + pPager->apHash = apNew; + pPager->nSize = nNewSize; + } + } + return UNQLITE_OK; +} +/* + * Unlink a page from the list of active pages. + */ +static int pager_unlink_page(Pager *pPager,Page *pPage) +{ + if( pPage->pNextCollide ){ + pPage->pNextCollide->pPrevCollide = pPage->pPrevCollide; + } + if( pPage->pPrevCollide ){ + pPage->pPrevCollide->pNextCollide = pPage->pNextCollide; + }else{ + sxu32 nBucket = PAGE_HASH(pPage->pgno) & (pPager->nSize - 1); + pPager->apHash[nBucket] = pPage->pNextCollide; + } + MACRO_LD_REMOVE(pPager->pAll,pPage); + pPager->nPage--; + return UNQLITE_OK; +} +/* + * Update the content of a cached page. + */ +static int pager_fill_page(Pager *pPager,pgno iNum,void *pContents) +{ + Page *pPage; + /* Fetch the page from the catch */ + pPage = pager_fetch_page(pPager,iNum); + if( pPage == 0 ){ + return SXERR_NOTFOUND; + } + /* Reflect the change */ + SyMemcpy(pContents,pPage->zData,pPager->iPageSize); + + return UNQLITE_OK; +} +/* + * Read the content of a page from disk. + */ +static int pager_get_page_contents(Pager *pPager,Page *pPage,int noContent) +{ + int rc = UNQLITE_OK; + if( pPager->is_mem || noContent || pPage->pgno >= pPager->dbSize ){ + /* Do not bother reading, zero the page contents only */ + SyZero(pPage->zData,pPager->iPageSize); + return UNQLITE_OK; + } + if( (pPager->iOpenFlags & UNQLITE_OPEN_MMAP) && (pPager->pMmap /* Paranoid edition */) ){ + unsigned char *zMap = (unsigned char *)pPager->pMmap; + pPage->zData = &zMap[pPage->pgno * pPager->iPageSize]; + }else{ + /* Read content */ + rc = unqliteOsRead(pPager->pfd,pPage->zData,pPager->iPageSize,pPage->pgno * pPager->iPageSize); + } + return rc; +} +/* + * Add a page to the dirty list. + */ +static void pager_page_to_dirty_list(Pager *pPager,Page *pPage) +{ + if( pPage->flags & PAGE_DIRTY ){ + /* Already set */ + return; + } + /* Mark the page as dirty */ + pPage->flags |= PAGE_DIRTY|PAGE_NEED_SYNC|PAGE_IN_JOURNAL; + /* Link to the list */ + pPage->pDirtyPrev = 0; + pPage->pDirtyNext = pPager->pDirty; + if( pPager->pDirty ){ + pPager->pDirty->pDirtyPrev = pPage; + } + pPager->pDirty = pPage; + if( pPager->pFirstDirty == 0 ){ + pPager->pFirstDirty = pPage; + } +} +/* + * Merge sort. + * The merge sort implementation is based on the one used by + * the PH7 Embeddable PHP Engine (http://ph7.symisc.net/). + */ +/* +** Inputs: +** a: A sorted, null-terminated linked list. (May be null). +** b: A sorted, null-terminated linked list. (May be null). +** cmp: A pointer to the comparison function. +** +** Return Value: +** A pointer to the head of a sorted list containing the elements +** of both a and b. +** +** Side effects: +** The "next", "prev" pointers for elements in the lists a and b are +** changed. +*/ +static Page * page_merge_dirty(Page *pA, Page *pB) +{ + Page result, *pTail; + /* Prevent compiler warning */ + result.pDirtyNext = result.pDirtyPrev = 0; + pTail = &result; + while( pA && pB ){ + if( pA->pgno < pB->pgno ){ + pTail->pDirtyPrev = pA; + pA->pDirtyNext = pTail; + pTail = pA; + pA = pA->pDirtyPrev; + }else{ + pTail->pDirtyPrev = pB; + pB->pDirtyNext = pTail; + pTail = pB; + pB = pB->pDirtyPrev; + } + } + if( pA ){ + pTail->pDirtyPrev = pA; + pA->pDirtyNext = pTail; + }else if( pB ){ + pTail->pDirtyPrev = pB; + pB->pDirtyNext = pTail; + }else{ + pTail->pDirtyPrev = pTail->pDirtyNext = 0; + } + return result.pDirtyPrev; +} +/* +** Inputs: +** Map: Input hashmap +** cmp: A comparison function. +** +** Return Value: +** Sorted hashmap. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define N_SORT_BUCKET 32 +static Page * pager_get_dirty_pages(Pager *pPager) +{ + Page *a[N_SORT_BUCKET], *p, *pIn; + sxu32 i; + if( pPager->pFirstDirty == 0 ){ + /* Don't bother sorting, the list is already empty */ + return 0; + } + SyZero(a, sizeof(a)); + /* Point to the first inserted entry */ + pIn = pPager->pFirstDirty; + while( pIn ){ + p = pIn; + pIn = p->pDirtyPrev; + p->pDirtyPrev = 0; + for(i=0; ipDirtyNext = 0; + return p; +} +/* + * See block comment above. + */ +static Page * page_merge_hot(Page *pA, Page *pB) +{ + Page result, *pTail; + /* Prevent compiler warning */ + result.pNextHot = result.pPrevHot = 0; + pTail = &result; + while( pA && pB ){ + if( pA->pgno < pB->pgno ){ + pTail->pPrevHot = pA; + pA->pNextHot = pTail; + pTail = pA; + pA = pA->pPrevHot; + }else{ + pTail->pPrevHot = pB; + pB->pNextHot = pTail; + pTail = pB; + pB = pB->pPrevHot; + } + } + if( pA ){ + pTail->pPrevHot = pA; + pA->pNextHot = pTail; + }else if( pB ){ + pTail->pPrevHot = pB; + pB->pNextHot = pTail; + }else{ + pTail->pPrevHot = pTail->pNextHot = 0; + } + return result.pPrevHot; +} +/* +** Inputs: +** Map: Input hashmap +** cmp: A comparison function. +** +** Return Value: +** Sorted hashmap. +** +** Side effects: +** The "next" pointers for elements in list are changed. +*/ +#define N_SORT_BUCKET 32 +static Page * pager_get_hot_pages(Pager *pPager) +{ + Page *a[N_SORT_BUCKET], *p, *pIn; + sxu32 i; + if( pPager->pFirstHot == 0 ){ + /* Don't bother sorting, the list is already empty */ + return 0; + } + SyZero(a, sizeof(a)); + /* Point to the first inserted entry */ + pIn = pPager->pFirstHot; + while( pIn ){ + p = pIn; + pIn = p->pPrevHot; + p->pPrevHot = 0; + for(i=0; ipNextHot = 0; + return p; +} +/* +** The format for the journal header is as follows: +** - 8 bytes: Magic identifying journal format. +** - 4 bytes: Number of records in journal. +** - 4 bytes: Random number used for page hash. +** - 8 bytes: Initial database page count. +** - 4 bytes: Sector size used by the process that wrote this journal. +** - 4 bytes: Database page size. +** +** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space. +*/ +/* +** Open the journal file and extract its header information. +** +** If the header is read successfully, *pNRec is set to the number of +** page records following this header and *pDbSize is set to the size of the +** database before the transaction began, in pages. Also, pPager->cksumInit +** is set to the value read from the journal header. UNQLITE_OK is returned +** in this case. +** +** If the journal header file appears to be corrupted, UNQLITE_DONE is +** returned and *pNRec and *PDbSize are undefined. If JOURNAL_HDR_SZ bytes +** cannot be read from the journal file an error code is returned. +*/ +static int pager_read_journal_header( + Pager *pPager, /* Pager object */ + sxu32 *pNRec, /* OUT: Value read from the nRec field */ + pgno *pDbSize /* OUT: Value of original database size field */ +) +{ + sxu32 iPageSize,iSectorSize; + unsigned char zMagic[8]; + sxi64 iHdrOfft; + sxi64 iSize; + int rc; + /* Offset to start reading from */ + iHdrOfft = 0; + /* Get the size of the journal */ + rc = unqliteOsFileSize(pPager->pjfd,&iSize); + if( rc != UNQLITE_OK ){ + return UNQLITE_DONE; + } + /* If the journal file is too small, return UNQLITE_DONE. */ + if( 32 /* Minimum sector size */> iSize ){ + return UNQLITE_DONE; + } + /* Make sure we are dealing with a valid journal */ + rc = unqliteOsRead(pPager->pjfd,zMagic,sizeof(zMagic),iHdrOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + if( SyMemcmp(zMagic,aJournalMagic,sizeof(zMagic)) != 0 ){ + return UNQLITE_DONE; + } + iHdrOfft += sizeof(zMagic); + /* Read the first three 32-bit fields of the journal header: The nRec + ** field, the checksum-initializer and the database size at the start + ** of the transaction. Return an error code if anything goes wrong. + */ + rc = ReadInt32(pPager->pjfd,pNRec,iHdrOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + iHdrOfft += 4; + rc = ReadInt32(pPager->pjfd,&pPager->cksumInit,iHdrOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + iHdrOfft += 4; + rc = ReadInt64(pPager->pjfd,pDbSize,iHdrOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + iHdrOfft += 8; + /* Read the page-size and sector-size journal header fields. */ + rc = ReadInt32(pPager->pjfd,&iSectorSize,iHdrOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + iHdrOfft += 4; + rc = ReadInt32(pPager->pjfd,&iPageSize,iHdrOfft); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Check that the values read from the page-size and sector-size fields + ** are within range. To be 'in range', both values need to be a power + ** of two greater than or equal to 512 or 32, and not greater than their + ** respective compile time maximum limits. + */ + if( iPageSize < UNQLITE_MIN_PAGE_SIZE || iSectorSize<32 + || iPageSize > UNQLITE_MAX_PAGE_SIZE || iSectorSize>MAX_SECTOR_SIZE + || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 + ){ + /* If the either the page-size or sector-size in the journal-header is + ** invalid, then the process that wrote the journal-header must have + ** crashed before the header was synced. In this case stop reading + ** the journal file here. + */ + return UNQLITE_DONE; + } + /* Update the assumed sector-size to match the value used by + ** the process that created this journal. If this journal was + ** created by a process other than this one, then this routine + ** is being called from within pager_playback(). The local value + ** of Pager.sectorSize is restored at the end of that routine. + */ + pPager->iSectorSize = iSectorSize; + pPager->iPageSize = iPageSize; + /* Ready to rollback */ + pPager->iJournalOfft = JOURNAL_HDR_SZ(pPager); + /* All done */ + return UNQLITE_OK; +} +/* + * Write the journal header in the given memory buffer. + * The given buffer is big enough to hold the whole header. + */ +static int pager_write_journal_header(Pager *pPager,unsigned char *zBuf) +{ + unsigned char *zPtr = zBuf; + /* 8 bytes magic number */ + SyMemcpy(aJournalMagic,zPtr,sizeof(aJournalMagic)); + zPtr += sizeof(aJournalMagic); + /* 4 bytes: Number of records in journal. */ + SyBigEndianPack32(zPtr,0); + zPtr += 4; + /* 4 bytes: Random number used to compute page checksum. */ + SyBigEndianPack32(zPtr,pPager->cksumInit); + zPtr += 4; + /* 8 bytes: Initial database page count. */ + SyBigEndianPack64(zPtr,pPager->dbOrigSize); + zPtr += 8; + /* 4 bytes: Sector size used by the process that wrote this journal. */ + SyBigEndianPack32(zPtr,(sxu32)pPager->iSectorSize); + zPtr += 4; + /* 4 bytes: Database page size. */ + SyBigEndianPack32(zPtr,(sxu32)pPager->iPageSize); + return UNQLITE_OK; +} +/* +** Parameter aData must point to a buffer of pPager->pageSize bytes +** of data. Compute and return a checksum based ont the contents of the +** page of data and the current value of pPager->cksumInit. +** +** This is not a real checksum. It is really just the sum of the +** random initial value (pPager->cksumInit) and every 200th byte +** of the page data, starting with byte offset (pPager->pageSize%200). +** Each byte is interpreted as an 8-bit unsigned integer. +** +** Changing the formula used to compute this checksum results in an +** incompatible journal file format. +** +** If journal corruption occurs due to a power failure, the most likely +** scenario is that one end or the other of the record will be changed. +** It is much less likely that the two ends of the journal record will be +** correct and the middle be corrupt. Thus, this "checksum" scheme, +** though fast and simple, catches the mostly likely kind of corruption. +*/ +static sxu32 pager_cksum(Pager *pPager,const unsigned char *zData) +{ + sxu32 cksum = pPager->cksumInit; /* Checksum value to return */ + int i = pPager->iPageSize-200; /* Loop counter */ + while( i>0 ){ + cksum += zData[i]; + i -= 200; + } + return cksum; +} +/* +** Read a single page from the journal file opened on file descriptor +** jfd. Playback this one page. Update the offset to read from. +*/ +static int pager_play_back_one_page(Pager *pPager,sxi64 *pOfft,unsigned char *zTmp) +{ + unsigned char *zData = zTmp; + sxi64 iOfft; /* Offset to read from */ + pgno iNum; /* Pager number */ + sxu32 ckSum; /* Sanity check */ + int rc; + /* Offset to start reading from */ + iOfft = *pOfft; + /* Database page number */ + rc = ReadInt64(pPager->pjfd,&iNum,iOfft); + if( rc != UNQLITE_OK ){ return rc; } + iOfft += 8; + /* Page data */ + rc = unqliteOsRead(pPager->pjfd,zData,pPager->iPageSize,iOfft); + if( rc != UNQLITE_OK ){ return rc; } + iOfft += pPager->iPageSize; + /* Page cksum */ + rc = ReadInt32(pPager->pjfd,&ckSum,iOfft); + if( rc != UNQLITE_OK ){ return rc; } + iOfft += 4; + /* Synchronize pointers */ + *pOfft = iOfft; + /* Make sure we are dealing with a valid page */ + if( ckSum != pager_cksum(pPager,zData) ){ + /* Ignore that page */ + return SXERR_IGNORE; + } + if( iNum >= pPager->dbSize ){ + /* Ignore that page */ + return UNQLITE_OK; + } + /* playback */ + rc = unqliteOsWrite(pPager->pfd,zData,pPager->iPageSize,iNum * pPager->iPageSize); + if( rc == UNQLITE_OK ){ + /* Flush the cache */ + pager_fill_page(pPager,iNum,zData); + } + return rc; +} +/* +** Playback the journal and thus restore the database file to +** the state it was in before we started making changes. +** +** The journal file format is as follows: +** +** (1) 8 byte prefix. A copy of aJournalMagic[]. +** (2) 4 byte big-endian integer which is the number of valid page records +** in the journal. +** (3) 4 byte big-endian integer which is the initial value for the +** sanity checksum. +** (4) 8 byte integer which is the number of pages to truncate the +** database to during a rollback. +** (5) 4 byte big-endian integer which is the sector size. The header +** is this many bytes in size. +** (6) 4 byte big-endian integer which is the page size. +** (7) zero padding out to the next sector size. +** (8) Zero or more pages instances, each as follows: +** + 4 byte page number. +** + pPager->pageSize bytes of data. +** + 4 byte checksum +** +** When we speak of the journal header, we mean the first 7 items above. +** Each entry in the journal is an instance of the 8th item. +** +** Call the value from the second bullet "nRec". nRec is the number of +** valid page entries in the journal. In most cases, you can compute the +** value of nRec from the size of the journal file. But if a power +** failure occurred while the journal was being written, it could be the +** case that the size of the journal file had already been increased but +** the extra entries had not yet made it safely to disk. In such a case, +** the value of nRec computed from the file size would be too large. For +** that reason, we always use the nRec value in the header. +** +** If the file opened as the journal file is not a well-formed +** journal file then all pages up to the first corrupted page are rolled +** back (or no pages if the journal header is corrupted). The journal file +** is then deleted and SQLITE_OK returned, just as if no corruption had +** been encountered. +** +** If an I/O or malloc() error occurs, the journal-file is not deleted +** and an error code is returned. +** +*/ +static int pager_playback(Pager *pPager) +{ + unsigned char *zTmp = 0; /* cc warning */ + sxu32 n,nRec; + sxi64 iOfft; + int rc; + /* Read the journal header*/ + rc = pager_read_journal_header(pPager,&nRec,&pPager->dbSize); + if( rc != UNQLITE_OK ){ + if( rc == UNQLITE_DONE ){ + goto end_playback; + } + unqliteGenErrorFormat(pPager->pDb,"IO error while reading journal file '%s' header",pPager->zJournal); + return rc; + } + /* Truncate the database back to its original size */ + rc = unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize); + if( rc != UNQLITE_OK ){ + unqliteGenError(pPager->pDb,"IO error while truncating database file"); + return rc; + } + /* Allocate a temporary page */ + zTmp = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize); + if( zTmp == 0 ){ + unqliteGenOutofMem(pPager->pDb); + return UNQLITE_NOMEM; + } + SyZero((void *)zTmp,(sxu32)pPager->iPageSize); + /* Copy original pages out of the journal and back into the + ** database file and/or page cache. + */ + iOfft = pPager->iJournalOfft; + for( n = 0 ; n < nRec ; ++n ){ + rc = pager_play_back_one_page(pPager,&iOfft,zTmp); + if( rc != UNQLITE_OK ){ + if( rc != SXERR_IGNORE ){ + unqliteGenError(pPager->pDb,"Page playback error"); + goto end_playback; + } + } + } +end_playback: + /* Release the temp page */ + SyMemBackendFree(pPager->pAllocator,(void *)zTmp); + if( rc == UNQLITE_OK ){ + /* Sync the database file */ + unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL); + } + if( rc == UNQLITE_DONE ){ + rc = UNQLITE_OK; + } + /* Return to the caller */ + return rc; +} +/* +** Unlock the database file to level eLock, which must be either NO_LOCK +** or SHARED_LOCK. Regardless of whether or not the call to xUnlock() +** succeeds, set the Pager.iLock variable to match the (attempted) new lock. +** +** Except, if Pager.iLock is set to NO_LOCK when this function is +** called, do not modify it. See the comment above the #define of +** NO_LOCK for an explanation of this. +*/ +static int pager_unlock_db(Pager *pPager, int eLock) +{ + int rc = UNQLITE_OK; + if( pPager->iLock != NO_LOCK ){ + rc = unqliteOsUnlock(pPager->pfd,eLock); + pPager->iLock = eLock; + } + return rc; +} +/* +** Lock the database file to level eLock, which must be either SHARED_LOCK, +** RESERVED_LOCK or EXCLUSIVE_LOCK. If the caller is successful, set the +** Pager.eLock variable to the new locking state. +** +** Except, if Pager.eLock is set to NO_LOCK when this function is +** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. +** See the comment above the #define of NO_LOCK for an explanation +** of this. +*/ +static int pager_lock_db(Pager *pPager, int eLock){ + int rc = UNQLITE_OK; + if( pPager->iLock < eLock || pPager->iLock == NO_LOCK ){ + rc = unqliteOsLock(pPager->pfd, eLock); + if( rc==UNQLITE_OK ){ + pPager->iLock = eLock; + }else{ + unqliteGenError(pPager->pDb, + rc == UNQLITE_BUSY ? "Another process or thread hold the requested lock" : "Error while requesting database lock" + ); + } + } + return rc; +} +/* +** Try to obtain a lock of type locktype on the database file. If +** a similar or greater lock is already held, this function is a no-op +** (returning UNQLITE_OK immediately). +** +** Otherwise, attempt to obtain the lock using unqliteOsLock(). Invoke +** the busy callback if the lock is currently not available. Repeat +** until the busy callback returns false or until the attempt to +** obtain the lock succeeds. +** +** Return UNQLITE_OK on success and an error code if we cannot obtain +** the lock. If the lock is obtained successfully, set the Pager.state +** variable to locktype before returning. +*/ +static int pager_wait_on_lock(Pager *pPager, int locktype){ + int rc; /* Return code */ + do { + rc = pager_lock_db(pPager,locktype); + }while( rc==UNQLITE_BUSY && pPager->xBusyHandler && pPager->xBusyHandler(pPager->pBusyHandlerArg) ); + return rc; +} +/* +** This function is called after transitioning from PAGER_OPEN to +** PAGER_SHARED state. It tests if there is a hot journal present in +** the file-system for the given pager. A hot journal is one that +** needs to be played back. According to this function, a hot-journal +** file exists if the following criteria are met: +** +** * The journal file exists in the file system, and +** * No process holds a RESERVED or greater lock on the database file, and +** * The database file itself is greater than 0 bytes in size, and +** * The first byte of the journal file exists and is not 0x00. +** +** If the current size of the database file is 0 but a journal file +** exists, that is probably an old journal left over from a prior +** database with the same name. In this case the journal file is +** just deleted using OsDelete, *pExists is set to 0 and UNQLITE_OK +** is returned. +** +** If a hot-journal file is found to exist, *pExists is set to 1 and +** UNQLITE_OK returned. If no hot-journal file is present, *pExists is +** set to 0 and UNQLITE_OK returned. If an IO error occurs while trying +** to determine whether or not a hot-journal file exists, the IO error +** code is returned and the value of *pExists is undefined. +*/ +static int pager_has_hot_journal(Pager *pPager, int *pExists) +{ + unqlite_vfs *pVfs = pPager->pVfs; + int rc = UNQLITE_OK; /* Return code */ + int exists = 1; /* True if a journal file is present */ + + *pExists = 0; + rc = unqliteOsAccess(pVfs, pPager->zJournal, UNQLITE_ACCESS_EXISTS, &exists); + if( rc==UNQLITE_OK && exists ){ + int locked = 0; /* True if some process holds a RESERVED lock */ + + /* Race condition here: Another process might have been holding the + ** the RESERVED lock and have a journal open at the unqliteOsAccess() + ** call above, but then delete the journal and drop the lock before + ** we get to the following unqliteOsCheckReservedLock() call. If that + ** is the case, this routine might think there is a hot journal when + ** in fact there is none. This results in a false-positive which will + ** be dealt with by the playback routine. + */ + rc = unqliteOsCheckReservedLock(pPager->pfd, &locked); + if( rc==UNQLITE_OK && !locked ){ + sxi64 n = 0; /* Size of db file in bytes */ + + /* Check the size of the database file. If it consists of 0 pages, + ** then delete the journal file. See the header comment above for + ** the reasoning here. Delete the obsolete journal file under + ** a RESERVED lock to avoid race conditions. + */ + rc = unqliteOsFileSize(pPager->pfd,&n); + if( rc==UNQLITE_OK ){ + if( n < 1 ){ + if( pager_lock_db(pPager, RESERVED_LOCK)==UNQLITE_OK ){ + unqliteOsDelete(pVfs, pPager->zJournal, 0); + pager_unlock_db(pPager, SHARED_LOCK); + } + }else{ + /* The journal file exists and no other connection has a reserved + ** or greater lock on the database file. */ + *pExists = 1; + } + } + } + } + return rc; +} +/* + * Rollback a journal file. (See block-comment above). + */ +static int pager_journal_rollback(Pager *pPager,int check_hot) +{ + int rc; + if( check_hot ){ + int iExists = 0; /* cc warning */ + /* Check if the journal file exists */ + rc = pager_has_hot_journal(pPager,&iExists); + if( rc != UNQLITE_OK ){ + /* IO error */ + return rc; + } + if( !iExists ){ + /* Journal file does not exists */ + return UNQLITE_OK; + } + } + if( pPager->is_rdonly ){ + unqliteGenErrorFormat(pPager->pDb, + "Cannot rollback journal file '%s' due to a read-only database handle",pPager->zJournal); + return UNQLITE_READ_ONLY; + } + /* Get an EXCLUSIVE lock on the database file. At this point it is + ** important that a RESERVED lock is not obtained on the way to the + ** EXCLUSIVE lock. If it were, another process might open the + ** database file, detect the RESERVED lock, and conclude that the + ** database is safe to read while this process is still rolling the + ** hot-journal back. + ** + ** Because the intermediate RESERVED lock is not requested, any + ** other process attempting to access the database file will get to + ** this point in the code and fail to obtain its own EXCLUSIVE lock + ** on the database file. + ** + ** Unless the pager is in locking_mode=exclusive mode, the lock is + ** downgraded to SHARED_LOCK before this function returns. + */ + /* Open the journal file */ + rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal,&pPager->pjfd,UNQLITE_OPEN_READWRITE); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: '%s'",pPager->zJournal); + goto fail; + } + rc = pager_lock_db(pPager,EXCLUSIVE_LOCK); + if( rc != UNQLITE_OK ){ + unqliteGenError(pPager->pDb,"Cannot acquire an exclusive lock on the database while journal rollback"); + goto fail; + } + /* Sync the journal file */ + unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); + /* Finally rollback the database */ + rc = pager_playback(pPager); + /* Switch back to shared lock */ + pager_unlock_db(pPager,SHARED_LOCK); +fail: + /* Close the journal handle */ + unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); + pPager->pjfd = 0; + if( rc == UNQLITE_OK ){ + /* Delete the journal file */ + unqliteOsDelete(pPager->pVfs,pPager->zJournal,TRUE); + } + return rc; +} +/* + * Write the unqlite header (First page). (Big-Endian) + */ +static int pager_write_db_header(Pager *pPager) +{ + unsigned char *zRaw = pPager->pHeader->zData; + unqlite_kv_engine *pEngine = pPager->pEngine; + sxu32 nDos; + sxu16 nLen; + /* Database signature */ + SyMemcpy(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1); + zRaw += sizeof(UNQLITE_DB_SIG)-1; + /* Database magic number */ + SyBigEndianPack32(zRaw,UNQLITE_DB_MAGIC); + zRaw += 4; /* 4 byte magic number */ + /* Database creation time */ + SyZero(&pPager->tmCreate,sizeof(Sytm)); + if( pPager->pVfs->xCurrentTime ){ + pPager->pVfs->xCurrentTime(pPager->pVfs,&pPager->tmCreate); + } + /* DOS time format (4 bytes) */ + SyTimeFormatToDos(&pPager->tmCreate,&nDos); + SyBigEndianPack32(zRaw,nDos); + zRaw += 4; /* 4 byte DOS time */ + /* Sector size */ + SyBigEndianPack32(zRaw,(sxu32)pPager->iSectorSize); + zRaw += 4; /* 4 byte sector size */ + /* Page size */ + SyBigEndianPack32(zRaw,(sxu32)pPager->iPageSize); + zRaw += 4; /* 4 byte page size */ + /* Key value storage engine */ + nLen = (sxu16)SyStrlen(pEngine->pIo->pMethods->zName); + SyBigEndianPack16(zRaw,nLen); /* 2 byte storage engine name */ + zRaw += 2; + SyMemcpy((const void *)pEngine->pIo->pMethods->zName,(void *)zRaw,nLen); + zRaw += nLen; + /* All rest are meta-data available to the host application */ + return UNQLITE_OK; +} +/* + * Read the unqlite header (first page). (Big-Endian) + */ +static int pager_extract_header(Pager *pPager,const unsigned char *zRaw,sxu32 nByte) +{ + const unsigned char *zEnd = &zRaw[nByte]; + sxu32 nDos,iMagic; + sxu16 nLen; + char *zKv; + /* Database signature */ + if( SyMemcmp(UNQLITE_DB_SIG,zRaw,sizeof(UNQLITE_DB_SIG)-1) != 0 ){ + /* Corrupt database */ + return UNQLITE_CORRUPT; + } + zRaw += sizeof(UNQLITE_DB_SIG)-1; + /* Database magic number */ + SyBigEndianUnpack32(zRaw,&iMagic); + zRaw += 4; /* 4 byte magic number */ + if( iMagic != UNQLITE_DB_MAGIC ){ + /* Corrupt database */ + return UNQLITE_CORRUPT; + } + /* Database creation time */ + SyBigEndianUnpack32(zRaw,&nDos); + zRaw += 4; /* 4 byte DOS time format */ + SyDosTimeFormat(nDos,&pPager->tmCreate); + /* Sector size */ + SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iSectorSize); + zRaw += 4; /* 4 byte sector size */ + /* Page size */ + SyBigEndianUnpack32(zRaw,(sxu32 *)&pPager->iPageSize); + zRaw += 4; /* 4 byte page size */ + /* Check that the values read from the page-size and sector-size fields + ** are within range. To be 'in range', both values need to be a power + ** of two greater than or equal to 512 or 32, and not greater than their + ** respective compile time maximum limits. + */ + if( pPager->iPageSizeiSectorSize<32 + || pPager->iPageSize>UNQLITE_MAX_PAGE_SIZE || pPager->iSectorSize>MAX_SECTOR_SIZE + || ((pPager->iPageSize<-1)&pPager->iPageSize)!=0 || ((pPager->iSectorSize-1)&pPager->iSectorSize)!=0 + ){ + return UNQLITE_CORRUPT; + } + /* Key value storage engine */ + SyBigEndianUnpack16(zRaw,&nLen); /* 2 byte storage engine length */ + zRaw += 2; + if( nLen > (sxu16)(zEnd - zRaw) ){ + nLen = (sxu16)(zEnd - zRaw); + } + zKv = (char *)SyMemBackendDup(pPager->pAllocator,(const char *)zRaw,nLen); + if( zKv == 0 ){ + return UNQLITE_NOMEM; + } + SyStringInitFromBuf(&pPager->sKv,zKv,nLen); + return UNQLITE_OK; +} +/* + * Read the database header. + */ +static int pager_read_db_header(Pager *pPager) +{ + unsigned char zRaw[UNQLITE_MIN_PAGE_SIZE]; /* Minimum page size */ + sxi64 n = 0; /* Size of db file in bytes */ + int rc; + /* Get the file size first */ + rc = unqliteOsFileSize(pPager->pfd,&n); + if( rc != UNQLITE_OK ){ + return rc; + } + pPager->dbByteSize = n; + if( n > 0 ){ + unqlite_kv_methods *pMethods; + SyString *pKv; + pgno nPage; + if( n < UNQLITE_MIN_PAGE_SIZE ){ + /* A valid unqlite database must be at least 512 bytes long */ + unqliteGenError(pPager->pDb,"Malformed database image"); + return UNQLITE_CORRUPT; + } + /* Read the database header */ + rc = unqliteOsRead(pPager->pfd,zRaw,sizeof(zRaw),0); + if( rc != UNQLITE_OK ){ + unqliteGenError(pPager->pDb,"IO error while reading database header"); + return rc; + } + /* Extract the header */ + rc = pager_extract_header(pPager,zRaw,sizeof(zRaw)); + if( rc != UNQLITE_OK ){ + unqliteGenError(pPager->pDb,rc == UNQLITE_NOMEM ? "Unqlite is running out of memory" : "Malformed database image"); + return rc; + } + /* Update pager state */ + nPage = (pgno)(n / pPager->iPageSize); + if( nPage==0 && n>0 ){ + nPage = 1; + } + pPager->dbSize = nPage; + /* Laod the target Key/Value storage engine */ + pKv = &pPager->sKv; + pMethods = unqliteFindKVStore(pKv->zString,pKv->nByte); + if( pMethods == 0 ){ + unqliteGenErrorFormat(pPager->pDb,"No such Key/Value storage engine '%z'",pKv); + return UNQLITE_NOTIMPLEMENTED; + } + /* Install the new KV storage engine */ + rc = unqlitePagerRegisterKvEngine(pPager,pMethods); + if( rc != UNQLITE_OK ){ + return rc; + } + }else{ + /* Set a default page and sector size */ + pPager->iSectorSize = GetSectorSize(pPager->pfd); + pPager->iPageSize = unqliteGetPageSize(); + SyStringInitFromBuf(&pPager->sKv,pPager->pEngine->pIo->pMethods->zName,SyStrlen(pPager->pEngine->pIo->pMethods->zName)); + pPager->dbSize = 0; + } + /* Allocate a temporary page size */ + pPager->zTmpPage = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iPageSize); + if( pPager->zTmpPage == 0 ){ + unqliteGenOutofMem(pPager->pDb); + return UNQLITE_NOMEM; + } + SyZero(pPager->zTmpPage,(sxu32)pPager->iPageSize); + return UNQLITE_OK; +} +/* + * Write the database header. + */ +static int pager_create_header(Pager *pPager) +{ + Page *pHeader; + int rc; + /* Allocate a new page */ + pHeader = pager_alloc_page(pPager,0); + if( pHeader == 0 ){ + return UNQLITE_NOMEM; + } + pPager->pHeader = pHeader; + /* Link the page */ + pager_link_page(pPager,pHeader); + /* Add to the dirty list */ + pager_page_to_dirty_list(pPager,pHeader); + /* Write the database header */ + rc = pager_write_db_header(pPager); + return rc; +} +/* +** This function is called to obtain a shared lock on the database file. +** It is illegal to call unqlitePagerAcquire() until after this function +** has been successfully called. If a shared-lock is already held when +** this function is called, it is a no-op. +** +** The following operations are also performed by this function. +** +** 1) If the pager is currently in PAGER_OPEN state (no lock held +** on the database file), then an attempt is made to obtain a +** SHARED lock on the database file. Immediately after obtaining +** the SHARED lock, the file-system is checked for a hot-journal, +** which is played back if present. +** +** If everything is successful, UNQLITE_OK is returned. If an IO error +** occurs while locking the database, checking for a hot-journal file or +** rolling back a journal file, the IO error code is returned. +*/ +static int pager_shared_lock(Pager *pPager) +{ + int rc = UNQLITE_OK; + if( pPager->iState == PAGER_OPEN ){ + unqlite_kv_methods *pMethods; + /* Open the target database */ + rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zFilename,&pPager->pfd,pPager->iOpenFlags); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pPager->pDb, + "IO error while opening the target database file: %s",pPager->zFilename + ); + return rc; + } + /* Try to obtain a shared lock */ + rc = pager_wait_on_lock(pPager,SHARED_LOCK); + if( rc == UNQLITE_OK ){ + if( pPager->iLock <= SHARED_LOCK ){ + /* Rollback any hot journal */ + rc = pager_journal_rollback(pPager,1); + if( rc != UNQLITE_OK ){ + return rc; + } + } + /* Read the database header */ + rc = pager_read_db_header(pPager); + if( rc != UNQLITE_OK ){ + return rc; + } + if(pPager->dbSize > 0 ){ + if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){ + const jx9_vfs *pVfs = jx9ExportBuiltinVfs(); + /* Obtain a read-only memory view of the whole file */ + if( pVfs && pVfs->xMmap ){ + int vr; + vr = pVfs->xMmap(pPager->zFilename,&pPager->pMmap,&pPager->dbByteSize); + if( vr != JX9_OK ){ + /* Generate a warning */ + unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database"); + pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP; + } + }else{ + /* Generate a warning */ + unqliteGenError(pPager->pDb,"Cannot obtain a read-only memory view of the target database"); + pPager->iOpenFlags &= ~UNQLITE_OPEN_MMAP; + } + } + } + /* Update the pager state */ + pPager->iState = PAGER_READER; + /* Invoke the xOpen methods if available */ + pMethods = pPager->pEngine->pIo->pMethods; + if( pMethods->xOpen ){ + rc = pMethods->xOpen(pPager->pEngine,pPager->dbSize); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pPager->pDb, + "xOpen() method of the underlying KV engine '%z' failed", + &pPager->sKv + ); + pager_unlock_db(pPager,NO_LOCK); + pPager->iState = PAGER_OPEN; + return rc; + } + } + }else if( rc == UNQLITE_BUSY ){ + unqliteGenError(pPager->pDb,"Another process or thread have a reserved or exclusive lock on this database"); + } + } + return rc; +} +/* +** Begin a write-transaction on the specified pager object. If a +** write-transaction has already been opened, this function is a no-op. +*/ +UNQLITE_PRIVATE int unqlitePagerBegin(Pager *pPager) +{ + int rc; + /* Obtain a shared lock on the database first */ + rc = pager_shared_lock(pPager); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pPager->iState >= PAGER_WRITER_LOCKED ){ + return UNQLITE_OK; + } + if( pPager->is_rdonly ){ + unqliteGenError(pPager->pDb,"Read-only database"); + /* Read only database */ + return UNQLITE_READ_ONLY; + } + /* Obtain a reserved lock on the database */ + rc = pager_wait_on_lock(pPager,RESERVED_LOCK); + if( rc == UNQLITE_OK ){ + /* Create the bitvec */ + pPager->pVec = unqliteBitvecCreate(pPager->pAllocator,pPager->dbSize); + if( pPager->pVec == 0 ){ + unqliteGenOutofMem(pPager->pDb); + rc = UNQLITE_NOMEM; + goto fail; + } + /* Change to the WRITER_LOCK state */ + pPager->iState = PAGER_WRITER_LOCKED; + pPager->dbOrigSize = pPager->dbSize; + pPager->iJournalOfft = 0; + pPager->nRec = 0; + if( pPager->dbSize < 1 ){ + /* Write the database header */ + rc = pager_create_header(pPager); + if( rc != UNQLITE_OK ){ + goto fail; + } + pPager->dbSize = 1; + } + }else if( rc == UNQLITE_BUSY ){ + unqliteGenError(pPager->pDb,"Another process or thread have a reserved lock on this database"); + } + return rc; +fail: + /* Downgrade to shared lock */ + pager_unlock_db(pPager,SHARED_LOCK); + return rc; +} +/* +** This function is called at the start of every write transaction. +** There must already be a RESERVED or EXCLUSIVE lock on the database +** file when this routine is called. +** +*/ +static int unqliteOpenJournal(Pager *pPager) +{ + unsigned char *zHeader; + int rc = UNQLITE_OK; + if( pPager->is_mem || pPager->no_jrnl ){ + /* Journaling is omitted for this database */ + goto finish; + } + if( pPager->iState >= PAGER_WRITER_CACHEMOD ){ + /* Already opened */ + return UNQLITE_OK; + } + /* Delete any previously journal with the same name */ + unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); + /* Open the journal file */ + rc = unqliteOsOpen(pPager->pVfs,pPager->pAllocator,pPager->zJournal, + &pPager->pjfd,UNQLITE_OPEN_CREATE|UNQLITE_OPEN_READWRITE); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pPager->pDb,"IO error while opening journal file: %s",pPager->zJournal); + return rc; + } + /* Write the journal header */ + zHeader = (unsigned char *)SyMemBackendAlloc(pPager->pAllocator,(sxu32)pPager->iSectorSize); + if( zHeader == 0 ){ + rc = UNQLITE_NOMEM; + goto fail; + } + pager_write_journal_header(pPager,zHeader); + /* Perform the disk write */ + rc = unqliteOsWrite(pPager->pjfd,zHeader,pPager->iSectorSize,0); + /* Offset to start writing from */ + pPager->iJournalOfft = pPager->iSectorSize; + /* All done, journal will be synced later */ + SyMemBackendFree(pPager->pAllocator,zHeader); +finish: + if( rc == UNQLITE_OK ){ + pPager->iState = PAGER_WRITER_CACHEMOD; + return UNQLITE_OK; + } +fail: + /* Unlink the journal file if something goes wrong */ + unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); + unqliteOsDelete(pPager->pVfs,pPager->zJournal,0); + pPager->pjfd = 0; + return rc; +} +/* +** Sync the journal. In other words, make sure all the pages that have +** been written to the journal have actually reached the surface of the +** disk and can be restored in the event of a hot-journal rollback. +* +* This routine try also to obtain an exlusive lock on the database. +*/ +static int unqliteFinalizeJournal(Pager *pPager,int *pRetry,int close_jrnl) +{ + int rc; + *pRetry = 0; + /* Grab the exclusive lock first */ + rc = pager_lock_db(pPager,EXCLUSIVE_LOCK); + if( rc != UNQLITE_OK ){ + /* Retry the excusive lock process */ + *pRetry = 1; + rc = UNQLITE_OK; + } + if( pPager->no_jrnl ){ + /* Journaling is omitted, return immediately */ + return UNQLITE_OK; + } + /* Write the total number of database records */ + rc = WriteInt32(pPager->pjfd,pPager->nRec,8 /* sizeof(aJournalRec) */); + if( rc != UNQLITE_OK ){ + if( pPager->nRec > 0 ){ + return rc; + }else{ + /* Not so fatal */ + rc = UNQLITE_OK; + } + } + /* Sync the journal and close it */ + rc = unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); + if( close_jrnl ){ + /* close the journal file */ + if( UNQLITE_OK != unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd) ){ + if( rc != UNQLITE_OK /* unqliteOsSync */ ){ + return rc; + } + } + pPager->pjfd = 0; + } + if( (*pRetry) == 1 ){ + if( pager_lock_db(pPager,EXCLUSIVE_LOCK) == UNQLITE_OK ){ + /* Got exclusive lock */ + *pRetry = 0; + } + } + return UNQLITE_OK; +} +/* + * Mark a single data page as writeable. The page is written into the + * main journal as required. + */ +static int page_write(Pager *pPager,Page *pPage) +{ + int rc; + if( !pPager->is_mem && !pPager->no_jrnl ){ + /* Write the page to the transaction journal */ + if( pPage->pgno < pPager->dbOrigSize && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){ + sxu32 cksum; + if( pPager->nRec == SXU32_HIGH ){ + /* Journal Limit reached */ + unqliteGenError(pPager->pDb,"Journal record limit reached, commit your changes"); + return UNQLITE_LIMIT; + } + /* Write the page number */ + rc = WriteInt64(pPager->pjfd,pPage->pgno,pPager->iJournalOfft); + if( rc != UNQLITE_OK ){ return rc; } + /* Write the raw page */ + /** CODEC */ + rc = unqliteOsWrite(pPager->pjfd,pPage->zData,pPager->iPageSize,pPager->iJournalOfft + 8); + if( rc != UNQLITE_OK ){ return rc; } + /* Compute the checksum */ + cksum = pager_cksum(pPager,pPage->zData); + rc = WriteInt32(pPager->pjfd,cksum,pPager->iJournalOfft + 8 + pPager->iPageSize); + if( rc != UNQLITE_OK ){ return rc; } + /* Update the journal offset */ + pPager->iJournalOfft += 8 /* page num */ + pPager->iPageSize + 4 /* cksum */; + pPager->nRec++; + /* Mark as journalled */ + unqliteBitvecSet(pPager->pVec,pPage->pgno); + } + } + /* Add the page to the dirty list */ + pager_page_to_dirty_list(pPager,pPage); + /* Update the database size and return. */ + if( (1 + pPage->pgno) > pPager->dbSize ){ + pPager->dbSize = 1 + pPage->pgno; + if( pPager->dbSize == SXU64_HIGH ){ + unqliteGenError(pPager->pDb,"Database maximum page limit (64-bit) reached"); + return UNQLITE_LIMIT; + } + } + return UNQLITE_OK; +} +/* +** The argument is the first in a linked list of dirty pages connected +** by the PgHdr.pDirty pointer. This function writes each one of the +** in-memory pages in the list to the database file. The argument may +** be NULL, representing an empty list. In this case this function is +** a no-op. +** +** The pager must hold at least a RESERVED lock when this function +** is called. Before writing anything to the database file, this lock +** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, +** UNQLITE_BUSY is returned and no data is written to the database file. +*/ +static int pager_write_dirty_pages(Pager *pPager,Page *pDirty) +{ + int rc = UNQLITE_OK; + Page *pNext; + for(;;){ + if( pDirty == 0 ){ + break; + } + /* Point to the next dirty page */ + pNext = pDirty->pDirtyPrev; /* Not a bug: Reverse link */ + if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){ + rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize); + if( rc != UNQLITE_OK ){ + /* A rollback should be done */ + break; + } + } + /* Remove stale flags */ + pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); + if( pDirty->nRef < 1 ){ + /* Unlink the page now it is unused */ + pager_unlink_page(pPager,pDirty); + /* Release the page */ + pager_release_page(pPager,pDirty); + } + /* Point to the next page */ + pDirty = pNext; + } + pPager->pDirty = pPager->pFirstDirty = 0; + pPager->pHotDirty = pPager->pFirstHot = 0; + pPager->nHot = 0; + return rc; +} +/* +** The argument is the first in a linked list of hot dirty pages connected +** by the PgHdr.pHotDirty pointer. This function writes each one of the +** in-memory pages in the list to the database file. The argument may +** be NULL, representing an empty list. In this case this function is +** a no-op. +** +** The pager must hold at least a RESERVED lock when this function +** is called. Before writing anything to the database file, this lock +** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, +** UNQLITE_BUSY is returned and no data is written to the database file. +*/ +static int pager_write_hot_dirty_pages(Pager *pPager,Page *pDirty) +{ + int rc = UNQLITE_OK; + Page *pNext; + for(;;){ + if( pDirty == 0 ){ + break; + } + /* Point to the next page */ + pNext = pDirty->pPrevHot; /* Not a bug: Reverse link */ + if( (pDirty->flags & PAGE_DONT_WRITE) == 0 ){ + rc = unqliteOsWrite(pPager->pfd,pDirty->zData,pPager->iPageSize,pDirty->pgno * pPager->iPageSize); + if( rc != UNQLITE_OK ){ + break; + } + } + /* Remove stale flags */ + pDirty->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); + /* Unlink from the list of dirty pages */ + if( pDirty->pDirtyPrev ){ + pDirty->pDirtyPrev->pDirtyNext = pDirty->pDirtyNext; + }else{ + pPager->pDirty = pDirty->pDirtyNext; + } + if( pDirty->pDirtyNext ){ + pDirty->pDirtyNext->pDirtyPrev = pDirty->pDirtyPrev; + }else{ + pPager->pFirstDirty = pDirty->pDirtyPrev; + } + /* Discard */ + pager_unlink_page(pPager,pDirty); + /* Release the page */ + pager_release_page(pPager,pDirty); + /* Next hot page */ + pDirty = pNext; + } + return rc; +} +/* + * Commit a transaction: Phase one. + */ +static int pager_commit_phase1(Pager *pPager) +{ + int get_excl = 0; + Page *pDirty; + int rc; + /* If no database changes have been made, return early. */ + if( pPager->iState < PAGER_WRITER_CACHEMOD ){ + return UNQLITE_OK; + } + if( pPager->is_mem ){ + /* An in-memory database */ + return UNQLITE_OK; + } + if( pPager->is_rdonly ){ + /* Read-Only DB */ + unqliteGenError(pPager->pDb,"Read-Only database"); + return UNQLITE_READ_ONLY; + } + /* Finalize the journal file */ + rc = unqliteFinalizeJournal(pPager,&get_excl,1); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Get the dirty pages */ + pDirty = pager_get_dirty_pages(pPager); + if( get_excl ){ + /* Wait one last time for the exclusive lock */ + rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK); + if( rc != UNQLITE_OK ){ + unqliteGenError(pPager->pDb,"Cannot obtain an Exclusive lock on the target database"); + return rc; + } + } + if( pPager->iFlags & PAGER_CTRL_DIRTY_COMMIT ){ + /* Sync the database first if a dirty commit have been applied */ + unqliteOsSync(pPager->pfd,UNQLITE_SYNC_NORMAL); + } + /* Write the dirty pages */ + rc = pager_write_dirty_pages(pPager,pDirty); + if( rc != UNQLITE_OK ){ + /* Rollback your DB */ + pPager->iFlags |= PAGER_CTRL_COMMIT_ERR; + pPager->pFirstDirty = pDirty; + unqliteGenError(pPager->pDb,"IO error while writing dirty pages, rollback your database"); + return rc; + } + /* release all pages */ + { + Page *p; + + while (1) { + p = pPager->pAll; + if (p == 0) { + break; + } + pager_unlink_page(pPager, p); + pager_release_page(pPager, p); + } + } + /* If the file on disk is not the same size as the database image, + * then use unqliteOsTruncate to grow or shrink the file here. + */ + if( pPager->dbSize != pPager->dbOrigSize ){ + unqliteOsTruncate(pPager->pfd,pPager->iPageSize * pPager->dbSize); + } + /* Sync the database file */ + unqliteOsSync(pPager->pfd,UNQLITE_SYNC_FULL); + /* Remove stale flags */ + pPager->iJournalOfft = 0; + pPager->nRec = 0; + return UNQLITE_OK; +} +/* + * Commit a transaction: Phase two. + */ +static int pager_commit_phase2(Pager *pPager) +{ + if( !pPager->is_mem ){ + if( pPager->iState == PAGER_OPEN ){ + return UNQLITE_OK; + } + if( pPager->iState != PAGER_READER ){ + if( !pPager->no_jrnl ){ + /* Finally, unlink the journal file */ + unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); + } + /* Downgrade to shared lock */ + pager_unlock_db(pPager,SHARED_LOCK); + pPager->iState = PAGER_READER; + if( pPager->pVec ){ + unqliteBitvecDestroy(pPager->pVec); + pPager->pVec = 0; + } + } + } + return UNQLITE_OK; +} +/* + * Perform a dirty commit. + */ +static int pager_dirty_commit(Pager *pPager) +{ + int get_excl = 0; + Page *pHot; + int rc; + /* Finalize the journal file without closing it */ + rc = unqliteFinalizeJournal(pPager,&get_excl,0); + if( rc != UNQLITE_OK ){ + /* It's not a fatal error if something goes wrong here since + * its not the final commit. + */ + return UNQLITE_OK; + } + /* Point to the list of hot pages */ + pHot = pager_get_hot_pages(pPager); + if( pHot == 0 ){ + return UNQLITE_OK; + } + if( get_excl ){ + /* Wait one last time for the exclusive lock */ + rc = pager_wait_on_lock(pPager,EXCLUSIVE_LOCK); + if( rc != UNQLITE_OK ){ + /* Not so fatal, will try another time */ + return UNQLITE_OK; + } + } + /* Tell that a dirty commit happen */ + pPager->iFlags |= PAGER_CTRL_DIRTY_COMMIT; + /* Write the hot pages now */ + rc = pager_write_hot_dirty_pages(pPager,pHot); + if( rc != UNQLITE_OK ){ + pPager->iFlags |= PAGER_CTRL_COMMIT_ERR; + unqliteGenError(pPager->pDb,"IO error while writing hot dirty pages, rollback your database"); + return rc; + } + pPager->pFirstHot = pPager->pHotDirty = 0; + pPager->nHot = 0; + /* No need to sync the database file here, since the journal is already + * open here and this is not the final commit. + */ + return UNQLITE_OK; +} +/* +** Commit a transaction and sync the database file for the pager pPager. +** +** This routine ensures that: +** +** * the journal is synced, +** * all dirty pages are written to the database file, +** * the database file is truncated (if required), and +** * the database file synced. +** * the journal file is deleted. +*/ +UNQLITE_PRIVATE int unqlitePagerCommit(Pager *pPager) +{ + int rc; + /* Commit: Phase One */ + rc = pager_commit_phase1(pPager); + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Commit: Phase Two */ + rc = pager_commit_phase2(pPager); + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Remove stale flags */ + pPager->iFlags &= ~PAGER_CTRL_COMMIT_ERR; + /* All done */ + return UNQLITE_OK; +fail: + /* Disable the auto-commit flag */ + pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; + return rc; +} +/* + * Reset the pager to its initial state. This is caused by + * a rollback operation. + */ +static int pager_reset_state(Pager *pPager,int bResetKvEngine) +{ + unqlite_kv_engine *pEngine = pPager->pEngine; + Page *pNext,*pPtr = pPager->pAll; + const unqlite_kv_io *pIo; + int rc; + /* Remove stale flags */ + pPager->iFlags &= ~(PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT); + pPager->iJournalOfft = 0; + pPager->nRec = 0; + /* Database original size */ + pPager->dbSize = pPager->dbOrigSize; + /* Discard all in-memory pages */ + for(;;){ + if( pPtr == 0 ){ + break; + } + pNext = pPtr->pNext; /* Reverse link */ + /* Remove stale flags */ + pPtr->flags &= ~(PAGE_DIRTY|PAGE_DONT_WRITE|PAGE_NEED_SYNC|PAGE_IN_JOURNAL|PAGE_HOT_DIRTY); + /* Release the page */ + pager_release_page(pPager,pPtr); + /* Point to the next page */ + pPtr = pNext; + } + pPager->pAll = 0; + pPager->nPage = 0; + pPager->pDirty = pPager->pFirstDirty = 0; + pPager->pHotDirty = pPager->pFirstHot = 0; + pPager->nHot = 0; + if( pPager->apHash ){ + /* Zero the table */ + SyZero((void *)pPager->apHash,sizeof(Page *) * pPager->nSize); + } + if( pPager->pVec ){ + unqliteBitvecDestroy(pPager->pVec); + pPager->pVec = 0; + } + /* Switch back to shared lock */ + pager_unlock_db(pPager,SHARED_LOCK); + pPager->iState = PAGER_READER; + if( bResetKvEngine ){ + /* Reset the underlying KV engine */ + pIo = pEngine->pIo; + if( pIo->pMethods->xRelease ){ + /* Call the release callback */ + pIo->pMethods->xRelease(pEngine); + } + /* Zero the structure */ + SyZero(pEngine,(sxu32)pIo->pMethods->szKv); + /* Fill in */ + pEngine->pIo = pIo; + if( pIo->pMethods->xInit ){ + /* Call the init method */ + rc = pIo->pMethods->xInit(pEngine,pPager->iPageSize); + if( rc != UNQLITE_OK ){ + return rc; + } + } + if( pIo->pMethods->xOpen ){ + /* Call the xOpen method */ + rc = pIo->pMethods->xOpen(pEngine,pPager->dbSize); + if( rc != UNQLITE_OK ){ + return rc; + } + } + } + /* All done */ + return UNQLITE_OK; +} +/* +** If a write transaction is open, then all changes made within the +** transaction are reverted and the current write-transaction is closed. +** The pager falls back to PAGER_READER state if successful. +** +** Otherwise, in rollback mode, this function performs two functions: +** +** 1) It rolls back the journal file, restoring all database file and +** in-memory cache pages to the state they were in when the transaction +** was opened, and +** +** 2) It finalizes the journal file, so that it is not used for hot +** rollback at any point in the future (i.e. deletion). +** +** Finalization of the journal file (task 2) is only performed if the +** rollback is successful. +** +*/ +UNQLITE_PRIVATE int unqlitePagerRollback(Pager *pPager,int bResetKvEngine) +{ + int rc = UNQLITE_OK; + if( pPager->iState < PAGER_WRITER_LOCKED ){ + /* A write transaction must be opened */ + return UNQLITE_OK; + } + if( pPager->is_mem ){ + /* As of this release 1.1.6: Transactions are not supported for in-memory databases */ + return UNQLITE_OK; + } + if( pPager->is_rdonly ){ + /* Read-Only DB */ + unqliteGenError(pPager->pDb,"Read-Only database"); + return UNQLITE_READ_ONLY; + } + if( pPager->iState >= PAGER_WRITER_CACHEMOD ){ + if( !pPager->no_jrnl ){ + /* Close any outstanding joural file */ + if( pPager->pjfd ){ + /* Sync the journal file */ + unqliteOsSync(pPager->pjfd,UNQLITE_SYNC_NORMAL); + } + unqliteOsCloseFree(pPager->pAllocator,pPager->pjfd); + pPager->pjfd = 0; + if( pPager->iFlags & (PAGER_CTRL_COMMIT_ERR|PAGER_CTRL_DIRTY_COMMIT) ){ + /* Perform the rollback */ + rc = pager_journal_rollback(pPager,0); + if( rc != UNQLITE_OK ){ + /* Set the auto-commit flag */ + pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; + return rc; + } + } + } + /* Unlink the journal file */ + unqliteOsDelete(pPager->pVfs,pPager->zJournal,1); + /* Reset the pager state */ + rc = pager_reset_state(pPager,bResetKvEngine); + if( rc != UNQLITE_OK ){ + /* Mostly an unlikely scenario */ + pPager->pDb->iFlags |= UNQLITE_FL_DISABLE_AUTO_COMMIT; /* Set the auto-commit flag */ + unqliteGenError(pPager->pDb,"Error while reseting pager to its initial state"); + return rc; + } + }else{ + /* Downgrade to shared lock */ + pager_unlock_db(pPager,SHARED_LOCK); + pPager->iState = PAGER_READER; + } + return UNQLITE_OK; +} +/* + * Mark a data page as non writeable. + */ +static int unqlitePagerDontWrite(unqlite_page *pMyPage) +{ + Page *pPage = (Page *)pMyPage; + if( pPage->pgno > 0 /* Page 0 is always writeable */ ){ + pPage->flags |= PAGE_DONT_WRITE; + } + return UNQLITE_OK; +} +/* +** Mark a data page as writeable. This routine must be called before +** making changes to a page. The caller must check the return value +** of this function and be careful not to change any page data unless +** this routine returns UNQLITE_OK. +*/ +static int unqlitePageWrite(unqlite_page *pMyPage) +{ + Page *pPage = (Page *)pMyPage; + Pager *pPager = pPage->pPager; + int rc; + /* Begin the write transaction */ + rc = unqlitePagerBegin(pPager); + if( rc != UNQLITE_OK ){ + return rc; + } + if( pPager->iState == PAGER_WRITER_LOCKED ){ + /* The journal file needs to be opened. Higher level routines have already + ** obtained the necessary locks to begin the write-transaction, but the + ** rollback journal might not yet be open. Open it now if this is the case. + */ + rc = unqliteOpenJournal(pPager); + if( rc != UNQLITE_OK ){ + return rc; + } + } + if( pPager->nHot > 127 ){ + /* Write hot dirty pages */ + rc = pager_dirty_commit(pPager); + if( rc != UNQLITE_OK ){ + /* A rollback must be done */ + unqliteGenError(pPager->pDb,"Please perform a rollback"); + return rc; + } + } + /* Write the page to the journal file */ + rc = page_write(pPager,pPage); + return rc; +} +/* +** Acquire a reference to page number pgno in pager pPager (a page +** reference has type unqlite_page*). If the requested reference is +** successfully obtained, it is copied to *ppPage and UNQLITE_OK returned. +** +** If the requested page is already in the cache, it is returned. +** Otherwise, a new page object is allocated and populated with data +** read from the database file. +*/ +static int unqlitePagerAcquire( + Pager *pPager, /* The pager open on the database file */ + pgno pgno, /* Page number to fetch */ + unqlite_page **ppPage, /* OUT: Acquired page */ + int fetchOnly, /* Cache lookup only */ + int noContent /* Do not bother reading content from disk if true */ +) +{ + Page *pPage; + int rc; + /* Acquire a shared lock (if not yet done) on the database and rollback any hot-journal if present */ + rc = pager_shared_lock(pPager); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Fetch the page from the cache */ + pPage = pager_fetch_page(pPager,pgno); + if( fetchOnly ){ + if( ppPage ){ + *ppPage = (unqlite_page *)pPage; + } + return pPage ? UNQLITE_OK : UNQLITE_NOTFOUND; + } + if( pPage == 0 ){ + /* Allocate a new page */ + pPage = pager_alloc_page(pPager,pgno); + if( pPage == 0 ){ + unqliteGenOutofMem(pPager->pDb); + return UNQLITE_NOMEM; + } + /* Read page contents */ + rc = pager_get_page_contents(pPager,pPage,noContent); + if( rc != UNQLITE_OK ){ + SyMemBackendPoolFree(pPager->pAllocator,pPage); + return rc; + } + /* Link the page */ + pager_link_page(pPager,pPage); + }else{ + if( ppPage ){ + page_ref(pPage); + } + } + /* All done, page is loaded in memeory */ + if( ppPage ){ + *ppPage = (unqlite_page *)pPage; + } + return UNQLITE_OK; +} +/* + * Return true if we are dealing with an in-memory database. + */ +static int unqliteInMemory(const char *zFilename) +{ + sxu32 n; + if( SX_EMPTY_STR(zFilename) ){ + /* NULL or the empty string means an in-memory database */ + return TRUE; + } + n = SyStrlen(zFilename); + if( n == sizeof(":mem:") - 1 && + SyStrnicmp(zFilename,":mem:",sizeof(":mem:") - 1) == 0 ){ + return TRUE; + } + if( n == sizeof(":memory:") - 1 && + SyStrnicmp(zFilename,":memory:",sizeof(":memory:") - 1) == 0 ){ + return TRUE; + } + return FALSE; +} +/* + * Allocate a new KV cursor. + */ +UNQLITE_PRIVATE int unqliteInitCursor(unqlite *pDb,unqlite_kv_cursor **ppOut) +{ + unqlite_kv_methods *pMethods; + unqlite_kv_cursor *pCur; + sxu32 nByte; + /* Storage engine methods */ + pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods; + if( pMethods->szCursor < 1 ){ + /* Implementation does not supprt cursors */ + unqliteGenErrorFormat(pDb,"Storage engine '%s' does not support cursors",pMethods->zName); + return UNQLITE_NOTIMPLEMENTED; + } + nByte = pMethods->szCursor; + if( nByte < sizeof(unqlite_kv_cursor) ){ + nByte += sizeof(unqlite_kv_cursor); + } + pCur = (unqlite_kv_cursor *)SyMemBackendPoolAlloc(&pDb->sMem,nByte); + if( pCur == 0 ){ + unqliteGenOutofMem(pDb); + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pCur,nByte); + /* Save the cursor */ + pCur->pStore = pDb->sDB.pPager->pEngine; + /* Invoke the initialization callback if any */ + if( pMethods->xCursorInit ){ + pMethods->xCursorInit(pCur); + } + /* All done */ + *ppOut = pCur; + return UNQLITE_OK; +} +/* + * Release a cursor. + */ +UNQLITE_PRIVATE int unqliteReleaseCursor(unqlite *pDb,unqlite_kv_cursor *pCur) +{ + unqlite_kv_methods *pMethods; + /* Storage engine methods */ + pMethods = pDb->sDB.pPager->pEngine->pIo->pMethods; + /* Invoke the release callback if available */ + if( pMethods->xCursorRelease ){ + pMethods->xCursorRelease(pCur); + } + /* Finally, free the whole instance */ + SyMemBackendPoolFree(&pDb->sMem,pCur); + return UNQLITE_OK; +} +/* + * Release the underlying KV storage engine and invoke + * its associated callbacks if available. + */ +static void pager_release_kv_engine(Pager *pPager) +{ + unqlite_kv_engine *pEngine = pPager->pEngine; + unqlite_db *pStorage = &pPager->pDb->sDB; + if( pStorage->pCursor ){ + /* Release the associated cursor */ + unqliteReleaseCursor(pPager->pDb,pStorage->pCursor); + pStorage->pCursor = 0; + } + if( pEngine->pIo->pMethods->xRelease ){ + pEngine->pIo->pMethods->xRelease(pEngine); + } + /* Release the whole instance */ + SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine->pIo); + SyMemBackendFree(&pPager->pDb->sMem,(void *)pEngine); + pPager->pEngine = 0; +} +/* Forward declaration */ +static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo); +/* + * Allocate, initialize and register a new KV storage engine + * within this database instance. + */ +UNQLITE_PRIVATE int unqlitePagerRegisterKvEngine(Pager *pPager,unqlite_kv_methods *pMethods) +{ + unqlite_db *pStorage = &pPager->pDb->sDB; + unqlite *pDb = pPager->pDb; + unqlite_kv_engine *pEngine; + unqlite_kv_io *pIo; + sxu32 nByte; + int rc; + if( pPager->pEngine ){ + if( pMethods == pPager->pEngine->pIo->pMethods ){ + /* Ticket 1432: Same implementation */ + return UNQLITE_OK; + } + /* Release the old KV engine */ + pager_release_kv_engine(pPager); + } + /* Allocate a new KV engine instance */ + nByte = (sxu32)pMethods->szKv; + pEngine = (unqlite_kv_engine *)SyMemBackendAlloc(&pDb->sMem,nByte); + if( pEngine == 0 ){ + unqliteGenOutofMem(pDb); + return UNQLITE_NOMEM; + } + pIo = (unqlite_kv_io *)SyMemBackendAlloc(&pDb->sMem,sizeof(unqlite_kv_io)); + if( pIo == 0 ){ + SyMemBackendFree(&pDb->sMem,pEngine); + unqliteGenOutofMem(pDb); + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pIo,sizeof(unqlite_io_methods)); + SyZero(pEngine,nByte); + /* Populate the IO structure */ + pager_kv_io_init(pPager,pMethods,pIo); + pEngine->pIo = pIo; + /* Invoke the init callback if avaialble */ + if( pMethods->xInit ){ + rc = pMethods->xInit(pEngine,unqliteGetPageSize()); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pDb, + "xInit() method of the underlying KV engine '%z' failed",&pPager->sKv); + goto fail; + } + pEngine->pIo = pIo; + } + pPager->pEngine = pEngine; + /* Allocate a new cursor */ + rc = unqliteInitCursor(pDb,&pStorage->pCursor); + if( rc != UNQLITE_OK ){ + goto fail; + } + return UNQLITE_OK; +fail: + SyMemBackendFree(&pDb->sMem,pEngine); + SyMemBackendFree(&pDb->sMem,pIo); + return rc; +} +/* + * Return the underlying KV storage engine instance. + */ +UNQLITE_PRIVATE unqlite_kv_engine * unqlitePagerGetKvEngine(unqlite *pDb) +{ + return pDb->sDB.pPager->pEngine; +} +/* +* Allocate and initialize a new Pager object. The pager should +* eventually be freed by passing it to unqlitePagerClose(). +* +* The zFilename argument is the path to the database file to open. +* If zFilename is NULL or ":memory:" then all information is held +* in cache. It is never written to disk. This can be used to implement +* an in-memory database. +*/ +UNQLITE_PRIVATE int unqlitePagerOpen( + unqlite_vfs *pVfs, /* The virtual file system to use */ + unqlite *pDb, /* Database handle */ + const char *zFilename, /* Name of the database file to open */ + unsigned int iFlags /* flags controlling this file */ + ) +{ + unqlite_kv_methods *pMethods = 0; + int is_mem,rd_only,no_jrnl; + Pager *pPager; + sxu32 nByte; + sxu32 nLen; + int rc; + + /* Select the appropriate KV storage subsytem */ + if( (iFlags & UNQLITE_OPEN_IN_MEMORY) || unqliteInMemory(zFilename) ){ + /* An in-memory database, record that */ + pMethods = unqliteFindKVStore("mem",sizeof("mem") - 1); /* Always available */ + iFlags |= UNQLITE_OPEN_IN_MEMORY; + }else{ + /* Install the default key value storage subsystem [i.e. Linear Hash] */ + pMethods = unqliteFindKVStore("hash",sizeof("hash")-1); + if( pMethods == 0 ){ + /* Use the b+tree storage backend if the linear hash storage is not available */ + pMethods = unqliteFindKVStore("btree",sizeof("btree")-1); + } + } + if( pMethods == 0 ){ + /* Can't happen */ + unqliteGenError(pDb,"Cannot install a default Key/Value storage engine"); + return UNQLITE_NOTIMPLEMENTED; + } + is_mem = (iFlags & UNQLITE_OPEN_IN_MEMORY) != 0; + rd_only = (iFlags & UNQLITE_OPEN_READONLY) != 0; + no_jrnl = (iFlags & UNQLITE_OPEN_OMIT_JOURNALING) != 0; + rc = UNQLITE_OK; + if( is_mem ){ + /* Omit journaling for in-memory database */ + no_jrnl = 1; + } + /* Total number of bytes to allocate */ + nByte = sizeof(Pager); + nLen = 0; + if( !is_mem ){ + nLen = SyStrlen(zFilename); + nByte += pVfs->mxPathname + nLen + sizeof(char) /* null termniator */; + } + /* Allocate */ + pPager = (Pager *)SyMemBackendAlloc(&pDb->sMem,nByte); + if( pPager == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pPager,nByte); + /* Fill-in the structure */ + pPager->pAllocator = &pDb->sMem; + pPager->pDb = pDb; + pDb->sDB.pPager = pPager; + /* Allocate page table */ + pPager->nSize = 128; /* Must be a power of two */ + nByte = pPager->nSize * sizeof(Page *); + pPager->apHash = (Page **)SyMemBackendAlloc(pPager->pAllocator,nByte); + if( pPager->apHash == 0 ){ + rc = UNQLITE_NOMEM; + goto fail; + } + SyZero(pPager->apHash,nByte); + pPager->is_mem = is_mem; + pPager->no_jrnl = no_jrnl; + pPager->is_rdonly = rd_only; + pPager->iOpenFlags = iFlags; + pPager->pVfs = pVfs; + SyRandomnessInit(&pPager->sPrng,0,0); + SyRandomness(&pPager->sPrng,(void *)&pPager->cksumInit,sizeof(sxu32)); + /* Unlimited cache size */ + pPager->nCacheMax = SXU32_HIGH; + /* Copy filename and journal name */ + if( !is_mem ){ + pPager->zFilename = (char *)&pPager[1]; + rc = UNQLITE_OK; + if( pVfs->xFullPathname ){ + rc = pVfs->xFullPathname(pVfs,zFilename,pVfs->mxPathname + nLen,pPager->zFilename); + } + if( rc != UNQLITE_OK ){ + /* Simple filename copy */ + SyMemcpy(zFilename,pPager->zFilename,nLen); + pPager->zFilename[nLen] = 0; + rc = UNQLITE_OK; + }else{ + nLen = SyStrlen(pPager->zFilename); + } + pPager->zJournal = (char *) SyMemBackendAlloc(pPager->pAllocator,nLen + sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) + sizeof(char)); + if( pPager->zJournal == 0 ){ + rc = UNQLITE_NOMEM; + goto fail; + } + /* Copy filename */ + SyMemcpy(pPager->zFilename,pPager->zJournal,nLen); + /* Copy journal suffix */ + SyMemcpy(UNQLITE_JOURNAL_FILE_SUFFIX,&pPager->zJournal[nLen],sizeof(UNQLITE_JOURNAL_FILE_SUFFIX)-1); + /* Append the nul terminator to the journal path */ + pPager->zJournal[nLen + ( sizeof(UNQLITE_JOURNAL_FILE_SUFFIX) - 1)] = 0; + } + /* Finally, register the selected KV engine */ + rc = unqlitePagerRegisterKvEngine(pPager,pMethods); + if( rc != UNQLITE_OK ){ + goto fail; + } + /* Set the pager state */ + if( pPager->is_mem ){ + pPager->iState = PAGER_WRITER_FINISHED; + pPager->iLock = EXCLUSIVE_LOCK; + }else{ + pPager->iState = PAGER_OPEN; + pPager->iLock = NO_LOCK; + } + /* All done, ready for processing */ + return UNQLITE_OK; +fail: + SyMemBackendFree(&pDb->sMem,pPager); + return rc; +} +/* + * Set a cache limit. Note that, this is a simple hint, the pager is not + * forced to honor this limit. + */ +UNQLITE_PRIVATE int unqlitePagerSetCachesize(Pager *pPager,int mxPage) +{ + if( mxPage < 256 ){ + return UNQLITE_INVALID; + } + pPager->nCacheMax = mxPage; + return UNQLITE_OK; +} +/* + * Shutdown the page cache. Free all memory and close the database file. + */ +UNQLITE_PRIVATE int unqlitePagerClose(Pager *pPager) +{ + /* Release the KV engine */ + pager_release_kv_engine(pPager); + if( pPager->iOpenFlags & UNQLITE_OPEN_MMAP ){ + const jx9_vfs *pVfs = jx9ExportBuiltinVfs(); + if( pVfs && pVfs->xUnmap && pPager->pMmap ){ + pVfs->xUnmap(pPager->pMmap,pPager->dbByteSize); + } + } + if( !pPager->is_mem && pPager->iState > PAGER_OPEN ){ + /* Release all lock on this database handle */ + pager_unlock_db(pPager,NO_LOCK); + /* Close the file */ + unqliteOsCloseFree(pPager->pAllocator,pPager->pfd); + } + if( pPager->pVec ){ + unqliteBitvecDestroy(pPager->pVec); + pPager->pVec = 0; + } + return UNQLITE_OK; +} +/* + * Generate a random string. + */ +UNQLITE_PRIVATE void unqlitePagerRandomString(Pager *pPager,char *zBuf,sxu32 nLen) +{ + static const char zBase[] = {"abcdefghijklmnopqrstuvwxyz"}; /* English Alphabet */ + sxu32 i; + /* Generate a binary string first */ + SyRandomness(&pPager->sPrng,zBuf,nLen); + /* Turn the binary string into english based alphabet */ + for( i = 0 ; i < nLen ; ++i ){ + zBuf[i] = zBase[zBuf[i] % (sizeof(zBase)-1)]; + } +} +/* + * Generate a random number. + */ +UNQLITE_PRIVATE sxu32 unqlitePagerRandomNum(Pager *pPager) +{ + sxu32 iNum; + SyRandomness(&pPager->sPrng,(void *)&iNum,sizeof(iNum)); + return iNum; +} +/* Exported KV IO Methods */ +/* + * Refer to [unqlitePagerAcquire()] + */ +static int unqliteKvIoPageGet(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage) +{ + int rc; + rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,0,0); + return rc; +} +/* + * Refer to [unqlitePagerAcquire()] + */ +static int unqliteKvIoPageLookup(unqlite_kv_handle pHandle,pgno iNum,unqlite_page **ppPage) +{ + int rc; + rc = unqlitePagerAcquire((Pager *)pHandle,iNum,ppPage,1,0); + return rc; +} +/* + * Refer to [unqlitePagerAcquire()] + */ +static int unqliteKvIoNewPage(unqlite_kv_handle pHandle,unqlite_page **ppPage) +{ + Pager *pPager = (Pager *)pHandle; + int rc; + /* + * Acquire a reader-lock first so that pPager->dbSize get initialized. + */ + rc = pager_shared_lock(pPager); + if( rc == UNQLITE_OK ){ + rc = unqlitePagerAcquire(pPager,pPager->dbSize == 0 ? /* Page 0 is reserved */ 1 : pPager->dbSize ,ppPage,0,0); + } + return rc; +} +/* + * Refer to [unqlitePageWrite()] + */ +static int unqliteKvIopageWrite(unqlite_page *pPage) +{ + int rc; + if( pPage == 0 ){ + /* TICKET 1433-0348 */ + return UNQLITE_OK; + } + rc = unqlitePageWrite(pPage); + return rc; +} +/* + * Refer to [unqlitePagerDontWrite()] + */ +static int unqliteKvIoPageDontWrite(unqlite_page *pPage) +{ + int rc; + if( pPage == 0 ){ + /* TICKET 1433-0348 */ + return UNQLITE_OK; + } + rc = unqlitePagerDontWrite(pPage); + return rc; +} +/* + * Refer to [unqliteBitvecSet()] + */ +static int unqliteKvIoPageDontJournal(unqlite_page *pRaw) +{ + Page *pPage = (Page *)pRaw; + Pager *pPager; + if( pPage == 0 ){ + /* TICKET 1433-0348 */ + return UNQLITE_OK; + } + pPager = pPage->pPager; + if( pPager->iState >= PAGER_WRITER_LOCKED ){ + if( !pPager->no_jrnl && pPager->pVec && !unqliteBitvecTest(pPager->pVec,pPage->pgno) ){ + unqliteBitvecSet(pPager->pVec,pPage->pgno); + } + } + return UNQLITE_OK; +} +/* + * Do not add a page to the hot dirty list. + */ +static int unqliteKvIoPageDontMakeHot(unqlite_page *pRaw) +{ + Page *pPage = (Page *)pRaw; + + if( pPage == 0 ){ + /* TICKET 1433-0348 */ + return UNQLITE_OK; + } + pPage->flags |= PAGE_DONT_MAKE_HOT; + + /* Remove from hot dirty list if it is already there */ + if( pPage->flags & PAGE_HOT_DIRTY ){ + Pager *pPager = pPage->pPager; + if( pPage->pNextHot ){ + pPage->pNextHot->pPrevHot = pPage->pPrevHot; + } + if( pPage->pPrevHot ){ + pPage->pPrevHot->pNextHot = pPage->pNextHot; + } + if( pPager->pFirstHot == pPage ){ + pPager->pFirstHot = pPage->pPrevHot; + } + if( pPager->pHotDirty == pPage ){ + pPager->pHotDirty = pPage->pNextHot; + } + pPager->nHot--; + pPage->flags &= ~PAGE_HOT_DIRTY; + } + + return UNQLITE_OK; +} +/* + * Refer to [page_ref()] + */ +static int unqliteKvIopage_ref(unqlite_page *pPage) +{ + if( pPage ){ + page_ref((Page *)pPage); + } + return UNQLITE_OK; +} +/* + * Refer to [page_unref()] + */ +static int unqliteKvIoPageUnRef(unqlite_page *pPage) +{ + if( pPage ){ + page_unref((Page *)pPage); + } + return UNQLITE_OK; +} +/* + * Refer to the declaration of the [Pager] structure + */ +static int unqliteKvIoReadOnly(unqlite_kv_handle pHandle) +{ + return ((Pager *)pHandle)->is_rdonly; +} +/* + * Refer to the declaration of the [Pager] structure + */ +static int unqliteKvIoPageSize(unqlite_kv_handle pHandle) +{ + return ((Pager *)pHandle)->iPageSize; +} +/* + * Refer to the declaration of the [Pager] structure + */ +static unsigned char * unqliteKvIoTempPage(unqlite_kv_handle pHandle) +{ + return ((Pager *)pHandle)->zTmpPage; +} +/* + * Set a page unpin callback. + * Refer to the declaration of the [Pager] structure + */ +static void unqliteKvIoPageUnpin(unqlite_kv_handle pHandle,void (*xPageUnpin)(void *)) +{ + Pager *pPager = (Pager *)pHandle; + pPager->xPageUnpin = xPageUnpin; +} +/* + * Set a page reload callback. + * Refer to the declaration of the [Pager] structure + */ +static void unqliteKvIoPageReload(unqlite_kv_handle pHandle,void (*xPageReload)(void *)) +{ + Pager *pPager = (Pager *)pHandle; + pPager->xPageReload = xPageReload; +} +/* + * Log an error. + * Refer to the declaration of the [Pager] structure + */ +static void unqliteKvIoErr(unqlite_kv_handle pHandle,const char *zErr) +{ + Pager *pPager = (Pager *)pHandle; + unqliteGenError(pPager->pDb,zErr); +} +/* + * Init an instance of the [unqlite_kv_io] structure. + */ +static int pager_kv_io_init(Pager *pPager,unqlite_kv_methods *pMethods,unqlite_kv_io *pIo) +{ + pIo->pHandle = pPager; + pIo->pMethods = pMethods; + + pIo->xGet = unqliteKvIoPageGet; + pIo->xLookup = unqliteKvIoPageLookup; + pIo->xNew = unqliteKvIoNewPage; + + pIo->xWrite = unqliteKvIopageWrite; + pIo->xDontWrite = unqliteKvIoPageDontWrite; + pIo->xDontJournal = unqliteKvIoPageDontJournal; + pIo->xDontMkHot = unqliteKvIoPageDontMakeHot; + + pIo->xPageRef = unqliteKvIopage_ref; + pIo->xPageUnref = unqliteKvIoPageUnRef; + + pIo->xPageSize = unqliteKvIoPageSize; + pIo->xReadOnly = unqliteKvIoReadOnly; + + pIo->xTmpPage = unqliteKvIoTempPage; + + pIo->xSetUnpin = unqliteKvIoPageUnpin; + pIo->xSetReload = unqliteKvIoPageReload; + + pIo->xErr = unqliteKvIoErr; + + return UNQLITE_OK; +} +/* + * ---------------------------------------------------------- + * File: unqlite_vm.c + * MD5: 2a0c56efb2ab87d3e52d0d7c3147c53b + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: unqlite_vm.c v1.0 Win7 2013-01-29 23:37 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* This file deals with low level stuff related to the unQLite Virtual Machine */ + +/* Record ID as a hash value */ +#define COL_RECORD_HASH(RID) (RID) +/* + * Fetch a record from a given collection. + */ +static unqlite_col_record * CollectionCacheFetchRecord( + unqlite_col *pCol, /* Target collection */ + jx9_int64 nId /* Unique record ID */ + ) +{ + unqlite_col_record *pEntry; + if( pCol->nRec < 1 ){ + /* Don't bother hashing */ + return 0; + } + pEntry = pCol->apRecord[COL_RECORD_HASH(nId) & (pCol->nRecSize - 1)]; + for(;;){ + if( pEntry == 0 ){ + break; + } + if( pEntry->nId == nId ){ + /* Record found */ + return pEntry; + } + /* Point to the next entry */ + pEntry = pEntry->pNextCol; + + } + /* No such record */ + return 0; +} +/* + * Install a freshly created record in a given collection. + */ +static int CollectionCacheInstallRecord( + unqlite_col *pCol, /* Target collection */ + jx9_int64 nId, /* Unique record ID */ + jx9_value *pValue /* JSON value */ + ) +{ + unqlite_col_record *pRecord; + sxu32 iBucket; + /* Fetch the record first */ + pRecord = CollectionCacheFetchRecord(pCol,nId); + if( pRecord ){ + /* Record already installed, overwrite its old value */ + jx9MemObjStore(pValue,&pRecord->sValue); + return UNQLITE_OK; + } + /* Allocate a new instance */ + pRecord = (unqlite_col_record *)SyMemBackendPoolAlloc(&pCol->pVm->sAlloc,sizeof(unqlite_col_record)); + if( pRecord == 0 ){ + return UNQLITE_NOMEM; + } + /* Zero the structure */ + SyZero(pRecord,sizeof(unqlite_col_record)); + /* Fill in the structure */ + jx9MemObjInit(pCol->pVm->pJx9Vm,&pRecord->sValue); + jx9MemObjStore(pValue,&pRecord->sValue); + pRecord->nId = nId; + pRecord->pCol = pCol; + /* Install in the corresponding bucket */ + iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1); + pRecord->pNextCol = pCol->apRecord[iBucket]; + if( pCol->apRecord[iBucket] ){ + pCol->apRecord[iBucket]->pPrevCol = pRecord; + } + pCol->apRecord[iBucket] = pRecord; + /* Link */ + MACRO_LD_PUSH(pCol->pList,pRecord); + pCol->nRec++; + if( (pCol->nRec >= pCol->nRecSize * 3) && pCol->nRec < 100000 ){ + /* Allocate a new larger table */ + sxu32 nNewSize = pCol->nRecSize << 1; + unqlite_col_record *pEntry; + unqlite_col_record **apNew; + sxu32 n; + + apNew = (unqlite_col_record **)SyMemBackendAlloc(&pCol->pVm->sAlloc, nNewSize * sizeof(unqlite_col_record *)); + if( apNew ){ + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(unqlite_col_record *)); + /* Rehash all entries */ + n = 0; + pEntry = pCol->pList; + for(;;){ + /* Loop one */ + if( n >= pCol->nRec ){ + break; + } + pEntry->pNextCol = pEntry->pPrevCol = 0; + /* Install in the new bucket */ + iBucket = COL_RECORD_HASH(pEntry->nId) & (nNewSize - 1); + pEntry->pNextCol = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevCol = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(&pCol->pVm->sAlloc,(void *)pCol->apRecord); + pCol->apRecord = apNew; + pCol->nRecSize = nNewSize; + } + } + /* All done */ + return UNQLITE_OK; +} +/* + * Remove a record from the collection table. + */ +UNQLITE_PRIVATE int unqliteCollectionCacheRemoveRecord( + unqlite_col *pCol, /* Target collection */ + jx9_int64 nId /* Unique record ID */ + ) +{ + unqlite_col_record *pRecord; + /* Fetch the record first */ + pRecord = CollectionCacheFetchRecord(pCol,nId); + if( pRecord == 0 ){ + /* No such record */ + return UNQLITE_NOTFOUND; + } + if( pRecord->pPrevCol ){ + pRecord->pPrevCol->pNextCol = pRecord->pNextCol; + }else{ + sxu32 iBucket = COL_RECORD_HASH(nId) & (pCol->nRecSize - 1); + pCol->apRecord[iBucket] = pRecord->pNextCol; + } + if( pRecord->pNextCol ){ + pRecord->pNextCol->pPrevCol = pRecord->pPrevCol; + } + /* Unlink */ + MACRO_LD_REMOVE(pCol->pList,pRecord); + pCol->nRec--; + return UNQLITE_OK; +} +/* + * Discard a collection and its records. + */ +static int CollectionCacheRelease(unqlite_col *pCol) +{ + unqlite_col_record *pNext,*pRec = pCol->pList; + unqlite_vm *pVm = pCol->pVm; + sxu32 n; + /* Discard all records */ + for( n = 0 ; n < pCol->nRec ; ++n ){ + pNext = pRec->pNext; + jx9MemObjRelease(&pRec->sValue); + SyMemBackendPoolFree(&pVm->sAlloc,(void *)pRec); + /* Point to the next record */ + pRec = pNext; + } + SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord); + pCol->nRec = pCol->nRecSize = 0; + pCol->pList = 0; + return UNQLITE_OK; +} +/* + * Install a freshly created collection in the unqlite VM. + */ +static int unqliteVmInstallCollection( + unqlite_vm *pVm, /* Target VM */ + unqlite_col *pCol /* Collection to install */ + ) +{ + SyString *pName = &pCol->sName; + sxu32 iBucket; + /* Hash the collection name */ + pCol->nHash = SyBinHash((const void *)pName->zString,pName->nByte); + /* Install it in the corresponding bucket */ + iBucket = pCol->nHash & (pVm->iColSize - 1); + pCol->pNextCol = pVm->apCol[iBucket]; + if( pVm->apCol[iBucket] ){ + pVm->apCol[iBucket]->pPrevCol = pCol; + } + pVm->apCol[iBucket] = pCol; + /* Link to the list of active collections */ + MACRO_LD_PUSH(pVm->pCol,pCol); + pVm->iCol++; + if( (pVm->iCol >= pVm->iColSize * 4) && pVm->iCol < 10000 ){ + /* Grow the hashtable */ + sxu32 nNewSize = pVm->iColSize << 1; + unqlite_col *pEntry; + unqlite_col **apNew; + sxu32 n; + + apNew = (unqlite_col **)SyMemBackendAlloc(&pVm->sAlloc, nNewSize * sizeof(unqlite_col *)); + if( apNew ){ + /* Zero the new table */ + SyZero((void *)apNew, nNewSize * sizeof(unqlite_col *)); + /* Rehash all entries */ + n = 0; + pEntry = pVm->pCol; + for(;;){ + /* Loop one */ + if( n >= pVm->iCol ){ + break; + } + pEntry->pNextCol = pEntry->pPrevCol = 0; + /* Install in the new bucket */ + iBucket = pEntry->nHash & (nNewSize - 1); + pEntry->pNextCol = apNew[iBucket]; + if( apNew[iBucket] ){ + apNew[iBucket]->pPrevCol = pEntry; + } + apNew[iBucket] = pEntry; + /* Point to the next entry */ + pEntry = pEntry->pNext; + n++; + } + /* Release the old table and reflect the change */ + SyMemBackendFree(&pVm->sAlloc,(void *)pVm->apCol); + pVm->apCol = apNew; + pVm->iColSize = nNewSize; + } + } + return UNQLITE_OK; +} +/* + * Fetch a collection from the target VM. + */ +static unqlite_col * unqliteVmFetchCollection( + unqlite_vm *pVm, /* Target VM */ + SyString *pName /* Lookup name */ + ) +{ + unqlite_col *pCol; + sxu32 nHash; + if( pVm->iCol < 1 ){ + /* Don't bother hashing */ + return 0; + } + nHash = SyBinHash((const void *)pName->zString,pName->nByte); + /* Perform the lookup */ + pCol = pVm->apCol[nHash & ( pVm->iColSize - 1)]; + for(;;){ + if( pCol == 0 ){ + break; + } + if( nHash == pCol->nHash && SyStringCmp(pName,&pCol->sName,SyMemcmp) == 0 ){ + /* Collection found */ + return pCol; + } + /* Point to the next entry */ + pCol = pCol->pNextCol; + } + /* No such collection */ + return 0; +} +/* + * Write and/or alter collection binary header. + */ +static int CollectionSetHeader( + unqlite_kv_engine *pEngine, /* Underlying KV storage engine */ + unqlite_col *pCol, /* Target collection */ + jx9_int64 iRec, /* Last record ID */ + jx9_int64 iTotal, /* Total number of records in this collection */ + jx9_value *pSchema /* Collection schema */ + ) +{ + SyBlob *pHeader = &pCol->sHeader; + unqlite_kv_methods *pMethods; + int iWrite = 0; + int rc; + if( pEngine == 0 ){ + /* Default storage engine */ + pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); + } + pMethods = pEngine->pIo->pMethods; + if( SyBlobLength(pHeader) < 1 ){ + Sytm *pCreate = &pCol->sCreation; /* Creation time */ + unqlite_vfs *pVfs; + sxu32 iDos; + /* Magic number */ + rc = SyBlobAppendBig16(pHeader,UNQLITE_COLLECTION_MAGIC); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Initial record ID */ + rc = SyBlobAppendBig64(pHeader,0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Total records in the collection */ + rc = SyBlobAppendBig64(pHeader,0); + if( rc != UNQLITE_OK ){ + return rc; + } + pVfs = (unqlite_vfs *)unqliteExportBuiltinVfs(); + /* Creation time of the collection */ + if( pVfs->xCurrentTime ){ + /* Get the creation time */ + pVfs->xCurrentTime(pVfs,pCreate); + }else{ + /* Zero the structure */ + SyZero(pCreate,sizeof(Sytm)); + } + /* Convert to DOS time */ + SyTimeFormatToDos(pCreate,&iDos); + rc = SyBlobAppendBig32(pHeader,iDos); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Offset to start writing collection schema */ + pCol->nSchemaOfft = SyBlobLength(pHeader); + iWrite = 1; + }else{ + unsigned char *zBinary = (unsigned char *)SyBlobData(pHeader); + /* Header update */ + if( iRec >= 0 ){ + /* Update record ID */ + SyBigEndianPack64(&zBinary[2/* Magic number*/],(sxu64)iRec); + iWrite = 1; + } + if( iTotal >= 0 ){ + /* Total records */ + SyBigEndianPack64(&zBinary[2/* Magic number*/+8/* Record ID*/],(sxu64)iTotal); + iWrite = 1; + } + if( pSchema ){ + /* Collection Schema */ + SyBlobTruncate(pHeader,pCol->nSchemaOfft); + /* Encode the schema to FastJson */ + rc = FastJsonEncode(pSchema,pHeader,0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Copy the collection schema */ + jx9MemObjStore(pSchema,&pCol->sSchema); + iWrite = 1; + } + } + if( iWrite ){ + SyString *pId = &pCol->sName; + /* Reflect the disk and/or in-memory image */ + rc = pMethods->xReplace(pEngine, + (const void *)pId->zString,pId->nByte, + SyBlobData(pHeader),SyBlobLength(pHeader) + ); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Cannot save collection '%z' header in the underlying storage engine", + pId + ); + return rc; + } + } + return UNQLITE_OK; +} +/* + * Load a binary collection from disk. + */ +static int CollectionLoadHeader(unqlite_col *pCol) +{ + SyBlob *pHeader = &pCol->sHeader; + unsigned char *zRaw,*zEnd; + sxu16 nMagic; + sxu32 iDos; + int rc; + SyBlobReset(pHeader); + /* Read the binary header */ + rc = unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pHeader); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Perform a sanity check */ + if( SyBlobLength(pHeader) < (2 /* magic */ + 8 /* record_id */ + 8 /* total_records */+ 4 /* DOS creation time*/) ){ + return UNQLITE_CORRUPT; + } + zRaw = (unsigned char *)SyBlobData(pHeader); + zEnd = &zRaw[SyBlobLength(pHeader)]; + /* Extract the magic number */ + SyBigEndianUnpack16(zRaw,&nMagic); + if( nMagic != UNQLITE_COLLECTION_MAGIC ){ + return UNQLITE_CORRUPT; + } + zRaw += 2; /* sizeof(sxu16) */ + /* Extract the record ID */ + SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nLastid); + zRaw += 8; /* sizeof(sxu64) */ + /* Total records in the collection */ + SyBigEndianUnpack64(zRaw,(sxu64 *)&pCol->nTotRec); + /* Extract the collection creation date (DOS) */ + zRaw += 8; /* sizeof(sxu64) */ + SyBigEndianUnpack32(zRaw,&iDos); + SyDosTimeFormat(iDos,&pCol->sCreation); + zRaw += 4; + /* Check for a collection schema */ + pCol->nSchemaOfft = (sxu32)(zRaw - (unsigned char *)SyBlobData(pHeader)); + if( zRaw < zEnd ){ + /* Decode the FastJson value */ + FastJsonDecode((const void *)zRaw,(sxu32)(zEnd-zRaw),&pCol->sSchema,0,0); + } + return UNQLITE_OK; +} +/* + * Load or create a binary collection. + */ +static int unqliteVmLoadCollection( + unqlite_vm *pVm, /* Target VM */ + const char *zName, /* Collection name */ + sxu32 nByte, /* zName length */ + int iFlag, /* Control flag */ + unqlite_col **ppOut /* OUT: in-memory collection */ + ) +{ + unqlite_kv_methods *pMethods; + unqlite_kv_engine *pEngine; + unqlite_kv_cursor *pCursor; + unqlite *pDb = pVm->pDb; + unqlite_col *pCol = 0; /* cc warning */ + int rc = SXERR_MEM; + char *zDup = 0; + /* Point to the underlying KV store */ + pEngine = unqlitePagerGetKvEngine(pVm->pDb); + pMethods = pEngine->pIo->pMethods; + /* Allocate a new cursor */ + rc = unqliteInitCursor(pDb,&pCursor); + if( rc != UNQLITE_OK ){ + return rc; + } + if( (iFlag & UNQLITE_VM_COLLECTION_CREATE) == 0 ){ + /* Seek to the desired location */ + rc = pMethods->xSeek(pCursor,(const void *)zName,(int)nByte,UNQLITE_CURSOR_MATCH_EXACT); + if( rc != UNQLITE_OK && (iFlag & UNQLITE_VM_COLLECTION_EXISTS) == 0){ + unqliteGenErrorFormat(pDb,"Collection '%.*s' not defined in the underlying database",nByte,zName); + + unqliteReleaseCursor(pDb,pCursor); + return rc; + } + else if((iFlag & UNQLITE_VM_COLLECTION_EXISTS)){ + unqliteReleaseCursor(pDb,pCursor); + return rc; + } + } + /* Allocate a new instance */ + pCol = (unqlite_col *)SyMemBackendPoolAlloc(&pVm->sAlloc,sizeof(unqlite_col)); + if( pCol == 0 ){ + unqliteGenOutofMem(pDb); + rc = UNQLITE_NOMEM; + goto fail; + } + SyZero(pCol,sizeof(unqlite_col)); + /* Fill in the structure */ + SyBlobInit(&pCol->sWorker,&pVm->sAlloc); + SyBlobInit(&pCol->sHeader,&pVm->sAlloc); + pCol->pVm = pVm; + pCol->pCursor = pCursor; + /* Duplicate collection name */ + zDup = SyMemBackendStrDup(&pVm->sAlloc,zName,nByte); + if( zDup == 0 ){ + unqliteGenOutofMem(pDb); + rc = UNQLITE_NOMEM; + goto fail; + } + pCol->nRecSize = 64; /* Must be a power of two */ + pCol->apRecord = (unqlite_col_record **)SyMemBackendAlloc(&pVm->sAlloc,pCol->nRecSize * sizeof(unqlite_col_record *)); + if( pCol->apRecord == 0 ){ + unqliteGenOutofMem(pDb); + rc = UNQLITE_NOMEM; + goto fail; + } + /* Zero the table */ + SyZero((void *)pCol->apRecord,pCol->nRecSize * sizeof(unqlite_col_record *)); + SyStringInitFromBuf(&pCol->sName,zDup,nByte); + jx9MemObjInit(pVm->pJx9Vm,&pCol->sSchema); + if( iFlag & UNQLITE_VM_COLLECTION_CREATE ){ + /* Create a new collection */ + if( pMethods->xReplace == 0 ){ + /* Read-only KV engine: Generate an error message and return */ + unqliteGenErrorFormat(pDb, + "Cannot create new collection '%z' due to a read-only Key/Value storage engine", + &pCol->sName + ); + rc = UNQLITE_ABORT; /* Abort VM execution */ + goto fail; + } + /* Write the collection header */ + rc = CollectionSetHeader(pEngine,pCol,0,0,0); + if( rc != UNQLITE_OK ){ + rc = UNQLITE_ABORT; /* Abort VM execution */ + goto fail; + } + }else{ + /* Read the collection header */ + rc = CollectionLoadHeader(pCol); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pDb,"Corrupt collection '%z' header",&pCol->sName); + goto fail; + } + } + /* Finally install the collection */ + unqliteVmInstallCollection(pVm,pCol); + /* All done */ + if( ppOut ){ + *ppOut = pCol; + } + return UNQLITE_OK; +fail: + unqliteReleaseCursor(pDb,pCursor); + if( zDup ){ + SyMemBackendFree(&pVm->sAlloc,zDup); + } + if( pCol ){ + if( pCol->apRecord ){ + SyMemBackendFree(&pVm->sAlloc,(void *)pCol->apRecord); + } + SyBlobRelease(&pCol->sHeader); + SyBlobRelease(&pCol->sWorker); + jx9MemObjRelease(&pCol->sSchema); + SyMemBackendPoolFree(&pVm->sAlloc,pCol); + } + return rc; +} +/* + * Fetch a collection. + */ +UNQLITE_PRIVATE unqlite_col * unqliteCollectionFetch( + unqlite_vm *pVm, /* Target VM */ + SyString *pName, /* Lookup key */ + int iFlag /* Control flag */ + ) +{ + unqlite_col *pCol = 0; /* cc warning */ + int rc; + /* Check if the collection is already loaded in memory */ + pCol = unqliteVmFetchCollection(pVm,pName); + if( pCol ){ + /* Already loaded in memory*/ + return pCol; + } + if( (iFlag & UNQLITE_VM_AUTO_LOAD) == 0 ){ + return 0; + } + /* Ask the storage engine for the collection */ + rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,0,&pCol); + /* Return to the caller */ + return rc == UNQLITE_OK ? pCol : 0; +} +/* + * Return the unique ID of the last inserted record. + */ +UNQLITE_PRIVATE jx9_int64 unqliteCollectionLastRecordId(unqlite_col *pCol) +{ + return pCol->nLastid == 0 ? 0 : (pCol->nLastid - 1); +} +/* + * Return the current record ID. + */ +UNQLITE_PRIVATE jx9_int64 unqliteCollectionCurrentRecordId(unqlite_col *pCol) +{ + return pCol->nCurid; +} +/* + * Return the total number of records in a given collection. + */ +UNQLITE_PRIVATE jx9_int64 unqliteCollectionTotalRecords(unqlite_col *pCol) +{ + return pCol->nTotRec; +} +/* + * Reset the record cursor. + */ +UNQLITE_PRIVATE void unqliteCollectionResetRecordCursor(unqlite_col *pCol) +{ + pCol->nCurid = 0; +} +/* + * Fetch a record by its unique ID. + */ +UNQLITE_PRIVATE int unqliteCollectionFetchRecordById( + unqlite_col *pCol, /* Target collection */ + jx9_int64 nId, /* Unique record ID */ + jx9_value *pValue /* OUT: record value */ + ) +{ + SyBlob *pWorker = &pCol->sWorker; + unqlite_col_record *pRec; + int rc; + jx9_value_null(pValue); + /* Perform a cache lookup first */ + pRec = CollectionCacheFetchRecord(pCol,nId); + if( pRec ){ + /* Copy record value */ + jx9MemObjStore(&pRec->sValue,pValue); + return UNQLITE_OK; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Generate the unique ID */ + SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId); + /* Reset the cursor */ + unqlite_kv_cursor_reset(pCol->pCursor); + /* Seek the cursor to the desired location */ + rc = unqlite_kv_cursor_seek(pCol->pCursor, + SyBlobData(pWorker),SyBlobLength(pWorker), + UNQLITE_CURSOR_MATCH_EXACT + ); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Consume the binary JSON */ + SyBlobReset(pWorker); + unqlite_kv_cursor_data_callback(pCol->pCursor,unqliteDataConsumer,pWorker); + if( SyBlobLength(pWorker) < 1 ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Empty record '%qd'",nId + ); + jx9_value_null(pValue); + }else{ + /* Decode the binary JSON */ + rc = FastJsonDecode(SyBlobData(pWorker),SyBlobLength(pWorker),pValue,0,0); + if( rc == UNQLITE_OK ){ + /* Install the record in the cache */ + CollectionCacheInstallRecord(pCol,nId,pValue); + } + } + return rc; +} +/* + * Fetch the next record from a given collection. + */ +UNQLITE_PRIVATE int unqliteCollectionFetchNextRecord(unqlite_col *pCol,jx9_value *pValue) +{ + int rc; + for(;;){ + if( pCol->nCurid >= pCol->nLastid ){ + /* No more records, reset the record cursor ID */ + pCol->nCurid = 0; + /* Return to the caller */ + return SXERR_EOF; + } + rc = unqliteCollectionFetchRecordById(pCol,pCol->nCurid,pValue); + /* Increment the record ID */ + pCol->nCurid++; + /* Lookup result */ + if( rc == UNQLITE_OK || rc != UNQLITE_NOTFOUND ){ + break; + } + } + return rc; +} +/* + * Judge a collection whether exists + */ +UNQLITE_PRIVATE int unqliteExistsCollection( + unqlite_vm *pVm, /* Target VM */ + SyString *pName /* Collection name */ + ) +{ + unqlite_col *pCol; + int rc; + /* Perform a lookup first */ + pCol = unqliteVmFetchCollection(pVm,pName); + if( pCol ){ + /* Already loaded in memory*/ + return UNQLITE_OK; + } + rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_EXISTS,0); + return rc; +} +/* + * Create a new collection. + */ +UNQLITE_PRIVATE int unqliteCreateCollection( + unqlite_vm *pVm, /* Target VM */ + SyString *pName /* Collection name */ + ) +{ + unqlite_col *pCol; + int rc; + /* Perform a lookup first */ + pCol = unqliteCollectionFetch(pVm,pName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + return UNQLITE_EXISTS; + } + /* Now, safely create the collection */ + rc = unqliteVmLoadCollection(pVm,pName->zString,pName->nByte,UNQLITE_VM_COLLECTION_CREATE,0); + return rc; +} +/* + * Set a schema (JSON object) for a given collection. + */ +UNQLITE_PRIVATE int unqliteCollectionSetSchema(unqlite_col *pCol,jx9_value *pValue) +{ + int rc; + if( !jx9_value_is_json_object(pValue) ){ + /* Must be a JSON object */ + return SXERR_INVALID; + } + rc = CollectionSetHeader(0,pCol,-1,-1,pValue); + return rc; +} +/* + * Perform a store operation on a given collection. + */ +static int CollectionStore( + unqlite_col *pCol, /* Target collection */ + jx9_value *pValue /* JSON value to be stored */ + ) +{ + SyBlob *pWorker = &pCol->sWorker; + unqlite_kv_methods *pMethods; + unqlite_kv_engine *pEngine; + sxu32 nKeyLen; + int rc; + /* Point to the underlying KV store */ + pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); + pMethods = pEngine->pIo->pMethods; + if( pCol->nTotRec >= SXI64_HIGH ){ + /* Collection limit reached. No more records */ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Collection '%z': Records limit reached", + &pCol->sName + ); + return UNQLITE_LIMIT; + } + if( pMethods->xReplace == 0 ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Cannot store record into collection '%z' due to a read-only Key/Value storage engine", + &pCol->sName + ); + return UNQLITE_READ_ONLY; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + if( jx9_value_is_json_object(pValue) ){ + jx9_value sId; + /* If the given type is a JSON object, then add the special __id field */ + jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,pCol->nLastid); + jx9_array_add_strkey_elem(pValue,"__id",&sId); + jx9MemObjRelease(&sId); + } + /* Prepare the unique ID for this record */ + SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,pCol->nLastid); + nKeyLen = SyBlobLength(pWorker); + if( nKeyLen < 1 ){ + unqliteGenOutofMem(pCol->pVm->pDb); + return UNQLITE_NOMEM; + } + /* Turn to FastJson */ + rc = FastJsonEncode(pValue,pWorker,0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Finally perform the insertion */ + rc = pMethods->xReplace( + pEngine, + SyBlobData(pWorker),nKeyLen, + SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen + ); + if( rc == UNQLITE_OK ){ + /* Save the value in the cache */ + CollectionCacheInstallRecord(pCol,pCol->nLastid,pValue); + /* Increment the unique __id */ + pCol->nLastid++; + pCol->nTotRec++; + /* Reflect the change */ + rc = CollectionSetHeader(0,pCol,pCol->nLastid,pCol->nTotRec,0); + } + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "IO error while storing record into collection '%z'", + &pCol->sName + ); + return rc; + } + return UNQLITE_OK; +} +/* + * Perform a update operation on a given collection. + */ +static int CollectionUpdate( + unqlite_col *pCol, /* Target collection */ + jx9_int64 nId, /* Record ID */ + jx9_value *pValue /* JSON value to be stored */ +) +{ + SyBlob *pWorker = &pCol->sWorker; + unqlite_kv_methods *pMethods; + unqlite_kv_engine *pEngine; + sxu32 nKeyLen; + int rc; + /* Point to the underlying KV store */ + pEngine = unqlitePagerGetKvEngine(pCol->pVm->pDb); + pMethods = pEngine->pIo->pMethods; + if( pCol->nTotRec >= SXI64_HIGH ){ + /* Collection limit reached. No more records */ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Collection '%z': Records limit reached", + &pCol->sName + ); + return UNQLITE_LIMIT; + } + if( pMethods->xReplace == 0 ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Cannot store record into collection '%z' due to a read-only Key/Value storage engine", + &pCol->sName + ); + return UNQLITE_READ_ONLY; + } + /* Reset the working buffer */ + SyBlobReset(pWorker); + + /* Prepare the unique ID for this record */ + SyBlobFormat(pWorker,"%z_%qd",&pCol->sName, nId); + + /* Reset the cursor */ + unqlite_kv_cursor_reset(pCol->pCursor); + /* Seek the cursor to the desired location */ + rc = unqlite_kv_cursor_seek(pCol->pCursor, + SyBlobData(pWorker),SyBlobLength(pWorker), + UNQLITE_CURSOR_MATCH_EXACT + ); + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "No record to update in collection '%z'", + &pCol->sName + ); + return rc; + } + + if( jx9_value_is_json_object(pValue) ){ + jx9_value sId; + /* If the given type is a JSON object, then add the special __id field */ + jx9MemObjInitFromInt(pCol->pVm->pJx9Vm,&sId,nId); + jx9_array_add_strkey_elem(pValue,"__id",&sId); + jx9MemObjRelease(&sId); + } + + nKeyLen = SyBlobLength(pWorker); + if( nKeyLen < 1 ){ + unqliteGenOutofMem(pCol->pVm->pDb); + return UNQLITE_NOMEM; + } + /* Turn to FastJson */ + rc = FastJsonEncode(pValue,pWorker,0); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Finally perform the insertion */ + rc = pMethods->xReplace( + pEngine, + SyBlobData(pWorker),nKeyLen, + SyBlobDataAt(pWorker,nKeyLen),SyBlobLength(pWorker)-nKeyLen + ); + if( rc == UNQLITE_OK ){ + /* Save the value in the cache */ + CollectionCacheInstallRecord(pCol,nId,pValue); + } + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "IO error while storing record into collection '%z'", + &pCol->sName + ); + return rc; + } + return UNQLITE_OK; +} +/* + * Array walker callback (Refer to jx9_array_walk()). + */ +static int CollectionRecordArrayWalker(jx9_value *pKey,jx9_value *pData,void *pUserData) +{ + unqlite_col *pCol = (unqlite_col *)pUserData; + int rc; + /* Perform the insertion */ + rc = CollectionStore(pCol,pData); + if( rc != UNQLITE_OK ){ + SXUNUSED(pKey); /* cc warning */ + } + return rc; +} +/* + * Perform a store operation on a given collection. + */ +UNQLITE_PRIVATE int unqliteCollectionPut(unqlite_col *pCol,jx9_value *pValue,int iFlag) +{ + int rc; + if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){ + /* Iterate over the array and store its members in the collection */ + rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol); + SXUNUSED(iFlag); /* cc warning */ + }else{ + rc = CollectionStore(pCol,pValue); + } + return rc; +} +/* + * Drop a record from a given collection. + */ +UNQLITE_PRIVATE int unqliteCollectionDropRecord( + unqlite_col *pCol, /* Target collection */ + jx9_int64 nId, /* Unique ID of the record to be droped */ + int wr_header, /* True to alter collection header */ + int log_err /* True to log error */ + ) +{ + SyBlob *pWorker = &pCol->sWorker; + int rc; + /* Reset the working buffer */ + SyBlobReset(pWorker); + /* Prepare the unique ID for this record */ + SyBlobFormat(pWorker,"%z_%qd",&pCol->sName,nId); + /* Reset the cursor */ + unqlite_kv_cursor_reset(pCol->pCursor); + /* Seek the cursor to the desired location */ + rc = unqlite_kv_cursor_seek(pCol->pCursor, + SyBlobData(pWorker),SyBlobLength(pWorker), + UNQLITE_CURSOR_MATCH_EXACT + ); + if( rc != UNQLITE_OK ){ + return rc; + } + /* Remove the record from the storage engine */ + rc = unqlite_kv_cursor_delete_entry(pCol->pCursor); + /* Finally, Remove the record from the cache */ + unqliteCollectionCacheRemoveRecord(pCol,nId); + if( rc == UNQLITE_OK ){ + pCol->nTotRec--; + if( wr_header ){ + /* Relect in the collection header */ + rc = CollectionSetHeader(0,pCol,-1,pCol->nTotRec,0); + } + }else if( rc == UNQLITE_NOTIMPLEMENTED ){ + if( log_err ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Cannot delete record from collection '%z' due to a read-only Key/Value storage engine", + &pCol->sName + ); + } + } + return rc; +} +/* + * Update a given record with new data + */ +UNQLITE_PRIVATE int unqliteCollectionUpdateRecord(unqlite_col *pCol,jx9_int64 nId, jx9_value *pValue,int iFlag) +{ + int rc; + if( !jx9_value_is_json_object(pValue) && jx9_value_is_json_array(pValue) ){ + /* Iterate over the array and store its members in the collection */ + rc = jx9_array_walk(pValue,CollectionRecordArrayWalker,pCol); + SXUNUSED(iFlag); /* cc warning */ + }else{ + rc = CollectionUpdate(pCol,nId,pValue); + } + return rc; +} +/* + * Drop a collection from the KV storage engine and the underlying + * unqlite VM. + */ +UNQLITE_PRIVATE int unqliteDropCollection(unqlite_col *pCol) +{ + unqlite_vm *pVm = pCol->pVm; + jx9_int64 nId; + int rc; + /* Reset the cursor */ + unqlite_kv_cursor_reset(pCol->pCursor); + /* Seek the cursor to the desired location */ + rc = unqlite_kv_cursor_seek(pCol->pCursor, + SyStringData(&pCol->sName),SyStringLength(&pCol->sName), + UNQLITE_CURSOR_MATCH_EXACT + ); + if( rc == UNQLITE_OK ){ + /* Remove the record from the storage engine */ + rc = unqlite_kv_cursor_delete_entry(pCol->pCursor); + } + if( rc != UNQLITE_OK ){ + unqliteGenErrorFormat(pCol->pVm->pDb, + "Cannot remove collection '%z' due to a read-only Key/Value storage engine", + &pCol->sName + ); + return rc; + } + /* Drop collection records */ + for( nId = 0 ; nId < pCol->nLastid ; ++nId ){ + unqliteCollectionDropRecord(pCol,nId,0,0); + } + /* Cleanup */ + CollectionCacheRelease(pCol); + SyBlobRelease(&pCol->sHeader); + SyBlobRelease(&pCol->sWorker); + SyMemBackendFree(&pVm->sAlloc,(void *)SyStringData(&pCol->sName)); + unqliteReleaseCursor(pVm->pDb,pCol->pCursor); + /* Unlink */ + if( pCol->pPrevCol ){ + pCol->pPrevCol->pNextCol = pCol->pNextCol; + }else{ + sxu32 iBucket = pCol->nHash & (pVm->iColSize - 1); + pVm->apCol[iBucket] = pCol->pNextCol; + } + if( pCol->pNextCol ){ + pCol->pNextCol->pPrevCol = pCol->pPrevCol; + } + MACRO_LD_REMOVE(pVm->pCol,pCol); + pVm->iCol--; + SyMemBackendPoolFree(&pVm->sAlloc,pCol); + return UNQLITE_OK; +} +/* + * ---------------------------------------------------------- + * File: unqlite_jx9.c + * MD5: 8fddc15b667e85d7b5df5367132518fb + * ---------------------------------------------------------- + */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2013, Symisc Systems http://unqlite.org/ + * Version 1.1.6 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ + /* $SymiscID: unql_jx9.c v1.2 FreeBSD 2013-01-24 22:45 stable $ */ +#ifndef UNQLITE_AMALGAMATION +#include "unqliteInt.h" +#endif +/* + * This file implements UnQLite functions (db_exists(), db_create(), db_put(), db_get(), etc.) for the + * underlying Jx9 Virtual Machine. + */ +/* + * string db_version(void) + * Return the current version of the unQLite database engine. + * Parameter + * None + * Return + * unQLite version number (string). + */ +static int unqliteBuiltin_db_version(jx9_context *pCtx,int argc,jx9_value **argv) +{ + SXUNUSED(argc); /* cc warning */ + SXUNUSED(argv); + jx9_result_string(pCtx,UNQLITE_VERSION,(int)sizeof(UNQLITE_VERSION)-1); + return JX9_OK; +} +/* + * string db_errlog(void) + * Return the database error log. + * Parameter + * None + * Return + * Database error log (string). + */ +static int unqliteBuiltin_db_errlog(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_vm *pVm; + SyBlob *pErr; + + SXUNUSED(argc); /* cc warning */ + SXUNUSED(argv); + + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Point to the error log */ + pErr = &pVm->pDb->sErr; + /* Return the log */ + jx9_result_string(pCtx,(const char *)SyBlobData(pErr),(int)SyBlobLength(pErr)); + return JX9_OK; +} +/* + * string db_copyright(void) + * string db_credits(void) + * Return the unQLite database engine copyright notice. + * Parameter + * None + * Return + * Copyright notice. + */ +static int unqliteBuiltin_db_credits(jx9_context *pCtx,int argc,jx9_value **argv) +{ + SXUNUSED(argc); /* cc warning */ + SXUNUSED(argv); + jx9_result_string(pCtx,UNQLITE_COPYRIGHT,(int)sizeof(UNQLITE_COPYRIGHT)-1); + return JX9_OK; +} +/* + * string db_sig(void) + * Return the unQLite database engine unique signature. + * Parameter + * None + * Return + * unQLite signature. + */ +static int unqliteBuiltin_db_sig(jx9_context *pCtx,int argc,jx9_value **argv) +{ + SXUNUSED(argc); /* cc warning */ + SXUNUSED(argv); + jx9_result_string(pCtx,UNQLITE_IDENT,sizeof(UNQLITE_IDENT)-1); + return JX9_OK; +} +/* + * bool collection_exists(string $name) + * bool db_exits(string $name) + * Check if a given collection exists in the underlying database. + * Parameter + * name: Lookup name + * Return + * TRUE if the collection exits. FALSE otherwise. + */ +static int unqliteBuiltin_collection_exists(jx9_context *pCtx,int argc,jx9_value **argv) +{ + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Perform the lookup */ + rc = unqliteExistsCollection(pVm, &sName); + /* Lookup result */ + jx9_result_bool(pCtx, rc == UNQLITE_OK ? 1 : 0); + return JX9_OK; +} +/* + * bool collection_create(string $name) + * bool db_create(string $name) + * Create a new collection. + * Parameter + * name: Collection name + * Return + * TRUE if the collection was successfuly created. FALSE otherwise. + */ +static int unqliteBuiltin_collection_create(jx9_context *pCtx,int argc,jx9_value **argv) +{ + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Try to create the collection */ + rc = unqliteCreateCollection(pVm,&sName); + /* Return the result to the caller */ + jx9_result_bool(pCtx,rc == UNQLITE_OK ? 1 : 0); + return JX9_OK; +} +/* + * value db_fetch(string $col_name) + * value db_get(string $col_name) + * Fetch the current record from a given collection and advance + * the record cursor. + * Parameter + * col_name: Collection name + * Return + * Record content success. NULL on failure (No more records to retrieve). + */ +static int unqliteBuiltin_db_fetch_next(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return null */ + jx9_result_null(pCtx); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + /* Fetch the current record */ + jx9_value *pValue; + pValue = jx9_context_new_scalar(pCtx); + if( pValue == 0 ){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); + jx9_result_null(pCtx); + return JX9_OK; + }else{ + rc = unqliteCollectionFetchNextRecord(pCol,pValue); + if( rc == UNQLITE_OK ){ + jx9_result_value(pCtx,pValue); + /* pValue will be automatically released as soon we return from this function */ + }else{ + /* Return null */ + jx9_result_null(pCtx); + } + } + }else{ + /* No such collection, return null */ + jx9_result_null(pCtx); + } + return JX9_OK; +} +/* + * value db_fetch_by_id(string $col_name,int64 $record_id) + * value db_get_by_id(string $col_name,int64 $record_id) + * Fetch a record using its unique ID from a given collection. + * Parameter + * col_name: Collection name + * record_id: Record number (__id field of a JSON object) + * Return + * Record content success. NULL on failure (No such record). + */ +static int unqliteBuiltin_db_fetch_by_id(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + jx9_int64 nId; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 2 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or record ID"); + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + /* Extract the record ID */ + nId = jx9_value_to_int(argv[1]); + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + /* Fetch the desired record */ + jx9_value *pValue; + pValue = jx9_context_new_scalar(pCtx); + if( pValue == 0 ){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); + jx9_result_null(pCtx); + return JX9_OK; + }else{ + rc = unqliteCollectionFetchRecordById(pCol,nId,pValue); + if( rc == UNQLITE_OK ){ + jx9_result_value(pCtx,pValue); + /* pValue will be automatically released as soon we return from this function */ + }else{ + /* No such record, return null */ + jx9_result_null(pCtx); + } + } + }else{ + /* No such collection, return null */ + jx9_result_null(pCtx); + } + return JX9_OK; +} +/* + * array db_fetch_all(string $col_name,[callback filter_callback]) + * array db_get_all(string $col_name,[callback filter_callback]) + * Retrieve all records of a given collection and apply the given + * callback if available to filter records. + * Parameter + * col_name: Collection name + * Return + * Contents of the collection (JSON array) on success. NULL on failure. + */ +static int unqliteBuiltin_db_fetch_all(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return NULL */ + jx9_result_null(pCtx); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + jx9_value *pValue,*pArray,*pCallback = 0; + jx9_value sResult; /* Callback result */ + /* Allocate an empty scalar value and an empty JSON array */ + pArray = jx9_context_new_array(pCtx); + pValue = jx9_context_new_scalar(pCtx); + jx9MemObjInit(pCtx->pVm,&sResult); + if( pValue == 0 || pArray == 0 ){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Jx9 is running out of memory"); + jx9_result_null(pCtx); + return JX9_OK; + } + if( argc > 1 && jx9_value_is_callable(argv[1]) ){ + pCallback = argv[1]; + } + unqliteCollectionResetRecordCursor(pCol); + /* Fetch collection records one after one */ + while( UNQLITE_OK == unqliteCollectionFetchNextRecord(pCol,pValue) ){ + if( pCallback ){ + jx9_value *apArg[2]; + /* Invoke the filter callback */ + apArg[0] = pValue; + rc = jx9VmCallUserFunction(pCtx->pVm,pCallback,1,apArg,&sResult); + if( rc == JX9_OK ){ + int iResult; /* Callback result */ + /* Extract callback result */ + iResult = jx9_value_to_bool(&sResult); + if( !iResult ){ + /* Discard the result */ + unqliteCollectionCacheRemoveRecord(pCol,unqliteCollectionCurrentRecordId(pCol) - 1); + continue; + } + } + } + /* Put the value in the JSON array */ + jx9_array_add_elem(pArray,0,pValue); + /* Release the value */ + jx9_value_null(pValue); + } + jx9MemObjRelease(&sResult); + /* Finally, return our array */ + jx9_result_value(pCtx,pArray); + /* pValue will be automatically released as soon we return from + * this foreign function. + */ + }else{ + /* No such collection, return null */ + jx9_result_null(pCtx); + } + return JX9_OK; +} +/* + * int64 db_last_record_id(string $col_name) + * Return the ID of the last inserted record. + * Parameter + * col_name: Collection name + * Return + * Record ID (64-bit integer) on success. FALSE on failure. + */ +static int unqliteBuiltin_db_last_record_id(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + jx9_result_int64(pCtx,unqliteCollectionLastRecordId(pCol)); + }else{ + /* No such collection, return FALSE */ + jx9_result_bool(pCtx,0); + } + return JX9_OK; +} +/* + * inr64 db_current_record_id(string $col_name) + * Return the current record ID. + * Parameter + * col_name: Collection name + * Return + * Current record ID (64-bit integer) on success. FALSE on failure. + */ +static int unqliteBuiltin_db_current_record_id(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + jx9_result_int64(pCtx,unqliteCollectionCurrentRecordId(pCol)); + }else{ + /* No such collection, return FALSE */ + jx9_result_bool(pCtx,0); + } + return JX9_OK; +} +/* + * bool db_reset_record_cursor(string $col_name) + * Reset the record ID cursor. + * Parameter + * col_name: Collection name + * Return + * TRUE on success. FALSE on failure. + */ +static int unqliteBuiltin_db_reset_record_cursor(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + unqliteCollectionResetRecordCursor(pCol); + jx9_result_bool(pCtx,1); + }else{ + /* No such collection */ + jx9_result_bool(pCtx,0); + } + return JX9_OK; +} +/* + * int64 db_total_records(string $col_name) + * Return the total number of inserted records in the given collection. + * Parameter + * col_name: Collection name + * Return + * Total number of records on success. FALSE on failure. + */ +static int unqliteBuiltin_db_total_records(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + unqlite_int64 nRec; + nRec = unqliteCollectionTotalRecords(pCol); + jx9_result_int64(pCtx,nRec); + }else{ + /* No such collection */ + jx9_result_bool(pCtx,0); + } + return JX9_OK; +} +/* + * string db_creation_date(string $col_name) + * Return the creation date of the given collection. + * Parameter + * col_name: Collection name + * Return + * Creation date on success. FALSE on failure. + */ +static int unqliteBuiltin_db_creation_date(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + Sytm *pTm = &pCol->sCreation; + jx9_result_string_format(pCtx,"%d-%d-%d %02d:%02d:%02d", + pTm->tm_year,pTm->tm_mon,pTm->tm_mday, + pTm->tm_hour,pTm->tm_min,pTm->tm_sec + ); + }else{ + /* No such collection */ + jx9_result_bool(pCtx,0); + } + return JX9_OK; +} +/* + * bool db_store(string $col_name,...) + * bool db_put(string $col_name,...) + * Store one or more JSON values in a given collection. + * Parameter + * col_name: Collection name + * Return + * TRUE on success. FALSE on failure. + */ +static int unqliteBuiltin_db_store(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + int rc; + int i; + /* Extract collection name */ + if( argc < 2 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol == 0 ){ + jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + /* Store the given values */ + for( i = 1 ; i < argc ; ++i ){ + rc = unqliteCollectionPut(pCol,argv[i],0); + if( rc != UNQLITE_OK){ + jx9_context_throw_error_format(pCtx,JX9_CTX_ERR, + "Error while storing record %d in collection '%z'",i,&sName + ); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + } + /* All done, return TRUE */ + jx9_result_bool(pCtx,1); + return JX9_OK; +} + +/* + * bool db_update_record(string $col_name, int_64 record_id, object $json_object) + * Update a given record with new json object + * Parameter + * col_name: Collection name + * record_id: ID of the record + * json_object: New Record data + * Return + * TRUE on success. FALSE on failure. + */ +static int unqliteBuiltin_db_update_record(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + jx9_int64 nId; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 2 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol == 0 ){ + jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + /* Update a record with the given value */ + nId = jx9_value_to_int64(argv[1]); + rc = unqliteCollectionUpdateRecord(pCol, nId, argv[2], 0); + /* All done, return TRUE */ + jx9_result_bool(pCtx,rc == UNQLITE_OK); + return JX9_OK; +} + +/* + * bool db_drop_collection(string $col_name) + * bool collection_delete(string $col_name) + * Remove a given collection from the database. + * Parameter + * col_name: Collection name + * Return + * TRUE on success. FALSE on failure. + */ +static int unqliteBuiltin_db_drop_col(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol == 0 ){ + jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + /* Drop the collection */ + rc = unqliteDropCollection(pCol); + /* Processing result */ + jx9_result_bool(pCtx,rc == UNQLITE_OK); + return JX9_OK; +} +/* + * bool db_drop_record(string $col_name,int64 record_id) + * Remove a given record from a collection. + * Parameter + * col_name: Collection name. + * record_id: ID of the record. + * Return + * TRUE on success. FALSE on failure. + */ +static int unqliteBuiltin_db_drop_record(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + jx9_int64 nId; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 2 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or records"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol == 0 ){ + jx9_context_throw_error_format(pCtx,JX9_CTX_ERR,"No such collection '%z'",&sName); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + /* Extract the record ID */ + nId = jx9_value_to_int64(argv[1]); + /* Drop the record */ + rc = unqliteCollectionDropRecord(pCol,nId,1,1); + /* Processing result */ + jx9_result_bool(pCtx,rc == UNQLITE_OK); + return JX9_OK; +} +/* + * bool db_set_schema(string $col_name, object $json_object) + * Set a schema for a given collection. + * Parameter + * col_name: Collection name. + * json_object: Collection schema (Must be a JSON object). + * Return + * TRUE on success. FALSE on failure. + */ +static int unqliteBuiltin_db_set_schema(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + int rc; + /* Extract collection name */ + if( argc < 2 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + if( !jx9_value_is_json_object(argv[1]) ){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection scheme"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + rc = UNQLITE_NOOP; + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + /* Set the collection scheme */ + rc = unqliteCollectionSetSchema(pCol,argv[1]); + }else{ + jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING, + "No such collection '%z'", + &sName + ); + } + /* Processing result */ + jx9_result_bool(pCtx,rc == UNQLITE_OK); + return JX9_OK; +} +/* + * object db_get_schema(string $col_name) + * Return the schema associated with a given collection. + * Parameter + * col_name: Collection name + * Return + * Collection schema on success. null otherwise. + */ +static int unqliteBuiltin_db_get_schema(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_col *pCol; + const char *zName; + unqlite_vm *pVm; + SyString sName; + int nByte; + /* Extract collection name */ + if( argc < 1 ){ + /* Missing arguments */ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Missing collection name and/or db scheme"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + zName = jx9_value_to_string(argv[0],&nByte); + if( nByte < 1){ + jx9_context_throw_error(pCtx,JX9_CTX_ERR,"Invalid collection name"); + /* Return false */ + jx9_result_bool(pCtx,0); + return JX9_OK; + } + SyStringInitFromBuf(&sName,zName,nByte); + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Fetch the collection */ + pCol = unqliteCollectionFetch(pVm,&sName,UNQLITE_VM_AUTO_LOAD); + if( pCol ){ + /* Return the collection schema */ + jx9_result_value(pCtx,&pCol->sSchema); + }else{ + jx9_context_throw_error_format(pCtx,JX9_CTX_WARNING, + "No such collection '%z'", + &sName + ); + jx9_result_null(pCtx); + } + return JX9_OK; +} +/* + * bool db_begin(void) + * Manually begin a write transaction. + * Parameter + * None + * Return + * TRUE on success. FALSE otherwise. + */ +static int unqliteBuiltin_db_begin(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_vm *pVm; + unqlite *pDb; + int rc; + SXUNUSED(argc); /* cc warning */ + SXUNUSED(argv); + /* Point to the unqlite Vm */ + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Point to the underlying database handle */ + pDb = pVm->pDb; + /* Begin the transaction */ + rc = unqlitePagerBegin(pDb->sDB.pPager); + /* result */ + jx9_result_bool(pCtx,rc == UNQLITE_OK ); + return JX9_OK; +} +/* + * bool db_commit(void) + * Manually commit a transaction. + * Parameter + * None + * Return + * TRUE if the transaction was successfuly commited. FALSE otherwise. + */ +static int unqliteBuiltin_db_commit(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_vm *pVm; + unqlite *pDb; + int rc; + SXUNUSED(argc); /* cc warning */ + SXUNUSED(argv); + /* Point to the unqlite Vm */ + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Point to the underlying database handle */ + pDb = pVm->pDb; + /* Commit the transaction if any */ + rc = unqlitePagerCommit(pDb->sDB.pPager); + /* Commit result */ + jx9_result_bool(pCtx,rc == UNQLITE_OK ); + return JX9_OK; +} +/* + * bool db_rollback(void) + * Manually rollback a transaction. + * Parameter + * None + * Return + * TRUE if the transaction was successfuly rolled back. FALSE otherwise + */ +static int unqliteBuiltin_db_rollback(jx9_context *pCtx,int argc,jx9_value **argv) +{ + unqlite_vm *pVm; + unqlite *pDb; + int rc; + SXUNUSED(argc); /* cc warning */ + SXUNUSED(argv); + /* Point to the unqlite Vm */ + pVm = (unqlite_vm *)jx9_context_user_data(pCtx); + /* Point to the underlying database handle */ + pDb = pVm->pDb; + /* Rollback the transaction if any */ + rc = unqlitePagerRollback(pDb->sDB.pPager,TRUE); + /* Rollback result */ + jx9_result_bool(pCtx,rc == UNQLITE_OK ); + return JX9_OK; +} +/* + * Register all the UnQLite foreign functions defined above. + */ +UNQLITE_PRIVATE int unqliteRegisterJx9Functions(unqlite_vm *pVm) +{ + static const jx9_builtin_func aBuiltin[] = { + { "db_version" , unqliteBuiltin_db_version }, + { "db_copyright", unqliteBuiltin_db_credits }, + { "db_credits" , unqliteBuiltin_db_credits }, + { "db_sig" , unqliteBuiltin_db_sig }, + { "db_errlog", unqliteBuiltin_db_errlog }, + { "collection_exists", unqliteBuiltin_collection_exists }, + { "db_exists", unqliteBuiltin_collection_exists }, + { "collection_create", unqliteBuiltin_collection_create }, + { "db_create", unqliteBuiltin_collection_create }, + { "db_fetch", unqliteBuiltin_db_fetch_next }, + { "db_get", unqliteBuiltin_db_fetch_next }, + { "db_fetch_by_id", unqliteBuiltin_db_fetch_by_id }, + { "db_get_by_id", unqliteBuiltin_db_fetch_by_id }, + { "db_fetch_all", unqliteBuiltin_db_fetch_all }, + { "db_get_all", unqliteBuiltin_db_fetch_all }, + { "db_last_record_id", unqliteBuiltin_db_last_record_id }, + { "db_current_record_id", unqliteBuiltin_db_current_record_id }, + { "db_reset_record_cursor", unqliteBuiltin_db_reset_record_cursor }, + { "db_total_records", unqliteBuiltin_db_total_records }, + { "db_creation_date", unqliteBuiltin_db_creation_date }, + { "db_store", unqliteBuiltin_db_store }, + { "db_update_record", unqliteBuiltin_db_update_record }, + { "db_put", unqliteBuiltin_db_store }, + { "db_drop_collection", unqliteBuiltin_db_drop_col }, + { "collection_delete", unqliteBuiltin_db_drop_col }, + { "db_drop_record", unqliteBuiltin_db_drop_record }, + { "db_set_schema", unqliteBuiltin_db_set_schema }, + { "db_get_schema", unqliteBuiltin_db_get_schema }, + { "db_begin", unqliteBuiltin_db_begin }, + { "db_commit", unqliteBuiltin_db_commit }, + { "db_rollback", unqliteBuiltin_db_rollback }, + }; + int rc = UNQLITE_OK; + sxu32 n; + /* Register the unQLite functions defined above in the Jx9 call table */ + for( n = 0 ; n < SX_ARRAYSIZE(aBuiltin) ; ++n ){ + rc = jx9_create_function(pVm->pJx9Vm,aBuiltin[n].zName,aBuiltin[n].xFunc,pVm); + } + return rc; +} +/* END-OF-IMPLEMENTATION: unqlite@embedded@symisc 34-09-46 */ +/* + * Symisc unQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2018, Symisc Systems http://unqlite.org/ + * Version 1.1.9 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ +/* + * Copyright (C) 2012, 2018 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/third_party/unqlite/unqlite.h b/third_party/unqlite/unqlite.h new file mode 100644 index 000000000..0305a087a --- /dev/null +++ b/third_party/unqlite/unqlite.h @@ -0,0 +1,956 @@ +/* This file was automatically generated. Do not edit (Except for compile time directives)! */ +#ifndef _UNQLITE_H_ +#define _UNQLITE_H_ +/* Make sure we can call this stuff from C++ */ +#ifdef __cplusplus + extern "C" { +#endif +/* + * Symisc UnQLite: An Embeddable NoSQL (Post Modern) Database Engine. + * Copyright (C) 2012-2018, Symisc Systems http://unqlite.org/ + * Version 1.1.9 + * For information on licensing, redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES + * please contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + * or visit: + * http://unqlite.org/licensing.html + */ +/* + * Copyright (C) 2012, 2018 Symisc Systems, S.U.A.R.L [M.I.A.G Mrad Chems Eddine ]. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY SYMISC SYSTEMS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR + * NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SYMISC SYSTEMS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + /* $SymiscID: unqlite.h v1.3 Win10 2108-04-27 02:35:11 stable $ */ +#include /* needed for the definition of va_list */ +/* + * Compile time engine version, signature, identification in the symisc source tree + * and copyright notice. + * Each macro have an equivalent C interface associated with it that provide the same + * information but are associated with the library instead of the header file. + * Refer to [unqlite_lib_version()], [unqlite_lib_signature()], [unqlite_lib_ident()] and + * [unqlite_lib_copyright()] for more information. + */ +/* + * The UNQLITE_VERSION C preprocessor macroevaluates to a string literal + * that is the unqlite version in the format "X.Y.Z" where X is the major + * version number and Y is the minor version number and Z is the release + * number. + */ +#define UNQLITE_VERSION "1.1.9" +/* + * The UNQLITE_VERSION_NUMBER C preprocessor macro resolves to an integer + * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same + * numbers used in [UNQLITE_VERSION]. + */ +#define UNQLITE_VERSION_NUMBER 1001009 +/* + * The UNQLITE_SIG C preprocessor macro evaluates to a string + * literal which is the public signature of the unqlite engine. + * This signature could be included for example in a host-application + * generated Server MIME header as follows: + * Server: YourWebServer/x.x unqlite/x.x.x \r\n + */ +#define UNQLITE_SIG "unqlite/1.1.9" +/* + * UnQLite identification in the Symisc source tree: + * Each particular check-in of a particular software released + * by symisc systems have an unique identifier associated with it. + * This macro hold the one associated with unqlite. + */ +#define UNQLITE_IDENT "unqlite:b172a1e2c3f62fb35c8e1fb2795121f82356cad6" +/* + * Copyright notice. + * If you have any questions about the licensing situation, please + * visit http://unqlite.org/licensing.html + * or contact Symisc Systems via: + * legal@symisc.net + * licensing@symisc.net + * contact@symisc.net + */ +#define UNQLITE_COPYRIGHT "Copyright (C) Symisc Systems, S.U.A.R.L [Mrad Chems Eddine ] 2012-2018, http://unqlite.org/" + +/* Forward declaration to public objects */ +typedef struct unqlite_io_methods unqlite_io_methods; +typedef struct unqlite_kv_methods unqlite_kv_methods; +typedef struct unqlite_kv_engine unqlite_kv_engine; +typedef struct jx9_io_stream unqlite_io_stream; +typedef struct jx9_context unqlite_context; +typedef struct jx9_value unqlite_value; +typedef struct unqlite_vfs unqlite_vfs; +typedef struct unqlite_vm unqlite_vm; +typedef struct unqlite unqlite; +/* + * ------------------------------ + * Compile time directives + * ------------------------------ + * For most purposes, UnQLite can be built just fine using the default compilation options. + * However, if required, the compile-time options documented below can be used to omit UnQLite + * features (resulting in a smaller compiled library size) or to change the default values + * of some parameters. + * Every effort has been made to ensure that the various combinations of compilation options + * work harmoniously and produce a working library. + * + * UNQLITE_ENABLE_THREADS + * This option controls whether or not code is included in UnQLite to enable it to operate + * safely in a multithreaded environment. The default is not. All mutexing code is omitted + * and it is unsafe to use UnQLite in a multithreaded program. When compiled with the + * UNQLITE_ENABLE_THREADS directive enabled, UnQLite can be used in a multithreaded program + * and it is safe to share the same virtual machine and engine handle between two or more threads. + * The value of UNQLITE_ENABLE_THREADS can be determined at run-time using the unqlite_lib_is_threadsafe() + * interface. + * When UnQLite has been compiled with threading support then the threading mode can be altered + * at run-time using the unqlite_lib_config() interface together with one of these verbs: + * UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE + * UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI + * Platforms others than Windows and UNIX systems must install their own mutex subsystem via + * unqlite_lib_config() with a configuration verb set to UNQLITE_LIB_CONFIG_USER_MUTEX. + * Otherwise the library is not threadsafe. + * Note that you must link UnQLite with the POSIX threads library under UNIX systems (i.e: -lpthread). + * + * Options To Omit/Enable Features + * + * The following options can be used to reduce the size of the compiled library by omitting optional + * features. This is probably only useful in embedded systems where space is especially tight, as even + * with all features included the UnQLite library is relatively small. Don't forget to tell your + * compiler to optimize for binary size! (the -Os option if using GCC). Telling your compiler + * to optimize for size usually has a much larger impact on library footprint than employing + * any of these compile-time options. + * + * JX9_DISABLE_BUILTIN_FUNC + * Jx9 is shipped with more than 312 built-in functions suitable for most purposes like + * string and INI processing, ZIP extracting, Base64 encoding/decoding, JSON encoding/decoding + * and so forth. + * If this directive is enabled, then all built-in Jx9 functions are omitted from the build. + * Note that special functions such as db_create(), db_store(), db_fetch(), etc. are not omitted + * from the build and are not affected by this directive. + * + * JX9_ENABLE_MATH_FUNC + * If this directive is enabled, built-in math functions such as sqrt(), abs(), log(), ceil(), etc. + * are included in the build. Note that you may need to link UnQLite with the math library in same + * Linux/BSD flavor (i.e: -lm). + * + * JX9_DISABLE_DISK_IO + * If this directive is enabled, built-in VFS functions such as chdir(), mkdir(), chroot(), unlink(), + * sleep(), etc. are omitted from the build. + * + * UNQLITE_ENABLE_JX9_HASH_IO + * If this directive is enabled, built-in hash functions such as md5(), sha1(), md5_file(), crc32(), etc. + * are included in the build. + */ +/* Symisc public definitions */ +#if !defined(SYMISC_STANDARD_DEFS) +#define SYMISC_STANDARD_DEFS +#if defined (_WIN32) || defined (WIN32) || defined(__MINGW32__) || defined (_MSC_VER) || defined (_WIN32_WCE) +/* Windows Systems */ +#if !defined(__WINNT__) +#define __WINNT__ +#endif +/* + * Determine if we are dealing with WindowsCE - which has a much + * reduced API. + */ +#if defined(_WIN32_WCE) +#ifndef __WIN_CE__ +#define __WIN_CE__ +#endif /* __WIN_CE__ */ +#endif /* _WIN32_WCE */ +#else +/* + * By default we will assume that we are compiling on a UNIX systems. + * Otherwise the OS_OTHER directive must be defined. + */ +#if !defined(OS_OTHER) +#if !defined(__UNIXES__) +#define __UNIXES__ +#endif /* __UNIXES__ */ +#else +#endif /* OS_OTHER */ +#endif /* __WINNT__/__UNIXES__ */ +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef signed __int64 sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned __int64 sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#else +typedef signed long long int sxi64; /* 64 bits(8 bytes) signed int64 */ +typedef unsigned long long int sxu64; /* 64 bits(8 bytes) unsigned int64 */ +#endif /* _MSC_VER */ +/* Signature of the consumer routine */ +typedef int (*ProcConsumer)(const void *, unsigned int, void *); +/* Forward reference */ +typedef struct SyMutexMethods SyMutexMethods; +typedef struct SyMemMethods SyMemMethods; +typedef struct SyString SyString; +typedef struct syiovec syiovec; +typedef struct SyMutex SyMutex; +typedef struct Sytm Sytm; +/* Scatter and gather array. */ +struct syiovec +{ +#if defined (__WINNT__) + /* Same fields type and offset as WSABUF structure defined one winsock2 header */ + unsigned long nLen; + char *pBase; +#else + void *pBase; + unsigned long nLen; +#endif +}; +struct SyString +{ + const char *zString; /* Raw string (may not be null terminated) */ + unsigned int nByte; /* Raw string length */ +}; +/* Time structure. */ +struct Sytm +{ + int tm_sec; /* seconds (0 - 60) */ + int tm_min; /* minutes (0 - 59) */ + int tm_hour; /* hours (0 - 23) */ + int tm_mday; /* day of month (1 - 31) */ + int tm_mon; /* month of year (0 - 11) */ + int tm_year; /* year + 1900 */ + int tm_wday; /* day of week (Sunday = 0) */ + int tm_yday; /* day of year (0 - 365) */ + int tm_isdst; /* is summer time in effect? */ + char *tm_zone; /* abbreviation of timezone name */ + long tm_gmtoff; /* offset from UTC in seconds */ +}; +/* Convert a tm structure (struct tm *) found in to a Sytm structure */ +#define STRUCT_TM_TO_SYTM(pTM, pSYTM) \ + (pSYTM)->tm_hour = (pTM)->tm_hour;\ + (pSYTM)->tm_min = (pTM)->tm_min;\ + (pSYTM)->tm_sec = (pTM)->tm_sec;\ + (pSYTM)->tm_mon = (pTM)->tm_mon;\ + (pSYTM)->tm_mday = (pTM)->tm_mday;\ + (pSYTM)->tm_year = (pTM)->tm_year + 1900;\ + (pSYTM)->tm_yday = (pTM)->tm_yday;\ + (pSYTM)->tm_wday = (pTM)->tm_wday;\ + (pSYTM)->tm_isdst = (pTM)->tm_isdst;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_zone = 0; + +/* Convert a SYSTEMTIME structure (LPSYSTEMTIME: Windows Systems only ) to a Sytm structure */ +#define SYSTEMTIME_TO_SYTM(pSYSTIME, pSYTM) \ + (pSYTM)->tm_hour = (pSYSTIME)->wHour;\ + (pSYTM)->tm_min = (pSYSTIME)->wMinute;\ + (pSYTM)->tm_sec = (pSYSTIME)->wSecond;\ + (pSYTM)->tm_mon = (pSYSTIME)->wMonth - 1;\ + (pSYTM)->tm_mday = (pSYSTIME)->wDay;\ + (pSYTM)->tm_year = (pSYSTIME)->wYear;\ + (pSYTM)->tm_yday = 0;\ + (pSYTM)->tm_wday = (pSYSTIME)->wDayOfWeek;\ + (pSYTM)->tm_gmtoff = 0;\ + (pSYTM)->tm_isdst = -1;\ + (pSYTM)->tm_zone = 0; + +/* Dynamic memory allocation methods. */ +struct SyMemMethods +{ + void * (*xAlloc)(unsigned int); /* [Required:] Allocate a memory chunk */ + void * (*xRealloc)(void *, unsigned int); /* [Required:] Re-allocate a memory chunk */ + void (*xFree)(void *); /* [Required:] Release a memory chunk */ + unsigned int (*xChunkSize)(void *); /* [Optional:] Return chunk size */ + int (*xInit)(void *); /* [Optional:] Initialization callback */ + void (*xRelease)(void *); /* [Optional:] Release callback */ + void *pUserData; /* [Optional:] First argument to xInit() and xRelease() */ +}; +/* Out of memory callback signature. */ +typedef int (*ProcMemError)(void *); +/* Mutex methods. */ +struct SyMutexMethods +{ + int (*xGlobalInit)(void); /* [Optional:] Global mutex initialization */ + void (*xGlobalRelease)(void); /* [Optional:] Global Release callback () */ + SyMutex * (*xNew)(int); /* [Required:] Request a new mutex */ + void (*xRelease)(SyMutex *); /* [Optional:] Release a mutex */ + void (*xEnter)(SyMutex *); /* [Required:] Enter mutex */ + int (*xTryEnter)(SyMutex *); /* [Optional:] Try to enter a mutex */ + void (*xLeave)(SyMutex *); /* [Required:] Leave a locked mutex */ +}; +#if defined (_MSC_VER) || defined (__MINGW32__) || defined (__GNUC__) && defined (__declspec) +#define SX_APIIMPORT __declspec(dllimport) +#define SX_APIEXPORT __declspec(dllexport) +#else +#define SX_APIIMPORT +#define SX_APIEXPORT +#endif +/* Standard return values from Symisc public interfaces */ +#define SXRET_OK 0 /* Not an error */ +#define SXERR_MEM (-1) /* Out of memory */ +#define SXERR_IO (-2) /* IO error */ +#define SXERR_EMPTY (-3) /* Empty field */ +#define SXERR_LOCKED (-4) /* Locked operation */ +#define SXERR_ORANGE (-5) /* Out of range value */ +#define SXERR_NOTFOUND (-6) /* Item not found */ +#define SXERR_LIMIT (-7) /* Limit reached */ +#define SXERR_MORE (-8) /* Need more input */ +#define SXERR_INVALID (-9) /* Invalid parameter */ +#define SXERR_ABORT (-10) /* User callback request an operation abort */ +#define SXERR_EXISTS (-11) /* Item exists */ +#define SXERR_SYNTAX (-12) /* Syntax error */ +#define SXERR_UNKNOWN (-13) /* Unknown error */ +#define SXERR_BUSY (-14) /* Busy operation */ +#define SXERR_OVERFLOW (-15) /* Stack or buffer overflow */ +#define SXERR_WILLBLOCK (-16) /* Operation will block */ +#define SXERR_NOTIMPLEMENTED (-17) /* Operation not implemented */ +#define SXERR_EOF (-18) /* End of input */ +#define SXERR_PERM (-19) /* Permission error */ +#define SXERR_NOOP (-20) /* No-op */ +#define SXERR_FORMAT (-21) /* Invalid format */ +#define SXERR_NEXT (-22) /* Not an error */ +#define SXERR_OS (-23) /* System call return an error */ +#define SXERR_CORRUPT (-24) /* Corrupted pointer */ +#define SXERR_CONTINUE (-25) /* Not an error: Operation in progress */ +#define SXERR_NOMATCH (-26) /* No match */ +#define SXERR_RESET (-27) /* Operation reset */ +#define SXERR_DONE (-28) /* Not an error */ +#define SXERR_SHORT (-29) /* Buffer too short */ +#define SXERR_PATH (-30) /* Path error */ +#define SXERR_TIMEOUT (-31) /* Timeout */ +#define SXERR_BIG (-32) /* Too big for processing */ +#define SXERR_RETRY (-33) /* Retry your call */ +#define SXERR_IGNORE (-63) /* Ignore */ +#endif /* SYMISC_PUBLIC_DEFS */ +/* + * Marker for exported interfaces. + */ +#define UNQLITE_APIEXPORT SX_APIEXPORT +/* + * If compiling for a processor that lacks floating point + * support, substitute integer for floating-point. + */ +#ifdef UNQLITE_OMIT_FLOATING_POINT +typedef sxi64 uqlite_real; +#else +typedef double unqlite_real; +#endif +typedef sxi64 unqlite_int64; +/* Standard UnQLite return values */ +#define UNQLITE_OK SXRET_OK /* Successful result */ +/* Beginning of error codes */ +#define UNQLITE_NOMEM SXERR_MEM /* Out of memory */ +#define UNQLITE_ABORT SXERR_ABORT /* Another thread have released this instance */ +#define UNQLITE_IOERR SXERR_IO /* IO error */ +#define UNQLITE_CORRUPT SXERR_CORRUPT /* Corrupt pointer */ +#define UNQLITE_LOCKED SXERR_LOCKED /* Forbidden Operation */ +#define UNQLITE_BUSY SXERR_BUSY /* The database file is locked */ +#define UNQLITE_DONE SXERR_DONE /* Operation done */ +#define UNQLITE_PERM SXERR_PERM /* Permission error */ +#define UNQLITE_NOTIMPLEMENTED SXERR_NOTIMPLEMENTED /* Method not implemented by the underlying Key/Value storage engine */ +#define UNQLITE_NOTFOUND SXERR_NOTFOUND /* No such record */ +#define UNQLITE_NOOP SXERR_NOOP /* No such method */ +#define UNQLITE_INVALID SXERR_INVALID /* Invalid parameter */ +#define UNQLITE_EOF SXERR_EOF /* End Of Input */ +#define UNQLITE_UNKNOWN SXERR_UNKNOWN /* Unknown configuration option */ +#define UNQLITE_LIMIT SXERR_LIMIT /* Database limit reached */ +#define UNQLITE_EXISTS SXERR_EXISTS /* Record exists */ +#define UNQLITE_EMPTY SXERR_EMPTY /* Empty record */ +#define UNQLITE_COMPILE_ERR (-70) /* Compilation error */ +#define UNQLITE_VM_ERR (-71) /* Virtual machine error */ +#define UNQLITE_FULL (-73) /* Full database (unlikely) */ +#define UNQLITE_CANTOPEN (-74) /* Unable to open the database file */ +#define UNQLITE_READ_ONLY (-75) /* Read only Key/Value storage engine */ +#define UNQLITE_LOCKERR (-76) /* Locking protocol error */ +/* end-of-error-codes */ +/* + * Database Handle Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure an UnQLite database handle. + * These constants must be passed as the second argument to [unqlite_config()]. + * + * Each options require a variable number of arguments. + * The [unqlite_config()] interface will return UNQLITE_OK on success, any other + * return value indicates failure. + * For a full discussion on the configuration verbs and their expected + * parameters, please refer to this page: + * http://unqlite.org/c_api/unqlite_config.html + */ +#define UNQLITE_CONFIG_JX9_ERR_LOG 1 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ +#define UNQLITE_CONFIG_MAX_PAGE_CACHE 2 /* ONE ARGUMENT: int nMaxPage */ +#define UNQLITE_CONFIG_ERR_LOG 3 /* TWO ARGUMENTS: const char **pzBuf, int *pLen */ +#define UNQLITE_CONFIG_KV_ENGINE 4 /* ONE ARGUMENT: const char *zKvName */ +#define UNQLITE_CONFIG_DISABLE_AUTO_COMMIT 5 /* NO ARGUMENTS */ +#define UNQLITE_CONFIG_GET_KV_NAME 6 /* ONE ARGUMENT: const char **pzPtr */ +/* + * UnQLite/Jx9 Virtual Machine Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the Jx9 (Via UnQLite) Virtual machine. + * These constants must be passed as the second argument to the [unqlite_vm_config()] + * interface. + * Each options require a variable number of arguments. + * The [unqlite_vm_config()] interface will return UNQLITE_OK on success, any other return + * value indicates failure. + * There are many options but the most importants are: UNQLITE_VM_CONFIG_OUTPUT which install + * a VM output consumer callback, UNQLITE_VM_CONFIG_HTTP_REQUEST which parse and register + * a HTTP request and UNQLITE_VM_CONFIG_ARGV_ENTRY which populate the $argv array. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://unqlite.org/c_api/unqlite_vm_config.html + */ +#define UNQLITE_VM_CONFIG_OUTPUT 1 /* TWO ARGUMENTS: int (*xConsumer)(const void *pOut, unsigned int nLen, void *pUserData), void *pUserData */ +#define UNQLITE_VM_CONFIG_IMPORT_PATH 2 /* ONE ARGUMENT: const char *zIncludePath */ +#define UNQLITE_VM_CONFIG_ERR_REPORT 3 /* NO ARGUMENTS: Report all run-time errors in the VM output */ +#define UNQLITE_VM_CONFIG_RECURSION_DEPTH 4 /* ONE ARGUMENT: int nMaxDepth */ +#define UNQLITE_VM_OUTPUT_LENGTH 5 /* ONE ARGUMENT: unsigned int *pLength */ +#define UNQLITE_VM_CONFIG_CREATE_VAR 6 /* TWO ARGUMENTS: const char *zName, unqlite_value *pValue */ +#define UNQLITE_VM_CONFIG_HTTP_REQUEST 7 /* TWO ARGUMENTS: const char *zRawRequest, int nRequestLength */ +#define UNQLITE_VM_CONFIG_SERVER_ATTR 8 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ +#define UNQLITE_VM_CONFIG_ENV_ATTR 9 /* THREE ARGUMENTS: const char *zKey, const char *zValue, int nLen */ +#define UNQLITE_VM_CONFIG_EXEC_VALUE 10 /* ONE ARGUMENT: unqlite_value **ppValue */ +#define UNQLITE_VM_CONFIG_IO_STREAM 11 /* ONE ARGUMENT: const unqlite_io_stream *pStream */ +#define UNQLITE_VM_CONFIG_ARGV_ENTRY 12 /* ONE ARGUMENT: const char *zValue */ +#define UNQLITE_VM_CONFIG_EXTRACT_OUTPUT 13 /* TWO ARGUMENTS: const void **ppOut, unsigned int *pOutputLen */ +/* + * Storage engine configuration commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the underlying storage engine (i.e Hash, B+tree, R+tree). + * These constants must be passed as the first argument to [unqlite_kv_config()]. + * Each options require a variable number of arguments. + * The [unqlite_kv_config()] interface will return UNQLITE_OK on success, any other return + * value indicates failure. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://unqlite.org/c_api/unqlite_kv_config.html + */ +#define UNQLITE_KV_CONFIG_HASH_FUNC 1 /* ONE ARGUMENT: unsigned int (*xHash)(const void *,unsigned int) */ +#define UNQLITE_KV_CONFIG_CMP_FUNC 2 /* ONE ARGUMENT: int (*xCmp)(const void *,const void *,unsigned int) */ +/* + * Global Library Configuration Commands. + * + * The following set of constants are the available configuration verbs that can + * be used by the host-application to configure the whole library. + * These constants must be passed as the first argument to [unqlite_lib_config()]. + * + * Each options require a variable number of arguments. + * The [unqlite_lib_config()] interface will return UNQLITE_OK on success, any other return + * value indicates failure. + * Notes: + * The default configuration is recommended for most applications and so the call to + * [unqlite_lib_config()] is usually not necessary. It is provided to support rare + * applications with unusual needs. + * The [unqlite_lib_config()] interface is not threadsafe. The application must insure that + * no other [unqlite_*()] interfaces are invoked by other threads while [unqlite_lib_config()] + * is running. Furthermore, [unqlite_lib_config()] may only be invoked prior to library + * initialization using [unqlite_lib_init()] or [unqlite_init()] or after shutdown + * by [unqlite_lib_shutdown()]. If [unqlite_lib_config()] is called after [unqlite_lib_init()] + * or [unqlite_init()] and before [unqlite_lib_shutdown()] then it will return UNQLITE_LOCKED. + * For a full discussion on the configuration verbs and their expected parameters, please + * refer to this page: + * http://unqlite.org/c_api/unqlite_lib.html + */ +#define UNQLITE_LIB_CONFIG_USER_MALLOC 1 /* ONE ARGUMENT: const SyMemMethods *pMemMethods */ +#define UNQLITE_LIB_CONFIG_MEM_ERR_CALLBACK 2 /* TWO ARGUMENTS: int (*xMemError)(void *), void *pUserData */ +#define UNQLITE_LIB_CONFIG_USER_MUTEX 3 /* ONE ARGUMENT: const SyMutexMethods *pMutexMethods */ +#define UNQLITE_LIB_CONFIG_THREAD_LEVEL_SINGLE 4 /* NO ARGUMENTS */ +#define UNQLITE_LIB_CONFIG_THREAD_LEVEL_MULTI 5 /* NO ARGUMENTS */ +#define UNQLITE_LIB_CONFIG_VFS 6 /* ONE ARGUMENT: const unqlite_vfs *pVfs */ +#define UNQLITE_LIB_CONFIG_STORAGE_ENGINE 7 /* ONE ARGUMENT: unqlite_kv_methods *pStorage */ +#define UNQLITE_LIB_CONFIG_PAGE_SIZE 8 /* ONE ARGUMENT: int iPageSize */ +/* + * These bit values are intended for use in the 3rd parameter to the [unqlite_open()] interface + * and in the 4th parameter to the xOpen method of the [unqlite_vfs] object. + */ +#define UNQLITE_OPEN_READONLY 0x00000001 /* Read only mode. Ok for [unqlite_open] */ +#define UNQLITE_OPEN_READWRITE 0x00000002 /* Ok for [unqlite_open] */ +#define UNQLITE_OPEN_CREATE 0x00000004 /* Ok for [unqlite_open] */ +#define UNQLITE_OPEN_EXCLUSIVE 0x00000008 /* VFS only */ +#define UNQLITE_OPEN_TEMP_DB 0x00000010 /* VFS only */ +#define UNQLITE_OPEN_NOMUTEX 0x00000020 /* Ok for [unqlite_open] */ +#define UNQLITE_OPEN_OMIT_JOURNALING 0x00000040 /* Omit journaling for this database. Ok for [unqlite_open] */ +#define UNQLITE_OPEN_IN_MEMORY 0x00000080 /* An in memory database. Ok for [unqlite_open]*/ +#define UNQLITE_OPEN_MMAP 0x00000100 /* Obtain a memory view of the whole file. Ok for [unqlite_open] */ +/* + * Synchronization Type Flags + * + * When UnQLite invokes the xSync() method of an [unqlite_io_methods] object it uses + * a combination of these integer values as the second argument. + * + * When the UNQLITE_SYNC_DATAONLY flag is used, it means that the sync operation only + * needs to flush data to mass storage. Inode information need not be flushed. + * If the lower four bits of the flag equal UNQLITE_SYNC_NORMAL, that means to use normal + * fsync() semantics. If the lower four bits equal UNQLITE_SYNC_FULL, that means to use + * Mac OS X style fullsync instead of fsync(). + */ +#define UNQLITE_SYNC_NORMAL 0x00002 +#define UNQLITE_SYNC_FULL 0x00003 +#define UNQLITE_SYNC_DATAONLY 0x00010 +/* + * File Locking Levels + * + * UnQLite uses one of these integer values as the second + * argument to calls it makes to the xLock() and xUnlock() methods + * of an [unqlite_io_methods] object. + */ +#define UNQLITE_LOCK_NONE 0 +#define UNQLITE_LOCK_SHARED 1 +#define UNQLITE_LOCK_RESERVED 2 +#define UNQLITE_LOCK_PENDING 3 +#define UNQLITE_LOCK_EXCLUSIVE 4 +/* + * CAPIREF: OS Interface: Open File Handle + * + * An [unqlite_file] object represents an open file in the [unqlite_vfs] OS interface + * layer. + * Individual OS interface implementations will want to subclass this object by appending + * additional fields for their own use. The pMethods entry is a pointer to an + * [unqlite_io_methods] object that defines methods for performing + * I/O operations on the open file. +*/ +typedef struct unqlite_file unqlite_file; +struct unqlite_file { + const unqlite_io_methods *pMethods; /* Methods for an open file. MUST BE FIRST */ +}; +/* + * CAPIREF: OS Interface: File Methods Object + * + * Every file opened by the [unqlite_vfs] xOpen method populates an + * [unqlite_file] object (or, more commonly, a subclass of the + * [unqlite_file] object) with a pointer to an instance of this object. + * This object defines the methods used to perform various operations + * against the open file represented by the [unqlite_file] object. + * + * If the xOpen method sets the unqlite_file.pMethods element + * to a non-NULL pointer, then the unqlite_io_methods.xClose method + * may be invoked even if the xOpen reported that it failed. The + * only way to prevent a call to xClose following a failed xOpen + * is for the xOpen to set the unqlite_file.pMethods element to NULL. + * + * The flags argument to xSync may be one of [UNQLITE_SYNC_NORMAL] or + * [UNQLITE_SYNC_FULL]. The first choice is the normal fsync(). + * The second choice is a Mac OS X style fullsync. The [UNQLITE_SYNC_DATAONLY] + * flag may be ORed in to indicate that only the data of the file + * and not its inode needs to be synced. + * + * The integer values to xLock() and xUnlock() are one of + * + * UNQLITE_LOCK_NONE + * UNQLITE_LOCK_SHARED + * UNQLITE_LOCK_RESERVED + * UNQLITE_LOCK_PENDING + * UNQLITE_LOCK_EXCLUSIVE + * + * xLock() increases the lock. xUnlock() decreases the lock. + * The xCheckReservedLock() method checks whether any database connection, + * either in this process or in some other process, is holding a RESERVED, + * PENDING, or EXCLUSIVE lock on the file. It returns true if such a lock exists + * and false otherwise. + * + * The xSectorSize() method returns the sector size of the device that underlies + * the file. The sector size is the minimum write that can be performed without + * disturbing other bytes in the file. + * + */ +struct unqlite_io_methods { + int iVersion; /* Structure version number (currently 1) */ + int (*xClose)(unqlite_file*); + int (*xRead)(unqlite_file*, void*, unqlite_int64 iAmt, unqlite_int64 iOfst); + int (*xWrite)(unqlite_file*, const void*, unqlite_int64 iAmt, unqlite_int64 iOfst); + int (*xTruncate)(unqlite_file*, unqlite_int64 size); + int (*xSync)(unqlite_file*, int flags); + int (*xFileSize)(unqlite_file*, unqlite_int64 *pSize); + int (*xLock)(unqlite_file*, int); + int (*xUnlock)(unqlite_file*, int); + int (*xCheckReservedLock)(unqlite_file*, int *pResOut); + int (*xSectorSize)(unqlite_file*); +}; +/* + * CAPIREF: OS Interface Object + * + * An instance of the unqlite_vfs object defines the interface between + * the UnQLite core and the underlying operating system. The "vfs" + * in the name of the object stands for "Virtual File System". + * + * Only a single vfs can be registered within the UnQLite core. + * Vfs registration is done using the [unqlite_lib_config()] interface + * with a configuration verb set to UNQLITE_LIB_CONFIG_VFS. + * Note that Windows and UNIX (Linux, FreeBSD, Solaris, Mac OS X, etc.) users + * does not have to worry about registering and installing a vfs since UnQLite + * come with a built-in vfs for these platforms that implements most the methods + * defined below. + * + * Clients running on exotic systems (ie: Other than Windows and UNIX systems) + * must register their own vfs in order to be able to use the UnQLite library. + * + * The value of the iVersion field is initially 1 but may be larger in + * future versions of UnQLite. + * + * The szOsFile field is the size of the subclassed [unqlite_file] structure + * used by this VFS. mxPathname is the maximum length of a pathname in this VFS. + * + * At least szOsFile bytes of memory are allocated by UnQLite to hold the [unqlite_file] + * structure passed as the third argument to xOpen. The xOpen method does not have to + * allocate the structure; it should just fill it in. Note that the xOpen method must + * set the unqlite_file.pMethods to either a valid [unqlite_io_methods] object or to NULL. + * xOpen must do this even if the open fails. UnQLite expects that the unqlite_file.pMethods + * element will be valid after xOpen returns regardless of the success or failure of the + * xOpen call. + * + */ +struct unqlite_vfs { + const char *zName; /* Name of this virtual file system [i.e: Windows, UNIX, etc.] */ + int iVersion; /* Structure version number (currently 1) */ + int szOsFile; /* Size of subclassed unqlite_file */ + int mxPathname; /* Maximum file pathname length */ + int (*xOpen)(unqlite_vfs*, const char *zName, unqlite_file*,unsigned int flags); + int (*xDelete)(unqlite_vfs*, const char *zName, int syncDir); + int (*xAccess)(unqlite_vfs*, const char *zName, int flags, int *pResOut); + int (*xFullPathname)(unqlite_vfs*, const char *zName,int buf_len,char *zBuf); + int (*xTmpDir)(unqlite_vfs*,char *zBuf,int buf_len); + int (*xSleep)(unqlite_vfs*, int microseconds); + int (*xCurrentTime)(unqlite_vfs*,Sytm *pOut); + int (*xGetLastError)(unqlite_vfs*, int, char *); +}; +/* + * Flags for the xAccess VFS method + * + * These integer constants can be used as the third parameter to + * the xAccess method of an [unqlite_vfs] object. They determine + * what kind of permissions the xAccess method is looking for. + * With UNQLITE_ACCESS_EXISTS, the xAccess method + * simply checks whether the file exists. + * With UNQLITE_ACCESS_READWRITE, the xAccess method + * checks whether the named directory is both readable and writable + * (in other words, if files can be added, removed, and renamed within + * the directory). + * The UNQLITE_ACCESS_READWRITE constant is currently used only by the + * [temp_store_directory pragma], though this could change in a future + * release of UnQLite. + * With UNQLITE_ACCESS_READ, the xAccess method + * checks whether the file is readable. The UNQLITE_ACCESS_READ constant is + * currently unused, though it might be used in a future release of + * UnQLite. + */ +#define UNQLITE_ACCESS_EXISTS 0 +#define UNQLITE_ACCESS_READWRITE 1 +#define UNQLITE_ACCESS_READ 2 +/* + * The type used to represent a page number. The first page in a file + * is called page 1. 0 is used to represent "not a page". + * A page number is an unsigned 64-bit integer. + */ +typedef sxu64 pgno; +/* + * A database disk page is represented by an instance + * of the follwoing structure. + */ +typedef struct unqlite_page unqlite_page; +struct unqlite_page +{ + unsigned char *zData; /* Content of this page */ + void *pUserData; /* Extra content */ + pgno pgno; /* Page number for this page */ +}; +/* + * UnQLite handle to the underlying Key/Value Storage Engine (See below). + */ +typedef void * unqlite_kv_handle; +/* + * UnQLite pager IO methods. + * + * An instance of the following structure define the exported methods of the UnQLite pager + * to the underlying Key/Value storage engine. + */ +typedef struct unqlite_kv_io unqlite_kv_io; +struct unqlite_kv_io +{ + unqlite_kv_handle pHandle; /* UnQLite handle passed as the first parameter to the + * method defined below. + */ + unqlite_kv_methods *pMethods; /* Underlying storage engine */ + /* Pager methods */ + int (*xGet)(unqlite_kv_handle,pgno,unqlite_page **); + int (*xLookup)(unqlite_kv_handle,pgno,unqlite_page **); + int (*xNew)(unqlite_kv_handle,unqlite_page **); + int (*xWrite)(unqlite_page *); + int (*xDontWrite)(unqlite_page *); + int (*xDontJournal)(unqlite_page *); + int (*xDontMkHot)(unqlite_page *); + int (*xPageRef)(unqlite_page *); + int (*xPageUnref)(unqlite_page *); + int (*xPageSize)(unqlite_kv_handle); + int (*xReadOnly)(unqlite_kv_handle); + unsigned char * (*xTmpPage)(unqlite_kv_handle); + void (*xSetUnpin)(unqlite_kv_handle,void (*xPageUnpin)(void *)); + void (*xSetReload)(unqlite_kv_handle,void (*xPageReload)(void *)); + void (*xErr)(unqlite_kv_handle,const char *); +}; +/* + * Key/Value Storage Engine Cursor Object + * + * An instance of a subclass of the following object defines a cursor + * used to scan through a key-value storage engine. + */ +typedef struct unqlite_kv_cursor unqlite_kv_cursor; +struct unqlite_kv_cursor +{ + unqlite_kv_engine *pStore; /* Must be first */ + /* Subclasses will typically add additional fields */ +}; +/* + * Possible seek positions. + */ +#define UNQLITE_CURSOR_MATCH_EXACT 1 +#define UNQLITE_CURSOR_MATCH_LE 2 +#define UNQLITE_CURSOR_MATCH_GE 3 +/* + * Key/Value Storage Engine. + * + * A Key-Value storage engine is defined by an instance of the following + * object. + * UnQLite works with run-time interchangeable storage engines (i.e. Hash, B+Tree, R+Tree, LSM, etc.). + * The storage engine works with key/value pairs where both the key + * and the value are byte arrays of arbitrary length and with no restrictions on content. + * UnQLite come with two built-in KV storage engine: A Virtual Linear Hash (VLH) storage + * engine is used for persistent on-disk databases with O(1) lookup time and an in-memory + * hash-table or Red-black tree storage engine is used for in-memory databases. + * Future versions of UnQLite might add other built-in storage engines (i.e. LSM). + * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] + * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. + */ +struct unqlite_kv_engine +{ + const unqlite_kv_io *pIo; /* IO methods: MUST be first */ + /* Subclasses will typically add additional fields */ +}; +/* + * Key/Value Storage Engine Virtual Method Table. + * + * Key/Value storage engine methods is defined by an instance of the following + * object. + * Registration of a Key/Value storage engine at run-time is done via [unqlite_lib_config()] + * with a configuration verb set to UNQLITE_LIB_CONFIG_STORAGE_ENGINE. + */ +struct unqlite_kv_methods +{ + const char *zName; /* Storage engine name [i.e. Hash, B+tree, LSM, R-tree, Mem, etc.]*/ + int szKv; /* 'unqlite_kv_engine' subclass size */ + int szCursor; /* 'unqlite_kv_cursor' subclass size */ + int iVersion; /* Structure version, currently 1 */ + /* Storage engine methods */ + int (*xInit)(unqlite_kv_engine *,int iPageSize); + void (*xRelease)(unqlite_kv_engine *); + int (*xConfig)(unqlite_kv_engine *,int op,va_list ap); + int (*xOpen)(unqlite_kv_engine *,pgno); + int (*xReplace)( + unqlite_kv_engine *, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ); + int (*xAppend)( + unqlite_kv_engine *, + const void *pKey,int nKeyLen, + const void *pData,unqlite_int64 nDataLen + ); + void (*xCursorInit)(unqlite_kv_cursor *); + int (*xSeek)(unqlite_kv_cursor *,const void *pKey,int nByte,int iPos); /* Mandatory */ + int (*xFirst)(unqlite_kv_cursor *); + int (*xLast)(unqlite_kv_cursor *); + int (*xValid)(unqlite_kv_cursor *); + int (*xNext)(unqlite_kv_cursor *); + int (*xPrev)(unqlite_kv_cursor *); + int (*xDelete)(unqlite_kv_cursor *); + int (*xKeyLength)(unqlite_kv_cursor *,int *); + int (*xKey)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); + int (*xDataLength)(unqlite_kv_cursor *,unqlite_int64 *); + int (*xData)(unqlite_kv_cursor *,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); + void (*xReset)(unqlite_kv_cursor *); + void (*xCursorRelease)(unqlite_kv_cursor *); +}; +/* + * UnQLite journal file suffix. + */ +#ifndef UNQLITE_JOURNAL_FILE_SUFFIX +#define UNQLITE_JOURNAL_FILE_SUFFIX "_unqlite_journal" +#endif +/* + * Call Context - Error Message Serverity Level. + * + * The following constans are the allowed severity level that can + * passed as the second argument to the [unqlite_context_throw_error()] or + * [unqlite_context_throw_error_format()] interfaces. + * Refer to the official documentation for additional information. + */ +#define UNQLITE_CTX_ERR 1 /* Call context error such as unexpected number of arguments, invalid types and so on. */ +#define UNQLITE_CTX_WARNING 2 /* Call context Warning */ +#define UNQLITE_CTX_NOTICE 3 /* Call context Notice */ +/* + * C-API-REF: Please refer to the official documentation for interfaces + * purpose and expected parameters. + */ + +/* Database Engine Handle */ +UNQLITE_APIEXPORT int unqlite_open(unqlite **ppDB,const char *zFilename,unsigned int iMode); +UNQLITE_APIEXPORT int unqlite_config(unqlite *pDb,int nOp,...); +UNQLITE_APIEXPORT int unqlite_close(unqlite *pDb); + + +/* Key/Value (KV) Store Interfaces */ +UNQLITE_APIEXPORT int unqlite_kv_store(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); +UNQLITE_APIEXPORT int unqlite_kv_append(unqlite *pDb,const void *pKey,int nKeyLen,const void *pData,unqlite_int64 nDataLen); +UNQLITE_APIEXPORT int unqlite_kv_store_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); +UNQLITE_APIEXPORT int unqlite_kv_append_fmt(unqlite *pDb,const void *pKey,int nKeyLen,const char *zFormat,...); +UNQLITE_APIEXPORT int unqlite_kv_fetch(unqlite *pDb,const void *pKey,int nKeyLen,void *pBuf,unqlite_int64 /* in|out */*pBufLen); +UNQLITE_APIEXPORT int unqlite_kv_fetch_callback(unqlite *pDb,const void *pKey, + int nKeyLen,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_kv_delete(unqlite *pDb,const void *pKey,int nKeyLen); +UNQLITE_APIEXPORT int unqlite_kv_config(unqlite *pDb,int iOp,...); + +/* Document (JSON) Store Interfaces powered by the Jx9 Scripting Language */ +UNQLITE_APIEXPORT int unqlite_compile(unqlite *pDb,const char *zJx9,int nByte,unqlite_vm **ppOut); +UNQLITE_APIEXPORT int unqlite_compile_file(unqlite *pDb,const char *zPath,unqlite_vm **ppOut); +UNQLITE_APIEXPORT int unqlite_vm_config(unqlite_vm *pVm,int iOp,...); +UNQLITE_APIEXPORT int unqlite_vm_exec(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_reset(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_release(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_dump(unqlite_vm *pVm, int (*xConsumer)(const void *, unsigned int, void *), void *pUserData); +UNQLITE_APIEXPORT unqlite_value * unqlite_vm_extract_variable(unqlite_vm *pVm,const char *zVarname); + +/* Cursor Iterator Interfaces */ +UNQLITE_APIEXPORT int unqlite_kv_cursor_init(unqlite *pDb,unqlite_kv_cursor **ppOut); +UNQLITE_APIEXPORT int unqlite_kv_cursor_release(unqlite *pDb,unqlite_kv_cursor *pCur); +UNQLITE_APIEXPORT int unqlite_kv_cursor_seek(unqlite_kv_cursor *pCursor,const void *pKey,int nKeyLen,int iPos); +UNQLITE_APIEXPORT int unqlite_kv_cursor_first_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_last_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_valid_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_next_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_prev_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_key(unqlite_kv_cursor *pCursor,void *pBuf,int *pnByte); +UNQLITE_APIEXPORT int unqlite_kv_cursor_key_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_kv_cursor_data(unqlite_kv_cursor *pCursor,void *pBuf,unqlite_int64 *pnData); +UNQLITE_APIEXPORT int unqlite_kv_cursor_data_callback(unqlite_kv_cursor *pCursor,int (*xConsumer)(const void *,unsigned int,void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_kv_cursor_delete_entry(unqlite_kv_cursor *pCursor); +UNQLITE_APIEXPORT int unqlite_kv_cursor_reset(unqlite_kv_cursor *pCursor); + +/* Manual Transaction Manager */ +UNQLITE_APIEXPORT int unqlite_begin(unqlite *pDb); +UNQLITE_APIEXPORT int unqlite_commit(unqlite *pDb); +UNQLITE_APIEXPORT int unqlite_rollback(unqlite *pDb); + +/* Utility interfaces */ +UNQLITE_APIEXPORT int unqlite_util_load_mmaped_file(const char *zFile,void **ppMap,unqlite_int64 *pFileSize); +UNQLITE_APIEXPORT int unqlite_util_release_mmaped_file(void *pMap,unqlite_int64 iFileSize); +UNQLITE_APIEXPORT int unqlite_util_random_string(unqlite *pDb,char *zBuf,unsigned int buf_size); +UNQLITE_APIEXPORT unsigned int unqlite_util_random_num(unqlite *pDb); + +/* In-process extending interfaces */ +UNQLITE_APIEXPORT int unqlite_create_function(unqlite_vm *pVm,const char *zName,int (*xFunc)(unqlite_context *,int,unqlite_value **),void *pUserData); +UNQLITE_APIEXPORT int unqlite_delete_function(unqlite_vm *pVm, const char *zName); +UNQLITE_APIEXPORT int unqlite_create_constant(unqlite_vm *pVm,const char *zName,void (*xExpand)(unqlite_value *, void *),void *pUserData); +UNQLITE_APIEXPORT int unqlite_delete_constant(unqlite_vm *pVm, const char *zName); + +/* On Demand Object allocation interfaces */ +UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_scalar(unqlite_vm *pVm); +UNQLITE_APIEXPORT unqlite_value * unqlite_vm_new_array(unqlite_vm *pVm); +UNQLITE_APIEXPORT int unqlite_vm_release_value(unqlite_vm *pVm,unqlite_value *pValue); +UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_scalar(unqlite_context *pCtx); +UNQLITE_APIEXPORT unqlite_value * unqlite_context_new_array(unqlite_context *pCtx); +UNQLITE_APIEXPORT void unqlite_context_release_value(unqlite_context *pCtx,unqlite_value *pValue); + +/* Dynamically Typed Value Object Management Interfaces */ +UNQLITE_APIEXPORT int unqlite_value_int(unqlite_value *pVal, int iValue); +UNQLITE_APIEXPORT int unqlite_value_int64(unqlite_value *pVal, unqlite_int64 iValue); +UNQLITE_APIEXPORT int unqlite_value_bool(unqlite_value *pVal, int iBool); +UNQLITE_APIEXPORT int unqlite_value_null(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_double(unqlite_value *pVal, double Value); +UNQLITE_APIEXPORT int unqlite_value_string(unqlite_value *pVal, const char *zString, int nLen); +UNQLITE_APIEXPORT int unqlite_value_string_format(unqlite_value *pVal, const char *zFormat,...); +UNQLITE_APIEXPORT int unqlite_value_reset_string_cursor(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_resource(unqlite_value *pVal, void *pUserData); +UNQLITE_APIEXPORT int unqlite_value_release(unqlite_value *pVal); + +/* Foreign Function Parameter Values */ +UNQLITE_APIEXPORT int unqlite_value_to_int(unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_value_to_bool(unqlite_value *pValue); +UNQLITE_APIEXPORT unqlite_int64 unqlite_value_to_int64(unqlite_value *pValue); +UNQLITE_APIEXPORT double unqlite_value_to_double(unqlite_value *pValue); +UNQLITE_APIEXPORT const char * unqlite_value_to_string(unqlite_value *pValue, int *pLen); +UNQLITE_APIEXPORT void * unqlite_value_to_resource(unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_value_compare(unqlite_value *pLeft, unqlite_value *pRight, int bStrict); + +/* Setting The Result Of A Foreign Function */ +UNQLITE_APIEXPORT int unqlite_result_int(unqlite_context *pCtx, int iValue); +UNQLITE_APIEXPORT int unqlite_result_int64(unqlite_context *pCtx, unqlite_int64 iValue); +UNQLITE_APIEXPORT int unqlite_result_bool(unqlite_context *pCtx, int iBool); +UNQLITE_APIEXPORT int unqlite_result_double(unqlite_context *pCtx, double Value); +UNQLITE_APIEXPORT int unqlite_result_null(unqlite_context *pCtx); +UNQLITE_APIEXPORT int unqlite_result_string(unqlite_context *pCtx, const char *zString, int nLen); +UNQLITE_APIEXPORT int unqlite_result_string_format(unqlite_context *pCtx, const char *zFormat, ...); +UNQLITE_APIEXPORT int unqlite_result_value(unqlite_context *pCtx, unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_result_resource(unqlite_context *pCtx, void *pUserData); + +/* Dynamically Typed Value Object Query Interfaces */ +UNQLITE_APIEXPORT int unqlite_value_is_int(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_float(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_bool(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_string(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_null(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_numeric(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_callable(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_scalar(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_json_array(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_json_object(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_resource(unqlite_value *pVal); +UNQLITE_APIEXPORT int unqlite_value_is_empty(unqlite_value *pVal); + +/* JSON Array/Object Management Interfaces */ +UNQLITE_APIEXPORT unqlite_value * unqlite_array_fetch(unqlite_value *pArray, const char *zKey, int nByte); +UNQLITE_APIEXPORT int unqlite_array_walk(unqlite_value *pArray, int (*xWalk)(unqlite_value *, unqlite_value *, void *), void *pUserData); +UNQLITE_APIEXPORT int unqlite_array_add_elem(unqlite_value *pArray, unqlite_value *pKey, unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_array_add_strkey_elem(unqlite_value *pArray, const char *zKey, unqlite_value *pValue); +UNQLITE_APIEXPORT int unqlite_array_count(unqlite_value *pArray); + +/* Call Context Handling Interfaces */ +UNQLITE_APIEXPORT int unqlite_context_output(unqlite_context *pCtx, const char *zString, int nLen); +UNQLITE_APIEXPORT int unqlite_context_output_format(unqlite_context *pCtx,const char *zFormat, ...); +UNQLITE_APIEXPORT int unqlite_context_throw_error(unqlite_context *pCtx, int iErr, const char *zErr); +UNQLITE_APIEXPORT int unqlite_context_throw_error_format(unqlite_context *pCtx, int iErr, const char *zFormat, ...); +UNQLITE_APIEXPORT unsigned int unqlite_context_random_num(unqlite_context *pCtx); +UNQLITE_APIEXPORT int unqlite_context_random_string(unqlite_context *pCtx, char *zBuf, int nBuflen); +UNQLITE_APIEXPORT void * unqlite_context_user_data(unqlite_context *pCtx); +UNQLITE_APIEXPORT int unqlite_context_push_aux_data(unqlite_context *pCtx, void *pUserData); +UNQLITE_APIEXPORT void * unqlite_context_peek_aux_data(unqlite_context *pCtx); +UNQLITE_APIEXPORT unsigned int unqlite_context_result_buf_length(unqlite_context *pCtx); +UNQLITE_APIEXPORT const char * unqlite_function_name(unqlite_context *pCtx); + +/* Call Context Memory Management Interfaces */ +UNQLITE_APIEXPORT void * unqlite_context_alloc_chunk(unqlite_context *pCtx,unsigned int nByte,int ZeroChunk,int AutoRelease); +UNQLITE_APIEXPORT void * unqlite_context_realloc_chunk(unqlite_context *pCtx,void *pChunk,unsigned int nByte); +UNQLITE_APIEXPORT void unqlite_context_free_chunk(unqlite_context *pCtx,void *pChunk); + +/* Global Library Management Interfaces */ +UNQLITE_APIEXPORT int unqlite_lib_config(int nConfigOp,...); +UNQLITE_APIEXPORT int unqlite_lib_init(void); +UNQLITE_APIEXPORT int unqlite_lib_shutdown(void); +UNQLITE_APIEXPORT int unqlite_lib_is_threadsafe(void); +UNQLITE_APIEXPORT const char * unqlite_lib_version(void); +UNQLITE_APIEXPORT const char * unqlite_lib_signature(void); +UNQLITE_APIEXPORT const char * unqlite_lib_ident(void); +UNQLITE_APIEXPORT const char * unqlite_lib_copyright(void); +#ifdef __cplusplus + } +#endif +#endif /* _UNQLITE_H_ */ diff --git a/wscript b/wscript index b46962bf1..90fd6ae54 100644 --- a/wscript +++ b/wscript @@ -1,6 +1,8 @@ #!/usr/bin/env python # encoding: utf-8 +from __future__ import print_function + try: from urllib2 import urlopen # Python 2 except ImportError: @@ -37,6 +39,11 @@ elif sys.platform == 'win32': from waflib.Tools.compiler_cxx import cxx_compiler cxx_compiler['linux'] = ['clang++', 'g++'] +print('\033[91m' + """### IMPORTANT ### +The Waf build is deprecated and will be removed in the future. Build cquery with +CMake instead. +### IMPORTANT ###\n""" + '\033[0m', file=sys.stderr) + # Creating symbolic link on Windows requires a special priviledge SeCreateSymboliclinkPrivilege, # which an non-elevated process lacks. Starting with Windows 10 build 14972, this got relaxed # when Developer Mode is enabled. Triggering this new behaviour requires a new flag.